From f352fe7b0a02341a8600ce41860fd09e3b6c092b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:01:41 +0000 Subject: [PATCH 1/4] Initial plan From 4976fb15132ba46a7b0758697e9b8265921793a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:13:51 +0000 Subject: [PATCH 2/4] Initial analysis for compute_parquet generic conversion Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- .../00_pkg_src/duckplyr/.aspell/defaults.R | 4 + .../00_pkg_src/duckplyr/.aspell/duckplyr.rds | Bin 0 -> 88 bytes .../00_pkg_src/duckplyr/DESCRIPTION | 48 + duckplyr.Rcheck/00_pkg_src/duckplyr/LICENSE | 2 + duckplyr.Rcheck/00_pkg_src/duckplyr/NAMESPACE | 369 + duckplyr.Rcheck/00_pkg_src/duckplyr/NEWS.md | 483 + .../00_pkg_src/duckplyr/R/aaa-meta.R | 9 + .../00_pkg_src/duckplyr/R/add_count.R | 41 + .../00_pkg_src/duckplyr/R/anti_join-rd.R | 13 + .../00_pkg_src/duckplyr/R/anti_join.R | 47 + .../00_pkg_src/duckplyr/R/arrange-rd.R | 22 + .../00_pkg_src/duckplyr/R/arrange.R | 82 + .../00_pkg_src/duckplyr/R/as_duckplyr_df.R | 45 + .../duckplyr/R/as_duckplyr_tibble.R | 24 + .../00_pkg_src/duckplyr/R/as_tbl.R | 54 + .../00_pkg_src/duckplyr/R/auto_copy.R | 22 + .../00_pkg_src/duckplyr/R/bisect_reduce.R | 11 + .../duckplyr/R/can_load_extension.R | 16 + .../00_pkg_src/duckplyr/R/collect-rd.R | 17 + .../00_pkg_src/duckplyr/R/collect.R | 21 + .../00_pkg_src/duckplyr/R/compute-rd.R | 42 + .../00_pkg_src/duckplyr/R/compute.R | 65 + .../00_pkg_src/duckplyr/R/compute_csv.R | 48 + .../00_pkg_src/duckplyr/R/compute_parquet.R | 49 + .../00_pkg_src/duckplyr/R/config.R | 67 + .../duckplyr/R/construct_constant.R | 46 + .../duckplyr/R/construct_function.R | 71 + .../duckplyr/R/construct_reference.R | 47 + .../00_pkg_src/duckplyr/R/construct_window.R | 50 + .../00_pkg_src/duckplyr/R/count-rd.R | 19 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/count.R | 95 + .../00_pkg_src/duckplyr/R/cross_join.R | 71 + .../00_pkg_src/duckplyr/R/distinct-rd.R | 18 + .../00_pkg_src/duckplyr/R/distinct.R | 106 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/do.R | 51 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/dots.R | 15 + .../00_pkg_src/duckplyr/R/dplyr-reimplement.R | 18 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr.R | 86 + .../00_pkg_src/duckplyr/R/duckdb.R | 1 + .../00_pkg_src/duckplyr/R/duckplyr-across.R | 232 + .../00_pkg_src/duckplyr/R/duckplyr-package.R | 224 + .../00_pkg_src/duckplyr/R/duckplyr_df.R | 142 + .../00_pkg_src/duckplyr/R/duckplyr_execute.R | 25 + .../00_pkg_src/duckplyr/R/ducktbl.R | 159 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/exec.R | 36 + .../00_pkg_src/duckplyr/R/explain-rd.R | 18 + .../00_pkg_src/duckplyr/R/explain.R | 27 + .../00_pkg_src/duckplyr/R/fallback.R | 467 + .../00_pkg_src/duckplyr/R/filter-rd.R | 18 + .../00_pkg_src/duckplyr/R/filter.R | 70 + .../00_pkg_src/duckplyr/R/flights.R | 17 + .../00_pkg_src/duckplyr/R/full_join-rd.R | 14 + .../00_pkg_src/duckplyr/R/full_join.R | 64 + .../00_pkg_src/duckplyr/R/globals.R | 301 + .../00_pkg_src/duckplyr/R/group_by.R | 40 + .../00_pkg_src/duckplyr/R/group_indices.R | 43 + .../00_pkg_src/duckplyr/R/group_keys.R | 43 + .../00_pkg_src/duckplyr/R/group_map.R | 59 + .../00_pkg_src/duckplyr/R/group_modify.R | 44 + .../00_pkg_src/duckplyr/R/group_nest.R | 38 + .../00_pkg_src/duckplyr/R/group_size.R | 34 + .../00_pkg_src/duckplyr/R/group_split.R | 44 + .../00_pkg_src/duckplyr/R/group_trim.R | 34 + .../00_pkg_src/duckplyr/R/group_vars.R | 21 + .../00_pkg_src/duckplyr/R/groups.R | 34 + .../00_pkg_src/duckplyr/R/handle_desc.R | 44 + .../00_pkg_src/duckplyr/R/head-rd.R | 15 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/head.R | 22 + .../duckplyr/R/import-standalone-purrr.R | 239 + .../00_pkg_src/duckplyr/R/inner_join-rd.R | 15 + .../00_pkg_src/duckplyr/R/inner_join.R | 65 + .../00_pkg_src/duckplyr/R/intersect-rd.R | 15 + .../00_pkg_src/duckplyr/R/intersect.R | 75 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-.R | 90 + .../00_pkg_src/duckplyr/R/io-csv.R | 59 + .../00_pkg_src/duckplyr/R/io-parquet.R | 59 + .../00_pkg_src/duckplyr/R/is_duckplyr_df.R | 26 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/join.R | 147 + .../00_pkg_src/duckplyr/R/join_ptype_common.R | 15 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/last.R | 23 + .../00_pkg_src/duckplyr/R/left_join-rd.R | 14 + .../00_pkg_src/duckplyr/R/left_join.R | 66 + .../00_pkg_src/duckplyr/R/mutate-rd.R | 17 + .../00_pkg_src/duckplyr/R/mutate.R | 157 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/n-col.R | 27 + .../00_pkg_src/duckplyr/R/n_groups.R | 34 + .../00_pkg_src/duckplyr/R/nest_by.R | 34 + .../00_pkg_src/duckplyr/R/nest_join.R | 137 + .../00_pkg_src/duckplyr/R/not-supported.R | 40 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/oo.R | 77 + .../00_pkg_src/duckplyr/R/overwrite.R | 61 + .../00_pkg_src/duckplyr/R/positron.R | 80 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/print.R | 29 + .../00_pkg_src/duckplyr/R/project.R | 17 + .../00_pkg_src/duckplyr/R/prudence.R | 28 + .../00_pkg_src/duckplyr/R/pull-rd.R | 17 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull.R | 59 + .../00_pkg_src/duckplyr/R/read_csv_duckdb.R | 42 + .../00_pkg_src/duckplyr/R/read_file_duckdb.R | 90 + .../00_pkg_src/duckplyr/R/read_json_duckdb.R | 26 + .../duckplyr/R/read_parquet_duckdb.R | 16 + .../00_pkg_src/duckplyr/R/reframe.R | 43 + .../00_pkg_src/duckplyr/R/relational-df.R | 56 + .../00_pkg_src/duckplyr/R/relational-duckdb.R | 510 + .../00_pkg_src/duckplyr/R/relational-expr.R | 152 + .../00_pkg_src/duckplyr/R/relational-rel.R | 347 + .../00_pkg_src/duckplyr/R/relational.R | 150 + .../00_pkg_src/duckplyr/R/relocate-rd.R | 15 + .../00_pkg_src/duckplyr/R/relocate.R | 64 + .../00_pkg_src/duckplyr/R/rename-rd.R | 15 + .../00_pkg_src/duckplyr/R/rename.R | 55 + .../00_pkg_src/duckplyr/R/rename_with.R | 55 + .../00_pkg_src/duckplyr/R/restore.R | 61 + .../00_pkg_src/duckplyr/R/right_join-rd.R | 14 + .../00_pkg_src/duckplyr/R/right_join.R | 65 + .../00_pkg_src/duckplyr/R/rows_append.R | 44 + .../00_pkg_src/duckplyr/R/rows_delete.R | 70 + .../00_pkg_src/duckplyr/R/rows_insert.R | 58 + .../00_pkg_src/duckplyr/R/rows_patch.R | 89 + .../00_pkg_src/duckplyr/R/rows_update.R | 82 + .../00_pkg_src/duckplyr/R/rows_upsert.R | 84 + .../00_pkg_src/duckplyr/R/rowwise.R | 35 + .../00_pkg_src/duckplyr/R/select-rd.R | 18 + .../00_pkg_src/duckplyr/R/select.R | 68 + .../00_pkg_src/duckplyr/R/semi_join-rd.R | 13 + .../00_pkg_src/duckplyr/R/semi_join.R | 47 + .../00_pkg_src/duckplyr/R/setdiff-rd.R | 16 + .../00_pkg_src/duckplyr/R/setdiff.R | 75 + .../00_pkg_src/duckplyr/R/setequal.R | 39 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/sets.R | 106 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice.R | 45 + .../00_pkg_src/duckplyr/R/slice_head-rd.R | 15 + .../00_pkg_src/duckplyr/R/slice_head.R | 61 + .../00_pkg_src/duckplyr/R/slice_sample.R | 52 + .../00_pkg_src/duckplyr/R/slice_tail.R | 43 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/sql.R | 36 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/stats.R | 29 + .../00_pkg_src/duckplyr/R/summarise-rd.R | 19 + .../00_pkg_src/duckplyr/R/summarise.R | 107 + .../00_pkg_src/duckplyr/R/symdiff-rd.R | 16 + .../00_pkg_src/duckplyr/R/symdiff.R | 78 + .../00_pkg_src/duckplyr/R/telemetry.R | 286 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch.R | 456 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch2.R | 338 + .../00_pkg_src/duckplyr/R/translate.R | 601 + .../00_pkg_src/duckplyr/R/transmute-rd.R | 17 + .../00_pkg_src/duckplyr/R/transmute.R | 73 + .../00_pkg_src/duckplyr/R/ungroup.R | 34 + .../00_pkg_src/duckplyr/R/union-rd.R | 15 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/union.R | 42 + .../00_pkg_src/duckplyr/R/union_all-rd.R | 15 + .../00_pkg_src/duckplyr/R/union_all.R | 77 + .../00_pkg_src/duckplyr/R/unique_table_name.R | 6 + .../00_pkg_src/duckplyr/R/zzz-auto.R | 5 + .../00_pkg_src/duckplyr/R/zzz-methods.R | 38 + duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz.R | 40 + duckplyr.Rcheck/00_pkg_src/duckplyr/README.md | 391 + .../00_pkg_src/duckplyr/build/vignette.rds | Bin 0 -> 472 bytes .../00_pkg_src/duckplyr/inst/doc/developers.R | 74 + .../duckplyr/inst/doc/developers.Rmd | 140 + .../duckplyr/inst/doc/developers.html | 537 + .../00_pkg_src/duckplyr/inst/doc/duckdb.R | 63 + .../00_pkg_src/duckplyr/inst/doc/duckdb.Rmd | 139 + .../00_pkg_src/duckplyr/inst/doc/duckdb.html | 462 + .../00_pkg_src/duckplyr/inst/doc/extend.R | 139 + .../00_pkg_src/duckplyr/inst/doc/extend.Rmd | 156 + .../00_pkg_src/duckplyr/inst/doc/extend.html | 536 + .../00_pkg_src/duckplyr/inst/doc/fallback.R | 71 + .../00_pkg_src/duckplyr/inst/doc/fallback.Rmd | 178 + .../duckplyr/inst/doc/fallback.html | 592 + .../00_pkg_src/duckplyr/inst/doc/large.R | 163 + .../00_pkg_src/duckplyr/inst/doc/large.Rmd | 327 + .../00_pkg_src/duckplyr/inst/doc/large.html | 635 + .../00_pkg_src/duckplyr/inst/doc/limits.R | 211 + .../00_pkg_src/duckplyr/inst/doc/limits.Rmd | 411 + .../00_pkg_src/duckplyr/inst/doc/limits.html | 971 + .../00_pkg_src/duckplyr/inst/doc/prudence.R | 151 + .../00_pkg_src/duckplyr/inst/doc/prudence.Rmd | 324 + .../duckplyr/inst/doc/prudence.html | 825 + .../00_pkg_src/duckplyr/inst/doc/telemetry.R | 30 + .../duckplyr/inst/doc/telemetry.Rmd | 67 + .../duckplyr/inst/doc/telemetry.html | 402 + .../duckplyr/man/anti_join.duckplyr_df.Rd | 69 + .../duckplyr/man/arrange.duckplyr_df.Rd | 69 + .../00_pkg_src/duckplyr/man/as_duckplyr_df.Rd | 44 + .../00_pkg_src/duckplyr/man/as_tbl.Rd | 36 + .../duckplyr/man/collect.duckplyr_df.Rd | 30 + .../duckplyr/man/compute.duckplyr_df.Rd | 61 + .../00_pkg_src/duckplyr/man/compute_csv.Rd | 54 + .../duckplyr/man/compute_parquet.Rd | 54 + .../00_pkg_src/duckplyr/man/config.Rd | 68 + .../duckplyr/man/count.duckplyr_df.Rd | 75 + .../00_pkg_src/duckplyr/man/db_exec.Rd | 38 + .../00_pkg_src/duckplyr/man/df_from_file.Rd | 130 + .../duckplyr/man/distinct.duckplyr_df.Rd | 38 + .../00_pkg_src/duckplyr/man/duckdb_tibble.Rd | 83 + .../duckplyr/man/duckplyr-package.Rd | 36 + .../duckplyr/man/duckplyr_execute.Rd | 29 + .../duckplyr/man/explain.duckplyr_df.Rd | 30 + .../00_pkg_src/duckplyr/man/fallback.Rd | 135 + .../man/figures/lifecycle-deprecated.svg | 21 + .../man/figures/lifecycle-experimental.svg | 21 + .../duckplyr/man/figures/lifecycle-stable.svg | 29 + .../man/figures/lifecycle-superseded.svg | 21 + .../00_pkg_src/duckplyr/man/figures/logo.png | Bin 0 -> 151220 bytes .../duckplyr/man/filter.duckplyr_df.Rd | 56 + .../00_pkg_src/duckplyr/man/flights_df.Rd | 19 + .../duckplyr/man/full_join.duckplyr_df.Rd | 152 + .../duckplyr/man/head.duckplyr_df.Rd | 36 + .../duckplyr/man/inner_join.duckplyr_df.Rd | 173 + .../duckplyr/man/intersect.duckplyr_df.Rd | 39 + .../00_pkg_src/duckplyr/man/is_duckplyr_df.Rd | 29 + .../00_pkg_src/duckplyr/man/last_rel.Rd | 17 + .../duckplyr/man/left_join.duckplyr_df.Rd | 172 + .../duckplyr/man/methods_overwrite.Rd | 34 + .../duckplyr/man/mutate.duckplyr_df.Rd | 71 + .../00_pkg_src/duckplyr/man/new_relational.Rd | 254 + .../00_pkg_src/duckplyr/man/new_relexpr.Rd | 110 + .../duckplyr/man/pull.duckplyr_df.Rd | 57 + .../duckplyr/man/read_csv_duckdb.Rd | 65 + .../duckplyr/man/read_file_duckdb.Rd | 63 + .../duckplyr/man/read_json_duckdb.Rd | 51 + .../duckplyr/man/read_parquet_duckdb.Rd | 38 + .../duckplyr/man/read_sql_duckdb.Rd | 49 + .../00_pkg_src/duckplyr/man/reexports.Rd | 16 + .../duckplyr/man/relocate.duckplyr_df.Rd | 42 + .../duckplyr/man/rename.duckplyr_df.Rd | 41 + .../duckplyr/man/right_join.duckplyr_df.Rd | 172 + .../duckplyr/man/select.duckplyr_df.Rd | 45 + .../duckplyr/man/semi_join.duckplyr_df.Rd | 69 + .../duckplyr/man/setdiff.duckplyr_df.Rd | 40 + .../duckplyr/man/slice_head.duckplyr_df.Rd | 63 + .../00_pkg_src/duckplyr/man/stats_show.Rd | 25 + .../duckplyr/man/summarise.duckplyr_df.Rd | 81 + .../duckplyr/man/symdiff.duckplyr_df.Rd | 40 + .../duckplyr/man/transmute.duckplyr_df.Rd | 48 + .../duckplyr/man/union.duckplyr_df.Rd | 28 + .../duckplyr/man/union_all.duckplyr_df.Rd | 39 + .../00_pkg_src/duckplyr/man/unsupported.Rd | 47 + .../00_pkg_src/duckplyr/tests/testthat.R | 12 + .../testthat/_snaps/as_duckplyr_tibble.md | 26 + .../duckplyr/tests/testthat/_snaps/compute.md | 17 + .../duckplyr/tests/testthat/_snaps/demo.md | 65 + .../tests/testthat/_snaps/dplyr-across.md | 357 + .../tests/testthat/_snaps/dplyr-all-equal.md | 108 + .../tests/testthat/_snaps/dplyr-arrange.md | 68 + .../tests/testthat/_snaps/dplyr-bind-cols.md | 36 + .../tests/testthat/_snaps/dplyr-bind-rows.md | 51 + .../tests/testthat/_snaps/dplyr-by.md | 33 + .../tests/testthat/_snaps/dplyr-case-match.md | 60 + .../tests/testthat/_snaps/dplyr-case-when.md | 120 + .../tests/testthat/_snaps/dplyr-coalesce.md | 63 + .../tests/testthat/_snaps/dplyr-conditions.md | 28 + .../testthat/_snaps/dplyr-consecutive-id.md | 23 + .../tests/testthat/_snaps/dplyr-context.md | 33 + .../testthat/_snaps/dplyr-count-tally.md | 102 + .../tests/testthat/_snaps/dplyr-desc.md | 8 + .../tests/testthat/_snaps/dplyr-distinct.md | 34 + .../tests/testthat/_snaps/dplyr-filter.md | 48 + .../tests/testthat/_snaps/dplyr-funs.md | 40 + .../tests/testthat/_snaps/dplyr-grouped-df.md | 105 + .../tests/testthat/_snaps/dplyr-if-else.md | 76 + .../tests/testthat/_snaps/dplyr-join-by.md | 473 + .../tests/testthat/_snaps/dplyr-join-cols.md | 148 + .../tests/testthat/_snaps/dplyr-join-cross.md | 9 + .../tests/testthat/_snaps/dplyr-join-rows.md | 401 + .../tests/testthat/_snaps/dplyr-join.md | 244 + .../tests/testthat/_snaps/dplyr-lead-lag.md | 70 + .../tests/testthat/_snaps/dplyr-mutate.md | 42 + .../tests/testthat/_snaps/dplyr-n-distinct.md | 20 + .../tests/testthat/_snaps/dplyr-na-if.md | 49 + .../tests/testthat/_snaps/dplyr-nth-value.md | 80 + .../tests/testthat/_snaps/dplyr-order-by.md | 33 + .../tests/testthat/_snaps/dplyr-pick.md | 131 + .../tests/testthat/_snaps/dplyr-rank.md | 32 + .../tests/testthat/_snaps/dplyr-reframe.md | 33 + .../tests/testthat/_snaps/dplyr-relocate.md | 8 + .../tests/testthat/_snaps/dplyr-rename.md | 24 + .../tests/testthat/_snaps/dplyr-rows.md | 347 + .../tests/testthat/_snaps/dplyr-rowwise.md | 84 + .../tests/testthat/_snaps/dplyr-sample.md | 82 + .../tests/testthat/_snaps/dplyr-select.md | 11 + .../tests/testthat/_snaps/dplyr-sets.md | 127 + .../tests/testthat/_snaps/dplyr-slice.md | 380 + .../tests/testthat/_snaps/dplyr-summarise.md | 55 + .../tests/testthat/_snaps/dplyr-transmute.md | 21 + .../tests/testthat/_snaps/duckplyr-across.md | 87 + .../tests/testthat/_snaps/duckplyr.md | 8 + .../duckplyr/tests/testthat/_snaps/ducktbl.md | 29 + .../duckplyr/tests/testthat/_snaps/expr.md | 36 + .../tests/testthat/_snaps/fallback.md | 261 + .../testthat/_snaps/fallback/fallback-2.dcf | 1 + .../testthat/_snaps/fallback/fallback.dcf | 5 + .../tests/testthat/_snaps/handle_desc.md | 24 + .../tests/testthat/_snaps/n_distinct.md | 24 + .../tests/testthat/_snaps/overwrite.md | 15 + .../testthat/_snaps/relational-duckdb.md | 80 + .../tests/testthat/_snaps/relational-rel.md | 9 + .../tests/testthat/_snaps/relational.md | 11 + .../tests/testthat/_snaps/telemetry.md | 326 + .../duckplyr/tests/testthat/_snaps/tpch.md | 129 + .../tests/testthat/_snaps/translate.md | 621 + .../tests/testthat/helper-dplyr-encoding.R | 78 + .../tests/testthat/helper-dplyr-pick.R | 6 + .../duckplyr/tests/testthat/helper-dplyr-s3.R | 48 + .../duckplyr/tests/testthat/helper-list_of.R | 1 + .../duckplyr/tests/testthat/helper-skip.R | 22 + .../duckplyr/tests/testthat/helper-taxi.R | 35 + .../duckplyr/tests/testthat/setup.R | 18 + .../duckplyr/tests/testthat/test-altrep.R | 14 + .../tests/testthat/test-as_duckplyr_df.R | 2727 +++ .../tests/testthat/test-as_duckplyr_tibble.R | 22 + .../duckplyr/tests/testthat/test-as_tbl.R | 30 + .../duckplyr/tests/testthat/test-compute.R | 36 + .../tests/testthat/test-compute_csv.R | 17 + .../tests/testthat/test-compute_parquet.R | 18 + .../duckplyr/tests/testthat/test-demo.R | 83 + .../duckplyr/tests/testthat/test-df.R | 3 + .../tests/testthat/test-dplyr-across.R | 1499 ++ .../tests/testthat/test-dplyr-all-equal.R | 194 + .../tests/testthat/test-dplyr-arrange.R | 571 + .../tests/testthat/test-dplyr-bind-cols.R | 144 + .../tests/testthat/test-dplyr-bind-rows.R | 266 + .../duckplyr/tests/testthat/test-dplyr-by.R | 107 + .../tests/testthat/test-dplyr-case-match.R | 72 + .../tests/testthat/test-dplyr-case-when.R | 314 + .../tests/testthat/test-dplyr-coalesce.R | 131 + .../tests/testthat/test-dplyr-colwise-funs.R | 14 + .../tests/testthat/test-dplyr-conditions.R | 211 + .../testthat/test-dplyr-consecutive-id.R | 31 + .../tests/testthat/test-dplyr-context.R | 80 + .../tests/testthat/test-dplyr-copy-to.R | 22 + .../tests/testthat/test-dplyr-count-tally.R | 229 + .../duckplyr/tests/testthat/test-dplyr-desc.R | 10 + .../tests/testthat/test-dplyr-distinct.R | 196 + .../tests/testthat/test-dplyr-filter.R | 729 + .../duckplyr/tests/testthat/test-dplyr-funs.R | 155 + .../tests/testthat/test-dplyr-generics.R | 198 + .../tests/testthat/test-dplyr-group-by.R | 624 + .../tests/testthat/test-dplyr-group-data.R | 132 + .../tests/testthat/test-dplyr-group-map.R | 137 + .../tests/testthat/test-dplyr-group-nest.R | 63 + .../tests/testthat/test-dplyr-group-split.R | 138 + .../tests/testthat/test-dplyr-group-trim.R | 29 + .../tests/testthat/test-dplyr-grouped-df.R | 221 + .../tests/testthat/test-dplyr-groups-with.R | 28 + .../tests/testthat/test-dplyr-if-else.R | 120 + .../tests/testthat/test-dplyr-join-by.R | 382 + .../tests/testthat/test-dplyr-join-cols.R | 208 + .../tests/testthat/test-dplyr-join-cross.R | 64 + .../tests/testthat/test-dplyr-join-rows.R | 476 + .../duckplyr/tests/testthat/test-dplyr-join.R | 773 + .../tests/testthat/test-dplyr-lead-lag.R | 177 + .../tests/testthat/test-dplyr-mutate.R | 915 + .../tests/testthat/test-dplyr-n-distinct.R | 52 + .../tests/testthat/test-dplyr-na-if.R | 94 + .../duckplyr/tests/testthat/test-dplyr-near.R | 10 + .../tests/testthat/test-dplyr-nest-by.R | 42 + .../tests/testthat/test-dplyr-nth-value.R | 246 + .../tests/testthat/test-dplyr-order-by.R | 54 + .../duckplyr/tests/testthat/test-dplyr-pick.R | 575 + .../duckplyr/tests/testthat/test-dplyr-pull.R | 34 + .../duckplyr/tests/testthat/test-dplyr-rank.R | 121 + .../tests/testthat/test-dplyr-reframe.R | 308 + .../tests/testthat/test-dplyr-relocate.R | 212 + .../tests/testthat/test-dplyr-rename.R | 104 + .../duckplyr/tests/testthat/test-dplyr-rows.R | 541 + .../tests/testthat/test-dplyr-rowwise.R | 146 + .../tests/testthat/test-dplyr-sample.R | 156 + .../testthat/test-dplyr-select-helpers.R | 28 + .../tests/testthat/test-dplyr-select.R | 211 + .../duckplyr/tests/testthat/test-dplyr-sets.R | 149 + .../tests/testthat/test-dplyr-slice.R | 667 + .../tests/testthat/test-dplyr-summarise.R | 592 + .../tests/testthat/test-dplyr-transmute.R | 102 + .../duckplyr/tests/testthat/test-dplyr.R | 49 + .../duckplyr/tests/testthat/test-duckdb.R | 6 + .../tests/testthat/test-duckplyr-across.R | 96 + .../duckplyr/tests/testthat/test-duckplyr.R | 43 + .../duckplyr/tests/testthat/test-ducktbl.R | 85 + .../duckplyr/tests/testthat/test-expr.R | 18 + .../duckplyr/tests/testthat/test-fallback.R | 313 + .../tests/testthat/test-handle_desc.R | 33 + .../duckplyr/tests/testthat/test-head.R | 29 + .../duckplyr/tests/testthat/test-io-csv.R | 12 + .../duckplyr/tests/testthat/test-io-parquet.R | 40 + .../duckplyr/tests/testthat/test-last.R | 12 + .../duckplyr/tests/testthat/test-n_distinct.R | 149 + .../duckplyr/tests/testthat/test-overwrite.R | 14 + .../duckplyr/tests/testthat/test-prom.R | 15 + .../duckplyr/tests/testthat/test-prudence.R | 66 + .../tests/testthat/test-read_csv_duckdb.R | 24 + .../tests/testthat/test-read_json_duckdb.R | 14 + .../tests/testthat/test-read_parquet_duckdb.R | 61 + .../duckplyr/tests/testthat/test-rel.R | 11 + .../duckplyr/tests/testthat/test-rel_api.R | 15183 +++++++++++++ .../tests/testthat/test-relational-duckdb.R | 140 + .../tests/testthat/test-relational-rel.R | 5 + .../duckplyr/tests/testthat/test-relational.R | 12 + .../duckplyr/tests/testthat/test-sets.R | 5 + .../duckplyr/tests/testthat/test-sql.R | 8 + .../duckplyr/tests/testthat/test-telemetry.R | 300 + .../duckplyr/tests/testthat/test-tpch.R | 271 + .../duckplyr/tests/testthat/test-translate.R | 237 + .../tests/testthat/tpch-sf0.01/q01.csv | 5 + .../tests/testthat/tpch-sf0.01/q02.csv | 5 + .../tests/testthat/tpch-sf0.01/q03.csv | 11 + .../tests/testthat/tpch-sf0.01/q04.csv | 6 + .../tests/testthat/tpch-sf0.01/q05.csv | 6 + .../tests/testthat/tpch-sf0.01/q06.csv | 2 + .../tests/testthat/tpch-sf0.01/q07.csv | 5 + .../tests/testthat/tpch-sf0.01/q08.csv | 3 + .../tests/testthat/tpch-sf0.01/q09.csv | 174 + .../tests/testthat/tpch-sf0.01/q10.csv | 21 + .../tests/testthat/tpch-sf0.01/q11.csv | 360 + .../tests/testthat/tpch-sf0.01/q12.csv | 3 + .../tests/testthat/tpch-sf0.01/q13.csv | 33 + .../tests/testthat/tpch-sf0.01/q14.csv | 2 + .../tests/testthat/tpch-sf0.01/q15.csv | 2 + .../tests/testthat/tpch-sf0.01/q16.csv | 297 + .../tests/testthat/tpch-sf0.01/q17.csv | 2 + .../tests/testthat/tpch-sf0.01/q18.csv | 3 + .../tests/testthat/tpch-sf0.01/q19.csv | 2 + .../tests/testthat/tpch-sf0.01/q20.csv | 2 + .../tests/testthat/tpch-sf0.01/q21.csv | 2 + .../tests/testthat/tpch-sf0.01/q22.csv | 8 + .../duckplyr/tests/testthat/tpch-sf1/q01.csv | 5 + .../duckplyr/tests/testthat/tpch-sf1/q02.csv | 101 + .../duckplyr/tests/testthat/tpch-sf1/q03.csv | 11 + .../duckplyr/tests/testthat/tpch-sf1/q04.csv | 6 + .../duckplyr/tests/testthat/tpch-sf1/q05.csv | 6 + .../duckplyr/tests/testthat/tpch-sf1/q06.csv | 2 + .../duckplyr/tests/testthat/tpch-sf1/q07.csv | 5 + .../duckplyr/tests/testthat/tpch-sf1/q08.csv | 3 + .../duckplyr/tests/testthat/tpch-sf1/q09.csv | 176 + .../duckplyr/tests/testthat/tpch-sf1/q10.csv | 21 + .../duckplyr/tests/testthat/tpch-sf1/q11.csv | 1049 + .../duckplyr/tests/testthat/tpch-sf1/q12.csv | 3 + .../duckplyr/tests/testthat/tpch-sf1/q13.csv | 43 + .../duckplyr/tests/testthat/tpch-sf1/q14.csv | 2 + .../duckplyr/tests/testthat/tpch-sf1/q15.csv | 2 + .../duckplyr/tests/testthat/tpch-sf1/q16.csv | 18315 ++++++++++++++++ .../duckplyr/tests/testthat/tpch-sf1/q17.csv | 2 + .../duckplyr/tests/testthat/tpch-sf1/q18.csv | 58 + .../duckplyr/tests/testthat/tpch-sf1/q19.csv | 2 + .../duckplyr/tests/testthat/tpch-sf1/q20.csv | 187 + .../duckplyr/tests/testthat/tpch-sf1/q21.csv | 101 + .../duckplyr/tests/testthat/tpch-sf1/q22.csv | 8 + .../duckplyr/tests/testthat/utf-8.txt | 19 + .../00_pkg_src/duckplyr/vignettes/dd.png | Bin 0 -> 178618 bytes .../duckplyr/vignettes/developers.Rmd | 140 + .../00_pkg_src/duckplyr/vignettes/duckdb.Rmd | 139 + .../00_pkg_src/duckplyr/vignettes/extend.Rmd | 156 + .../duckplyr/vignettes/fallback.Rmd | 178 + .../00_pkg_src/duckplyr/vignettes/large.Rmd | 327 + .../00_pkg_src/duckplyr/vignettes/limits.Rmd | 411 + .../duckplyr/vignettes/prudence.Rmd | 324 + .../duckplyr/vignettes/telemetry.Rmd | 67 + duckplyr.Rcheck/00check.log | 284 + duckplyr.Rcheck/00install.out | 15 + duckplyr.Rcheck/R_check_bin/R | 2 + duckplyr.Rcheck/R_check_bin/Rscript | 2 + duckplyr.Rcheck/duckplyr-Ex.R | 1251 ++ duckplyr.Rcheck/duckplyr-Ex.Rout | 1904 ++ duckplyr.Rcheck/duckplyr-Ex.pdf | Bin 0 -> 3611 bytes duckplyr.Rcheck/duckplyr-Ex.timings | 47 + duckplyr.Rcheck/duckplyr/DESCRIPTION | 49 + duckplyr.Rcheck/duckplyr/INDEX | 51 + duckplyr.Rcheck/duckplyr/LICENSE | 2 + duckplyr.Rcheck/duckplyr/Meta/Rd.rds | Bin 0 -> 2090 bytes duckplyr.Rcheck/duckplyr/Meta/features.rds | Bin 0 -> 123 bytes duckplyr.Rcheck/duckplyr/Meta/hsearch.rds | Bin 0 -> 1879 bytes duckplyr.Rcheck/duckplyr/Meta/links.rds | Bin 0 -> 1067 bytes duckplyr.Rcheck/duckplyr/Meta/nsInfo.rds | Bin 0 -> 2493 bytes duckplyr.Rcheck/duckplyr/Meta/package.rds | Bin 0 -> 1953 bytes duckplyr.Rcheck/duckplyr/Meta/vignette.rds | Bin 0 -> 472 bytes duckplyr.Rcheck/duckplyr/NAMESPACE | 369 + duckplyr.Rcheck/duckplyr/NEWS.md | 483 + duckplyr.Rcheck/duckplyr/R/duckplyr | 27 + duckplyr.Rcheck/duckplyr/R/duckplyr.rdb | Bin 0 -> 782100 bytes duckplyr.Rcheck/duckplyr/R/duckplyr.rdx | Bin 0 -> 7836 bytes duckplyr.Rcheck/duckplyr/doc/developers.R | 74 + duckplyr.Rcheck/duckplyr/doc/developers.Rmd | 140 + duckplyr.Rcheck/duckplyr/doc/developers.html | 537 + duckplyr.Rcheck/duckplyr/doc/duckdb.R | 63 + duckplyr.Rcheck/duckplyr/doc/duckdb.Rmd | 139 + duckplyr.Rcheck/duckplyr/doc/duckdb.html | 462 + duckplyr.Rcheck/duckplyr/doc/extend.R | 139 + duckplyr.Rcheck/duckplyr/doc/extend.Rmd | 156 + duckplyr.Rcheck/duckplyr/doc/extend.html | 536 + duckplyr.Rcheck/duckplyr/doc/fallback.R | 71 + duckplyr.Rcheck/duckplyr/doc/fallback.Rmd | 178 + duckplyr.Rcheck/duckplyr/doc/fallback.html | 592 + duckplyr.Rcheck/duckplyr/doc/index.html | 65 + duckplyr.Rcheck/duckplyr/doc/large.R | 163 + duckplyr.Rcheck/duckplyr/doc/large.Rmd | 327 + duckplyr.Rcheck/duckplyr/doc/large.html | 635 + duckplyr.Rcheck/duckplyr/doc/limits.R | 211 + duckplyr.Rcheck/duckplyr/doc/limits.Rmd | 411 + duckplyr.Rcheck/duckplyr/doc/limits.html | 971 + duckplyr.Rcheck/duckplyr/doc/prudence.R | 151 + duckplyr.Rcheck/duckplyr/doc/prudence.Rmd | 324 + duckplyr.Rcheck/duckplyr/doc/prudence.html | 825 + duckplyr.Rcheck/duckplyr/doc/telemetry.R | 30 + duckplyr.Rcheck/duckplyr/doc/telemetry.Rmd | 67 + duckplyr.Rcheck/duckplyr/doc/telemetry.html | 402 + duckplyr.Rcheck/duckplyr/help/AnIndex | 91 + duckplyr.Rcheck/duckplyr/help/aliases.rds | Bin 0 -> 778 bytes duckplyr.Rcheck/duckplyr/help/duckplyr.rdb | Bin 0 -> 187814 bytes duckplyr.Rcheck/duckplyr/help/duckplyr.rdx | Bin 0 -> 1310 bytes .../help/figures/lifecycle-deprecated.svg | 21 + .../help/figures/lifecycle-experimental.svg | 21 + .../help/figures/lifecycle-stable.svg | 29 + .../help/figures/lifecycle-superseded.svg | 21 + .../duckplyr/help/figures/logo.png | Bin 0 -> 151220 bytes duckplyr.Rcheck/duckplyr/help/paths.rds | Bin 0 -> 598 bytes duckplyr.Rcheck/duckplyr/html/00Index.html | 179 + duckplyr.Rcheck/duckplyr/html/R.css | 130 + duckplyr.Rcheck/tests/startup.Rs | 4 + duckplyr.Rcheck/tests/testthat.R | 12 + duckplyr.Rcheck/tests/testthat.Rout | 262 + .../testthat/_snaps/as_duckplyr_tibble.md | 26 + .../tests/testthat/_snaps/compute.md | 17 + duckplyr.Rcheck/tests/testthat/_snaps/demo.md | 65 + .../tests/testthat/_snaps/dplyr-across.md | 357 + .../tests/testthat/_snaps/dplyr-all-equal.md | 108 + .../tests/testthat/_snaps/dplyr-arrange.md | 68 + .../tests/testthat/_snaps/dplyr-bind-cols.md | 36 + .../tests/testthat/_snaps/dplyr-bind-rows.md | 51 + .../tests/testthat/_snaps/dplyr-by.md | 33 + .../tests/testthat/_snaps/dplyr-case-match.md | 60 + .../tests/testthat/_snaps/dplyr-case-when.md | 120 + .../tests/testthat/_snaps/dplyr-coalesce.md | 63 + .../tests/testthat/_snaps/dplyr-conditions.md | 28 + .../testthat/_snaps/dplyr-consecutive-id.md | 23 + .../tests/testthat/_snaps/dplyr-context.md | 33 + .../testthat/_snaps/dplyr-count-tally.md | 102 + .../tests/testthat/_snaps/dplyr-desc.md | 8 + .../tests/testthat/_snaps/dplyr-distinct.md | 34 + .../tests/testthat/_snaps/dplyr-filter.md | 48 + .../tests/testthat/_snaps/dplyr-funs.md | 40 + .../tests/testthat/_snaps/dplyr-grouped-df.md | 105 + .../tests/testthat/_snaps/dplyr-if-else.md | 76 + .../tests/testthat/_snaps/dplyr-join-by.md | 473 + .../tests/testthat/_snaps/dplyr-join-cols.md | 148 + .../tests/testthat/_snaps/dplyr-join-cross.md | 9 + .../tests/testthat/_snaps/dplyr-join-rows.md | 401 + .../tests/testthat/_snaps/dplyr-join.md | 244 + .../tests/testthat/_snaps/dplyr-lead-lag.md | 70 + .../tests/testthat/_snaps/dplyr-mutate.md | 42 + .../tests/testthat/_snaps/dplyr-n-distinct.md | 20 + .../tests/testthat/_snaps/dplyr-na-if.md | 49 + .../tests/testthat/_snaps/dplyr-nth-value.md | 80 + .../tests/testthat/_snaps/dplyr-order-by.md | 33 + .../tests/testthat/_snaps/dplyr-pick.md | 131 + .../tests/testthat/_snaps/dplyr-rank.md | 32 + .../tests/testthat/_snaps/dplyr-reframe.md | 33 + .../tests/testthat/_snaps/dplyr-relocate.md | 8 + .../tests/testthat/_snaps/dplyr-rename.md | 24 + .../tests/testthat/_snaps/dplyr-rows.md | 347 + .../tests/testthat/_snaps/dplyr-rowwise.md | 84 + .../tests/testthat/_snaps/dplyr-sample.md | 82 + .../tests/testthat/_snaps/dplyr-select.md | 11 + .../tests/testthat/_snaps/dplyr-sets.md | 127 + .../tests/testthat/_snaps/dplyr-slice.md | 380 + .../tests/testthat/_snaps/dplyr-summarise.md | 55 + .../tests/testthat/_snaps/dplyr-transmute.md | 21 + .../tests/testthat/_snaps/duckplyr-across.md | 87 + .../tests/testthat/_snaps/duckplyr.md | 8 + .../tests/testthat/_snaps/ducktbl.md | 29 + duckplyr.Rcheck/tests/testthat/_snaps/expr.md | 36 + .../tests/testthat/_snaps/fallback.md | 261 + .../testthat/_snaps/fallback/fallback-2.dcf | 1 + .../testthat/_snaps/fallback/fallback.dcf | 5 + .../tests/testthat/_snaps/handle_desc.md | 24 + .../tests/testthat/_snaps/n_distinct.md | 24 + .../tests/testthat/_snaps/overwrite.md | 15 + .../testthat/_snaps/relational-duckdb.md | 80 + .../tests/testthat/_snaps/relational-rel.md | 9 + .../tests/testthat/_snaps/relational.md | 11 + .../tests/testthat/_snaps/telemetry.md | 326 + duckplyr.Rcheck/tests/testthat/_snaps/tpch.md | 129 + .../tests/testthat/_snaps/translate.md | 621 + .../tests/testthat/helper-dplyr-encoding.R | 78 + .../tests/testthat/helper-dplyr-pick.R | 6 + .../tests/testthat/helper-dplyr-s3.R | 48 + .../tests/testthat/helper-list_of.R | 1 + duckplyr.Rcheck/tests/testthat/helper-skip.R | 22 + duckplyr.Rcheck/tests/testthat/helper-taxi.R | 35 + duckplyr.Rcheck/tests/testthat/setup.R | 18 + duckplyr.Rcheck/tests/testthat/test-altrep.R | 14 + .../tests/testthat/test-as_duckplyr_df.R | 2727 +++ .../tests/testthat/test-as_duckplyr_tibble.R | 22 + duckplyr.Rcheck/tests/testthat/test-as_tbl.R | 30 + duckplyr.Rcheck/tests/testthat/test-compute.R | 36 + .../tests/testthat/test-compute_csv.R | 17 + .../tests/testthat/test-compute_parquet.R | 18 + duckplyr.Rcheck/tests/testthat/test-demo.R | 83 + duckplyr.Rcheck/tests/testthat/test-df.R | 3 + .../tests/testthat/test-dplyr-across.R | 1499 ++ .../tests/testthat/test-dplyr-all-equal.R | 194 + .../tests/testthat/test-dplyr-arrange.R | 571 + .../tests/testthat/test-dplyr-bind-cols.R | 144 + .../tests/testthat/test-dplyr-bind-rows.R | 266 + .../tests/testthat/test-dplyr-by.R | 107 + .../tests/testthat/test-dplyr-case-match.R | 72 + .../tests/testthat/test-dplyr-case-when.R | 314 + .../tests/testthat/test-dplyr-coalesce.R | 131 + .../tests/testthat/test-dplyr-colwise-funs.R | 14 + .../tests/testthat/test-dplyr-conditions.R | 211 + .../testthat/test-dplyr-consecutive-id.R | 31 + .../tests/testthat/test-dplyr-context.R | 80 + .../tests/testthat/test-dplyr-copy-to.R | 22 + .../tests/testthat/test-dplyr-count-tally.R | 229 + .../tests/testthat/test-dplyr-desc.R | 10 + .../tests/testthat/test-dplyr-distinct.R | 196 + .../tests/testthat/test-dplyr-filter.R | 729 + .../tests/testthat/test-dplyr-funs.R | 155 + .../tests/testthat/test-dplyr-generics.R | 198 + .../tests/testthat/test-dplyr-group-by.R | 624 + .../tests/testthat/test-dplyr-group-data.R | 132 + .../tests/testthat/test-dplyr-group-map.R | 137 + .../tests/testthat/test-dplyr-group-nest.R | 63 + .../tests/testthat/test-dplyr-group-split.R | 138 + .../tests/testthat/test-dplyr-group-trim.R | 29 + .../tests/testthat/test-dplyr-grouped-df.R | 221 + .../tests/testthat/test-dplyr-groups-with.R | 28 + .../tests/testthat/test-dplyr-if-else.R | 120 + .../tests/testthat/test-dplyr-join-by.R | 382 + .../tests/testthat/test-dplyr-join-cols.R | 208 + .../tests/testthat/test-dplyr-join-cross.R | 64 + .../tests/testthat/test-dplyr-join-rows.R | 476 + .../tests/testthat/test-dplyr-join.R | 773 + .../tests/testthat/test-dplyr-lead-lag.R | 177 + .../tests/testthat/test-dplyr-mutate.R | 915 + .../tests/testthat/test-dplyr-n-distinct.R | 52 + .../tests/testthat/test-dplyr-na-if.R | 94 + .../tests/testthat/test-dplyr-near.R | 10 + .../tests/testthat/test-dplyr-nest-by.R | 42 + .../tests/testthat/test-dplyr-nth-value.R | 246 + .../tests/testthat/test-dplyr-order-by.R | 54 + .../tests/testthat/test-dplyr-pick.R | 575 + .../tests/testthat/test-dplyr-pull.R | 34 + .../tests/testthat/test-dplyr-rank.R | 121 + .../tests/testthat/test-dplyr-reframe.R | 308 + .../tests/testthat/test-dplyr-relocate.R | 212 + .../tests/testthat/test-dplyr-rename.R | 104 + .../tests/testthat/test-dplyr-rows.R | 541 + .../tests/testthat/test-dplyr-rowwise.R | 146 + .../tests/testthat/test-dplyr-sample.R | 156 + .../testthat/test-dplyr-select-helpers.R | 28 + .../tests/testthat/test-dplyr-select.R | 211 + .../tests/testthat/test-dplyr-sets.R | 149 + .../tests/testthat/test-dplyr-slice.R | 667 + .../tests/testthat/test-dplyr-summarise.R | 592 + .../tests/testthat/test-dplyr-transmute.R | 102 + duckplyr.Rcheck/tests/testthat/test-dplyr.R | 49 + duckplyr.Rcheck/tests/testthat/test-duckdb.R | 6 + .../tests/testthat/test-duckplyr-across.R | 96 + .../tests/testthat/test-duckplyr.R | 43 + duckplyr.Rcheck/tests/testthat/test-ducktbl.R | 85 + duckplyr.Rcheck/tests/testthat/test-expr.R | 18 + .../tests/testthat/test-fallback.R | 313 + .../tests/testthat/test-handle_desc.R | 33 + duckplyr.Rcheck/tests/testthat/test-head.R | 29 + duckplyr.Rcheck/tests/testthat/test-io-csv.R | 12 + .../tests/testthat/test-io-parquet.R | 40 + duckplyr.Rcheck/tests/testthat/test-last.R | 12 + .../tests/testthat/test-n_distinct.R | 149 + .../tests/testthat/test-overwrite.R | 14 + duckplyr.Rcheck/tests/testthat/test-prom.R | 15 + .../tests/testthat/test-prudence.R | 66 + .../tests/testthat/test-read_csv_duckdb.R | 24 + .../tests/testthat/test-read_json_duckdb.R | 14 + .../tests/testthat/test-read_parquet_duckdb.R | 61 + duckplyr.Rcheck/tests/testthat/test-rel.R | 11 + duckplyr.Rcheck/tests/testthat/test-rel_api.R | 15183 +++++++++++++ .../tests/testthat/test-relational-duckdb.R | 140 + .../tests/testthat/test-relational-rel.R | 5 + .../tests/testthat/test-relational.R | 12 + duckplyr.Rcheck/tests/testthat/test-sets.R | 5 + duckplyr.Rcheck/tests/testthat/test-sql.R | 8 + .../tests/testthat/test-telemetry.R | 300 + duckplyr.Rcheck/tests/testthat/test-tpch.R | 271 + .../tests/testthat/test-translate.R | 237 + .../tests/testthat/tpch-sf0.01/q01.csv | 5 + .../tests/testthat/tpch-sf0.01/q02.csv | 5 + .../tests/testthat/tpch-sf0.01/q03.csv | 11 + .../tests/testthat/tpch-sf0.01/q04.csv | 6 + .../tests/testthat/tpch-sf0.01/q05.csv | 6 + .../tests/testthat/tpch-sf0.01/q06.csv | 2 + .../tests/testthat/tpch-sf0.01/q07.csv | 5 + .../tests/testthat/tpch-sf0.01/q08.csv | 3 + .../tests/testthat/tpch-sf0.01/q09.csv | 174 + .../tests/testthat/tpch-sf0.01/q10.csv | 21 + .../tests/testthat/tpch-sf0.01/q11.csv | 360 + .../tests/testthat/tpch-sf0.01/q12.csv | 3 + .../tests/testthat/tpch-sf0.01/q13.csv | 33 + .../tests/testthat/tpch-sf0.01/q14.csv | 2 + .../tests/testthat/tpch-sf0.01/q15.csv | 2 + .../tests/testthat/tpch-sf0.01/q16.csv | 297 + .../tests/testthat/tpch-sf0.01/q17.csv | 2 + .../tests/testthat/tpch-sf0.01/q18.csv | 3 + .../tests/testthat/tpch-sf0.01/q19.csv | 2 + .../tests/testthat/tpch-sf0.01/q20.csv | 2 + .../tests/testthat/tpch-sf0.01/q21.csv | 2 + .../tests/testthat/tpch-sf0.01/q22.csv | 8 + .../tests/testthat/tpch-sf1/q01.csv | 5 + .../tests/testthat/tpch-sf1/q02.csv | 101 + .../tests/testthat/tpch-sf1/q03.csv | 11 + .../tests/testthat/tpch-sf1/q04.csv | 6 + .../tests/testthat/tpch-sf1/q05.csv | 6 + .../tests/testthat/tpch-sf1/q06.csv | 2 + .../tests/testthat/tpch-sf1/q07.csv | 5 + .../tests/testthat/tpch-sf1/q08.csv | 3 + .../tests/testthat/tpch-sf1/q09.csv | 176 + .../tests/testthat/tpch-sf1/q10.csv | 21 + .../tests/testthat/tpch-sf1/q11.csv | 1049 + .../tests/testthat/tpch-sf1/q12.csv | 3 + .../tests/testthat/tpch-sf1/q13.csv | 43 + .../tests/testthat/tpch-sf1/q14.csv | 2 + .../tests/testthat/tpch-sf1/q15.csv | 2 + .../tests/testthat/tpch-sf1/q16.csv | 18315 ++++++++++++++++ .../tests/testthat/tpch-sf1/q17.csv | 2 + .../tests/testthat/tpch-sf1/q18.csv | 58 + .../tests/testthat/tpch-sf1/q19.csv | 2 + .../tests/testthat/tpch-sf1/q20.csv | 187 + .../tests/testthat/tpch-sf1/q21.csv | 101 + .../tests/testthat/tpch-sf1/q22.csv | 8 + duckplyr.Rcheck/tests/testthat/utf-8.txt | 19 + duckplyr_1.1.3.9004.tar.gz | Bin 0 -> 881880 bytes 731 files changed, 160966 insertions(+) create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/defaults.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/duckplyr.rds create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/DESCRIPTION create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/LICENSE create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/NAMESPACE create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/NEWS.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/aaa-meta.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/add_count.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_tibble.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_tbl.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/auto_copy.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/bisect_reduce.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/can_load_extension.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_csv.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_parquet.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/config.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_constant.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_function.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_reference.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_window.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/count-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/count.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/cross_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/do.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/dots.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr-reimplement.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-across.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-package.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_execute.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/ducktbl.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/exec.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/fallback.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/flights.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/globals.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_indices.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_keys.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_map.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_modify.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_nest.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_size.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_split.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_trim.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_vars.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/groups.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/handle_desc.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/head-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/head.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/import-standalone-purrr.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-csv.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-parquet.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/is_duckplyr_df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/join_ptype_common.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/last.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/n-col.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/n_groups.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/not-supported.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/oo.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/overwrite.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/positron.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/print.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/project.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/prudence.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_csv_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_file_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_json_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_parquet_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/reframe.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-expr.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-rel.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename_with.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/restore.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_append.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_delete.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_insert.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_patch.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_update.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_upsert.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/rowwise.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/select-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/select.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/setequal.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/sets.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_sample.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_tail.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/sql.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/stats.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/telemetry.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch2.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/translate.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/ungroup.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/union-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/union.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all-rd.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/unique_table_name.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-auto.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-methods.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/README.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/build/vignette.rds create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.html create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/anti_join.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/arrange.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_tbl.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/collect.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_csv.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_parquet.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/config.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/count.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/db_exec.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/df_from_file.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/distinct.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckdb_tibble.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr-package.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr_execute.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/explain.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/fallback.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-deprecated.svg create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-experimental.svg create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-stable.svg create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-superseded.svg create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/logo.png create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/filter.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/flights_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/full_join.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/head.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/inner_join.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/intersect.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/is_duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/last_rel.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/left_join.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/methods_overwrite.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/mutate.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relational.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relexpr.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/pull.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_csv_duckdb.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_file_duckdb.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_json_duckdb.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_parquet_duckdb.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_sql_duckdb.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/reexports.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/relocate.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/rename.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/right_join.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/select.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/semi_join.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/setdiff.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/slice_head.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/stats_show.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/summarise.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/symdiff.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/transmute.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/union.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/union_all.duckplyr_df.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/man/unsupported.Rd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/as_duckplyr_tibble.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/compute.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/demo.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-across.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-all-equal.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-arrange.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-cols.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-rows.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-by.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-match.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-when.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-coalesce.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-conditions.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-consecutive-id.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-context.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-count-tally.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-desc.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-distinct.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-filter.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-funs.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-grouped-df.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-if-else.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-by.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cols.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cross.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-rows.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-lead-lag.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-mutate.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-n-distinct.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-na-if.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-nth-value.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-order-by.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-pick.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rank.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-reframe.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-relocate.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rename.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rows.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rowwise.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sample.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-select.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sets.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-slice.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-summarise.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-transmute.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr-across.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/ducktbl.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/expr.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback-2.dcf create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback.dcf create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/handle_desc.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/n_distinct.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/overwrite.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-duckdb.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-rel.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/telemetry.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/tpch.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/translate.md create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-encoding.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-pick.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-s3.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-list_of.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-skip.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-taxi.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/setup.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-altrep.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_tibble.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_tbl.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_csv.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_parquet.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-demo.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-across.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-all-equal.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-arrange.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-cols.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-rows.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-match.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-when.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-coalesce.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-colwise-funs.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-conditions.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-consecutive-id.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-context.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-copy-to.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-count-tally.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-desc.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-distinct.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-filter.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-funs.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-generics.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-data.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-map.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-nest.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-split.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-trim.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-grouped-df.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-groups-with.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-if-else.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cols.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cross.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-rows.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-lead-lag.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-mutate.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-n-distinct.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-na-if.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-near.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nest-by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nth-value.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-order-by.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pick.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pull.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rank.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-reframe.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-relocate.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rename.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rows.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rowwise.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sample.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select-helpers.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sets.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-slice.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-summarise.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-transmute.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr-across.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-ducktbl.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-expr.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-fallback.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-handle_desc.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-head.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-csv.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-parquet.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-last.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-n_distinct.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-overwrite.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prom.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prudence.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_csv_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_json_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_parquet_duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel_api.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-duckdb.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-rel.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sets.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sql.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-telemetry.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-tpch.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-translate.R create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q01.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q02.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q03.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q04.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q05.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q06.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q07.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q08.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q09.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q10.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q11.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q12.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q13.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q14.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q15.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q16.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q17.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q18.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q19.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q20.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q21.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q22.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q01.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q02.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q03.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q04.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q05.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q06.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q07.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q08.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q09.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q10.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q11.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q12.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q13.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q14.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q15.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q16.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q17.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q18.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q19.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q20.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q21.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q22.csv create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/utf-8.txt create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/dd.png create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/developers.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/duckdb.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/extend.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/fallback.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/large.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/limits.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/prudence.Rmd create mode 100644 duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/telemetry.Rmd create mode 100644 duckplyr.Rcheck/00check.log create mode 100644 duckplyr.Rcheck/00install.out create mode 100755 duckplyr.Rcheck/R_check_bin/R create mode 100755 duckplyr.Rcheck/R_check_bin/Rscript create mode 100644 duckplyr.Rcheck/duckplyr-Ex.R create mode 100644 duckplyr.Rcheck/duckplyr-Ex.Rout create mode 100644 duckplyr.Rcheck/duckplyr-Ex.pdf create mode 100644 duckplyr.Rcheck/duckplyr-Ex.timings create mode 100644 duckplyr.Rcheck/duckplyr/DESCRIPTION create mode 100644 duckplyr.Rcheck/duckplyr/INDEX create mode 100644 duckplyr.Rcheck/duckplyr/LICENSE create mode 100644 duckplyr.Rcheck/duckplyr/Meta/Rd.rds create mode 100644 duckplyr.Rcheck/duckplyr/Meta/features.rds create mode 100644 duckplyr.Rcheck/duckplyr/Meta/hsearch.rds create mode 100644 duckplyr.Rcheck/duckplyr/Meta/links.rds create mode 100644 duckplyr.Rcheck/duckplyr/Meta/nsInfo.rds create mode 100644 duckplyr.Rcheck/duckplyr/Meta/package.rds create mode 100644 duckplyr.Rcheck/duckplyr/Meta/vignette.rds create mode 100644 duckplyr.Rcheck/duckplyr/NAMESPACE create mode 100644 duckplyr.Rcheck/duckplyr/NEWS.md create mode 100644 duckplyr.Rcheck/duckplyr/R/duckplyr create mode 100644 duckplyr.Rcheck/duckplyr/R/duckplyr.rdb create mode 100644 duckplyr.Rcheck/duckplyr/R/duckplyr.rdx create mode 100644 duckplyr.Rcheck/duckplyr/doc/developers.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/developers.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/developers.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/duckdb.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/duckdb.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/duckdb.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/extend.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/extend.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/extend.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/fallback.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/fallback.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/fallback.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/index.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/large.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/large.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/large.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/limits.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/limits.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/limits.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/prudence.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/prudence.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/prudence.html create mode 100644 duckplyr.Rcheck/duckplyr/doc/telemetry.R create mode 100644 duckplyr.Rcheck/duckplyr/doc/telemetry.Rmd create mode 100644 duckplyr.Rcheck/duckplyr/doc/telemetry.html create mode 100644 duckplyr.Rcheck/duckplyr/help/AnIndex create mode 100644 duckplyr.Rcheck/duckplyr/help/aliases.rds create mode 100644 duckplyr.Rcheck/duckplyr/help/duckplyr.rdb create mode 100644 duckplyr.Rcheck/duckplyr/help/duckplyr.rdx create mode 100644 duckplyr.Rcheck/duckplyr/help/figures/lifecycle-deprecated.svg create mode 100644 duckplyr.Rcheck/duckplyr/help/figures/lifecycle-experimental.svg create mode 100644 duckplyr.Rcheck/duckplyr/help/figures/lifecycle-stable.svg create mode 100644 duckplyr.Rcheck/duckplyr/help/figures/lifecycle-superseded.svg create mode 100644 duckplyr.Rcheck/duckplyr/help/figures/logo.png create mode 100644 duckplyr.Rcheck/duckplyr/help/paths.rds create mode 100644 duckplyr.Rcheck/duckplyr/html/00Index.html create mode 100644 duckplyr.Rcheck/duckplyr/html/R.css create mode 100644 duckplyr.Rcheck/tests/startup.Rs create mode 100644 duckplyr.Rcheck/tests/testthat.R create mode 100644 duckplyr.Rcheck/tests/testthat.Rout create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/as_duckplyr_tibble.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/compute.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/demo.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-across.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-all-equal.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-arrange.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-bind-cols.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-bind-rows.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-by.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-case-match.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-case-when.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-coalesce.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-conditions.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-consecutive-id.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-context.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-count-tally.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-desc.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-distinct.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-filter.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-funs.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-grouped-df.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-if-else.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-join-by.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-join-cols.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-join-cross.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-join-rows.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-join.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-lead-lag.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-mutate.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-n-distinct.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-na-if.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-nth-value.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-order-by.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-pick.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-rank.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-reframe.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-relocate.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-rename.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-rows.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-rowwise.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-sample.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-select.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-sets.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-slice.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-summarise.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/dplyr-transmute.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/duckplyr-across.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/duckplyr.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/ducktbl.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/expr.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/fallback.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/fallback/fallback-2.dcf create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/fallback/fallback.dcf create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/handle_desc.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/n_distinct.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/overwrite.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/relational-duckdb.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/relational-rel.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/relational.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/telemetry.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/tpch.md create mode 100644 duckplyr.Rcheck/tests/testthat/_snaps/translate.md create mode 100644 duckplyr.Rcheck/tests/testthat/helper-dplyr-encoding.R create mode 100644 duckplyr.Rcheck/tests/testthat/helper-dplyr-pick.R create mode 100644 duckplyr.Rcheck/tests/testthat/helper-dplyr-s3.R create mode 100644 duckplyr.Rcheck/tests/testthat/helper-list_of.R create mode 100644 duckplyr.Rcheck/tests/testthat/helper-skip.R create mode 100644 duckplyr.Rcheck/tests/testthat/helper-taxi.R create mode 100644 duckplyr.Rcheck/tests/testthat/setup.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-altrep.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-as_duckplyr_df.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-as_duckplyr_tibble.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-as_tbl.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-compute.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-compute_csv.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-compute_parquet.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-demo.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-df.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-across.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-all-equal.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-arrange.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-bind-cols.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-bind-rows.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-by.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-case-match.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-case-when.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-coalesce.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-colwise-funs.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-conditions.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-consecutive-id.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-context.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-copy-to.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-count-tally.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-desc.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-distinct.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-filter.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-funs.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-generics.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-group-by.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-group-data.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-group-map.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-group-nest.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-group-split.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-group-trim.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-grouped-df.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-groups-with.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-if-else.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-join-by.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-join-cols.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-join-cross.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-join-rows.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-join.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-lead-lag.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-mutate.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-n-distinct.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-na-if.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-near.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-nest-by.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-nth-value.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-order-by.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-pick.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-pull.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-rank.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-reframe.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-relocate.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-rename.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-rows.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-rowwise.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-sample.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-select-helpers.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-select.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-sets.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-slice.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-summarise.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr-transmute.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-dplyr.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-duckdb.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-duckplyr-across.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-duckplyr.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-ducktbl.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-expr.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-fallback.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-handle_desc.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-head.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-io-csv.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-io-parquet.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-last.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-n_distinct.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-overwrite.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-prom.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-prudence.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-read_csv_duckdb.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-read_json_duckdb.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-read_parquet_duckdb.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-rel.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-rel_api.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-relational-duckdb.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-relational-rel.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-relational.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-sets.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-sql.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-telemetry.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-tpch.R create mode 100644 duckplyr.Rcheck/tests/testthat/test-translate.R create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q01.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q02.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q03.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q04.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q05.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q06.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q07.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q08.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q09.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q10.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q11.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q12.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q13.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q14.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q15.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q16.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q17.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q18.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q19.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q20.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q21.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf0.01/q22.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q01.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q02.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q03.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q04.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q05.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q06.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q07.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q08.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q09.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q10.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q11.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q12.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q13.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q14.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q15.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q16.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q17.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q18.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q19.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q20.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q21.csv create mode 100644 duckplyr.Rcheck/tests/testthat/tpch-sf1/q22.csv create mode 100644 duckplyr.Rcheck/tests/testthat/utf-8.txt create mode 100644 duckplyr_1.1.3.9004.tar.gz diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/defaults.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/defaults.R new file mode 100644 index 000000000..a1d6c8d93 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/defaults.R @@ -0,0 +1,4 @@ +Rd_files <- vignettes <- R_files <- description <- + list(encoding = "UTF-8", + language = "en", + dictionaries = c("en_stats", "duckplyr")) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/duckplyr.rds b/duckplyr.Rcheck/00_pkg_src/duckplyr/.aspell/duckplyr.rds new file mode 100644 index 0000000000000000000000000000000000000000..5d9d21547fd350bd252b28476c45ea2058bc89ab GIT binary patch literal 88 zcmb2|=3oE==I#ec2?+^F35jVb2}x{5k}M4~rZ%=V3cD;9c@iS#Xqlt8^yE!FzW}-N ov^+<@th}r^Pc0ut{Yew53_~XD-C<;|YOKr4F#Y;l-VC6D0Mi^C+5i9m literal 0 HcmV?d00001 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/DESCRIPTION b/duckplyr.Rcheck/00_pkg_src/duckplyr/DESCRIPTION new file mode 100644 index 000000000..0588dbab3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/DESCRIPTION @@ -0,0 +1,48 @@ +Type: Package +Package: duckplyr +Title: A 'DuckDB'-Backed Version of 'dplyr' +Version: 1.1.3.9004 +Authors@R: c( + person("Hannes", "Mühleisen", role = "aut", + comment = c(ORCID = "0000-0001-8552-0029")), + person("Kirill", "Müller", , "kirill@cynkra.com", role = c("aut", "cre"), + comment = c(ORCID = "0000-0002-1416-3412")), + person("Posit Software, PBC", role = c("cph", "fnd"), + comment = c(ROR = "03wc8by49")) + ) +Description: A drop-in replacement for 'dplyr', powered by 'DuckDB' for + performance. Offers convenient utilities for working with in-memory and + larger-than-memory data while retaining full 'dplyr' compatibility. +License: MIT + file LICENSE +URL: https://duckplyr.tidyverse.org, + https://github.com/tidyverse/duckplyr +BugReports: https://github.com/tidyverse/duckplyr/issues +Depends: R (>= 4.0.0), dplyr (>= 1.1.4) +Imports: cli, collections, DBI, duckdb (>= 1.2.2), glue, jsonlite, + lifecycle, magrittr, memoise, pillar (>= 1.10.2), rlang (>= + 1.0.6), tibble, tidyselect, utils, vctrs (>= 0.6.3) +Suggests: arrow, brio, callr, conflicted, constructive (>= 1.0.0), + curl, dbplyr, hms, knitr, lobstr, lubridate, nycflights13, + palmerpenguins, prettycode, purrr, readr, rmarkdown, testthat + (>= 3.1.5), usethis, withr +Enhances: qs +Config/Needs/check: anthonynorth/roxyglobals +Config/Needs/development: devtools, qs, reprex, r-lib/roxygen2, + roxyglobals, rstudioapi, tidyverse +Config/Needs/website: dbplyr, rmarkdown, tidyverse/tidytemplate +Config/testthat/edition: 3 +Config/testthat/parallel: false +Config/testthat/start-first: rel_api, tpch, as_duckplyr_df, + dplyr-mutate, dplyr-filter, dplyr-count-tally +Encoding: UTF-8 +Roxygen: list( markdown = TRUE, roclets = c("collate", "namespace", + "rd", "roxyglobals::global_roclet") ) +RoxygenNote: 7.3.3.9000 +VignetteBuilder: knitr +NeedsCompilation: no +Packaged: 2025-11-26 10:09:41 UTC; runner +Author: Hannes Mühleisen [aut] (ORCID: ), + Kirill Müller [aut, cre] (ORCID: + ), + Posit Software, PBC [cph, fnd] (ROR: ) +Maintainer: Kirill Müller diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/LICENSE b/duckplyr.Rcheck/00_pkg_src/duckplyr/LICENSE new file mode 100644 index 000000000..6b766784f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2023 +COPYRIGHT HOLDER: duckplyr authors diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/NAMESPACE b/duckplyr.Rcheck/00_pkg_src/duckplyr/NAMESPACE new file mode 100644 index 000000000..455b13a32 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/NAMESPACE @@ -0,0 +1,369 @@ +# Generated by roxygen2: do not edit by hand + +S3method(.cstr_construct.relational_relexpr_constant,relexpr_constant) +S3method(.cstr_construct.relational_relexpr_function,relexpr_function) +S3method(.cstr_construct.relational_relexpr_reference,relexpr_reference) +S3method(.cstr_construct.relational_relexpr_window,relexpr_window) +S3method(add_count,duckplyr_df) +S3method(anti_join,duckplyr_df) +S3method(arrange,duckplyr_df) +S3method(as.data.frame,duckplyr_df) +S3method(as.data.frame,prudent_duckplyr_df) +S3method(as_duckdb_tibble,data.frame) +S3method(as_duckdb_tibble,default) +S3method(as_duckdb_tibble,duckplyr_df) +S3method(as_duckdb_tibble,grouped_df) +S3method(as_duckdb_tibble,rowwise_df) +S3method(as_duckdb_tibble,spec_tbl_df) +S3method(as_duckdb_tibble,tbl_duckdb_connection) +S3method(as_tibble,duckplyr_df) +S3method(auto_copy,duckplyr_df) +S3method(collect,duckplyr_df) +S3method(collect,prudent_duckplyr_df) +S3method(compute,duckplyr_df) +S3method(count,duckplyr_df) +S3method(cross_join,duckplyr_df) +S3method(distinct,duckplyr_df) +S3method(do,duckplyr_df) +S3method(explain,duckplyr_df) +S3method(filter,duckplyr_df) +S3method(full_join,duckplyr_df) +S3method(group_by,duckplyr_df) +S3method(group_indices,duckplyr_df) +S3method(group_keys,duckplyr_df) +S3method(group_map,duckplyr_df) +S3method(group_modify,duckplyr_df) +S3method(group_nest,duckplyr_df) +S3method(group_size,duckplyr_df) +S3method(group_split,duckplyr_df) +S3method(group_trim,duckplyr_df) +S3method(group_vars,duckplyr_df) +S3method(groups,duckplyr_df) +S3method(head,duckplyr_df) +S3method(inner_join,duckplyr_df) +S3method(intersect,duckplyr_df) +S3method(left_join,duckplyr_df) +S3method(mutate,duckplyr_df) +S3method(n_groups,duckplyr_df) +S3method(nest_by,duckplyr_df) +S3method(nest_join,duckplyr_df) +S3method(print,relational_relexpr) +S3method(pull,duckplyr_df) +S3method(reframe,duckplyr_df) +S3method(rel_aggregate,duckdb_relation) +S3method(rel_aggregate,relational_df) +S3method(rel_alias,duckdb_relation) +S3method(rel_alias,relational_df) +S3method(rel_distinct,duckdb_relation) +S3method(rel_distinct,relational_df) +S3method(rel_explain,duckdb_relation) +S3method(rel_explain,relational_df) +S3method(rel_filter,duckdb_relation) +S3method(rel_filter,relational_df) +S3method(rel_join,duckdb_relation) +S3method(rel_join,relational_df) +S3method(rel_limit,duckdb_relation) +S3method(rel_limit,relational_df) +S3method(rel_names,duckdb_relation) +S3method(rel_names,relational_df) +S3method(rel_order,duckdb_relation) +S3method(rel_order,relational_df) +S3method(rel_project,duckdb_relation) +S3method(rel_project,relational_df) +S3method(rel_set_alias,duckdb_relation) +S3method(rel_set_alias,relational_df) +S3method(rel_set_diff,duckdb_relation) +S3method(rel_set_diff,relational_df) +S3method(rel_set_intersect,duckdb_relation) +S3method(rel_set_intersect,relational_df) +S3method(rel_set_symdiff,duckdb_relation) +S3method(rel_set_symdiff,relational_df) +S3method(rel_to_df,duckdb_relation) +S3method(rel_to_df,relational_df) +S3method(rel_union_all,duckdb_relation) +S3method(rel_union_all,relational_df) +S3method(relocate,duckplyr_df) +S3method(rename,duckplyr_df) +S3method(rename_with,duckplyr_df) +S3method(right_join,duckplyr_df) +S3method(rows_append,duckplyr_df) +S3method(rows_delete,duckplyr_df) +S3method(rows_insert,duckplyr_df) +S3method(rows_patch,duckplyr_df) +S3method(rows_update,duckplyr_df) +S3method(rows_upsert,duckplyr_df) +S3method(rowwise,duckplyr_df) +S3method(select,duckplyr_df) +S3method(semi_join,duckplyr_df) +S3method(setdiff,duckplyr_df) +S3method(setequal,duckplyr_df) +S3method(slice,duckplyr_df) +S3method(slice_head,duckplyr_df) +S3method(slice_sample,duckplyr_df) +S3method(slice_tail,duckplyr_df) +S3method(summarise,duckplyr_df) +S3method(symdiff,duckplyr_df) +S3method(tbl_format_setup,duckplyr_df) +S3method(tbl_nrow,duckplyr_df) +S3method(tbl_sum,duckplyr_df) +S3method(transmute,duckplyr_df) +S3method(ungroup,duckplyr_df) +S3method(union,duckplyr_df) +S3method(union_all,duckplyr_df) +export("%>%") +export(as_duckdb_tibble) +export(as_duckplyr_df) +export(as_duckplyr_tibble) +export(as_tbl) +export(compute_csv) +export(compute_parquet) +export(db_exec) +export(df_from_csv) +export(df_from_file) +export(df_from_parquet) +export(df_to_parquet) +export(duckdb_tibble) +export(duckplyr_df_from_csv) +export(duckplyr_df_from_file) +export(duckplyr_df_from_parquet) +export(duckplyr_execute) +export(fallback_config) +export(fallback_purge) +export(fallback_review) +export(fallback_sitrep) +export(fallback_upload) +export(flights_df) +export(is_duckdb_tibble) +export(is_duckplyr_df) +export(last_rel) +export(methods_overwrite) +export(methods_restore) +export(new_relational) +export(new_relexpr) +export(read_csv_duckdb) +export(read_file_duckdb) +export(read_json_duckdb) +export(read_parquet_duckdb) +export(read_sql_duckdb) +export(rel_aggregate) +export(rel_alias) +export(rel_distinct) +export(rel_explain) +export(rel_filter) +export(rel_join) +export(rel_limit) +export(rel_names) +export(rel_order) +export(rel_project) +export(rel_set_alias) +export(rel_set_diff) +export(rel_set_intersect) +export(rel_set_symdiff) +export(rel_to_df) +export(rel_union_all) +export(relexpr_comparison) +export(relexpr_constant) +export(relexpr_function) +export(relexpr_reference) +export(relexpr_set_alias) +export(relexpr_window) +export(stats_show) +import(rlang) +importFrom(collections,dict) +importFrom(collections,queue) +importFrom(dplyr,across) +importFrom(dplyr,add_count) +importFrom(dplyr,add_tally) +importFrom(dplyr,all_equal) +importFrom(dplyr,anti_join) +importFrom(dplyr,arrange) +importFrom(dplyr,auto_copy) +importFrom(dplyr,between) +importFrom(dplyr,bind_cols) +importFrom(dplyr,bind_rows) +importFrom(dplyr,c_across) +importFrom(dplyr,case_match) +importFrom(dplyr,case_when) +importFrom(dplyr,coalesce) +importFrom(dplyr,collapse) +importFrom(dplyr,collect) +importFrom(dplyr,compute) +importFrom(dplyr,consecutive_id) +importFrom(dplyr,count) +importFrom(dplyr,cross_join) +importFrom(dplyr,cumall) +importFrom(dplyr,cumany) +importFrom(dplyr,cume_dist) +importFrom(dplyr,cummean) +importFrom(dplyr,cur_column) +importFrom(dplyr,cur_group) +importFrom(dplyr,cur_group_id) +importFrom(dplyr,cur_group_rows) +importFrom(dplyr,dense_rank) +importFrom(dplyr,desc) +importFrom(dplyr,distinct) +importFrom(dplyr,do) +importFrom(dplyr,dplyr_col_modify) +importFrom(dplyr,dplyr_reconstruct) +importFrom(dplyr,dplyr_row_slice) +importFrom(dplyr,explain) +importFrom(dplyr,filter) +importFrom(dplyr,first) +importFrom(dplyr,full_join) +importFrom(dplyr,funs) +importFrom(dplyr,funs_) +importFrom(dplyr,group_by) +importFrom(dplyr,group_by_prepare) +importFrom(dplyr,group_cols) +importFrom(dplyr,group_data) +importFrom(dplyr,group_indices) +importFrom(dplyr,group_keys) +importFrom(dplyr,group_map) +importFrom(dplyr,group_modify) +importFrom(dplyr,group_nest) +importFrom(dplyr,group_rows) +importFrom(dplyr,group_size) +importFrom(dplyr,group_split) +importFrom(dplyr,group_trim) +importFrom(dplyr,group_vars) +importFrom(dplyr,group_walk) +importFrom(dplyr,grouped_df) +importFrom(dplyr,groups) +importFrom(dplyr,if_any) +importFrom(dplyr,if_else) +importFrom(dplyr,inner_join) +importFrom(dplyr,intersect) +importFrom(dplyr,is_grouped_df) +importFrom(dplyr,join_by) +importFrom(dplyr,lag) +importFrom(dplyr,last) +importFrom(dplyr,lead) +importFrom(dplyr,left_join) +importFrom(dplyr,min_rank) +importFrom(dplyr,mutate) +importFrom(dplyr,mutate_all) +importFrom(dplyr,n) +importFrom(dplyr,n_distinct) +importFrom(dplyr,n_groups) +importFrom(dplyr,na_if) +importFrom(dplyr,near) +importFrom(dplyr,nest_by) +importFrom(dplyr,nest_join) +importFrom(dplyr,new_grouped_df) +importFrom(dplyr,new_rowwise_df) +importFrom(dplyr,nth) +importFrom(dplyr,ntile) +importFrom(dplyr,order_by) +importFrom(dplyr,percent_rank) +importFrom(dplyr,pick) +importFrom(dplyr,pull) +importFrom(dplyr,reframe) +importFrom(dplyr,relocate) +importFrom(dplyr,rename) +importFrom(dplyr,rename_all) +importFrom(dplyr,rename_at) +importFrom(dplyr,rename_if) +importFrom(dplyr,rename_with) +importFrom(dplyr,right_join) +importFrom(dplyr,row_number) +importFrom(dplyr,rows_append) +importFrom(dplyr,rows_delete) +importFrom(dplyr,rows_insert) +importFrom(dplyr,rows_patch) +importFrom(dplyr,rows_update) +importFrom(dplyr,rows_upsert) +importFrom(dplyr,rowwise) +importFrom(dplyr,same_src) +importFrom(dplyr,sample_frac) +importFrom(dplyr,sample_n) +importFrom(dplyr,select) +importFrom(dplyr,select_all) +importFrom(dplyr,select_at) +importFrom(dplyr,select_if) +importFrom(dplyr,semi_join) +importFrom(dplyr,setdiff) +importFrom(dplyr,setequal) +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,summarise) +importFrom(dplyr,summarise_all) +importFrom(dplyr,summarise_at) +importFrom(dplyr,summarize) +importFrom(dplyr,symdiff) +importFrom(dplyr,tally) +importFrom(dplyr,tbl_vars) +importFrom(dplyr,tibble) +importFrom(dplyr,top_n) +importFrom(dplyr,transmute) +importFrom(dplyr,ungroup) +importFrom(dplyr,union) +importFrom(dplyr,union_all) +importFrom(dplyr,vars) +importFrom(dplyr,with_groups) +importFrom(dplyr,with_order) +importFrom(glue,glue) +importFrom(glue,glue_collapse) +importFrom(lifecycle,deprecated) +importFrom(magrittr,"%>%") +importFrom(pillar,tbl_format_setup) +importFrom(pillar,tbl_nrow) +importFrom(pillar,tbl_sum) +importFrom(rlang,.data) +importFrom(tibble,add_row) +importFrom(tibble,as_tibble) +importFrom(tibble,deframe) +importFrom(tibble,is_tibble) +importFrom(tibble,new_tibble) +importFrom(tibble,tibble) +importFrom(tibble,tribble) +importFrom(tidyselect,all_of) +importFrom(tidyselect,any_of) +importFrom(tidyselect,contains) +importFrom(tidyselect,everything) +importFrom(tidyselect,last_col) +importFrom(tidyselect,matches) +importFrom(tidyselect,num_range) +importFrom(tidyselect,one_of) +importFrom(tidyselect,starts_with) +importFrom(tidyselect,where) +importFrom(utils,head) +importFrom(vctrs,new_data_frame) +importFrom(vctrs,new_date) +importFrom(vctrs,new_list_of) +importFrom(vctrs,new_rcrd) +importFrom(vctrs,new_vctr) +importFrom(vctrs,obj_check_list) +importFrom(vctrs,unspecified) +importFrom(vctrs,vec_as_names) +importFrom(vctrs,vec_assign) +importFrom(vctrs,vec_c) +importFrom(vctrs,vec_cast) +importFrom(vctrs,vec_cast_common) +importFrom(vctrs,vec_cbind) +importFrom(vctrs,vec_check_size) +importFrom(vctrs,vec_count) +importFrom(vctrs,vec_data) +importFrom(vctrs,vec_detect_complete) +importFrom(vctrs,vec_in) +importFrom(vctrs,vec_init) +importFrom(vctrs,vec_match) +importFrom(vctrs,vec_ptype) +importFrom(vctrs,vec_ptype2) +importFrom(vctrs,vec_ptype_finalise) +importFrom(vctrs,vec_ptype_full) +importFrom(vctrs,vec_rbind) +importFrom(vctrs,vec_recycle_common) +importFrom(vctrs,vec_rep) +importFrom(vctrs,vec_rep_each) +importFrom(vctrs,vec_set_difference) +importFrom(vctrs,vec_set_intersect) +importFrom(vctrs,vec_set_symmetric_difference) +importFrom(vctrs,vec_set_union) +importFrom(vctrs,vec_size) +importFrom(vctrs,vec_slice) +importFrom(vctrs,vec_split) +importFrom(vctrs,vec_unique_loc) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/NEWS.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/NEWS.md new file mode 100644 index 000000000..89dd338e9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/NEWS.md @@ -0,0 +1,483 @@ + + +# duckplyr 1.1.3.9004 (2025-11-17) + +## Continuous integration + +- Install binaries from r-universe for dev workflow (#813). + + +# duckplyr 1.1.3.9003 (2025-11-12) + +## Continuous integration + +- Fix reviewdog and add commenting workflow (#810). + + +# duckplyr 1.1.3.9002 (2025-11-11) + +## Continuous integration + +- Use workflows for fledge (#807). + + +# duckplyr 1.1.3.9001 (2025-11-08) + +## Continuous integration + +- Sync (#805). + + +# duckplyr 1.1.3.9000 (2025-11-04) + +## fledge + +- CRAN release v1.1.3 (#803). + + +# duckplyr 1.1.3 (2025-11-04) + +## Features + +- `read_file_duckdb()` only wraps `path` into a list if the length is not equal to one, to support `read_stat()`. + +## Continuous integration + +- Avoid example failing in R 4.2 and older. + +## Documentation + +- Add "Supported by Posit" badge. + + +# duckplyr 1.1.2 (2025-09-17) + +## Features + +- Fully support `dd::...()` syntax (#795). + +- Threshold for `prudence = "thrifty"` is reduced to 1000 cells when the data comes from a remote data source. + +- Support named arguments for `dd::...()` functions. + +## Performance + +- Generate a more balanced expresion when translating `%in%` to avoid performance problems in duckdb v1.4.0. + + +# duckplyr 1.1.1 (2025-07-29) + +## Chore + +- Fix CRAN failure with `_R_CHECK_THINGS_IN_OTHER_DIRS_=true`. + + +# duckplyr 1.1.0 (2025-05-08) + +This release improves compatibility with dbplyr and DuckDB. +See `vignette("duckdb")` for details. + +## Features + +- Pass functions prefixed with `dd$` directly to DuckDB, e.g., `dd$ROW()` will be translated as DuckDB's `ROW()` function (#658). + +- New `as_tbl()` to convert to a dbplyr tbl object (#634, #685). + +- Register Ark methods for Positron's "Variables" pane (@DavisVaughan, #661, #678). DuckDB tibbles are no longer displayed as data frames in the "Variables" pane due to a limitation in Positron. Use `collect()` to convert them to data frames if you rely on the viewer functionality. + +- Translate `n_distinct()` as macro with support for `na.rm = TRUE` (@joakimlinde, #572, #655). + +- Translate `coalesce()`. + +- `compute()` does not have a fallback, failures are reported to the client (#637). + +- Implement `slice_head()` (#640). + +## Bug fixes + +- Set functions like `union()` no longer trigger materialization (#654, #692). + +- Joins no longer materialize the input data when the package is used with `methods_overwrite()` or `library(duckplyr)` (#641). + +- Correct formatting for controlled fallbacks with `Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE)`. + +## Chore + +- Bump duckdb and pillar dependencies. + +- Use roxyglobals from CRAN rather than GitHub (@andreranza, #659). + +- Bring tools and patch up to date (@joakimlinde, #647). + +- Internal `rel_to_df()` needs `prudence` argument (#644). + +- Fix sync scripts and add reproducible code (#639). + +- Check loadability of extensions in test (#636). + +## Documentation + +- Document `slice_head()` as supported. + +- Add Posit's ROR ID (#592). + +- Add `vignette("duckdb")` (#690). + +- Add experimental badge. + +- Verbose `conflict_prefer()` (#667, #684). + +- Typos + clarification edits to "large" vignette (@mine-cetinkaya-rundel, #665). + +## Testing + +- Skip tests using `grep()` or `sub()` on CRAN. + + +# duckplyr 1.0.1 (2025-02-21) + +## Bug fixes + +- Check if extensions can be loaded before running examples and vignettes (#620). + +- Show source of error if data frame cannot be converted to duck frame (#614). + +- Correct formatting for controlled fallbacks with `Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE)` + +## Chore + +- Require duckdb \>= 1.2.0 (#619). + +- Break this version with duckdb 2.0.0 (#623). + +## Documentation + +- Separate `?compute_parquet` and `?compute_csv` (#610, #622). + +- Italicize book title in README (@wibeasley, #607). + +- Fix typo in `filter(.by = ...)` error message (@maelle, #611). + +- Fix link in documentation (#600, #601). + + +# duckplyr 1.0.0 (2025-02-02) + +## Features + +### Large data + +- Improved support for handling large data from files and S3: ingestion with `read_parquet_duckdb()` and others, and materialization with `as_duckdb_tibble()`, `compute.duckplyr_df()` and `compute_file()`. See `vignette("large")` for details. + +- Control automatic materialization of duckplyr frames with the new `prudence` argument to `as_duckdb_tibble()`, `duckdb_tibble()`, `compute.duckplyr_df()` and `compute_file()`. See `vignette("prudence")` for details. + +### New functions + +- `read_csv_duckdb()` and others, deprecating `duckplyr_df_from_csv()` and `df_from_csv()` (#210, #396, #459). + +- `read_sql_duckdb()` (experimental) to run SQL queries against the default DuckDB connection and return the result as a duckplyr frame (duckdb/duckdb-r#32, #397). + +- `db_exec()` to execute configuration queries against the default duckdb connection (#39, #165, #227, #404, #459). + +- `duckdb_tibble()` (#382, #457). + +- `as_duckdb_tibble()`, replaces `as_duckplyr_tibble()` and `as_duckplyr_df()` (#383, #457) and supports dbplyr connections to a duckdb database (#86, #211, #226). + +- `compute_parquet()` and `compute_csv()`, implement `compute.duckplyr_df()` (#409, #430). + +- `fallback_config()` to create a configuration file for the settings that do not affect behavior (#216, #426). + +- `is_duckdb_tibble()`, deprecates `is_duckplyr_df()` (#391, #392). + +- `last_rel()` to retrieve the last relation object used in materialization (#209, #375). + +- Add `"prudent_duckplyr_df"` class that stops automatic materialization and requires `collect()` (#381, #390). + +### Translations + +- Partial support for `across()` in `mutate()` and `summarise()` (#296, #306, #318, @lionel-, @DavisVaughan). + +- Implement `na.rm` handling for `sum()`, `min()`, `max()`, `any()` and `all()`, with fallback for window functions (#205, #566). + +- Add support for `sub()` and `gsub()` (@toppyy, #420). + +- Handle `dplyr::desc()` (#550). + +- Avoid forwarding `is.na()` to `is.nan()` to support non-numeric data, avoid checking roundtrip for timestamp data (#482). + +- Correctly handle missing values in `if_else()`. + +- Limit number of items that can be handled with `%in%` (#319). + +- `duckdb_tibble()` checks if columns can be represented in DuckDB (#537). + +- Fall back to dplyr when passing `multiple` with joins (#323). + +### Error messages + +- Improve fallback error message by explicitly materializing (#432, #456). + +- Point to the native CSV reader if encountering data frames read with readr (#127, #469). + +- Improve `as_duckdb_tibble()` error message for invalid `x` (@maelle, #339). + +### Behavior + +- Depend on dplyr instead of reexporting all generics (#405). Nothing changes for users in scripts. When using duckplyr in a package, you now also need to import dplyr. + +- Fallback logging is now on by default, can be disabled with configuration (#422). + +- The default DuckDB connection is now based on a file, the location defaults to a subdirectory of `tempdir()` and can be controlled with the `DUCKPLYR_TEMP_DIR` environment variable (#439, #448, #561). + +- `collect()` returns a tibble (#438, #447). + +- `explain()` returns the input, invisibly (#331). + +## Bug fixes + +- Compute ptype only for join columns in a safe way without materialization, not for the entire data frame (#289). + +- Internal `expr_scrub()` (used for telemetry) can handle function-definitions (@toppyy, #268, #271). + +- Harden telemetry code against invalid arguments (#321). + +## Documentation + +- New articles: `vignette("large")`, `vignette("prudence")`, `vignette("fallback")`, `vignette("limits")`, `vignette("developers")`, `vignette("telemetry")` (#207, #504). + +- New `flights_df()` used instead of `palmerpenguins::penguins` (#408). + +- Move to the tidyverse GitHub organization, new repository URL (#225). + +- Avoid base pipe in examples for compatibility with R 4.0.0 (#463, #466). + +## Performance + +- Comparison expressions are translated in a way that allows them to be pushed down to Parquet (@toppyy, #270). + +- Printing a duckplyr frame no longer materializes (#255, #378). + +- Prefer `vctrs::new_data_frame()` over `tibble()` (#500). + + +# duckplyr 0.4.1 (2024-07-11) + +## Features + +- `df_from_file()` and related functions support multiple files (#194, #195), show a clear error message for non-string `path` arguments (#182), and create a tibble by default (#177). +- New `as_duckplyr_tibble()` to convert a data frame to a duckplyr tibble (#177). +- Support descending sort for character and other non-numeric data (@toppyy, #92, #175). +- Avoid setting memory limit (#193). +- Check compatibility of join columns (#168, #185). +- Explicitly list supported functions, add contributing guide, add analysis scripts for GitHub activity data (#179). + +## Documentation + +- Add contributing guide (#179). +- Show a startup message at package load if telemetry is not configured (#188, #198). +- `?df_from_file` shows how to read multiple files (#181, #186) and how to specify CSV column types (#140, #189), and is shown correctly in reference index (#173, #190). +- Discuss dbplyr in README (#145, #191). +- Add analysis scripts for GitHub activity data (#179). + + +# duckplyr 0.4.0 (2024-05-21) + +## Features + +- Use built-in rfuns extension to implement equality and inequality operators, improve translation for `as.integer()`, `NA` and `%in%` (#83, #154, #148, #155, #159, #160). +- Reexport non-deprecated dplyr functions (#144, #163). +- `library(duckplyr)` calls `methods_overwrite()` (#164). +- Only allow constant patterns in `grepl()`. +- Explicitly reject calls with named arguments for now. +- Reduce default memory limit to 1 GB. + +## Bug fixes + +- Stricter type checks in the set operations `intersect()`, `setdiff()`, `symdiff()`, `union()`, and `union_all()` (#169). +- Distinguish between constant `NA` and those used in an expression (#157). +- `head(-1)` forwards to the default implementation (#131, #156). +- Fix cli syntax for internal error message (#151). +- More careful detection of row names in data frame. +- Always check roundtrip for timestamp columns. +- `left_join()` and other join functions call `auto_copy()`. +- Only reset expression depth if it has been set before. +- Require fallback if the result contains duplicate column names when ignoring case. +- `row_number()` returns integer. +- `is.na(NaN)` is `TRUE`. +- `summarise(count = n(), count = n())` creates only one column named `count`. +- Correct wording in instructions for enabling fallback logging (@TimTaylor, #141). + +## Chore + +- Remove styler dependency (#137, #138). +- Avoid error from stats collection. + +## Documentation + +- Mention wildcards to read multiple files in `?df_from_file` (@andreranza, #133, #134). + +## Testing + +- Reenable tests that now run successfully (#166). +- Synchronize tests (#153). +- Test that `vec_ptype()` does not materialize (#149). +- Improve telemetry tests. +- Promote equality checks to `expect_identical()` to capture differences between doubles and integers. + + +# duckplyr 0.3.2 (2024-03-17) + +## Bug fixes + +- Run autoupload in function so that it will be checked by static analysis (#122). + +## Features + +- New `df_to_parquet()` to write to Parquet, new convenience functions `df_from_csv()`, `duckdb_df_from_csv()`, `df_from_parquet()` and `duckdb_df_from_parquet()` (#87, #89, #96, #128). + + +# duckplyr 0.3.1 (2024-03-08) + +## Bug fixes + +- Forbid reuse of new columns created in `summarise()` (#72, #106). +- `summarise()` no longer restores subclass. +- Disambiguate computation of `log10()` and `log()`. +- Fix division by zero for positive and negative numbers. + +## Features + +- New `fallback_sitrep()` and related functionality for collecting telemetry data (#102, #107, #110, #111, #115). No data is collected by default, only a message is displayed once per session and then every eight hours. Opt in or opt out by setting environment variables. +- Implement `group_by()` and other methods to collect fallback information (#94, #104, #105). +- Set memory limit and temporary directory for duckdb. +- Implement `suppressWarnings()` as the identity function. +- Prefer `cli::cli_abort()` over `stop()` or `rlang::abort()` (#114). +- Translate `.data$a` and `.env$a`. +- Strict checks for column class, only supporting `integer`, `numeric`, `logical`, `Date`, `POSIXct`, and `difftime` for now. +- If the environment variable `DUCKPLYR_METHODS_OVERWRITE` is set to `TRUE`, loading duckplyr automatically calls `methods_overwrite()`. + +## Internal + +- Better duckdb tests. +- Use standalone purrr for dplyr compatibility. + +## Testing + +- Add tests for correct base of `log()` and `log10()`. + +## Documentation + +- `methods_overwrite()` and `methods_restore()` show a message. + + +# duckplyr 0.3.0 (2023-12-10) + +## Bug fixes + +- `grepl(x = NA)` gives correct results. +- Fix `auto_copy()` for non-data-frame input. +- Add output order preservation for filters. +- `distinct()` now preserves order in corner cases (#77, #78). +- Consistent computation of `log(0)` and `log(-1)` (#75, #76). + +## Features + +- Only allow constants in `mutate()` that are actually representable in duckdb (#73). +- Avoid translating `ifelse()`, support `if_else()` (#79). + +## Documentation + +- Separate and explain the new relational examples (@wibeasley, #84). + +## Testing + +- Add test that TPC-H queries can be processed. + +## Chore + +- Sync with dplyr 1.1.4 (#82). +- Remove `dplyr_reconstruct()` method (#48). +- Render README. +- Fix code generated by `meta_replay()`. +- Bump constructive dependency. +- Fix output order for `arrange()` in case of ties. +- Update duckdb tests. +- Only implement newer `slice_sample()`, not `sample_n()` or `sample_frac()` (#74). +- Sync generated files (#71). + + +# duckplyr 0.2.3 (2023-11-08) + +## Performance + +- Join using `IS NOT DISTINCT FROM` for faster execution (duckdb/duckdb-r#41, #68). + +## Documentation + +- Add stability to README output (@maelle, #62, #65). + + +# duckplyr 0.2.2 (2023-10-16) + +## Bug fixes + +- `summarise()` keeps `"duckplyr_df"` class (#63, #64). + +- Fix compatibility with duckdb \>= 0.9.1. + +## Chore + +- Skip tests that give different output on dev tidyselect. + +- Import `utils::globalVariables()`. + +## Documentation + +- Small README improvements (@maelle, #34, #57). + +- Fix 301 in README. + + +# duckplyr 0.2.1 (2023-09-16) + +- Improve documentation. + +- Work around problem with `dplyr_reconstruct()` in R 4.3. + +- Rename `duckdb_from_file()` to `df_from_file()`. + +- Unexport private `duckdb_rel_from_df()`, `rel_from_df()`, `wrap_df()` and `wrap_integer()`. + +- Reexport `%>%` and `tibble()`. + + +# duckplyr 0.2.0 (2023-09-10) + +- Implement relational API for DuckDB. + + +# duckplyr 0.1.0 (2023-07-03) + +## Bug fixes + +- Fix examples. + +## Chore + +- Add CRAN install instructions. +- Satisfy `R CMD check`. +- Document argument. +- Error on NOTE. +- Remove `relexpr_window()` for now. + +## Documentation + +- Clean up reference. + +## Uncategorized + +Initial version, exporting: +- `new_relational()` to construct objects of class `"relational"` +- Generics `rel_aggregate()`, `rel_distinct()`, `rel_filter()`, `rel_join()`, `rel_limit()`, `rel_names()`, `rel_order()`, `rel_project()`, `rel_set_diff()`, `rel_set_intersect()`, `rel_set_symdiff()`, `rel_to_df()`, `rel_union_all()` +- `new_relexpr()` to construct objects of class `"relational_relexpr"` +- Expression builders `relexpr_constant()`, `relexpr_function()`, `relexpr_reference()`, `relexpr_set_alias()`, `relexpr_window()` diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/aaa-meta.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/aaa-meta.R new file mode 100644 index 000000000..769b993e3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/aaa-meta.R @@ -0,0 +1,9 @@ +# Overwritten in meta.R +meta_reset <- function(...) {} +meta_call <- function(...) {} +meta_ext_register <- function(...) {} +meta_rel_register <- function(...) {} +meta_rel_register_df <- function(...) {} +meta_rel_register_file <- function(...) {} +meta_rel_get <- function(...) {} +meta_macro_register <- function(...) {} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/add_count.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/add_count.R new file mode 100644 index 000000000..235b096ec --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/add_count.R @@ -0,0 +1,41 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +add_count.duckplyr_df <- function(x, ..., wt = NULL, sort = FALSE, name = NULL, .drop = deprecated()) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code add_count()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + add_count <- dplyr$add_count.data.frame + out <- add_count(x, ..., wt = {{ wt }}, sort = sort, name = name, .drop = .drop) + return(out) + + # dplyr implementation + out <- add_count_impl( + x, + ..., + wt = {{ wt }}, + sort = sort, + name = name, + .drop = .drop + ) + dplyr_reconstruct(out, x) +} + +duckplyr_add_count <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- add_count(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join-rd.R new file mode 100644 index 000000000..916a2f8e6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join-rd.R @@ -0,0 +1,13 @@ +#' @title Anti join +#' +#' @description This is a method for the [dplyr::anti_join()] generic. +#' `anti_join()` returns all rows from `x` with**out** a match in `y`. +#' +#' @inheritParams dplyr::anti_join +#' @examples +#' library(duckplyr) +#' band_members %>% anti_join(band_instruments) +#' @seealso [dplyr::anti_join()] +#' @rdname anti_join.duckplyr_df +#' @name anti_join.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join.R new file mode 100644 index 000000000..85dccb9bf --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/anti_join.R @@ -0,0 +1,47 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname anti_join.duckplyr_df +#' @export +anti_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, ..., na_matches = c("na", "never")) { + check_dots_empty0(...) + error_call <- caller_env() + y <- auto_copy(x, y, copy = copy) + + # https://github.com/duckdb/duckdb/issues/6597 + na_matches <- check_na_matches(na_matches, error_call = error_call) + + # Our implementation + duckplyr_error <- rel_try(list(name = "anti_join", x = x, y = y, args = try_list(by = if (!is.null(by) && !is_cross_by(by)) as_join_by(by), copy = copy, na_matches = na_matches)), + "No restrictions" = FALSE, + { + out <- rel_join_impl(x, y, by, "anti", na_matches, error_call = error_call) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + anti_join <- dplyr$anti_join.data.frame + out <- anti_join(x, y, by, copy = FALSE, ..., na_matches = na_matches) + return(out) + + # dplyr implementation + check_dots_empty0(...) + y <- auto_copy(x, y, copy = copy) + join_filter(x, y, by = by, type = "anti", na_matches = na_matches, user_env = caller_env()) +} + +duckplyr_anti_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- anti_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange-rd.R new file mode 100644 index 000000000..4c483fb7a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange-rd.R @@ -0,0 +1,22 @@ +#' @title Order rows using column values +#' +#' @description This is a method for the [dplyr::arrange()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `arrange()` orders the rows of a data frame by the values of selected +#' columns. +#' +#' Unlike other dplyr verbs, `arrange()` largely ignores grouping; you +#' need to explicitly mention grouping variables (or use `.by_group = TRUE`) +#' in order to group by them, and functions of variables are evaluated +#' once per data frame, not once per group. +#' +#' +#' @inheritParams dplyr::arrange +#' @examples +#' library(duckplyr) +#' arrange(mtcars, cyl, disp) +#' arrange(mtcars, desc(disp)) +#' @seealso [dplyr::arrange()] +#' @rdname arrange.duckplyr_df +#' @name arrange.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange.R new file mode 100644 index 000000000..f1268d4a7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/arrange.R @@ -0,0 +1,82 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname arrange.duckplyr_df +#' @export +arrange.duckplyr_df <- function(.data, ..., .by_group = FALSE, .locale = NULL) { + force(.data) + + dots <- enquos(...) + dots <- unname(dots) + + duckplyr_error <- rel_try(list(name = "arrange", x = .data, args = try_list(dots = dots, .by_group = .by_group)), + #' @section Fallbacks: + #' There is no DuckDB translation in `arrange.duckplyr_df()` + #' - with `.by_group = TRUE`, + #' - providing a value for the `.locale` argument, + #' - providing a value for the `dplyr.legacy_locale` option. + #' + #' These features fall back to [dplyr::arrange()], see `vignette("fallback")` for details. + "{.arg .by_group} = {.value TRUE} not supported" = !identical(.by_group, FALSE), + "{.arg .locale} argument not supported" = !is.null(.locale), + "dplyr.legacy_locale not supported" = isTRUE(getOption("dplyr.legacy_locale")), + { + # Translate to df before early exit, so that we can bail out for subclasses + rel <- duckdb_rel_from_df(.data) + if (length(dots) == 0) { + return(.data) + } + + dots_ascending <- handle_desc(dots) + dots <- dots_ascending$dots + ascending <- dots_ascending$ascending + + exprs <- rel_translate_dots(dots, .data) + + if (oo_force()) { + rel <- oo_prep(rel, force = TRUE) + exprs <- c(exprs, list(relexpr_reference("___row_number"))) + ascending <- c(ascending, TRUE) + } + + rel <- rel_order(rel, exprs, ascending) + + # Don't need to sort here, already sorting by ___row_number + if (oo_force()) { + out_rel <- oo_restore_cols(rel) + } else { + out_rel <- rel + } + + out <- duckplyr_reconstruct(out_rel, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + arrange <- dplyr$arrange.data.frame + out <- arrange(.data, ..., .by_group = .by_group, .locale = .locale) + return(out) + + # dplyr implementation + dots <- enquos(...) + + if (.by_group) { + dots <- c(quos(!!!groups(.data)), dots) + } + + loc <- arrange_rows(.data, dots = dots, locale = .locale) + dplyr_row_slice(.data, loc) +} + +duckplyr_arrange <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- arrange(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_df.R new file mode 100644 index 000000000..fd2f73249 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_df.R @@ -0,0 +1,45 @@ +#' Convert to a duckplyr data frame +#' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' These functions convert a data-frame-like input to an object of class `"duckpylr_df"`. +#' For such objects, +#' dplyr verbs such as [mutate()], [select()] or [filter()] will attempt to use DuckDB. +#' If this is not possible, the original dplyr implementation is used. +#' +#' `as_duckplyr_df()` requires the input to be a plain data frame or a tibble, +#' and will fail for any other classes, including subclasses of `"data.frame"` or `"tbl_df"`. +#' This behavior is likely to change, do not rely on it. +#' +#' @details +#' Set the `DUCKPLYR_FALLBACK_INFO` and `DUCKPLYR_FORCE` environment variables +#' for more control over the behavior, see [config] for more details. +#' +#' @param .data data frame or tibble to transform +#' +#' @return For `as_duckplyr_df()`, an object of class `"duckplyr_df"`, +#' inheriting from the classes of the `.data` argument. +#' +#' @keywords internal +#' @export +#' @examples +#' tibble(a = 1:3) %>% +#' mutate(b = a + 1) +as_duckplyr_df <- function(.data) { + lifecycle::deprecate_soft("1.0.0", "as_duckplyr_df()", "as_duckdb_tibble()") + + as_duckplyr_df_impl(.data) +} + +as_duckplyr_df_impl <- function(x, error_call = caller_env()) { + # FIXME: Move to as_duckdb_tibble() + if (!identical(class(x), "data.frame") && !identical(class(x), c("tbl_df", "tbl", "data.frame"))) { + cli::cli_abort(call = error_call, c( + "Must pass a plain data frame or a tibble, not {.obj_type_friendly {x}}.", + i = "Convert it with {.fun as.data.frame} or {.fun tibble::as_tibble}." + )) + } + + new_duckdb_tibble(x, class = class(x), error_call = error_call) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_tibble.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_tibble.R new file mode 100644 index 000000000..0cd7c280a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_duckplyr_tibble.R @@ -0,0 +1,24 @@ +#' as_duckplyr_tibble +#' +#' `as_duckplyr_tibble()` converts the input to a tibble and then to a duckplyr data frame. +#' +#' @return For `as_duckplyr_tibble()`, an object of class +#' `c("duckplyr_df", class(tibble()))` . +#' +#' @rdname as_duckplyr_df +#' @export +as_duckplyr_tibble <- function(.data) { + lifecycle::deprecate_soft("1.0.0", "as_duckplyr_tibble()", "as_duckdb_tibble()") + + if (inherits(.data, "tbl_duckdb_connection")) { + con <- dbplyr::remote_con(.data) + sql <- dbplyr::remote_query(.data) + rel <- duckdb$rel_from_sql(con, sql) + out <- rel_to_df(rel) + class(out) <- c("duckplyr_df", class(new_tibble(list()))) + return(out) + } + + # Extra as.data.frame() call for good measure and perhaps https://github.com/tidyverse/tibble/issues/1556 + as_duckplyr_df_impl(as_tibble(as.data.frame(.data))) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_tbl.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_tbl.R new file mode 100644 index 000000000..9d7199d9a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/as_tbl.R @@ -0,0 +1,54 @@ +#' Convert a duckplyr frame to a dbplyr table +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' This function converts a lazy duckplyr frame or a data frame +#' to a dbplyr table in duckplyr's internal connection. +#' This allows using dbplyr functions on the data, +#' including hand-written SQL queries. +#' Use [as_duckdb_tibble()] to convert back to a lazy duckplyr frame. +#' +#' @param .data A lazy duckplyr frame or a data frame. +#' @return A dbplyr table. +#' @export +#' @examplesIf requireNamespace("dbplyr", quietly = TRUE) +#' df <- duckdb_tibble(a = 1L) +#' df +#' +#' tbl <- as_tbl(df) +#' tbl +#' +#' tbl %>% +#' mutate(b = sql("a + 1")) %>% +#' as_duckdb_tibble() +as_tbl <- function(.data) { + check_installed("dbplyr") + + rel <- duckdb_rel_from_df(.data) + + con <- get_default_duckdb_connection() + name <- unique_table_name("as_tbl_") + + # Drop view when object goes out of scope + scope_guard <- new_environment() + reg.finalizer( + scope_guard, + function(e) { + tryCatch(DBI::dbExecute(con, paste0("DROP VIEW ", name)), + # Ignore errors + error = function(e) { + message(conditionMessage(e)) + } + ) + }, + onexit = FALSE + ) + + # Side effect + duckdb$rel_to_view(rel, "", name, temporary = TRUE) + + out <- dplyr::tbl(con, name) + attr(out, "duckplyr_scope_guard") <- scope_guard + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/auto_copy.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/auto_copy.R new file mode 100644 index 000000000..baee477c3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/auto_copy.R @@ -0,0 +1,22 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +auto_copy.duckplyr_df <- function(x, y, copy = FALSE, ...) { + return(as_duckplyr_df_impl(as.data.frame(y))) + + # dplyr implementation + as.data.frame(y) +} + +duckplyr_auto_copy <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- auto_copy(x, y, ...) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/bisect_reduce.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/bisect_reduce.R new file mode 100644 index 000000000..81bd6638b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/bisect_reduce.R @@ -0,0 +1,11 @@ +bisect_reduce <- function(x, fun) { + n <- length(x) + if (n == 1) { + return(x[[1]]) + } + mid <- floor(n / 2) + incl <- seq_len(mid) + left <- bisect_reduce(x[incl], fun) + right <- bisect_reduce(x[-incl], fun) + fun(left, right) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/can_load_extension.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/can_load_extension.R new file mode 100644 index 000000000..35390e643 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/can_load_extension.R @@ -0,0 +1,16 @@ +can_load_extension <- function(name) { + tryCatch( + callr::r(args = list(name), function(name) { + con <- DBI::dbConnect(duckdb::duckdb()) + DBI::dbExecute(con, paste0("INSTALL ", name)) + DBI::dbExecute(con, paste0("LOAD ", name)) + TRUE + }), + error = function(e) FALSE + ) +} + +on_load({ + env <- environment() + assign("can_load_extension", memoise::memoise(can_load_extension), envir = env) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect-rd.R new file mode 100644 index 000000000..90989137d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect-rd.R @@ -0,0 +1,17 @@ +#' @title Force conversion to a data frame +#' +#' @description This is a method for the [dplyr::collect()] generic. +#' `collect()` converts the input to a tibble, materializing any lazy operations. +#' +#' @inheritParams dplyr::collect +#' @examples +#' library(duckplyr) +#' df <- duckdb_tibble(x = c(1, 2), .lazy = TRUE) +#' df +#' try(print(df$x)) +#' df <- collect(df) +#' df +#' @seealso [dplyr::collect()] +#' @rdname collect.duckplyr_df +#' @name collect.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect.R new file mode 100644 index 000000000..c1c9d51d9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/collect.R @@ -0,0 +1,21 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname collect.duckplyr_df +#' @export +collect.duckplyr_df <- function(x, ...) { + # Side effect: ALTREP materialization is triggered + nrow(x) + class(x) <- setdiff(class(x), "duckplyr_df") + x +} + +duckplyr_collect <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- collect(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute-rd.R new file mode 100644 index 000000000..6d52e506d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute-rd.R @@ -0,0 +1,42 @@ +#' @title Compute results +#' +#' @description This is a method for the [dplyr::compute()] generic. +#' For a duckplyr frame, +#' `compute()` executes a query but stores it in a (temporary) table, +#' or in a Parquet or CSV file. +#' The result is a duckplyr frame that can be used with subsequent dplyr verbs. +#' +#' @inheritParams dplyr::compute +#' @inheritParams duckdb_tibble +#' @param x A duckplyr frame. +#' @param name The name of the table to store the result in. +#' @param schema_name The schema to store the result in, defaults to the current schema. +#' @param temporary Set to `FALSE` to store the result in a permanent table. +#' @param prudence Memory protection, controls if DuckDB may convert +#' intermediate results in DuckDB-managed memory to data frames in R memory. +#' +#' - `"lavish"`: regardless of size, +#' - `"stingy"`: never, +#' - `"thrifty"`: up to a maximum size of 1 million cells. +#' +#' The default is to inherit from the input. +#' This argument is provided here only for convenience. +#' The same effect can be achieved by forwarding the output to [as_duckdb_tibble()] +#' with the desired prudence. +#' See `vignette("prudence")` for more information. +#' +# @inheritSection duckdb_tibble Fine-tuning prudence # Omitting here, too much detail +#' +#' @return A duckplyr frame. +#' +#' @examples +#' library(duckplyr) +#' df <- duckdb_tibble(x = c(1, 2)) +#' df <- mutate(df, y = 2) +#' explain(df) +#' df <- compute(df) +#' explain(df) +#' @seealso [dplyr::collect()] +#' @rdname compute.duckplyr_df +#' @name compute.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute.R new file mode 100644 index 000000000..d33f21347 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute.R @@ -0,0 +1,65 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname compute.duckplyr_df +#' @export +compute.duckplyr_df <- function( + x, + ..., + prudence = NULL, + name = NULL, + schema_name = NULL, + temporary = TRUE +) { + if (is.null(prudence)) { + prudence <- get_prudence_duckplyr_df(x) + } + if (is.null(schema_name)) { + schema_name <- "" + } + if (is.null(name)) { + if (isTRUE(temporary)) { + name <- unique_table_name() + } else { + cli::cli_abort("{.arg name} must be provided if {.arg temporary} is {.value FALSE}") + } + } + + # Our implementation + duckplyr_error <- rel_try(NULL, + { + rel <- duckdb_rel_from_df(x) + + duckdb$rel_to_table(rel, schema_name, name, temporary) + + # API inconsistency: order of name and schema_name + out_rel <- duckdb$rel_from_table(get_default_duckdb_connection(), name, schema_name) + + out <- duckplyr_reconstruct(out_rel, x) + + if (get_prudence_duckplyr_df(out) != prudence) { + out <- as_duckdb_tibble(out, prudence = prudence) + } + + return(out) + } + ) + + # Unconditionally signal error + # (If we can't compute, something's weird here.) + if (is.character(duckplyr_error)) { + cli::cli_abort(duckplyr_error) + } else { + cnd_signal(duckplyr_error) + } +} + +duckplyr_compute <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- compute(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_csv.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_csv.R new file mode 100644 index 000000000..f777dcabc --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_csv.R @@ -0,0 +1,48 @@ +#' Compute results to a CSV file +#' +#' For a duckplyr frame, this function executes the query +#' and stores the results in a CSV file, +#' without converting it to an R data frame. +#' The result is a duckplyr frame that can be used with subsequent dplyr verbs. +#' This function can also be used as a CSV writer for regular data frames. +#' +#' @inheritParams rlang::args_dots_empty +#' @inheritParams compute.duckplyr_df +#' @inheritParams compute_parquet +#' @param options A list of additional options to pass to create the storage format, +#' see +#' for details. +#' +#' @return A duckplyr frame. +#' +#' @export +#' @examples +#' library(duckplyr) +#' df <- data.frame(x = c(1, 2)) +#' df <- mutate(df, y = 2) +#' path <- tempfile(fileext = ".csv") +#' df <- compute_csv(df, path) +#' readLines(path) +#' @seealso [compute_parquet()], [compute.duckplyr_df()], [dplyr::collect()] +compute_csv <- function(x, path, ..., prudence = NULL, options = NULL) { + check_dots_empty() + + if (is.null(options)) { + options <- list() + } + + if (is.null(prudence)) { + prudence <- get_prudence_duckplyr_df(x) + } + + rel <- duckdb_rel_from_df(x) + + duckdb$rel_to_csv(rel, path, options) + + # If the path is a directory, we assume that the user wants to write multiple files + if (dir.exists(path)) { + path <- file.path(path, "**", "**.csv") + } + + read_csv_duckdb(path, prudence = prudence) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_parquet.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_parquet.R new file mode 100644 index 000000000..228f556a1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/compute_parquet.R @@ -0,0 +1,49 @@ +#' Compute results to a Parquet file +#' +#' For a duckplyr frame, this function executes the query +#' and stores the results in a Parquet file, +#' without converting it to an R data frame. +#' The result is a duckplyr frame that can be used with subsequent dplyr verbs. +#' This function can also be used as a Parquet writer for regular data frames. +#' +#' @inheritParams rlang::args_dots_empty +#' @inheritParams compute.duckplyr_df +#' @param x A duckplyr frame. +#' @param path The path of the Parquet file to create. +#' @param options A list of additional options to pass to create the Parquet file, +#' see +#' for details. +#' +#' @return A duckplyr frame. +#' +#' @export +#' @examples +#' library(duckplyr) +#' df <- data.frame(x = c(1, 2)) +#' df <- mutate(df, y = 2) +#' path <- tempfile(fileext = ".parquet") +#' df <- compute_parquet(df, path) +#' explain(df) +#' @seealso [compute_csv()], [compute.duckplyr_df()], [dplyr::collect()] +compute_parquet <- function(x, path, ..., prudence = NULL, options = NULL) { + check_dots_empty() + + if (is.null(options)) { + options <- list() + } + + if (is.null(prudence)) { + prudence <- get_prudence_duckplyr_df(x) + } + + rel <- duckdb_rel_from_df(x) + + duckdb$rel_to_parquet(rel, path, options) + + # If the path is a directory, we assume that the user wants to write multiple files + if (dir.exists(path)) { + path <- file.path(path, "**", "**.parquet") + } + + read_parquet_duckdb(path, prudence = prudence) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/config.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/config.R new file mode 100644 index 000000000..db37b345c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/config.R @@ -0,0 +1,67 @@ +#' Configuration options +#' +#' The behavior of duckplyr can be fine-tuned with several environment variables, +#' and one option. +#' +#' @section Environment variables: +#' +#' `DUCKPLYR_TEMP_DIR`: Set to a path where temporary files can be created. +#' By default, [tempdir()] is used. +#' +#' `DUCKPLYR_OUTPUT_ORDER`: If `TRUE`, row output order is preserved. +#' The default may change the row order where dplyr would keep it stable. +#' Preserving the order leads to more complicated execution plans +#' with less potential for optimization, and thus may be slower. +#' +#' `DUCKPLYR_FORCE`: If `TRUE`, fail if duckdb cannot handle a request. +#' +#' `DUCKPLYR_CHECK_ROUNDTRIP`: If `TRUE`, check if all columns are roundtripped perfectly +#' when creating a relational object from a data frame, +#' This is slow, and mostly useful for debugging. +#' The default is to check roundtrip of attributes. +#' +#' `DUCKPLYR_METHODS_OVERWRITE`: If `TRUE`, call `methods_overwrite()` +#' when the package is loaded. +#' +#' See [fallback] for more options related to printing, logging, and uploading +#' of fallback events. +#' +# Not available in the CRAN package: +# `DUCKPLYR_META_ENABLE`: Skip recording the operations, replay not available. +# `DUCKPLYR_META_GLOBAL`: Assume data frames in the global environment as "known". +# `DUCKPLYR_SKIP_DPLYR_TESTS`: Skip dplyr tests for performance +#' @name config +#' @examples +#' # Sys.setenv(DUCKPLYR_OUTPUT_ORDER = TRUE) +#' data.frame(a = 3:1) %>% +#' as_duckdb_tibble() %>% +#' inner_join(data.frame(a = 1:4), by = "a") +#' +#' withr::with_envvar(c(DUCKPLYR_OUTPUT_ORDER = "TRUE"), { +#' data.frame(a = 3:1) %>% +#' as_duckdb_tibble() %>% +#' inner_join(data.frame(a = 1:4), by = "a") +#' }) +#' +#' # Sys.setenv(DUCKPLYR_FORCE = TRUE) +#' add_one <- function(x) { +#' x + 1 +#' } +#' +#' data.frame(a = 3:1) %>% +#' as_duckdb_tibble() %>% +#' mutate(b = add_one(a)) +#' +#' try(withr::with_envvar(c(DUCKPLYR_FORCE = "TRUE"), { +#' data.frame(a = 3:1) %>% +#' as_duckdb_tibble() %>% +#' mutate(b = add_one(a)) +#' })) +#' +#' # Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE) +#' withr::with_envvar(c(DUCKPLYR_FALLBACK_INFO = "TRUE"), { +#' data.frame(a = 3:1) %>% +#' as_duckdb_tibble() %>% +#' mutate(b = add_one(a)) +#' }) +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_constant.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_constant.R new file mode 100644 index 000000000..8f14e967f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_constant.R @@ -0,0 +1,46 @@ +#' Constructive options for class 'relational_relexpr_constant' +#' +#' These options will be used on objects of class 'relational_relexpr_constant'. +#' +#' Depending on `constructor`, we construct the object as follows: +#' * `"relexpr_constant"` (default): We build the object using `relexpr_constant()`. +#' * `"next"` : Use the constructor for the next supported class. +#' +#' @param constructor String. Name of the function used to construct the object. +#' @param ... Additional options used by user defined constructors through the `opts` object +#' @return An object of class +#' @noRd +opts_relational_relexpr_constant <- function(constructor = c("relexpr_constant", "next"), ...) { + constructive::.cstr_options("relational_relexpr_constant", constructor = constructor[[1]], ...) +} + +.cstr_construct.relational_relexpr_constant <- function(x, ...) { + opts <- list(...)$opts$relational_relexpr_constant %||% opts_relational_relexpr_constant() + if (is_corrupted_relational_relexpr_constant(x) || opts$constructor == "next") { + return(NextMethod()) + } + UseMethod(".cstr_construct.relational_relexpr_constant", structure(NA, class = opts$constructor)) +} + +is_corrupted_relational_relexpr_constant <- function(x) { + FALSE +} + +#' @export +#' @method .cstr_construct.relational_relexpr_constant relexpr_constant +.cstr_construct.relational_relexpr_constant.relexpr_constant <- function(x, ...) { + # opts <- list(...)$opts$relational_relexpr_constant %||% opts_relational_relexpr_constant() + args <- compact(list( + x$val, + alias = x$alias + )) + code <- constructive::.cstr_apply(args, fun = "relexpr_constant", ...) + constructive::.cstr_repair_attributes( + x, code, ..., + idiomatic_class = c("relational_relexpr_constant", "relational_relexpr") + ) +} + +on_load({ + vctrs::s3_register("constructive::.cstr_construct", "relational_relexpr_constant") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_function.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_function.R new file mode 100644 index 000000000..e8a5a0013 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_function.R @@ -0,0 +1,71 @@ +#' Constructive options for class 'relational_relexpr_function' +#' +#' These options will be used on objects of class 'relational_relexpr_function'. +#' +#' Depending on `constructor`, we construct the object as follows: +#' * `"relexpr_function"` (default): We build the object using `relexpr_function()`. +#' * `"next"` : Use the constructor for the next supported class. +#' +#' @param constructor String. Name of the function used to construct the object. +#' @param ... Additional options used by user defined constructors through the `opts` object +#' @return An object of class +#' @noRd +opts_relational_relexpr_function <- function(constructor = c("relexpr_function", "next"), ...) { + # What's forwarded through `...`will be accessible through the `opts` + # object in the methods. + # You might add arguments to the function, to document those options, + # don't forget to forward them below as well + constructive::.cstr_options("relational_relexpr_function", constructor = constructor[[1]], ...) +} + +.cstr_construct.relational_relexpr_function <- function(x, ...) { + # There is probably no need for you to modify this function at all + opts <- list(...)$opts$relational_relexpr_function %||% opts_relational_relexpr_function() + if (is_corrupted_relational_relexpr_function(x) || opts$constructor == "next") { + return(NextMethod()) + } + # This odd looking code dispatches to a method based on the name of + # the constructor rather than the class + UseMethod(".cstr_construct.relational_relexpr_function", structure(NA, class = opts$constructor)) +} + +is_corrupted_relational_relexpr_function <- function(x) { + # check here if the object has the right structure to be constructed + # leaving FALSE is fine but you'll be vulnerable to corrupted objects + FALSE +} + +#' @export +#' @method .cstr_construct.relational_relexpr_function relexpr_function +.cstr_construct.relational_relexpr_function.relexpr_function <- function(x, ...) { + # If needed, fetch additional options fed through opts_relational_relexpr_function() + # opts <- list(...)$opts$relational_relexpr_function %||% opts_relational_relexpr_function() + + # Instead of the call below we need to fetch the args of the constructor in `x`. + args <- Filter(function(.x) !is.null(.x), list( + x$name, + x$args, + alias = x$alias + )) + + # This creates a call relexpr_function(...) where ... is the constructed code + # of the arguments stored in `args` + # Sometimes we want to construct the code of the args separately, i.e. store + # code rather than objects in `args`, and use `recurse = FALSE` below + code <- constructive::.cstr_apply(args, fun = "relexpr_function", ...) + + # constructive::.cstr_repair_attributes() makes sure that attributes that are not built + # by the idiomatic constructor are generated + constructive::.cstr_repair_attributes( + x, code, ..., + # attributes built by the constructor + # ignore =, + + # not necessarily just a string, but the whole class(x) vector + idiomatic_class = c("relational_relexpr_function", "relational_relexpr") + ) +} + +on_load({ + vctrs::s3_register("constructive::.cstr_construct", "relational_relexpr_function") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_reference.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_reference.R new file mode 100644 index 000000000..2cd97adb1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_reference.R @@ -0,0 +1,47 @@ +#' Constructive options for class 'relational_relexpr_reference' +#' +#' These options will be used on objects of class 'relational_relexpr_reference'. +#' +#' Depending on `constructor`, we construct the object as follows: +#' * `"relexpr_reference"` (default): We build the object using `relexpr_reference()`. +#' * `"next"` : Use the constructor for the next supported class. +#' +#' @param constructor String. Name of the function used to construct the object. +#' @param ... Additional options used by user defined constructors through the `opts` object +#' @return An object of class +#' @noRd +opts_relational_relexpr_reference <- function(constructor = c("relexpr_reference", "next"), ...) { + constructive::.cstr_options("relational_relexpr_reference", constructor = constructor[[1]], ...) +} + +.cstr_construct.relational_relexpr_reference <- function(x, ...) { + opts <- list(...)$opts$relational_relexpr_reference %||% opts_relational_relexpr_reference() + if (is_corrupted_relational_relexpr_reference(x) || opts$constructor == "next") { + return(NextMethod()) + } + UseMethod(".cstr_construct.relational_relexpr_reference", structure(NA, class = opts$constructor)) +} + +is_corrupted_relational_relexpr_reference <- function(x) { + FALSE +} + +#' @export +#' @method .cstr_construct.relational_relexpr_reference relexpr_reference +.cstr_construct.relational_relexpr_reference.relexpr_reference <- function(x, ...) { + # opts <- list(...)$opts$relational_relexpr_reference %||% opts_relational_relexpr_reference() + args <- compact(list( + x$name, + rel = x$rel, + alias = x$alias + )) + code <- constructive::.cstr_apply(args, fun = "relexpr_reference", ...) + constructive::.cstr_repair_attributes( + x, code, ..., + idiomatic_class = c("relational_relexpr_reference", "relational_relexpr") + ) +} + +on_load({ + vctrs::s3_register("constructive::.cstr_construct", "relational_relexpr_reference") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_window.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_window.R new file mode 100644 index 000000000..e74ee897d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/construct_window.R @@ -0,0 +1,50 @@ +#' Constructive options for class 'relational_relexpr_window' +#' +#' These options will be used on objects of class 'relational_relexpr_window'. +#' +#' Depending on `constructor`, we construct the object as follows: +#' * `"relexpr_window"` (default): We build the object using `relexpr_window()`. +#' * `"next"` : Use the constructor for the next supported class. +#' +#' @param constructor String. Name of the function used to construct the object. +#' @param ... Additional options used by user defined constructors through the `opts` object +#' @return An object of class +#' @noRd +opts_relational_relexpr_window <- function(constructor = c("relexpr_window", "next"), ...) { + constructive::.cstr_options("relational_relexpr_window", constructor = constructor[[1]], ...) +} + +.cstr_construct.relational_relexpr_window <- function(x, ...) { + opts <- list(...)$opts$relational_relexpr_window %||% opts_relational_relexpr_window() + if (is_corrupted_relational_relexpr_window(x) || opts$constructor == "next") { + return(NextMethod()) + } + UseMethod(".cstr_construct.relational_relexpr_window", structure(NA, class = opts$constructor)) +} + +is_corrupted_relational_relexpr_window <- function(x) { + FALSE +} + +#' @export +#' @method .cstr_construct.relational_relexpr_window relexpr_window +.cstr_construct.relational_relexpr_window.relexpr_window <- function(x, ...) { + # opts <- list(...)$opts$relational_relexpr_window %||% opts_relational_relexpr_window() + args <- Filter(function(.x) !is.null(.x), list( + x$expr, + x$partitions, + order_bys = if (length(x$order_bys) > 0) x$order_bys, + offset_expr = x$offset_expr, + default_expr = x$default_expr, + alias = x$alias + )) + code <- constructive::.cstr_apply(args, fun = "relexpr_window", ...) + constructive::.cstr_repair_attributes( + x, code, ..., + idiomatic_class = c("relational_relexpr_window", "relational_relexpr") + ) +} + +on_load({ + vctrs::s3_register("constructive::.cstr_construct", "relational_relexpr_window") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/count-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/count-rd.R new file mode 100644 index 000000000..3608aa6d9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/count-rd.R @@ -0,0 +1,19 @@ +#' @title Count the observations in each group +#' +#' @description This is a method for the [dplyr::count()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `count()` lets you quickly count the unique values of one or more variables: +#' `df %>% count(a, b)` is roughly equivalent to +#' `df %>% group_by(a, b) %>% summarise(n = n())`. +#' `count()` is paired with `tally()`, a lower-level helper that is equivalent +#' to `df %>% summarise(n = n())`. Supply `wt` to perform weighted counts, +#' switching the summary from `n = n()` to `n = sum(wt)`. +#' +#' @inheritParams dplyr::count +#' @examples +#' library(duckplyr) +#' count(mtcars, am) +#' @seealso [dplyr::count()] +#' @rdname count.duckplyr_df +#' @name count.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/count.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/count.R new file mode 100644 index 000000000..73b8a9700 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/count.R @@ -0,0 +1,95 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname count.duckplyr_df +#' @export +count.duckplyr_df <- function(x, ..., wt = NULL, sort = FALSE, name = NULL, .drop = group_by_drop_default(x)) { + force(x) + + dplyr_local_error_call() + + by <- dplyr_quosures(...) + by <- fix_auto_name(by) + + by_exprs <- unname(map(by, quo_get_expr)) + is_name <- map_lgl(by_exprs, is_symbol) + + # For better error message + if (!is.null(name)) { + check_string(name, call = dplyr_error_call()) + } + + # Passing `name` reliably is surprisingly complicated. + duckplyr_error <- rel_try(list(name = "count", x = x, args = try_list(dots = enquos(...), wt = enquo(wt), sort = sort, .drop = .drop)), + #' @section Fallbacks: + #' There is no DuckDB translation in `count.duckplyr_df()` + #' - with complex expressions in `...`, + #' - with `.drop = FALSE`, + #' - with `sort = TRUE`. + #' + #' These features fall back to [dplyr::count()], see `vignette("fallback")` for details. + "{.code count()} requires columns in {.arg ...}" = !all(is_name), + "{.code count()} only implemented for {.arg .drop} = {.value TRUE}" = !.drop, + "{.code count()} only implemented for {.arg sort} = {.value FALSE}" = sort, + { + rel <- duckdb_rel_from_df(x) + + by_chr <- map_chr(by_exprs, as_string) + name_was_null <- is.null(name) + name <- check_n_name(name, by_chr, call = dplyr_error_call()) + + if (name %in% by_chr) { + cli::cli_abort("Name clash in `count()`") + } + n <- tally_n(x, {{ wt }}) + + groups <- rel_translate_dots(by, x) + aggregates <- list(rel_translate(n, x, alias = name)) + + out_rel <- rel_aggregate(rel, groups, unname(aggregates)) + if (length(groups) > 0) { + sort_cols <- nexprs(names(groups)) + out_rel <- rel_order(out_rel, sort_cols) + } + + out <- duckplyr_reconstruct(out_rel, x) + return(out) + } + ) + + # FIXME: optimize, no need to forward dots + # out <- count(x_df, !!!quos, wt = {{ wt }}, sort = sort, name = name, .drop = .drop) + + # dplyr forward + check_prudence(x, duckplyr_error) + + count <- dplyr$count.data.frame + out <- count(x, ..., wt = {{ wt }}, sort = sort, name = name, .drop = .drop) + return(out) + + # dplyr implementation + dplyr_local_error_call() + + if (!missing(...)) { + out <- group_by(x, ..., .add = TRUE, .drop = .drop) + } else { + out <- x + } + + out <- tally(out, wt = !!enquo(wt), sort = sort, name = name) + + # Ensure grouping is transient + out <- dplyr_reconstruct(out, x) + + out +} + +duckplyr_count <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- count(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/cross_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/cross_join.R new file mode 100644 index 000000000..e83e80feb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/cross_join.R @@ -0,0 +1,71 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +cross_join.duckplyr_df <- function(x, y, ..., copy = FALSE, suffix = c(".x", ".y")) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code cross_join()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + cross_join <- dplyr$cross_join.data.frame + out <- cross_join(x, y, ..., copy = copy, suffix = suffix) + return(out) + + # dplyr implementation + check_dots_empty0(...) + + y <- auto_copy(x, y, copy = copy) + + x_names <- tbl_vars(x) + y_names <- tbl_vars(y) + + # Empty join by with no keys + by <- new_join_by() + + # Particular value isn't too important, as there are no keys to keep/drop + keep <- FALSE + + vars <- join_cols( + x_names = x_names, + y_names = y_names, + by = by, + suffix = suffix, + keep = keep + ) + + x_in <- as_tibble(x, .name_repair = "minimal") + y_in <- as_tibble(y, .name_repair = "minimal") + + x_size <- vec_size(x_in) + y_size <- vec_size(y_in) + + x_out <- set_names(x_in, names(vars$x$out)) + y_out <- set_names(y_in, names(vars$y$out)) + + x_out <- vec_rep_each(x_out, times = y_size) + y_out <- vec_rep(y_out, times = x_size) + + x_out[names(y_out)] <- y_out + + dplyr_reconstruct(x_out, x) +} + +duckplyr_cross_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- cross_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct-rd.R new file mode 100644 index 000000000..a7259ba90 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct-rd.R @@ -0,0 +1,18 @@ +#' @title Keep distinct/unique rows +#' +#' @description This is a method for the [dplyr::distinct()] generic. +#' Keep only unique/distinct rows from a data frame. +#' This is similar to `unique.data.frame()` but considerably faster. +#' +#' @inheritParams dplyr::distinct +#' @examples +#' df <- duckdb_tibble( +#' x = sample(10, 100, rep = TRUE), +#' y = sample(10, 100, rep = TRUE) +#' ) +#' nrow(df) +#' nrow(distinct(df)) +#' @seealso [dplyr::distinct()] +#' @rdname distinct.duckplyr_df +#' @name distinct.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct.R new file mode 100644 index 000000000..1614e6291 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/distinct.R @@ -0,0 +1,106 @@ +# Generated by 02-duckplyr_df-methods.R +utils::globalVariables("___row_number_by") + +#' @rdname distinct.duckplyr_df +#' @export +distinct.duckplyr_df <- function(.data, ..., .keep_all = FALSE) { + dots <- enquos(..., .named = TRUE) + + # Our implementation + duckplyr_error <- rel_try(list(name = "distinct", x = .data, args = try_list(dots = dots, .keep_all = .keep_all)), + "Implemented for all cases?" = FALSE, + { + # FIXME: avoid column duplication in a cleaner way + dupes <- duplicated(names(dots), fromLast = TRUE) + dots <- dots[!dupes] + + rel <- duckdb_rel_from_df(.data) + + oo <- .keep_all || oo_force() + + if (oo) { + # Push row number as separate projection + rel <- oo_prep(rel, force = TRUE) + + exprs <- rel_translate_dots(dots, .data) + all_exprs <- NULL + if (length(exprs) == 0) { + exprs <- imap(set_names(names(.data)), relexpr_reference, rel = NULL) + all_exprs <- exprs + } + + if (.keep_all) { + proj_exprs <- all_exprs %||% imap(set_names(names(.data)), relexpr_reference, rel = NULL) + } else { + proj_exprs <- exprs + } + + proj_exprs <- c(proj_exprs, list( + relexpr_reference("___row_number"), + relexpr_window( + relexpr_function("row_number", list()), + partitions = exprs, + order_bys = list(relexpr_reference("___row_number")), + alias = "___row_number_by" + ) + )) + + rel <- rel_project(rel, unname(proj_exprs)) + + expr_filter <- rel_translate( + quo(`___row_number_by` == 1L), + tibble(`___row_number_by` = 1L) + ) + out_rel <- rel_filter(rel, list(expr_filter)) + + out_rel <- oo_restore_order(out_rel, force = TRUE) + out_rel <- oo_restore_cols(out_rel, extra = "___row_number_by", force = TRUE) + } else { + exprs <- rel_translate_dots(dots, .data) + if (length(exprs) > 0) { + rel <- rel_project(rel, exprs) + } + out_rel <- rel_distinct(rel) + } + + out <- duckplyr_reconstruct(out_rel, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + distinct <- dplyr$distinct.data.frame + out <- distinct(.data, ..., .keep_all = .keep_all) + return(out) + + # dplyr implementation + prep <- distinct_prepare( + .data, + vars = enquos(...), + group_vars = group_vars(.data), + .keep_all = .keep_all, + caller_env = caller_env() + ) + + out <- prep$data + + cols <- dplyr_col_select(out, prep$vars) + loc <- vec_unique_loc(cols) + + out <- dplyr_col_select(out, prep$keep) + dplyr_row_slice(out, loc) +} + +duckplyr_distinct <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- distinct(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/do.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/do.R new file mode 100644 index 000000000..98784ff9c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/do.R @@ -0,0 +1,51 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +do.duckplyr_df <- function(.data, ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code do()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + do <- dplyr$do.data.frame + out <- do(.data, ...) + return(out) + + # dplyr implementation + args <- enquos(...) + named <- named_args(args) + + # Create custom data mask with `.` pronoun + mask <- new_data_mask(new_environment()) + env_bind_do_pronouns(mask, .data) + + if (!named) { + out <- eval_tidy(args[[1]], mask) + if (!inherits(out, "data.frame")) { + msg <- glue("Result must be a data frame, not {fmt_classes(out)}.") + abort(msg) + } + } else { + out <- map(args, function(arg) list(eval_tidy(arg, mask))) + names(out) <- names(args) + out <- tibble::as_tibble(out, .name_repair = "minimal") + } + + out +} + +duckplyr_do <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- do(.data, ...) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dots.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dots.R new file mode 100644 index 000000000..d0bcf967d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dots.R @@ -0,0 +1,15 @@ +fix_auto_name <- function(dots) { + if (is.null(names(dots))) { + dots <- set_names(dots, "") + } + + for (i in seq_along(dots)) { + dot <- dots[[i]] + if (names(dots)[[i]] == "") { + quo_data <- attr(dot, "dplyr:::data") + names(dots)[[i]] <- as_label(quo_data$name) + } + } + + dots +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr-reimplement.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr-reimplement.R new file mode 100644 index 000000000..2df171f02 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr-reimplement.R @@ -0,0 +1,18 @@ +duckplyr_mutate_keep <- function(out, keep, used, names_new, names_groups) { + if (keep == "all") { + return(out) + } + + names <- names(out) + + names_keep <- switch(keep, + used = names(used)[used], + unused = names(used)[!used], + none = character(), + abort("Unknown `keep`.", .internal = TRUE) + ) + + names_out <- intersect(names, c(names_new, names_groups, names_keep)) + + select(out, !!!names_out) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr.R new file mode 100644 index 000000000..0edd8c39a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/dplyr.R @@ -0,0 +1,86 @@ +dplyr <- asNamespace("dplyr") + +across_glue_mask <- dplyr$across_glue_mask +as_across_fn_call <- dplyr$as_across_fn_call +as_group_map_function <- dplyr$as_group_map_function +as_fun_list <- dplyr$as_fun_list +as_join_by <- dplyr$as_join_by +check_filter <- dplyr$check_filter +check_keep <- dplyr$check_keep +check_name <- dplyr$check_name +check_n_name <- dplyr$check_n_name +check_na_matches <- dplyr$check_na_matches +check_string <- dplyr$check_string +check_transmute_args <- dplyr$check_transmute_args +compute_by <- dplyr$compute_by +context_poke <- dplyr$context_poke +count_regroups <- dplyr$count_regroups +cur_column <- dplyr$cur_column +DataMask <- dplyr$DataMask +dplyr_error_call <- dplyr$dplyr_error_call +dplyr_local_error_call <- dplyr$dplyr_local_error_call +dplyr_quosures <- dplyr$dplyr_quosures +eval_relocate <- dplyr$eval_relocate +eval_select_by <- dplyr$eval_select_by +expand_across <- dplyr$expand_across +expand_if_across <- dplyr$expand_if_across +expr_substitute <- dplyr$expr_substitute +get_slice_size <- dplyr$get_slice_size +group_by_drop_default <- dplyr$group_by_drop_default +group_keys0 <- dplyr$group_keys0 +is_cross_by <- dplyr$is_cross_by +join_by_common <- dplyr$join_by_common +join_cols <- dplyr$join_cols +# Need to reimplement mutate_keep() to avoid dplyr_col_select() +# mutate_keep <- dplyr$mutate_keep +mutate_relocate <- dplyr$mutate_relocate +new_dplyr_quosure <- dplyr$new_dplyr_quosure +new_join_by <- dplyr$new_join_by +quo_eval_fns <- dplyr$quo_eval_fns +sample_int <- dplyr$sample_int +some <- dplyr$some +tally_n <- dplyr$tally_n + +# Only for dead legacy code? +add_count_impl <- dplyr$add_count_impl +arrange_rows <- dplyr$arrange_rows +check_frac <- dplyr$check_frac +check_size <- dplyr$check_size +coalesce <- dplyr$coalesce +commas <- dplyr$commas +compact_null <- dplyr$compact_null +distinct_prepare <- dplyr$distinct_prepare +dplyr_col_select <- dplyr$dplyr_col_select +dplyr_local_slice_by_arg <- dplyr$dplyr_local_slice_by_arg +dplyr_new_list <- dplyr$dplyr_new_list +ensure_group_vars <- dplyr$ensure_group_vars +env_bind_do_pronouns <- dplyr$env_bind_do_pronouns +filter_rows <- dplyr$filter_rows +groups <- dplyr$groups +group_nest_impl <- dplyr$group_nest_impl +group_split_impl <- dplyr$group_split_impl +join_cast_common <- dplyr$join_cast_common +join_filter <- dplyr$join_filter +join_mutate <- dplyr$join_mutate +join_rows <- dplyr$join_rows +mutate_cols <- dplyr$mutate_cols +mutate_keep <- dplyr$mutate_keep +named_args <- dplyr$named_args +rethrow_error_join_incompatible_type <- dplyr$rethrow_error_join_incompatible_type +rows_bind <- dplyr$rows_bind +rows_cast_y <- dplyr$rows_cast_y +rows_check_by <- dplyr$rows_check_by +rows_check_contains_by <- dplyr$rows_check_contains_by +rows_check_unique <- dplyr$rows_check_unique +rows_check_x_contains_y <- dplyr$rows_check_x_contains_y +rows_check_y_conflict <- dplyr$rows_check_y_conflict +rows_check_y_unmatched <- dplyr$rows_check_y_unmatched +rows_df_in_place <- dplyr$rows_df_in_place +rowwise_df <- dplyr$rowwise_df +slice_rows <- dplyr$slice_rows +summarise_build <- dplyr$summarise_build +summarise_cols <- dplyr$summarise_cols +summarise_deprecate_variable_size <- dplyr$summarise_deprecate_variable_size +the <- dplyr$the +tick_if_needed <- dplyr$tick_if_needed +warn_join_cross_by <- dplyr$warn_join_cross_by diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckdb.R new file mode 100644 index 000000000..1aadc3b50 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckdb.R @@ -0,0 +1 @@ +duckdb <- asNamespace("duckdb") diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-across.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-across.R new file mode 100644 index 000000000..c5c7f3da2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-across.R @@ -0,0 +1,232 @@ +# A simplified version of functions in dplyr's across.R + +duckplyr_expand_across <- function(data, quo) { + stopifnot(is.character(data)) + + quo_data <- attr(quo, "dplyr:::data") + if (!quo_is_call(quo, "across", ns = c("", "dplyr")) || quo_data$is_named) { + return(NULL) + } + + # Expand dots in lexical env + env <- quo_get_env(quo) + expr <- match.call( + definition = dplyr::across, + call = quo_get_expr(quo), + expand.dots = FALSE, + envir = env + ) + + # Abort expansion if there are any expression supplied because dots + # must be evaluated once per group in the data mask. Expanding the + # `across()` call would lead to either `n_group * n_col` evaluations + # if dots are delayed or only 1 evaluation if they are eagerly + # evaluated. + if (!is_null(expr$...)) { + return(NULL) + } + + if (".unpack" %in% names(expr)) { + # In dplyr this evaluates in the mask to reproduce the `mutate()` or + # `summarise()` context. We don't have a mask here but it's probably fine in + # almost all cases. + unpack <- eval_tidy(expr$.unpack, env = env) + } else { + unpack <- FALSE + } + + # Abort expansion if unpacking as expansion makes named expressions and we + # need the expressions to remain unnamed + if (!is_false(unpack)) { + return(NULL) + } + + # Differentiate between missing and null (`match.call()` doesn't + # expand default argument) + if (!(".cols" %in% names(expr))) { + # This is deprecated, let dplyr warn + return(NULL) + } + + if (!(".fns" %in% names(expr))) { + # To be deprecated, let dplyr deal with this + return(NULL) + } + + cols <- as_quosure(expr$.cols, env) + + fns <- as_quosure(expr$.fns, env) + fns <- quo_eval_fns(fns, mask = env, error_call = error_call) + + # In dplyr this evaluates in the mask to reproduce the `mutate()` or + # `summarise()` context. We don't have a mask here but it's probably fine in + # almost all cases. + names <- eval_tidy(expr$.names, env = env) + + setup <- duckplyr_across_setup( + data, + cols, + fns = fns, + names = names, + .caller_env = env, + error_call = error_call + ) + + vars <- setup$vars + + # Empty expansion + if (length(vars) == 0L) { + return(NULL) + } + + fns <- setup$fns + names <- setup$names %||% vars + + n_vars <- length(vars) + n_fns <- length(fns) + + seq_vars <- seq_len(n_vars) + seq_fns <- seq_len(n_fns) + + exprs <- new_list(n_vars * n_fns, names = names) + + k <- 1L + for (i in seq_vars) { + var <- vars[[i]] + + for (j in seq_fns) { + fn_expr <- fn_to_expr(fns[[j]], env) + # Note: `mask` isn't actually used inside this helper + fn_call <- as_across_fn_call(fn_expr, var, env, mask = env) + + # We can't translate spliced functions: + if (is.function(quo_get_expr(fn_call)[[1]])) { + return(NULL) + } + + name <- names[[k]] + + exprs[[k]] <- new_dplyr_quosure( + fn_call, + name = name, + is_named = TRUE, + index = c(quo_data$index, k), + column = var + ) + + k <- k + 1L + } + } + + exprs +} + +duckplyr_across_setup <- function(data, + cols, + fns, + names, + .caller_env, + error_call = caller_env()) { + data <- set_names(seq_along(data), data) + + vars <- tidyselect::eval_select( + cols, + data = data, + allow_predicates = FALSE, + error_call = error_call + ) + names_vars <- names(vars) + vars <- names(data)[vars] + + names_fns <- names(fns) + + # apply `.names` smart default + if (is.function(fns)) { + names <- names %||% "{.col}" + fns <- list("1" = fns) + } else { + names <- names %||% "{.col}_{.fn}" + } + + if (!is.list(fns)) { + abort("Expected a list.", .internal = TRUE) + } + + # make sure fns has names, use number to replace unnamed + if (is.null(names(fns))) { + names_fns <- seq_along(fns) + } else { + names_fns <- names(fns) + empties <- which(names_fns == "") + if (length(empties)) { + names_fns[empties] <- empties + } + } + + glue_mask <- across_glue_mask(.caller_env, + .col = rep(names_vars, each = length(fns)), + .fn = rep(names_fns , length(vars)) + ) + names <- vec_as_names( + glue(names, .envir = glue_mask), + repair = "check_unique", + call = error_call + ) + + list( + vars = vars, + fns = fns, + names = names + ) +} + +fn_to_expr <- function(fn, env) { + fn_env <- environment(fn) + if (!is_namespace(fn_env)) { + return(fn) + } + + # This is an environment that maps hashes to function names + ns_exports_lookup <- get_ns_exports_lookup(fn_env) + + # Can we find the function among the exports in the namespace? + fun_name <- ns_exports_lookup[[hash(fn)]] + if (is.null(fun_name)) { + return(fn) + } + + # Triple-check: Does the expression actually evaluate to fn? + ns_name <- getNamespaceName(fn_env) + out <- call2("::", sym(ns_name), sym(fun_name)) + if (!identical(eval(out, env), fn)) { + return(fn) + } + + out +} + +# Memoize get_ns_exports_lookup() to avoid recomputing the hash of +# every function in every namespace every time +on_load({ + env <- environment() + assign("get_ns_exports_lookup", memoise::memoise(get_ns_exports_lookup), envir = env) +}) + +get_ns_exports_lookup <- function(ns) { + names <- getNamespaceExports(ns) + objs <- mget(names, ns, ifnotfound = list(NULL)) + funs <- objs[map_lgl(objs, is.function)] + + hashes <- map_chr(funs, hash) + # Reverse, return as environment + new_environment(set_names(as.list(names(hashes)), hashes)) +} + +test_duckplyr_expand_across <- function(data, expr) { + quo <- new_dplyr_quosure(enquo(expr), is_named = FALSE, index = 1L) + out <- duckplyr_expand_across(data, quo) + if (is.null(out)) { + return(NULL) + } + call2(rlang::expr(tibble), !!!map(out, quo_get_expr)) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-package.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-package.R new file mode 100644 index 000000000..5e2db992e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr-package.R @@ -0,0 +1,224 @@ +#' @keywords internal +"_PACKAGE" + +# @rawNamespace import(vctrs, except = data_frame) +# an alternative for importing nearly everything from vctrs +# https://github.com/tidyverse/dplyr/blob/16b472fb2afc50a87502c2b4ed803e2f5f82b9d6/R/dplyr.R#L7 +# +# Can't use blanket cli import, imports must align with dplyr's imports +# (except we can import all of dplyr) +# +#' @import rlang +## usethis namespace: start +#' @importFrom collections dict +#' @importFrom collections queue +#' @importFrom dplyr auto_copy +#' @importFrom dplyr do +#' @importFrom dplyr dplyr_col_modify +#' @importFrom dplyr dplyr_reconstruct +#' @importFrom dplyr dplyr_row_slice +#' @importFrom dplyr funs +#' @importFrom dplyr funs_ +#' @importFrom dplyr group_by +#' @importFrom dplyr group_by_prepare +#' @importFrom dplyr group_cols +#' @importFrom dplyr group_data +#' @importFrom dplyr group_indices +#' @importFrom dplyr group_keys +#' @importFrom dplyr group_map +#' @importFrom dplyr group_modify +#' @importFrom dplyr group_nest +#' @importFrom dplyr group_rows +#' @importFrom dplyr group_size +#' @importFrom dplyr group_split +#' @importFrom dplyr group_trim +#' @importFrom dplyr group_vars +#' @importFrom dplyr group_walk +#' @importFrom dplyr grouped_df +#' @importFrom dplyr groups +#' @importFrom dplyr if_else +#' @importFrom dplyr is_grouped_df +#' @importFrom dplyr mutate_all +#' @importFrom dplyr n_groups +#' @importFrom dplyr new_grouped_df +#' @importFrom dplyr new_rowwise_df +#' @importFrom dplyr rename_all +#' @importFrom dplyr rename_at +#' @importFrom dplyr rename_if +#' @importFrom dplyr rowwise +#' @importFrom dplyr same_src +#' @importFrom dplyr select_all +#' @importFrom dplyr select_at +#' @importFrom dplyr select_if +#' @importFrom dplyr summarise_all +#' @importFrom dplyr summarise_at +#' @importFrom dplyr vars +#' @importFrom glue glue +#' @importFrom glue glue_collapse +#' @importFrom lifecycle deprecated +#' @importFrom tibble as_tibble +#' @importFrom tibble deframe +#' @importFrom tibble is_tibble +#' @importFrom tibble new_tibble +#' @importFrom tibble tibble +#' @importFrom tidyselect everything +#' @importFrom utils head +#' @importFrom vctrs new_data_frame +#' @importFrom vctrs new_date +#' @importFrom vctrs new_list_of +#' @importFrom vctrs new_rcrd +#' @importFrom vctrs new_vctr +#' @importFrom vctrs obj_check_list +#' @importFrom vctrs unspecified +#' @importFrom vctrs vec_as_names +#' @importFrom vctrs vec_assign +#' @importFrom vctrs vec_c +#' @importFrom vctrs vec_cast +#' @importFrom vctrs vec_cast_common +#' @importFrom vctrs vec_cbind +#' @importFrom vctrs vec_check_size +#' @importFrom vctrs vec_count +#' @importFrom vctrs vec_data +#' @importFrom vctrs vec_detect_complete +#' @importFrom vctrs vec_in +#' @importFrom vctrs vec_init +#' @importFrom vctrs vec_match +#' @importFrom vctrs vec_ptype +#' @importFrom vctrs vec_ptype_finalise +#' @importFrom vctrs vec_ptype_full +#' @importFrom vctrs vec_ptype2 +#' @importFrom vctrs vec_rbind +#' @importFrom vctrs vec_recycle_common +#' @importFrom vctrs vec_rep +#' @importFrom vctrs vec_rep_each +#' @importFrom vctrs vec_set_difference +#' @importFrom vctrs vec_set_intersect +#' @importFrom vctrs vec_set_symmetric_difference +#' @importFrom vctrs vec_set_union +#' @importFrom vctrs vec_size +#' @importFrom vctrs vec_slice +#' @importFrom vctrs vec_split +#' @importFrom vctrs vec_unique_loc +## usethis namespace: end +NULL + +# Enable to use compiled code again +# @useDynLib duckplyr, .registration = TRUE +NULL + +#' @importFrom magrittr %>% +#' @export +magrittr::"%>%" + +#' @importFrom dplyr tibble +#' @importFrom dplyr across +#' @importFrom dplyr add_count +#' @importFrom dplyr add_tally +#' @importFrom dplyr all_equal +#' @importFrom dplyr anti_join +#' @importFrom dplyr arrange +#' @importFrom dplyr between +#' @importFrom dplyr bind_cols +#' @importFrom dplyr bind_rows +#' @importFrom dplyr c_across +#' @importFrom dplyr case_match +#' @importFrom dplyr case_when +#' @importFrom dplyr coalesce +#' @importFrom dplyr collapse +#' @importFrom dplyr collect +#' @importFrom dplyr compute +#' @importFrom dplyr consecutive_id +#' @importFrom dplyr count +#' @importFrom dplyr cross_join +#' @importFrom dplyr cumall +#' @importFrom dplyr cumany +#' @importFrom dplyr cume_dist +#' @importFrom dplyr cummean +#' @importFrom dplyr cur_column +#' @importFrom dplyr cur_group +#' @importFrom dplyr cur_group_id +#' @importFrom dplyr cur_group_rows +#' @importFrom dplyr dense_rank +#' @importFrom dplyr desc +#' @importFrom dplyr distinct +#' @importFrom dplyr explain +#' @importFrom dplyr filter +#' @importFrom dplyr first +#' @importFrom dplyr full_join +#' @importFrom dplyr if_any +#' @importFrom dplyr if_else +#' @importFrom dplyr inner_join +#' @importFrom dplyr intersect +#' @importFrom dplyr join_by +#' @importFrom dplyr lag +#' @importFrom dplyr last +#' @importFrom dplyr lead +#' @importFrom dplyr left_join +#' @importFrom dplyr min_rank +#' @importFrom dplyr mutate +#' @importFrom dplyr n +#' @importFrom dplyr n_distinct +#' @importFrom dplyr na_if +#' @importFrom dplyr near +#' @importFrom dplyr nest_by +#' @importFrom dplyr nest_join +#' @importFrom dplyr nth +#' @importFrom dplyr ntile +#' @importFrom dplyr order_by +#' @importFrom dplyr percent_rank +#' @importFrom dplyr pick +#' @importFrom dplyr pull +#' @importFrom dplyr reframe +#' @importFrom dplyr relocate +#' @importFrom dplyr rename +#' @importFrom dplyr rename_with +#' @importFrom dplyr right_join +#' @importFrom dplyr row_number +#' @importFrom dplyr rows_append +#' @importFrom dplyr rows_delete +#' @importFrom dplyr rows_insert +#' @importFrom dplyr rows_patch +#' @importFrom dplyr rows_update +#' @importFrom dplyr rows_upsert +#' @importFrom dplyr sample_frac +#' @importFrom dplyr sample_n +#' @importFrom dplyr select +#' @importFrom dplyr semi_join +#' @importFrom dplyr setdiff +#' @importFrom dplyr setequal +#' @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 summarise +#' @importFrom dplyr summarize +#' @importFrom dplyr symdiff +#' @importFrom dplyr tally +#' @importFrom dplyr tbl_vars +#' @importFrom dplyr top_n +#' @importFrom dplyr transmute +#' @importFrom dplyr ungroup +#' @importFrom dplyr union +#' @importFrom dplyr union_all +#' @importFrom dplyr with_groups +#' @importFrom dplyr with_order +#' @importFrom rlang .data +#' @importFrom tibble add_row +#' @importFrom tibble as_tibble +#' @importFrom tibble tribble +#' @importFrom tidyselect all_of +#' @importFrom tidyselect any_of +#' @importFrom tidyselect contains +#' @importFrom tidyselect everything +#' @importFrom tidyselect last_col +#' @importFrom tidyselect matches +#' @importFrom tidyselect num_range +#' @importFrom tidyselect one_of +#' @importFrom tidyselect starts_with +#' @importFrom tidyselect where +NULL + +# Only in this package +dplyr_mode <- FALSE diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_df.R new file mode 100644 index 000000000..5cb735e40 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_df.R @@ -0,0 +1,142 @@ +#' @param prudence Only adds the class, does not recreate the relation object! +#' @noRd +new_duckdb_tibble <- function(x, class = NULL, prudence = "lavish", adjust_prudence = FALSE, error_call = caller_env()) { + if (is.null(class)) { + class <- c("tbl_df", "tbl", "data.frame") + } else { + class <- setdiff(class, c("prudent_duckplyr_df", "duckplyr_df")) + } + + if (!inherits(x, "duckplyr_df")) { + if (anyNA(names(x)) || any(names(x) == "")) { + cli::cli_abort("Missing or empty names not allowed.", call = error_call) + } + } + + prudence_parsed <- prudence_parse(prudence, error_call) + + # Before setting class, needs prudence_parsed + if (adjust_prudence) { + rel <- duckdb_rel_from_df(x, call = error_call) + + # Copied from rel_to_df.duckdb_relation(), to avoid recursion + x <- duckdb$rel_to_altrep( + rel, + n_rows = prudence_parsed$n_rows, + n_cells = prudence_parsed$n_cells + ) + } + + class(x) <- c( + if (!identical(prudence_parsed$prudence, "lavish")) "prudent_duckplyr_df", + "duckplyr_df", + class + ) + + prudence_attr <- c( + rows = if (is.finite(prudence_parsed$n_rows)) prudence_parsed$n_rows, + cells = if (is.finite(prudence_parsed$n_cells)) prudence_parsed$n_cells + ) + attr(x, "prudence") <- prudence_attr + + x +} + +is_prudent_duckplyr_df <- function(x) { + inherits(x, "prudent_duckplyr_df") +} + +prudence_parse <- function(prudence, remote, call = caller_env()) { + n_rows <- Inf + n_cells <- Inf + + if (is.numeric(prudence)) { + if (is.null(names(prudence))) { + cli::cli_abort("{.arg prudence} must have names if it is a named vector.", call = call) + } + extra_names <- setdiff(names(prudence), c("rows", "cells")) + if (length(extra_names) > 0) { + cli::cli_abort("Unknown name in {.arg prudence}: {extra_names[[1]]}", call = call) + } + + if ("rows" %in% names(prudence)) { + n_rows <- prudence[["rows"]] + if (is.na(n_rows) || n_rows < 0) { + cli::cli_abort("The {.val rows} component of {.arg prudence} must be a non-negative integer", call = call) + } + } + if ("cells" %in% names(prudence)) { + n_cells <- prudence[["cells"]] + if (is.na(n_cells) || n_cells < 0) { + cli::cli_abort("The {.val cells} component of {.arg prudence} must be a non-negative integer", call = call) + } + } + allow_materialization <- is.finite(n_rows) || is.finite(n_cells) + prudence <- "stingy" + } else if (!is.character(prudence)) { + cli::cli_abort("{.arg prudence} must be an unnamed character vector or a named numeric vector", call = call) + } else { + if (identical(prudence, "frugal")) { + lifecycle::deprecate_warn("1.0.0", + I('Use `prudence = "stingy"` instead, `prudence = "frugal"`'), + always = TRUE, + env = call + ) + prudence <- "stingy" + } + # Can't change second argument to arg_match() here + prudence <- arg_match(prudence, c("lavish", "stingy", "thrifty"), error_call = call) + + allow_materialization <- !identical(prudence, "stingy") + if (!allow_materialization) { + n_cells <- 0 + } else if (identical(prudence, "thrifty")) { + if (isTRUE(remote)) { + n_cells <- 1e3 + } else { + n_cells <- 1e6 + } + } + } + + list( + prudence = prudence, + n_rows = n_rows, + n_cells = n_cells + ) +} + +get_prudence_duckplyr_df <- function(x) { + if (!is_duckdb_tibble(x)) { + # Avoid function calls for speed + prudence <- duckplyr_the$default_df_prudence + if (is.null(prudence)) { + prudence <- "lavish" + } + + return(prudence) + } + + if (!is_prudent_duckplyr_df(x)) { + return("lavish") + } + + prudence <- attr(x, "prudence") + if (is.null(prudence)) { + return("stingy") + } + + if (identical(prudence, c(cells = 1e6))) { + return("thrifty") + } + + prudence +} + +duckplyr_reconstruct <- function(rel, template) { + out <- rel_to_df( + rel, + prudence = get_prudence_duckplyr_df(template) + ) + dplyr_reconstruct(out, template) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_execute.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_execute.R new file mode 100644 index 000000000..8f0e38461 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/duckplyr_execute.R @@ -0,0 +1,25 @@ +#' Execute a statement for the default connection +#' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' The \pkg{duckplyr} package relies on a DBI connection +#' to an in-memory database. +#' The `duckplyr_execute()` function allows running SQL statements +#' with this connection to, e.g., set up credentials +#' or attach other databases. +#' See +#' for more information on the configuration options. +#' +#' @param sql The statement to run. +#' @return The return value of the [DBI::dbExecute()] call, invisibly. +#' @keywords internal +#' @export +#' @examples +#' duckplyr_execute("SET threads TO 2") +duckplyr_execute <- function(sql) { + lifecycle::deprecate_soft("1.0.0", "duckplyr_execute()", "db_exec()") + + con <- get_default_duckdb_connection() + invisible(DBI::dbExecute(con, sql)) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/ducktbl.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/ducktbl.R new file mode 100644 index 000000000..74123bc02 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/ducktbl.R @@ -0,0 +1,159 @@ +#' duckplyr data frames +#' +#' @description +#' Data frames backed by duckplyr have a special class, `"duckplyr_df"`, +#' in addition to the default classes. +#' This ensures that dplyr methods are dispatched correctly. +#' For such objects, +#' dplyr verbs such as [mutate()], [select()] or [filter()] will use DuckDB. +#' +#' `duckdb_tibble()` works like [tibble()]. +#' +#' @param ... For `duckdb_tibble()`, passed on to [tibble()]. +#' For `as_duckdb_tibble()`, passed on to methods. +#' @param prudence,.prudence Memory protection, controls if DuckDB may convert +#' intermediate results in DuckDB-managed memory to data frames in R memory. +#' +#' - `"lavish"`: regardless of size, +#' - `"stingy"`: never, +#' - `"thrifty"`: up to a maximum size of 1 million cells. +#' +#' The default is `"lavish"` for `duckdb_tibble()` and `as_duckdb_tibble()`, +#' and may be different for other functions. +#' See `vignette("prudence")` for more information. +#' +#' @section Fine-tuning prudence: +#' `r lifecycle::badge("experimental")` +#' +#' The `prudence` argument can also be a named numeric vector +#' with at least one of `cells` or `rows` +#' to limit the cells (values) and rows in the resulting data frame +#' after automatic materialization. +#' If both limits are specified, both are enforced. +#' The equivalent of `"thrifty"` is `c(cells = 1e6)`. +#' +#' @return For `duckdb_tibble()` and `as_duckdb_tibble()`, an object with the following classes: +#' - `"prudent_duckplyr_df"` if `prudence` is not `"lavish"` +#' - `"duckplyr_df"` +#' - Classes of a [tibble] +#' +#' @examples +#' x <- duckdb_tibble(a = 1) +#' x +#' +#' library(dplyr) +#' x %>% +#' mutate(b = 2) +#' +#' x$a +#' +#' y <- duckdb_tibble(a = 1, .prudence = "stingy") +#' y +#' try(length(y$a)) +#' length(collect(y)$a) +#' @export +duckdb_tibble <- function(..., .prudence = c("lavish", "thrifty", "stingy")) { + out <- tibble::tibble(...) + + new_duckdb_tibble(out, class(out), prudence = .prudence, adjust_prudence = TRUE) +} + +#' as_duckdb_tibble +#' +#' `as_duckdb_tibble()` converts a data frame or a dplyr lazy table to a duckplyr data frame. +#' This is a generic function that can be overridden for custom classes. +#' +#' @param x The object to convert or to test. +#' @rdname duckdb_tibble +#' @export +as_duckdb_tibble <- function(x, ..., prudence = c("lavish", "thrifty", "stingy")) { + # Handle the prudence arg in the generic, only the other args will be dispatched + as_duckdb_tibble <- function(x, ...) { + UseMethod("as_duckdb_tibble") + } + + out <- as_duckdb_tibble(x, ...) + new_duckdb_tibble(out, class(out), prudence = prudence, adjust_prudence = TRUE) +} + +#' @export +as_duckdb_tibble.tbl_duckdb_connection <- function(x, ...) { + check_dots_empty() + + con <- dbplyr::remote_con(x) + sql <- dbplyr::remote_query(x) + + # Start restrictive to avoid accidental materialization + read_sql_duckdb(sql, prudence = "stingy", con = con) +} + +#' @export +as_duckdb_tibble.duckplyr_df <- function(x, ...) { + check_dots_empty() + x +} + +#' @export +as_duckdb_tibble.data.frame <- function(x, ...) { + check_dots_empty() + + # Only if not materialized yet + if (is.null(duckdb$rel_from_altrep_df(x, strict = FALSE, allow_materialized = FALSE))) { + x <- as_tibble(x) + } + + new_duckdb_tibble(x) +} + +#' @export +as_duckdb_tibble.default <- function(x, ...) { + check_dots_empty() + + # - as.data.frame() call for good measure and perhaps https://github.com/tidyverse/tibble/issues/1556 + # - as_tibble() to remove row names + # Could call as_duckdb_tibble(as.data.frame(x)) here, but that would be slower + new_duckdb_tibble(as_tibble(as.data.frame(x))) +} + +#' @export +as_duckdb_tibble.grouped_df <- function(x, ...) { + check_dots_empty() + + cli::cli_abort(c( + "{.pkg duckplyr} does not support {.code group_by()}.", + i = "Use {.arg .by} instead.", + i = "To proceed with {.pkg dplyr}, use {.code as_tibble()} or {.code as.data.frame()}." + )) +} + +#' @export +as_duckdb_tibble.rowwise_df <- function(x, ...) { + check_dots_empty() + + cli::cli_abort(c( + "{.pkg duckplyr} does not support {.code rowwise()}.", + i = "To proceed with {.pkg dplyr}, use {.code as_tibble()} or {.code as.data.frame()}." + )) +} + +#' @export +as_duckdb_tibble.spec_tbl_df <- function(x, ...) { + check_dots_empty() + + cli::cli_abort(c( + "The input is data read by {.pkg readr}, and {.pkg duckplyr} supports reading CSV files directly.", + i = "Use {.code read_csv_duckdb()} to read with the built-in reader.", + i = "To proceed with the data as read by {.pkg readr}, use {.code as_tibble()} before {.code as_duckdb_tibble()}." + )) +} + +#' is_duckdb_tibble +#' +#' `is_duckdb_tibble()` returns `TRUE` if `x` is a duckplyr data frame. +#' +#' @return For `is_duckdb_tibble()`, a scalar logical. +#' @rdname duckdb_tibble +#' @export +is_duckdb_tibble <- function(x) { + inherits(x, "duckplyr_df") +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/exec.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/exec.R new file mode 100644 index 000000000..14e0b590c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/exec.R @@ -0,0 +1,36 @@ +#' Execute a statement for the default connection +#' +#' The \pkg{duckplyr} package relies on a DBI connection +#' to an in-memory database. +#' The `db_exec()` function allows running SQL statements +#' with side effects on this connection. +#' It can be used to execute statements that start with +#' `PRAGMA`, `SET`, or `ATTACH` +#' to, e.g., set up credentials, change configuration options, +#' or attach other databases. +#' See +#' for more information on the configuration options, +#' and +#' for attaching databases. +#' +#' @seealso [read_sql_duckdb()] +#' +#' @param sql The statement to run. +#' @inheritParams read_sql_duckdb +#' @return The return value of the [DBI::dbExecute()] call, invisibly. +#' @export +#' @examples +#' db_exec("SET threads TO 2") +db_exec <- function(sql, ..., con = NULL) { + check_dots_empty() + + if (!is_string(sql)) { + cli::cli_abort("{.arg sql} must be a string.") + } + + if (is.null(con)) { + con <- get_default_duckdb_connection() + } + + invisible(DBI::dbExecute(con, sql)) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain-rd.R new file mode 100644 index 000000000..38cd9838f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain-rd.R @@ -0,0 +1,18 @@ +#' @title Explain details of a tbl +#' +#' @description This is a method for the [dplyr::explain()] generic. +#' This is a generic function which gives more details about an object +#' than `print()`, and is more focused on human readable output than `str()`. +#' +#' @inheritParams dplyr::explain +#' +#' @return The input, invisibly. +#' @examples +#' library(duckplyr) +#' df <- duckdb_tibble(x = c(1, 2)) +#' df <- mutate(df, y = 2) +#' explain(df) +#' @seealso [dplyr::explain()] +#' @rdname explain.duckplyr_df +#' @name explain.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain.R new file mode 100644 index 000000000..5b8515c3c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/explain.R @@ -0,0 +1,27 @@ +#' @rdname explain.duckplyr_df +#' @export +explain.duckplyr_df <- function(x, ...) { + duckplyr_error <- rel_try(list(name = "explain", x = x), + "No restrictions" = FALSE, + { + rel <- duckdb_rel_from_df(x) + rel_explain(rel) + return(invisible(x)) + } + ) + + writeLines("Can't convert to relational, fallback implementation will be used.") + invisible(x) +} + +duckplyr_explain <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- explain(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + invisible(out) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/fallback.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/fallback.R new file mode 100644 index 000000000..6c195b7f1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/fallback.R @@ -0,0 +1,467 @@ +#' Fallback to dplyr +#' +#' @description +#' The \pkg{duckplyr} package aims at providing +#' a fully compatible drop-in replacement for \pkg{dplyr}. +#' To achieve this, only a carefully selected subset of \pkg{dplyr}'s operations, +#' R functions, and R data types are implemented. +#' Whenever a request cannot be handled by DuckDB, +#' \pkg{duckplyr} falls back to \pkg{dplyr}. +#' See `vignette("fallback"`)` for details. +#' +#' To assist future development, the fallback situations can be logged +#' to the console or to a local file and uploaded for analysis. +#' By default, \pkg{duckplyr} will not log or upload anything. +#' The functions and environment variables on this page control the process. +#' +#' @details +#' Logging is on by default, but can be turned off. +#' Uploading is opt-in. +#' +#' The following environment variables control the logging and uploading: +#' +#' - `DUCKPLYR_FALLBACK_INFO` controls human-friendly alerts +#' for fallback events. +#' If `TRUE`, a message is printed when a fallback to dplyr occurs +#' because DuckDB cannot handle a request. +#' These messages are never logged. +#' +#' - `DUCKPLYR_FALLBACK_COLLECT` controls logging, set it +#' to 1 or greater to enable logging. +#' If the value is 0, logging is disabled. +#' Future versions of \pkg{duckplyr} may start logging additional data +#' and thus require a higher value to enable logging. +#' Set to 99 to enable logging for all future versions. +#' Use [usethis::edit_r_environ()] to edit the environment file. +#' +#' - `DUCKPLYR_FALLBACK_AUTOUPLOAD` controls uploading, set it +#' to 1 or greater to enable uploading. +#' If the value is 0, uploading is disabled. +#' Currently, uploading is active if the value is 1 or greater. +#' Future versions of \pkg{duckplyr} may start logging additional data +#' and thus require a higher value to enable uploading. +#' Set to 99 to enable uploading for all future versions. +#' Use [usethis::edit_r_environ()] to edit the environment file. +#' +#' - `DUCKPLYR_FALLBACK_LOG_DIR` controls the location of the logs. +#' It must point to a directory (existing or not) where the logs will be written. +#' By default, logs are written to a directory in the user's cache directory +#' as returned by `tools::R_user_dir("duckplyr", "cache")`. +#' +#' - `DUCKPLYR_FALLBACK_VERBOSE` controls printing of log data, set it +#' to `TRUE` or `FALSE` to enable or disable printing. +#' If the value is `TRUE`, a message is printed to the console +#' for each fallback situation. +#' This setting is only relevant if logging is enabled, +#' and mostly useful for \pkg{duckplyr}'s internal tests. +#' +#' All code related to fallback logging and uploading is in the +#' [`fallback.R`](https://github.com/tidyverse/duckplyr/blob/main/R/fallback.R) and +#' [`telemetry.R`](https://github.com/tidyverse/duckplyr/blob/main/R/telemetry.R) files. +#' +#' @name fallback +#' @examples +#' fallback_sitrep() +NULL + +#' fallback_sitrep +#' +#' `fallback_sitrep()` prints the current settings for fallback printing, logging, +#' and uploading, the number of reports ready for upload, and the location of the logs. +#' @rdname fallback +#' @export +fallback_sitrep <- function() { + fallback_logging <- tel_fallback_logging() + fallback_info <- (Sys.getenv("DUCKPLYR_FALLBACK_INFO") == TRUE) + fallback_autoupload <- tel_fallback_autoupload() + fallback_log_dir <- tel_fallback_log_dir() + fallback_logs <- tel_fallback_logs() + + msg <- c( + fallback_txt_header(), + # + if (fallback_info) { + c("v" = "Fallback printing is enabled.") + } else { + c("x" = "Fallback printing is disabled.") + }, + if (isTRUE(fallback_logging)) { + c( + "v" = "Fallback logging is enabled.", + if (is.null(attr(fallback_logging, "val"))) { + c("i" = "Fallback logging is not controlled, see {.help duckplyr::fallback}.") + }, + "i" = "Logs are written to {.file {fallback_log_dir}}." + ) + } else { + c("x" = "Fallback logging is disabled.") + }, + # + fallback_txt_autoupload(fallback_autoupload), + # + if (isTRUE(fallback_logging)) { + fallback_txt_sitrep_logs(fallback_logs) + }, + # + fallback_txt_help(), + # + NULL + ) + + cli::cli_inform(msg) +} + +fallback_txt_header <- function() { + "The {.pkg duckplyr} package is configured to fall back to {.pkg dplyr} when it encounters an incompatibility. Fallback events can be collected and uploaded for analysis to guide future development. By default, data will be collected but no data will be uploaded." +} + +fallback_txt_autoupload <- function(fallback_autoupload) { + if (is.na(fallback_autoupload)) { + c("i" = "Automatic fallback uploading is not controlled and therefore disabled, see {.help duckplyr::fallback}.") + } else if (fallback_autoupload) { + c("v" = "Automatic fallback uploading is enabled.") + } else { + c("x" = "Automatic fallback uploading is disabled.") + } +} + +fallback_txt_sitrep_logs <- function(fallback_logs) { + if (length(fallback_logs) > 0) { + c( + "v" = "Number of reports ready for upload: {.strong {length(fallback_logs)}}.", + ">" = "Review with {.run duckplyr::fallback_review()}, upload with {.run duckplyr::fallback_upload()}." + ) + } else { + c("i" = "No reports ready for upload.") + } +} + +fallback_txt_run_sitrep <- function() { + c( + ">" = "Run {.run duckplyr::fallback_sitrep()} to review the current settings." + ) +} + +fallback_txt_help <- function() { + c( + "i" = "See {.help duckplyr::fallback_config} for details." + ) +} + +#' fallback_config +#' +#' `fallback_config()` configures the current settings for fallback printing, +#' logging, and uploading. +#' Only settings that do not affect computation results can be configured, +#' this is by design. +#' The configuration is stored in a file under `tools::R_user_dir("duckplyr", "config")` . +#' When the \pkg{duckplyr} package is loaded, the configuration is read from this file, +#' and the corresponding environment variables are set. +#' +#' @inheritParams rlang::args_dots_empty +#' @param reset_all Set to `TRUE` to reset all settings to their defaults. +#' The R session must be restarted for the changes to take effect. +#' @param info Set to `TRUE` to enable fallback printing. +#' @param logging Set to `FALSE` to disable fallback logging, +#' set to `TRUE` to explicitly enable it. +#' @param autoupload Set to `TRUE` to enable automatic fallback uploading, +#' set to `FALSE` to disable it. +#' @param log_dir Set the location of the logs in the file system. +#' The directory will be created if it does not exist. +#' @param verbose Set to `TRUE` to enable verbose logging. +#' @rdname fallback +#' @export +fallback_config <- function( + ..., + reset_all = FALSE, + info = NULL, + logging = NULL, + autoupload = NULL, + log_dir = NULL, + verbose = NULL +) { + check_dots_empty() + + if (isTRUE(reset_all)) { + config <- list() + } else { + config <- fallback_config_read() + } + + if (!is.null(logging)) { + if (isTRUE(logging)) { + logging <- 1 + } else { + logging <- 0 + } + } + + if (!is.null(autoupload)) { + if (isTRUE(autoupload)) { + autoupload <- 1 + } else { + autoupload <- 0 + } + } + + config <- fallback_config_set(config, info, "info") + config <- fallback_config_set(config, logging, "logging") + config <- fallback_config_set(config, autoupload, "autoupload") + config <- fallback_config_set(config, log_dir, "log_dir") + config <- fallback_config_set(config, verbose, "verbose") + + fallback_config_write(config) + fallback_config_apply(config) + + if (isTRUE(reset_all)) { + cli::cli_alert_info("Restart the R session to reset all values to their defaults.") + } +} + +fallback_config_read <- function() { + config_file <- fallback_config_path() + + if (!file.exists(config_file)) { + return(list()) + } + + tryCatch( + { + as.list(read.dcf(config_file, all = TRUE)) + }, + error = function(e) { + rlang::cnd_signal(rlang::message_cnd(message = "Error reading duckplyr, fallback configuration, deleting file.", parent = e)) + unlink(config_file) + list() + } + ) +} + +fallback_config_write <- function(config) { + config_path <- fallback_config_path() + + if (length(config) == 0) { + unlink(config_path, force = TRUE) + } else { + write.dcf(config, config_path) + } +} + +fallback_config_set <- function(config, value, name) { + if (!is.null(value)) { + config[[name]] <- value + } + config +} + +fallback_config_apply <- function(config) { + if (!is.null(config$info)) { + Sys.setenv(DUCKPLYR_FALLBACK_INFO = config$info) + } + if (!is.null(config$logging)) { + Sys.setenv(DUCKPLYR_FALLBACK_LOGGING = config$logging) + } + if (!is.null(config$autoupload)) { + Sys.setenv(DUCKPLYR_FALLBACK_AUTOUPLOAD = config$autoupload) + } + if (!is.null(config$log_dir)) { + Sys.setenv(DUCKPLYR_FALLBACK_LOG_DIR = config$log_dir) + } + if (!is.null(config$verbose)) { + Sys.setenv(DUCKPLYR_FALLBACK_VERBOSE = config$verbose) + } +} + +on_load({ + fallback_config_load() +}) + +fallback_config_load <- function() { + config <- fallback_config_read() + orig_config <- config + + config <- fallback_config_reset(config, "info", "DUCKPLYR_FALLBACK_INFO") + config <- fallback_config_reset(config, "logging", "DUCKPLYR_FALLBACK_LOGGING") + config <- fallback_config_reset(config, "autoupload", "DUCKPLYR_FALLBACK_AUTOUPLOAD") + config <- fallback_config_reset(config, "log_dir", "DUCKPLYR_FALLBACK_LOG_DIR") + config <- fallback_config_reset(config, "verbose", "DUCKPLYR_FALLBACK_VERBOSE") + + msg <- setdiff(names(orig_config), names(config)) + if (length(msg) > 0) { + msg <- set_names(paste0("{.envvar ", msg, "}"), rep_along(msg, "*")) + packageStartupMessage(cli::format_message(c( + "Some configuration values are set as environment variables and in the configuration file {.file {fallback_config_path()}}:", + msg, + i = "Use {.run duckplyr::fallback_config(reset_all = TRUE)} to reset the configuration.", + i = "Use {.run usethis::edit_r_environ()} to edit {.file ~/.Renviron}." + ))) + } + + fallback_config_apply(config) +} + +fallback_config_reset <- function(config, name, envvar) { + if (is.null(config[[name]])) { + return(config) + } + + val <- Sys.getenv(envvar, unset = NA) + if (!is.na(val) && !identical(val, config[[name]])) { + config[[name]] <- NULL + } + config +} + +# Side effect: create directory if it doesn't exist +fallback_config_path <- function() { + config_root <- tools::R_user_dir("duckplyr", "config") + dir.create(config_root, showWarnings = FALSE) + file.path(config_root, "fallback.dcf") +} + +#' fallback_review +#' +#' `fallback_review()` prints the available reports for review to the console. +#' +#' @param oldest,newest The number of oldest or newest reports to review. +#' If not specified, all reports are dispayed. +#' @param detail Print the full content of the reports. +#' Set to `FALSE` to only print the file names. +#' @rdname fallback +#' @export +fallback_review <- function(oldest = NULL, newest = NULL, detail = TRUE) { + fallback_logs <- tel_fallback_logs(oldest, newest, detail) + if (length(fallback_logs) == 0) { + cli::cli_inform("No reports ready for upload.") + return(invisible()) + } + + for (i in seq_along(fallback_logs)) { + file <- names(fallback_logs)[[i]] + + cli::cli_inform(c( + "*" = "{.file {file}}", + " " = if (detail) "{fallback_logs[[i]]}" + )) + } + + invisible() +} + +#' fallback_upload +#' +#' `fallback_upload()` uploads the available reports to a central server for analysis. +#' The server is hosted on AWS and the reports are stored in a private S3 bucket. +#' Only authorized personnel have access to the reports. +#' +#' @param strict If `TRUE`, the function aborts if any of the reports fail to upload. +#' With `FALSE`, only a message is printed. +#' +#' @rdname fallback +#' @export +fallback_upload <- function(oldest = NULL, newest = NULL, strict = TRUE) { + if (strict) { + check_installed( + "curl", + reason = "to upload duckplyr fallback reports." + ) + } else if (!is_installed("curl")) { + cli::cli_inform("Skipping upload of duckplyr fallback reports because the {.pkg curl} package is not installed.") + return(invisible()) + } + + fallback_logs <- tel_fallback_logs(oldest, newest, detail = TRUE) + if (length(fallback_logs) == 0) { + cli::cli_inform("No {.pkg duckplyr} fallback reports ready for upload.") + return(invisible()) + } + + cli::cli_inform("Uploading {.strong {length(fallback_logs)}} {.pkg duckplyr} fallback report{?s}.") + + failures <- character() + + pool <- curl::new_pool() + imap(fallback_logs, ~ { + contents <- .x + file <- .y + + done <- function(request) { + unlink(file) + } + + fail <- function(message) { + failures <<- c(failures, message) + } + + tel_post_async(contents, done, fail, pool) + }) + + curl::multi_run(pool = pool) + + if (length(failures) > 0) { + msg <- c( + "Failed to upload {length(failures)} {.pkg duckplyr} fallback report{?s}.", + "i" = "The upload will be attempted again the next time {.code duckplyr::fallback_upload()} is called.", + " " = '{paste(unique(failures), collapse = "\n")}' + ) + + if (strict) { + cli::cli_abort(msg) + } else { + cli::cli_inform(msg) + } + } else { + cli::cli_inform("All {.pkg duckplyr} fallback reports uploaded successfully.") + } + + invisible() +} + +on_load({ + fallback_autoupload() +}) + +fallback_autoupload <- function() { + autoupload <- tel_fallback_autoupload() + if (isTRUE(autoupload)) { + msg <- character() + suppressMessages(withCallingHandlers( + fallback_upload(strict = FALSE), + message = function(e) { + msg <<- c(msg, conditionMessage(e)) + } + )) + if (length(msg) > 0) { + packageStartupMessage(paste(msg, collapse = "\n")) + } + } else if (is.na(autoupload)) { + fallback_logs <- tel_fallback_logs() + if (length(fallback_logs) > 0) { + msg <- c( + fallback_txt_header(), + fallback_txt_autoupload(autoupload), + fallback_txt_sitrep_logs(fallback_logs), + "i" = cli::col_silver("Configure automatic uploading with {.code duckplyr::fallback_config()}.") + ) + packageStartupMessage(cli::format_message(msg)) + } + } +} + +#' fallback_purge +#' +#' `fallback_purge()` deletes some or all available reports. +#' +#' @rdname fallback +#' @export +fallback_purge <- function(oldest = NULL, newest = NULL) { + fallback_logs <- tel_fallback_logs(oldest, newest, detail = FALSE) + if (length(fallback_logs) == 0) { + cli::cli_inform("No {.pkg duckplyr} fallback reports ready to delete.") + return(invisible()) + } + + unlink(names(fallback_logs)) + cli::cli_inform("Deleted {.strong {length(fallback_logs)}} {.pkg duckplyr} fallback report{?s}.") + invisible() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter-rd.R new file mode 100644 index 000000000..2b2e2f5bb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter-rd.R @@ -0,0 +1,18 @@ +#' @title Keep rows that match a condition +#' +#' @description This is a method for the [dplyr::filter()] generic. +#' See "Fallbacks" section for differences in implementation. +#' The `filter()` function is used to subset a data frame, +#' retaining all rows that satisfy your conditions. +#' To be retained, the row must produce a value of `TRUE` for all conditions. +#' Note that when a condition evaluates to `NA` the row will be dropped, +#' unlike base subsetting with `[`. +#' +#' @inheritParams dplyr::filter +#' @examples +#' df <- duckdb_tibble(x = 1:3, y = 3:1) +#' filter(df, x >= 2) +#' @seealso [dplyr::filter()] +#' @rdname filter.duckplyr_df +#' @name filter.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter.R new file mode 100644 index 000000000..8ace026f2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/filter.R @@ -0,0 +1,70 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname filter.duckplyr_df +#' @export +filter.duckplyr_df <- function(.data, ..., .by = NULL, .preserve = FALSE) { + force(.data) + + dots <- dplyr_quosures(...) + check_filter(dots) + + by <- enquo(.by) + + duckplyr_error <- rel_try(list(name = "filter", x = .data, args = try_list(dots = dots, by = by, preserve = .preserve)), + #' @section Fallbacks: + #' There is no DuckDB translation in `filter.duckplyr_df()` + #' - with no filter conditions, + #' - nor for a grouped operation (if `.by` is set). + #' + #' These features fall back to [dplyr::filter()], see `vignette("fallback")` for details. + "Can't use relational without filter conditions." = (length(dots) == 0), + "{.code filter(.by = ...)} not implemented, try {.code mutate(.by = ...)} followed by a simple {.code filter()}." = (!quo_is_null(by)), # (length(by$names) > 0), + { + rel <- duckdb_rel_from_df(.data) + exprs <- rel_translate_dots(dots, .data) + + # FIXME: Seems to be necessary only if alternations `|` are used in `exprs`. + # Add only then? + rel <- oo_prep(rel) + + rel <- rel_filter(rel, exprs) + + out_rel <- oo_restore(rel) + + out <- duckplyr_reconstruct(out_rel, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + filter <- dplyr$filter.data.frame + out <- filter(.data, ..., .by = {{ .by }}, .preserve = .preserve) + return(out) + + # dplyr implementation + dots <- dplyr_quosures(...) + check_filter(dots) + + by <- compute_by( + by = {{ .by }}, + data = .data, + by_arg = ".by", + data_arg = ".data" + ) + + loc <- filter_rows(.data, dots, by) + dplyr_row_slice(.data, loc, preserve = .preserve) +} + +duckplyr_filter <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- filter(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/flights.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/flights.R new file mode 100644 index 000000000..bb2bc0ef9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/flights.R @@ -0,0 +1,17 @@ +#' Flight data +#' +#' Provides a variant of `nycflights13::flights` that is compatible with duckplyr, +#' as a tibble: +#' the timezone has been set to UTC to work around a current limitation of duckplyr, see `vignette("limits")`. +#' Call [as_duckdb_tibble()] to enable duckplyr operations. +#' +#' @export +#' @examplesIf requireNamespace("nycflights13", quietly = TRUE) +#' flights_df() +flights_df <- function() { + check_installed("nycflights13") + + out <- nycflights13::flights + attr(out$time_hour, "tzone") <- "UTC" + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join-rd.R new file mode 100644 index 000000000..cfbf2ea2a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join-rd.R @@ -0,0 +1,14 @@ +#' @title Full join +#' +#' @description This is a method for the [dplyr::full_join()] generic. +#' See "Fallbacks" section for differences in implementation. +#' A `full_join()` keeps all observations in `x` and `y`. +#' +#' @inheritParams dplyr::full_join +#' @examples +#' library(duckplyr) +#' full_join(band_members, band_instruments) +#' @seealso [dplyr::full_join()] +#' @rdname full_join.duckplyr_df +#' @name full_join.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join.R new file mode 100644 index 000000000..cc20a427d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/full_join.R @@ -0,0 +1,64 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname full_join.duckplyr_df +#' @export +full_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ..., keep = NULL, na_matches = c("na", "never"), multiple = "all", relationship = NULL) { + check_dots_empty0(...) + error_call <- caller_env() + y <- auto_copy(x, y, copy = copy) + + # Our implementation + duckplyr_error <- rel_try(list(name = "full_join", x = x, y = y, args = try_list(by = if (!is.null(by) && !is_cross_by(by)) as_join_by(by), copy = copy, keep = keep, na_matches = na_matches, multiple = multiple, relationship = relationship)), + #' @section Fallbacks: + #' There is no DuckDB translation in `full_join.duckplyr_df()` + #' - for an implicit cross join, + #' - for a value of the `multiple` argument that isn't the default `"all"`. + #' + #' These features fall back to [dplyr::full_join()], see `vignette("fallback")` for details. + "No implicit cross joins for {.code full_join()}" = is_cross_by(by), + "{.arg multiple} not supported" = !identical(multiple, "all"), + { + out <- rel_join_impl(x, y, by, "full", na_matches, suffix, keep, error_call) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + full_join <- dplyr$full_join.data.frame + out <- full_join(x, y, by, copy = FALSE, suffix, ..., keep = keep, na_matches = na_matches, multiple = multiple, relationship = relationship) + return(out) + + # dplyr implementation + check_dots_empty0(...) + y <- auto_copy(x, y, copy = copy) + join_mutate( + x = x, + y = y, + by = by, + type = "full", + suffix = suffix, + na_matches = na_matches, + keep = keep, + multiple = multiple, + # All keys from both inputs are retained. Erroring never makes sense. + unmatched = "drop", + relationship = relationship, + user_env = caller_env() + ) +} + +duckplyr_full_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- full_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/globals.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/globals.R new file mode 100644 index 000000000..c9a50f686 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/globals.R @@ -0,0 +1,301 @@ +# Generated by roxyglobals: do not edit by hand + +utils::globalVariables(c( + "lineitem", # + "l_shipdate", # + "l_returnflag", # + "l_linestatus", # + "l_quantity", # + "l_extendedprice", # + "l_discount", # + "l_tax", # + "partsupp", # + "ps_partkey", # + "ps_suppkey", # + "ps_supplycost", # + "part", # + "p_partkey", # + "p_type", # + "p_size", # + "p_mfgr", # + "supplier", # + "s_suppkey", # + "s_nationkey", # + "s_acctbal", # + "s_name", # + "s_address", # + "s_phone", # + "s_comment", # + "nation", # + "region", # + "r_name", # + "n_nationkey", # + "n_name", # + "orders", # + "o_orderkey", # + "o_custkey", # + "o_orderdate", # + "o_shippriority", # + "customer", # + "c_custkey", # + "c_mktsegment", # + "lineitem", # + "l_orderkey", # + "l_shipdate", # + "l_extendedprice", # + "l_discount", # + "volume", # + "revenue", # + "lineitem", # + "l_orderkey", # + "l_commitdate", # + "l_receiptdate", # + "orders", # + "o_orderkey", # + "o_orderdate", # + "o_orderpriority", # + "nation", # + "n_nationkey", # + "n_regionkey", # + "n_name", # + "region", # + "r_regionkey", # + "r_name", # + "supplier", # + "s_suppkey", # + "s_nationkey", # + "lineitem", # + "l_suppkey", # + "l_orderkey", # + "l_extendedprice", # + "l_discount", # + "orders", # + "o_orderdate", # + "o_orderkey", # + "o_custkey", # + "customer", # + "c_custkey", # + "c_nationkey", # + "volume", # + "revenue", # + "lineitem", # + "l_shipdate", # + "l_extendedprice", # + "l_discount", # + "l_quantity", # + "supplier", # + "s_nationkey", # + "s_suppkey", # + "nation", # + "n_nationkey", # + "n_name", # + "n1_name", # + "customer", # + "c_custkey", # + "c_nationkey", # + "n2_name", # + "orders", # + "o_custkey", # + "o_orderkey", # + "lineitem", # + "l_orderkey", # + "l_suppkey", # + "l_shipdate", # + "l_extendedprice", # + "l_discount", # + "supp_nation", # + "cust_nation", # + "l_year", # + "volume", # + "nation", # + "n_nationkey", # + "n_regionkey", # + "region", # + "r_regionkey", # + "r_name", # + "n1_nationkey", # + "customer", # + "c_custkey", # + "c_nationkey", # + "orders", # + "o_orderkey", # + "o_custkey", # + "o_orderdate", # + "lineitem", # + "l_orderkey", # + "l_partkey", # + "l_suppkey", # + "l_extendedprice", # + "l_discount", # + "part", # + "p_partkey", # + "p_type", # + "supplier", # + "s_suppkey", # + "s_nationkey", # + "n_name", # + "n2_name", # + "o_year", # + "volume", # + "part", # + "p_name", # + "p_partkey", # + "partsupp", # + "ps_suppkey", # + "ps_partkey", # + "ps_supplycost", # + "supplier", # + "s_suppkey", # + "s_nationkey", # + "nation", # + "n_nationkey", # + "n_name", # + "lineitem", # + "l_suppkey", # + "l_partkey", # + "l_orderkey", # + "l_extendedprice", # + "l_discount", # + "l_quantity", # + "orders", # + "o_orderkey", # + "o_orderdate", # + "o_year", # + "amount", # + "lineitem", # + "l_orderkey", # + "l_returnflag", # + "l_extendedprice", # + "l_discount", # + "orders", # + "o_orderkey", # + "o_custkey", # + "o_orderdate", # + "volume", # + "customer", # + "c_custkey", # + "c_nationkey", # + "c_name", # + "c_acctbal", # + "c_phone", # + "c_address", # + "c_comment", # + "nation", # + "n_nationkey", # + "n_name", # + "revenue", # + "n_name", # + "partsupp", # + "supplier", # + "ps_supplycost", # + "ps_availqty", # + "ps_partkey", # + "value", # + "global_value", # + "lineitem", # + "l_shipmode", # + "l_commitdate", # + "l_receiptdate", # + "l_shipdate", # + "orders", # + "o_orderpriority", # + "customer", # + "orders", # + "o_comment", # + "o_orderkey", # + "c_custkey", # + "c_count", # + "custdist", # + "lineitem", # + "l_shipdate", # + "part", # + "p_type", # + "l_extendedprice", # + "l_discount", # + "lineitem", # + "l_shipdate", # + "l_extendedprice", # + "l_discount", # + "l_suppkey", # + "total_revenue", # + "global_agr_key", # + "max_total_revenue", # + "supplier", # + "s_name", # + "s_address", # + "s_phone", # + "part", # + "p_brand", # + "p_type", # + "p_size", # + "supplier", # + "s_comment", # + "partsupp", # + "ps_partkey", # + "ps_suppkey", # + "supplier_cnt", # + "part", # + "p_brand", # + "p_container", # + "lineitem", # + "l_quantity", # + "l_partkey", # + "quantity_threshold", # + "l_extendedprice", # + "lineitem", # + "l_quantity", # + "l_orderkey", # + "orders", # + "customer", # + "c_name", # + "o_custkey", # + "o_orderkey", # + "o_orderdate", # + "o_totalprice", # + "lineitem", # + "part", # + "p_brand", # + "p_container", # + "l_quantity", # + "p_size", # + "l_shipmode", # + "l_shipinstruct", # + "l_extendedprice", # + "l_discount", # + "supplier", # + "nation", # + "n_name", # + "s_suppkey", # + "s_name", # + "s_address", # + "part", # + "p_name", # + "partsupp", # + "lineitem", # + "l_shipdate", # + "l_quantity", # + "l_suppkey", # + "ps_availqty", # + "lineitem", # + "l_orderkey", # + "l_suppkey", # + "n_supplier", # + "orders", # + "o_orderstatus", # + "l_receiptdate", # + "l_commitdate", # + "failed_delivery_commit", # + "num_failed", # + "supplier", # + "nation", # + "n_name", # + "s_name", # + "numwait", # + "customer", # + "c_phone", # + "c_acctbal", # + "cntrycode", # + "acctbal_min", # + "orders", # + NULL +)) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_by.R new file mode 100644 index 000000000..804169b05 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_by.R @@ -0,0 +1,40 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_by.duckplyr_df <- function(.data, ..., .add = FALSE, .drop = group_by_drop_default(.data)) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "Try {.code summarise(.by = ...)} or {.code mutate(.by = ...)} instead of {.code group_by()} and {.code ungroup()}." = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + group_by <- dplyr$group_by.data.frame + out <- group_by(.data, ..., .add = .add, .drop = .drop) + return(out) + + # dplyr implementation + groups <- group_by_prepare( + .data, + ..., + .add = .add, + error_call = current_env() + ) + grouped_df(groups$data, groups$group_names, .drop) +} + +duckplyr_group_by <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_by(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_indices.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_indices.R new file mode 100644 index 000000000..413b94537 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_indices.R @@ -0,0 +1,43 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_indices.duckplyr_df <- function(.data, ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_indices()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + group_indices <- dplyr$group_indices.data.frame + out <- group_indices(.data, ...) + return(out) + + # dplyr implementation + if (dots_n(...) > 0) { + lifecycle::deprecate_warn( + "1.0.0", "group_indices(... = )", + details = "Please `group_by()` first", + always = TRUE + ) + .data <- group_by(.data, ...) + } + + # .Call(`dplyr_group_indices`, .data, group_rows(.data)) +} + +duckplyr_group_indices <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_indices(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_keys.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_keys.R new file mode 100644 index 000000000..0e6a1301f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_keys.R @@ -0,0 +1,43 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_keys.duckplyr_df <- function(.tbl, ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_keys()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.tbl, duckplyr_error) + + group_keys <- dplyr$group_keys.data.frame + out <- group_keys(.tbl, ...) + return(out) + + # dplyr implementation + if (dots_n(...) > 0) { + lifecycle::deprecate_warn( + "1.0.0", "group_keys(... = )", + details = "Please `group_by()` first", + always = TRUE + ) + .tbl <- group_by(.tbl, ...) + } + out <- group_data(.tbl) + group_keys0(out) +} + +duckplyr_group_keys <- function(.tbl, ...) { + try_fetch( + .tbl <- as_duckplyr_df_impl(.tbl), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_keys(.tbl, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_map.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_map.R new file mode 100644 index 000000000..e8cb4840a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_map.R @@ -0,0 +1,59 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_map.duckplyr_df <- function(.data, .f, ..., .keep = FALSE, keep = deprecated()) { + if (!missing(keep)) { + lifecycle::deprecate_warn("1.0.0", "group_map(keep = )", "group_map(.keep = )", always = TRUE) + .keep <- keep + } + + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_map()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + group_map <- dplyr$group_map.data.frame + out <- group_map(.data, .f, ..., .keep = .keep) + return(out) + + # dplyr implementation + if (!missing(keep)) { + lifecycle::deprecate_warn("1.0.0", "group_map(keep = )", "group_map(.keep = )", always = TRUE) + .keep <- keep + } + .f <- as_group_map_function(.f) + + # call the function on each group + chunks <- if (is_grouped_df(.data)) { + group_split(.data, .keep = isTRUE(.keep)) + } else { + group_split(.data) + } + keys <- group_keys(.data) + group_keys <- map(seq_len(nrow(keys)), function(i) keys[i, , drop = FALSE]) + + if (length(chunks)) { + map2(chunks, group_keys, .f, ...) + } else { + # calling .f with .x and .y set to prototypes + structure(list(), ptype = .f(attr(chunks, "ptype"), keys[integer(0L), ], ...)) + } +} + +duckplyr_group_map <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_map(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_modify.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_modify.R new file mode 100644 index 000000000..1169b465a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_modify.R @@ -0,0 +1,44 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_modify.duckplyr_df <- function(.data, .f, ..., .keep = FALSE, keep = deprecated()) { + if (!missing(keep)) { + lifecycle::deprecate_warn("1.0.0", "group_modify(keep = )", "group_modify(.keep = )", always = TRUE) + .keep <- keep + } + + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_modify()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + group_modify <- dplyr$group_modify.data.frame + out <- group_modify(.data, .f, ..., .keep = .keep) + return(out) + + # dplyr implementation + if (!missing(keep)) { + lifecycle::deprecate_warn("1.0.0", "group_modify(keep = )", "group_modify(.keep = )", always = TRUE) + .keep <- keep + } + .f <- as_group_map_function(.f) + .f(.data, group_keys(.data), ...) +} + +duckplyr_group_modify <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_modify(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_nest.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_nest.R new file mode 100644 index 000000000..43b930167 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_nest.R @@ -0,0 +1,38 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_nest.duckplyr_df <- function(.tbl, ..., .key = "data", keep = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_nest()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.tbl, duckplyr_error) + + group_nest <- dplyr$group_nest.data.frame + out <- group_nest(.tbl, ..., .key = .key, keep = keep) + return(out) + + # dplyr implementation + if (dots_n(...)) { + group_nest_impl(group_by(.tbl, ...), .key = .key, keep = keep) + } else { + tibble(!!.key := list(.tbl)) + } +} + +duckplyr_group_nest <- function(.tbl, ...) { + try_fetch( + .tbl <- as_duckplyr_df_impl(.tbl), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_nest(.tbl, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_size.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_size.R new file mode 100644 index 000000000..01088472b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_size.R @@ -0,0 +1,34 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_size.duckplyr_df <- function(x) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_size()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + group_size <- dplyr$group_size.data.frame + out <- group_size(x) + return(out) + + # dplyr implementation + lengths(group_rows(x)) +} + +duckplyr_group_size <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_size(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_split.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_split.R new file mode 100644 index 000000000..90fd211f5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_split.R @@ -0,0 +1,44 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_split.duckplyr_df <- function(.tbl, ..., .keep = TRUE, keep = deprecated()) { + if (!missing(keep)) { + lifecycle::deprecate_warn("1.0.0", "group_split(keep = )", "group_split(.keep = )", always = TRUE) + .keep <- keep + } + + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_split()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.tbl, duckplyr_error) + + group_split <- dplyr$group_split.data.frame + out <- group_split(.tbl, ..., .keep = .keep) + return(out) + + # dplyr implementation + if (!missing(keep)) { + lifecycle::deprecate_warn("1.0.0", "group_split(keep = )", "group_split(.keep = )", always = TRUE) + .keep <- keep + } + data <- group_by(.tbl, ...) + group_split_impl(data, .keep = .keep) +} + +duckplyr_group_split <- function(.tbl, ...) { + try_fetch( + .tbl <- as_duckplyr_df_impl(.tbl), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_split(.tbl, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_trim.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_trim.R new file mode 100644 index 000000000..7459e771d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_trim.R @@ -0,0 +1,34 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_trim.duckplyr_df <- function(.tbl, .drop = group_by_drop_default(.tbl)) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code group_trim()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.tbl, duckplyr_error) + + group_trim <- dplyr$group_trim.data.frame + out <- group_trim(.tbl, .drop) + return(out) + + # dplyr implementation + .tbl +} + +duckplyr_group_trim <- function(.tbl, ...) { + try_fetch( + .tbl <- as_duckplyr_df_impl(.tbl), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_trim(.tbl, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_vars.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_vars.R new file mode 100644 index 000000000..bd8635af1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/group_vars.R @@ -0,0 +1,21 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +group_vars.duckplyr_df <- function(x) { + if (inherits(x, c("grouped_df", "rowwise_df"))) { + return(dplyr$group_vars.data.frame(x)) + } + + # Avoid calling group_data() + character() +} + +duckplyr_group_vars <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- group_vars(x, ...) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/groups.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/groups.R new file mode 100644 index 000000000..63ca12561 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/groups.R @@ -0,0 +1,34 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +groups.duckplyr_df <- function(x) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code groups()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + groups <- dplyr$groups.data.frame + out <- groups(x) + return(out) + + # dplyr implementation + syms(group_vars(x)) +} + +duckplyr_groups <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- groups(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/handle_desc.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/handle_desc.R new file mode 100644 index 000000000..19ff4a9e4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/handle_desc.R @@ -0,0 +1,44 @@ +# Used in arrange() +# Handles calls to 'desc' function by +# - extracting the sort order +# - removing any desc-function calls from the expressions: desc(colname) -> colname +handle_desc <- function(dots, call = caller_env()) { + ascending <- rep(TRUE, length(dots)) + + for (i in seq_along(dots)) { + expr <- quo_get_expr(dots[[i]]) + env <- quo_get_env(dots[[i]]) + + if (is_desc(expr, env, call)) { + ascending[[i]] <- FALSE + dots[[i]] <- new_quosure(expr[[2]], env = env) + } + } + + list(dots = dots, ascending = ascending) +} + +is_desc <- function(expr, env, call) { + if (!is.call(expr)) { + return(FALSE) + } + + if (expr[[1]] == "desc") { + if (!identical(eval(expr[[1]], env), dplyr::desc)) { + # Error handled elsewhere + return(FALSE) + } + } else if (expr[[1]][[1]] == "::") { + if (expr[[1]][[2]] != "dplyr") { + return(FALSE) + } + } else { + return(FALSE) + } + + if (length(expr) > 2) { + cli::cli_abort("Function {.fun desc} must be called with exactly one argument.", call = call) + } + + TRUE +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/head-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/head-rd.R new file mode 100644 index 000000000..125b00bfe --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/head-rd.R @@ -0,0 +1,15 @@ +#' @title Return the First Parts of an Object +#' +#' @description This is a method for the [head()] generic. +#' See "Fallbacks" section for differences in implementation. +#' Return the first rows of a data.frame +#' +#' @param x A data.frame +#' @param n A positive integer, how many rows to return. +#' @param ... Not used yet. +#' @examples +#' head(mtcars, 2) +#' @seealso [head()] +#' @rdname head.duckplyr_df +#' @name head.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/head.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/head.R new file mode 100644 index 000000000..dd0ead629 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/head.R @@ -0,0 +1,22 @@ +#' @rdname head.duckplyr_df +#' @export +head.duckplyr_df <- function(x, n = 6L, ...) { + stopifnot(is_integerish(n)) + + duckplyr_error <- rel_try(list(name = "head", x = x, args = try_list(n = n)), + #' @section Fallbacks: + #' There is no DuckDB translation in `head.duckplyr_df()` + #' - with a negative `n`. + #' + #' These features fall back to [head()], see `vignette("fallback")` for details. + "{.code slice_head(n = ...)} with negative values not supported" = (n < 0), + { + rel <- duckdb_rel_from_df(x) + out_rel <- rel_limit(rel, n) + out <- duckplyr_reconstruct(out_rel, x) + return(out) + } + ) + + NextMethod() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/import-standalone-purrr.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/import-standalone-purrr.R new file mode 100644 index 000000000..33b0fc5d2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/import-standalone-purrr.R @@ -0,0 +1,239 @@ +# Standalone file: do not edit by hand +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-purrr.R +# last-updated: 2023-02-23 +# license: https://unlicense.org +# imports: rlang +# --- +# +# This file provides a minimal shim to provide a purrr-like API on top of +# base R functions. They are not drop-in replacements but allow a similar style +# of programming. +# +# ## Changelog +# +# 2023-02-23: +# * Added `list_c()` +# +# 2022-06-07: +# * `transpose()` is now more consistent with purrr when inner names +# are not congruent (#1346). +# +# 2021-12-15: +# * `transpose()` now supports empty lists. +# +# 2021-05-21: +# * Fixed "object `x` not found" error in `imap()` (@mgirlich) +# +# 2020-04-14: +# * Removed `pluck*()` functions +# * Removed `*_cpl()` functions +# * Used `as_function()` to allow use of `~` +# * Used `.` prefix for helpers +# +# nocov start + +map <- function(.x, .f, ...) { + .f <- as_function(.f, env = global_env()) + lapply(.x, .f, ...) +} +walk <- function(.x, .f, ...) { + map(.x, .f, ...) + invisible(.x) +} + +map_lgl <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, logical(1), ...) +} +map_int <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, integer(1), ...) +} +map_dbl <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, double(1), ...) +} +map_chr <- function(.x, .f, ...) { + .rlang_purrr_map_mold(.x, .f, character(1), ...) +} +.rlang_purrr_map_mold <- function(.x, .f, .mold, ...) { + .f <- as_function(.f, env = global_env()) + out <- vapply(.x, .f, .mold, ..., USE.NAMES = FALSE) + names(out) <- names(.x) + out +} + +map2 <- function(.x, .y, .f, ...) { + .f <- as_function(.f, env = global_env()) + out <- mapply(.f, .x, .y, MoreArgs = list(...), SIMPLIFY = FALSE) + if (length(out) == length(.x)) { + set_names(out, names(.x)) + } else { + set_names(out, NULL) + } +} +map2_lgl <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "logical") +} +map2_int <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "integer") +} +map2_dbl <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "double") +} +map2_chr <- function(.x, .y, .f, ...) { + as.vector(map2(.x, .y, .f, ...), "character") +} +imap <- function(.x, .f, ...) { + map2(.x, names(.x) %||% seq_along(.x), .f, ...) +} + +pmap <- function(.l, .f, ...) { + .f <- as.function(.f) + args <- .rlang_purrr_args_recycle(.l) + do.call("mapply", c( + FUN = list(quote(.f)), + args, MoreArgs = quote(list(...)), + SIMPLIFY = FALSE, USE.NAMES = FALSE + )) +} +.rlang_purrr_args_recycle <- function(args) { + lengths <- map_int(args, length) + n <- max(lengths) + + stopifnot(all(lengths == 1L | lengths == n)) + to_recycle <- lengths == 1L + args[to_recycle] <- map(args[to_recycle], function(x) rep.int(x, n)) + + args +} + +keep <- function(.x, .f, ...) { + .x[.rlang_purrr_probe(.x, .f, ...)] +} +discard <- function(.x, .p, ...) { + sel <- .rlang_purrr_probe(.x, .p, ...) + .x[is.na(sel) | !sel] +} +map_if <- function(.x, .p, .f, ...) { + matches <- .rlang_purrr_probe(.x, .p) + .x[matches] <- map(.x[matches], .f, ...) + .x +} +.rlang_purrr_probe <- function(.x, .p, ...) { + if (is_logical(.p)) { + stopifnot(length(.p) == length(.x)) + .p + } else { + .p <- as_function(.p, env = global_env()) + map_lgl(.x, .p, ...) + } +} + +compact <- function(.x) { + Filter(length, .x) +} + +transpose <- function(.l) { + if (!length(.l)) { + return(.l) + } + + inner_names <- names(.l[[1]]) + + if (is.null(inner_names)) { + fields <- seq_along(.l[[1]]) + } else { + fields <- set_names(inner_names) + .l <- map(.l, function(x) { + if (is.null(names(x))) { + set_names(x, inner_names) + } else { + x + } + }) + } + + # This way missing fields are subsetted as `NULL` instead of causing + # an error + .l <- map(.l, as.list) + + map(fields, function(i) { + map(.l, .subset2, i) + }) +} + +every <- function(.x, .p, ...) { + .p <- as_function(.p, env = global_env()) + + for (i in seq_along(.x)) { + if (!rlang::is_true(.p(.x[[i]], ...))) return(FALSE) + } + TRUE +} +some <- function(.x, .p, ...) { + .p <- as_function(.p, env = global_env()) + + for (i in seq_along(.x)) { + if (rlang::is_true(.p(.x[[i]], ...))) return(TRUE) + } + FALSE +} +negate <- function(.p) { + .p <- as_function(.p, env = global_env()) + function(...) !.p(...) +} + +reduce <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(x, y, ...) + Reduce(f, .x, init = .init) +} +reduce_right <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(y, x, ...) + Reduce(f, .x, init = .init, right = TRUE) +} +accumulate <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(x, y, ...) + Reduce(f, .x, init = .init, accumulate = TRUE) +} +accumulate_right <- function(.x, .f, ..., .init) { + f <- function(x, y) .f(y, x, ...) + Reduce(f, .x, init = .init, right = TRUE, accumulate = TRUE) +} + +detect <- function(.x, .f, ..., .right = FALSE, .p = is_true) { + .p <- as_function(.p, env = global_env()) + .f <- as_function(.f, env = global_env()) + + for (i in .rlang_purrr_index(.x, .right)) { + if (.p(.f(.x[[i]], ...))) { + return(.x[[i]]) + } + } + NULL +} +detect_index <- function(.x, .f, ..., .right = FALSE, .p = is_true) { + .p <- as_function(.p, env = global_env()) + .f <- as_function(.f, env = global_env()) + + for (i in .rlang_purrr_index(.x, .right)) { + if (.p(.f(.x[[i]], ...))) { + return(i) + } + } + 0L +} +.rlang_purrr_index <- function(x, right = FALSE) { + idx <- seq_along(x) + if (right) { + idx <- rev(idx) + } + idx +} + +list_c <- function(x) { + inject(c(!!!x)) +} + +# nocov end diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join-rd.R new file mode 100644 index 000000000..9fbb4392c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join-rd.R @@ -0,0 +1,15 @@ +#' @title Inner join +#' +#' @description This is a method for the [dplyr::inner_join()] generic. +#' See "Fallbacks" section for differences in implementation. +#' An `inner_join()` only keeps observations from `x` +#' that have a matching key in `y`. +#' +#' @inheritParams dplyr::inner_join +#' @examples +#' library(duckplyr) +#' inner_join(band_members, band_instruments) +#' @seealso [dplyr::inner_join()] +#' @rdname inner_join.duckplyr_df +#' @name inner_join.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join.R new file mode 100644 index 000000000..3e0c9fc83 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/inner_join.R @@ -0,0 +1,65 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname inner_join.duckplyr_df +#' @export +inner_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ..., keep = NULL, na_matches = c("na", "never"), multiple = "all", unmatched = "drop", relationship = NULL) { + check_dots_empty0(...) + error_call <- caller_env() + y <- auto_copy(x, y, copy = copy) + + # Our implementation + duckplyr_error <- rel_try(list(name = "inner_join", x = x, y = y, args = try_list(by = if (!is.null(by) && !is_cross_by(by)) as_join_by(by), copy = copy, keep = keep, na_matches = na_matches, multiple = multiple, unmatched = unmatched, relationship = relationship)), + #' @section Fallbacks: + #' There is no DuckDB translation in `inner_join.duckplyr_df()` + #' - for an implicit crossjoin, + #' - for a value of the `multiple` argument that isn't the default `"all"`. + #' - for a value of the `unmatched` argument that isn't the default `"drop"`. + #' + #' These features fall back to [dplyr::inner_join()], see `vignette("fallback")` for details. + "No implicit cross joins for {.code inner_join()}" = is_cross_by(by), + "{.arg multiple} not supported" = !identical(multiple, "all"), + "{.arg unmatched} not supported" = !identical(unmatched, "drop"), + { + out <- rel_join_impl(x, y, by, "inner", na_matches, suffix, keep, error_call) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + inner_join <- dplyr$inner_join.data.frame + out <- inner_join(x, y, by, copy = FALSE, suffix, ..., keep = keep, na_matches = na_matches, multiple = multiple, unmatched = unmatched, relationship = relationship) + return(out) + + # dplyr implementation + check_dots_empty0(...) + y <- auto_copy(x, y, copy = copy) + join_mutate( + x = x, + y = y, + by = by, + type = "inner", + suffix = suffix, + na_matches = na_matches, + keep = keep, + multiple = multiple, + unmatched = unmatched, + relationship = relationship, + user_env = caller_env() + ) +} + +duckplyr_inner_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- inner_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect-rd.R new file mode 100644 index 000000000..cec34e332 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect-rd.R @@ -0,0 +1,15 @@ +#' @title Intersect +#' +#' @description This is a method for the [dplyr::intersect()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `intersect(x, y)` finds all rows in both `x` and `y`. +#' +#' @inheritParams dplyr::intersect +#' @examples +#' df1 <- duckdb_tibble(x = 1:3) +#' df2 <- duckdb_tibble(x = 3:5) +#' intersect(df1, df2) +#' @seealso [dplyr::intersect()] +#' @rdname intersect.duckplyr_df +#' @name intersect.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect.R new file mode 100644 index 000000000..74bcb9a93 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/intersect.R @@ -0,0 +1,75 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname intersect.duckplyr_df +#' @export +intersect.duckplyr_df <- function(x, y, ...) { + # Our implementation + check_dots_empty() + check_compatible(x, y) + + x_names <- names(x) + y_names <- names(y) + if (identical(x_names, y_names)) { + # Ensure identical() is very cheap + y_names <- x_names + } + + duckplyr_error <- rel_try(list(name = "intersect", x = x, y = y), + #' @section Fallbacks: + #' There is no DuckDB translation in `intersect.duckplyr_df()` + #' - if column names are duplicated in one of the tables, + #' - if column names are different in both tables. + #' + #' These features fall back to [dplyr::intersect()], see `vignette("fallback")` for details. + "No duplicate names" = !identical(x_names, y_names) && anyDuplicated(x_names) && anyDuplicated(y_names), + "Tables of different width" = length(x_names) != length(y_names), + "Name mismatch" = !identical(x_names, y_names) && !all(y_names %in% x_names), + { + if (oo_force()) { + both <- semi_join(x, y, by = x_names) + out <- distinct(both) + } else { + x_rel <- duckdb_rel_from_df(x) + y_rel <- duckdb_rel_from_df(y) + if (!identical(x_names, y_names)) { + # FIXME: Select by position + exprs <- nexprs_from_loc(x_names, set_names(seq_along(x_names), x_names)) + y_rel <- rel_project(y_rel, exprs) + } + + rel <- rel_set_intersect(x_rel, y_rel) + out <- duckplyr_reconstruct(rel, x) + } + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + intersect <- dplyr$intersect.data.frame + out <- intersect(x, y, ...) + return(out) + + # dplyr implementation + check_dots_empty() + check_compatible(x, y) + + out <- vec_set_intersect(x, y, error_call = current_env()) + + dplyr_reconstruct(out, x) +} + +duckplyr_intersect <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- intersect(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-.R new file mode 100644 index 000000000..68ab52c75 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-.R @@ -0,0 +1,90 @@ +#' Read Parquet, CSV, and other files using DuckDB +#' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' `df_from_file()` uses arbitrary table functions to read data. +#' See for a documentation +#' of the available functions and their options. +#' To read multiple files with the same schema, +#' pass a wildcard or a character vector to the `path` argument, +#' +#' @inheritParams rlang::args_dots_empty +#' +#' @param path Path to files, glob patterns `*` and `?` are supported. +#' @param table_function The name of a table-valued +#' DuckDB function such as `"read_parquet"`, +#' `"read_csv"`, `"read_csv_auto"` or `"read_json"`. +#' @param options Arguments to the DuckDB function +#' indicated by `table_function`. +#' @param class The class of the output. +#' By default, a tibble is created. +#' The returned object will always be a data frame. +#' Use `class = "data.frame"` or `class = character()` +#' to create a plain data frame. +#' +#' @return A data frame for `df_from_file()`, or a `duckplyr_df` for +#' `duckplyr_df_from_file()`, extended by the provided `class`. +#' +#' @export +#' @keywords internal +df_from_file <- function(path, + table_function, + ..., + options = list(), + class = NULL) { + check_dots_empty() + lifecycle::deprecate_soft("1.0.0", "df_from_file()", "read_file_duckdb()") + + if (!rlang::is_character(path)) { + cli::cli_abort("{.arg path} must be a character vector.") + } + + if (length(path) != 1) { + path <- list(path) + } + + if (is.null(class)) { + class <- default_df_class() + } + + # FIXME: For some reason, it's important to create an alias here + con <- get_default_duckdb_connection() + + out <- duckdb$rel_from_table_function( + con, + table_function, + list(path), + options + ) + + meta_rel_register_file(out, table_function, path, options) + + out <- duckdb$rel_to_altrep(out) + class(out) <- unique(c(class, "data.frame"), fromLast = TRUE) + out +} + +#' duckplyr_df_from_file +#' +#' `duckplyr_df_from_file()` is a thin wrapper around `df_from_file()` +#' that calls `as_duckplyr_df()` on the output. +#' +#' @rdname df_from_file +#' @export +duckplyr_df_from_file <- function( + path, + table_function, + ..., + options = list(), + class = NULL) { + check_dots_empty() + lifecycle::deprecate_soft("1.0.0", "duckplyr_df_from_file()", "read_file_duckdb()") + + out <- df_from_file(path, table_function, options = options, class = class) + as_duckplyr_df_impl(out) +} + +default_df_class <- function() { + class(new_tibble(list())) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-csv.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-csv.R new file mode 100644 index 000000000..042a251cf --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-csv.R @@ -0,0 +1,59 @@ +#' df_from_csv +#' +#' @description +#' These functions ingest data from a file using a table function. +#' The results are transparently converted to a data frame, but the data is only read when +#' the resulting data frame is actually accessed. +#' +#' `df_from_csv()` reads a CSV file using the `read_csv_auto()` table function. +#' +#' @rdname df_from_file +#' @export +#' @examples +#' # Create simple CSV file +#' path <- tempfile("duckplyr_test_", fileext = ".csv") +#' write.csv(data.frame(a = 1:3, b = letters[4:6]), path, row.names = FALSE) +#' +#' # Reading is immediate +#' df <- df_from_csv(path) +#' +#' # Materialization only upon access +#' names(df) +#' df$a +#' +#' # Return as tibble, specify column types: +#' df_from_file( +#' path, +#' "read_csv", +#' options = list(delim = ",", types = list(c("DOUBLE", "VARCHAR"))), +#' class = class(tibble()) +#' ) +df_from_csv <- function(path, ..., options = list(), class = NULL) { + check_dots_empty() + lifecycle::deprecate_soft("1.0.0", "df_from_csv()", "read_csv_duckdb()") + + df_from_file(path, "read_csv_auto", options = options, class = class) +} + +#' duckplyr_df_from_csv +#' +#' `duckplyr_df_from_csv()` is a thin wrapper around `df_from_csv()` +#' that calls `as_duckplyr_df()` on the output. +#' +#' @rdname df_from_file +#' @export +#' @examples +#' +#' # Read multiple file at once +#' path2 <- tempfile("duckplyr_test_", fileext = ".csv") +#' write.csv(data.frame(a = 4:6, b = letters[7:9]), path2, row.names = FALSE) +#' +#' duckplyr_df_from_csv(file.path(tempdir(), "duckplyr_test_*.csv")) +#' +#' unlink(c(path, path2)) +duckplyr_df_from_csv <- function(path, ..., options = list(), class = NULL) { + check_dots_empty() + lifecycle::deprecate_soft("1.0.0", "duckplyr_df_from_csv()", "read_csv_duckdb()") + + duckplyr_df_from_file(path, "read_csv_auto", options = options, class = class) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-parquet.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-parquet.R new file mode 100644 index 000000000..5592bac74 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/io-parquet.R @@ -0,0 +1,59 @@ +#' df_from_parquet +#' +#' @description +#' `df_from_parquet()` reads a Parquet file using the `read_parquet()` table function. +#' +#' @rdname df_from_file +#' @export +df_from_parquet <- function(path, ..., options = list(), class = NULL) { + check_dots_empty() + lifecycle::deprecate_soft("1.0.0", "df_from_parquet()", "read_parquet_duckdb()") + + df_from_file(path, "read_parquet", options = options, class = class) +} + +#' duckplyr_df_from_parquet +#' +#' `duckplyr_df_from_parquet()` is a thin wrapper around `df_from_parquet()` +#' that calls `as_duckplyr_df()` on the output. +#' +#' @rdname df_from_file +#' @export +duckplyr_df_from_parquet <- function(path, ..., options = list(), class = NULL) { + check_dots_empty() + lifecycle::deprecate_soft("1.0.0", "duckplyr_df_from_parquet()", "read_parquet_duckdb()") + + duckplyr_df_from_file(path, "read_parquet", options = options, class = class) +} + +#' df_to_parquet +#' +#' `df_to_parquet()` writes a data frame to a Parquet file via DuckDB. +#' If the data frame is a `duckplyr_df`, the materialization occurs outside of R. +#' An existing file will be overwritten. +#' This function requires duckdb >= 0.10.0. +#' +#' @param data A data frame to be written to disk. +#' +#' @rdname df_from_file +#' @export +#' @examples +#' +#' # Write a Parquet file: +#' path_parquet <- tempfile(fileext = ".parquet") +#' df_to_parquet(df, path_parquet) +#' +#' # With a duckplyr_df, the materialization occurs outside of R: +#' df %>% +#' as_duckplyr_df() %>% +#' mutate(b = a + 1) %>% +#' df_to_parquet(path_parquet) +#' +#' duckplyr_df_from_parquet(path_parquet) +#' +#' unlink(path_parquet) +df_to_parquet <- function(data, path) { + lifecycle::deprecate_soft("1.0.0", "df_to_parquet()", "compute_parquet()") + rel <- duckdb_rel_from_df(data) + duckdb$rel_to_parquet(rel, path) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/is_duckplyr_df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/is_duckplyr_df.R new file mode 100644 index 000000000..5f79b2c90 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/is_duckplyr_df.R @@ -0,0 +1,26 @@ +#' Class predicate for duckplyr data frames +#' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' Tests if the input object is of class `"duckplyr_df"`. +#' +#' @param .data The object to test +#' +#' @return `TRUE` if the input object is of class `"duckplyr_df"`, +#' otherwise `FALSE`. +#' +#' @keywords internal +#' @export +#' @examples +#' tibble(a = 1:3) %>% +#' is_duckplyr_df() +#' +#' tibble(a = 1:3) %>% +#' as_duckplyr_df() %>% +#' is_duckplyr_df() +is_duckplyr_df <- function(.data) { + lifecycle::deprecate_soft("1.0.0", "is_duckplyr_df()", "is_duckdb_tibble()") + + inherits(.data, "duckplyr_df") +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/join.R new file mode 100644 index 000000000..af5113908 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/join.R @@ -0,0 +1,147 @@ +rel_join_impl <- function( + x, + y, + by, + join, + na_matches, + suffix = c(".x", ".y"), + keep = NULL, + error_call = caller_env() +) { + mutating <- !(join %in% c("semi", "anti")) + + if (mutating) { + check_keep(keep, error_call = error_call) + } + + na_matches <- check_na_matches(na_matches, error_call = error_call) + + x_names <- tbl_vars_safe(x) + y_names <- tbl_vars_safe(y) + + if (is_null(by)) { + by <- join_by_common(x_names, y_names, error_call = error_call) + } else { + by <- as_join_by(by, error_call = error_call) + } + + x_by <- by$x + y_by <- by$y + x_rel <- duckdb_rel_from_df(x) + x_rel <- rel_set_alias(x_rel, "lhs") + y_rel <- duckdb_rel_from_df(y) + y_rel <- rel_set_alias(y_rel, "rhs") + + # FIXME: Split join_cols, https://github.com/tidyverse/dplyr/issues/7050 + vars <- join_cols( + x_names = x_names, + y_names = y_names, + by = by, + suffix = suffix, + keep = keep, + error_call = error_call + ) + + # vec_ptype_safe: https://github.com/r-lib/vctrs/issues/1956 + x_in <- map(as.list(x)[vars$x$key], vec_ptype_safe) + y_in <- map(as.list(y)[vars$y$key], vec_ptype_safe) + + x_key <- as.data.frame(set_names(x_in, names(vars$x$key))) + y_key <- as.data.frame(set_names(y_in, names(vars$x$key))) + + # Side effect: check join compatibility + join_ptype_common(x_key, y_key, vars, error_call = error_call) + + # Rename if non-unique column names + if (mutating) { + if (length(intersect(x_names, y_names)) != 0) { + x_names_remap <- paste0(x_names, "_x") + x_by <- paste0(x_by, "_x") + x_exprs <- exprs_from_loc(x, set_names(seq_along(x_names_remap), x_names_remap)) + x_rel <- rel_project(x_rel, x_exprs) + + y_names_remap <- paste0(y_names, "_y") + y_by <- paste0(y_by, "_y") + y_exprs <- exprs_from_loc(y, set_names(seq_along(y_names_remap), y_names_remap)) + y_rel <- rel_project(y_rel, y_exprs) + } else { + x_names_remap <- x_names + y_names_remap <- y_names + } + } + + x_rel <- oo_prep(x_rel, "___row_number_x") + if (mutating) { + y_rel <- oo_prep(y_rel, "___row_number_y") + } + + x_by <- map(x_by, relexpr_reference, rel = x_rel) + y_by <- map(y_by, relexpr_reference, rel = y_rel) + + cond_by <- by$condition + + if (na_matches == "na") { + cond_by[cond_by == "=="] <- "___eq_na_matches_na" + } + + conds <- pmap(list(cond_by, x_by, y_by), function(...) { + relexpr_function(..1, list(..2, ..3)) + }) + + if (any(by$filter != "none")) { + join_ref_type <- "asof" + } else { + join_ref_type <- "regular" + } + + joined <- rel_join(x_rel, y_rel, conds, join, join_ref_type) + + if (mutating) { + joined <- oo_restore_order( + joined, + c("___row_number_x", "___row_number_y"), + list(x_rel, y_rel) + ) + + exprs <- c( + nexprs_from_loc(x_names_remap, vars$x$out), + nexprs_from_loc(y_names_remap, vars$y$out) + ) + + remap <- (is.null(keep) || is_false(keep)) + + if (remap) { + by_pos <- match(names(vars$x$key), x_names) + # Only coalesce for equi-joins + eq_idx <- (by$condition == "==") + + if (join == "right") { + exprs[by_pos[eq_idx]] <- map2(y_by[eq_idx], names(vars$x$key)[eq_idx], relexpr_set_alias) + } else { + exprs[by_pos[eq_idx]] <- pmap( + list(x_by[eq_idx], y_by[eq_idx], names(vars$x$key)[eq_idx]), + function(...) { + relexpr_function("___coalesce", list(..1, ..2), alias = ..3) + } + ) + } + } + + out <- rel_project(joined, exprs) + } else { + out <- oo_restore(joined, "___row_number_x", list(x_rel)) + } # if (mutating) + + out <- duckplyr_reconstruct(out, x) + + return(out) +} + +# Needed because dplyr::tbl_vars() calls dplyr::group_vars() which calls dplyr::group_data() +# https://github.com/tidyverse/dplyr/issues/7668 +tbl_vars_safe <- function(x) { + if (inherits(x, "grouped_df")) { + return(tbl_vars(x)) + } + names(x) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/join_ptype_common.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/join_ptype_common.R new file mode 100644 index 000000000..1bea51975 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/join_ptype_common.R @@ -0,0 +1,15 @@ +# https://github.com/tidyverse/dplyr/pull/7029 + +join_ptype_common <- function(x, y, vars, error_call = caller_env()) { + # Explicit `x/y_arg = ""` to avoid auto naming in `cnd$x_arg` + ptype <- try_fetch( + vec_ptype2(x, y, x_arg = "", y_arg = "", call = error_call), + vctrs_error_incompatible_type = function(cnd) { + rethrow_error_join_incompatible_type(cnd, vars, error_call) + } + ) + # Finalize unspecified columns (#6804) + ptype <- vec_ptype_finalise(ptype) + + ptype +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/last.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/last.R new file mode 100644 index 000000000..b976eefde --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/last.R @@ -0,0 +1,23 @@ +#' Retrieve details about the most recent computation +#' +#' Before a result is computed, it is specified as a "relation" object. +#' This function retrieves this object for the last computation that led to the +#' materialization of a data frame. +#' +#' @return A duckdb "relation" object, or `NULL` if no computation has been +#' performed yet. +#' @export +last_rel <- function() { + duckplyr_the$last_rel +} + +# Ellipsis for future extensions +last_rel_store <- function(rel, ...) { + duckplyr_the$last_rel <- rel +} + +on_load({ + options(duckdb.materialize_callback = last_rel_store) +}) + +duckplyr_the <- new_environment() diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join-rd.R new file mode 100644 index 000000000..1f7eed451 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join-rd.R @@ -0,0 +1,14 @@ +#' @title Left join +#' +#' @description This is a method for the [dplyr::left_join()] generic. +#' See "Fallbacks" section for differences in implementation. +#' A `left_join()` keeps all observations in `x`. +#' +#' @inheritParams dplyr::left_join +#' @examples +#' library(duckplyr) +#' left_join(band_members, band_instruments) +#' @seealso [dplyr::left_join()] +#' @rdname left_join.duckplyr_df +#' @name left_join.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join.R new file mode 100644 index 000000000..897db3920 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/left_join.R @@ -0,0 +1,66 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname left_join.duckplyr_df +#' @export +left_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ..., keep = NULL, na_matches = c("na", "never"), multiple = "all", unmatched = "drop", relationship = NULL) { + check_dots_empty0(...) + error_call <- caller_env() + y <- auto_copy(x, y, copy = copy) + + # Our implementation + duckplyr_error <- rel_try(list(name = "left_join", x = x, y = y, args = try_list(by = if (!is.null(by) && !is_cross_by(by)) as_join_by(by), copy = copy, keep = keep, na_matches = na_matches, multiple = multiple, unmatched = unmatched, relationship = relationship)), + #' @section Fallbacks: + #' There is no DuckDB translation in `left_join.duckplyr_df()` + #' - for an implicit cross join, + #' - for a value of the `multiple` argument that isn't the default `"all"`. + #' - for a value of the `unmatched` argument that isn't the default `"drop"`. + #' + #' These features fall back to [dplyr::left_join()], see `vignette("fallback")` for details. + + "No implicit cross joins for {.code left_join()}" = is_cross_by(by), + "{.arg multiple} not supported" = !identical(multiple, "all"), + "{.arg unmatched} not supported" = !identical(unmatched, "drop"), + { + out <- rel_join_impl(x, y, by, "left", na_matches, suffix, keep, error_call) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + left_join <- dplyr$left_join.data.frame + out <- left_join(x, y, by, copy = FALSE, suffix, ..., keep = keep, na_matches = na_matches, multiple = multiple, unmatched = unmatched, relationship = relationship) + return(out) + + # dplyr implementation + check_dots_empty0(...) + y <- auto_copy(x, y, copy = copy) + join_mutate( + x = x, + y = y, + by = by, + type = "left", + suffix = suffix, + na_matches = na_matches, + keep = keep, + multiple = multiple, + unmatched = unmatched, + relationship = relationship, + user_env = caller_env() + ) +} + +duckplyr_left_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- left_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate-rd.R new file mode 100644 index 000000000..ad707ed1b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate-rd.R @@ -0,0 +1,17 @@ +#' @title Create, modify, and delete columns +#' +#' @description This is a method for the [dplyr::mutate()] generic. +#' `mutate()` creates new columns that are functions of existing variables. +#' It can also modify (if the name is the same as an existing column) +#' and delete columns (by setting their value to `NULL`). +#' +#' @inheritParams dplyr::mutate +#' @examples +#' library(duckplyr) +#' df <- data.frame(x = c(1, 2)) +#' df <- mutate(df, y = 2) +#' df +#' @seealso [dplyr::mutate()] +#' @rdname mutate.duckplyr_df +#' @name mutate.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate.R new file mode 100644 index 000000000..2b2a80d52 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/mutate.R @@ -0,0 +1,157 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname mutate.duckplyr_df +#' @export +mutate.duckplyr_df <- function(.data, ..., .by = NULL, .keep = c("all", "used", "unused", "none"), .before = NULL, .after = NULL) { + by_arg <- enquo(.by) + keep <- arg_match(.keep) + + by_names <- eval_select_by(by_arg, .data) + + # Our implementation + duckplyr_error <- rel_try(list(name = "mutate", x = .data, args = try_list(dots = enquos(...), .by = by_arg, .keep = .keep)), + "Implemented for all cases?" = FALSE, + { + rel <- duckdb_rel_from_df(.data) + + if (length(by_names) > 0) { + rel <- oo_prep(rel) + } + + dots <- dplyr_quosures(...) + dots <- fix_auto_name(dots) + names_dots <- names(dots) + + names_used <- character() + names_new <- character() + current_data <- rel_to_df(rel, prudence = "stingy") + + # FIXME: use fewer projections + for (i in seq_along(dots)) { + dot <- dots[[i]] + name_dot <- names_dots[[i]] + + # Try expanding this `dot` if we see it is an `across()` call + expanded <- duckplyr_expand_across(names(current_data), dot) + + if (is.null(expanded)) { + # Nothing we can expand, create a list with just the 1 expression to + # loop over + quos <- set_names(list(dot), name_dot) + } else { + # Actually expanded an `across()` call, make sure to fix up names + # again with the new set of dplyr quosures + quos <- expanded + quos <- fix_auto_name(quos) + } + + names_quos <- names(quos) + + # Set up `exprs` outside the loop. All expressions expanded from an + # `across()` are evaluated together using the same projection. + exprs <- imap(set_names(names(current_data)), relexpr_reference, rel = NULL) + + for (j in seq_along(quos)) { + quo <- quos[[j]] + new <- names_quos[[j]] + + names_new <- c(names_new, new) + + new_pos <- match(new, names(current_data), nomatch = length(current_data) + j) + new_expr <- rel_translate( + quo, + current_data, + alias = new, + partition = by_names, + need_window = TRUE + ) + exprs[[new_pos]] <- new_expr + + new_names_used <- intersect(attr(new_expr, "used"), names(.data)) + names_used <- c(names_used, setdiff(new_names_used, names_used)) + } + + rel <- rel_project(rel, unname(exprs)) + current_data <- rel_to_df(rel, prudence = "stingy") + } + + if (length(by_names) > 0) { + rel <- oo_restore(rel) + } + + out <- duckplyr_reconstruct(rel, .data) + + names_original <- names(.data) + + out <- mutate_relocate( + out = out, + before = {{ .before }}, + after = {{ .after }}, + names_original = names_original + ) + + used <- set_names(names(out) %in% names_used, names(out)) + names_groups <- by_names + + out <- duckplyr_mutate_keep( + out = out, + keep = keep, + used = used, + names_new = names_new, + names_groups = names_groups + ) + + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + mutate <- dplyr$mutate.data.frame + out <- mutate(.data, ..., .by = {{ .by }}, .keep = .keep, .before = {{ .before }}, .after = {{ .after }}) + return(out) + + # dplyr implementation + keep <- arg_match0(.keep, values = c("all", "used", "unused", "none")) + + by <- compute_by({{ .by }}, .data, by_arg = ".by", data_arg = ".data") + + cols <- mutate_cols(.data, dplyr_quosures(...), by) + used <- attr(cols, "used") + + out <- dplyr_col_modify(.data, cols) + + names_original <- names(.data) + + out <- mutate_relocate( + out = out, + before = {{ .before }}, + after = {{ .after }}, + names_original = names_original + ) + + names_new <- names(cols) + names_groups <- by$names + + out <- mutate_keep( + out = out, + keep = keep, + used = used, + names_new = names_new, + names_groups = names_groups + ) + + out +} + +duckplyr_mutate <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- mutate(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/n-col.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/n-col.R new file mode 100644 index 000000000..2e12bd2b4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/n-col.R @@ -0,0 +1,27 @@ +# Copied from dplyr, can be reexported with dplyr > 1.1.4 + +# Masks `ncol()` to avoid accidentally materializing ALTREP duckplyr +# data frames. +ncol <- function(x) { + abort("Use `df_n_col()` or `mat_n_col()` instead.") +} + +# Alternative to `ncol()` which avoids `dim()`. +# +# `dim()` also requires knowing the number of rows, +# which forces ALTREP duckplyr data frames to materialize. +# +# This function makes the same assertion as vctrs about data frame structure, +# i.e. if `x` inherits from `"data.frame"`, then it is a VECSXP with length +# equal to the number of columns. +df_n_col <- function(x) { + x <- unclass(x) + obj_check_list(x) + length(x) +} + +# In a few places we call `ncol()` on matrices, and in those +# cases we want to continue using the base version. +mat_n_col <- function(x) { + base::ncol(x) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/n_groups.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/n_groups.R new file mode 100644 index 000000000..0cfb92d9e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/n_groups.R @@ -0,0 +1,34 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +n_groups.duckplyr_df <- function(x) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code n_groups()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + n_groups <- dplyr$n_groups.data.frame + out <- n_groups(x) + return(out) + + # dplyr implementation + nrow(group_data(x)) +} + +duckplyr_n_groups <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- n_groups(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_by.R new file mode 100644 index 000000000..0efb1c00f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_by.R @@ -0,0 +1,34 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +nest_by.duckplyr_df <- function(.data, ..., .key = "data", .keep = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code nest_by()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + nest_by <- dplyr$nest_by.data.frame + out <- nest_by(.data, ..., .key = .key, .keep = .keep) + return(out) + + # dplyr implementation + .data <- group_by(.data, ...) + # nest_by.grouped_df(.data, .key = .key, .keep = .keep) +} + +duckplyr_nest_by <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- nest_by(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_join.R new file mode 100644 index 000000000..f1f28d19c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/nest_join.R @@ -0,0 +1,137 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +nest_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, keep = NULL, name = NULL, ..., na_matches = c("na", "never"), unmatched = "drop") { + # from dplyr implementation + check_keep(keep) + na_matches <- check_na_matches(na_matches) + + if (is.null(name)) { + name <- as_label(enexpr(y)) + } else { + check_string(name) + } + + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code nest_join()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + x_df <- x + class(x_df) <- setdiff(class(x_df), "duckplyr_df") + y_df <- y + class(y_df) <- setdiff(class(y_df), "duckplyr_df") + nest_join <- dplyr$nest_join.data.frame + out <- nest_join(x_df, y_df, by, copy, keep, name, ..., na_matches = na_matches, unmatched = unmatched) + out <- dplyr_reconstruct(out, x) + return(out) + + # dplyr implementation + check_dots_empty0(...) + check_keep(keep) + na_matches <- check_na_matches(na_matches) + + if (is.null(name)) { + name <- as_label(enexpr(y)) + } else { + check_string(name) + } + + x_names <- tbl_vars(x) + y_names <- tbl_vars(y) + + if (is_cross_by(by)) { + warn_join_cross_by() + by <- new_join_by() + cross <- TRUE + } else { + cross <- FALSE + } + + if (is_null(by)) { + by <- join_by_common(x_names, y_names) + } else { + by <- as_join_by(by) + } + + vars <- join_cols(x_names, y_names, by = by, suffix = c("", ""), keep = keep) + y <- auto_copy(x, y, copy = copy) + + x_in <- as_tibble(x, .name_repair = "minimal") + y_in <- as_tibble(y, .name_repair = "minimal") + + x_key <- set_names(x_in[vars$x$key], names(vars$x$key)) + y_key <- set_names(y_in[vars$y$key], names(vars$x$key)) + + args <- join_cast_common(x_key, y_key, vars) + x_key <- args$x + y_key <- args$y + + condition <- by$condition + filter <- by$filter + + # We always want to retain all of the matches. We never experience a Cartesian + # explosion because `nrow(x) == nrow(out)` is an invariant of `nest_join()`, + # and the whole point of `nest_join()` is to nest all of the matches for that + # row of `x` (#6392). + multiple <- "all" + + # Will be set to `"none"` in `join_rows()`. Because we can't have a Cartesian + # explosion, we don't care about many-to-many relationships. + relationship <- NULL + + rows <- join_rows( + x_key = x_key, + y_key = y_key, + type = "nest", + na_matches = na_matches, + condition = condition, + filter = filter, + cross = cross, + multiple = multiple, + unmatched = unmatched, + relationship = relationship, + user_env = caller_env() + ) + + y_loc <- vec_split(rows$y, rows$x)$val + + out <- set_names(x_in[vars$x$out], names(vars$x$out)) + + # Modify all columns in one step so that we only need to re-group once + new_cols <- vec_cast(out[names(x_key)], x_key) + + y_out <- set_names(y_in[vars$y$out], names(vars$y$out)) + y_out <- map(y_loc, vec_slice, x = y_out) + y_out <- map(y_out, dplyr_reconstruct, template = y) + new_cols[[name]] <- y_out + + out <- dplyr_col_modify(out, new_cols) + dplyr_reconstruct(out, x) +} + +duckplyr_nest_join <- function(x, y, by = NULL, copy = FALSE, keep = NULL, name = NULL, ...) { + if (is.null(name)) { + name <- as_label(enexpr(y)) + } else { + check_string(name) + } + + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- nest_join(x, y, by, copy, keep, name, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/not-supported.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/not-supported.R new file mode 100644 index 000000000..0205d6077 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/not-supported.R @@ -0,0 +1,40 @@ +#' Verbs not implemented in duckplyr +#' +#' The following dplyr generics have no counterpart method in duckplyr. +#' If you want to help add a new verb, +#' please refer to our contributing guide +#' @rdname unsupported +#' @name unsupported +#' @section Unsupported verbs: +#' For these verbs, duckplyr will fall back to dplyr. +#' - [add_count()] +#' - [cross_join()] +#' - [do()] +#' - [group_by()] +#' - [group_indices()] +#' - [group_keys()] +#' - [group_map()] +#' - [group_modify()] +#' - [group_nest()] +#' - [group_size()] +#' - [group_split()] +#' - [group_trim()] +#' - [groups()] +#' - [n_groups()] +#' - [nest_by()] +#' - [nest_join()] +#' - [reframe()] +#' - [rename_with()] +#' - [rows_append()] +#' - [rows_delete()] +#' - [rows_insert()] +#' - [rows_patch()] +#' - [rows_update()] +#' - [rows_upsert()] +#' - [rowwise()] +#' - [setequal()] +#' - [slice_sample()] +#' - [slice_tail()] +#' - [slice()] +#' - [ungroup()] +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/oo.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/oo.R new file mode 100644 index 000000000..e23aecb0f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/oo.R @@ -0,0 +1,77 @@ +oo_force <- function() { + if (dplyr_mode) { + return(TRUE) + } + + if (Sys.getenv("DUCKPLYR_OUTPUT_ORDER") == "TRUE") { + return(TRUE) + } + + return(FALSE) +} + +oo_prep <- function( + rel, + colname = "___row_number", + ..., + extra_cols_pre = character(), + extra_cols_post = character(), + force = oo_force()) { + check_dots_empty0(...) + + if (!force) { + return(rel) + } + + names <- rel_names(rel) + + if (colname %in% names) { + cli::cli_abort("Can't use column {.var {colname}} already present in rel for order preservation") + } + + proj_exprs <- imap(set_names(names), relexpr_reference, rel = NULL) + proj_exprs <- c( + proj_exprs, + if (length(extra_cols_pre)) map(extra_cols_pre, relexpr_constant, val = NA_integer_), + list(relexpr_window( + relexpr_function("row_number", list()), + partitions = list(), + alias = colname + )), + if (length(extra_cols_post)) map(extra_cols_post, relexpr_constant, val = NA_integer_), + NULL + ) + + rel_project(rel, unname(proj_exprs)) +} + +oo_restore <- function(rel, colname = "___row_number", column_rels = list(NULL)) { + rel <- oo_restore_order(rel, colname, column_rels) + oo_restore_cols(rel, colname) +} + +oo_restore_order <- function(rel, colname = "___row_number", column_rels = list(NULL), force = oo_force()) { + if (!force) { + return(rel) + } + + order_exprs <- map2(colname, column_rels, relexpr_reference) + rel_order(rel, order_exprs) +} + +oo_restore_cols <- function(rel, colname = "___row_number", extra = NULL, force = oo_force()) { + if (!force) { + if (!is.null(extra)) { + names <- setdiff(rel_names(rel), extra) + proj_exprs <- imap(set_names(names), relexpr_reference, rel = NULL) + rel <- rel_project(rel, unname(proj_exprs)) + } + + return(rel) + } + + names <- setdiff(rel_names(rel), c(colname, extra)) + proj_exprs <- imap(set_names(names), relexpr_reference, rel = NULL) + + rel_project(rel, unname(proj_exprs)) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/overwrite.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/overwrite.R new file mode 100644 index 000000000..b0d51125d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/overwrite.R @@ -0,0 +1,61 @@ +# Generated by 07-overwrite.R, do not edit by hand + +methods_overwrite_impl <- function() { + vctrs::s3_register("dplyr::add_count", "data.frame", add_count.duckplyr_df) + vctrs::s3_register("dplyr::anti_join", "data.frame", anti_join.duckplyr_df) + vctrs::s3_register("dplyr::arrange", "data.frame", arrange.duckplyr_df) + vctrs::s3_register("dplyr::auto_copy", "data.frame", auto_copy.duckplyr_df) + vctrs::s3_register("dplyr::collect", "data.frame", collect.duckplyr_df) + vctrs::s3_register("dplyr::compute", "data.frame", compute.duckplyr_df) + vctrs::s3_register("dplyr::count", "data.frame", count.duckplyr_df) + vctrs::s3_register("dplyr::cross_join", "data.frame", cross_join.duckplyr_df) + vctrs::s3_register("dplyr::distinct", "data.frame", distinct.duckplyr_df) + vctrs::s3_register("dplyr::do", "data.frame", do.duckplyr_df) + vctrs::s3_register("dplyr::filter", "data.frame", filter.duckplyr_df) + vctrs::s3_register("dplyr::full_join", "data.frame", full_join.duckplyr_df) + vctrs::s3_register("dplyr::group_by", "data.frame", group_by.duckplyr_df) + vctrs::s3_register("dplyr::group_indices", "data.frame", group_indices.duckplyr_df) + vctrs::s3_register("dplyr::group_keys", "data.frame", group_keys.duckplyr_df) + vctrs::s3_register("dplyr::group_map", "data.frame", group_map.duckplyr_df) + vctrs::s3_register("dplyr::group_modify", "data.frame", group_modify.duckplyr_df) + vctrs::s3_register("dplyr::group_nest", "data.frame", group_nest.duckplyr_df) + vctrs::s3_register("dplyr::group_size", "data.frame", group_size.duckplyr_df) + vctrs::s3_register("dplyr::group_split", "data.frame", group_split.duckplyr_df) + vctrs::s3_register("dplyr::group_trim", "data.frame", group_trim.duckplyr_df) + vctrs::s3_register("dplyr::group_vars", "data.frame", group_vars.duckplyr_df) + vctrs::s3_register("dplyr::groups", "data.frame", groups.duckplyr_df) + vctrs::s3_register("dplyr::inner_join", "data.frame", inner_join.duckplyr_df) + vctrs::s3_register("dplyr::intersect", "data.frame", intersect.duckplyr_df) + vctrs::s3_register("dplyr::left_join", "data.frame", left_join.duckplyr_df) + vctrs::s3_register("dplyr::mutate", "data.frame", mutate.duckplyr_df) + vctrs::s3_register("dplyr::n_groups", "data.frame", n_groups.duckplyr_df) + vctrs::s3_register("dplyr::nest_by", "data.frame", nest_by.duckplyr_df) + vctrs::s3_register("dplyr::nest_join", "data.frame", nest_join.duckplyr_df) + vctrs::s3_register("dplyr::pull", "data.frame", pull.duckplyr_df) + vctrs::s3_register("dplyr::reframe", "data.frame", reframe.duckplyr_df) + vctrs::s3_register("dplyr::relocate", "data.frame", relocate.duckplyr_df) + vctrs::s3_register("dplyr::rename", "data.frame", rename.duckplyr_df) + vctrs::s3_register("dplyr::rename_with", "data.frame", rename_with.duckplyr_df) + vctrs::s3_register("dplyr::right_join", "data.frame", right_join.duckplyr_df) + vctrs::s3_register("dplyr::rows_append", "data.frame", rows_append.duckplyr_df) + vctrs::s3_register("dplyr::rows_delete", "data.frame", rows_delete.duckplyr_df) + vctrs::s3_register("dplyr::rows_insert", "data.frame", rows_insert.duckplyr_df) + vctrs::s3_register("dplyr::rows_patch", "data.frame", rows_patch.duckplyr_df) + vctrs::s3_register("dplyr::rows_update", "data.frame", rows_update.duckplyr_df) + vctrs::s3_register("dplyr::rows_upsert", "data.frame", rows_upsert.duckplyr_df) + vctrs::s3_register("dplyr::rowwise", "data.frame", rowwise.duckplyr_df) + vctrs::s3_register("dplyr::select", "data.frame", select.duckplyr_df) + vctrs::s3_register("dplyr::semi_join", "data.frame", semi_join.duckplyr_df) + vctrs::s3_register("dplyr::setdiff", "data.frame", setdiff.duckplyr_df) + vctrs::s3_register("dplyr::setequal", "data.frame", setequal.duckplyr_df) + vctrs::s3_register("dplyr::slice", "data.frame", slice.duckplyr_df) + vctrs::s3_register("dplyr::slice_head", "data.frame", slice_head.duckplyr_df) + vctrs::s3_register("dplyr::slice_sample", "data.frame", slice_sample.duckplyr_df) + vctrs::s3_register("dplyr::slice_tail", "data.frame", slice_tail.duckplyr_df) + vctrs::s3_register("dplyr::summarise", "data.frame", summarise.duckplyr_df) + vctrs::s3_register("dplyr::symdiff", "data.frame", symdiff.duckplyr_df) + vctrs::s3_register("dplyr::transmute", "data.frame", transmute.duckplyr_df) + vctrs::s3_register("dplyr::ungroup", "data.frame", ungroup.duckplyr_df) + vctrs::s3_register("dplyr::union", "data.frame", union.duckplyr_df) + vctrs::s3_register("dplyr::union_all", "data.frame", union_all.duckplyr_df) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/positron.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/positron.R new file mode 100644 index 000000000..d07c324c9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/positron.R @@ -0,0 +1,80 @@ +on_load({ + ark_register_methods() +}) + +ark_register_methods <- function() { + ark_register_method( + "ark_positron_variable_display_value", + "duckplyr_df", + duckplyr_df_variable_display_value + ) + ark_register_method( + "ark_positron_variable_display_type", + "duckplyr_df", + duckplyr_df_variable_display_type + ) + ark_register_method( + "ark_positron_variable_has_children", + "duckplyr_df", + duckplyr_df_variable_has_children + ) + ark_register_method( + "ark_positron_variable_has_viewer", + "duckplyr_df", + duckplyr_df_variable_has_viewer + ) +} + +# Registration either succeeds or silently fails to be as unobtrusive as possible +ark_register_method <- function(generic, class, method) { + tryCatch( + eval(call( + ".ark.register_method", + quote(generic), + quote(class), + quote(method) + )), + error = function(cnd) { + # Errors indicate that we aren't in ark and `.ark.register_method()` + # doesn't exist, or we called it wrong. + NULL + }, + warning = function(cnd) { + # Warnings likely indicate that we are in ark < 0.1.176, where duckplyr + # was not yet allowed to register ark methods, and ark would warn about + # this. + NULL + } + ) +} + +duckplyr_df_variable_display_value <- function(x, ...) { + n_col <- df_n_col(x) + + if (n_col == 1L) { + col_word <- "column" + } else { + col_word <- "columns" + } + + paste0("[? rows x ", n_col, " ", col_word, "] ") +} + +# You don't ever see this on the Positron side because it's a table and we +# show the table icon instead, but we still need this because Ark will otherwise +# try and compute the number of rows (materializing the query). +duckplyr_df_variable_display_type <- function(x, ...) { + "duckplyr_df" +} + +# We disable children and kind for safety, +# until Positron catches errors in materialization +duckplyr_df_variable_has_children <- function(x, ...) { + FALSE +} + +# This is to hide the viewer button, +# until the viewer can handle thrifty duckplyr frames +duckplyr_df_variable_has_viewer <- function(x, ...) { + FALSE +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/print.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/print.R new file mode 100644 index 000000000..b294f6a57 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/print.R @@ -0,0 +1,29 @@ +#' @importFrom pillar tbl_sum +#' @export +tbl_sum.duckplyr_df <- function(x) { + c("A duckplyr data frame" = cli::pluralize("{length(x)} variable{?s}")) +} + +# dim.prudent_duckplyr_df is not called, special dispatch + +#' @importFrom pillar tbl_nrow +#' @export +tbl_nrow.duckplyr_df <- function(x, ...) { + NA_real_ +} + +#' @importFrom pillar tbl_format_setup +#' @export +tbl_format_setup.duckplyr_df <- function( + x, + width, + ..., + setup, + n, + max_extra_cols, + max_footer_lines, + focus +) { + local_options(duckdb.materialize_callback = NULL, duckdb.materialize_message = NULL) + NextMethod() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/project.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/project.R new file mode 100644 index 000000000..e5c054544 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/project.R @@ -0,0 +1,17 @@ +exprs_from_loc <- function(.data, loc) { + nexprs_from_loc(names(.data), loc) +} + +nexprs_from_loc <- function(names, loc) { + stopifnot(is.integer(loc)) + map2(names[loc], names(loc), ~ relexpr_reference(.x, alias = .y)) +} + +nexprs <- function(names) { + map(names, ~ relexpr_reference(.x, alias = .x)) +} + +exprs_project <- function(rel, exprs, .data) { + out_rel <- rel_project(rel, exprs) + duckplyr_reconstruct(out_rel, .data) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/prudence.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/prudence.R new file mode 100644 index 000000000..cbd8d7c7d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/prudence.R @@ -0,0 +1,28 @@ +#' @export +collect.prudent_duckplyr_df <- function(x, ...) { + # Do nothing if already materialized + adjust_prudence <- !is.null(duckdb$rel_from_altrep_df(x, strict = FALSE, allow_materialized = FALSE)) + + out <- new_duckdb_tibble(x, class(x), adjust_prudence = adjust_prudence, prudence = "lavish") + collect(out) +} + +#' @export +as.data.frame.duckplyr_df <- function(x, row.names = NULL, optional = FALSE, ...) { + out <- collect(x) + class(out) <- setdiff(class(out), c("duckplyr_df", "tbl_df", "tbl")) + as.data.frame(out, row.names = row.names, optional = optional, ...) +} + +#' @export +as.data.frame.prudent_duckplyr_df <- function(x, row.names = NULL, optional = FALSE, ...) { + out <- collect(x) + as.data.frame(out, row.names = row.names, optional = optional, ...) +} + +#' @export +as_tibble.duckplyr_df <- function(x, ...) { + out <- collect(x) + class(out) <- setdiff(class(out), "duckplyr_df") + as_tibble(out) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull-rd.R new file mode 100644 index 000000000..6abec300d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull-rd.R @@ -0,0 +1,17 @@ +#' @title Extract a single column +#' +#' @description This is a method for the [dplyr::pull()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `pull()` is similar to `$`. +#' It's mostly useful because it looks a little nicer in pipes, +#' it also works with remote data frames, and it can optionally name the output. +#' +#' @inheritParams dplyr::pull +#' @examples +#' library(duckplyr) +#' pull(mtcars, cyl) +#' pull(mtcars, 1) +#' @seealso [dplyr::pull()] +#' @rdname pull.duckplyr_df +#' @name pull.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull.R new file mode 100644 index 000000000..201c596d0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/pull.R @@ -0,0 +1,59 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname pull.duckplyr_df +#' @export +pull.duckplyr_df <- function(.data, var = -1, name = NULL, ...) { + # dplyr implementation + my_var <- tidyselect::vars_pull(names(.data), !!enquo(var)) + my_name <- enquo(name) + if (!quo_is_null(my_name)) { + my_name <- tidyselect::vars_pull(names(.data), !!my_name) + my_var <- c(my_name, my_var) + } + + loc <- set_names(match(my_var, names(.data)), my_var) + + exprs <- exprs_from_loc(.data, loc) + + duckplyr_error <- rel_try(list(name = "pull", .data = .data), + #' @section Fallbacks: + #' There is no DuckDB translation in `pull.duckplyr_df()` + #' - with a selection that returns no columns. + #' + #' These features fall back to [dplyr::pull()], see `vignette("fallback")` for details. + "Zero-column result set not supported." = (length(exprs) == 0), + { + rel <- duckdb_rel_from_df(.data) + out_rel <- rel_project(rel, exprs) + out <- rel_to_df(out_rel, prudence = "lavish") + out <- tibble::deframe(out) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + pull <- dplyr$pull.data.frame + out <- pull(.data, {{ var }}, {{ name }}, ...) + return(out) + + # dplyr implementation + var <- tidyselect::vars_pull(names(.data), !!enquo(var)) + name <- enquo(name) + if (quo_is_null(name)) { + return(.data[[var]]) + } + name <- tidyselect::vars_pull(names(.data), !!name) + set_names(.data[[var]], nm = .data[[name]]) +} + +duckplyr_pull <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- pull(.data, ...) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_csv_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_csv_duckdb.R new file mode 100644 index 000000000..5829ca435 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_csv_duckdb.R @@ -0,0 +1,42 @@ +#' Read CSV files using DuckDB +#' +#' @description +#' `read_csv_duckdb()` reads a CSV file using DuckDB's `read_csv_auto()` table function. +#' +#' @inheritParams read_file_duckdb +#' @param options Arguments to the DuckDB `read_csv_auto` table function. +#' +#' @seealso [read_parquet_duckdb()], [read_json_duckdb()] +#' +#' @export +#' @examples +#' # Create simple CSV file +#' path <- tempfile("duckplyr_test_", fileext = ".csv") +#' write.csv(data.frame(a = 1:3, b = letters[4:6]), path, row.names = FALSE) +#' +#' # Reading is immediate +#' df <- read_csv_duckdb(path) +#' +#' # Names are always available +#' names(df) +#' +#' # Materialization upon access is turned off by default +#' try(print(df$a)) +#' +#' # Materialize explicitly +#' collect(df)$a +#' +#' # Automatic materialization with prudence = "lavish" +#' df <- read_csv_duckdb(path, prudence = "lavish") +#' df$a +#' +#' # Specify column types +#' read_csv_duckdb( +#' path, +#' options = list(delim = ",", types = list(c("DOUBLE", "VARCHAR"))) +#' ) +read_csv_duckdb <- function(path, ..., prudence = c("thrifty", "lavish", "stingy"), options = list()) { + check_dots_empty() + + read_file_duckdb(path, "read_csv_auto", prudence = prudence, options = options) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_file_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_file_duckdb.R new file mode 100644 index 000000000..3aa8eb200 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_file_duckdb.R @@ -0,0 +1,90 @@ +#' Read files using DuckDB +#' +#' @description +#' `read_file_duckdb()` uses arbitrary readers to read data. +#' See for a documentation +#' of the available functions and their options. +#' To read multiple files with the same schema, +#' pass a wildcard or a character vector to the `path` argument, +#' +#' @inheritParams rlang::args_dots_empty +#' +#' @param path Path to files, glob patterns `*` and `?` are supported. +#' @param table_function The name of a table-valued +#' DuckDB function such as `"read_parquet"`, +#' `"read_csv"`, `"read_csv_auto"` or `"read_json"`. +#' @param prudence Memory protection, controls if DuckDB may convert +#' intermediate results in DuckDB-managed memory to data frames in R memory. +#' +#' - `"thrifty"`: up to a maximum size of 1 million cells, +#' - `"lavish"`: regardless of size, +#' - `"stingy"`: never. +#' +#' The default is `"thrifty"` for the ingestion functions, +#' and may be different for other functions. +#' See `vignette("prudence")` for more information. +#' +#' @param options Arguments to the DuckDB function +#' indicated by `table_function`. +#' +#' @inheritSection duckdb_tibble Fine-tuning prudence +#' +#' @return A duckplyr frame, see [as_duckdb_tibble()] for details. +#' +#' @seealso [read_csv_duckdb()], [read_parquet_duckdb()], [read_json_duckdb()] +#' +#' @rdname read_file_duckdb +#' @export +read_file_duckdb <- function( + path, + table_function, + ..., + prudence = c("thrifty", "lavish", "stingy"), + options = list() +) { + check_dots_empty() + + if (!rlang::is_character(path)) { + cli::cli_abort("{.arg path} must be a character vector.") + } + + remote <- any(grepl("^[a-zA-Z]+://", path)) + + if (length(path) != 1) { + path <- list(path) + } + + duckfun( + table_function, + c(list(path), options), + prudence = prudence, + remote = remote + ) +} + +duckfun <- function(table_function, args, ..., prudence, remote = FALSE) { + if (!is.list(args)) { + cli::cli_abort("{.arg args} must be a list.") + } + if (length(args) == 0) { + cli::cli_abort("{.arg args} must not be empty.") + } + + # FIXME: For some reason, it's important to create an alias here + con <- get_default_duckdb_connection() + + # FIXME: Provide better duckdb API + path <- args[[1]] + options <- args[-1] + + rel <- duckdb$rel_from_table_function( + con, + table_function, + list(path), + options + ) + + meta_rel_register_file(rel, table_function, path, options) + + rel_to_df(rel, prudence = prudence, remote = remote) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_json_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_json_duckdb.R new file mode 100644 index 000000000..cf252b7d5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_json_duckdb.R @@ -0,0 +1,26 @@ +#' Read JSON files using DuckDB +#' +#' @description +#' `read_json_duckdb()` reads a JSON file using DuckDB's `read_json()` table function. +#' +#' @inheritParams read_file_duckdb +#' @param options Arguments to the DuckDB `read_json` table function. +#' +#' @seealso [read_csv_duckdb()], [read_parquet_duckdb()] +#' +#' @export +#' @examplesIf identical(Sys.getenv("IN_PKGDOWN"), "TRUE") +#' +#' # Create and read a simple JSON file +#' path <- tempfile("duckplyr_test_", fileext = ".json") +#' writeLines('[{"a": 1, "b": "x"}, {"a": 2, "b": "y"}]', path) +#' +#' # Reading needs the json extension +#' db_exec("INSTALL json") +#' db_exec("LOAD json") +#' read_json_duckdb(path) +read_json_duckdb <- function(path, ..., prudence = c("thrifty", "lavish", "stingy"), options = list()) { + check_dots_empty() + + read_file_duckdb(path, "read_json", prudence = prudence, options = options) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_parquet_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_parquet_duckdb.R new file mode 100644 index 000000000..aa53751c9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/read_parquet_duckdb.R @@ -0,0 +1,16 @@ +#' Read Parquet files using DuckDB +#' +#' @description +#' `read_parquet_duckdb()` reads a Parquet file using DuckDB's `read_parquet()` table function. +#' +#' @inheritParams read_file_duckdb +#' @param options Arguments to the DuckDB `read_parquet` table function. +#' +#' @seealso [read_csv_duckdb()], [read_json_duckdb()] +#' +#' @export +read_parquet_duckdb <- function(path, ..., prudence = c("thrifty", "lavish", "stingy"), options = list()) { + check_dots_empty() + + read_file_duckdb(path, "read_parquet", prudence = prudence, options = options) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/reframe.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/reframe.R new file mode 100644 index 000000000..d1f1881cb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/reframe.R @@ -0,0 +1,43 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +reframe.duckplyr_df <- function(.data, ..., .by = NULL) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code reframe()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + reframe <- dplyr$reframe.data.frame + out <- reframe(.data, ..., .by = {{ .by }}) + return(out) + + # dplyr implementation + by <- compute_by({{ .by }}, .data, by_arg = ".by", data_arg = ".data") + + cols <- summarise_cols(.data, dplyr_quosures(...), by, "reframe") + out <- summarise_build(by, cols) + + if (!is_tibble(.data)) { + # The `by` group data we build from is always a tibble, + # so we have to manually downcast as needed + out <- as.data.frame(out) + } + + out +} + +duckplyr_reframe <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- reframe(.data, ...) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-df.R new file mode 100644 index 000000000..d6fcbe12f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-df.R @@ -0,0 +1,56 @@ +rel_from_df <- function(df) { + # FIXME: make generic + stopifnot(is.data.frame(df)) + new_relational(list(df), class = "relational_df") +} + +#' @export +rel_to_df.relational_df <- function(rel, ...) { + rel[[1L]] +} + +#' @export +rel_filter.relational_df <- function(rel, exprs, ...) {} + +#' @export +rel_project.relational_df <- function(rel, exprs, ...) {} + +#' @export +rel_aggregate.relational_df <- function(rel, groups, aggregates, ...) {} + +#' @export +rel_order.relational_df <- function(rel, orders, ...) {} + +#' @export +rel_join.relational_df <- function(left, right, conds, ...) {} + +#' @export +rel_limit.relational_df <- function(rel, n, ...) {} + +#' @export +rel_distinct.relational_df <- function(rel, ...) {} + +#' @export +rel_set_intersect.relational_df <- function(rel_a, rel_b, ...) {} + +#' @export +rel_set_diff.relational_df <- function(rel_a, rel_b, ...) {} + +#' @export +rel_set_symdiff.relational_df <- function(rel_a, rel_b, ...) {} + +#' @export +rel_union_all.relational_df <- function(rel_a, rel_b, ...) {} + + +#' @export +rel_explain.relational_df <- function(rel, ...) {} + +#' @export +rel_alias.relational_df <- function(rel, ...) {} + +#' @export +rel_set_alias.relational_df <- function(rel, alias, ...) {} + +#' @export +rel_names.relational_df <- function(rel, ...) {} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-duckdb.R new file mode 100644 index 000000000..351f40565 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-duckdb.R @@ -0,0 +1,510 @@ +# To be moved to duckdb + +# singleton DuckDB instance since we need only one really +# we need a finalizer to disconnect on exit otherwise we get a warning +default_duckdb_connection <- new.env(parent = emptyenv()) +get_default_duckdb_connection <- function() { + if (is.null(default_duckdb_connection$con)) { + default_duckdb_connection$con <- create_default_duckdb_connection() + reg.finalizer(default_duckdb_connection, onexit = TRUE, reset_default_duckdb_connection) + } + default_duckdb_connection$con +} + +reset_default_duckdb_connection <- function(e = NULL) { + if (is.null(e)) { + e <- default_duckdb_connection + } + DBI::dbDisconnect(e$con) + # duckdb::duckdb_shutdown(e$con@driver) + e$con <- NULL +} + +create_default_duckdb_connection <- function() { + dbroot <- Sys.getenv("DUCKPLYR_TEMP_DIR", file.path(tempdir(), "duckplyr")) + dbdir <- tempfile("duckplyr", tmpdir = dbroot, fileext = ".duckdb") + dir.create(dbroot, recursive = TRUE, showWarnings = FALSE) + + drv <- duckdb::duckdb(dbdir = dbdir) + con <- DBI::dbConnect(drv) + + DBI::dbExecute(con, paste0("pragma temp_directory='", dbroot, "'")) + + duckdb$rapi_load_rfuns(drv@database_ref) + + for (i in seq_along(duckplyr_macros)) { + sql <- paste0('CREATE MACRO "', names(duckplyr_macros)[[i]], '"', duckplyr_macros[[i]]) + DBI::dbExecute(con, sql) + } + + con +} + +duckdb_rel_from_df <- function(df, call = caller_env()) { + # FIXME: make generic + stopifnot(is.data.frame(df)) + + rel <- duckdb$rel_from_altrep_df(df, strict = FALSE, allow_materialized = FALSE) + if (!is.null(rel)) { + # Once we're here, we know it's an ALTREP data frame + # We don't get here if it's already materialized + + rel_names <- duckdb$rapi_rel_names(rel) + if (!identical(rel_names, names(df))) { + # This can happen when column names change for an existing relational data frame + exprs <- nexprs_from_loc(rel_names, set_names(seq_along(df), names(df))) + rel <- rel_project.duckdb_relation(rel, exprs) + } + return(rel) + } + + if (!is_duckdb_tibble(df)) { + df <- as_duckplyr_df_impl(df) + } + + out <- check_df_for_rel(df, call) + + meta_rel_register_df(out, df) + + out + + # Causes protection errors + # duckdb$rel_from_df(get_default_duckdb_connection(), df) +} + +# FIXME: This should be duckdb's responsibility +check_df_for_rel <- function(df, call = caller_env()) { + rni <- .row_names_info(df, 0L) + if (is.character(rni)) { + cli::cli_abort("Need data frame without row names to convert to relational, got character row names.", call = call) + } + if (length(rni) != 0) { + if (length(rni) != 2L || !is.na(rni[[1]])) { + cli::cli_abort("Need data frame without row names to convert to relational, got numeric row names.", call = call) + } + } + + if (length(df) == 0L) { + cli::cli_abort("Can't convert empty data frame to relational.", call = call) + } + + for (i in seq_along(df)) { + col <- .subset2(df, i) + if (!is.null(names(col))) { + cli::cli_abort("Can't convert named vectors to relational. Affected column: {.var {names(df)[[i]]}}.", call = call) + } + if (!is.null(dim(col))) { + cli::cli_abort("Can't convert arrays or matrices to relational. Affected column: {.var {names(df)[[i]]}}.", call = call) + } + if (isS4(col)) { + cli::cli_abort("Can't convert S4 columns to relational. Affected column: {.var {names(df)[[i]]}}.", call = call) + } + + # Factors: https://github.com/duckdb/duckdb/issues/8561 + + # When adding new classes, make sure to adapt the first test in test-relational-duckdb.R + + col_class <- class(col) + if (length(col_class) == 1) { + valid <- col_class %in% c("logical", "integer", "numeric", "character", "Date", "difftime") + } else if (length(col_class) == 2) { + valid <- identical(col_class, c("POSIXct", "POSIXt")) || identical(col_class, c("hms", "difftime")) + } else { + valid <- FALSE + } + if (!valid) { + cli::cli_abort("Can't convert columns of class {.cls {col_class}} to relational. Affected column: {.var {names(df)[[i]]}}.", call = call) + } + } + + # FIXME: For some reason, it's important to create an alias here + con <- get_default_duckdb_connection() + + # FIXME: For some other reason, it seems crucial to assign the result to a + # variable before returning it + out <- duckdb$rel_from_df(con, df) + + roundtrip <- duckdb$rapi_rel_to_altrep(out) + if (Sys.getenv("DUCKPLYR_CHECK_ROUNDTRIP") == "TRUE") { + rlang::with_options(duckdb.materialize_callback = NULL, { + for (i in seq_along(df)) { + if (!identical(df[[i]], roundtrip[[i]])) { + cli::cli_abort("Imperfect roundtrip. Affected column: {.var {names(df)[[i]]}}.", call = call) + } + } + }) + } else { + for (i in seq_along(df)) { + df_attrib <- attributes(df[[i]]) + roundtrip_attrib <- attributes(roundtrip[[i]]) + if (!identical(df_attrib, roundtrip_attrib)) { + cli::cli_abort("Attributes are lost during conversion. Affected column: {.var {names(df)[[i]]}}.", call = call) + } + } + } + + out +} + +# https://github.com/r-lib/vctrs/issues/1956 +vec_ptype_safe <- function(x) { + if (inherits(x, "Date")) { + return(new_date()) + } + + exec(structure, vec_ptype(unclass(x)), !!!attributes(x)) +} + +#' @export +rel_to_df.duckdb_relation <- function(rel, ..., prudence = NULL, remote = FALSE) { + if (is.null(prudence)) { + cli::cli_abort("Argument {.arg {prudence}} is missing.") + } + + # Same code in new_duckdb_tibble(), to avoid recursion there + prudence_parsed <- prudence_parse(prudence, remote) + out <- duckdb$rel_to_altrep( + rel, + n_rows = prudence_parsed$n_rows, + n_cells = prudence_parsed$n_cells + ) + + new_duckdb_tibble(out, prudence = prudence) +} + +#' @export +rel_filter.duckdb_relation <- function(rel, exprs, ...) { + duckdb_exprs <- to_duckdb_exprs(exprs) + out <- duckdb$rel_filter(rel, duckdb_exprs) + + meta_rel_register(out, expr(duckdb$rel_filter( + !!meta_rel_get(rel)$name, + list(!!!to_duckdb_exprs_meta(exprs)) + ))) + + out +} + +#' @export +rel_project.duckdb_relation <- function(rel, exprs, ...) { + duckdb_exprs <- to_duckdb_exprs(exprs) + + out <- duckdb$rel_project(rel, duckdb_exprs) + + meta_rel_register(out, expr(duckdb$rel_project( + !!meta_rel_get(rel)$name, + list(!!!to_duckdb_exprs_meta(exprs)) + ))) + + check_duplicate_names(out) + + out +} + +#' @export +rel_aggregate.duckdb_relation <- function(rel, groups, aggregates, ...) { + duckdb_groups <- to_duckdb_exprs(groups) + duckdb_aggregates <- to_duckdb_exprs(aggregates) + + out <- duckdb$rel_aggregate( + rel, + groups = duckdb_groups, + aggregates = duckdb_aggregates + ) + + meta_rel_register(out, expr(duckdb$rel_aggregate( + !!meta_rel_get(rel)$name, + groups = list(!!!to_duckdb_exprs_meta(groups)), + aggregates = list(!!!to_duckdb_exprs_meta(aggregates)) + ))) + + check_duplicate_names(out) + + out +} + +#' @export +rel_order.duckdb_relation <- function(rel, orders, ascending = NULL, ...) { + duckdb_orders <- to_duckdb_exprs(orders) + + out <- duckdb$rel_order(rel, duckdb_orders, ascending) + + meta_rel_register(out, expr(duckdb$rel_order( + !!meta_rel_get(rel)$name, + list(!!!to_duckdb_exprs_meta(orders)) + ))) + + out +} + +#' @export +rel_join.duckdb_relation <- function(left, right, conds, join, join_ref_type, ...) { + duckdb_conds <- to_duckdb_exprs(conds) + if (join == "full") { + join <- "outer" + } + + if (join_ref_type == "regular") { + # Compatibility with older duckdb versions + out <- duckdb$rel_join(left, right, duckdb_conds, join) + + meta_rel_register(out, expr(duckdb$rel_join( + !!meta_rel_get(left)$name, + !!meta_rel_get(right)$name, + list(!!!to_duckdb_exprs_meta(conds)), + !!join + ))) + } else { + out <- duckdb$rel_join(left, right, duckdb_conds, join, join_ref_type) + + meta_rel_register(out, expr(duckdb$rel_join( + !!meta_rel_get(left)$name, + !!meta_rel_get(right)$name, + list(!!!to_duckdb_exprs_meta(conds)), + !!join, + !!join_ref_type + ))) + } + + check_duplicate_names(out) + + out +} + +#' @export +rel_limit.duckdb_relation <- function(rel, n, ...) { + out <- duckdb$rel_limit(rel, n) + + meta_rel_register(out, expr(duckdb$rel_limit( + !!meta_rel_get(rel)$name, + !!n + ))) + + out +} + +#' @export +rel_distinct.duckdb_relation <- function(rel, ...) { + out <- duckdb$rel_distinct(rel) + + meta_rel_register(out, expr(duckdb$rel_distinct( + !!meta_rel_get(rel)$name + ))) + + out +} + +#' @export +rel_set_intersect.duckdb_relation <- function(rel_a, rel_b, ...) { + out <- duckdb$rel_set_intersect(rel_a, rel_b) + + meta_rel_register(out, expr(duckdb$rel_set_intersect( + !!meta_rel_get(rel_a)$name, + !!meta_rel_get(rel_b)$name + ))) + + out +} + +#' @export +rel_set_diff.duckdb_relation <- function(rel_a, rel_b, ...) { + out <- duckdb$rel_set_diff(rel_a, rel_b) + + meta_rel_register(out, expr(duckdb$rel_set_diff( + !!meta_rel_get(rel_a)$name, + !!meta_rel_get(rel_b)$name + ))) + + out +} + +#' @export +rel_set_symdiff.duckdb_relation <- function(rel_a, rel_b, ...) { + out <- duckdb$rel_set_symdiff(rel_a, rel_b) + + meta_rel_register(out, expr(duckdb$rel_set_symdiff( + !!meta_rel_get(rel_a)$name, + !!meta_rel_get(rel_b)$name + ))) + + out +} + +#' @export +rel_union_all.duckdb_relation <- function(rel_a, rel_b, ...) { + out <- duckdb$rel_union_all(rel_a, rel_b) + + meta_rel_register(out, expr(duckdb$rel_union_all( + !!meta_rel_get(rel_a)$name, + !!meta_rel_get(rel_b)$name + ))) + + out +} + +#' @export +rel_explain.duckdb_relation <- function(rel, ...) { + duckdb$rel_explain(rel) +} + +#' @export +rel_alias.duckdb_relation <- function(rel, ...) {} + +#' @export +rel_set_alias.duckdb_relation <- function(rel, alias, ...) { + out <- duckdb$rel_set_alias(rel, alias) + + meta_rel_register(out, expr(duckdb$rel_set_alias( + !!meta_rel_get(rel)$name, + !!alias + ))) + + out +} + +#' @export +rel_names.duckdb_relation <- function(rel, ...) { + duckdb$rapi_rel_names(rel) +} + +to_duckdb_exprs <- function(exprs) { + lapply(exprs, to_duckdb_expr) +} + +to_duckdb_expr <- function(x) { + switch(class(x)[[1]], + relational_relexpr_reference = { + out <- duckdb$expr_reference(x$name, if (is.null(x$rel)) "" else x$rel) + if (!is.null(x$alias)) { + duckdb$expr_set_alias(out, x$alias) + } + out + }, + relational_relexpr_comparison = { + out <- duckdb$expr_comparison(x$cmp_op, to_duckdb_exprs(x$exprs)) + if (!is.null(x$alias)) { + duckdb$expr_set_alias(out, x$alias) + } + out + }, + relational_relexpr_function = { + out <- duckdb$expr_function(x$name, to_duckdb_exprs(x$args)) + if (!is.null(x$alias)) { + duckdb$expr_set_alias(out, x$alias) + } + out + }, + relational_relexpr_window = { + out <- duckdb$expr_window( + to_duckdb_expr(x$expr), + to_duckdb_exprs(x$partitions), + to_duckdb_exprs(x$order_bys), + offset_expr = to_duckdb_expr(x$offset_expr), + default_expr = to_duckdb_expr(x$default_expr) + ) + if (!is.null(x$alias)) { + duckdb$expr_set_alias(out, x$alias) + } + out + }, + relational_relexpr_constant = { + # FIXME: Should be duckdb's responsibility + # Example: https://github.com/dschafer/activatr/issues/18 + check_df_for_rel(vctrs::new_data_frame(list(constant = x$val))) + + out <- duckdb$expr_constant(x$val) + + if (!is.null(x$alias)) { + duckdb$expr_set_alias(out, x$alias) + } + out + }, + NULL = NULL, + cli::cli_abort("Unknown expr class: {.cls {class(x)}}") + ) +} + +to_duckdb_exprs_meta <- function(exprs) { + lapply(exprs, to_duckdb_expr_meta) +} + +to_duckdb_expr_meta <- function(x) { + switch(class(x)[[1]], + relational_relexpr_reference = { + args <- list(x$name) + if (!is.null(x$rel)) { + args <- c(args, meta_rel_get(x$rel)$name) + } + out <- expr(duckdb$expr_reference(!!!args)) + if (!is.null(x$alias)) { + out <- expr({ + tmp_expr <- !!out + duckdb$expr_set_alias(tmp_expr, !!x$alias) + tmp_expr + }) + } + out + }, + relational_relexpr_comparison = { + out <- expr(duckdb$expr_comparison(!!x$cmp_op, list(!!!to_duckdb_exprs_meta(x$exprs)))) + if (!is.null(x$alias)) { + out <- expr({ + tmp_expr <- !!out + duckdb$expr_set_alias(tmp_expr, !!x$alias) + tmp_expr + }) + } + out + }, + relational_relexpr_function = { + meta_macro_register(x$name) + out <- expr(duckdb$expr_function(!!x$name, list(!!!to_duckdb_exprs_meta(x$args)))) + if (!is.null(x$alias)) { + out <- expr({ + tmp_expr <- !!out + duckdb$expr_set_alias(tmp_expr, !!x$alias) + tmp_expr + }) + } + out + }, + relational_relexpr_window = { + out <- expr(duckdb$expr_window( + !!to_duckdb_expr_meta(x$expr), + list(!!!to_duckdb_exprs_meta(x$partitions)), + list(!!!to_duckdb_exprs_meta(x$order_bys)), + offset_expr = !!to_duckdb_expr_meta(x$offset_expr), + default_expr = !!to_duckdb_expr_meta(x$default_expr) + )) + if (!is.null(x$alias)) { + out <- expr({ + tmp_expr <- !!out + duckdb$expr_set_alias(tmp_expr, !!x$alias) + tmp_expr + }) + } + out + }, + relational_relexpr_constant = { + out <- expr(duckdb$expr_constant(!!x$val)) + + if (!is.null(x$alias)) { + out <- expr({ + tmp_expr <- !!out + duckdb$expr_set_alias(tmp_expr, !!x$alias) + tmp_expr + }) + } + out + }, + NULL = expr(NULL), + cli::cli_abort("Unknown expr class: {.cls {class(x)}}") + ) +} + +check_duplicate_names <- function(rel) { + # https://github.com/duckdb/duckdb/discussions/14682 + if (anyDuplicated(tolower(duckdb$rel_names(rel)))) { + cli::cli_abort("Column names are case-insensitive in duckdb, fallback required.") + } +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-expr.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-expr.R new file mode 100644 index 000000000..f75dc213b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-expr.R @@ -0,0 +1,152 @@ +#' Relational expressions +#' +#' @description +#' These functions provide a backend-agnostic way to construct expression trees +#' built of column references, constants, and functions. +#' All subexpressions in an expression tree can have an alias. +#' +#' `new_relexpr()` constructs an object of class `"relational_relexpr"`. +#' It is used by the higher-level constructors, +#' users should rarely need to call it directly. +#' +#' @param x An object. +#' @param class Classes added in front of the `"relational_relexpr"` base class. +#' +#' @name new_relexpr +#' @return an object of class `"relational_relexpr"` +#' @export +#' @examples +#' relexpr_set_alias( +#' alias = "my_predicate", +#' relexpr_function( +#' "<", +#' list( +#' relexpr_reference("my_number"), +#' relexpr_constant(42) +#' ) +#' ) +#' ) +new_relexpr <- function(x, class = NULL) { + structure(x, class = unique(c(class, "relational_relexpr"), fromLast = TRUE)) +} + +#' relexpr_reference +#' +#' `relexpr_reference()` constructs a reference to a column. +#' +#' @param name The name of the column or function to reference. +#' @param rel The name of the relation to reference. +#' @param alias An alias for the new expression. +#' @rdname new_relexpr +#' @return an object of class `"relational_relexpr"` +#' @export +relexpr_reference <- function(name, rel = NULL, alias = NULL) { + stopifnot(is_string(name)) + stopifnot(is.null(rel) || inherits(rel, "duckdb_relation")) + stopifnot(is.null(alias) || is_string(alias)) + new_relexpr(list(name = name, rel = rel, alias = alias), class = "relational_relexpr_reference") +} + +#' relexpr_constant +#' +#' `relexpr_constant()` wraps a constant value. +#' +#' @param val The value to use in the constant expression. +#' @rdname new_relexpr +#' @return an object of class `"relational_relexpr"` +#' @export +relexpr_constant <- function(val, alias = NULL) { + stopifnot(length(val) == 1) + stopifnot(is.null(alias) || is_string(alias)) + new_relexpr(list(val = val, alias = alias), class = "relational_relexpr_constant") +} + +#' relexpr_function +#' +#' `relexpr_function()` applies a function. +#' The arguments to this function are a list of other expression objects. +#' +#' @param args Function arguments, a list of `expr` objects. +#' @rdname new_relexpr +#' @return an object of class `"relational_relexpr"` +#' @export +relexpr_function <- function(name, args, alias = NULL) { + stopifnot(is_string(name)) + stopifnot(is.list(args)) + stopifnot(is.null(alias) || is_string(alias)) + new_relexpr(list(name = name, args = args, alias = alias), class = "relational_relexpr_function") +} + +#' relexpr_comparison +#' +#' `relexpr_comparison()` wraps a comparison expression. +#' +#' @param exprs Expressions to compare, a list of `expr` objects. +#' @param cmp_op Comparison operator, e.g., `"<"` or `"=="`. +#' @rdname new_relexpr +#' @return an object of class `"relational_relexpr"` +#' @export +relexpr_comparison <- function(cmp_op, exprs) { + stopifnot(is_string(cmp_op)) + stopifnot(is.list(exprs)) + new_relexpr(list(cmp_op = cmp_op, exprs = exprs), class = "relational_relexpr_comparison") +} + + +#' relexpr_window +#' +#' `relexpr_window()` applies a function over a window, +#' similarly to the SQL `OVER` clause. +#' +#' @param partitions Partitions, a list of `expr` objects. +#' @param order_bys which variables to order results by (list). +#' @param offset_expr offset relational expression. +#' @param default_expr default relational expression. +#' @rdname new_relexpr +#' @export +relexpr_window <- function( + expr, + partitions, + order_bys = list(), + offset_expr = NULL, + default_expr = NULL, + alias = NULL) { + stopifnot(inherits(expr, "relational_relexpr")) + stopifnot(is.list(partitions)) + stopifnot(is.list(order_bys)) + stopifnot(is.null(offset_expr) || inherits(offset_expr, "relational_relexpr")) + stopifnot(is.null(default_expr) || inherits(default_expr, "relational_relexpr")) + stopifnot(is.null(alias) || is_string(alias)) + new_relexpr( + list( + expr = expr, + partitions = partitions, + order_bys = order_bys, + offset_expr = offset_expr, + default_expr = default_expr, + alias = alias + ), + class = "relational_relexpr_window" + ) +} + +#' relexpr_set_alias +#' +#' `relexpr_set_alias()` assigns an alias to an expression. +#' +#' @param expr An `expr` object. +#' @rdname new_relexpr +#' @return an object of class `"relational_relexpr"` +#' @export +relexpr_set_alias <- function(expr, alias = NULL) { + stopifnot(inherits(expr, "relational_relexpr")) + stopifnot(is.null(alias) || is_string(alias)) + expr$alias <- alias + expr +} + +#' @export +print.relational_relexpr <- function(x, ...) { + utils::str(x) + invisible(x) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-rel.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-rel.R new file mode 100644 index 000000000..149c86a6d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational-rel.R @@ -0,0 +1,347 @@ +rel_stats_env <- new.env(parent = emptyenv(), size = 937L) + +rel_stats_clean <- function() { + rm(list = ls(rel_stats_env, all.names = TRUE), pos = rel_stats_env) +} + +rel_stats_get <- function() { + arrange(tibble::enframe(unlist(as.list(rel_stats_env)), "fun", "count"), desc(count)) +} + +#' Relational implementer's interface +#' +#' @description +#' The constructor and generics described here define a class +#' that helps separating dplyr's user interface from the actual underlying operations. +#' In the longer term, this will help packages that implement the dplyr interface +#' (such as \pkg{dbplyr}, \pkg{dtplyr}, \pkg{arrow} and similar) +#' to focus on the core details of their functionality, +#' rather than on the intricacies of dplyr's user interface. +#' +#' `new_relational()` constructs an object of class `"relational"`. +#' Users are encouraged to provide the `class` argument. +#' The typical use case will be to create a wrapper function. +#' +#' @param ... Passed on to [structure()]. +#' @param class Classes added in front of the `"relational"` base class. +#' +#' @return +#' - `new_relational()` returns a new relational object. +#' - `rel_to_df()` returns a data frame. +#' - `rel_names()` returns a character vector. +#' - All other generics return a modified relational object. +#' @name new_relational +#' @export +#' @examples +#' new_dfrel <- function(x) { +#' stopifnot(is.data.frame(x)) +#' new_relational(list(x), class = "dfrel") +#' } +#' mtcars_rel <- new_dfrel(mtcars[1:5, 1:4]) +new_relational <- function(..., class = NULL) { + structure(..., class = unique(c(class, "relational"), fromLast = TRUE)) +} + +#' rel_to_df() +#' +#' `rel_to_df()` extracts a data frame representation from a relational object, +#' to be used by [dplyr::collect()]. +#' +#' @param rel,rel_a,rel_b,left,right A relational object. +#' @param ... Reserved for future extensions, must be empty. +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_to_df.dfrel <- function(rel, ...) { +#' unclass(rel)[[1]] +#' } +#' rel_to_df(mtcars_rel) +rel_to_df <- function(rel, ...) { + rel_stats_env$rel_to_df <- (rel_stats_env$rel_to_df %||% 0L) + 1L + UseMethod("rel_to_df") +} + +#' rel_filter +#' +#' `rel_filter()` keeps rows that match a predicate, +#' to be used by [dplyr::filter()]. +#' +#' @param exprs A list of `"relational_relexpr"` objects to filter by, +#' created by [new_relexpr()]. +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_filter.dfrel <- function(rel, exprs, ...) { +#' df <- unclass(rel)[[1]] +#' +#' # A real implementation would evaluate the predicates defined +#' # by the exprs argument +#' new_dfrel(df[seq_len(min(3, nrow(df))), ]) +#' } +#' +#' rel_filter( +#' mtcars_rel, +#' list( +#' relexpr_function( +#' "gt", +#' list(relexpr_reference("cyl"), relexpr_constant("6")) +#' ) +#' ) +#' ) +rel_filter <- function(rel, exprs, ...) { + rel_stats_env$rel_filter <- (rel_stats_env$rel_filter %||% 0L) + 1L + UseMethod("rel_filter") +} + +#' rel_project +#' +#' `rel_project()` selects columns or creates new columns, +#' to be used by [dplyr::select()], [dplyr::rename()], +#' [dplyr::mutate()], [dplyr::relocate()], and others. +#' +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_project.dfrel <- function(rel, exprs, ...) { +#' df <- unclass(rel)[[1]] +#' +#' # A real implementation would evaluate the expressions defined +#' # by the exprs argument +#' new_dfrel(df[seq_len(min(3, ncol(df)))]) +#' } +#' +#' rel_project( +#' mtcars_rel, +#' list(relexpr_reference("cyl"), relexpr_reference("disp")) +#' ) +rel_project <- function(rel, exprs, ...) { + rel_stats_env$rel_project <- (rel_stats_env$rel_project %||% 0L) + 1L + UseMethod("rel_project") +} + +#' rel_aggregate +#' +#' `rel_aggregate()` combines several rows into one, +#' to be used by [dplyr::summarize()]. +#' +#' @param groups A list of expressions to group by. +#' @param aggregates A list of expressions with aggregates to compute. +#' @rdname new_relational +#' @export +rel_aggregate <- function(rel, groups, aggregates, ...) { + rel_stats_env$rel_aggregate <- (rel_stats_env$rel_aggregate %||% 0L) + 1L + UseMethod("rel_aggregate") +} + +#' rel_order +#' +#' `rel_order()` reorders rows by columns or expressions, +#' to be used by [dplyr::arrange()]. +#' +#' @param orders A list of expressions to order by. +#' @param ascending A logical vector describing the sort order. +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_order.dfrel <- function(rel, exprs, ...) { +#' df <- unclass(rel)[[1]] +#' +#' # A real implementation would evaluate the expressions defined +#' # by the exprs argument +#' new_dfrel(df[order(df[[1]]), ]) +#' } +#' +#' rel_order( +#' mtcars_rel, +#' list(relexpr_reference("mpg")) +#' ) +rel_order <- function(rel, orders, ascending, ...) { + rel_stats_env$rel_order <- (rel_stats_env$rel_order %||% 0L) + 1L + UseMethod("rel_order") +} + +#' rel_join +#' +#' `rel_join()` joins or merges two tables, +#' to be used by [dplyr::left_join()], [dplyr::right_join()], +#' [dplyr::inner_join()], [dplyr::full_join()], [dplyr::cross_join()], +#' [dplyr::semi_join()], and [dplyr::anti_join()]. +#' +#' @param conds A list of expressions to use for the join. +#' @param join The type of join. +#' @param join_ref_type The ref type of join. +#' @rdname new_relational +#' @export +#' @examplesIf requireNamespace("dplyr", quietly = TRUE) +#' rel_join.dfrel <- function(left, right, conds, join, ...) { +#' left_df <- unclass(left)[[1]] +#' right_df <- unclass(right)[[1]] +#' +#' # A real implementation would evaluate the expressions +#' # defined by the conds argument, +#' # use different join types based on the join argument, +#' # and implement the join itself instead of relaying to left_join(). +#' new_dfrel(dplyr::left_join(left_df, right_df)) +#' } +#' +#' rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel) +rel_join <- function(left, + right, + conds, + join = c("inner", "left", "right", "outer", "cross", "semi", "anti"), + join_ref_type = c("regular", "natural", "cross", "positional", "asof"), + ...) { + rel_stats_env$rel_join <- (rel_stats_env$rel_join %||% 0L) + 1L + UseMethod("rel_join") +} + +#' rel_limit +#' +#' `rel_limit()` limits the number of rows in a table, +#' to be used by [utils::head()]. +#' +#' @param n The number of rows. +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_limit.dfrel <- function(rel, n, ...) { +#' df <- unclass(rel)[[1]] +#' +#' new_dfrel(df[seq_len(n), ]) +#' } +#' +#' rel_limit(mtcars_rel, 3) +rel_limit <- function(rel, n, ...) { + rel_stats_env$rel_limit <- (rel_stats_env$rel_limit %||% 0L) + 1L + UseMethod("rel_limit") +} + +#' rel_distinct() +#' +#' `rel_distinct()` only keeps the distinct rows in a table, +#' to be used by [dplyr::distinct()]. +#' +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_distinct.dfrel <- function(rel, ...) { +#' df <- unclass(rel)[[1]] +#' +#' new_dfrel(df[!duplicated(df), ]) +#' } +#' +#' rel_distinct(new_dfrel(mtcars[1:3, 1:4])) +rel_distinct <- function(rel, ...) { + rel_stats_env$rel_distinct <- (rel_stats_env$rel_distinct %||% 0L) + 1L + UseMethod("rel_distinct") +} + +#' rel_set_intersect() +#' +#' `rel_set_intersect()` returns rows present in both tables, +#' to be used by [intersect()]. +#' +#' @rdname new_relational +#' @export +rel_set_intersect <- function(rel_a, rel_b, ...) { + rel_stats_env$rel_set_intersect <- (rel_stats_env$rel_set_intersect %||% 0L) + 1L + UseMethod("rel_set_intersect") +} + +#' rel_set_diff() +#' +#' `rel_set_diff()` returns rows present in any of both tables, +#' to be used by [setdiff()]. +#' +#' @rdname new_relational +#' @export +rel_set_diff <- function(rel_a, rel_b, ...) { + rel_stats_env$rel_set_diff <- (rel_stats_env$rel_set_diff %||% 0L) + 1L + UseMethod("rel_set_diff") +} + +#' rel_set_symdiff() +#' +#' `rel_set_symdiff()` returns rows present in any of both tables, +#' to be used by [dplyr::symdiff()]. +#' +#' @rdname new_relational +#' @export +rel_set_symdiff <- function(rel_a, rel_b, ...) { + rel_stats_env$rel_set_symdiff <- (rel_stats_env$rel_set_symdiff %||% 0L) + 1L + UseMethod("rel_set_symdiff") +} + +#' rel_union_all() +#' +#' `rel_union_all()` returns rows present in any of both tables, +#' to be used by [dplyr::union_all()]. +#' +#' @rdname new_relational +#' @export +rel_union_all <- function(rel_a, rel_b, ...) { + rel_stats_env$rel_union_all <- (rel_stats_env$rel_union_all %||% 0L) + 1L + UseMethod("rel_union_all") +} + +#' rel_explain +#' +#' `rel_explain()` prints an explanation of the plan +#' executed by the relational object. +#' +#' @rdname new_relational +#' @export +rel_explain <- function(rel, ...) { + rel_stats_env$rel_explain <- (rel_stats_env$rel_explain %||% 0L) + 1L + UseMethod("rel_explain") +} + +#' rel_alias +#' +#' `rel_alias()` returns the alias name for a relational object. +#' +#' @rdname new_relational +#' @export +rel_alias <- function(rel, ...) { + rel_stats_env$rel_alias <- (rel_stats_env$rel_alias %||% 0L) + 1L + UseMethod("rel_alias") +} + +#' rel_set_alias +#' +#' `rel_set_alias()` sets the alias name for a relational object. +#' +#' @rdname new_relational +#' @param alias the new alias +#' @export +rel_set_alias <- function(rel, alias, ...) { + rel_stats_env$rel_set_alias <- (rel_stats_env$rel_set_alias %||% 0L) + 1L + UseMethod("rel_set_alias") +} + +#' rel_names() +#' +#' `rel_names()` returns the column names as character vector, +#' to be used by [colnames()]. +#' +#' @rdname new_relational +#' @export +#' @examples +#' +#' rel_names.dfrel <- function(rel, ...) { +#' df <- unclass(rel)[[1]] +#' +#' names(df) +#' } +#' +#' rel_names(mtcars_rel) +rel_names <- function(rel, ...) { + rel_stats_env$rel_names <- (rel_stats_env$rel_names %||% 0L) + 1L + UseMethod("rel_names") +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational.R new file mode 100644 index 000000000..456da9769 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relational.R @@ -0,0 +1,150 @@ +#' @return A string or a condition object. +#' @noRd +rel_try <- function(call, rel, ...) { + call_name <- as.character(sys.call(-1)[[1]]) + + meta_call(gsub("[.]duckplyr_df$", "", call_name)) + + # Avoid error when called via dplyr:::filter.data.frame() (in yamlet) + if (length(call_name) == 1 && !(call_name %in% stats$calls)) { + stats$calls <- c(stats$calls, call_name) + } + + stats$attempts <- stats$attempts + 1L + + if (Sys.getenv("DUCKPLYR_TELEMETRY_PREP_TEST") == "TRUE") { + force(call) + } + + if (Sys.getenv("DUCKPLYR_TELEMETRY_TEST") == "TRUE") { + force(call) + json <- call_to_json( + error_cnd(message = paste0("Error in ", call$name)), + call + ) + cli::cli_abort("{call$name}: {json}") + } + + if (Sys.getenv("DUCKPLYR_FALLBACK_FORCE") == "TRUE") { + stats$fallback <- stats$fallback + 1L + return("Fallback enforced") + } + + dots <- list(...) + for (i in seq_along(dots)) { + if (isTRUE(dots[[i]])) { + stats$fallback <- stats$fallback + 1L + if (!dplyr_mode) { + message <- names(dots)[[i]] + if (message != "-") { + tel_collect(message, call) + } + + if (Sys.getenv("DUCKPLYR_FALLBACK_INFO") == "TRUE") { + inform(message = c( + "Cannot process duckplyr query with DuckDB, falling back to dplyr.", + i = cli::format_inline(message) + )) + } + if (Sys.getenv("DUCKPLYR_FORCE") == "TRUE") { + cli::cli_abort(c( + "Fallback not available with {.envvar DUCKPLYR_FORCE}.", + i = message + )) + } + } + + return(message) + } + } + + if (Sys.getenv("DUCKPLYR_FORCE") == "TRUE") { + force(rel) + cli::cli_abort("Must use a {.code return()} in {.code rel_try()}.", .internal = TRUE) + } + + out <- rlang::try_fetch(rel, error = identity) + if (inherits(out, "error")) { + tel_collect(out, call) + + if (Sys.getenv("DUCKPLYR_FALLBACK_INFO") == "TRUE") { + rlang::cnd_signal(rlang::message_cnd(message = "Error processing duckplyr query with DuckDB, falling back to dplyr.", parent = out)) + } + stats$fallback <- stats$fallback + 1L + return(out) + } + + # Never reached due to return() in code + cli::cli_abort("Must use a {.code return()} in {.code rel_try()}.", .internal = TRUE) +} + +rel_translate_dots <- function(dots, data, call = caller_env()) { + if (is.null(names(dots))) { + map(dots, rel_translate, data, call = call) + } else { + imap(dots, rel_translate, data = data, call = call) + } +} + +# Currently does not support referring to names created during the `summarise()` call. +# Also has specific support for `across()`. +rel_translate_dots_summarise <- function(dots, data, call = caller_env()) { + stopifnot( + !is.null(names(dots)) + ) + + out <- reduce(seq_along(dots), .init = NULL, function(.x, .y) { + current_names <- c(names(data), .x$new) + + dot <- dots[[.y]] + expanded <- duckplyr_expand_across(current_names, dot) + + if (is.null(expanded)) { + new <- names(dots)[[.y]] + translation <- list(rel_translate( + dots[[.y]], + alias = new, + data, + names_forbidden = .x$new, + call = call + )) + } else { + new <- names(expanded) + translation <- imap(expanded, function(expr, name) rel_translate( + expr, + alias = name, + data, + names_forbidden = .x$new, + call = call + )) + } + + list( + new = c(.x$new, new), + translation = c(.x$translation, translation) + ) + }) + out$translation +} + +new_failing_mask <- function(names_data) { + env <- new_environment() + walk(names_data, ~ env_bind_lazy(env, !!.x := stop("Can't access data in this context"))) + new_data_mask(env) +} + +#' @param duckplyr_error Return value from rel_try() +#' @noRd +check_prudence <- function(x, duckplyr_error, call = caller_env()) { + msg <- tryCatch(nrow(x), error = conditionMessage) + if (is.character(msg)) { + duckplyr_error_msg <- if (is.character(duckplyr_error)) duckplyr_error + duckplyr_error_parent <- if (is_condition(duckplyr_error)) duckplyr_error + cli::cli_abort(parent = duckplyr_error_parent, call = call, c( + "This operation cannot be carried out by DuckDB, and the input is a stingy duckplyr frame.", + "*" = duckplyr_error_msg, + "i" = 'Use {.code compute(prudence = "lavish")} to materialize to temporary storage and continue with {.pkg duckplyr}.', + "i" = 'See {.run vignette("prudence")} for other options.' + )) + } +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate-rd.R new file mode 100644 index 000000000..49b2bd5a4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate-rd.R @@ -0,0 +1,15 @@ +#' @title Change column order +#' +#' @description This is a method for the [dplyr::relocate()] generic. +#' See "Fallbacks" section for differences in implementation. +#' Use `relocate()` to change column positions, +#' using the same syntax as `select()` to make it easy to move blocks of columns at once. +#' +#' @inheritParams dplyr::relocate +#' @examples +#' df <- duckdb_tibble(a = 1, b = 1, c = 1, d = "a", e = "a", f = "a") +#' relocate(df, f) +#' @seealso [dplyr::relocate()] +#' @rdname relocate.duckplyr_df +#' @name relocate.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate.R new file mode 100644 index 000000000..232d8bd0c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/relocate.R @@ -0,0 +1,64 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname relocate.duckplyr_df +#' @export +relocate.duckplyr_df <- function(.data, ..., .before = NULL, .after = NULL) { + loc <- eval_relocate( + expr = expr(c(...)), + data = .data, + before = enquo(.before), + after = enquo(.after), + before_arg = ".before", + after_arg = ".after" + ) + + exprs <- exprs_from_loc(.data, loc) + + # Ensure `relocate()` appears in call stack + duckplyr_error <- rel_try(list(name = "relocate", x = .data, args = try_list(dots = enquos(...), .before = enquo(.before), .after = enquo(.after))), + #' @section Fallbacks: + #' There is no DuckDB translation in `relocate.duckplyr_df()` + #' - with a selection that returns no columns. + #' + #' These features fall back to [dplyr::relocate()], see `vignette("fallback")` for details. + "Zero-column result set not supported." = (length(exprs) == 0), + { + rel <- duckdb_rel_from_df(.data) + out <- exprs_project(rel, exprs, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + relocate <- dplyr$relocate.data.frame + out <- relocate(.data, ..., .before = {{ .before }}, .after = {{ .after }}) + return(out) + + # dplyr implementation + loc <- eval_relocate( + expr = expr(c(...)), + data = .data, + before = enquo(.before), + after = enquo(.after), + before_arg = ".before", + after_arg = ".after" + ) + + out <- dplyr_col_select(.data, loc) + out <- set_names(out, names(loc)) + + out +} + +duckplyr_relocate <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- relocate(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename-rd.R new file mode 100644 index 000000000..e6b0f52f2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename-rd.R @@ -0,0 +1,15 @@ +#' @title Rename columns +#' +#' @description This is a method for the [dplyr::rename()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `rename()` changes the names of individual variables +#' using `new_name = old_name` syntax. +#' +#' @inheritParams dplyr::rename +#' @examples +#' library(duckplyr) +#' rename(mtcars, thing = mpg) +#' @seealso [dplyr::rename()] +#' @rdname rename.duckplyr_df +#' @name rename.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename.R new file mode 100644 index 000000000..3afb1efc3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename.R @@ -0,0 +1,55 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname rename.duckplyr_df +#' @export +rename.duckplyr_df <- function(.data, ...) { + loc <- tidyselect::eval_rename(expr(c(...)), .data) + dupes <- duplicated(loc, fromLast = TRUE) + loc <- loc[!dupes] + + # eval_rename() only returns changes + proj <- rlang::set_names(seq_along(.data), names(.data)) + names(proj)[loc] <- names(loc) + + exprs <- exprs_from_loc(.data, proj) + + duckplyr_error <- rel_try(list(name = "rename", x = .data, args = try_list(dots = enquos(...))), + #' @section Fallbacks: + #' There is no DuckDB translation in `rename.duckplyr_df()` + #' - with a selection that returns no columns. + #' + #' These features fall back to [dplyr::rename()], see `vignette("fallback")` for details. + "Zero-column result set not supported." = (length(exprs) == 0), + { + rel <- duckdb_rel_from_df(.data) + out <- exprs_project(rel, exprs, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + rename <- dplyr$rename.data.frame + out <- rename(.data, ...) + return(out) + + # dplyr implementation + loc <- tidyselect::eval_rename(expr(c(...)), .data) + # eval_rename() only returns changes + names <- names(.data) + names[loc] <- names(loc) + + set_names(.data, names) +} + +duckplyr_rename <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rename(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename_with.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename_with.R new file mode 100644 index 000000000..76e339021 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rename_with.R @@ -0,0 +1,55 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rename_with.duckplyr_df <- function(.data, .fn, .cols = everything(), ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rename_with()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + rename_with <- dplyr$rename_with.data.frame + out <- rename_with(.data, .fn, {{ .cols }}, ...) + return(out) + + # dplyr implementation + .fn <- as_function(.fn) + cols <- tidyselect::eval_select(enquo(.cols), .data, allow_rename = FALSE) + + names <- names(.data) + + sel <- vec_slice(names, cols) + new <- .fn(sel, ...) + + if (!is_character(new)) { + cli::cli_abort( + "{.arg .fn} must return a character vector, not {.obj_type_friendly {new}}." + ) + } + if (length(new) != length(sel)) { + cli::cli_abort( + "{.arg .fn} must return a vector of length {length(sel)}, not {length(new)}." + ) + } + + names <- vec_assign(names, cols, new) + names <- vec_as_names(names, repair = "check_unique") + + set_names(.data, names) +} + +duckplyr_rename_with <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rename_with(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/restore.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/restore.R new file mode 100644 index 000000000..162f2eb44 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/restore.R @@ -0,0 +1,61 @@ +# Generated by 07-overwrite.R, do not edit by hand + +methods_restore_impl <- function() { + vctrs::s3_register("dplyr::add_count", "data.frame", dplyr$add_count.data.frame) + vctrs::s3_register("dplyr::anti_join", "data.frame", dplyr$anti_join.data.frame) + vctrs::s3_register("dplyr::arrange", "data.frame", dplyr$arrange.data.frame) + vctrs::s3_register("dplyr::auto_copy", "data.frame", dplyr$auto_copy.data.frame) + vctrs::s3_register("dplyr::collect", "data.frame", dplyr$collect.data.frame) + vctrs::s3_register("dplyr::compute", "data.frame", dplyr$compute.data.frame) + vctrs::s3_register("dplyr::count", "data.frame", dplyr$count.data.frame) + vctrs::s3_register("dplyr::cross_join", "data.frame", dplyr$cross_join.data.frame) + vctrs::s3_register("dplyr::distinct", "data.frame", dplyr$distinct.data.frame) + vctrs::s3_register("dplyr::do", "data.frame", dplyr$do.data.frame) + vctrs::s3_register("dplyr::filter", "data.frame", dplyr$filter.data.frame) + vctrs::s3_register("dplyr::full_join", "data.frame", dplyr$full_join.data.frame) + vctrs::s3_register("dplyr::group_by", "data.frame", dplyr$group_by.data.frame) + vctrs::s3_register("dplyr::group_indices", "data.frame", dplyr$group_indices.data.frame) + vctrs::s3_register("dplyr::group_keys", "data.frame", dplyr$group_keys.data.frame) + vctrs::s3_register("dplyr::group_map", "data.frame", dplyr$group_map.data.frame) + vctrs::s3_register("dplyr::group_modify", "data.frame", dplyr$group_modify.data.frame) + vctrs::s3_register("dplyr::group_nest", "data.frame", dplyr$group_nest.data.frame) + vctrs::s3_register("dplyr::group_size", "data.frame", dplyr$group_size.data.frame) + vctrs::s3_register("dplyr::group_split", "data.frame", dplyr$group_split.data.frame) + vctrs::s3_register("dplyr::group_trim", "data.frame", dplyr$group_trim.data.frame) + vctrs::s3_register("dplyr::group_vars", "data.frame", dplyr$group_vars.data.frame) + vctrs::s3_register("dplyr::groups", "data.frame", dplyr$groups.data.frame) + vctrs::s3_register("dplyr::inner_join", "data.frame", dplyr$inner_join.data.frame) + vctrs::s3_register("dplyr::intersect", "data.frame", dplyr$intersect.data.frame) + vctrs::s3_register("dplyr::left_join", "data.frame", dplyr$left_join.data.frame) + vctrs::s3_register("dplyr::mutate", "data.frame", dplyr$mutate.data.frame) + vctrs::s3_register("dplyr::n_groups", "data.frame", dplyr$n_groups.data.frame) + vctrs::s3_register("dplyr::nest_by", "data.frame", dplyr$nest_by.data.frame) + vctrs::s3_register("dplyr::nest_join", "data.frame", dplyr$nest_join.data.frame) + vctrs::s3_register("dplyr::pull", "data.frame", dplyr$pull.data.frame) + vctrs::s3_register("dplyr::reframe", "data.frame", dplyr$reframe.data.frame) + vctrs::s3_register("dplyr::relocate", "data.frame", dplyr$relocate.data.frame) + vctrs::s3_register("dplyr::rename", "data.frame", dplyr$rename.data.frame) + vctrs::s3_register("dplyr::rename_with", "data.frame", dplyr$rename_with.data.frame) + vctrs::s3_register("dplyr::right_join", "data.frame", dplyr$right_join.data.frame) + vctrs::s3_register("dplyr::rows_append", "data.frame", dplyr$rows_append.data.frame) + vctrs::s3_register("dplyr::rows_delete", "data.frame", dplyr$rows_delete.data.frame) + vctrs::s3_register("dplyr::rows_insert", "data.frame", dplyr$rows_insert.data.frame) + vctrs::s3_register("dplyr::rows_patch", "data.frame", dplyr$rows_patch.data.frame) + vctrs::s3_register("dplyr::rows_update", "data.frame", dplyr$rows_update.data.frame) + vctrs::s3_register("dplyr::rows_upsert", "data.frame", dplyr$rows_upsert.data.frame) + vctrs::s3_register("dplyr::rowwise", "data.frame", dplyr$rowwise.data.frame) + vctrs::s3_register("dplyr::select", "data.frame", dplyr$select.data.frame) + vctrs::s3_register("dplyr::semi_join", "data.frame", dplyr$semi_join.data.frame) + vctrs::s3_register("dplyr::setdiff", "data.frame", dplyr$setdiff.data.frame) + vctrs::s3_register("dplyr::setequal", "data.frame", dplyr$setequal.data.frame) + vctrs::s3_register("dplyr::slice", "data.frame", dplyr$slice.data.frame) + vctrs::s3_register("dplyr::slice_head", "data.frame", dplyr$slice_head.data.frame) + vctrs::s3_register("dplyr::slice_sample", "data.frame", dplyr$slice_sample.data.frame) + vctrs::s3_register("dplyr::slice_tail", "data.frame", dplyr$slice_tail.data.frame) + vctrs::s3_register("dplyr::summarise", "data.frame", dplyr$summarise.data.frame) + vctrs::s3_register("dplyr::symdiff", "data.frame", dplyr$symdiff.data.frame) + vctrs::s3_register("dplyr::transmute", "data.frame", dplyr$transmute.data.frame) + vctrs::s3_register("dplyr::ungroup", "data.frame", dplyr$ungroup.data.frame) + vctrs::s3_register("dplyr::union", "data.frame", dplyr$union.data.frame) + vctrs::s3_register("dplyr::union_all", "data.frame", dplyr$union_all.data.frame) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join-rd.R new file mode 100644 index 000000000..1af54e95d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join-rd.R @@ -0,0 +1,14 @@ +#' @title Right join +#' +#' @description This is a method for the [dplyr::right_join()] generic. +#' See "Fallbacks" section for differences in implementation. +#' A `right_join()` keeps all observations in `y`. +#' +#' @inheritParams dplyr::right_join +#' @examples +#' library(duckplyr) +#' right_join(band_members, band_instruments) +#' @seealso [dplyr::right_join()] +#' @rdname right_join.duckplyr_df +#' @name right_join.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join.R new file mode 100644 index 000000000..9a1dc5a07 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/right_join.R @@ -0,0 +1,65 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname right_join.duckplyr_df +#' @export +right_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ..., keep = NULL, na_matches = c("na", "never"), multiple = "all", unmatched = "drop", relationship = NULL) { + check_dots_empty0(...) + error_call <- caller_env() + y <- auto_copy(x, y, copy = copy) + + # Our implementation + duckplyr_error <- rel_try(list(name = "right_join", x = x, y = y, args = try_list(by = if (!is.null(by) && !is_cross_by(by)) as_join_by(by), copy = copy, keep = keep, na_matches = na_matches, multiple = multiple, unmatched = unmatched, relationship = relationship)), + #' @section Fallbacks: + #' There is no DuckDB translation in `right_join.duckplyr_df()` + #' - for an implicit cross join, + #' - for a value of the `multiple` argument that isn't the default `"all"`. + #' - for a value of the `unmatched` argument that isn't the default `"drop"`. + #' + #' These features fall back to [dplyr::right_join()], see `vignette("fallback")` for details. + "No implicit cross joins for {.code right_join()}" = is_cross_by(by), + "{.arg multiple} not supported" = !identical(multiple, "all"), + "{.arg unmatched} not supported" = !identical(unmatched, "drop"), + { + out <- rel_join_impl(x, y, by, "right", na_matches, suffix, keep, error_call) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + right_join <- dplyr$right_join.data.frame + out <- right_join(x, y, by, copy = FALSE, suffix, ..., keep = keep, na_matches = na_matches, multiple = multiple, unmatched = unmatched, relationship = relationship) + return(out) + + # dplyr implementation + check_dots_empty0(...) + y <- auto_copy(x, y, copy = copy) + join_mutate( + x = x, + y = y, + by = by, + type = "right", + suffix = suffix, + na_matches = na_matches, + keep = keep, + multiple = multiple, + unmatched = unmatched, + relationship = relationship, + user_env = caller_env() + ) +} + +duckplyr_right_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- right_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_append.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_append.R new file mode 100644 index 000000000..177c08bf3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_append.R @@ -0,0 +1,44 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rows_append.duckplyr_df <- function(x, y, ..., copy = FALSE, in_place = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rows_append()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + rows_append <- dplyr$rows_append.data.frame + out <- rows_append(x, y, ..., copy = copy, in_place = in_place) + return(out) + + # dplyr implementation + check_dots_empty() + rows_df_in_place(in_place) + + y <- auto_copy(x, y, copy = copy) + + rows_check_x_contains_y(x, y) + y <- rows_cast_y(y, x) + + rows_bind(x, y) +} + +duckplyr_rows_append <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rows_append(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_delete.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_delete.R new file mode 100644 index 000000000..d6321d495 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_delete.R @@ -0,0 +1,70 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rows_delete.duckplyr_df <- function(x, y, by = NULL, ..., unmatched = c("error", "ignore"), copy = FALSE, in_place = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rows_delete()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + rows_delete <- dplyr$rows_delete.data.frame + out <- rows_delete(x, y, by, ..., unmatched = unmatched, copy = copy, in_place = in_place) + return(out) + + # dplyr implementation + check_dots_empty() + rows_df_in_place(in_place) + + y <- auto_copy(x, y, copy = copy) + + by <- rows_check_by(by, y) + + rows_check_contains_by(x, by, "x") + rows_check_contains_by(y, by, "y") + + x_key <- dplyr_col_select(x, by) + y_key <- dplyr_col_select(y, by) + + args <- vec_cast_common(x = x_key, y = y_key) + x_key <- args$x + y_key <- args$y + + keep <- rows_check_y_unmatched(x_key, y_key, unmatched) + + if (!is.null(keep)) { + y_key <- dplyr_row_slice(y_key, keep) + } + + extra <- setdiff(names(y), names(y_key)) + if (!is_empty(extra)) { + message <- glue("Ignoring extra `y` columns: ", commas(tick_if_needed(extra))) + inform(message, class = c("dplyr_message_delete_extra_cols", "dplyr_message")) + } + + loc <- vec_match(x_key, y_key) + unmatched <- is.na(loc) + + x_loc <- which(unmatched) + + dplyr_row_slice(x, x_loc) +} + +duckplyr_rows_delete <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rows_delete(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_insert.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_insert.R new file mode 100644 index 000000000..de2ee482f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_insert.R @@ -0,0 +1,58 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rows_insert.duckplyr_df <- function(x, y, by = NULL, ..., conflict = c("error", "ignore"), copy = FALSE, in_place = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rows_insert()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + rows_insert <- dplyr$rows_insert.data.frame + out <- rows_insert(x, y, by, ..., conflict = conflict, copy = copy, in_place = in_place) + return(out) + + # dplyr implementation + check_dots_empty() + rows_df_in_place(in_place) + + y <- auto_copy(x, y, copy = copy) + + by <- rows_check_by(by, y) + + rows_check_x_contains_y(x, y) + rows_check_contains_by(x, by, "x") + rows_check_contains_by(y, by, "y") + + y <- rows_cast_y(y, x) + + x_key <- dplyr_col_select(x, by) + y_key <- dplyr_col_select(y, by) + + keep <- rows_check_y_conflict(x_key, y_key, conflict) + + if (!is.null(keep)) { + y <- dplyr_row_slice(y, keep) + } + + rows_bind(x, y) +} + +duckplyr_rows_insert <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rows_insert(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_patch.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_patch.R new file mode 100644 index 000000000..08a8d221d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_patch.R @@ -0,0 +1,89 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rows_patch.duckplyr_df <- function(x, y, by = NULL, ..., unmatched = c("error", "ignore"), copy = FALSE, in_place = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rows_patch()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + rows_patch <- dplyr$rows_patch.data.frame + out <- rows_patch(x, y, by, ..., unmatched = unmatched, copy = copy, in_place = in_place) + return(out) + + # dplyr implementation + check_dots_empty() + rows_df_in_place(in_place) + + y <- auto_copy(x, y, copy = copy) + + by <- rows_check_by(by, y) + + rows_check_x_contains_y(x, y) + rows_check_contains_by(x, by, "x") + rows_check_contains_by(y, by, "y") + + x_key <- dplyr_col_select(x, by) + y_key <- dplyr_col_select(y, by) + + rows_check_unique(y_key, "y") + + args <- vec_cast_common(x = x_key, y = y_key) + x_key <- args$x + y_key <- args$y + + values_names <- setdiff(names(y), names(y_key)) + + x_values <- dplyr_col_select(x, values_names) + y_values <- dplyr_col_select(y, values_names) + y_values <- rows_cast_y(y_values, x_values) + + keep <- rows_check_y_unmatched(x_key, y_key, unmatched) + + if (!is.null(keep)) { + y_key <- dplyr_row_slice(y_key, keep) + y_values <- dplyr_row_slice(y_values, keep) + } + + loc <- vec_match(x_key, y_key) + match <- !is.na(loc) + + y_loc <- loc[match] + x_loc <- which(match) + + x_slice <- dplyr_row_slice(x_values, x_loc) + x_slice <- dplyr_new_list(x_slice) + + y_slice <- dplyr_row_slice(y_values, y_loc) + y_slice <- dplyr_new_list(y_slice) + + x_patched <- map2(x_slice, y_slice, coalesce) + x_patched <- new_data_frame(x_patched, n = length(x_loc)) + + x_values <- vec_assign(x_values, x_loc, x_patched) + x_values <- dplyr_new_list(x_values) + + x <- dplyr_col_modify(x, x_values) + + x +} + +duckplyr_rows_patch <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rows_patch(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_update.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_update.R new file mode 100644 index 000000000..234705a9a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_update.R @@ -0,0 +1,82 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rows_update.duckplyr_df <- function(x, y, by = NULL, ..., unmatched = c("error", "ignore"), copy = FALSE, in_place = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rows_update()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + rows_update <- dplyr$rows_update.data.frame + out <- rows_update(x, y, by, ..., unmatched = unmatched, copy = copy, in_place = in_place) + return(out) + + # dplyr implementation + check_dots_empty() + rows_df_in_place(in_place) + + y <- auto_copy(x, y, copy = copy) + + by <- rows_check_by(by, y) + + rows_check_x_contains_y(x, y) + rows_check_contains_by(x, by, "x") + rows_check_contains_by(y, by, "y") + + x_key <- dplyr_col_select(x, by) + y_key <- dplyr_col_select(y, by) + + rows_check_unique(y_key, "y") + + args <- vec_cast_common(x = x_key, y = y_key) + x_key <- args$x + y_key <- args$y + + values_names <- setdiff(names(y), names(y_key)) + + x_values <- dplyr_col_select(x, values_names) + y_values <- dplyr_col_select(y, values_names) + y_values <- rows_cast_y(y_values, x_values) + + keep <- rows_check_y_unmatched(x_key, y_key, unmatched) + + if (!is.null(keep)) { + y_key <- dplyr_row_slice(y_key, keep) + y_values <- dplyr_row_slice(y_values, keep) + } + + loc <- vec_match(x_key, y_key) + match <- !is.na(loc) + + y_loc <- loc[match] + x_loc <- which(match) + + y_values <- dplyr_row_slice(y_values, y_loc) + + x_values <- vec_assign(x_values, x_loc, y_values) + x_values <- dplyr_new_list(x_values) + + x <- dplyr_col_modify(x, x_values) + + x +} + +duckplyr_rows_update <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rows_update(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_upsert.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_upsert.R new file mode 100644 index 000000000..55b6cc860 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rows_upsert.R @@ -0,0 +1,84 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rows_upsert.duckplyr_df <- function(x, y, by = NULL, ..., copy = FALSE, in_place = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code rows_upsert()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + rows_upsert <- dplyr$rows_upsert.data.frame + out <- rows_upsert(x, y, by, ..., copy = copy, in_place = in_place) + return(out) + + # dplyr implementation + check_dots_empty() + rows_df_in_place(in_place) + + y <- auto_copy(x, y, copy = copy) + + by <- rows_check_by(by, y) + + rows_check_x_contains_y(x, y) + rows_check_contains_by(x, by, "x") + rows_check_contains_by(y, by, "y") + + x_key <- dplyr_col_select(x, by) + y_key <- dplyr_col_select(y, by) + + rows_check_unique(y_key, "y") + + args <- vec_cast_common(x = x_key, y = y_key) + x_key <- args$x + y_key <- args$y + + values_names <- setdiff(names(y), names(y_key)) + + x_values <- dplyr_col_select(x, values_names) + y_values <- dplyr_col_select(y, values_names) + y_values <- rows_cast_y(y_values, x_values) + + loc <- vec_match(x_key, y_key) + match <- !is.na(loc) + + y_loc <- loc[match] + x_loc <- which(match) + + # Update + y_values <- dplyr_row_slice(y_values, y_loc) + x_values <- vec_assign(x_values, x_loc, y_values) + x_values <- dplyr_new_list(x_values) + + x <- dplyr_col_modify(x, x_values) + + # Insert + y_size <- vec_size(y_key) + y_extra <- dplyr$vec_as_location_invert(y_loc, y_size) + + y <- dplyr_row_slice(y, y_extra) + y <- rows_cast_y(y, x) + + x <- rows_bind(x, y) + + x +} + +duckplyr_rows_upsert <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rows_upsert(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rowwise.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rowwise.R new file mode 100644 index 000000000..0fa20a240 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/rowwise.R @@ -0,0 +1,35 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +rowwise.duckplyr_df <- function(data, ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + # Always fall back to dplyr + "No relational implementation for {.code rowwise()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(data, duckplyr_error) + + rowwise <- dplyr$rowwise.data.frame + out <- rowwise(data, ...) + return(out) + + # dplyr implementation + vars <- tidyselect::eval_select(expr(c(...)), data) + rowwise_df(data, vars) +} + +duckplyr_rowwise <- function(data, ...) { + try_fetch( + data <- as_duckplyr_df_impl(data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- rowwise(data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/select-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/select-rd.R new file mode 100644 index 000000000..1ced140a4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/select-rd.R @@ -0,0 +1,18 @@ +#' @title Keep or drop columns using their names and types +#' +#' @description This is a method for the [dplyr::select()] generic. +#' See "Fallbacks" section for differences in implementation. +#' Select (and optionally rename) variables in a data frame, +#' using a concise mini-language that makes it easy to refer to variables +#' based on their name (e.g. `a:f` selects all columns from a on the left +#' to f on the right) or type +#' (e.g. `where(is.numeric)` selects all numeric columns). +#' +#' @inheritParams dplyr::select +#' @examples +#' library(duckplyr) +#' select(mtcars, mpg) +#' @seealso [dplyr::select()] +#' @rdname select.duckplyr_df +#' @name select.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/select.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/select.R new file mode 100644 index 000000000..a3a3a7971 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/select.R @@ -0,0 +1,68 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname select.duckplyr_df +#' @export +select.duckplyr_df <- function(.data, ...) { + force(.data) + + error_call <- dplyr_error_call() + + loc <- tidyselect::eval_select( + expr(c(...)), + data = .data, + error_call = error_call + ) + + exprs <- exprs_from_loc(.data, loc) + + duckplyr_error <- rel_try(list(name = "select", x = .data, args = try_list(dots = enquos(...))), + # We could count and create a zero-col data frame, but we can't + # create a duckplyr frame from it anyway. + #' @section Fallbacks: + #' There is no DuckDB translation in `select.duckplyr_df()` + #' - with no expression, + #' - nor with a selection that returns no columns. + #' + #' These features fall back to [dplyr::select()], see `vignette("fallback")` for details. + "Zero-column result set not supported." = (length(exprs) == 0), + { + rel <- duckdb_rel_from_df(.data) + out <- exprs_project(rel, exprs, .data) + return(out) + } + ) + + + # dplyr forward + check_prudence(.data, duckplyr_error) + + select <- dplyr$select.data.frame + out <- select(.data, ...) + return(out) + + # dplyr implementation + error_call <- dplyr_error_call() + + loc <- tidyselect::eval_select( + expr(c(...)), + data = .data, + error_call = error_call + ) + loc <- ensure_group_vars(loc, .data, notify = TRUE) + + out <- dplyr_col_select(.data, loc) + out <- set_names(out, names(loc)) + + out +} + +duckplyr_select <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- select(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join-rd.R new file mode 100644 index 000000000..f592665be --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join-rd.R @@ -0,0 +1,13 @@ +#' @title Semi join +#' +#' @description This is a method for the [dplyr::semi_join()] generic. +#' `semi_join()` returns all rows from x with a match in y. +#' +#' @inheritParams dplyr::semi_join +#' @examples +#' library(duckplyr) +#' band_members %>% semi_join(band_instruments) +#' @seealso [dplyr::semi_join()] +#' @rdname semi_join.duckplyr_df +#' @name semi_join.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join.R new file mode 100644 index 000000000..af5ae5cba --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/semi_join.R @@ -0,0 +1,47 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname semi_join.duckplyr_df +#' @export +semi_join.duckplyr_df <- function(x, y, by = NULL, copy = FALSE, ..., na_matches = c("na", "never")) { + check_dots_empty0(...) + error_call <- caller_env() + y <- auto_copy(x, y, copy = copy) + + # https://github.com/duckdb/duckdb/issues/6597 + na_matches <- check_na_matches(na_matches, error_call = error_call) + + # Our implementation + duckplyr_error <- rel_try(list(name = "semi_join", x = x, y = y, args = try_list(by = if (!is.null(by) && !is_cross_by(by)) as_join_by(by), copy = copy, na_matches = na_matches)), + "No restrictions" = FALSE, + { + out <- rel_join_impl(x, y, by, "semi", na_matches, error_call = error_call) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + semi_join <- dplyr$semi_join.data.frame + out <- semi_join(x, y, by, copy = FALSE, ..., na_matches = na_matches) + return(out) + + # dplyr implementation + check_dots_empty0(...) + y <- auto_copy(x, y, copy = copy) + join_filter(x, y, by = by, type = "semi", na_matches = na_matches, user_env = caller_env()) +} + +duckplyr_semi_join <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- semi_join(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff-rd.R new file mode 100644 index 000000000..e3af21796 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff-rd.R @@ -0,0 +1,16 @@ +#' @title Set difference +#' +#' @description This is a method for the [dplyr::setdiff()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `setdiff(x, y)` finds all rows in `x` that aren't in `y`. +#' +#' @inheritParams dplyr::setdiff +#' @examples +#' df1 <- duckdb_tibble(x = 1:3) +#' df2 <- duckdb_tibble(x = 3:5) +#' setdiff(df1, df2) +#' setdiff(df2, df1) +#' @seealso [dplyr::setdiff()] +#' @rdname setdiff.duckplyr_df +#' @name setdiff.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff.R new file mode 100644 index 000000000..2f179c7ae --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setdiff.R @@ -0,0 +1,75 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname setdiff.duckplyr_df +#' @export +setdiff.duckplyr_df <- function(x, y, ...) { + # Our implementation + check_dots_empty() + check_compatible(x, y) + + x_names <- names(x) + y_names <- names(y) + if (identical(x_names, y_names)) { + # Ensure identical() is very cheap + y_names <- x_names + } + + duckplyr_error <- rel_try(list(name = "setdiff", x = x, y = y), + #' @section Fallbacks: + #' There is no DuckDB translation in `setdiff.duckplyr_df()` + #' - if column names are duplicated in one of the tables, + #' - if column names are different in both tables. + #' + #' These features fall back to [dplyr::setdiff()], see `vignette("fallback")` for details. + "No duplicate names" = !identical(x_names, y_names) && anyDuplicated(x_names) && anyDuplicated(y_names), + "Tables of different width" = length(x_names) != length(y_names), + "Name mismatch" = !identical(x_names, y_names) && !all(y_names %in% x_names), + { + if (oo_force()) { + delta <- anti_join(x, y, by = x_names) + out <- distinct(delta) + } else { + x_rel <- duckdb_rel_from_df(x) + y_rel <- duckdb_rel_from_df(y) + if (!identical(x_names, y_names)) { + # FIXME: Select by position + exprs <- nexprs_from_loc(x_names, set_names(seq_along(x_names), x_names)) + y_rel <- rel_project(y_rel, exprs) + } + + rel <- rel_set_diff(x_rel, y_rel) + out <- duckplyr_reconstruct(rel, x) + } + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + setdiff <- dplyr$setdiff.data.frame + out <- setdiff(x, y, ...) + return(out) + + # dplyr implementation + check_dots_empty() + check_compatible(x, y) + + out <- vec_set_difference(x, y, error_call = current_env()) + + dplyr_reconstruct(out, x) +} + +duckplyr_setdiff <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- setdiff(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setequal.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setequal.R new file mode 100644 index 000000000..1ec78f919 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/setequal.R @@ -0,0 +1,39 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +setequal.duckplyr_df <- function(x, y, ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code setequal()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + setequal <- dplyr$setequal.data.frame + out <- setequal(x, y, ...) + return(out) + + # dplyr implementation + check_dots_empty() + check_compatible(x, y) + + cast <- vec_cast_common(x = x, y = y) + all(vec_in(cast$x, cast$y)) && all(vec_in(cast$y, cast$x)) +} + +duckplyr_setequal <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- setequal(x, y, ...) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/sets.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/sets.R new file mode 100644 index 000000000..3ba7066f2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/sets.R @@ -0,0 +1,106 @@ +# https://github.com/tidyverse/duckplyr/issues/654 +# +# FIXME: Remove when dplyr 1.1.5 is out + +# Helpers ----------------------------------------------------------------- + +is_compatible <- function(x, y, ignore_col_order = TRUE, convert = TRUE) { + if (!is.data.frame(y)) { + return("`y` must be a data frame.") + } + + nc <- df_n_col(x) + if (nc != df_n_col(y)) { + return( + c(x = glue("Different number of columns: {nc} vs {df_n_col(y)}.")) + ) + } + + names_x <- names(x) + names_y <- names(y) + + names_y_not_in_x <- setdiff(names_y, names_x) + names_x_not_in_y <- setdiff(names_x, names_y) + + if (length(names_y_not_in_x) == 0L && length(names_x_not_in_y) == 0L) { + # check if same order + if (!isTRUE(ignore_col_order)) { + if (!identical(names_x, names_y)) { + return(c(x = "Same column names, but different order.")) + } + } + } else { + # names are not the same, explain why + + msg <- c() + if (length(names_y_not_in_x)) { + wrong <- glue_collapse(glue('`{names_y_not_in_x}`'), sep = ", ") + msg <- c( + msg, + x = glue("Cols in `y` but not `x`: {wrong}.") + ) + } + if (length(names_x_not_in_y)) { + wrong <- glue_collapse(glue('`{names_x_not_in_y}`'), sep = ", ") + msg <- c( + msg, + x = glue("Cols in `x` but not `y`: {wrong}.") + ) + } + return(msg) + } + + msg <- c() + for (name in names_x) { + x_i <- x[[name]] + y_i <- y[[name]] + + if (convert) { + tryCatch( + vec_ptype2(x_i, y_i), + error = function(e) { + msg <<- c( + msg, + x = glue( + "Incompatible types for column `{name}`: {vec_ptype_full(x_i)} vs {vec_ptype_full(y_i)}." + ) + ) + } + ) + } else { + if (!identical(vec_ptype(x_i), vec_ptype(y_i))) { + msg <- c( + msg, + x = glue( + "Different types for column `{name}`: {vec_ptype_full(x_i)} vs {vec_ptype_full(y_i)}." + ) + ) + } + } + } + if (length(msg)) { + return(msg) + } + + TRUE +} + +check_compatible <- function( + x, + y, + ignore_col_order = TRUE, + convert = TRUE, + error_call = caller_env() +) { + compat <- is_compatible( + x, + y, + ignore_col_order = ignore_col_order, + convert = convert + ) + if (isTRUE(compat)) { + return() + } + + abort(c("`x` and `y` are not compatible.", compat), call = error_call) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice.R new file mode 100644 index 000000000..a45aff0bf --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice.R @@ -0,0 +1,45 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +slice.duckplyr_df <- function(.data, ..., .by = NULL, .preserve = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code slice()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + slice <- dplyr$slice.data.frame + out <- slice(.data, ..., .by = {{ .by }}, .preserve = .preserve) + return(out) + + # dplyr implementation + check_dots_unnamed() + + dots <- enquos(...) + + by <- compute_by( + by = {{ .by }}, + data = .data, + by_arg = the$slice_by_arg, + data_arg = ".data" + ) + + loc <- slice_rows(.data, dots, by) + dplyr_row_slice(.data, loc, preserve = .preserve) +} + +duckplyr_slice <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- slice(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head-rd.R new file mode 100644 index 000000000..86f34c4e8 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head-rd.R @@ -0,0 +1,15 @@ +#' @title Subset rows using their positions +#' +#' @description This is a method for the [dplyr::slice_head()] generic. +#' `slice_head()` selects the first rows. +#' +#' @inheritParams dplyr::slice_head +#' @examples +#' library(duckplyr) +#' df <- data.frame(x = 1:3) +#' df <- slice_head(df, n = 2) +#' df +#' @seealso [dplyr::slice_head()] +#' @rdname slice_head.duckplyr_df +#' @name slice_head.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head.R new file mode 100644 index 000000000..7e8032261 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_head.R @@ -0,0 +1,61 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname slice_head.duckplyr_df +#' @export +slice_head.duckplyr_df <- function(.data, ..., n, prop, by = NULL) { + if (!missing(n)) { + n_valid <- (n >= 0) + } else { + n_valid <- TRUE + } + + # Our implementation + duckplyr_error <- rel_try(NULL, + #' @section Fallbacks: + #' There is no DuckDB translation in `slice_head.duckplyr_df()` + #' - if `by` or `prop` is provided, + #' - with a negative `n`. + #' + #' These features fall back to [slice_head()], see `vignette("fallback")` for details. + "{.code slice_head(by = ...)} not supported" = !missing(by), + "{.code slice_head(prop = ...)} not supported" = !missing(prop), + "{.code slice_head(n = ...)} with negative values not supported" = !n_valid, + { + rel <- duckdb_rel_from_df(.data) + out_rel <- rel_limit(rel, n) + out <- duckplyr_reconstruct(out_rel, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + slice_head <- dplyr$slice_head.data.frame + out <- slice_head(.data, ..., n = n, prop = prop, by = {{ by }}) + return(out) + + # dplyr implementation + check_dots_empty0(...) + + size <- get_slice_size(n = n, prop = prop) + idx <- function(n) { + seq2(1, size(n)) + } + + dplyr_local_error_call() + dplyr_local_slice_by_arg("by") + + slice(.data, idx(dplyr::n()), .by = {{ by }}) +} + +duckplyr_slice_head <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- slice_head(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_sample.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_sample.R new file mode 100644 index 000000000..4c3e5c7d4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_sample.R @@ -0,0 +1,52 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +slice_sample.duckplyr_df <- function(.data, ..., n, prop, by = NULL, weight_by = NULL, replace = FALSE) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code slice_sample()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + slice_sample <- dplyr$slice_sample.data.frame + out <- slice_sample(.data, ..., n = n, prop = prop, by = {{ by }}, weight_by = {{ weight_by }}, replace = replace) + return(out) + + # dplyr implementation + check_dots_empty0(...) + + size <- get_slice_size(n = n, prop = prop, allow_outsize = replace) + + dplyr_local_error_call() + dplyr_local_slice_by_arg("by") + + slice( + .data, + .by = {{ by }}, + local({ + weight_by <- {{ weight_by }} + + n <- dplyr::n() + if (!is.null(weight_by)) { + vec_check_size(weight_by, size = n) + } + sample_int(n, size(n), replace = !!replace, wt = weight_by) + }) + ) +} + +duckplyr_slice_sample <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- slice_sample(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_tail.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_tail.R new file mode 100644 index 000000000..134ee6290 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/slice_tail.R @@ -0,0 +1,43 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +slice_tail.duckplyr_df <- function(.data, ..., n, prop, by = NULL) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code slice_tail()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + slice_tail <- dplyr$slice_tail.data.frame + out <- slice_tail(.data, ..., n = n, prop = prop, by = {{ by }}) + return(out) + + # dplyr implementation + check_dots_empty0(...) + + size <- get_slice_size(n = n, prop = prop) + idx <- function(n) { + seq2(n - size(n) + 1, n) + } + + dplyr_local_error_call() + dplyr_local_slice_by_arg("by") + + slice(.data, idx(dplyr::n()), .by = {{ by }}) +} + +duckplyr_slice_tail <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- slice_tail(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/sql.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/sql.R new file mode 100644 index 000000000..3d15fbe78 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/sql.R @@ -0,0 +1,36 @@ +#' Return SQL query as duckdb_tibble +#' +#' @description +#' `r lifecycle::badge("experimental")` +#' +#' Runs a query and returns it as a duckplyr frame. +#' +#' @details +#' Using data frames from the calling environment is not supported yet, +#' see for details. +#' +#' @seealso [db_exec()] +#' +#' @inheritParams read_file_duckdb +#' @param sql The SQL to run. +#' @param con The connection, defaults to the default connection. +#' +#' @export +#' @examplesIf getRversion() >= "4.3" +#' read_sql_duckdb("FROM duckdb_settings()") +read_sql_duckdb <- function(sql, ..., prudence = c("thrifty", "lavish", "stingy"), con = NULL) { + if (!is_string(sql)) { + cli::cli_abort("{.arg sql} must be a string.") + } + + # FIXME: For some reason, it's important to create an alias here + if (is.null(con)) { + con <- get_default_duckdb_connection() + } + + rel <- duckdb$rel_from_sql(con, sql) + + meta_rel_register(rel, expr(duckdb$rel_from_sql(con, !!sql))) + + rel_to_df(rel, prudence = prudence) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/stats.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/stats.R new file mode 100644 index 000000000..42e4b48be --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/stats.R @@ -0,0 +1,29 @@ +stats <- new_environment(list(attempts = 0L, fallback = 0L, calls = character())) + +#' Show stats +#' +#' Prints statistics on how many calls were handled by DuckDB. +#' The output shows the total number of requests in the current session, +#' split by fallbacks to dplyr and requests handled by duckdb. +#' +#' @return Called for its side effect. +#' +#' @export +#' @examples +#' stats_show() +#' +#' tibble(a = 1:3) %>% +#' as_duckplyr_tibble() %>% +#' mutate(b = a + 1) +#' +#' stats_show() +stats_show <- function() { + writeLines(paste0( + c("\U0001f6e0", "\U0001f528", "\U0001f986"), + paste0("\u003A", " "), + format(c(stats$attempts, stats$fallback, stats$attempts - stats$fallback)) + )) + calls <- sort(gsub("[.]duckplyr_df", "", stats$calls)) + writeLines(paste(calls, collapse = ", ")) + invisible() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise-rd.R new file mode 100644 index 000000000..f1a59f5aa --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise-rd.R @@ -0,0 +1,19 @@ +#' @title Summarise each group down to one row +#' +#' @description This is a method for the [dplyr::summarise()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `summarise()` creates a new data frame. +#' It returns one row for each combination of grouping variables; +#' if there are no grouping variables, +#' the output will have a single row summarising all observations in the input. +#' It will contain one column for each grouping variable +#' and one column for each of the summary statistics that you have specified. +#' +#' @inheritParams dplyr::summarise +#' @examples +#' library(duckplyr) +#' summarise(mtcars, mean = mean(disp), n = n()) +#' @seealso [dplyr::summarise()] +#' @rdname summarise.duckplyr_df +#' @name summarise.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise.R new file mode 100644 index 000000000..c1b417d20 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/summarise.R @@ -0,0 +1,107 @@ +# Generated by 02-duckplyr_df-methods.R +utils::globalVariables("___row_number") + +#' @rdname summarise.duckplyr_df +#' @export +summarise.duckplyr_df <- function(.data, ..., .by = NULL, .groups = NULL) { + force(.data) + + by <- eval_select_by(enquo(.by), .data) + + duckplyr_error <- rel_try(list(name = "summarise", x = .data, args = try_list(dots = enquos(...), by = syms(by), .groups = .groups)), + #' @section Fallbacks: + #' There is no DuckDB translation in `summarise.duckplyr_df()` + #' - with `.groups = "rowwise"`. + #' + #' These features fall back to [dplyr::summarise()], see `vignette("fallback")` for details. + '{.code summarise()} with {.arg .groups} = {.value "rowwise")} not supported' = identical(.groups, "rowwise"), + { + rel <- duckdb_rel_from_df(.data) + + dots <- dplyr_quosures(...) + dots <- fix_auto_name(dots) + dots <- dots[!duplicated(names(dots), fromLast = TRUE)] + + oo <- (length(by) > 0) && oo_force() + if (oo) { + rel <- oo_prep(rel, colname = "___row_number", force = TRUE) + } + + groups <- lapply(by, relexpr_reference) + aggregates <- rel_translate_dots_summarise(dots, .data) + + if (oo) { + aggregates <- c( + list(rel_translate( + quo(min(`___row_number`)), + new_data_frame(list(`___row_number` = integer())), + alias = "___row_number" + )), + aggregates + ) + } + + out_rel <- rel_aggregate(rel, groups, unname(aggregates)) + # https://github.com/duckdb/duckdb/issues/7095 + if (length(groups) == 0) { + out_rel <- rel_distinct(out_rel) + } + + if (oo) { + out_rel <- oo_restore(out_rel, "___row_number") + } + + out <- rel_to_df(out_rel, prudence = get_prudence_duckplyr_df(.data)) + # https://github.com/tidyverse/dplyr/pull/6988 + class(out) <- intersect(c("prudent_duckplyr_df", "duckplyr_df", "tbl_df", "tbl", "data.frame"), class(.data)) + + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + summarise <- dplyr$summarise.data.frame + out <- summarise(.data, ..., .by = {{ .by }}, .groups = .groups) + # dplyr_reconstruct() is not called here, restoring manually + if (!identical(.groups, "rowwise")) { + # https://github.com/tidyverse/dplyr/pull/6988 + class(out) <- intersect(c("duckplyr_df", "tbl_df", "tbl", "data.frame"), class(.data)) + } + return(out) + + # dplyr implementation + by <- compute_by({{ .by }}, .data, by_arg = ".by", data_arg = ".data") + + cols <- summarise_cols(.data, dplyr_quosures(...), by, "summarise") + out <- summarise_build(by, cols) + + if (!cols$all_one) { + summarise_deprecate_variable_size() + } + + if (!is_tibble(.data)) { + # The `by` group data we build from is always a tibble, + # so we have to manually downcast as needed + out <- as.data.frame(out) + } + + if (identical(.groups, "rowwise")) { + out <- rowwise_df(out, character()) + } + + out +} + +duckplyr_summarise <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- summarise(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff-rd.R new file mode 100644 index 000000000..42a3a1de6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff-rd.R @@ -0,0 +1,16 @@ +#' @title Symmetric difference +#' +#' @description This is a method for the [dplyr::symdiff()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `symdiff(x, y)` computes the symmetric difference, +#' i.e. all rows in `x` that aren't in `y` and all rows in `y` that aren't in `x`. +#' +#' @inheritParams dplyr::symdiff +#' @examples +#' df1 <- duckdb_tibble(x = 1:3) +#' df2 <- duckdb_tibble(x = 3:5) +#' symdiff(df1, df2) +#' @seealso [dplyr::symdiff()] +#' @rdname symdiff.duckplyr_df +#' @name symdiff.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff.R new file mode 100644 index 000000000..7a197d299 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/symdiff.R @@ -0,0 +1,78 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname symdiff.duckplyr_df +#' @export +symdiff.duckplyr_df <- function(x, y, ...) { + # Our implementation + check_dots_empty() + check_compatible(x, y) + + x_names <- names(x) + y_names <- names(y) + if (identical(x_names, y_names)) { + # Ensure identical() is very cheap + y_names <- x_names + } + + duckplyr_error <- rel_try(list(name = "symdiff", x = x, y = y), + "No duplicate names" = !identical(x_names, y_names) && anyDuplicated(x_names) && anyDuplicated(y_names), + #' @section Fallbacks: + #' There is no DuckDB translation in `symdiff.duckplyr_df()` + #' - if column names are duplicated in one of the tables, + #' - if column names are different in both tables. + #' + #' These features fall back to [dplyr::symdiff()], see `vignette("fallback")` for details. + "No duplicate names" = !identical(x_names, y_names) && anyDuplicated(x_names) && anyDuplicated(y_names), + "Tables of different width" = length(x_names) != length(y_names), + "Name mismatch" = !identical(x_names, y_names) && !all(y_names %in% x_names), + { + if (oo_force()) { + x_not_y <- anti_join(x, y, by = x_names) + y_not_x <- anti_join(y, x, by = x_names) + out <- union(x_not_y, y_not_x) + } else { + x_rel <- duckdb_rel_from_df(x) + y_rel <- duckdb_rel_from_df(y) + + if (!identical(x_names, y_names)) { + # FIXME: Select by position + exprs <- nexprs_from_loc(x_names, set_names(seq_along(x_names), x_names)) + y_rel <- rel_project(y_rel, exprs) + } + + rel <- rel_set_symdiff(x_rel, y_rel) + out <- duckplyr_reconstruct(rel, x) + } + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + symdiff <- dplyr$symdiff.data.frame + out <- symdiff(x, y, ...) + return(out) + + # dplyr implementation + check_dots_empty() + check_compatible(x, y) + + out <- vec_set_symmetric_difference(x, y, error_call = current_env()) + + dplyr_reconstruct(out, x) +} + +duckplyr_symdiff <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- symdiff(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/telemetry.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/telemetry.R new file mode 100644 index 000000000..03ca165d9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/telemetry.R @@ -0,0 +1,286 @@ +try_list <- function(...) { + out <- vector("list", length = ...length()) + for (i in seq_len(...length())) { + # Single-bracket assignment for the handling of NULLs + out[i] <- list(tryCatch( + ...elt(i), + error = function(e) { + paste0("") + } + )) + } + names(out) <- ...names() + out +} + +on_load({ + # ...names() needs R 4.1: + if (getRversion() < "4.1") { + env <- environment() + assign("try_list", list, env) + } +}) + +tel_fallback_logging <- function() { + # Volkswagen + cran <- Sys.getenv("_R_CHECK_THINGS_IN_OTHER_DIRS_") + if (cran != "") { + return(FALSE) + } + + val <- Sys.getenv("DUCKPLYR_FALLBACK_COLLECT") + if (val == "") { + return(TRUE) + } + if (grepl("^[0-9]+$", val)) { + out <- (as.integer(val) >= 1) + } else { + out <- FALSE + } + structure(out, val = val) +} + +tel_fallback_verbose <- function() { + val <- Sys.getenv("DUCKPLYR_FALLBACK_VERBOSE") + val == "TRUE" +} + +tel_fallback_autoupload <- function() { + val <- Sys.getenv("DUCKPLYR_FALLBACK_AUTOUPLOAD") + if (val == "") { + return(NA) + } + if (!grepl("^[0-9]+$", val)) { + return(FALSE) + } + as.integer(val) >= 1 +} + +tel_fallback_log_dir <- function() { + if (Sys.getenv("DUCKPLYR_FALLBACK_LOG_DIR") != "") { + return(Sys.getenv("DUCKPLYR_FALLBACK_LOG_DIR")) + } + + cache_path <- tools::R_user_dir("duckplyr", "cache") + telemetry_path <- file.path(cache_path, "telemetry") + + # We do not distinguish between an empty an a missing directory. + # From that perspective, this is still a pure function. + dir.create(telemetry_path, recursive = TRUE, showWarnings = FALSE) + + telemetry_path +} + +tel_fallback_logs <- function(oldest = NULL, newest = NULL, detail = FALSE, envir = parent.frame()) { + if (!is.null(oldest) && !is.null(newest)) { + cli::cli_abort("Specify either {.arg oldest} or {.arg newest}, not both.", .envir = envir) + } + + # For mocking + if (Sys.getenv("DUCKPLYR_TELEMETRY_FALLBACK_LOGS") != "") { + return(strsplit(Sys.getenv("DUCKPLYR_TELEMETRY_FALLBACK_LOGS"), ",")[[1]]) + } + + telemetry_path <- tel_fallback_log_dir() + fallback_logs <- list.files(telemetry_path, full.names = TRUE, pattern = "[.]ndjson$") + + info <- file.info(fallback_logs) + info <- info[order(info$mtime), ] + + review <- rownames(info) + if (!is.null(oldest)) { + review <- utils::head(review, oldest) + } else if (!is.null(newest)) { + review <- utils::tail(review, newest) + } + + if (isTRUE(detail)) { + contents <- map_chr(review, ~ paste0(readLines(.x), "\n", collapse = "")) + } else { + contents <- rep_along(review, NA_character_) + } + + set_names(contents, review) +} + +tel_collect <- function(cnd, call) { + logging <- tel_fallback_logging() + if (!isTRUE(logging)) { + return() + } + + call_json <- call_to_json(cnd, call) + + telemetry_path <- tel_fallback_log_dir() + telemetry_file <- file.path(telemetry_path, paste0(Sys.getpid(), ".ndjson")) + + cat(call_json, "\n", sep = "", file = telemetry_file, append = TRUE) + + if (tel_fallback_verbose()) { + cli::cli_inform(c( + "i" = "dplyr fallback recorded", + " " = "{call_json}" + )) + } +} + +tel_post_async <- function(content, done = NULL, fail = NULL, pool = NULL) { + url <- "https://duckplyr-telemetry.duckdblabs.com/" + + # Create a new curl handle + handle <- curl::new_handle() + curl::handle_setopt(handle, customrequest = "POST") + curl::handle_setheaders(handle, "Content-Type" = "text/plain") + curl::handle_setopt(handle, postfields = content) + curl::curl_fetch_multi( + url, + done = done, + fail = fail, + pool = pool, + handle = handle + ) +} + +# --- + +call_to_json <- function(cnd, call) { + name_map <- get_name_map(c(names(call$x), names(call$y), names(call$args$dots))) + if (!is.null(names(call$args$dots))) { + names(call$args$dots) <- name_map[names2(call$args$dots)] + } + + if (identical(Sys.getenv("TESTTHAT"), "true")) { + log_version <- "0.3.1" + } else { + version <- getNamespaceInfo("duckplyr", "spec")["version"] + semantic <- strsplit(version, ".", fixed = TRUE)[[1]][1:3] + log_version <- paste0(semantic, collapse = ".") + } + + out <- list2( + version = log_version, + message = cnd_to_json(cnd, name_map), + name = call$name, + x = df_to_json(call$x, name_map), + y = df_to_json(call$y, name_map), + args = map(compact(call$args), arg_to_json, name_map) + ) + + jsonlite::toJSON(compact(out), auto_unbox = TRUE, null = "null") +} + +get_name_map <- function(x) { + unique <- unique(x) + new_names <- paste0("...", seq_along(unique)) + names(new_names) <- unique + new_names +} + +cnd_to_json <- function(cnd, name_map) { + if (is_condition(cnd)) { + # If conditionMessage() is called at the call site, + # the error message changes + msg <- cli::ansi_strip(conditionMessage(cnd)) + } else if (is.character(cnd)) { + msg <- cnd + } else { + msg <- paste0("Unknown message of class ", paste(class(cnd), collapse = "/")) + } + + search <- paste0("`", names(name_map), "`") + replace <- paste0("`", name_map, "`") + + for (i in seq_along(search)) { + msg <- gsub(search[[i]], replace[[i]], msg, fixed = TRUE) + } + msg +} + +df_to_json <- function(df, name_map) { + if (is.null(df)) { + return(NULL) + } + if (length(df) == 0) { + return(list()) + } + + out <- map(df, ~ paste0(class(.x), collapse = "/")) + names(out) <- name_map[names(df)] + out +} + +arg_to_json <- function(x, name_map) { + if (is.atomic(x)) { + x + } else if (is_quosures(x)) { + quos_to_json(x, name_map) + } else if (is_quosure(x)) { + quo_to_json(x, name_map) + } else if (is_call(x) || is_symbol(x)) { + expr_to_json(x, name_map) + } else if (inherits(x, "dplyr_join_by")) { + list( + condition = x$condition, + filter = x$filter, + x = arg_to_json(syms(x$x), name_map), + y = arg_to_json(syms(x$y), name_map) + ) + } else if (is.list(x)) { + map(x, ~ arg_to_json(.x, name_map)) + } else { + paste0("Can't translate object of class ", paste(class(x), collapse = "/")) + } +} + +quos_to_json <- function(x, name_map) { + map(x, ~ quo_to_json(.x, name_map)) +} + +quo_to_json <- function(x, name_map) { + expr_to_json(quo_get_expr(x), name_map) +} + +expr_to_json <- function(x, name_map) { + scrubbed <- expr_scrub(x, name_map) + expr_deparse(scrubbed, width = 500L) +} + +expr_scrub <- function(x, name_map = character()) { + do_scrub <- function(xx, callee = FALSE) { + if (is.character(xx)) { + return("") + } else if (is.factor(xx)) { + return("") + } else if (is.null(xx)) { + # Needed for R 4.4 + return(xx) + } else if (is.atomic(xx)) { + return(xx) + } else if (is_missing(xx)) { + # Arguments without default values are empty + return(xx) + } else if (is_symbol(xx)) { + if (callee) { + return(xx) + } + + match <- name_map[as.character(xx)] + if (is.na(match)) { + new_pos <- length(name_map) + 1 + match <- paste0("...", new_pos) + name_map[as.character(xx)] <<- match + } + + sym(unname(match)) + } else if (is_call(xx)) { + args <- map(as.list(xx)[-1], do_scrub) + call2(do_scrub(xx[[1]], callee = TRUE), !!!args) + } else if (is_pairlist(xx)) { + as.pairlist(map(as.list(xx), do_scrub)) + } else { + paste0("Don't know how to scrub ", paste(class(xx), collapse = "/")) + } + } + + do_scrub(x) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch.R new file mode 100644 index 000000000..c9ea01b9a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch.R @@ -0,0 +1,456 @@ +select_opt <- select +# select_opt <- function(x, ...) x + +TPCH_NA_MATCHES <- "never" + +#' @autoglobal +tpch_01 <- function() { + lineitem %>% + select_opt(l_shipdate, l_returnflag, l_linestatus, l_quantity, l_extendedprice, l_discount, l_tax) %>% + filter(l_shipdate <= !!as.Date("1998-09-02")) %>% + select_opt(l_returnflag, l_linestatus, l_quantity, l_extendedprice, l_discount, l_tax) %>% + summarise( + sum_qty = sum(na.rm = TRUE, l_quantity), + sum_base_price = sum(na.rm = TRUE, l_extendedprice), + sum_disc_price = sum(na.rm = TRUE, l_extendedprice * (1 - l_discount)), + sum_charge = sum(na.rm = TRUE, l_extendedprice * (1 - l_discount) * (1 + l_tax)), + avg_qty = mean(l_quantity), + avg_price = mean(l_extendedprice), + avg_disc = mean(l_discount), + count_order = n(), + .by = c(l_returnflag, l_linestatus) + ) %>% + arrange(l_returnflag, l_linestatus) +} + +#' @autoglobal +tpch_02 <- function() { + ps <- partsupp %>% select_opt(ps_partkey, ps_suppkey, ps_supplycost) + + p <- part %>% + select_opt(p_partkey, p_type, p_size, p_mfgr) %>% + filter(p_size == 15, grepl("BRASS$", p_type)) %>% + select_opt(p_partkey, p_mfgr) + + psp <- inner_join(na_matches = TPCH_NA_MATCHES, p, ps, by = c("p_partkey" = "ps_partkey")) + + sp <- supplier %>% + select_opt( + s_suppkey, s_nationkey, s_acctbal, s_name, + s_address, s_phone, s_comment + ) + + psps <- inner_join(psp, sp, + by = c("ps_suppkey" = "s_suppkey") + ) %>% + select_opt( + p_partkey, ps_supplycost, p_mfgr, s_nationkey, + s_acctbal, s_name, s_address, s_phone, s_comment + ) + + nr <- inner_join( + nation, + region %>% filter(r_name == "EUROPE"), + by = c("n_regionkey" = "r_regionkey") + ) %>% + select_opt(n_nationkey, n_name) + + pspsnr <- inner_join(psps, nr, by = c("s_nationkey" = "n_nationkey")) %>% + select_opt( + p_partkey, ps_supplycost, p_mfgr, n_name, s_acctbal, + s_name, s_address, s_phone, s_comment + ) + + aggr <- pspsnr %>% + summarise(min_ps_supplycost = min(ps_supplycost), .by = p_partkey) + + sj <- inner_join(pspsnr, aggr, + by = c( + "p_partkey" = "p_partkey", + "ps_supplycost" = "min_ps_supplycost" + ) + ) + + res <- sj %>% + select( + s_acctbal, s_name, n_name, p_partkey, p_mfgr, + s_address, s_phone, s_comment + ) %>% + arrange(desc(s_acctbal), n_name, s_name, p_partkey) %>% + slice_head(n = 100) + + res +} + +#' @autoglobal +tpch_03 <- function() { + oc <- inner_join( + orders %>% + select_opt(o_orderkey, o_custkey, o_orderdate, o_shippriority) %>% + filter(o_orderdate < !!as.Date("1995-03-15")), + customer %>% + select_opt(c_custkey, c_mktsegment) %>% + filter(c_mktsegment == "BUILDING"), + by = c("o_custkey" = "c_custkey") + ) %>% + select_opt(o_orderkey, o_orderdate, o_shippriority) + + loc <- inner_join( + lineitem %>% + select_opt(l_orderkey, l_shipdate, l_extendedprice, l_discount) %>% + filter(l_shipdate > !!as.Date("1995-03-15")) %>% + select_opt(l_orderkey, l_extendedprice, l_discount), + oc, + by = c("l_orderkey" = "o_orderkey") + ) + + aggr <- loc %>% + mutate(volume = l_extendedprice * (1 - l_discount)) %>% + summarise(revenue = sum(na.rm = TRUE, volume), .by = c(l_orderkey, o_orderdate, o_shippriority)) %>% + select(l_orderkey, revenue, o_orderdate, o_shippriority) %>% + arrange(desc(revenue), o_orderdate) %>% + slice_head(n = 10) + aggr +} + +#' @autoglobal +tpch_04 <- function() { + l <- lineitem %>% + select_opt(l_orderkey, l_commitdate, l_receiptdate) %>% + filter(l_commitdate < l_receiptdate) %>% + select(l_orderkey) + + o <- orders %>% + select_opt(o_orderkey, o_orderdate, o_orderpriority) %>% + filter(o_orderdate >= !!as.Date("1993-07-01"), o_orderdate < !!as.Date("1993-10-01")) %>% + select(o_orderkey, o_orderpriority) + + # distinct after join, tested and indeed faster + lo <- inner_join(na_matches = TPCH_NA_MATCHES, l, o, by = c("l_orderkey" = "o_orderkey")) %>% + distinct() %>% + select_opt(o_orderpriority) + + aggr <- lo %>% + summarise(order_count = n(), .by = o_orderpriority) %>% + arrange(o_orderpriority) + + aggr +} + +#' @autoglobal +tpch_05 <- function() { + nr <- inner_join( + na_matches = TPCH_NA_MATCHES, + nation %>% + select_opt(n_nationkey, n_regionkey, n_name), + region %>% + select_opt(r_regionkey, r_name) %>% + filter(r_name == "ASIA"), + by = c("n_regionkey" = "r_regionkey") + ) %>% + select_opt(n_nationkey, n_name) + + snr <- inner_join( + na_matches = TPCH_NA_MATCHES, + supplier %>% + select_opt(s_suppkey, s_nationkey), + nr, + by = c("s_nationkey" = "n_nationkey") + ) %>% + select_opt(s_suppkey, s_nationkey, n_name) + + lsnr <- inner_join( + na_matches = TPCH_NA_MATCHES, + lineitem %>% select_opt(l_suppkey, l_orderkey, l_extendedprice, l_discount), + snr, + by = c("l_suppkey" = "s_suppkey") + ) + + o <- orders %>% + select_opt(o_orderdate, o_orderkey, o_custkey) %>% + filter(o_orderdate >= !!as.Date("1994-01-01"), o_orderdate < !!as.Date("1995-01-01")) %>% + select_opt(o_orderkey, o_custkey) + + oc <- inner_join( + na_matches = TPCH_NA_MATCHES, o, customer %>% select_opt(c_custkey, c_nationkey), + by = c("o_custkey" = "c_custkey") + ) %>% + select_opt(o_orderkey, c_nationkey) + + lsnroc <- inner_join( + na_matches = TPCH_NA_MATCHES, lsnr, oc, + by = c("l_orderkey" = "o_orderkey", "s_nationkey" = "c_nationkey") + ) %>% + select_opt(l_extendedprice, l_discount, n_name) + + aggr <- lsnroc %>% + mutate(volume = l_extendedprice * (1 - l_discount)) %>% + summarise(revenue = sum(na.rm = TRUE, volume), .by = n_name) %>% + arrange(desc(revenue)) + + aggr +} + +#' @autoglobal +tpch_06 <- function() { + lineitem %>% + select_opt(l_shipdate, l_extendedprice, l_discount, l_quantity) %>% + filter( + l_shipdate >= !!as.Date("1994-01-01"), + l_shipdate < !!as.Date("1995-01-01"), + l_discount >= 0.05, + l_discount <= 0.07, + l_quantity < 24 + ) %>% + select_opt(l_extendedprice, l_discount) %>% + summarise(revenue = sum(na.rm = TRUE, l_extendedprice * l_discount)) +} + +#' @autoglobal +tpch_07 <- function() { + sn <- inner_join( + na_matches = TPCH_NA_MATCHES, + supplier %>% + select_opt(s_nationkey, s_suppkey), + nation %>% + select(n1_nationkey = n_nationkey, n1_name = n_name) %>% + # filter(n1_name %in% c("FRANCE", "GERMANY")), TODO + filter(n1_name == "FRANCE" | n1_name == "GERMANY"), + by = c("s_nationkey" = "n1_nationkey") + ) %>% + select_opt(s_suppkey, n1_name) + + cn <- inner_join( + na_matches = TPCH_NA_MATCHES, + customer %>% + select_opt(c_custkey, c_nationkey), + nation %>% + select(n2_nationkey = n_nationkey, n2_name = n_name) %>% + # filter(n2_name %in% c("FRANCE", "GERMANY")), + filter(n2_name == "FRANCE" | n2_name == "GERMANY"), + by = c("c_nationkey" = "n2_nationkey") + ) %>% + select_opt(c_custkey, n2_name) + + cno <- inner_join( + na_matches = TPCH_NA_MATCHES, + orders %>% + select_opt(o_custkey, o_orderkey), + cn, + by = c("o_custkey" = "c_custkey") + ) %>% + select_opt(o_orderkey, n2_name) + + cnol <- inner_join( + na_matches = TPCH_NA_MATCHES, + lineitem %>% + select_opt(l_orderkey, l_suppkey, l_shipdate, l_extendedprice, l_discount) %>% + filter(l_shipdate >= !!as.Date("1995-01-01"), l_shipdate <= !!as.Date("1996-12-31")), + cno, + by = c("l_orderkey" = "o_orderkey") + ) %>% + select_opt(l_suppkey, l_shipdate, l_extendedprice, l_discount, n2_name) + + all <- inner_join(na_matches = TPCH_NA_MATCHES, cnol, sn, by = c("l_suppkey" = "s_suppkey")) + + aggr <- all %>% + filter((n1_name == "FRANCE" & n2_name == "GERMANY") | + (n1_name == "GERMANY" & n2_name == "FRANCE")) %>% + mutate( + supp_nation = n1_name, + cust_nation = n2_name, + l_year = as.integer(strftime(l_shipdate, "%Y")), + volume = l_extendedprice * (1 - l_discount) + ) %>% + select_opt(supp_nation, cust_nation, l_year, volume) %>% + summarise(revenue = sum(na.rm = TRUE, volume), .by = c(supp_nation, cust_nation, l_year)) %>% + arrange(supp_nation, cust_nation, l_year) + + aggr +} + +#' @autoglobal +tpch_08 <- function() { + nr <- inner_join( + na_matches = TPCH_NA_MATCHES, + nation %>% + select(n1_nationkey = n_nationkey, n1_regionkey = n_regionkey), + region %>% + select_opt(r_regionkey, r_name) %>% + filter(r_name == "AMERICA") %>% + select_opt(r_regionkey), + by = c("n1_regionkey" = "r_regionkey") + ) %>% + select_opt(n1_nationkey) + + cnr <- inner_join( + na_matches = TPCH_NA_MATCHES, + customer %>% + select_opt(c_custkey, c_nationkey), + nr, + by = c("c_nationkey" = "n1_nationkey") + ) %>% + select_opt(c_custkey) + + ocnr <- inner_join( + na_matches = TPCH_NA_MATCHES, + orders %>% + select_opt(o_orderkey, o_custkey, o_orderdate) %>% + filter(o_orderdate >= !!as.Date("1995-01-01"), o_orderdate <= !!as.Date("1996-12-31")), + cnr, + by = c("o_custkey" = "c_custkey") + ) %>% + select_opt(o_orderkey, o_orderdate) + + locnr <- inner_join( + na_matches = TPCH_NA_MATCHES, + lineitem %>% + select_opt(l_orderkey, l_partkey, l_suppkey, l_extendedprice, l_discount), + ocnr, + by = c("l_orderkey" = "o_orderkey") + ) %>% + select_opt(l_partkey, l_suppkey, l_extendedprice, l_discount, o_orderdate) + + locnrp <- inner_join( + na_matches = TPCH_NA_MATCHES, locnr, + part %>% + select_opt(p_partkey, p_type) %>% + filter(p_type == "ECONOMY ANODIZED STEEL") %>% + select_opt(p_partkey), + by = c("l_partkey" = "p_partkey") + ) %>% + select_opt(l_suppkey, l_extendedprice, l_discount, o_orderdate) + + locnrps <- inner_join( + na_matches = TPCH_NA_MATCHES, locnrp, + supplier %>% + select_opt(s_suppkey, s_nationkey), + by = c("l_suppkey" = "s_suppkey") + ) %>% + select_opt(l_extendedprice, l_discount, o_orderdate, s_nationkey) + + all <- inner_join( + na_matches = TPCH_NA_MATCHES, locnrps, + nation %>% + select(n2_nationkey = n_nationkey, n2_name = n_name), + by = c("s_nationkey" = "n2_nationkey") + ) %>% + select_opt(l_extendedprice, l_discount, o_orderdate, n2_name) + + aggr <- all %>% + mutate( + o_year = as.integer(strftime(o_orderdate, "%Y")), + volume = l_extendedprice * (1 - l_discount), + nation = n2_name + ) %>% + select_opt(o_year, volume, nation) %>% + summarise( + mkt_share = sum(na.rm = TRUE, if_else(nation == "BRAZIL", volume, 0)) / sum(na.rm = TRUE, volume), + .by = o_year + ) %>% + arrange(o_year) + aggr +} + +#' @autoglobal +tpch_09 <- function() { + p <- part %>% + select_opt(p_name, p_partkey) %>% + filter(grepl("green", p_name)) %>% + select_opt(p_partkey) + + psp <- inner_join( + na_matches = TPCH_NA_MATCHES, + partsupp %>% + select_opt(ps_suppkey, ps_partkey, ps_supplycost), + p, + by = c("ps_partkey" = "p_partkey") + ) + + sn <- inner_join( + na_matches = TPCH_NA_MATCHES, + supplier %>% + select_opt(s_suppkey, s_nationkey), + nation %>% + select_opt(n_nationkey, n_name), + by = c("s_nationkey" = "n_nationkey") + ) %>% + select_opt(s_suppkey, n_name) + + pspsn <- inner_join(na_matches = TPCH_NA_MATCHES, psp, sn, by = c("ps_suppkey" = "s_suppkey")) + + lpspsn <- inner_join( + na_matches = TPCH_NA_MATCHES, + lineitem %>% + select_opt(l_suppkey, l_partkey, l_orderkey, l_extendedprice, l_discount, l_quantity), + pspsn, + by = c("l_suppkey" = "ps_suppkey", "l_partkey" = "ps_partkey") + ) %>% + select_opt(l_orderkey, l_extendedprice, l_discount, l_quantity, ps_supplycost, n_name) + + all <- inner_join( + na_matches = TPCH_NA_MATCHES, + orders %>% + select_opt(o_orderkey, o_orderdate), + lpspsn, + by = c("o_orderkey" = "l_orderkey") + ) %>% + select_opt(l_extendedprice, l_discount, l_quantity, ps_supplycost, n_name, o_orderdate) + + aggr <- all %>% + mutate( + nation = n_name, + o_year = as.integer(strftime(o_orderdate, "%Y")), + amount = l_extendedprice * (1 - l_discount) - ps_supplycost * l_quantity + ) %>% + select_opt(nation, o_year, amount) %>% + summarise(sum_profit = sum(na.rm = TRUE, amount), .by = c(nation, o_year)) %>% + arrange(nation, desc(o_year)) + + aggr +} + +#' @autoglobal +tpch_10 <- function() { + l <- lineitem %>% + select_opt(l_orderkey, l_returnflag, l_extendedprice, l_discount) %>% + filter(l_returnflag == "R") %>% + select_opt(l_orderkey, l_extendedprice, l_discount) + + o <- orders %>% + select_opt(o_orderkey, o_custkey, o_orderdate) %>% + filter(o_orderdate >= !!as.Date("1993-10-01"), o_orderdate < !!as.Date("1994-01-01")) %>% + select_opt(o_orderkey, o_custkey) + + lo <- inner_join( + na_matches = TPCH_NA_MATCHES, l, o, + by = c("l_orderkey" = "o_orderkey") + ) %>% + select_opt(l_extendedprice, l_discount, o_custkey) + # first aggregate, then join with customer/nation, + # otherwise we need to aggr over lots of cols + + lo_aggr <- lo %>% + mutate(volume = l_extendedprice * (1 - l_discount)) %>% + summarise(revenue = sum(na.rm = TRUE, volume), .by = o_custkey) + + c <- customer %>% + select_opt(c_custkey, c_nationkey, c_name, c_acctbal, c_phone, c_address, c_comment) + + loc <- inner_join(na_matches = TPCH_NA_MATCHES, c, lo_aggr, by = c("c_custkey" = "o_custkey")) + + locn <- inner_join( + na_matches = TPCH_NA_MATCHES, loc, nation %>% select_opt(n_nationkey, n_name), + by = c("c_nationkey" = "n_nationkey") + ) + + res <- locn %>% + select( + c_custkey, c_name, revenue, c_acctbal, n_name, + c_address, c_phone, c_comment + ) %>% + arrange(desc(revenue)) %>% + slice_head(n = 20) + + res +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch2.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch2.R new file mode 100644 index 000000000..31729d861 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/tpch2.R @@ -0,0 +1,338 @@ +#' @autoglobal +tpch_11 <- function() { + nation <- nation %>% + filter(n_name == "GERMANY") + + joined_filtered <- partsupp %>% + inner_join(na_matches = TPCH_NA_MATCHES, supplier, by = c("ps_suppkey" = "s_suppkey")) %>% + inner_join(na_matches = TPCH_NA_MATCHES, nation, by = c("s_nationkey" = "n_nationkey")) + + global_agr <- joined_filtered %>% + summarise( + global_value = sum(na.rm = TRUE, ps_supplycost * ps_availqty) * 0.0001000000 + ) %>% + mutate(global_agr_key = 1L) + + partkey_agr <- joined_filtered %>% + summarise( + value = sum(na.rm = TRUE, ps_supplycost * ps_availqty), + .by = ps_partkey + ) + + partkey_agr %>% + mutate(global_agr_key = 1L) %>% + inner_join(na_matches = TPCH_NA_MATCHES, global_agr, by = "global_agr_key") %>% + filter(value > global_value) %>% + arrange(desc(value)) %>% + select(ps_partkey, value) +} + +#' @autoglobal +tpch_12 <- function() { + lineitem %>% + filter( + l_shipmode %in% c("MAIL", "SHIP"), + l_commitdate < l_receiptdate, + l_shipdate < l_commitdate, + l_receiptdate >= !!as.Date("1994-01-01"), + l_receiptdate < !!as.Date("1995-01-01") + ) %>% + inner_join( + na_matches = TPCH_NA_MATCHES, + orders, + by = c("l_orderkey" = "o_orderkey") + ) %>% + summarise( + high_line_count = sum(na.rm = TRUE, + if_else( + (o_orderpriority == "1-URGENT") | (o_orderpriority == "2-HIGH"), + 1L, + 0L + ) + ), + low_line_count = sum(na.rm = TRUE, + if_else( + (o_orderpriority != "1-URGENT") & (o_orderpriority != "2-HIGH"), + 1L, + 0L + ) + ), + .by = l_shipmode + ) %>% + arrange(l_shipmode) +} + +#' @autoglobal +tpch_13 <- function() { + c_orders <- customer %>% + left_join( + na_matches = TPCH_NA_MATCHES, + orders %>% + filter(!grepl("special.*?requests", o_comment)), + by = c("c_custkey" = "o_custkey") + ) %>% + summarise( + # FIXME: sum(na.rm = TRUE, !is.na(o_orderkey)) + c_count = sum(na.rm = TRUE, if_else(is.na(o_orderkey), 0L, 1L)), + .by = c_custkey + ) + + c_orders %>% + summarise(custdist = n(), .by = c_count) %>% + arrange(desc(custdist), desc(c_count)) +} + +#' @autoglobal +tpch_14 <- function() { + lineitem %>% + filter( + l_shipdate >= !!as.Date("1995-09-01"), + l_shipdate < !!as.Date("1995-10-01") + ) %>% + inner_join(na_matches = TPCH_NA_MATCHES, part, by = c("l_partkey" = "p_partkey")) %>% + summarise( + promo_revenue = 100 * sum(na.rm = TRUE, + if_else(grepl("^PROMO", p_type), l_extendedprice * (1 - l_discount), 0) + ) / sum(na.rm = TRUE, l_extendedprice * (1 - l_discount)) + ) +} + +#' @autoglobal +tpch_15 <- function() { + revenue_by_supplier <- lineitem %>% + filter( + l_shipdate >= !!as.Date("1996-01-01"), + l_shipdate < !!as.Date("1996-04-01") + ) %>% + summarise( + total_revenue = sum(na.rm = TRUE, l_extendedprice * (1 - l_discount)), + .by = l_suppkey + ) + + global_revenue <- revenue_by_supplier %>% + mutate(global_agr_key = 1L) %>% + summarise( + max_total_revenue = max(total_revenue), + .by = global_agr_key + ) + + revenue_by_supplier %>% + mutate(global_agr_key = 1L) %>% + inner_join(na_matches = TPCH_NA_MATCHES, global_revenue, by = "global_agr_key") %>% + filter(abs(total_revenue - max_total_revenue) < 1e-9) %>% + inner_join(na_matches = TPCH_NA_MATCHES, supplier, by = c("l_suppkey" = "s_suppkey")) %>% + select(s_suppkey = l_suppkey, s_name, s_address, s_phone, total_revenue) +} + + +#' @autoglobal +tpch_16 <- function() { + part_filtered <- part %>% + filter( + p_brand != "Brand#45", + !grepl("^MEDIUM POLISHED", p_type), + p_size %in% c(49, 14, 23, 45, 19, 3, 36, 9) + ) + + supplier_filtered <- supplier %>% + filter(!grepl("Customer.*?Complaints", s_comment)) + + partsupp_filtered <- partsupp %>% + inner_join(na_matches = TPCH_NA_MATCHES, supplier_filtered, by = c("ps_suppkey" = "s_suppkey")) %>% + select(ps_partkey, ps_suppkey) + + part_filtered %>% + inner_join(na_matches = TPCH_NA_MATCHES, partsupp_filtered, by = c("p_partkey" = "ps_partkey")) %>% + summarise( + supplier_cnt = n_distinct(ps_suppkey), + .by = c(p_brand, p_type, p_size) + ) %>% + select(p_brand, p_type, p_size, supplier_cnt) %>% + arrange(desc(supplier_cnt), p_brand, p_type, p_size) +} + +#' @autoglobal +tpch_17 <- function() { + parts_filtered <- part %>% + filter( + p_brand == "Brand#23", + p_container == "MED BOX" + ) + + joined <- lineitem %>% + inner_join(na_matches = TPCH_NA_MATCHES, parts_filtered, by = c("l_partkey" = "p_partkey")) + + quantity_by_part <- joined %>% + summarise(quantity_threshold = 0.2 * mean(l_quantity), .by = l_partkey) + + joined %>% + inner_join(na_matches = TPCH_NA_MATCHES, quantity_by_part, by = "l_partkey") %>% + filter(l_quantity < quantity_threshold) %>% + summarise(avg_yearly = sum(na.rm = TRUE, l_extendedprice) / 7.0) +} + +#' @autoglobal +tpch_18 <- function() { + big_orders <- lineitem %>% + summarise(sum = sum(na.rm = TRUE, l_quantity), .by = l_orderkey) %>% + filter(sum > 300) + + orders %>% + inner_join(na_matches = TPCH_NA_MATCHES, big_orders, by = c("o_orderkey" = "l_orderkey")) %>% + inner_join(na_matches = TPCH_NA_MATCHES, customer, by = c("o_custkey" = "c_custkey")) %>% + select( + c_name, + c_custkey = o_custkey, o_orderkey, + o_orderdate, o_totalprice, sum + ) %>% + arrange(desc(o_totalprice), o_orderdate) %>% + slice_head(n = 100) +} + +#' @autoglobal +tpch_19 <- function() { + joined <- lineitem %>% + inner_join(na_matches = TPCH_NA_MATCHES, part, by = c("l_partkey" = "p_partkey")) + + result <- joined %>% + filter( + ( + p_brand == "Brand#12" & + p_container %in% c("SM CASE", "SM BOX", "SM PACK", "SM PKG") & + l_quantity >= 1 & + l_quantity <= (1 + 10) & + p_size >= 1 & + p_size <= 5 & + l_shipmode %in% c("AIR", "AIR REG") & + l_shipinstruct == "DELIVER IN PERSON" + ) | + ( + p_brand == "Brand#23" & + p_container %in% c("MED BAG", "MED BOX", "MED PKG", "MED PACK") & + l_quantity >= 10 & + l_quantity <= (10 + 10) & + p_size >= 1 & + p_size <= 10 & + l_shipmode %in% c("AIR", "AIR REG") & + l_shipinstruct == "DELIVER IN PERSON" + ) | + ( + p_brand == "Brand#34" & + p_container %in% c("LG CASE", "LG BOX", "LG PACK", "LG PKG") & + l_quantity >= 20 & + l_quantity <= (20 + 10) & + p_size >= 1 & + p_size <= 15 & + l_shipmode %in% c("AIR", "AIR REG") & + l_shipinstruct == "DELIVER IN PERSON" + ) + ) + + result %>% + summarise( + revenue = sum(na.rm = TRUE, l_extendedprice * (1 - l_discount)) + ) +} + +#' @autoglobal +tpch_20 <- function() { + supplier_ca <- supplier %>% + inner_join( + na_matches = TPCH_NA_MATCHES, + nation %>% filter(n_name == "CANADA"), + by = c("s_nationkey" = "n_nationkey") + ) %>% + select(s_suppkey, s_name, s_address) + + part_forest <- part %>% + filter(grepl("^forest", p_name)) + + partsupp_forest_ca <- partsupp %>% + semi_join(na_matches = TPCH_NA_MATCHES, supplier_ca, c("ps_suppkey" = "s_suppkey")) %>% + semi_join(na_matches = TPCH_NA_MATCHES, part_forest, by = c("ps_partkey" = "p_partkey")) + + qty_threshold <- lineitem %>% + filter( + l_shipdate >= !!as.Date("1994-01-01"), + l_shipdate < !!as.Date("1995-01-01") + ) %>% + semi_join(na_matches = TPCH_NA_MATCHES, partsupp_forest_ca, by = c("l_partkey" = "ps_partkey", "l_suppkey" = "ps_suppkey")) %>% + summarise(qty_threshold = 0.5 * sum(na.rm = TRUE, l_quantity), .by = l_suppkey) + + partsupp_forest_ca_filtered <- partsupp_forest_ca %>% + inner_join( + na_matches = TPCH_NA_MATCHES, + qty_threshold, + by = c("ps_suppkey" = "l_suppkey") + ) %>% + filter(ps_availqty > qty_threshold) + + supplier_ca %>% + semi_join(na_matches = TPCH_NA_MATCHES, partsupp_forest_ca_filtered, by = c("s_suppkey" = "ps_suppkey")) %>% + select(s_name, s_address) %>% + arrange(s_name) +} + +#' @autoglobal +tpch_21 <- function() { + orders_with_more_than_one_supplier <- lineitem %>% + count(l_orderkey, l_suppkey) %>% + summarise(n_supplier = n(), .by = l_orderkey) %>% + filter(n_supplier > 1) + + line_items_needed <- lineitem %>% + semi_join(na_matches = TPCH_NA_MATCHES, orders_with_more_than_one_supplier, by = "l_orderkey") %>% + inner_join(na_matches = TPCH_NA_MATCHES, orders, by = c("l_orderkey" = "o_orderkey")) %>% + filter(o_orderstatus == "F") %>% + summarise( + failed_delivery_commit = any(l_receiptdate > l_commitdate), + .by = c(l_orderkey, l_suppkey) + ) %>% + summarise( + n_supplier = n(), + num_failed = sum(na.rm = TRUE, if_else(failed_delivery_commit, 1, 0)), + .by = l_orderkey + ) %>% + filter(n_supplier > 1 & num_failed == 1) + + line_items <- lineitem %>% + semi_join(na_matches = TPCH_NA_MATCHES, line_items_needed, by = "l_orderkey") + + supplier %>% + inner_join(na_matches = TPCH_NA_MATCHES, line_items, by = c("s_suppkey" = "l_suppkey")) %>% + filter(l_receiptdate > l_commitdate) %>% + inner_join(na_matches = TPCH_NA_MATCHES, nation, by = c("s_nationkey" = "n_nationkey")) %>% + filter(n_name == "SAUDI ARABIA") %>% + summarise(numwait = n(), .by = s_name) %>% + arrange(desc(numwait), s_name) %>% + slice_head(n = 100) +} + +#' @autoglobal +tpch_22 <- function() { + acctbal_mins <- customer %>% + filter( + # FIXME: substr(c_phone, 1, 2) + substr(c_phone, 1L, 2L) %in% c("13", "31", "23", "29", "30", "18", "17") & + c_acctbal > 0 + ) %>% + # FIXME: mean(na.rm = TRUE) + summarise(acctbal_min = mean(c_acctbal), join_id = 1L) + + customer %>% + # FIXME: substr(c_phone, 1, 2) + mutate(cntrycode = substr(c_phone, 1L, 2L), join_id = 1L) %>% + left_join(na_matches = TPCH_NA_MATCHES, acctbal_mins, by = "join_id") %>% + filter( + cntrycode %in% c("13", "31", "23", "29", "30", "18", "17") & + c_acctbal > acctbal_min + ) %>% + anti_join(na_matches = TPCH_NA_MATCHES, orders, by = c("c_custkey" = "o_custkey")) %>% + select(cntrycode, c_acctbal) %>% + summarise( + numcust = n(), + totacctbal = sum(na.rm = TRUE, c_acctbal), + .by = cntrycode + ) %>% + arrange(cntrycode) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/translate.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/translate.R new file mode 100644 index 000000000..49aa14023 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/translate.R @@ -0,0 +1,601 @@ +# Documented in `.github/CONTRIBUTING.md` + +duckplyr_macros <- c( + # https://github.com/duckdb/duckdb-r/pull/156 + "___null" = "() AS CAST(NULL AS BOOLEAN)", + # + "<" = "(x, y) AS (x < y)", + "<=" = "(x, y) AS (x <= y)", + ">" = "(x, y) AS (x > y)", + ">=" = "(x, y) AS (x >= y)", + "==" = "(x, y) AS (x == y)", + "!=" = "(x, y) AS (x != y)", + # + "___divide" = "(x, y) AS CASE WHEN y = 0 THEN CASE WHEN x = 0 THEN CAST('NaN' AS double) WHEN x > 0 THEN CAST('+Infinity' AS double) ELSE CAST('-Infinity' AS double) END ELSE CAST(x AS double) / y END", + # + "is.na" = "(x) AS (x IS NULL)", + "n" = "() AS CAST(COUNT(*) AS int32)", + # + "___log10" = "(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE log10(x) END", + "___log" = "(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE ln(x) END", + # TPCH + + # https://github.com/duckdb/duckdb/discussions/8599 + # "as.Date" = '(x) AS strptime(x, \'%Y-%m-%d\')', + + "sub" = "(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement))", + "gsub" = "(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement, 'g'))", + "grepl" = "(pattern, x) AS (CASE WHEN x IS NULL THEN FALSE ELSE regexp_matches(x, pattern) END)", + "if_else" = "(test, yes, no) AS (CASE WHEN test IS NULL THEN NULL ELSE CASE WHEN test THEN yes ELSE no END END)", + "|" = "(x, y) AS (x OR y)", + "&" = "(x, y) AS (x AND y)", + "!" = "(x) AS (NOT x)", + # + "wday" = "(x) AS CAST(weekday(CAST (x AS DATE)) + 1 AS int32)", + # + "___eq_na_matches_na" = "(x, y) AS (x IS NOT DISTINCT FROM y)", + "___coalesce" = "(x, y) AS COALESCE(x, y)", + # + # FIXME: Need a better way? + "suppressWarnings" = "(x) AS (x)", + # + "___sum_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE SUM(x) END)", + "___min_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE MIN(x) END)", + "___max_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE MAX(x) END)", + "___any_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE bool_or(x) END)", + "___all_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE bool_and(x) END)", + "___mean_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)", + "___sd_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE STDDEV(x) END)", + "___median_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE percentile_cont(0.5) WITHIN GROUP (ORDER BY x) END)", + # + # In n_distinct() many NAs count as 1 if not filtered out with na.rm = TRUE + "___n_distinct_na" = "(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN (COUNT(DISTINCT x)+1) ELSE COUNT(DISTINCT x) END)", + "___n_distinct" = "(x) AS (COUNT(DISTINCT x))", + # + NULL +) + +rel_find_packages <- function(name) { + # Remember to update limits.Rmd when adding new functions! + switch(name, + # Handled in a special way, not mentioned here + "desc" = "dplyr", + + "==" = "base", + "/" = "base", + "$" = "base", # very special, also with constant folding + "mean" = "base", + "n" = "dplyr", + ">" = "base", + "%in%" = "base", + "sum" = "base", + "!" = "base", + "&" = "base", + "-" = "base", + "(" = "base", + "is.na" = "base", + # "ifelse" = "base", + "!=" = "base", + # "c" = "base", + "*" = "base", + "+" = "base", + "<" = "base", + # "[" = "base", # won't implement? + ">=" = "base", + "n_distinct" = "dplyr", + "max" = "base", + "<=" = "base", + # "as.numeric" = "base", + "|" = "base", + # "factor" = "base", + # "^" = "base", + "min" = "base", + # "replace" = "base", + "sub" = "base", + "gsub" = "base", + "grepl" = "base", + # ":" = "base", + # "as.character" = "base", + # "paste" = "base", + # "round" = "base", + # "paste0" = "base", + # "length" = "base", + # ".data$" = "dplyr", + "sd" = "stats", + # "[[" = "base", # won't implement? + # "gsub" = "base", + # "str_detect" = "stringr", + "median" = "stats", + # "~" = "base", # won't implement? + # "unique" = "base", # what's the use case? + # ".$" = "dplyr", + # "%>%" = "magrittr", # with the help of magrittr? + # "as.Date" = "base", + "as.integer" = "base", + # "nrow" = "base", + # "as.factor" = "base", + # "%<=>%" = "???", # what is this? + "row_number" = "dplyr", + # "rev" = "base", # what's the use case? + # "seq" = "base", # what's the use case? + # "sqrt" = "base", + "abs" = "base", + "if_else" = "dplyr", + # + "any" = "base", + "all" = "base", + "suppressWarnings" = "base", + "lag" = "dplyr", + "lead" = "dplyr", + "log10" = "base", + "log" = "base", + "hour" = "lubridate", + "minute" = "lubridate", + "second" = "lubridate", + "wday" = "lubridate", + "strftime" = "base", + "substr" = "base", + + "coalesce" = "dplyr", + + NULL + ) + # Remember to update limits.Rmd when adding new functions! +} + +rel_find_call_candidates <- function(fun, call = caller_env()) { + name <- as.character(fun) + + if (length(name) == 1) { + pkgs <- rel_find_packages(name) + + if (!is.null(pkgs)) { + return(list( + packages = pkgs, + name = name, + check = TRUE + )) + } + } else if (name[[1]] == "::") { + my_pkg <- name[[2]] + name <- name[[3]] + + if (my_pkg == "dd" || my_pkg %in% rel_find_packages(name)) { + # Package name provided by the user, shortcut if found in list of packages + # (requires non-NULL pkgs), no check needed + return(list( + packages = my_pkg, + name = name, + check = FALSE + )) + } + } else if (name[[1]] == "$") { + # Passthrough for functions prefixed with dd$ + my_pkg <- name[[2]] + name <- name[[3]] + + if (identical(my_pkg, "dd")) { + # Check performed by DuckDB + return(list( + packages = my_pkg, + name = name, + check = FALSE + )) + } + } + + cli::cli_abort("Can't translate function {.code {expr_deparse(fun)}()}.", call = call) +} + +rel_find_call <- function(fun, env, call = caller_env()) { + call_cand <- rel_find_call_candidates(fun, call = call) + pkgs <- call_cand$packages + name <- call_cand$name + + if (!isTRUE(call_cand$check)) { + return(c(pkgs, name)) + } + + # Order from https://docs.google.com/spreadsheets/d/1j3AFOKiAknTGpXU1uSH7JzzscgYjVbUEwmdRHS7268E/edit?gid=769885824#gid=769885824, + # generated as `expr_result` by 63-gh-detail.R + + # https://github.com/tidyverse/dplyr/pull/7046 + if (name == "n") { + return(c("dplyr", "n")) + } + + fun_val <- get0(as.character(fun), env, mode = "function", inherits = TRUE) + if (is.null(fun_val)) { + cli::cli_abort("Function {.fun {name}} not found.", call = call) + } + + for (pkg in pkgs) { + if (identical(fun_val, get(name, envir = asNamespace(pkg)))) { + return(c(pkg, name)) + } + } + + if (length(pkgs) == 1) { + cli::cli_abort("Function {.fun {name}} does not map to {.fun {pkgs}::{name}}.", call = call) + } else { + cli::cli_abort("Function {.fun {name}} does not map to the corresponding function in {.pkg {pkgs}}.", call = call) + } +} + +infer_class_of_expr <- function(expr, data) { + if (typeof(expr) == "symbol") { + name <- as.character(expr) + if (name %in% names(data)) { + return(class(data[[name]])[[1]]) + } + } + return(class(expr)[[1]]) +} + +classes_are_comparable <- function(left, right) { + if (left == "integer" && right == "numeric") { + return(TRUE) + } + if (left == "numeric" && right == "integer") { + return(TRUE) + } + left == right +} + +rel_translate_lang <- function( + expr, + do_translate, + data, + # FIXME: Perform constant folding instead + env, + # FIXME: Perform constant folding instead + partition, + in_window, + need_window, + call = caller_env() +) { + pkg_name <- rel_find_call(expr[[1]], env, call = call) + pkg <- pkg_name[[1]] + name <- pkg_name[[2]] + + # Special case: passthrough to DuckDB + if (pkg == "dd") { + args_r <- as.list(expr[-1]) + + # FIXME: How to deal with window functions? + args <- map(args_r, do_translate, in_window = in_window) + + if (!is.null(names(args_r))) { + need_names <- (names(args_r) != "") + args[need_names] <- map2(args[need_names], names(args_r)[need_names], relexpr_set_alias) + } + fun <- relexpr_function(name, args) + return(fun) + } + + if (name %in% c(">", "<", "==", ">=", "<=")) { + if (length(expr) != 3) { + cli::cli_abort("Expected three expressions for comparison. Got {length(expr)}", call = call) + } + + class_left <- infer_class_of_expr(expr[[2]], data) + class_right <- infer_class_of_expr(expr[[3]], data) + + if (classes_are_comparable(class_left, class_right)) { + return( + relexpr_comparison( + name, + list(do_translate(expr[[2]]), do_translate(expr[[3]])) + ) + ) + } + } + + + if (!(name %in% c("wday", "strftime", "lag", "lead", "sum", "min", "max", "any", "all", "mean", "median", "sd", "n_distinct"))) { + if (!is.null(names(expr)) && any(names(expr) != "")) { + # Fix grepl() and sum()/min()/max() logic below when allowing matching by argument name + cli::cli_abort("Can't translate named argument {.code {name}({names(expr)[names(expr) != ''][[1]]} = )}.", call = call) + } + } + + switch(name, + "(" = { + return(do_translate(expr[[2]], in_window = in_window)) + }, + # Hack + "wday" = { + if (!is.null(pkg) && pkg != "lubridate") { + cli::cli_abort("Don't know how to translate {.code {pkg}::{name}}.", call = call) + } + call <- call_match(expr, lubridate::wday, dots_env = env) + args <- as.list(call[-1]) + bad <- !(names(args) %in% c("x")) + if (any(bad)) { + cli::cli_abort("{name}({names(args)[which(bad)[[1]]]} = ) not supported", call = call) + } + if (!is.null(getOption("lubridate.week.start"))) { + cli::cli_abort('{.code wday()} with {.code option("lubridate.week.start")} not supported', call = call) + } + }, + "strftime" = { + call <- call_match(expr, strftime, dots_env = env) + args <- as.list(call[-1]) + bad <- !(names(args) %in% c("x", "format")) + if (any(bad)) { + cli::cli_abort("{name}({names(args)[which(bad)[[1]]]} = ) not supported", call = call) + } + }, + "%in%" = { + values <- eval_tidy(expr[[3]], data = new_failing_mask(names(data)), env = env) + if (length(values) == 0) { + return(relexpr_constant(FALSE)) + } + + lhs <- do_translate(expr[[2]]) + + if (anyNA(values)) { + has_na <- TRUE + values <- values[!is.na(values)] + if (length(values) == 0) { + return(relexpr_function("is.na", list(lhs))) + } + } else { + has_na <- FALSE + } + + if (length(values) > 100) { + cli::cli_abort("Can't translate {.code {name}} with more than 100 values.", call = call) + } + + consts <- map(values, do_translate) + ops <- map(consts, ~ list(lhs, .x)) + cmp <- map(ops, relexpr_function, name = "r_base::==") + alt <- bisect_reduce(cmp, function(.x, .y) { + relexpr_function("|", list(.x, .y)) + }) + coalesce <- relexpr_function("___coalesce", list(alt, relexpr_constant(has_na))) + meta_ext_register() + return(coalesce) + }, + "$" = { + if (expr[[2]] == ".data") { + return(do_translate(expr[[3]], in_window = in_window)) + } else if (expr[[2]] == ".env") { + var_name <- as.character(expr[[3]]) + if (exists(var_name, envir = env)) { + return(do_translate(get(var_name, env), in_window = in_window)) + } else { + cli::cli_abort("object not found, should also be triggered by the dplyr fallback", call = call) + } + } + }, + "coalesce" = { + if (length(expr) != 3) { + cli::cli_abort("Can only translate {.call coalesce(x, y)} with two arguments.", call = call) + } + } + ) + + aliases <- c( + "sd" = "stddev", + "first" = "first_value", + "last" = "last_value", + "nth" = "nth_value", + "/" = "___divide", + "log10" = "___log10", + "log" = "___log", + "as.integer" = "r_base::as.integer", + "<" = "r_base::<", + "<=" = "r_base::<=", + ">" = "r_base::>", + ">=" = "r_base::>=", + "==" = "r_base::==", + "!=" = "r_base::!=", + "coalesce" = "___coalesce", + + NULL + ) + + known_window <- c( + # Window functions + "row_number", + # Not yet implemented + "ntile", + "first", "last", "nth", + # Difficult to implement + "rank", "dense_rank", "percent_rank", "cume_dist", + "lead", "lag", + + # Aggregates + "sum", "min", "max", "any", "all", "mean", "sd", "median", + "n_distinct", + # + NULL + ) + + window <- need_window && (name %in% known_window) + + if (name %in% names(aliases)) { + aliased_name <- aliases[[name]] + if (grepl("^r_base::", aliased_name)) { + meta_ext_register() + } + } else { + aliased_name <- name + } + + order_bys <- list() + offset_expr <- NULL + default_expr <- NULL + if (name %in% c("lag", "lead")) { + # x, n = 1L, default = NULL, order_by = NULL + expr <- call_match(expr, lag, dots_env = env) + + offset_expr <- relexpr_constant(expr$n %||% 1L) + expr$n <- NULL + + if (!is.null(expr$default)) { + default_expr <- do_translate(expr$default, in_window = TRUE) + expr$default <- NULL + } + + if (!is.null(expr$order_by)) { + order_bys <- list(do_translate(expr$order_by, in_window = TRUE)) + expr$order_by <- NULL + } + } + + # Other primitives: prod, range + # Other aggregates: var(), cum*(), quantile() + if (name %in% c("sum", "min", "max", "any", "all", "mean", "sd", "median", "n_distinct")) { + is_primitive <- (name %in% c("sum", "min", "max", "any", "all")) + + if (is_primitive) { + def_primitive <- function(..., na.rm = FALSE) {} + def <- def_primitive + good_names <- c("", "na.rm") + unnamed_args <- 1 + } else { + def_regular <- function(x, ..., na.rm = FALSE) {} + def <- def_regular + good_names <- c("x", "na.rm") + unnamed_args <- 0 + } + + expr <- call_match(expr, def, dots_env = env) + args <- as.list(expr[-1]) + bad <- !(names(args) %in% good_names) + if (sum(names2(args) == "") != unnamed_args) { + cli::cli_abort("{.fun {name}} needs exactly one argument besides the optional {.arg na.rm}", call = call) + } + if (any(bad)) { + cli::cli_abort("{.code {name}({names(args)[which(bad)[[1]]]} = )} not supported", call = call) + } + + na_rm <- FALSE + if (length(args) > 1) { + na_rm <- eval(args[[2]], env) + } + + if (window) { + if (name == "n_distinct") { + cli::cli_abort("{.code {name}()} not supported in window functions", call = call) + } else { + if (identical(na_rm, FALSE)) { + cli::cli_abort(call = call, c( + "{.code {name}(na.rm = FALSE)} not supported in window functions", + i = "Use {.code {name}(na.rm = TRUE)} after checking for missing values" + )) + } else if (!identical(na_rm, TRUE)) { + cli::cli_abort("Invalid value for {.arg na.rm} in call to {.fun {name}}", call = call) + } + } + } else { + if (identical(na_rm, FALSE)) { + aliased_name <- paste0("___", name, "_na") # ___sum_na, ___min_na, ___max_na + } else if (identical(na_rm, TRUE)) { + if (name == "n_distinct") { + aliased_name <- paste0("___", name) + } + } else { + cli::cli_abort("Invalid value for {.arg na.rm} in call to {.fun {name}}", call = call) + } + } + + expr <- expr[1:2] + } + + args <- map(as.list(expr[-1]), do_translate, in_window = in_window || window) + + if (name == "grepl") { + if (!inherits(args[[1]], "relational_relexpr_constant")) { + cli::cli_abort("Only constant patterns are supported in {.fun grepl}", call = call) + } + } + + fun <- relexpr_function(aliased_name, args) + if (window) { + partitions <- map(partition, relexpr_reference) + fun <- relexpr_window( + fun, + partitions, + order_bys, + offset_expr = offset_expr, + default_expr = default_expr + ) + + if (name == "row_number") { + fun <- relexpr_function("r_base::as.integer", list(fun)) + meta_ext_register() + } + } + fun +} + +rel_translate <- function( + quo, + data, + alias = NULL, + partition = NULL, + need_window = FALSE, + names_forbidden = NULL, + call = caller_env() +) { + if (is_expression(quo)) { + expr <- quo + env <- baseenv() + } else { + expr <- quo_get_expr(quo) + env <- quo_get_env(quo) + } + used <- character() + + do_translate <- function(expr, in_window = FALSE, top_level = FALSE) { + stopifnot(!is_quosure(expr)) + switch(typeof(expr), + character = , + integer = , + double = relexpr_constant(expr), + # https://github.com/duckdb/duckdb-r/pull/156 + logical = if (top_level && length(expr) == 1 && is.na(expr)) relexpr_function("___null", list()) else relexpr_constant(expr), + # + symbol = { + if (as.character(expr) %in% names_forbidden) { + cli::cli_abort("Can't reuse summary variable {.var {as.character(expr)}}.", call = call) + } + if (as.character(expr) %in% names(data)) { + ref <- as.character(expr) + if (!(ref %in% used)) { + used <<- c(used, ref) + } + relexpr_reference(ref) + } else { + val <- eval_tidy(expr, env = env) + relexpr_constant(val) + } + }, + # + language = rel_translate_lang( + expr, + do_translate, + data, + env, + partition, + in_window, + need_window, + call = call + ), + # + cli::cli_abort("Internal: Unknown type {.val {typeof(expr)}}", call = call) + ) + } + + out <- do_translate(expr, top_level = TRUE) + + if (!is.null(alias) && !identical(alias, "")) { + out <- relexpr_set_alias(out, alias) + } + + structure(out, used = used) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute-rd.R new file mode 100644 index 000000000..8668c5600 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute-rd.R @@ -0,0 +1,17 @@ +#' @title Create, modify, and delete columns +#' +#' @description This is a method for the [dplyr::transmute()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `transmute()` creates a new data frame containing only the specified computations. +#' It's superseded because you can perform the same job with `mutate(.keep = "none")`. + + +#' +#' @inheritParams dplyr::transmute +#' @examples +#' library(duckplyr) +#' transmute(mtcars, mpg2 = mpg*2) +#' @seealso [dplyr::transmute()] +#' @rdname transmute.duckplyr_df +#' @name transmute.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute.R new file mode 100644 index 000000000..2f098c6d3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/transmute.R @@ -0,0 +1,73 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname transmute.duckplyr_df +#' @export +transmute.duckplyr_df <- function(.data, ...) { + force(.data) + + dots <- check_transmute_args(...) + dots <- dplyr_quosures(!!!dots) + dots <- fix_auto_name(dots) + + duckplyr_error <- rel_try(list(name = "transmute", x = .data, args = try_list(dots = enquos(...))), + #' @section Fallbacks: + #' There is no DuckDB translation in `transmute.duckplyr_df()` + #' - with a selection that returns no columns: + #' + #' These features fall back to [dplyr::transmute()], see `vignette("fallback")` for details. + "Zero-column result set not supported." = (length(dots) == 0), + { + exprs <- rel_translate_dots(dots, .data) + rel <- duckdb_rel_from_df(.data) + out_rel <- rel_project(rel, exprs) + out <- duckplyr_reconstruct(out_rel, .data) + return(out) + } + ) + + # dplyr forward + check_prudence(.data, duckplyr_error) + + transmute <- dplyr$transmute.data.frame + out <- transmute(.data, ...) + return(out) + + # dplyr implementation + dots <- check_transmute_args(...) + dots <- dplyr_quosures(!!!dots) + + # We don't expose `.by` because `transmute()` is superseded + by <- compute_by(by = NULL, data = .data) + + cols <- mutate_cols(.data, dots, by) + + out <- dplyr_col_modify(.data, cols) + + # Compact out `NULL` columns that got removed. + # These won't exist in `out`, but we don't want them to look "new". + # Note that `dplyr_col_modify()` makes it impossible to `NULL` a group column, + # which we rely on below. + cols <- compact_null(cols) + + # Retain expression columns in order of their appearance + cols_expr <- names(cols) + + # Retain untouched group variables up front + cols_group <- by$names + cols_group <- setdiff(cols_group, cols_expr) + + cols_retain <- c(cols_group, cols_expr) + + dplyr_col_select(out, cols_retain) +} + +duckplyr_transmute <- function(.data, ...) { + try_fetch( + .data <- as_duckplyr_df_impl(.data), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- transmute(.data, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/ungroup.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/ungroup.R new file mode 100644 index 000000000..b7535904f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/ungroup.R @@ -0,0 +1,34 @@ +# Generated by 02-duckplyr_df-methods.R +#' @export +ungroup.duckplyr_df <- function(x, ...) { + # Our implementation + duckplyr_error <- rel_try(NULL, + "No relational implementation for {.code ungroup()}" = TRUE, + { + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + ungroup <- dplyr$ungroup.data.frame + out <- ungroup(x, ...) + return(out) + + # dplyr implementation + check_dots_empty() + x +} + +duckplyr_ungroup <- function(x, ...) { + try_fetch( + x <- as_duckplyr_df_impl(x), + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- ungroup(x, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union-rd.R new file mode 100644 index 000000000..0628331ba --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union-rd.R @@ -0,0 +1,15 @@ +#' @title Union +#' +#' @description This is a method for the [dplyr::union()] generic. +#' `union(x, y)` finds all rows in either x or y, excluding duplicates. +#' The implementation forwards to `distinct(union_all(x, y))`. +#' +#' @inheritParams dplyr::union +#' @examples +#' df1 <- duckdb_tibble(x = 1:3) +#' df2 <- duckdb_tibble(x = 3:5) +#' union(df1, df2) +#' @seealso [dplyr::union()] +#' @rdname union.duckplyr_df +#' @name union.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union.R new file mode 100644 index 000000000..0f6026085 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union.R @@ -0,0 +1,42 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname union.duckplyr_df +#' @export +union.duckplyr_df <- function(x, y, ...) { + # Our implementation + check_dots_empty() + + # This is difficult to do manually due to order preservation + return(distinct(union_all(x, y))) + + duckplyr_error <- NULL + + # dplyr forward + check_prudence(x, duckplyr_error) + + union <- dplyr$union.data.frame + out <- union(x, y, ...) + return(out) + + # dplyr implementation + check_dots_empty() + check_compatible(x, y) + + out <- vec_set_union(x, y, error_call = current_env()) + + dplyr_reconstruct(out, x) +} + +duckplyr_union <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- union(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all-rd.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all-rd.R new file mode 100644 index 000000000..f6d37cfdb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all-rd.R @@ -0,0 +1,15 @@ +#' @title Union of all +#' +#' @description This is a method for the [dplyr::union_all()] generic. +#' See "Fallbacks" section for differences in implementation. +#' `union_all(x, y)` finds all rows in either x or y, including duplicates. +#' +#' @inheritParams dplyr::union_all +#' @examples +#' df1 <- duckdb_tibble(x = 1:3) +#' df2 <- duckdb_tibble(x = 3:5) +#' union_all(df1, df2) +#' @seealso [dplyr::union_all()] +#' @rdname union_all.duckplyr_df +#' @name union_all.duckplyr_df +NULL diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all.R new file mode 100644 index 000000000..641066fba --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/union_all.R @@ -0,0 +1,77 @@ +# Generated by 02-duckplyr_df-methods.R +#' @rdname union_all.duckplyr_df +#' @export +union_all.duckplyr_df <- function(x, y, ...) { + # Our implementation + check_dots_empty() + check_compatible(x, y) + + x_names <- names(x) + y_names <- names(y) + if (identical(x_names, y_names)) { + # Ensure identical() is very cheap + y_names <- x_names + } + + duckplyr_error <- rel_try(list(name = "union_all", x = x, y = y), + "No duplicate names" = !identical(x_names, y_names) && anyDuplicated(x_names) && anyDuplicated(y_names), + #' @section Fallbacks: + #' There is no DuckDB translation in `union_all.duckplyr_df()` + #' - if column names are duplicated in one of the tables, + #' - if column names are different in both tables. + #' + #' These features fall back to [dplyr::union_all()], see `vignette("fallback")` for details. + "No duplicate names" = !identical(x_names, y_names) && anyDuplicated(x_names) && anyDuplicated(y_names), + "Tables of different width" = length(x_names) != length(y_names), + "Name mismatch" = !identical(x_names, y_names) && !all(y_names %in% x_names), + { + x_rel <- duckdb_rel_from_df(x) + y_rel <- duckdb_rel_from_df(y) + if (!identical(x_names, y_names)) { + # FIXME: Select by position + exprs <- nexprs_from_loc(x_names, set_names(seq_along(x_names), x_names)) + y_rel <- rel_project(y_rel, exprs) + } + + x_rel <- oo_prep(x_rel, "___row_number_x", extra_cols_post = "___row_number_y") + y_rel <- oo_prep(y_rel, "___row_number_y", extra_cols_pre = "___row_number_x") + + rel <- rel_union_all(x_rel, y_rel) + + # NULLs sort first in duckdb! + rel <- oo_restore(rel, c("___row_number_x", "___row_number_y")) + + out <- duckplyr_reconstruct(rel, x) + return(out) + } + ) + + # dplyr forward + check_prudence(x, duckplyr_error) + + union_all <- dplyr$union_all.data.frame + out <- union_all(x, y, ...) + return(out) + + # dplyr implementation + check_dots_empty() + check_compatible(x, y) + + out <- vec_rbind(x, y) + dplyr_reconstruct(out, x) +} + +duckplyr_union_all <- function(x, y, ...) { + try_fetch( + { + x <- as_duckplyr_df_impl(x) + y <- as_duckplyr_df_impl(y) + }, + error = function(e) { + testthat::skip(conditionMessage(e)) + } + ) + out <- union_all(x, y, ...) + class(out) <- setdiff(class(out), "duckplyr_df") + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/unique_table_name.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/unique_table_name.R new file mode 100644 index 000000000..42130925a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/unique_table_name.R @@ -0,0 +1,6 @@ +# From dbplyr +unique_table_name <- function(prefix = "") { + vals <- c(letters, LETTERS, 0:9) + name <- paste0(sample(vals, 10, replace = TRUE), collapse = "") + paste0(prefix, "duckplyr_", name) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-auto.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-auto.R new file mode 100644 index 000000000..6a67603e0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-auto.R @@ -0,0 +1,5 @@ +on_load({ + if (Sys.getenv("DUCKPLYR_METHODS_OVERWRITE") == "TRUE") { + methods_overwrite() + } +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-methods.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-methods.R new file mode 100644 index 000000000..06898e966 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz-methods.R @@ -0,0 +1,38 @@ +#' Forward all dplyr methods to duckplyr +#' +#' After calling `methods_overwrite()`, all dplyr methods are redirected to duckplyr +#' for the duraton of the session, or until a call to `methods_restore()`. +#' The `methods_overwrite()` function is called automatically when the package is loaded +#' if the environment variable `DUCKPLYR_METHODS_OVERWRITE` is set to `TRUE`. +#' +#' @return Called for their side effects. +#' @export +#' @examples +#' tibble(a = 1:3) %>% +#' mutate(b = a + 1) +#' +#' methods_overwrite() +#' +#' tibble(a = 1:3) %>% +#' mutate(b = a + 1) +#' +#' methods_restore() +#' +#' tibble(a = 1:3) %>% +#' mutate(b = a + 1) +methods_overwrite <- function() { + cli::cli_inform( + c( + v = "Overwriting {.pkg dplyr} methods with {.pkg duckplyr} methods.", + i = "Turn off with {.run duckplyr::methods_restore()}." + ) + ) + methods_overwrite_impl() +} + +#' @rdname methods_overwrite +#' @export +methods_restore <- function() { + cli::cli_inform(c(i = "Restoring {.pkg dplyr} methods.")) + methods_restore_impl() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz.R new file mode 100644 index 000000000..738e020ed --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/R/zzz.R @@ -0,0 +1,40 @@ +# Bump to 3.0.0 "long enough" before releasing duckdb 2.0.0 +on_load({ + if (getNamespaceInfo("duckdb", "spec")[["version"]] >= as.package_version("2.0.0")) { + cli::cli_abort(c( + "This version of {.pkg duckplyr} does not support {.pkg duckdb} >= 2.0.0.", + i = "Please upgrade to a more recent version of {.pkg duckplyr}." + )) + } +}) + +.onLoad <- function(lib, pkg) { + run_on_load() +} + +.onAttach <- function(lib, pkg) { + if (!exists(".__DEVTOOLS__", asNamespace("duckplyr"))) { + msg <- character() + suppressMessages(try_fetch(methods_overwrite(), message = function(cond) { + msg <<- c(msg, conditionMessage(cond)) + zap() + })) + packageStartupMessage(msg) + } +} + +.onDetach <- function(lib) { + if (!exists(".__DEVTOOLS__", asNamespace("duckplyr"))) { + msg <- character() + suppressMessages(try_fetch(methods_restore(), message = function(cond) { + msg <<- c(msg, conditionMessage(cond)) + zap() + })) + packageStartupMessage(msg) + } +} + +# Avoid R CMD check warning +dummy <- function() { + memoise::memoise() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/README.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/README.md new file mode 100644 index 000000000..0fce884dd --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/README.md @@ -0,0 +1,391 @@ + + + +# duckplyr + + + +[![Lifecycle: +stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) +[![R-CMD-check](https://github.com/tidyverse/duckplyr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tidyverse/duckplyr/actions/workflows/R-CMD-check.yaml) +[![Codecov test +coverage](https://codecov.io/gh/tidyverse/duckplyr/graph/badge.svg)](https://app.codecov.io/gh/tidyverse/duckplyr) + + +> A **drop-in replacement** for dplyr, powered by DuckDB for **speed**. + +[dplyr](https://dplyr.tidyverse.org/) is the grammar of data +manipulation in the tidyverse. The duckplyr package will run all of your +existing dplyr code with identical results, using +[DuckDB](https://duckdb.org/) where possible to compute the results +faster. In addition, you can analyze larger-than-memory datasets +straight from files on your disk or from the web. + +If you are new to dplyr, the best place to start is the [data +transformation chapter](https://r4ds.hadley.nz/data-transform) in *R for +Data Science*. + +## Installation + +Install duckplyr from CRAN with: + +``` r +install.packages("duckplyr") +``` + +You can also install the development version of duckplyr from +[R-universe](https://tidyverse.r-universe.dev/builds): + +``` r +install.packages("duckplyr", repos = c("https://tidyverse.r-universe.dev", "https://cloud.r-project.org")) +``` + +Or from [GitHub](https://github.com/) with: + +``` r +# install.packages("pak") +pak::pak("tidyverse/duckplyr") +``` + +## Drop-in replacement for dplyr + +Calling `library(duckplyr)` overwrites dplyr methods, enabling duckplyr +for the entire session. + +``` r +library(conflicted) +library(duckplyr) +#> Loading required package: dplyr +#> ✔ Overwriting dplyr methods with duckplyr methods. +#> ℹ Turn off with `duckplyr::methods_restore()`. +``` + +``` r +conflict_prefer("filter", "dplyr") +#> [conflicted] Will prefer dplyr::filter +#> over any other package. +``` + +The following code aggregates the inflight delay by year and month for +the first half of the year. We use a variant of the +`nycflights13::flights` dataset, where the timezone has been set to UTC +to work around a current limitation of duckplyr, see +[`vignette("limits")`](https://duckplyr.tidyverse.org/articles/limits.html). + +``` r +flights_df() +#> # A tibble: 336,776 × 19 +#> year month day dep_time sched_d…¹ dep_d…² arr_t…³ sched…⁴ arr_d…⁵ +#> +#> 1 2013 1 1 517 515 2 830 819 11 +#> 2 2013 1 1 533 529 4 850 830 20 +#> 3 2013 1 1 542 540 2 923 850 33 +#> 4 2013 1 1 544 545 -1 1004 1022 -18 +#> 5 2013 1 1 554 600 -6 812 837 -25 +#> 6 2013 1 1 554 558 -4 740 728 12 +#> 7 2013 1 1 555 600 -5 913 854 19 +#> 8 2013 1 1 557 600 -3 709 723 -14 +#> 9 2013 1 1 557 600 -3 838 846 -8 +#> 10 2013 1 1 558 600 -2 753 745 8 +#> # ℹ 336,766 more rows +#> # ℹ abbreviated names: ¹​sched_dep_time, ²​dep_delay, ³​arr_time, +#> # ⁴​sched_arr_time, ⁵​arr_delay +#> # ℹ 10 more variables: carrier , flight , tailnum , +#> # origin , dest , air_time , distance , +#> # hour , minute , time_hour + +out <- + flights_df() |> + filter(!is.na(arr_delay), !is.na(dep_delay)) |> + mutate(inflight_delay = arr_delay - dep_delay) |> + summarize( + .by = c(year, month), + mean_inflight_delay = mean(inflight_delay), + median_inflight_delay = median(inflight_delay), + ) |> + filter(month <= 6) +``` + +The result is a plain tibble: + +``` r +class(out) +#> [1] "tbl_df" "tbl" "data.frame" +``` + +Nothing has been computed yet. Querying the number of rows, or a column, +starts the computation: + +``` r +out$month +#> [1] 1 2 3 4 5 6 +``` + +Note that, unlike dplyr, the results are not ordered, see `?config` for +details. However, once materialized, the results are stable: + +``` r +out +#> # A tibble: 6 × 4 +#> year month mean_inflight_delay median_inflight_delay +#> +#> 1 2013 1 -3.86 -5 +#> 2 2013 2 -5.15 -6 +#> 3 2013 3 -7.36 -9 +#> 4 2013 4 -2.67 -5 +#> 5 2013 5 -9.37 -10 +#> 6 2013 6 -4.24 -7 +``` + +If a computation is not supported by DuckDB, duckplyr will automatically +fall back to dplyr. + +``` r +flights_df() |> + summarize( + .by = origin, + dest = paste(sort(unique(dest)), collapse = " ") + ) +#> # A tibble: 3 × 2 +#> origin dest +#> +#> 1 EWR ALB ANC ATL AUS AVL BDL BNA BOS BQN BTV BUF BWI BZN CAE CHS C… +#> 2 LGA ATL AVL BGR BHM BNA BOS BTV BUF BWI CAE CAK CHO CHS CLE CLT C… +#> 3 JFK ABQ ACK ATL AUS BHM BNA BOS BQN BTV BUF BUR BWI CHS CLE CLT C… +``` + +Restart R, or call `duckplyr::methods_restore()` to revert to the +default dplyr implementation. + +``` r +duckplyr::methods_restore() +#> ℹ Restoring dplyr methods. +``` + +## Analyzing larger-than-memory data + +An extended variant of the `nycflights13::flights` dataset is also +available for download as Parquet files. + +``` r +year <- 2022:2024 +base_url <- "https://blobs.duckdb.org/flight-data-partitioned/" +files <- paste0("Year=", year, "/data_0.parquet") +urls <- paste0(base_url, files) +tibble(urls) +#> # A tibble: 3 × 1 +#> urls +#> +#> 1 https://blobs.duckdb.org/flight-data-partitioned/Year=2022/data_0.pa… +#> 2 https://blobs.duckdb.org/flight-data-partitioned/Year=2023/data_0.pa… +#> 3 https://blobs.duckdb.org/flight-data-partitioned/Year=2024/data_0.pa… +``` + +Using the [httpfs DuckDB +extension](https://duckdb.org/docs/extensions/httpfs/overview.html), we +can query these files directly from R, without even downloading them +first. + +``` r +db_exec("INSTALL httpfs") +db_exec("LOAD httpfs") + +flights <- read_parquet_duckdb(urls) +``` + +Like with local data frames, queries on the remote data are executed +lazily. Unlike with local data frames, the default is to disallow +automatic materialization if the result is too large in order to protect +memory: the results are not materialized until explicitly requested, +with a `collect()` call for instance. + +``` r +nrow(flights) +#> Error: Materialization would result in more than 9090 rows. Use collect() or as_tibble() to materialize. +``` + +For printing, only the first few rows of the result are fetched. + +``` r +flights +#> # A duckplyr data frame: 110 variables +#> Year Quarter Month DayofMonth DayOfWeek FlightDate Report…¹ DOT_I…² +#> +#> 1 2022 1 1 14 5 2022-01-14 YX 20452 +#> 2 2022 1 1 15 6 2022-01-15 YX 20452 +#> 3 2022 1 1 16 7 2022-01-16 YX 20452 +#> 4 2022 1 1 17 1 2022-01-17 YX 20452 +#> 5 2022 1 1 18 2 2022-01-18 YX 20452 +#> 6 2022 1 1 19 3 2022-01-19 YX 20452 +#> 7 2022 1 1 20 4 2022-01-20 YX 20452 +#> 8 2022 1 1 21 5 2022-01-21 YX 20452 +#> 9 2022 1 1 22 6 2022-01-22 YX 20452 +#> 10 2022 1 1 23 7 2022-01-23 YX 20452 +#> # ℹ more rows +#> # ℹ abbreviated names: ¹​Reporting_Airline, ²​DOT_ID_Reporting_Airline +#> # ℹ 102 more variables: IATA_CODE_Reporting_Airline , +#> # Tail_Number , Flight_Number_Reporting_Airline , +#> # OriginAirportID , OriginAirportSeqID , +#> # OriginCityMarketID , Origin , OriginCityName , +#> # OriginState , OriginStateFips , OriginStateName , +#> # OriginWac , DestAirportID , DestAirportSeqID , +#> # DestCityMarketID , Dest , DestCityName , +#> # DestState , DestStateFips , DestStateName , +#> # DestWac , CRSDepTime , DepTime , DepDelay , +#> # DepDelayMinutes , DepDel15 , … +``` + +``` r +flights |> + count(Year) +#> # A duckplyr data frame: 2 variables +#> Year n +#> +#> 1 2022 6729125 +#> 2 2023 6847899 +#> 3 2024 3461319 +``` + +Complex queries can be executed on the remote data. Note how only the +relevant columns are fetched and the 2024 data isn’t even touched, as +it’s not needed for the result. + +``` r +out <- + flights |> + mutate(InFlightDelay = ArrDelay - DepDelay) |> + summarize( + .by = c(Year, Month), + MeanInFlightDelay = mean(InFlightDelay, na.rm = TRUE), + MedianInFlightDelay = median(InFlightDelay, na.rm = TRUE), + ) |> + filter(Year < 2024) + +out |> + explain() +#> ┌───────────────────────────┐ +#> │ HASH_GROUP_BY │ +#> │ ──────────────────── │ +#> │ Groups: │ +#> │ #0 │ +#> │ #1 │ +#> │ │ +#> │ Aggregates: │ +#> │ mean(#2) │ +#> │ median(#3) │ +#> │ │ +#> │ ~6729125 Rows │ +#> └─────────────┬─────────────┘ +#> ┌─────────────┴─────────────┐ +#> │ PROJECTION │ +#> │ ──────────────────── │ +#> │ Year │ +#> │ Month │ +#> │ InFlightDelay │ +#> │ InFlightDelay │ +#> │ │ +#> │ ~13458250 Rows │ +#> └─────────────┬─────────────┘ +#> ┌─────────────┴─────────────┐ +#> │ PROJECTION │ +#> │ ──────────────────── │ +#> │ Year │ +#> │ Month │ +#> │ InFlightDelay │ +#> │ │ +#> │ ~13458250 Rows │ +#> └─────────────┬─────────────┘ +#> ┌─────────────┴─────────────┐ +#> │ READ_PARQUET │ +#> │ ──────────────────── │ +#> │ Function: │ +#> │ READ_PARQUET │ +#> │ │ +#> │ Projections: │ +#> │ Year │ +#> │ Month │ +#> │ DepDelay │ +#> │ ArrDelay │ +#> │ │ +#> │ File Filters: │ +#> │ (CAST(Year AS DOUBLE) < │ +#> │ 2024.0) │ +#> │ │ +#> │ Scanning Files: 2/3 │ +#> │ │ +#> │ ~13458250 Rows │ +#> └───────────────────────────┘ + +out |> + print() |> + system.time() +#> # A duckplyr data frame: 4 variables +#> Year Month MeanInFlightDelay MedianInFlightDelay +#> +#> 1 2022 11 -5.21 -7 +#> 2 2023 11 -7.10 -8 +#> 3 2022 8 -5.27 -7 +#> 4 2023 4 -4.54 -6 +#> 5 2022 7 -5.13 -7 +#> 6 2022 4 -4.88 -6 +#> 7 2023 8 -5.73 -7 +#> 8 2023 7 -4.47 -7 +#> 9 2022 2 -6.52 -8 +#> 10 2023 5 -6.17 -7 +#> # ℹ more rows +#> user system elapsed +#> 1.145 0.455 9.402 +``` + +Over 10M rows analyzed in about 10 seconds over the internet, that’s not +bad. Of course, working with Parquet, CSV, or JSON files downloaded +locally is possible as well. + +For full compatibility, `na.rm = FALSE` by default in the aggregation +functions: + +``` r +flights |> + summarize(mean(ArrDelay - DepDelay)) +#> # A duckplyr data frame: 1 variable +#> `mean(ArrDelay - DepDelay)` +#> +#> 1 NA +``` + +## Further reading + +- [`vignette("large")`](https://duckplyr.tidyverse.org/articles/large.html): + Tools for working with large data + +- [`vignette("prudence")`](https://duckplyr.tidyverse.org/articles/prudence.html): + How duckplyr can help protect memory when working with large data + +- [`vignette("fallback")`](https://duckplyr.tidyverse.org/articles/fallback.html): + How the fallback to dplyr works internally + +- [`vignette("limits")`](https://duckplyr.tidyverse.org/articles/limits.html): + Translation of dplyr employed by duckplyr, and current limitations + +- [`vignette("duckdb")`](https://duckplyr.tidyverse.org/articles/duckdb.html): + Using the full power of DuckDB + +- [`vignette("developers")`](https://duckplyr.tidyverse.org/articles/developers.html): + Using duckplyr for individual data frames and in other packages + +- [`vignette("telemetry")`](https://duckplyr.tidyverse.org/articles/telemetry.html): + Telemetry in duckplyr + +## Getting help + +If you encounter a clear bug, please file an issue with a minimal +reproducible example on +[GitHub](https://github.com/tidyverse/duckplyr/issues). For questions +and other discussion, please use +[forum.posit.co](https://forum.posit.co/). + +## Code of conduct + +Please note that this project is released with a [Contributor Code of +Conduct](https://duckplyr.tidyverse.org/CODE_OF_CONDUCT). By +participating in this project you agree to abide by its terms. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/build/vignette.rds b/duckplyr.Rcheck/00_pkg_src/duckplyr/build/vignette.rds new file mode 100644 index 0000000000000000000000000000000000000000..935568bdbf7e27754eed252e46825519de9ca28e GIT binary patch literal 472 zcmV;}0Vn<+iwFP!000001Fcd`Pa821T{a0MX#z;xP!Dsf)I*d|Ar-h)Q6vON73I*Y zvz~y3*Is!Y!uHgk-4kIpcoK&zJ@{ju=h@FQ?{_yzk~C?y(j;xrwsAz$E{#JP?WCD> zXx*3igxVJ9%E4_e1KO4I>`_i*1fM+`86mxy(0VGe#|Z0a;qEb}X_YGA+VJ1{-%8^bX#dJc38mL>@SYlU94r} zt$^$WXEA|&#e|KkSbXFu3XfKb-jpRDSpU;K&6K8{Ahl&gj%7I94PbMkG+VKCr~Eo- z{2Wihc4u9kF&0!m_Fnr6tA%wGZ$H#=b}`_``ZUtNG%c6QfA1A#T9mAa!ESB(!ORh# OqL)_>Z?hI(1ONbIFXedv literal 0 HcmV?d00001 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.R new file mode 100644 index 000000000..8bfcf5106 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.R @@ -0,0 +1,74 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") + +## ----------------------------------------------------------------------------- +lazy <- + duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + mutate(inflight_delay = arr_delay - dep_delay) |> + summarize( + .by = c(year, month), + mean_inflight_delay = mean(inflight_delay, na.rm = TRUE), + median_inflight_delay = median(inflight_delay, na.rm = TRUE), + ) |> + filter(month <= 6) + +## ----------------------------------------------------------------------------- +class(lazy) + +names(lazy) + +## ----------------------------------------------------------------------------- +lazy |> + explain() + +## ----------------------------------------------------------------------------- +lazy$mean_inflight_delay + +## ----------------------------------------------------------------------------- +lazy + +## ----------------------------------------------------------------------------- +library(duckplyr) + +methods_restore() + +## ----error = TRUE------------------------------------------------------------- +try({ +flights_df() |> + mutate(inflight_delay = arr_delay - dep_delay) |> + explain() +}) + +## ----------------------------------------------------------------------------- +data <- duckdb_tibble( + x = 1:3, + y = 5, + z = letters[1:3] +) +data + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.Rmd new file mode 100644 index 000000000..feaa81e2f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.Rmd @@ -0,0 +1,140 @@ +--- +title: "Selective use of duckplyr" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{30 Selective use of duckplyr} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This vignette demonstrates how to use duckplyr selectively, for individual data frames or for other packages. + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + +## Introduction + +The default behavior of duckplyr is to enable itself for all data frames in the session. +This happens when the package is attached with `library(duckplyr)`, or by calling `methods_overwrite()`. +To enable duckplyr for individual data frames instead of session-wide, it is sufficient to prefix all calls to duckplyr functions with `duckplyr::` and not attach the package. +Alternatively, `methods_restore()` can be called to undo the session-wide overwrite after `library(duckplyr)`. + +## External data with explicit qualification + +The following example uses `duckplyr::as_duckdb_tibble()` to convert a data frame to a duckplyr frame and to enable duckplyr operation. + +```{r} +lazy <- + duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + mutate(inflight_delay = arr_delay - dep_delay) |> + summarize( + .by = c(year, month), + mean_inflight_delay = mean(inflight_delay, na.rm = TRUE), + median_inflight_delay = median(inflight_delay, na.rm = TRUE), + ) |> + filter(month <= 6) +``` + +The result is a tibble, with its own class. + +```{r} +class(lazy) + +names(lazy) +``` + +DuckDB is responsible for eventually carrying out the operations. +Despite the filter coming very late in the pipeline, it is applied to the raw data. + +```{r} +lazy |> + explain() +``` + +All data frame operations are supported. +Computation happens upon the first request. + +```{r} +lazy$mean_inflight_delay +``` + +After the computation has been carried out, the results are preserved and available immediately: + +```{r} +lazy +``` + +## Restoring dplyr methods + +The same can be achieved by calling `methods_restore()` after `library(duckplyr)`. + +```{r} +library(duckplyr) + +methods_restore() +``` + +If the input is a plain data frame, duckplyr is not involved. + +```{r error = TRUE} +flights_df() |> + mutate(inflight_delay = arr_delay - dep_delay) |> + explain() +``` + + +## Own data + +Construct duckplyr frames directly with `duckdb_tibble()`: + +```{r} +data <- duckdb_tibble( + x = 1:3, + y = 5, + z = letters[1:3] +) +data +``` + + +## In other packages + +Like other dependencies, duckplyr must be declared in the `DESCRIPTION` file and optionally imported in the `NAMESPACE` file. +Because duckplyr does not import dplyr, it is necessary to import both packages. +The recipe below shows how to achieve this with the usethis package. + +- Add dplyr as a dependency with `usethis::use_package("dplyr")` +- Add duckplyr as a dependency with `usethis::use_package("duckplyr")` +- In your code, use a pattern like `data |> duckplyr::as_duckdb_tibble() |> dplyr::filter(...)` +- To avoid the package prefix and simply write `as_duckdb_tibble()` or `filter()`: + - Import the duckplyr function with `usethis::use_import_from("duckplyr", "as_duckdb_tibble")` + - Import the dplyr function with `usethis::use_import_from("dplyr", "filter")` + +Learn more about prudence in `vignette("prudence")`, about fallbacks to dplyr in `vignette("fallback")`, about the translation employed by duckplyr in `vignette("limits")`, about direct use of DuckDB functions in `vignette("duckdb")`, and about the usethis package at . diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.html new file mode 100644 index 000000000..2acfa5868 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/developers.html @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + +Selective use of duckplyr + + + + + + + + + + + + + + + + + + + + + + + + + + +

Selective use of duckplyr

+ + + +

This vignette demonstrates how to use duckplyr selectively, for +individual data frames or for other packages.

+
library(conflicted)
+library(dplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Will prefer dplyr::filter over any other package.
+
+

Introduction

+

The default behavior of duckplyr is to enable itself for all data +frames in the session. This happens when the package is attached with +library(duckplyr), or by calling +methods_overwrite(). To enable duckplyr for individual data +frames instead of session-wide, it is sufficient to prefix all calls to +duckplyr functions with duckplyr:: and not attach the +package. Alternatively, methods_restore() can be called to +undo the session-wide overwrite after +library(duckplyr).

+
+
+

External data with explicit qualification

+

The following example uses duckplyr::as_duckdb_tibble() +to convert a data frame to a duckplyr frame and to enable duckplyr +operation.

+
lazy <-
+  duckplyr::flights_df() |>
+  duckplyr::as_duckdb_tibble() |>
+  mutate(inflight_delay = arr_delay - dep_delay) |>
+  summarize(
+    .by = c(year, month),
+    mean_inflight_delay = mean(inflight_delay, na.rm = TRUE),
+    median_inflight_delay = median(inflight_delay, na.rm = TRUE),
+  ) |>
+  filter(month <= 6)
+

The result is a tibble, with its own class.

+
class(lazy)
+#> [1] "duckplyr_df" "tbl_df"      "tbl"         "data.frame"
+
+names(lazy)
+#> [1] "year"                  "month"                 "mean_inflight_delay"  
+#> [4] "median_inflight_delay"
+

DuckDB is responsible for eventually carrying out the operations. +Despite the filter coming very late in the pipeline, it is applied to +the raw data.

+
lazy |>
+  explain()
+#> ┌---------------------------┐
+#> │       HASH_GROUP_BY       │
+#> │    --------------------   │
+#> │          Groups:          │
+#> │             #0            │
+#> │             #1            │
+#> │                           │
+#> │        Aggregates:        │
+#> │          mean(#2)         │
+#> │         median(#3)        │
+#> │                           │
+#> │        ~67,354 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │            year           │
+#> │           month           │
+#> │       inflight_delay      │
+#> │       inflight_delay      │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │            year           │
+#> │           month           │
+#> │       inflight_delay      │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │           FILTER          │
+#> │    --------------------   │
+#> │ (CAST(month AS DOUBLE) <= │
+#> │            6.0)           │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │                           │
+#> │        Projections:       │
+#> │            year           │
+#> │           month           │
+#> │         dep_delay         │
+#> │         arr_delay         │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └---------------------------┘
+

All data frame operations are supported. Computation happens upon the +first request.

+
lazy$mean_inflight_delay
+#> [1] -5.147220 -3.855519 -9.370201 -7.356713 -2.673124 -4.244284
+

After the computation has been carried out, the results are preserved +and available immediately:

+
lazy
+#> # A duckplyr data frame: 4 variables
+#>    year month mean_inflight_delay median_inflight_delay
+#>   <int> <int>               <dbl>                 <dbl>
+#> 1  2013     2               -5.15                    -6
+#> 2  2013     1               -3.86                    -5
+#> 3  2013     5               -9.37                   -10
+#> 4  2013     3               -7.36                    -9
+#> 5  2013     4               -2.67                    -5
+#> 6  2013     6               -4.24                    -7
+
+
+

Restoring dplyr methods

+

The same can be achieved by calling methods_restore() +after library(duckplyr).

+
library(duckplyr)
+#> ✔ Overwriting dplyr methods with duckplyr methods.
+#> ℹ Turn off with `duckplyr::methods_restore()`.
+
+methods_restore()
+#> ℹ Restoring dplyr methods.
+

If the input is a plain data frame, duckplyr is not involved.

+
flights_df() |>
+  mutate(inflight_delay = arr_delay - dep_delay) |>
+  explain()
+#> Error in UseMethod("explain"): no applicable method for 'explain' applied to an object of class "c('tbl_df', 'tbl', 'data.frame')"
+
+
+

Own data

+

Construct duckplyr frames directly with +duckdb_tibble():

+
data <- duckdb_tibble(
+  x = 1:3,
+  y = 5,
+  z = letters[1:3]
+)
+data
+#> # A duckplyr data frame: 3 variables
+#>       x     y z    
+#>   <int> <dbl> <chr>
+#> 1     1     5 a    
+#> 2     2     5 b    
+#> 3     3     5 c
+
+
+

In other packages

+

Like other dependencies, duckplyr must be declared in the +DESCRIPTION file and optionally imported in the +NAMESPACE file. Because duckplyr does not import dplyr, it +is necessary to import both packages. The recipe below shows how to +achieve this with the usethis package.

+
    +
  • Add dplyr as a dependency with +usethis::use_package("dplyr")
  • +
  • Add duckplyr as a dependency with +usethis::use_package("duckplyr")
  • +
  • In your code, use a pattern like +data |> duckplyr::as_duckdb_tibble() |> dplyr::filter(...)
  • +
  • To avoid the package prefix and simply write +as_duckdb_tibble() or filter(): +
      +
    • Import the duckplyr function with +usethis::use_import_from("duckplyr", "as_duckdb_tibble")
    • +
    • Import the dplyr function with +usethis::use_import_from("dplyr", "filter")
    • +
  • +
+

Learn more about prudence in vignette("prudence"), about +fallbacks to dplyr in vignette("fallback"), about the +translation employed by duckplyr in vignette("limits"), +about direct use of DuckDB functions in vignette("duckdb"), +and about the usethis package at https://usethis.r-lib.org/.

+
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.R new file mode 100644 index 000000000..c2a12b382 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.R @@ -0,0 +1,63 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13", "callr")) && duckplyr:::can_load_extension("httpfs")), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +# library(conflicted) +# library(duckplyr) +# conflict_prefer("filter", "dplyr") + +## ----------------------------------------------------------------------------- +# df <- duckdb_tibble(a = 2L) +# df +# +# tbl <- as_tbl(df) +# tbl + +## ----------------------------------------------------------------------------- +# tbl %>% +# mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>% +# show_query() + +## ----error = TRUE------------------------------------------------------------- +try({ +# least_common_multiple(2, 3) +}) + +## ----------------------------------------------------------------------------- +# tbl %>% +# mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) + +## ----------------------------------------------------------------------------- +# tbl %>% +# mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>% +# as_duckdb_tibble() + +## ----------------------------------------------------------------------------- +# duckdb_tibble(a = 2L, b = 3L) %>% +# mutate(c = dd$least_common_multiple(a, b)) + +## ----------------------------------------------------------------------------- +# duckdb_tibble(a = "dbplyr", b = "duckplyr") %>% +# mutate(c = dd$damerau_levenshtein(a, b)) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.Rmd new file mode 100644 index 000000000..35622c6c4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.Rmd @@ -0,0 +1,139 @@ +--- +title: "Interoperability with DuckDB and dbplyr" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{25 Interoperability with DuckDB and dbplyr} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13", "callr")) && duckplyr:::can_load_extension("httpfs")), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This article describes how to use the full power of DuckDB with duckplyr. +Two options are discussed: interoperability with dbplyr and the use of DuckDB's functions in duckplyr. + + +```{r attach} +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") +``` + + +## Introduction + +The duckplyr package is a drop-in replacement for dplyr, designed to work with DuckDB as the backend. +There is a translation layer that converts R function calls to DuckDB functions or macros, aiming at full compatibility with R. +Many functions are translated already, and many more are not. +For functions that cannot be translated, duckplyr falls back to the original R implementation, disrupting the DuckDB pipeline and materializing intermediate results. + +Furthermore, DuckDB has functions with no R equivalent. +These might be used already by code that interacts with DuckDB through dbplyr, either making use of its passthrough feature (unknown functions are translated to SQL verbatim), or by using the `mutate(x = sql(...))` pattern. +When working with duckplyr, this functionality is still accessible, albeit through experimental interfaces: + +- `as_tbl()` converts a duckplyr table to a duckdb `tbl` object +- for a duckplyr table, the escape hatch `dd$fun(...)` can be used to call arbitrary DuckDB functions + +## From duckplyr to dbplyr + +The experimental `as_tbl()` function, introduced in duckplyr 1.1.0, transparently converts a duckplyr frame to a dbplyr `tbl` object: + +```{r} +df <- duckdb_tibble(a = 2L) +df + +tbl <- as_tbl(df) +tbl +``` + +It achieves this by creating a temporary view that points to the relational object created internally by duckplyr, in the same DBI connection as the duckplyr object. +No data is copied in this operation. +The view is discarded when the `tbl` object goes out of scope. + +This allows using arbitrary SQL code, either through `sql()` or by relying on dbplyr's passthrough feature. + +```{r} +tbl %>% + mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>% + show_query() +``` + +There is no R function called `least_common_multiple()`, it is interpreted as a SQL function. + +```{r, error = TRUE} +least_common_multiple(2, 3) +``` + +```{r} +tbl %>% + mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) +``` + +To continue processing with duckplyr, use `as_duckdb_tibble()`: + +```{r} +tbl %>% + mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>% + as_duckdb_tibble() +``` + +## Call arbitrary functions in duckplyr + +The escape hatch, also introduced in duckplyr 1.1.0, allows calling arbitrary DuckDB functions directly from duckplyr, without going through SQL: + +```{r} +duckdb_tibble(a = 2L, b = 3L) %>% + mutate(c = dd$least_common_multiple(a, b)) +``` + +The `dd` prefix has been picked for the following reasons: + +- it is an abbreviation of "DuckDB" +- it is short and easy to type +- there is no package of this name +- objects are not commonly named `dd` in R + +A prefix is necessary to avoid name clashes with existing R functions. +If this is used widely, large-scale code analysis may help prioritize the translation of functions that are not yet supported by duckplyr. + +The [dd package](https://github.com/cynkra/dd), when attached, will provide a `dd` object containing many known DuckDB functions. +This adds support for autocomplete: + +![Screenshot for autocomplete with the dd package](dd.png) + +This package is not necessary to use duckplyr, and the list of functions is incomplete and growing. +In case you're wondering: + +```{r} +duckdb_tibble(a = "dbplyr", b = "duckplyr") %>% + mutate(c = dd$damerau_levenshtein(a, b)) +``` + +## Conclusion + +While duckplyr is designed to be a drop-in replacement for dplyr, it still allows to harness most if not all of the power of DuckDB. + +See `vignette("limits")` for limitations in the translation employed by duckplyr, `vignette("fallback")` for more information on fallback, and `vignette("telemetry")` for existing attempts to prioritize work on the translation layer. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.html new file mode 100644 index 000000000..3b14c3854 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/duckdb.html @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + +Interoperability with DuckDB and dbplyr + + + + + + + + + + + + + + + + + + + + + + + + + + +

Interoperability with DuckDB and +dbplyr

+ + + +

This article describes how to use the full power of DuckDB with +duckplyr. Two options are discussed: interoperability with dbplyr and +the use of DuckDB’s functions in duckplyr.

+
library(conflicted)
+library(duckplyr)
+conflict_prefer("filter", "dplyr")
+
+

Introduction

+

The duckplyr package is a drop-in replacement for dplyr, designed to +work with DuckDB as the backend. There is a translation layer that +converts R function calls to DuckDB functions or macros, aiming at full +compatibility with R. Many functions are translated already, and many +more are not. For functions that cannot be translated, duckplyr falls +back to the original R implementation, disrupting the DuckDB pipeline +and materializing intermediate results.

+

Furthermore, DuckDB has functions with no R equivalent. These might +be used already by code that interacts with DuckDB through dbplyr, +either making use of its passthrough feature (unknown functions are +translated to SQL verbatim), or by using the +mutate(x = sql(...)) pattern. When working with duckplyr, +this functionality is still accessible, albeit through experimental +interfaces:

+
    +
  • as_tbl() converts a duckplyr table to a duckdb +tbl object
  • +
  • for a duckplyr table, the escape hatch dd$fun(...) can +be used to call arbitrary DuckDB functions
  • +
+
+
+

From duckplyr to dbplyr

+

The experimental as_tbl() function, introduced in +duckplyr 1.1.0, transparently converts a duckplyr frame to a dbplyr +tbl object:

+
df <- duckdb_tibble(a = 2L)
+df
+
+tbl <- as_tbl(df)
+tbl
+

It achieves this by creating a temporary view that points to the +relational object created internally by duckplyr, in the same DBI +connection as the duckplyr object. No data is copied in this operation. +The view is discarded when the tbl object goes out of +scope.

+

This allows using arbitrary SQL code, either through +sql() or by relying on dbplyr’s passthrough feature.

+
tbl %>%
+  mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>%
+  show_query()
+

There is no R function called least_common_multiple(), +it is interpreted as a SQL function.

+
least_common_multiple(2, 3)
+
tbl %>%
+  mutate(b = sql("a + 1"), c = least_common_multiple(a, b))
+

To continue processing with duckplyr, use +as_duckdb_tibble():

+
tbl %>%
+  mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>%
+  as_duckdb_tibble()
+
+
+

Call arbitrary functions in duckplyr

+

The escape hatch, also introduced in duckplyr 1.1.0, allows calling +arbitrary DuckDB functions directly from duckplyr, without going through +SQL:

+
duckdb_tibble(a = 2L, b = 3L) %>%
+  mutate(c = dd$least_common_multiple(a, b))
+

The dd prefix has been picked for the following +reasons:

+
    +
  • it is an abbreviation of “DuckDB”
  • +
  • it is short and easy to type
  • +
  • there is no package of this name
  • +
  • objects are not commonly named dd in R
  • +
+

A prefix is necessary to avoid name clashes with existing R +functions. If this is used widely, large-scale code analysis may help +prioritize the translation of functions that are not yet supported by +duckplyr.

+

The dd package, when +attached, will provide a dd object containing many known +DuckDB functions. This adds support for autocomplete:

+
+Screenshot for autocomplete with the dd package +
Screenshot for autocomplete with the dd +package
+
+

This package is not necessary to use duckplyr, and the list of +functions is incomplete and growing. In case you’re wondering:

+
duckdb_tibble(a = "dbplyr", b = "duckplyr") %>%
+  mutate(c = dd$damerau_levenshtein(a, b))
+
+
+

Conclusion

+

While duckplyr is designed to be a drop-in replacement for dplyr, it +still allows to harness most if not all of the power of DuckDB.

+

See vignette("limits") for limitations in the +translation employed by duckplyr, vignette("fallback") for +more information on fallback, and vignette("telemetry") for +existing attempts to prioritize work on the translation layer.

+
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.R new file mode 100644 index 000000000..083a95645 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.R @@ -0,0 +1,139 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") + +## ----extensibility------------------------------------------------------------ +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +library(duckplyr) + +## ----overwrite, echo = FALSE-------------------------------------------------- +methods_overwrite() + +## ----extensibility2----------------------------------------------------------- +# Create a relational to be used by examples below +new_dfrel <- function(x) { + stopifnot(is.data.frame(x)) + new_relational(list(x), class = "dfrel") +} +mtcars_rel <- new_dfrel(mtcars[1:5, 1:4]) + +# Example 1: return a data.frame +rel_to_df.dfrel <- function(rel, ...) { + unclass(rel)[[1]] +} +rel_to_df(mtcars_rel) + +# Example 2: A (random) filter +rel_filter.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the predicates defined + # by the exprs argument + new_dfrel(df[sample.int(nrow(df), 3, replace = TRUE), ]) +} + +rel_filter( + mtcars_rel, + list( + relexpr_function( + "gt", + list(relexpr_reference("cyl"), relexpr_constant("6")) + ) + ) +) + +# Example 3: A custom projection +rel_project.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, base::ncol(df)))]) +} + +rel_project( + mtcars_rel, + list(relexpr_reference("cyl"), relexpr_reference("disp")) +) + +# Example 4: A custom ordering (eg, ascending by mpg) +rel_order.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[order(df[[1]]), ]) +} + +rel_order( + mtcars_rel, + list(relexpr_reference("mpg")) +) + +# Example 5: A custom join +rel_join.dfrel <- function(left, right, conds, join, ...) { + left_df <- unclass(left)[[1]] + right_df <- unclass(right)[[1]] + + # A real implementation would evaluate the expressions + # defined by the conds argument, + # use different join types based on the join argument, + # and implement the join itself instead of relaying to left_join(). + new_dfrel(dplyr::left_join(left_df, right_df)) +} + +rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel) + +# Example 6: Limit the maximum rows returned +rel_limit.dfrel <- function(rel, n, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[seq_len(n), ]) +} + +rel_limit(mtcars_rel, 3) + +# Example 7: Suppress duplicate rows +# (ignoring row names) +rel_distinct.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[!duplicated(df), ]) +} + +rel_distinct(new_dfrel(mtcars[1:3, 1:4])) + +# Example 8: Return column names +rel_names.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + names(df) +} + +rel_names(mtcars_rel) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.Rmd new file mode 100644 index 000000000..456c7cbde --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.Rmd @@ -0,0 +1,156 @@ +--- +title: "Implementer's interface" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{99 Implementer's interface} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + +duckplyr also defines a set of generics that provide a low-level implementer's interface for dplyr's high-level user interface. +Other packages may then implement methods for those generics. + +```{r extensibility} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +library(duckplyr) +``` + + +```{r overwrite, echo = FALSE} +methods_overwrite() +``` + +```{r extensibility2} +# Create a relational to be used by examples below +new_dfrel <- function(x) { + stopifnot(is.data.frame(x)) + new_relational(list(x), class = "dfrel") +} +mtcars_rel <- new_dfrel(mtcars[1:5, 1:4]) + +# Example 1: return a data.frame +rel_to_df.dfrel <- function(rel, ...) { + unclass(rel)[[1]] +} +rel_to_df(mtcars_rel) + +# Example 2: A (random) filter +rel_filter.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the predicates defined + # by the exprs argument + new_dfrel(df[sample.int(nrow(df), 3, replace = TRUE), ]) +} + +rel_filter( + mtcars_rel, + list( + relexpr_function( + "gt", + list(relexpr_reference("cyl"), relexpr_constant("6")) + ) + ) +) + +# Example 3: A custom projection +rel_project.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, base::ncol(df)))]) +} + +rel_project( + mtcars_rel, + list(relexpr_reference("cyl"), relexpr_reference("disp")) +) + +# Example 4: A custom ordering (eg, ascending by mpg) +rel_order.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[order(df[[1]]), ]) +} + +rel_order( + mtcars_rel, + list(relexpr_reference("mpg")) +) + +# Example 5: A custom join +rel_join.dfrel <- function(left, right, conds, join, ...) { + left_df <- unclass(left)[[1]] + right_df <- unclass(right)[[1]] + + # A real implementation would evaluate the expressions + # defined by the conds argument, + # use different join types based on the join argument, + # and implement the join itself instead of relaying to left_join(). + new_dfrel(dplyr::left_join(left_df, right_df)) +} + +rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel) + +# Example 6: Limit the maximum rows returned +rel_limit.dfrel <- function(rel, n, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[seq_len(n), ]) +} + +rel_limit(mtcars_rel, 3) + +# Example 7: Suppress duplicate rows +# (ignoring row names) +rel_distinct.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[!duplicated(df), ]) +} + +rel_distinct(new_dfrel(mtcars[1:3, 1:4])) + +# Example 8: Return column names +rel_names.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + names(df) +} + +rel_names(mtcars_rel) +``` diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.html new file mode 100644 index 000000000..4b275d969 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/extend.html @@ -0,0 +1,536 @@ + + + + + + + + + + + + + + +Implementer’s interface + + + + + + + + + + + + + + + + + + + + + + + + + + +

Implementer’s interface

+ + + +
library(conflicted)
+library(dplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Removing existing preference.
+#> [conflicted] Will prefer dplyr::filter over any other package.
+

duckplyr also defines a set of generics that provide a low-level +implementer’s interface for dplyr’s high-level user interface. Other +packages may then implement methods for those generics.

+
library(conflicted)
+library(dplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Removing existing preference.
+#> [conflicted] Will prefer dplyr::filter over any other package.
+library(duckplyr)
+
#> ✔ Overwriting dplyr methods with duckplyr methods.
+#> ℹ Turn off with `duckplyr::methods_restore()`.
+
# Create a relational to be used by examples below
+new_dfrel <- function(x) {
+  stopifnot(is.data.frame(x))
+  new_relational(list(x), class = "dfrel")
+}
+mtcars_rel <- new_dfrel(mtcars[1:5, 1:4])
+
+# Example 1: return a data.frame
+rel_to_df.dfrel <- function(rel, ...) {
+  unclass(rel)[[1]]
+}
+rel_to_df(mtcars_rel)
+#>                    mpg cyl disp  hp
+#> Mazda RX4         21.0   6  160 110
+#> Mazda RX4 Wag     21.0   6  160 110
+#> Datsun 710        22.8   4  108  93
+#> Hornet 4 Drive    21.4   6  258 110
+#> Hornet Sportabout 18.7   8  360 175
+
+# Example 2: A (random) filter
+rel_filter.dfrel <- function(rel, exprs, ...) {
+  df <- unclass(rel)[[1]]
+
+  # A real implementation would evaluate the predicates defined
+  # by the exprs argument
+  new_dfrel(df[sample.int(nrow(df), 3, replace = TRUE), ])
+}
+
+rel_filter(
+  mtcars_rel,
+  list(
+    relexpr_function(
+      "gt",
+      list(relexpr_reference("cyl"), relexpr_constant("6"))
+    )
+  )
+)
+#> [[1]]
+#>                    mpg cyl disp  hp
+#> Hornet 4 Drive    21.4   6  258 110
+#> Hornet Sportabout 18.7   8  360 175
+#> Datsun 710        22.8   4  108  93
+#> 
+#> attr(,"class")
+#> [1] "dfrel"      "relational"
+
+# Example 3: A custom projection
+rel_project.dfrel <- function(rel, exprs, ...) {
+  df <- unclass(rel)[[1]]
+
+  # A real implementation would evaluate the expressions defined
+  # by the exprs argument
+  new_dfrel(df[seq_len(min(3, base::ncol(df)))])
+}
+
+rel_project(
+  mtcars_rel,
+  list(relexpr_reference("cyl"), relexpr_reference("disp"))
+)
+#> [[1]]
+#>                    mpg cyl disp
+#> Mazda RX4         21.0   6  160
+#> Mazda RX4 Wag     21.0   6  160
+#> Datsun 710        22.8   4  108
+#> Hornet 4 Drive    21.4   6  258
+#> Hornet Sportabout 18.7   8  360
+#> 
+#> attr(,"class")
+#> [1] "dfrel"      "relational"
+
+# Example 4: A custom ordering (eg, ascending by mpg)
+rel_order.dfrel <- function(rel, exprs, ...) {
+  df <- unclass(rel)[[1]]
+
+  # A real implementation would evaluate the expressions defined
+  # by the exprs argument
+  new_dfrel(df[order(df[[1]]), ])
+}
+
+rel_order(
+  mtcars_rel,
+  list(relexpr_reference("mpg"))
+)
+#> [[1]]
+#>                    mpg cyl disp  hp
+#> Hornet Sportabout 18.7   8  360 175
+#> Mazda RX4         21.0   6  160 110
+#> Mazda RX4 Wag     21.0   6  160 110
+#> Hornet 4 Drive    21.4   6  258 110
+#> Datsun 710        22.8   4  108  93
+#> 
+#> attr(,"class")
+#> [1] "dfrel"      "relational"
+
+# Example 5: A custom join
+rel_join.dfrel <- function(left, right, conds, join, ...) {
+  left_df <- unclass(left)[[1]]
+  right_df <- unclass(right)[[1]]
+
+  # A real implementation would evaluate the expressions
+  # defined by the conds argument,
+  # use different join types based on the join argument,
+  # and implement the join itself instead of relaying to left_join().
+  new_dfrel(dplyr::left_join(left_df, right_df))
+}
+
+rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel)
+#> Joining with `by = join_by(mpg)`
+#> Joining with `by = join_by(mpg)`
+#> [[1]]
+#>   mpg cyl disp  hp
+#> 1  21   6  160 110
+#> 2  21   6  160 110
+#> 
+#> attr(,"class")
+#> [1] "dfrel"      "relational"
+
+# Example 6: Limit the maximum rows returned
+rel_limit.dfrel <- function(rel, n, ...) {
+  df <- unclass(rel)[[1]]
+
+  new_dfrel(df[seq_len(n), ])
+}
+
+rel_limit(mtcars_rel, 3)
+#> [[1]]
+#>                mpg cyl disp  hp
+#> Mazda RX4     21.0   6  160 110
+#> Mazda RX4 Wag 21.0   6  160 110
+#> Datsun 710    22.8   4  108  93
+#> 
+#> attr(,"class")
+#> [1] "dfrel"      "relational"
+
+# Example 7: Suppress duplicate rows
+#  (ignoring row names)
+rel_distinct.dfrel <- function(rel, ...) {
+  df <- unclass(rel)[[1]]
+
+  new_dfrel(df[!duplicated(df), ])
+}
+
+rel_distinct(new_dfrel(mtcars[1:3, 1:4]))
+#> [[1]]
+#>             mpg cyl disp  hp
+#> Mazda RX4  21.0   6  160 110
+#> Datsun 710 22.8   4  108  93
+#> 
+#> attr(,"class")
+#> [1] "dfrel"      "relational"
+
+# Example 8: Return column names
+rel_names.dfrel <- function(rel, ...) {
+  df <- unclass(rel)[[1]]
+
+  names(df)
+}
+
+rel_names(mtcars_rel)
+#> [1] "mpg"  "cyl"  "disp" "hp"
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.R new file mode 100644 index 000000000..6b47158ba --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.R @@ -0,0 +1,71 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13"))), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") + +## ----------------------------------------------------------------------------- +duckdb <- + duckplyr::duckdb_tibble(a = 1:3) |> + arrange(desc(a)) |> + mutate(b = a + 1) |> + select(-a) + +## ----------------------------------------------------------------------------- +duckdb |> + explain() + +## ----------------------------------------------------------------------------- +duckplyr::last_rel() + +## ----------------------------------------------------------------------------- +duckdb |> collect() +duckplyr::last_rel() + +## ----------------------------------------------------------------------------- +verbose_plus_one <- function(x) { + message("Adding one to ", paste(x, collapse = ", ")) + x + 1 +} + +fallback <- + duckplyr::duckdb_tibble(a = 1:3) |> + arrange(desc(a)) |> + mutate(b = verbose_plus_one(a)) |> + select(-a) + +## ----------------------------------------------------------------------------- +duckplyr::last_rel() + +## ----------------------------------------------------------------------------- +fallback |> + explain() + +## ----------------------------------------------------------------------------- +fallback |> collect() + +duckplyr::last_rel() + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.Rmd new file mode 100644 index 000000000..b4c4bcaee --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.Rmd @@ -0,0 +1,178 @@ +--- +title: "Fallback to dplyr" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{15 Fallback} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13"))), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This article details the fallback mechanism in duckplyr, which allows support for all dplyr verbs and R functions. + + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + + +## Introduction + +The duckplyr package aims at providing a fully compatible drop-in replacement for dplyr. +All operations, R functions, and data types that are supported by dplyr should work in an identical way with duckplyr. +This is achieved in two ways: + +- A carefully selected subset of dplyr operations, R functions, and R data types are implemented in DuckDB, focusing on faithful translation. +- When DuckDB does not support an operation, duckplyr falls back to dplyr, guaranteeing identical behavior. + +## DuckDB mode + +The following operation is supported by duckplyr: + +```{r} +duckdb <- + duckplyr::duckdb_tibble(a = 1:3) |> + arrange(desc(a)) |> + mutate(b = a + 1) |> + select(-a) +``` + +The `explain()` function shows what happens under the hood: + +```{r} +duckdb |> + explain() +``` + +The plan shows three operations: + +- a data frame scan (the input), +- a sort operation, +- a projection (adding the `b` column and removing the `a` column). + +Each operation is supported by DuckDB. +The resulting object contains a plan for the entire pipeline that is executed lazily, only when the data is needed. + +## Relation objects + +DuckDB accepts a tree of interconnected _relation objects_ as input. +Each relation object represents a logical step of the execution plan. +The duckplyr package translates dplyr verbs into relation objects. + +The `last_rel()` function shows the last relation that has been materialized: + +```{r} +duckplyr::last_rel() +``` + +It is `NULL` because nothing has been computed yet. +Converting the object to a data frame triggers the computation: + +```{r} +duckdb |> collect() +duckplyr::last_rel() +``` + +The `last_rel()` function now shows a relation that describes logical plan for executing the whole pipeline. + +## Help from dplyr + +Using a custom function with a side effect is not supported by DuckDB and triggers a dplyr fallback: + +```{r} +verbose_plus_one <- function(x) { + message("Adding one to ", paste(x, collapse = ", ")) + x + 1 +} + +fallback <- + duckplyr::duckdb_tibble(a = 1:3) |> + arrange(desc(a)) |> + mutate(b = verbose_plus_one(a)) |> + select(-a) +``` + +The `verbose_plus_one()` function is not supported by DuckDB, so the `mutate()` step is forwarded to dplyr and already executed (eagerly) when the pipeline is defined. +This is confirmed by the `last_rel()` function: + +```{r} +duckplyr::last_rel() +``` + +Only the `arrange()` step is executed by DuckDB. +Because the dplyr implementation of `mutate()` needs the data before it can proceed, the data is first converted to a data frame, and this triggers the materialization of the first step. + +The `explain()` function also confirms indirectly that at least a part of the operation is handled by dplyr: + +```{r} +fallback |> + explain() +``` + +The final plan now only consists of a data frame scan. +This is the result of the `mutate()` step, which at this stage already has been executed by dplyr. + +Converting the final object to a data frame triggers the rest of the computation: + +```{r} +fallback |> collect() + +duckplyr::last_rel() +``` + +The `last_rel()` function confirms that only the final `select()` is handled by DuckDB again. + +## Enforce DuckDB operation + +For any duck frame, one can control the automatic materialization. +For fallbacks to dplyr, automatic materialization must be allowed for the duck frame at hand, as dplyr necessitates eager evaluation. + +Therefore, by making a data frame stingy, one can ensure a pipeline will error when a fallback to dplyr would have normally happened. +See `vignette("prudence")` for details. + +By using operations supported by duckplyr and avoiding fallbacks as much as possible, your pipelines will be executed by DuckDB in an optimized way. +As of duckplyr 1.1.0, most DuckDB functions can be used directly, see `vignette("duckdb")` for details. + +## Configure fallbacks + +Using the `fallback_sitrep()` and `fallback_config()` functions you can examine and change settings related to fallbacks. + +- You can choose to make fallbacks verbose with `fallback_config(info = TRUE)`. + +- You can change settings related to logging and reporting fallback to duckplyr development team to inform their work. + +See `vignette("telemetry")` for details. + +## Conclusion + +The fallback mechanism in duckplyr allows for a seamless integration of dplyr verbs and R functions that are not supported by DuckDB. +It is transparent to the user and only triggers when necessary. +With small or medium-sized data sets, it will not even be noticeable in most settings. + +See `vignette("large")` for techniques for working with large data, `vignette("limits")` for the currently implementated translations, `vignette("duckdb")` for direct access to DuckDB functions, `vignette("prudence")` for details on controlling fallback behavior, and `vignette("telemetry")` for the automatic reporting of fallback situations. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.html new file mode 100644 index 000000000..66d8a7e9c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/fallback.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + +Fallback to dplyr + + + + + + + + + + + + + + + + + + + + + + + + + + +

Fallback to dplyr

+ + + +

This article details the fallback mechanism in duckplyr, which allows +support for all dplyr verbs and R functions.

+
library(conflicted)
+library(dplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Removing existing preference.
+#> [conflicted] Will prefer dplyr::filter over any other package.
+
+

Introduction

+

The duckplyr package aims at providing a fully compatible drop-in +replacement for dplyr. All operations, R functions, and data types that +are supported by dplyr should work in an identical way with duckplyr. +This is achieved in two ways:

+
    +
  • A carefully selected subset of dplyr operations, R functions, and R +data types are implemented in DuckDB, focusing on faithful +translation.
  • +
  • When DuckDB does not support an operation, duckplyr falls back to +dplyr, guaranteeing identical behavior.
  • +
+
+
+

DuckDB mode

+

The following operation is supported by duckplyr:

+
duckdb <-
+  duckplyr::duckdb_tibble(a = 1:3) |>
+  arrange(desc(a)) |>
+  mutate(b = a + 1) |>
+  select(-a)
+

The explain() function shows what happens under the +hood:

+
duckdb |>
+  explain()
+#> ┌---------------------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │             b             │
+#> │                           │
+#> │          ~3 rows          │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │          ORDER_BY         │
+#> │    --------------------   │
+#> │      dataframe_42_42      │
+#> │       0344959.a DESC      │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │       Projections: a      │
+#> │                           │
+#> │          ~3 rows          │
+#> └---------------------------┘
+

The plan shows three operations:

+
    +
  • a data frame scan (the input),
  • +
  • a sort operation,
  • +
  • a projection (adding the b column and removing the +a column).
  • +
+

Each operation is supported by DuckDB. The resulting object contains +a plan for the entire pipeline that is executed lazily, only when the +data is needed.

+
+
+

Relation objects

+

DuckDB accepts a tree of interconnected relation objects as +input. Each relation object represents a logical step of the execution +plan. The duckplyr package translates dplyr verbs into relation +objects.

+

The last_rel() function shows the last relation that has +been materialized:

+
duckplyr::last_rel()
+#> DuckDB Relation: 
+#> ---------------------
+#> --- Relation Tree ---
+#> ---------------------
+#> Filter [("month" <= 6.0)]
+#>   Aggregate ["year", "month", mean(inflight_delay), median(inflight_delay)]
+#>     Projection ["year" as year, "month" as month, "day" as day, dep_time as dep_time, sched_dep_time as sched_dep_time, dep_delay as dep_delay, arr_time as arr_time, sched_arr_time as sched_arr_time, arr_delay as arr_delay, carrier as carrier, flight as flight, tailnum as tailnum, origin as origin, dest as dest, air_time as air_time, distance as distance, "hour" as hour, "minute" as minute, time_hour as time_hour, "-"(arr_delay, dep_delay) as inflight_delay]
+#>       r_dataframe_scan(0xdeadbeef)
+#> 
+#> ---------------------
+#> -- Result Columns  --
+#> ---------------------
+#> - year (INTEGER)
+#> - month (INTEGER)
+#> - mean_inflight_delay (DOUBLE)
+#> - median_inflight_delay (DOUBLE)
+

It is NULL because nothing has been computed yet. +Converting the object to a data frame triggers the computation:

+
duckdb |> collect()
+#> # A tibble: 3 × 1
+#>       b
+#>   <dbl>
+#> 1     4
+#> 2     3
+#> 3     2
+duckplyr::last_rel()
+#> DuckDB Relation: 
+#> ---------------------
+#> --- Relation Tree ---
+#> ---------------------
+#> Projection [b as b]
+#>   Projection [a as a, "+"(a, 1.0) as b]
+#>     Order [a DESC]
+#>       r_dataframe_scan(0xdeadbeef)
+#> 
+#> ---------------------
+#> -- Result Columns  --
+#> ---------------------
+#> - b (DOUBLE)
+

The last_rel() function now shows a relation that +describes logical plan for executing the whole pipeline.

+
+
+

Help from dplyr

+

Using a custom function with a side effect is not supported by DuckDB +and triggers a dplyr fallback:

+
verbose_plus_one <- function(x) {
+  message("Adding one to ", paste(x, collapse = ", "))
+  x + 1
+}
+
+fallback <-
+  duckplyr::duckdb_tibble(a = 1:3) |>
+  arrange(desc(a)) |>
+  mutate(b = verbose_plus_one(a)) |>
+  select(-a)
+#> Adding one to 3, 2, 1
+

The verbose_plus_one() function is not supported by +DuckDB, so the mutate() step is forwarded to dplyr and +already executed (eagerly) when the pipeline is defined. This is +confirmed by the last_rel() function:

+
duckplyr::last_rel()
+#> DuckDB Relation: 
+#> ---------------------
+#> --- Relation Tree ---
+#> ---------------------
+#> Order [a DESC]
+#>   r_dataframe_scan(0xdeadbeef)
+#> 
+#> ---------------------
+#> -- Result Columns  --
+#> ---------------------
+#> - a (INTEGER)
+

Only the arrange() step is executed by DuckDB. Because +the dplyr implementation of mutate() needs the data before +it can proceed, the data is first converted to a data frame, and this +triggers the materialization of the first step.

+

The explain() function also confirms indirectly that at +least a part of the operation is handled by dplyr:

+
fallback |>
+  explain()
+#> ┌---------------------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │       Projections: b      │
+#> │                           │
+#> │          ~3 rows          │
+#> └---------------------------┘
+

The final plan now only consists of a data frame scan. This is the +result of the mutate() step, which at this stage already +has been executed by dplyr.

+

Converting the final object to a data frame triggers the rest of the +computation:

+
fallback |> collect()
+#> # A tibble: 3 × 1
+#>       b
+#>   <dbl>
+#> 1     4
+#> 2     3
+#> 3     2
+
+duckplyr::last_rel()
+#> DuckDB Relation: 
+#> ---------------------
+#> --- Relation Tree ---
+#> ---------------------
+#> Projection [b as b]
+#>   r_dataframe_scan(0xdeadbeef)
+#> 
+#> ---------------------
+#> -- Result Columns  --
+#> ---------------------
+#> - b (DOUBLE)
+

The last_rel() function confirms that only the final +select() is handled by DuckDB again.

+
+
+

Enforce DuckDB operation

+

For any duck frame, one can control the automatic materialization. +For fallbacks to dplyr, automatic materialization must be allowed for +the duck frame at hand, as dplyr necessitates eager evaluation.

+

Therefore, by making a data frame stingy, one can ensure a pipeline +will error when a fallback to dplyr would have normally happened. See +vignette("prudence") for details.

+

By using operations supported by duckplyr and avoiding fallbacks as +much as possible, your pipelines will be executed by DuckDB in an +optimized way. As of duckplyr 1.1.0, most DuckDB functions can be used +directly, see vignette("duckdb") for details.

+
+
+

Configure fallbacks

+

Using the fallback_sitrep() and +fallback_config() functions you can examine and change +settings related to fallbacks.

+
    +
  • You can choose to make fallbacks verbose with +fallback_config(info = TRUE).

  • +
  • You can change settings related to logging and reporting fallback +to duckplyr development team to inform their work.

  • +
+

See vignette("telemetry") for details.

+
+
+

Conclusion

+

The fallback mechanism in duckplyr allows for a seamless integration +of dplyr verbs and R functions that are not supported by DuckDB. It is +transparent to the user and only triggers when necessary. With small or +medium-sized data sets, it will not even be noticeable in most +settings.

+

See vignette("large") for techniques for working with +large data, vignette("limits") for the currently +implementated translations, vignette("duckdb") for direct +access to DuckDB functions, vignette("prudence") for +details on controlling fallback behavior, and +vignette("telemetry") for the automatic reporting of +fallback situations.

+
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.R new file mode 100644 index 000000000..34cba4512 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.R @@ -0,0 +1,163 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13", "callr")) && duckplyr:::can_load_extension("httpfs")), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +# library(conflicted) +# library(duckplyr) +# conflict_prefer("filter", "dplyr") + +## ----------------------------------------------------------------------------- +# df <- duckdb_tibble(x = 1:3, y = letters[1:3]) +# df + +## ----------------------------------------------------------------------------- +# flights_df() |> +# as_duckdb_tibble() + +## ----------------------------------------------------------------------------- +# path_duckdb <- tempfile(fileext = ".duckdb") +# con <- DBI::dbConnect(duckdb::duckdb(path_duckdb)) +# DBI::dbWriteTable(con, "data", data.frame(x = 1:3, y = letters[1:3])) +# +# dbplyr_data <- tbl(con, "data") +# dbplyr_data +# +# dbplyr_data |> +# explain() + +## ----------------------------------------------------------------------------- +# dbplyr_data |> +# as_duckdb_tibble() +# +# dbplyr_data |> +# as_duckdb_tibble() |> +# explain() + +## ----------------------------------------------------------------------------- +# DBI::dbDisconnect(con) + +## ----error = TRUE------------------------------------------------------------- +try({ +# duckdb_tibble(a = 1) |> +# group_by(a) |> +# as_duckdb_tibble() +}) + +## ----error = TRUE------------------------------------------------------------- +try({ +# duckdb_tibble(a = 1) |> +# rowwise() |> +# as_duckdb_tibble() +}) + +## ----error = TRUE------------------------------------------------------------- +try({ +# readr::read_csv("a\n1", show_col_types = FALSE) |> +# as_duckdb_tibble() +}) + +## ----------------------------------------------------------------------------- +# path_csv_1 <- tempfile(fileext = ".csv") +# writeLines("x,y\n1,a\n2,b\n3,c", path_csv_1) +# read_csv_duckdb(path_csv_1) + +## ----------------------------------------------------------------------------- +# path_csv_2 <- tempfile(fileext = ".csv") +# writeLines("x,y\n4,d\n5,e\n6,f", path_csv_2) +# read_csv_duckdb(c(path_csv_1, path_csv_2)) + +## ----------------------------------------------------------------------------- +# db_exec("INSTALL httpfs") +# db_exec("LOAD httpfs") + +## ----------------------------------------------------------------------------- +# url <- "https://blobs.duckdb.org/flight-data-partitioned/Year=2024/data_0.parquet" +# flights_parquet <- read_parquet_duckdb(url) +# flights_parquet + +## ----------------------------------------------------------------------------- +# sql_attach <- paste0( +# "ATTACH DATABASE '", +# path_duckdb, +# "' AS external (READ_ONLY)" +# ) +# db_exec(sql_attach) + +## ----------------------------------------------------------------------------- +# read_sql_duckdb("SELECT * FROM external.data") + +## ----------------------------------------------------------------------------- +# simple_data <- +# duckdb_tibble(a = 1) |> +# mutate(b = 2) +# +# simple_data |> +# explain() +# +# simple_data_computed <- +# simple_data |> +# compute() + +## ----------------------------------------------------------------------------- +# simple_data_computed |> +# explain() + +## ----------------------------------------------------------------------------- +# duckdb_tibble(a = 1) |> +# mutate(b = 2) |> +# collect() + +## ----------------------------------------------------------------------------- +# path_csv_out <- tempfile(fileext = ".csv") +# duckdb_tibble(a = 1) |> +# mutate(b = 2) |> +# compute_csv(path_csv_out) +# +# writeLines(readLines(path_csv_out)) + +## ----------------------------------------------------------------------------- +# path_parquet_out <- tempfile(fileext = ".parquet") +# duckdb_tibble(a = 1) |> +# mutate(b = 2) |> +# compute_parquet(path_parquet_out) |> +# explain() + +## ----------------------------------------------------------------------------- +# read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit") +# +# db_exec("PRAGMA memory_limit = '1GB'") +# +# read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit") + +## ----error = TRUE------------------------------------------------------------- +try({ +# flights_parquet |> +# group_by(Month) +}) + +## ----------------------------------------------------------------------------- +# flights_parquet |> +# count(Month, DayofMonth) |> +# group_by(Month) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.Rmd new file mode 100644 index 000000000..d2c3bfe43 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.Rmd @@ -0,0 +1,327 @@ +--- +title: "Large data" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{01 Large data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13", "callr")) && duckplyr:::can_load_extension("httpfs")), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +Working with large datasets in R can be challenging, especially when performance and memory constraints are a concern. +The duckplyr package, built on top of DuckDB, offers a powerful solution by enabling efficient data manipulation using familiar dplyr syntax. +This article explores strategies for handling large datasets with duckplyr, covering ingestion, materialization of intermediate and final results, and good practice. + +```{r attach} +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") +``` + + +## Introduction + +Data frames and other objects in R are stored in RAM. +This can become problematic: + +- Data must be loaded into RAM first, even if only part of it is needed. +- Data must be stored in RAM, even if it is not used. +- RAM is limited, and data sets can be larger than the available RAM. + +A variety of tools have been developed to work with large data sets, also in R. +One example is the dbplyr package, a dplyr backend that connects to SQL databases and is designed to work with various databases that support SQL. +This is a viable approach if the data is already stored in a database, or if the data is stored in Parquet or CSV files and loaded as a lazy table via `duckdb::tbl_file()`. + +The dbplyr package translates dplyr code to SQL. +The syntax and semantics are very similar, but not identical to plain dplyr. +In contrast, the duckplyr package aims to be a fully compatible drop-in replacement for dplyr, with *exactly* the same syntax and semantics: + +- Input and output are data frames or tibbles. +- All dplyr verbs are supported, with fallback. +- All R data types and functions are supported, with fallback. +- No SQL is generated, instead, DuckDB's "relational" interface is used. + +Full compatibility means fewer surprises and less cognitive load for the user. +With DuckDB as the backend, duckplyr can also handle large data sets that do not fit into RAM, keeping full dplyr compatibility. +The tools for bringing data into and out of R memory are modeled after the dplyr and dbplyr packages, and are described in the following sections. + +See `vignette("prudence")` on eager and lazy data, `vignette("limits")` for limitations in the translation employed by duckplyr, `vignette("duckdb")` for a way to overcome these limitations, and `vignette("fallback")` for more information on fallback. + + +## To duckplyr + +The `duckdb_tibble()` function creates a duckplyr data frame from vectors: + +```{r} +df <- duckdb_tibble(x = 1:3, y = letters[1:3]) +df +``` + +The `duckdb_tibble()` function is a drop-in replacement for `tibble()`, and can be used in the same way. + +Similarly, `as_duckdb_tibble()` can be used to convert a data frame or another object to a duckplyr data frame: + +```{r} +flights_df() |> + as_duckdb_tibble() +``` + +Existing code that uses DuckDB via dbplyr can also take advantage. +The following code creates a DuckDB connection and writes a data frame to a table: + +```{r} +path_duckdb <- tempfile(fileext = ".duckdb") +con <- DBI::dbConnect(duckdb::duckdb(path_duckdb)) +DBI::dbWriteTable(con, "data", data.frame(x = 1:3, y = letters[1:3])) + +dbplyr_data <- tbl(con, "data") +dbplyr_data + +dbplyr_data |> + explain() +``` + +The `explain()` output shows that the data is actually coming from a DuckDB table. +The `as_duckdb_tibble()` function can then be used to seamlessly convert the data to a duckplyr frame: + +```{r} +dbplyr_data |> + as_duckdb_tibble() + +dbplyr_data |> + as_duckdb_tibble() |> + explain() +``` + +This only works for DuckDB connections. +For other databases, turn the data into an R data frame or export it to a file before using `as_duckdb_tibble()`. + +```{r} +DBI::dbDisconnect(con) +``` + +For other common cases, the `duckdb_tibble()` function fails with a helpful error message: + +- duckplyr does not support `group_by()`: + +```{r error = TRUE} +duckdb_tibble(a = 1) |> + group_by(a) |> + as_duckdb_tibble() +``` + +- duckplyr does not support `rowwise()`: + +```{r error = TRUE} +duckdb_tibble(a = 1) |> + rowwise() |> + as_duckdb_tibble() +``` + +- Use `read_csv_duckdb()` to read with the built-in reader: + +```{r error = TRUE} +readr::read_csv("a\n1", show_col_types = FALSE) |> + as_duckdb_tibble() +``` + +In all cases, `as_tibble()` can be used to proceed with the existing code. + +## From files + +DuckDB supports data ingestion from CSV, Parquet, and JSON files. +The `read_csv_duckdb()` function accepts a file path and returns a duckplyr frame. + +```{r} +path_csv_1 <- tempfile(fileext = ".csv") +writeLines("x,y\n1,a\n2,b\n3,c", path_csv_1) +read_csv_duckdb(path_csv_1) +``` + +Reading multiple files is also supported: + +```{r} +path_csv_2 <- tempfile(fileext = ".csv") +writeLines("x,y\n4,d\n5,e\n6,f", path_csv_2) +read_csv_duckdb(c(path_csv_1, path_csv_2)) +``` + +The `options` argument can be used to control the reading. + +Similarly, the `read_parquet_duckdb()` and `read_json_duckdb()` functions can be used to read Parquet and JSON files, respectively. + +For reading from HTTPS or S3 URLs, the [httpfs extension](https://duckdb.org/docs/extensions/httpfs/overview.html) must be installed and loaded in each session. + +```{r} +db_exec("INSTALL httpfs") +db_exec("LOAD httpfs") +``` + +Installation is fast if the extension is already installed. +Once loaded, the `read_csv_duckdb()`, `read_parquet_duckdb()`, and `read_json_duckdb()` functions can be used with URLs: + +```{r} +url <- "https://blobs.duckdb.org/flight-data-partitioned/Year=2024/data_0.parquet" +flights_parquet <- read_parquet_duckdb(url) +flights_parquet +``` + +In all cases, the data is read lazily: only the metadata is read initially, and the data is read as required. +This means that data can be read from files that are larger than the available RAM. +The Parquet format is particularly efficient for this purpose, as it stores data in a columnar format and allows reading only the columns that are required. +See `vignette("prudence")` for more details on the concept of lazy data. + +## From DuckDB + +In addition to `as_duckdb_tibble()`, arbitrary DuckDB queries can be executed and the result can be converted to a duckplyr frame. +For this, [attach](https://duckdb.org/docs/sql/statements/attach.html) an existing DuckDB database first: + +```{r} +sql_attach <- paste0( + "ATTACH DATABASE '", + path_duckdb, + "' AS external (READ_ONLY)" +) +db_exec(sql_attach) +``` + +Then, use `read_sql_duckdb()` to execute a query and return a duckplyr frame: + +```{r} +read_sql_duckdb("SELECT * FROM external.data") +``` + +## Materialization + +In dbplyr, `compute()` is used to materialize a lazy table in a temporary table on the database, and `collect()` is used to bring the data into R memory. +This interface works exactly the same in duckplyr: + +```{r} +simple_data <- + duckdb_tibble(a = 1) |> + mutate(b = 2) + +simple_data |> + explain() + +simple_data_computed <- + simple_data |> + compute() +``` + +The `compute.duckplyr_df()` function returns a duckplyr frame that is materialized in a temporary table. +The return value of the function is a duckplyr frame that can be used in further computations. +The materialization is done in a temporary table, so the data is not persisted after the session ends: + +```{r} +simple_data_computed |> + explain() +``` + +The `collect()` function brings the data into R memory and returns a plain tibble: + +```{r} +duckdb_tibble(a = 1) |> + mutate(b = 2) |> + collect() +``` + +## To files + +To materialize data in a persistent file, the `compute_csv()` and `compute_parquet()` functions can be used. +The `compute_csv()` function writes the data to a CSV file: + +```{r} +path_csv_out <- tempfile(fileext = ".csv") +duckdb_tibble(a = 1) |> + mutate(b = 2) |> + compute_csv(path_csv_out) + +writeLines(readLines(path_csv_out)) +``` + +The `compute_parquet()` function writes the data to a Parquet file: + +```{r} +path_parquet_out <- tempfile(fileext = ".parquet") +duckdb_tibble(a = 1) |> + mutate(b = 2) |> + compute_parquet(path_parquet_out) |> + explain() +``` + +Just like with `compute.duckplyr_df()`, the return value of `compute_csv()` and `compute_parquet()` is a duckplyr frame that uses the created CSV or Parquet file and can be used in further computations. +At the time of writing, direct JSON export is not supported. + +## Memory usage + +Computations carried out by DuckDB allocate RAM in the context of the R process. +This memory separate from the memory used by R objects, and is managed by DuckDB. +Limit the memory used by DuckDB by setting a pragma with `db_exec()`: + +```{r} +read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit") + +db_exec("PRAGMA memory_limit = '1GB'") + +read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit") +``` + +See [the DuckDB documentation](https://duckdb.org/docs/configuration/overview.html) for other configuration options. + +## The big picture + +The functions shown in this vignette allow the construction of data transformation pipelines spanning multiple data sources and data that is too large to fit into memory. +Full compatibility with dplyr is provided, so existing code can be used with duckplyr with minimal changes. +The lazy computation of duckplyr frames allows for efficient data processing, as only the required data is read from disk. +The materialization functions allow the data to be persisted in temporary tables or files, depending on the use case. +A typical workflow might look like this: + +- Prepare all data sources as duckplyr frames: local data frames and files +- Combine the data sources using dplyr verbs +- Preview intermediate results as usual: the computation will be faster because only the first few rows are requested +- To avoid rerunning the whole pipeline all over, use `compute.duckplyr_df()` or `compute_parquet()` to materialize any intermediate result that is too large to fit into memory +- Collect the final result using `collect.duckplyr_df()` or write it to a file using `compute_csv()` or `compute_parquet()` + +There is a caveat: due to the design of duckplyr, if a dplyr verb is not supported or uses a function that is not supported, the data will be read into memory before being processed further. +By default, if the data pipeline starts with an ingestion function, the data will only be read into memory if it has less than 1 million cells or values in the table: + +```{r error = TRUE} +flights_parquet |> + group_by(Month) +``` + +Because `group_by()` is not supported, the data will be attempted to read into memory before the `group_by()` operation is executed. +Once the data is small enough to fit into memory, this works transparently. + +```{r} +flights_parquet |> + count(Month, DayofMonth) |> + group_by(Month) +``` + +See `vignette("prudence")` for the concepts and mechanisms at play, and `vignette("fallback")` for a detailed explanation of the fallback mechanism. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.html new file mode 100644 index 000000000..7932fb148 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/large.html @@ -0,0 +1,635 @@ + + + + + + + + + + + + + + +Large data + + + + + + + + + + + + + + + + + + + + + + + + + + +

Large data

+ + + +

Working with large datasets in R can be challenging, especially when +performance and memory constraints are a concern. The duckplyr package, +built on top of DuckDB, offers a powerful solution by enabling efficient +data manipulation using familiar dplyr syntax. This article explores +strategies for handling large datasets with duckplyr, covering +ingestion, materialization of intermediate and final results, and good +practice.

+
library(conflicted)
+library(duckplyr)
+conflict_prefer("filter", "dplyr")
+
+

Introduction

+

Data frames and other objects in R are stored in RAM. This can become +problematic:

+
    +
  • Data must be loaded into RAM first, even if only part of it is +needed.
  • +
  • Data must be stored in RAM, even if it is not used.
  • +
  • RAM is limited, and data sets can be larger than the available +RAM.
  • +
+

A variety of tools have been developed to work with large data sets, +also in R. One example is the dbplyr package, a dplyr backend that +connects to SQL databases and is designed to work with various databases +that support SQL. This is a viable approach if the data is already +stored in a database, or if the data is stored in Parquet or CSV files +and loaded as a lazy table via duckdb::tbl_file().

+

The dbplyr package translates dplyr code to SQL. The syntax and +semantics are very similar, but not identical to plain dplyr. In +contrast, the duckplyr package aims to be a fully compatible drop-in +replacement for dplyr, with exactly the same syntax and +semantics:

+
    +
  • Input and output are data frames or tibbles.
  • +
  • All dplyr verbs are supported, with fallback.
  • +
  • All R data types and functions are supported, with fallback.
  • +
  • No SQL is generated, instead, DuckDB’s “relational” interface is +used.
  • +
+

Full compatibility means fewer surprises and less cognitive load for +the user. With DuckDB as the backend, duckplyr can also handle large +data sets that do not fit into RAM, keeping full dplyr compatibility. +The tools for bringing data into and out of R memory are modeled after +the dplyr and dbplyr packages, and are described in the following +sections.

+

See vignette("prudence") on eager and lazy data, +vignette("limits") for limitations in the translation +employed by duckplyr, vignette("duckdb") for a way to +overcome these limitations, and vignette("fallback") for +more information on fallback.

+
+
+

To duckplyr

+

The duckdb_tibble() function creates a duckplyr data +frame from vectors:

+
df <- duckdb_tibble(x = 1:3, y = letters[1:3])
+df
+

The duckdb_tibble() function is a drop-in replacement +for tibble(), and can be used in the same way.

+

Similarly, as_duckdb_tibble() can be used to convert a +data frame or another object to a duckplyr data frame:

+
flights_df() |>
+  as_duckdb_tibble()
+

Existing code that uses DuckDB via dbplyr can also take advantage. +The following code creates a DuckDB connection and writes a data frame +to a table:

+
path_duckdb <- tempfile(fileext = ".duckdb")
+con <- DBI::dbConnect(duckdb::duckdb(path_duckdb))
+DBI::dbWriteTable(con, "data", data.frame(x = 1:3, y = letters[1:3]))
+
+dbplyr_data <- tbl(con, "data")
+dbplyr_data
+
+dbplyr_data |>
+  explain()
+

The explain() output shows that the data is actually +coming from a DuckDB table. The as_duckdb_tibble() function +can then be used to seamlessly convert the data to a duckplyr frame:

+
dbplyr_data |>
+  as_duckdb_tibble()
+
+dbplyr_data |>
+  as_duckdb_tibble() |>
+  explain()
+

This only works for DuckDB connections. For other databases, turn the +data into an R data frame or export it to a file before using +as_duckdb_tibble().

+
DBI::dbDisconnect(con)
+

For other common cases, the duckdb_tibble() function +fails with a helpful error message:

+
    +
  • duckplyr does not support group_by():
  • +
+
duckdb_tibble(a = 1) |>
+  group_by(a) |>
+  as_duckdb_tibble()
+
    +
  • duckplyr does not support rowwise():
  • +
+
duckdb_tibble(a = 1) |>
+  rowwise() |>
+  as_duckdb_tibble()
+
    +
  • Use read_csv_duckdb() to read with the built-in +reader:
  • +
+
readr::read_csv("a\n1", show_col_types = FALSE) |>
+  as_duckdb_tibble()
+

In all cases, as_tibble() can be used to proceed with +the existing code.

+
+
+

From files

+

DuckDB supports data ingestion from CSV, Parquet, and JSON files. The +read_csv_duckdb() function accepts a file path and returns +a duckplyr frame.

+
path_csv_1 <- tempfile(fileext = ".csv")
+writeLines("x,y\n1,a\n2,b\n3,c", path_csv_1)
+read_csv_duckdb(path_csv_1)
+

Reading multiple files is also supported:

+
path_csv_2 <- tempfile(fileext = ".csv")
+writeLines("x,y\n4,d\n5,e\n6,f", path_csv_2)
+read_csv_duckdb(c(path_csv_1, path_csv_2))
+

The options argument can be used to control the +reading.

+

Similarly, the read_parquet_duckdb() and +read_json_duckdb() functions can be used to read Parquet +and JSON files, respectively.

+

For reading from HTTPS or S3 URLs, the httpfs +extension must be installed and loaded in each session.

+
db_exec("INSTALL httpfs")
+db_exec("LOAD httpfs")
+

Installation is fast if the extension is already installed. Once +loaded, the read_csv_duckdb(), +read_parquet_duckdb(), and read_json_duckdb() +functions can be used with URLs:

+
url <- "https://blobs.duckdb.org/flight-data-partitioned/Year=2024/data_0.parquet"
+flights_parquet <- read_parquet_duckdb(url)
+flights_parquet
+

In all cases, the data is read lazily: only the metadata is read +initially, and the data is read as required. This means that data can be +read from files that are larger than the available RAM. The Parquet +format is particularly efficient for this purpose, as it stores data in +a columnar format and allows reading only the columns that are required. +See vignette("prudence") for more details on the concept of +lazy data.

+
+
+

From DuckDB

+

In addition to as_duckdb_tibble(), arbitrary DuckDB +queries can be executed and the result can be converted to a duckplyr +frame. For this, attach an +existing DuckDB database first:

+
sql_attach <- paste0(
+  "ATTACH DATABASE '",
+  path_duckdb,
+  "' AS external (READ_ONLY)"
+)
+db_exec(sql_attach)
+

Then, use read_sql_duckdb() to execute a query and +return a duckplyr frame:

+
read_sql_duckdb("SELECT * FROM external.data")
+
+
+

Materialization

+

In dbplyr, compute() is used to materialize a lazy table +in a temporary table on the database, and collect() is used +to bring the data into R memory. This interface works exactly the same +in duckplyr:

+
simple_data <-
+  duckdb_tibble(a = 1) |>
+  mutate(b = 2)
+
+simple_data |>
+  explain()
+
+simple_data_computed <-
+  simple_data |>
+  compute()
+

The compute.duckplyr_df() function returns a duckplyr +frame that is materialized in a temporary table. The return value of the +function is a duckplyr frame that can be used in further computations. +The materialization is done in a temporary table, so the data is not +persisted after the session ends:

+
simple_data_computed |>
+  explain()
+

The collect() function brings the data into R memory and +returns a plain tibble:

+
duckdb_tibble(a = 1) |>
+  mutate(b = 2) |>
+  collect()
+
+
+

To files

+

To materialize data in a persistent file, the +compute_csv() and compute_parquet() functions +can be used. The compute_csv() function writes the data to +a CSV file:

+
path_csv_out <- tempfile(fileext = ".csv")
+duckdb_tibble(a = 1) |>
+  mutate(b = 2) |>
+  compute_csv(path_csv_out)
+
+writeLines(readLines(path_csv_out))
+

The compute_parquet() function writes the data to a +Parquet file:

+
path_parquet_out <- tempfile(fileext = ".parquet")
+duckdb_tibble(a = 1) |>
+  mutate(b = 2) |>
+  compute_parquet(path_parquet_out) |>
+  explain()
+

Just like with compute.duckplyr_df(), the return value +of compute_csv() and compute_parquet() is a +duckplyr frame that uses the created CSV or Parquet file and can be used +in further computations. At the time of writing, direct JSON export is +not supported.

+
+
+

Memory usage

+

Computations carried out by DuckDB allocate RAM in the context of the +R process. This memory separate from the memory used by R objects, and +is managed by DuckDB. Limit the memory used by DuckDB by setting a +pragma with db_exec():

+
read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit")
+
+db_exec("PRAGMA memory_limit = '1GB'")
+
+read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit")
+

See the +DuckDB documentation for other configuration options.

+
+
+

The big picture

+

The functions shown in this vignette allow the construction of data +transformation pipelines spanning multiple data sources and data that is +too large to fit into memory. Full compatibility with dplyr is provided, +so existing code can be used with duckplyr with minimal changes. The +lazy computation of duckplyr frames allows for efficient data +processing, as only the required data is read from disk. The +materialization functions allow the data to be persisted in temporary +tables or files, depending on the use case. A typical workflow might +look like this:

+
    +
  • Prepare all data sources as duckplyr frames: local data frames and +files
  • +
  • Combine the data sources using dplyr verbs
  • +
  • Preview intermediate results as usual: the computation will be +faster because only the first few rows are requested
  • +
  • To avoid rerunning the whole pipeline all over, use +compute.duckplyr_df() or compute_parquet() to +materialize any intermediate result that is too large to fit into +memory
  • +
  • Collect the final result using collect.duckplyr_df() or +write it to a file using compute_csv() or +compute_parquet()
  • +
+

There is a caveat: due to the design of duckplyr, if a dplyr verb is +not supported or uses a function that is not supported, the data will be +read into memory before being processed further. By default, if the data +pipeline starts with an ingestion function, the data will only be read +into memory if it has less than 1 million cells or values in the +table:

+
flights_parquet |>
+  group_by(Month)
+

Because group_by() is not supported, the data will be +attempted to read into memory before the group_by() +operation is executed. Once the data is small enough to fit into memory, +this works transparently.

+
flights_parquet |>
+  count(Month, DayofMonth) |>
+  group_by(Month)
+

See vignette("prudence") for the concepts and mechanisms +at play, and vignette("fallback") for a detailed +explanation of the fallback mechanism.

+
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.R new file mode 100644 index 000000000..af85f61dc --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.R @@ -0,0 +1,211 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13", "lubridate"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +conflict_prefer("lag", "dplyr") + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble( + logical = TRUE, + integer = 1L, + numeric = 1.1, + character = "a", + Date = as.Date("2025-01-11"), + POSIXct = as.POSIXct("2025-01-11 19:23:00", tz = "UTC"), + difftime = as.difftime(1, units = "secs"), +) |> + compute() + +## ----error = TRUE------------------------------------------------------------- +try({ +duckplyr::duckdb_tibble() +duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |> + select(-a) +}) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |> + mutate((a + b) * c) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble( + a = c(1, 2, NA), + b = c(2, NA, 3), + c = c(NA, 3, 4), + .prudence = "stingy" +) |> + mutate(a > b, b != c, c < a, a >= b, b <= c) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |> + mutate(a + b, a / b, a - b, a * b) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1, b = 2, c = -3, .prudence = "stingy") |> + mutate(log10(a), log(b), abs(c)) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = FALSE, b = TRUE, c = NA, .prudence = "stingy") |> + mutate(!a, a & b, b | c) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1, b = NA, .prudence = "stingy") |> + mutate(is.na(b), if_else(is.na(b), 0, 1), as.integer(b)) + +duckplyr::duckdb_tibble( + a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"), + .prudence = "stingy") |> + mutate(strftime(a, "%H:%M:%S")) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = "abbc", .prudence = "stingy") |> + mutate(grepl("b", a), substr(a, 2L, 3L), sub("b", "B", a), gsub("b", "B", a)) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble( + a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"), + .prudence = "stingy" +) |> + mutate( + hour = lubridate::hour(a), + minute = lubridate::minute(a), + second = lubridate::second(a), + wday = lubridate::wday(a) + ) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, 2), .prudence = "stingy") |> + summarize( + sum(a), + n(), + n_distinct(b), + ) + +duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, NA), .prudence = "stingy") |> + summarize( + mean(b, na.rm = TRUE), + median(a), + sd(b), + ) + +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + summarize( + min(a), + max(a), + any(a > 1), + all(a > 1), + ) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a), lead(a)) +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a, 2), lead(a, n = 2)) +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a, default = 0), lead(a, default = 4)) +duckplyr::duckdb_tibble(a = 1:3, b = c(2, 3, 1), .prudence = "stingy") |> + mutate(lag(a, order_by = b), lead(a, order_by = b)) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = c(1, 2, 2, 3), .prudence = "stingy") |> + mutate(row_number()) + +## ----------------------------------------------------------------------------- +b <- 4 +duckplyr::duckdb_tibble(a = 1, b = 2, .prudence = "stingy") |> + mutate(.data$a + .data$b, .env$b) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(a %in% c(1, 3)) |> + collect() +duckplyr::last_rel() + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + arrange(desc(a)) |> + explain() + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |> + mutate(suppressWarnings(a + 1)) + +## ----------------------------------------------------------------------------- +duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + summarize(paste(day, collapse = " ")) # fallback + +duckplyr::flights_df() |> + distinct(day) |> + summarize(paste(day, collapse = " ")) + +## ----------------------------------------------------------------------------- +duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + explain() + +withr::with_envvar( + c(DUCKPLYR_OUTPUT_ORDER = "TRUE"), + duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + explain() +) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = 1:100) |> + summarize(sum(a)) + +duckplyr::duckdb_tibble(a = 1:1000000) |> + summarize(sum(a)) + +tibble(a = 1:100) |> + summarize(sum(a)) + +tibble(a = 1:1000000) |> + summarize(sum(a)) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = integer(), b = logical()) |> + summarize(sum(a), any(b), all(b), min(a), max(a)) +tibble(a = integer(), b = logical()) |> + summarize(sum(a), any(b), all(b), min(a), max(a)) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = c(TRUE, FALSE)) |> + summarize(min(a), max(a)) + +tibble(a = c(TRUE, FALSE)) |> + summarize(min(a), max(a)) + +## ----------------------------------------------------------------------------- +duckplyr::duckdb_tibble(a = c(NA, NaN)) |> + mutate(is.na(a)) + +tibble(a = c(NA, NaN)) |> + mutate(is.na(a)) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.Rmd new file mode 100644 index 000000000..f3108aa9d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.Rmd @@ -0,0 +1,411 @@ +--- +title: "Translations" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{20 Translations} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13", "lubridate"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This article describes the translations provided by duckplyr for different data types, verbs, and functions within verbs. +If a translation is not provided, duckplyr falls back to dplyr, see `vignette("fallback")` for details. +The translation layer can be bypassed, see `vignette("duckdb")` for details. + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +conflict_prefer("lag", "dplyr") +``` + +## Data types + +duckplyr supports the following data types: + +- `is.logical()` +- `is.integer()` +- `is.numeric()` +- `is.character()` +- `is.Date()` +- `is.POSIXct()` (with UTC time zone) +- `is.difftime()` + +```{r} +duckplyr::duckdb_tibble( + logical = TRUE, + integer = 1L, + numeric = 1.1, + character = "a", + Date = as.Date("2025-01-11"), + POSIXct = as.POSIXct("2025-01-11 19:23:00", tz = "UTC"), + difftime = as.difftime(1, units = "secs"), +) |> + compute() +``` + +Generally, zero-column tibbles are not supported by duckplyr, neither as input nor as a result. + +```{r error = TRUE} +duckplyr::duckdb_tibble() +duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |> + select(-a) +``` + +Support for more data types, and passthrough of unknown data types, is planned. +Let's [discuss](https://github.com/tidyverse/duckplyr/discussions/) any additional data types you would like to see supported. + + +## Verbs + +Not all dplyr verbs are implemented within duckplyr. +For unsupported verbs, duckplyr automatically falls back to dplyr. +See `?unsupported` for a list of verbs for which duckplyr does not provide a method. + +See the [reference index](https://duckplyr.tidyverse.org/reference/index.html) for a list of verbs with corresponding duckplyr methods. + +Let's [discuss](https://github.com/tidyverse/duckplyr/discussions/) any additional verbs you would like to see supported. + + +## Functions within verbs + +For all functions used in dplyr verbs, translations must be provided. +If an expression contains a function for which no translation is provided, duckplyr falls back to dplyr. +With some exceptions, only positional matching is implemented. + +As of now, here are the translations provided: + +### Parentheses + +Implemented: `(`. + +Reference: `?Paren`. + +```{r} +duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |> + mutate((a + b) * c) +``` + +### Comparison operators + +Implemented: `>`, `>=`, `<`, `<=`, `==`, `!=`. + +Reference: `?Comparison`. + +```{r} +duckplyr::duckdb_tibble( + a = c(1, 2, NA), + b = c(2, NA, 3), + c = c(NA, 3, 4), + .prudence = "stingy" +) |> + mutate(a > b, b != c, c < a, a >= b, b <= c) +``` + +### Basic arithmetics + +Implemented: `+`, `-`, `*`, `/`. + +Reference: `?Arithmetic`. + +```{r} +duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |> + mutate(a + b, a / b, a - b, a * b) +``` + +### Math functions + +Implemented: `log()`, `log10()`, `abs()`. + +Reference: `?Math`. + +```{r} +duckplyr::duckdb_tibble(a = 1, b = 2, c = -3, .prudence = "stingy") |> + mutate(log10(a), log(b), abs(c)) +``` + +### Logical operators + +Implemented: `!`, `&`, `|`. + +Reference: `?Logic`. + +```{r} +duckplyr::duckdb_tibble(a = FALSE, b = TRUE, c = NA, .prudence = "stingy") |> + mutate(!a, a & b, b | c) +``` + +### Branching and conversion + +Implemented: + +- `is.na()`, `as.integer()` +- `dplyr::if_else()`, `dplyr::coalesce()` +- `strftime(x, format)` + +```{r} +duckplyr::duckdb_tibble(a = 1, b = NA, .prudence = "stingy") |> + mutate(is.na(b), if_else(is.na(b), 0, 1), as.integer(b)) + +duckplyr::duckdb_tibble( + a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"), + .prudence = "stingy") |> + mutate(strftime(a, "%H:%M:%S")) +``` + +### String manipulation + +Implemented: `grepl()`, `substr()`, `sub()`, `gsub()`. + +```{r} +duckplyr::duckdb_tibble(a = "abbc", .prudence = "stingy") |> + mutate(grepl("b", a), substr(a, 2L, 3L), sub("b", "B", a), gsub("b", "B", a)) +``` + +### Date manipulation + +Implemented: `lubridate::hour()`, `lubridate::minute()`, `lubridate::second()`, `lubridate::wday()`. + +```{r} +duckplyr::duckdb_tibble( + a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"), + .prudence = "stingy" +) |> + mutate( + hour = lubridate::hour(a), + minute = lubridate::minute(a), + second = lubridate::second(a), + wday = lubridate::wday(a) + ) +``` + +### Aggregation + +Implemented: + +- `sum(x, na.rm)`, `dplyr::n()`, `dplyr::n_distinct()` +- `mean(x, na.rm)`, `median(x, na.rm)`, `sd(x, na.rm)` +- `min()`, `max()`, `any()`, `all()` + +```{r} +duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, 2), .prudence = "stingy") |> + summarize( + sum(a), + n(), + n_distinct(b), + ) + +duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, NA), .prudence = "stingy") |> + summarize( + mean(b, na.rm = TRUE), + median(a), + sd(b), + ) + +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + summarize( + min(a), + max(a), + any(a > 1), + all(a > 1), + ) +``` + +### Shifting + +All optional arguments to `dplyr::lag()` and `dplyr::lead()` are supported. + +```{r} +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a), lead(a)) +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a, 2), lead(a, n = 2)) +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a, default = 0), lead(a, default = 4)) +duckplyr::duckdb_tibble(a = 1:3, b = c(2, 3, 1), .prudence = "stingy") |> + mutate(lag(a, order_by = b), lead(a, order_by = b)) +``` + +### Ranking + +[Ranking in DuckDB](https://duckdb.org/docs/sql/functions/window_functions.html) is very different from dplyr. +Most functions in DuckDB rank only by the current row number, whereas in dplyr, ranking is done by a column. +It will be difficult to provide translations for the following ranking functions. + +- `rank()`, `dplyr::min_rank()`, `dplyr::dense_rank()` +- `dplyr::percent_rank()`, `dplyr::cume_dist()` + +Implementing `dplyr::ntile()` is feasible for the `n` argument. +The only ranking function currently implemented is `dplyr::row_number()`. + +```{r} +duckplyr::duckdb_tibble(a = c(1, 2, 2, 3), .prudence = "stingy") |> + mutate(row_number()) +``` + +### Special cases + +`$` (`?Extract`) is implemented if the LHS is `.data` or `.env`: + +```{r} +b <- 4 +duckplyr::duckdb_tibble(a = 1, b = 2, .prudence = "stingy") |> + mutate(.data$a + .data$b, .env$b) +``` + +`%in%` (`?match`) is implemented if the RHS is a constant with up to 100 values: + +```{r} +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(a %in% c(1, 3)) |> + collect() +duckplyr::last_rel() +``` + +`dplyr::desc()` is only implemented in the context of `dplyr::arrange()`: + +```{r} +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + arrange(desc(a)) |> + explain() +``` + +`suppressWarnings()` is a no-op: + +```{r} +duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |> + mutate(suppressWarnings(a + 1)) +``` + +### Contributing + +Refer to [our contributing guide](https://duckplyr.tidyverse.org/CONTRIBUTING.html#new-translations-for-functions) to learn how to contribute new translations to the package. +Ideally, duckplyr will also support adding custom translations for functions for the duration of the current R session. + +## Known incompatibilities + +This section tracks known incompatibilities between dplyr and duckplyr. +Changing these is likely to require substantial effort, and might be best addressed by providing new functions with consistent behavior in both dplyr and DuckDB. + +### Output order stability + +DuckDB does not guarantee order stability for the output. +For performance reasons, duckplyr does not enable output order stability by default. + +```{r} +duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + summarize(paste(day, collapse = " ")) # fallback + +duckplyr::flights_df() |> + distinct(day) |> + summarize(paste(day, collapse = " ")) +``` + +This can be changed globally with the `DUCKPLYR_OUTPUT_ORDER` environment variable, see `?config` for details. +With this setting, the output order is stable, but the plans are more complicated, and DuckDB needs to do more work. + +```{r} +duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + explain() + +withr::with_envvar( + c(DUCKPLYR_OUTPUT_ORDER = "TRUE"), + duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + explain() +) +``` + +### `sum()` + +In duckplyr, this function returns a numeric value also for integers, due to DuckDB's type stability requirement. + +```{r} +duckplyr::duckdb_tibble(a = 1:100) |> + summarize(sum(a)) + +duckplyr::duckdb_tibble(a = 1:1000000) |> + summarize(sum(a)) + +tibble(a = 1:100) |> + summarize(sum(a)) + +tibble(a = 1:1000000) |> + summarize(sum(a)) +``` + +### Empty vectors in aggregate functions + +At the time of writing, empty vectors only occur when summarizing an empty table without grouping. +In all cases, duckplyr returns `NA`, and the behavior of dplyr is different: + +- `sum()` for an empty vector returns `0` +- `any()` and `all()` return `FALSE` +- `min()` and `max()` return infinity values (with a warning) + +```{r} +duckplyr::duckdb_tibble(a = integer(), b = logical()) |> + summarize(sum(a), any(b), all(b), min(a), max(a)) +tibble(a = integer(), b = logical()) |> + summarize(sum(a), any(b), all(b), min(a), max(a)) +``` + +### `min()` and `max()` for logical input + +For completeness, duckplyr returns a logical for `min()` and `max()` when the input is logical, while dplyr returns an integer. + +```{r} +duckplyr::duckdb_tibble(a = c(TRUE, FALSE)) |> + summarize(min(a), max(a)) + +tibble(a = c(TRUE, FALSE)) |> + summarize(min(a), max(a)) +``` + +### `n_distinct()` and multiple arguments + +This function needs exactly one argument besides the optional `na.rm`. Multiple arguments is not supported. + +### `is.na()` and `NaN` values + +This function returns `FALSE` for `NaN` values in duckplyr, while it returns `TRUE` in dplyr. + +```{r} +duckplyr::duckdb_tibble(a = c(NA, NaN)) |> + mutate(is.na(a)) + +tibble(a = c(NA, NaN)) |> + mutate(is.na(a)) +``` + +### Other differences + +Does the same pipeline give different results with `tibble()` and `duckdb_tibble()`? +We would love to hear about it, please file an [issue](https://github.com/tidyverse/duckplyr/issues/new). diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.html new file mode 100644 index 000000000..fabc0e9e6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/limits.html @@ -0,0 +1,971 @@ + + + + + + + + + + + + + + +Translations + + + + + + + + + + + + + + + + + + + + + + + + + + +

Translations

+ + + +

This article describes the translations provided by duckplyr for +different data types, verbs, and functions within verbs. If a +translation is not provided, duckplyr falls back to dplyr, see +vignette("fallback") for details. The translation layer can +be bypassed, see vignette("duckdb") for details.

+
library(conflicted)
+library(dplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Removing existing preference.
+#> [conflicted] Will prefer dplyr::filter over any other package.
+conflict_prefer("lag", "dplyr")
+#> [conflicted] Will prefer dplyr::lag over any other package.
+
+

Data types

+

duckplyr supports the following data types:

+
    +
  • is.logical()
  • +
  • is.integer()
  • +
  • is.numeric()
  • +
  • is.character()
  • +
  • is.Date()
  • +
  • is.POSIXct() (with UTC time zone)
  • +
  • is.difftime()
  • +
+
duckplyr::duckdb_tibble(
+  logical = TRUE,
+  integer = 1L,
+  numeric = 1.1,
+  character = "a",
+  Date = as.Date("2025-01-11"),
+  POSIXct = as.POSIXct("2025-01-11 19:23:00", tz = "UTC"),
+  difftime = as.difftime(1, units = "secs"),
+) |>
+  compute()
+#> # A duckplyr data frame: 7 variables
+#>   logical integer numeric character Date       POSIXct             difftime
+#>   <lgl>     <int>   <dbl> <chr>     <date>     <dttm>              <drtn>  
+#> 1 TRUE          1     1.1 a         2025-01-11 2025-01-11 19:23:00 1 secs
+

Generally, zero-column tibbles are not supported by duckplyr, neither +as input nor as a result.

+
duckplyr::duckdb_tibble()
+#> Error in `duckplyr::duckdb_tibble()`:
+#> ! Can't convert empty data frame to relational.
+duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |>
+  select(-a)
+#> Error in `select()`:
+#> ! This operation cannot be carried out by DuckDB, and the input is a
+#>   stingy duckplyr frame.
+#> • Zero-column result set not supported.
+#> ℹ Use `compute(prudence = "lavish")` to materialize to temporary storage and
+#>   continue with duckplyr.
+#> ℹ See `vignette("prudence")` for other options.
+

Support for more data types, and passthrough of unknown data types, +is planned. Let’s discuss +any additional data types you would like to see supported.

+
+
+

Verbs

+

Not all dplyr verbs are implemented within duckplyr. For unsupported +verbs, duckplyr automatically falls back to dplyr. See +?unsupported for a list of verbs for which duckplyr does +not provide a method.

+

See the reference +index for a list of verbs with corresponding duckplyr methods.

+

Let’s discuss +any additional verbs you would like to see supported.

+
+
+

Functions within verbs

+

For all functions used in dplyr verbs, translations must be provided. +If an expression contains a function for which no translation is +provided, duckplyr falls back to dplyr. With some exceptions, only +positional matching is implemented.

+

As of now, here are the translations provided:

+
+

Parentheses

+

Implemented: (.

+

Reference: ?Paren.

+
duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |>
+  mutate((a + b) * c)
+#> # A duckplyr data frame: 4 variables
+#>       a     b     c `(a + b) * c`
+#>   <dbl> <dbl> <dbl>         <dbl>
+#> 1     1     2     3             9
+
+
+

Comparison operators

+

Implemented: >, >=, +<, <=, ==, +!=.

+

Reference: ?Comparison.

+
duckplyr::duckdb_tibble(
+  a = c(1, 2, NA),
+  b = c(2, NA, 3),
+  c = c(NA, 3, 4),
+  .prudence = "stingy"
+) |>
+  mutate(a > b, b != c, c < a, a >= b, b <= c)
+#> # A duckplyr data frame: 8 variables
+#>       a     b     c `a > b` `b != c` `c < a` `a >= b` `b <= c`
+#>   <dbl> <dbl> <dbl> <lgl>   <lgl>    <lgl>   <lgl>    <lgl>   
+#> 1     1     2    NA FALSE   NA       NA      FALSE    NA      
+#> 2     2    NA     3 NA      NA       FALSE   NA       NA      
+#> 3    NA     3     4 NA      TRUE     NA      NA       TRUE
+
+
+

Basic arithmetics

+

Implemented: +, -, *, +/.

+

Reference: ?Arithmetic.

+
duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |>
+  mutate(a + b, a / b, a - b, a * b)
+#> # A duckplyr data frame: 7 variables
+#>       a     b     c `a + b` `a/b` `a - b` `a * b`
+#>   <dbl> <dbl> <dbl>   <dbl> <dbl>   <dbl>   <dbl>
+#> 1     1     2     3       3   0.5      -1       2
+
+
+

Math functions

+

Implemented: log(), log10(), +abs().

+

Reference: ?Math.

+
duckplyr::duckdb_tibble(a = 1, b = 2, c = -3, .prudence = "stingy") |>
+  mutate(log10(a), log(b), abs(c))
+#> # A duckplyr data frame: 6 variables
+#>       a     b     c `log10(a)` `log(b)` `abs(c)`
+#>   <dbl> <dbl> <dbl>      <dbl>    <dbl>    <dbl>
+#> 1     1     2    -3          0    0.693        3
+
+
+

Logical operators

+

Implemented: !, &, |.

+

Reference: ?Logic.

+
duckplyr::duckdb_tibble(a = FALSE, b = TRUE, c = NA, .prudence = "stingy") |>
+  mutate(!a, a & b, b | c)
+#> # A duckplyr data frame: 6 variables
+#>   a     b     c     `!a`  `a & b` `b | c`
+#>   <lgl> <lgl> <lgl> <lgl> <lgl>   <lgl>  
+#> 1 FALSE TRUE  NA    TRUE  FALSE   TRUE
+
+
+

Branching and conversion

+

Implemented:

+
    +
  • is.na(), as.integer()
  • +
  • dplyr::if_else(), dplyr::coalesce()
  • +
  • strftime(x, format)
  • +
+
duckplyr::duckdb_tibble(a = 1, b = NA, .prudence = "stingy") |>
+  mutate(is.na(b), if_else(is.na(b), 0, 1), as.integer(b))
+#> # A duckplyr data frame: 5 variables
+#>       a b     `is.na(b)` `if_else(is.na(b), 0, 1)` `as.integer(b)`
+#>   <dbl> <lgl> <lgl>                          <dbl>           <int>
+#> 1     1 NA    TRUE                               0              NA
+
+duckplyr::duckdb_tibble(
+  a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"),
+  .prudence = "stingy") |>
+  mutate(strftime(a, "%H:%M:%S"))
+#> # A duckplyr data frame: 2 variables
+#>   a                   `strftime(a, "%H:%M:%S")`
+#>   <dttm>              <chr>                    
+#> 1 2025-01-11 19:23:46 19:23:46
+
+
+

String manipulation

+

Implemented: grepl(), substr(), +sub(), gsub().

+
duckplyr::duckdb_tibble(a = "abbc", .prudence = "stingy") |>
+  mutate(grepl("b", a), substr(a, 2L, 3L), sub("b", "B", a), gsub("b", "B", a))
+#> # A duckplyr data frame: 5 variables
+#>   a     `grepl("b", a)` `substr(a, 2L, 3L)` `sub("b", "B", a)`
+#>   <chr> <lgl>           <chr>               <chr>             
+#> 1 abbc  TRUE            bbc                 aBbc              
+#> # ℹ 1 more variable: `gsub("b", "B", a)` <chr>
+
+
+

Date manipulation

+

Implemented: lubridate::hour(), +lubridate::minute(), lubridate::second(), +lubridate::wday().

+
duckplyr::duckdb_tibble(
+  a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"),
+  .prudence = "stingy"
+) |>
+  mutate(
+    hour = lubridate::hour(a),
+    minute = lubridate::minute(a),
+    second = lubridate::second(a),
+    wday = lubridate::wday(a)
+  )
+#> # A duckplyr data frame: 5 variables
+#>   a                    hour minute second  wday
+#>   <dttm>              <dbl>  <dbl>  <dbl> <int>
+#> 1 2025-01-11 19:23:46    19     23     46     7
+
+
+

Aggregation

+

Implemented:

+
    +
  • sum(x, na.rm), dplyr::n(), +dplyr::n_distinct()
  • +
  • mean(x, na.rm), median(x, na.rm), +sd(x, na.rm)
  • +
  • min(), max(), any(), +all()
  • +
+
duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, 2), .prudence = "stingy") |>
+  summarize(
+    sum(a),
+    n(),
+    n_distinct(b),
+  )
+#> # A duckplyr data frame: 3 variables
+#>   `sum(a)` `n()` `n_distinct(b)`
+#>      <dbl> <int>           <dbl>
+#> 1        6     3               2
+
+duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, NA), .prudence = "stingy") |>
+  summarize(
+    mean(b, na.rm = TRUE),
+    median(a),
+    sd(b),
+  )
+#> # A duckplyr data frame: 3 variables
+#>   `mean(b, na.rm = TRUE)` `median(a)` `sd(b)`
+#>                     <dbl>       <dbl>   <dbl>
+#> 1                     1.5           2      NA
+
+duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |>
+  summarize(
+    min(a),
+    max(a),
+    any(a > 1),
+    all(a > 1),
+  )
+#> # A duckplyr data frame: 4 variables
+#>   `min(a)` `max(a)` `any(a > 1)` `all(a > 1)`
+#>      <int>    <int> <lgl>        <lgl>       
+#> 1        1        3 TRUE         FALSE
+
+
+

Shifting

+

All optional arguments to dplyr::lag() and +dplyr::lead() are supported.

+
duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |>
+  mutate(lag(a), lead(a))
+#> # A duckplyr data frame: 3 variables
+#>       a `lag(a)` `lead(a)`
+#>   <int>    <int>     <int>
+#> 1     1       NA         2
+#> 2     2        1         3
+#> 3     3        2        NA
+duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |>
+  mutate(lag(a, 2), lead(a, n = 2))
+#> # A duckplyr data frame: 3 variables
+#>       a `lag(a, 2)` `lead(a, n = 2)`
+#>   <int>       <int>            <int>
+#> 1     1          NA                3
+#> 2     2          NA               NA
+#> 3     3           1               NA
+duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |>
+  mutate(lag(a, default = 0), lead(a, default = 4))
+#> # A duckplyr data frame: 3 variables
+#>       a `lag(a, default = 0)` `lead(a, default = 4)`
+#>   <int>                 <int>                  <int>
+#> 1     1                     0                      2
+#> 2     2                     1                      3
+#> 3     3                     2                      4
+duckplyr::duckdb_tibble(a = 1:3, b = c(2, 3, 1), .prudence = "stingy") |>
+  mutate(lag(a, order_by = b), lead(a, order_by = b))
+#> # A duckplyr data frame: 4 variables
+#>       a     b `lag(a, order_by = b)` `lead(a, order_by = b)`
+#>   <int> <dbl>                  <int>                   <int>
+#> 1     3     1                     NA                       1
+#> 2     1     2                      3                       2
+#> 3     2     3                      1                      NA
+
+
+

Ranking

+

Ranking +in DuckDB is very different from dplyr. Most functions in DuckDB +rank only by the current row number, whereas in dplyr, ranking is done +by a column. It will be difficult to provide translations for the +following ranking functions.

+
    +
  • rank(), dplyr::min_rank(), +dplyr::dense_rank()
  • +
  • dplyr::percent_rank(), +dplyr::cume_dist()
  • +
+

Implementing dplyr::ntile() is feasible for the +n argument. The only ranking function currently implemented +is dplyr::row_number().

+
duckplyr::duckdb_tibble(a = c(1, 2, 2, 3), .prudence = "stingy") |>
+  mutate(row_number())
+#> # A duckplyr data frame: 2 variables
+#>       a `row_number()`
+#>   <dbl>          <int>
+#> 1     1              1
+#> 2     2              2
+#> 3     2              3
+#> 4     3              4
+
+
+

Special cases

+

$ (?Extract) is implemented if the LHS is +.data or .env:

+
b <- 4
+duckplyr::duckdb_tibble(a = 1, b = 2, .prudence = "stingy") |>
+  mutate(.data$a + .data$b, .env$b)
+#> # A duckplyr data frame: 4 variables
+#>       a     b `.data$a + .data$b` `.env$b`
+#>   <dbl> <dbl>               <dbl>    <dbl>
+#> 1     1     2                   3        4
+

%in% (?match) is implemented if the RHS is +a constant with up to 100 values:

+
duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |>
+  mutate(a %in% c(1, 3)) |>
+  collect()
+#> # A tibble: 3 × 2
+#>       a `a %in% c(1, 3)`
+#>   <int> <lgl>           
+#> 1     1 TRUE            
+#> 2     2 FALSE           
+#> 3     3 TRUE
+duckplyr::last_rel()
+#> DuckDB Relation: 
+#> ---------------------
+#> --- Relation Tree ---
+#> ---------------------
+#> Projection [a as a, ___coalesce("|"("r_base::=="(a, 1.0), "r_base::=="(a, 3.0)), false) as a %in% c(1, 3)]
+#>   r_dataframe_scan(0xdeadbeef)
+#> 
+#> ---------------------
+#> -- Result Columns  --
+#> ---------------------
+#> - a (INTEGER)
+#> - a %in% c(1, 3) (BOOLEAN)
+

dplyr::desc() is only implemented in the context of +dplyr::arrange():

+
duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |>
+  arrange(desc(a)) |>
+  explain()
+#> ┌---------------------------┐
+#> │          ORDER_BY         │
+#> │    --------------------   │
+#> │      dataframe_42_42      │
+#> │      10491496.a DESC      │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │       Projections: a      │
+#> │                           │
+#> │          ~3 rows          │
+#> └---------------------------┘
+

suppressWarnings() is a no-op:

+
duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |>
+  mutate(suppressWarnings(a + 1))
+#> # A duckplyr data frame: 2 variables
+#>       a `suppressWarnings(a + 1)`
+#>   <dbl>                     <dbl>
+#> 1     1                         2
+
+
+

Contributing

+

Refer to our +contributing guide to learn how to contribute new translations to +the package. Ideally, duckplyr will also support adding custom +translations for functions for the duration of the current R +session.

+
+
+
+

Known incompatibilities

+

This section tracks known incompatibilities between dplyr and +duckplyr. Changing these is likely to require substantial effort, and +might be best addressed by providing new functions with consistent +behavior in both dplyr and DuckDB.

+
+

Output order stability

+

DuckDB does not guarantee order stability for the output. For +performance reasons, duckplyr does not enable output order stability by +default.

+
duckplyr::flights_df() |>
+  duckplyr::as_duckdb_tibble() |>
+  distinct(day) |>
+  summarize(paste(day, collapse = " ")) # fallback
+#> # A duckplyr data frame: 1 variable
+#>   `paste(day, collapse = " ")`                                                  
+#>   <chr>                                                                         
+#> 1 5 9 11 14 15 16 17 22 26 30 31 3 4 6 12 20 23 24 2 10 18 28 29 1 7 8 13 19 21…
+
+duckplyr::flights_df() |>
+  distinct(day) |>
+  summarize(paste(day, collapse = " "))
+#> # A tibble: 1 × 1
+#>   `paste(day, collapse = " ")`                                                  
+#>   <chr>                                                                         
+#> 1 5 9 11 14 15 16 17 22 26 30 31 3 4 6 12 20 23 24 1 7 8 13 19 21 25 27 2 10 18…
+

This can be changed globally with the +DUCKPLYR_OUTPUT_ORDER environment variable, see +?config for details. With this setting, the output order is +stable, but the plans are more complicated, and DuckDB needs to do more +work.

+
duckplyr::flights_df() |>
+  duckplyr::as_duckdb_tibble() |>
+  distinct(day) |>
+  explain()
+#> ┌---------------------------┐
+#> │       HASH_GROUP_BY       │
+#> │    --------------------   │
+#> │         Groups: #0        │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │            day            │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │      Projections: day     │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └---------------------------┘
+
+withr::with_envvar(
+  c(DUCKPLYR_OUTPUT_ORDER = "TRUE"),
+  duckplyr::flights_df() |>
+    duckplyr::as_duckdb_tibble() |>
+    distinct(day) |>
+    explain()
+)
+#> ┌---------------------------┐
+#> │          ORDER_BY         │
+#> │    --------------------   │
+#> │      dataframe_42_42      │
+#> │ 42.___row_number ASC │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │             #0            │
+#> │             #1            │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │           FILTER          │
+#> │    --------------------   │
+#> │   (___row_number_by = 1)  │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │             #0            │
+#> │             #1            │
+#> │             #2            │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │           WINDOW          │
+#> │    --------------------   │
+#> │        Projections:       │
+#> │     ROW_NUMBER() OVER     │
+#> │ (PARTITION BY day ORDER BY│
+#> │   ___row_number ASC NULLS │
+#> │            LAST)          │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │             #0            │
+#> │             #1            │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │      STREAMING_WINDOW     │
+#> │    --------------------   │
+#> │        Projections:       │
+#> │    ROW_NUMBER() OVER ()   │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │      Projections: day     │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └---------------------------┘
+
+
+

sum()

+

In duckplyr, this function returns a numeric value also for integers, +due to DuckDB’s type stability requirement.

+
duckplyr::duckdb_tibble(a = 1:100) |>
+  summarize(sum(a))
+#> # A duckplyr data frame: 1 variable
+#>   `sum(a)`
+#>      <dbl>
+#> 1     5050
+
+duckplyr::duckdb_tibble(a = 1:1000000) |>
+  summarize(sum(a))
+#> # A duckplyr data frame: 1 variable
+#>       `sum(a)`
+#>          <dbl>
+#> 1 500000500000
+
+tibble(a = 1:100) |>
+  summarize(sum(a))
+#> # A tibble: 1 × 1
+#>   `sum(a)`
+#>      <dbl>
+#> 1     5050
+
+tibble(a = 1:1000000) |>
+  summarize(sum(a))
+#> # A tibble: 1 × 1
+#>       `sum(a)`
+#>          <dbl>
+#> 1 500000500000
+
+
+

Empty vectors in aggregate functions

+

At the time of writing, empty vectors only occur when summarizing an +empty table without grouping. In all cases, duckplyr returns +NA, and the behavior of dplyr is different:

+
    +
  • sum() for an empty vector returns 0
  • +
  • any() and all() return +FALSE
  • +
  • min() and max() return infinity values +(with a warning)
  • +
+
duckplyr::duckdb_tibble(a = integer(), b = logical()) |>
+  summarize(sum(a), any(b), all(b), min(a), max(a))
+#> # A duckplyr data frame: 5 variables
+#>   `sum(a)` `any(b)` `all(b)` `min(a)` `max(a)`
+#>      <dbl> <lgl>    <lgl>       <int>    <int>
+#> 1       NA NA       NA             NA       NA
+tibble(a = integer(), b = logical()) |>
+  summarize(sum(a), any(b), all(b), min(a), max(a))
+#> # A tibble: 1 × 5
+#>   `sum(a)` `any(b)` `all(b)` `min(a)` `max(a)`
+#>      <dbl> <lgl>    <lgl>       <int>    <int>
+#> 1       NA NA       NA             NA       NA
+
+
+

min() and max() for logical input

+

For completeness, duckplyr returns a logical for min() +and max() when the input is logical, while dplyr returns an +integer.

+
duckplyr::duckdb_tibble(a = c(TRUE, FALSE)) |>
+  summarize(min(a), max(a))
+#> # A duckplyr data frame: 2 variables
+#>   `min(a)` `max(a)`
+#>   <lgl>    <lgl>   
+#> 1 FALSE    TRUE
+
+tibble(a = c(TRUE, FALSE)) |>
+  summarize(min(a), max(a))
+#> # A tibble: 1 × 2
+#>   `min(a)` `max(a)`
+#>   <lgl>    <lgl>   
+#> 1 FALSE    TRUE
+
+
+

n_distinct() and multiple arguments

+

This function needs exactly one argument besides the optional +na.rm. Multiple arguments is not supported.

+
+
+

is.na() and NaN values

+

This function returns FALSE for NaN values +in duckplyr, while it returns TRUE in dplyr.

+
duckplyr::duckdb_tibble(a = c(NA, NaN)) |>
+  mutate(is.na(a))
+#> # A duckplyr data frame: 2 variables
+#>       a `is.na(a)`
+#>   <dbl> <lgl>     
+#> 1    NA TRUE      
+#> 2   NaN FALSE
+
+tibble(a = c(NA, NaN)) |>
+  mutate(is.na(a))
+#> # A tibble: 2 × 2
+#>       a `is.na(a)`
+#>   <dbl> <lgl>     
+#> 1    NA TRUE      
+#> 2   NaN FALSE
+
+
+

Other differences

+

Does the same pipeline give different results with +tibble() and duckdb_tibble()? We would love to +hear about it, please file an issue.

+
+
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.R new file mode 100644 index 000000000..7c74ed168 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.R @@ -0,0 +1,151 @@ +## ----include = FALSE---------------------------------------------------------- +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +## ----attach------------------------------------------------------------------- +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") + +## ----------------------------------------------------------------------------- +df <- + duckplyr::duckdb_tibble(x = 1:3) |> + mutate(y = x + 1) +df + +class(df) + +df$y + +nrow(df) + +## ----------------------------------------------------------------------------- +flights <- duckplyr::flights_df() + +flights_duckdb <- + flights |> + duckplyr::as_duckdb_tibble() + +system.time( + mean_arr_delay_ewr <- + flights_duckdb |> + filter(origin == "EWR", !is.na(arr_delay)) |> + summarize( + .by = month, + mean_arr_delay = mean(arr_delay), + min_arr_delay = min(arr_delay), + max_arr_delay = max(arr_delay), + median_arr_delay = median(arr_delay), + ) +) + +## ----------------------------------------------------------------------------- +mean_arr_delay_ewr |> + explain() + +## ----------------------------------------------------------------------------- +system.time(mean_arr_delay_ewr$mean_arr_delay[[1]]) + +## ----------------------------------------------------------------------------- +system.time( + flights |> + filter(origin == "EWR", !is.na(arr_delay)) |> + summarize( + .by = c(month, day), + mean_arr_delay = mean(arr_delay), + min_arr_delay = min(arr_delay), + max_arr_delay = max(arr_delay), + median_arr_delay = median(arr_delay), + ) +) + +## ----------------------------------------------------------------------------- +flights_stingy <- + flights |> + duckplyr::as_duckdb_tibble(prudence = "stingy") + +## ----------------------------------------------------------------------------- +flights_stingy + +names(flights_stingy)[1:10] + +class(flights_stingy) + +class(flights_stingy[[1]]) + +## ----error = TRUE------------------------------------------------------------- +try({ +nrow(flights_stingy) + +flights_stingy[[1]] +}) + +## ----error = TRUE------------------------------------------------------------- +try({ +flights_stingy |> + group_by(origin) |> + summarize(n = n()) |> + ungroup() +}) + +## ----------------------------------------------------------------------------- +flights_stingy |> + duckplyr::as_duckdb_tibble(prudence = "lavish") |> + group_by(origin) |> + summarize(n = n()) |> + ungroup() + +## ----------------------------------------------------------------------------- +flights_stingy |> + duckplyr::as_duckdb_tibble(prudence = "lavish") |> + class() + +flights_stingy |> + collect() |> + class() + +## ----------------------------------------------------------------------------- +flights_stingy |> + as_tibble() |> + class() + +flights_stingy |> + as.data.frame() |> + class() + +## ----------------------------------------------------------------------------- +nrow(flights) +flights_partial <- + flights |> + duckplyr::as_duckdb_tibble(prudence = "thrifty") + +## ----error = TRUE------------------------------------------------------------- +try({ +flights_partial |> + select(origin, dest, dep_delay, arr_delay) |> + nrow() +}) + +## ----------------------------------------------------------------------------- +flights_partial |> + count(origin) |> + nrow() + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.Rmd new file mode 100644 index 000000000..05117acb7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.Rmd @@ -0,0 +1,324 @@ +--- +title: "Memory protection: controlling automatic materialization" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{10 Memory protection: controlling automatic materialization} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +Unlike traditional data frames, duckplyr defers computation until absolutely necessary, allowing DuckDB to optimize execution. +This article explains how to control the materialization of data to maintain a seamless dplyr-like experience while remaining cautious of memory usage. + + + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + +## Introduction + +From a user's perspective, data frames backed by duckplyr, with class `"duckplyr_df"`, behave as regular data frames in almost all respects. +In particular, direct column access like `df$x`, or retrieving the number of rows with `nrow()`, works identically. +Conceptually, duckplyr frames are "eager": + +```{r} +df <- + duckplyr::duckdb_tibble(x = 1:3) |> + mutate(y = x + 1) +df + +class(df) + +df$y + +nrow(df) +``` + +Under the hood, two key differences provide improved performance and usability: + +- **lazy materialization**: Unlike traditional data frames, duckplyr defers computation until absolutely necessary, i.e. lazily, allowing DuckDB to optimize execution. +- **prudence**: Automatic materialization is controllable, as automatic materialization of large data might otherwise inadvertently lead to memory problems. + +The term "prudence" is introduced here to set a clear distinction from the concept of "laziness", and because "control of automatic materialization" is a mouthful. + +## Eager and lazy computation + +For a duckplyr frame that is the result of a dplyr operation, accessing column data or retrieving the number of rows will trigger a computation that is carried out by DuckDB, not dplyr. +In this sense, duckplyr frames are also "lazy": the computation is deferred until the last possible moment, allowing DuckDB to optimize the whole pipeline. + +### Example + +This is explained in the following example that computes the mean arrival delay for flights departing from Newark airport (EWR) by day and month: + +```{r} +flights <- duckplyr::flights_df() + +flights_duckdb <- + flights |> + duckplyr::as_duckdb_tibble() + +system.time( + mean_arr_delay_ewr <- + flights_duckdb |> + filter(origin == "EWR", !is.na(arr_delay)) |> + summarize( + .by = month, + mean_arr_delay = mean(arr_delay), + min_arr_delay = min(arr_delay), + max_arr_delay = max(arr_delay), + median_arr_delay = median(arr_delay), + ) +) +``` + +Setting up the pipeline is fast, the size of the data does not affect the setup costs. +Because the computation is deferred, DuckDB can optimize the whole pipeline, which can be seen in the output below: + +```{r} +mean_arr_delay_ewr |> + explain() +``` + +The first step in the pipeline is to prune the unneeded columns, only `origin`, `month`, and `arr_delay` are kept. +The result becomes available when accessed: + +```{r} +system.time(mean_arr_delay_ewr$mean_arr_delay[[1]]) +``` + +### Comparison + +The functionality is similar to lazy tables in [dbplyr](https://dbplyr.tidyverse.org/) and lazy frames in [dtplyr](https://dtplyr.tidyverse.org/). +However, the behavior is different: at the time of writing, the internal structure of a lazy table or frame is different from a data frame, and columns cannot be accessed directly. + +| | **Eager** 😃 | **Lazy** 😴 | +|-------------|:------------:|:-----------:| +| **dplyr** | ✅ | | +| **dbplyr** | | ✅ | +| **dtplyr** | | ✅ | +| **duckplyr**| ✅ | ✅ | + +In contrast, with [dplyr](https://dplyr.tidyverse.org/), each intermediate step and also the final result is a proper data frame, and computed right away, forfeiting the opportunity for optimization: + +```{r} +system.time( + flights |> + filter(origin == "EWR", !is.na(arr_delay)) |> + summarize( + .by = c(month, day), + mean_arr_delay = mean(arr_delay), + min_arr_delay = min(arr_delay), + max_arr_delay = max(arr_delay), + median_arr_delay = median(arr_delay), + ) +) +``` + +See also the [duckplyr: dplyr Powered by DuckDB](https://duckdb.org/2024/04/02/duckplyr.html) blog post for more information. + +## Prudence + +Being both "eager" and "lazy" at the same time introduces a challenge: +it is too easy to accidentally trigger computation, +which is prohibitive if an intermediate result is too large to fit into memory. +Prudence is a setting for duckplyr frames that limits the size of the data that is materialized automatically. + +### Concept + +Three levels of prudence are available: + +- _lavish_ (careless about resources): always automatically materialize, as in the first example. +- _stingy_ (avoid spending at all cost): never automatically materialize, throw an error when attempting to access the data. +- _thrifty_ (use resources wisely): only automaticaly materialize the data if it is small, otherwise throw an error. + +For lavish duckplyr frames, as in the two previous examples, the underlying DuckDB computation is carried out upon the first request. +Once the results are computed, they are cached and subsequent requests are fast. +This is a good choice for small to medium-sized data, where DuckDB can provide a nice speedup but materializing the data is affordable at any stage. +This is the default for `duckdb_tibble()` and `as_duckdb_tibble()`. + +For stingy duckplyr frames, accessing a column or requesting the number of rows triggers an error. +This is a good choice for large data sets where the cost of materializing the data may be prohibitive due to size or computation time, and the user wants to control when the computation is carried out and where the results are stored. +Results can be materialized explicitly with `collect()` and other functions. + +Thrifty duckplyr frames are a compromise between lavish and stingy, discussed further below. + + +### Example + +Passing `prudence = "stingy"` to `as_duckdb_tibble()` creates a stingy duckplyr frame. + +```{r} +flights_stingy <- + flights |> + duckplyr::as_duckdb_tibble(prudence = "stingy") +``` + +The data can be displayed, and column names and types can be accessed. + +```{r} +flights_stingy + +names(flights_stingy)[1:10] + +class(flights_stingy) + +class(flights_stingy[[1]]) +``` + +On the other hand, accessing a column or requesting the number of rows triggers an error: + +```{r error = TRUE} +nrow(flights_stingy) + +flights_stingy[[1]] +``` + +This means that stingy duckplyr frames can also be used to enforce DuckDB operation for a pipeline. + +### Enforcing DuckDB operation + +For operations not supported by duckplyr, the original dplyr implementation is used as a fallback. +As the original dplyr implementation accesses columns directly, the data must be materialized before a fallback can be executed. +Therefore, stingy frames allow you to check that all operations are supported by DuckDB: for a stingy frame, fallbacks to dplyr are not possible. + +```{r error = TRUE} +flights_stingy |> + group_by(origin) |> + summarize(n = n()) |> + ungroup() +``` + +The same pipeline with a lavish frame works, but the computation is carried out by dplyr: + +```{r} +flights_stingy |> + duckplyr::as_duckdb_tibble(prudence = "lavish") |> + group_by(origin) |> + summarize(n = n()) |> + ungroup() +``` + +By using operations supported by duckplyr and avoiding fallbacks as much as possible, your pipelines will be executed by DuckDB in an optimized way. + + +### From stingy to lavish + +A stingy duckplyr frame can be converted to a lavish one with `as_duckdb_tibble(prudence = "lavish")`. +The `collect.duckplyr_df()` method triggers computation and converts to a plain tibble. +The difference between the two is the class of the returned object: + +```{r} +flights_stingy |> + duckplyr::as_duckdb_tibble(prudence = "lavish") |> + class() + +flights_stingy |> + collect() |> + class() +``` + +The same behavior is achieved with `as_tibble()` and `as.data.frame()`: + +```{r} +flights_stingy |> + as_tibble() |> + class() + +flights_stingy |> + as.data.frame() |> + class() +``` + +### Comparison + +Stingy duckplyr frames behave like lazy tables in dbplyr and lazy frames in dtplyr: the computation only starts when you _explicitly_ request it with `collect.duckplyr_df()` or through other means. +However, stingy duckplyr frames can be converted to lavish ones at any time, and vice versa. +In dtplyr and dbplyr, there are no lavish frames: collection always needs to be explicit. + + +## Thrift + +Thrifty is a compromise between stingy and lavish. +Materialization is allowed for data up to a certain size, measured in cells (values) and rows in the resulting data frame. + +```{r} +nrow(flights) +flights_partial <- + flights |> + duckplyr::as_duckdb_tibble(prudence = "thrifty") +``` + +With this setting, the data is materialized only if the result has fewer than 1,000,000 cells (rows multiplied by columns). + +```{r error = TRUE} +flights_partial |> + select(origin, dest, dep_delay, arr_delay) |> + nrow() +``` + +The original input is too large to be materialized, so the operation fails. +On the other hand, the result after aggregation is small enough to be materialized: + +```{r} +flights_partial |> + count(origin) |> + nrow() +``` + +Thrifty is a good choice for data sets where the cost of materializing the data is prohibitive only for large results. + +### File ingestion and custom limits + +Thrifty is the default for the ingestion functions like `read_parquet_duckdb()`. +Here, the limit is adapted depending on the source of the data: + +- For local files, the limit is 1,000,000 cells. +- For remote files (if the file name starts with a URL protocol specifier), the limit is 1,000 cells. + +A custom limit can be set by passing a named vector to `prudence`, with elements `cells` and/or `rows`: + +```r +read_parquet_duckdb( + "personas.parquet", + prudence = c(cells = 10000, rows = 1000) +) +``` + + +## Conclusion + +The duckplyr package provides + +- a drop-in replacement for duckplyr, which necessitates "eager" data frames that automatically materialize like in dplyr, +- optimization by DuckDB, which means "lazy" evaluation where the data is materialized at the latest possible stage. + +Automatic materialization can be dangerous for memory with large data, so duckplyr provides a setting called `prudence` that controls automatic materialization: +is the data automatically materialized _always_ ("lavish" frames), _never_ ("stingy" frames) or _up to a certain size_ ("thrifty" frames). + +See `vignette("large")` for more details on working with large data sets, `vignette("fallback")` for fallbacks to dplyr, `vignette("limits")` for the operations supported by duckplyr, and `vignette("duckdb")` for using DuckDB functions directly. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.html new file mode 100644 index 000000000..70b6bc14d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/prudence.html @@ -0,0 +1,825 @@ + + + + + + + + + + + + + + +Memory protection: controlling automatic materialization + + + + + + + + + + + + + + + + + + + + + + + + + + +

Memory protection: controlling automatic +materialization

+ + + +

Unlike traditional data frames, duckplyr defers computation until +absolutely necessary, allowing DuckDB to optimize execution. This +article explains how to control the materialization of data to maintain +a seamless dplyr-like experience while remaining cautious of memory +usage.

+
library(conflicted)
+library(dplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Removing existing preference.
+#> [conflicted] Will prefer dplyr::filter over any other package.
+
+

Introduction

+

From a user’s perspective, data frames backed by duckplyr, with class +"duckplyr_df", behave as regular data frames in almost all +respects. In particular, direct column access like df$x, or +retrieving the number of rows with nrow(), works +identically. Conceptually, duckplyr frames are “eager”:

+
df <-
+  duckplyr::duckdb_tibble(x = 1:3) |>
+  mutate(y = x + 1)
+df
+#> # A duckplyr data frame: 2 variables
+#>       x     y
+#>   <int> <dbl>
+#> 1     1     2
+#> 2     2     3
+#> 3     3     4
+
+class(df)
+#> [1] "duckplyr_df" "tbl_df"      "tbl"         "data.frame"
+
+df$y
+#> [1] 2 3 4
+
+nrow(df)
+#> [1] 3
+

Under the hood, two key differences provide improved performance and +usability:

+
    +
  • lazy materialization: Unlike traditional data +frames, duckplyr defers computation until absolutely necessary, +i.e. lazily, allowing DuckDB to optimize execution.
  • +
  • prudence: Automatic materialization is +controllable, as automatic materialization of large data might otherwise +inadvertently lead to memory problems.
  • +
+

The term “prudence” is introduced here to set a clear distinction +from the concept of “laziness”, and because “control of automatic +materialization” is a mouthful.

+
+
+

Eager and lazy computation

+

For a duckplyr frame that is the result of a dplyr operation, +accessing column data or retrieving the number of rows will trigger a +computation that is carried out by DuckDB, not dplyr. In this sense, +duckplyr frames are also “lazy”: the computation is deferred until the +last possible moment, allowing DuckDB to optimize the whole +pipeline.

+
+

Example

+

This is explained in the following example that computes the mean +arrival delay for flights departing from Newark airport (EWR) by day and +month:

+
flights <- duckplyr::flights_df()
+
+flights_duckdb <-
+  flights |>
+  duckplyr::as_duckdb_tibble()
+
+system.time(
+  mean_arr_delay_ewr <-
+    flights_duckdb |>
+    filter(origin == "EWR", !is.na(arr_delay)) |>
+    summarize(
+      .by = month,
+      mean_arr_delay = mean(arr_delay),
+      min_arr_delay = min(arr_delay),
+      max_arr_delay = max(arr_delay),
+      median_arr_delay = median(arr_delay),
+    )
+)
+#>    user  system elapsed 
+#>   0.007   0.004   0.011
+

Setting up the pipeline is fast, the size of the data does not affect +the setup costs. Because the computation is deferred, DuckDB can +optimize the whole pipeline, which can be seen in the output below:

+
mean_arr_delay_ewr |>
+  explain()
+#> ┌---------------------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │           month           │
+#> │       mean_arr_delay      │
+#> │       min_arr_delay       │
+#> │       max_arr_delay       │
+#> │      median_arr_delay     │
+#> │                           │
+#> │        ~61,046 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │           month           │
+#> │     (sum(CASE  WHEN (     │
+#> │ (arr_delay IS NULL)) THEN │
+#> │ (CAST(1 AS INTEGER)) ELSE │
+#> │  CAST(0 AS INTEGER) END) >│
+#> │             0)            │
+#> │       avg(arr_delay)      │
+#> │       min(arr_delay)      │
+#> │       max(arr_delay)      │
+#> │  quantile_cont(arr_delay) │
+#> │                           │
+#> │        ~61,046 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │       HASH_GROUP_BY       │
+#> │    --------------------   │
+#> │         Groups: #0        │
+#> │                           │
+#> │        Aggregates:        │
+#> │    sum_no_overflow(#1)    │
+#> │          avg(#2)          │
+#> │          min(#3)          │
+#> │          max(#4)          │
+#> │     quantile_cont(#5)     │
+#> │                           │
+#> │        ~61,046 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │           month           │
+#> │ CASE  WHEN ((arr_delay IS │
+#> │ NULL)) THEN (1) ELSE 0 END
+#> │         arr_delay         │
+#> │         arr_delay         │
+#> │         arr_delay         │
+#> │         arr_delay         │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │         PROJECTION        │
+#> │    --------------------   │
+#> │             #0            │
+#> │             #1            │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │           FILTER          │
+#> │    --------------------   │
+#> │ ((NOT (arr_delay IS NULL))│
+#> │    AND (origin = 'EWR'))  │
+#> │                           │
+#> │        ~67,355 rows       │
+#> └-------------┬-------------┘
+#> ┌-------------┴-------------┐
+#> │     R_DATAFRAME_SCAN      │
+#> │    --------------------   │
+#> │      Text: data.frame     │
+#> │                           │
+#> │        Projections:       │
+#> │           month           │
+#> │         arr_delay         │
+#> │           origin          │
+#> │                           │
+#> │       ~336,776 rows       │
+#> └---------------------------┘
+

The first step in the pipeline is to prune the unneeded columns, only +origin, month, and arr_delay are +kept. The result becomes available when accessed:

+
system.time(mean_arr_delay_ewr$mean_arr_delay[[1]])
+#>    user  system elapsed 
+#>   0.044   0.000   0.022
+
+
+

Comparison

+

The functionality is similar to lazy tables in dbplyr and lazy frames in dtplyr. However, the behavior +is different: at the time of writing, the internal structure of a lazy +table or frame is different from a data frame, and columns cannot be +accessed directly.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Eager 😃Lazy 😴
dplyr
dbplyr
dtplyr
duckplyr
+

In contrast, with dplyr, +each intermediate step and also the final result is a proper data frame, +and computed right away, forfeiting the opportunity for +optimization:

+
system.time(
+  flights |>
+    filter(origin == "EWR", !is.na(arr_delay)) |>
+    summarize(
+      .by = c(month, day),
+      mean_arr_delay = mean(arr_delay),
+      min_arr_delay = min(arr_delay),
+      max_arr_delay = max(arr_delay),
+      median_arr_delay = median(arr_delay),
+    )
+)
+#>    user  system elapsed 
+#>   0.008   0.004   0.012
+

See also the duckplyr: dplyr +Powered by DuckDB blog post for more information.

+
+
+
+

Prudence

+

Being both “eager” and “lazy” at the same time introduces a +challenge: it is too easy to accidentally trigger computation, which is +prohibitive if an intermediate result is too large to fit into memory. +Prudence is a setting for duckplyr frames that limits the size of the +data that is materialized automatically.

+
+

Concept

+

Three levels of prudence are available:

+
    +
  • lavish (careless about resources): always automatically +materialize, as in the first example.
  • +
  • stingy (avoid spending at all cost): never automatically +materialize, throw an error when attempting to access the data.
  • +
  • thrifty (use resources wisely): only automaticaly +materialize the data if it is small, otherwise throw an error.
  • +
+

For lavish duckplyr frames, as in the two previous examples, the +underlying DuckDB computation is carried out upon the first request. +Once the results are computed, they are cached and subsequent requests +are fast. This is a good choice for small to medium-sized data, where +DuckDB can provide a nice speedup but materializing the data is +affordable at any stage. This is the default for +duckdb_tibble() and as_duckdb_tibble().

+

For stingy duckplyr frames, accessing a column or requesting the +number of rows triggers an error. This is a good choice for large data +sets where the cost of materializing the data may be prohibitive due to +size or computation time, and the user wants to control when the +computation is carried out and where the results are stored. Results can +be materialized explicitly with collect() and other +functions.

+

Thrifty duckplyr frames are a compromise between lavish and stingy, +discussed further below.

+
+
+

Example

+

Passing prudence = "stingy" to +as_duckdb_tibble() creates a stingy duckplyr frame.

+
flights_stingy <-
+  flights |>
+  duckplyr::as_duckdb_tibble(prudence = "stingy")
+

The data can be displayed, and column names and types can be +accessed.

+
flights_stingy
+#> # A duckplyr data frame: 19 variables
+#>     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
+#>  7  2013     1     1      555            600        -5      913            854
+#>  8  2013     1     1      557            600        -3      709            723
+#>  9  2013     1     1      557            600        -3      838            846
+#> 10  2013     1     1      558            600        -2      753            745
+#> # ℹ more rows
+#> # ℹ 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 <dttm>
+
+names(flights_stingy)[1:10]
+#>  [1] "year"           "month"          "day"            "dep_time"      
+#>  [5] "sched_dep_time" "dep_delay"      "arr_time"       "sched_arr_time"
+#>  [9] "arr_delay"      "carrier"
+
+class(flights_stingy)
+#> [1] "prudent_duckplyr_df" "duckplyr_df"         "tbl_df"             
+#> [4] "tbl"                 "data.frame"
+
+class(flights_stingy[[1]])
+#> [1] "integer"
+

On the other hand, accessing a column or requesting the number of +rows triggers an error:

+
nrow(flights_stingy)
+#> Error:
+#> ! Materialization is disabled, use `collect()` or `as_tibble()` to materialize.
+#> ℹ Context: GetQueryResult
+
+flights_stingy[[1]]
+#> Error:
+#> ! Materialization is disabled, use `collect()` or `as_tibble()` to materialize.
+#> ℹ Context: GetQueryResult
+

This means that stingy duckplyr frames can also be used to enforce +DuckDB operation for a pipeline.

+
+
+

Enforcing DuckDB operation

+

For operations not supported by duckplyr, the original dplyr +implementation is used as a fallback. As the original dplyr +implementation accesses columns directly, the data must be materialized +before a fallback can be executed. Therefore, stingy frames allow you to +check that all operations are supported by DuckDB: for a stingy frame, +fallbacks to dplyr are not possible.

+
flights_stingy |>
+  group_by(origin) |>
+  summarize(n = n()) |>
+  ungroup()
+#> Error in `group_by()`:
+#> ! This operation cannot be carried out by DuckDB, and the input is a
+#>   stingy duckplyr frame.
+#> • Try `summarise(.by = ...)` or `mutate(.by = ...)` instead of `group_by()` and
+#>   `ungroup()`.
+#> ℹ Use `compute(prudence = "lavish")` to materialize to temporary storage and
+#>   continue with duckplyr.
+#> ℹ See `vignette("prudence")` for other options.
+

The same pipeline with a lavish frame works, but the computation is +carried out by dplyr:

+
flights_stingy |>
+  duckplyr::as_duckdb_tibble(prudence = "lavish") |>
+  group_by(origin) |>
+  summarize(n = n()) |>
+  ungroup()
+#> # A tibble: 3 × 2
+#>   origin      n
+#>   <chr>   <int>
+#> 1 EWR    120835
+#> 2 JFK    111279
+#> 3 LGA    104662
+

By using operations supported by duckplyr and avoiding fallbacks as +much as possible, your pipelines will be executed by DuckDB in an +optimized way.

+
+
+

From stingy to lavish

+

A stingy duckplyr frame can be converted to a lavish one with +as_duckdb_tibble(prudence = "lavish"). The +collect.duckplyr_df() method triggers computation and +converts to a plain tibble. The difference between the two is the class +of the returned object:

+
flights_stingy |>
+  duckplyr::as_duckdb_tibble(prudence = "lavish") |>
+  class()
+#> [1] "duckplyr_df" "tbl_df"      "tbl"         "data.frame"
+
+flights_stingy |>
+  collect() |>
+  class()
+#> [1] "tbl_df"     "tbl"        "data.frame"
+

The same behavior is achieved with as_tibble() and +as.data.frame():

+
flights_stingy |>
+  as_tibble() |>
+  class()
+#> [1] "tbl_df"     "tbl"        "data.frame"
+
+flights_stingy |>
+  as.data.frame() |>
+  class()
+#> [1] "data.frame"
+
+
+

Comparison

+

Stingy duckplyr frames behave like lazy tables in dbplyr and lazy +frames in dtplyr: the computation only starts when you +explicitly request it with collect.duckplyr_df() +or through other means. However, stingy duckplyr frames can be converted +to lavish ones at any time, and vice versa. In dtplyr and dbplyr, there +are no lavish frames: collection always needs to be explicit.

+
+
+
+

Thrift

+

Thrifty is a compromise between stingy and lavish. Materialization is +allowed for data up to a certain size, measured in cells (values) and +rows in the resulting data frame.

+
nrow(flights)
+#> [1] 336776
+flights_partial <-
+  flights |>
+  duckplyr::as_duckdb_tibble(prudence = "thrifty")
+

With this setting, the data is materialized only if the result has +fewer than 1,000,000 cells (rows multiplied by columns).

+
flights_partial |>
+  select(origin, dest, dep_delay, arr_delay) |>
+  nrow()
+#> Error:
+#> ! Materialization would result in more than 250000 rows. Use `collect()` or `as_tibble()` to materialize.
+#> ℹ Context: GetQueryResult
+

The original input is too large to be materialized, so the operation +fails. On the other hand, the result after aggregation is small enough +to be materialized:

+
flights_partial |>
+  count(origin) |>
+  nrow()
+#> [1] 3
+

Thrifty is a good choice for data sets where the cost of +materializing the data is prohibitive only for large results.

+
+

File ingestion and custom limits

+

Thrifty is the default for the ingestion functions like +read_parquet_duckdb(). Here, the limit is adapted depending +on the source of the data:

+
    +
  • For local files, the limit is 1,000,000 cells.
  • +
  • For remote files (if the file name starts with a URL protocol +specifier), the limit is 1,000 cells.
  • +
+

A custom limit can be set by passing a named vector to +prudence, with elements cells and/or +rows:

+
read_parquet_duckdb(
+  "personas.parquet",
+  prudence = c(cells = 10000, rows = 1000)
+)
+
+
+
+

Conclusion

+

The duckplyr package provides

+
    +
  • a drop-in replacement for duckplyr, which necessitates “eager” data +frames that automatically materialize like in dplyr,
  • +
  • optimization by DuckDB, which means “lazy” evaluation where the data +is materialized at the latest possible stage.
  • +
+

Automatic materialization can be dangerous for memory with large +data, so duckplyr provides a setting called prudence that +controls automatic materialization: is the data automatically +materialized always (“lavish” frames), never (“stingy” +frames) or up to a certain size (“thrifty” frames).

+

See vignette("large") for more details on working with +large data sets, vignette("fallback") for fallbacks to +dplyr, vignette("limits") for the operations supported by +duckplyr, and vignette("duckdb") for using DuckDB functions +directly.

+
+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.R new file mode 100644 index 000000000..4b0d3a610 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.R @@ -0,0 +1,30 @@ +## ----include = FALSE---------------------------------------------------------- +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +options(conflicts.policy = list(warn = FALSE)) + +## ----attach------------------------------------------------------------------- +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") + +## ----include = FALSE---------------------------------------------------------- +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = "") +Sys.setenv(DUCKPLYR_FALLBACK_AUTOUPLOAD = "") +fallback_purge() + +## ----------------------------------------------------------------------------- +Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE) +out <- + flights_df() |> + summarize(.by = origin, paste(dest, collapse = " ")) + +## ----echo = FALSE------------------------------------------------------------- +duckplyr:::fallback_autoupload() + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.Rmd new file mode 100644 index 000000000..e104f3c20 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.Rmd @@ -0,0 +1,67 @@ +--- +title: "Telemetry" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{80 Telemetry} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +options(conflicts.policy = list(warn = FALSE)) +``` + +```{r attach} +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") +``` + +As a drop-in replacement for dplyr, duckplyr will use DuckDB for the operations only if it can, and fall back to dplyr otherwise. +A fallback will not change the correctness of the results, but it may be slower or consume more memory. +We would like to guide our efforts towards improving duckplyr, focusing on the features with the most impact. +To this end, duckplyr collects and uploads telemetry data about fallback situations, but only if permitted by the user: + +- Collection is on by default, but can be turned off. +- Uploads are done upon request only. +- There is an option to automatically upload when the package is loaded, this is also opt-in. + +The data collected contains: + +- The package version +- The error message +- The operation being performed, and the arguments + - For the input data frames, only the structure is included (column types only), no column names or data + +```{r include = FALSE} +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = "") +Sys.setenv(DUCKPLYR_FALLBACK_AUTOUPLOAD = "") +fallback_purge() +``` + +Fallback is silent by default, but can be made verbose. + +```{r} +Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE) +out <- + flights_df() |> + summarize(.by = origin, paste(dest, collapse = " ")) +``` + +After logs have been collected, the upload options are displayed the next time the duckplyr package is loaded in an R session. + +```{r, echo = FALSE} +duckplyr:::fallback_autoupload() +``` + +The `fallback_sitrep()` function describes the current configuration and the available options. + +See `vignette("fallback")` for details on the fallback mechanism. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.html b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.html new file mode 100644 index 000000000..a33b3f85d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/inst/doc/telemetry.html @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + +Telemetry + + + + + + + + + + + + + + + + + + + + + + + + + + +

Telemetry

+ + + +
library(conflicted)
+library(duckplyr)
+conflict_prefer("filter", "dplyr")
+#> [conflicted] Removing existing preference.
+#> [conflicted] Will prefer dplyr::filter over any other package.
+

As a drop-in replacement for dplyr, duckplyr will use DuckDB for the +operations only if it can, and fall back to dplyr otherwise. A fallback +will not change the correctness of the results, but it may be slower or +consume more memory. We would like to guide our efforts towards +improving duckplyr, focusing on the features with the most impact. To +this end, duckplyr collects and uploads telemetry data about fallback +situations, but only if permitted by the user:

+
    +
  • Collection is on by default, but can be turned off.
  • +
  • Uploads are done upon request only.
  • +
  • There is an option to automatically upload when the package is +loaded, this is also opt-in.
  • +
+

The data collected contains:

+
    +
  • The package version
  • +
  • The error message
  • +
  • The operation being performed, and the arguments +
      +
    • For the input data frames, only the structure is included (column +types only), no column names or data
    • +
  • +
+

Fallback is silent by default, but can be made verbose.

+
Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE)
+out <-
+  flights_df() |>
+  summarize(.by = origin, paste(dest, collapse = " "))
+#> Error processing duckplyr query with DuckDB, falling back to dplyr.
+#> Caused by error in `summarize()`:
+#> ! Can't translate function `paste()`.
+

After logs have been collected, the upload options are displayed the +next time the duckplyr package is loaded in an R session.

+

The fallback_sitrep() function describes the current +configuration and the available options.

+

See vignette("fallback") for details on the fallback +mechanism.

+ + + + + + + + + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/anti_join.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/anti_join.duckplyr_df.Rd new file mode 100644 index 000000000..6bb565092 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/anti_join.duckplyr_df.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/anti_join-rd.R, R/anti_join.R +\name{anti_join.duckplyr_df} +\alias{anti_join.duckplyr_df} +\title{Anti join} +\usage{ +\method{anti_join}{duckplyr_df}(x, y, by = NULL, copy = FALSE, ..., na_matches = c("na", "never")) +} +\arguments{ +\item{x, y}{A pair of data frames, data frame extensions (e.g. a tibble), or +lazy data frames (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} +specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. + +To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with +multiple expressions. For example, \code{join_by(a == b, c == d)} will match +\code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between +\code{x} and \code{y}, you can shorten this by listing only the variable names, like +\code{join_by(a, c)}. + +\code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap +joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on +these types of joins. + +For simple equality joins, you can alternatively specify a character vector +of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} +to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, +use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. + +To perform a cross-join, generating all combinations of \code{x} and \code{y}, see +\code{\link[dplyr:cross_join]{cross_join()}}.} + +\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 the +same src as \code{x}. This allows you to join tables across srcs, but +it is a potentially expensive operation so you must opt into it.} + +\item{...}{Other parameters passed onto methods.} + +\item{na_matches}{Should two \code{NA} or two \code{NaN} values match? +\itemize{ +\item \code{"na"}, the default, treats two \code{NA} or two \code{NaN} values as equal, like +\code{\%in\%}, \code{\link[=match]{match()}}, and \code{\link[=merge]{merge()}}. +\item \code{"never"} treats two \code{NA} or two \code{NaN} values as different, and will +never match them together or to any other values. This is similar to joins +for database sources and to \code{base::merge(incomparables = NA)}. +}} +} +\description{ +This is a method for the \code{\link[dplyr:filter-joins]{dplyr::anti_join()}} generic. +\code{anti_join()} returns all rows from \code{x} with\strong{out} a match in \code{y}. +} +\examples{ +library(duckplyr) +band_members \%>\% anti_join(band_instruments) +} +\seealso{ +\code{\link[dplyr:filter-joins]{dplyr::anti_join()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/arrange.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/arrange.duckplyr_df.Rd new file mode 100644 index 000000000..dba1086ee --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/arrange.duckplyr_df.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/arrange-rd.R, R/arrange.R +\name{arrange.duckplyr_df} +\alias{arrange.duckplyr_df} +\title{Order rows using column values} +\usage{ +\method{arrange}{duckplyr_df}(.data, ..., .by_group = FALSE, .locale = NULL) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[rlang:args_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.} + +\item{.locale}{The locale to sort character vectors in. +\itemize{ +\item If \code{NULL}, the default, uses the \code{"C"} locale unless the +\code{dplyr.legacy_locale} global option escape hatch is active. See the +\link[dplyr]{dplyr-locale} help page for more details. +\item If a single string from \code{\link[stringi:stri_locale_list]{stringi::stri_locale_list()}} is supplied, then +this will be used as the locale to sort with. For example, \code{"en"} will +sort with the American English locale. This requires the stringi package. +\item If \code{"C"} is supplied, then character vectors will always be sorted in the +C locale. This does not require stringi and is often much faster than +supplying a locale identifier. +} + +The C locale is not the same as English locales, such as \code{"en"}, +particularly when it comes to data containing a mix of upper and lower case +letters. This is explained in more detail on the \link[dplyr:dplyr-locale]{locale} +help page under the \verb{Default locale} section.} +} +\description{ +This is a method for the \code{\link[dplyr:arrange]{dplyr::arrange()}} generic. +See "Fallbacks" section for differences in implementation. +\code{arrange()} orders the rows of a data frame by the values of selected +columns. + +Unlike other dplyr verbs, \code{arrange()} largely ignores grouping; you +need to explicitly mention grouping variables (or use \code{.by_group = TRUE}) +in order to group by them, and functions of variables are evaluated +once per data frame, not once per group. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{arrange.duckplyr_df()} +\itemize{ +\item with \code{.by_group = TRUE}, +\item providing a value for the \code{.locale} argument, +\item providing a value for the \code{dplyr.legacy_locale} option. +} + +These features fall back to \code{\link[dplyr:arrange]{dplyr::arrange()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +arrange(mtcars, cyl, disp) +arrange(mtcars, desc(disp)) +} +\seealso{ +\code{\link[dplyr:arrange]{dplyr::arrange()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_duckplyr_df.Rd new file mode 100644 index 000000000..b54a55942 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_duckplyr_df.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_duckplyr_df.R, R/as_duckplyr_tibble.R +\name{as_duckplyr_df} +\alias{as_duckplyr_df} +\alias{as_duckplyr_tibble} +\title{Convert to a duckplyr data frame} +\usage{ +as_duckplyr_df(.data) + +as_duckplyr_tibble(.data) +} +\arguments{ +\item{.data}{data frame or tibble to transform} +} +\value{ +For \code{as_duckplyr_df()}, an object of class \code{"duckplyr_df"}, +inheriting from the classes of the \code{.data} argument. + +For \code{as_duckplyr_tibble()}, an object of class +\code{c("duckplyr_df", class(tibble()))} . +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +These functions convert a data-frame-like input to an object of class \code{"duckpylr_df"}. +For such objects, +dplyr verbs such as \code{\link[dplyr:mutate]{dplyr::mutate()}}, \code{\link[dplyr:select]{dplyr::select()}} or \code{\link[dplyr:filter]{dplyr::filter()}} will attempt to use DuckDB. +If this is not possible, the original dplyr implementation is used. + +\code{as_duckplyr_df()} requires the input to be a plain data frame or a tibble, +and will fail for any other classes, including subclasses of \code{"data.frame"} or \code{"tbl_df"}. +This behavior is likely to change, do not rely on it. + +\code{as_duckplyr_tibble()} converts the input to a tibble and then to a duckplyr data frame. +} +\details{ +Set the \code{DUCKPLYR_FALLBACK_INFO} and \code{DUCKPLYR_FORCE} environment variables +for more control over the behavior, see \link{config} for more details. +} +\examples{ +tibble(a = 1:3) \%>\% + mutate(b = a + 1) +} +\keyword{internal} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_tbl.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_tbl.Rd new file mode 100644 index 000000000..76d9bd766 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/as_tbl.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_tbl.R +\name{as_tbl} +\alias{as_tbl} +\title{Convert a duckplyr frame to a dbplyr table} +\usage{ +as_tbl(.data) +} +\arguments{ +\item{.data}{A lazy duckplyr frame or a data frame.} +} +\value{ +A dbplyr table. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +This function converts a lazy duckplyr frame or a data frame +to a dbplyr table in duckplyr's internal connection. +This allows using dbplyr functions on the data, +including hand-written SQL queries. +Use \code{\link[=as_duckdb_tibble]{as_duckdb_tibble()}} to convert back to a lazy duckplyr frame. +} +\examples{ +\dontshow{if (requireNamespace("dbplyr", quietly = TRUE)) withAutoprint(\{ # examplesIf} +df <- duckdb_tibble(a = 1L) +df + +tbl <- as_tbl(df) +tbl + +tbl \%>\% + mutate(b = sql("a + 1")) \%>\% + as_duckdb_tibble() +\dontshow{\}) # examplesIf} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/collect.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/collect.duckplyr_df.Rd new file mode 100644 index 000000000..6a32045cb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/collect.duckplyr_df.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/collect-rd.R, R/collect.R +\name{collect.duckplyr_df} +\alias{collect.duckplyr_df} +\title{Force conversion to a data frame} +\usage{ +\method{collect}{duckplyr_df}(x, ...) +} +\arguments{ +\item{x}{A data frame, data frame extension (e.g. a tibble), or a lazy +data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for more +details.} + +\item{...}{Arguments passed on to methods} +} +\description{ +This is a method for the \code{\link[dplyr:compute]{dplyr::collect()}} generic. +\code{collect()} converts the input to a tibble, materializing any lazy operations. +} +\examples{ +library(duckplyr) +df <- duckdb_tibble(x = c(1, 2), .lazy = TRUE) +df +try(print(df$x)) +df <- collect(df) +df +} +\seealso{ +\code{\link[dplyr:compute]{dplyr::collect()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute.duckplyr_df.Rd new file mode 100644 index 000000000..cf61d9ef2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute.duckplyr_df.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/compute-rd.R, R/compute.R +\name{compute.duckplyr_df} +\alias{compute.duckplyr_df} +\title{Compute results} +\usage{ +\method{compute}{duckplyr_df}( + x, + ..., + prudence = NULL, + name = NULL, + schema_name = NULL, + temporary = TRUE +) +} +\arguments{ +\item{x}{A duckplyr frame.} + +\item{...}{Arguments passed on to methods} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never, +\item \code{"thrifty"}: up to a maximum size of 1 million cells. +} + +The default is to inherit from the input. +This argument is provided here only for convenience. +The same effect can be achieved by forwarding the output to \code{\link[=as_duckdb_tibble]{as_duckdb_tibble()}} +with the desired prudence. +See \code{vignette("prudence")} for more information.} + +\item{name}{The name of the table to store the result in.} + +\item{schema_name}{The schema to store the result in, defaults to the current schema.} + +\item{temporary}{Set to \code{FALSE} to store the result in a permanent table.} +} +\value{ +A duckplyr frame. +} +\description{ +This is a method for the \code{\link[dplyr:compute]{dplyr::compute()}} generic. +For a duckplyr frame, +\code{compute()} executes a query but stores it in a (temporary) table, +or in a Parquet or CSV file. +The result is a duckplyr frame that can be used with subsequent dplyr verbs. +} +\examples{ +library(duckplyr) +df <- duckdb_tibble(x = c(1, 2)) +df <- mutate(df, y = 2) +explain(df) +df <- compute(df) +explain(df) +} +\seealso{ +\code{\link[dplyr:compute]{dplyr::collect()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_csv.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_csv.Rd new file mode 100644 index 000000000..9e7f0cc38 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_csv.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/compute_csv.R +\name{compute_csv} +\alias{compute_csv} +\title{Compute results to a CSV file} +\usage{ +compute_csv(x, path, ..., prudence = NULL, options = NULL) +} +\arguments{ +\item{x}{A duckplyr frame.} + +\item{path}{The path of the Parquet file to create.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never, +\item \code{"thrifty"}: up to a maximum size of 1 million cells. +} + +The default is to inherit from the input. +This argument is provided here only for convenience. +The same effect can be achieved by forwarding the output to \code{\link[=as_duckdb_tibble]{as_duckdb_tibble()}} +with the desired prudence. +See \code{vignette("prudence")} for more information.} + +\item{options}{A list of additional options to pass to create the storage format, +see \url{https://duckdb.org/docs/sql/statements/copy.html#csv-options} +for details.} +} +\value{ +A duckplyr frame. +} +\description{ +For a duckplyr frame, this function executes the query +and stores the results in a CSV file, +without converting it to an R data frame. +The result is a duckplyr frame that can be used with subsequent dplyr verbs. +This function can also be used as a CSV writer for regular data frames. +} +\examples{ +library(duckplyr) +df <- data.frame(x = c(1, 2)) +df <- mutate(df, y = 2) +path <- tempfile(fileext = ".csv") +df <- compute_csv(df, path) +readLines(path) +} +\seealso{ +\code{\link[=compute_parquet]{compute_parquet()}}, \code{\link[=compute.duckplyr_df]{compute.duckplyr_df()}}, \code{\link[dplyr:compute]{dplyr::collect()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_parquet.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_parquet.Rd new file mode 100644 index 000000000..c6430d159 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/compute_parquet.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/compute_parquet.R +\name{compute_parquet} +\alias{compute_parquet} +\title{Compute results to a Parquet file} +\usage{ +compute_parquet(x, path, ..., prudence = NULL, options = NULL) +} +\arguments{ +\item{x}{A duckplyr frame.} + +\item{path}{The path of the Parquet file to create.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never, +\item \code{"thrifty"}: up to a maximum size of 1 million cells. +} + +The default is to inherit from the input. +This argument is provided here only for convenience. +The same effect can be achieved by forwarding the output to \code{\link[=as_duckdb_tibble]{as_duckdb_tibble()}} +with the desired prudence. +See \code{vignette("prudence")} for more information.} + +\item{options}{A list of additional options to pass to create the Parquet file, +see \url{https://duckdb.org/docs/sql/statements/copy.html#parquet-options} +for details.} +} +\value{ +A duckplyr frame. +} +\description{ +For a duckplyr frame, this function executes the query +and stores the results in a Parquet file, +without converting it to an R data frame. +The result is a duckplyr frame that can be used with subsequent dplyr verbs. +This function can also be used as a Parquet writer for regular data frames. +} +\examples{ +library(duckplyr) +df <- data.frame(x = c(1, 2)) +df <- mutate(df, y = 2) +path <- tempfile(fileext = ".parquet") +df <- compute_parquet(df, path) +explain(df) +} +\seealso{ +\code{\link[=compute_csv]{compute_csv()}}, \code{\link[=compute.duckplyr_df]{compute.duckplyr_df()}}, \code{\link[dplyr:compute]{dplyr::collect()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/config.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/config.Rd new file mode 100644 index 000000000..7bef808b4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/config.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{config} +\alias{config} +\title{Configuration options} +\description{ +The behavior of duckplyr can be fine-tuned with several environment variables, +and one option. +} +\section{Environment variables}{ + + +\code{DUCKPLYR_TEMP_DIR}: Set to a path where temporary files can be created. +By default, \code{\link[=tempdir]{tempdir()}} is used. + +\code{DUCKPLYR_OUTPUT_ORDER}: If \code{TRUE}, row output order is preserved. +The default may change the row order where dplyr would keep it stable. +Preserving the order leads to more complicated execution plans +with less potential for optimization, and thus may be slower. + +\code{DUCKPLYR_FORCE}: If \code{TRUE}, fail if duckdb cannot handle a request. + +\code{DUCKPLYR_CHECK_ROUNDTRIP}: If \code{TRUE}, check if all columns are roundtripped perfectly +when creating a relational object from a data frame, +This is slow, and mostly useful for debugging. +The default is to check roundtrip of attributes. + +\code{DUCKPLYR_METHODS_OVERWRITE}: If \code{TRUE}, call \code{methods_overwrite()} +when the package is loaded. + +See \link{fallback} for more options related to printing, logging, and uploading +of fallback events. +} + +\examples{ +# Sys.setenv(DUCKPLYR_OUTPUT_ORDER = TRUE) +data.frame(a = 3:1) \%>\% + as_duckdb_tibble() \%>\% + inner_join(data.frame(a = 1:4), by = "a") + +withr::with_envvar(c(DUCKPLYR_OUTPUT_ORDER = "TRUE"), { + data.frame(a = 3:1) \%>\% + as_duckdb_tibble() \%>\% + inner_join(data.frame(a = 1:4), by = "a") +}) + +# Sys.setenv(DUCKPLYR_FORCE = TRUE) +add_one <- function(x) { + x + 1 +} + +data.frame(a = 3:1) \%>\% + as_duckdb_tibble() \%>\% + mutate(b = add_one(a)) + +try(withr::with_envvar(c(DUCKPLYR_FORCE = "TRUE"), { + data.frame(a = 3:1) \%>\% + as_duckdb_tibble() \%>\% + mutate(b = add_one(a)) +})) + +# Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE) +withr::with_envvar(c(DUCKPLYR_FALLBACK_INFO = "TRUE"), { + data.frame(a = 3:1) \%>\% + as_duckdb_tibble() \%>\% + mutate(b = add_one(a)) +}) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/count.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/count.duckplyr_df.Rd new file mode 100644 index 000000000..878deeb8b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/count.duckplyr_df.Rd @@ -0,0 +1,75 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/count-rd.R, R/count.R +\name{count.duckplyr_df} +\alias{count.duckplyr_df} +\title{Count the observations in each group} +\usage{ +\method{count}{duckplyr_df}( + x, + ..., + wt = NULL, + sort = FALSE, + name = NULL, + .drop = group_by_drop_default(x) +) +} +\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[rlang:args_data_masking]{data-masking}}> Variables to group +by.} + +\item{wt}{<\code{\link[rlang:args_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 use \code{nn}. If there's a column called \code{n} and \code{nn}, it'll use +\code{nnn}, and so on, adding \code{n}s until it gets a new name.} + +\item{.drop}{Handling of factor levels that don't appear in the data, passed +on to \code{\link[dplyr:group_by]{group_by()}}. + +For \code{count()}: if \code{FALSE} will include counts for empty groups (i.e. for +levels of factors that don't exist in the data). + +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} For \code{add_count()}: deprecated since it +can't actually affect the output.} +} +\description{ +This is a method for the \code{\link[dplyr:count]{dplyr::count()}} generic. +See "Fallbacks" section for differences in implementation. +\code{count()} lets you quickly count the unique values of one or more variables: +\code{df \%>\% count(a, b)} is roughly equivalent to +\code{df \%>\% group_by(a, b) \%>\% summarise(n = n())}. +\code{count()} is paired with \code{tally()}, a lower-level helper that is equivalent +to \code{df \%>\% summarise(n = n())}. Supply \code{wt} to perform weighted counts, +switching the summary from \code{n = n()} to \code{n = sum(wt)}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{count.duckplyr_df()} +\itemize{ +\item with complex expressions in \code{...}, +\item with \code{.drop = FALSE}, +\item with \code{sort = TRUE}. +} + +These features fall back to \code{\link[dplyr:count]{dplyr::count()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +count(mtcars, am) +} +\seealso{ +\code{\link[dplyr:count]{dplyr::count()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/db_exec.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/db_exec.Rd new file mode 100644 index 000000000..662aa6f41 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/db_exec.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/exec.R +\name{db_exec} +\alias{db_exec} +\title{Execute a statement for the default connection} +\usage{ +db_exec(sql, ..., con = NULL) +} +\arguments{ +\item{sql}{The statement to run.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{con}{The connection, defaults to the default connection.} +} +\value{ +The return value of the \code{\link[DBI:dbExecute]{DBI::dbExecute()}} call, invisibly. +} +\description{ +The \pkg{duckplyr} package relies on a DBI connection +to an in-memory database. +The \code{db_exec()} function allows running SQL statements +with side effects on this connection. +It can be used to execute statements that start with +\code{PRAGMA}, \code{SET}, or \code{ATTACH} +to, e.g., set up credentials, change configuration options, +or attach other databases. +See \url{https://duckdb.org/docs/configuration/overview.html} +for more information on the configuration options, +and \url{https://duckdb.org/docs/sql/statements/attach.html} +for attaching databases. +} +\examples{ +db_exec("SET threads TO 2") +} +\seealso{ +\code{\link[=read_sql_duckdb]{read_sql_duckdb()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/df_from_file.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/df_from_file.Rd new file mode 100644 index 000000000..88f77f6d9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/df_from_file.Rd @@ -0,0 +1,130 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/io-.R, R/io-csv.R, R/io-parquet.R +\name{df_from_file} +\alias{df_from_file} +\alias{duckplyr_df_from_file} +\alias{df_from_csv} +\alias{duckplyr_df_from_csv} +\alias{df_from_parquet} +\alias{duckplyr_df_from_parquet} +\alias{df_to_parquet} +\title{Read Parquet, CSV, and other files using DuckDB} +\usage{ +df_from_file(path, table_function, ..., options = list(), class = NULL) + +duckplyr_df_from_file( + path, + table_function, + ..., + options = list(), + class = NULL +) + +df_from_csv(path, ..., options = list(), class = NULL) + +duckplyr_df_from_csv(path, ..., options = list(), class = NULL) + +df_from_parquet(path, ..., options = list(), class = NULL) + +duckplyr_df_from_parquet(path, ..., options = list(), class = NULL) + +df_to_parquet(data, path) +} +\arguments{ +\item{path}{Path to files, glob patterns \code{*} and \verb{?} are supported.} + +\item{table_function}{The name of a table-valued +DuckDB function such as \code{"read_parquet"}, +\code{"read_csv"}, \code{"read_csv_auto"} or \code{"read_json"}.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{options}{Arguments to the DuckDB function +indicated by \code{table_function}.} + +\item{class}{The class of the output. +By default, a tibble is created. +The returned object will always be a data frame. +Use \code{class = "data.frame"} or \code{class = character()} +to create a plain data frame.} + +\item{data}{A data frame to be written to disk.} +} +\value{ +A data frame for \code{df_from_file()}, or a \code{duckplyr_df} for +\code{duckplyr_df_from_file()}, extended by the provided \code{class}. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +\code{df_from_file()} uses arbitrary table functions to read data. +See \url{https://duckdb.org/docs/data/overview} for a documentation +of the available functions and their options. +To read multiple files with the same schema, +pass a wildcard or a character vector to the \code{path} argument, + +\code{duckplyr_df_from_file()} is a thin wrapper around \code{df_from_file()} +that calls \code{as_duckplyr_df()} on the output. + +These functions ingest data from a file using a table function. +The results are transparently converted to a data frame, but the data is only read when +the resulting data frame is actually accessed. + +\code{df_from_csv()} reads a CSV file using the \code{read_csv_auto()} table function. + +\code{duckplyr_df_from_csv()} is a thin wrapper around \code{df_from_csv()} +that calls \code{as_duckplyr_df()} on the output. + +\code{df_from_parquet()} reads a Parquet file using the \code{read_parquet()} table function. + +\code{duckplyr_df_from_parquet()} is a thin wrapper around \code{df_from_parquet()} +that calls \code{as_duckplyr_df()} on the output. + +\code{df_to_parquet()} writes a data frame to a Parquet file via DuckDB. +If the data frame is a \code{duckplyr_df}, the materialization occurs outside of R. +An existing file will be overwritten. +This function requires duckdb >= 0.10.0. +} +\examples{ +# Create simple CSV file +path <- tempfile("duckplyr_test_", fileext = ".csv") +write.csv(data.frame(a = 1:3, b = letters[4:6]), path, row.names = FALSE) + +# Reading is immediate +df <- df_from_csv(path) + +# Materialization only upon access +names(df) +df$a + +# Return as tibble, specify column types: +df_from_file( + path, + "read_csv", + options = list(delim = ",", types = list(c("DOUBLE", "VARCHAR"))), + class = class(tibble()) +) + +# Read multiple file at once +path2 <- tempfile("duckplyr_test_", fileext = ".csv") +write.csv(data.frame(a = 4:6, b = letters[7:9]), path2, row.names = FALSE) + +duckplyr_df_from_csv(file.path(tempdir(), "duckplyr_test_*.csv")) + +unlink(c(path, path2)) + +# Write a Parquet file: +path_parquet <- tempfile(fileext = ".parquet") +df_to_parquet(df, path_parquet) + +# With a duckplyr_df, the materialization occurs outside of R: +df \%>\% + as_duckplyr_df() \%>\% + mutate(b = a + 1) \%>\% + df_to_parquet(path_parquet) + +duckplyr_df_from_parquet(path_parquet) + +unlink(path_parquet) +} +\keyword{internal} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/distinct.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/distinct.duckplyr_df.Rd new file mode 100644 index 000000000..5de54d5cf --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/distinct.duckplyr_df.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/distinct-rd.R, R/distinct.R +\name{distinct.duckplyr_df} +\alias{distinct.duckplyr_df} +\title{Keep distinct/unique rows} +\usage{ +\method{distinct}{duckplyr_df}(.data, ..., .keep_all = FALSE) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Optional variables to +use when determining uniqueness. If there are multiple rows for a given +combination of inputs, only the first row will be preserved. If omitted, +will use all variables in the data frame.} + +\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.} +} +\description{ +This is a method for the \code{\link[dplyr:distinct]{dplyr::distinct()}} generic. +Keep only unique/distinct rows from a data frame. +This is similar to \code{unique.data.frame()} but considerably faster. +} +\examples{ +df <- duckdb_tibble( + x = sample(10, 100, rep = TRUE), + y = sample(10, 100, rep = TRUE) +) +nrow(df) +nrow(distinct(df)) +} +\seealso{ +\code{\link[dplyr:distinct]{dplyr::distinct()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckdb_tibble.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckdb_tibble.Rd new file mode 100644 index 000000000..d22f84c6d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckdb_tibble.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ducktbl.R +\name{duckdb_tibble} +\alias{duckdb_tibble} +\alias{as_duckdb_tibble} +\alias{is_duckdb_tibble} +\title{duckplyr data frames} +\usage{ +duckdb_tibble(..., .prudence = c("lavish", "thrifty", "stingy")) + +as_duckdb_tibble(x, ..., prudence = c("lavish", "thrifty", "stingy")) + +is_duckdb_tibble(x) +} +\arguments{ +\item{...}{For \code{duckdb_tibble()}, passed on to \code{\link[tibble:tibble]{tibble::tibble()}}. +For \code{as_duckdb_tibble()}, passed on to methods.} + +\item{x}{The object to convert or to test.} + +\item{prudence, .prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never, +\item \code{"thrifty"}: up to a maximum size of 1 million cells. +} + +The default is \code{"lavish"} for \code{duckdb_tibble()} and \code{as_duckdb_tibble()}, +and may be different for other functions. +See \code{vignette("prudence")} for more information.} +} +\value{ +For \code{duckdb_tibble()} and \code{as_duckdb_tibble()}, an object with the following classes: +\itemize{ +\item \code{"prudent_duckplyr_df"} if \code{prudence} is not \code{"lavish"} +\item \code{"duckplyr_df"} +\item Classes of a \link[tibble:tibble]{tibble::tibble} +} + +For \code{is_duckdb_tibble()}, a scalar logical. +} +\description{ +Data frames backed by duckplyr have a special class, \code{"duckplyr_df"}, +in addition to the default classes. +This ensures that dplyr methods are dispatched correctly. +For such objects, +dplyr verbs such as \code{\link[dplyr:mutate]{dplyr::mutate()}}, \code{\link[dplyr:select]{dplyr::select()}} or \code{\link[dplyr:filter]{dplyr::filter()}} will use DuckDB. + +\code{duckdb_tibble()} works like \code{\link[tibble:tibble]{tibble::tibble()}}. + +\code{as_duckdb_tibble()} converts a data frame or a dplyr lazy table to a duckplyr data frame. +This is a generic function that can be overridden for custom classes. + +\code{is_duckdb_tibble()} returns \code{TRUE} if \code{x} is a duckplyr data frame. +} +\section{Fine-tuning prudence}{ + +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +The \code{prudence} argument can also be a named numeric vector +with at least one of \code{cells} or \code{rows} +to limit the cells (values) and rows in the resulting data frame +after automatic materialization. +If both limits are specified, both are enforced. +The equivalent of \code{"thrifty"} is \code{c(cells = 1e6)}. +} + +\examples{ +x <- duckdb_tibble(a = 1) +x + +library(dplyr) +x \%>\% + mutate(b = 2) + +x$a + +y <- duckdb_tibble(a = 1, .prudence = "stingy") +y +try(length(y$a)) +length(collect(y)$a) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr-package.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr-package.Rd new file mode 100644 index 000000000..69de84109 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr-package.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/duckplyr-package.R +\docType{package} +\name{duckplyr-package} +\alias{duckplyr} +\alias{duckplyr-package} +\title{duckplyr: A 'DuckDB'-Backed Version of 'dplyr'} +\description{ +\if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} + +A drop-in replacement for 'dplyr', powered by 'DuckDB' for performance. Offers convenient utilities for working with in-memory and larger-than-memory data while retaining full 'dplyr' compatibility. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://duckplyr.tidyverse.org} + \item \url{https://github.com/tidyverse/duckplyr} + \item Report bugs at \url{https://github.com/tidyverse/duckplyr/issues} +} + +} +\author{ +\strong{Maintainer}: Kirill Müller \email{kirill@cynkra.com} (\href{https://orcid.org/0000-0002-1416-3412}{ORCID}) + +Authors: +\itemize{ + \item Hannes Mühleisen (\href{https://orcid.org/0000-0001-8552-0029}{ORCID}) +} + +Other contributors: +\itemize{ + \item Posit Software, PBC (\href{https://ror.org/03wc8by49}{ROR}) [copyright holder, funder] +} + +} +\keyword{internal} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr_execute.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr_execute.Rd new file mode 100644 index 000000000..b662aded5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/duckplyr_execute.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/duckplyr_execute.R +\name{duckplyr_execute} +\alias{duckplyr_execute} +\title{Execute a statement for the default connection} +\usage{ +duckplyr_execute(sql) +} +\arguments{ +\item{sql}{The statement to run.} +} +\value{ +The return value of the \code{\link[DBI:dbExecute]{DBI::dbExecute()}} call, invisibly. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +The \pkg{duckplyr} package relies on a DBI connection +to an in-memory database. +The \code{duckplyr_execute()} function allows running SQL statements +with this connection to, e.g., set up credentials +or attach other databases. +See \url{https://duckdb.org/docs/configuration/overview.html} +for more information on the configuration options. +} +\examples{ +duckplyr_execute("SET threads TO 2") +} +\keyword{internal} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/explain.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/explain.duckplyr_df.Rd new file mode 100644 index 000000000..090509aeb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/explain.duckplyr_df.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/explain-rd.R, R/explain.R +\name{explain.duckplyr_df} +\alias{explain.duckplyr_df} +\title{Explain details of a tbl} +\usage{ +\method{explain}{duckplyr_df}(x, ...) +} +\arguments{ +\item{x}{An object to explain} + +\item{...}{Other parameters possibly used by generic} +} +\value{ +The input, invisibly. +} +\description{ +This is a method for the \code{\link[dplyr:explain]{dplyr::explain()}} generic. +This is a generic function which gives more details about an object +than \code{print()}, and is more focused on human readable output than \code{str()}. +} +\examples{ +library(duckplyr) +df <- duckdb_tibble(x = c(1, 2)) +df <- mutate(df, y = 2) +explain(df) +} +\seealso{ +\code{\link[dplyr:explain]{dplyr::explain()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/fallback.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/fallback.Rd new file mode 100644 index 000000000..4ccfc21f5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/fallback.Rd @@ -0,0 +1,135 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fallback.R +\name{fallback} +\alias{fallback} +\alias{fallback_sitrep} +\alias{fallback_config} +\alias{fallback_review} +\alias{fallback_upload} +\alias{fallback_purge} +\title{Fallback to dplyr} +\usage{ +fallback_sitrep() + +fallback_config( + ..., + reset_all = FALSE, + info = NULL, + logging = NULL, + autoupload = NULL, + log_dir = NULL, + verbose = NULL +) + +fallback_review(oldest = NULL, newest = NULL, detail = TRUE) + +fallback_upload(oldest = NULL, newest = NULL, strict = TRUE) + +fallback_purge(oldest = NULL, newest = NULL) +} +\arguments{ +\item{...}{These dots are for future extensions and must be empty.} + +\item{reset_all}{Set to \code{TRUE} to reset all settings to their defaults. +The R session must be restarted for the changes to take effect.} + +\item{info}{Set to \code{TRUE} to enable fallback printing.} + +\item{logging}{Set to \code{FALSE} to disable fallback logging, +set to \code{TRUE} to explicitly enable it.} + +\item{autoupload}{Set to \code{TRUE} to enable automatic fallback uploading, +set to \code{FALSE} to disable it.} + +\item{log_dir}{Set the location of the logs in the file system. +The directory will be created if it does not exist.} + +\item{verbose}{Set to \code{TRUE} to enable verbose logging.} + +\item{oldest, newest}{The number of oldest or newest reports to review. +If not specified, all reports are dispayed.} + +\item{detail}{Print the full content of the reports. +Set to \code{FALSE} to only print the file names.} + +\item{strict}{If \code{TRUE}, the function aborts if any of the reports fail to upload. +With \code{FALSE}, only a message is printed.} +} +\description{ +The \pkg{duckplyr} package aims at providing +a fully compatible drop-in replacement for \pkg{dplyr}. +To achieve this, only a carefully selected subset of \pkg{dplyr}'s operations, +R functions, and R data types are implemented. +Whenever a request cannot be handled by DuckDB, +\pkg{duckplyr} falls back to \pkg{dplyr}. +See \verb{vignette("fallback"})` for details. + +To assist future development, the fallback situations can be logged +to the console or to a local file and uploaded for analysis. +By default, \pkg{duckplyr} will not log or upload anything. +The functions and environment variables on this page control the process. + +\code{fallback_sitrep()} prints the current settings for fallback printing, logging, +and uploading, the number of reports ready for upload, and the location of the logs. + +\code{fallback_config()} configures the current settings for fallback printing, +logging, and uploading. +Only settings that do not affect computation results can be configured, +this is by design. +The configuration is stored in a file under \code{tools::R_user_dir("duckplyr", "config")} . +When the \pkg{duckplyr} package is loaded, the configuration is read from this file, +and the corresponding environment variables are set. + +\code{fallback_review()} prints the available reports for review to the console. + +\code{fallback_upload()} uploads the available reports to a central server for analysis. +The server is hosted on AWS and the reports are stored in a private S3 bucket. +Only authorized personnel have access to the reports. + +\code{fallback_purge()} deletes some or all available reports. +} +\details{ +Logging is on by default, but can be turned off. +Uploading is opt-in. + +The following environment variables control the logging and uploading: +\itemize{ +\item \code{DUCKPLYR_FALLBACK_INFO} controls human-friendly alerts +for fallback events. +If \code{TRUE}, a message is printed when a fallback to dplyr occurs +because DuckDB cannot handle a request. +These messages are never logged. +\item \code{DUCKPLYR_FALLBACK_COLLECT} controls logging, set it +to 1 or greater to enable logging. +If the value is 0, logging is disabled. +Future versions of \pkg{duckplyr} may start logging additional data +and thus require a higher value to enable logging. +Set to 99 to enable logging for all future versions. +Use \code{\link[usethis:edit]{usethis::edit_r_environ()}} to edit the environment file. +\item \code{DUCKPLYR_FALLBACK_AUTOUPLOAD} controls uploading, set it +to 1 or greater to enable uploading. +If the value is 0, uploading is disabled. +Currently, uploading is active if the value is 1 or greater. +Future versions of \pkg{duckplyr} may start logging additional data +and thus require a higher value to enable uploading. +Set to 99 to enable uploading for all future versions. +Use \code{\link[usethis:edit]{usethis::edit_r_environ()}} to edit the environment file. +\item \code{DUCKPLYR_FALLBACK_LOG_DIR} controls the location of the logs. +It must point to a directory (existing or not) where the logs will be written. +By default, logs are written to a directory in the user's cache directory +as returned by \code{tools::R_user_dir("duckplyr", "cache")}. +\item \code{DUCKPLYR_FALLBACK_VERBOSE} controls printing of log data, set it +to \code{TRUE} or \code{FALSE} to enable or disable printing. +If the value is \code{TRUE}, a message is printed to the console +for each fallback situation. +This setting is only relevant if logging is enabled, +and mostly useful for \pkg{duckplyr}'s internal tests. +} + +All code related to fallback logging and uploading is in the +\href{https://github.com/tidyverse/duckplyr/blob/main/R/fallback.R}{\code{fallback.R}} and +\href{https://github.com/tidyverse/duckplyr/blob/main/R/telemetry.R}{\code{telemetry.R}} files. +} +\examples{ +fallback_sitrep() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-deprecated.svg b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-deprecated.svg new file mode 100644 index 000000000..b61c57c3f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-experimental.svg b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-experimental.svg new file mode 100644 index 000000000..5d88fc2c6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-experimental.svg @@ -0,0 +1,21 @@ + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-stable.svg b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-stable.svg new file mode 100644 index 000000000..9bf21e76b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-stable.svg @@ -0,0 +1,29 @@ + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-superseded.svg b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-superseded.svg new file mode 100644 index 000000000..db8d757f7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/lifecycle-superseded.svg @@ -0,0 +1,21 @@ + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/logo.png b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/figures/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fa3750d648801961e4d2dbe03b204cce9737b998 GIT binary patch literal 151220 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≷#GNCV!qt8W*7?S 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

&2bb7BDD~ymmkzPyPZ1#rC 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 Gzy3d0BaSKn literal 0 HcmV?d00001 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/filter.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/filter.duckplyr_df.Rd new file mode 100644 index 000000000..9485a1110 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/filter.duckplyr_df.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filter-rd.R, R/filter.R +\name{filter.duckplyr_df} +\alias{filter.duckplyr_df} +\title{Keep rows that match a condition} +\usage{ +\method{filter}{duckplyr_df}(.data, ..., .by = NULL, .preserve = FALSE) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Expressions that +return a logical value, and are defined in terms of the variables in +\code{.data}. If multiple expressions are included, they are combined with the +\code{&} operator. Only rows for which all conditions evaluate to \code{TRUE} are +kept.} + +\item{.by}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Optionally, a selection of columns to +group by for just this operation, functioning as an alternative to \code{\link[dplyr:group_by]{group_by()}}. For +details and examples, see \link[dplyr:dplyr_by]{?dplyr_by}.} + +\item{.preserve}{Relevant when the \code{.data} input is grouped. +If \code{.preserve = FALSE} (the default), the grouping structure +is recalculated based on the resulting data, otherwise the grouping is kept as is.} +} +\description{ +This is a method for the \code{\link[dplyr:filter]{dplyr::filter()}} generic. +See "Fallbacks" section for differences in implementation. +The \code{filter()} function is used to subset a data frame, +retaining all rows that satisfy your conditions. +To be retained, the row must produce a value of \code{TRUE} for all conditions. +Note that when a condition evaluates to \code{NA} the row will be dropped, +unlike base subsetting with \code{[}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{filter.duckplyr_df()} +\itemize{ +\item with no filter conditions, +\item nor for a grouped operation (if \code{.by} is set). +} + +These features fall back to \code{\link[dplyr:filter]{dplyr::filter()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +df <- duckdb_tibble(x = 1:3, y = 3:1) +filter(df, x >= 2) +} +\seealso{ +\code{\link[dplyr:filter]{dplyr::filter()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/flights_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/flights_df.Rd new file mode 100644 index 000000000..d2f0e09b5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/flights_df.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/flights.R +\name{flights_df} +\alias{flights_df} +\title{Flight data} +\usage{ +flights_df() +} +\description{ +Provides a variant of \code{nycflights13::flights} that is compatible with duckplyr, +as a tibble: +the timezone has been set to UTC to work around a current limitation of duckplyr, see \code{vignette("limits")}. +Call \code{\link[=as_duckdb_tibble]{as_duckdb_tibble()}} to enable duckplyr operations. +} +\examples{ +\dontshow{if (requireNamespace("nycflights13", quietly = TRUE)) withAutoprint(\{ # examplesIf} +flights_df() +\dontshow{\}) # examplesIf} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/full_join.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/full_join.duckplyr_df.Rd new file mode 100644 index 000000000..9de2db2dc --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/full_join.duckplyr_df.Rd @@ -0,0 +1,152 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/full_join-rd.R, R/full_join.R +\name{full_join.duckplyr_df} +\alias{full_join.duckplyr_df} +\title{Full join} +\usage{ +\method{full_join}{duckplyr_df}( + x, + y, + by = NULL, + copy = FALSE, + suffix = c(".x", ".y"), + ..., + keep = NULL, + na_matches = c("na", "never"), + multiple = "all", + relationship = NULL +) +} +\arguments{ +\item{x, y}{A pair of data frames, data frame extensions (e.g. a tibble), or +lazy data frames (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} +specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. + +To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with +multiple expressions. For example, \code{join_by(a == b, c == d)} will match +\code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between +\code{x} and \code{y}, you can shorten this by listing only the variable names, like +\code{join_by(a, c)}. + +\code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap +joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on +these types of joins. + +For simple equality joins, you can alternatively specify a character vector +of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} +to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, +use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. + +To perform a cross-join, generating all combinations of \code{x} and \code{y}, see +\code{\link[dplyr:cross_join]{cross_join()}}.} + +\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 the +same src as \code{x}. This allows you to join tables across srcs, but +it is a 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{...}{Other parameters passed onto methods.} + +\item{keep}{Should the join keys from both \code{x} and \code{y} be preserved in the +output? +\itemize{ +\item If \code{NULL}, the default, joins on equality retain only the keys from \code{x}, +while joins on inequality retain the keys from both inputs. +\item If \code{TRUE}, all keys from both inputs are retained. +\item If \code{FALSE}, only keys from \code{x} are retained. For right and full joins, +the data in key columns corresponding to rows that only exist in \code{y} are +merged into the key columns from \code{x}. Can't be used when joining on +inequality conditions. +}} + +\item{na_matches}{Should two \code{NA} or two \code{NaN} values match? +\itemize{ +\item \code{"na"}, the default, treats two \code{NA} or two \code{NaN} values as equal, like +\code{\%in\%}, \code{\link[=match]{match()}}, and \code{\link[=merge]{merge()}}. +\item \code{"never"} treats two \code{NA} or two \code{NaN} values as different, and will +never match them together or to any other values. This is similar to joins +for database sources and to \code{base::merge(incomparables = NA)}. +}} + +\item{multiple}{Handling of rows in \code{x} with multiple matches in \code{y}. +For each row of \code{x}: +\itemize{ +\item \code{"all"}, the default, returns every match detected in \code{y}. This is the +same behavior as SQL. +\item \code{"any"} returns one match detected in \code{y}, with no guarantees on which +match will be returned. It is often faster than \code{"first"} and \code{"last"} +if you just need to detect if there is at least one match. +\item \code{"first"} returns the first match detected in \code{y}. +\item \code{"last"} returns the last match detected in \code{y}. +}} + +\item{relationship}{Handling of the expected relationship between the keys of +\code{x} and \code{y}. If the expectations chosen from the list below are +invalidated, an error is thrown. +\itemize{ +\item \code{NULL}, the default, doesn't expect there to be any relationship between +\code{x} and \code{y}. However, for equality joins it will check for a many-to-many +relationship (which is typically unexpected) and will warn if one occurs, +encouraging you to either take a closer look at your inputs or make this +relationship explicit by specifying \code{"many-to-many"}. + +See the \emph{Many-to-many relationships} section for more details. +\item \code{"one-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"one-to-many"} expects: +\itemize{ +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"many-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +} +\item \code{"many-to-many"} doesn't perform any relationship checks, but is provided +to allow you to be explicit about this relationship if you know it +exists. +} + +\code{relationship} doesn't handle cases where there are zero matches. For that, +see \code{unmatched}.} +} +\description{ +This is a method for the \code{\link[dplyr:mutate-joins]{dplyr::full_join()}} generic. +See "Fallbacks" section for differences in implementation. +A \code{full_join()} keeps all observations in \code{x} and \code{y}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{full_join.duckplyr_df()} +\itemize{ +\item for an implicit cross join, +\item for a value of the \code{multiple} argument that isn't the default \code{"all"}. +} + +These features fall back to \code{\link[dplyr:mutate-joins]{dplyr::full_join()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +full_join(band_members, band_instruments) +} +\seealso{ +\code{\link[dplyr:mutate-joins]{dplyr::full_join()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/head.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/head.duckplyr_df.Rd new file mode 100644 index 000000000..838c143f8 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/head.duckplyr_df.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/head-rd.R, R/head.R +\name{head.duckplyr_df} +\alias{head.duckplyr_df} +\title{Return the First Parts of an Object} +\usage{ +\method{head}{duckplyr_df}(x, n = 6L, ...) +} +\arguments{ +\item{x}{A data.frame} + +\item{n}{A positive integer, how many rows to return.} + +\item{...}{Not used yet.} +} +\description{ +This is a method for the \code{\link[=head]{head()}} generic. +See "Fallbacks" section for differences in implementation. +Return the first rows of a data.frame +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{head.duckplyr_df()} +\itemize{ +\item with a negative \code{n}. +} + +These features fall back to \code{\link[=head]{head()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +head(mtcars, 2) +} +\seealso{ +\code{\link[=head]{head()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/inner_join.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/inner_join.duckplyr_df.Rd new file mode 100644 index 000000000..6ec3d12e5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/inner_join.duckplyr_df.Rd @@ -0,0 +1,173 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/inner_join-rd.R, R/inner_join.R +\name{inner_join.duckplyr_df} +\alias{inner_join.duckplyr_df} +\title{Inner join} +\usage{ +\method{inner_join}{duckplyr_df}( + x, + y, + by = NULL, + copy = FALSE, + suffix = c(".x", ".y"), + ..., + keep = NULL, + na_matches = c("na", "never"), + multiple = "all", + unmatched = "drop", + relationship = NULL +) +} +\arguments{ +\item{x, y}{A pair of data frames, data frame extensions (e.g. a tibble), or +lazy data frames (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} +specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. + +To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with +multiple expressions. For example, \code{join_by(a == b, c == d)} will match +\code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between +\code{x} and \code{y}, you can shorten this by listing only the variable names, like +\code{join_by(a, c)}. + +\code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap +joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on +these types of joins. + +For simple equality joins, you can alternatively specify a character vector +of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} +to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, +use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. + +To perform a cross-join, generating all combinations of \code{x} and \code{y}, see +\code{\link[dplyr:cross_join]{cross_join()}}.} + +\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 the +same src as \code{x}. This allows you to join tables across srcs, but +it is a 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{...}{Other parameters passed onto methods.} + +\item{keep}{Should the join keys from both \code{x} and \code{y} be preserved in the +output? +\itemize{ +\item If \code{NULL}, the default, joins on equality retain only the keys from \code{x}, +while joins on inequality retain the keys from both inputs. +\item If \code{TRUE}, all keys from both inputs are retained. +\item If \code{FALSE}, only keys from \code{x} are retained. For right and full joins, +the data in key columns corresponding to rows that only exist in \code{y} are +merged into the key columns from \code{x}. Can't be used when joining on +inequality conditions. +}} + +\item{na_matches}{Should two \code{NA} or two \code{NaN} values match? +\itemize{ +\item \code{"na"}, the default, treats two \code{NA} or two \code{NaN} values as equal, like +\code{\%in\%}, \code{\link[=match]{match()}}, and \code{\link[=merge]{merge()}}. +\item \code{"never"} treats two \code{NA} or two \code{NaN} values as different, and will +never match them together or to any other values. This is similar to joins +for database sources and to \code{base::merge(incomparables = NA)}. +}} + +\item{multiple}{Handling of rows in \code{x} with multiple matches in \code{y}. +For each row of \code{x}: +\itemize{ +\item \code{"all"}, the default, returns every match detected in \code{y}. This is the +same behavior as SQL. +\item \code{"any"} returns one match detected in \code{y}, with no guarantees on which +match will be returned. It is often faster than \code{"first"} and \code{"last"} +if you just need to detect if there is at least one match. +\item \code{"first"} returns the first match detected in \code{y}. +\item \code{"last"} returns the last match detected in \code{y}. +}} + +\item{unmatched}{How should unmatched keys that would result in dropped rows +be handled? +\itemize{ +\item \code{"drop"} drops unmatched keys from the result. +\item \code{"error"} throws an error if unmatched keys are detected. +} + +\code{unmatched} is intended to protect you from accidentally dropping rows +during a join. It only checks for unmatched keys in the input that could +potentially drop rows. +\itemize{ +\item For left joins, it checks \code{y}. +\item For right joins, it checks \code{x}. +\item For inner joins, it checks both \code{x} and \code{y}. In this case, \code{unmatched} is +also allowed to be a character vector of length 2 to specify the behavior +for \code{x} and \code{y} independently. +}} + +\item{relationship}{Handling of the expected relationship between the keys of +\code{x} and \code{y}. If the expectations chosen from the list below are +invalidated, an error is thrown. +\itemize{ +\item \code{NULL}, the default, doesn't expect there to be any relationship between +\code{x} and \code{y}. However, for equality joins it will check for a many-to-many +relationship (which is typically unexpected) and will warn if one occurs, +encouraging you to either take a closer look at your inputs or make this +relationship explicit by specifying \code{"many-to-many"}. + +See the \emph{Many-to-many relationships} section for more details. +\item \code{"one-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"one-to-many"} expects: +\itemize{ +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"many-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +} +\item \code{"many-to-many"} doesn't perform any relationship checks, but is provided +to allow you to be explicit about this relationship if you know it +exists. +} + +\code{relationship} doesn't handle cases where there are zero matches. For that, +see \code{unmatched}.} +} +\description{ +This is a method for the \code{\link[dplyr:mutate-joins]{dplyr::inner_join()}} generic. +See "Fallbacks" section for differences in implementation. +An \code{inner_join()} only keeps observations from \code{x} +that have a matching key in \code{y}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{inner_join.duckplyr_df()} +\itemize{ +\item for an implicit crossjoin, +\item for a value of the \code{multiple} argument that isn't the default \code{"all"}. +\item for a value of the \code{unmatched} argument that isn't the default \code{"drop"}. +} + +These features fall back to \code{\link[dplyr:mutate-joins]{dplyr::inner_join()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +inner_join(band_members, band_instruments) +} +\seealso{ +\code{\link[dplyr:mutate-joins]{dplyr::inner_join()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/intersect.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/intersect.duckplyr_df.Rd new file mode 100644 index 000000000..b71e152e0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/intersect.duckplyr_df.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intersect-rd.R, R/intersect.R +\name{intersect.duckplyr_df} +\alias{intersect.duckplyr_df} +\title{Intersect} +\usage{ +\method{intersect}{duckplyr_df}(x, y, ...) +} +\arguments{ +\item{x, y}{Pair of compatible data frames. A pair of data frames is +compatible if they have the same column names (possibly in different +orders) and compatible types.} + +\item{...}{These dots are for future extensions and must be empty.} +} +\description{ +This is a method for the \code{\link[dplyr:setops]{dplyr::intersect()}} generic. +See "Fallbacks" section for differences in implementation. +\code{intersect(x, y)} finds all rows in both \code{x} and \code{y}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{intersect.duckplyr_df()} +\itemize{ +\item if column names are duplicated in one of the tables, +\item if column names are different in both tables. +} + +These features fall back to \code{\link[dplyr:setops]{dplyr::intersect()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +intersect(df1, df2) +} +\seealso{ +\code{\link[dplyr:setops]{dplyr::intersect()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/is_duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/is_duckplyr_df.Rd new file mode 100644 index 000000000..7267a1bad --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/is_duckplyr_df.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/is_duckplyr_df.R +\name{is_duckplyr_df} +\alias{is_duckplyr_df} +\title{Class predicate for duckplyr data frames} +\usage{ +is_duckplyr_df(.data) +} +\arguments{ +\item{.data}{The object to test} +} +\value{ +\code{TRUE} if the input object is of class \code{"duckplyr_df"}, +otherwise \code{FALSE}. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +Tests if the input object is of class \code{"duckplyr_df"}. +} +\examples{ +tibble(a = 1:3) \%>\% + is_duckplyr_df() + +tibble(a = 1:3) \%>\% + as_duckplyr_df() \%>\% + is_duckplyr_df() +} +\keyword{internal} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/last_rel.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/last_rel.Rd new file mode 100644 index 000000000..fee4f8014 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/last_rel.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/last.R +\name{last_rel} +\alias{last_rel} +\title{Retrieve details about the most recent computation} +\usage{ +last_rel() +} +\value{ +A duckdb "relation" object, or \code{NULL} if no computation has been +performed yet. +} +\description{ +Before a result is computed, it is specified as a "relation" object. +This function retrieves this object for the last computation that led to the +materialization of a data frame. +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/left_join.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/left_join.duckplyr_df.Rd new file mode 100644 index 000000000..492db6379 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/left_join.duckplyr_df.Rd @@ -0,0 +1,172 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/left_join-rd.R, R/left_join.R +\name{left_join.duckplyr_df} +\alias{left_join.duckplyr_df} +\title{Left join} +\usage{ +\method{left_join}{duckplyr_df}( + x, + y, + by = NULL, + copy = FALSE, + suffix = c(".x", ".y"), + ..., + keep = NULL, + na_matches = c("na", "never"), + multiple = "all", + unmatched = "drop", + relationship = NULL +) +} +\arguments{ +\item{x, y}{A pair of data frames, data frame extensions (e.g. a tibble), or +lazy data frames (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} +specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. + +To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with +multiple expressions. For example, \code{join_by(a == b, c == d)} will match +\code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between +\code{x} and \code{y}, you can shorten this by listing only the variable names, like +\code{join_by(a, c)}. + +\code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap +joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on +these types of joins. + +For simple equality joins, you can alternatively specify a character vector +of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} +to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, +use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. + +To perform a cross-join, generating all combinations of \code{x} and \code{y}, see +\code{\link[dplyr:cross_join]{cross_join()}}.} + +\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 the +same src as \code{x}. This allows you to join tables across srcs, but +it is a 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{...}{Other parameters passed onto methods.} + +\item{keep}{Should the join keys from both \code{x} and \code{y} be preserved in the +output? +\itemize{ +\item If \code{NULL}, the default, joins on equality retain only the keys from \code{x}, +while joins on inequality retain the keys from both inputs. +\item If \code{TRUE}, all keys from both inputs are retained. +\item If \code{FALSE}, only keys from \code{x} are retained. For right and full joins, +the data in key columns corresponding to rows that only exist in \code{y} are +merged into the key columns from \code{x}. Can't be used when joining on +inequality conditions. +}} + +\item{na_matches}{Should two \code{NA} or two \code{NaN} values match? +\itemize{ +\item \code{"na"}, the default, treats two \code{NA} or two \code{NaN} values as equal, like +\code{\%in\%}, \code{\link[=match]{match()}}, and \code{\link[=merge]{merge()}}. +\item \code{"never"} treats two \code{NA} or two \code{NaN} values as different, and will +never match them together or to any other values. This is similar to joins +for database sources and to \code{base::merge(incomparables = NA)}. +}} + +\item{multiple}{Handling of rows in \code{x} with multiple matches in \code{y}. +For each row of \code{x}: +\itemize{ +\item \code{"all"}, the default, returns every match detected in \code{y}. This is the +same behavior as SQL. +\item \code{"any"} returns one match detected in \code{y}, with no guarantees on which +match will be returned. It is often faster than \code{"first"} and \code{"last"} +if you just need to detect if there is at least one match. +\item \code{"first"} returns the first match detected in \code{y}. +\item \code{"last"} returns the last match detected in \code{y}. +}} + +\item{unmatched}{How should unmatched keys that would result in dropped rows +be handled? +\itemize{ +\item \code{"drop"} drops unmatched keys from the result. +\item \code{"error"} throws an error if unmatched keys are detected. +} + +\code{unmatched} is intended to protect you from accidentally dropping rows +during a join. It only checks for unmatched keys in the input that could +potentially drop rows. +\itemize{ +\item For left joins, it checks \code{y}. +\item For right joins, it checks \code{x}. +\item For inner joins, it checks both \code{x} and \code{y}. In this case, \code{unmatched} is +also allowed to be a character vector of length 2 to specify the behavior +for \code{x} and \code{y} independently. +}} + +\item{relationship}{Handling of the expected relationship between the keys of +\code{x} and \code{y}. If the expectations chosen from the list below are +invalidated, an error is thrown. +\itemize{ +\item \code{NULL}, the default, doesn't expect there to be any relationship between +\code{x} and \code{y}. However, for equality joins it will check for a many-to-many +relationship (which is typically unexpected) and will warn if one occurs, +encouraging you to either take a closer look at your inputs or make this +relationship explicit by specifying \code{"many-to-many"}. + +See the \emph{Many-to-many relationships} section for more details. +\item \code{"one-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"one-to-many"} expects: +\itemize{ +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"many-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +} +\item \code{"many-to-many"} doesn't perform any relationship checks, but is provided +to allow you to be explicit about this relationship if you know it +exists. +} + +\code{relationship} doesn't handle cases where there are zero matches. For that, +see \code{unmatched}.} +} +\description{ +This is a method for the \code{\link[dplyr:mutate-joins]{dplyr::left_join()}} generic. +See "Fallbacks" section for differences in implementation. +A \code{left_join()} keeps all observations in \code{x}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{left_join.duckplyr_df()} +\itemize{ +\item for an implicit cross join, +\item for a value of the \code{multiple} argument that isn't the default \code{"all"}. +\item for a value of the \code{unmatched} argument that isn't the default \code{"drop"}. +} + +These features fall back to \code{\link[dplyr:mutate-joins]{dplyr::left_join()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +left_join(band_members, band_instruments) +} +\seealso{ +\code{\link[dplyr:mutate-joins]{dplyr::left_join()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/methods_overwrite.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/methods_overwrite.Rd new file mode 100644 index 000000000..32e22af3a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/methods_overwrite.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/zzz-methods.R +\name{methods_overwrite} +\alias{methods_overwrite} +\alias{methods_restore} +\title{Forward all dplyr methods to duckplyr} +\usage{ +methods_overwrite() + +methods_restore() +} +\value{ +Called for their side effects. +} +\description{ +After calling \code{methods_overwrite()}, all dplyr methods are redirected to duckplyr +for the duraton of the session, or until a call to \code{methods_restore()}. +The \code{methods_overwrite()} function is called automatically when the package is loaded +if the environment variable \code{DUCKPLYR_METHODS_OVERWRITE} is set to \code{TRUE}. +} +\examples{ +tibble(a = 1:3) \%>\% + mutate(b = a + 1) + +methods_overwrite() + +tibble(a = 1:3) \%>\% + mutate(b = a + 1) + +methods_restore() + +tibble(a = 1:3) \%>\% + mutate(b = a + 1) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/mutate.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/mutate.duckplyr_df.Rd new file mode 100644 index 000000000..e60000fd9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/mutate.duckplyr_df.Rd @@ -0,0 +1,71 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mutate-rd.R, R/mutate.R +\name{mutate.duckplyr_df} +\alias{mutate.duckplyr_df} +\title{Create, modify, and delete columns} +\usage{ +\method{mutate}{duckplyr_df}( + .data, + ..., + .by = NULL, + .keep = c("all", "used", "unused", "none"), + .before = NULL, + .after = NULL +) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Name-value pairs. +The name gives the name of the column in the output. + +The value can be: +\itemize{ +\item A vector of length 1, which will be recycled to the correct length. +\item A vector the same length as the current group (or the whole data frame +if ungrouped). +\item \code{NULL}, to remove the column. +\item A data frame or tibble, to create multiple columns in the output. +}} + +\item{.by}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Optionally, a selection of columns to +group by for just this operation, functioning as an alternative to \code{\link[dplyr:group_by]{group_by()}}. For +details and examples, see \link[dplyr:dplyr_by]{?dplyr_by}.} + +\item{.keep}{Control which columns from \code{.data} are retained in the output. Grouping +columns and columns created by \code{...} are always kept. +\itemize{ +\item \code{"all"} retains all columns from \code{.data}. This is the default. +\item \code{"used"} retains only the columns used in \code{...} to create new +columns. This is useful for checking your work, as it displays inputs +and outputs side-by-side. +\item \code{"unused"} retains only the columns \emph{not} used in \code{...} to create new +columns. This is useful if you generate new columns, but no longer need +the columns used to generate them. +\item \code{"none"} doesn't retain any extra columns from \code{.data}. Only the grouping +variables and columns created by \code{...} are kept. +}} + +\item{.before, .after}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Optionally, control where new columns +should appear (the default is to add to the right hand side). See +\code{\link[dplyr:relocate]{relocate()}} for more details.} +} +\description{ +This is a method for the \code{\link[dplyr:mutate]{dplyr::mutate()}} generic. +\code{mutate()} creates new columns that are functions of existing variables. +It can also modify (if the name is the same as an existing column) +and delete columns (by setting their value to \code{NULL}). +} +\examples{ +library(duckplyr) +df <- data.frame(x = c(1, 2)) +df <- mutate(df, y = 2) +df +} +\seealso{ +\code{\link[dplyr:mutate]{dplyr::mutate()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relational.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relational.Rd new file mode 100644 index 000000000..0245d9e21 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relational.Rd @@ -0,0 +1,254 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/relational-rel.R +\name{new_relational} +\alias{new_relational} +\alias{rel_to_df} +\alias{rel_filter} +\alias{rel_project} +\alias{rel_aggregate} +\alias{rel_order} +\alias{rel_join} +\alias{rel_limit} +\alias{rel_distinct} +\alias{rel_set_intersect} +\alias{rel_set_diff} +\alias{rel_set_symdiff} +\alias{rel_union_all} +\alias{rel_explain} +\alias{rel_alias} +\alias{rel_set_alias} +\alias{rel_names} +\title{Relational implementer's interface} +\usage{ +new_relational(..., class = NULL) + +rel_to_df(rel, ...) + +rel_filter(rel, exprs, ...) + +rel_project(rel, exprs, ...) + +rel_aggregate(rel, groups, aggregates, ...) + +rel_order(rel, orders, ascending, ...) + +rel_join( + left, + right, + conds, + join = c("inner", "left", "right", "outer", "cross", "semi", "anti"), + join_ref_type = c("regular", "natural", "cross", "positional", "asof"), + ... +) + +rel_limit(rel, n, ...) + +rel_distinct(rel, ...) + +rel_set_intersect(rel_a, rel_b, ...) + +rel_set_diff(rel_a, rel_b, ...) + +rel_set_symdiff(rel_a, rel_b, ...) + +rel_union_all(rel_a, rel_b, ...) + +rel_explain(rel, ...) + +rel_alias(rel, ...) + +rel_set_alias(rel, alias, ...) + +rel_names(rel, ...) +} +\arguments{ +\item{...}{Reserved for future extensions, must be empty.} + +\item{class}{Classes added in front of the \code{"relational"} base class.} + +\item{rel, rel_a, rel_b, left, right}{A relational object.} + +\item{exprs}{A list of \code{"relational_relexpr"} objects to filter by, +created by \code{\link[=new_relexpr]{new_relexpr()}}.} + +\item{groups}{A list of expressions to group by.} + +\item{aggregates}{A list of expressions with aggregates to compute.} + +\item{orders}{A list of expressions to order by.} + +\item{ascending}{A logical vector describing the sort order.} + +\item{conds}{A list of expressions to use for the join.} + +\item{join}{The type of join.} + +\item{join_ref_type}{The ref type of join.} + +\item{n}{The number of rows.} + +\item{alias}{the new alias} +} +\value{ +\itemize{ +\item \code{new_relational()} returns a new relational object. +\item \code{rel_to_df()} returns a data frame. +\item \code{rel_names()} returns a character vector. +\item All other generics return a modified relational object. +} +} +\description{ +The constructor and generics described here define a class +that helps separating dplyr's user interface from the actual underlying operations. +In the longer term, this will help packages that implement the dplyr interface +(such as \pkg{dbplyr}, \pkg{dtplyr}, \pkg{arrow} and similar) +to focus on the core details of their functionality, +rather than on the intricacies of dplyr's user interface. + +\code{new_relational()} constructs an object of class \code{"relational"}. +Users are encouraged to provide the \code{class} argument. +The typical use case will be to create a wrapper function. + +\code{rel_to_df()} extracts a data frame representation from a relational object, +to be used by \code{\link[dplyr:compute]{dplyr::collect()}}. + +\code{rel_filter()} keeps rows that match a predicate, +to be used by \code{\link[dplyr:filter]{dplyr::filter()}}. + +\code{rel_project()} selects columns or creates new columns, +to be used by \code{\link[dplyr:select]{dplyr::select()}}, \code{\link[dplyr:rename]{dplyr::rename()}}, +\code{\link[dplyr:mutate]{dplyr::mutate()}}, \code{\link[dplyr:relocate]{dplyr::relocate()}}, and others. + +\code{rel_aggregate()} combines several rows into one, +to be used by \code{\link[dplyr:summarise]{dplyr::summarize()}}. + +\code{rel_order()} reorders rows by columns or expressions, +to be used by \code{\link[dplyr:arrange]{dplyr::arrange()}}. + +\code{rel_join()} joins or merges two tables, +to be used by \code{\link[dplyr:mutate-joins]{dplyr::left_join()}}, \code{\link[dplyr:mutate-joins]{dplyr::right_join()}}, +\code{\link[dplyr:mutate-joins]{dplyr::inner_join()}}, \code{\link[dplyr:mutate-joins]{dplyr::full_join()}}, \code{\link[dplyr:cross_join]{dplyr::cross_join()}}, +\code{\link[dplyr:filter-joins]{dplyr::semi_join()}}, and \code{\link[dplyr:filter-joins]{dplyr::anti_join()}}. + +\code{rel_limit()} limits the number of rows in a table, +to be used by \code{\link[utils:head]{utils::head()}}. + +\code{rel_distinct()} only keeps the distinct rows in a table, +to be used by \code{\link[dplyr:distinct]{dplyr::distinct()}}. + +\code{rel_set_intersect()} returns rows present in both tables, +to be used by \code{\link[generics:setops]{generics::intersect()}}. + +\code{rel_set_diff()} returns rows present in any of both tables, +to be used by \code{\link[generics:setops]{generics::setdiff()}}. + +\code{rel_set_symdiff()} returns rows present in any of both tables, +to be used by \code{\link[dplyr:setops]{dplyr::symdiff()}}. + +\code{rel_union_all()} returns rows present in any of both tables, +to be used by \code{\link[dplyr:setops]{dplyr::union_all()}}. + +\code{rel_explain()} prints an explanation of the plan +executed by the relational object. + +\code{rel_alias()} returns the alias name for a relational object. + +\code{rel_set_alias()} sets the alias name for a relational object. + +\code{rel_names()} returns the column names as character vector, +to be used by \code{\link[=colnames]{colnames()}}. +} +\examples{ +new_dfrel <- function(x) { + stopifnot(is.data.frame(x)) + new_relational(list(x), class = "dfrel") +} +mtcars_rel <- new_dfrel(mtcars[1:5, 1:4]) + +rel_to_df.dfrel <- function(rel, ...) { + unclass(rel)[[1]] +} +rel_to_df(mtcars_rel) + +rel_filter.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the predicates defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, nrow(df))), ]) +} + +rel_filter( + mtcars_rel, + list( + relexpr_function( + "gt", + list(relexpr_reference("cyl"), relexpr_constant("6")) + ) + ) +) + +rel_project.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, ncol(df)))]) +} + +rel_project( + mtcars_rel, + list(relexpr_reference("cyl"), relexpr_reference("disp")) +) + +rel_order.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[order(df[[1]]), ]) +} + +rel_order( + mtcars_rel, + list(relexpr_reference("mpg")) +) +\dontshow{if (requireNamespace("dplyr", quietly = TRUE)) withAutoprint(\{ # examplesIf} +rel_join.dfrel <- function(left, right, conds, join, ...) { + left_df <- unclass(left)[[1]] + right_df <- unclass(right)[[1]] + + # A real implementation would evaluate the expressions + # defined by the conds argument, + # use different join types based on the join argument, + # and implement the join itself instead of relaying to left_join(). + new_dfrel(dplyr::left_join(left_df, right_df)) +} + +rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel) +\dontshow{\}) # examplesIf} + +rel_limit.dfrel <- function(rel, n, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[seq_len(n), ]) +} + +rel_limit(mtcars_rel, 3) + +rel_distinct.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[!duplicated(df), ]) +} + +rel_distinct(new_dfrel(mtcars[1:3, 1:4])) + +rel_names.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + names(df) +} + +rel_names(mtcars_rel) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relexpr.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relexpr.Rd new file mode 100644 index 000000000..e044d8646 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/new_relexpr.Rd @@ -0,0 +1,110 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/relational-expr.R +\name{new_relexpr} +\alias{new_relexpr} +\alias{relexpr_reference} +\alias{relexpr_constant} +\alias{relexpr_function} +\alias{relexpr_comparison} +\alias{relexpr_window} +\alias{relexpr_set_alias} +\title{Relational expressions} +\usage{ +new_relexpr(x, class = NULL) + +relexpr_reference(name, rel = NULL, alias = NULL) + +relexpr_constant(val, alias = NULL) + +relexpr_function(name, args, alias = NULL) + +relexpr_comparison(cmp_op, exprs) + +relexpr_window( + expr, + partitions, + order_bys = list(), + offset_expr = NULL, + default_expr = NULL, + alias = NULL +) + +relexpr_set_alias(expr, alias = NULL) +} +\arguments{ +\item{x}{An object.} + +\item{class}{Classes added in front of the \code{"relational_relexpr"} base class.} + +\item{name}{The name of the column or function to reference.} + +\item{rel}{The name of the relation to reference.} + +\item{alias}{An alias for the new expression.} + +\item{val}{The value to use in the constant expression.} + +\item{args}{Function arguments, a list of \code{expr} objects.} + +\item{cmp_op}{Comparison operator, e.g., \code{"<"} or \code{"=="}.} + +\item{exprs}{Expressions to compare, a list of \code{expr} objects.} + +\item{expr}{An \code{expr} object.} + +\item{partitions}{Partitions, a list of \code{expr} objects.} + +\item{order_bys}{which variables to order results by (list).} + +\item{offset_expr}{offset relational expression.} + +\item{default_expr}{default relational expression.} +} +\value{ +an object of class \code{"relational_relexpr"} + +an object of class \code{"relational_relexpr"} + +an object of class \code{"relational_relexpr"} + +an object of class \code{"relational_relexpr"} + +an object of class \code{"relational_relexpr"} + +an object of class \code{"relational_relexpr"} +} +\description{ +These functions provide a backend-agnostic way to construct expression trees +built of column references, constants, and functions. +All subexpressions in an expression tree can have an alias. + +\code{new_relexpr()} constructs an object of class \code{"relational_relexpr"}. +It is used by the higher-level constructors, +users should rarely need to call it directly. + +\code{relexpr_reference()} constructs a reference to a column. + +\code{relexpr_constant()} wraps a constant value. + +\code{relexpr_function()} applies a function. +The arguments to this function are a list of other expression objects. + +\code{relexpr_comparison()} wraps a comparison expression. + +\code{relexpr_window()} applies a function over a window, +similarly to the SQL \code{OVER} clause. + +\code{relexpr_set_alias()} assigns an alias to an expression. +} +\examples{ +relexpr_set_alias( + alias = "my_predicate", + relexpr_function( + "<", + list( + relexpr_reference("my_number"), + relexpr_constant(42) + ) + ) +) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/pull.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/pull.duckplyr_df.Rd new file mode 100644 index 000000000..e5de71767 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/pull.duckplyr_df.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pull-rd.R, R/pull.R +\name{pull.duckplyr_df} +\alias{pull.duckplyr_df} +\title{Extract a single column} +\usage{ +\method{pull}{duckplyr_df}(.data, var = -1, name = NULL, ...) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\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:topic-inject]{quasiquotation} (you can unquote column +names and column locations).} + +\item{name}{An optional parameter that specifies the column to be used +as names for a named vector. Specified in a similar manner as \code{var}.} + +\item{...}{For use by methods.} +} +\description{ +This is a method for the \code{\link[dplyr:pull]{dplyr::pull()}} generic. +See "Fallbacks" section for differences in implementation. +\code{pull()} is similar to \code{$}. +It's mostly useful because it looks a little nicer in pipes, +it also works with remote data frames, and it can optionally name the output. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{pull.duckplyr_df()} +\itemize{ +\item with a selection that returns no columns. +} + +These features fall back to \code{\link[dplyr:pull]{dplyr::pull()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +pull(mtcars, cyl) +pull(mtcars, 1) +} +\seealso{ +\code{\link[dplyr:pull]{dplyr::pull()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_csv_duckdb.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_csv_duckdb.Rd new file mode 100644 index 000000000..3016a8c23 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_csv_duckdb.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_csv_duckdb.R +\name{read_csv_duckdb} +\alias{read_csv_duckdb} +\title{Read CSV files using DuckDB} +\usage{ +read_csv_duckdb( + path, + ..., + prudence = c("thrifty", "lavish", "stingy"), + options = list() +) +} +\arguments{ +\item{path}{Path to files, glob patterns \code{*} and \verb{?} are supported.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"thrifty"}: up to a maximum size of 1 million cells, +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never. +} + +The default is \code{"thrifty"} for the ingestion functions, +and may be different for other functions. +See \code{vignette("prudence")} for more information.} + +\item{options}{Arguments to the DuckDB \code{read_csv_auto} table function.} +} +\description{ +\code{read_csv_duckdb()} reads a CSV file using DuckDB's \code{read_csv_auto()} table function. +} +\examples{ +# Create simple CSV file +path <- tempfile("duckplyr_test_", fileext = ".csv") +write.csv(data.frame(a = 1:3, b = letters[4:6]), path, row.names = FALSE) + +# Reading is immediate +df <- read_csv_duckdb(path) + +# Names are always available +names(df) + +# Materialization upon access is turned off by default +try(print(df$a)) + +# Materialize explicitly +collect(df)$a + +# Automatic materialization with prudence = "lavish" +df <- read_csv_duckdb(path, prudence = "lavish") +df$a + +# Specify column types +read_csv_duckdb( + path, + options = list(delim = ",", types = list(c("DOUBLE", "VARCHAR"))) +) +} +\seealso{ +\code{\link[=read_parquet_duckdb]{read_parquet_duckdb()}}, \code{\link[=read_json_duckdb]{read_json_duckdb()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_file_duckdb.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_file_duckdb.Rd new file mode 100644 index 000000000..5e7d15b39 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_file_duckdb.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_file_duckdb.R +\name{read_file_duckdb} +\alias{read_file_duckdb} +\title{Read files using DuckDB} +\usage{ +read_file_duckdb( + path, + table_function, + ..., + prudence = c("thrifty", "lavish", "stingy"), + options = list() +) +} +\arguments{ +\item{path}{Path to files, glob patterns \code{*} and \verb{?} are supported.} + +\item{table_function}{The name of a table-valued +DuckDB function such as \code{"read_parquet"}, +\code{"read_csv"}, \code{"read_csv_auto"} or \code{"read_json"}.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"thrifty"}: up to a maximum size of 1 million cells, +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never. +} + +The default is \code{"thrifty"} for the ingestion functions, +and may be different for other functions. +See \code{vignette("prudence")} for more information.} + +\item{options}{Arguments to the DuckDB function +indicated by \code{table_function}.} +} +\value{ +A duckplyr frame, see \code{\link[=as_duckdb_tibble]{as_duckdb_tibble()}} for details. +} +\description{ +\code{read_file_duckdb()} uses arbitrary readers to read data. +See \url{https://duckdb.org/docs/data/overview} for a documentation +of the available functions and their options. +To read multiple files with the same schema, +pass a wildcard or a character vector to the \code{path} argument, +} +\section{Fine-tuning prudence}{ + +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +The \code{prudence} argument can also be a named numeric vector +with at least one of \code{cells} or \code{rows} +to limit the cells (values) and rows in the resulting data frame +after automatic materialization. +If both limits are specified, both are enforced. +The equivalent of \code{"thrifty"} is \code{c(cells = 1e6)}. +} + +\seealso{ +\code{\link[=read_csv_duckdb]{read_csv_duckdb()}}, \code{\link[=read_parquet_duckdb]{read_parquet_duckdb()}}, \code{\link[=read_json_duckdb]{read_json_duckdb()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_json_duckdb.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_json_duckdb.Rd new file mode 100644 index 000000000..b2c32b31d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_json_duckdb.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_json_duckdb.R +\name{read_json_duckdb} +\alias{read_json_duckdb} +\title{Read JSON files using DuckDB} +\usage{ +read_json_duckdb( + path, + ..., + prudence = c("thrifty", "lavish", "stingy"), + options = list() +) +} +\arguments{ +\item{path}{Path to files, glob patterns \code{*} and \verb{?} are supported.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"thrifty"}: up to a maximum size of 1 million cells, +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never. +} + +The default is \code{"thrifty"} for the ingestion functions, +and may be different for other functions. +See \code{vignette("prudence")} for more information.} + +\item{options}{Arguments to the DuckDB \code{read_json} table function.} +} +\description{ +\code{read_json_duckdb()} reads a JSON file using DuckDB's \code{read_json()} table function. +} +\examples{ +\dontshow{if (identical(Sys.getenv("IN_PKGDOWN"), "TRUE")) withAutoprint(\{ # examplesIf} + +# Create and read a simple JSON file +path <- tempfile("duckplyr_test_", fileext = ".json") +writeLines('[{"a": 1, "b": "x"}, {"a": 2, "b": "y"}]', path) + +# Reading needs the json extension +db_exec("INSTALL json") +db_exec("LOAD json") +read_json_duckdb(path) +\dontshow{\}) # examplesIf} +} +\seealso{ +\code{\link[=read_csv_duckdb]{read_csv_duckdb()}}, \code{\link[=read_parquet_duckdb]{read_parquet_duckdb()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_parquet_duckdb.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_parquet_duckdb.Rd new file mode 100644 index 000000000..09cc5e5f1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_parquet_duckdb.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_parquet_duckdb.R +\name{read_parquet_duckdb} +\alias{read_parquet_duckdb} +\title{Read Parquet files using DuckDB} +\usage{ +read_parquet_duckdb( + path, + ..., + prudence = c("thrifty", "lavish", "stingy"), + options = list() +) +} +\arguments{ +\item{path}{Path to files, glob patterns \code{*} and \verb{?} are supported.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"thrifty"}: up to a maximum size of 1 million cells, +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never. +} + +The default is \code{"thrifty"} for the ingestion functions, +and may be different for other functions. +See \code{vignette("prudence")} for more information.} + +\item{options}{Arguments to the DuckDB \code{read_parquet} table function.} +} +\description{ +\code{read_parquet_duckdb()} reads a Parquet file using DuckDB's \code{read_parquet()} table function. +} +\seealso{ +\code{\link[=read_csv_duckdb]{read_csv_duckdb()}}, \code{\link[=read_json_duckdb]{read_json_duckdb()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_sql_duckdb.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_sql_duckdb.Rd new file mode 100644 index 000000000..cd6725c03 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/read_sql_duckdb.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sql.R +\name{read_sql_duckdb} +\alias{read_sql_duckdb} +\title{Return SQL query as duckdb_tibble} +\usage{ +read_sql_duckdb( + sql, + ..., + prudence = c("thrifty", "lavish", "stingy"), + con = NULL +) +} +\arguments{ +\item{sql}{The SQL to run.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{prudence}{Memory protection, controls if DuckDB may convert +intermediate results in DuckDB-managed memory to data frames in R memory. +\itemize{ +\item \code{"thrifty"}: up to a maximum size of 1 million cells, +\item \code{"lavish"}: regardless of size, +\item \code{"stingy"}: never. +} + +The default is \code{"thrifty"} for the ingestion functions, +and may be different for other functions. +See \code{vignette("prudence")} for more information.} + +\item{con}{The connection, defaults to the default connection.} +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Runs a query and returns it as a duckplyr frame. +} +\details{ +Using data frames from the calling environment is not supported yet, +see \url{https://github.com/duckdb/duckdb-r/issues/645} for details. +} +\examples{ +\dontshow{if (getRversion() >= "4.3") withAutoprint(\{ # examplesIf} +read_sql_duckdb("FROM duckdb_settings()") +\dontshow{\}) # examplesIf} +} +\seealso{ +\code{\link[=db_exec]{db_exec()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/reexports.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/reexports.Rd new file mode 100644 index 000000000..a0c8f45e8 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/reexports.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/duckplyr-package.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]{\%>\%}}} +}} + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/relocate.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/relocate.duckplyr_df.Rd new file mode 100644 index 000000000..395abdade --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/relocate.duckplyr_df.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/relocate-rd.R, R/relocate.R +\name{relocate.duckplyr_df} +\alias{relocate.duckplyr_df} +\title{Change column order} +\usage{ +\method{relocate}{duckplyr_df}(.data, ..., .before = NULL, .after = NULL) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Columns to move.} + +\item{.before, .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{ +This is a method for the \code{\link[dplyr:relocate]{dplyr::relocate()}} generic. +See "Fallbacks" section for differences in implementation. +Use \code{relocate()} to change column positions, +using the same syntax as \code{select()} to make it easy to move blocks of columns at once. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{relocate.duckplyr_df()} +\itemize{ +\item with a selection that returns no columns. +} + +These features fall back to \code{\link[dplyr:relocate]{dplyr::relocate()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +df <- duckdb_tibble(a = 1, b = 1, c = 1, d = "a", e = "a", f = "a") +relocate(df, f) +} +\seealso{ +\code{\link[dplyr:relocate]{dplyr::relocate()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/rename.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/rename.duckplyr_df.Rd new file mode 100644 index 000000000..e61c87e26 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/rename.duckplyr_df.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rename-rd.R, R/rename.R +\name{rename.duckplyr_df} +\alias{rename.duckplyr_df} +\title{Rename columns} +\usage{ +\method{rename}{duckplyr_df}(.data, ...) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{For \code{rename()}: <\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Use +\code{new_name = old_name} to rename selected variables. + +For \code{rename_with()}: additional arguments passed onto \code{.fn}.} +} +\description{ +This is a method for the \code{\link[dplyr:rename]{dplyr::rename()}} generic. +See "Fallbacks" section for differences in implementation. +\code{rename()} changes the names of individual variables +using \code{new_name = old_name} syntax. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{rename.duckplyr_df()} +\itemize{ +\item with a selection that returns no columns. +} + +These features fall back to \code{\link[dplyr:rename]{dplyr::rename()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +rename(mtcars, thing = mpg) +} +\seealso{ +\code{\link[dplyr:rename]{dplyr::rename()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/right_join.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/right_join.duckplyr_df.Rd new file mode 100644 index 000000000..7afcc1849 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/right_join.duckplyr_df.Rd @@ -0,0 +1,172 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/right_join-rd.R, R/right_join.R +\name{right_join.duckplyr_df} +\alias{right_join.duckplyr_df} +\title{Right join} +\usage{ +\method{right_join}{duckplyr_df}( + x, + y, + by = NULL, + copy = FALSE, + suffix = c(".x", ".y"), + ..., + keep = NULL, + na_matches = c("na", "never"), + multiple = "all", + unmatched = "drop", + relationship = NULL +) +} +\arguments{ +\item{x, y}{A pair of data frames, data frame extensions (e.g. a tibble), or +lazy data frames (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} +specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. + +To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with +multiple expressions. For example, \code{join_by(a == b, c == d)} will match +\code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between +\code{x} and \code{y}, you can shorten this by listing only the variable names, like +\code{join_by(a, c)}. + +\code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap +joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on +these types of joins. + +For simple equality joins, you can alternatively specify a character vector +of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} +to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, +use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. + +To perform a cross-join, generating all combinations of \code{x} and \code{y}, see +\code{\link[dplyr:cross_join]{cross_join()}}.} + +\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 the +same src as \code{x}. This allows you to join tables across srcs, but +it is a 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{...}{Other parameters passed onto methods.} + +\item{keep}{Should the join keys from both \code{x} and \code{y} be preserved in the +output? +\itemize{ +\item If \code{NULL}, the default, joins on equality retain only the keys from \code{x}, +while joins on inequality retain the keys from both inputs. +\item If \code{TRUE}, all keys from both inputs are retained. +\item If \code{FALSE}, only keys from \code{x} are retained. For right and full joins, +the data in key columns corresponding to rows that only exist in \code{y} are +merged into the key columns from \code{x}. Can't be used when joining on +inequality conditions. +}} + +\item{na_matches}{Should two \code{NA} or two \code{NaN} values match? +\itemize{ +\item \code{"na"}, the default, treats two \code{NA} or two \code{NaN} values as equal, like +\code{\%in\%}, \code{\link[=match]{match()}}, and \code{\link[=merge]{merge()}}. +\item \code{"never"} treats two \code{NA} or two \code{NaN} values as different, and will +never match them together or to any other values. This is similar to joins +for database sources and to \code{base::merge(incomparables = NA)}. +}} + +\item{multiple}{Handling of rows in \code{x} with multiple matches in \code{y}. +For each row of \code{x}: +\itemize{ +\item \code{"all"}, the default, returns every match detected in \code{y}. This is the +same behavior as SQL. +\item \code{"any"} returns one match detected in \code{y}, with no guarantees on which +match will be returned. It is often faster than \code{"first"} and \code{"last"} +if you just need to detect if there is at least one match. +\item \code{"first"} returns the first match detected in \code{y}. +\item \code{"last"} returns the last match detected in \code{y}. +}} + +\item{unmatched}{How should unmatched keys that would result in dropped rows +be handled? +\itemize{ +\item \code{"drop"} drops unmatched keys from the result. +\item \code{"error"} throws an error if unmatched keys are detected. +} + +\code{unmatched} is intended to protect you from accidentally dropping rows +during a join. It only checks for unmatched keys in the input that could +potentially drop rows. +\itemize{ +\item For left joins, it checks \code{y}. +\item For right joins, it checks \code{x}. +\item For inner joins, it checks both \code{x} and \code{y}. In this case, \code{unmatched} is +also allowed to be a character vector of length 2 to specify the behavior +for \code{x} and \code{y} independently. +}} + +\item{relationship}{Handling of the expected relationship between the keys of +\code{x} and \code{y}. If the expectations chosen from the list below are +invalidated, an error is thrown. +\itemize{ +\item \code{NULL}, the default, doesn't expect there to be any relationship between +\code{x} and \code{y}. However, for equality joins it will check for a many-to-many +relationship (which is typically unexpected) and will warn if one occurs, +encouraging you to either take a closer look at your inputs or make this +relationship explicit by specifying \code{"many-to-many"}. + +See the \emph{Many-to-many relationships} section for more details. +\item \code{"one-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"one-to-many"} expects: +\itemize{ +\item Each row in \code{y} matches at most 1 row in \code{x}. +} +\item \code{"many-to-one"} expects: +\itemize{ +\item Each row in \code{x} matches at most 1 row in \code{y}. +} +\item \code{"many-to-many"} doesn't perform any relationship checks, but is provided +to allow you to be explicit about this relationship if you know it +exists. +} + +\code{relationship} doesn't handle cases where there are zero matches. For that, +see \code{unmatched}.} +} +\description{ +This is a method for the \code{\link[dplyr:mutate-joins]{dplyr::right_join()}} generic. +See "Fallbacks" section for differences in implementation. +A \code{right_join()} keeps all observations in \code{y}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{right_join.duckplyr_df()} +\itemize{ +\item for an implicit cross join, +\item for a value of the \code{multiple} argument that isn't the default \code{"all"}. +\item for a value of the \code{unmatched} argument that isn't the default \code{"drop"}. +} + +These features fall back to \code{\link[dplyr:mutate-joins]{dplyr::right_join()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +right_join(band_members, band_instruments) +} +\seealso{ +\code{\link[dplyr:mutate-joins]{dplyr::right_join()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/select.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/select.duckplyr_df.Rd new file mode 100644 index 000000000..e56d8f7d5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/select.duckplyr_df.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/select-rd.R, R/select.R +\name{select.duckplyr_df} +\alias{select.duckplyr_df} +\title{Keep or drop columns using their names and types} +\usage{ +\method{select}{duckplyr_df}(.data, ...) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> One or more unquoted +expressions separated by commas. Variable names can be used as if they +were positions in the data frame, so expressions like \code{x:y} can +be used to select a range of variables.} +} +\description{ +This is a method for the \code{\link[dplyr:select]{dplyr::select()}} generic. +See "Fallbacks" section for differences in implementation. +Select (and optionally rename) variables in a data frame, +using a concise mini-language that makes it easy to refer to variables +based on their name (e.g. \code{a:f} selects all columns from a on the left +to f on the right) or type +(e.g. \code{where(is.numeric)} selects all numeric columns). +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{select.duckplyr_df()} +\itemize{ +\item with no expression, +\item nor with a selection that returns no columns. +} + +These features fall back to \code{\link[dplyr:select]{dplyr::select()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +select(mtcars, mpg) +} +\seealso{ +\code{\link[dplyr:select]{dplyr::select()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/semi_join.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/semi_join.duckplyr_df.Rd new file mode 100644 index 000000000..26a02b941 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/semi_join.duckplyr_df.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/semi_join-rd.R, R/semi_join.R +\name{semi_join.duckplyr_df} +\alias{semi_join.duckplyr_df} +\title{Semi join} +\usage{ +\method{semi_join}{duckplyr_df}(x, y, by = NULL, copy = FALSE, ..., na_matches = c("na", "never")) +} +\arguments{ +\item{x, y}{A pair of data frames, data frame extensions (e.g. a tibble), or +lazy data frames (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} +specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. + +To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with +multiple expressions. For example, \code{join_by(a == b, c == d)} will match +\code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between +\code{x} and \code{y}, you can shorten this by listing only the variable names, like +\code{join_by(a, c)}. + +\code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap +joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on +these types of joins. + +For simple equality joins, you can alternatively specify a character vector +of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} +to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, +use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. + +To perform a cross-join, generating all combinations of \code{x} and \code{y}, see +\code{\link[dplyr:cross_join]{cross_join()}}.} + +\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 the +same src as \code{x}. This allows you to join tables across srcs, but +it is a potentially expensive operation so you must opt into it.} + +\item{...}{Other parameters passed onto methods.} + +\item{na_matches}{Should two \code{NA} or two \code{NaN} values match? +\itemize{ +\item \code{"na"}, the default, treats two \code{NA} or two \code{NaN} values as equal, like +\code{\%in\%}, \code{\link[=match]{match()}}, and \code{\link[=merge]{merge()}}. +\item \code{"never"} treats two \code{NA} or two \code{NaN} values as different, and will +never match them together or to any other values. This is similar to joins +for database sources and to \code{base::merge(incomparables = NA)}. +}} +} +\description{ +This is a method for the \code{\link[dplyr:filter-joins]{dplyr::semi_join()}} generic. +\code{semi_join()} returns all rows from x with a match in y. +} +\examples{ +library(duckplyr) +band_members \%>\% semi_join(band_instruments) +} +\seealso{ +\code{\link[dplyr:filter-joins]{dplyr::semi_join()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/setdiff.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/setdiff.duckplyr_df.Rd new file mode 100644 index 000000000..a6ab4cd70 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/setdiff.duckplyr_df.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setdiff-rd.R, R/setdiff.R +\name{setdiff.duckplyr_df} +\alias{setdiff.duckplyr_df} +\title{Set difference} +\usage{ +\method{setdiff}{duckplyr_df}(x, y, ...) +} +\arguments{ +\item{x, y}{Pair of compatible data frames. A pair of data frames is +compatible if they have the same column names (possibly in different +orders) and compatible types.} + +\item{...}{These dots are for future extensions and must be empty.} +} +\description{ +This is a method for the \code{\link[dplyr:setops]{dplyr::setdiff()}} generic. +See "Fallbacks" section for differences in implementation. +\code{setdiff(x, y)} finds all rows in \code{x} that aren't in \code{y}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{setdiff.duckplyr_df()} +\itemize{ +\item if column names are duplicated in one of the tables, +\item if column names are different in both tables. +} + +These features fall back to \code{\link[dplyr:setops]{dplyr::setdiff()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +setdiff(df1, df2) +setdiff(df2, df1) +} +\seealso{ +\code{\link[dplyr:setops]{dplyr::setdiff()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/slice_head.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/slice_head.duckplyr_df.Rd new file mode 100644 index 000000000..19a4bfeb9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/slice_head.duckplyr_df.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/slice_head-rd.R, R/slice_head.R +\name{slice_head.duckplyr_df} +\alias{slice_head.duckplyr_df} +\title{Subset rows using their positions} +\usage{ +\method{slice_head}{duckplyr_df}(.data, ..., n, prop, by = NULL) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{For \code{slice()}: <\code{\link[rlang:args_data_masking]{data-masking}}> +Integer row values. + +Provide either positive values to keep, or negative values to drop. +The values provided must be either all positive or all negative. +Indices beyond the number of rows in the input are silently ignored. + +For \verb{slice_*()}, these arguments are passed on to methods.} + +\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. +\code{prop} will be rounded towards zero to generate an integer number of +rows. + +A negative value of \code{n} or \code{prop} will be subtracted from the group +size. For example, \code{n = -2} with a group of 5 rows will select 5 - 2 = 3 +rows; \code{prop = -0.25} with 8 rows will select 8 * (1 - 0.25) = 6 rows.} + +\item{by}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Optionally, a selection of columns to +group by for just this operation, functioning as an alternative to \code{\link[dplyr:group_by]{group_by()}}. For +details and examples, see \link[dplyr:dplyr_by]{?dplyr_by}.} +} +\description{ +This is a method for the \code{\link[dplyr:slice]{dplyr::slice_head()}} generic. +\code{slice_head()} selects the first rows. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{slice_head.duckplyr_df()} +\itemize{ +\item if \code{by} or \code{prop} is provided, +\item with a negative \code{n}. +} + +These features fall back to \code{\link[dplyr:slice]{dplyr::slice_head()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +df <- data.frame(x = 1:3) +df <- slice_head(df, n = 2) +df +} +\seealso{ +\code{\link[dplyr:slice]{dplyr::slice_head()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/stats_show.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/stats_show.Rd new file mode 100644 index 000000000..8e8e3c293 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/stats_show.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/stats.R +\name{stats_show} +\alias{stats_show} +\title{Show stats} +\usage{ +stats_show() +} +\value{ +Called for its side effect. +} +\description{ +Prints statistics on how many calls were handled by DuckDB. +The output shows the total number of requests in the current session, +split by fallbacks to dplyr and requests handled by duckdb. +} +\examples{ +stats_show() + +tibble(a = 1:3) \%>\% + as_duckplyr_tibble() \%>\% + mutate(b = a + 1) + +stats_show() +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/summarise.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/summarise.duckplyr_df.Rd new file mode 100644 index 000000000..1b19d71c2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/summarise.duckplyr_df.Rd @@ -0,0 +1,81 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/summarise-rd.R, R/summarise.R +\name{summarise.duckplyr_df} +\alias{summarise.duckplyr_df} +\title{Summarise each group down to one row} +\usage{ +\method{summarise}{duckplyr_df}(.data, ..., .by = NULL, .groups = NULL) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Name-value pairs of +summary functions. The name will be the name of the variable in the result. + +The value can be: +\itemize{ +\item A vector of length 1, e.g. \code{min(x)}, \code{n()}, or \code{sum(is.na(y))}. +\item A data frame, to add multiple columns from a single expression. +} + +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Returning values with size 0 or >1 was +deprecated as of 1.1.0. Please use \code{\link[dplyr:reframe]{reframe()}} for this instead.} + +\item{.by}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +<\code{\link[dplyr:dplyr_tidy_select]{tidy-select}}> Optionally, a selection of columns to +group by for just this operation, functioning as an alternative to \code{\link[dplyr:group_by]{group_by()}}. For +details and examples, see \link[dplyr:dplyr_by]{?dplyr_by}.} + +\item{.groups}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[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}. +\item "rowwise": Each row is its own group. +} + +When \code{.groups} is not specified, it is chosen +based on the number of rows of the results: +\itemize{ +\item If all the results have 1 row, you get "drop_last". +\item If the number of rows varies, you get "keep" (note that returning a +variable number of rows was deprecated in favor of \code{\link[dplyr:reframe]{reframe()}}, which +also unconditionally drops all levels of grouping). +} + +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.} +} +\description{ +This is a method for the \code{\link[dplyr:summarise]{dplyr::summarise()}} generic. +See "Fallbacks" section for differences in implementation. +\code{summarise()} creates a new data frame. +It returns one row for each combination of grouping variables; +if there are no grouping variables, +the output will have a single row summarising all observations in the input. +It will contain one column for each grouping variable +and one column for each of the summary statistics that you have specified. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{summarise.duckplyr_df()} +\itemize{ +\item with \code{.groups = "rowwise"}. +} + +These features fall back to \code{\link[dplyr:summarise]{dplyr::summarise()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +summarise(mtcars, mean = mean(disp), n = n()) +} +\seealso{ +\code{\link[dplyr:summarise]{dplyr::summarise()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/symdiff.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/symdiff.duckplyr_df.Rd new file mode 100644 index 000000000..6c92af181 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/symdiff.duckplyr_df.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/symdiff-rd.R, R/symdiff.R +\name{symdiff.duckplyr_df} +\alias{symdiff.duckplyr_df} +\title{Symmetric difference} +\usage{ +\method{symdiff}{duckplyr_df}(x, y, ...) +} +\arguments{ +\item{x, y}{Pair of compatible data frames. A pair of data frames is +compatible if they have the same column names (possibly in different +orders) and compatible types.} + +\item{...}{These dots are for future extensions and must be empty.} +} +\description{ +This is a method for the \code{\link[dplyr:setops]{dplyr::symdiff()}} generic. +See "Fallbacks" section for differences in implementation. +\code{symdiff(x, y)} computes the symmetric difference, +i.e. all rows in \code{x} that aren't in \code{y} and all rows in \code{y} that aren't in \code{x}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{symdiff.duckplyr_df()} +\itemize{ +\item if column names are duplicated in one of the tables, +\item if column names are different in both tables. +} + +These features fall back to \code{\link[dplyr:setops]{dplyr::symdiff()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +symdiff(df1, df2) +} +\seealso{ +\code{\link[dplyr:setops]{dplyr::symdiff()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/transmute.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/transmute.duckplyr_df.Rd new file mode 100644 index 000000000..a4f2b0a80 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/transmute.duckplyr_df.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/transmute-rd.R, R/transmute.R +\name{transmute.duckplyr_df} +\alias{transmute.duckplyr_df} +\title{Create, modify, and delete columns} +\usage{ +\method{transmute}{duckplyr_df}(.data, ...) +} +\arguments{ +\item{.data}{A data frame, data frame extension (e.g. a tibble), or a +lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for +more details.} + +\item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Name-value pairs. +The name gives the name of the column in the output. + +The value can be: +\itemize{ +\item A vector of length 1, which will be recycled to the correct length. +\item A vector the same length as the current group (or the whole data frame +if ungrouped). +\item \code{NULL}, to remove the column. +\item A data frame or tibble, to create multiple columns in the output. +}} +} +\description{ +This is a method for the \code{\link[dplyr:transmute]{dplyr::transmute()}} generic. +See "Fallbacks" section for differences in implementation. +\code{transmute()} creates a new data frame containing only the specified computations. +It's superseded because you can perform the same job with \code{mutate(.keep = "none")}. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{transmute.duckplyr_df()} +\itemize{ +\item with a selection that returns no columns: +} + +These features fall back to \code{\link[dplyr:transmute]{dplyr::transmute()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +library(duckplyr) +transmute(mtcars, mpg2 = mpg*2) +} +\seealso{ +\code{\link[dplyr:transmute]{dplyr::transmute()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/union.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/union.duckplyr_df.Rd new file mode 100644 index 000000000..f8c1c1af6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/union.duckplyr_df.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/union-rd.R, R/union.R +\name{union.duckplyr_df} +\alias{union.duckplyr_df} +\title{Union} +\usage{ +\method{union}{duckplyr_df}(x, y, ...) +} +\arguments{ +\item{x, y}{Pair of compatible data frames. A pair of data frames is +compatible if they have the same column names (possibly in different +orders) and compatible types.} + +\item{...}{These dots are for future extensions and must be empty.} +} +\description{ +This is a method for the \code{\link[dplyr:setops]{dplyr::union()}} generic. +\code{union(x, y)} finds all rows in either x or y, excluding duplicates. +The implementation forwards to \code{distinct(union_all(x, y))}. +} +\examples{ +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +union(df1, df2) +} +\seealso{ +\code{\link[dplyr:setops]{dplyr::union()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/union_all.duckplyr_df.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/union_all.duckplyr_df.Rd new file mode 100644 index 000000000..a6d2c21f9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/union_all.duckplyr_df.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/union_all-rd.R, R/union_all.R +\name{union_all.duckplyr_df} +\alias{union_all.duckplyr_df} +\title{Union of all} +\usage{ +\method{union_all}{duckplyr_df}(x, y, ...) +} +\arguments{ +\item{x, y}{Pair of compatible data frames. A pair of data frames is +compatible if they have the same column names (possibly in different +orders) and compatible types.} + +\item{...}{These dots are for future extensions and must be empty.} +} +\description{ +This is a method for the \code{\link[dplyr:setops]{dplyr::union_all()}} generic. +See "Fallbacks" section for differences in implementation. +\code{union_all(x, y)} finds all rows in either x or y, including duplicates. +} +\section{Fallbacks}{ + +There is no DuckDB translation in \code{union_all.duckplyr_df()} +\itemize{ +\item if column names are duplicated in one of the tables, +\item if column names are different in both tables. +} + +These features fall back to \code{\link[dplyr:setops]{dplyr::union_all()}}, see \code{vignette("fallback")} for details. +} + +\examples{ +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +union_all(df1, df2) +} +\seealso{ +\code{\link[dplyr:setops]{dplyr::union_all()}} +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/man/unsupported.Rd b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/unsupported.Rd new file mode 100644 index 000000000..a6878b79a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/man/unsupported.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/not-supported.R +\name{unsupported} +\alias{unsupported} +\title{Verbs not implemented in duckplyr} +\description{ +The following dplyr generics have no counterpart method in duckplyr. +If you want to help add a new verb, +please refer to our contributing guide \url{https://duckplyr.tidyverse.org/CONTRIBUTING.html#support-new-verbs} +} +\section{Unsupported verbs}{ + +For these verbs, duckplyr will fall back to dplyr. +\itemize{ +\item \code{\link[dplyr:count]{dplyr::add_count()}} +\item \code{\link[dplyr:cross_join]{dplyr::cross_join()}} +\item \code{\link[dplyr:do]{dplyr::do()}} +\item \code{\link[dplyr:group_by]{dplyr::group_by()}} +\item \code{\link[dplyr:group_data]{dplyr::group_indices()}} +\item \code{\link[dplyr:group_data]{dplyr::group_keys()}} +\item \code{\link[dplyr:group_map]{dplyr::group_map()}} +\item \code{\link[dplyr:group_map]{dplyr::group_modify()}} +\item \code{\link[dplyr:group_nest]{dplyr::group_nest()}} +\item \code{\link[dplyr:group_data]{dplyr::group_size()}} +\item \code{\link[dplyr:group_split]{dplyr::group_split()}} +\item \code{\link[dplyr:group_trim]{dplyr::group_trim()}} +\item \code{\link[dplyr:group_data]{dplyr::groups()}} +\item \code{\link[dplyr:group_data]{dplyr::n_groups()}} +\item \code{\link[dplyr:nest_by]{dplyr::nest_by()}} +\item \code{\link[dplyr:nest_join]{dplyr::nest_join()}} +\item \code{\link[dplyr:reframe]{dplyr::reframe()}} +\item \code{\link[dplyr:rename]{dplyr::rename_with()}} +\item \code{\link[dplyr:rows]{dplyr::rows_append()}} +\item \code{\link[dplyr:rows]{dplyr::rows_delete()}} +\item \code{\link[dplyr:rows]{dplyr::rows_insert()}} +\item \code{\link[dplyr:rows]{dplyr::rows_patch()}} +\item \code{\link[dplyr:rows]{dplyr::rows_update()}} +\item \code{\link[dplyr:rows]{dplyr::rows_upsert()}} +\item \code{\link[dplyr:rowwise]{dplyr::rowwise()}} +\item \code{\link[generics:setops]{generics::setequal()}} +\item \code{\link[dplyr:slice]{dplyr::slice_sample()}} +\item \code{\link[dplyr:slice]{dplyr::slice_tail()}} +\item \code{\link[dplyr:slice]{dplyr::slice()}} +\item \code{\link[dplyr:group_by]{dplyr::ungroup()}} +} +} + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat.R new file mode 100644 index 000000000..7931333d9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/tests.html +# * https://testthat.r-lib.org/reference/test_package.html#special-files + +library(testthat) +library(duckplyr) + +test_check("duckplyr") diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/as_duckplyr_tibble.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/as_duckplyr_tibble.md new file mode 100644 index 000000000..8f23faf3d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/as_duckplyr_tibble.md @@ -0,0 +1,26 @@ +# as_duckplyr_tibble() works + + Code + as_duckplyr_tibble(tibble(a = 1)) + Condition + Warning: + `as_duckplyr_tibble()` was deprecated in duckplyr 1.0.0. + i Please use `as_duckdb_tibble()` instead. + Output + # A duckplyr data frame: 1 variable + a + + 1 1 + +# as_duckplyr_df() and special df + + Code + as_duckplyr_df(by_cyl) + Condition + Warning: + `as_duckplyr_df()` was deprecated in duckplyr 1.0.0. + i Please use `as_duckdb_tibble()` instead. + Error in `as_duckplyr_df()`: + ! Must pass a plain data frame or a tibble, not a object. + i Convert it with `as.data.frame()` or `tibble::as_tibble()`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/compute.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/compute.md new file mode 100644 index 000000000..87d426ba1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/compute.md @@ -0,0 +1,17 @@ +# compute() + + Code + duckdb_rel_from_df(out) + Message + DuckDB Relation: + --------------------- + --- Relation Tree --- + --------------------- + Scan Table [duckplyr_4hYuvhNS26] + + --------------------- + -- Result Columns -- + --------------------- + - x (DOUBLE) + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/demo.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/demo.md new file mode 100644 index 000000000..005f76328 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/demo.md @@ -0,0 +1,65 @@ +# demo queries work + + Code + tips_by_day_hour <- taxi_data_2019 %>% filter(total_amount > 0) %>% mutate( + tip_pct = 100 * tip_amount / total_amount, dn = wday(pickup_datetime), hr = hour( + pickup_datetime)) %>% summarise(avg_tip_pct = mean(tip_pct), n = n(), .by = c( + dn, hr)) %>% arrange(desc(avg_tip_pct)) + tips_by_day_hour + Output + # A duckplyr data frame: 4 variables + # i 4 variables: dn , hr , avg_tip_pct , n + +--- + + Code + tips_by_passenger <- taxi_data_2019 %>% filter(total_amount > 0) %>% mutate( + tip_pct = 100 * tip_amount / total_amount) %>% summarise(avg_tip_pct = median( + tip_pct), n = n(), .by = passenger_count) %>% arrange(desc(passenger_count)) + tips_by_passenger + Output + # A duckplyr data frame: 3 variables + # i 3 variables: passenger_count , avg_tip_pct , n + +--- + + Code + popular_manhattan_cab_rides <- taxi_data_2019 %>% filter(total_amount > 0) %>% + inner_join(zone_map, by = join_by(pickup_location_id == LocationID)) %>% + inner_join(zone_map, by = join_by(dropoff_location_id == LocationID)) %>% + filter(Borough.x == "Manhattan", Borough.y == "Manhattan") %>% select( + start_neighborhood = Zone.x, end_neighborhood = Zone.y) %>% summarise( + num_trips = n(), .by = c(start_neighborhood, end_neighborhood), ) %>% arrange( + desc(num_trips)) + popular_manhattan_cab_rides + Output + # A duckplyr data frame: 3 variables + # i 3 variables: start_neighborhood , end_neighborhood , + # num_trips + +--- + + Code + num_trips_per_borough <- taxi_data_2019 %>% filter(total_amount > 0) %>% + inner_join(zone_map, by = join_by(pickup_location_id == LocationID)) %>% + inner_join(zone_map, by = join_by(dropoff_location_id == LocationID)) %>% + mutate(pickup_borough = Borough.x, dropoff_borough = Borough.y) %>% select( + pickup_borough, dropoff_borough, tip_amount) %>% summarise(num_trips = n(), + .by = c(pickup_borough, dropoff_borough)) + num_trips_per_borough_no_tip <- taxi_data_2019 %>% filter(total_amount > 0, + tip_amount == 0) %>% inner_join(zone_map, by = join_by(pickup_location_id == + LocationID)) %>% inner_join(zone_map, by = join_by(dropoff_location_id == + LocationID)) %>% mutate(pickup_borough = Borough.x, dropoff_borough = Borough.y, + tip_amount) %>% summarise(num_zero_tip_trips = n(), .by = c(pickup_borough, + dropoff_borough)) + num_zero_percent_trips <- num_trips_per_borough %>% inner_join( + num_trips_per_borough_no_tip, by = join_by(pickup_borough, dropoff_borough)) %>% + mutate(num_trips = num_trips, percent_zero_tips_trips = 100 * + num_zero_tip_trips / num_trips) %>% select(pickup_borough, dropoff_borough, + num_trips, percent_zero_tips_trips) %>% arrange(desc(percent_zero_tips_trips)) + num_zero_percent_trips + Output + # A duckplyr data frame: 4 variables + # i 4 variables: pickup_borough , dropoff_borough , num_trips , + # percent_zero_tips_trips + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-across.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-across.md new file mode 100644 index 000000000..cc9e6108b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-across.md @@ -0,0 +1,357 @@ +# across(.unpack =) errors if the unpacked data frame has non-unique names + + Code + duckplyr_mutate(df, across(x:y, fn, .unpack = "{outer}")) + Condition + Error in `mutate()`: + i In argument: `across(x:y, fn, .unpack = "{outer}")`. + Caused by error in `across()`: + ! Names must be unique. + x These names are duplicated: + * "x" at locations 1 and 2. + * "y" at locations 3 and 4. + +# `.unpack` is validated + + Code + duckplyr_summarise(df, across(x, mean, .unpack = 1)) + Condition + Error in `summarise()`: + i In argument: `across(x, mean, .unpack = 1)`. + Caused by error in `across()`: + ! `.unpack` must be `TRUE`, `FALSE`, or a single string, not the number 1. + +--- + + Code + duckplyr_summarise(df, across(x, mean, .unpack = c("x", "y"))) + Condition + Error in `summarise()`: + i In argument: `across(x, mean, .unpack = c("x", "y"))`. + Caused by error in `across()`: + ! `.unpack` must be `TRUE`, `FALSE`, or a single string, not a character vector. + +--- + + Code + duckplyr_summarise(df, across(x, mean, .unpack = NA)) + Condition + Error in `summarise()`: + i In argument: `across(x, mean, .unpack = NA)`. + Caused by error in `across()`: + ! `.unpack` must be `TRUE`, `FALSE`, or a single string, not `NA`. + +# across() throws meaningful error with failure during expansion (#6534) + + Code + duckplyr_summarise(df, across(everything(), fn())) + Condition + Error in `summarise()`: + i In argument: `across(everything(), fn())`. + Caused by error in `fn()`: + ! oh no! + +--- + + Code + duckplyr_summarise(df, across(everything(), fn()), .by = g) + Condition + Error in `summarise()`: + i In argument: `across(everything(), fn())`. + Caused by error in `fn()`: + ! oh no! + +# across() gives meaningful messages + + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(across(where(is.numeric), 42))) + ) + Output + + Error in `summarise()`: + i In argument: `across(where(is.numeric), 42)`. + Caused by error in `across()`: + ! `.fns` must be a function, a formula, or a list of functions/formulas. + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(across(y, mean)))) + Output + + Error in `summarise()`: + i In argument: `across(y, mean)`. + Caused by error in `across()`: + ! Can't select columns that don't exist. + x Column `y` doesn't exist. + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(res = across(where( + is.numeric), 42)))) + Output + + Error in `summarise()`: + i In argument: `res = across(where(is.numeric), 42)`. + Caused by error in `across()`: + ! `.fns` must be a function, a formula, or a list of functions/formulas. + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(z = across(y, mean)))) + Output + + Error in `summarise()`: + i In argument: `z = across(y, mean)`. + Caused by error in `across()`: + ! Can't select columns that don't exist. + x Column `y` doesn't exist. + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(res = sum(if_any(where( + is.numeric), 42))))) + Output + + Error in `summarise()`: + i In argument: `res = sum(if_any(where(is.numeric), 42))`. + Caused by error in `if_any()`: + ! `.fns` must be a function, a formula, or a list of functions/formulas. + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(res = sum(if_all(~ mean(.x))))) + ) + Output + + Error in `summarise()`: + i In argument: `res = sum(if_all(~mean(.x)))`. + Caused by error in `if_all()`: + ! Must supply a column selection. + i You most likely meant: `if_all(everything(), ~mean(.x))`. + i The first argument `.cols` selects a set of columns. + i The second argument `.fns` operates on each selected columns. + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(res = sum(if_any(~ mean(.x))))) + ) + Output + + Error in `summarise()`: + i In argument: `res = sum(if_any(~mean(.x)))`. + Caused by error in `if_any()`: + ! Must supply a column selection. + i You most likely meant: `if_any(everything(), ~mean(.x))`. + i The first argument `.cols` selects a set of columns. + i The second argument `.fns` operates on each selected columns. + Code + (expect_error(across())) + Output + + Error in `across()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + Code + (expect_error(c_across())) + Output + + Error in `c_across()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + Code + error_fn <- (function(.) { + if (all(. > 10)) { + rlang::abort("too small", call = call("error_fn")) + } else { + 42 + } + }) + (expect_error(tibble(x = 1:10, y = 11:20) %>% duckplyr_summarise(across( + everything(), error_fn)))) + Output + + Error in `summarise()`: + i In argument: `across(everything(), error_fn)`. + Caused by error in `across()`: + ! Can't compute column `y`. + Caused by error in `error_fn()`: + ! too small + Code + (expect_error(tibble(x = 1:10, y = 11:20) %>% duckplyr_mutate(across(everything(), + error_fn)))) + Output + + Error in `mutate()`: + i In argument: `across(everything(), error_fn)`. + Caused by error in `across()`: + ! Can't compute column `y`. + Caused by error in `error_fn()`: + ! too small + Code + (expect_error(tibble(x = 1:10, y = 11:20) %>% duckplyr_summarise(force(across( + everything(), error_fn))))) + Output + + Error in `summarise()`: + i In argument: `force(across(everything(), error_fn))`. + Caused by error in `across()`: + ! Can't compute column `y`. + Caused by error in `error_fn()`: + ! too small + Code + (expect_error(tibble(x = 1:10, y = 11:20) %>% duckplyr_mutate(force(across( + everything(), error_fn))))) + Output + + Error in `mutate()`: + i In argument: `force(across(everything(), error_fn))`. + Caused by error in `across()`: + ! Can't compute column `y`. + Caused by error in `error_fn()`: + ! too small + Code + (expect_error(tibble(x = 1) %>% duckplyr_summarise(across(everything(), list(f = mean, + f = mean))))) + Output + + Error in `summarise()`: + i In argument: `across(everything(), list(f = mean, f = mean))`. + Caused by error in `across()`: + ! Names must be unique. + x These names are duplicated: + * "x_f" at locations 1 and 2. + +# if_any() and if_all() aborts when predicate mistakingly used in .cols= (#5732) + + Code + (expect_error(duckplyr_filter(df, if_any(~ .x > 5)))) + Output + + Error in `filter()`: + i In argument: `if_any(~.x > 5)`. + Caused by error in `if_any()`: + ! Must supply a column selection. + i You most likely meant: `if_any(everything(), ~.x > 5)`. + i The first argument `.cols` selects a set of columns. + i The second argument `.fns` operates on each selected columns. + Code + (expect_error(duckplyr_filter(df, if_all(~ .x > 5)))) + Output + + Error in `filter()`: + i In argument: `if_all(~.x > 5)`. + Caused by error in `if_all()`: + ! Must supply a column selection. + i You most likely meant: `if_all(everything(), ~.x > 5)`. + i The first argument `.cols` selects a set of columns. + i The second argument `.fns` operates on each selected columns. + Code + (expect_error(duckplyr_filter(df, !if_any(~ .x > 5)))) + Output + + Error in `filter()`: + i In argument: `!if_any(~.x > 5)`. + Caused by error in `if_any()`: + ! Must supply a column selection. + i You most likely meant: `if_any(everything(), ~.x > 5)`. + i The first argument `.cols` selects a set of columns. + i The second argument `.fns` operates on each selected columns. + Code + (expect_error(duckplyr_filter(df, !if_all(~ .x > 5)))) + Output + + Error in `filter()`: + i In argument: `!if_all(~.x > 5)`. + Caused by error in `if_all()`: + ! Must supply a column selection. + i You most likely meant: `if_all(everything(), ~.x > 5)`. + i The first argument `.cols` selects a set of columns. + i The second argument `.fns` operates on each selected columns. + +# inlined and non inlined lambdas work + + Code + (expect_error(df %>% duckplyr_mutate(across(1:2, ~ .y + mean(bar))))) + Output + + Error in `mutate()`: + i In argument: `across(1:2, ~.y + mean(bar))`. + Caused by error in `across()`: + ! Can't compute column `foo`. + Caused by error: + ! the ... list contains fewer than 2 elements + Code + (expect_error(df %>% duckplyr_mutate((across(1:2, ~ .y + mean(bar)))))) + Output + + Error in `mutate()`: + i In argument: `(across(1:2, ~.y + mean(bar)))`. + Caused by error in `across()`: + ! Can't compute column `foo`. + Caused by error in `fn()`: + ! the ... list contains fewer than 2 elements + +# anonymous function `.fns` can access the `.data` pronoun even when not inlined + + Code + duckplyr_mutate(df, across(y, fn)) + Condition + Error in `mutate()`: + i In argument: `across(y, fn)`. + Caused by error in `across()`: + ! Can't compute column `y`. + Caused by error: + ! Can't subset `.data` outside of a data mask context. + +# can't rename during selection (#6522) + + Code + duckplyr_mutate(df, z = c_across(c(y = x))) + Condition + Error in `mutate()`: + i In argument: `z = c_across(c(y = x))`. + Caused by error in `c_across()`: + ! Can't rename variables in this context. + +# across() applies old `.cols = everything()` default with a warning + + Code + out <- duckplyr_mutate(df, across(.fns = times_two)) + Condition + Warning: + There was 1 warning in `mutate()`. + i In argument: `across(.fns = times_two)`. + Caused by warning: + ! Using `across()` without supplying `.cols` was deprecated in dplyr 1.1.0. + i Please supply `.cols` instead. + +# if_any() and if_all() apply old `.cols = everything()` default with a warning + + Code + out <- duckplyr_filter(df, if_any()) + Condition + Warning: + Using `if_any()` without supplying `.cols` was deprecated in dplyr 1.1.0. + i Please supply `.cols` instead. + +# across errors with non-empty dots and no `.fns` supplied (#6638) + + Code + duckplyr_mutate(df, across(x, .funs = ~ . * 1000)) + Condition + Error in `mutate()`: + i In argument: `across(x, .funs = ~. * 1000)`. + Caused by error in `across()`: + ! `...` must be empty. + x Problematic argument: + * .funs = ~. * 1000 + +# across(...) is deprecated + + Code + duckplyr_summarise(df, across(everything(), mean, na.rm = TRUE)) + Condition + Warning: + There was 1 warning in `summarise()`. + i In argument: `across(everything(), mean, na.rm = TRUE)`. + Caused by warning: + ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0. + Supply arguments directly to `.fns` through an anonymous function instead. + + # Previously + across(a:b, mean, na.rm = TRUE) + + # Now + across(a:b, \(x) mean(x, na.rm = TRUE)) + Output + # A tibble: 1 x 1 + x + + 1 1 + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-all-equal.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-all-equal.md new file mode 100644 index 000000000..a724372b6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-all-equal.md @@ -0,0 +1,108 @@ +# all_equal is deprecated + + Code + all_equal(mtcars, mtcars) + Condition + Warning: + `all_equal()` was deprecated in dplyr 1.1.0. + i Please use `all.equal()` instead. + i And manually order the rows/cols as needed + Output + [1] TRUE + +# data frames not equal if missing row + + Code + all_equal(mtcars, mtcars[-1, ]) + Output + [1] "Different number of rows." + Code + all_equal(iris, iris[-1, ]) + Output + [1] "Different number of rows." + Code + all_equal(df_all, df_all[-1, ]) + Output + [1] "Different number of rows." + +# data frames not equal if missing col + + Code + all_equal(mtcars, mtcars[, -1]) + Output + Different number of columns: 11 vs 10. + Code + all_equal(iris, iris[, -1]) + Output + Different number of columns: 5 vs 4. + Code + all_equal(df_all, df_all[, -1]) + Output + Different number of columns: 7 vs 6. + +# factors equal only if levels equal + + Code + all_equal(df1, df2) + Output + Different types for column `x`: factor<38051> vs factor. + Code + all_equal(df2, df1) + Output + Different types for column `x`: factor vs factor<38051>. + +# factor comparison requires strict equality of levels (#2440) + + Code + all_equal(df1, df2) + Output + Different types for column `x`: factor<4d52a> vs factor<38051>. + Code + all_equal(df2, df1) + Output + Different types for column `x`: factor<38051> vs factor<4d52a>. + +# equality test fails when convert is FALSE and types don't match (#1484) + + Code + all_equal(df1, df2, convert = FALSE) + Output + Different types for column `x`: character vs factor<4d52a>. + +# equality returns a message for convert = TRUE + + Code + all_equal(df1, df2) + Output + Different types for column `x`: integer vs character. + Code + all_equal(df1, df2, convert = TRUE) + Output + Incompatible types for column `x`: integer vs character. + +# numeric and integer can be compared if convert = TRUE + + Code + all_equal(df1, df2) + Output + Different types for column `x`: integer vs double. + +# returns vector for more than one difference (#1819) + + Code + all_equal(tibble(a = 1, b = 2), tibble(a = 1L, b = 2L)) + Output + Different types for column `a`: double vs integer. + Different types for column `b`: double vs integer. + +# ignore column order + + Code + all_equal(tibble(a = 1, b = 2), tibble(b = 2, a = 1), ignore_col_order = FALSE) + Output + Same column names, but different order. + Code + all_equal(tibble(a = 1, b = 2), tibble(a = 1), ignore_col_order = FALSE) + Output + Different number of columns: 2 vs 1. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-arrange.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-arrange.md new file mode 100644 index 000000000..40ffbb6e3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-arrange.md @@ -0,0 +1,68 @@ +# duckplyr_arrange() gives meaningful errors + + Code + (expect_error(tibble(x = 1, x = 1, .name_repair = "minimal") %>% + duckplyr_arrange(x))) + Output + + Error in `arrange()`: + ! Can't transform a data frame with duplicate names. + Code + (expect_error(tibble(x = 1) %>% duckplyr_arrange(y))) + Output + + Error in `arrange()`: + i In argument: `..1 = y`. + Caused by error: + ! object 'y' not found + Code + (expect_error(tibble(x = 1) %>% duckplyr_arrange(rep(x, 2)))) + Output + + Error in `arrange()`: + i In argument: `..1 = rep(x, 2)`. + Caused by error: + ! `..1` must be size 1, not 2. + +# arrange errors if stringi is not installed and a locale identifier is used + + Code + locale_to_chr_proxy_collate("fr", has_stringi = FALSE) + Condition + Error in `locale_to_chr_proxy_collate()`: + ! could not find function "locale_to_chr_proxy_collate" + +# arrange validates `.locale` + + Code + duckplyr_arrange(df, .locale = 1) + Condition + Error in `arrange()`: + ! `.locale` must be a string or `NULL`. + +--- + + Code + duckplyr_arrange(df, .locale = c("en_US", "fr_BF")) + Condition + Error in `arrange()`: + ! If `.locale` is a character vector, it must be a single string. + +# arrange validates that `.locale` must be one from stringi + + Code + duckplyr_arrange(df, .locale = "x") + Condition + Error in `arrange()`: + ! `.locale` must be one of the locales within `stringi::stri_locale_list()`. + +# desc() inside duckplyr_arrange() checks the number of arguments (#5921) + + Code + df <- data.frame(x = 1, y = 2) + (expect_error(duckplyr_arrange(df, desc(x, y)))) + Output + + Error in `arrange()`: + ! `desc()` must be called with exactly one argument. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-cols.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-cols.md new file mode 100644 index 000000000..6c9909161 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-cols.md @@ -0,0 +1,36 @@ +# bind_cols() repairs names + + Code + bound <- bind_cols(df, df) + Message + New names: + * `a` -> `a...1` + * `b` -> `b...2` + * `a` -> `a...3` + * `b` -> `b...4` + +# bind_cols() handles unnamed list with name repair (#3402) + + Code + df <- bind_cols(list(1, 2)) + Message + New names: + * `` -> `...1` + * `` -> `...2` + +# bind_cols() gives informative errors + + Code + # # incompatible size + (expect_error(bind_cols(a = 1:2, mtcars))) + Output + + Error in `bind_cols()`: + ! Can't recycle `a` (size 2) to match `..2` (size 32). + Code + (expect_error(bind_cols(mtcars, a = 1:3))) + Output + + Error in `bind_cols()`: + ! Can't recycle `..1` (size 32) to match `a` (size 3). + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-rows.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-rows.md new file mode 100644 index 000000000..a8e198a3f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-bind-rows.md @@ -0,0 +1,51 @@ +# bind_rows() only flattens S3 lists that inherit from list (#3924) + + Code + bind_rows(lst1) + Condition + Error in `bind_rows()`: + ! Argument 1 must be a data frame or a named atomic vector. + +# bind_rows() validates lists (#5417) + + Code + bind_rows(list(x = 1), list(x = 1:3, y = 1:2)) + Condition + Error in `vctrs::data_frame()`: + ! Can't recycle `x` (size 3) to match `y` (size 2). + +# bind_rows() give informative errors + + Code + # invalid .id + df1 <- tibble(x = 1:3) + df2 <- tibble(x = 4:6) + (expect_error(bind_rows(df1, df2, .id = 5))) + Output + + Error in `bind_rows()`: + ! `.id` must be a single string, not the number 5. + Code + # invalid type + ll <- list(tibble(a = 1:5), env(a = 1)) + (expect_error(bind_rows(ll))) + Output + + Error in `bind_rows()`: + ! Argument 2 must be a data frame or a named atomic vector. + Code + df1 <- tibble(a = factor("a")) + df2 <- tibble(a = 1L) + (expect_error(bind_rows(df1, df2))) + Output + + Error in `bind_rows()`: + ! Can't combine `..1$a` > and `..2$a` . + Code + # unnamed vectors + (expect_error(bind_rows(1:2))) + Output + + Error in `bind_rows()`: + ! Argument 1 must be a data frame or a named atomic vector. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-by.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-by.md new file mode 100644 index 000000000..0147b13cb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-by.md @@ -0,0 +1,33 @@ +# throws tidyselect errors + + Code + compute_by(by = y, data = df) + Condition + Error: + ! Can't select columns that don't exist. + x Column `y` doesn't exist. + +# can't set `.by` with a grouped-df + + Code + compute_by(x, gdf) + Condition + Error: + ! Can't supply `by` when `data` is a grouped data frame. + +# can't set `.by` with a rowwise-df + + Code + compute_by(x, rdf) + Condition + Error: + ! Can't supply `by` when `data` is a rowwise data frame. + +# can tweak the error args + + Code + compute_by(x, gdf, by_arg = "x", data_arg = "dat") + Condition + Error: + ! Can't supply `x` when `dat` is a grouped data frame. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-match.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-match.md new file mode 100644 index 000000000..5d4bee45a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-match.md @@ -0,0 +1,60 @@ +# requires at least one condition + + Code + case_match(1) + Condition + Error in `case_match()`: + ! At least one condition must be supplied. + +--- + + Code + case_match(1, NULL) + Condition + Error in `case_match()`: + ! At least one condition must be supplied. + +# `.default` is part of common type computation + + Code + case_match(1, 1 ~ 1L, .default = "x") + Condition + Error in `case_match()`: + ! Can't combine `..1 (right)` and `.default` . + +# `NULL` formula element throws meaningful error + + Code + case_match(1, 1 ~ NULL) + Condition + Error in `case_match()`: + ! `..1 (right)` must be a vector, not `NULL`. + +--- + + Code + case_match(1, NULL ~ 1) + Condition + Error in `case_match()`: + ! `..1 (left)` must be a vector, not `NULL`. + +# throws chained errors when formula evaluation fails + + Code + case_match(1, 1 ~ 2, 3 ~ stop("oh no!")) + Condition + Error in `case_match()`: + ! Failed to evaluate the right-hand side of formula 2. + Caused by error: + ! oh no! + +--- + + Code + case_match(1, 1 ~ 2, stop("oh no!") ~ 4) + Condition + Error in `case_match()`: + ! Failed to evaluate the left-hand side of formula 2. + Caused by error: + ! oh no! + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-when.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-when.md new file mode 100644 index 000000000..0945263f7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-case-when.md @@ -0,0 +1,120 @@ +# `.default` isn't part of recycling + + Code + case_when(FALSE ~ 1L, .default = 2:5) + Condition + Error in `case_when()`: + ! `.default` must have size 1, not size 4. + +# `.default` is part of common type computation + + Code + case_when(TRUE ~ 1L, .default = "x") + Condition + Error in `case_when()`: + ! Can't combine `..1 (right)` and `.default` . + +# passes through `.size` correctly + + Code + case_when(TRUE ~ 1:2, .size = 3) + Condition + Error in `case_when()`: + ! Can't recycle `..1 (right)` (size 2) to size 3. + +# invalid type errors are correct (#6261) (#6206) + + Code + case_when(TRUE ~ 1, TRUE ~ "x") + Condition + Error in `case_when()`: + ! Can't combine `..1 (right)` and `..2 (right)` . + +# `NULL` formula element throws meaningful error + + Code + case_when(1 ~ NULL) + Condition + Error in `case_when()`: + ! `..1 (right)` must be a vector, not `NULL`. + +--- + + Code + case_when(NULL ~ 1) + Condition + Error in `case_when()`: + ! `..1 (left)` must be a logical vector, not `NULL`. + +# throws chained errors when formula evaluation fails + + Code + case_when(1 ~ 2, 3 ~ stop("oh no!")) + Condition + Error in `case_when()`: + ! Failed to evaluate the right-hand side of formula 2. + Caused by error: + ! oh no! + +--- + + Code + case_when(1 ~ 2, stop("oh no!") ~ 4) + Condition + Error in `case_when()`: + ! Failed to evaluate the left-hand side of formula 2. + Caused by error: + ! oh no! + +# case_when() give meaningful errors + + Code + (expect_error(case_when(c(TRUE, FALSE) ~ 1:3, c(FALSE, TRUE) ~ 1:2))) + Output + + Error in `case_when()`: + ! Can't recycle `..1 (left)` (size 2) to match `..1 (right)` (size 3). + Code + (expect_error(case_when(c(TRUE, FALSE) ~ 1, c(FALSE, TRUE, FALSE) ~ 2, c(FALSE, + TRUE, FALSE, NA) ~ 3))) + Output + + Error in `case_when()`: + ! Can't recycle `..1 (left)` (size 2) to match `..2 (left)` (size 3). + Code + (expect_error(case_when(50 ~ 1:3))) + Output + + Error in `case_when()`: + ! `..1 (left)` must be a logical vector, not a double vector. + Code + (expect_error(case_when(paste(50)))) + Output + + Error in `case_when()`: + ! Case 1 (`paste(50)`) must be a two-sided formula, not the string "50". + Code + (expect_error(case_when(y ~ x, paste(50)))) + Output + + Error in `case_when()`: + ! Case 2 (`paste(50)`) must be a two-sided formula, not the string "50". + Code + (expect_error(case_when())) + Output + + Error in `case_when()`: + ! At least one condition must be supplied. + Code + (expect_error(case_when(NULL))) + Output + + Error in `case_when()`: + ! At least one condition must be supplied. + Code + (expect_error(case_when(~ 1:2))) + Output + + Error in `case_when()`: + ! Case 1 (`~1:2`) must be a two-sided formula. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-coalesce.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-coalesce.md new file mode 100644 index 000000000..f78e286df --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-coalesce.md @@ -0,0 +1,63 @@ +# coalesce() gives meaningful error messages + + Code + (expect_error(coalesce(1:2, 1:3))) + Output + + Error in `coalesce()`: + ! Can't recycle `..1` (size 2) to match `..2` (size 3). + Code + (expect_error(coalesce(1:2, letters[1:2]))) + Output + + Error in `coalesce()`: + ! Can't combine `..1` and `..2` . + +# `.size` overrides the common size + + Code + coalesce(x, 1:2, .size = vec_size(x)) + Condition + Error in `coalesce()`: + ! Can't recycle `..2` (size 2) to size 1. + +# must have at least one non-`NULL` vector + + Code + coalesce() + Condition + Error in `coalesce()`: + ! `...` can't be empty. + +--- + + Code + coalesce(NULL, NULL) + Condition + Error in `coalesce()`: + ! `...` can't be empty. + +# inputs must be vectors + + Code + coalesce(1, environment()) + Condition + Error in `coalesce()`: + ! `..2` must be a vector, not an environment. + +# names in error messages are indexed correctly + + Code + coalesce(1, "x") + Condition + Error in `coalesce()`: + ! Can't combine `..1` and `..2` . + +--- + + Code + coalesce(1, y = "x") + Condition + Error in `coalesce()`: + ! Can't combine `..1` and `y` . + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-conditions.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-conditions.md new file mode 100644 index 000000000..6c738c94b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-conditions.md @@ -0,0 +1,28 @@ +# errors during dots collection are not enriched (#6178) + + Code + duckplyr_mutate(mtcars, !!foobarbaz()) + Condition + Error in `foobarbaz()`: + ! could not find function "foobarbaz" + Code + duckplyr_transmute(mtcars, !!foobarbaz()) + Condition + Error in `foobarbaz()`: + ! could not find function "foobarbaz" + Code + duckplyr_select(mtcars, !!foobarbaz()) + Condition + Error in `foobarbaz()`: + ! could not find function "foobarbaz" + Code + duckplyr_arrange(mtcars, !!foobarbaz()) + Condition + Error in `foobarbaz()`: + ! could not find function "foobarbaz" + Code + duckplyr_filter(mtcars, !!foobarbaz()) + Condition + Error in `foobarbaz()`: + ! could not find function "foobarbaz" + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-consecutive-id.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-consecutive-id.md new file mode 100644 index 000000000..5a8fcdf2c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-consecutive-id.md @@ -0,0 +1,23 @@ +# follows recycling rules + + Code + consecutive_id(1:3, 1:4) + Condition + Error in `consecutive_id()`: + ! Can't recycle `..1` (size 3) to match `..2` (size 4). + +# generates useful errors + + Code + consecutive_id(x = 1:4) + Condition + Error in `consecutive_id()`: + ! Arguments in `...` must be passed by position, not name. + x Problematic argument: + * x = 1:4 + Code + consecutive_id(mean) + Condition + Error in `consecutive_id()`: + ! `..1` must be a vector, not a function. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-context.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-context.md new file mode 100644 index 000000000..0fe46540f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-context.md @@ -0,0 +1,33 @@ +# give useful error messages when not applicable + + Code + (expect_error(n())) + Output + + Error in `n()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + Code + (expect_error(cur_column())) + Output + + Error in `cur_column()`: + ! Must only be used inside `across()`. + Code + (expect_error(cur_group())) + Output + + Error in `cur_group()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + Code + (expect_error(cur_group_id())) + Output + + Error in `cur_group_id()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + Code + (expect_error(cur_group_rows())) + Output + + Error in `cur_group_rows()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-count-tally.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-count-tally.md new file mode 100644 index 000000000..de8775437 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-count-tally.md @@ -0,0 +1,102 @@ +# name must be string + + Code + duckplyr_count(df, x, name = 1) + Condition + Error in `count()`: + ! `name` must be a single string, not the number 1. + +--- + + Code + duckplyr_count(df, x, name = letters) + Condition + Error in `count()`: + ! `name` must be a single string, not a character vector. + +# can only explicitly chain together multiple tallies + + Code + df <- data.frame(g = c(1, 1, 2, 2), n = 1:4) + df %>% duckplyr_count(g, wt = n) + Output + g n + 1 1 3 + 2 2 7 + Code + df %>% duckplyr_count(g, wt = n) %>% duckplyr_count(wt = n) + Output + n + 1 10 + Code + df %>% duckplyr_count(n) + Message + Storing counts in `nn`, as `n` already present in input + i Use `name = "new_name"` to pick a new name. + Output + n nn + 1 1 1 + 2 2 1 + 3 3 1 + 4 4 1 + +# duckplyr_count() owns errors (#6139) + + Code + (expect_error(duckplyr_count(mtcars, new = 1 + ""))) + Output + + Error in `group_by()`: + i In argument: `new = 1 + ""`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + Code + (expect_error(duckplyr_count(mtcars, wt = 1 + ""))) + Output + + Error in `summarise()`: + i In argument: `n = sum(1 + "", na.rm = TRUE)`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + +# tally() owns errors (#6139) + + Code + (expect_error(tally(mtcars, wt = 1 + ""))) + Output + + Error in `tally()`: + i In argument: `n = sum(1 + "", na.rm = TRUE)`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + +# duckplyr_add_count() owns errors (#6139) + + Code + (expect_error(duckplyr_add_count(mtcars, new = 1 + ""))) + Output + + Error in `group_by()`: + i In argument: `new = 1 + ""`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + Code + (expect_error(duckplyr_add_count(mtcars, wt = 1 + ""))) + Output + + Error in `mutate()`: + i In argument: `n = sum(1 + "", na.rm = TRUE)`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + +# add_tally() owns errors (#6139) + + Code + (expect_error(add_tally(mtcars, wt = 1 + ""))) + Output + + Error in `add_tally()`: + i In argument: `n = sum(1 + "", na.rm = TRUE)`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-desc.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-desc.md new file mode 100644 index 000000000..5f5d4f164 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-desc.md @@ -0,0 +1,8 @@ +# errors cleanly on non-vectors + + Code + desc(mean) + Condition + Error in `desc()`: + ! `x` must be a vector, not a function. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-distinct.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-distinct.md new file mode 100644 index 000000000..9f36e7dcd --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-distinct.md @@ -0,0 +1,34 @@ +# distinct errors when selecting an unknown column (#3140) + + Code + df <- tibble(g = c(1, 2), x = c(1, 2)) + (expect_error(df %>% duckplyr_distinct(aa, x))) + Output + + Error in `distinct()`: + ! Must use existing variables. + x `aa` not found in `.data`. + Code + (expect_error(df %>% duckplyr_distinct(aa, bb))) + Output + + Error in `distinct()`: + ! Must use existing variables. + x `aa` not found in `.data`. + x `bb` not found in `.data`. + Code + (expect_error(df %>% duckplyr_distinct(.data$aa))) + Output + + Error in `distinct()`: + ! Must use existing variables. + x `aa` not found in `.data`. + Code + (expect_error(df %>% duckplyr_distinct(y = a + 1))) + Output + + Error in `distinct()`: + i In argument: `y = a + 1`. + Caused by error: + ! object 'a' not found + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-filter.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-filter.md new file mode 100644 index 000000000..a5ff39c59 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-filter.md @@ -0,0 +1,48 @@ +# duckplyr_filter() allows matrices with 1 column with a deprecation warning (#6091) + + Code + out <- duckplyr_filter(df, matrix(c(TRUE, FALSE), nrow = 2)) + Condition + Warning: + Using one column matrices in `filter()` was deprecated in dplyr 1.1.0. + i Please use one dimensional logical vectors instead. + +# duckplyr_filter() disallows matrices with >1 column + + Code + (expect_error(duckplyr_filter(df, matrix(TRUE, nrow = 3, ncol = 2)))) + Output + + Error in `filter()`: + i In argument: `matrix(TRUE, nrow = 3, ncol = 2)`. + Caused by error: + ! `..1` must be a logical vector, not a logical matrix. + +# duckplyr_filter() disallows arrays with >2 dimensions + + Code + (expect_error(duckplyr_filter(df, array(TRUE, dim = c(3, 1, 1))))) + Output + + Error in `filter()`: + i In argument: `array(TRUE, dim = c(3, 1, 1))`. + Caused by error: + ! `..1` must be a logical vector, not a logical array. + +# can't use `.by` with `.preserve` + + Code + duckplyr_filter(df, .by = x, .preserve = TRUE) + Condition + Error in `filter()`: + ! Can't supply both `.by` and `.preserve`. + +# catches `by` typo (#6647) + + Code + duckplyr_filter(df, by = x) + Condition + Error in `filter()`: + ! Can't specify an argument named `by` in this verb. + i Did you mean to use `.by` instead? + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-funs.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-funs.md new file mode 100644 index 000000000..9cac715c6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-funs.md @@ -0,0 +1,40 @@ +# takes the common type between all inputs (#6478) + + Code + between("1", 2, 3) + Condition + Error in `between()`: + ! Can't combine `x` and `left` . + +--- + + Code + between(1, "2", 3) + Condition + Error in `between()`: + ! Can't combine `x` and `left` . + +--- + + Code + between(1, 2, "3") + Condition + Error in `between()`: + ! Can't combine `x` and `right` . + +# recycles `left` and `right` to the size of `x` + + Code + between(1:3, 1:2, 1L) + Condition + Error in `between()`: + ! Can't recycle `left` (size 2) to size 3. + +--- + + Code + between(1:3, 1L, 1:2) + Condition + Error in `between()`: + ! Can't recycle `right` (size 2) to size 3. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-grouped-df.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-grouped-df.md new file mode 100644 index 000000000..28f19a2b3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-grouped-df.md @@ -0,0 +1,105 @@ +# validate_grouped_df() gives useful errors + + Code + (expect_error(validate_grouped_df(df1))) + Output + + Error in `validate_grouped_df()`: + ! The `.rows` column must be list of one-based integer vectors. + Code + (expect_error(group_data(df1))) + Output + + Error in `group_data()`: + ! `.data` must be a valid object. + Caused by error in `validate_grouped_df()`: + ! The `.rows` column must be list of one-based integer vectors. + Code + (expect_error(validate_grouped_df(df2))) + Output + + Error in `validate_grouped_df()`: + ! The last column of the `groups` attribute must be called `.rows`. + Code + (expect_error(validate_grouped_df(df2))) + Output + + Error in `validate_grouped_df()`: + ! The last column of the `groups` attribute must be called `.rows`. + Code + (expect_error(validate_grouped_df(df3))) + Output + + Error in `validate_grouped_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(validate_grouped_df(df4))) + Output + + Error in `validate_grouped_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(validate_grouped_df(df5))) + Output + + Error in `validate_grouped_df()`: + ! Corrupt `grouped_df` using old (< 0.8.0) format. + i Strip off old grouping with `ungroup()`. + Code + (expect_error(validate_grouped_df(df6, check_bounds = TRUE))) + Output + + Error in `validate_grouped_df()`: + ! out of bounds indices. + Code + (expect_error(validate_grouped_df(df7, check_bounds = TRUE))) + Output + + Error in `validate_grouped_df()`: + ! out of bounds indices. + Code + (expect_error(validate_grouped_df(df8, check_bounds = TRUE))) + Output + + Error in `validate_grouped_df()`: + ! out of bounds indices. + Code + (expect_error(validate_grouped_df(df10))) + Output + + Error in `validate_grouped_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(validate_grouped_df(df11))) + Output + + Error in `validate_grouped_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(new_grouped_df(tibble(x = 1:10), tibble(other = list(1:2))))) + Output + + Error in `new_grouped_df()`: + ! The last column of `groups` must be called ".rows". + Code + (expect_error(new_grouped_df(10))) + Output + + Error in `new_grouped_df()`: + ! `x` must be a data frame. + +# helper gives meaningful error messages + + Code + (expect_error(grouped_df(data.frame(x = 1), "y", FALSE))) + Output + + Error in `compute_groups()`: + ! `vars` missing from `data`: `y`. + Code + (expect_error(grouped_df(data.frame(x = 1), 1))) + Output + + Error in `grouped_df()`: + ! `vars` must be a character vector. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-if-else.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-if-else.md new file mode 100644 index 000000000..cb779fdff --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-if-else.md @@ -0,0 +1,76 @@ +# takes the common type of `true` and `false` (#6243) + + Code + if_else(TRUE, 1, "x") + Condition + Error in `if_else()`: + ! Can't combine `true` and `false` . + +# includes `missing` in the common type computation if used + + Code + if_else(TRUE, 1, 2, missing = "x") + Condition + Error in `if_else()`: + ! Can't combine `true` and `missing` . + +# `condition` must be logical (and isn't cast to logical!) + + Code + if_else(1:10, 1, 2) + Condition + Error in `if_else()`: + ! `condition` must be a logical vector, not an integer vector. + +# `true`, `false`, and `missing` must recycle to the size of `condition` + + Code + if_else(x < 2, bad, x) + Condition + Error in `if_else()`: + ! `true` must have size 3, not size 2. + +--- + + Code + if_else(x < 2, x, bad) + Condition + Error in `if_else()`: + ! `false` must have size 3, not size 2. + +--- + + Code + if_else(x < 2, x, x, missing = bad) + Condition + Error in `if_else()`: + ! `missing` must have size 3, not size 2. + +# must have empty dots + + Code + if_else(TRUE, 1, 2, missing = 3, 4) + Condition + Error in `if_else()`: + ! `...` must be empty. + x Problematic argument: + * ..1 = 4 + i Did you forget to name an argument? + +# `ptype` overrides the common type + + Code + if_else(TRUE, 1L, 2.5, ptype = integer()) + Condition + Error in `if_else()`: + ! Can't convert from `false` to due to loss of precision. + * Locations: 1 + +# `size` overrides the `condition` size + + Code + if_else(TRUE, 1, 2, size = 2) + Condition + Error in `if_else()`: + ! `condition` must have size 2, not size 1. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-by.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-by.md new file mode 100644 index 000000000..0ae8873aa --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-by.md @@ -0,0 +1,473 @@ +# joining by nothing is an error + + Code + join_by() + Condition + Error in `join_by()`: + ! Must supply at least one expression. + i If you want a cross join, use `cross_join()`. + +# nicely catches required missing arguments when wrapped + + Code + fn(a) + Condition + Error: + ! Expressions using `==` can't contain missing arguments. + x Argument `y` is missing. + +# allows for namespaced helpers (#6838) + + Code + join_by(dplyr::between(x, left, right)) + Output + Join By: + - dplyr::between(x, left, right) + +--- + + Code + join_by(dplyr::within(xl, xu, yl, yu)) + Output + Join By: + - dplyr::within(xl, xu, yl, yu) + +--- + + Code + join_by(dplyr::overlaps(xl, xu, yl, yu)) + Output + Join By: + - dplyr::overlaps(xl, xu, yl, yu) + +--- + + Code + join_by(dplyr::closest(x < y)) + Output + Join By: + - dplyr::closest(x < y) + +# has an informative print method + + Code + join_by(a, b) + Output + Join By: + - a + - b + +--- + + Code + join_by("a", "b") + Output + Join By: + - "a" + - "b" + +--- + + Code + join_by(a == a, b >= c) + Output + Join By: + - a == a + - b >= c + +--- + + Code + join_by(a == a, b >= "c") + Output + Join By: + - a == a + - b >= "c" + +--- + + Code + join_by(a == a, closest(b >= c), closest(d < e)) + Output + Join By: + - a == a + - closest(b >= c) + - closest(d < e) + +# has informative error messages + + Code + join_by(a = b) + Condition + Error in `join_by()`: + ! Can't name join expressions. + i Did you use `=` instead of `==`? + +--- + + Code + join_by(NULL) + Condition + Error in `join_by()`: + ! Expressions can't be empty. + x Expression 1 is empty. + +--- + + Code + join_by(foo(x > y)) + Condition + Error in `join_by()`: + ! Expressions must use one of: `==`, `>=`, `>`, `<=`, `<`, `closest()`, `between()`, `overlaps()`, or `within()`. + i Expression 1 is `foo(x > y)`. + +--- + + Code + join_by(x == y, x^y) + Condition + Error in `join_by()`: + ! Expressions must use one of: `==`, `>=`, `>`, `<=`, `<`, `closest()`, `between()`, `overlaps()`, or `within()`. + i Expression 2 is `x^y`. + +--- + + Code + join_by(x + 1 == y) + Condition + Error in `join_by()`: + ! Expressions can't contain computed columns, and can only reference columns by name or by explicitly specifying a side, like `x$col` or `y$col`. + i Expression 1 contains `x + 1`. + +--- + + Code + join_by(x == y + 1) + Condition + Error in `join_by()`: + ! Expressions can't contain computed columns, and can only reference columns by name or by explicitly specifying a side, like `x$col` or `y$col`. + i Expression 1 contains `y + 1`. + +--- + + Code + join_by(1) + Condition + Error in `join_by()`: + ! Each element of `...` must be a single column name or a join by expression. + x Element 1 is not a name and not an expression. + +--- + + Code + join_by(1()) + Condition + Error in `join_by()`: + ! Expressions must use one of: `==`, `>=`, `>`, `<=`, `<`, `closest()`, `between()`, `overlaps()`, or `within()`. + i Expression 1 is `1()`. + +--- + + Code + join_by(dplyrr::between(x, left, right)) + Condition + Error in `join_by()`: + ! Expressions can only be namespace prefixed with `dplyr::`. + i Expression 1 is `dplyrr::between(x, left, right)`. + +--- + + Code + join_by(x$a) + Condition + Error in `join_by()`: + ! Can't use `$` when specifying a single column name. + i Expression 1 is `x$a`. + +--- + + Code + join_by(z$a == y$b) + Condition + Error in `join_by()`: + ! The left-hand side of a `$` expression must be either `x$` or `y$`. + i Expression 1 contains `z$a`. + +--- + + Code + join_by(x$a == z$b) + Condition + Error in `join_by()`: + ! The left-hand side of a `$` expression must be either `x$` or `y$`. + i Expression 1 contains `z$b`. + +--- + + Code + join_by((x + 1)$y == b) + Condition + Error in `join_by()`: + ! The left-hand side of a `$` expression must be a symbol or string. + i Expression 1 contains `(x + 1)$y`. + +--- + + Code + join_by(x$a == x$b) + Condition + Error in `join_by()`: + ! The left and right-hand sides of a binary expression must reference different tables. + i Expression 1 contains `x$a == x$b`. + +--- + + Code + join_by(y$a == b) + Condition + Error in `join_by()`: + ! The left and right-hand sides of a binary expression must reference different tables. + i Expression 1 contains `y$a == b`. + +--- + + Code + join_by(between(x$a, x$a, x$b)) + Condition + Error in `join_by()`: + ! Expressions containing `between()` can't all reference the same table. + i Expression 1 is `between(x$a, x$a, x$b)`. + +--- + + Code + join_by(within(x$a, x$b, x$a, x$b)) + Condition + Error in `join_by()`: + ! Expressions containing `within()` can't all reference the same table. + i Expression 1 is `within(x$a, x$b, x$a, x$b)`. + +--- + + Code + join_by(overlaps(a, b, x$a, x$b)) + Condition + Error in `join_by()`: + ! Expressions containing `overlaps()` can't all reference the same table. + i Expression 1 is `overlaps(a, b, x$a, x$b)`. + +--- + + Code + join_by(closest(x$a >= x$b)) + Condition + Error in `join_by()`: + ! The left and right-hand sides of a binary expression must reference different tables. + i Expression 1 contains `x$a >= x$b`. + +--- + + Code + join_by(between(a, x$a, y$b)) + Condition + Error in `join_by()`: + ! Expressions containing `between()` must reference the same table for the lower and upper bounds. + i Expression 1 is `between(a, x$a, y$b)`. + +--- + + Code + join_by(within(x$a, y$b, y$a, y$b)) + Condition + Error in `join_by()`: + ! Expressions containing `within()` must reference the same table for the left-hand side lower and upper bounds. + i Expression 1 is `within(x$a, y$b, y$a, y$b)`. + +--- + + Code + join_by(overlaps(x$a, x$b, y$a, x$b)) + Condition + Error in `join_by()`: + ! Expressions containing `overlaps()` must reference the same table for the right-hand side lower and upper bounds. + i Expression 1 is `overlaps(x$a, x$b, y$a, x$b)`. + +--- + + Code + join_by(`>`(x)) + Condition + Error: + ! Expressions using `>` can't contain missing arguments. + x Argument `y` is missing. + +--- + + Code + join_by(between(x)) + Condition + Error: + ! Expressions using `between()` can't contain missing arguments. + x Argument `y_lower` is missing. + +--- + + Code + join_by(within(x)) + Condition + Error: + ! Expressions using `within()` can't contain missing arguments. + x Argument `x_upper` is missing. + +--- + + Code + join_by(overlaps(x)) + Condition + Error: + ! Expressions using `overlaps()` can't contain missing arguments. + x Argument `x_upper` is missing. + +--- + + Code + join_by(closest()) + Condition + Error: + ! Expressions using `closest()` can't contain missing arguments. + x Argument `expr` is missing. + +--- + + Code + join_by(`$`(x) > y) + Condition + Error: + ! Expressions using `$` can't contain missing arguments. + x Argument `name` is missing. + +--- + + Code + join_by(closest(a >= b, 1)) + Condition + Error in `closest()`: + ! unused argument (1) + +--- + + Code + join_by(closest(a == b)) + Condition + Error in `join_by()`: + ! The expression used in `closest()` can't use `==`. + i Expression 1 is `closest(a == b)`. + +--- + + Code + join_by(closest(x)) + Condition + Error in `join_by()`: + ! The first argument of `closest()` must be an expression. + i Expression 1 is `closest(x)`. + +--- + + Code + join_by(closest(1)) + Condition + Error in `join_by()`: + ! The first argument of `closest()` must be an expression. + i Expression 1 is `closest(1)`. + +--- + + Code + join_by(closest(x + y)) + Condition + Error in `join_by()`: + ! The expression used in `closest()` must use one of: `>=`, `>`, `<=`, or `<`. + i Expression 1 is `closest(x + y)`. + +--- + + Code + join_by(between(x, lower, upper, bounds = 1)) + Condition + Error: + ! `bounds` must be a string or character vector. + +--- + + Code + join_by(between(x, lower, upper, bounds = "a")) + Condition + Error: + ! `bounds` must be one of "[]", "[)", "(]", or "()", not "a". + +--- + + Code + join_by(overlaps(x, y, lower, upper, bounds = 1)) + Condition + Error: + ! `bounds` must be a string or character vector. + +--- + + Code + join_by(overlaps(x, y, lower, upper, bounds = "a")) + Condition + Error: + ! `bounds` must be one of "[]", "[)", "(]", or "()", not "a". + +--- + + Code + join_by(between(x, lower, upper, foo = 1)) + Condition + Error: + ! `...` must be empty. + i Non-empty dots were detected inside `between()`. + +--- + + Code + join_by(overlaps(x, y, lower, upper, foo = 1)) + Condition + Error: + ! `...` must be empty. + i Non-empty dots were detected inside `overlaps()`. + +# as_join_by() emits useful errors + + Code + as_join_by(FALSE) + Condition + Error: + ! `by` must be a (named) character vector, list, `join_by()` result, or NULL, not `FALSE`. + +# join_by_common() emits useful information + + Code + by <- join_by_common(c("x", "y"), c("x", "y")) + Message + Joining with `by = join_by(x, y)` + +--- + + Code + by <- join_by_common(c("_x", "foo bar"), c("_x", "foo bar")) + Message + Joining with `by = join_by(`_x`, `foo bar`)` + +--- + + Code + join_by_common(c("x", "y"), c("w", "z")) + Condition + Error: + ! `by` must be supplied when `x` and `y` have no common variables. + i Use `cross_join()` to perform a cross-join. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cols.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cols.md new file mode 100644 index 000000000..f6adf3056 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cols.md @@ -0,0 +1,148 @@ +# can't mix non-equi conditions with `keep = FALSE` (#6499) + + Code + join_cols(c("x", "y"), c("x", "z"), by = join_by(x, y > z), keep = FALSE) + Condition + Error: + ! Can't set `keep = FALSE` when using an inequality, rolling, or overlap join. + +--- + + Code + join_cols(c("xl", "xu"), c("yl", "yu"), by = join_by(xl >= yl, xu < yu), keep = FALSE) + Condition + Error: + ! Can't set `keep = FALSE` when using an inequality, rolling, or overlap join. + +--- + + Code + join_cols("x", c("yl", "yu"), by = join_by(between(x, yl, yu)), keep = FALSE) + Condition + Error: + ! Can't set `keep = FALSE` when using an inequality, rolling, or overlap join. + +--- + + Code + join_cols(c("xl", "xu"), c("yl", "yu"), by = join_by(overlaps(xl, xu, yl, yu)), + keep = FALSE) + Condition + Error: + ! Can't set `keep = FALSE` when using an inequality, rolling, or overlap join. + +# can't duplicate key between equi condition and non-equi condition + + Code + join_cols("x", c("xl", "xu"), by = join_by(x > xl, x == xu)) + Condition + Error: + ! Join columns in `x` must be unique. + x Problem with `x`. + +--- + + Code + join_cols(c("xl", "xu"), "x", by = join_by(xl < x, xu == x)) + Condition + Error: + ! Join columns in `y` must be unique. + x Problem with `x`. + +# emits useful messages + + Code + join_cols(c("x", "y"), c("y", "y"), join_by(y)) + Condition + Error: + ! Input columns in `y` must be unique. + x Problem with `y`. + +--- + + Code + join_cols(c("y", "y"), c("x", "y"), join_by(y)) + Condition + Error: + ! Input columns in `x` must be unique. + x Problem with `y`. + +--- + + Code + join_cols(xy, xy, by = as_join_by(list("1", y = "2"))) + Condition + Error in `as_join_by()`: + ! `by$x` must evaluate to a character vector. + +--- + + Code + join_cols(xy, xy, by = as_join_by(list(x = "1", "2"))) + Condition + Error in `as_join_by()`: + ! `by$y` must evaluate to a character vector. + +--- + + Code + join_cols(xy, xy, by = as_join_by(c("x", NA))) + Condition + Error: + ! Join columns in `x` can't be `NA`. + x Problem at position 2. + +--- + + Code + join_cols(xy, xy, by = as_join_by(c("aaa", "bbb"))) + Condition + Error: + ! Join columns in `x` must be present in the data. + x Problem with `aaa` and `bbb`. + +--- + + Code + join_cols(xy, xy, by = as_join_by(c("x", "x", "x"))) + Condition + Error: + ! Join columns in `x` must be unique. + x Problem with `x`. + +--- + + Code + join_cols(xyz, xyz, by = join_by(x, x > y, z)) + Condition + Error: + ! Join columns in `x` must be unique. + x Problem with `x`. + +--- + + Code + join_cols(xy, xy, by = join_by(x), suffix = "x") + Condition + Error: + ! `suffix` must be a character vector of length 2, not the string "x" of length 1. + +--- + + Code + join_cols(xy, xy, by = join_by(x), suffix = c("", NA)) + Condition + Error: + ! `suffix` can't be `NA`. + +# references original column in `y` when there are type errors (#6465) + + Code + (expect_error(join_cast_common(x_key, y_key, vars))) + Output + + Error: + ! Can't join `x$a` with `y$b` due to incompatible types. + i `x$a` is a . + i `y$b` is a . + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cross.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cross.md new file mode 100644 index 000000000..6df443ca2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-cross.md @@ -0,0 +1,9 @@ +# cross join checks for duplicate names + + Code + duckplyr_cross_join(df1, df2) + Condition + Error in `cross_join()`: + ! Input columns in `x` must be unique. + x Problem with `a`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-rows.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-rows.md new file mode 100644 index 000000000..07d31a943 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join-rows.md @@ -0,0 +1,401 @@ +# `relationship` default behavior is correct + + Code + out <- join_rows(c(1, 1), c(1, 1), condition = "==") + Condition + Warning: + Detected an unexpected many-to-many relationship between `x` and `y`. + i Row 1 of `x` matches multiple rows in `y`. + i Row 1 of `y` matches multiple rows in `x`. + i If a many-to-many relationship is expected, set `relationship = "many-to-many"` to silence this warning. + +# join_rows() allows `unmatched` to be specified independently for inner joins + + Code + join_rows(c(1, 3), c(1, 2), type = "inner", unmatched = c("drop", "error")) + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 2 of `y` was not matched. + +# join_rows() expects incompatible type errors to have been handled by join_cast_common() + + Code + (expect_error(join_rows(data.frame(x = 1), data.frame(x = factor("a"))))) + Output + + Error: + ! `join_cast_common()` should have handled this. + i This is an internal error that was detected in the dplyr package. + Please report it at with a reprex () and the full backtrace. + +# join_rows() gives meaningful one-to-one errors + + Code + join_rows(1, c(1, 1), relationship = "one-to-one") + Condition + Error: + ! Each row in `x` must match at most 1 row in `y`. + i Row 1 of `x` matches multiple rows in `y`. + +--- + + Code + join_rows(c(1, 1), 1, relationship = "one-to-one") + Condition + Error: + ! Each row in `y` must match at most 1 row in `x`. + i Row 1 of `y` matches multiple rows in `x`. + +# join_rows() gives meaningful one-to-many errors + + Code + join_rows(c(1, 1), 1, relationship = "one-to-many") + Condition + Error: + ! Each row in `y` must match at most 1 row in `x`. + i Row 1 of `y` matches multiple rows in `x`. + +# join_rows() gives meaningful many-to-one errors + + Code + join_rows(1, c(1, 1), relationship = "many-to-one") + Condition + Error: + ! Each row in `x` must match at most 1 row in `y`. + i Row 1 of `x` matches multiple rows in `y`. + +# join_rows() gives meaningful error message on unmatched rows + + Code + join_rows(data.frame(x = c(1, 2)), data.frame(x = c(3, 1)), type = "left", + unmatched = "error") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = c(1, 2)), data.frame(x = c(3, 1)), type = "nest", + unmatched = "error") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = c(1, 2)), data.frame(x = c(3, 1)), type = "right", + unmatched = "error") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 2 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = c(1, 2)), data.frame(x = 1), type = "inner", + unmatched = "error") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 2 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = c(1, 2)), data.frame(x = 1), type = "inner", + unmatched = c("error", "drop")) + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 2 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = 1), data.frame(x = c(1, 2)), type = "inner", + unmatched = "error") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 2 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = 1), data.frame(x = c(1, 2)), type = "inner", + unmatched = c("drop", "error")) + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 2 of `y` was not matched. + +# join_rows() always errors on unmatched missing values + + Code + join_rows(data.frame(x = 1), data.frame(x = NA), type = "left", unmatched = "error", + na_matches = "na") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = NA), data.frame(x = NA), type = "left", unmatched = "error", + na_matches = "never") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = 1), data.frame(x = NA), type = "nest", unmatched = "error", + na_matches = "na") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = NA), data.frame(x = NA), type = "nest", unmatched = "error", + na_matches = "never") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = NA), data.frame(x = 1), type = "right", unmatched = "error", + na_matches = "na") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 1 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = NA), data.frame(x = NA), type = "right", unmatched = "error", + na_matches = "never") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 1 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = 1), data.frame(x = c(1, NA)), type = "inner", + unmatched = "error", na_matches = "na") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 2 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = 1), data.frame(x = c(1, NA)), type = "inner", + unmatched = c("drop", "error"), na_matches = "na") + Condition + Error: + ! Each row of `y` must be matched by `x`. + i Row 2 of `y` was not matched. + +--- + + Code + join_rows(data.frame(x = c(1, NA)), data.frame(x = 1), type = "inner", + unmatched = "error", na_matches = "na") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 2 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = c(1, NA)), data.frame(x = 1), type = "inner", + unmatched = c("error", "drop"), na_matches = "na") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 2 of `x` does not have a match. + +--- + + Code + join_rows(data.frame(x = NA), data.frame(x = NA), type = "inner", unmatched = "error", + na_matches = "never") + Condition + Error: + ! Each row of `x` must have a match in `y`. + i Row 1 of `x` does not have a match. + +# join_rows() validates `unmatched` + + Code + join_rows(df, df, unmatched = 1) + Condition + Error: + ! `unmatched` must be a character vector, not the number 1. + Code + join_rows(df, df, unmatched = "foo") + Condition + Error: + ! `unmatched` must be one of "drop" or "error", not "foo". + Code + join_rows(df, df, type = "left", unmatched = character()) + Condition + Error: + ! `unmatched` must be length 1, not 0. + Code + join_rows(df, df, type = "left", unmatched = c("drop", "error")) + Condition + Error: + ! `unmatched` must be length 1, not 2. + Code + join_rows(df, df, type = "inner", unmatched = character()) + Condition + Error: + ! `unmatched` must be length 1 or 2, not 0. + Code + join_rows(df, df, type = "inner", unmatched = c("drop", "error", "error")) + Condition + Error: + ! `unmatched` must be length 1 or 2, not 3. + Code + join_rows(df, df, type = "inner", unmatched = c("drop", "dr")) + Condition + Error: + ! `unmatched` must be one of "drop" or "error", not "dr". + i Did you mean "drop"? + +# join_rows() validates `relationship` + + Code + join_rows(df, df, relationship = 1) + Condition + Error: + ! `relationship` must be a string or character vector. + +--- + + Code + join_rows(df, df, relationship = "none") + Condition + Error: + ! `relationship` must be one of "one-to-one", "one-to-many", "many-to-one", or "many-to-many", not "none". + +--- + + Code + join_rows(df, df, relationship = "warn-many-to-many") + Condition + Error: + ! `relationship` must be one of "one-to-one", "one-to-many", "many-to-one", or "many-to-many", not "warn-many-to-many". + i Did you mean "many-to-many"? + +# join_rows() rethrows overflow error nicely (#6912) + + Code + join_rows(df, df, condition = ">=") + Condition + Error: + ! This join would result in more rows than dplyr can handle. + i 50000005000000 rows would be returned. 2147483647 rows is the maximum number allowed. + i Double check your join keys. This error commonly occurs due to a missing join key, or an improperly specified join condition. + +# `multiple = NULL` is deprecated and results in `'all'` (#6731) + + Code + out <- join_rows(df1, df2, multiple = NULL) + Condition + Warning: + Specifying `multiple = NULL` was deprecated in dplyr 1.1.1. + i Please use `multiple = "all"` instead. + +--- + + Code + duckplyr_left_join(df1, df2, by = join_by(x), multiple = NULL) + Condition + Warning: + Specifying `multiple = NULL` was deprecated in dplyr 1.1.1. + i Please use `multiple = "all"` instead. + Output + # A tibble: 3 x 1 + x + + 1 1 + 2 2 + 3 2 + +# `multiple = 'error'` is deprecated (#6731) + + Code + join_rows(df1, df2, multiple = "error") + Condition + Warning: + Specifying `multiple = "error"` was deprecated in dplyr 1.1.1. + i Please use `relationship = "many-to-one"` instead. + Error: + ! Each row in `x` must match at most 1 row in `y`. + i Row 2 of `x` matches multiple rows in `y`. + +--- + + Code + duckplyr_left_join(df1, df2, by = join_by(x), multiple = "error") + Condition + Warning: + Specifying `multiple = "error"` was deprecated in dplyr 1.1.1. + i Please use `relationship = "many-to-one"` instead. + Error in `left_join()`: + ! Each row in `x` must match at most 1 row in `y`. + i Row 2 of `x` matches multiple rows in `y`. + +# `multiple = 'warning'` is deprecated (#6731) + + Code + out <- join_rows(df1, df2, multiple = "warning") + Condition + Warning: + Specifying `multiple = "warning"` was deprecated in dplyr 1.1.1. + i Please use `relationship = "many-to-one"` instead. + Warning: + Each row in `x` is expected to match at most 1 row in `y`. + i Row 2 of `x` matches multiple rows. + +--- + + Code + duckplyr_left_join(df1, df2, by = join_by(x), multiple = "warning") + Condition + Warning: + Specifying `multiple = "warning"` was deprecated in dplyr 1.1.1. + i Please use `relationship = "many-to-one"` instead. + Warning in `left_join()`: + Each row in `x` is expected to match at most 1 row in `y`. + i Row 2 of `x` matches multiple rows. + Output + # A tibble: 3 x 1 + x + + 1 1 + 2 2 + 3 2 + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join.md new file mode 100644 index 000000000..73111ec5a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-join.md @@ -0,0 +1,244 @@ +# can't use `keep = FALSE` with non-equi conditions (#6499) + + Code + duckplyr_left_join(df1, df2, join_by(overlaps(xl, xu, yl, yu)), keep = FALSE) + Condition + Error in `left_join()`: + ! Can't set `keep = FALSE` when using an inequality, rolling, or overlap join. + +--- + + Code + duckplyr_full_join(df1, df2, join_by(overlaps(xl, xu, yl, yu)), keep = FALSE) + Condition + Error in `full_join()`: + ! Can't set `keep = FALSE` when using an inequality, rolling, or overlap join. + +# join_mutate() validates arguments + + Code + join_mutate(df, df, by = 1, type = "left") + Condition + Error: + ! `by` must be a (named) character vector, list, `join_by()` result, or NULL, not the number 1. + Code + join_mutate(df, df, by = "x", type = "left", suffix = 1) + Condition + Error: + ! `suffix` must be a character vector of length 2, not the number 1 of length 1. + Code + join_mutate(df, df, by = "x", type = "left", na_matches = "foo") + Condition + Error: + ! `na_matches` must be one of "na" or "never", not "foo". + Code + join_mutate(df, df, by = "x", type = "left", keep = 1) + Condition + Error: + ! `keep` must be `TRUE`, `FALSE`, or `NULL`, not the number 1. + +# join_filter() validates arguments + + Code + join_filter(df, df, by = 1, type = "semi") + Condition + Error: + ! `by` must be a (named) character vector, list, `join_by()` result, or NULL, not the number 1. + Code + join_filter(df, df, by = "x", type = "semi", na_matches = "foo") + Condition + Error: + ! `na_matches` must be one of "na" or "never", not "foo". + +# mutating joins compute common columns + + Code + out <- duckplyr_left_join(df1, df2) + Message + Joining with `by = join_by(x)` + +# filtering joins compute common columns + + Code + out <- duckplyr_semi_join(df1, df2) + Message + Joining with `by = join_by(x)` + +# mutating joins reference original column in `y` when there are type errors (#6465) + + Code + (expect_error(duckplyr_left_join(x, y, by = join_by(a == b)))) + Output + + Error in `left_join()`: + ! Can't join `x$a` with `y$b` due to incompatible types. + i `x$a` is a . + i `y$b` is a . + +# filtering joins reference original column in `y` when there are type errors (#6465) + + Code + (expect_error(duckplyr_semi_join(x, y, by = join_by(a == b)))) + Output + + Error in `semi_join()`: + ! Can't join `x$a` with `y$b` due to incompatible types. + i `x$a` is a . + i `y$b` is a . + +# error if passed additional arguments + + Code + duckplyr_inner_join(df1, df2, on = "a") + Condition + Error in `inner_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + Code + duckplyr_left_join(df1, df2, on = "a") + Condition + Error in `left_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + Code + duckplyr_right_join(df1, df2, on = "a") + Condition + Error in `right_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + Code + duckplyr_full_join(df1, df2, on = "a") + Condition + Error in `full_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + Code + duckplyr_nest_join(df1, df2, on = "a") + Condition + Error in `nest_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + Code + duckplyr_anti_join(df1, df2, on = "a") + Condition + Error in `anti_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + Code + duckplyr_semi_join(df1, df2, on = "a") + Condition + Error in `semi_join()`: + ! `...` must be empty. + x Problematic argument: + * on = "a" + +# nest_join computes common columns + + Code + out <- duckplyr_nest_join(df1, df2) + Message + Joining with `by = join_by(x)` + +# nest_join references original column in `y` when there are type errors (#6465) + + Code + (expect_error(duckplyr_nest_join(x, y, by = join_by(a == b)))) + Output + + Error in `nest_join()`: + ! Can't join `x$a` with `y$b` due to incompatible types. + i `x$a` is a . + i `y$b` is a . + +# validates inputs + + Code + duckplyr_nest_join(df1, df2, by = 1) + Condition + Error in `nest_join()`: + ! `by` must be a (named) character vector, list, `join_by()` result, or NULL, not the number 1. + Code + duckplyr_nest_join(df1, df2, keep = 1) + Condition + Error in `nest_join()`: + ! `keep` must be `TRUE`, `FALSE`, or `NULL`, not the number 1. + Code + duckplyr_nest_join(df1, df2, name = 1) + Condition + Error in `duckplyr_nest_join()`: + ! `name` must be a single string, not the number 1. + Code + duckplyr_nest_join(df1, df2, na_matches = 1) + Condition + Error in `nest_join()`: + ! `na_matches` must be a string or character vector. + +# `by = character()` technically respects `unmatched` + + Code + duckplyr_left_join(df1, df2, by = character(), unmatched = "error") + Condition + Error in `left_join()`: + ! Each row of `y` must be matched by `x`. + i Row 1 of `y` was not matched. + +# `by = character()` technically respects `relationship` + + Code + duckplyr_left_join(df, df, by = character(), relationship = "many-to-one") + Condition + Error in `left_join()`: + ! Each row in `x` must match at most 1 row in `y`. + i Row 1 of `x` matches multiple rows in `y`. + +# `by = character()` for a cross join is deprecated (#6604) + + Code + out <- duckplyr_left_join(df1, df2, by = character()) + Condition + Warning: + Using `by = character()` to perform a cross join was deprecated in dplyr 1.1.0. + i Please use `cross_join()` instead. + +--- + + Code + out <- duckplyr_semi_join(df1, df2, by = character()) + Condition + Warning: + Using `by = character()` to perform a cross join was deprecated in dplyr 1.1.0. + i Please use `cross_join()` instead. + +--- + + Code + out <- duckplyr_nest_join(df1, df2, by = character()) + Condition + Warning: + Using `by = character()` to perform a cross join was deprecated in dplyr 1.1.0. + i Please use `cross_join()` instead. + +# `by = named character()` for a cross join works + + Code + out <- duckplyr_left_join(df1, df2, by = by) + Condition + Warning: + Using `by = character()` to perform a cross join was deprecated in dplyr 1.1.0. + i Please use `cross_join()` instead. + +# `by = list(x = character(), y = character())` for a cross join is deprecated (#6604) + + Code + out <- duckplyr_left_join(df1, df2, by = list(x = character(), y = character())) + Condition + Warning: + Using `by = character()` to perform a cross join was deprecated in dplyr 1.1.0. + i Please use `cross_join()` instead. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-lead-lag.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-lead-lag.md new file mode 100644 index 000000000..ef2bce01d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-lead-lag.md @@ -0,0 +1,70 @@ +# `lag()` gives informative error for objects + + Code + lag(ts(1:10)) + Condition + Error in `lag()`: + ! `x` must be a vector, not a , do you want `stats::lag()`? + +# `lead()` / `lag()` validate `n` + + Code + lead(1:5, n = 1:2) + Condition + Error in `lead()`: + ! `n` must be a whole number, not an integer vector. + Code + lead(1:5, -1) + Condition + Error in `lead()`: + ! `n` must be positive. + +--- + + Code + lag(1:5, n = 1:2) + Condition + Error in `lag()`: + ! `n` must be a whole number, not an integer vector. + Code + lag(1:5, -1) + Condition + Error in `lag()`: + ! `n` must be positive. + +# `lead()` / `lag()` check for empty dots + + Code + lead(1:5, deault = 1) + Condition + Error in `lead()`: + ! `...` must be empty. + x Problematic argument: + * deault = 1 + +--- + + Code + lag(1:5, deault = 1) + Condition + Error in `lag()`: + ! `...` must be empty. + x Problematic argument: + * deault = 1 + +# `lead()` / `lag()` require that `x` is a vector + + Code + lead(environment()) + Condition + Error in `lead()`: + ! `x` must be a vector, not an environment. + +--- + + Code + lag(environment()) + Condition + Error in `lag()`: + ! `x` must be a vector, not an environment. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-mutate.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-mutate.md new file mode 100644 index 000000000..a6cb3f8f4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-mutate.md @@ -0,0 +1,42 @@ +# can't overwrite column active bindings (#6666) + + Code + duckplyr_mutate(df, y = { + x <<- 2 + x + }) + Condition + Error in `mutate()`: + i In argument: `y = { ... }`. + Caused by error: + ! unused argument (base::quote(2)) + +--- + + Code + duckplyr_mutate(df, .by = g, y = { + x <<- 2 + x + }) + Condition + Error in `mutate()`: + i In argument: `y = { ... }`. + i In group 1: `g = 1`. + Caused by error: + ! unused argument (base::quote(2)) + +# can't share local variables across expressions (#6666) + + Code + duckplyr_mutate(df, x2 = { + foo <- x + x + }, y2 = { + foo + }) + Condition + Error in `mutate()`: + i In argument: `y2 = { ... }`. + Caused by error: + ! object 'foo' not found + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-n-distinct.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-n-distinct.md new file mode 100644 index 000000000..ad43e4584 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-n-distinct.md @@ -0,0 +1,20 @@ +# n_distinct() generates useful errors + + Code + n_distinct() + Condition + Error in `n_distinct()`: + ! `...` is absent, but must be supplied. + Code + n_distinct(x = 1:4) + Condition + Error in `n_distinct()`: + ! Arguments in `...` must be passed by position, not name. + x Problematic argument: + * x = 1:4 + Code + n_distinct(mean) + Condition + Error in `n_distinct()`: + ! `..1` must be a vector, not a function. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-na-if.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-na-if.md new file mode 100644 index 000000000..b153cb3a6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-na-if.md @@ -0,0 +1,49 @@ +# is type stable on `x` + + Code + na_if(0L, 1.5) + Condition + Error in `na_if()`: + ! Can't convert from `y` to `x` due to loss of precision. + * Locations: 1 + +# is size stable on `x` + + Code + na_if(1, integer()) + Condition + Error in `na_if()`: + ! Can't recycle `y` (size 0) to size 1. + +--- + + Code + na_if(1, c(1, 2)) + Condition + Error in `na_if()`: + ! Can't recycle `y` (size 2) to size 1. + +--- + + Code + na_if(c(1, 2, 3), c(1, 2)) + Condition + Error in `na_if()`: + ! Can't recycle `y` (size 2) to size 3. + +# requires vector types for `x` and `y` + + Code + na_if(environment(), 1) + Condition + Error in `na_if()`: + ! `x` must be a vector, not an environment. + +--- + + Code + na_if(1, environment()) + Condition + Error in `na_if()`: + ! `y` must be a vector, not an environment. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-nth-value.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-nth-value.md new file mode 100644 index 000000000..f4def1989 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-nth-value.md @@ -0,0 +1,80 @@ +# `na_rm` is validated + + Code + nth(1, 1, na_rm = 1) + Condition + Error in `nth()`: + ! `na_rm` must be `TRUE` or `FALSE`, not the number 1. + +--- + + Code + nth(1, 1, na_rm = c(TRUE, FALSE)) + Condition + Error in `nth()`: + ! `na_rm` must be `TRUE` or `FALSE`, not a logical vector. + +# `default` must be size 1 (when not used with lists) + + Code + nth(1L, n = 2L, default = 1:2) + Condition + Error in `nth()`: + ! `default` must have size 1, not size 2. + +# `default` is cast to the type of `x` (when not used with lists) + + Code + nth("x", 2, default = 2) + Condition + Error in `nth()`: + ! Can't convert `default` to match type of `x` . + +# `n` is validated (#5466) + + Code + nth(1:10, n = "x") + Condition + Error in `nth()`: + ! Can't convert `n` to . + +--- + + Code + nth(1:10, n = 1:2) + Condition + Error in `nth()`: + ! `n` must have size 1, not size 2. + +--- + + Code + nth(1:10, n = NA_integer_) + Condition + Error in `nth()`: + ! `n` can't be `NA`. + +# `x` must be a vector + + Code + nth(environment(), 1L) + Condition + Error in `vec_size()`: + ! `x` must be a vector, not an environment. + +# `order_by` must be the same size as `x` + + Code + nth(1:5, n = 1L, order_by = 1:2) + Condition + Error in `nth()`: + ! `order_by` must have size 5, not size 2. + +--- + + Code + nth(1:5, n = 6L, order_by = 1:2) + Condition + Error in `nth()`: + ! `order_by` must have size 5, not size 2. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-order-by.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-order-by.md new file mode 100644 index 000000000..456157c91 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-order-by.md @@ -0,0 +1,33 @@ +# order_by() gives useful error messages + + Code + (expect_error(order_by(mtcars, 10))) + Output + + Error in `order_by()`: + ! `call` must be a function call, not the number 10. + Code + (expect_error(order_by(mtcars, cyl))) + Output + + Error in `order_by()`: + ! `call` must be a function call, not a symbol. + i Did you mean `arrange(mtcars, cyl)`? + +# `with_order()` requires `order_by` and `x` to be the same size + + Code + with_order(1:2, identity, 1:3) + Condition + Error in `with_order()`: + ! `order_by` must have size 3, not size 2. + +# order_by() give meaningful errors + + Code + (expect_error(order_by(NULL, 1L))) + Output + + Error in `order_by()`: + ! `call` must be a function call, not the number 1. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-pick.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-pick.md new file mode 100644 index 000000000..1f7179e5e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-pick.md @@ -0,0 +1,131 @@ +# must supply at least one selector to `pick()` + + Code + duckplyr_mutate(df, y = pick()) + Condition + Error in `mutate()`: + i In argument: `y = pick()`. + Caused by error in `pick()`: + ! Must supply at least one input to `pick()`. + +--- + + Code + duckplyr_mutate(df, y = pick_wrapper()) + Condition + Error in `mutate()`: + i In argument: `y = pick_wrapper()`. + Caused by error in `pick()`: + ! Must supply at least one input to `pick()`. + +# errors correctly outside mutate context + + Code + pick() + Condition + Error in `pick()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + +--- + + Code + pick(a, b) + Condition + Error in `pick()`: + ! Must only be used inside data-masking verbs like `mutate()`, `filter()`, and `group_by()`. + +# when expansion occurs, error labels use the pre-expansion quosure + + Code + duckplyr_mutate(df, if (cur_group_id() == 1L) pick(x) else "x", .by = g) + Condition + Error in `mutate()`: + i In argument: `if (cur_group_id() == 1L) pick(x) else "x"`. + Caused by error: + ! `if (cur_group_id() == 1L) pick(x) else "x"` must return compatible vectors across groups. + i Result of type > for group 1: `g = 1`. + i Result of type for group 2: `g = 2`. + +# doesn't allow renaming + + Code + duckplyr_mutate(data.frame(x = 1), pick(y = x)) + Condition + Error in `mutate()`: + i In argument: `pick(y = x)`. + Caused by error in `pick()`: + ! Can't rename variables in this context. + +--- + + Code + duckplyr_mutate(data.frame(x = 1), pick_wrapper(y = x)) + Condition + Error in `mutate()`: + i In argument: `pick_wrapper(y = x)`. + Caused by error in `pick()`: + ! Can't rename variables in this context. + +# `pick()` errors in `duckplyr_arrange()` are useful + + Code + duckplyr_arrange(df, pick(y)) + Condition + Error in `arrange()`: + i In argument: `..1 = pick(y)`. + Caused by error in `pick()`: + ! Can't select columns that don't exist. + x Column `y` doesn't exist. + +--- + + Code + duckplyr_arrange(df, foo(pick(x))) + Condition + Error in `arrange()`: + i In argument: `..1 = foo(pick(x))`. + Caused by error in `foo()`: + ! could not find function "foo" + +# `duckplyr_filter()` with `pick()` that uses invalid tidy-selection errors + + Code + duckplyr_filter(df, pick(x, a)) + Condition + Error in `filter()`: + i In argument: `pick(x, a)`. + Caused by error in `pick()`: + ! Can't select columns that don't exist. + x Column `a` doesn't exist. + +--- + + Code + duckplyr_filter(df, pick_wrapper(x, a)) + Condition + Error in `filter()`: + i In argument: `pick_wrapper(x, a)`. + Caused by error in `pick()`: + ! Can't select columns that don't exist. + x Column `a` doesn't exist. + +# `duckplyr_filter()` that doesn't use `pick()` result correctly errors + + Code + duckplyr_filter(df, pick(x, y)$x) + Condition + Error in `filter()`: + i In argument: `asNamespace("dplyr")$dplyr_pick_tibble(x = x, y = y)$x`. + Caused by error: + ! `..1` must be a logical vector, not a double vector. + +--- + + Code + duckplyr_filter(df, pick_wrapper(x, y)$x) + Condition + Error in `filter()`: + i In argument: `pick_wrapper(x, y)$x`. + Caused by error: + ! `..1` must be a logical vector, not a double vector. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rank.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rank.md new file mode 100644 index 000000000..dc94a7a71 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rank.md @@ -0,0 +1,32 @@ +# ntile() validates `n` + + Code + ntile(1, n = 1.5) + Condition + Error in `ntile()`: + ! `n` must be a whole number, not the number 1.5. + +--- + + Code + ntile(1, n = c(1, 2)) + Condition + Error in `ntile()`: + ! `n` must be a whole number, not a double vector. + +--- + + Code + ntile(1, n = NA_real_) + Condition + Error in `ntile()`: + ! `n` must be a whole number, not a numeric `NA`. + +--- + + Code + ntile(1, n = 0) + Condition + Error in `ntile()`: + ! `n` must be positive. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-reframe.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-reframe.md new file mode 100644 index 000000000..fe4584db5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-reframe.md @@ -0,0 +1,33 @@ +# `duckplyr_reframe()` throws intelligent recycling errors + + Code + duckplyr_reframe(df, x = 1:2, y = 3:5) + Condition + Error in `reframe()`: + ! Can't recycle `y = 3:5`. + Caused by error: + ! `y` must be size 2 or 1, not 3. + i An earlier column had size 2. + +--- + + Code + duckplyr_reframe(df, x = 1:2, y = 3:5, .by = g) + Condition + Error in `reframe()`: + ! Can't recycle `y = 3:5`. + i In group 1: `g = 1`. + Caused by error: + ! `y` must be size 2 or 1, not 3. + i An earlier column had size 2. + +# `duckplyr_reframe()` doesn't message about regrouping when multiple group columns are supplied + + Code + out <- duckplyr_reframe(df, x = mean(x), .by = c(a, b)) + +# `duckplyr_reframe()` doesn't message about regrouping when >1 rows are returned per group + + Code + out <- duckplyr_reframe(df, x = vec_rep_each(x, x), .by = g) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-relocate.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-relocate.md new file mode 100644 index 000000000..f986cd438 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-relocate.md @@ -0,0 +1,8 @@ +# can only supply one of .before and .after + + Code + duckplyr_relocate(df, .before = 1, .after = 1) + Condition + Error in `relocate()`: + ! Can't supply both `.before` and `.after`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rename.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rename.md new file mode 100644 index 000000000..f5e62f223 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rename.md @@ -0,0 +1,24 @@ +# `.fn` result type is checked (#6561) + + Code + duckplyr_rename_with(df, fn) + Condition + Error in `rename_with()`: + ! `.fn` must return a character vector, not an integer. + +# `.fn` result size is checked (#6561) + + Code + duckplyr_rename_with(df, fn) + Condition + Error in `rename_with()`: + ! `.fn` must return a vector of length 2, not 3. + +# can't rename in `.cols` + + Code + duckplyr_rename_with(df, toupper, .cols = c(y = x)) + Condition + Error in `rename_with()`: + ! Can't rename variables in this context. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rows.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rows.md new file mode 100644 index 000000000..6748aeb3b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rows.md @@ -0,0 +1,347 @@ +# duckplyr_rows_insert() doesn't allow insertion of matched keys by default + + Code + (expect_error(duckplyr_rows_insert(x, y, by = "a"))) + Output + + Error in `rows_insert()`: + ! `y` can't contain keys that already exist in `x`. + i The following rows in `y` have keys that already exist in `x`: `c(1)`. + i Use `conflict = "ignore"` if you want to ignore these `y` rows. + +--- + + Code + (expect_error(duckplyr_rows_insert(x, y, by = "a"))) + Output + + Error in `rows_insert()`: + ! `y` can't contain keys that already exist in `x`. + i The following rows in `y` have keys that already exist in `x`: `c(1, 2, 3)`. + i Use `conflict = "ignore"` if you want to ignore these `y` rows. + +# duckplyr_rows_insert() casts keys to the type of `x` + + Code + (expect_error(duckplyr_rows_insert(x, y, "key"))) + Output + + Error in `rows_insert()`: + ! Can't convert from `y$key` to `x$key` due to loss of precision. + * Locations: 1 + +# duckplyr_rows_insert() casts values to the type of `x` + + Code + (expect_error(duckplyr_rows_insert(x, y, "key"))) + Output + + Error in `rows_insert()`: + ! Can't convert from `y$value` to `x$value` due to loss of precision. + * Locations: 1 + +# duckplyr_rows_insert() checks that `x` and `y` contain `by` (#6652) + + Code + (expect_error(duckplyr_rows_insert(x, y, by = "c"))) + Output + + Error in `rows_insert()`: + ! All columns specified through `by` must exist in `x` and `y`. + i The following columns are missing from `x`: `c`. + +--- + + Code + (expect_error(duckplyr_rows_insert(x, y, by = c("a", "b")))) + Output + + Error in `rows_insert()`: + ! All columns specified through `by` must exist in `x` and `y`. + i The following columns are missing from `y`: `b`. + +# `conflict` is validated + + Code + (expect_error(duckplyr_rows_insert(x, y, by = "a", conflict = "foo"))) + Output + + Error in `rows_insert()`: + ! `conflict` must be one of "error" or "ignore", not "foo". + Code + (expect_error(duckplyr_rows_insert(x, y, by = "a", conflict = 1))) + Output + + Error in `rows_insert()`: + ! `conflict` must be a string or character vector. + +# duckplyr_rows_append() casts to the type of `x` + + Code + (expect_error(duckplyr_rows_append(x, y))) + Output + + Error in `rows_append()`: + ! Can't convert from `y$key` to `x$key` due to loss of precision. + * Locations: 1 + +# duckplyr_rows_append() requires that `y` columns be a subset of `x` + + Code + (expect_error(duckplyr_rows_append(x, y))) + Output + + Error in `rows_append()`: + ! All columns in `y` must exist in `x`. + i The following columns only exist in `y`: `c`. + +# duckplyr_rows_update() requires `y` keys to exist in `x` by default + + Code + (expect_error(duckplyr_rows_update(x, y, "a"))) + Output + + Error in `rows_update()`: + ! `y` must contain keys that already exist in `x`. + i The following rows in `y` have keys that don't exist in `x`: `c(1, 3)`. + i Use `unmatched = "ignore"` if you want to ignore these `y` rows. + +# duckplyr_rows_update() doesn't allow `y` keys to be duplicated (#5553) + + Code + (expect_error(duckplyr_rows_update(x, y, by = "a"))) + Output + + Error in `rows_update()`: + ! `y` key values must be unique. + i The following rows contain duplicate key values: `c(1, 2)`. + +# duckplyr_rows_update() casts keys to their common type for matching but retains `x` type + + Code + (expect_error(duckplyr_rows_update(x, y, "key"))) + Output + + Error in `rows_update()`: + ! Can't combine `x$key` and `y$key` . + +# duckplyr_rows_update() casts values to the type of `x` + + Code + (expect_error(duckplyr_rows_update(x, y, "key"))) + Output + + Error in `rows_update()`: + ! Can't convert from `y$value` to `x$value` due to loss of precision. + * Locations: 1 + +# `unmatched` is validated + + Code + (expect_error(duckplyr_rows_update(x, y, by = "a", unmatched = "foo"))) + Output + + Error in `rows_update()`: + ! `unmatched` must be one of "error" or "ignore", not "foo". + Code + (expect_error(duckplyr_rows_update(x, y, by = "a", unmatched = 1))) + Output + + Error in `rows_update()`: + ! `unmatched` must be a string or character vector. + +# duckplyr_rows_patch() requires `y` keys to exist in `x` by default + + Code + (expect_error(duckplyr_rows_patch(x, y, "a"))) + Output + + Error in `rows_patch()`: + ! `y` must contain keys that already exist in `x`. + i The following rows in `y` have keys that don't exist in `x`: `c(1, 3)`. + i Use `unmatched = "ignore"` if you want to ignore these `y` rows. + +# duckplyr_rows_patch() doesn't allow `y` keys to be duplicated (#5553) + + Code + (expect_error(duckplyr_rows_patch(x, y, by = "a"))) + Output + + Error in `rows_patch()`: + ! `y` key values must be unique. + i The following rows contain duplicate key values: `c(1, 2)`. + +# duckplyr_rows_patch() casts keys to their common type for matching but retains `x` type + + Code + (expect_error(duckplyr_rows_patch(x, y, "key"))) + Output + + Error in `rows_patch()`: + ! Can't combine `x$key` and `y$key` . + +# duckplyr_rows_patch() casts values to the type of `x` + + Code + (expect_error(duckplyr_rows_patch(x, y, "key"))) + Output + + Error in `rows_patch()`: + ! Can't convert from `y$value` to `x$value` due to loss of precision. + * Locations: 1 + +# duckplyr_rows_upsert() doesn't allow `y` keys to be duplicated (#5553) + + Code + (expect_error(duckplyr_rows_upsert(x, y, by = "a"))) + Output + + Error in `rows_upsert()`: + ! `y` key values must be unique. + i The following rows contain duplicate key values: `c(1, 2)`. + +# duckplyr_rows_upsert() casts keys to their common type for matching but retains `x` type + + Code + (expect_error(duckplyr_rows_upsert(x, y, "key"))) + Output + + Error in `rows_upsert()`: + ! Can't combine `x$key` and `y$key` . + +# duckplyr_rows_upsert() casts keys to the type of `x` + + Code + (expect_error(duckplyr_rows_upsert(x, y, "key"))) + Output + + Error in `rows_upsert()`: + ! Can't convert from `y$key` to `x$key` due to loss of precision. + * Locations: 1 + +# duckplyr_rows_upsert() casts values to the type of `x` + + Code + (expect_error(duckplyr_rows_upsert(x, y, "key"))) + Output + + Error in `rows_upsert()`: + ! Can't convert from `y$value` to `x$value` due to loss of precision. + * Locations: 1 + +# duckplyr_rows_delete() ignores extra `y` columns, with a message + + Code + out <- duckplyr_rows_delete(x, y) + Message + Matching, by = "a" + Ignoring extra `y` columns: b + +--- + + Code + out <- duckplyr_rows_delete(x, y, by = "a") + Message + Ignoring extra `y` columns: b + +# duckplyr_rows_delete() requires `y` keys to exist in `x` by default + + Code + (expect_error(duckplyr_rows_delete(x, y, "a"))) + Output + + Error in `rows_delete()`: + ! `y` must contain keys that already exist in `x`. + i The following rows in `y` have keys that don't exist in `x`: `c(1, 3)`. + i Use `unmatched = "ignore"` if you want to ignore these `y` rows. + +# duckplyr_rows_delete() casts keys to their common type for matching but retains `x` type + + Code + (expect_error(duckplyr_rows_delete(x, y, "key"))) + Output + + Error in `rows_delete()`: + ! Can't combine `x$key` and `y$key` . + +# rows_check_x_contains_y() checks that `y` columns are in `x` + + Code + (expect_error(rows_check_x_contains_y(x, y))) + Output + + Error: + ! All columns in `y` must exist in `x`. + i The following columns only exist in `y`: `b`. + +# rows_check_by() checks that `y` has at least 1 column before using it (#6061) + + Code + (expect_error(rows_check_by(by = NULL, y = y))) + Output + + Error: + ! `y` must have at least one column. + +# rows_check_by() uses the first column from `y` by default, with a message + + Code + by <- rows_check_by(by = NULL, y = y) + Message + Matching, by = "a" + +# rows_check_by() validates `by` + + Code + (expect_error(rows_check_by(by = 1, y = y))) + Output + + Error: + ! `by` must be a character vector. + Code + (expect_error(rows_check_by(by = character(), y = y))) + Output + + Error: + ! `by` must specify at least 1 column. + Code + (expect_error(rows_check_by(by = c(x = "y"), y = y))) + Output + + Error: + ! `by` must be unnamed. + +# rows_check_contains_by() checks that all `by` columns are in `x` + + Code + (expect_error(rows_check_contains_by(x, "y", arg = "x"))) + Output + + Error: + ! All columns specified through `by` must exist in `x` and `y`. + i The following columns are missing from `x`: `y`. + Code + (expect_error(rows_check_contains_by(x, c("y", "x", "z"), arg = "y"))) + Output + + Error: + ! All columns specified through `by` must exist in `x` and `y`. + i The following columns are missing from `y`: `y` and `z`. + +# rows_check_unique() requires uniqueness + + Code + (expect_error(rows_check_unique(x["x"], "x"))) + Output + + Error: + ! `x` key values must be unique. + i The following rows contain duplicate key values: `c(1, 2, 3)`. + Code + (expect_error(rows_check_unique(x[c("x", "y")], "y"))) + Output + + Error: + ! `y` key values must be unique. + i The following rows contain duplicate key values: `c(1, 3)`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rowwise.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rowwise.md new file mode 100644 index 000000000..50fa6ade9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-rowwise.md @@ -0,0 +1,84 @@ +# rowwise has decent print method + + Code + rf + Output + # A tibble: 5 x 1 + # Rowwise: x + x + + 1 1 + 2 2 + 3 3 + 4 4 + 5 5 + +# validate_rowwise_df() gives useful errors + + Code + (expect_error(validate_rowwise_df(df1))) + Output + + Error in `validate_rowwise_df()`: + ! The `.rows` column must be a list of size 1, one-based integer vectors with the right value. + Code + (expect_error(validate_rowwise_df(df2))) + Output + + Error in `validate_rowwise_df()`: + ! The last column of the `groups` attribute must be called `.rows`. + Code + (expect_error(validate_rowwise_df(df3))) + Output + + Error in `validate_rowwise_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(validate_rowwise_df(df4))) + Output + + Error in `validate_rowwise_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(validate_rowwise_df(df7))) + Output + + Error in `validate_rowwise_df()`: + ! The `.rows` column must be a list of size 1, one-based integer vectors with the right value. + Code + (expect_error(attr(df8, "groups")$.rows <- 1:8)) + Output + + Error in `$<-`: + ! Assigned data `1:8` must be compatible with existing data. + x Existing data has 10 rows. + x Assigned data has 8 rows. + i Only vectors of size 1 are recycled. + Caused by error in `vectbl_recycle_rhs_rows()`: + ! Can't recycle input of size 8 to size 10. + Code + (expect_error(validate_rowwise_df(df10))) + Output + + Error in `validate_rowwise_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(validate_rowwise_df(df11))) + Output + + Error in `validate_rowwise_df()`: + ! The `groups` attribute must be a data frame. + Code + (expect_error(new_rowwise_df(tibble(x = 1:10), tibble(".rows" := list(1:5, -1L)))) + ) + Output + + Error in `new_rowwise_df()`: + ! `group_data` must be a tibble without a `.rows` column. + Code + (expect_error(new_rowwise_df(tibble(x = 1:10), 1:10))) + Output + + Error in `new_rowwise_df()`: + ! `group_data` must be a tibble without a `.rows` column. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sample.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sample.md new file mode 100644 index 000000000..356fa1ce4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sample.md @@ -0,0 +1,82 @@ +# sample_*() gives meaningful error messages + + Code + df2 <- tibble(x = rep(1:2, 100), y = rep(c(0, 1), 100), g = rep(1:2, each = 100)) + grp <- df2 %>% duckplyr_group_by(g) + (expect_error(sample_n(grp, nrow(df2) / 2, weight = y))) + Output + + Error in `sample_n()`: + ! Can't compute indices. + i In group 1: `g = 1`. + Caused by error in `sample.int()`: + ! too few positive probabilities + Code + (expect_error(sample_frac(grp, 1, weight = y))) + Output + + Error in `sample_frac()`: + ! Can't compute indices. + i In group 1: `g = 1`. + Caused by error in `sample.int()`: + ! too few positive probabilities + Code + (expect_error(mtcars %>% duckplyr_group_by(cyl) %>% sample_n(10))) + Output + + Error in `sample_n()`: + ! Can't compute indices. + i In group 2: `cyl = 6`. + Caused by error: + ! `size` must be less than or equal to 7 (size of data). + i set `replace = TRUE` to use sampling with replacement. + Code + (expect_error(sample_n(list()))) + Output + + Error in `sample_n()`: + ! `tbl` must be a data frame, not an empty list. + Code + (expect_error(sample_frac(list()))) + Output + + Error in `sample_frac()`: + ! `tbl` must be a data frame, not an empty list. + Code + # # respects weight + df <- data.frame(x = 1:2, y = c(0, 1)) + (expect_error(sample_n(df, 2, weight = y))) + Output + + Error in `sample_n()`: + ! Can't compute indices. + Caused by error in `sample.int()`: + ! too few positive probabilities + Code + (expect_error(sample_frac(df, 2))) + Output + + Error in `sample_frac()`: + ! Can't compute indices. + Caused by error: + ! `size` of sampled fraction must be less or equal to one. + i set `replace = TRUE` to use sampling with replacement. + Code + (expect_error(sample_frac(df %>% duckplyr_group_by(y), 2))) + Output + + Error in `sample_frac()`: + ! Can't compute indices. + i In group 1: `y = 0`. + Caused by error: + ! `size` of sampled fraction must be less or equal to one. + i set `replace = TRUE` to use sampling with replacement. + Code + (expect_error(sample_frac(df, 1, weight = y))) + Output + + Error in `sample_frac()`: + ! Can't compute indices. + Caused by error in `sample.int()`: + ! too few positive probabilities + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-select.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-select.md new file mode 100644 index 000000000..2742e625f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-select.md @@ -0,0 +1,11 @@ +# duckplyr_select() provides informative errors + + Code + (expect_error(duckplyr_select(mtcars, 1 + ""))) + Output + + Error in `select()`: + i In argument: `1 + ""`. + Caused by error in `1 + ""`: + ! non-numeric argument to binary operator + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sets.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sets.md new file mode 100644 index 000000000..450069e4e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-sets.md @@ -0,0 +1,127 @@ +# extra arguments in ... error (#5891) + + Code + duckplyr_intersect(df1, df2, z = 3) + Condition + Error in `intersect()`: + ! `...` must be empty. + x Problematic argument: + * z = 3 + Code + duckplyr_union(df1, df2, z = 3) + Condition + Error in `union()`: + ! `...` must be empty. + x Problematic argument: + * z = 3 + Code + duckplyr_union_all(df1, df2, z = 3) + Condition + Error in `union_all()`: + ! `...` must be empty. + x Problematic argument: + * z = 3 + Code + duckplyr_setdiff(df1, df2, z = 3) + Condition + Error in `setdiff()`: + ! `...` must be empty. + x Problematic argument: + * z = 3 + Code + duckplyr_symdiff(df1, df2, z = 3) + Condition + Error in `symdiff()`: + ! `...` must be empty. + x Problematic argument: + * z = 3 + +# incompatible data frames error (#903) + + Code + duckplyr_intersect(df1, df2) + Condition + Error in `intersect()`: + ! `x` and `y` are not compatible. + x Different number of columns: 1 vs 2. + Code + duckplyr_union(df1, df2) + Condition + Error in `union_all()`: + ! `x` and `y` are not compatible. + x Different number of columns: 1 vs 2. + Code + duckplyr_union_all(df1, df2) + Condition + Error in `union_all()`: + ! `x` and `y` are not compatible. + x Different number of columns: 1 vs 2. + Code + duckplyr_setdiff(df1, df2) + Condition + Error in `setdiff()`: + ! `x` and `y` are not compatible. + x Different number of columns: 1 vs 2. + Code + duckplyr_symdiff(df1, df2) + Condition + Error in `symdiff()`: + ! `x` and `y` are not compatible. + x Different number of columns: 1 vs 2. + +# is_compatible generates useful messages for different cases + + Code + cat(is_compatible(tibble(x = 1), 1)) + Output + `y` must be a data frame. + Code + cat(is_compatible(tibble(x = 1), tibble(x = 1, y = 2))) + Output + Different number of columns: 1 vs 2. + Code + cat(is_compatible(tibble(x = 1, y = 1), tibble(y = 1, x = 1), ignore_col_order = FALSE)) + Output + Same column names, but different order. + Code + cat(is_compatible(tibble(x = 1), tibble(y = 1))) + Output + Cols in `y` but not `x`: `y`. Cols in `x` but not `y`: `x`. + Code + cat(is_compatible(tibble(x = 1), tibble(x = 1L), convert = FALSE)) + Output + Different types for column `x`: double vs integer. + Code + cat(is_compatible(tibble(x = 1), tibble(x = "a"))) + Output + Incompatible types for column `x`: double vs character. + +# setequal tibbles must have same rows and columns + + Code + duckplyr_setequal(tibble(x = 1:2), tibble(y = 1:2)) + Condition + Error in `setequal()`: + ! `x` and `y` are not compatible. + x Cols in `y` but not `x`: `y`. + x Cols in `x` but not `y`: `x`. + +--- + + Code + duckplyr_setequal(tibble(x = 1:2), tibble(x = c("a", "b"))) + Condition + Error in `setequal()`: + ! `x` and `y` are not compatible. + x Incompatible types for column `x`: integer vs character. + +# setequal checks for extra arguments + + Code + duckplyr_setequal(mtcars, mtcars, z = 2) + Condition + Error in `setequal()`: + ! `...` must be empty. + x Problematic argument: + * z = 2 + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-slice.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-slice.md new file mode 100644 index 000000000..dff054e92 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-slice.md @@ -0,0 +1,380 @@ +# slice errors if positive and negative indices mixed + + Code + duckplyr_slice(tibble(), 1, -1) + Condition + Error in `slice()`: + ! Can't compute indices. + Caused by error: + ! Can't subset elements with `1`. + x Negative and positive locations can't be mixed. + i Subscript `1` has a positive value at location 1. + +# slicing with one-column matrix is deprecated + + Code + out <- duckplyr_slice(df, matrix(c(1, 3))) + Condition + Warning: + Slicing with a 1-column matrix was deprecated in dplyr 1.1.0. + +# slice errors if index is not numeric + + Code + duckplyr_slice(tibble(), "a") + Condition + Error in `slice()`: + ! Can't process indices. + Caused by error: + ! Can't subset elements with `"a"`. + x `"a"` must be numeric, not the string "a". + +# `...` can't be named (#6554) + + Code + duckplyr_slice(df, 1, foo = g) + Condition + Error in `slice()`: + ! Arguments in `...` must be passed by position, not name. + x Problematic argument: + * foo = g + +# can't use `.by` with `.preserve` + + Code + duckplyr_slice(df, .by = x, .preserve = TRUE) + Condition + Error in `slice()`: + ! Can't supply both `.by` and `.preserve`. + +# catches `by` typo (#6647) + + Code + duckplyr_slice(df, by = x) + Condition + Error in `slice()`: + ! Can't specify an argument named `by` in this verb. + i Did you mean to use `.by` instead? + +# slice_helpers() call get_slice_size() + + Code + duckplyr_slice_head(df, n = "a") + Condition + Error in `slice_head()`: + ! `n` must be a round number, not the string "a". + Code + duckplyr_slice_tail(df, n = "a") + Condition + Error in `slice_tail()`: + ! `n` must be a round number, not the string "a". + Code + slice_min(df, x, n = "a") + Condition + Error in `slice_min()`: + ! `n` must be a round number, not the string "a". + Code + slice_max(df, x, n = "a") + Condition + Error in `slice_max()`: + ! `n` must be a round number, not the string "a". + Code + duckplyr_slice_sample(df, n = "a") + Condition + Error in `slice_sample()`: + ! `n` must be a round number, not the string "a". + +# get_slice_size() validates its inputs + + Code + get_slice_size(n = 1, prop = 1) + Condition + Error: + ! Must supply `n` or `prop`, but not both. + Code + get_slice_size(n = "a") + Condition + Error: + ! `n` must be a round number, not the string "a". + Code + get_slice_size(prop = "a") + Condition + Error: + ! `prop` must be a number, not the string "a". + +# get_slice_size() snapshots + + Code + body(get_slice_size(prop = 0)) + Output + clamp(0, floor(0 * n), n) + Code + body(get_slice_size(prop = 0.4)) + Output + clamp(0, floor(0.4 * n), n) + Code + body(get_slice_size(prop = 2)) + Output + clamp(0, floor(2 * n), n) + Code + body(get_slice_size(prop = 2, allow_outsize = TRUE)) + Output + floor(2 * n) + Code + body(get_slice_size(prop = -0.4)) + Output + clamp(0, ceiling(n + -0.4 * n), n) + Code + body(get_slice_size(prop = -2)) + Output + clamp(0, ceiling(n + -2 * n), n) + Code + body(get_slice_size(n = 0)) + Output + clamp(0, 0, n) + Code + body(get_slice_size(n = 4)) + Output + clamp(0, 4, n) + Code + body(get_slice_size(n = 20)) + Output + clamp(0, 20, n) + Code + body(get_slice_size(n = 20, allow_outsize = TRUE)) + Output + [1] 20 + Code + body(get_slice_size(n = -4)) + Output + clamp(0, ceiling(n + -4), n) + Code + body(get_slice_size(n = -20)) + Output + clamp(0, ceiling(n + -20), n) + +# n must be an integer + + Code + duckplyr_slice_head(df, n = 1.1) + Condition + Error in `slice_head()`: + ! `n` must be a round number, not the number 1.1. + +# slice_*() checks that `n=` is explicitly named and ... is empty + + Code + duckplyr_slice_head(df, 5) + Condition + Error in `slice_head()`: + ! `n` must be explicitly named. + i Did you mean `slice_head(n = 5)`? + Code + duckplyr_slice_tail(df, 5) + Condition + Error in `slice_tail()`: + ! `n` must be explicitly named. + i Did you mean `slice_tail(n = 5)`? + Code + slice_min(df, x, 5) + Condition + Error in `slice_min()`: + ! `n` must be explicitly named. + i Did you mean `slice_min(n = 5)`? + Code + slice_max(df, x, 5) + Condition + Error in `slice_max()`: + ! `n` must be explicitly named. + i Did you mean `slice_max(n = 5)`? + Code + duckplyr_slice_sample(df, 5) + Condition + Error in `slice_sample()`: + ! `n` must be explicitly named. + i Did you mean `slice_sample(n = 5)`? + +--- + + Code + dplyr::duckplyr_slice_head(df, 5) + Condition + Error: + ! 'duckplyr_slice_head' is not an exported object from 'namespace:dplyr' + Code + dplyr::duckplyr_slice_tail(df, 5) + Condition + Error: + ! 'duckplyr_slice_tail' is not an exported object from 'namespace:dplyr' + Code + dplyr::slice_min(df, x, 5) + Condition + Error in `dplyr::slice_min()`: + ! `n` must be explicitly named. + i Did you mean `dplyr::slice_min(n = 5)`? + Code + dplyr::slice_max(df, x, 5) + Condition + Error in `dplyr::slice_max()`: + ! `n` must be explicitly named. + i Did you mean `dplyr::slice_max(n = 5)`? + Code + dplyr::duckplyr_slice_sample(df, 5) + Condition + Error: + ! 'duckplyr_slice_sample' is not an exported object from 'namespace:dplyr' + +--- + + Code + duckplyr_slice_head(df, 5, 2) + Condition + Error in `slice_head()`: + ! `...` must be empty. + x Problematic arguments: + * ..1 = 5 + * ..2 = 2 + i Did you forget to name an argument? + Code + duckplyr_slice_tail(df, 5, 2) + Condition + Error in `slice_tail()`: + ! `...` must be empty. + x Problematic arguments: + * ..1 = 5 + * ..2 = 2 + i Did you forget to name an argument? + Code + slice_min(df, x, 5, 2) + Condition + Error in `slice_min()`: + ! `...` must be empty. + x Problematic arguments: + * ..1 = 5 + * ..2 = 2 + i Did you forget to name an argument? + Code + slice_max(df, x, 5, 2) + Condition + Error in `slice_max()`: + ! `...` must be empty. + x Problematic arguments: + * ..1 = 5 + * ..2 = 2 + i Did you forget to name an argument? + Code + duckplyr_slice_sample(df, 5, 2) + Condition + Error in `slice_sample()`: + ! `...` must be empty. + x Problematic arguments: + * ..1 = 5 + * ..2 = 2 + i Did you forget to name an argument? + +# slice_helper catches `.by` typo (#6647) + + Code + duckplyr_slice_head(df, n = 1, .by = x) + Condition + Error in `slice_head()`: + ! Can't specify an argument named `.by` in this verb. + i Did you mean to use `by` instead? + Code + duckplyr_slice_tail(df, n = 1, .by = x) + Condition + Error in `slice_tail()`: + ! Can't specify an argument named `.by` in this verb. + i Did you mean to use `by` instead? + Code + slice_min(df, order_by = x, .by = x) + Condition + Error in `slice_min()`: + ! Can't specify an argument named `.by` in this verb. + i Did you mean to use `by` instead? + Code + slice_max(df, order_by = x, .by = x) + Condition + Error in `slice_max()`: + ! Can't specify an argument named `.by` in this verb. + i Did you mean to use `by` instead? + Code + duckplyr_slice_sample(df, n = 1, .by = x) + Condition + Error in `slice_sample()`: + ! Can't specify an argument named `.by` in this verb. + i Did you mean to use `by` instead? + +# slice_min/max() check size of `order_by=` (#5922) + + Code + slice_min(data.frame(x = 1:10), 1:6) + Condition + Error in `slice_min()`: + ! Can't compute indices. + Caused by error: + ! `order_by` must have size 10, not size 6. + Code + slice_max(data.frame(x = 1:10), 1:6) + Condition + Error in `slice_max()`: + ! Can't compute indices. + Caused by error: + ! `order_by` must have size 10, not size 6. + +# slice_min/max() validate simple arguments + + Code + slice_min(data.frame(x = 1:10)) + Condition + Error in `slice_min()`: + ! `order_by` is absent but must be supplied. + Code + slice_max(data.frame(x = 1:10)) + Condition + Error in `slice_max()`: + ! `order_by` is absent but must be supplied. + Code + slice_min(data.frame(x = 1:10), x, with_ties = 1) + Condition + Error in `slice_min()`: + ! `with_ties` must be `TRUE` or `FALSE`, not the number 1. + Code + slice_max(data.frame(x = 1:10), x, with_ties = 1) + Condition + Error in `slice_max()`: + ! `with_ties` must be `TRUE` or `FALSE`, not the number 1. + Code + slice_min(data.frame(x = 1:10), x, na_rm = 1) + Condition + Error in `slice_min()`: + ! `na_rm` must be `TRUE` or `FALSE`, not the number 1. + Code + slice_max(data.frame(x = 1:10), x, na_rm = 1) + Condition + Error in `slice_max()`: + ! `na_rm` must be `TRUE` or `FALSE`, not the number 1. + +# duckplyr_slice_sample() checks size of `weight_by=` (#5922) + + Code + duckplyr_slice_sample(df, n = 2, weight_by = 1:6) + Condition + Error in `slice()`: + ! Can't compute indices. + Caused by error: + ! `weight_by` must have size 10, not size 6. + +# `duckplyr_slice_sample()` validates `replace` + + Code + duckplyr_slice_sample(df, replace = 1) + Condition + Error in `slice_sample()`: + ! `replace` must be `TRUE` or `FALSE`, not the number 1. + Code + duckplyr_slice_sample(df, replace = NA) + Condition + Error in `slice_sample()`: + ! `replace` must be `TRUE` or `FALSE`, not `NA`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-summarise.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-summarise.md new file mode 100644 index 000000000..5b58bc652 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-summarise.md @@ -0,0 +1,55 @@ +# can't overwrite column active bindings (#6666) + + Code + duckplyr_summarise(df, y = { + x <<- x + 2L + mean(x) + }) + Condition + Error in `summarise()`: + i In argument: `y = { ... }`. + Caused by error: + ! unused argument (base::quote(3:6)) + +--- + + Code + duckplyr_summarise(df, .by = g, y = { + x <<- x + 2L + mean(x) + }) + Condition + Error in `summarise()`: + i In argument: `y = { ... }`. + i In group 1: `g = 1`. + Caused by error: + ! unused argument (base::quote(3:4)) + +# can't use `.by` with `.groups` + + Code + duckplyr_summarise(df, .by = x, .groups = "drop") + Condition + Error in `summarise()`: + ! Can't supply both `.by` and `.groups`. + +# non-summary results are deprecated in favor of `duckplyr_reframe()` (#6382) + + Code + out <- duckplyr_summarise(df, x = which(x < 3)) + Condition + Warning: + Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0. + i Please use `reframe()` instead. + i When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly. + +--- + + Code + out <- duckplyr_summarise(df, x = which(x < 3), .by = g) + Condition + Warning: + Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0. + i Please use `reframe()` instead. + i When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-transmute.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-transmute.md new file mode 100644 index 000000000..c0e0f4c7b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/dplyr-transmute.md @@ -0,0 +1,21 @@ +# duckplyr_transmute() error messages + + Code + (expect_error(duckplyr_transmute(mtcars, cyl2 = cyl, .keep = "all"))) + Output + + Error in `transmute()`: + ! The `.keep` argument is not supported. + Code + (expect_error(duckplyr_transmute(mtcars, cyl2 = cyl, .before = disp))) + Output + + Error in `transmute()`: + ! The `.before` argument is not supported. + Code + (expect_error(duckplyr_transmute(mtcars, cyl2 = cyl, .after = disp))) + Output + + Error in `transmute()`: + ! The `.after` argument is not supported. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr-across.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr-across.md new file mode 100644 index 000000000..d6115e16a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr-across.md @@ -0,0 +1,87 @@ +# duckplyr_expand_across() successful + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, mean)) + Output + tibble(x = base::mean(x), y = base::mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, function(x) mean(x))) + Output + tibble(x = mean(x), y = mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(c(x_mean = x, y_mean = y), mean)) + Output + tibble(x_mean = base::mean(x), y_mean = base::mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(c(x_mean = x, y_mean = y), mean, + .names = "{.col}_{.fn}")) + Output + tibble(x_mean_1 = base::mean(x), y_mean_1 = base::mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, function(x) mean(x, na.rm = TRUE))) + Output + tibble(x = mean(x, na.rm = TRUE), y = mean(y, na.rm = TRUE)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y", "a"), across(c(a, x), function(x) x + 1)) + Output + tibble(a = a + 1, x = x + 1) + +--- + + Code + test_duckplyr_expand_across(c("x", "y", "a"), across(c(a, x), function(x) x * + 2 + 1)) + Output + tibble(a = a * 2 + 1, x = x * 2 + 1) + +--- + + Code + test_duckplyr_expand_across(c("x", "y", "a"), across(-a, function(x) x * x)) + Output + tibble(x = x * x, y = y * y) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, base::mean)) + Output + tibble(x = base::mean(x), y = base::mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, list(mean))) + Output + tibble(x_1 = base::mean(x), y_1 = base::mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, list(mean = mean))) + Output + tibble(x_mean = base::mean(x), y_mean = base::mean(y)) + +--- + + Code + test_duckplyr_expand_across(c("x", "y"), across(x:y, list(mean = mean, median = median))) + Output + tibble(x_mean = base::mean(x), x_median = stats::median(x), y_mean = base::mean(y), + y_median = stats::median(y)) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr.md new file mode 100644 index 000000000..f6b2d9584 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/duckplyr.md @@ -0,0 +1,8 @@ +# collect() works + + Code + print(z) + Output + a + 1 1 + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/ducktbl.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/ducktbl.md new file mode 100644 index 000000000..a35a49e04 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/ducktbl.md @@ -0,0 +1,29 @@ +# as_duckdb_tibble() and grouped df + + Code + as_duckdb_tibble(dplyr::group_by(mtcars, cyl)) + Condition + Error in `as_duckdb_tibble()`: + ! duckplyr does not support `group_by()`. + i Use `.by` instead. + i To proceed with dplyr, use `as_tibble()` or `as.data.frame()`. + +# as_duckdb_tibble() and rowwise df + + Code + as_duckdb_tibble(dplyr::rowwise(mtcars)) + Condition + Error in `as_duckdb_tibble()`: + ! duckplyr does not support `rowwise()`. + i To proceed with dplyr, use `as_tibble()` or `as.data.frame()`. + +# as_duckdb_tibble() and readr data + + Code + as_duckdb_tibble(readr::read_csv(path, show_col_types = FALSE)) + Condition + Error in `as_duckdb_tibble()`: + ! The input is data read by readr, and duckplyr supports reading CSV files directly. + i Use `read_csv_duckdb()` to read with the built-in reader. + i To proceed with the data as read by readr, use `as_tibble()` before `as_duckdb_tibble()`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/expr.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/expr.md new file mode 100644 index 000000000..ad9969355 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/expr.md @@ -0,0 +1,36 @@ +# can construct expressions + + Code + relexpr_reference("column") + Output + List of 3 + $ name : chr "column" + $ rel : NULL + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + Code + relexpr_constant(42) + Output + List of 2 + $ val : num 42 + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_constant" "relational_relexpr" + Code + relexpr_function("+", list(relexpr_reference("column"), relexpr_constant(42, + alias = "fortytwo"))) + Output + List of 3 + $ name : chr "+" + $ args :List of 2 + ..$ :List of 3 + .. ..$ name : chr "column" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ :List of 2 + .. ..$ val : num 42 + .. ..$ alias: chr "fortytwo" + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_constant" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback.md new file mode 100644 index 000000000..1b4b53bb9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback.md @@ -0,0 +1,261 @@ +# fallback_sitrep() default + + Code + fallback_sitrep() + Message + The duckplyr package is configured to fall back to dplyr when it encounters an incompatibility. Fallback events can be collected and uploaded for analysis to guide future development. By default, data will be collected but no data will be uploaded. + x Fallback printing is disabled. + v Fallback logging is enabled. + i Fallback logging is not controlled, see `?duckplyr::fallback()`. + i Logs are written to 'fallback/log/dir'. + i Automatic fallback uploading is not controlled and therefore disabled, see `?duckplyr::fallback()`. + i No reports ready for upload. + i See `?duckplyr::fallback_config()` for details. + +# fallback_sitrep() enabled + + Code + fallback_sitrep() + Message + The duckplyr package is configured to fall back to dplyr when it encounters an incompatibility. Fallback events can be collected and uploaded for analysis to guide future development. By default, data will be collected but no data will be uploaded. + x Fallback printing is disabled. + v Fallback logging is enabled. + i Logs are written to 'fallback/log/dir'. + v Automatic fallback uploading is enabled. + v Number of reports ready for upload: 3. + > Review with `duckplyr::fallback_review()`, upload with `duckplyr::fallback_upload()`. + i See `?duckplyr::fallback_config()` for details. + +# fallback_sitrep() enabled silent + + Code + fallback_sitrep() + Message + The duckplyr package is configured to fall back to dplyr when it encounters an incompatibility. Fallback events can be collected and uploaded for analysis to guide future development. By default, data will be collected but no data will be uploaded. + v Fallback printing is enabled. + v Fallback logging is enabled. + i Logs are written to 'fallback/log/dir'. + v Automatic fallback uploading is enabled. + v Number of reports ready for upload: 3. + > Review with `duckplyr::fallback_review()`, upload with `duckplyr::fallback_upload()`. + i See `?duckplyr::fallback_config()` for details. + +# fallback_sitrep() disabled + + Code + fallback_sitrep() + Message + The duckplyr package is configured to fall back to dplyr when it encounters an incompatibility. Fallback events can be collected and uploaded for analysis to guide future development. By default, data will be collected but no data will be uploaded. + x Fallback printing is disabled. + x Fallback logging is disabled. + x Automatic fallback uploading is disabled. + i See `?duckplyr::fallback_config()` for details. + +# summarize() + + Code + duckdb_tibble(a = 1, b = 2, c = 3) %>% summarize(.by = a, e = sum(b), f = sum(e)) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"Can't reuse summary variable `...4`.","name":"summarise","x":{"...1":"numeric","...2":"numeric","...3":"numeric"},"args":{"dots":{"...4":"sum(...2)","...5":"sum(...4)"},"by":["...1"]}} + Output + # A duckplyr data frame: 3 variables + a e f + + 1 1 2 2 + +# wday() + + Code + duckdb_tibble(a = as.Date("2024-03-08")) %>% mutate(b = lubridate::wday(a, + label = TRUE)) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"wday(label = ) not supported","name":"mutate","x":{"...1":"Date"},"args":{"dots":{"...2":"...3::...4(...1, label = TRUE)"},".by":"NULL",".keep":["all","used","unused","none"]}} + Output + # A duckplyr data frame: 2 variables + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"Can't convert columns of class to relational. Affected column: `...2`.","name":"head","x":{"...1":"Date","...2":"ordered/factor"},"args":{"n":21}} + Output + a b + + 1 2024-03-08 Fri + +--- + + Code + duckdb_tibble(a = as.Date("2024-03-08")) %>% mutate(b = lubridate::wday(a)) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"`wday()` with `option(\"lubridate.week.start\")` not supported","name":"mutate","x":{"...1":"Date"},"args":{"dots":{"...2":"...3::...4(...1)"},".by":"NULL",".keep":["all","used","unused","none"]}} + Output + # A duckplyr data frame: 2 variables + a b + + 1 2024-03-08 5 + +# strftime() + + Code + duckdb_tibble(a = as.Date("2024-03-08")) %>% mutate(b = strftime(a, format = "%Y-%m-%d", + tz = "CET")) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"strftime(tz = ) not supported","name":"mutate","x":{"...1":"Date"},"args":{"dots":{"...2":"strftime(...1, format = \"\", tz = \"\")"},".by":"NULL",".keep":["all","used","unused","none"]}} + Output + # A duckplyr data frame: 2 variables + a b + + 1 2024-03-08 2024-03-08 + +# $ + + Code + duckdb_tibble(a = 1, b = 2) %>% mutate(c = .env$x) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"object not found, should also be triggered by the dplyr fallback","name":"mutate","x":{"...1":"numeric","...2":"numeric"},"args":{"dots":{"...3":"...4$...5"},".by":"NULL",".keep":["all","used","unused","none"]}} + Condition + Error in `mutate()`: + i In argument: `c = .env$x`. + Caused by error: + ! object 'x' not found + +# unknown function + + Code + duckdb_tibble(a = 1, b = 2) %>% mutate(c = foo(a, b)) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"Can't translate function `foo()`.","name":"mutate","x":{"...1":"numeric","...2":"numeric"},"args":{"dots":{"...3":"foo(...1, ...2)"},".by":"NULL",".keep":["all","used","unused","none"]}} + Output + # A duckplyr data frame: 3 variables + a b c + + 1 1 2 3 + +# row names + + Code + mtcars[1:2, ] %>% as_duckdb_tibble() %>% select(mpg, cyl) + Output + # A duckplyr data frame: 2 variables + mpg cyl + + 1 21 6 + 2 21 6 + +# named column + + Code + duckdb_tibble(a = c(x = 1)) + Condition + Error in `duckdb_tibble()`: + ! Can't convert named vectors to relational. Affected column: `a`. + +--- + + Code + duckdb_tibble(a = matrix(1:4, ncol = 2)) + Condition + Error in `duckdb_tibble()`: + ! Can't convert arrays or matrices to relational. Affected column: `a`. + +# list column + + Code + duckdb_tibble(a = 1, b = 2, c = list(3)) + Condition + Error in `duckdb_tibble()`: + ! Can't convert columns of class to relational. Affected column: `c`. + +# __row_number + + Code + duckdb_tibble(`___row_number` = 1, b = 2:3) %>% arrange(b) + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"Can't use column `...1` already present in rel for order preservation","name":"arrange","x":{"...1":"numeric","...2":"integer"},"args":{"dots":["...2"],".by_group":false}} + Output + # A duckplyr data frame: 2 variables + `___row_number` b + + 1 1 2 + 2 1 3 + +# rel_try() + + Code + duckdb_tibble(a = 1) %>% count(a, .drop = FALSE, name = "n") + Message + i dplyr fallback recorded + {"version":"0.3.1","message":"{.code count()} only implemented for {.arg .drop} = {.value TRUE}","name":"count","x":{"...1":"numeric"},"args":{"dots":{"1":"...1"},"wt":"NULL","sort":false,".drop":false}} + i dplyr fallback recorded + {"version":"0.3.1","message":"Try {.code summarise(.by = ...)} or {.code mutate(.by = ...)} instead of {.code group_by()} and {.code ungroup()}."} + Output + # A duckplyr data frame: 2 variables + a n + + 1 1 1 + +# fallback_config() + + Code + # No conflicts + fallback_config_load() + +--- + + Code + # Reset and set config + fallback_config(reset_all = TRUE, logging = FALSE) + Message + i Restart the R session to reset all values to their defaults. + +--- + + Code + # Conflicts with environment variable and setting + fallback_config_load() + Message + Some configuration values are set as environment variables and in the configuration file 'fallback.dcf': + * `logging` + i Use `duckplyr::fallback_config(reset_all = TRUE)` to reset the configuration. + i Use `usethis::edit_r_environ()` to edit '~/.Renviron'. + +--- + + Code + # Reset config + fallback_config(reset_all = TRUE) + Message + i Restart the R session to reset all values to their defaults. + +--- + + Code + fallback_config(boo = FALSE) + Condition + Error in `fallback_config()`: + ! `...` must be empty. + x Problematic argument: + * boo = FALSE + +# fallback_config() failure + + Code + fallback_config_load() + Message + Error reading duckplyr, fallback configuration, deleting file. + Caused by error in `read.dcf()`: + ! Invalid DCF format. + Regular lines must have a tag. + Offending lines start with: + boo + +--- + + Code + fallback_config_load() + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback-2.dcf b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback-2.dcf new file mode 100644 index 000000000..1365c886a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback-2.dcf @@ -0,0 +1 @@ +logging: 0 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback.dcf b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback.dcf new file mode 100644 index 000000000..b780640f0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/fallback/fallback.dcf @@ -0,0 +1,5 @@ +info: TRUE +logging: 1 +autoupload: 1 +log_dir: / +verbose: TRUE diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/handle_desc.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/handle_desc.md new file mode 100644 index 000000000..67d4583b8 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/handle_desc.md @@ -0,0 +1,24 @@ +# desc() fails if it points elsewhere + + Code + duckdb_tibble(a = 1:3, .prudence = "stingy") %>% arrange(desc(a)) + Condition + Error in `arrange()`: + ! This operation cannot be carried out by DuckDB, and the input is a stingy duckplyr frame. + i Use `compute(prudence = "lavish")` to materialize to temporary storage and continue with duckplyr. + i See `vignette("prudence")` for other options. + Caused by error in `arrange()`: + ! Function `desc()` does not map to `dplyr::desc()`. + +# desc() fails for more than one argument + + Code + duckdb_tibble(a = 1:3, b = 4:6, .prudence = "stingy") %>% arrange(desc(a, b)) + Condition + Error in `arrange()`: + ! This operation cannot be carried out by DuckDB, and the input is a stingy duckplyr frame. + i Use `compute(prudence = "lavish")` to materialize to temporary storage and continue with duckplyr. + i See `vignette("prudence")` for other options. + Caused by error in `arrange()`: + ! Function `desc()` must be called with exactly one argument. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/n_distinct.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/n_distinct.md new file mode 100644 index 000000000..4bade7c30 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/n_distinct.md @@ -0,0 +1,24 @@ +# duckdb n_distinct() error with more than one argument + + Code + df %>% summarise(dummy = n_distinct(a, b)) + Condition + Error in `summarise()`: + ! `n_distinct()` needs exactly one argument besides the optional `na.rm` + +# duckdb n_distinct() error with na.rm not being TRUE/FALSE + + Code + df %>% summarise(dummy = n_distinct(a, na.rm = "b")) + Condition + Error in `summarise()`: + ! Invalid value for `na.rm` in call to `n_distinct()` + +# duckdb n_distinct() error with mutate + + Code + df %>% mutate(dummy = n_distinct(a)) + Condition + Error in `mutate()`: + ! `n_distinct()` not supported in window functions + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/overwrite.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/overwrite.md new file mode 100644 index 000000000..a07442b4f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/overwrite.md @@ -0,0 +1,15 @@ +# methods_overwrite() works + + Code + methods_overwrite() + Message + v Overwriting dplyr methods with duckplyr methods. + i Turn off with `duckplyr::methods_restore()`. + +--- + + Code + methods_restore() + Message + i Restoring dplyr methods. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-duckdb.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-duckdb.md new file mode 100644 index 000000000..62bb68a10 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-duckdb.md @@ -0,0 +1,80 @@ +# duckdb_rel_from_df() + + Code + data.frame(a = vctrs::new_vctr(1:3)) %>% duckdb_rel_from_df() + Condition + Error: + ! Can't convert columns of class to relational. Affected column: `a`. + +# duckdb_rel_from_df() error call + + Code + as_duckdb_tibble(data.frame(a = factor(letters))) + Condition + Error in `as_duckdb_tibble()`: + ! Can't convert columns of class to relational. Affected column: `a`. + +# rel_aggregate() + + Code + grouped %>% rel_to_df(prudence = "lavish") %>% arrange(species) + Output + # A duckplyr data frame: 2 variables + species mean_bill_length_mm + + 1 Adelie 38.8 + 2 Chinstrap 48.8 + 3 Gentoo 47.5 + Code + ungrouped %>% rel_to_df(prudence = "lavish") + Output + # A duckplyr data frame: 1 variable + mean_bill_length_mm + + 1 43.9 + +# duckdb_rel_from_df() uses materialized results + + Code + duckdb_rel_from_df(df) + Message + DuckDB Relation: + --------------------- + --- Relation Tree --- + --------------------- + Projection [a as a] + Order [___row_number ASC] + Filter [(a = 1.0)] + Projection [a as a, row_number() OVER () as ___row_number] + r_dataframe_scan(0xdeadbeef) + + --------------------- + -- Result Columns -- + --------------------- + - a (DOUBLE) + + +--- + + Code + nrow(df) + Output + [1] 1 + +--- + + Code + duckdb_rel_from_df(df) + Message + DuckDB Relation: + --------------------- + --- Relation Tree --- + --------------------- + r_dataframe_scan(0xdeadbeef) + + --------------------- + -- Result Columns -- + --------------------- + - a (DOUBLE) + + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-rel.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-rel.md new file mode 100644 index 000000000..336fdc899 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational-rel.md @@ -0,0 +1,9 @@ +# new_relational() + + Code + new_relational(list()) + Output + list() + attr(,"class") + [1] "relational" + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational.md new file mode 100644 index 000000000..a5f11721b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/relational.md @@ -0,0 +1,11 @@ +# rel_try() with reason + + Code + rel_try(NULL, `Not affected: {.code FALSE}` = FALSE, `Affected: {.code TRUE}` = TRUE, + { }) + Message + Cannot process duckplyr query with DuckDB, falling back to dplyr. + i Affected: `TRUE` + Output + [1] "Affected: {.code TRUE}" + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/telemetry.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/telemetry.md new file mode 100644 index 000000000..49e263a3a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/telemetry.md @@ -0,0 +1,326 @@ +# telemetry and anti_join() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% anti_join(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! anti_join: {"version":"0.3.1","message":"Error in anti_join","name":"anti_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"copy":false,"na_matches":"na"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% anti_join(tibble(a = 1:3, b = 4:6), by = "a", + copy = FALSE, na_matches = "na") + Condition + Error in `rel_try()`: + ! anti_join: {"version":"0.3.1","message":"Error in anti_join","name":"anti_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...1"]},"copy":false,"na_matches":"na"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% anti_join(tibble(a = 1:3, b = 4:6), by = c( + a = "b"), copy = FALSE, na_matches = "na") + Condition + Error in `rel_try()`: + ! anti_join: {"version":"0.3.1","message":"Error in anti_join","name":"anti_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...2"]},"copy":false,"na_matches":"na"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% anti_join(tibble(a = 1:3, b = 4:6), by = join_by( + a == b), copy = FALSE, na_matches = "never") + Condition + Error in `rel_try()`: + ! anti_join: {"version":"0.3.1","message":"Error in anti_join","name":"anti_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...2"]},"copy":false,"na_matches":"never"}} + +# telemetry and arrange() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% arrange(a) + Condition + Error in `rel_try()`: + ! arrange: {"version":"0.3.1","message":"Error in arrange","name":"arrange","x":{"...1":"integer","...2":"integer"},"args":{"dots":["...1"],".by_group":false}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% arrange(a, .by_group = TRUE) + Condition + Error in `rel_try()`: + ! arrange: {"version":"0.3.1","message":"Error in arrange","name":"arrange","x":{"...1":"integer","...2":"integer"},"args":{"dots":["...1"],".by_group":true}} + +# telemetry and count() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% count(a) + Condition + Error in `rel_try()`: + ! count: {"version":"0.3.1","message":"Error in count","name":"count","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...1"},"wt":"NULL","sort":false,".drop":true}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% count(a, wt = b, sort = TRUE, name = "nn", + .drop = FALSE) + Condition + Error in `rel_try()`: + ! count: {"version":"0.3.1","message":"Error in count","name":"count","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...1"},"wt":"...2","sort":true,".drop":false}} + +# telemetry and distinct() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% distinct(a) + Condition + Error in `rel_try()`: + ! distinct: {"version":"0.3.1","message":"Error in distinct","name":"distinct","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...1":"...1"},".keep_all":false}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% distinct(a, b, .keep_all = TRUE) + Condition + Error in `rel_try()`: + ! distinct: {"version":"0.3.1","message":"Error in distinct","name":"distinct","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...1":"...1","...2":"...2"},".keep_all":true}} + +# telemetry and filter() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% filter(a > 1) + Condition + Error in `rel_try()`: + ! filter: {"version":"0.3.1","message":"Error in filter","name":"filter","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...1 > 1"},"by":"NULL","preserve":false}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% filter(a > 1, .by = b) + Condition + Error in `rel_try()`: + ! filter: {"version":"0.3.1","message":"Error in filter","name":"filter","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...1 > 1"},"by":"...2","preserve":false}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% filter(a > 1, .preserve = TRUE) + Condition + Error in `rel_try()`: + ! filter: {"version":"0.3.1","message":"Error in filter","name":"filter","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...1 > 1"},"by":"NULL","preserve":true}} + +# telemetry and full_join() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% full_join(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! full_join: {"version":"0.3.1","message":"Error in full_join","name":"full_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"copy":false,"na_matches":["na","never"],"multiple":"all"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% full_join(tibble(a = 1:3, b = 4:6), by = "a", + copy = TRUE, suffix = c("x", "y"), keep = TRUE, na_matches = "na", multiple = "all", + relationship = "one-to-one") + Condition + Error in `rel_try()`: + ! full_join: {"version":"0.3.1","message":"Error in full_join","name":"full_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...1"]},"copy":true,"keep":true,"na_matches":"na","multiple":"all","relationship":"one-to-one"}} + +# telemetry and inner_join() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% inner_join(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! inner_join: {"version":"0.3.1","message":"Error in inner_join","name":"inner_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"copy":false,"na_matches":["na","never"],"multiple":"all","unmatched":"drop"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% inner_join(tibble(a = 1:3, b = 4:6), by = "a", + copy = TRUE, suffix = c("x", "y"), keep = TRUE, na_matches = "na", multiple = "all", + unmatched = "error", relationship = "one-to-one") + Condition + Error in `rel_try()`: + ! inner_join: {"version":"0.3.1","message":"Error in inner_join","name":"inner_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...1"]},"copy":true,"keep":true,"na_matches":"na","multiple":"all","unmatched":"error","relationship":"one-to-one"}} + +# telemetry and intersect() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% intersect(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! intersect: {"version":"0.3.1","message":"Error in intersect","name":"intersect","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"}} + +# telemetry and left_join() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% left_join(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! left_join: {"version":"0.3.1","message":"Error in left_join","name":"left_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"copy":false,"na_matches":["na","never"],"multiple":"all","unmatched":"drop"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% left_join(tibble(a = 1:3, b = 4:6), by = "a", + copy = TRUE, suffix = c("x", "y"), keep = TRUE, na_matches = "na", multiple = "all", + unmatched = "error", relationship = "one-to-one") + Condition + Error in `rel_try()`: + ! left_join: {"version":"0.3.1","message":"Error in left_join","name":"left_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...1"]},"copy":true,"keep":true,"na_matches":"na","multiple":"all","unmatched":"error","relationship":"one-to-one"}} + +# telemetry and mutate() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% mutate(c = a + b) + Condition + Error in `rel_try()`: + ! mutate: {"version":"0.3.1","message":"Error in mutate","name":"mutate","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"...1 + ...2"},".by":"NULL",".keep":["all","used","unused","none"]}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% mutate(c = a + b, .by = a, .keep = "unused", + ) + Condition + Error in `rel_try()`: + ! mutate: {"version":"0.3.1","message":"Error in mutate","name":"mutate","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"...1 + ...2"},".by":"...1",".keep":"unused"}} + +# telemetry and relocate() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% relocate(b) + Condition + Error in `rel_try()`: + ! relocate: {"version":"0.3.1","message":"Error in relocate","name":"relocate","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...2"},".before":"NULL",".after":"NULL"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% relocate(b, .before = a) + Condition + Error in `rel_try()`: + ! relocate: {"version":"0.3.1","message":"Error in relocate","name":"relocate","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...2"},".before":"...1",".after":"NULL"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% relocate(a, .after = b) + Condition + Error in `rel_try()`: + ! relocate: {"version":"0.3.1","message":"Error in relocate","name":"relocate","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"1":"...1"},".before":"NULL",".after":"...2"}} + +# telemetry and rename() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% rename(c = a) + Condition + Error in `rel_try()`: + ! rename: {"version":"0.3.1","message":"Error in rename","name":"rename","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"...1"}}} + +# telemetry and right_join() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% right_join(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! right_join: {"version":"0.3.1","message":"Error in right_join","name":"right_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"copy":false,"na_matches":["na","never"],"multiple":"all","unmatched":"drop"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% right_join(tibble(a = 1:3, b = 4:6), by = "a", + copy = TRUE, suffix = c("x", "y"), keep = TRUE, na_matches = "na", multiple = "all", + unmatched = "error", relationship = "one-to-one") + Condition + Error in `rel_try()`: + ! right_join: {"version":"0.3.1","message":"Error in right_join","name":"right_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...1"]},"copy":true,"keep":true,"na_matches":"na","multiple":"all","unmatched":"error","relationship":"one-to-one"}} + +# telemetry and select() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% select(c = b) + Condition + Error in `rel_try()`: + ! select: {"version":"0.3.1","message":"Error in select","name":"select","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"...2"}}} + +# telemetry and semi_join() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% semi_join(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! semi_join: {"version":"0.3.1","message":"Error in semi_join","name":"semi_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"copy":false,"na_matches":"na"}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% semi_join(tibble(a = 1:3, b = 4:6), by = "a", + copy = TRUE, na_matches = "na") + Condition + Error in `rel_try()`: + ! semi_join: {"version":"0.3.1","message":"Error in semi_join","name":"semi_join","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"},"args":{"by":{"condition":"==","filter":"none","x":["...1"],"y":["...1"]},"copy":true,"na_matches":"na"}} + +# telemetry and setdiff() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% setdiff(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! setdiff: {"version":"0.3.1","message":"Error in setdiff","name":"setdiff","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"}} + +# telemetry and summarise() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% summarise(c = sum(b)) + Condition + Error in `rel_try()`: + ! summarise: {"version":"0.3.1","message":"Error in summarise","name":"summarise","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"sum(...2)"}}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% summarise(c = sum(b), .by = a) + Condition + Error in `rel_try()`: + ! summarise: {"version":"0.3.1","message":"Error in summarise","name":"summarise","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"sum(...2)"},"by":["...1"]}} + +--- + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% summarise(c = sum(b), .groups = "rowwise") + Condition + Error in `rel_try()`: + ! summarise: {"version":"0.3.1","message":"Error in summarise","name":"summarise","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"sum(...2)"},".groups":"rowwise"}} + +# telemetry and symdiff() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% symdiff(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! symdiff: {"version":"0.3.1","message":"Error in symdiff","name":"symdiff","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"}} + +# telemetry and transmute() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% transmute(c = a + b) + Condition + Error in `rel_try()`: + ! transmute: {"version":"0.3.1","message":"Error in transmute","name":"transmute","x":{"...1":"integer","...2":"integer"},"args":{"dots":{"...3":"...1 + ...2"}}} + +# telemetry and union_all() + + Code + duckdb_tibble(a = 1:3, b = 4:6) %>% union_all(tibble(a = 1:3, b = 4:6)) + Condition + Error in `rel_try()`: + ! union_all: {"version":"0.3.1","message":"Error in union_all","name":"union_all","x":{"...1":"integer","...2":"integer"},"y":{"...1":"integer","...2":"integer"}} + +# scrubbing function declarations + + Code + expr <- expr(across(x:y, function(arg) mean(arg, na.rm = TRUE))) + expr_scrub(expr) + Output + across(...1:...2, function(arg) mean(...3, na.rm = TRUE)) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/tpch.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/tpch.md new file mode 100644 index 000000000..0ee53e488 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/tpch.md @@ -0,0 +1,129 @@ +# TPCH queries can be parsed and run + + Code + tpch_01() + Output + # A duckplyr data frame: 10 variables + # i 10 variables: l_returnflag , l_linestatus , sum_qty , + # sum_base_price , sum_disc_price , sum_charge , + # avg_qty , avg_price , avg_disc , count_order + Code + tpch_02() + Output + # A duckplyr data frame: 8 variables + # i 8 variables: s_acctbal , s_name , n_name , p_partkey , + # p_mfgr , s_address , s_phone , s_comment + Code + tpch_03() + Output + # A duckplyr data frame: 4 variables + # i 4 variables: l_orderkey , revenue , o_orderdate , + # o_shippriority + Code + tpch_04() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: o_orderpriority , order_count + Code + tpch_05() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: n_name , revenue + Code + tpch_06() + Output + # A duckplyr data frame: 1 variable + revenue + + 1 NA + Code + tpch_07() + Output + # A duckplyr data frame: 4 variables + # i 4 variables: supp_nation , cust_nation , l_year , + # revenue + Code + tpch_08() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: o_year , mkt_share + Code + tpch_09() + Output + # A duckplyr data frame: 3 variables + # i 3 variables: nation , o_year , sum_profit + Code + tpch_10() + Output + # A duckplyr data frame: 8 variables + # i 8 variables: c_custkey , c_name , revenue , c_acctbal , + # n_name , c_address , c_phone , c_comment + Code + tpch_11() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: ps_partkey , value + Code + tpch_12() + Output + # A duckplyr data frame: 3 variables + # i 3 variables: l_shipmode , high_line_count , low_line_count + Code + tpch_13() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: c_count , custdist + Code + tpch_14() + Output + # A duckplyr data frame: 1 variable + promo_revenue + + 1 NA + Code + tpch_15() + Output + # A duckplyr data frame: 5 variables + # i 5 variables: s_suppkey , s_name , s_address , s_phone , + # total_revenue + Code + tpch_16() + Output + # A duckplyr data frame: 4 variables + # i 4 variables: p_brand , p_type , p_size , supplier_cnt + Code + tpch_17() + Output + # A duckplyr data frame: 1 variable + avg_yearly + + 1 NA + Code + tpch_18() + Output + # A duckplyr data frame: 6 variables + # i 6 variables: c_name , c_custkey , o_orderkey , + # o_orderdate , o_totalprice , sum + Code + tpch_19() + Output + # A duckplyr data frame: 1 variable + revenue + + 1 NA + Code + tpch_20() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: s_name , s_address + Code + tpch_21() + Output + # A duckplyr data frame: 2 variables + # i 2 variables: s_name , numwait + Code + tpch_22() + Output + # A duckplyr data frame: 3 variables + # i 3 variables: cntrycode , numcust , totacctbal + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/translate.md b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/translate.md new file mode 100644 index 000000000..0cd69d993 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/_snaps/translate.md @@ -0,0 +1,621 @@ +# call with named argument + + Code + rel_translate(quo(c(1, b = 2))) + Condition + Error: + ! Can't translate function `c()`. + +# a %in% b + + Code + rel_translate(quo(a %in% b), data.frame(a = 1:3, b = 2:4)) + Condition + Error: + ! Can't access data in this context + +# comparison expression translated + + Code + rel_translate(quo(a > 123L), df) + Output + List of 2 + $ cmp_op: chr ">" + $ exprs :List of 2 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ :List of 2 + .. ..$ val : int 123 + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_constant" "relational_relexpr" + - attr(*, "class")= chr [1:2] "relational_relexpr_comparison" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(quo(a > 123), df) + Output + List of 2 + $ cmp_op: chr ">" + $ exprs :List of 2 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ :List of 2 + .. ..$ val : num 123 + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_constant" "relational_relexpr" + - attr(*, "class")= chr [1:2] "relational_relexpr_comparison" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(quo(a == b), df) + Output + List of 2 + $ cmp_op: chr "==" + $ exprs :List of 2 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ :List of 3 + .. ..$ name : chr "b" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + - attr(*, "class")= chr [1:2] "relational_relexpr_comparison" "relational_relexpr" + - attr(*, "used")= chr [1:2] "a" "b" + +--- + + Code + rel_translate(quo(a <= c), df) + Output + List of 2 + $ cmp_op: chr "<=" + $ exprs :List of 2 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ :List of 3 + .. ..$ name : chr "c" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + - attr(*, "class")= chr [1:2] "relational_relexpr_comparison" "relational_relexpr" + - attr(*, "used")= chr [1:2] "a" "c" + +# aggregation primitives + + Code + rel_translate(expr(sum(a)), df) + Output + List of 3 + $ name : chr "___sum_na" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(sum(a, b)), df) + Condition + Error: + ! `sum()` needs exactly one argument besides the optional `na.rm` + +--- + + Code + rel_translate(expr(sum(a, na.rm = TRUE)), df) + Output + List of 3 + $ name : chr "sum" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(sum(a, na.rm = FALSE)), df) + Output + List of 3 + $ name : chr "___sum_na" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(sum(a, na.rm = b)), df) + Condition + Error: + ! object 'b' not found + +--- + + Code + rel_translate(expr(sum(a, na.rm = 1)), df) + Condition + Error: + ! Invalid value for `na.rm` in call to `sum()` + +--- + + Code + rel_translate(expr(sum(a)), df, need_window = TRUE) + Condition + Error: + ! `sum(na.rm = FALSE)` not supported in window functions + i Use `sum(na.rm = TRUE)` after checking for missing values + +--- + + Code + rel_translate(expr(min(a)), df) + Output + List of 3 + $ name : chr "___min_na" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(min(a, na.rm = TRUE)), df) + Output + List of 3 + $ name : chr "min" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(max(a)), df) + Output + List of 3 + $ name : chr "___max_na" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(max(a, na.rm = TRUE)), df) + Output + List of 3 + $ name : chr "max" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(any(a)), df) + Output + List of 3 + $ name : chr "___any_na" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(any(a, na.rm = TRUE)), df) + Output + List of 3 + $ name : chr "any" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(all(a)), df) + Output + List of 3 + $ name : chr "___all_na" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(all(a, na.rm = TRUE)), df) + Output + List of 3 + $ name : chr "all" + $ args :List of 1 + ..$ :List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(mean(a)), df) + Output + List of 3 + $ name : chr "___mean_na" + $ args :List of 1 + ..$ x:List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(mean(a, b)), df) + Condition + Error: + ! `mean()` needs exactly one argument besides the optional `na.rm` + +--- + + Code + rel_translate(expr(mean(a, na.rm = TRUE)), df) + Output + List of 3 + $ name : chr "mean" + $ args :List of 1 + ..$ x:List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(mean(a, na.rm = FALSE)), df) + Output + List of 3 + $ name : chr "___mean_na" + $ args :List of 1 + ..$ x:List of 3 + .. ..$ name : chr "a" + .. ..$ rel : NULL + .. ..$ alias: NULL + .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + $ alias: NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(mean(a, na.rm = b)), df) + Condition + Error: + ! object 'b' not found + +--- + + Code + rel_translate(expr(mean(a, na.rm = 1)), df) + Condition + Error: + ! Invalid value for `na.rm` in call to `mean()` + +--- + + Code + rel_translate(expr(mean(a)), df, need_window = TRUE) + Condition + Error: + ! `mean(na.rm = FALSE)` not supported in window functions + i Use `mean(na.rm = TRUE)` after checking for missing values + +# aggregation primitives with na.rm and window functions + + Code + # Test aggregation primitives with na.rm = TRUE in window functions + rel_translate(expr(sum(a, na.rm = TRUE)), df, need_window = TRUE) + Output + List of 6 + $ expr :List of 3 + ..$ name : chr "sum" + ..$ args :List of 1 + .. ..$ :List of 3 + .. .. ..$ name : chr "a" + .. .. ..$ rel : NULL + .. .. ..$ alias: NULL + .. .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ alias: NULL + ..- attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + $ partitions : list() + $ order_bys : list() + $ offset_expr : NULL + $ default_expr: NULL + $ alias : NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_window" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(min(a, na.rm = TRUE)), df, need_window = TRUE) + Output + List of 6 + $ expr :List of 3 + ..$ name : chr "min" + ..$ args :List of 1 + .. ..$ :List of 3 + .. .. ..$ name : chr "a" + .. .. ..$ rel : NULL + .. .. ..$ alias: NULL + .. .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ alias: NULL + ..- attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + $ partitions : list() + $ order_bys : list() + $ offset_expr : NULL + $ default_expr: NULL + $ alias : NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_window" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(max(a, na.rm = TRUE)), df, need_window = TRUE) + Output + List of 6 + $ expr :List of 3 + ..$ name : chr "max" + ..$ args :List of 1 + .. ..$ :List of 3 + .. .. ..$ name : chr "a" + .. .. ..$ rel : NULL + .. .. ..$ alias: NULL + .. .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ alias: NULL + ..- attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + $ partitions : list() + $ order_bys : list() + $ offset_expr : NULL + $ default_expr: NULL + $ alias : NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_window" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + rel_translate(expr(mean(a, na.rm = TRUE)), df, need_window = TRUE) + Output + List of 6 + $ expr :List of 3 + ..$ name : chr "mean" + ..$ args :List of 1 + .. ..$ x:List of 3 + .. .. ..$ name : chr "a" + .. .. ..$ rel : NULL + .. .. ..$ alias: NULL + .. .. ..- attr(*, "class")= chr [1:2] "relational_relexpr_reference" "relational_relexpr" + ..$ alias: NULL + ..- attr(*, "class")= chr [1:2] "relational_relexpr_function" "relational_relexpr" + $ partitions : list() + $ order_bys : list() + $ offset_expr : NULL + $ default_expr: NULL + $ alias : NULL + - attr(*, "class")= chr [1:2] "relational_relexpr_window" "relational_relexpr" + - attr(*, "used")= chr "a" + +--- + + Code + # Test error when na.rm = FALSE in window functions + rel_translate(expr(sum(a, na.rm = FALSE)), df, need_window = TRUE) + Condition + Error: + ! `sum(na.rm = FALSE)` not supported in window functions + i Use `sum(na.rm = TRUE)` after checking for missing values + +--- + + Code + rel_translate(expr(mean(a, na.rm = FALSE)), df, need_window = TRUE) + Condition + Error: + ! `mean(na.rm = FALSE)` not supported in window functions + i Use `mean(na.rm = TRUE)` after checking for missing values + +# rel_find_call() success paths + + Code + # Success: Translate base function + rel_find_call(quote(mean), env) + Output + [1] "base" "mean" + +--- + + Code + # Success: Translate dplyr::n() function + # https://github.com/tidyverse/dplyr/pull/7046 + rel_find_call(quote(n), env) + Output + [1] "dplyr" "n" + +--- + + Code + # Success: Translate DuckDB function with 'dd$' prefix + rel_find_call(quote(dd$ROW), env) + Output + [1] "dd" "ROW" + +--- + + Code + # Success: Translate stats function when stats is available + rel_find_call(quote(sd), new_environment(parent = asNamespace("stats"))) + Output + [1] "stats" "sd" + +--- + + Code + # Success: Translate lubridate function + rel_find_call(quote(lubridate::wday), env) + Output + [1] "lubridate" "wday" + +--- + + Code + # Success: Translate lubridate function when exported + rel_find_call(quote(wday), new_environment(list(wday = lubridate::wday))) + Output + [1] "lubridate" "wday" + +# rel_find_call() error paths + + Code + # Error: Can't translate function with invalid '::' structure + rel_find_call(quote(pkg::""), env) + Condition + Error: + ! Can't translate function `pkg::""()`. + +--- + + Code + # Error: Can't translate function with invalid '::' components + rel_find_call(call("::", "pkg", 123), env, env) + Condition + Error: + ! Can't translate function `"pkg"::123()`. + +--- + + Code + # Error: No translation for unknown function + rel_find_call(quote(unknown_function), env) + Condition + Error: + ! Can't translate function `unknown_function()`. + +--- + + Code + # Error: No translation for unknown function from some package + rel_find_call(quote(somepkg::unknown_function), env) + Condition + Error: + ! Can't translate function `somepkg::unknown_function()`. + +--- + + Code + # Error: Function not found in the environment + rel_find_call(quote(mean), new_environment()) + Condition + Error: + ! Function `mean()` not found. + +--- + + Code + # Error: Function does not map to the corresponding package + rel_find_call(quote(mean), new_environment(list(mean = stats::sd))) + Condition + Error: + ! Function `mean()` does not map to `base::mean()`. + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-encoding.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-encoding.R new file mode 100644 index 000000000..9a767ef1a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-encoding.R @@ -0,0 +1,78 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +get_lang_strings <- function() { + lang_strings <- c( + de = "Gl\u00fcck", + cn = "\u5e78\u798f", + ru = "\u0441\u0447\u0430\u0441\u0442\u044c\u0435", + ko = "\ud589\ubcf5" + ) + + native_lang_strings <- enc2native(lang_strings) + + same <- (lang_strings == native_lang_strings) + + list( + same = lang_strings[same], + different = lang_strings[!same] + ) +} + +get_native_lang_string <- function() { + lang_strings <- get_lang_strings() + if (length(lang_strings$same) == 0) testthat::skip("No native language string available") + lang_strings$same[[1L]] +} + +get_alien_lang_string <- function() { + lang_strings <- get_lang_strings() + if (length(lang_strings$different) == 0) testthat::skip("No alien language string available") + lang_strings$different[[1L]] +} + +has_locale <- function(locale, category) { + original <- Sys.getlocale(category = category) + on.exit(Sys.setlocale(category = category, locale = original), add = TRUE) + + tryCatch( + expr = { + Sys.setlocale(category = category, locale = locale) + TRUE + }, + warning = function(w) FALSE, + error = function(e) FALSE + ) +} +has_collate_locale <- function(locale) { + has_locale(locale = locale, category = "LC_COLLATE") +} +has_ctype_locale <- function(enc) { + has_locale(locale = enc, category = "LC_CTYPE") +} + +non_utf8_encoding <- function(enc = NULL) { + if (!l10n_info()$`UTF-8`) { + return(Sys.getlocale("LC_CTYPE")) + } + enc <- enc %||% c( + "en_US.ISO8859-1", + "en_US.ISO8859-15", + "fr_CH.ISO8859-1", + "fr_CH.ISO8859-15" + ) + available <- vapply(enc, has_ctype_locale, logical(1)) + if (any(available)) { + enc[available][1] + } else { + NULL + } +} + +local_non_utf8_encoding <- function(enc = NULL, env = parent.frame()) { + non_utf8 <- non_utf8_encoding(enc) + if (is.null(non_utf8)) { + skip("Can't set a non-UTF-8 encoding") + } else { + withr::local_locale(c(LC_CTYPE = non_utf8), .local_envir = env) + } +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-pick.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-pick.R new file mode 100644 index 000000000..41c1c9999 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-pick.R @@ -0,0 +1,6 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +pick_wrapper <- function(...) { + # Wrapping `pick()` forces evaluation fallback + pick(...) +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-s3.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-s3.R new file mode 100644 index 000000000..6c73de445 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-dplyr-s3.R @@ -0,0 +1,48 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +local_methods <- function(..., .frame = caller_env()) { + local_bindings(..., .env = global_env(), .frame = .frame) +} + +local_foo_df <- function(frame = caller_env()) { + local_methods(.frame = frame, + group_by.foo_df = function(.data, ...) { + out <- NextMethod() + if (missing(...)) { + class(out) <- c("foo_df", class(out)) + } else { + class(out) <- c("grouped_foo_df", class(out)) + } + out + }, + ungroup.grouped_foo_df = function(x, ...) { + out <- NextMethod() + class(out) <- c("foo_df", class(out)) + out + } + ) +} + +new_ctor <- function(base_class) { + function(x = list(), ..., class = NULL) { + if (inherits(x, "tbl_df")) { + tibble::new_tibble(x, class = c(class, base_class), nrow = nrow(x)) + } else if (is.data.frame(x)) { + structure(x, class = c(class, base_class, "data.frame"), ...) + } else { + structure(x, class = c(class, base_class), ...) + } + } +} + +foobar <- new_ctor("dplyr_foobar") +foobaz <- new_ctor("dplyr_foobaz") +quux <- new_ctor("dplyr_quux") + +# For testing reconstructing methods that break invariants by adding +# new columns +new_dispatched_quux <- function(x) { + out <- quux(x) + out$dispatched <- rep(TRUE, nrow(out)) + out +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-list_of.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-list_of.R new file mode 100644 index 000000000..1e5dba809 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-list_of.R @@ -0,0 +1 @@ +list_of <- vctrs::list_of diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-skip.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-skip.R new file mode 100644 index 000000000..1c445d990 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-skip.R @@ -0,0 +1,22 @@ +# Avoiding functions used only for internal tests +compute_groups <- function(...) { + skip("dplyr:::compute_groups() is only needed to test dplyr internals") +} +err_locs <- function(...) { + skip("dplyr:::err_locs() is only needed to test dplyr internals") +} +expand_pick <- function(...) { + skip("dplyr:::expand_pick() is only needed to test dplyr internals") +} +group_labels_details <- function(...) { + skip("dplyr:::group_labels_details() is only needed to test dplyr internals") +} +is_sel_vars <- function(...) { + skip("dplyr:::is_sel_vars() is only needed to test dplyr internals") +} +reset_dplyr_warnings <- function(...) { + skip("dplyr:::reset_dplyr_warnings() is only needed to test dplyr internals") +} +shift <- function(...) { + skip("dplyr:::shift() is only needed to test dplyr internals") +} diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-taxi.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-taxi.R new file mode 100644 index 000000000..507da3c23 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/helper-taxi.R @@ -0,0 +1,35 @@ +taxi_data_2019 <- duckdb_tibble( + vendor_name = character(0), + pickup_datetime = as.POSIXct(character(0), tz = "UTC"), + dropoff_datetime = as.POSIXct(character(0), tz = "UTC"), + passenger_count = numeric(0), + trip_distance = numeric(0), + pickup_longitude = numeric(0), + pickup_latitude = numeric(0), + rate_code = character(0), + store_and_fwd = character(0), + dropoff_longitude = numeric(0), + dropoff_latitude = numeric(0), + payment_type = character(0), + fare_amount = numeric(0), + extra = numeric(0), + mta_tax = numeric(0), + tip_amount = numeric(0), + tolls_amount = numeric(0), + total_amount = numeric(0), + improvement_surcharge = numeric(0), + congestion_surcharge = numeric(0), + pickup_location_id = numeric(0), + dropoff_location_id = numeric(0), + year = character(0), + month = character(0) +) + +zone_map <- duckdb_tibble( + LocationID = numeric(0), + Borough = character(0), + Zone = character(0), + service_zone = character(0) +) + +meta_reset() diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/setup.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/setup.R new file mode 100644 index 000000000..76776b99f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/setup.R @@ -0,0 +1,18 @@ +start <- Sys.time() + +withr::local_envvar(DUCKPLYR_FALLBACK_COLLECT = 0, .local_envir = testthat::teardown_env()) + +withr::local_envvar(DUCKPLYR_OUTPUT_ORDER = TRUE, .local_envir = testthat::teardown_env()) + +# withr::local_envvar(DUCKPLYR_TELEMETRY_PREP_TEST = TRUE, .local_envir = testthat::teardown_env()) + +# withr::local_envvar(DUCKPLYR_FORCE = TRUE, .local_envir = testthat::teardown_env()) + +withr::defer(envir = testthat::teardown_env(), { + writeLines("") + stats_show() + writeLines("") + if (is_installed("hms")) { + writeLines(format(hms::as_hms(Sys.time() - start))) + } +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-altrep.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-altrep.R new file mode 100644 index 000000000..3d75ce5d0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-altrep.R @@ -0,0 +1,14 @@ +test_that("Can query ptype without triggering materialization", { + n_calls <- 0 + local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + x <- data.frame(a = 1) %>% duckplyr_mutate(b = 2) + + vctrs::vec_ptype(x) + expect_equal(n_calls, 0) + + nrow(x) + expect_equal(n_calls, 1) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_df.R new file mode 100644 index 000000000..cc22b22d7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_df.R @@ -0,0 +1,2727 @@ +# Generated by 03-tests.R + +withr::local_envvar(DUCKPLYR_FORCE = "TRUE") + +meta <- testthat::is_parallel() # Slow! +# meta <- TRUE + +test_that("as_duckplyr_df_impl() and add_count()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% add_count() + post <- test_df %>% add_count() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and add_count()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% add_count() + post <- test_df %>% add_count() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and anti_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% anti_join(test_df_y, join_by(a)) + post <- test_df_x %>% anti_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and anti_join(join_by(a))", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% anti_join(test_df_y, join_by(a)) + post <- test_df_x %>% anti_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and arrange()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% arrange() + post <- test_df %>% arrange() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and arrange()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% arrange() + post <- test_df %>% arrange() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and arrange(a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% arrange(a) + post <- test_df %>% arrange(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and arrange(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% arrange(g) + post <- test_df %>% arrange(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and arrange(g, a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% arrange(g, a) + post <- test_df %>% arrange(g, a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and arrange(a, g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% arrange(a, g) + post <- test_df %>% arrange(a, g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and auto_copy()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% auto_copy(test_df_y) + post <- test_df_x %>% auto_copy(test_df_y) + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and auto_copy()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% auto_copy(test_df_y) + post <- test_df_x %>% auto_copy(test_df_y) + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and collapse()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% collapse() + post <- test_df %>% collapse() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and collapse()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% collapse() + post <- test_df %>% collapse() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and collect()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% collect() + post <- test_df %>% collect() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and collect()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% collect() + post <- test_df %>% collect() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and compute()", { + # No fallback + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% compute() + post <- test_df %>% compute() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and count()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count() + post <- test_df %>% count() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and count()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count() + post <- test_df %>% count() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and count(a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count(a) + post <- test_df %>% count(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and count(b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count(b) + post <- test_df %>% count(b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and count(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count(g) + post <- test_df %>% count(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and count(g, a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count(g, a) + post <- test_df %>% count(g, a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and count(b, g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% count(b, g) + post <- test_df %>% count(b, g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and cross_join()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% cross_join(test_df_y) + post <- test_df_x %>% cross_join(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and cross_join()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% cross_join(test_df_y) + post <- test_df_x %>% cross_join(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and distinct()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct() + post <- test_df %>% distinct() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and distinct()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct() + post <- test_df %>% distinct() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and distinct(a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct(a) + post <- test_df %>% distinct(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and distinct(a, b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct(a, b) + post <- test_df %>% distinct(a, b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and distinct(b, b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct(b, b) + post <- test_df %>% distinct(b, b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and distinct(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct(g) + post <- test_df %>% distinct(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union_all(data.frame(a = 1L, b = 3, g = 2L)) %>% distinct(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% union_all(data.frame(a = 1L, b = 3, g = 2L)) %>% distinct(g) + post <- test_df %>% union_all(data.frame(a = 1L, b = 3, g = 2L)) %>% distinct(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union_all(data.frame(a = 1L, b = 4, g = 2L)) %>% distinct(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% union_all(data.frame(a = 1L, b = 4, g = 2L)) %>% distinct(g) + post <- test_df %>% union_all(data.frame(a = 1L, b = 4, g = 2L)) %>% distinct(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union_all(data.frame(a = 1L, b = 5, g = 2L)) %>% distinct(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% union_all(data.frame(a = 1L, b = 5, g = 2L)) %>% distinct(g) + post <- test_df %>% union_all(data.frame(a = 1L, b = 5, g = 2L)) %>% distinct(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union_all(data.frame(a = 1L, b = 6, g = 2L)) %>% distinct(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% union_all(data.frame(a = 1L, b = 6, g = 2L)) %>% distinct(g) + post <- test_df %>% union_all(data.frame(a = 1L, b = 6, g = 2L)) %>% distinct(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union_all(data.frame(a = 1L, b = 7, g = 2L)) %>% distinct(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% union_all(data.frame(a = 1L, b = 7, g = 2L)) %>% distinct(g) + post <- test_df %>% union_all(data.frame(a = 1L, b = 7, g = 2L)) %>% distinct(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and distinct(g, .keep_all = TRUE)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% distinct(g, .keep_all = TRUE) + post <- test_df %>% distinct(g, .keep_all = TRUE) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and do(data.frame(c = 1))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% do(data.frame(c = 1)) + post <- test_df %>% do(data.frame(c = 1)) + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and do(data.frame(c = 1))", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% do(data.frame(c = 1)) + post <- test_df %>% do(data.frame(c = 1)) + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and dplyr_reconstruct(test_df)", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Hack") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% dplyr_reconstruct(test_df) + post <- test_df %>% dplyr_reconstruct(test_df) + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and filter(a == 1)", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% filter(a == 1) + post <- test_df %>% filter(a == 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and filter(a == 1)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% filter(a == 1) + post <- test_df %>% filter(a == 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and filter(a %in% 2:3, g == 2)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% filter(a %in% 2:3, g == 2) + post <- test_df %>% filter(a %in% 2:3, g == 2) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and filter(a %in% 2:3 & g == 2)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% filter(a %in% 2:3 & g == 2) + post <- test_df %>% filter(a %in% 2:3 & g == 2) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and filter(a != 2 | g != 2)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% filter(a != 2 | g != 2) + post <- test_df %>% filter(a != 2 | g != 2) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and full_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% full_join(test_df_y, join_by(a)) + post <- test_df_x %>% full_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and full_join(join_by(a))", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% full_join(test_df_y, join_by(a)) + post <- test_df_x %>% full_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_by()", { + skip("Grouped") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_by() + post <- test_df %>% group_by() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_indices()", { + skip("Special") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_indices() + post <- test_df %>% group_indices() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_keys()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Special") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_keys() + post <- test_df %>% group_keys() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_map(~ .x)", { + skip("WAT") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_map(~ .x) + post <- test_df %>% group_map(~ .x) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_modify(~ .x)", { + skip("Grouped") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_modify(~ .x) + post <- test_df %>% group_modify(~ .x) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_nest()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Always returns tibble") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_nest() + post <- test_df %>% group_nest() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_size()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Special") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_size() + post <- test_df %>% group_size() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_split()", { + skip("WAT") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_split() + post <- test_df %>% group_split() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_trim()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Grouped") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_trim() + post <- test_df %>% group_trim() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and group_vars()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_vars() + post <- test_df %>% group_vars() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and group_vars()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% group_vars() + post <- test_df %>% group_vars() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and groups()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Special") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% groups() + post <- test_df %>% groups() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and inner_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% inner_join(test_df_y, join_by(a)) + post <- test_df_x %>% inner_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and inner_join(join_by(a))", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% inner_join(test_df_y, join_by(a)) + post <- test_df_x %>% inner_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and intersect()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% intersect(test_df_y) + post <- test_df_x %>% intersect(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and intersect()", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% intersect(test_df_y) + post <- test_df_x %>% intersect(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and left_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% left_join(test_df_y, join_by(a)) + post <- test_df_x %>% left_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and left_join(join_by(a))", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% left_join(test_df_y, join_by(a)) + post <- test_df_x %>% left_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and mutate()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate() + post <- test_df %>% mutate() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate() + post <- test_df %>% mutate() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(a + 1)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(a + 1) + post <- test_df %>% mutate(a + 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(a + 1, .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(a + 1, .by = g) + post <- test_df %>% mutate(a + 1, .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = a + 1)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = a + 1) + post <- test_df %>% mutate(c = a + 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(`if` = a + 1)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(`if` = a + 1) + post <- test_df %>% mutate(`if` = a + 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(sum(a, na.rm = TRUE))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(sum(a, na.rm = TRUE)) + post <- test_df %>% mutate(sum(a, na.rm = TRUE)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(sum(a, na.rm = TRUE), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(sum(a, na.rm = TRUE), .by = g) + post <- test_df %>% mutate(sum(a, na.rm = TRUE), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(mean(a, na.rm = TRUE))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(mean(a, na.rm = TRUE)) + post <- test_df %>% mutate(mean(a, na.rm = TRUE)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(mean(a, na.rm = TRUE), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(mean(a, na.rm = TRUE), .by = g) + post <- test_df %>% mutate(mean(a, na.rm = TRUE), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(sd(a, na.rm = TRUE))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(sd(a, na.rm = TRUE)) + post <- test_df %>% mutate(sd(a, na.rm = TRUE)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(sd(a, na.rm = TRUE), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(sd(a, na.rm = TRUE), .by = g) + post <- test_df %>% mutate(sd(a, na.rm = TRUE), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a)) + post <- test_df %>% mutate(lag(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a), .by = g) + post <- test_df %>% mutate(lag(a), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a)) + post <- test_df %>% mutate(lead(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a), .by = g) + post <- test_df %>% mutate(lead(a), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a, 2))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a, 2)) + post <- test_df %>% mutate(lag(a, 2)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a, 2), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a, 2), .by = g) + post <- test_df %>% mutate(lag(a, 2), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a, 2))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a, 2)) + post <- test_df %>% mutate(lead(a, 2)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a, 2), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a, 2), .by = g) + post <- test_df %>% mutate(lead(a, 2), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a, 4))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a, 4)) + post <- test_df %>% mutate(lag(a, 4)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a, 4), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a, 4), .by = g) + post <- test_df %>% mutate(lag(a, 4), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a, 4))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a, 4)) + post <- test_df %>% mutate(lead(a, 4)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a, 4), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a, 4), .by = g) + post <- test_df %>% mutate(lead(a, 4), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a, default = 0))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a, default = 0)) + post <- test_df %>% mutate(lag(a, default = 0)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lag(a, default = 0), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lag(a, default = 0), .by = g) + post <- test_df %>% mutate(lag(a, default = 0), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a, default = 1000))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a, default = 1000)) + post <- test_df %>% mutate(lead(a, default = 1000)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(lead(a, default = 1000), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(lead(a, default = 1000), .by = g) + post <- test_df %>% mutate(lead(a, default = 1000), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(min(a, na.rm = TRUE))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(min(a, na.rm = TRUE)) + post <- test_df %>% mutate(min(a, na.rm = TRUE)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(min(a, na.rm = TRUE), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(min(a, na.rm = TRUE), .by = g) + post <- test_df %>% mutate(min(a, na.rm = TRUE), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(max(a, na.rm = TRUE))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(max(a, na.rm = TRUE)) + post <- test_df %>% mutate(max(a, na.rm = TRUE)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(max(a, na.rm = TRUE), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(max(a, na.rm = TRUE), .by = g) + post <- test_df %>% mutate(max(a, na.rm = TRUE), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(a / b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(a / b) + post <- test_df %>% mutate(a / b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = 0, e = 1 / d, f = 0 / d, g = -1 / d)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = 0, e = 1 / d, f = 0 / d, g = -1 / d) + post <- test_df %>% mutate(d = 0, e = 1 / d, f = 0 / d, g = -1 / d) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = 0, d = -1, e = log(c), f = suppressWarnings(log(d)))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = 0, d = -1, e = log(c), f = suppressWarnings(log(d))) + post <- test_df %>% mutate(c = 0, d = -1, e = log(c), f = suppressWarnings(log(d))) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = 0, d = -1, e = log10(c), f = suppressWarnings(log10(d)))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = 0, d = -1, e = log10(c), f = suppressWarnings(log10(d))) + post <- test_df %>% mutate(c = 0, d = -1, e = log10(c), f = suppressWarnings(log10(d))) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = 10, d = log(c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = 10, d = log(c)) + post <- test_df %>% mutate(c = 10, d = log(c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = 10, d = log10(c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = 10, d = log10(c)) + post <- test_df %>% mutate(c = 10, d = log10(c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = NA_character_, d = grepl('.', c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = NA_character_, d = grepl('.', c)) + post <- test_df %>% mutate(c = NA_character_, d = grepl('.', c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = 'abbc', d = gsub('(b|c)', 'z' , c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = 'abbc', d = gsub('(b|c)', 'z' , c)) + post <- test_df %>% mutate(c = 'abbc', d = gsub('(b|c)', 'z' , c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = 'abbc', d = sub('(b|c)', 'z' , c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = 'abbc', d = sub('(b|c)', 'z' , c)) + post <- test_df %>% mutate(c = 'abbc', d = sub('(b|c)', 'z' , c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = NA_character_, d = gsub('.', '-' , c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = NA_character_, d = gsub('.', '-' , c)) + post <- test_df %>% mutate(c = NA_character_, d = gsub('.', '-' , c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = NA_character_, d = sub('.', '-' , c))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = NA_character_, d = sub('.', '-' , c)) + post <- test_df %>% mutate(c = NA_character_, d = sub('.', '-' , c)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = a %in% NA_real_)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = a %in% NA_real_) + post <- test_df %>% mutate(d = a %in% NA_real_) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = a %in% NULL)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = a %in% NULL) + post <- test_df %>% mutate(d = a %in% NULL) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = a %in% integer())", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = a %in% integer()) + post <- test_df %>% mutate(d = a %in% integer()) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = NA_real_, e = is.na(d))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = NA_real_, e = is.na(d)) + post <- test_df %>% mutate(d = NA_real_, e = is.na(d)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = row_number())", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = row_number()) + post <- test_df %>% mutate(d = row_number()) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = row_number(), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = row_number(), .by = g) + post <- test_df %>% mutate(d = row_number(), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(c = .data$b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(c = .data$b) + post <- test_df %>% mutate(c = .data$b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = NA)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = NA) + post <- test_df %>% mutate(d = NA) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = NA_integer_)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = NA_integer_) + post <- test_df %>% mutate(d = NA_integer_) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = NA_real_)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = NA_real_) + post <- test_df %>% mutate(d = NA_real_) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = NA_character_)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = NA_character_) + post <- test_df %>% mutate(d = NA_character_) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and mutate(d = if_else(a > 1, \"ok\", NA))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% mutate(d = if_else(a > 1, "ok", NA)) + post <- test_df %>% mutate(d = if_else(a > 1, "ok", NA)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and n_groups()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Special") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% n_groups() + post <- test_df %>% n_groups() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and nest_by()", { + skip("WAT") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% nest_by() + post <- test_df %>% nest_by() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and nest_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% nest_join(test_df_y, join_by(a)) + post <- test_df_x %>% nest_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and nest_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% nest_join(test_df_y, join_by(a)) + post <- test_df_x %>% nest_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and pull()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% pull() + post <- test_df %>% pull() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and pull()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% pull() + post <- test_df %>% pull() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and reframe()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% reframe() + post <- test_df %>% reframe() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and reframe()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% reframe() + post <- test_df %>% reframe() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and relocate(g)", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% relocate(g) + post <- test_df %>% relocate(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and relocate(g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% relocate(g) + post <- test_df %>% relocate(g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and relocate(a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% relocate(a) + post <- test_df %>% relocate(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and relocate(g, .before = b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% relocate(g, .before = b) + post <- test_df %>% relocate(g, .before = b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and relocate(a:b, .after = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% relocate(a:b, .after = g) + post <- test_df %>% relocate(a:b, .after = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rename()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% rename() + post <- test_df %>% rename() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rename()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% rename() + post <- test_df %>% rename() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rename(c = a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% rename(c = a) + post <- test_df %>% rename(c = a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rename_with(identity)", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% rename_with(identity) + post <- test_df %>% rename_with(identity) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rename_with(identity)", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% rename_with(identity) + post <- test_df %>% rename_with(identity) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and right_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% right_join(test_df_y, join_by(a)) + post <- test_df_x %>% right_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and right_join(join_by(a))", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% right_join(test_df_y, join_by(a)) + post <- test_df_x %>% right_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rows_append()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_append(test_df_y) + post <- test_df_x %>% rows_append(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rows_append()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_append(test_df_y) + post <- test_df_x %>% rows_append(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rows_delete(by = c(\"a\", \"b\"), unmatched = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_delete(test_df_y, by = c("a", "b"), unmatched = "ignore") + post <- test_df_x %>% rows_delete(test_df_y, by = c("a", "b"), unmatched = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rows_delete(by = c(\"a\", \"b\"), unmatched = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_delete(test_df_y, by = c("a", "b"), unmatched = "ignore") + post <- test_df_x %>% rows_delete(test_df_y, by = c("a", "b"), unmatched = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rows_insert(by = \"a\", conflict = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_insert(test_df_y, by = "a", conflict = "ignore") + post <- test_df_x %>% rows_insert(test_df_y, by = "a", conflict = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rows_insert(by = \"a\", conflict = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_insert(test_df_y, by = "a", conflict = "ignore") + post <- test_df_x %>% rows_insert(test_df_y, by = "a", conflict = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rows_patch(by = \"a\", unmatched = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_patch(test_df_y, by = "a", unmatched = "ignore") + post <- test_df_x %>% rows_patch(test_df_y, by = "a", unmatched = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rows_patch(by = \"a\", unmatched = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_patch(test_df_y, by = "a", unmatched = "ignore") + post <- test_df_x %>% rows_patch(test_df_y, by = "a", unmatched = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rows_update(by = \"a\", unmatched = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_update(test_df_y, by = "a", unmatched = "ignore") + post <- test_df_x %>% rows_update(test_df_y, by = "a", unmatched = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rows_update(by = \"a\", unmatched = \"ignore\")", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_update(test_df_y, by = "a", unmatched = "ignore") + post <- test_df_x %>% rows_update(test_df_y, by = "a", unmatched = "ignore") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rows_upsert(by = \"a\")", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_upsert(test_df_y, by = "a") + post <- test_df_x %>% rows_upsert(test_df_y, by = "a") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and rows_upsert(by = \"a\")", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% rows_upsert(test_df_y, by = "a") + post <- test_df_x %>% rows_upsert(test_df_y, by = "a") %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and rowwise()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Stack overflow") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% rowwise() + post <- test_df %>% rowwise() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and sample_frac()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Random seed") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% sample_frac() + post <- test_df %>% sample_frac() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and sample_n(size = 1)", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Random seed") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% sample_n(size = 1) + post <- test_df %>% sample_n(size = 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and select(a)", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% select(a) + post <- test_df %>% select(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and select(a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% select(a) + post <- test_df %>% select(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and select(-g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% select(-g) + post <- test_df %>% select(-g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and select(everything())", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% select(everything()) + post <- test_df %>% select(everything()) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and semi_join(join_by(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% semi_join(test_df_y, join_by(a)) + post <- test_df_x %>% semi_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and semi_join(join_by(a))", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% semi_join(test_df_y, join_by(a)) + post <- test_df_x %>% semi_join(test_df_y, join_by(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and setdiff()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% setdiff(test_df_y) + post <- test_df_x %>% setdiff(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and setdiff()", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% setdiff(test_df_y) + post <- test_df_x %>% setdiff(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and setequal()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% setequal(test_df_y) + post <- test_df_x %>% setequal(test_df_y) + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and setequal()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% setequal(test_df_y) + post <- test_df_x %>% setequal(test_df_y) + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and slice()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice() + post <- test_df %>% slice() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and slice()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice() + post <- test_df %>% slice() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and slice_head(n = 2)", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice_head(n = 2) + post <- test_df %>% slice_head(n = 2) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and slice_head(n = 2)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice_head(n = 2) + post <- test_df %>% slice_head(n = 2) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and slice_max(a)", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("External vector?") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice_max(a) + post <- test_df %>% slice_max(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and slice_min(a)", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("External vector?") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice_min(a) + post <- test_df %>% slice_min(a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and slice_sample()", { + skip("External vector?") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice_sample() + post <- test_df %>% slice_sample() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and slice_tail()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("External vector?") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% slice_tail() + post <- test_df %>% slice_tail() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and summarise(c = mean(a))", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(c = mean(a)) + post <- test_df %>% summarise(c = mean(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(c = mean(a))", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(c = mean(a)) + post <- test_df %>% summarise(c = mean(a)) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(c = mean(a), .by = b)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(c = mean(a), .by = b) + post <- test_df %>% summarise(c = mean(a), .by = b) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(c = mean(a), .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(c = mean(a), .by = g) + post <- test_df %>% summarise(c = mean(a), .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(c = 1)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(c = 1) + post <- test_df %>% summarise(c = 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(c = 1, .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(c = 1, .by = g) + post <- test_df %>% summarise(c = 1, .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(n = n(), n = n() + 1L, .by = g)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(n = n(), n = n() + 1L, .by = g) + post <- test_df %>% summarise(n = n(), n = n() + 1L, .by = g) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and summarise(n = n(), n = n() + 1L)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% summarise(n = n(), n = n() + 1L) + post <- test_df %>% summarise(n = n(), n = n() + 1L) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and symdiff()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% symdiff(test_df_y) + post <- test_df_x %>% symdiff(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and symdiff()", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% symdiff(test_df_y) + post <- test_df_x %>% symdiff(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and tally()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% tally() + post <- test_df %>% tally() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and tally()", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% tally() + post <- test_df %>% tally() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and tbl_vars()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% tbl_vars() + post <- test_df %>% tbl_vars() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and tbl_vars()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% tbl_vars() + post <- test_df %>% tbl_vars() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and transmute(c = a + 1)", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% transmute(c = a + 1) + post <- test_df %>% transmute(c = a + 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and transmute(c = a + 1)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% transmute(c = a + 1) + post <- test_df %>% transmute(c = a + 1) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and transmute(row = a)", { + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% transmute(row = a) + post <- test_df %>% transmute(row = a) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and ungroup()", { + withr::local_envvar(DUCKPLYR_FORCE = "FALSE") + + skip("Grouped") + + # Data + test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3)) + + # Run + pre <- test_df %>% as_duckplyr_df_impl() %>% ungroup() + post <- test_df %>% ungroup() %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and union()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% union(test_df_y) + post <- test_df_x %>% union(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union()", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% union(test_df_y) + post <- test_df_x %>% union(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + +test_that("as_duckplyr_df_impl() and union_all()", { + withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE") + + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% union_all(test_df_y) + post <- test_df_x %>% union_all(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) + + +test_that("as_duckplyr_df_impl() and union_all()", { + # Data + test_df_x <- data.frame(a = 1:4, b = 2) + test_df_y <- data.frame(a = 2:5, b = 2) + + # Run + pre <- test_df_x %>% as_duckplyr_df_impl() %>% union_all(test_df_y) + post <- test_df_x %>% union_all(test_df_y) %>% as_duckplyr_df_impl() + + # Compare + expect_identical(pre, post) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_tibble.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_tibble.R new file mode 100644 index 000000000..cfa340d45 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_duckplyr_tibble.R @@ -0,0 +1,22 @@ +test_that("as_duckplyr_tibble() works", { + expect_snapshot({ + as_duckplyr_tibble(tibble(a = 1)) + }) + + local_options(lifecycle_verbosity = "quiet") + + expect_s3_class(as_duckplyr_tibble(tibble(a = 1)), "duckplyr_df") + expect_equal(class(as_duckplyr_tibble(tibble(a = 1))), c("duckplyr_df", class(tibble()))) + + expect_s3_class(as_duckplyr_tibble(data.frame(a = 1)), "duckplyr_df") + expect_equal(class(as_duckplyr_tibble(data.frame(a = 1))), c("duckplyr_df", class(tibble()))) +}) + +test_that("as_duckplyr_df() and special df", { + # https://github.com/tidyverse/duckplyr/issues/127 + + by_cyl <- dplyr::group_by(mtcars, cyl) + expect_snapshot(error = TRUE, { + as_duckplyr_df(by_cyl) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_tbl.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_tbl.R new file mode 100644 index 000000000..ea0806228 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-as_tbl.R @@ -0,0 +1,30 @@ +test_that("as_tbl()", { + skip_if_not_installed("dbplyr") + + df <- duckdb_tibble(a = 1L) + + tbl <- as_tbl(df) + expect_s3_class(tbl, "tbl_dbi") + expect_equal(collect(tbl), tibble(a = 1L)) + + sql <- dbplyr::sql_render(tbl) + + out <- + tbl %>% + mutate(b = sql("a + 1")) %>% + as_duckdb_tibble() + + expect_s3_class(out, "duckplyr_df") + expect_equal(head(out), duckdb_tibble(a = 1L, b = 2L)) + + # Test destruction of underlying view + expect_equal(DBI::dbGetQuery(get_default_duckdb_connection(), sql), data.frame(a = 1L)) + + rm(tbl) + gc() + + expect_error(DBI::dbGetQuery(get_default_duckdb_connection(), sql)) + # This is expected but perhaps undesirable. + # We could carry over the duckplyr_scope_guard to the relational object. + expect_error(collect(out)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute.R new file mode 100644 index 000000000..e475d928d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute.R @@ -0,0 +1,36 @@ +test_that("compute()", { + set.seed(20241230) + + df <- duckdb_tibble(x = c(1, 2)) + out <- compute(df) + expect_snapshot({ + duckdb_rel_from_df(out) + }) + + expect_identical(out, as_duckdb_tibble(df)) + expect_false(is_prudent_duckplyr_df(out)) +}) + +test_that("prudence with failure", { + set.seed(20250124) + + df <- duckdb_tibble(x = 1:10, .prudence = c(rows = 5)) + out <- compute(df) + + expect_identical(collect(out), collect(df)) + expect_identical(get_prudence_duckplyr_df(out), c(rows = 5)) + + expect_error(nrow(out)) +}) + +test_that("prudence with success", { + set.seed(20250126) + + df <- duckdb_tibble(x = 1:10, .prudence = c(rows = 20)) + out <- compute(df) + + expect_identical(collect(out), collect(df)) + expect_identical(get_prudence_duckplyr_df(out), c(rows = 20)) + + expect_error(nrow(out), NA) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_csv.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_csv.R new file mode 100644 index 000000000..06d05d0fc --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_csv.R @@ -0,0 +1,17 @@ +test_that("compute_csv()", { + df <- data.frame(x = c(1, 2)) + withr::defer(unlink("test.csv")) + out <- compute_csv(df, path = "test.csv") + + expect_identical(out, as_duckdb_tibble(df)) + expect_false(is_prudent_duckplyr_df(out)) +}) + +test_that("compute_csv() prudence", { + df <- data.frame(x = c(1, 2)) + withr::defer(unlink("test.csv")) + out <- compute_csv(df, path = "test.csv", prudence = "stingy") + + expect_true(is_prudent_duckplyr_df(out)) + expect_identical(collect(out), as_tibble(df)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_parquet.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_parquet.R new file mode 100644 index 000000000..9eef99a76 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-compute_parquet.R @@ -0,0 +1,18 @@ +test_that("compute_parquet()", { + df <- data.frame(x = c(1, 2)) + withr::defer(unlink("test.parquet")) + out <- compute_parquet(df, path = "test.parquet") + + expect_identical(out, as_duckdb_tibble(df)) + expect_false(is_prudent_duckplyr_df(out)) +}) + +test_that("compute_parquet() with options", { + df <- data.frame(x = c(1, 2), a = c("a", "b")) + withr::defer(unlink("test", recursive = TRUE)) + dir.create("test") + out <- compute_parquet(df, path = "test", options = list(partition_by = "a")) + + expect_identical(out, as_duckdb_tibble(df)) + expect_false(is_prudent_duckplyr_df(out)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-demo.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-demo.R new file mode 100644 index 000000000..2db155089 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-demo.R @@ -0,0 +1,83 @@ +test_that("demo queries work", { + skip_if_not_installed("lubridate") + + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + wday <- lubridate::wday + hour <- lubridate::hour + + expect_snapshot({ + tips_by_day_hour <- taxi_data_2019 %>% + filter(total_amount > 0) %>% + mutate(tip_pct = 100 * tip_amount / total_amount, dn = wday(pickup_datetime), hr = hour(pickup_datetime)) %>% + summarise( + avg_tip_pct = mean(tip_pct), + n = n(), + .by = c(dn, hr) + ) %>% + arrange(desc(avg_tip_pct)) + + tips_by_day_hour + }) + + expect_snapshot({ + tips_by_passenger <- taxi_data_2019 %>% + filter(total_amount > 0) %>% + mutate(tip_pct = 100 * tip_amount / total_amount) %>% + summarise( + avg_tip_pct = median(tip_pct), + n = n(), + .by = passenger_count + ) %>% + arrange(desc(passenger_count)) + + tips_by_passenger + }) + + expect_snapshot({ + popular_manhattan_cab_rides <- taxi_data_2019 %>% + filter(total_amount > 0) %>% + inner_join(zone_map, by = join_by(pickup_location_id == LocationID)) %>% + inner_join(zone_map, by = join_by(dropoff_location_id == LocationID)) %>% + filter(Borough.x == "Manhattan", Borough.y == "Manhattan") %>% + select(start_neighborhood = Zone.x, end_neighborhood = Zone.y) %>% + summarise( + num_trips = n(), + .by = c(start_neighborhood, end_neighborhood), + ) %>% + arrange(desc(num_trips)) + + popular_manhattan_cab_rides + }) + + expect_snapshot({ + num_trips_per_borough <- taxi_data_2019 %>% + filter(total_amount > 0) %>% + inner_join(zone_map, by = join_by(pickup_location_id == LocationID)) %>% + inner_join(zone_map, by = join_by(dropoff_location_id == LocationID)) %>% + mutate(pickup_borough = Borough.x, dropoff_borough = Borough.y) %>% + select(pickup_borough, dropoff_borough, tip_amount) %>% + summarise( + num_trips = n(), + .by = c(pickup_borough, dropoff_borough) + ) + + num_trips_per_borough_no_tip <- taxi_data_2019 %>% + filter(total_amount > 0, tip_amount == 0) %>% + inner_join(zone_map, by = join_by(pickup_location_id == LocationID)) %>% + inner_join(zone_map, by = join_by(dropoff_location_id == LocationID)) %>% + mutate(pickup_borough = Borough.x, dropoff_borough = Borough.y, tip_amount) %>% + summarise( + num_zero_tip_trips = n(), + .by = c(pickup_borough, dropoff_borough) + ) + + num_zero_percent_trips <- num_trips_per_borough %>% + inner_join(num_trips_per_borough_no_tip, by = join_by(pickup_borough, dropoff_borough)) %>% + mutate(num_trips = num_trips, percent_zero_tips_trips = 100 * num_zero_tip_trips / num_trips) %>% + select(pickup_borough, dropoff_borough, num_trips, percent_zero_tips_trips) %>% + arrange(desc(percent_zero_tips_trips)) + + num_zero_percent_trips + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-df.R new file mode 100644 index 000000000..4cbde6212 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-df.R @@ -0,0 +1,3 @@ +test_that("rel_to_df()", { + expect_equal(rel_to_df(rel_from_df(data.frame(a = 1))), data.frame(a = 1)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-across.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-across.R new file mode 100644 index 000000000..397b83b06 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-across.R @@ -0,0 +1,1499 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# across ------------------------------------------------------------------ + +test_that("across() works on one column data.frame", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1) + + out <- df %>% duckplyr_mutate(across(everything(), identity)) + expect_equal(out, df) +}) + +test_that("across() does not select grouping variables", { + df <- data.frame(g = 1, x = 1) + + out <- df %>% duckplyr_group_by(g) %>% duckplyr_summarise(x = across(everything(), identity)) %>% duckplyr_pull() + expect_equal(out, tibble(x = 1)) +}) + +test_that("across() correctly names output columns", { + gf <- tibble(x = 1, y = 2, z = 3, s = "") %>% duckplyr_group_by(x) + + expect_named( + duckplyr_summarise(gf, across(everything(), identity)), + c("x", "y", "z", "s") + ) + expect_named( + duckplyr_summarise(gf, across(everything(), identity, .names = "id_{.col}")), + c("x", "id_y", "id_z", "id_s") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), mean)), + c("x", "y", "z") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), mean, .names = "mean_{.col}")), + c("x", "mean_y", "mean_z") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), list(mean = mean, sum = sum))), + c("x", "y_mean", "y_sum", "z_mean", "z_sum") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), list(mean = mean, sum))), + c("x", "y_mean", "y_2", "z_mean", "z_2") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), list(mean, sum = sum))), + c("x", "y_1", "y_sum", "z_1", "z_sum") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), list(mean, sum))), + c("x", "y_1", "y_2", "z_1", "z_2") + ) + expect_named( + duckplyr_summarise(gf, across(where(is.numeric), list(mean = mean, sum = sum), .names = "{.fn}_{.col}")), + c("x", "mean_y", "sum_y", "mean_z", "sum_z") + ) +}) + +test_that("across(.unpack =) can unpack data frame columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + fn1 <- function(x) { + tibble(a = x, b = x + 1) + } + fn2 <- function(x) { + tibble(c = -x, d = x - 1) + } + + df <- tibble(x = 1:2, y = 3:4) + + out <- duckplyr_mutate(df, across(x:y, list(one = fn1, two = fn2), .unpack = TRUE)) + + expect <- tibble( + x = 1:2, + y = 3:4, + x_one_a = x, + x_one_b = x + 1, + x_two_c = -x, + x_two_d = x - 1, + y_one_a = y, + y_one_b = y + 1, + y_two_c = -y, + y_two_d = y - 1 + ) + + expect_identical(out, expect) +}) + +test_that("across(.unpack =) allows a glue specification for `.unpack`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + fn <- function(x) { + tibble(a = x, b = x + 1) + } + + df <- tibble(x = 1) + out <- duckplyr_mutate(df, across(x, fn, .unpack = "{outer}.{inner}")) + expect_named(out, c("x", "x.a", "x.b")) + + # Can use variables from caller env + out <- local({ + name <- "name" + duckplyr_mutate(df, across(x, fn, .unpack = "{name}.{inner}")) + }) + expect_named(out, c("x", "name.a", "name.b")) +}) + +test_that("across(.unpack =) skips unpacking non-df-cols", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + fn <- function(x) { + tibble(a = x, b = x + 1) + } + + df <- tibble(x = 1) + + out <- duckplyr_mutate(df, across(x, list(fn = fn, double = ~.x * 2), .unpack = TRUE)) + + expect <- tibble(x = 1, x_fn_a = 1, x_fn_b = 2, x_double = 2) + + expect_identical(out, expect) +}) + +test_that("across(.unpack =) uses the result of `.names` as `{outer}`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + fn <- function(x) { + tibble(a = x, b = x + 1) + } + + df <- tibble(x = 1, y = 2) + + out <- df %>% duckplyr_mutate( + across(x:y, list(f = fn), .names = "{.col}.{.fn}", .unpack = "{inner}.{outer}") + ) + + expect_named(out, c("x", "y", "a.x.f", "b.x.f", "a.y.f", "b.y.f")) +}) + +test_that("across(.unpack =) errors if the unpacked data frame has non-unique names", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + fn <- function(x) { + tibble(a = x, b = x) + } + + df <- tibble(x = 1, y = 2) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, across(x:y, fn, .unpack = "{outer}")) + }) +}) + +test_that("`.unpack` is validated", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, across(x, mean, .unpack = 1)) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, across(x, mean, .unpack = c("x", "y"))) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, across(x, mean, .unpack = NA)) + }) +}) + +test_that("across() result locations are aligned with column names (#4967)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2, y = c("a", "b")) + expect <- tibble(x_cls = "integer", x_type = TRUE, y_cls = "character", y_type = FALSE) + + x <- duckplyr_summarise(df, across(everything(), list(cls = class, type = is.numeric))) + + expect_identical(x, expect) +}) + + +test_that("across() works sequentially (#4907)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1) + expect_equal( + duckplyr_mutate(df, x = df_n_col(across(where(is.numeric), identity)), y = df_n_col(across(where(is.numeric), identity))), + tibble(a = 1, x = 1L, y = 2L) + ) + expect_equal( + duckplyr_mutate(df, a = "x", y = df_n_col(across(where(is.numeric), identity))), + tibble(a = "x", y = 0L) + ) + expect_equal( + duckplyr_mutate(df, x = 1, y = df_n_col(across(where(is.numeric), identity))), + tibble(a = 1, x = 1, y = 2L) + ) +}) + +test_that("across() retains original ordering", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1, b = 2) + expect_named(duckplyr_mutate(df, a = 2, x = across(everything(), identity))$x, c("a", "b")) +}) + +test_that("across() throws meaningful error with failure during expansion (#6534)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = 1, x = 1, y = 2, z = 3) + gdf <- duckplyr_group_by(df, g) + + fn <- function() { + stop("oh no!") + } + + # Ends up failing inside the `fn()` call, which gets evaluated + # during `across()` expansion but outside any group context + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, across(everything(), fn())) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, across(everything(), fn()), .by = g) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(gdf, across(everything(), fn())) + }) +}) + +test_that("across() gives meaningful messages", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot({ + # expanding + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(across(where(is.numeric), 42)) + )) + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(across(y, mean)) + )) + + # computing + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(res = across(where(is.numeric), 42)) + )) + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(z = across(y, mean)) + )) + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(res = sum(if_any(where(is.numeric), 42))) + )) + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(res = sum(if_all(~mean(.x)))) + )) + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(res = sum(if_any(~mean(.x)))) + )) + + (expect_error(across())) + (expect_error(c_across())) + + # problem while computing + error_fn <- function(.) { + if (all(. > 10)) { + rlang::abort("too small", call = call("error_fn")) + } else { + 42 + } + } + (expect_error( # expanding + tibble(x = 1:10, y = 11:20) %>% + duckplyr_summarise(across(everything(), error_fn)) + )) + (expect_error( # expanding + tibble(x = 1:10, y = 11:20) %>% + duckplyr_mutate(across(everything(), error_fn)) + )) + + (expect_error( # evaluating + tibble(x = 1:10, y = 11:20) %>% + duckplyr_summarise(force(across(everything(), error_fn))) + )) + (expect_error( # evaluating + tibble(x = 1:10, y = 11:20) %>% + duckplyr_mutate(force(across(everything(), error_fn))) + )) + + # name issue + (expect_error( + tibble(x = 1) %>% + duckplyr_summarise(across(everything(), list(f = mean, f = mean))) + )) + + }) + +}) + +test_that("monitoring cache - across() can be used twice in the same expression", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1, b = 2) + expect_equal( + duckplyr_mutate(df, x = df_n_col(across(where(is.numeric), identity)) + df_n_col(across(a, identity))), + tibble(a = 1, b = 2, x = 3) + ) +}) + +test_that("monitoring cache - across() can be used in separate expressions", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1, b = 2) + expect_equal( + duckplyr_mutate(df, x = df_n_col(across(where(is.numeric), identity)), y = df_n_col(across(a, identity))), + tibble(a = 1, b = 2, x = 2, y = 1) + ) +}) + +test_that("monitoring cache - across() usage can depend on the group id", { + df <- tibble(g = 1:2, a = 1:2, b = 3:4) + df <- duckplyr_group_by(df, g) + + switcher <- function() { + if_else(cur_group_id() == 1L, across(a, identity)$a, across(b, identity)$b) + } + + expect <- df + expect$x <- c(1L, 4L) + + expect_equal( + duckplyr_mutate(df, x = switcher()), + expect + ) +}) + +test_that("monitoring cache - across() internal cache key depends on all inputs", { + df <- tibble(g = rep(1:2, each = 2), a = 1:4) + df <- duckplyr_group_by(df, g) + + expect_identical( + duckplyr_mutate(df, tibble(x = across(where(is.numeric), mean)$a, y = across(where(is.numeric), max)$a)), + duckplyr_mutate(df, x = mean(a), y = max(a)) + ) +}) + +test_that("across() rejects non vectors", { + expect_error( + data.frame(x = 1) %>% duckplyr_summarise(across(everything(), ~sym("foo"))) + ) +}) + +test_that("across() uses tidy recycling rules", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_equal( + data.frame(x = 1, y = 2) %>% duckplyr_reframe(across(everything(), ~rep(42, .))), + data.frame(x = rep(42, 2), y = rep(42, 2)) + ) + + expect_error( + data.frame(x = 2, y = 3) %>% duckplyr_reframe(across(everything(), ~rep(42, .))) + ) +}) + +test_that("across() returns a data frame with 1 row (#5204)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:42) + expect_equal( + duckplyr_mutate(df, across(c(), as.factor)), + df + ) + expect_equal( + duckplyr_mutate(df, y = across(c(), as.factor))$y, + tibble::new_tibble(list(), nrow = 42) + ) + duckplyr_mutate(df, { + res <- across(c(), as.factor) + expect_equal(nrow(res), 1L) + res + }) +}) + +test_that("across(.names=) can use local variables in addition to {col} and {fn}", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + res <- local({ + prefix <- "MEAN" + data.frame(x = 42) %>% + duckplyr_summarise(across(everything(), mean, .names = "{prefix}_{.col}")) + }) + expect_identical(res, data.frame(MEAN_x = 42)) +}) + +test_that("across(.unpack=) can use local variables in addition to {outer} and {inner}", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + fn <- function(x) { + tibble(x = x, y = x + 1) + } + + res <- local({ + prefix <- "FN" + data.frame(col1 = 42, col2 = 24) %>% + duckplyr_summarise(across(everything(), fn, .unpack = "{prefix}_{outer}_{inner}")) + }) + + expect_identical( + res, + data.frame( + FN_col1_x = 42, FN_col1_y = 43, + FN_col2_x = 24, FN_col2_y = 25 + ) + ) +}) + +test_that("across() uses environment from the current quosure (#5460)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # If the data frame `y` is selected, causes a subscript conversion + # error since it is fractional + df <- data.frame(x = 1, y = 2.4) + y <- "x" + expect_equal(df %>% duckplyr_summarise(across(all_of(y), mean)), data.frame(x = 1)) + expect_equal(df %>% duckplyr_mutate(across(all_of(y), mean)), df) + expect_equal(df %>% duckplyr_filter(if_all(all_of(y), ~ .x < 2)), df) + + # Inherited case + expect_error(df %>% duckplyr_summarise(local(across(all_of(y), mean)))) + + expect_equal( + df %>% duckplyr_summarise(duckplyr_summarise(pick(everything()), across(all_of(y), mean))), + df %>% duckplyr_summarise(across(all_of(y), mean)) + ) +}) + +test_that("across() sees columns in the recursive case (#5498)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if_not_installed("purrr") + df <- tibble( + vars = list("foo"), + data = list(data.frame(foo = 1, bar = 2)) + ) + + out <- df %>% duckplyr_mutate(data = purrr::map2(data, vars, ~ { + .x %>% duckplyr_mutate(across(all_of(.y), ~ NA)) + })) + exp <- tibble( + vars = list("foo"), + data = list(data.frame(foo = NA, bar = 2)) + ) + expect_identical(out, exp) + + out <- df %>% duckplyr_mutate(data = purrr::map2(data, vars, ~ { + local({ + .y <- "bar" + .x %>% duckplyr_mutate(across(all_of(.y), ~ NA)) + }) + })) + exp <- tibble( + vars = list("foo"), + data = list(data.frame(foo = 1, bar = NA)) + ) + expect_identical(out, exp) +}) + +test_that("across() works with empty data frames (#5523)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_equal( + duckplyr_mutate(tibble(), across(everything(), identity)), + tibble() + ) +}) + +test_that("lambdas in duckplyr_mutate() + across() can use columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2, y = 4, z = 8) + expect_identical( + df %>% duckplyr_mutate(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(across(everything(), ~ .x / y)) + ) + expect_identical( + df %>% duckplyr_mutate(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(+across(everything(), ~ .x / y)) + ) + + expect_identical( + df %>% duckplyr_mutate(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(across(everything(), ~ .x / .data$y)) + ) + expect_identical( + df %>% duckplyr_mutate(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(+across(everything(), ~ .x / .data$y)) + ) +}) + +test_that("lambdas in duckplyr_summarise() + across() can use columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2, y = 4, z = 8) + expect_identical( + df %>% duckplyr_summarise(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(across(everything(), ~ .x / y)) + ) + expect_identical( + df %>% duckplyr_summarise(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(+across(everything(), ~ .x / y)) + ) + + expect_identical( + df %>% duckplyr_summarise(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(across(everything(), ~ .x / .data$y)) + ) + expect_identical( + df %>% duckplyr_summarise(data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(+across(everything(), ~ .x / .data$y)) + ) +}) + +test_that("lambdas in duckplyr_mutate() + across() can use columns in follow up expressions (#5717)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2, y = 4, z = 8) + expect_identical( + df %>% duckplyr_mutate(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(a = 2, across(c(x, y, z), ~ .x / y)) + ) + expect_identical( + df %>% duckplyr_mutate(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(a = 2, +across(c(x, y, z), ~ .x / y)) + ) + + expect_identical( + df %>% duckplyr_mutate(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(a = 2, across(c(x, y, z), ~ .x / .data$y)) + ) + expect_identical( + df %>% duckplyr_mutate(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_mutate(a = 2, +across(c(x, y, z), ~ .x / .data$y)) + ) +}) + +test_that("lambdas in duckplyr_summarise() + across() can use columns in follow up expressions (#5717)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2, y = 4, z = 8) + expect_identical( + df %>% duckplyr_summarise(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(a = 2, across(c(x, y, z), ~ .x / y)) + ) + expect_identical( + df %>% duckplyr_summarise(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(a = 2, +across(c(x, y, z), ~ .x / y)) + ) + + expect_identical( + df %>% duckplyr_summarise(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(a = 2, across(c(x, y, z), ~ .x / .data$y)) + ) + expect_identical( + df %>% duckplyr_summarise(a = 2, data.frame(x = x / y, y = y / y, z = z / y)), + df %>% duckplyr_summarise(a = 2, +across(c(x, y, z), ~ .x / .data$y)) + ) +}) + +test_that("functions defined inline can use columns (#5734)", { + df <- data.frame(x = 1, y = 2) + expect_equal( + df %>% duckplyr_mutate(across('x', function(.x) .x / y)) %>% duckplyr_pull(x), + 0.5 + ) +}) + +test_that("if_any() and if_all() do not enforce logical", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # We used to coerce to logical using vctrs. Now we use base + # semantics because we expand `if_all(x:y)` to `x & y`. + d <- data.frame(x = 10, y = 10) + expect_equal(duckplyr_filter(d, if_all(x:y, identity)), d) + expect_equal(duckplyr_filter(d, if_any(x:y, identity)), d) + + expect_equal( + duckplyr_mutate(d, ok = if_any(x:y, identity)), + duckplyr_mutate(d, ok = TRUE) + ) + expect_equal( + duckplyr_mutate(d, ok = if_all(x:y, identity)), + duckplyr_mutate(d, ok = TRUE) + ) +}) + +test_that("if_any() and if_all() can be used in duckplyr_mutate() (#5709)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + d <- data.frame(x = c(1, 5, 10, 10), y = c(0, 0, 0, 10), z = c(10, 5, 1, 10)) + res <- d %>% + duckplyr_mutate( + any = if_any(x:z, ~ . > 8), + all = if_all(x:z, ~ . > 8) + ) + expect_equal(res$any, c(TRUE, FALSE, TRUE, TRUE)) + expect_equal(res$all, c(FALSE, FALSE, FALSE, TRUE)) +}) + +test_that("across() caching not confused when used from if_any() and if_all() (#5782)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + res <- data.frame(x = 1:3) %>% + duckplyr_mutate( + any = if_any(x, ~ . >= 2) + if_any(x, ~ . >= 3), + all = if_all(x, ~ . >= 2) + if_all(x, ~ . >= 3) + ) + expect_equal(res$any, c(0, 1, 2)) + expect_equal(res$all, c(0, 1, 2)) +}) + +test_that("if_any() and if_all() respect duckplyr_filter()-like NA handling", { + df <- expand.grid( + x = c(TRUE, FALSE, NA), y = c(TRUE, FALSE, NA) + ) + expect_identical( + duckplyr_filter(df, x & y), + duckplyr_filter(df, if_all(c(x,y), identity)) + ) + expect_identical( + duckplyr_filter(df, x | y), + duckplyr_filter(df, if_any(c(x,y), identity)) + ) +}) + +test_that("if_any() and if_all() aborts when predicate mistakingly used in .cols= (#5732)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1:10, y = 1:10) + expect_snapshot({ + # expanded case + (expect_error(duckplyr_filter(df, if_any(~ .x > 5)))) + (expect_error(duckplyr_filter(df, if_all(~ .x > 5)))) + + # non expanded case + (expect_error(duckplyr_filter(df, !if_any(~ .x > 5)))) + (expect_error(duckplyr_filter(df, !if_all(~ .x > 5)))) + }) +}) + +test_that("across() correctly reset column", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_error(cur_column()) + res <- data.frame(x = 1) %>% + duckplyr_summarise( + a = { expect_error(cur_column()); 2}, + across(x, ~{ expect_equal(cur_column(), "x"); 3}, .names = "b"), # top_across() + c = { expect_error(cur_column()); 4}, + force(across(x, ~{ expect_equal(cur_column(), "x"); 5}, .names = "d")), # across() + e = { expect_error(cur_column()); 6} + ) + expect_equal(res, data.frame(a = 2, b = 3, c = 4, d = 5, e = 6)) + expect_error(cur_column()) + + res <- data.frame(x = 1) %>% + duckplyr_mutate( + a = { expect_error(cur_column()); 2}, + across(x, ~{ expect_equal(cur_column(), "x"); 3}, .names = "b"), # top_across() + c = { expect_error(cur_column()); 4}, + force(across(x, ~{ expect_equal(cur_column(), "x"); 5}, .names = "d")), # across() + e = { expect_error(cur_column()); 6} + ) + expect_equal(res, data.frame(x = 1, a = 2, b = 3, c = 4, d = 5, e = 6)) + expect_error(cur_column()) +}) + +test_that("across() can omit dots", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = tibble(foo = 1), y = tibble(foo = 2)) + + # top + res <- duckplyr_mutate(df, across( + everything(), + list + )) + expect_equal(res$x[[1]]$foo, 1) + expect_equal(res$y[[1]]$foo, 2) + + # not top + res <- duckplyr_mutate(df, force(across( + everything(), + list + ))) + expect_equal(res$x[[1]]$foo, 1) + expect_equal(res$y[[1]]$foo, 2) +}) + +test_that("group variables are in scope (#5832)", { + f <- function(x, z) x + z + gdf <- data.frame(x = 1:2, y = 3:4, g = 1:2) %>% duckplyr_group_by(g) + exp <- gdf %>% duckplyr_summarise(x = f(x, z = y)) + + expect_equal( + gdf %>% duckplyr_summarise(across(x, ~ f(.x, z = y))), + exp + ) + + expect_equal( + gdf %>% duckplyr_summarise(across(x, ~ f(.x, z = y))), + exp + ) +}) + +test_that("can pass quosure through `across()`", { + summarise_mean <- function(data, vars) { + data %>% duckplyr_summarise(across({{ vars }}, mean)) + } + gdf <- data.frame(g = c(1, 1, 2), x = 1:3) %>% duckplyr_group_by(g) + + expect_equal( + gdf %>% summarise_mean(where(is.numeric)), + duckplyr_summarise(gdf, x = mean(x)) + ) +}) + +test_that("across() inlines formulas", { + # Env of captured quosure passed to `as_across_fn_call()`. The + # unevaluated lambdas should inherit from that env after inlining. + env <- env() + + lambda <- quo_eval_fns(quo(function(x) fn(x)), mask = env) + out <- as_across_fn_call(lambda, quote(var), env, env) + expect_equal(out, new_quosure(quote(fn(var)), env)) + + formula <- quo_eval_fns(quo(~ fn(.x)), mask = env) + out <- as_across_fn_call(formula, quote(var), env, env) + expect_equal(out, new_quosure(quote(fn(var)), env)) + + # Evaluated formulas preserve their own env + f <- local(~ fn(.x)) + fn <- quo_eval_fns(quo(!!f), mask = env) + out <- as_across_fn_call(fn, quote(var), env, env) + expect_equal(get_env(f), get_env(fn)) + expect_equal(out, new_quosure(call2(fn, quote(var)), env)) + + # Inlining is disabled for complex lambda calls + fn <- quo_eval_fns(quo(function(x, y) x), mask = env) + out <- as_across_fn_call(fn, quote(var), env, env) + expect_equal(out, new_quosure(call2(fn, quote(var)), env)) + + # Formulas are converted to functions + expect_rlang_lambda <- function(fn) { + expect_s3_class(fn, "rlang_lambda_function") + out <- as_across_fn_call(fn, quote(var), env, env) + expect_equal(out, new_quosure(call2(fn, quote(var)), env)) + } + + out <- quo_eval_fns(quo(~ .y), mask = env) + expect_rlang_lambda(out) + + out <- quo_eval_fns(quo(list(~ .y)), mask = env) + expect_type(out, "list") + map(out, expect_rlang_lambda) + + # All formula-lambda arguments are interpolated + fn <- quo_eval_fns(quo(~ list(.x, ., .x)), mask = env) + out <- as_across_fn_call(fn, quote(var), env, env) + expect_equal( + out, + new_quosure(quote(list(var, var, var)), f_env(f)) + ) +}) + +test_that("inlined and non inlined lambdas work", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(foo = 1:2, bar = 100:101) + exp <- data.frame(foo = c(101.5, 102.5), bar = c(200.5, 201.5)) + + expect_equal(df %>% duckplyr_mutate(across(1:2, function(x) x + mean(bar))), exp) + expect_equal(df %>% duckplyr_mutate((across(1:2, function(x) x + mean(bar)))), exp) + + expect_equal(df %>% duckplyr_mutate(across(1:2, ~ .x + mean(bar))), exp) + expect_equal(df %>% duckplyr_mutate((across(1:2, ~ .x + mean(bar)))), exp) + + expect_equal(df %>% duckplyr_mutate(across(1:2, ~ ..1 + mean(bar))), exp) + expect_equal(df %>% duckplyr_mutate((across(1:2, ~ ..1 + mean(bar)))), exp) + + # Message generated by base R changed + skip_if_not_installed("base", "3.6.0") + expect_snapshot({ + (expect_error(df %>% duckplyr_mutate(across(1:2, ~ .y + mean(bar))))) + (expect_error(df %>% duckplyr_mutate((across(1:2, ~ .y + mean(bar)))))) + }) +}) + +test_that("list of lambdas work", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(foo = 1:2, bar = 100:101) + exp <- cbind( + df, + data.frame(foo_1 = c(101.5, 102.5), bar_1 = c(200.5, 201.5)) + ) + + expect_equal(df %>% duckplyr_mutate(across(1:2, list(function(x) x + mean(bar)))), exp) + expect_equal(df %>% duckplyr_mutate((across(1:2, list(function(x) x + mean(bar))))), exp) + + expect_equal(df %>% duckplyr_mutate(across(1:2, list(~ .x + mean(bar)))), exp) + expect_equal(df %>% duckplyr_mutate((across(1:2, list(~ .x + mean(bar))))), exp) +}) + +test_that("anonymous function `.fns` can access the `.data` pronoun even when not inlined", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2, y = 3:4) + + # Can't access it here, `fn()`'s environment doesn't know about `.data` + fn <- function(col) { + .data[["x"]] + } + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, across(y, fn)) + }) + + # Can access it with inlinable quosures + out <- duckplyr_mutate(df, across(y, function(col) { + .data[["x"]] + })) + expect_identical(out$y, out$x) + + # Can access it with non-inlinable quosures + out <- duckplyr_mutate(df, across(y, function(col) { + return(.data[["x"]]) + })) + expect_identical(out$y, out$x) +}) + +test_that("across() uses local formula environment (#5881)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + f <- local({ + prefix <- "foo" + ~ paste(prefix, .x) + }) + df <- tibble(x = "x") + expect_equal( + duckplyr_mutate(df, across(x, f)), + tibble(x = "foo x") + ) + expect_equal( + duckplyr_mutate(df, across(x, list(f = f))), + tibble(x = "x", x_f = "foo x") + ) + + local({ + # local() here is not necessary, it's just in case the + # code is run directly without the test_that() + prefix <- "foo" + expect_equal( + duckplyr_mutate(df, across(x, ~paste(prefix, .x))), + tibble(x = "foo x") + ) + expect_equal( + duckplyr_mutate(df, across(x, list(f = ~paste(prefix, .x)))), + tibble(x = "x", x_f = "foo x") + ) + }) + + expect_equal( + data.frame(x = 1) %>% duckplyr_mutate(across(1, list(f = local(~ . + 1)))), + data.frame(x = 1, x_f = 2) + ) + + expect_equal( + data.frame(x = 1) %>% duckplyr_mutate(across(1, local({ + `_local_var` <- 1 + ~ . + `_local_var` + }))), + data.frame(x = 2) + ) +}) + +test_that("unevaluated formulas (currently) fail", { + df <- tibble(x = "x") + expect_error( + duckplyr_mutate(df, across(x, quote(~ paste("foo", .x)))) + ) +}) + +test_that("across() can access lexical scope (#5862)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + f_across <- function(data, cols, fn) { + data %>% + duckplyr_summarise( + across({{ cols }}, fn) + ) + } + + df <- data.frame(x = 1:10, y = 1:10) + expect_equal( + f_across(df, c(x, y), mean), + duckplyr_summarise(df, across(c(x, y), mean)) + ) +}) + +test_that("across() allows renaming in `.cols` (#6895)", { + df <- tibble(x = 1, y = 2, z = 3) + cols <- set_names(c("x", "y"), c("a", "b")) + + expect_identical( + duckplyr_mutate(df, across(all_of(cols), identity)), + duckplyr_mutate(df, a = x, b = y) + ) + expect_identical( + duckplyr_mutate(df, (across(all_of(cols), identity))), + duckplyr_mutate(df, a = x, b = y) + ) + + expect_identical( + duckplyr_mutate(df, across(all_of(cols), identity, .names = "{.col}_name")), + duckplyr_mutate(df, a_name = x, b_name = y) + ) + expect_identical( + duckplyr_mutate(df, (across(all_of(cols), identity, .names = "{.col}_name"))), + duckplyr_mutate(df, a_name = x, b_name = y) + ) +}) + +test_that("if_any() and if_all() expansions deal with no inputs or single inputs", { + skip("TODO duckdb") + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + d <- data.frame(x = 1) + + # No inputs + expect_equal( + duckplyr_filter(d, if_any(starts_with("c"), ~ FALSE)), + duckplyr_filter(d, FALSE) + ) + expect_equal( + duckplyr_filter(d, if_all(starts_with("c"), ~ FALSE)), + duckplyr_filter(d) + ) + + # Single inputs + expect_equal( + duckplyr_filter(d, if_any(x, ~ FALSE)), + duckplyr_filter(d, FALSE) + ) + expect_equal( + duckplyr_filter(d, if_all(x, ~ FALSE)), + duckplyr_filter(d, FALSE) + ) +}) + +test_that("if_any() on zero-column selection behaves like any() (#7059)", { + skip("TODO duckdb") + tbl <- tibble( + x1 = 1:5, + x2 = c(-1, 4, 5, 4, 1), + y = c(1, 4, 2, 4, 9), + ) + + expect_equal( + duckplyr_filter(tbl, if_any(c(), ~ is.na(.x))), + tbl[0, ] + ) +}) + +test_that("if_all() on zero-column selection behaves like all() (#7059)", { + tbl <- tibble( + x1 = 1:5, + x2 = c(-1, 4, 5, 4, 1), + y = c(1, 4, 2, 4, 9), + ) + + expect_equal( + duckplyr_filter(tbl, if_all(c(), ~ is.na(.x))), + tbl + ) +}) + +test_that("if_any() and if_all() wrapped deal with no inputs or single inputs", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + d <- data.frame(x = 1) + + # No inputs + expect_equal( + duckplyr_filter(d, (if_any(starts_with("c"), ~ FALSE))), + duckplyr_filter(d) + ) + expect_equal( + duckplyr_filter(d, (if_all(starts_with("c"), ~ FALSE))), + duckplyr_filter(d) + ) + + # Single inputs + expect_equal( + duckplyr_filter(d, (if_any(x, ~ FALSE))), + duckplyr_filter(d, FALSE) + ) + expect_equal( + duckplyr_filter(d, (if_all(x, ~ FALSE))), + duckplyr_filter(d, FALSE) + ) +}) + +test_that("expanded if_any() finds local data", { + limit <- 7 + df <- data.frame(x = 1:10, y = 10:1) + + expect_identical( + duckplyr_filter(df, if_any(everything(), ~ .x > limit)), + duckplyr_filter(df, x > limit | y > limit) + ) +}) + +test_that("across() can use named selections", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1, y = 2) + + # no fns + expect_equal( + df %>% duckplyr_summarise(across(c(a = x, b = y))), + data.frame(a = 1, b = 2) + ) + expect_equal( + df %>% duckplyr_summarise(across(all_of(c(a = "x", b = "y")))), + data.frame(a = 1, b = 2) + ) + + # no fns, non expanded + expect_equal( + df %>% duckplyr_summarise((across(c(a = x, b = y)))), + data.frame(a = 1, b = 2) + ) + expect_equal( + df %>% duckplyr_summarise((across(all_of(c(a = "x", b = "y"))))), + data.frame(a = 1, b = 2) + ) + + # one fn + expect_equal( + df %>% duckplyr_summarise(across(c(a = x, b = y), mean)), + data.frame(a = 1, b = 2) + ) + expect_equal( + df %>% duckplyr_summarise(across(all_of(c(a = "x", b = "y")), mean)), + data.frame(a = 1, b = 2) + ) + + # one fn - non expanded + expect_equal( + df %>% duckplyr_summarise((across(c(a = x, b = y), mean))), + data.frame(a = 1, b = 2) + ) + expect_equal( + df %>% duckplyr_summarise((across(all_of(c(a = "x", b = "y")), mean))), + data.frame(a = 1, b = 2) + ) + + # multiple fns + expect_equal( + df %>% duckplyr_summarise(across(c(a = x, b = y), list(mean = mean, sum = sum))), + data.frame(a_mean = 1, a_sum = 1, b_mean = 2, b_sum = 2) + ) + expect_equal( + df %>% duckplyr_summarise(across(all_of(c(a = "x", b = "y")), list(mean = mean, sum = sum))), + data.frame(a_mean = 1, a_sum = 1, b_mean = 2, b_sum = 2) + ) + + # multiple fns - non expanded + expect_equal( + df %>% duckplyr_summarise((across(c(a = x, b = y), list(mean = mean, sum = sum)))), + data.frame(a_mean = 1, a_sum = 1, b_mean = 2, b_sum = 2) + ) + expect_equal( + df %>% duckplyr_summarise((across(all_of(c(a = "x", b = "y")), list(mean = mean, sum = sum)))), + data.frame(a_mean = 1, a_sum = 1, b_mean = 2, b_sum = 2) + ) +}) + +test_that("expr_subtitute() stops at lambdas (#5896)", { + expect_identical( + expr_substitute(expr(map(.x, ~mean(.x))), quote(.x), quote(a)), + expr(map(a, ~mean(.x))) + ) + expect_identical( + expr_substitute(expr(map(.x, function(.x) mean(.x))), quote(.x), quote(a)), + expr(map(a, function(.x) mean(.x))) + ) +}) + +test_that("expr_subtitute() keeps at double-sided formula (#5894)", { + expect_identical( + expr_substitute(expr(case_when(.x < 5 ~ 5, .default = .x)), quote(.x), quote(a)), + expr(case_when(a < 5 ~ 5, .default = a)) + ) + + expect_identical( + expr_substitute(expr(case_when(. < 5 ~ 5, .default = .)), quote(.), quote(a)), + expr(case_when(a < 5 ~ 5, .default = a)) + ) +}) + +test_that("across() predicates operate on whole data", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble( + x = c(1, 1, 2), + g = c(1, 1, 2) + ) + + out <- df %>% + duckplyr_mutate(across(where(~ n_distinct(.x) > 1), ~ .x + 10)) + + exp <- tibble( + x = c(11, 11, 12), + g = c(11, 11, 12) + ) + + expect_equal(out, exp) + + + out <- df %>% + duckplyr_group_by(g) %>% + duckplyr_mutate(across(where(~ n_distinct(.x) > 1), ~ .x + 10)) + + exp <- tibble( + x = c(11, 11, 12), + g = c(1, 1, 2) + ) %>% + duckplyr_group_by(g) + + expect_equal(out, exp) +}) + +test_that("expand_across() expands lambdas", { + quo <- quo(across(c(cyl, am), ~ identity(.x))) + quo <- new_dplyr_quosure( + quo, + name = quo, + is_named = FALSE, + index = 1 + ) + + by <- compute_by(by = NULL, data = mtcars, error_call = call("caller")) + DataMask$new(mtcars, by, "mutate", call("caller")) + + expect_equal( + map(expand_across(quo), quo_get_expr), + exprs( + cyl = identity(cyl), + am = identity(am) + ) + ) +}) + +test_that("expand_if_across() expands lambdas", { + quo <- quo(if_any(c(cyl, am), ~ . > 4)) + quo <- new_dplyr_quosure( + quo, + name = quo, + is_named = FALSE, + index = 1 + ) + + by <- compute_by(by = NULL, data = mtcars, error_call = call("caller")) + DataMask$new(mtcars, by, "mutate", call("caller")) + + expect_equal( + map(expand_if_across(quo), quo_squash), + alist(`|`(cyl > 4, am > 4)) + ) +}) + +test_that("duckplyr_rowwise() preserves list-cols iff no `.fns` (#5951, #6264)", { + # TODO: Deprecate this behavior in favor of `pick()`, which doesn't preserve + # list-cols but is well-defined as pure macro expansion. + + rf <- duckplyr_rowwise(tibble(x = list(1:2, 3:5))) + + # Need to unchop so works like duckplyr_mutate(rf, x = length(x)) + out <- duckplyr_mutate(rf, across(everything(), length)) + expect_equal(out$x, c(2, 3)) + + # Need to preserve to create valid data frame + out <- duckplyr_mutate(rf, across = list(across(everything()))) + expect_equal(out$across, list( + tibble(x = list(1:2)), + tibble(x = list(3:5)) + )) +}) + +# c_across ---------------------------------------------------------------- + +test_that("selects and combines columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1:2, y = 3:4) + out <- df %>% duckplyr_summarise(z = list(c_across(x:y))) + expect_equal(out$z, list(1:4)) +}) + +test_that("can't rename during selection (#6522)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, z = c_across(c(y = x))) + }) +}) + +test_that("can't explicitly select grouping columns (#6522)", { + # Related to removing the mask layer from the quosure environments + df <- tibble(g = 1, x = 2) + gdf <- duckplyr_group_by(df, g) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, y = c_across(g)) + }) +}) + +test_that("`all_of()` is evaluated in the correct environment (#6522)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # Related to removing the mask layer from the quosure environments + df <- tibble(x = 1, y = 2) + + # We expect an "object not found" error, but we don't control that + # so we aren't going to snapshot it, especially since the call reported + # by those kinds of errors changed in R 4.3. + expect_error(duckplyr_mutate(df, z = c_across(all_of(y)))) + + y <- "x" + expect <- df[["x"]] + + out <- duckplyr_mutate(df, z = c_across(all_of(y))) + expect_identical(out$z, expect) +}) + +# cols deprecation -------------------------------------------------------- + +test_that("across() applies old `.cols = everything()` default with a warning", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "warning") + + df <- tibble(g = c(1, 2), x = c(1, 2), y = c(3, 4)) + gdf <- duckplyr_group_by(df, g) + + times_two <- function(x) x * 2 + + # Expansion path + expect_snapshot(out <- duckplyr_mutate(df, across(.fns = times_two))) + expect_identical(out$g, df$g * 2) + expect_identical(out$x, df$x * 2) + expect_identical(out$y, df$y * 2) + expect_snapshot(out <- duckplyr_mutate(gdf, across(.fns = times_two))) + expect_identical(out$g, df$g) + expect_identical(out$x, df$x * 2) + expect_identical(out$y, df$y * 2) + + # Evaluation path + expect_snapshot(out <- duckplyr_mutate(df, (across(.fns = times_two)))) + expect_identical(out$g, df$g * 2) + expect_identical(out$x, df$x * 2) + expect_identical(out$y, df$y * 2) + expect_snapshot(out <- duckplyr_mutate(gdf, (across(.fns = times_two)))) + expect_identical(out$g, df$g) + expect_identical(out$x, df$x * 2) + expect_identical(out$y, df$y * 2) +}) + +test_that("if_any() and if_all() apply old `.cols = everything()` default with a warning", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "warning") + + df <- tibble(x = c(TRUE, FALSE, TRUE), y = c(FALSE, FALSE, TRUE)) + gdf <- duckplyr_mutate(df, g = c(1, 1, 2), .before = 1) + gdf <- duckplyr_group_by(gdf, g) + + # Expansion path + expect_snapshot(out <- duckplyr_filter(df, if_any())) + expect_identical(out, df[c(1, 3),]) + expect_snapshot(out <- duckplyr_filter(gdf, if_any())) + expect_identical(out, gdf[c(1, 3),]) + + expect_snapshot(out <- duckplyr_filter(df, if_all())) + expect_identical(out, df[3,]) + expect_snapshot(out <- duckplyr_filter(gdf, if_all())) + expect_identical(out, gdf[3,]) + + # Evaluation path + expect_snapshot(out <- duckplyr_filter(df, (if_any()))) + expect_identical(out, df[c(1, 3),]) + expect_snapshot(out <- duckplyr_filter(gdf, (if_any()))) + expect_identical(out, gdf[c(1, 3),]) + + expect_snapshot(out <- duckplyr_filter(df, (if_all()))) + expect_identical(out, df[3,]) + expect_snapshot(out <- duckplyr_filter(gdf, (if_all()))) + expect_identical(out, gdf[3,]) +}) + +test_that("c_across() applies old `cols = everything()` default with a warning", { + local_options(lifecycle_verbosity = "warning") + + df <- tibble(x = c(1, 3), y = c(2, 4)) + df <- duckplyr_rowwise(df) + + # Will see 2 warnings because verbosity option forces it to warn every time + expect_snapshot(out <- duckplyr_mutate(df, z = sum(c_across()))) + expect_identical(out$z, c(3, 7)) +}) + +# fns deprecation --------------------------------------------------------- + +test_that("across() applies old `.fns = NULL` default", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + + # Expansion path + out <- duckplyr_mutate(df, z = across(everything())) + expect_identical(out$z, df) + + # Evaluation path + out <- duckplyr_mutate(df, z = (across(everything()))) + expect_identical(out$z, df) +}) + +test_that("if_any() and if_all() apply old `.fns = NULL` default", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = c(TRUE, FALSE, TRUE), y = c(FALSE, FALSE, TRUE)) + + # Expansion path + expect_identical(duckplyr_filter(df, if_any(everything())), df[c(1, 3),]) + expect_identical(duckplyr_filter(df, if_all(everything())), df[3,]) + + # Evaluation path + expect_identical(duckplyr_filter(df, (if_any(everything()))), df[c(1, 3),]) + expect_identical(duckplyr_filter(df, (if_all(everything()))), df[3,]) +}) + +test_that("across errors with non-empty dots and no `.fns` supplied (#6638)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + + expect_snapshot( + error = TRUE, + duckplyr_mutate(df, across(x, .funs = ~ . * 1000)) + ) +}) + +# dots -------------------------------------------------------------------- + +test_that("across(...) is deprecated", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + + df <- tibble(x = c(1, NA)) + expect_snapshot(duckplyr_summarise(df, across(everything(), mean, na.rm = TRUE))) + +}) + +test_that("across() passes ... to functions", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + options(lifecycle_verbosity = "quiet") + + df <- tibble(x = c(1, NA)) + expect_equal( + duckplyr_summarise(df, across(everything(), mean, na.rm = TRUE)), + tibble(x = 1) + ) + expect_equal( + duckplyr_summarise(df, across(everything(), list(mean = mean, median = median), na.rm = TRUE)), + tibble(x_mean = 1, x_median = 1) + ) +}) + +test_that("across() passes unnamed arguments following .fns as ... (#4965)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + options(lifecycle_verbosity = "quiet") + + df <- tibble(x = 1) + expect_equal(duckplyr_mutate(df, across(x, `+`, 1)), tibble(x = 2)) +}) + +test_that("across() avoids simple argument name collisions with ... (#4965)", { + options(lifecycle_verbosity = "quiet") + + df <- tibble(x = c(1, 2)) + expect_equal(summarize(df, across(x, tail, n = 1)), tibble(x = 2)) +}) + + +test_that("across() evaluates ... with promise semantics (#5813)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + options(lifecycle_verbosity = "quiet") + + df <- tibble(x = tibble(foo = 1), y = tibble(foo = 2)) + + res <- duckplyr_mutate(df, across( + everything(), + mutate, + foo = foo + 1 + )) + expect_equal(res$x$foo, 2) + expect_equal(res$y$foo, 3) + + # Dots are evaluated only once + new_counter <- function() { + n <- 0L + function() { + n <<- n + 1L + n + } + } + counter <- new_counter() + list_second <- function(...) { + list(..2) + } + res <- duckplyr_mutate(df, across( + everything(), + list_second, + counter() + )) + expect_equal(res$x[[1]], 1) + expect_equal(res$y[[1]], 1) +}) + + +test_that("arguments in dots are evaluated once per group", { + options(lifecycle_verbosity = "quiet") + + set.seed(0) + out <- data.frame(g = 1:3, var = NA) %>% + duckplyr_group_by(g) %>% + duckplyr_mutate(across(var, function(x, y) y, rnorm(1))) %>% + duckplyr_pull(var) + + set.seed(0) + expect_equal(out, rnorm(3)) +}) + +test_that("group variables are in scope when passed in dots (#5832)", { + options(lifecycle_verbosity = "quiet") + + f <- function(x, z) x + z + gdf <- data.frame(x = 1:2, y = 3:4, g = 1:2) %>% duckplyr_group_by(g) + exp <- gdf %>% duckplyr_summarise(x = f(x, z = y)) + + expect_equal( + gdf %>% duckplyr_summarise(across(x, f, z = y)), + exp + ) + + expect_equal( + gdf %>% duckplyr_summarise((across(x, f, z = y))), + exp + ) +}) + +test_that("symbols are looked up as list or functions (#6545)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(mean = 1:5) + exp <- duckplyr_summarise(df, across(everything(), function(x) mean(x))) + + expect_equal( + duckplyr_summarise(df, across(everything(), mean)), + exp + ) + expect_equal( + duckplyr_summarise(df, (across(everything(), mean))), + exp + ) + + exp <- duckplyr_summarise(df, across(everything(), list(function(x) mean(x)))) + + expect_equal( + summarize(df, across(everything(), list(mean))), + exp + ) + expect_equal( + summarize(df, (across(everything(), list(mean)))), + exp + ) +}) + +test_that("non-inlinable but maskable lambdas give precedence to function arguments", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame( + foo = 1, + bar = "a" + ) + out <- duckplyr_mutate(df, across(1:2, function(foo) return(foo))) + expect_equal(out, df) +}) + +test_that("maskable lambdas can refer to their lexical environment", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + foo <- "OK" + df <- tibble(bar = "a") + + # Non-inlinable + expect_equal( + duckplyr_mutate(df, across(1, function(x) return(paste(x, foo)))), + tibble(bar = "a OK") + ) + expect_equal( + duckplyr_mutate(df, across(1, ~ return(paste(.x, foo)))), + tibble(bar = "a OK") + ) + + # Inlinable + expect_equal( + duckplyr_mutate(df, across(1, function(x) paste(x, foo))), + tibble(bar = "a OK") + ) + expect_equal( + duckplyr_mutate(df, across(1, ~ paste(.x, foo))), + tibble(bar = "a OK") + ) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-all-equal.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-all-equal.R new file mode 100644 index 000000000..f04956928 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-all-equal.R @@ -0,0 +1,194 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("all_equal is deprecated", { + expect_snapshot(all_equal(mtcars, mtcars)) +}) + +# A data frame with all major types +df_all <- data.frame( + a = c(1, 2.5), + b = 1:2, + c = c(T, F), + d = c("a", "b"), + e = factor(c("a", "b")), + f = Sys.Date() + 1:2, + g = Sys.time() + 1:2, + stringsAsFactors = FALSE +) + +test_that("data frames equal to themselves", { + local_options(lifecycle_verbosity = "quiet") + + expect_true(all_equal(mtcars, mtcars)) + expect_true(all_equal(iris, iris)) + expect_true(all_equal(df_all, df_all)) +}) + +test_that("data frames not equal if missing row", { + local_options(lifecycle_verbosity = "quiet") + expect_snapshot({ + all_equal(mtcars, mtcars[-1, ]) + all_equal(iris, iris[-1, ]) + all_equal(df_all, df_all[-1, ]) + }) +}) + +test_that("data frames not equal if missing col", { + local_options(lifecycle_verbosity = "quiet") + expect_snapshot({ + all_equal(mtcars, mtcars[, -1]) + all_equal(iris, iris[, -1]) + all_equal(df_all, df_all[, -1]) + }) +}) + +test_that("factors equal only if levels equal", { + local_options(lifecycle_verbosity = "quiet") + df1 <- tibble(x = factor(c("a", "b"))) + df2 <- tibble(x = factor(c("a", "d"))) + expect_snapshot({ + all_equal(df1, df2) + all_equal(df2, df1) + }) +}) + +test_that("factor comparison requires strict equality of levels (#2440)", { + local_options(lifecycle_verbosity = "quiet") + + df1 <- tibble(x = factor("a")) + df2 <- tibble(x = factor("a", levels = c("a", "b"))) + expect_true(all_equal(df1, df2, convert = TRUE)) + expect_true(all_equal(df2, df1, convert = TRUE)) + + expect_snapshot({ + all_equal(df1, df2) + all_equal(df2, df1) + }) +}) + +test_that("all.equal.data.frame handles data.frames with NULL names", { + local_options(lifecycle_verbosity = "quiet") + + x <- data.frame(LETTERS[1:3], rnorm(3)) + names(x) <- NULL + suppressMessages( + expect_true(all_equal(x, x)) + ) +}) + +test_that("data frame equality test with ignore_row_order=TRUE detects difference in number of rows. #1065", { + local_options(lifecycle_verbosity = "quiet") + + DF1 <- tibble(a = 1:4, b = letters[1:4]) + DF2 <- tibble(a = c(1:4, 4L), b = letters[c(1:4, 4L)]) + expect_false(isTRUE(all_equal(DF1, DF2, ignore_row_order = TRUE))) + + DF1 <- tibble(a = c(1:4, 2L), b = letters[c(1:4, 2L)]) + DF2 <- tibble(a = c(1:4, 4L), b = letters[c(1:4, 4L)]) + expect_false(isTRUE(all_equal(DF1, DF2, ignore_row_order = TRUE))) +}) + +test_that("all.equal handles NA_character_ correctly. #1095", { + local_options(lifecycle_verbosity = "quiet") + + d1 <- tibble(x = c(NA_character_)) + expect_true(all_equal(d1, d1)) + + d2 <- tibble(x = c(NA_character_, "foo", "bar")) + expect_true(all_equal(d2, d2)) +}) + +test_that("handle Date columns of different types, integer and numeric (#1204)", { + local_options(lifecycle_verbosity = "quiet") + + a <- data.frame(date = as.Date("2015-06-07")) + b <- data.frame(date = structure(as.integer(a$date), class = "Date")) + expect_true(all_equal(a, b)) +}) + +test_that("equality test fails when convert is FALSE and types don't match (#1484)", { + local_options(lifecycle_verbosity = "quiet") + + df1 <- tibble(x = "a") + df2 <- tibble(x = factor("a")) + expect_true(all_equal(df1, df2, convert = TRUE)) + + expect_snapshot({ + all_equal(df1, df2, convert = FALSE) + }) +}) + +test_that("equality handles data frames with 0 rows (#1506)", { + df0 <- tibble(x = numeric(0), y = character(0)) + expect_equal(df0, df0) +}) + +test_that("equality handles data frames with 0 columns (#1506)", { + df0 <- tibble(a = 1:10)[-1] + expect_equal(df0, df0) +}) + +test_that("equality handle raw columns", { + local_options(lifecycle_verbosity = "quiet") + + df <- tibble(a = 1:3, b = as.raw(1:3)) + expect_true(all_equal(df, df)) +}) + +test_that("equality returns a message for convert = TRUE", { + local_options(lifecycle_verbosity = "quiet") + + df1 <- tibble(x = 1:3) + df2 <- tibble(x = as.character(1:3)) + + expect_snapshot({ + all_equal(df1, df2) + all_equal(df1, df2, convert = TRUE) + }) +}) + +test_that("numeric and integer can be compared if convert = TRUE", { + local_options(lifecycle_verbosity = "quiet") + + df1 <- tibble(x = 1:3) + df2 <- tibble(x = as.numeric(1:3)) + expect_true(all_equal(df1, df2, convert = TRUE)) + + expect_snapshot({ + all_equal(df1, df2) + }) +}) + +test_that("returns vector for more than one difference (#1819)", { + local_options(lifecycle_verbosity = "quiet") + + expect_snapshot({ + all_equal(tibble(a = 1, b = 2), tibble(a = 1L, b = 2L)) + }) +}) + +test_that("ignore column order", { + local_options(lifecycle_verbosity = "quiet") + + expect_snapshot({ + all_equal(tibble(a = 1, b = 2), tibble(b = 2, a = 1), ignore_col_order = FALSE) + all_equal(tibble(a = 1, b = 2), tibble(a = 1), ignore_col_order = FALSE) + }) +}) + +# Errors ------------------------------------------------------------------ + +test_that("duckplyr_count() give meaningful errors", { + skip("TODO duckdb") + local_options(lifecycle_verbosity = "quiet") + + expect_snapshot({ + (expect_error(duckplyr_union(tibble(a = 1), tibble(a = "1")))) + (expect_error(duckplyr_union(tibble(a = 1, b = 2), tibble(a = "1", b = "2")))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-arrange.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-arrange.R new file mode 100644 index 000000000..bea50702e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-arrange.R @@ -0,0 +1,571 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# To turn on warnings from tibble::`names<-()` +local_options(lifecycle_verbosity = "warning") + +test_that("empty duckplyr_arrange() returns input", { + df <- tibble(x = 1:10, y = 1:10) + gf <- duckplyr_group_by(df, x) + + expect_identical(duckplyr_arrange(df), df) + expect_identical(duckplyr_arrange(gf), gf) + + expect_identical(duckplyr_arrange(df, !!!list()), df) + expect_identical(duckplyr_arrange(gf, !!!list()), gf) +}) + +test_that("can sort empty data frame", { + df <- tibble(a = numeric(0)) + expect_equal(duckplyr_arrange(df, a), df) +}) + +test_that("local arrange sorts missing values to end", { + df <- data.frame(x = c(2, 1, NA)) + + expect_equal(df %>% duckplyr_arrange(x) %>% duckplyr_pull(), c(1, 2, NA)) + expect_equal(df %>% duckplyr_arrange(desc(x)) %>% duckplyr_pull(), c(2, 1, NA)) +}) + +test_that("duckplyr_arrange() gives meaningful errors", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot({ + # duplicated column name + (expect_error( + tibble(x = 1, x = 1, .name_repair = "minimal") %>% duckplyr_arrange(x) + )) + + # error in duckplyr_mutate() step + (expect_error( + tibble(x = 1) %>% duckplyr_arrange(y) + )) + (expect_error( + tibble(x = 1) %>% duckplyr_arrange(rep(x, 2)) + )) + }) + +}) + +# column types ---------------------------------------------------------- + +test_that("arrange handles list columns (#282)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # no intrinsic ordering + df <- tibble(x = 1:3, y = list(3, 2, 1)) + expect_equal(duckplyr_arrange(df, y), df) + + df <- tibble(x = 1:3, y = list(sum, mean, sd)) + expect_equal(duckplyr_arrange(df, y), df) +}) + +test_that("arrange handles raw columns (#1803)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3, y = as.raw(3:1)) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("arrange handles matrix columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3, y = matrix(6:1, ncol = 2)) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("arrange handles data.frame columns (#3153)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3, y = data.frame(z = 3:1)) + expect_equal(duckplyr_arrange(df, y), tibble(x = 3:1, y = data.frame(z = 1:3))) +}) + +test_that("arrange handles complex columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3, y = 3:1 + 2i) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("arrange handles S4 classes (#1105)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + TestS4 <- suppressWarnings(setClass("TestS4", contains = "integer")) + setMethod('[', 'TestS4', function(x, i, ...){ TestS4(unclass(x)[i, ...]) }) + on.exit(removeClass("TestS4"), add = TRUE) + + df <- tibble(x = 1:3, y = TestS4(3:1)) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("arrange works with two columns when the first has a data frame proxy (#6268)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # `id1` has a data frame proxy for `vec_proxy_order()` + df <- tibble( + id1 = new_rcrd(list(x = 1, y = 1)), + id2 = c(1, 3, 2) + ) + + out <- duckplyr_arrange(df, id1, id2) + + expect_identical(out$id2, c(1, 2, 3)) +}) + +test_that("arrange ignores NULLs (#6193)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2) + y <- NULL + + out <- duckplyr_arrange(df, y, desc(x)) + expect_equal(out$x, 2:1) + + out <- duckplyr_arrange(df, y, desc(x), y) + expect_equal(out$x, 2:1) +}) + +test_that("`duckplyr_arrange()` works with `numeric_version` (#6680)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + x <- numeric_version(c("1.11", "1.2.3", "1.2.2")) + df <- tibble(x = x) + + expect <- df[c(3, 2, 1),] + + expect_identical(duckplyr_arrange(df, x), expect) +}) + +# locale -------------------------------------------------------------- + +test_that("arrange defaults to the C locale", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + x <- c("A", "a", "b", "B") + df <- tibble(x = x) + + res <- duckplyr_arrange(df, x) + expect_identical(res$x, c("A", "B", "a", "b")) + + res <- duckplyr_arrange(df, desc(x)) + expect_identical(res$x, rev(c("A", "B", "a", "b"))) +}) + +test_that("locale can be set to an English locale", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if_not_installed("stringi", "1.5.3") + + x <- c("A", "a", "b", "B") + df <- tibble(x = x) + + res <- duckplyr_arrange(df, x, .locale = "en") + expect_identical(res$x, c("a", "A", "b", "B")) +}) + +test_that("non-English locales can be used", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if_not_installed("stringi", "1.5.3") + + # Danish `o` with `/` through it sorts after `z` in Danish locale + x <- c("o", "\u00F8", "p", "z") + df <- tibble(x = x) + + # American English locale puts it right after `o` + res <- duckplyr_arrange(df, x, .locale = "en") + expect_identical(res$x, x) + + res <- duckplyr_arrange(df, x, .locale = "da") + expect_identical(res$x, x[c(1, 3, 4, 2)]) +}) + +test_that("arrange errors if stringi is not installed and a locale identifier is used", { + expect_snapshot(error = TRUE, { + locale_to_chr_proxy_collate("fr", has_stringi = FALSE) + }) +}) + +test_that("arrange validates `.locale`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble() + + expect_snapshot(error = TRUE, { + duckplyr_arrange(df, .locale = 1) + }) + expect_snapshot(error = TRUE, { + duckplyr_arrange(df, .locale = c("en_US", "fr_BF")) + }) +}) + +test_that("arrange validates that `.locale` must be one from stringi", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if_not_installed("stringi", "1.5.3") + + df <- tibble() + + expect_snapshot(error = TRUE, { + duckplyr_arrange(df, .locale = "x") + }) +}) + +# data ---------------------------------------------------------------- + +test_that("arrange preserves input class", { + df1 <- data.frame(x = 1:3, y = 3:1) + df2 <- tibble(x = 1:3, y = 3:1) + df3 <- df1 %>% duckplyr_group_by(x) + + expect_s3_class(duckplyr_arrange(df1, x), "data.frame", exact = TRUE) + expect_s3_class(duckplyr_arrange(df2, x), "tbl_df") + expect_s3_class(duckplyr_arrange(df3, x), "grouped_df") +}) + +test_that("grouped arrange ignores group, unless requested with .by_group", { + df <- data.frame(g = c(2, 1, 2, 1), x = 4:1) + gf <- duckplyr_group_by(df, g) + + expect_equal(duckplyr_arrange(gf, x), gf[4:1, ,]) + expect_equal(duckplyr_arrange(gf, x, .by_group = TRUE), gf[c(4, 2, 3, 1), ,]) +}) + +test_that("arrange updates the grouping structure (#605)", { + df <- tibble(g = c(2, 2, 1, 1), x = c(1, 3, 2, 4)) + res <- df %>% duckplyr_group_by(g) %>% duckplyr_arrange(x) + expect_s3_class(res, "grouped_df") + expect_equal(group_rows(res), list_of(c(2L, 4L), c(1L, 3L))) +}) + +test_that("duckplyr_arrange() supports across() and pick() (#4679)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = c(1, 3, 2, 1), y = c(4, 3, 2, 1)) + + expect_identical( + df %>% duckplyr_arrange(pick(everything())), + df %>% duckplyr_arrange(x, y) + ) + expect_identical( + df %>% duckplyr_arrange(across(everything(), .fns = desc)), + df %>% duckplyr_arrange(desc(x), desc(y)) + ) + expect_identical( + df %>% duckplyr_arrange(pick(x)), + df %>% duckplyr_arrange(x) + ) + expect_identical( + df %>% duckplyr_arrange(across(y, .fns = identity)), + df %>% duckplyr_arrange(y) + ) +}) + +test_that("duckplyr_arrange() works with across() and pick() cols that return multiple columns (#6490)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble( + a = c(1, 1, 1), + b = c(2, 2, 2), + c = c(4, 4, 3), + d = c(5, 2, 7) + ) + + expect_identical( + duckplyr_arrange(df, across(c(a, b), .fns = identity), across(c(c, d), .fns = identity)), + df[c(3, 2, 1),] + ) + expect_identical( + duckplyr_arrange(df, pick(a, b), pick(c, d)), + df[c(3, 2, 1),] + ) +}) + +test_that("duckplyr_arrange() evaluates each pick() call on the original data (#6495)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2:1) + + out <- duckplyr_arrange(df, TRUE, pick(everything())) + expect_identical(out, df[c(2, 1),]) + + out <- duckplyr_arrange(df, NULL, pick(everything())) + expect_identical(out, df[c(2, 1),]) +}) + +test_that("duckplyr_arrange() with empty dots still calls dplyr_row_slice()", { + tbl <- new_tibble(list(x = 1), nrow = 1L) + foo <- structure(tbl, class = c("foo_df", class(tbl))) + + local_methods( + # `foo_df` always loses class when row slicing + dplyr_row_slice.foo_df = function(data, i, ...) { + out <- NextMethod() + new_tibble(out, nrow = nrow(out)) + } + ) + + expect_s3_class(duckplyr_arrange(foo), class(tbl), exact = TRUE) + expect_s3_class(duckplyr_arrange(foo, x), class(tbl), exact = TRUE) +}) + +test_that("can duckplyr_arrange() with unruly class", { + local_methods( + `[.dplyr_foobar` = function(x, i, ...) new_dispatched_quux(vec_slice(x, i)), + dplyr_row_slice.dplyr_foobar = function(x, i, ...) x[i, ] + ) + + df <- foobar(data.frame(x = 1:3)) + expect_identical( + duckplyr_arrange(df, desc(x)), + quux(data.frame(x = 3:1, dispatched = TRUE)) + ) +}) + +test_that("duckplyr_arrange() preserves the call stack on error (#5308)", { + foobar <- function() stop("foo") + + stack <- NULL + expect_error( + withCallingHandlers( + error = function(...) stack <<- sys.calls(), + duckplyr_arrange(mtcars, foobar()) + ) + ) + + expect_true(some(stack, is_call, "foobar")) +}) + +test_that("desc() inside duckplyr_arrange() checks the number of arguments (#5921)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot({ + df <- data.frame(x = 1, y = 2) + + (expect_error(duckplyr_arrange(df, desc(x, y)))) + }) +}) + +test_that("arrange keeps zero length groups",{ + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal( duckplyr_group_size(duckplyr_arrange(df)), c(2, 2, 0) ) + expect_equal( duckplyr_group_size(duckplyr_arrange(df, x)), c(2, 2, 0) ) +}) + +# legacy -------------------------------------------------------------- + +test_that("legacy - using the global option `dplyr.legacy_locale` forces the system locale", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if_not(has_collate_locale("en_US"), message = "Can't use 'en_US' locale") + + local_options(dplyr.legacy_locale = TRUE) + withr::local_collate("en_US") + + df <- tibble(x = c("a", "A", "Z", "b")) + + expect_identical(duckplyr_arrange(df, x)$x, c("a", "A", "b", "Z")) +}) + +test_that("legacy - usage of `.locale` overrides `dplyr.legacy_locale`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if_not_installed("stringi", "1.5.3") + + local_options(dplyr.legacy_locale = TRUE) + + # Danish `o` with `/` through it sorts after `z` in Danish locale + x <- c("o", "\u00F8", "p", "z") + df <- tibble(x = x) + + # American English locale puts it right after `o` + res <- duckplyr_arrange(df, x, .locale = "en") + expect_identical(res$x, x) + + res <- duckplyr_arrange(df, x, .locale = "da") + expect_identical(res$x, x[c(1, 3, 4, 2)]) +}) + +test_that("legacy - empty duckplyr_arrange() returns input", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(x = 1:10, y = 1:10) + gf <- duckplyr_group_by(df, x) + + expect_identical(duckplyr_arrange(df), df) + expect_identical(duckplyr_arrange(gf), gf) + + expect_identical(duckplyr_arrange(df, !!!list()), df) + expect_identical(duckplyr_arrange(gf, !!!list()), gf) +}) + +test_that("legacy - can sort empty data frame", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(a = numeric(0)) + expect_equal(duckplyr_arrange(df, a), df) +}) + +test_that("legacy - local arrange sorts missing values to end", { + local_options(dplyr.legacy_locale = TRUE) + + df <- data.frame(x = c(2, 1, NA)) + + expect_equal(df %>% duckplyr_arrange(x) %>% duckplyr_pull(), c(1, 2, NA)) + expect_equal(df %>% duckplyr_arrange(desc(x)) %>% duckplyr_pull(), c(2, 1, NA)) +}) + +test_that("legacy - arrange handles list columns (#282)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + # no intrinsic ordering + df <- tibble(x = 1:3, y = list(3, 2, 1)) + expect_equal(duckplyr_arrange(df, y), df) + + df <- tibble(x = 1:3, y = list(sum, mean, sd)) + expect_equal(duckplyr_arrange(df, y), df) +}) + +test_that("legacy - arrange handles raw columns (#1803)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(x = 1:3, y = as.raw(3:1)) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("legacy - arrange handles matrix columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(x = 1:3, y = matrix(6:1, ncol = 2)) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("legacy - arrange handles data.frame columns (#3153)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(x = 1:3, y = data.frame(z = 3:1)) + expect_equal(duckplyr_arrange(df, y), tibble(x = 3:1, y = data.frame(z = 1:3))) +}) + +test_that("legacy - arrange handles complex columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(x = 1:3, y = 3:1 + 2i) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("legacy - arrange handles S4 classes (#1105)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + TestS4 <- suppressWarnings(setClass("TestS4", contains = "integer")) + setMethod('[', 'TestS4', function(x, i, ...){ TestS4(unclass(x)[i, ...]) }) + on.exit(removeClass("TestS4"), add = TRUE) + + df <- tibble(x = 1:3, y = TestS4(3:1)) + expect_equal(duckplyr_arrange(df, y), df[3:1, ]) +}) + +test_that("legacy - `duckplyr_arrange()` works with `numeric_version` (#6680)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + x <- numeric_version(c("1.11", "1.2.3", "1.2.2")) + df <- tibble(x = x) + + expect <- df[c(3, 2, 1),] + + expect_identical(duckplyr_arrange(df, x), expect) +}) + +test_that("legacy - arrange works with two columns when the first has a data frame proxy (#6268)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + # `id1` has a data frame proxy for `vec_proxy_order()` + df <- tibble( + id1 = new_rcrd(list(x = 1, y = 1)), + id2 = c(1, 3, 2) + ) + + out <- duckplyr_arrange(df, id1, id2) + + expect_identical(out$id2, c(1, 2, 3)) +}) + +test_that("legacy - duckplyr_arrange() supports across() and pick() (#4679)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble(x = c(1, 3, 2, 1), y = c(4, 3, 2, 1)) + + expect_identical( + df %>% duckplyr_arrange(pick(everything())), + df %>% duckplyr_arrange(x, y) + ) + expect_identical( + df %>% duckplyr_arrange(across(everything(), .fns = desc)), + df %>% duckplyr_arrange(desc(x), desc(y)) + ) + expect_identical( + df %>% duckplyr_arrange(pick(x)), + df %>% duckplyr_arrange(x) + ) + expect_identical( + df %>% duckplyr_arrange(across(y, .fns = identity)), + df %>% duckplyr_arrange(y) + ) +}) + +test_that("legacy - duckplyr_arrange() works with across() and pick() cols that return multiple columns (#6490)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + df <- tibble( + a = c(1, 1, 1), + b = c(2, 2, 2), + c = c(4, 4, 3), + d = c(5, 2, 7) + ) + + expect_identical( + duckplyr_arrange(df, across(c(a, b), .fns = identity), across(c(c, d), .fns = identity)), + df[c(3, 2, 1),] + ) + expect_identical( + duckplyr_arrange(df, pick(a, b), pick(c, d)), + df[c(3, 2, 1),] + ) +}) + +test_that("legacy - arrange sorts missings in df-cols correctly", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + col <- tibble(a = c(1, 1, 1), b = c(3, NA, 1)) + df <- tibble(x = col) + + expect_identical(duckplyr_arrange(df, x), df[c(3, 1, 2),]) + expect_identical(duckplyr_arrange(df, desc(x)), df[c(1, 3, 2),]) +}) + +test_that("legacy - arrange with duplicates in a df-col uses a stable sort", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + col <- tibble(a = c(1, 1, 1, 1, 1), b = c(3, NA, 2, 3, NA)) + df <- tibble(x = col, y = 1:5) + + expect_identical(duckplyr_arrange(df, x)$y, c(3L, 1L, 4L, 2L, 5L)) + expect_identical(duckplyr_arrange(df, desc(x))$y, c(1L, 4L, 3L, 2L, 5L)) +}) + +test_that("legacy - arrange with doubly nested df-col doesn't infloop", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(dplyr.legacy_locale = TRUE) + + one <- tibble(a = c(1, 1, 1, 1, 1), b = c(1, 1, 2, 2, 2)) + two <- tibble(a = c(1, 1, 1, 1, 1), b = c(2, 1, 1, 2, 2)) + col <- tibble(one = one, two = two) + df <- tibble(x = col, y = c(1, 1, 1, 1, 0)) + + expect_identical(duckplyr_arrange(df, x, y), df[c(2, 1, 3, 5, 4),]) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-cols.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-cols.R new file mode 100644 index 000000000..c21b25336 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-cols.R @@ -0,0 +1,144 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("bind_cols() uses shallow copies", { + skip_if_not_installed("lobstr") + df1 <- data.frame( + int = 1:10, + num = rnorm(10), + cha = letters[1:10], + stringsAsFactors = FALSE + ) + df2 <- data.frame( + log = sample(c(T, F), 10, replace = TRUE), + dat = seq.Date(Sys.Date(), length.out = 10, by = "day"), + tim = seq(Sys.time(), length.out = 10, by = "1 hour") + ) + df <- bind_cols(df1, df2) + + expect_equal(lobstr::obj_addrs(df1), lobstr::obj_addrs(df[names(df1)])) + expect_equal(lobstr::obj_addrs(df2), lobstr::obj_addrs(df[names(df2)])) +}) + +test_that("bind_cols() handles lists (#1104)", { + exp <- tibble(x = 1, y = "a", z = 2) + + l1 <- list(x = 1, y = "a") + l2 <- list(z = 2) + + expect_identical(bind_cols(l1, l2), exp) + expect_identical(bind_cols(list(l1, l2)), exp) +}) + +test_that("bind_cols() handles empty argument list (#1963)", { + expect_equal(bind_cols(), tibble()) +}) + +test_that("bind_cols() handles all-NULL values (#2303)", { + expect_identical(bind_cols(list(a = NULL, b = NULL)), tibble()) + expect_identical(bind_cols(NULL), tibble()) +}) + +test_that("bind_cols() repairs names", { + df <- tibble(a = 1, b = 2) + expect_snapshot(bound <- bind_cols(df, df)) + + expect_message( + repaired <- as_tibble( + data.frame(a = 1, b = 2, a = 1, b = 2, check.names = FALSE), + .name_repair = "unique" + ), "New names" + ) + + expect_identical(bound, repaired) +}) + +test_that("bind_cols() unpacks tibbles", { + expect_equal( + bind_cols(list(y = tibble(x = 1:2))), + tibble(x = 1:2) + ) + expect_equal( + bind_cols(list(y = tibble(x = 1:2), z = tibble(y = 1:2))), + tibble(x = 1:2, y = 1:2) + ) +}) + +test_that("bind_cols() honours .name_repair=", { + expect_message(res <- bind_cols( + data.frame(a = 1), data.frame(a = 2) + )) + expect_equal(res, data.frame(a...1 = 1, a...2 = 2)) + + expect_error(bind_cols(.name_repair = "check_unique", + data.frame(a = 1), data.frame(a = 2) + )) +}) + +test_that("bind_cols() accepts NULL (#1148)", { + df1 <- tibble(a = 1:10, b = 1:10) + df2 <- tibble(c = 1:10, d = 1:10) + + res1 <- bind_cols(df1, df2) + res2 <- bind_cols(NULL, df1, df2) + res3 <- bind_cols(df1, NULL, df2) + res4 <- bind_cols(df1, df2, NULL) + + expect_identical(res1, res2) + expect_identical(res1, res3) + expect_identical(res1, res4) +}) + +test_that("bind_cols() infers classes from first result (#1692)", { + d1 <- data.frame(a = 1:10, b = rep(1:2, each = 5)) + d2 <- tibble(c = 1:10, d = rep(1:2, each = 5)) + d3 <- duckplyr_group_by(d2, d) + d4 <- duckplyr_rowwise(d2) + d5 <- list(c = 1:10, d = rep(1:2, each = 5)) + + suppressMessages({ + expect_equal(class(bind_cols(d1, d1)), "data.frame") + expect_equal(class(bind_cols(d2, d1)), c("tbl_df", "tbl", "data.frame")) + }) + res3 <- bind_cols(d3, d1) + expect_equal(class(res3), c("grouped_df", "tbl_df", "tbl", "data.frame")) + expect_equal(map_int(group_rows(res3), length), c(5, 5)) + expect_equal(class(bind_cols(d4, d1)), c("rowwise_df", "tbl_df", "tbl", "data.frame")) + expect_equal(class(bind_cols(d5, d1)), "data.frame") +}) + +test_that("accepts named columns", { + expect_identical(bind_cols(a = 1:2, b = 3:4), tibble(a = 1:2, b = 3:4)) +}) + +test_that("ignores NULL values", { + expect_identical(bind_cols(a = 1, NULL, b = 2, NULL), tibble(a = 1, b = 2)) +}) + +test_that("bind_cols() handles unnamed list with name repair (#3402)", { + expect_snapshot(df <- bind_cols(list(1, 2))) + + expect_identical(df, bind_cols(list(...1 = 1, ...2 = 2))) +}) + +test_that("bind_cols() doesn't squash record types", { + df <- data.frame(x = 1) + posixlt <- as.POSIXlt(as.Date("1970-01-01")) + + expect_identical( + bind_cols(df, y = posixlt), + new_data_frame(list(x = 1, y = posixlt)) + ) +}) + +test_that("bind_cols() gives informative errors", { + expect_snapshot({ + "# incompatible size" + (expect_error(bind_cols(a = 1:2, mtcars))) + (expect_error(bind_cols(mtcars, a = 1:3))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-rows.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-rows.R new file mode 100644 index 000000000..6c3bbfe22 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-bind-rows.R @@ -0,0 +1,266 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("bind_rows() handles simple inputs", { + df1 <- tibble(x = 1:2, y = letters[1:2]) + df2 <- tibble(x = 3:4, y = letters[3:4]) + + out <- bind_rows(df1, df2) + expect_equal(out, tibble(x = 1:4, y = letters[1:4])) +}) + +test_that("bind_rows() reorders columns to match first df", { + df1 <- tibble(x = 1, y = 2) + df2 <- tibble(y = 1, x = 2) + + expect_named(bind_rows(df1, df2), c("x", "y")) +}) + +test_that("bind_rows() returns union of columns", { + df1 <- tibble(x = 1) + df2 <- tibble(y = 2) + out <- bind_rows(df1, df2) + + expect_equal(out, tibble(x = c(1, NA), y = c(NA, 2))) +}) + +test_that("bind_rows() handles zero column data frames (#2175)", { + df1 <- tibble(.rows = 1) + df2 <- tibble(x = 1) + out <- bind_rows(df1, df2) + + expect_equal(out, tibble(x = c(NA, 1))) +}) + +test_that("bind_rows() handles zero row data frames (#597)", { + df1 <- tibble(x = numeric()) + df2 <- tibble(y = 1) + + out <- bind_rows(df1, df2) + expect_equal(out, tibble(x = NA_real_, y = 1)) +}) + +test_that("bind_rows() ignores NULL (#2056)", { + df <- tibble(a = 1) + + expect_equal(bind_rows(df, NULL), df) + expect_equal(bind_rows(list(df, NULL)), df) +}) + +test_that("bind_rows() creates a column of identifiers (#1337)", { + df1 <- tibble(x = 1:2) + df2 <- tibble(x = 3) + + # with + out <- bind_rows(a = df1, b = df2, .id = "id") + expect_equal(out, tibble(id = c("a", "a", "b"), x = 1:3)) + + out <- bind_rows(list(a = df1, b = df2), .id = "id") + expect_equal(out, tibble(id = c("a", "a", "b"), x = 1:3)) + + # or without names + out <- bind_rows(df1, df2, .id = "id") + expect_equal(out, tibble(id = c("1", "1", "2"), x = 1:3)) +}) + +test_that("bind_rows deduplicates row names", { + df1 <- data.frame(x = 1:2, row.names = c("a", "b")) + df2 <- data.frame(x = 3:4, row.names = c("a", "c")) + out <- bind_rows(df1, df2) + + expect_equal(rownames(out), c("a...1", "b", "a...3", "c")) +}) + +test_that("bind_rows respects the drop attribute of grouped df",{ + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + gg <- bind_rows(df, df) + expect_equal(duckplyr_group_size(gg), c(4L,4L,0L)) +}) + +# bind_rows() magic --------------------------------------------------- + +test_that("bind_rows() handles lists of data frames #1389", { + df <- tibble(x = 1) + + res <- bind_rows(list(df, df), list(df, df)) + expect_equal(nrow(res), 4) +}) + +test_that("bind_rows() ignores empty lists (#2826)", { + df <- tibble(x = 1:10) + expect_equal(bind_rows(list(df, list())), df) +}) + +test_that("bind_rows() accepts lists of dataframe-like lists as first argument", { + ll <- list(a = 1, b = 2) + expect_equal(bind_rows(list(ll)), tibble(a = 1, b = 2)) + expect_equal(bind_rows(list(ll, ll)), tibble(a = c(1, 1), b = c(2, 2))) +}) + +test_that("bind_rows() can handle lists (#1104)", { + ll <- list(list(x = 1, y = "a"), list(x = 2, y = "b")) + out <- bind_rows(ll) + expect_equal(out, tibble(x = c(1, 2), y = c("a", "b"))) + + out <- bind_rows(ll[[1]], ll[2]) + expect_equal(out, tibble(x = c(1, 2), y = c("a", "b"))) +}) + +test_that("bind_rows() handles 0-length named list (#1515)", { + x <- set_names(list()) + expect_equal(bind_rows(x), tibble()) +}) + +test_that("bind_rows() handles tibbles + vectors", { + out <- bind_rows( + tibble(a = 1, b = 2), + c(a = 3, b = 4) + ) + expect_equal(out, tibble(a = c(1, 3), b = c(2, 4))) + + out <- bind_rows( + a = c(a = 1, b = 2), + b = c(a = 3, b = 4), + .id = "id" + ) + expect_equal(out, tibble(id = c("a", "b"), a = c(1, 3), b = c(2, 4))) +}) + +test_that("bind_rows() only flattens S3 lists that inherit from list (#3924)", { + df <- data.frame(x = 1, y = 2) + + lst1 <- structure(list(df, df, df), class = "special_lst") + expect_snapshot(bind_rows(lst1), error = TRUE) + + lst2 <- structure(list(df, df, df), class = c("special_lst", "list")) + expect_equal(bind_rows(lst2), bind_rows(df,df,df)) +}) + +test_that("bind_rows() handles named list", { + x <- list(x = 1, y = 2, z = 3) + expect_equal(bind_rows(x), tibble(x = 1, y = 2, z = 3)) +}) + +test_that("bind_rows() validates lists (#5417)", { + out <- bind_rows(list(x = 1), list(x = 1, y = 1:2)) + expect_equal(out, tibble(x = c(1, 1, 1), y = c(NA, 1:2))) + + expect_snapshot(bind_rows(list(x = 1), list(x = 1:3, y = 1:2)), error = TRUE) +}) + +test_that("bind_rows() handles missing, null, and empty elements (#5429)", { + x <- list(a = NULL, b = NULL) + y <- list(a = "B", b = 2) + l <- list(x, y) + expect_identical( + bind_rows(l), + tibble(a = "B", b = 2) + ) + + x <- list(a = NULL, b = 1) + y <- list(a = "B", b = 2) + l <- list(x, y) + expect_identical( + bind_rows(l), + tibble(b = c(1, 2), a = c(NA, "B")) + ) + + x <- list(a = character(0), b = 1) + y <- list(a = "B", b = 2) + l <- list(x, y) + expect_identical( + bind_rows(l), + tibble(a = "B", b = 2) + ) +}) + +test_that("bind_rows(.id= NULL) does not set names (#5089)", { + out <- bind_rows(list(a = tibble(x = 1:2))) + expect_equal(attr(out, "row.names"), 1:2) + + out <- bind_rows(x = c(a = 1)) + expect_identical(attr(out, "row.names"), 1L) +}) + +# Column coercion -------------------------------------------------------------- + +test_that("bind_rows() promotes integer to numeric", { + df1 <- tibble(a = 1L, b = 1L) + df2 <- tibble(a = 1, b = 1L) + + res <- bind_rows(df1, df2) + expect_type(res$a, "double") + expect_type(res$b, "integer") +}) + +test_that("bind_rows() coerces factor when levels don't match", { + df1 <- data.frame(a = factor("a")) + df2 <- data.frame(a = factor("b")) + + res <- bind_rows(df1, df2) + expect_equal(res$a, factor(c("a", "b"))) +}) + +test_that("bind_rows() handles complex. #933", { + df1 <- tibble(x = 1 + 1i) + df2 <- tibble(x = 2 + 1i) + + out <- bind_rows(df1, df2) + expect_equal(out, tibble(x = c(1 + 1i, 2 + 1i))) +}) + +test_that("bind_rows() handles POSIXct (#1125)", { + df1 <- data.frame(date = as.POSIXct(NA)) + df2 <- data.frame(date = as.POSIXct("2015-05-05")) + res <- bind_rows(df1, df2) + expect_equal(nrow(res), 2L) + expect_true(is.na(res$date[1])) +}) + +test_that("bind_rows() accepts data frame columns (#2015)", { + df1 <- tibble(x = 1, y = tibble(a = 1, b = 1)) + df2 <- tibble(x = 2, y = tibble(a = 2, b = 2)) + + out <- bind_rows(df1, df2) + expect_equal(out, tibble(x = 1:2, y = tibble(a = 1:2, b = 1:2))) +}) + +test_that("bind_rows() accepts difftime objects", { + df1 <- data.frame(x = as.difftime(1, units = "hours")) + df2 <- data.frame(x = as.difftime(1, units = "mins")) + res <- bind_rows(df1, df2) + expect_equal(res$x, as.difftime(c(3600, 60), units = "secs")) +}) + +# Errors ------------------------------------------------------------ + +test_that("bind_rows() give informative errors", { + expect_snapshot({ + "invalid .id" + df1 <- tibble(x = 1:3) + df2 <- tibble(x = 4:6) + (expect_error(bind_rows(df1, df2, .id = 5))) + + "invalid type" + ll <- list(tibble(a = 1:5), env(a = 1)) + (expect_error(bind_rows(ll))) + + df1 <- tibble(a = factor("a")) + df2 <- tibble(a = 1L) + (expect_error(bind_rows(df1, df2))) + + "unnamed vectors" + (expect_error(bind_rows(1:2))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-by.R new file mode 100644 index 000000000..8c98f4898 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-by.R @@ -0,0 +1,107 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("computes group data when `by` is set", { + df <- tibble(x = c(1, 1, 2, 2, 1)) + + out <- compute_by(by = x, data = df) + expect_identical(out$type, "grouped") + expect_identical(out$names, "x") + + expect_identical( + out$data, + tibble(x = c(1, 2), ".rows" := list_of(c(1L, 2L, 5L), c(3L, 4L))) + ) +}) + +test_that("computes `by` group data in order of appearance", { + df <- tibble( + x = c(5, 4, 5, 5), + y = c(2, 3, 1, 2) + ) + + out <- compute_by(by = c(x, y), data = df) + + expect <- tibble( + x = c(5, 4, 5), + y = c(2, 3, 1), + ".rows" := list_of(c(1L, 4L), 2L, 3L) + ) + + expect_identical(out$data, expect) +}) + +test_that("extracts existing data when `by = NULL`", { + df <- data.frame(x = c(1, 1, 2, 2, 1)) + out <- compute_by(by = NULL, data = df) + expect_identical(out$type, "ungrouped") + expect_identical(out$names, character()) + # `compute_by()` is always type stable on `$data` and returns a bare tibble + expect_identical(out$data, as_tibble(group_data(df))) + + df <- tibble(x = c(1, 1, 2, 2, 1)) + out <- compute_by(by = NULL, data = df) + expect_identical(out$type, "ungrouped") + expect_identical(out$names, character()) + expect_identical(out$data, group_data(df)) + + gdf <- duckplyr_group_by(df, x) + out <- compute_by(by = NULL, data = gdf) + expect_identical(out$type, "grouped") + expect_identical(out$names, "x") + expect_identical(out$data, group_data(gdf)) + + rdf <- duckplyr_rowwise(df) + out <- compute_by(by = NULL, data = rdf) + expect_identical(out$type, "rowwise") + expect_identical(out$names, character()) + expect_identical(out$data, group_data(rdf)) +}) + +test_that("empty selection results in ungrouped group data", { + df <- tibble(x = 1) + + out <- compute_by(by = c(), data = df) + expect_identical(out$type, "ungrouped") + expect_identical(out$names, character()) + expect_identical(out$data, group_data(df)) +}) + +test_that("throws tidyselect errors", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + compute_by(by = y, data = df) + }) +}) + +test_that("can't set `.by` with a grouped-df", { + df <- tibble(x = 1:5) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + compute_by(x, gdf) + }) +}) + +test_that("can't set `.by` with a rowwise-df", { + df <- tibble(x = 1:5) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + compute_by(x, rdf) + }) +}) + +test_that("can tweak the error args", { + df <- tibble(x = 1:5) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + compute_by(x, gdf, by_arg = "x", data_arg = "dat") + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-match.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-match.R new file mode 100644 index 000000000..54b5eeaa0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-match.R @@ -0,0 +1,72 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("LHS can match multiple values", { + expect_equal(case_match(1, 1:2 ~ "x"), "x") +}) + +test_that("LHS can match special values", { + expect_equal(case_match(NA, NA ~ "x"), "x") + expect_equal(case_match(NaN, NaN ~ "x"), "x") +}) + +test_that("RHS is recycled to match x", { + x <- 1:3 + expect_equal(case_match(x, c(1, 3) ~ x * 2), c(2, NA, 6)) +}) + +test_that("`NULL` values in `...` are dropped", { + expect_identical( + case_match(1:2, 1 ~ "a", NULL, 2 ~ "b", NULL), + c("a", "b") + ) +}) + +test_that("requires at least one condition", { + expect_snapshot(error = TRUE, { + case_match(1) + }) + expect_snapshot(error = TRUE, { + case_match(1, NULL) + }) +}) + +test_that("passes through `.default` correctly", { + expect_identical(case_match(1, 3 ~ 1, .default = 2), 2) + expect_identical(case_match(1:5, 6 ~ 1, .default = 2), rep(2, 5)) + expect_identical(case_match(1:5, 6 ~ 1:5, .default = 2:6), 2:6) +}) + +test_that("`.default` is part of common type computation", { + expect_identical(case_match(1, 1 ~ 1L, .default = 2), 1) + + expect_snapshot(error = TRUE, { + case_match(1, 1 ~ 1L, .default = "x") + }) +}) + +test_that("passes through `.ptype` correctly", { + expect_identical(case_match(1, 1 ~ 1, .ptype = integer()), 1L) +}) + +test_that("`NULL` formula element throws meaningful error", { + expect_snapshot(error = TRUE, { + case_match(1, 1 ~ NULL) + }) + expect_snapshot(error = TRUE, { + case_match(1, NULL ~ 1) + }) +}) + +test_that("throws chained errors when formula evaluation fails", { + expect_snapshot(error = TRUE, { + case_match(1, 1 ~ 2, 3 ~ stop("oh no!")) + }) + expect_snapshot(error = TRUE, { + case_match(1, 1 ~ 2, stop("oh no!") ~ 4) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-when.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-when.R new file mode 100644 index 000000000..add563e89 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-case-when.R @@ -0,0 +1,314 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("matches values in order", { + x <- 1:3 + expect_equal( + case_when( + x <= 1 ~ 1, + x <= 2 ~ 2, + x <= 3 ~ 3 + ), + c(1, 2, 3) + ) +}) + +test_that("unmatched gets missing value", { + x <- 1:3 + expect_equal( + case_when( + x <= 1 ~ 1, + x <= 2 ~ 2 + ), + c(1, 2, NA) + ) +}) + +test_that("missing values can be replaced (#1999)", { + x <- c(1:3, NA) + expect_equal( + case_when( + x <= 1 ~ 1, + x <= 2 ~ 2, + is.na(x) ~ 0 + ), + c(1, 2, NA, 0) + ) +}) + +test_that("NA conditions (#2927)", { + expect_equal( + case_when( + c(TRUE, FALSE, NA) ~ 1:3, + TRUE ~ 4L + ), + c(1L, 4L, 4L) + ) +}) + +test_that("any `TRUE` overrides an `NA`", { + x <- c(1, 2, NA, 3) + expect <- c("one", "not_one", "missing", "not_one") + + # `TRUE` overriding before the `NA` + expect_identical( + case_when( + is.na(x) ~ "missing", + x == 1 ~ "one", + .default = "not_one" + ), + expect + ) + + # `TRUE` overriding after the `NA` + expect_identical( + case_when( + x == 1 ~ "one", + is.na(x) ~ "missing", + .default = "not_one" + ), + expect + ) +}) + +test_that("atomic conditions (#2909)", { + expect_equal( + case_when( + TRUE ~ 1:3, + FALSE ~ 4:6 + ), + 1:3 + ) + expect_equal( + case_when( + NA ~ 1:3, + TRUE ~ 4:6 + ), + 4:6 + ) +}) + +test_that("zero-length conditions and values (#3041)", { + expect_equal( + case_when( + TRUE ~ integer(), + FALSE ~ integer() + ), + integer() + ) + expect_equal( + case_when( + logical() ~ 1, + logical() ~ 2 + ), + numeric() + ) +}) + +test_that("case_when can be used in anonymous functions (#3422)", { + res <- tibble(a = 1:3) %>% + duckplyr_mutate(b = (function(x) case_when(x < 2 ~ TRUE, .default = FALSE))(a)) %>% + duckplyr_pull() + expect_equal(res, c(TRUE, FALSE, FALSE)) +}) + +test_that("case_when() can be used inside duckplyr_mutate()", { + out <- mtcars[1:4, ] %>% + duckplyr_mutate(out = case_when( + cyl == 4 ~ 1, + .data[["am"]] == 1 ~ 2, + .default = 0 + )) %>% + duckplyr_pull() + expect_identical(out, c(2, 2, 1, 0)) +}) + +test_that("case_when() accepts logical conditions with attributes (#6678)", { + x <- structure(c(FALSE, TRUE), label = "foo") + expect_identical(case_when(x ~ 1, .default = 2), c(2, 1)) +}) + +test_that("can pass quosures to case_when()", { + fs <- local({ + x <- 3:1 + quos( + x < 2 ~ TRUE, + TRUE ~ FALSE + ) + }) + expect_identical(case_when(!!!fs), c(FALSE, FALSE, TRUE)) +}) + +test_that("can pass nested quosures to case_when()", { + fs <- local({ + foo <- mtcars$cyl[1:4] + quos( + !!quo(foo) == 4 ~ 1, + TRUE ~ 0 + ) + }) + expect_identical(case_when(!!!fs), c(0, 0, 1, 0)) +}) + +test_that("can pass unevaluated formulas to case_when()", { + x <- 6:8 + fs <- exprs( + x == 7L ~ TRUE, + TRUE ~ FALSE + ) + expect_identical(case_when(!!!fs), c(FALSE, TRUE, FALSE)) + + out <- local({ + x <- 7:9 + case_when(!!!fs) + }) + expect_identical(out, c(TRUE, FALSE, FALSE)) +}) + +test_that("unevaluated formulas can refer to data mask", { + fs <- exprs( + cyl == 4 ~ 1, + am == 1 ~ 2, + TRUE ~ 0 + ) + out <- mtcars[1:4, ] %>% duckplyr_mutate(out = case_when(!!!fs)) %>% duckplyr_pull() + expect_identical(out, c(2, 2, 1, 0)) +}) + +test_that("unevaluated formulas can contain quosures", { + quo <- local({ + n <- 4 + quo(n) + }) + fs <- exprs( + cyl == !!quo ~ 1, + am == 1 ~ 2, + TRUE ~ 0 + ) + out <- mtcars[1:4, ] %>% duckplyr_mutate(out = case_when(!!!fs)) %>% duckplyr_pull() + expect_identical(out, c(2, 2, 1, 0)) +}) + +test_that("NULL inputs are compacted", { + x <- 1:3 + + bool <- FALSE + out <- case_when( + x == 2 ~ TRUE, + if (bool) x == 3 ~ NA, + .default = FALSE + ) + expect_identical(out, c(FALSE, TRUE, FALSE)) + + bool <- TRUE + out <- case_when( + x == 2 ~ TRUE, + if (bool) x == 3 ~ NA, + .default = FALSE + ) + expect_identical(out, c(FALSE, TRUE, NA)) +}) + +test_that("passes through `.default` correctly", { + expect_identical(case_when(FALSE ~ 1, .default = 2), 2) + expect_identical(case_when(FALSE ~ 1:5, .default = 2), rep(2, 5)) + expect_identical(case_when(FALSE ~ 1:5, .default = 2:6), 2:6) +}) + +test_that("`.default` isn't part of recycling", { + # Because eventually we want to only take the output size from the LHS conditions, + # so having `.default` participate in the common size is a step in the wrong + # direction + expect_snapshot(error = TRUE, { + case_when(FALSE ~ 1L, .default = 2:5) + }) +}) + +test_that("`.default` is part of common type computation", { + expect_identical(case_when(TRUE ~ 1L, .default = 2), 1) + + expect_snapshot(error = TRUE, { + case_when(TRUE ~ 1L, .default = "x") + }) +}) + +test_that("passes through `.ptype` correctly", { + expect_identical(case_when(TRUE ~ 1, .ptype = integer()), 1L) +}) + +test_that("passes through `.size` correctly", { + expect_identical(case_when(TRUE ~ 1, .size = 2), c(1, 1)) + + expect_snapshot(error = TRUE, { + case_when(TRUE ~ 1:2, .size = 3) + }) +}) + +# Errors ------------------------------------------------------------------ + +test_that("invalid type errors are correct (#6261) (#6206)", { + expect_snapshot(error = TRUE, { + case_when(TRUE ~ 1, TRUE ~ "x") + }) +}) + +test_that("`NULL` formula element throws meaningful error", { + expect_snapshot(error = TRUE, { + case_when(1 ~ NULL) + }) + expect_snapshot(error = TRUE, { + case_when(NULL ~ 1) + }) +}) + +test_that("throws chained errors when formula evaluation fails", { + expect_snapshot(error = TRUE, { + case_when(1 ~ 2, 3 ~ stop("oh no!")) + }) + expect_snapshot(error = TRUE, { + case_when(1 ~ 2, stop("oh no!") ~ 4) + }) +}) + +test_that("case_when() give meaningful errors", { + expect_snapshot({ + (expect_error( + case_when( + c(TRUE, FALSE) ~ 1:3, + c(FALSE, TRUE) ~ 1:2 + ) + )) + + (expect_error( + case_when( + c(TRUE, FALSE) ~ 1, + c(FALSE, TRUE, FALSE) ~ 2, + c(FALSE, TRUE, FALSE, NA) ~ 3 + ) + )) + + (expect_error( + case_when(50 ~ 1:3) + )) + (expect_error( + case_when(paste(50)) + )) + (expect_error( + case_when(y ~ x, paste(50)) + )) + (expect_error( + case_when() + )) + (expect_error( + case_when(NULL) + )) + (expect_error( + case_when(~1:2) + )) + }) + +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-coalesce.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-coalesce.R new file mode 100644 index 000000000..160d7a753 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-coalesce.R @@ -0,0 +1,131 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("non-missing scalar replaces all missing values", { + x <- c(NA, 1) + expect_equal(coalesce(x, 1), c(1, 1)) +}) + +test_that("coerces to common type", { + expect_identical(coalesce(NA, 1), 1) + + f <- factor("x", levels = c("x", "y")) + expect_identical(coalesce(NA, f), f) +}) + +test_that("inputs are recycled to their common size", { + expect_identical(coalesce(1, c(2, 3)), c(1, 1)) +}) + +test_that("finds non-missing values in multiple positions", { + x1 <- c(1L, NA, NA) + x2 <- c(NA, 2L, NA) + x3 <- c(NA, NA, 3L) + + expect_equal(coalesce(x1, x2, x3), 1:3) +}) + +test_that("coalesce() gives meaningful error messages", { + expect_snapshot({ + (expect_error(coalesce(1:2, 1:3))) + (expect_error(coalesce(1:2, letters[1:2]))) + }) +}) + +test_that("coalesce() supports one-dimensional arrays (#5557)", { + x <- array(1:10) + out <- coalesce(x, 0L) + expect_identical(out, x) +}) + +test_that("only updates entirely missing matrix rows", { + x <- c( + 1, NA, + NA, NA + ) + x <- matrix(x, nrow = 2, byrow = TRUE) + + y <- c( + 2, 2, + NA, 1 + ) + y <- matrix(y, nrow = 2, byrow = TRUE) + + expect <- c( + 1, NA, + NA, 1 + ) + expect <- matrix(expect, nrow = 2, byrow = TRUE) + + expect_identical(coalesce(x, y), expect) +}) + +test_that("only updates entirely missing data frame rows", { + x <- tibble(x = c(1, NA), y = c(NA, NA)) + y <- tibble(x = c(2, NA), y = c(TRUE, TRUE)) + + expect <- tibble(x = c(1, NA), y = c(NA, TRUE)) + + expect_identical(coalesce(x, y), expect) +}) + +test_that("only updates entirely missing rcrd observations", { + x <- new_rcrd(list(x = c(1, NA), y = c(NA, NA))) + y <- new_rcrd(list(x = c(2, NA), y = c(TRUE, TRUE))) + + expect <- new_rcrd(list(x = c(1, NA), y = c(NA, TRUE))) + + expect_identical(coalesce(x, y), expect) +}) + +test_that("recycling is done on the values early", { + expect_identical(coalesce(1, 1:2), c(1, 1)) +}) + +test_that("`.ptype` overrides the common type (r-lib/funs#64)", { + x <- c(1L, NA) + expect_identical(coalesce(x, 99, .ptype = x), c(1L, 99L)) +}) + +test_that("`.size` overrides the common size", { + x <- 1L + + expect_snapshot(error = TRUE, { + coalesce(x, 1:2, .size = vec_size(x)) + }) +}) + +test_that("must have at least one non-`NULL` vector", { + expect_snapshot(error = TRUE, { + coalesce() + }) + expect_snapshot(error = TRUE, { + coalesce(NULL, NULL) + }) +}) + +test_that("`NULL`s are discarded (r-lib/funs#80)", { + expect_identical( + coalesce(c(1, NA, NA), NULL, c(1, 2, NA), NULL, 3), + c(1, 2, 3) + ) +}) + +test_that("inputs must be vectors", { + expect_snapshot(error = TRUE, { + coalesce(1, environment()) + }) +}) + +test_that("names in error messages are indexed correctly", { + expect_snapshot(error = TRUE, { + coalesce(1, "x") + }) + expect_snapshot(error = TRUE, { + coalesce(1, y = "x") + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-colwise-funs.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-colwise-funs.R new file mode 100644 index 000000000..b81089454 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-colwise-funs.R @@ -0,0 +1,14 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("as_fun_list() uses rlang auto-naming", { + nms <- names(as_fun_list(list(min, max), env())) + + # Just check they are labellised as literals enclosed in brackets to + # insulate from upstream changes + expect_true(all(grepl("^<", nms))) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-conditions.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-conditions.R new file mode 100644 index 000000000..cb96386fb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-conditions.R @@ -0,0 +1,211 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("can pass verb-level error call", { + dplyr_local_error_call(call("foo")) + expect_snapshot(error = TRUE, { + duckplyr_mutate(mtcars, 1 + "") + duckplyr_transmute(mtcars, 1 + "") + duckplyr_summarise(mtcars, 1 + "") + duckplyr_summarise(duckplyr_group_by(mtcars, cyl), 1 + "") + duckplyr_filter(mtcars, 1 + "") + duckplyr_arrange(mtcars, 1 + "") + duckplyr_select(mtcars, 1 + "") + duckplyr_slice(mtcars, 1 + "") + }) +}) + +test_that("can pass verb-level error call (example case)", { + my_verb <- function(data, var1, var2) { + dplyr_local_error_call() + duckplyr_pull(duckplyr_transmute(data, .result = {{ var1 }} * {{ var2 }})) + } + expect_snapshot(error = TRUE, { + my_verb(mtcars, 1 + "", am) + my_verb(mtcars, cyl, c(am, vs)) + }) +}) + +test_that("`err_locs()` works as expected", { + expect_snapshot(error = TRUE, err_locs(1.5)) + expect_snapshot(error = TRUE, err_locs(integer())) + + expect_snapshot({ + err_locs(1L) + err_locs(1:5) + err_locs(1:6) + err_locs(1:7) + }) +}) + +test_that("errors during dots collection are not enriched (#6178)", { + expect_snapshot(error = TRUE, { + duckplyr_mutate(mtcars, !!foobarbaz()) + duckplyr_transmute(mtcars, !!foobarbaz()) + duckplyr_select(mtcars, !!foobarbaz()) + duckplyr_arrange(mtcars, !!foobarbaz()) + duckplyr_filter(mtcars, !!foobarbaz()) + }) +}) + +test_that("warnings are collected for `last_dplyr_warnings()`", { + skip_if_not_installed("base", "3.6.0") + + local_options( + rlang_trace_format_srcrefs = FALSE + ) + + df <- tibble(id = 1:2) + f <- function() { + warning("msg") + 1 + } + + reset_dplyr_warnings() + expect_snapshot({ + "Ungrouped" + df %>% + duckplyr_mutate(x = f()) %>% + invisible() + last_dplyr_warnings() + }) + + reset_dplyr_warnings() + expect_snapshot({ + "Grouped" + df %>% + duckplyr_group_by(id) %>% + duckplyr_mutate(x = f()) %>% + invisible() + last_dplyr_warnings() + }) + + reset_dplyr_warnings() + expect_snapshot({ + "Rowwise" + df %>% + duckplyr_rowwise() %>% + duckplyr_mutate(x = f()) %>% + invisible() + last_dplyr_warnings() + }) + + reset_dplyr_warnings() + expect_snapshot({ + "Multiple type of warnings within multiple verbs" + df %>% + duckplyr_group_by(g = f():n()) %>% + duckplyr_rowwise() %>% + duckplyr_mutate(x = f()) %>% + duckplyr_group_by(id) %>% + duckplyr_mutate(x = f()) %>% + invisible() + last_dplyr_warnings() + }) + + reset_dplyr_warnings() + expect_snapshot({ + "Truncated (1 more)" + df %>% + duckplyr_rowwise() %>% + duckplyr_mutate(x = f()) + last_dplyr_warnings(n = 1) + }) + + reset_dplyr_warnings() + expect_snapshot({ + "Truncated (several more)" + df <- tibble(id = 1:5) + df %>% + duckplyr_rowwise() %>% + duckplyr_mutate(x = f()) + last_dplyr_warnings(n = 1) + }) +}) + +test_that("complex backtraces with base and rlang warnings", { + skip_if_not_installed("base", "3.6.0") + local_options( + rlang_trace_format_srcrefs = FALSE + ) + reset_dplyr_warnings() + + df <- tibble(id = 1:3) + + f <- function(...) g(...) + g <- function(...) h(...) + h <- function(x, base = TRUE) { + if (base) { + warning("foo") + } else { + warn("foo") + } + x + } + + foo <- function() bar() + bar <- function() { + df %>% + duckplyr_group_by(x = f(1):n()) %>% + duckplyr_mutate(x = f(1, base = FALSE)) + } + + expect_snapshot({ + foo() + last_dplyr_warnings() + }) +}) + +test_that("`last_dplyr_warnings()` only records 5 backtraces", { + reset_dplyr_warnings() + + f <- function() { + warning("msg") + 1 + } + df <- tibble(id = 1:10) + + expect_warning( + df %>% + duckplyr_group_by(id) %>% + duckplyr_mutate(x = f()) + ) + + warnings <- last_dplyr_warnings(Inf) + + traces <- map(warnings, `[[`, "trace") + expect_equal( + sum(map_lgl(traces, is_null)), + 5 + ) +}) + +test_that("can collect warnings in main verbs", { + reset_dplyr_warnings() + + f <- function() { + warning("foo") + TRUE + } + + expect_snapshot({ + invisible( + mtcars %>% + duckplyr_rowwise() %>% + duckplyr_filter(f()) %>% + duckplyr_arrange(f()) %>% + duckplyr_mutate(a = f()) %>% + duckplyr_summarise(b = f()) + ) + + warnings <- last_dplyr_warnings(Inf) + warnings[[1]] # duckplyr_filter() + warnings[[33]] # duckplyr_arrange() + warnings[[65]] # duckplyr_mutate() + warnings[[97]] # duckplyr_summarise() + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-consecutive-id.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-consecutive-id.R new file mode 100644 index 000000000..af6d2b870 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-consecutive-id.R @@ -0,0 +1,31 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("works with simple vectors", { + expect_equal(consecutive_id(c(1, 1, 2, 1, 2)), c(1, 1, 2, 3, 4)) +}) + +test_that("handles data frames", { + df <- tibble(x = c(1, 1, 1, 1), y = c(1, 2, 2, 1)) + expect_equal(consecutive_id(df), c(1, 2, 2, 3)) +}) + +test_that("follows recycling rules", { + expect_equal(consecutive_id(double(), 1), integer()) + expect_equal(consecutive_id(1:2, 1), 1:2) + + expect_snapshot(error = TRUE, { + consecutive_id(1:3, 1:4) + }) +}) + +test_that("generates useful errors", { + expect_snapshot(error = TRUE, { + consecutive_id(x = 1:4) + consecutive_id(mean) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-context.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-context.R new file mode 100644 index 000000000..e5ff8d3ed --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-context.R @@ -0,0 +1,80 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("cur_group() works", { + df <- tibble(g = 1, x = 1) + gf <- duckplyr_group_by(df, g) + + expect_equal( + df %>% duckplyr_summarise(key = list(cur_group())) %>% duckplyr_pull(key), + list(tibble(.rows = 1L)) + ) + expect_equal( + gf %>% duckplyr_summarise(key = list(cur_group())) %>% duckplyr_pull(key), + list(tibble(g = 1)) + ) + +}) + +test_that("cur_group() works with empty grouped data frame (#6304)", { + df <- tibble(x = integer()) + gdf <- duckplyr_group_by(df, x) + + out <- duckplyr_mutate(df, y = cur_group()) + expect_identical(out$y, tibble()) + + out <- duckplyr_mutate(gdf, y = cur_group()) + expect_identical(out$y, tibble(x = integer())) +}) + +test_that("cur_group_idx() gives unique id", { + df <- tibble(x = c("b", "a", "b")) + gf <- duckplyr_group_by(df, x) + + expect_equal( + duckplyr_summarise(gf, id = cur_group_id()), + tibble(x = c("a", "b"), id = 1:2) + ) + expect_equal( + duckplyr_mutate(gf, id = cur_group_id()), + duckplyr_group_by(tibble(x = df$x, id = c(2, 1, 2)), x) + ) +}) + +test_that("cur_group_rows() retrieves row position in original data", { + df <- tibble(x = c("b", "a", "b"), y = 1:3) + gf <- duckplyr_group_by(df, x) + + expect_equal( + df %>% duckplyr_summarise(x = list(cur_group_rows())) %>% duckplyr_pull(), + list(1:3) + ) + + expect_equal( + gf %>% duckplyr_summarise(x = list(cur_group_rows())) %>% duckplyr_pull(), + list(2L, c(1L, 3L)) + ) +}) + +test_that("give useful error messages when not applicable", { + expect_snapshot({ + (expect_error(n())) + + (expect_error(cur_column())) + (expect_error(cur_group())) + (expect_error(cur_group_id())) + (expect_error(cur_group_rows())) + }) + +}) + +test_that("group labels are correctly formatted", { + expect_snapshot({ + group_labels_details(c("a" = 1)) + group_labels_details(c("a" = 1, "b" = 2)) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-copy-to.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-copy-to.R new file mode 100644 index 000000000..0797b89ff --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-copy-to.R @@ -0,0 +1,22 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("`duckplyr_auto_copy()` is a no-op when they share the same source", { + skip("TODO duckdb") + df1 <- tibble(x = 1) + df2 <- tibble(x = 2) + + expect_identical(duckplyr_auto_copy(df1, df2), df2) +}) + +test_that("`duckplyr_auto_copy()` throws an informative error on different sources (#6798)", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_auto_copy(df, NULL) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-count-tally.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-count-tally.R new file mode 100644 index 000000000..677bc6a50 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-count-tally.R @@ -0,0 +1,229 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# count ------------------------------------------------------------------- + +test_that("count sorts output by keys by default", { + # Due to usage of `duckplyr_summarise()` internally + df <- tibble(x = c(2, 1, 1, 2, 1)) + out <- duckplyr_count(df, x) + expect_equal(out, tibble(x = c(1, 2), n = c(3, 2))) +}) + +test_that("count can sort output by `n`", { + df <- tibble(x = c(1, 1, 2, 2, 2)) + out <- duckplyr_count(df, x, sort = TRUE) + expect_equal(out, tibble(x = c(2, 1), n = c(3, 2))) +}) + +test_that("count can rename grouping columns", { + # But should it really allow this? + df <- tibble(x = c(2, 1, 1, 2, 1)) + out <- duckplyr_count(df, y = x) + expect_equal(out, tibble(y = c(1, 2), n = c(3, 2))) +}) + +test_that("informs if n column already present, unless overridden", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(n = c(1, 1, 2, 2, 2)) + expect_message(out <- duckplyr_count(df1, n), "already present") + expect_named(out, c("n", "nn")) + + # not a good idea, but supported + expect_message(out <- duckplyr_count(df1, n, name = "n"), NA) + expect_named(out, "n") + + expect_message(out <- duckplyr_count(df1, n, name = "nn"), NA) + expect_named(out, c("n", "nn")) + + df2 <- tibble(n = c(1, 1, 2, 2, 2), nn = 1:5) + expect_message(out <- duckplyr_count(df2, n), "already present") + expect_named(out, c("n", "nn")) + + expect_message(out <- duckplyr_count(df2, n, nn), "already present") + expect_named(out, c("n", "nn", "nnn")) +}) + +test_that("name must be string", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = c(1, 2)) + expect_snapshot(error = TRUE, duckplyr_count(df, x, name = 1)) + expect_snapshot(error = TRUE, duckplyr_count(df, x, name = letters)) +}) + +test_that("output includes empty levels with .drop = FALSE", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(f = factor("b", levels = c("a", "b", "c"))) + out <- duckplyr_count(df, f, .drop = FALSE) + expect_equal(out$n, c(0, 1, 0)) + + out <- duckplyr_count(duckplyr_group_by(df, f, .drop = FALSE)) + expect_equal(out$n, c(0, 1, 0)) +}) + +test_that("count preserves grouping", { + df <- tibble(g = c(1, 2, 2, 2)) + exp <- tibble(g = c(1, 2), n = c(1, 3)) + + expect_equal(df %>% duckplyr_count(g), exp) + expect_equal(df %>% duckplyr_group_by(g) %>% duckplyr_count(), exp %>% duckplyr_group_by(g)) +}) + +test_that("output preserves class & attributes where possible", { + df <- data.frame(g = c(1, 2, 2, 2)) + attr(df, "my_attr") <- 1 + + out <- df %>% duckplyr_count(g) + expect_s3_class(out, "data.frame", exact = TRUE) + expect_equal(attr(out, "my_attr"), 1) + + out <- df %>% duckplyr_group_by(g) %>% duckplyr_count() + expect_s3_class(out, "grouped_df") + expect_equal(duckplyr_group_vars(out), "g") + # duckplyr_summarise() currently drops attributes + expect_null(attr(out, "my_attr")) +}) + +test_that("works with dbplyr", { + skip_if_not_installed("dbplyr") + skip_if_not_installed("RSQLite") + + db <- dbplyr::memdb_frame(x = c(1, 1, 1, 2, 2)) + df1 <- db %>% duckplyr_count(x) %>% as_tibble() + expect_equal(df1, tibble(x = c(1, 2), n = c(3, 2))) + + df2 <- db %>% duckplyr_add_count(x) %>% as_tibble() + expect_equal(df2, tibble(x = c(1, 1, 1, 2, 2), n = c(3, 3, 3, 2, 2))) +}) + +test_that("dbplyr `duckplyr_count()` method has transient internal grouping (#6338, tidyverse/dbplyr#940)", { + skip_if_not_installed("dbplyr") + skip_if_not_installed("RSQLite") + + db <- dbplyr::memdb_frame( + x = c(1, 1, 1, 2, 2), + y = c("a", "a", "b", "c", "c") + ) + + df <- db %>% + duckplyr_count(x, y) %>% + duckplyr_collect() + + expect <- tibble( + x = c(1, 1, 2), + y = c("a", "b", "c"), + n = c(2L, 1L, 2L) + ) + + expect_false(is_grouped_df(df)) + expect_identical(df, expect) +}) + +test_that("can only explicitly chain together multiple tallies", { + expect_snapshot({ + df <- data.frame(g = c(1, 1, 2, 2), n = 1:4) + + df %>% duckplyr_count(g, wt = n) + df %>% duckplyr_count(g, wt = n) %>% duckplyr_count(wt = n) + df %>% duckplyr_count(n) + }) +}) + +test_that("wt = n() is deprecated", { + df <- data.frame(x = 1:3) + expect_warning(duckplyr_count(df, wt = n()), "`wt = n()`", fixed = TRUE) +}) + +test_that("duckplyr_count() owns errors (#6139)", { + expect_snapshot({ + (expect_error(duckplyr_count(mtcars, new = 1 + ""))) + (expect_error(duckplyr_count(mtcars, wt = 1 + ""))) + }) +}) + +# tally ------------------------------------------------------------------- + +test_that("tally can sort output", { + gf <- duckplyr_group_by(tibble(x = c(1, 1, 2, 2, 2)), x) + out <- tally(gf, sort = TRUE) + expect_equal(out, tibble(x = c(2, 1), n = c(3, 2))) +}) + +test_that("weighted tally drops NAs (#1145)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = c(1, 1, NA)) + expect_equal(tally(df, x)$n, 2) +}) + +test_that("tally() drops last group (#5199) ", { + df <- data.frame(x = 1, y = 2, z = 3) + + res <- expect_message(df %>% duckplyr_group_by(x, y) %>% tally(wt = z), NA) + expect_equal(duckplyr_group_vars(res), "x") +}) + +test_that("tally() owns errors (#6139)", { + expect_snapshot({ + (expect_error(tally(mtcars, wt = 1 + ""))) + }) +}) + +# add_count --------------------------------------------------------------- + +test_that("add_count preserves grouping", { + df <- tibble(g = c(1, 2, 2, 2)) + exp <- tibble(g = c(1, 2, 2, 2), n = c(1, 3, 3, 3)) + + expect_equal(df %>% duckplyr_add_count(g), exp) + expect_equal(df %>% duckplyr_group_by(g) %>% duckplyr_add_count(), exp %>% duckplyr_group_by(g)) +}) + +test_that(".drop is deprecated", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "warning") + + df <- tibble(f = factor("b", levels = c("a", "b", "c"))) + expect_warning(out <- duckplyr_add_count(df, f, .drop = FALSE), "deprecated") +}) + +test_that("duckplyr_add_count() owns errors (#6139)", { + expect_snapshot({ + (expect_error(duckplyr_add_count(mtcars, new = 1 + ""))) + (expect_error(duckplyr_add_count(mtcars, wt = 1 + ""))) + }) +}) + +# add_tally --------------------------------------------------------------- + +test_that("can add tallies of a variable", { + df <- tibble(a = c(2, 1, 1)) + expect_equal( + df %>% duckplyr_group_by(a) %>% add_tally(), + duckplyr_group_by(tibble(a = c(2, 1, 1), n = c(1, 2, 2)), a) + ) +}) + +test_that("add_tally can be given a weighting variable", { + df <- data.frame(a = c(1, 1, 2, 2, 2), w = c(1, 1, 2, 3, 4)) + + out <- df %>% duckplyr_group_by(a) %>% add_tally(wt = w) + expect_equal(out$n, c(2, 2, 9, 9, 9)) + + out <- df %>% duckplyr_group_by(a) %>% add_tally(wt = w + 1) + expect_equal(out$n, c(4, 4, 12, 12, 12)) +}) + +test_that("can override output column", { + df <- data.frame(g = c(1, 1, 2, 2, 2), x = c(3, 2, 5, 5, 5)) + expect_named(add_tally(df, name = "xxx"), c("g", "x", "xxx")) +}) + +test_that("add_tally() owns errors (#6139)", { + expect_snapshot({ + (expect_error(add_tally(mtcars, wt = 1 + ""))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-desc.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-desc.R new file mode 100644 index 000000000..9e4274ace --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-desc.R @@ -0,0 +1,10 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("errors cleanly on non-vectors", { + expect_snapshot(desc(mean), error = TRUE) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-distinct.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-distinct.R new file mode 100644 index 000000000..03cb7df27 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-distinct.R @@ -0,0 +1,196 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("distinct equivalent to local unique when keep_all is TRUE", { + df <- data.frame( + x = c(1, 1, 1, 1), + y = c(1, 1, 2, 2), + z = c(1, 2, 1, 2) + ) + + expect_equal(duckplyr_distinct(df), unique(df)) +}) + +test_that("distinct for single column works as expected (#1937)", { + df <- tibble( + x = c(1, 1, 1, 1), + y = c(1, 1, 2, 2), + z = c(1, 2, 1, 2) + ) + + expect_equal(duckplyr_distinct(df, x, .keep_all = FALSE), unique(df["x"])) + expect_equal(duckplyr_distinct(df, y, .keep_all = FALSE), unique(df["y"])) +}) + +test_that("distinct works for 0-sized columns (#1437)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:10) %>% duckplyr_select(-x) + ddf <- duckplyr_distinct(df) + expect_equal(df_n_col(ddf), 0L) +}) + +test_that("if no variables specified, uses all", { + df <- tibble(x = c(1, 1), y = c(2, 2)) + expect_equal(duckplyr_distinct(df), tibble(x = 1, y = 2)) +}) + +test_that("distinct keeps only specified cols", { + df <- tibble(x = c(1, 1, 1), y = c(1, 1, 1)) + expect_equal(df %>% duckplyr_distinct(x), tibble(x = 1)) +}) + +test_that("unless .keep_all = TRUE", { + df <- tibble(x = c(1, 1, 1), y = 3:1) + + expect_equal(df %>% duckplyr_distinct(x), tibble(x = 1)) + expect_equal(df %>% duckplyr_distinct(x, .keep_all = TRUE), tibble(x = 1, y = 3L)) +}) + +test_that("distinct doesn't duplicate columns", { + df <- tibble(a = 1:3, b = 4:6) + + expect_named(df %>% duckplyr_distinct(a, a), "a") + expect_named(df %>% duckplyr_group_by(a) %>% duckplyr_distinct(a), "a") +}) + +test_that("grouped distinct always includes group cols", { + df <- tibble(g = c(1, 2), x = c(1, 2)) + + out <- df %>% duckplyr_group_by(g) %>% duckplyr_distinct(x) + expect_named(out, c("g", "x")) +}) + +test_that("empty grouped distinct equivalent to empty ungrouped", { + df <- tibble(g = c(1, 2), x = c(1, 2)) + + df1 <- df %>% duckplyr_distinct() %>% duckplyr_group_by(g) + df2 <- df %>% duckplyr_group_by(g) %>% duckplyr_distinct() + + expect_equal(df1, df2) +}) + +test_that("distinct on a new, mutated variable is equivalent to mutate followed by distinct", { + df <- tibble(g = c(1, 2), x = c(1, 2)) + + df1 <- df %>% duckplyr_distinct(aa = g * 2) + df2 <- df %>% duckplyr_mutate(aa = g * 2) %>% duckplyr_distinct(aa) + + expect_equal(df1, df2) +}) + +test_that("distinct on a new, copied variable is equivalent to mutate followed by distinct (#3234)", { + df <- tibble(g = c(1, 2), x = c(1, 2)) + + df1 <- df %>% duckplyr_distinct(aa = g) + df2 <- df %>% duckplyr_mutate(aa = g) %>% duckplyr_distinct(aa) + + expect_equal(df1, df2) +}) + +test_that("distinct on a dataframe or tibble with columns of type list throws an error", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble( + a = c("1", "1", "2", "2", "3", "3"), + b = list("A") + ) + df2 <- data.frame(x = 1:5, y = I(list(1:3, 2:4, 3:5, 4:6, 5:7))) + + expect_identical(df2 %>% duckplyr_distinct(), df2) + expect_identical(df %>% duckplyr_distinct(), df %>% duckplyr_slice(c(1, 3, 5))) +}) + +test_that("distinct handles 0 columns edge case (#2954)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + d <- duckplyr_select(data.frame(x= c(1, 1)), one_of(character(0))) + res <- duckplyr_distinct(d) + expect_equal(nrow(res), 1L) + + expect_equal(nrow(duckplyr_distinct(tibble())), 0L) +}) + +test_that("distinct respects order of the specified variables (#3195, #6156)",{ + d <- data.frame(x = 1:2, y = 3:4) + expect_named(duckplyr_distinct(d, y, x), c("y", "x")) +}) + +test_that("distinct adds grouping variables to front if missing",{ + d <- data.frame(x = 1:2, y = 3:4) + expect_named(duckplyr_distinct(duckplyr_group_by(d, y), x), c("y", "x")) + expect_named(duckplyr_distinct(duckplyr_group_by(d, y), x, y), c("x", "y")) +}) + +test_that("duckplyr_distinct() understands both NA variants (#4516)", { + df <- data.frame(col_a = c(1, NA, NA)) + df$col_a <- df$col_a+0 + df$col_a[2] <- NA_real_ + expect_equal(nrow(duckplyr_distinct(df)), 2L) + + df_1 <- data.frame(col_a = c(1, NA)) + df_2 <- data.frame(col_a = c(1, NA)) + + df_1$col_a <- df_1$col_a+0 + df_2$col_a <- df_2$col_a+0 + + df_1$col_a[2] <- NA + expect_equal(nrow(duckplyr_setdiff(df_1, df_2)), 0L) +}) + +test_that("duckplyr_distinct() handles auto splicing", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_equal( + iris %>% duckplyr_distinct(Species), + iris %>% duckplyr_distinct(data.frame(Species=Species)) + ) + + expect_equal( + iris %>% duckplyr_distinct(Species), + iris %>% duckplyr_distinct(pick(Species)) + ) + + expect_equal( + iris %>% duckplyr_mutate(across(starts_with("Sepal"), round)) %>% duckplyr_distinct(Sepal.Length, Sepal.Width), + iris %>% duckplyr_distinct(across(starts_with("Sepal"), round)) + ) +}) + +test_that("distinct preserves grouping", { + gf <- duckplyr_group_by(tibble(x = c(1, 1, 2, 2), y = x), x) + + i <- count_regroups(out <- duckplyr_distinct(gf)) + expect_equal(i, 0) + expect_equal(duckplyr_group_vars(out), "x") + + i <- count_regroups(out <- duckplyr_distinct(gf, x = x + 2)) + expect_equal(i, 1) + expect_equal(duckplyr_group_vars(out), "x") +}) + +test_that("duckplyr_distinct() preserves attributes on bare data frames (#6318)", { + df <- vctrs::data_frame(x = c(1, 1)) + attr(df, "foo") <- "bar" + + out <- duckplyr_distinct(df, x) + expect_identical(attr(out, "foo"), "bar") + + out <- duckplyr_distinct(df, y = x + 1L) + expect_identical(attr(out, "foo"), "bar") +}) + +# Errors ------------------------------------------------------------------ + +test_that("distinct errors when selecting an unknown column (#3140)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot({ + df <- tibble(g = c(1, 2), x = c(1, 2)) + + (expect_error(df %>% duckplyr_distinct(aa, x))) + (expect_error(df %>% duckplyr_distinct(aa, bb))) + (expect_error(df %>% duckplyr_distinct(.data$aa))) + + (expect_error(df %>% duckplyr_distinct(y = a + 1))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-filter.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-filter.R new file mode 100644 index 000000000..9d5608bc8 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-filter.R @@ -0,0 +1,729 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("filter handles passing ...", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1:4) + + f <- function(...) { + x1 <- 4 + f1 <- function(y) y + duckplyr_filter(df, ..., f1(x1) > x) + } + g <- function(...) { + x2 <- 2 + f(x > x2, ...) + } + res <- g() + expect_equal(res$x, 3L) + + df <- duckplyr_group_by(df, x) + res <- g() + expect_equal(res$x, 3L) +}) + +test_that("filter handles simple symbols", { + df <- data.frame(x = 1:4, test = rep(c(T, F), each = 2)) + res <- duckplyr_filter(df, test) + + gdf <- duckplyr_group_by(df, x) + res <- duckplyr_filter(gdf, test) + + h <- function(data) { + test2 <- c(T, T, F, F) + duckplyr_filter(data, test2) + } + expect_equal(h(df), df[1:2, ]) + + f <- function(data, ...) { + one <- 1 + duckplyr_filter(data, test, x > one, ...) + } + g <- function(data, ...) { + four <- 4 + f(data, x < four, ...) + } + res <- g(df) + expect_equal(res$x, 2L) + expect_equal(res$test, TRUE) + + res <- g(gdf) + expect_equal(res$x, 2L) + expect_equal(res$test, TRUE) +}) + +test_that("filter handlers scalar results", { + expect_equal(duckplyr_filter(mtcars, min(mpg) > 0), mtcars, ignore_attr = TRUE) + expect_equal(duckplyr_filter(duckplyr_group_by(mtcars, cyl), min(mpg) > 0), duckplyr_group_by(mtcars, cyl)) +}) + +test_that("filter propagates attributes", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + date.start <- ISOdate(2010, 01, 01, 0) + test <- data.frame(Date = ISOdate(2010, 01, 01, 1:10)) + test2 <- test %>% duckplyr_filter(Date < ISOdate(2010, 01, 01, 5)) + expect_equal(test$Date[1:4], test2$Date) +}) + +test_that("filter discards NA", { + temp <- data.frame( + i = 1:5, + x = c(NA, 1L, 1L, 0L, 0L) + ) + res <- duckplyr_filter(temp, x == 1) + expect_equal(nrow(res), 2L) +}) + +test_that("date class remains on filter (#273)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + x1 <- x2 <- data.frame( + date = seq.Date(as.Date("2013-01-01"), by = "1 days", length.out = 2), + var = c(5, 8) + ) + x1.filter <- x1 %>% duckplyr_filter(as.Date(date) > as.Date("2013-01-01")) + x2$date <- x2$date + 1 + x2.filter <- x2 %>% duckplyr_filter(as.Date(date) > as.Date("2013-01-01")) + + expect_equal(class(x1.filter$date), "Date") + expect_equal(class(x2.filter$date), "Date") +}) + +test_that("filter handles $ correctly (#278)", { + d1 <- tibble( + num1 = as.character(sample(1:10, 1000, T)), + var1 = runif(1000), + ) + d2 <- data.frame(num1 = as.character(1:3), stringsAsFactors = FALSE) + + res1 <- d1 %>% duckplyr_filter(num1 %in% c("1", "2", "3")) + res2 <- d1 %>% duckplyr_filter(num1 %in% d2$num1) + expect_equal(res1, res2) +}) + +test_that("duckplyr_filter() returns the input data if no parameters are given", { + expect_identical(duckplyr_filter(mtcars), mtcars) + expect_identical(duckplyr_filter(mtcars, !!!list()), mtcars) +}) + +test_that("$ does not end call traversing. #502", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # Suppose some analysis options are set much earlier in the script + analysis_opts <- list(min_outcome = 0.25) + + # Generate some dummy data + d <- expand.grid(Subject = 1:3, TrialNo = 1:2, Time = 1:3) %>% + as_tibble() %>% + duckplyr_arrange(Subject, TrialNo, Time) %>% + duckplyr_mutate(Outcome = (1:18 %% c(5, 7, 11)) / 10) + + # Do some aggregation + trial_outcomes <- d %>% + duckplyr_group_by(Subject, TrialNo) %>% + duckplyr_summarise(MeanOutcome = mean(Outcome), .groups = "drop") + + left <- duckplyr_filter(trial_outcomes, MeanOutcome < analysis_opts$min_outcome) + right <- duckplyr_filter(trial_outcomes, analysis_opts$min_outcome > MeanOutcome) + + expect_equal(left, right) +}) + +test_that("filter handles POSIXlt", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + datesDF <- read.csv(stringsAsFactors = FALSE, text = " +X +2014-03-13 16:08:19 +2014-03-13 16:16:23 +2014-03-13 16:28:28 +2014-03-13 16:28:54 +") + + datesDF$X <- as.POSIXlt(datesDF$X) + expect_equal( + nrow(duckplyr_filter(datesDF, X > as.POSIXlt("2014-03-13"))), + 4 + ) +}) + +test_that("filter handles complex vectors (#436)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + d <- data.frame(x = 1:10, y = 1:10 + 2i) + expect_equal(duckplyr_filter(d, x < 4)$y, 1:3 + 2i) + expect_equal(duckplyr_filter(d, Re(y) < 4)$y, 1:3 + 2i) +}) + +test_that("%in% works as expected (#126)", { + df <- tibble(a = c("a", "b", "ab"), g = c(1, 1, 2)) + + res <- df %>% duckplyr_filter(a %in% letters) + expect_equal(nrow(res), 2L) + + res <- df %>% duckplyr_group_by(g) %>% duckplyr_filter(a %in% letters) + expect_equal(nrow(res), 2L) +}) + +test_that("row_number does not segfault with example from #781", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + z <- data.frame(a = c(1, 2, 3)) + b <- "a" + res <- z %>% duckplyr_filter(row_number(b) == 2) + expect_equal(nrow(res), 0L) +}) + +test_that("row_number works on 0 length columns (#3454)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_identical( + duckplyr_mutate(tibble(), a = row_number()), + tibble(a = integer()) + ) +}) + +test_that("filter does not alter expression (#971)", { + my_filter <- ~ am == 1 + expect_equal(my_filter[[2]][[2]], as.name("am")) +}) + +test_that("hybrid evaluation handles $ correctly (#1134)", { + df <- tibble(x = 1:10, g = rep(1:5, 2)) + res <- df %>% duckplyr_group_by(g) %>% duckplyr_filter(x > min(df$x)) + expect_equal(nrow(res), 9L) +}) + +test_that("filter correctly handles empty data frames (#782)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + res <- tibble() %>% duckplyr_filter(F) + expect_equal(nrow(res), 0L) + expect_equal(length(names(res)), 0L) +}) + +test_that("duckplyr_filter(.,TRUE,TRUE) works (#1210)", { + df <- data.frame(x = 1:5) + res <- duckplyr_filter(df, TRUE, TRUE) + expect_equal(res, df) +}) + +test_that("filter, slice and arrange preserves attributes (#1064)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- structure( + data.frame(x = 1:10, g1 = rep(1:2, each = 5), g2 = rep(1:5, 2)), + meta = "this is important" + ) + res <- duckplyr_filter(df, x < 5) %>% attr("meta") + expect_equal(res, "this is important") + + res <- duckplyr_filter(df, x < 5, x > 4) %>% attr("meta") + expect_equal(res, "this is important") + + res <- df %>% duckplyr_slice(1:50) %>% attr("meta") + expect_equal(res, "this is important") + + res <- df %>% duckplyr_arrange(x) %>% attr("meta") + expect_equal(res, "this is important") +}) + +test_that("filter works with rowwise data (#1099)", { + df <- tibble(First = c("string1", "string2"), Second = c("Sentence with string1", "something")) + res <- df %>% duckplyr_rowwise() %>% duckplyr_filter(grepl(First, Second, fixed = TRUE)) + expect_equal(nrow(res), 1L) + expect_equal(df[1, ], duckplyr_ungroup(res)) +}) + +test_that("grouped filter handles indices (#880)", { + res <- iris %>% duckplyr_group_by(Species) %>% duckplyr_filter(Sepal.Length > 5) + res2 <- duckplyr_mutate(res, Petal = Petal.Width * Petal.Length) + expect_equal(nrow(res), nrow(res2)) + expect_equal(group_rows(res), group_rows(res2)) + expect_equal(duckplyr_group_keys(res), duckplyr_group_keys(res2)) +}) + +test_that("duckplyr_filter(FALSE) handles indices", { + out <- mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(FALSE, .preserve = TRUE) %>% + group_rows() + expect_identical(out, list_of(integer(), integer(), integer(), .ptype = integer())) + + out <- mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(FALSE, .preserve = FALSE) %>% + group_rows() + expect_identical(out, list_of(.ptype = integer())) +}) + +test_that("filter handles S4 objects (#1366)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + env <- environment() + Numbers <- suppressWarnings(setClass( + "Numbers", + slots = c(foo = "numeric"), contains = "integer", where = env + )) + setMethod("[", "Numbers", function(x, i, ...){ + Numbers(unclass(x)[i, ...], foo = x@foo) + }) + on.exit(removeClass("Numbers", where = env)) + + df <- data.frame(x = Numbers(1:10, foo = 10)) + res <- duckplyr_filter(df, x > 3) + expect_s4_class(res$x, "Numbers") + expect_equal(res$x@foo, 10) +}) + +test_that("hybrid lag and default value for string columns work (#1403)", { + res <- mtcars %>% + duckplyr_mutate(xx = LETTERS[gear]) %>% + duckplyr_filter(xx == lag(xx, default = "foo")) + xx <- LETTERS[mtcars$gear] + ok <- xx == lag(xx, default = "foo") + expect_equal(xx[ok], res$xx) + + res <- mtcars %>% + duckplyr_mutate(xx = LETTERS[gear]) %>% + duckplyr_filter(xx == lead(xx, default = "foo")) + xx <- LETTERS[mtcars$gear] + ok <- xx == lead(xx, default = "foo") + expect_equal(xx[ok], res$xx) +}) + +# .data and .env tests now in test-hybrid-traverse.R + +test_that("filter handles raw vectors (#1803)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1:3, b = as.raw(1:3)) + expect_identical(duckplyr_filter(df, a == 1), tibble(a = 1L, b = as.raw(1))) + expect_identical(duckplyr_filter(df, b == 1), tibble(a = 1L, b = as.raw(1))) +}) + +test_that("`vars` attribute is not added if empty (#2772)", { + expect_identical(tibble(x = 1:2) %>% duckplyr_filter(x == 1), tibble(x = 1L)) +}) + +test_that("filter handles list columns", { + res <- tibble(a=1:2, x = list(1:10, 1:5)) %>% + duckplyr_filter(a == 1) %>% + duckplyr_pull(x) + expect_equal(res, list(1:10)) + + res <- tibble(a=1:2, x = list(1:10, 1:5)) %>% + duckplyr_group_by(a) %>% + duckplyr_filter(a == 1) %>% + duckplyr_pull(x) + expect_equal(res, list(1:10)) +}) + +test_that("hybrid function row_number does not trigger warning in filter (#3750)", { + out <- tryCatch({ + mtcars %>% duckplyr_filter(row_number() > 1, row_number() < 5); TRUE + }, warning = function(w) FALSE ) + expect_true(out) +}) + +test_that("duckplyr_filter() preserve order across groups (#3989)", { + df <- tibble(g = c(1, 2, 1, 2, 1), time = 5:1, x = 5:1) + res1 <- df %>% + duckplyr_group_by(g) %>% + duckplyr_filter(x <= 4) %>% + duckplyr_arrange(time) + + res2 <- df %>% + duckplyr_group_by(g) %>% + duckplyr_arrange(time) %>% + duckplyr_filter(x <= 4) + + res3 <- df %>% + duckplyr_filter(x <= 4) %>% + duckplyr_arrange(time) %>% + duckplyr_group_by(g) + + expect_equal(res1, res2) + expect_equal(res1, res3) + expect_false(is.unsorted(res1$time)) + expect_false(is.unsorted(res2$time)) + expect_false(is.unsorted(res3$time)) +}) + +test_that("duckplyr_filter() with two conditions does not freeze (#4049)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_identical( + iris %>% duckplyr_filter(Sepal.Length > 7, Petal.Length < 6), + iris %>% duckplyr_filter(Sepal.Length > 7 & Petal.Length < 6) + ) +}) + +test_that("duckplyr_filter() handles matrix and data frame columns (#3630)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble( + x = 1:2, + y = matrix(1:4, ncol = 2), + z = data.frame(A = 1:2, B = 3:4) + ) + expect_equal(duckplyr_filter(df, x == 1), df[1, ]) + expect_equal(duckplyr_filter(df, y[,1] == 1), df[1, ]) + expect_equal(duckplyr_filter(df, z$A == 1), df[1, ]) + + gdf <- duckplyr_group_by(df, x) + expect_equal(duckplyr_filter(gdf, x == 1), gdf[1, ]) + expect_equal(duckplyr_filter(gdf, y[,1] == 1), gdf[1, ]) + expect_equal(duckplyr_filter(gdf, z$A == 1), gdf[1, ]) + + gdf <- duckplyr_group_by(df, y) + expect_equal(duckplyr_filter(gdf, x == 1), gdf[1, ]) + expect_equal(duckplyr_filter(gdf, y[,1] == 1), gdf[1, ]) + expect_equal(duckplyr_filter(gdf, z$A == 1), gdf[1, ]) + + gdf <- duckplyr_group_by(df, z) + expect_equal(duckplyr_filter(gdf, x == 1), gdf[1, ]) + expect_equal(duckplyr_filter(gdf, y[,1] == 1), gdf[1, ]) + expect_equal(duckplyr_filter(gdf, z$A == 1), gdf[1, ]) +}) + +test_that("duckplyr_filter() handles named logical (#4638)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + tbl <- tibble(a = c(a = TRUE)) + expect_equal(duckplyr_filter(tbl, a), tbl) +}) + +test_that("duckplyr_filter() allows named constants that resolve to logical vectors (#4612)", { + filters <- mtcars %>% + duckplyr_transmute( + cyl %in% 6:8, + hp / drat > 50 + ) + + expect_identical( + mtcars %>% duckplyr_filter(!!!filters), + mtcars %>% duckplyr_filter(!!!unname(filters)) + ) +}) + +test_that("duckplyr_filter() allows 1 dimension arrays", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = array(c(TRUE, FALSE, TRUE))) + expect_identical(duckplyr_filter(df, x), df[c(1, 3),]) +}) + +test_that("duckplyr_filter() allows matrices with 1 column with a deprecation warning (#6091)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2) + expect_snapshot({ + out <- duckplyr_filter(df, matrix(c(TRUE, FALSE), nrow = 2)) + }) + expect_identical(out, tibble(x = 1L)) + + # Only warns once when grouped + df <- tibble(x = c(1, 1, 2, 2)) + gdf <- duckplyr_group_by(df, x) + expect_snapshot({ + out <- duckplyr_filter(gdf, matrix(c(TRUE, FALSE), nrow = 2)) + }) + expect_identical(out, duckplyr_group_by(tibble(x = c(1, 2)), x)) +}) + +test_that("duckplyr_filter() disallows matrices with >1 column", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3) + + expect_snapshot({ + (expect_error(duckplyr_filter(df, matrix(TRUE, nrow = 3, ncol = 2)))) + }) +}) + +test_that("duckplyr_filter() disallows arrays with >2 dimensions", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3) + + expect_snapshot({ + (expect_error(duckplyr_filter(df, array(TRUE, dim = c(3, 1, 1))))) + }) +}) + +test_that("duckplyr_filter() gives useful error messages", { + expect_snapshot({ + # wrong type + (expect_error( + iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(1:n()) + )) + (expect_error( + iris %>% + duckplyr_filter(1:n()) + )) + + # matrix with > 1 columns + (expect_error( + duckplyr_filter(data.frame(x = 1:2), matrix(c(TRUE, FALSE, TRUE, FALSE), nrow = 2)) + )) + + # wrong size + (expect_error( + iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(c(TRUE, FALSE)) + )) + (expect_error( + iris %>% + duckplyr_rowwise(Species) %>% + duckplyr_filter(c(TRUE, FALSE)) + )) + (expect_error( + iris %>% + duckplyr_filter(c(TRUE, FALSE)) + )) + + # wrong size in column + (expect_error( + iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(data.frame(c(TRUE, FALSE))) + )) + (expect_error( + iris %>% + duckplyr_rowwise() %>% + duckplyr_filter(data.frame(c(TRUE, FALSE))) + )) + (expect_error( + iris %>% + duckplyr_filter(data.frame(c(TRUE, FALSE))) + )) + (expect_error( + tibble(x = 1) %>% + duckplyr_filter(c(TRUE, TRUE)) + )) + + # wrong type in column + (expect_error( + iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(data.frame(Sepal.Length > 3, 1:n())) + )) + (expect_error( + iris %>% + duckplyr_filter(data.frame(Sepal.Length > 3, 1:n())) + )) + + # evaluation error + (expect_error( + mtcars %>% duckplyr_filter(`_x`) + )) + (expect_error( + mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(`_x`) + )) + + # named inputs + (expect_error( + duckplyr_filter(mtcars, x = 1) + )) + (expect_error( + duckplyr_filter(mtcars, y > 2, z = 3) + )) + (expect_error( + duckplyr_filter(mtcars, TRUE, x = 1) + )) + + # ts + (expect_error( + duckplyr_filter(ts(1:10)) + )) + + # Error that contains { + (expect_error( + tibble() %>% duckplyr_filter(stop("{")) + )) + + # across() in duckplyr_filter() does not warn yet + data.frame(x = 1, y = 1) %>% + duckplyr_filter(across(everything(), ~ .x > 0)) + + data.frame(x = 1, y = 1) %>% + duckplyr_filter(data.frame(x > 0, y > 0)) + }) +}) + +test_that("filter preserves grouping", { + gf <- duckplyr_group_by(tibble(g = c(1, 1, 1, 2, 2), x = 1:5), g) + + i <- count_regroups(out <- duckplyr_filter(gf, x %in% c(3,4))) + expect_equal(i, 0L) + expect_equal(duckplyr_group_vars(gf), "g") + expect_equal(group_rows(out), list_of(1L, 2L)) + + i <- count_regroups(out <- duckplyr_filter(gf, x < 3)) + expect_equal(i, 0L) + expect_equal(duckplyr_group_vars(gf), "g") + expect_equal(group_rows(out), list_of(c(1L, 2L))) +}) + +test_that("duckplyr_filter() with empty dots still calls dplyr_row_slice()", { + tbl <- new_tibble(list(x = 1), nrow = 1L) + foo <- structure(tbl, class = c("foo_df", class(tbl))) + + local_methods( + # `foo_df` always loses class when row slicing + dplyr_row_slice.foo_df = function(data, i, ...) { + out <- NextMethod() + new_tibble(out, nrow = nrow(out)) + } + ) + + expect_s3_class(duckplyr_filter(foo), class(tbl), exact = TRUE) + expect_s3_class(duckplyr_filter(foo, x == 1), class(tbl), exact = TRUE) +}) + +test_that("can duckplyr_filter() with unruly class", { + local_methods( + `[.dplyr_foobar` = function(x, i, ...) new_dispatched_quux(vec_slice(x, i)), + dplyr_row_slice.dplyr_foobar = function(x, i, ...) x[i, ] + ) + + df <- foobar(data.frame(x = 1:3)) + expect_identical( + duckplyr_filter(df, x <= 2), + quux(data.frame(x = 1:2, dispatched = TRUE)) + ) +}) + +test_that("duckplyr_filter() preserves the call stack on error (#5308)", { + foobar <- function() stop("foo") + + stack <- NULL + expect_error( + withCallingHandlers( + error = function(...) stack <<- sys.calls(), + duckplyr_filter(mtcars, foobar()) + ) + ) + + expect_true(some(stack, is_call, "foobar")) +}) + +test_that("if_any() and if_all() work", { + df <- tibble(x1 = 1:10, x2 = c(1:5, 10:6)) + expect_equal( + duckplyr_filter(df, if_all(starts_with("x"), ~ . > 6)), + duckplyr_filter(df, x1 > 6 & x2 > 6) + ) + + expect_equal( + duckplyr_filter(df, if_any(starts_with("x"), ~ . > 6)), + duckplyr_filter(df, x1 > 6 | x2 > 6) + ) +}) + +test_that("filter keeps zero length groups", { + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal(duckplyr_group_size(duckplyr_filter(df, f == 1)), c(2, 0, 0) ) +}) + +test_that("filtering retains labels for zero length groups", { + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal( + duckplyr_ungroup(duckplyr_count(duckplyr_filter(df, f == 1))), + tibble( + e = 1, + f = factor(1:3), + g = c(1, 2, NA), + n = c(2L, 0L, 0L) + ) + ) +}) + +test_that("`duckplyr_filter()` doesn't allow data frames with missing or empty names (#6758)", { + df1 <- new_data_frame(set_names(list(1), "")) + df2 <- new_data_frame(set_names(list(1), NA_character_)) + + expect_snapshot(error = TRUE, { + duckplyr_filter(df1) + }) + expect_snapshot(error = TRUE, { + duckplyr_filter(df2) + }) +}) + +# .by ------------------------------------------------------------------------- + +test_that("can group transiently using `.by`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2, 1, 2), x = c(5, 10, 1, 2, 3)) + + out <- duckplyr_filter(df, x > mean(x), .by = g) + + expect_identical(out$g, c(1, 2)) + expect_identical(out$x, c(10, 3)) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains bare data.frame class", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2, 1, 2), x = c(5, 10, 1, 2, 3)) + out <- duckplyr_filter(df, x > mean(x), .by = g) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains data frame attributes", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # With data.frames or tibbles + df <- data.frame(g = c(1, 1, 2), x = c(1, 2, 1)) + tbl <- as_tibble(df) + + attr(df, "foo") <- "bar" + attr(tbl, "foo") <- "bar" + + out <- duckplyr_filter(df, x > mean(x), .by = g) + expect_identical(attr(out, "foo"), "bar") + + out <- duckplyr_filter(tbl, x > mean(x), .by = g) + expect_identical(attr(out, "foo"), "bar") +}) + +test_that("can't use `.by` with `.preserve`", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_filter(df, .by = x, .preserve = TRUE) + }) +}) + +test_that("catches `.by` with grouped-df", { + df <- tibble(x = 1) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + duckplyr_filter(gdf, .by = x) + }) +}) + +test_that("catches `.by` with rowwise-df", { + df <- tibble(x = 1) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + duckplyr_filter(rdf, .by = x) + }) +}) + +test_that("catches `by` typo (#6647)", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_filter(df, by = x) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-funs.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-funs.R new file mode 100644 index 000000000..c5f276405 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-funs.R @@ -0,0 +1,155 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# between ------------------------------------------------------------------- + +test_that("returns NA if any argument is NA", { + na <- NA_real_ + expect_equal(between(1, 1, na), NA) + expect_equal(between(1, na, 1), NA) + expect_equal(between(na, 1, 1), NA) +}) + +test_that("can be vectorized along `left` and `right`", { + expect_identical(between(1:2, c(0L, 4L), 5L), c(TRUE, FALSE)) + expect_identical(between(1:2, 0L, c(0L, 3L)), c(FALSE, TRUE)) +}) + +test_that("compatible with base R", { + x <- runif(1e3) + expect_equal(between(x, 0.25, 0.5), x >= 0.25 & x <= 0.5) +}) + +test_that("works with S3 objects", { + x <- new_vctr(c(1, 5), class = "foo") + left <- new_vctr(0, class = "foo") + right <- new_vctr(3, class = "foo") + + expect_identical(between(x, left, right), c(TRUE, FALSE)) +}) + +test_that("works with date-time `x` and date `left/right` (#6183)", { + jan2 <- as.POSIXct("2022-01-02", tz = "UTC") + + jan1 <- as.Date("2022-01-01") + jan3 <- as.Date("2022-01-03") + + expect_true(between(jan2, jan1, jan3)) +}) + +test_that("works with data frames", { + x <- tibble(year = c(2020, 2020, 2021), month = c(1, 3, 6)) + left <- tibble(year = c(2019, 2020, 2021), month = c(1, 4, 3)) + right <- tibble(year = c(2020, 2020, 2022), month = c(1, 6, 3)) + + expect_identical(between(x, left, right), c(TRUE, FALSE, TRUE)) +}) + +test_that("works with rcrds", { + x <- new_rcrd(list(year = c(2020, 2020, 2021), month = c(1, 3, 6))) + left <- new_rcrd(list(year = c(2019, 2020, 2021), month = c(1, 4, 3))) + right <- new_rcrd(list(year = c(2020, 2020, 2022), month = c(1, 6, 3))) + + expect_identical(between(x, left, right), c(TRUE, FALSE, TRUE)) +}) + +test_that("takes the common type between all inputs (#6478)", { + expect_identical(between(1L, 1.5, 2L), FALSE) + expect_identical(between(1L, 0.5, 2.5), TRUE) + + expect_snapshot(error = TRUE, { + between("1", 2, 3) + }) + expect_snapshot(error = TRUE, { + between(1, "2", 3) + }) + expect_snapshot(error = TRUE, { + between(1, 2, "3") + }) +}) + +test_that("recycles `left` and `right` to the size of `x`", { + expect_snapshot(error = TRUE, { + between(1:3, 1:2, 1L) + }) + expect_snapshot(error = TRUE, { + between(1:3, 1L, 1:2) + }) +}) + +test_that("ptype argument works as expected with non-alphabetical ordered factors", { + skip("TODO duckdb") + # Create an ordered factor with non-alphabetical order + x <- factor(c("b", "c", "a", "d"), levels = c("d", "c", "b", "a"), ordered = TRUE) + + # Test with ptype specified (uses factor order) + expect_identical( + between(x, "c", "a", ptype = x), + c(TRUE, TRUE, TRUE, FALSE) + ) + + # Test without ptype (uses alphabetical order) + expect_identical( + between(x, "c", "a"), + c(FALSE, FALSE, FALSE, FALSE) + ) +}) + +test_that("ptype argument affects type casting", { + skip("TODO duckdb") + x <- 1:5 + expect_identical( + between(x, 1.5, 3.5), + c(FALSE, TRUE, TRUE, FALSE, FALSE) + ) + expect_snapshot(error = TRUE, { + between(x, 1.5, 3.5, ptype = integer()) + }) +}) + +# cum* -------------------------------------------------------------------- + +test_that("cum(sum,min,max) return expected results for simple cases", { + expect_equal(cummean(numeric()), numeric()) + x <- c(5, 10, 2, 4) + expect_equal(cummean(x), cumsum(x) / seq_along(x)) + + expect_equal(cumany(logical()), logical()) + + expect_equal(cumany(FALSE), FALSE) + expect_equal(cumany(TRUE), TRUE) + + expect_equal(cumany(c(FALSE, FALSE)), c(FALSE, FALSE)) + expect_equal(cumany(c(TRUE, FALSE)), c(TRUE, TRUE)) + expect_equal(cumany(c(FALSE, TRUE)), c(FALSE, TRUE)) + expect_equal(cumany(c(TRUE, TRUE)), c(TRUE, TRUE)) + + expect_equal(cumall(logical()), logical()) + + expect_equal(cumall(FALSE), FALSE) + expect_equal(cumall(TRUE), TRUE) + + expect_equal(cumall(c(FALSE, FALSE)), c(FALSE, FALSE)) + expect_equal(cumall(c(TRUE, FALSE)), c(TRUE, FALSE)) + expect_equal(cumall(c(FALSE, TRUE)), c(FALSE, FALSE)) + expect_equal(cumall(c(TRUE, TRUE)), c(TRUE, TRUE)) +}) + +test_that("cumany/cumall propagate NAs (#408, #3749, #4132)", { + expect_equal(cumall(c(NA, NA)), c(NA, NA)) + expect_equal(cumall(c(NA, TRUE)), c(NA, NA)) + expect_equal(cumall(c(NA, FALSE)), c(NA, FALSE)) + + expect_equal(cumany(c(NA, NA)), c(NA, NA)) + expect_equal(cumany(c(NA, TRUE)), c(NA, TRUE)) + expect_equal(cumany(c(NA, FALSE)), c(NA, NA)) +}) + +test_that("cummean is not confused by FP error (#1387)", { + a <- rep(99, 9) + expect_true(all(cummean(a) == a)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-generics.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-generics.R new file mode 100644 index 000000000..7fe3c71ba --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-generics.R @@ -0,0 +1,198 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("row_slice recomputes groups", { + gf <- duckplyr_group_by(data.frame(g = c(1, 1, 2, 2, 3, 3)), g) + out <- dplyr_row_slice(gf, c(1L, 3L, 5L)) + expect_equal(group_data(out)$.rows, list_of(1L, 2L, 3L)) + + out <- dplyr_row_slice(gf, c(4L, 3L)) + expect_equal(group_data(out)$.rows, list_of(c(1L, 2L))) +}) + +test_that("row_slice preserves empty groups if requested", { + gf <- duckplyr_group_by(data.frame(g = c(1, 1, 2, 2, 3, 3)), g, .drop = FALSE) + out <- dplyr_row_slice(gf, c(3L, 4L)) + expect_equal(group_data(out)$.rows, list_of(integer(), c(1L, 2L), integer())) +}) + + +# dplyr_col_modify -------------------------------------------------------- + +test_that("empty cols returns input", { + df <- data.frame(x = 1) + expect_equal(dplyr_col_modify(df, list()), df) +}) + +test_that("applies tidyverse recycling rules", { + expect_equal( + dplyr_col_modify(data.frame(x = 1:2), list(y = 1)), + data.frame(x = 1:2, y = c(1, 1)) + ) + expect_equal( + dplyr_col_modify(data.frame(x = integer()), list(y = 1)), + data.frame(x = integer(), y = integer()) + ) + + expect_error( + dplyr_col_modify(data.frame(x = 1:4), list(y = 1:2)), + class = "vctrs_error_recycle_incompatible_size" + ) +}) + +test_that("can add, remove, and replace columns", { + df <- data.frame(x = 1, y = 2) + expect_equal(dplyr_col_modify(df, list(y = NULL)), data.frame(x = 1)) + expect_equal(dplyr_col_modify(df, list(y = 3)), data.frame(x = 1, y = 3)) + expect_equal(dplyr_col_modify(df, list(z = 3)), data.frame(x = 1, y = 2, z = 3)) +}) + +test_that("doesn't expand row names", { + df <- data.frame(x = 1:10) + out <- dplyr_col_modify(df, list(y = 1)) + + expect_equal(.row_names_info(out, 1), -10) +}) + +test_that("preserves existing row names", { + df <- data.frame(x = c(1, 2), row.names = c("a", "b")) + out <- dplyr_col_modify(df, list(y = 1)) + expect_equal(row.names(df), c("a", "b")) +}) + +test_that("reconstruct method gets a data frame", { + seen_df <- NULL + + local_methods( + dplyr_reconstruct.dplyr_foobar = function(data, template) { + if (is.data.frame(data)) { + seen_df <<- TRUE + } + NextMethod() + } + ) + + df <- foobar(data.frame(x = 1)) + + seen_df <- FALSE + dplyr_col_modify(df, list(y = 2)) + expect_true(seen_df) + + seen_df <- FALSE + dplyr_row_slice(df, 1) + expect_true(seen_df) +}) + +# dplyr_reconstruct ------------------------------------------------------- + +test_that("classes are restored", { + expect_identical( + dplyr_reconstruct(tibble(), data.frame()), + data.frame() + ) + expect_identical( + dplyr_reconstruct(tibble(), tibble()), + tibble() + ) + expect_identical( + dplyr_reconstruct(tibble(), new_data_frame(class = "foo")), + new_data_frame(class = "foo") + ) +}) + +test_that("attributes of `template` are kept", { + expect_identical( + dplyr_reconstruct(new_tibble(list(), nrow = 1), new_data_frame(foo = 1)), + new_data_frame(n = 1L, foo = 1) + ) +}) + +test_that("compact row names are retained", { + data <- vec_rbind(tibble(a = 1), tibble(a = 2)) + template <- tibble() + + x <- dplyr_reconstruct(data, template) + expect <- tibble(a = c(1, 2)) + + expect_identical(x, expect) + + # Explicitly ensure internal row name structure is identical + expect_identical( + .row_names_info(x, type = 0L), + .row_names_info(expect, type = 0L) + ) +}) + +test_that("dplyr_reconstruct() strips attributes before dispatch", { + local_methods( + dplyr_reconstruct.dplyr_foobar = function(data, template) { + out <<- data + } + ) + + df <- foobar(data.frame(x = 1), foo = "bar") + out <- NULL + dplyr_reconstruct(df, df) + expect_identical(out, data.frame(x = 1)) + + df <- foobar(data.frame(x = 1, row.names = "a"), foo = "bar") + out <- NULL + dplyr_reconstruct(df, df) + expect_identical(out, data.frame(x = 1, row.names = "a")) +}) + +test_that("`dplyr_reconstruct()` retains attribute ordering of `template`", { + df <- vctrs::data_frame(x = 1) + expect_identical( + attributes(dplyr_reconstruct(df, df)), + attributes(df) + ) +}) + +test_that("`dplyr_reconstruct()` doesn't modify the original `data` in place", { + data <- new_data_frame(list(x = 1), foo = "bar") + template <- vctrs::data_frame(x = 1) + + out <- dplyr_reconstruct(data, template) + + expect_null(attr(out, "foo")) + expect_identical(attr(data, "foo"), "bar") +}) + +test_that("`dplyr_reconstruct()`, which gets and sets attributes, doesn't touch `row.names` (#6525)", { + skip("TODO duckdb") + skip_if_no_lazy_character() + + dplyr_attributes <- function(x) { + .Call(ffi_test_dplyr_attributes, x) + } + dplyr_set_attributes <- function(x, attributes) { + .Call(ffi_test_dplyr_set_attributes, x, attributes) + } + + df <- vctrs::data_frame(x = 1) + + attributes <- attributes(df) + attributes$row.names <- new_lazy_character(function() "a") + attributes <- as.pairlist(attributes) + + df_with_lazy_row_names <- dplyr_set_attributes(df, attributes) + + # Ensure `data` row names aren't materialized + x <- dplyr_reconstruct(df_with_lazy_row_names, df) + attributes <- dplyr_attributes(df_with_lazy_row_names) + expect_false(lazy_character_is_materialized(attributes$row.names)) + + # `data` row names should also propagate into the result unmaterialized + attributes <- dplyr_attributes(x) + expect_false(lazy_character_is_materialized(attributes$row.names)) + + # Ensure `template` row names aren't materialized + x <- dplyr_reconstruct(df, df_with_lazy_row_names) + attributes <- dplyr_attributes(df_with_lazy_row_names) + expect_false(lazy_character_is_materialized(attributes$row.names)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-by.R new file mode 100644 index 000000000..dd8876d55 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-by.R @@ -0,0 +1,624 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +df <- data.frame(x = rep(1:3, each = 10), y = rep(1:6, each = 5)) + +test_that("duckplyr_group_by() with .add = TRUE adds groups", { + add_groups1 <- function(tbl) duckplyr_group_by(tbl, x, y, .add = TRUE) + add_groups2 <- function(tbl) duckplyr_group_by(duckplyr_group_by(tbl, x, .add = TRUE), y, .add = TRUE) + + expect_equal(duckplyr_group_vars(add_groups1(df)), c("x", "y")) + expect_equal(duckplyr_group_vars(add_groups2(df)), c("x", "y")) +}) + +test_that("duckplyr_group_by(, ) computes the expressions on the ungrouped data frame (#5938)", { + df <- data.frame( + x = 1:4, + g = rep(1:2, each = 2) + ) + + count <- 0 + out <- df %>% duckplyr_group_by(g) %>% duckplyr_group_by(big = { count <<- count + 1; x > mean(x) }) + expect_equal(out$big, c(FALSE, FALSE, TRUE, TRUE)) + expect_equal(count, 1L) + expect_equal(duckplyr_group_vars(out), c("big")) + + count <- 0 + out <- df %>% duckplyr_group_by(g) %>% duckplyr_group_by(big = { count <<- count + 1; x > mean(x) }, .add = TRUE) + expect_equal(out$big, c(FALSE, FALSE, TRUE, TRUE)) + expect_equal(count, 1L) + expect_equal(duckplyr_group_vars(out), c("g", "big")) + + count <- 0 + out <- df %>% duckplyr_group_by(g) %>% duckplyr_mutate(big = { count <<- count + 1; x > mean(x)}) %>% duckplyr_group_by(big) + expect_equal(out$big, c(FALSE, TRUE, FALSE, TRUE)) + expect_equal(count, 2L) + expect_equal(duckplyr_group_vars(out), c("big")) + + count <- 0 + out <- df %>% duckplyr_group_by(g) %>% duckplyr_mutate(big = { count <<- count + 1; x > mean(x)}) %>% duckplyr_group_by(big, .add = TRUE) + expect_equal(out$big, c(FALSE, TRUE, FALSE, TRUE)) + expect_equal(count, 2L) + expect_equal(duckplyr_group_vars(out), c("g", "big")) +}) + +test_that("add = TRUE is deprecated", { + rlang::local_options(lifecycle_verbosity = "warning") + + df <- tibble(x = 1, y = 2) + + expect_warning( + out <- df %>% duckplyr_group_by(x) %>% duckplyr_group_by(y, add = TRUE), + "deprecated" + ) + expect_equal(duckplyr_group_vars(out), c("x", "y")) +}) + +test_that("joins preserve grouping", { + df <- data.frame(x = rep(1:2, each = 4), y = rep(1:4, each = 2)) + g <- duckplyr_group_by(df, x) + + expect_equal(duckplyr_group_vars(duckplyr_inner_join(g, g, by = c("x", "y"), relationship = "many-to-many")), "x") + expect_equal(duckplyr_group_vars(duckplyr_left_join(g, g, by = c("x", "y"), relationship = "many-to-many")), "x") + expect_equal(duckplyr_group_vars(duckplyr_semi_join(g, g, by = c("x", "y"))), "x") + expect_equal(duckplyr_group_vars(duckplyr_anti_join(g, g, by = c("x", "y"))), "x") +}) + +test_that("constructors drops groups", { + df <- data.frame(x = 1:3) %>% duckplyr_group_by(x) + expect_equal(duckplyr_group_vars(as_tibble(df)), character()) +}) + +test_that("grouping by constant adds column (#410)", { + grouped <- duckplyr_group_by(mtcars, "cyl") %>% duckplyr_summarise(foo = n()) + expect_equal(names(grouped), c('"cyl"', "foo")) + expect_equal(nrow(grouped), 1L) +}) + +test_that("can partially `duckplyr_ungroup()` (#6606)", { + df <- tibble(g1 = 1:2, g2 = 3:4, x = 5:6) + gdf <- duckplyr_group_by(df, g1, g2) + + expect_identical(duckplyr_ungroup(gdf, g1), duckplyr_group_by(df, g2)) + expect_identical(duckplyr_ungroup(gdf, g1, g2), df) +}) + +test_that("can't rename while partially `duckplyr_ungroup()`-ing (#6606)", { + df <- tibble(g = 1:2, x = 3:4) + gdf <- duckplyr_group_by(df, g) + + expect_snapshot(error = TRUE, { + duckplyr_ungroup(gdf, g2 = g) + }) +}) + +test_that(".dots is soft deprecated", { + rlang::local_options(lifecycle_verbosity = "warning") + + df <- tibble(x = 1, y = 1) + expect_warning(gf <- duckplyr_group_by(df, .dots = "x"), "deprecated") +}) + +# Test full range of variable types -------------------------------------------- + + +test_that("local group_by preserves variable types", { + df_var <- tibble( + l = c(T, F), + i = 1:2, + d = Sys.Date() + 1:2, + f = factor(letters[1:2]), + num = 1:2 + 0.5, + t = Sys.time() + 1:2, + c = letters[1:2] + ) + attr(df_var$t, "tzone") <- "" + + for (var in names(df_var)) { + expected <- tibble(!!var := sort(unique(df_var[[var]])), n = 1L) + + summarised <- df_var %>% duckplyr_group_by(!!sym(var)) %>% duckplyr_summarise(n = n()) + expect_equal(summarised, expected) + } +}) + +test_that("mutate does not lose variables (#144)", { + df <- tibble(a = rep(1:4, 2), b = rep(1:4, each = 2), x = runif(8)) + by_ab <- duckplyr_group_by(df, a, b) + by_a <- duckplyr_summarise(by_ab, x = sum(x), .groups = "drop_last") + by_a_quartile <- duckplyr_group_by(by_a, quartile = ntile(x, 4)) + + expect_equal(names(by_a_quartile), c("a", "b", "x", "quartile")) +}) + +test_that("group_by uses shallow copy", { + skip_if_not_installed("lobstr") + m1 <- duckplyr_group_by(mtcars, cyl) + expect_equal(duckplyr_group_vars(mtcars), character()) + + expect_equal( + lobstr::obj_addrs(mtcars), + lobstr::obj_addrs(m1) + ) +}) + +test_that("group_by orders by groups. #242", { + df <- data.frame(a = sample(1:10, 3000, replace = TRUE)) %>% duckplyr_group_by(a) + expect_equal(group_data(df)$a, 1:10) + + df <- data.frame(a = sample(letters[1:10], 3000, replace = TRUE), stringsAsFactors = FALSE) %>% duckplyr_group_by(a) + expect_equal(group_data(df)$a, letters[1:10]) + + df <- data.frame(a = sample(sqrt(1:10), 3000, replace = TRUE)) %>% duckplyr_group_by(a) + expect_equal(group_data(df)$a, sqrt(1:10)) +}) + +test_that("Can duckplyr_group_by() a POSIXlt", { + skip_if_not_installed("tibble", "2.99.99") + df <- tibble(x = 1:5, times = as.POSIXlt(seq.Date(Sys.Date(), length.out = 5, by = "day"))) + g <- duckplyr_group_by(df, times) + expect_equal(nrow(group_data(g)), 5L) +}) + +test_that("duckplyr_group_by() handles list as grouping variables", { + df <- tibble(x = 1:3, y = list(1:2, 1:3, 1:2)) + gdata <- group_data(duckplyr_group_by(df, y)) + expect_equal(nrow(gdata), 2L) + expect_equal(gdata$y, list(1:2, 1:3)) + expect_equal(gdata$.rows, list_of(c(1L, 3L), 2L)) +}) + +test_that("duckplyr_select(duckplyr_group_by(.)) implicitly adds grouping variables (#170)", { + expect_snapshot( + res <- mtcars %>% duckplyr_group_by(vs) %>% duckplyr_select(mpg) + ) + expect_equal(names(res), c("vs", "mpg")) +}) + +test_that("group_by only creates one group for NA (#401)", { + x <- as.numeric(c(NA, NA, NA, 10:1, 10:1)) + w <- c(20, 30, 40, 1:10, 1:10) * 10 + + n_distinct(x) # 11 OK + res <- data.frame(x = x, w = w) %>% duckplyr_group_by(x) %>% duckplyr_summarise(n = n()) + expect_equal(nrow(res), 11L) +}) + +test_that("there can be 0 groups (#486)", { + data <- tibble(a = numeric(0), g = character(0)) %>% duckplyr_group_by(g) + expect_equal(length(data$a), 0L) + expect_equal(length(data$g), 0L) + expect_equal(map_int(group_rows(data), length), integer(0)) +}) + +test_that("group_by works with zero-row data frames (#486)", { + df <- data.frame(a = numeric(0), b = numeric(0), g = character(0)) + dfg <- duckplyr_group_by(df, g, .drop = FALSE) + expect_equal(dim(dfg), c(0, 3)) + expect_equal(duckplyr_group_vars(dfg), "g") + expect_equal(duckplyr_group_size(dfg), integer(0)) + + x <- duckplyr_summarise(dfg, n = n()) + expect_equal(dim(x), c(0, 2)) + expect_equal(duckplyr_group_vars(x), character()) + + x <- duckplyr_mutate(dfg, c = b + 1) + expect_equal(dim(x), c(0, 4)) + expect_equal(duckplyr_group_vars(x), "g") + expect_equal(duckplyr_group_size(x), integer(0)) + + x <- duckplyr_filter(dfg, a == 100) + expect_equal(dim(x), c(0, 3)) + expect_equal(duckplyr_group_vars(x), "g") + expect_equal(duckplyr_group_size(x), integer(0)) + + x <- duckplyr_arrange(dfg, a, g) + expect_equal(dim(x), c(0, 3)) + expect_equal(duckplyr_group_vars(x), "g") + expect_equal(duckplyr_group_size(x), integer(0)) + + expect_snapshot( + x <- duckplyr_select(dfg, a) # Only select 'a' column; should result in 'g' and 'a' + ) + expect_equal(dim(x), c(0, 2)) + expect_equal(duckplyr_group_vars(x), "g") + expect_equal(duckplyr_group_size(x), integer(0)) +}) + +test_that("[ on grouped_df preserves grouping if subset includes grouping vars", { + df <- tibble(x = 1:5, ` ` = 6:10) + by_x <- df %>% duckplyr_group_by(x) + expect_equal(by_x %>% duckplyr_groups(), by_x %>% `[`(1:2) %>% duckplyr_groups()) + + # non-syntactic name + by_ns <- df %>% duckplyr_group_by(` `) + expect_equal(by_ns %>% duckplyr_groups(), by_ns %>% `[`(1:2) %>% duckplyr_groups()) +}) + +test_that("[ on grouped_df drops grouping if subset doesn't include grouping vars", { + by_cyl <- mtcars %>% duckplyr_group_by(cyl) + no_cyl <- by_cyl %>% `[`(c(1, 3)) + + expect_equal(duckplyr_group_vars(no_cyl), character()) + expect_s3_class(no_cyl, "tbl_df") +}) + +test_that("group_by works after arrange (#959)", { + df <- tibble(Log = c(1, 2, 1, 2, 1, 2), Time = c(10, 1, 3, 0, 15, 11)) + res <- df %>% + duckplyr_arrange(Time) %>% + duckplyr_group_by(Log) %>% + duckplyr_mutate(Diff = Time - lag(Time)) + expect_true(all(is.na(res$Diff[c(1, 3)]))) + expect_equal(res$Diff[c(2, 4, 5, 6)], c(1, 7, 10, 5)) +}) + +test_that("group_by keeps attributes", { + d <- data.frame(x = structure(1:10, foo = "bar")) + gd <- duckplyr_group_by(d) + expect_equal(attr(gd$x, "foo"), "bar") +}) + +test_that("ungroup.rowwise_df gives a tbl_df (#936)", { + res <- mtcars %>% duckplyr_rowwise() %>% duckplyr_ungroup() %>% class() + expect_equal(res, c("tbl_df", "tbl", "data.frame")) +}) + +test_that("group_by handles encodings for native strings (#1507)", { + local_non_utf8_encoding() + + special <- get_native_lang_string() + + df <- data.frame(x = 1:3, Eng = 2:4) + + for (names_converter in c(enc2native, enc2utf8)) { + for (dots_converter in c(enc2native, enc2utf8)) { + names(df) <- names_converter(c(special, "Eng")) + res <- duckplyr_group_by(df, !!!syms(dots_converter(special))) + expect_equal(names(res), names(df)) + expect_equal(duckplyr_group_vars(res), special) + } + } + + for (names_converter in c(enc2native, enc2utf8)) { + names(df) <- names_converter(c(special, "Eng")) + + res <- duckplyr_group_by(df, !!!special) + expect_equal(names(res), c(names(df), deparse(special))) + expect_equal(duckplyr_groups(res), list(as.name(enc2native(deparse(special))))) + } +}) + +test_that("group_by handles raw columns (#1803)", { + df <- tibble(a = 1:3, b = as.raw(1:3)) + expect_identical(duckplyr_ungroup(duckplyr_group_by(df, a)), df) + expect_identical(duckplyr_ungroup(duckplyr_group_by(df, b)), df) +}) + +test_that("rowwise handles raw columns (#1803)", { + df <- tibble(a = 1:3, b = as.raw(1:3)) + expect_s3_class(duckplyr_rowwise(df), "rowwise_df") +}) + +test_that("duckplyr_group_by() names pronouns correctly (#2686)", { + expect_named(duckplyr_group_by(tibble(x = 1), .data$x), "x") + expect_named(duckplyr_group_by(tibble(x = 1), .data[["x"]]), "x") +}) + +test_that("duckplyr_group_by() does not affect input data (#3028)", { + x <- + data.frame(old1 = c(1, 2, 3), old2 = c(4, 5, 6)) %>% + duckplyr_group_by(old1) + + y <- + x %>% + duckplyr_select(new1 = old1, new2 = old2) + + expect_identical(duckplyr_groups(x), syms(quote(old1))) +}) + +test_that("duckplyr_group_by() does not mutate for nothing when using the .data pronoun (#2752, #3533)", { + expect_identical( + iris %>% duckplyr_group_by(Species) %>% duckplyr_group_by(.data$Species), + iris %>% duckplyr_group_by(Species) + ) + expect_identical( + iris %>% duckplyr_group_by(Species) %>% duckplyr_group_by(.data[["Species"]]), + iris %>% duckplyr_group_by(Species) + ) + + df <- tibble(x = 1:5) + attr(df, "y") <- 1 + + expect_equal( df %>% duckplyr_group_by(.data$x) %>% attr("y"), 1 ) + expect_equal( df %>% duckplyr_group_by(.data[["x"]]) %>% attr("y"), 1 ) +}) + +test_that("tbl_sum gets the right number of groups", { + res <- data.frame(x=c(1,1,2,2)) %>% duckplyr_group_by(x) %>% pillar::tbl_sum() + expect_equal(res, c("A tibble" = "4 x 1", "Groups" = "x [2]")) +}) + +test_that("group_by ignores empty quosures (3780)", { + empty <- quo() + expect_equal(duckplyr_group_by(mtcars, cyl), duckplyr_group_by(mtcars, cyl, !!empty)) +}) + +# Zero groups --------------------------------------------------- + +test_that("mutate handles grouped tibble with 0 groups (#3935)", { + df <- tibble(x=integer()) %>% duckplyr_group_by(x) + res <- duckplyr_mutate(df, y = mean(x), z = +mean(x), n = n()) + expect_equal(names(res), c("x", "y", "z", "n")) + expect_equal(nrow(res), 0L) + expect_equal(res$y, double()) + expect_equal(res$z, double()) + expect_equal(res$n, integer()) +}) + +test_that("summarise handles grouped tibble with 0 groups (#3935)", { + df <- tibble(x=integer()) %>% duckplyr_group_by(x) + res <- duckplyr_summarise(df, y = mean(x), z = +mean(x), n = n()) + expect_equal(names(res), c("x", "y", "z", "n")) + expect_equal(nrow(res), 0L) + expect_equal(res$y, double()) + expect_equal(res$n, integer()) + expect_equal(res$z, double()) +}) + +test_that("filter handles grouped tibble with 0 groups (#3935)", { + df <- tibble(x=integer()) %>% duckplyr_group_by(x) + res <- duckplyr_filter(df, x > 3L) + expect_identical(df, res) +}) + +test_that("select handles grouped tibble with 0 groups (#3935)", { + df <- tibble(x=integer()) %>% duckplyr_group_by(x) + res <- duckplyr_select(df, x) + expect_identical(df, res) +}) + +test_that("arrange handles grouped tibble with 0 groups (#3935)", { + df <- tibble(x=integer()) %>% duckplyr_group_by(x) + res <- duckplyr_arrange(df, x) + expect_identical(df, res) +}) + +test_that("duckplyr_group_by() with empty spec produces a grouped data frame with 0 grouping variables", { + gdata <- group_data(duckplyr_group_by(iris)) + expect_equal(names(gdata), ".rows") + expect_equal(gdata$.rows, list_of(1:nrow(iris))) + + gdata <- group_data(duckplyr_group_by(iris, !!!list())) + expect_equal(names(gdata), ".rows") + expect_equal(gdata$.rows, list_of(1:nrow(iris))) +}) + +# .drop = TRUE --------------------------------------------------- + +test_that("duckplyr_group_by(.drop = TRUE) drops empty groups (4061)", { + res <- iris %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_by(Species, .drop = TRUE) + + expect_identical( + group_data(res), + structure( + tibble(Species = factor("setosa", levels = levels(iris$Species)), .rows := list_of(1:50)), + .drop = TRUE + ) + ) + + expect_true(group_by_drop_default(res)) +}) + +test_that("grouped data frames remember their .drop (#4061)", { + res <- iris %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_by(Species, .drop = TRUE) + + res2 <- res %>% + duckplyr_filter(Sepal.Length > 5) + expect_true(group_by_drop_default(res2)) + + res3 <- res %>% + duckplyr_filter(Sepal.Length > 5, .preserve = FALSE) + expect_true(group_by_drop_default(res3)) + + res4 <- res3 %>% + duckplyr_group_by(Species) + expect_true(group_by_drop_default(res4)) + expect_equal(nrow(group_data(res4)), 1L) +}) + +test_that("grouped data frames remember their .drop = FALSE (#4337)", { + res <- iris %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_by(Species, .drop = FALSE) + expect_false(group_by_drop_default(res)) + + res2 <- res %>% + duckplyr_group_by(Species) + expect_false(group_by_drop_default(res2)) +}) + +test_that("duckplyr_group_by(.drop = FALSE) preserve ordered factors (#5455)", { + df <- tibble(x = ordered("x")) + drop <- df %>% duckplyr_group_by(x) %>% group_data() + nodrop <- df %>% duckplyr_group_by(x, .drop = FALSE) %>% group_data() + + expect_equal(is.ordered(drop$x), is.ordered(nodrop$x)) + expect_true(is.ordered(nodrop$x)) +}) + +test_that("summarise maintains the .drop attribute (#4061)", { + df <- tibble( + f1 = factor("a", levels = c("a", "b", "c")), + f2 = factor("d", levels = c("d", "e", "f", "g")), + x = 42 + ) + + res <- df %>% + duckplyr_group_by(f1, f2, .drop = TRUE) + expect_equal(duckplyr_n_groups(res), 1L) + + res2 <- duckplyr_summarise(res, x = sum(x), .groups = "drop_last") + expect_equal(duckplyr_n_groups(res2), 1L) + expect_true(group_by_drop_default(res2)) +}) + +test_that("joins maintain the .drop attribute (#4061)", { + df1 <- duckplyr_group_by(tibble( + f1 = factor(c("a", "b"), levels = c("a", "b", "c")), + x = 42:43 + ), f1, .drop = TRUE) + + df2 <- duckplyr_group_by(tibble( + f1 = factor(c("a"), levels = c("a", "b", "c")), + y = 1 + ), f1, .drop = TRUE) + + res <- duckplyr_left_join(df1, df2, by = "f1") + expect_equal(duckplyr_n_groups(res), 2L) + + df2 <- duckplyr_group_by(tibble( + f1 = factor(c("a", "c"), levels = c("a", "b", "c")), + y = 1:2 + ), f1, .drop = TRUE) + res <- duckplyr_full_join(df1, df2, by = "f1") + expect_equal(duckplyr_n_groups(res), 3L) +}) + +test_that("duckplyr_group_by(add = TRUE) sets .drop if the origonal data was .drop", { + d <- tibble( + f1 = factor("b", levels = c("a", "b", "c")), + f2 = factor("g", levels = c("e", "f", "g")), + x = 48 + ) + + res <- duckplyr_group_by(duckplyr_group_by(d, f1, .drop = TRUE), f2, .add = TRUE) + expect_equal(duckplyr_n_groups(res), 1L) + expect_true(group_by_drop_default(res)) +}) + +test_that("group_by_drop_default() is forgiving about corrupt grouped df (#4306)",{ + df <- tibble(x = 1:2, y = 1:2) %>% + structure(class = c("grouped_df", "tbl_df", "tbl", "data.frame")) + + expect_true(group_by_drop_default(df)) +}) + +test_that("duckplyr_group_by() puts NA groups last in STRSXP (#4227)", { + res <- tibble(x = c("apple", NA, "banana"), y = 1:3) %>% + duckplyr_group_by(x) %>% + group_data() + expect_identical(res$x, c("apple", "banana", NA_character_)) + expect_identical(res$.rows, list_of(1L, 3L, 2L)) +}) + +test_that("duckplyr_group_by() does not create arbitrary NA groups for factors when drop = TRUE (#4460)", { + res <- expect_warning(group_data(duckplyr_group_by(iris, Species)[0, ]), NA) + expect_equal(nrow(res), 0L) + + res <- expect_warning(group_data(duckplyr_group_by(iris[0, ], Species)), NA) + expect_equal(nrow(res), 0L) +}) + +test_that("duckplyr_group_by() can handle auto splicing in the duckplyr_mutate() step", { + expect_identical( + iris %>% duckplyr_group_by(Species), + iris %>% duckplyr_group_by(data.frame(Species = Species)) + ) + + expect_identical( + iris %>% duckplyr_group_by(Species), + iris %>% duckplyr_group_by(pick(Species)) + ) + + expect_identical( + iris %>% duckplyr_mutate(across(starts_with("Sepal"), round)) %>% duckplyr_group_by(Sepal.Length, Sepal.Width), + iris %>% duckplyr_group_by(across(starts_with("Sepal"), round)) + ) +}) + +test_that("duckplyr_group_by() can combine usual spec and auto-splicing-duckplyr_mutate() step", { + expect_identical( + iris %>% duckplyr_mutate(across(starts_with("Sepal"), round)) %>% duckplyr_group_by(Sepal.Length, Sepal.Width, Species), + iris %>% duckplyr_group_by(across(starts_with("Sepal"), round), Species) + ) + + expect_identical( + iris %>% duckplyr_mutate(across(starts_with("Sepal"), round)) %>% duckplyr_group_by(Species, Sepal.Length, Sepal.Width), + iris %>% duckplyr_group_by(Species, across(starts_with("Sepal"), round)) + ) +}) + +# duckplyr_mutate() semantics + +test_that("duckplyr_group_by() has duckplyr_mutate() semantics (#4984)", { + expect_equal( + tibble(a = 1, b = 2) %>% duckplyr_group_by(c = a * b, d = c + 1), + tibble(a = 1, b = 2) %>% duckplyr_mutate(c = a * b, d = c + 1) %>% duckplyr_group_by(c, d) + ) +}) + +test_that("implicit duckplyr_mutate() operates on ungrouped data (#5598)", { + skip("TODO duckdb") + vars <- tibble(x = c(1,2), y = c(3,4), z = c(5,6)) %>% + dplyr::duckplyr_group_by(y) %>% + dplyr::duckplyr_group_by(pick(any_of(c('y','z')))) %>% + dplyr::duckplyr_group_vars() + expect_equal(vars, c("y", "z")) +}) + +test_that("grouped_df() does not break row.names (#5745)", { + groups <- compute_groups(data.frame(x = 1:10), "x") + expect_equal(.row_names_info(groups, type = 0), c(NA, -10L)) +}) + +test_that("duckplyr_group_by() keeps attributes unrelated to the grouping (#5760)", { + d <- data.frame(x = 453, y = 642) + attr(d, "foo") <- "bar" + + d2 <- duckplyr_group_by(d, x) + expect_equal(attr(d2, "foo"), "bar") + + d3 <- duckplyr_group_by(d2, y, .add = TRUE) + expect_equal(attr(d2, "foo"), "bar") + + d4 <- duckplyr_group_by(d2, y2 = y * 2, .add = TRUE) + expect_equal(attr(d2, "foo"), "bar") +}) + +test_that("duckplyr_group_by() works with quosures (tidyverse/lubridate#959)", { + ignore <- function(...) NA + f <- function(var) { + tibble(x = 1) %>% duckplyr_group_by(g = ignore({{ var }})) + } + g <- function(var) { + # This used to fail with the extra argument + tibble(x = 1) %>% duckplyr_group_by(g = ignore({{ var }}, 1)) + } + expect_equal(f(), tibble(x = 1, g = NA) %>% duckplyr_group_by(g)) + expect_equal(g(), tibble(x = 1, g = NA) %>% duckplyr_group_by(g)) +}) + + +# Errors ------------------------------------------------------------------ + +test_that("duckplyr_group_by() and duckplyr_ungroup() give meaningful error messages", { + expect_snapshot({ + df <- tibble(x = 1, y = 2) + + (expect_error(df %>% duckplyr_group_by(unknown))) + (expect_error(df %>% duckplyr_ungroup(x))) + (expect_error(df %>% duckplyr_group_by(x, y) %>% duckplyr_ungroup(z))) + + (expect_error(df %>% duckplyr_group_by(z = a + 1))) + }) + +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-data.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-data.R new file mode 100644 index 000000000..64557f6f9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-data.R @@ -0,0 +1,132 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + + +# group_data -------------------------------------------------------------- + +test_that("group_data() returns a data frame", { + df <- data.frame(x = 1:3) + gd <- group_data(df) + + expect_s3_class(gd, "data.frame", exact = TRUE) + expect_equal(gd$.rows, list_of(1:3)) +}) + +test_that("group_data() returns a tibble", { + df <- tibble(x = 1:3) + gd <- group_data(df) + + expect_s3_class(gd, "tbl_df") + expect_equal(gd, tibble(".rows" := list_of(1:3))) +}) + +test_that("group_data() returns a tibble", { + df <- tibble(x = c(1, 1, 2)) + gf <- duckplyr_group_by(df, x) + gd <- group_data(gf) + + expect_s3_class(gd, "tbl_df") + expect_equal( + gd, + tibble(x = c(1, 2), ".rows" := list_of(1:2, 3L)), + ignore_attr = TRUE + ) +}) + +test_that("group_data(% duckplyr_group_keys(x), "deprecated") + expect_equal(out, tibble(x = 1)) +}) + +# duckplyr_group_indices() --------------------------------------------------------- + +test_that("no arg duckplyr_group_indices() is deprecated", { + df <- tibble(x = 1) + expect_warning(out <- duckplyr_summarise(df, id = duckplyr_group_indices()), "deprecated") + expect_equal(out, tibble(id = 1)) +}) + +test_that("duckplyr_group_indices(...) is deprecated", { + rlang::local_options(lifecycle_verbosity = "error") + + df <- tibble(x = 1, y = 2) + expect_error(df %>% duckplyr_group_indices(x), "deprecated") +}) + +test_that("duckplyr_group_indices(...) still works though", { + rlang::local_options(lifecycle_verbosity = "quiet") + + df <- tibble(x = 1, y = 2) + out <- df %>% duckplyr_group_indices(x) + expect_equal(out, 1) +}) + +test_that("duckplyr_group_indices() returns expected values", { + df <- tibble(x = c("b", "a", "b")) + gf <- duckplyr_group_by(df, x) + + expect_equal(duckplyr_group_indices(df), c(1, 1, 1)) + expect_equal(duckplyr_group_indices(gf), c(2, 1, 2)) +}) + +test_that("duckplyr_group_indices() handles 0 rows data frames (#5541)", { + df <- new_grouped_df( + data.frame(x = integer(), y = integer()), + groups = data.frame(x=0, .rows = vctrs::list_of(1:1000)) + ) + expect_equal(duckplyr_group_indices(df), integer()) +}) + +# group_size -------------------------------------------------------------- + +test_that("ungrouped data has 1 group, with group size = nrow()", { + df <- tibble(x = rep(1:3, each = 10), y = rep(1:6, each = 5)) + + expect_equal(duckplyr_n_groups(df), 1L) + expect_equal(duckplyr_group_size(df), 30) +}) + +test_that("rowwise data has one group for each group", { + rw <- duckplyr_rowwise(mtcars) + expect_equal(duckplyr_n_groups(rw), 32) + expect_equal(duckplyr_group_size(rw), rep(1, 32)) +}) + +test_that("group_size correct for grouped data", { + df <- tibble(x = rep(1:3, each = 10), y = rep(1:6, each = 5)) %>% duckplyr_group_by(x) + expect_equal(duckplyr_n_groups(df), 3L) + expect_equal(duckplyr_group_size(df), rep(10, 3)) +}) + +# n_groups ---------------------------------------------------------------- + +test_that("n_groups respects zero-length groups (#341)", { + df <- tibble(x = factor(1:3, levels = 1:4)) %>% duckplyr_group_by(x, .drop = FALSE) + expect_equal(duckplyr_n_groups(df), 4) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-map.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-map.R new file mode 100644 index 000000000..f94f81cd1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-map.R @@ -0,0 +1,137 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("duckplyr_group_map() respects empty groups", { + res <- duckplyr_group_by(mtcars, cyl) %>% + duckplyr_group_map(~ head(.x, 2L)) + expect_equal(length(res), 3L) + + res <- iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_map(~ tally(.x)) + expect_equal(length(res), 1L) + + res <- iris %>% + duckplyr_group_by(Species, .drop = FALSE) %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_map(~ tally(.x)) + expect_equal(length(res), 3L) +}) + +test_that("duckplyr_group_map() can return arbitrary objects", { + expect_equal( + duckplyr_group_by(mtcars, cyl) %>% duckplyr_group_map(~ 10), + rep(list(10), 3) + ) +}) + +test_that("duckplyr_group_map() works on ungrouped data frames (#4067)", { + expect_identical( + duckplyr_group_map(mtcars, ~ head(.x, 2L)), + list(head(as_tibble(mtcars), 2L)) + ) +}) + +test_that("duckplyr_group_modify() makes a grouped_df", { + res <- duckplyr_group_by(mtcars, cyl) %>% + duckplyr_group_modify(~ head(.x, 2L)) + + expect_equal(nrow(res), 6L) + expect_equal(group_rows(res), list_of(1:2, 3:4, 5:6)) + + res <- iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_modify(~ tally(.x)) + expect_equal(nrow(res), 1L) + expect_equal(group_rows(res), list_of(1L)) + + res <- iris %>% + duckplyr_group_by(Species, .drop = FALSE) %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_modify(~ tally(.x)) + expect_equal(nrow(res), 3L) + expect_equal(as.list(group_rows(res)), list(1L, 2L, 3L)) +}) + +test_that("duckplyr_group_modify() and duckplyr_group_map() want functions with at least 2 arguments, or ... (#3996)", { + head1 <- function(d, ...) head(d, 1) + + g <- iris %>% duckplyr_group_by(Species) + expect_equal(nrow(duckplyr_group_modify(g, head1)), 3L) + expect_equal(length(duckplyr_group_map(g, head1)), 3L) +}) + +test_that("duckplyr_group_modify() works on ungrouped data frames (#4067)", { + expect_identical( + duckplyr_group_modify(mtcars, ~ head(.x, 2L)), + head(mtcars, 2L) + ) +}) + +test_that("duckplyr_group_map() uses ptype on empty splits (#4421)", { + res <- mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(hp > 1000) %>% + duckplyr_group_map(~.x) + expect_equal(res, list(), ignore_attr = TRUE) + ptype <- attr(res, "ptype") + expect_equal(names(ptype), duckplyr_setdiff(names(mtcars), "cyl")) + expect_equal(nrow(ptype), 0L) + expect_s3_class(ptype, "data.frame") +}) + +test_that("duckplyr_group_modify() uses ptype on empty splits (#4421)", { + res <- mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(hp > 1000) %>% + duckplyr_group_modify(~.x) + expect_equal(res, duckplyr_group_by(mtcars[integer(0L), names(res)], cyl)) +}) + +test_that("duckplyr_group_modify() works with additional arguments (#4509)", { + myfun <- function(.x, .y, foo) { + .x[[foo]] <- 1 + .x + } + + srcdata <- + data.frame( + A=rep(1:2, each = 3) + ) %>% + duckplyr_group_by(A) + targetdata <- srcdata + targetdata$bar <- 1 + + expect_equal( + duckplyr_group_modify(.data = srcdata, .f = myfun, foo = "bar"), + targetdata + ) +}) + +test_that("duckplyr_group_map() does not warn about .keep= for rowwise_df", { + expect_warning( + data.frame(x = 1) %>% duckplyr_rowwise() %>% group_walk(~ {}), + NA + ) +}) + +test_that("duckplyr_group_map() give meaningful errors", { + head1 <- function(d) head(d, 1) + + expect_snapshot({ + # duckplyr_group_modify() + (expect_error(mtcars %>% duckplyr_group_by(cyl) %>% duckplyr_group_modify(~ data.frame(cyl = 19)))) + (expect_error(mtcars %>% duckplyr_group_by(cyl) %>% duckplyr_group_modify(~ 10))) + (expect_error(iris %>% duckplyr_group_by(Species) %>% duckplyr_group_modify(head1))) + + # duckplyr_group_map() + (expect_error(iris %>% duckplyr_group_by(Species) %>% duckplyr_group_map(head1))) + }) + +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-nest.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-nest.R new file mode 100644 index 000000000..0d4ad679d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-nest.R @@ -0,0 +1,63 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("duckplyr_group_nest() works", { + grouped <- duckplyr_group_by(starwars, species, homeworld) + gdata <- group_data(grouped) + + res <- duckplyr_group_nest(starwars, species, homeworld) + expect_type(duckplyr_pull(res), "list") + expect_equal(attr(duckplyr_pull(res), "ptype"), vec_slice(duckplyr_select(starwars, -species, -homeworld), 0L)) + + expect_equal(res[1:2], structure(gdata[1:2], .drop = NULL)) + + nested <- bind_rows(!!!res$data) + expect_equal(names(nested), duckplyr_setdiff(names(starwars), c("species", "homeworld"))) +}) + +test_that("duckplyr_group_nest() can keep the grouping variables", { + grouped <- duckplyr_group_by(starwars, species, homeworld) + gdata <- group_data(grouped) + + res <- duckplyr_group_nest(starwars, species, homeworld, keep = TRUE) + nested <- bind_rows(!!!res$data) + expect_equal(names(nested), names(starwars)) +}) + +test_that("duckplyr_group_nest() works on grouped data frames", { + grouped <- duckplyr_group_by(starwars, species, homeworld) + gdata <- group_data(grouped) + + res <- duckplyr_group_nest(grouped) + expect_type(duckplyr_pull(res), "list") + expect_equal(res[1:2], structure(gdata[1:2], .drop = NULL)) + expect_equal(names(bind_rows(!!!res$data)), duckplyr_setdiff(names(starwars), c("species", "homeworld"))) + + res <- duckplyr_group_nest(grouped, keep = TRUE) + expect_type(duckplyr_pull(res), "list") + expect_equal(attr(duckplyr_pull(res), "ptype"), vec_slice(starwars, 0L)) + + expect_equal(res[1:2], structure(gdata[1:2], .drop = NULL)) + expect_equal(names(bind_rows(!!!res$data)), names(starwars)) +}) + +test_that("group_nest.grouped_df() warns about ...", { + expect_warning(duckplyr_group_nest(duckplyr_group_by(mtcars, cyl), cyl)) +}) + +test_that("duckplyr_group_nest() works if no grouping column", { + skip("TODO duckdb") + res <- duckplyr_group_nest(iris) + expect_equal(res$data, list(iris)) + expect_equal(names(res), "data") +}) + +test_that("duckplyr_group_nest() respects .drop", { + nested <- tibble(f = factor("b", levels = c("a", "b", "c")), x = 1, y = 2) %>% + duckplyr_group_nest(f, .drop = TRUE) + expect_equal(nrow(nested), 1L) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-split.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-split.R new file mode 100644 index 000000000..7983a63ac --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-split.R @@ -0,0 +1,138 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("duckplyr_group_split() keeps the grouping variables by default", { + tbl <- tibble(x = 1:4, g = factor(rep(c("a", "b"), each = 2))) + res <- duckplyr_group_split(tbl, g) + + expect_equal(res, list_of(tbl[1:2,], tbl[3:4,])) + expect_identical(res, list_of(tbl[1:2,], tbl[3:4,])) + expect_s3_class(res, "vctrs_list_of") + expect_identical(attr(res, "ptype"), tibble(x = integer(), g = factor(levels = c("a", "b")))) +}) + +test_that("duckplyr_group_split() can discard the grouping variables with .keep = FALSE", { + tbl <- tibble(x = 1:4, g = factor(rep(c("a", "b"), each = 2))) + res <- duckplyr_group_split(tbl, g, .keep = FALSE) + + expect_identical(res, list_of(tbl[1:2, 1, drop = FALSE], tbl[3:4,1, drop = FALSE])) + expect_s3_class(res, "vctrs_list_of") + expect_identical(attr(res, "ptype"), tibble(x = integer())) +}) + +test_that("duckplyr_group_split() respects empty groups", { + tbl <- tibble(x = 1:4, g = factor(rep(c("a", "b"), each = 2), levels = c("a", "b", "c"))) + res <- duckplyr_group_split(tbl, g) + + expect_identical(res, list_of(tbl[1:2,], tbl[3:4,])) + expect_s3_class(res, "vctrs_list_of") + expect_identical(attr(res, "ptype"), tibble(x = integer(), g = factor(levels = c("a", "b", "c")))) + + res <- duckplyr_group_split(tbl, g, .drop = FALSE) + expect_identical(res, list_of(tbl[1:2,], tbl[3:4,], tbl[integer(), ])) +}) + +test_that("group_split.grouped_df() warns about ...", { + expect_warning(duckplyr_group_split(duckplyr_group_by(mtcars, cyl), cyl)) +}) + +test_that("group_split.rowwise_df() warns about ...", { + expect_warning(duckplyr_group_split(duckplyr_rowwise(mtcars), cyl)) +}) + +test_that("group_split.grouped_df() works", { + iris <- as_tibble(iris) + + expect_identical( + iris %>% duckplyr_group_by(Species) %>% duckplyr_group_split(), + iris %>% duckplyr_group_split(Species) + ) +}) + +test_that("group_split / bind_rows round trip", { + setosa <- iris %>% duckplyr_filter(Species == "setosa") %>% as_tibble() + + chunks <- setosa %>% duckplyr_group_split(Species) + expect_identical(length(chunks), 1L) + expect_identical(bind_rows(chunks), setosa) + + chunks <- setosa %>% duckplyr_group_split(Species, .drop = FALSE) + expect_identical(length(chunks), 3L) + expect_identical(bind_rows(chunks), setosa) +}) + +test_that("duckplyr_group_split() works if no grouping column", { + expect_identical(duckplyr_group_split(iris), list_of(as_tibble(iris))) +}) + +test_that("duckplyr_group_split(keep=FALSE) does not try to remove virtual grouping columns (#4045)", { + iris3 <- as_tibble(iris[1:3,]) + rows <- list(c(1L, 3L, 2L), c(3L, 2L, 3L)) + df <- new_grouped_df( + iris3, + groups = tibble(.bootstrap = 1:2, .rows := rows) + ) + res <- duckplyr_group_split(df, .keep = FALSE) + + expect_identical( + res, + list_of(iris3[rows[[1L]],], iris3[rows[[2L]],]) + ) +}) + +test_that("duckplyr_group_split() respects .drop", { + chunks <- tibble(f = factor("b", levels = c("a", "b", "c"))) %>% + duckplyr_group_split(f, .drop = TRUE) + expect_identical(length(chunks), 1L) +}) + +test_that("duckplyr_group_split() on a bare data frame returns bare tibbles", { + df <- data.frame(x = 1:2) + tib <- as_tibble(df) + expect <- list_of(vec_slice(tib, 1), vec_slice(tib, 2)) + expect_identical(duckplyr_group_split(df, x), expect) +}) + +test_that("duckplyr_group_split() on a grouped df returns a list of tibbles", { + df <- tibble(x = 1:2) + gdf <- duckplyr_group_by(df, x) + expect <- list_of(vec_slice(df, 1), vec_slice(df, 2)) + expect_identical(duckplyr_group_split(gdf), expect) +}) + +test_that("duckplyr_group_split() on a rowwise df returns a list of tibbles", { + df <- tibble(x = 1:2) + rdf <- duckplyr_rowwise(df) + expect <- list_of(vec_slice(df, 1), vec_slice(df, 2)) + expect_identical(duckplyr_group_split(rdf), expect) +}) + +test_that("duckplyr_group_split() works with subclasses implementing duckplyr_group_by() / duckplyr_ungroup()", { + local_foo_df() + + df <- list(x = c(1, 2, 2)) + df <- new_tibble(df, nrow = 3L, class = "foo_df") + + expect <- list_of(vec_slice(df, 1), vec_slice(df, 2:3)) + + expect_identical(duckplyr_group_split(df, x), expect) +}) + +test_that("duckplyr_group_split() internally uses dplyr_row_slice()", { + local_foo_df() + + df <- list(x = c(1, 2, 2)) + df <- new_tibble(df, nrow = 3L, class = "foo_df") + + local_methods( + dplyr_row_slice.foo_df = function(x, i, ...) { + abort(class = "dplyr_row_slice_called") + } + ) + + expect_error(duckplyr_group_split(df, x), class = "dplyr_row_slice_called") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-trim.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-trim.R new file mode 100644 index 000000000..a73e47a92 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-group-trim.R @@ -0,0 +1,29 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("duckplyr_group_trim() is identity on non grouped data", { + expect_identical(duckplyr_group_trim(iris), iris) +}) + +test_that("duckplyr_group_trim() always regroups even if no factors", { + res <- mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(cyl == 6, .preserve = TRUE) %>% + duckplyr_group_trim() + expect_equal(duckplyr_n_groups(res), 1L) +}) + +test_that("duckplyr_group_trim() drops factor levels in data and grouping structure", { + res <- iris %>% + duckplyr_group_by(Species) %>% + duckplyr_filter(Species == "setosa") %>% + duckplyr_group_trim() + + expect_equal(duckplyr_n_groups(res), 1L) + expect_equal(levels(res$Species), "setosa") + expect_equal(levels(attr(res, "groups")$Species), "setosa") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-grouped-df.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-grouped-df.R new file mode 100644 index 000000000..f766285d9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-grouped-df.R @@ -0,0 +1,221 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("new_grouped_df can create alternative grouping structures (#3837)", { + tbl <- new_grouped_df( + tibble(x = rnorm(10)), + groups = tibble(".rows" := replicate(5, sample(1:10, replace = TRUE), simplify = FALSE)) + ) + res <- duckplyr_summarise(tbl, x = mean(x)) + expect_equal(nrow(res), 5L) +}) + +test_that("new_grouped_df does not have rownames (#4173)", { + tbl <- new_grouped_df( + tibble(x = rnorm(10)), + groups = tibble(".rows" := replicate(5, sample(1:10, replace = TRUE), simplify = FALSE)) + ) + expect_false(tibble::has_rownames(tbl)) +}) + +test_that("[ method can remove grouping vars", { + df <- tibble(x = 1, y = 2, z = 3) + gf <- duckplyr_group_by(df, x, y) + + expect_equal(gf, gf) + expect_equal(gf[1], duckplyr_group_by(df[1], x)) + expect_equal(gf[3], df[3]) +}) + +test_that("[ method reuses group_data() if possible", { + df <- tibble(x = 1, y = 2, z = 3) + gf <- duckplyr_group_by(df, x, y) + + expect_true(rlang::is_reference(group_data(gf), group_data(gf[1:2]))) + expect_true(rlang::is_reference(group_data(gf), group_data(gf[, 1:2]))) +}) + +test_that("[ supports drop=TRUE (#3714)", { + df <- tibble(x = 1, y = 2) + gf <- duckplyr_group_by(df, x) + + expect_type(gf[, "y", drop = TRUE], "double") + expect_s3_class(gf[, c("x", "y"), drop = TRUE], "tbl_df") +}) + +test_that("$<-, [[<-, and [<- update grouping data if needed", { + df <- tibble(x = 1, y = 2) + gf <- duckplyr_group_by(df, x) + + # value has to be past the ellipsis in $<-() + expect_equal(group_data(`$<-`(gf, "x", value = 2))$x, 2) + expect_equal(group_data(`$<-`(gf, "y", value = 2))$x, 1) + + expect_equal(group_data({gf2 <- gf; gf2[[1]] <- 3; gf2})$x, 3) + expect_equal(group_data(`[<-`(gf, 1, "x", value = 4))$x, 4) +}) + +test_that("can remove grouping cols with subset assignment", { + df <- tibble(x = 1, y = 2) + gf1 <- gf2 <- gf3 <- duckplyr_group_by(df, x, y) + + gf1$x <- NULL + gf2[["x"]] <- NULL + gf3[, "x"] <- NULL + + expect_named(group_data(gf1), c("y", ".rows")) + expect_named(group_data(gf2), c("y", ".rows")) + expect_named(group_data(gf3), c("y", ".rows")) +}) + +test_that("names<- updates grouping data", { + df <- tibble(x = 1, y = 2, z = 3) + gf <- duckplyr_group_by(df, x, y) + + names(gf) <- c("z1", "z2", "z3") + expect_named(group_data(gf), c("z1", "z2", ".rows")) + + names(gf)[1] <- c("Z1") + expect_named(group_data(gf), c("Z1", "z2", ".rows")) +}) + +test_that("names<- doesn't modify group data if not necessary", { + df <- tibble(x = 1, y = 2) + gf1 <- gf2 <- duckplyr_group_by(df, x) + expect_true(rlang::is_reference(group_data(gf1), group_data(gf2))) + + names(gf1) <- c("x", "Y") + expect_true(rlang::is_reference(group_data(gf1), group_data(gf2))) +}) + +test_that("group order is maintained in grouped-df methods (#5040)", { + gdf <- duckplyr_group_by(mtcars, cyl, am, vs) + + x <- gdf[0,] + expect_identical(duckplyr_group_vars(x), duckplyr_group_vars(gdf)) + + x <- gdf + x$am <- 1 + expect_identical(duckplyr_group_vars(x), duckplyr_group_vars(gdf)) + + x <- gdf + x["am"] <- 1 + expect_identical(duckplyr_group_vars(x), duckplyr_group_vars(gdf)) + + x <- gdf + x[["am"]] <- 1 + expect_identical(duckplyr_group_vars(x), duckplyr_group_vars(gdf)) + + x <- gdf + names <- names(x) + names[9] <- "am2" + names(x) <- names + expect_identical(duckplyr_group_vars(x), duckplyr_group_vars(duckplyr_group_by(x, cyl, am2, vs))) +}) + + +# validate ---------------------------------------------------------------- + +test_that("validate_grouped_df() gives useful errors", { + df1 <- duckplyr_group_by(tibble(x = 1:4, g = rep(1:2, each = 2)), g) + groups <- attr(df1, "groups") + groups[[2]] <- 1:2 + attr(df1, "groups") <- groups + + df2 <- duckplyr_group_by(tibble(x = 1:4, g = rep(1:2, each = 2)), g) + groups <- attr(df2, "groups") + names(groups) <- c("g", "not.rows") + attr(df2, "groups") <- groups + + df3 <- df2 + attr(df3, "groups") <- tibble() + + df4 <- df3 + attr(df4, "groups") <- NA + + df5 <- tibble(x = 1:4, g = rep(1:2, each = 2)) + attr(df5, "vars") <- "g" + attr(df5, "class") <- c("grouped_df", "tbl_df", "tbl", "data.frame") + + df6 <- new_grouped_df( + tibble(x = 1:10), + groups = tibble(".rows" := list(1:5, -1L)) + ) + df7 <- df6 + attr(df7, "groups")$.rows <- list(11L) + + df8 <- df6 + attr(df8, "groups")$.rows <- list(0L) + + df10 <- df6 + attr(df10, "groups") <- tibble() + + df11 <- df6 + attr(df11, "groups") <- NULL + + expect_snapshot({ + # Invalid `groups` attribute + (expect_error(validate_grouped_df(df1))) + (expect_error(group_data(df1))) + (expect_error(validate_grouped_df(df2))) + (expect_error(validate_grouped_df(df2))) + (expect_error(validate_grouped_df(df3))) + (expect_error(validate_grouped_df(df4))) + + # Older style grouped_df + (expect_error(validate_grouped_df(df5))) + + # validate_grouped_df( + (expect_error(validate_grouped_df(df6, check_bounds = TRUE))) + (expect_error(validate_grouped_df(df7, check_bounds = TRUE))) + (expect_error(validate_grouped_df(df8, check_bounds = TRUE))) + (expect_error(validate_grouped_df(df10))) + (expect_error(validate_grouped_df(df11))) + + # new_grouped_df() + (expect_error( + new_grouped_df( + tibble(x = 1:10), + tibble(other = list(1:2)) + ) + )) + (expect_error(new_grouped_df(10))) + }) + +}) + +# compute_group ---------------------------------------------------------- + +test_that("helper gives meaningful error messages", { + expect_snapshot({ + (expect_error(grouped_df(data.frame(x = 1), "y", FALSE))) + (expect_error(grouped_df(data.frame(x = 1), 1))) + }) +}) + +test_that("NA and NaN are in separate groups at the end", { + df <- tibble(x = c(NA, NaN, NA, 1)) + result <- compute_groups(df, "x") + expect_identical(result$x, c(1, NaN, NA)) +}) + +test_that("groups are ordered in the C locale", { + df <- tibble(x = c("a", "A", "Z", "b")) + result <- compute_groups(df, "x") + expect_identical(result$x, c("A", "Z", "a", "b")) +}) + +test_that("using the global option `dplyr.legacy_locale` forces the system locale", { + skip_if_not(has_collate_locale("en_US"), message = "Can't use 'en_US' locale") + + local_options(dplyr.legacy_locale = TRUE) + withr::local_collate("en_US") + + df <- tibble(x = c("a", "A", "Z", "b")) + result <- compute_groups(df, "x") + expect_identical(result$x, c("a", "A", "b", "Z")) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-groups-with.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-groups-with.R new file mode 100644 index 000000000..9cdfe500e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-groups-with.R @@ -0,0 +1,28 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("restores original class", { + df <- data.frame(x = 1:2) + gf <- duckplyr_group_by(df, x) + + expect_s3_class(with_groups(df, x, mutate), "data.frame", exact = TRUE) + expect_s3_class(with_groups(gf, x, mutate), "grouped_df") +}) + +test_that(".groups = NULL ungroups", { + gf <- duckplyr_group_by(tibble(x = 1:2), x) + out <- gf %>% with_groups(NULL, mutate, y = mean(x)) + expect_equal(out$y, c(1.5, 1.5)) +}) + +test_that(".groups is defused with context", { + local_fn <- identity + expect_identical( + with_groups(mtcars, local_fn(2), mutate, disp = disp / sd(disp)), + with_groups(mtcars, 2, mutate, disp = disp / sd(disp)) + ) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-if-else.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-if-else.R new file mode 100644 index 000000000..797df6453 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-if-else.R @@ -0,0 +1,120 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("scalar true and false are vectorised", { + x <- c(TRUE, TRUE, FALSE, FALSE) + expect_equal(if_else(x, 1, 2), c(1, 1, 2, 2)) +}) + +test_that("vector true and false are ok", { + x <- c(-1, 0, 1) + + expect_equal(if_else(x < 0, x, 0), c(-1, 0, 0)) + expect_equal(if_else(x > 0, x, 0), c(0, 0, 1)) +}) + +test_that("missing values are missing", { + expect_equal(if_else(c(TRUE, NA, FALSE), -1, 1), c(-1, NA, 1)) +}) + +test_that("works with lists", { + x <- list(1, 2, 3) + + expect_equal( + if_else(c(TRUE, TRUE, FALSE), x, list(NULL)), + list(1, 2, NULL) + ) +}) + +test_that("works with data frames", { + true <- tibble(x = 1, y = 2) + false <- tibble(x = 3, y = 4) + + expect_identical( + if_else(c(TRUE, FALSE, NA, TRUE), true, false), + vec_c(true, false, NA, true) + ) +}) + +test_that("works with vctrs rcrd types", { + true <- new_rcrd(list(x = 1, y = 2)) + false <- new_rcrd(list(x = 3, y = 4)) + + expect_identical( + if_else(c(TRUE, FALSE, NA, TRUE), true, false), + vec_c(true, false, NA, true) + ) +}) + +test_that("takes the common type of `true` and `false` (#6243)", { + expect_identical(if_else(TRUE, 1L, 1.5), 1) + + expect_snapshot(error = TRUE, { + if_else(TRUE, 1, "x") + }) +}) + +test_that("includes `missing` in the common type computation if used", { + expect_identical(if_else(TRUE, 1L, 2L, missing = 3), 1) + + expect_snapshot(error = TRUE, { + if_else(TRUE, 1, 2, missing = "x") + }) +}) + +test_that("can recycle to size 0 `condition`", { + expect_identical(if_else(logical(), 1, 2, missing = 3), double()) +}) + +test_that("accepts logical conditions with attributes (#6678)", { + x <- structure(TRUE, label = "foo") + expect_identical(if_else(x, 1, 2), 1) +}) + +test_that("`condition` must be logical (and isn't cast to logical!)", { + expect_snapshot(error = TRUE, { + if_else(1:10, 1, 2) + }) +}) + +test_that("`true`, `false`, and `missing` must recycle to the size of `condition`", { + x <- 1:3 + bad <- 1:2 + + expect_snapshot(error = TRUE, { + if_else(x < 2, bad, x) + }) + expect_snapshot(error = TRUE, { + if_else(x < 2, x, bad) + }) + expect_snapshot(error = TRUE, { + if_else(x < 2, x, x, missing = bad) + }) +}) + +test_that("must have empty dots", { + expect_snapshot(error = TRUE, { + if_else(TRUE, 1, 2, missing = 3, 4) + }) +}) + +test_that("`ptype` overrides the common type", { + expect_identical(if_else(TRUE, 2, 1L, ptype = integer()), 2L) + + expect_snapshot(error = TRUE, { + if_else(TRUE, 1L, 2.5, ptype = integer()) + }) +}) + +test_that("`size` overrides the `condition` size", { + expect_identical(if_else(c(TRUE, FALSE), 1, 2, size = 2), c(1, 2)) + + # Note that `condition` is used as the name in the error message + expect_snapshot(error = TRUE, { + if_else(TRUE, 1, 2, size = 2) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-by.R new file mode 100644 index 000000000..841af6096 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-by.R @@ -0,0 +1,382 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# ------------------------------------------------------------------------------ +# `join_by()` + +test_that("works with equi conditions", { + by <- join_by(x == y, a == b) + + expect_identical(by$x, c("x", "a")) + expect_identical(by$y, c("y", "b")) + expect_identical(by$condition, c("==", "==")) + expect_identical(by$filter, c("none", "none")) +}) + +test_that("works with non-equi conditions", { + by <- join_by(x == y, a > b, a >= b, a < b, a <= b) + + expect_identical(by$x, c("x", rep("a", 4))) + expect_identical(by$y, c("y", rep("b", 4))) + expect_identical(by$condition, c("==", ">", ">=", "<", "<=")) +}) + +test_that("works with `closest()`", { + by <- join_by(x == y, closest(a >= b)) + + expect_identical(by$x, c("x", "a")) + expect_identical(by$y, c("y", "b")) + expect_identical(by$filter, c("none", "max")) + expect_identical(by$condition, c("==", ">=")) + + by <- join_by(x == y, closest(a > b)) + + expect_identical(by$x, c("x", "a")) + expect_identical(by$y, c("y", "b")) + expect_identical(by$filter, c("none", "max")) + expect_identical(by$condition, c("==", ">")) + + by <- join_by(x == y, closest(a <= b)) + + expect_identical(by$x, c("x", "a")) + expect_identical(by$y, c("y", "b")) + expect_identical(by$filter, c("none", "min")) + expect_identical(by$condition, c("==", "<=")) + + by <- join_by(x == y, closest(a < b)) + + expect_identical(by$x, c("x", "a")) + expect_identical(by$y, c("y", "b")) + expect_identical(by$filter, c("none", "min")) + expect_identical(by$condition, c("==", "<")) +}) + +test_that("works with single arguments", { + by <- join_by(a, b) + expect_identical(by$x, c("a", "b")) + expect_identical(by$y, c("a", "b")) +}) + +test_that("works with character strings", { + by1 <- join_by("a", "b" == "c", closest("d" >= "e")) + by2 <- join_by(a, b == c, closest(d >= e)) + + expect_identical(by1$condition, by2$condition) + expect_identical(by1$filter, by2$filter) + expect_identical(by1$x, by2$x) + expect_identical(by1$y, by2$y) +}) + +test_that("works with explicit referencing", { + by <- join_by(x$a == y$b) + expect_identical(by$x, "a") + expect_identical(by$y, "b") + + by <- join_by(y$a == x$b) + expect_identical(by$x, "b") + expect_identical(by$y, "a") +}) + +test_that("join condition is correctly reversed with explicit referencing", { + by <- join_by(y$a == x$a, y$a >= x$a, y$a > x$a, y$a <= x$a, y$a < x$a) + expect_identical(by$condition, c("==", "<=", "<", ">=", ">")) +}) + +test_that("`closest()` works with explicit referencing", { + by <- join_by(closest(y$a <= x$b), closest(y$a > x$b)) + expect_identical(by$x, c("b", "b")) + expect_identical(by$y, c("a", "a")) + expect_identical(by$filter, c("max", "min")) + expect_identical(by$condition, c(">=", "<")) +}) + +test_that("between conditions expand correctly", { + by <- join_by(between(a, b, c)) + expect_identical(by$x, c("a", "a")) + expect_identical(by$y, c("b", "c")) + + by <- join_by(between(y$a, x$b, x$c)) + expect_identical(by$x, c("b", "c")) + expect_identical(by$y, c("a", "a")) + + by <- join_by(between(a, b, c, bounds = "[]")) + expect_identical(by$condition, c(">=", "<=")) + by <- join_by(between(a, b, c, bounds = "[)")) + expect_identical(by$condition, c(">=", "<")) + by <- join_by(between(a, b, c, bounds = "(]")) + expect_identical(by$condition, c(">", "<=")) + by <- join_by(between(a, b, c, bounds = "()")) + expect_identical(by$condition, c(">", "<")) + + by <- join_by(between(y$a, x$b, x$c, bounds = "[]")) + expect_identical(by$condition, c("<=", ">=")) + by <- join_by(between(y$a, x$b, x$c, bounds = "[)")) + expect_identical(by$condition, c("<=", ">")) + by <- join_by(between(y$a, x$b, x$c, bounds = "(]")) + expect_identical(by$condition, c("<", ">=")) + by <- join_by(between(y$a, x$b, x$c, bounds = "()")) + expect_identical(by$condition, c("<", ">")) +}) + +test_that("within conditions expand correctly", { + by <- join_by(within(a, b, c, d)) + expect_identical(by$x, c("a", "b")) + expect_identical(by$y, c("c", "d")) + expect_identical(by$condition, c(">=", "<=")) + + by <- join_by(within(y$a, y$b, x$b, x$c)) + expect_identical(by$x, c("b", "c")) + expect_identical(by$y, c("a", "b")) + expect_identical(by$condition, c("<=", ">=")) +}) + +test_that("overlaps conditions expand correctly", { + by <- join_by(overlaps(a, b, c, d)) + expect_identical(by$x, c("a", "b")) + expect_identical(by$y, c("d", "c")) + + by <- join_by(overlaps(y$a, y$b, x$b, x$c)) + expect_identical(by$x, c("c", "b")) + expect_identical(by$y, c("a", "b")) + + by <- join_by(overlaps(a, b, c, d, bounds = "[]")) + expect_identical(by$condition, c("<=", ">=")) + by <- join_by(overlaps(a, b, c, d, bounds = "[)")) + expect_identical(by$condition, c("<", ">")) + by <- join_by(overlaps(a, b, c, d, bounds = "(]")) + expect_identical(by$condition, c("<", ">")) + by <- join_by(overlaps(a, b, c, d, bounds = "()")) + expect_identical(by$condition, c("<", ">")) + + by <- join_by(overlaps(y$a, y$b, x$b, x$c, bounds = "[]")) + expect_identical(by$condition, c(">=", "<=")) + by <- join_by(overlaps(y$a, y$b, x$b, x$c, bounds = "[)")) + expect_identical(by$condition, c(">", "<")) + by <- join_by(overlaps(y$a, y$b, x$b, x$c, bounds = "(]")) + expect_identical(by$condition, c(">", "<")) + by <- join_by(overlaps(y$a, y$b, x$b, x$c, bounds = "()")) + expect_identical(by$condition, c(">", "<")) +}) + +test_that("between / overlaps / within / closest can use named arguments", { + by <- join_by(between(a, y_upper = b, y_lower = c)) + expect_identical(by$x, c("a", "a")) + expect_identical(by$y, c("c", "b")) + + by <- join_by(overlaps(y_lower = c, y_upper = d, x_lower = a, x_upper = b)) + expect_identical(by$x, c("a", "b")) + expect_identical(by$y, c("d", "c")) + expect_identical(by$condition, c("<=", ">=")) + + by <- join_by(overlaps(y_lower = x$c, y_upper = x$d, x_lower = y$a, x_upper = y$b)) + expect_identical(by$x, c("d", "c")) + expect_identical(by$y, c("a", "b")) + expect_identical(by$condition, c(">=", "<=")) + + by <- join_by(within(y_lower = c, y_upper = d, x_lower = a, x_upper = b)) + expect_identical(by$x, c("a", "b")) + expect_identical(by$y, c("c", "d")) + expect_identical(by$condition, c(">=", "<=")) + + by <- join_by(within(y_lower = x$c, y_upper = x$d, x_lower = y$a, x_upper = y$b)) + expect_identical(by$x, c("c", "d")) + expect_identical(by$y, c("a", "b")) + expect_identical(by$condition, c("<=", ">=")) + + by <- join_by(closest(expr = a > b)) + expect_identical(by$x, "a") + expect_identical(by$y, "b") +}) + +test_that("joining by nothing is an error", { + expect_snapshot(error = TRUE, { + join_by() + }) +}) + +test_that("can pass `...` on to wrapped `join_by()`", { + fn <- function(...) { + join_by(...) + } + fn2 <- function(x) { + fn({{x}} == y) + } + + expect_identical(fn(x == y, a <= b), join_by(x == y, a <= b)) + expect_identical(fn2(a), join_by(a == y)) +}) + +test_that("can wrap `join_by()` and use embracing to inject columns (#6469)", { + fn <- function(x) { + join_by({{x}} == y) + } + expect_identical(fn("foo"), join_by("foo" == y)) + + # Expression substitution, not quosure evaluation + a <- "foo" + expect_identical(fn(a), join_by(a == y)) + + # But you can inline with `!!` + expect_identical(fn(!!a), join_by("foo" == y)) + + fn <- function(x, top) { + join_by(between({{x}}, lower, {{top}})) + } + expect_identical(fn(x, y), join_by(between(x, lower, y))) +}) + +test_that("can wrap `join_by()` and use embracing to inject expressions", { + fn <- function(expr) { + join_by({{expr}}, a <= b) + } + expect_identical(fn(a == b), join_by(a == b, a <= b)) +}) + +test_that("nicely catches required missing arguments when wrapped", { + fn <- function(x, y) { + join_by({{x}} == {{y}}) + } + expect_snapshot(error = TRUE, fn(a)) +}) + +test_that("allows for namespaced helpers (#6838)", { + # Captures namespaced expression for printing + expect_snapshot(join_by(dplyr::between(x, left, right))) + expect_snapshot(join_by(dplyr::within(xl, xu, yl, yu))) + expect_snapshot(join_by(dplyr::overlaps(xl, xu, yl, yu))) + expect_snapshot(join_by(dplyr::closest(x < y))) + + # Underlying values are otherwise the same as non-namespaced version + by <- join_by(dplyr::between(x, left, right)) + reference <- join_by(between(x, left, right)) + + expect_identical(by$condition, reference$condition) + expect_identical(by$filter, reference$filter) + expect_identical(by$x, reference$x) + expect_identical(by$y, reference$y) +}) + +test_that("has an informative print method", { + expect_snapshot(join_by(a, b)) + expect_snapshot(join_by("a", "b")) + expect_snapshot(join_by(a == a, b >= c)) + expect_snapshot(join_by(a == a, b >= "c")) + expect_snapshot(join_by(a == a, closest(b >= c), closest(d < e))) +}) + +test_that("has informative error messages", { + # `=` rather than `==` + expect_snapshot(error = TRUE, join_by(a = b)) + + # Empty expression + expect_snapshot(error = TRUE, join_by(NULL)) + + # Improper helper specification + expect_snapshot(error = TRUE, join_by(foo(x > y))) + + # Improper separator + expect_snapshot(error = TRUE, join_by(x == y, x ^ y)) + + # Improper LHS + expect_snapshot(error = TRUE, join_by(x + 1 == y)) + + # Improper RHS + expect_snapshot(error = TRUE, join_by(x == y + 1)) + + # Garbage input + expect_snapshot(error = TRUE, join_by(1)) + + # Call with non-symbol first element + expect_snapshot(error = TRUE, join_by(1())) + + # Namespace prefixed helper with non-dplyr namespace + # (typo or re-export, which currently isn't allowed) + expect_snapshot(error = TRUE, join_by(dplyrr::between(x, left, right))) + + # Top level usage of `$` + expect_snapshot(error = TRUE, join_by(x$a)) + + # `$` must only contain x/y on LHS + expect_snapshot(error = TRUE, join_by(z$a == y$b)) + expect_snapshot(error = TRUE, join_by(x$a == z$b)) + + # Extra cautious check for horrible usage of `$` + expect_snapshot(error = TRUE, join_by(`$`(x+1, y) == b)) + + # Referencing the same table + expect_snapshot(error = TRUE, join_by(x$a == x$b)) + expect_snapshot(error = TRUE, join_by(y$a == b)) + expect_snapshot(error = TRUE, join_by(between(x$a, x$a, x$b))) + expect_snapshot(error = TRUE, join_by(within(x$a, x$b, x$a, x$b))) + expect_snapshot(error = TRUE, join_by(overlaps(a, b, x$a, x$b))) + expect_snapshot(error = TRUE, join_by(closest(x$a >= x$b))) + + # Referencing different tables in lower/upper bound pairs + expect_snapshot(error = TRUE, join_by(between(a, x$a, y$b))) + expect_snapshot(error = TRUE, join_by(within(x$a, y$b, y$a, y$b))) + expect_snapshot(error = TRUE, join_by(overlaps(x$a, x$b, y$a, x$b))) + + # Too few arguments + expect_snapshot(error = TRUE, join_by(`>`(x))) + expect_snapshot(error = TRUE, join_by(between(x))) + expect_snapshot(error = TRUE, join_by(within(x))) + expect_snapshot(error = TRUE, join_by(overlaps(x))) + expect_snapshot(error = TRUE, join_by(closest())) + expect_snapshot(error = TRUE, join_by(`$`(x) > y)) + + # Too many arguments + expect_snapshot(error = TRUE, join_by(closest(a >= b, 1))) + + # `==` in `closest()` + expect_snapshot(error = TRUE, join_by(closest(a == b))) + + # Non-expression in `closest()` + expect_snapshot(error = TRUE, join_by(closest(x))) + expect_snapshot(error = TRUE, join_by(closest(1))) + + # Invalid expression in `closest()` + expect_snapshot(error = TRUE, join_by(closest(x + y))) + + # Invalid `bounds` in `between()` and `overlaps()` + expect_snapshot(error = TRUE, join_by(between(x, lower, upper, bounds = 1))) + expect_snapshot(error = TRUE, join_by(between(x, lower, upper, bounds = "a"))) + expect_snapshot(error = TRUE, join_by(overlaps(x, y, lower, upper, bounds = 1))) + expect_snapshot(error = TRUE, join_by(overlaps(x, y, lower, upper, bounds = "a"))) + + # Non-empty dots in `between()` and `overlaps()` + expect_snapshot(error = TRUE, join_by(between(x, lower, upper, foo = 1))) + expect_snapshot(error = TRUE, join_by(overlaps(x, y, lower, upper, foo = 1))) +}) + +# ------------------------------------------------------------------------------ +# `as_join_by()` + +test_that("as_join_by() emits useful errors", { + expect_snapshot(error = TRUE, as_join_by(FALSE)) +}) + +# ------------------------------------------------------------------------------ +# `join_by_common()` + +test_that("automatically finds common variables", { + x_names <- c("x", "y") + y_names <- c("x", "z") + expect_message(by <- join_by_common(x_names, y_names)) + expect_identical(by$x, "x") + expect_identical(by$y, "x") +}) + +test_that("join_by_common() emits useful information", { + # Common by message + expect_snapshot(by <- join_by_common(c("x", "y"), c("x", "y"))) + + # Works with names that need backticks + expect_snapshot(by <- join_by_common(c("_x", "foo bar"), c("_x", "foo bar"))) + + # No common variables error + expect_snapshot(error = TRUE, join_by_common(c("x", "y"), c("w", "z"))) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cols.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cols.R new file mode 100644 index 000000000..dc86917fb --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cols.R @@ -0,0 +1,208 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("key vars are found", { + vars <- join_cols(c("x", "y"), c("x", "z"), by = join_by(x)) + expect_equal(vars$x$key, c(x = 1L)) + expect_equal(vars$y$key, c(x = 1L)) + + vars <- join_cols(c("a", "x", "b"), c("x", "a"), by = join_by(x)) + expect_equal(vars$x$key, c(x = 2L)) + expect_equal(vars$y$key, c(x = 1L)) + + vars <- join_cols(c("x", "y"), c("a", "x", "z"), by = join_by(y == z)) + expect_equal(vars$x$key, c(y = 2L)) + expect_equal(vars$y$key, c(z = 3L)) + + vars <- join_cols(c("x", "y"), c("a", "x", "z"), by = join_by(y >= z)) + expect_equal(vars$x$key, c(y = 2L)) + expect_equal(vars$y$key, c(z = 3L)) +}) + +test_that("y key matches order and names of x key", { + vars <- join_cols(c("x", "y", "z"), c("c", "b", "a"), by = join_by(x == a, y == b)) + expect_equal(vars$x$key, c(x = 1L, y = 2L)) + expect_equal(vars$y$key, c(a = 3L, b = 2L)) +}) + +test_that("duplicate column names are given suffixes", { + vars <- join_cols(c("x", "y"), c("x", "y"), by = join_by(x)) + expect_equal(vars$x$out, c("x" = 1, "y.x" = 2)) + expect_equal(vars$y$out, c("y.y" = 2)) + + # including join vars when keep = TRUE + vars <- join_cols(c("x", "y"), c("x", "y"), by = join_by(x), keep = TRUE) + expect_equal(vars$x$out, c("x.x" = 1, "y.x" = 2)) + expect_equal(vars$y$out, c("x.y" = 1, "y.y" = 2)) + + vars <- join_cols(c("x", "y"), c("x", "y"), by = join_by(x < x), keep = TRUE) + expect_equal(vars$x$out, c("x.x" = 1, "y.x" = 2)) + expect_equal(vars$y$out, c("x.y" = 1, "y.y" = 2)) + + # suffixes don't create duplicates + vars <- join_cols(c("x", "y", "y.x"), c("x", "y"), by = join_by(x)) + expect_equal(vars$x$out, c("x" = 1, "y.x" = 2, "y.x.x" = 3)) + expect_equal(vars$y$out, c("y.y" = 2)) + + # but not when they're the join vars + vars <- join_cols(c("A", "A.x"), c("B", "A.x", "A"), by = join_by(A.x)) + expect_named(vars$x$out, c("A.x.x", "A.x")) + expect_named(vars$y$out, c("B", "A.y")) + + # or when no suffix is requested + vars <- join_cols(c("x", "y"), c("x", "y"), by = join_by(x), suffix = c("", ".y")) + expect_equal(vars$x$out, c("x" = 1, "y" = 2)) + expect_equal(vars$y$out, c("y.y" = 2)) +}) + +test_that("duplicate non-equi key columns are given suffixes", { + vars <- join_cols(c("a", "y", "z"), c("b", "y", "z"), by = join_by(y >= y, z <= z)) + expect_equal(vars$x$out, c("a" = 1, "y.x" = 2, "z.x" = 3)) + expect_equal(vars$y$out, c("b" = 1, "y.y" = 2, "z.y" = 3)) +}) + +test_that("NA names are preserved", { + vars <- join_cols(c("x", NA), c("x", "z"), by = join_by(x)) + expect_named(vars$x$out, c("x", NA)) + + vars <- join_cols(c("x", NA), c("x", NA), by = join_by(x)) + expect_named(vars$x$out, c("x", "NA.x")) + expect_named(vars$y$out, "NA.y") +}) + +test_that("by default, `by` columns omitted from `y` with equi-conditions, but not non-equi conditions" , { + # equi keys always keep the LHS name, regardless of whether of not a duplicate exists in the RHS + # non-equi keys will get a suffix if a duplicate exists + vars <- join_cols(c("x", "y", "z"), c("x", "y", "z"), by = join_by(x == y, y > z)) + expect_equal(vars$x$out, c("x" = 1, "y" = 2, "z.x" = 3)) + expect_equal(vars$y$out, c("x.y" = 1, "z.y" = 3)) + + # unless specifically requested with `keep = TRUE` + vars <- join_cols(c("x", "y", "z"), c("x", "y", "z"), by = join_by(x == y, y > z), keep = TRUE) + expect_equal(vars$x$out, c("x.x" = 1, "y.x" = 2, "z.x" = 3)) + expect_equal(vars$y$out, c("x.y" = 1, "y.y" = 2, "z.y" = 3)) +}) + +test_that("can't mix non-equi conditions with `keep = FALSE` (#6499)", { + expect_snapshot(error = TRUE, { + join_cols(c("x", "y"), c("x", "z"), by = join_by(x, y > z), keep = FALSE) + }) + expect_snapshot(error = TRUE, { + join_cols(c("xl", "xu"), c("yl", "yu"), by = join_by(xl >= yl, xu < yu), keep = FALSE) + }) + + # Doesn't make sense here. + # With right/full joins we'd have to merge both `yl` and `yu` into `x` somehow. + expect_snapshot(error = TRUE, { + join_cols("x", c("yl", "yu"), by = join_by(between(x, yl, yu)), keep = FALSE) + }) + + # Doesn't make sense here. + # With right/full joins, based on how the binary conditions are generated + # we'd merge: + # - `yu` into `xl` + # - `yl` into `xu` + # Which can result in `xl` and `xu` columns that don't maintain a `xl <= xu` + # invariant. + expect_snapshot(error = TRUE, { + join_cols(c("xl", "xu"), c("yl", "yu"), by = join_by(overlaps(xl, xu, yl, yu)), keep = FALSE) + }) +}) + +test_that("can duplicate key between non-equi conditions", { + vars <- join_cols("x", c("xl", "xu"), by = join_by(x > xl, x < xu)) + + expect_identical(vars$x$key, c(x = 1L, x = 1L)) + expect_identical(vars$x$out, c(x = 1L)) + + expect_identical(vars$y$key, c(xl = 1L, xu = 2L)) + expect_identical(vars$y$out, c(xl = 1L, xu = 2L)) + + expect_identical( + join_cols("x", c("xl", "xu"), by = join_by(x > xl, x < xu), keep = NULL), + join_cols("x", c("xl", "xu"), by = join_by(x > xl, x < xu), keep = TRUE) + ) +}) + +test_that("can't duplicate key between equi condition and non-equi condition", { + expect_snapshot(error = TRUE, join_cols("x", c("xl", "xu"), by = join_by(x > xl, x == xu))) + expect_snapshot(error = TRUE, join_cols(c("xl", "xu"), "x", by = join_by(xl < x, xu == x))) +}) + +test_that("emits useful messages", { + # names + expect_snapshot(error = TRUE, join_cols(c("x", "y"), c("y", "y"), join_by(y))) + expect_snapshot(error = TRUE, join_cols(c("y", "y"), c("x", "y"), join_by(y))) + + xy <- c("x", "y") + xyz <- c("x", "y", "z") + + # join vars errors + expect_snapshot(error = TRUE, join_cols(xy, xy, by = as_join_by(list("1", y = "2")))) + expect_snapshot(error = TRUE, join_cols(xy, xy, by = as_join_by(list(x = "1", "2")))) + expect_snapshot(error = TRUE, join_cols(xy, xy, by = as_join_by(c("x", NA)))) + expect_snapshot(error = TRUE, join_cols(xy, xy, by = as_join_by(c("aaa", "bbb")))) + + # join vars uniqueness + expect_snapshot(error = TRUE, join_cols(xy, xy, by = as_join_by(c("x", "x", "x")))) + expect_snapshot(error = TRUE, join_cols(xyz, xyz, by = join_by(x, x > y, z))) + + # suffixes + expect_snapshot(error = TRUE, join_cols(xy, xy, by = join_by(x), suffix = "x")) + expect_snapshot(error = TRUE, join_cols(xy, xy, by = join_by(x), suffix = c("", NA))) +}) + +# ------------------------------------------------------------------------------ +# join_cast_common() + +test_that("takes common type", { + x <- tibble(a = 1, b = 2L) + y <- tibble(a = 1L, b = 3) + + vars <- join_cols(names(x), names(y), by = join_by(a, b)) + + out <- join_cast_common(x, y, vars) + + expect_identical(out$x, tibble(a = 1, b = 2)) + expect_identical(out$y, tibble(a = 1, b = 3)) +}) + +test_that("finalizes unspecified columns (#6804)", { + vars <- join_cols(x_names = "x", y_names = "x", by = join_by(x)) + + x <- tibble(x = NA) + y <- tibble(x = NA) + out <- join_cast_common(x, y, vars) + expect_identical(out$x, tibble(x = NA)) + expect_identical(out$y, tibble(x = NA)) + + x <- tibble(x = NA) + y <- tibble(x = unspecified()) + out <- join_cast_common(x, y, vars) + expect_identical(out$x, tibble(x = NA)) + expect_identical(out$y, tibble(x = logical())) + + x <- tibble(x = unspecified()) + y <- tibble(x = unspecified()) + out <- join_cast_common(x, y, vars) + expect_identical(out$x, tibble(x = logical())) + expect_identical(out$y, tibble(x = logical())) +}) + +test_that("references original column in `y` when there are type errors (#6465)", { + x <- tibble(a = 1) + y <- tibble(b = "1") + + x_key <- x + y_key <- set_names(y, names(x)) + + vars <- join_cols(names(x), names(y), by = join_by(a == b)) + + expect_snapshot({ + (expect_error(join_cast_common(x_key, y_key, vars))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cross.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cross.R new file mode 100644 index 000000000..733e4f0e2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-cross.R @@ -0,0 +1,64 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("cross join works", { + df1 <- tibble(x = 1:2) + df2 <- tibble(y = 1:3) + + expect_identical( + duckplyr_cross_join(df1, df2), + tibble( + x = vec_rep_each(1:2, times = 3), + y = vec_rep(1:3, times = 2) + ) + ) +}) + +test_that("cross join results in 0 rows if either input has 0 rows", { + df1 <- tibble(x = 1:2) + df2 <- tibble(y = integer()) + + expect_identical( + duckplyr_cross_join(df1, df2), + tibble(x = integer(), y = integer()) + ) + expect_identical( + duckplyr_cross_join(df2, df1), + tibble(y = integer(), x = integer()) + ) +}) + +test_that("cross join works with 0 column, >0 row tibbles", { + df1 <- new_tibble(list(), nrow = 3) + df2 <- tibble(x = 1:2) + + expect_identical( + duckplyr_cross_join(df1, df1), + new_tibble(list(), nrow = 9) + ) + expect_identical( + duckplyr_cross_join(df1, df2), + vec_rep(df2, times = 3) + ) +}) + +test_that("cross join applies `suffix`", { + df1 <- tibble(x = 1, y = 2) + df2 <- tibble(x = 2, z = 3) + + expect_named(duckplyr_cross_join(df1, df2), c("x.x", "y", "x.y", "z")) + expect_named(duckplyr_cross_join(df1, df2, suffix = c("", "_y")), c("x", "y", "x_y", "z")) +}) + +test_that("cross join checks for duplicate names", { + df1 <- tibble(a = 1, b = 2, a = 3, .name_repair = "minimal") + df2 <- tibble(a = 2, c = 3) + + expect_snapshot(error = TRUE, { + duckplyr_cross_join(df1, df2) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-rows.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-rows.R new file mode 100644 index 000000000..3cd8ff956 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join-rows.R @@ -0,0 +1,476 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("`relationship` default behavior is correct", { + # "warn-many-to-many" for equality joins + expect_snapshot(out <- join_rows(c(1, 1), c(1, 1), condition = "==")) + expect_equal(out$x, c(1L, 1L, 2L, 2L)) + expect_equal(out$y, c(1L, 2L, 1L, 2L)) + + # "none" for rolling joins + expect_warning(out <- join_rows(c(1, 2), c(1, 1), condition = ">=", filter = "max"), NA) + expect_equal(out$x, c(1L, 1L, 2L, 2L)) + expect_equal(out$y, c(1L, 2L, 1L, 2L)) + # If rolling joins warned on many-to-many relationships, it would be a little + # hard to explain that the above example warns, but this wouldn't just because + # we've removed `2` as a key from `x`: + # `join_rows(1, c(1, 1), condition = ">=", filter = "max")` + + # "none" for inequality joins (and overlap joins) + expect_warning(out <- join_rows(c(1, 2), c(0, 1), condition = ">="), NA) + expect_equal(out$x, c(1L, 1L, 2L, 2L)) + expect_equal(out$y, c(1L, 2L, 1L, 2L)) + + # "none" for deprecated cross joins + expect_warning(out <- join_rows(c(1, 1), c(1, 1), cross = TRUE), NA) + expect_equal(out$x, c(1L, 1L, 2L, 2L)) + expect_equal(out$y, c(1L, 2L, 1L, 2L)) +}) + +test_that("`multiple` first/last/any works correctly", { + out <- join_rows(c(1, 1), c(1, 1), multiple = "first") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y, c(1L, 1L)) + + out <- join_rows(c(1, 1), c(1, 1), multiple = "last") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y, c(2L, 2L)) + + out <- join_rows(c(1, 1), c(1, 1), multiple = "any") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y %in% c(1L, 2L), c(TRUE, TRUE)) +}) + +test_that("inner join only outputs matching keys", { + out <- join_rows(c(2, 1), c(3, 4, 1), type = "inner") + expect_equal(out$x, 2L) + expect_equal(out$y, 3L) + + out <- join_rows(c(2, 1), c(3, 4, 1), type = "inner", condition = ">") + expect_equal(out$x, 1L) + expect_equal(out$y, 3L) +}) + +test_that("left join contains all keys from x", { + out <- join_rows(c(2, 1), c(3, 4, 1), type = "left") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y, c(NA, 3L)) + + out <- join_rows(c(2, 1), c(3, 4, 1), type = "left", condition = ">") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y, c(3L, NA)) +}) + +test_that("right join contains all keys from y", { + out <- join_rows(c(2, 1), c(3, 4, 1), type = "right") + expect_equal(out$x, c(2L, NA, NA)) + expect_equal(out$y, c(3L, 1L, 2L)) + + out <- join_rows(c(2, 1), c(3, 4, 1), type = "right", condition = ">=") + expect_equal(out$x, c(1L, 2L, NA, NA)) + expect_equal(out$y, c(3L, 3L, 1L, 2L)) +}) + +test_that("full join contains all keys from both", { + out <- join_rows(c(2, 1), c(3, 1), type = "full") + expect_equal(out$x, c(1L, 2L, NA)) + expect_equal(out$y, c(NA, 2L, 1L)) + + out <- join_rows(c(2, 1), c(3, 1), type = "full", condition = ">") + expect_equal(out$x, c(1L, 2L, NA)) + expect_equal(out$y, c(2L, NA, 1L)) +}) + +test_that("nest join returns 0L for unmatched x keys", { + out <- join_rows(c(2, 1), c(3, 4, 1), type = "nest") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y, c(0L, 3L)) +}) + +test_that("nest join returns 0L for missing x keys with `na_matches = 'never'`", { + out <- join_rows(c(NA, 1), 1, type = "nest", na_matches = "never") + expect_equal(out$x, c(1L, 2L)) + expect_equal(out$y, c(0L, 1L)) +}) + +test_that("matching rows can be filtered", { + out <- join_rows(c(3, 5), c(2, 4, 1), condition = ">=", filter = "max") + expect_equal(out$x, 1:2) + expect_equal(out$y, 1:2) + + out <- join_rows(c(3, 5), c(2, 4, 1), condition = ">=", filter = "min") + expect_equal(out$x, 1:2) + expect_equal(out$y, c(3, 3)) +}) + +test_that("missing values only match with `==`, `>=`, and `<=` conditions", { + out <- join_rows(NA, NA, condition = "==") + expect_identical(out$x, 1L) + expect_identical(out$y, 1L) + + out <- join_rows(NA, NA, condition = ">=") + expect_identical(out$x, 1L) + expect_identical(out$y, 1L) + + out <- join_rows(NA, NA, condition = "<=") + expect_identical(out$x, 1L) + expect_identical(out$y, 1L) + + out <- join_rows(NA, NA, condition = ">") + expect_identical(out$x, integer()) + expect_identical(out$y, integer()) + + out <- join_rows(NA, NA, condition = "<") + expect_identical(out$x, integer()) + expect_identical(out$y, integer()) + + + x <- tibble(x = c(1, 1), y = c(2, NA)) + y <- tibble(x = c(1, 1), y = c(3, NA)) + + out <- join_rows(x, y, condition = c("==", "<=")) + expect_identical(out$x, c(1L, 2L)) + expect_identical(out$y, c(1L, 2L)) + + out <- join_rows(x, y, condition = c("==", "<")) + expect_identical(out$x, 1L) + expect_identical(out$y, 1L) +}) + +test_that("join_rows() doesn't error on unmatched rows if they won't be dropped", { + # 2 is unmatched, but a left join means we always retain that key + out <- join_rows(c(1, 2), 1, type = "left", unmatched = "error") + expect_identical(out$x, c(1L, 2L)) + expect_identical(out$y, c(1L, NA)) + + out <- join_rows(c(1, 2), c(1, 3), type = "full", unmatched = "error") + expect_identical(out$x, c(1L, 2L, NA)) + expect_identical(out$y, c(1L, NA, 2L)) +}) + +test_that("join_rows() allows `unmatched` to be specified independently for inner joins", { + out <- join_rows(c(1, 2), 1, type = "inner", unmatched = c("drop", "error")) + expect_identical(out$x, 1L) + expect_identical(out$y, 1L) + + out <- join_rows(1, c(2, 1), type = "inner", unmatched = c("error", "drop")) + expect_identical(out$x, 1L) + expect_identical(out$y, 2L) + + # Both have dropped rows, only `y` is mentioned in the error + expect_snapshot(error = TRUE, { + join_rows(c(1, 3), c(1, 2), type = "inner", unmatched = c("drop", "error")) + }) +}) + +test_that("join_rows() expects incompatible type errors to have been handled by join_cast_common()", { + expect_snapshot({ + (expect_error( + join_rows(data.frame(x = 1), data.frame(x = factor("a"))) + )) + }) +}) + +test_that("join_rows() gives meaningful one-to-one errors", { + expect_snapshot(error = TRUE, { + join_rows(1, c(1, 1), relationship = "one-to-one") + }) + expect_snapshot(error = TRUE, { + join_rows(c(1, 1), 1, relationship = "one-to-one") + }) +}) + +test_that("join_rows() gives meaningful one-to-many errors", { + expect_snapshot(error = TRUE, { + join_rows(c(1, 1), 1, relationship = "one-to-many") + }) +}) + +test_that("join_rows() gives meaningful many-to-one errors", { + expect_snapshot(error = TRUE, { + join_rows(1, c(1, 1), relationship = "many-to-one") + }) +}) + +test_that("join_rows() gives meaningful many-to-many warnings", { + skip("TODO duckdb") + expect_snapshot({ + join_rows(c(1, 1), c(1, 1)) + }) + + # With proof that the defaults flow through user facing functions + df <- data.frame(x = c(1, 1)) + expect_snapshot({ + duckplyr_left_join(df, df, by = join_by(x)) + }) +}) + +test_that("join_rows() gives meaningful error message on unmatched rows", { + # Unmatched in the RHS + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, 2)), + data.frame(x = c(3, 1)), + type = "left", + unmatched = "error" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, 2)), + data.frame(x = c(3, 1)), + type = "nest", + unmatched = "error" + ) + ) + + # Unmatched in the LHS + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, 2)), + data.frame(x = c(3, 1)), + type = "right", + unmatched = "error" + ) + ) + + # Unmatched in either side + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, 2)), + data.frame(x = 1), + type = "inner", + unmatched = "error" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, 2)), + data.frame(x = 1), + type = "inner", + unmatched = c("error", "drop") + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = 1), + data.frame(x = c(1, 2)), + type = "inner", + unmatched = "error" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = 1), + data.frame(x = c(1, 2)), + type = "inner", + unmatched = c("drop", "error") + ) + ) +}) + +test_that("join_rows() always errors on unmatched missing values", { + # Unmatched in the RHS + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = 1), + data.frame(x = NA), + type = "left", + unmatched = "error", + na_matches = "na" + ) + ) + expect_snapshot( + error = TRUE, + join_rows( + data.frame(x = NA), + data.frame(x = NA), + type = "left", + unmatched = "error", + na_matches = "never" + ) + ) + expect_snapshot( + error = TRUE, + join_rows( + data.frame(x = 1), + data.frame(x = NA), + type = "nest", + unmatched = "error", + na_matches = "na" + ) + ) + expect_snapshot( + error = TRUE, + join_rows( + data.frame(x = NA), + data.frame(x = NA), + type = "nest", + unmatched = "error", + na_matches = "never" + ) + ) + + # Unmatched in the LHS + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = NA), + data.frame(x = 1), + type = "right", + unmatched = "error", + na_matches = "na" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = NA), + data.frame(x = NA), + type = "right", + unmatched = "error", + na_matches = "never" + ) + ) + + # Unmatched in either side + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = 1), + data.frame(x = c(1, NA)), + type = "inner", + unmatched = "error", + na_matches = "na" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = 1), + data.frame(x = c(1, NA)), + type = "inner", + unmatched = c("drop", "error"), + na_matches = "na" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, NA)), + data.frame(x = 1), + type = "inner", + unmatched = "error", + na_matches = "na" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = c(1, NA)), + data.frame(x = 1), + type = "inner", + unmatched = c("error", "drop"), + na_matches = "na" + ) + ) + expect_snapshot(error = TRUE, + join_rows( + data.frame(x = NA), + data.frame(x = NA), + type = "inner", + unmatched = "error", + na_matches = "never" + ) + ) +}) + +test_that("join_rows() validates `unmatched`", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + join_rows(df, df, unmatched = 1) + join_rows(df, df, unmatched = "foo") + + # One `unmatched` input is allowed for most joins + join_rows(df, df, type = "left", unmatched = character()) + join_rows(df, df, type = "left", unmatched = c("drop", "error")) + + # Two `unmatched` inputs are allowed for inner joins + join_rows(df, df, type = "inner", unmatched = character()) + join_rows(df, df, type = "inner", unmatched = c("drop", "error", "error")) + + join_rows(df, df, type = "inner", unmatched = c("drop", "dr")) + }) +}) + +test_that("join_rows() validates `relationship`", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + join_rows(df, df, relationship = 1) + }) + + # Notably can't use the vctrs options + expect_snapshot(error = TRUE, { + join_rows(df, df, relationship = "none") + }) + expect_snapshot(error = TRUE, { + join_rows(df, df, relationship = "warn-many-to-many") + }) +}) + +test_that("join_rows() rethrows overflow error nicely (#6912)", { + skip_on_cran() + # Windows 32-bit doesn't support long vectors of this size, and the + # intermediate `r_ssize` will be too large + skip_if(.Machine$sizeof.pointer < 8L, message = "No long vector support") + + df <- tibble(x = 1:1e7) + + expect_snapshot(error = TRUE, { + join_rows(df, df, condition = ">=") + }) +}) + +# Deprecated behavior ---------------------------------------------------------- + +test_that("`multiple = NULL` is deprecated and results in `'all'` (#6731)", { + df1 <- tibble(x = c(1, 2)) + df2 <- tibble(x = c(2, 1, 2)) + + expect_snapshot({ + out <- join_rows(df1, df2, multiple = NULL) + }) + expect_identical(out$x, c(1L, 2L, 2L)) + expect_identical(out$y, c(2L, 1L, 3L)) + + expect_snapshot({ + duckplyr_left_join(df1, df2, by = join_by(x), multiple = NULL) + }) +}) + +test_that("`multiple = 'error'` is deprecated (#6731)", { + df1 <- tibble(x = c(1, 2)) + df2 <- tibble(x = c(2, 1, 2)) + + expect_snapshot(error = TRUE, { + join_rows(df1, df2, multiple = "error") + }) + expect_snapshot(error = TRUE, { + duckplyr_left_join(df1, df2, by = join_by(x), multiple = "error") + }) +}) + +test_that("`multiple = 'warning'` is deprecated (#6731)", { + df1 <- tibble(x = c(1, 2)) + df2 <- tibble(x = c(2, 1, 2)) + + expect_snapshot({ + out <- join_rows(df1, df2, multiple = "warning") + }) + expect_identical(out$x, c(1L, 2L, 2L)) + expect_identical(out$y, c(2L, 1L, 3L)) + + expect_snapshot({ + duckplyr_left_join(df1, df2, by = join_by(x), multiple = "warning") + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join.R new file mode 100644 index 000000000..60b8355ef --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-join.R @@ -0,0 +1,773 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# Basic properties -------------------------------------------------------- + +test_that("mutating joins preserve row and column order of x", { + df1 <- data.frame(a = 1:3) + df2 <- data.frame(b = 1, c = 2, a = 4:1) + + out <- duckplyr_inner_join(df1, df2, by = "a") + expect_named(out, c("a", "b", "c")) + expect_equal(out$a, 1:3) + + out <- duckplyr_left_join(df1, df2, by = "a") + expect_named(out, c("a", "b", "c")) + expect_equal(out$a, 1:3) + + out <- duckplyr_right_join(df1, df2, by = "a") + expect_named(out, c("a", "b", "c")) + expect_equal(out$a, 1:4) + + out <- duckplyr_full_join(df1, df2, by = "a") + expect_named(out, c("a", "b", "c")) + expect_equal(out$a, 1:4) +}) + +test_that("even when column names change", { + df1 <- data.frame(x = c(1, 1, 2, 3), z = 1:4, a = 1) + df2 <- data.frame(z = 1:3, b = 1, x = c(1, 2, 4)) + + out <- duckplyr_inner_join(df1, df2, by = "x") + expect_named(out, c("x", "z.x", "a", "z.y", "b")) +}) + +test_that("filtering joins preserve row and column order of x (#2964)", { + df1 <- data.frame(a = 4:1, b = 1) + df2 <- data.frame(b = 1, c = 2, a = 2:3) + + out <- duckplyr_semi_join(df1, df2, by = "a") + expect_named(out, c("a", "b")) + expect_equal(out$a, 3:2) + + out <- duckplyr_anti_join(df1, df2, by = "a") + expect_named(out, c("a", "b")) + expect_equal(out$a, c(4L, 1L)) +}) + +test_that("keys are coerced to symmetric type", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + foo <- tibble(id = 1:2, var1 = "foo") + bar <- tibble(id = as.numeric(1:2), var2 = "bar") + expect_type(duckplyr_inner_join(foo, bar, by = "id")$id, "double") + expect_type(duckplyr_inner_join(bar, foo, by = "id")$id, "double") + + foo <- tibble(id = factor(c("a", "b")), var1 = "foo") + bar <- tibble(id = c("a", "b"), var2 = "bar") + expect_type(duckplyr_inner_join(foo, bar, by = "id")$id, "character") + expect_type(duckplyr_inner_join(bar, foo, by = "id")$id, "character") +}) + +test_that("factor keys are coerced to the union factor type", { + df1 <- tibble(x = 1, y = factor("a")) + df2 <- tibble(x = 2, y = factor("b")) + out <- duckplyr_full_join(df1, df2, by = c("x", "y")) + expect_equal(out$y, factor(c("a", "b"))) +}) + +test_that("keys of non-equi conditions are not coerced if `keep = NULL`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + foo <- tibble(id = factor(c("a", "b")), col1 = c(1, 2), var1 = "foo") + bar <- tibble(id = c("a", "b"), col2 = c(1L, 2L), var2 = "bar") + + out <- duckplyr_inner_join(foo, bar, by = join_by(id, col1 >= col2)) + expect_type(out$id, "character") + expect_type(out$col1, "double") + expect_type(out$col2, "integer") + + out <- duckplyr_inner_join(bar, foo, by = join_by(id, col2 <= col1)) + expect_type(out$id, "character") + expect_type(out$col1, "double") + expect_type(out$col2, "integer") +}) + +test_that("when keep = TRUE, duckplyr_left_join() preserves both sets of keys", { + # when keys have different names + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(x = c(3, 4), y = c(3, 4)) + out <- duckplyr_left_join(df1, df2, by = c("a" = "x"), keep = TRUE) + expect_equal(out$a, c(2, 3)) + expect_equal(out$x, c(NA, 3)) + + # when keys have same name + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(a = c(3, 4), y = c(3, 4)) + out <- duckplyr_left_join(df1, df2, by = c("a"), keep = TRUE) + expect_equal(out$a.x, c(2, 3)) + expect_equal(out$a.y, c(NA, 3)) +}) + +test_that("when keep = TRUE, duckplyr_right_join() preserves both sets of keys", { + # when keys have different names + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(x = c(3, 4), y = c(3, 4)) + out <- duckplyr_right_join(df1, df2, by = c("a" = "x"), keep = TRUE) + expect_equal(out$a, c(3, NA)) + expect_equal(out$x, c(3, 4)) + + # when keys have same name + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(a = c(3, 4), y = c(3, 4)) + out <- duckplyr_right_join(df1, df2, by = c("a"), keep = TRUE) + expect_equal(out$a.x, c(3, NA)) + expect_equal(out$a.y, c(3, 4)) +}) + +test_that("when keep = TRUE, duckplyr_full_join() preserves both sets of keys", { + # when keys have different names + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(x = c(3, 4), y = c(3, 4)) + out <- duckplyr_full_join(df1, df2, by = c("a" = "x"), keep = TRUE) + expect_equal(out$a, c(2, 3, NA)) + expect_equal(out$x, c(NA, 3, 4)) + + # when keys have same name + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(a = c(3, 4), y = c(3, 4)) + out <- duckplyr_full_join(df1, df2, by = c("a"), keep = TRUE) + expect_equal(out$a.x, c(2, 3, NA)) + expect_equal(out$a.y, c(NA, 3, 4)) +}) + +test_that("when keep = TRUE, duckplyr_inner_join() preserves both sets of keys (#5581)", { + # when keys have different names + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(x = c(3, 4), y = c(3, 4)) + out <- duckplyr_inner_join(df1, df2, by = c("a" = "x"), keep = TRUE) + expect_equal(out$a, c(3)) + expect_equal(out$x, c(3)) + + # when keys have same name + df1 <- tibble(a = c(2, 3), b = c(1, 2)) + df2 <- tibble(a = c(3, 4), y = c(3, 4)) + out <- duckplyr_inner_join(df1, df2, by = c("a"), keep = TRUE) + expect_equal(out$a.x, c(3)) + expect_equal(out$a.y, c(3)) +}) + +test_that("can't use `keep = FALSE` with non-equi conditions (#6499)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(xl = c(1, 3), xu = c(4, 7)) + df2 <- tibble(yl = c(2, 5, 8), yu = c(6, 8, 9)) + + expect_snapshot(error = TRUE, { + duckplyr_left_join(df1, df2, join_by(overlaps(xl, xu, yl, yu)), keep = FALSE) + }) + + # Would never make sense here. + # Based on how the binary conditions are generated we'd merge: + # - `yu` into `xl` + # - `yl` into `xu` + # Which results in `xl` and `xu` columns that don't maintain `xl <= xu`. + expect_snapshot(error = TRUE, { + duckplyr_full_join(df1, df2, join_by(overlaps(xl, xu, yl, yu)), keep = FALSE) + }) +}) + +test_that("joins matches NAs by default (#892, #2033)", { + df1 <- tibble(x = c(NA_character_, 1)) + df2 <- tibble(x = c(NA_character_, 2)) + + expect_equal(nrow(duckplyr_inner_join(df1, df2, by = "x")), 1) + expect_equal(nrow(duckplyr_semi_join(df1, df2, by = "x")), 1) +}) + +test_that("joins don't match NA when na_matches = 'never' (#2033)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(a = c(1, NA)) + df2 <- tibble(a = c(1, NA), b = 1:2) + + out <- duckplyr_left_join(df1, df2, by = "a", na_matches = "never") + expect_equal(out, tibble(a = c(1, NA), b = c(1, NA))) + + out <- duckplyr_inner_join(df1, df2, by = "a", na_matches = "never") + expect_equal(out, tibble(a = 1, b = 1)) + + out <- duckplyr_semi_join(df1, df2, by = "a", na_matches = "never") + expect_equal(out, tibble(a = 1)) + + out <- duckplyr_anti_join(df1, df2, by = "a", na_matches = "never") + expect_equal(out, tibble(a = NA_integer_)) + + out <- duckplyr_nest_join(df1, df2, by = "a", na_matches = "never") + expect <- tibble(a = c(1, NA), df2 = list(tibble(b = 1L), tibble(b = integer()))) + expect_equal(out, expect) + + dat1 <- tibble( + name = c("a", "c"), + var1 = c(1, 2) + ) + dat3 <- tibble( + name = c("a", NA_character_), + var3 = c(5, 6) + ) + expect_equal( + duckplyr_full_join(dat1, dat3, by = "name", na_matches = "never"), + tibble(name = c("a", "c", NA), var1 = c(1, 2, NA), var3 = c(5, NA, 6)) + ) +}) + +test_that("`duckplyr_left_join(by = join_by(closest(...)))` works as expected", { + df1 <- tibble(x = 1:5) + df2 <- tibble(y = c(1, 2, 4)) + + out <- duckplyr_left_join(df1, df2, by = join_by(closest(x <= y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(1, 2, 4, 4, NA)) + + out <- duckplyr_left_join(df1, df2, by = join_by(closest(x < y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(2, 4, 4, NA, NA)) + + out <- duckplyr_left_join(df1, df2, by = join_by(closest(x >= y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(1, 2, 2, 4, 4)) + + out <- duckplyr_left_join(df1, df2, by = join_by(closest(x > y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(NA, 1, 2, 2, 4)) +}) + +test_that("`duckplyr_full_join(by = join_by(closest(...)))` works as expected", { + df1 <- tibble(x = 1:5) + df2 <- tibble(y = c(1, 2, 4)) + + out <- duckplyr_full_join(df1, df2, by = join_by(closest(x <= y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(1, 2, 4, 4, NA)) + + out <- duckplyr_full_join(df1, df2, by = join_by(closest(x < y))) + expect_identical(out$x, c(1:5, NA)) + expect_identical(out$y, c(2, 4, 4, NA, NA, 1)) + + out <- duckplyr_full_join(df1, df2, by = join_by(closest(x >= y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(1, 2, 2, 4, 4)) + + out <- duckplyr_full_join(df1, df2, by = join_by(closest(x > y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(NA, 1, 2, 2, 4)) +}) + +test_that("`duckplyr_right_join(by = join_by(closest(...)))` works as expected", { + df1 <- tibble(x = 1:5) + df2 <- tibble(y = c(1, 2, 4)) + + out <- duckplyr_right_join(df1, df2, by = join_by(closest(x <= y))) + expect_identical(out$x, 1:4) + expect_identical(out$y, c(1, 2, 4, 4)) + + out <- duckplyr_right_join(df1, df2, by = join_by(closest(x < y))) + expect_identical(out$x, c(1:3, NA)) + expect_identical(out$y, c(2, 4, 4, 1)) + + out <- duckplyr_right_join(df1, df2, by = join_by(closest(x >= y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(1, 2, 2, 4, 4)) + + out <- duckplyr_right_join(df1, df2, by = join_by(closest(x > y))) + expect_identical(out$x, 2:5) + expect_identical(out$y, c(1, 2, 2, 4)) +}) + +test_that("`duckplyr_inner_join(by = join_by(closest(...)))` works as expected", { + df1 <- tibble(x = 1:5) + df2 <- tibble(y = c(1, 2, 4)) + + out <- duckplyr_inner_join(df1, df2, by = join_by(closest(x <= y))) + expect_identical(out$x, 1:4) + expect_identical(out$y, c(1, 2, 4, 4)) + + out <- duckplyr_inner_join(df1, df2, by = join_by(closest(x < y))) + expect_identical(out$x, 1:3) + expect_identical(out$y, c(2, 4, 4)) + + out <- duckplyr_inner_join(df1, df2, by = join_by(closest(x >= y))) + expect_identical(out$x, 1:5) + expect_identical(out$y, c(1, 2, 2, 4, 4)) + + out <- duckplyr_inner_join(df1, df2, by = join_by(closest(x > y))) + expect_identical(out$x, 2:5) + expect_identical(out$y, c(1, 2, 2, 4)) +}) + +test_that("joins using `between(bounds =)` work as expected (#6488)", { + df1 <- tibble(x = 1:5) + df2 <- tibble(lower = 2, upper = 4) + + out <- duckplyr_full_join(df1, df2, by = join_by(between(x, lower, upper, bounds = "[]"))) + expect_identical(out$lower, c(NA, 2, 2, 2, NA)) + expect_identical(out$upper, c(NA, 4, 4, 4, NA)) + + out <- duckplyr_full_join(df1, df2, by = join_by(between(x, lower, upper, bounds = "[)"))) + expect_identical(out$lower, c(NA, 2, 2, NA, NA)) + expect_identical(out$upper, c(NA, 4, 4, NA, NA)) + + out <- duckplyr_full_join(df1, df2, by = join_by(between(x, lower, upper, bounds = "(]"))) + expect_identical(out$lower, c(NA, NA, 2, 2, NA)) + expect_identical(out$upper, c(NA, NA, 4, 4, NA)) + + out <- duckplyr_full_join(df1, df2, by = join_by(between(x, lower, upper, bounds = "()"))) + expect_identical(out$lower, c(NA, NA, 2, NA, NA)) + expect_identical(out$upper, c(NA, NA, 4, NA, NA)) +}) + +test_that("joins using `overlaps(bounds =)` work as expected (#6488)", { + df1 <- tibble(x_lower = c(1, 1, 3, 4), x_upper = c(2, 3, 4, 5)) + df2 <- tibble(y_lower = 2, y_upper = 4) + + expect_closed <- vec_cbind(df1, vec_c(df2, df2, df2, df2)) + + out <- duckplyr_full_join(df1, df2, by = join_by(overlaps(x_lower, x_upper, y_lower, y_upper, bounds = "[]"))) + expect_identical(out, expect_closed) + + # `[)`, `(]`, and `()` all generate the same binary conditions but are useful + # for consistency with `between(bounds =)` + expect_open <- vec_cbind(df1, vec_c(NA, df2, df2, NA)) + + out <- duckplyr_full_join(df1, df2, by = join_by(overlaps(x_lower, x_upper, y_lower, y_upper, bounds = "[)"))) + expect_identical(out, expect_open) + out <- duckplyr_full_join(df1, df2, by = join_by(overlaps(x_lower, x_upper, y_lower, y_upper, bounds = "(]"))) + expect_identical(out, expect_open) + out <- duckplyr_full_join(df1, df2, by = join_by(overlaps(x_lower, x_upper, y_lower, y_upper, bounds = "()"))) + expect_identical(out, expect_open) +}) + +test_that("join_mutate() validates arguments", { + df <- tibble(x = 1) + + # Mutating joins + expect_snapshot(error = TRUE, { + join_mutate(df, df, by = 1, type = "left") + join_mutate(df, df, by = "x", type = "left", suffix = 1) + join_mutate(df, df, by = "x", type = "left", na_matches = "foo") + join_mutate(df, df, by = "x", type = "left", keep = 1) + }) +}) + +test_that("join_filter() validates arguments", { + df <- tibble(x = 1) + + # Filtering joins + expect_snapshot(error = TRUE, { + join_filter(df, df, by = 1, type = "semi") + join_filter(df, df, by = "x", type = "semi", na_matches = "foo") + }) +}) + +test_that("mutating joins trigger many-to-many warning", { + skip("TODO duckdb") + df <- tibble(x = c(1, 1)) + expect_snapshot(out <- duckplyr_left_join(df, df, join_by(x))) +}) + +test_that("mutating joins don't trigger many-to-many warning when called indirectly", { + skip("TODO duckdb") + df <- tibble(x = c(1, 1)) + + fn <- function(df1, df2, relationship = NULL) { + duckplyr_left_join(df1, df2, join_by(x), relationship = relationship) + } + + # Directly calling `duckplyr_left_join()` from a function you control results in a warning + expect_warning(fn(df, df), class = "dplyr_warning_join_relationship_many_to_many") + + # Now mimic calling an "rlang function" which you don't control that calls `duckplyr_left_join()` + fn_env(fn) <- ns_env("rlang") + + # Indirectly calling `duckplyr_left_join()` through a function you don't control + # doesn't warn + expect_no_warning(fn(df, df), class = "dplyr_warning_join_relationship_many_to_many") +}) + +test_that("mutating joins compute common columns", { + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 3), z = c(2, 3)) + expect_snapshot(out <- duckplyr_left_join(df1, df2)) +}) + +test_that("filtering joins compute common columns", { + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 3), z = c(2, 3)) + expect_snapshot(out <- duckplyr_semi_join(df1, df2)) +}) + +test_that("mutating joins finalize unspecified columns (#6804)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = NA) + df2 <- tibble(x = NA) + + expect_identical( + duckplyr_inner_join(df1, df2, by = join_by(x)), + tibble(x = NA) + ) + expect_identical( + duckplyr_inner_join(df1, df2, by = join_by(x), na_matches = "never"), + tibble(x = logical()) + ) + + # Pre-existing `unspecified()` vectors get finalized, because they are + # considered internal types and we took a "common type" between the keys + df1 <- tibble(x = unspecified()) + df2 <- tibble(x = unspecified()) + + expect_identical( + duckplyr_inner_join(df1, df2, by = join_by(x)), + tibble(x = logical()) + ) +}) + +test_that("filtering joins finalize unspecified columns (#6804)", { + df1 <- tibble(x = NA) + df2 <- tibble(x = NA) + + expect_identical( + duckplyr_semi_join(df1, df2, by = join_by(x)), + tibble(x = NA) + ) + expect_identical( + duckplyr_semi_join(df1, df2, by = join_by(x), na_matches = "never"), + tibble(x = logical()) + ) + + # Pre-existing `unspecified()` vectors aren't finalized, + # because we don't take the common type of the keys. + # We retain the exact type of `x`. + df1 <- tibble(x = unspecified()) + df2 <- tibble(x = NA) + + expect_identical( + duckplyr_semi_join(df1, df2, by = join_by(x)), + tibble(x = unspecified()) + ) +}) + +test_that("mutating joins reference original column in `y` when there are type errors (#6465)", { + x <- tibble(a = 1) + y <- tibble(b = "1") + + expect_snapshot({ + (expect_error(duckplyr_left_join(x, y, by = join_by(a == b)))) + }) +}) + +test_that("filtering joins reference original column in `y` when there are type errors (#6465)", { + x <- tibble(a = 1) + y <- tibble(b = "1") + + expect_snapshot({ + (expect_error(duckplyr_semi_join(x, y, by = join_by(a == b)))) + }) +}) + +test_that("error if passed additional arguments", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- data.frame(a = 1:3) + df2 <- data.frame(a = 1) + + expect_snapshot(error = TRUE, { + duckplyr_inner_join(df1, df2, on = "a") + duckplyr_left_join(df1, df2, on = "a") + duckplyr_right_join(df1, df2, on = "a") + duckplyr_full_join(df1, df2, on = "a") + duckplyr_nest_join(df1, df2, on = "a") + duckplyr_anti_join(df1, df2, on = "a") + duckplyr_semi_join(df1, df2, on = "a") + }) +}) + +# nest_join --------------------------------------------------------------- + +test_that("nest_join returns list of tibbles (#3570)",{ + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 1), z = c(2, 3)) + out <- duckplyr_nest_join(df1, df2, by = "x") + + expect_named(out, c("x", "y", "df2")) + expect_type(out$df2, "list") + expect_s3_class(out$df2[[1]], "tbl_df") +}) + +test_that("nest_join respects types of y (#6295)",{ + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- duckplyr_rowwise(tibble(x = c(1, 1), z = c(2, 3))) + out <- duckplyr_nest_join(df1, df2, by = "x") + + expect_s3_class(out$df2[[1]], "rowwise_df") +}) + +test_that("nest_join preserves data frame attributes on `x` and `y` (#6295)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- data.frame(x = c(1, 2), y = c(3, 4)) + attr(df1, "foo") <- 1 + df2 <- data.frame(x = c(1, 2), z = c(3, 4)) + attr(df2, "foo") <- 2 + + out <- duckplyr_nest_join(df1, df2, by = "x") + expect_identical(attr(out, "foo"), 1) + expect_identical(attr(out$df2[[1]], "foo"), 2) +}) + +test_that("nest_join computes common columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 3), z = c(2, 3)) + expect_snapshot(out <- duckplyr_nest_join(df1, df2)) +}) + +test_that("nest_join finalizes unspecified columns (#6804)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = NA) + df2 <- tibble(x = NA) + + expect_identical( + duckplyr_nest_join(df1, df2, by = join_by(x)), + tibble(x = NA, df2 = list(tibble(.rows = 1L))) + ) + expect_identical( + duckplyr_nest_join(df1, df2, by = join_by(x), keep = TRUE), + tibble(x = NA, df2 = list(tibble(x = NA))) + ) + expect_identical( + duckplyr_nest_join(df1, df2, by = join_by(x), na_matches = "never"), + tibble(x = NA, df2 = list(tibble())) + ) + + # Pre-existing `unspecified()` vectors get finalized, because they are + # considered internal types and we took a "common type" between the keys + df1 <- tibble(x = unspecified()) + df2 <- tibble(x = unspecified()) + + expect_identical( + duckplyr_nest_join(df1, df2, by = join_by(x)), + tibble(x = logical(), df2 = list()) + ) +}) + +test_that("nest_join references original column in `y` when there are type errors (#6465)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + x <- tibble(a = 1) + y <- tibble(b = "1") + + expect_snapshot({ + (expect_error(duckplyr_nest_join(x, y, by = join_by(a == b)))) + }) +}) + +test_that("nest_join handles multiple matches in x (#3642)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = c(1, 1)) + df2 <- tibble(x = 1, y = 1:2) + + out <- duckplyr_nest_join(df1, df2, by = "x") + expect_equal(out$df2[[1]], out$df2[[2]]) +}) + +test_that("nest_join forces `multiple = 'all'` internally (#6392)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1) + df2 <- tibble(x = 1, y = 1:2) + + expect_no_warning(out <- duckplyr_nest_join(df1, df2, by = "x")) + expect_identical(nrow(out$df2[[1]]), 2L) +}) + +test_that("y keys dropped by default for equi conditions", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 3), z = c(2, 3)) + out <- duckplyr_nest_join(df1, df2, by = "x") + expect_named(out, c("x", "y", "df2")) + expect_named(out$df2[[1]], "z") + + out <- duckplyr_nest_join(df1, df2, by = "x", keep = TRUE) + expect_named(out$df2[[1]], c("x", "z")) +}) + +test_that("y keys kept by default for non-equi conditions", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 3), z = c(2, 3)) + + out <- duckplyr_nest_join(df1, df2, by = join_by(x >= x)) + expect_named(out, c("x", "y", "df2")) + expect_named(out$df2[[1]], c("x", "z")) +}) + +test_that("validates inputs", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = c(1, 2), y = c(2, 3)) + df2 <- tibble(x = c(1, 3), z = c(2, 3)) + + expect_snapshot(error = TRUE, { + duckplyr_nest_join(df1, df2, by = 1) + duckplyr_nest_join(df1, df2, keep = 1) + duckplyr_nest_join(df1, df2, name = 1) + duckplyr_nest_join(df1, df2, na_matches = 1) + }) + +}) + +# output type --------------------------------------------------------------- + +test_that("joins x preserve type of x", { + df1 <- data.frame(x = 1) + df2 <- tibble(x = 2) + + expect_s3_class(duckplyr_inner_join(df1, df2, by = "x"), "data.frame", exact = TRUE) + expect_s3_class(duckplyr_inner_join(df2, df1, by = "x"), "tbl_df") +}) + +test_that("joins preserve groups", { + gf1 <- tibble(a = 1:3) %>% duckplyr_group_by(a) + gf2 <- tibble(a = rep(1:4, 2), b = 1) %>% duckplyr_group_by(b) + + i <- count_regroups(out <- duckplyr_inner_join(gf1, gf2, by = "a")) + expect_equal(i, 1L) + expect_equal(duckplyr_group_vars(out), "a") + + i <- count_regroups(out <- duckplyr_semi_join(gf1, gf2, by = "a")) + expect_equal(i, 0L) + expect_equal(duckplyr_group_vars(out), "a") + + # once for x + once for each row for y + i <- count_regroups(out <- duckplyr_nest_join(gf1, gf2, by = "a")) + expect_equal(i, 4L) + expect_equal(duckplyr_group_vars(out), "a") + expect_equal(duckplyr_group_vars(out$gf2[[1]]), "b") +}) + +test_that("joins respect zero length groups", { + df1 <- tibble(f = factor( c(1,1,2,2), levels = 1:3), x = c(1,2,1,4)) %>% + duckplyr_group_by(f) + + df2 <- tibble(f = factor( c(2,2,3,3), levels = 1:3), y = c(1,2,3,4)) %>% + duckplyr_group_by(f) + + expect_equal(duckplyr_group_size(duckplyr_left_join( df1, df2, by = "f", relationship = "many-to-many")), c(2,4)) + expect_equal(duckplyr_group_size(duckplyr_right_join( df1, df2, by = "f", relationship = "many-to-many")), c(4,2)) + expect_equal(duckplyr_group_size(duckplyr_full_join( df1, df2, by = "f", relationship = "many-to-many")), c(2,4,2)) + expect_equal(duckplyr_group_size(duckplyr_anti_join( df1, df2, by = "f")), c(2)) + expect_equal(duckplyr_group_size(duckplyr_inner_join( df1, df2, by = "f", relationship = "many-to-many")), c(4)) + + + df1 <- tibble(f = factor( c(1,1,2,2), levels = 1:3), x = c(1,2,1,4)) %>% + duckplyr_group_by(f, .drop = FALSE) + df2 <- tibble(f = factor( c(2,2,3,3), levels = 1:3), y = c(1,2,3,4)) %>% + duckplyr_group_by(f, .drop = FALSE) + + expect_equal(duckplyr_group_size(duckplyr_left_join( df1, df2, by = "f", relationship = "many-to-many")), c(2,4,0)) + expect_equal(duckplyr_group_size(duckplyr_right_join( df1, df2, by = "f", relationship = "many-to-many")), c(0,4,2)) + expect_equal(duckplyr_group_size(duckplyr_full_join( df1, df2, by = "f", relationship = "many-to-many")), c(2,4,2)) + expect_equal(duckplyr_group_size(duckplyr_anti_join( df1, df2, by = "f")), c(2,0,0)) + expect_equal(duckplyr_group_size(duckplyr_inner_join( df1, df2, by = "f", relationship = "many-to-many")), c(0,4,0)) +}) + +test_that("group column names reflect renamed duplicate columns (#2330)", { + df1 <- tibble(x = 1:5, y = 1:5) %>% duckplyr_group_by(x, y) + df2 <- tibble(x = 1:5, y = 1:5) + + out <- duckplyr_inner_join(df1, df2, by = "x") + expect_equal(duckplyr_group_vars(out), "x") + # TODO: fix this issue: https://github.com/tidyverse/dplyr/issues/4917 + # expect_equal(duckplyr_group_vars(out), c("x", "y.x")) +}) + +test_that("rowwise group structure is updated after a join (#5227)", { + df1 <- duckplyr_rowwise(tibble(x = 1:2)) + df2 <- tibble(x = c(1:2, 2L)) + + x <- duckplyr_left_join(df1, df2, by = "x") + + expect_identical(group_rows(x), list_of(1L, 2L, 3L)) +}) + +# deprecated ---------------------------------------------------------------- + +test_that("by = character() generates cross (#4206)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "quiet") + + df1 <- tibble(x = 1:2) + df2 <- tibble(y = 1:2) + out <- duckplyr_left_join(df1, df2, by = character()) + + expect_equal(out$x, rep(1:2, each = 2)) + expect_equal(out$y, rep(1:2, 2)) +}) + +test_that("`by = character()` technically respects `unmatched`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "quiet") + + df1 <- tibble() + df2 <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_left_join(df1, df2, by = character(), unmatched = "error") + }) +}) + +test_that("`by = character()` technically respects `relationship`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "quiet") + + df <- tibble(x = 1:2) + + expect_snapshot(error = TRUE, { + duckplyr_left_join(df, df, by = character(), relationship = "many-to-one") + }) +}) + +test_that("`by = character()` for a cross join is deprecated (#6604)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1:2) + df2 <- tibble(y = 1:2) + + # Mutating join + expect_snapshot({ + out <- duckplyr_left_join(df1, df2, by = character()) + }) + + # Filtering join + expect_snapshot({ + out <- duckplyr_semi_join(df1, df2, by = character()) + }) + + # Nest join + expect_snapshot({ + out <- duckplyr_nest_join(df1, df2, by = character()) + }) +}) + +test_that("`by = named character()` for a cross join works", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # Used by the sift package + df1 <- tibble(x = 1:2) + df2 <- tibble(y = 1:2) + + by <- set_names(character(), nm = character()) + + expect_snapshot({ + out <- duckplyr_left_join(df1, df2, by = by) + }) + expect_identical( + out, + duckplyr_cross_join(df1, df2) + ) +}) + +test_that("`by = list(x = character(), y = character())` for a cross join is deprecated (#6604)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1:2) + df2 <- tibble(y = 1:2) + + expect_snapshot({ + out <- duckplyr_left_join(df1, df2, by = list(x = character(), y = character())) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-lead-lag.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-lead-lag.R new file mode 100644 index 000000000..d6c7be529 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-lead-lag.R @@ -0,0 +1,177 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("`lead()` / `lag()` get the direction right", { + expect_identical(lead(1:5), c(2:5, NA)) + expect_identical(lag(1:5), c(NA, 1:4)) +}) + +test_that("If n = 0, lead and lag return x", { + x <- c(10L, 8L, 1L, 3L, 6L, 9L, 4L, 2L, 5L, 7L) + expect_equal(lead(x, 0), x) + expect_equal(lag(x, 0), x) +}) + +test_that("If n = length(x), returns all missing", { + x <- c(10L, 8L, 1L, 3L, 6L, 9L, 4L, 2L, 5L, 7L) + + expect_equal(lead(x, length(x)), rep(NA_integer_, length(x))) + expect_equal(lag(x, length(x)), rep(NA_integer_, length(x))) +}) + +test_that("`lag()` gives informative error for objects", { + expect_snapshot(error = TRUE, { + lag(ts(1:10)) + }) +}) + +test_that("lead() and lag() work for matrices (#5028)", { + m <- matrix(1:6, ncol = 2) + expect_equal(lag(m, 1), matrix(c(NA_integer_, 1L, 2L, NA_integer_, 4L, 5L), ncol = 2)) + expect_equal(lag(m, 1, default = NA), matrix(c(NA_integer_, 1L, 2L, NA_integer_, 4L, 5L), ncol= 2)) + + expect_equal(lead(m, 1), matrix(c(2L, 3L, NA_integer_, 5L, 6L, NA_integer_), ncol = 2)) + expect_equal(lead(m, 1, default = NA), matrix(c(2L, 3L, NA_integer_, 5L, 6L, NA_integer_), ncol = 2)) +}) + +test_that("lead and lag preserve factors", { + x <- factor(c("a", "b", "c")) + + expect_equal(levels(lead(x)), c("a", "b", "c")) + expect_equal(levels(lag(x)), c("a", "b", "c")) +}) + +test_that("lead and lag preserves dates and times", { + x <- as.Date("2013-01-01") + 1:3 + y <- as.POSIXct(x) + + expect_s3_class(lead(x), "Date") + expect_s3_class(lag(x), "Date") + + expect_s3_class(lead(y), "POSIXct") + expect_s3_class(lag(y), "POSIXct") +}) + +test_that("`lead()` / `lag()` validate `n`", { + expect_snapshot(error = TRUE, { + lead(1:5, n = 1:2) + lead(1:5, -1) + }) + expect_snapshot(error = TRUE, { + lag(1:5, n = 1:2) + lag(1:5, -1) + }) +}) + +test_that("`lead()` / `lag()` check for empty dots", { + expect_snapshot(error = TRUE, { + lead(1:5, deault = 1) + }) + expect_snapshot(error = TRUE, { + lag(1:5, deault = 1) + }) +}) + +test_that("`lead()` / `lag()` require that `x` is a vector", { + expect_snapshot(error = TRUE, { + lead(environment()) + }) + expect_snapshot(error = TRUE, { + lag(environment()) + }) +}) + +# ------------------------------------------------------------------------------ +# shift() + +test_that("works with all 4 combinations of with/without `default` and lag/lead", { + x <- 1:5 + + expect_identical(shift(x, n = 2L), c(NA, NA, 1L, 2L, 3L)) + expect_identical(shift(x, n = 2L, default = 0L), c(0L, 0L, 1L, 2L, 3L)) + + expect_identical(shift(x, n = -2L), c(3L, 4L, 5L, NA, NA)) + expect_identical(shift(x, n = -2L, default = 0L), c(3L, 4L, 5L, 0L, 0L)) +}) + +test_that("works with size 0 input", { + x <- integer() + + expect_identical(shift(x, n = 2L), x) + expect_identical(shift(x, n = 2L, default = 3L), x) + expect_identical(shift(x, n = -2L), x) + expect_identical(shift(x, n = -2L, default = 3L), x) +}) + +test_that("works with `n = 0` with and without `default`", { + x <- 1:5 + + expect_identical(shift(x, n = 0L), x) + expect_identical(shift(x, n = 0L, default = -1L), x) + + x <- integer() + + expect_identical(shift(x, n = 0L), x) + expect_identical(shift(x, n = 0L, default = -1L), x) +}) + +test_that("works with data frames", { + df <- tibble(a = 1:3, b = letters[1:3]) + + expect_identical(shift(df, n = 1), vec_slice(df, c(NA, 1, 2))) + expect_identical(shift(df, n = -1), vec_slice(df, c(2, 3, NA))) + + default <- tibble(a = 0L, b = "") + + expect_identical( + shift(df, n = 2, default = default), + vec_c(default, default, vec_slice(df, 1)) + ) +}) + +test_that("is affected by `order_by`", { + x <- 1:5 + order_by <- c(2, 3, 2, 1, 5) + + expect_identical( + shift(x, n = 1, order_by = order_by), + c(4L, 3L, 1L, NA, 2L) + ) + expect_identical( + shift(x, n = -2, order_by = order_by), + c(2L, NA, 5L, 3L, NA) + ) +}) + +test_that("`default` is cast to the type of `x` (#6330)", { + expect_identical(shift(1L, default = 2), 2L) + + expect_snapshot(error = TRUE, { + shift(1L, default = 1.5) + }) +}) + +test_that("`default` must be size 1 (#5641)", { + expect_snapshot(error = TRUE, { + shift(1:5, default = 1:2) + }) + expect_snapshot(error = TRUE, { + shift(1:5, default = integer()) + }) +}) + +test_that("`n` is validated", { + expect_snapshot(error = TRUE, { + shift(1, n = 1:2) + }) +}) + +test_that("`order_by` must be the same size as `x`", { + expect_snapshot(error = TRUE, { + shift(1:5, order_by = 1:4) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-mutate.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-mutate.R new file mode 100644 index 000000000..0cc483cac --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-mutate.R @@ -0,0 +1,915 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("empty mutate returns input", { + df <- tibble(x = 1) + gf <- duckplyr_group_by(df, x) + + expect_equal(duckplyr_mutate(df), df) + expect_equal(duckplyr_mutate(df, .by = x), df) + expect_equal(duckplyr_mutate(gf), gf) + + expect_equal(duckplyr_mutate(df, !!!list()), df) + expect_equal(duckplyr_mutate(df, !!!list(), .by = x), df) + expect_equal(duckplyr_mutate(gf, !!!list()), gf) +}) + +test_that("rownames preserved", { + df <- data.frame(x = c(1, 2), row.names = c("a", "b")) + + df <- duckplyr_mutate(df, y = 2) + expect_equal(row.names(df), c("a", "b")) + + df <- duckplyr_mutate(df, y = 2, .by = x) + expect_equal(row.names(df), c("a", "b")) +}) + +test_that("mutations applied progressively", { + df <- tibble(x = 1) + expect_equal(df %>% duckplyr_mutate(y = x + 1, z = y + 1), tibble(x = 1, y = 2, z = 3)) + expect_equal(df %>% duckplyr_mutate(x = x + 1, x = x + 1), tibble(x = 3)) + expect_equal(df %>% duckplyr_mutate(x = 2, y = x), tibble(x = 2, y = 2)) + + df <- data.frame(x = 1, y = 2) + expect_equal( + df %>% duckplyr_mutate(x2 = x, x3 = x2 + 1), + df %>% duckplyr_mutate(x2 = x + 0, x3 = x2 + 1) + ) +}) + +test_that("length-1 vectors are recycled (#152)", { + df <- tibble(x = 1:4) + expect_equal(duckplyr_mutate(df, y = 1)$y, rep(1, 4)) + expect_error(duckplyr_mutate(df, y = 1:2)) +}) + +test_that("can remove variables with NULL (#462)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3, y = 1:3) + gf <- duckplyr_group_by(df, x) + + expect_equal(df %>% duckplyr_mutate(y = NULL), df[1]) + expect_equal(gf %>% duckplyr_mutate(y = NULL), gf[1]) + + # even if it doesn't exist + expect_equal(df %>% duckplyr_mutate(z = NULL), df) + # or was just created + expect_equal(df %>% duckplyr_mutate(z = 1, z = NULL), df) + + # regression test for https://github.com/tidyverse/dplyr/issues/4974 + expect_equal( + duckplyr_mutate(data.frame(x = 1, y = 1), z = 1, x = NULL, y = NULL), + data.frame(z = 1) + ) +}) + +test_that("duckplyr_mutate() names pronouns correctly (#2686)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_named(duckplyr_mutate(tibble(x = 1), .data$x), "x") + expect_named(duckplyr_mutate(tibble(x = 1), .data[["x"]]), "x") +}) + +test_that("duckplyr_mutate() supports unquoted values", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2, 2, 2), x = 1:5) + expect_identical(duckplyr_mutate(df, out = !!1), duckplyr_mutate(df, out = 1)) + expect_identical(duckplyr_mutate(df, out = !!(1:5)), duckplyr_mutate(df, out = 1:5)) + expect_identical(duckplyr_mutate(df, out = !!quote(1:5)), duckplyr_mutate(df, out = 1:5)) + + gdf <- duckplyr_group_by(df, g) + expect_identical(duckplyr_mutate(gdf, out = !!1), duckplyr_mutate(gdf, out = 1)) +}) + +test_that("assignments don't overwrite variables (#315)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + out <- df %>% duckplyr_mutate(z = {x <- 10; x}) + expect_equal(out, tibble(x = 1, y = 2, z = 10)) +}) + +test_that("can mutate a data frame with zero columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- new_data_frame(n = 2L) + expect_equal(duckplyr_mutate(df, x = 1), data.frame(x = c(1, 1))) +}) + +test_that("duckplyr_mutate() handles symbol expressions", { + df <- tibble(x = structure(1, class = "alien")) + res <- duckplyr_mutate(df, y = x) + expect_identical(df$x, res$y) + + gf <- duckplyr_group_by(df, x) + res <- duckplyr_mutate(df, y = x) + expect_identical(df$x, res$y) +}) + +test_that("duckplyr_mutate() supports constants (#6056, #6305)", { + df <- data.frame(x = 1:10, g = rep(1:2, each = 5)) + y <- 1:10 + z <- 1:5 + + expect_identical(df %>% duckplyr_mutate(y = !!y) %>% duckplyr_pull(y), y) + expect_identical(df %>% duckplyr_group_by(g) %>% duckplyr_mutate(y = !!y) %>% duckplyr_pull(y), y) + expect_identical(df %>% duckplyr_rowwise() %>% duckplyr_mutate(y = !!y) %>% duckplyr_pull(y), y) + + expect_snapshot({ + (expect_error(df %>% duckplyr_mutate(z = !!z))) + (expect_error(df %>% duckplyr_group_by(g) %>% duckplyr_mutate(z = !!z))) + (expect_error(df %>% duckplyr_rowwise() %>% duckplyr_mutate(z = !!z))) + }) + + # `.env$` is used for per group evaluation + expect_identical(df %>% duckplyr_mutate(y = .env$y) %>% duckplyr_pull(y), y) + expect_identical(df %>% duckplyr_group_by(g) %>% duckplyr_mutate(z = .env$z) %>% duckplyr_pull(z), c(z, z)) + + expect_snapshot({ + (expect_error(df %>% duckplyr_group_by(g) %>% duckplyr_mutate(y = .env$y))) + (expect_error(df %>% duckplyr_rowwise() %>% duckplyr_mutate(y = .env$y))) + }) +}) + +test_that("can't overwrite column active bindings (#6666)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if(getRversion() < "3.6.3", message = "Active binding error changed") + + df <- tibble(g = 1:2, x = 3:4) + gdf <- duckplyr_group_by(df, g) + + # The error seen here comes from trying to `<-` to an active binding when + # the active binding function has 0 arguments. + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, y = { + x <<- 2 + x + }) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, .by = g, y = { + x <<- 2 + x + }) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, y = { + x <<- 2 + x + }) + }) +}) + +test_that("assigning with `<-` doesn't affect the mask (#6666)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = 1:2, x = 3:4) + gdf <- duckplyr_group_by(df, g) + + out <- duckplyr_mutate(df, .by = g, y = { + x <- x + 2L + x + }) + expect_identical(out$x, c(3L, 4L)) + expect_identical(out$y, c(5L, 6L)) + + out <- duckplyr_mutate(gdf, y = { + x <- x + 2L + x + }) + expect_identical(out$x, c(3L, 4L)) + expect_identical(out$y, c(5L, 6L)) +}) + +test_that("`across()` inline expansions that use `<-` don't affect the mask (#6666)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = 1:2, x = 3:4) + + out <- df %>% + duckplyr_mutate( + across(x, function(col) { + col <- col + 2L + col + }), + .by = g + ) + + expect_identical(out$x, c(5L, 6L)) +}) + +test_that("can't share local variables across expressions (#6666)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2, y = 3:4) + + expect_snapshot(error = TRUE, { + duckplyr_mutate( + df, + x2 = { + foo <- x + x + }, + y2 = { + foo + } + ) + }) +}) + +# column types ------------------------------------------------------------ + +test_that("glue() is supported", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_equal( + tibble(x = 1) %>% duckplyr_mutate(y = glue("")), + tibble(x = 1, y = glue("")) + ) +}) + +test_that("mutate disambiguates NA and NaN (#1448)", { + df <- tibble(x = c(1, NA, NaN)) + out <- duckplyr_mutate(df, y = x * 1) + expect_equal(out$y, df$x) +}) + +test_that("mutate preserves names (#1689, #2675)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1:3) + out1 <- df %>% duckplyr_mutate(b = setNames(1:3, letters[1:3])) + out2 <- df %>% duckplyr_mutate(b = setNames(as.list(1:3), letters[1:3])) + + expect_named(out1$b, letters[1:3]) + expect_named(out2$b, letters[1:3]) +}) + +test_that("mutate handles matrix columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(a = rep(1:3, each = 2), b = 1:6) + + df_regular <- duckplyr_mutate(df, b = scale(b)) + df_grouped <- duckplyr_mutate(duckplyr_group_by(df, a), b = scale(b)) + df_rowwise <- duckplyr_mutate(duckplyr_rowwise(df), b = scale(b)) + + expect_equal(dim(df_regular$b), c(6, 1)) + expect_equal(dim(df_grouped$b), c(6, 1)) + expect_equal(dim(df_rowwise$b), c(6, 1)) +}) + +test_that("mutate handles data frame columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame("a" = c(1, 2, 3), "b" = c(2, 3, 4), "base_col" = c(3, 4, 5)) + res <- duckplyr_mutate(df, new_col = data.frame(x = 1:3)) + expect_equal(res$new_col, data.frame(x = 1:3)) + + res <- duckplyr_mutate(duckplyr_group_by(df, a), new_col = data.frame(x = a)) + expect_equal(res$new_col, data.frame(x = 1:3)) + + res <- duckplyr_mutate(duckplyr_rowwise(df), new_col = data.frame(x = a)) + expect_equal(res$new_col, data.frame(x = 1:3)) +}) + +test_that("unnamed data frames are automatically unspliced (#2326, #3630)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_identical( + tibble(a = 1) %>% duckplyr_mutate(tibble(b = 2)), + tibble(a = 1, b = 2) + ) + expect_identical( + tibble(a = 1) %>% duckplyr_mutate(tibble(b = 2), tibble(b = 3)), + tibble(a = 1, b = 3) + ) + expect_identical( + tibble(a = 1) %>% duckplyr_mutate(tibble(b = 2), c = b), + tibble(a = 1, b = 2, c = 2) + ) +}) + +test_that("named data frames are packed (#2326, #3630)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + out <- df %>% duckplyr_mutate(y = tibble(a = x)) + expect_equal(out, tibble(x = 1, y = tibble(a = 1))) +}) + +test_that("unchop only called for when multiple groups", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(g = 1, x = 1:5) + out <- duckplyr_mutate(df, x = ts(x, start = c(1971, 1), frequency = 52)) + expect_s3_class(out$x, "ts") + + gdf <- duckplyr_group_by(df, g) + out <- duckplyr_mutate(gdf, x = ts(x, start = c(1971, 1), frequency = 52)) + expect_s3_class(out$x, "ts") +}) + +# output types ------------------------------------------------------------ + +test_that("mutate preserves grouping", { + gf <- duckplyr_group_by(tibble(x = 1:2, y = 2), x) + + i <- count_regroups(out <- duckplyr_mutate(gf, x = 1)) + expect_equal(i, 1L) + expect_equal(duckplyr_group_vars(out), "x") + expect_equal(nrow(group_data(out)), 1) + + i <- count_regroups(out <- duckplyr_mutate(gf, z = 1)) + expect_equal(i, 0) + expect_equal(group_data(out), group_data(gf)) +}) + +test_that("mutate works on zero-row grouped data frame (#596)", { + dat <- data.frame(a = numeric(0), b = character(0), stringsAsFactors = TRUE) + res <- dat %>% duckplyr_group_by(b, .drop = FALSE) %>% duckplyr_mutate(a2 = a * 2) + expect_type(res$a2, "double") + expect_s3_class(res, "grouped_df") + expect_equal(res$a2, numeric(0)) + + expect_type(group_rows(res), "list") + expect_equal(attr(group_rows(res), "ptype"), integer()) + expect_equal(group_data(res)$b, factor(character(0))) +}) + +test_that("mutate preserves class of zero-row rowwise (#4224, #6303)", { + # Each case needs to test both x and identity(x) because these flow + # through two slightly different pathways. + + rf <- duckplyr_rowwise(tibble(x = character(0))) + out <- duckplyr_mutate(rf, x2 = identity(x), x3 = x) + expect_equal(out$x2, character()) + expect_equal(out$x3, character()) + + # including list-of classes of list-cols where possible + rf <- duckplyr_rowwise(tibble(x = list_of(.ptype = character()))) + out <- duckplyr_mutate(rf, x2 = identity(x), x3 = x) + expect_equal(out$x2, character()) + expect_equal(out$x3, character()) + + # an empty list is turns into a logical (aka unspecified) + rf <- duckplyr_rowwise(tibble(x = list())) + out <- duckplyr_mutate(rf, x2 = identity(x), x3 = x) + expect_equal(out$x2, logical()) + expect_equal(out$x3, logical()) +}) + +test_that("mutate works on empty data frames (#1142)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame() + res <- df %>% duckplyr_mutate() + expect_equal(nrow(res), 0L) + expect_equal(length(res), 0L) + + res <- df %>% duckplyr_mutate(x = numeric()) + expect_equal(names(res), "x") + expect_equal(nrow(res), 0L) + expect_equal(length(res), 1L) +}) + +test_that("mutate handles 0 rows rowwise (#1300)", { + res <- tibble(y = character()) %>% duckplyr_rowwise() %>% duckplyr_mutate(z = 1) + expect_equal(nrow(res), 0L) +}) + +test_that("rowwise mutate gives expected results (#1381)", { + f <- function(x) ifelse(x < 2, NA_real_, x) + res <- tibble(x = 1:3) %>% duckplyr_rowwise() %>% duckplyr_mutate(y = f(x)) + expect_equal(res$y, c(NA, 2, 3)) +}) + +test_that("rowwise mutate un-lists existing size-1 list-columns (#6302)", { + # Existing column + rf <- duckplyr_rowwise(tibble(x = as.list(1:3))) + out <- duckplyr_mutate(rf, y = x) + expect_equal(out$y, 1:3) + + # New column + rf <- duckplyr_rowwise(tibble(x = 1:3)) + out <- duckplyr_mutate(rf, y = list(1), z = y) + expect_identical(out$z, c(1, 1, 1)) + + # Column of data 1-row data frames + rf <- duckplyr_rowwise(tibble(x = list(tibble(a = 1), tibble(a = 2)))) + out <- duckplyr_mutate(rf, y = x) + expect_identical(out$y, tibble(a = c(1, 2))) + + # Preserves known list-of type + rf <- duckplyr_rowwise(tibble(x = list_of(.ptype = character()))) + out <- duckplyr_mutate(rf, y = x) + expect_identical(out$y, character()) + + # Errors if it's not a length-1 list + df <- duckplyr_rowwise(tibble(x = list(1, 2:3))) + expect_snapshot(duckplyr_mutate(df, y = x), error = TRUE) +}) + + +test_that("grouped mutate does not drop grouping attributes (#1020)", { + d <- data.frame(subject = c("Jack", "Jill"), id = c(2, 1)) %>% duckplyr_group_by(subject) + a1 <- names(attributes(d)) + a2 <- names(attributes(d %>% duckplyr_mutate(foo = 1))) + expect_equal(duckplyr_setdiff(a1, a2), character(0)) +}) + +test_that("duckplyr_mutate() hands list columns with rowwise magic to follow up expressions (#4845)", { + test <- duckplyr_rowwise(tibble(x = 1:2)) + + expect_identical( + test %>% + duckplyr_mutate(a = list(1)) %>% + duckplyr_mutate(b = list(a + 1)), + test %>% + duckplyr_mutate(a = list(1), b = list(a + 1)) + ) +}) + +test_that("mutate keeps zero length groups", { + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal( duckplyr_group_size(duckplyr_mutate(df, z = 2)), c(2, 2, 0) ) +}) + +# other ------------------------------------------------------------------- + +test_that("no utf8 invasion (#722)", { + skip("TODO duckdb") + skip_if_not(l10n_info()$"UTF-8") + skip_if_not_installed("lobstr") + source("utf-8.txt", local = TRUE, encoding = "UTF-8") +}) + +test_that("duckplyr_mutate() to UTF-8 column names", { + df <- tibble(a = 1) %>% duckplyr_mutate("\u5e78" := a) + + expect_equal(colnames(df), c("a", "\u5e78")) +}) + +test_that("Non-ascii column names in version 0.3 are not duplicated (#636)", { + local_non_utf8_encoding() + + df <- tibble(a = "1", b = "2") + names(df) <- c("a", enc2native("\u4e2d")) + + res <- df %>% mutate_all(as.numeric) + expect_equal(names(res), as_utf8_character(names(df))) +}) + +test_that("mutate coerces results from one group with all NA values (#1463) ", { + df <- tibble(x = c(1, 2), y = c(1, NA)) + res <- df %>% duckplyr_group_by(x) %>% duckplyr_mutate(z = ifelse(y > 1, 1, 2)) + expect_true(is.na(res$z[2])) + expect_type(res$z, "double") +}) + +test_that("grouped subsets are not lazy (#3360)", { + make_call <- function(x) { + quo(!!x) + } + + res <- tibble(name = 1:2, value = letters[1:2]) %>% + duckplyr_rowwise() %>% + duckplyr_mutate(call = list(make_call(value))) %>% + duckplyr_pull() + + expect_identical(res, list(make_call("a"), make_call("b"))) + + res <- tibble(name = 1:2, value = letters[1:2]) %>% + duckplyr_group_by(name) %>% + duckplyr_mutate(call = list(make_call(value))) %>% + duckplyr_pull() + + expect_identical(res, list(make_call("a"), make_call("b"))) +}) + +test_that("duckplyr_mutate() evaluates expression for empty groups", { + df <- tibble(f = factor(c("a", "b"), levels = c("a", "b", "c"))) + gf <- duckplyr_group_by(df, f, .drop = FALSE) + + count <- 0 + duckplyr_mutate(gf, x = {count <<- count + 1}) + expect_equal(count, 3L) +}) + +test_that("DataMask$add() forces chunks (#4677)", { + df <- tibble(bf10 = 0.244) %>% + duckplyr_mutate( + bf01 = 1 / bf10, + log_e_bf10 = log(bf10), + log_e_bf01 = log(bf01) + ) + expect_equal(df$log_e_bf01, log(1 / 0.244)) +}) + +test_that("DataMask uses fresh copies of group id / size variables (#6762)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2) + + fn <- function() { + df <- tibble(a = 1) + # Otherwise, this nested `duckplyr_mutate()` can modify the same + # id/size variable as the outer one, which causes havoc + duckplyr_mutate(df, b = a + 1) + } + + out <- duckplyr_mutate(df, y = {fn(); x}) + + expect_identical(out$x, 1:2) + expect_identical(out$y, 1:2) +}) + +test_that("duckplyr_mutate() correctly auto-names expressions (#6741)", { + df <- tibble(a = 1L) + + expect_identical(duckplyr_mutate(df, -a), tibble(a = 1L, "-a" = -1L)) + + foo <- "foobar" + expect_identical(duckplyr_mutate(df, foo), tibble(a = 1L, foo = "foobar")) + + a <- 2L + expect_identical(duckplyr_mutate(df, a), tibble(a = 1L)) + + df <- tibble(a = 1L, "a + 1" = 5L) + a <- 2L + expect_identical(duckplyr_mutate(df, a + 1), tibble(a = 1L, "a + 1" = 2)) +}) + +# .by ------------------------------------------------------------------------- + +test_that("can group transiently using `.by`", { + df <- tibble(g = c(1, 1, 2, 1, 2), x = c(5, 2, 1, 2, 3)) + + out <- duckplyr_mutate(df, x = mean(x), .by = g) + + expect_identical(out$g, df$g) + expect_identical(out$x, c(3, 3, 2, 3, 2)) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains bare data.frame class", { + df <- data.frame(g = c(1, 1, 2, 1, 2), x = c(5, 2, 1, 2, 3)) + out <- duckplyr_mutate(df, x = mean(x), .by = g) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains data frame attributes (#6100)", { + skip("TODO duckdb") + # With data.frames or tibbles + df <- data.frame(g = c(1, 1, 2), x = c(1, 2, 1)) + tbl <- as_tibble(df) + + attr(df, "foo") <- "bar" + attr(tbl, "foo") <- "bar" + + out <- duckplyr_mutate(df, x = mean(x), .by = g) + expect_identical(attr(out, "foo"), "bar") + + out <- duckplyr_mutate(tbl, x = mean(x), .by = g) + expect_identical(attr(out, "foo"), "bar") +}) + +test_that("can `NULL` out the `.by` column", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:3) + + expect_identical( + duckplyr_mutate(df, x = NULL, .by = x), + new_tibble(list(), nrow = 3) + ) +}) + +test_that("catches `.by` with grouped-df", { + df <- tibble(x = 1) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, .by = x) + }) +}) + +test_that("catches `.by` with rowwise-df", { + df <- tibble(x = 1) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(rdf, .by = x) + }) +}) + +# .before, .after, .keep ------------------------------------------------------ + +test_that(".keep = 'unused' keeps variables explicitly mentioned", { + df <- tibble(x = 1, y = 2) + out <- duckplyr_mutate(df, x1 = x + 1, y = y, .keep = "unused") + expect_named(out, c("y", "x1")) +}) + +test_that(".keep = 'used' not affected by across() or pick()", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2, z = 3, a = "a", b = "b", c = "c") + + # This must evaluate every column in order to figure out if should + # be included in the set or not, but that shouldn't be counted for + # the purposes of "used" variables + out <- duckplyr_mutate(df, across(where(is.numeric), identity), .keep = "unused") + expect_named(out, names(df)) + + out <- duckplyr_mutate(df, pick(where(is.numeric)), .keep = "unused") + expect_named(out, names(df)) +}) + +test_that(".keep = 'used' keeps variables used in expressions", { + df <- tibble(a = 1, b = 2, c = 3, x = 1, y = 2) + out <- duckplyr_mutate(df, xy = x + y, .keep = "used") + expect_named(out, c("x", "y", "xy")) +}) + +test_that(".keep = 'none' only keeps grouping variables", { + df <- tibble(x = 1, y = 2) + gf <- duckplyr_group_by(df, x) + + expect_named(duckplyr_mutate(df, z = 1, .keep = "none"), "z") + expect_named(duckplyr_mutate(gf, z = 1, .keep = "none"), c("x", "z")) +}) + +test_that(".keep = 'none' retains original ordering (#5967)", { + df <- tibble(x = 1, y = 2) + expect_named(df %>% duckplyr_mutate(y = 1, x = 2, .keep = "none"), c("x", "y")) + + # even when grouped + gf <- duckplyr_group_by(df, x) + expect_named(gf %>% duckplyr_mutate(y = 1, x = 2, .keep = "none"), c("x", "y")) +}) + +test_that("can use .before and .after to control column position", { + df <- tibble(x = 1, y = 2) + expect_named(duckplyr_mutate(df, z = 1), c("x", "y", "z")) + expect_named(duckplyr_mutate(df, z = 1, .before = 1), c("z", "x", "y")) + expect_named(duckplyr_mutate(df, z = 1, .after = 1), c("x", "z", "y")) + + # but doesn't affect order of existing columns + df <- tibble(x = 1, y = 2) + expect_named(duckplyr_mutate(df, x = 1, .after = y), c("x", "y")) +}) + +test_that("attributes of bare data frames are retained when `.before` and `.after` are used (#6341)", { + # We require `[` methods to be in charge of keeping extra attributes for all + # data frame subclasses (except for data.tables) + df <- vctrs::data_frame(x = 1, y = 2) + attr(df, "foo") <- "bar" + + out <- duckplyr_mutate(df, z = 3, .before = x) + + expect_identical(attr(out, "foo"), "bar") +}) + +test_that(".keep and .before/.after interact correctly", { + df <- tibble(x = 1, y = 1, z = 1, a = 1, b = 2, c = 3) %>% + duckplyr_group_by(a, b) + + expect_named(duckplyr_mutate(df, d = 1, x = 2, .keep = "none"), c("x", "a", "b", "d")) + expect_named(duckplyr_mutate(df, d = 1, x = 2, .keep = "none", .before = "a"), c("x", "d", "a", "b")) + expect_named(duckplyr_mutate(df, d = 1, x = 2, .keep = "none", .after = "a"), c("x", "a", "d", "b")) +}) + +test_that("dropping column with `NULL` then readding it retains original location", { + df <- tibble(x = 1, y = 2, z = 3, a = 4) + df <- duckplyr_group_by(df, z) + + expect_named(duckplyr_mutate(df, y = NULL, y = 3, .keep = "all"), c("x", "y", "z", "a")) + expect_named(duckplyr_mutate(df, b = a, y = NULL, y = 3, .keep = "used"), c("y", "z", "a", "b")) + expect_named(duckplyr_mutate(df, b = a, y = NULL, y = 3, .keep = "unused"), c("x", "y", "z", "b")) + + # It isn't treated as a "new" column + expect_named(duckplyr_mutate(df, y = NULL, y = 3, .keep = "all", .before = x), c("x", "y", "z", "a")) +}) + +test_that("setting a new column to `NULL` works with `.before` and `.after` (#6563)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2, z = 3, a = 4) + + expect_named(duckplyr_mutate(df, b = NULL, .before = 1), names(df)) + expect_named(duckplyr_mutate(df, b = 1, b = NULL, .before = 1), names(df)) + expect_named(duckplyr_mutate(df, b = NULL, b = 1, .before = 1), c("b", "x", "y", "z", "a")) + + expect_named(duckplyr_mutate(df, b = NULL, c = 1, .after = 2), c("x", "y", "c", "z", "a")) +}) + +test_that(".keep= always retains grouping variables (#5582)", { + df <- tibble(x = 1, y = 2, z = 3) %>% duckplyr_group_by(z) + expect_equal( + df %>% duckplyr_mutate(a = x + 1, .keep = "none"), + tibble(z = 3, a = 2) %>% duckplyr_group_by(z) + ) + expect_equal( + df %>% duckplyr_mutate(a = x + 1, .keep = "all"), + tibble(x = 1, y = 2, z = 3, a = 2) %>% duckplyr_group_by(z) + ) + expect_equal( + df %>% duckplyr_mutate(a = x + 1, .keep = "used"), + tibble(x = 1, z = 3, a = 2) %>% duckplyr_group_by(z) + ) + expect_equal( + df %>% duckplyr_mutate(a = x + 1, .keep = "unused"), + tibble(y = 2, z = 3, a = 2) %>% duckplyr_group_by(z) + ) +}) + +test_that("duckplyr_mutate() preserves the call stack on error (#5308)", { + foobar <- function() stop("foo") + + stack <- NULL + expect_error( + withCallingHandlers( + error = function(...) stack <<- sys.calls(), + duckplyr_mutate(mtcars, foobar()) + ) + ) + + expect_true(some(stack, is_call, "foobar")) +}) + +test_that("dplyr data mask can become obsolete", { + lazy <- function(x) { + list(enquo(x)) + } + df <- tibble( + x = 1:2 + ) + + res <- df %>% + duckplyr_rowwise() %>% + duckplyr_mutate(y = lazy(x), .keep = "unused") + expect_equal(names(res), c("x", "y")) + expect_error(eval_tidy(res$y[[1]])) +}) + +test_that("duckplyr_mutate() deals with 0 groups (#5534)", { + df <- data.frame(x = numeric()) %>% + duckplyr_group_by(x) + + expect_equal( + duckplyr_mutate(df, y = x + 1), + data.frame(x = numeric(), y = numeric()) %>% duckplyr_group_by(x) + ) + + expect_snapshot({ + duckplyr_mutate(df, y = max(x)) + }) +}) + +test_that("functions are not skipped in data pronoun (#5608)", { + f <- function(i) i + 1 + df <- tibble(a = list(f), b = 1) + + two <- df %>% + duckplyr_rowwise() %>% + duckplyr_mutate(res = .data$a(.data$b)) %>% + duckplyr_pull(res) + + expect_equal(two, 2) +}) + +test_that("duckplyr_mutate() casts data frame results to common type (#5646)", { + df <- data.frame(x = 1:2, g = 1:2) %>% duckplyr_group_by(g) + + res <- df %>% + duckplyr_mutate(if (g == 1) data.frame(y = 1) else data.frame(y = 1, z = 2)) + expect_equal(res$z, c(NA, 2)) +}) + +test_that("duckplyr_mutate() supports empty list columns in rowwise data frames (#5804", { + res <- tibble(a = list()) %>% + duckplyr_rowwise() %>% + duckplyr_mutate(n = lengths(a)) + expect_equal(res$n, integer()) +}) + +test_that("duckplyr_mutate() fails on named empty arguments (#5925)", { + expect_error( + duckplyr_mutate(tibble(), bogus = ) + ) +}) + +# Error messages ---------------------------------------------------------- + +test_that("duckplyr_mutate() give meaningful errors", { + expect_snapshot({ + tbl <- tibble(x = 1:2, y = 1:2) + + # setting column to NULL makes it unavailable + (expect_error(tbl %>% duckplyr_mutate(y = NULL, a = sum(y)))) + (expect_error(tbl %>% + duckplyr_group_by(x) %>% + duckplyr_mutate(y = NULL, a = sum(y)) + )) + + # incompatible column type + (expect_error(tibble(x = 1) %>% duckplyr_mutate(y = mean))) + + # Unsupported type" + df <- tibble(g = c(1, 1, 2, 2, 2), x = 1:5) + (expect_error(df %>% duckplyr_mutate(out = env(a = 1)))) + (expect_error(df %>% + duckplyr_group_by(g) %>% + duckplyr_mutate(out = env(a = 1)) + )) + (expect_error(df %>% + duckplyr_rowwise() %>% + duckplyr_mutate(out = rnorm) + )) + + # incompatible types across groups + (expect_error( + data.frame(x = rep(1:5, each = 3)) %>% + duckplyr_group_by(x) %>% + duckplyr_mutate(val = ifelse(x < 3, "foo", 2)) + )) + + # mixed nulls + (expect_error( + tibble(a = 1:3, b=4:6) %>% + duckplyr_group_by(a) %>% + duckplyr_mutate(if(a==1) NULL else "foo") + )) + (expect_error( + tibble(a = 1:3, b=4:6) %>% + duckplyr_group_by(a) %>% + duckplyr_mutate(if(a==2) NULL else "foo") + )) + + # incompatible size + (expect_error( + data.frame(x = c(2, 2, 3, 3)) %>% duckplyr_mutate(int = 1:5) + )) + (expect_error( + data.frame(x = c(2, 2, 3, 3)) %>% + duckplyr_group_by(x) %>% + duckplyr_mutate(int = 1:5) + )) + (expect_error( + data.frame(x = c(2, 3, 3)) %>% + duckplyr_group_by(x) %>% + duckplyr_mutate(int = 1:5) + )) + (expect_error( + data.frame(x = c(2, 2, 3, 3)) %>% + duckplyr_rowwise() %>% + duckplyr_mutate(int = 1:5) + )) + (expect_error( + tibble(y = list(1:3, "a")) %>% + duckplyr_rowwise() %>% + duckplyr_mutate(y2 = y) + )) + (expect_error( + data.frame(x = 1:10) %>% duckplyr_mutate(y = 11:20, y = 1:2) + )) + + # .data pronoun + (expect_error( + tibble(a = 1) %>% duckplyr_mutate(c = .data$b) + )) + (expect_error( + tibble(a = 1:3) %>% + duckplyr_group_by(a) %>% + duckplyr_mutate(c = .data$b) + )) + + # obsolete data mask + lazy <- function(x) list(enquo(x)) + res <- tbl %>% + duckplyr_rowwise() %>% + duckplyr_mutate(z = lazy(x), .keep = "unused") + (expect_error( + eval_tidy(res$z[[1]]) + )) + + + # Error that contains { + (expect_error( + tibble() %>% duckplyr_mutate(stop("{")) + )) + }) +}) + +test_that("duckplyr_mutate() errors refer to expressions if not named", { + expect_snapshot({ + (expect_error(duckplyr_mutate(mtcars, 1:3))) + (expect_error(duckplyr_mutate(duckplyr_group_by(mtcars, cyl), 1:3))) + }) +}) + +test_that("`duckplyr_mutate()` doesn't allow data frames with missing or empty names (#6758)", { + df1 <- new_data_frame(set_names(list(1), "")) + df2 <- new_data_frame(set_names(list(1), NA_character_)) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(df1) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(df2) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-n-distinct.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-n-distinct.R new file mode 100644 index 000000000..113b3f4f0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-n-distinct.R @@ -0,0 +1,52 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("n_distinct() counts empty inputs", { + expect_equal(n_distinct(NULL), 0) + expect_equal(n_distinct(data.frame()), 0) +}) + +test_that("n_distinct() counts unique values in simple vectors", { + expect_equal(n_distinct(c(TRUE, FALSE, NA)), 3) + expect_equal(n_distinct(c(1, 2, NA)), 3) + expect_equal(n_distinct(c(1L, 2L, NA)), 3) + expect_equal(n_distinct(c("x", "y", NA)), 3) +}) + +test_that("n_distinct() counts unique combinations", { + expect_equal(n_distinct(c(1, 1, 1), c(2, 2, 2)), 1) + expect_equal(n_distinct(c(1, 1, 2), c(1, 2, 2)), 3) +}) + +test_that("n_distinct() handles data frames", { + expect_equal(n_distinct(data.frame(c(1, 1, 1), c(2, 2, 2))), 1) + expect_equal(n_distinct(data.frame(c(1, 1, 2), c(1, 2, 2))), 3) +}) + +test_that("n_distinct() can drop missing values", { + expect_equal(n_distinct(NA, na.rm = TRUE), 0) + expect_equal(n_distinct(c(NA, 0), na.rm = TRUE), 1) + + expect_equal(n_distinct(c(NA, 0), c(0, NA), na.rm = TRUE), 0) + expect_equal(n_distinct(c(NA, 0), c(0, 0), na.rm = TRUE), 1) + + # check tibbles unpacked correctly + expect_equal(n_distinct(1, tibble(x = 2, y = NA), na.rm = TRUE), 0) +}) + +test_that("n_distinct() follows recycling rules", { + expect_equal(n_distinct(double(), 1), 0) + expect_equal(n_distinct(1:2, 1), 2) +}) + +test_that("n_distinct() generates useful errors", { + expect_snapshot(error = TRUE, { + n_distinct() + n_distinct(x = 1:4) + n_distinct(mean) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-na-if.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-na-if.R new file mode 100644 index 000000000..d2a750203 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-na-if.R @@ -0,0 +1,94 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("scalar y replaces all matching x", { + x <- c(0, 1, 0) + expect_identical(na_if(x, 0), c(NA, 1, NA)) + expect_identical(na_if(x, 1), c(0, NA, 0)) +}) + +test_that("`y` can be a vector the same length as `x` (matching SQL NULLIF)", { + x <- c(0, 1, 0) + y <- c(0, 1, 2) + expect_identical(na_if(x, y), c(NA, NA, 0)) +}) + +test_that("`NA` replacing itself is a no-op", { + expect_identical(na_if(NA, NA), NA) +}) + +test_that("missing values are allowed to equal each other, so `NaN`s can be standardized", { + expect_identical(na_if(NaN, NaN), NA_real_) +}) + +test_that("missing values equal each other in partially incomplete data frame rows", { + x <- tibble( + x = c(2, 1, NA, 1), + y = c(1, NA, NA, NA), + z = c(3, NaN, NA, NaN) + ) + + y <- tibble(x = 1, y = NA, z = NaN) + + expect <- vec_assign(x, i = c(2, 4), value = NA) + + expect_identical(na_if(x, y), expect) +}) + +test_that("works when there are missings in either input", { + expect_identical(na_if(c(1, NA, 2), 1), c(NA, NA, 2)) + expect_identical(na_if(c(1, NA, 2), c(1, NA, NA)), c(NA, NA, 2)) +}) + +test_that("works with data frames", { + x <- tibble(a = c(1, 99, 99, 99), b = c("x", "NA", "bar", "NA")) + y <- tibble(a = 99, b = "NA") + + expect_identical( + na_if(x, y), + x[c(1, NA, 3, NA),] + ) +}) + +test_that("works with rcrd types", { + x <- new_rcrd(list(a = c(1, 99, 99, 99), b = c("x", "NA", "bar", "NA"))) + y <- new_rcrd(list(a = 99, b = "NA")) + + expect_identical( + na_if(x, y), + x[c(1, NA, 3, NA)] + ) +}) + +test_that("is type stable on `x`", { + expect_identical(na_if(0L, 0), NA_integer_) + + expect_snapshot(error = TRUE, { + na_if(0L, 1.5) + }) +}) + +test_that("is size stable on `x`", { + expect_snapshot(error = TRUE, { + na_if(1, integer()) + }) + expect_snapshot(error = TRUE, { + na_if(1, c(1, 2)) + }) + expect_snapshot(error = TRUE, { + na_if(c(1, 2, 3), c(1, 2)) + }) +}) + +test_that("requires vector types for `x` and `y`", { + expect_snapshot(error = TRUE, { + na_if(environment(), 1) + }) + expect_snapshot(error = TRUE, { + na_if(1, environment()) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-near.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-near.R new file mode 100644 index 000000000..cf50d9f41 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-near.R @@ -0,0 +1,10 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("near accepts nearby fp values", { + expect_true(near(sqrt(2)^2, 2)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nest-by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nest-by.R new file mode 100644 index 000000000..5b99a711e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nest-by.R @@ -0,0 +1,42 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("returns expected type/data", { + df <- data.frame(g = 1:2, x = 1:2, y = 1:2) + out <- duckplyr_nest_by(df, g) + + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "g") + expect_named(out, c("g", "data")) +}) + +test_that("can control key col", { + df <- data.frame(g = 1:2, x = 1:2, y = 1:2) + out <- duckplyr_nest_by(df, g, .key = "key") + expect_named(out, c("g", "key")) +}) + +test_that("duckplyr_nest_by() inherits grouping", { + df <- data.frame(g1 = 1:2, g2 = 1:2, x = 1:2, y = 1:2) + + expect_equal( + df %>% duckplyr_group_by(g1) %>% duckplyr_nest_by() %>% duckplyr_group_vars(), + "g1" + ) + + # And you can't have it both ways + expect_error(df %>% duckplyr_group_by(g1) %>% duckplyr_nest_by("g2"), "re-group") +}) + +test_that("can control whether grouping data in list-col", { + df <- data.frame(g = 1:2, x = 1:2, y = 1:2) + out <- duckplyr_nest_by(df, g) + expect_named(out$data[[1]], c("x", "y")) + + out <- duckplyr_nest_by(df, g, .keep = TRUE) + expect_named(out$data[[1]], c("g", "x", "y")) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nth-value.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nth-value.R new file mode 100644 index 000000000..e65f4d17b --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-nth-value.R @@ -0,0 +1,246 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# ------------------------------------------------------------------------------ +# nth() + +test_that("nth works with lists and uses `vec_slice2()` to return elements (#6331)", { + # We'd like to use `vec_slice()` everywhere, but it breaks too many revdeps + # that rely on `nth()` returning list elements + x <- list(1, 2, 3:5) + + expect_equal(nth(x, 1), 1) + expect_equal(nth(x, 3), 3:5) +}) + +test_that("nth `default` for lists defaults to `NULL` since it uses `vec_slice2()`", { + expect_null(nth(list(1), 2)) + expect_null(nth(list(), 1)) +}) + +test_that("nth `default` for lists can be anything", { + # Because list elements can be anything + x <- list(1, 2) + + default <- environment() + expect_identical(nth(x, 3, default = default), default) + + default <- 1:3 + expect_identical(nth(x, 3, default = default), default) +}) + +test_that("nth treats list-of like lists", { + x <- list_of(1, 2, c(3, 4)) + + expect_identical(nth(x, 3), c(3, 4)) + expect_identical(nth(x, 4), NULL) + + # Not particularly strict about `default` here, + # even though `list_of()` elements are typed + expect_identical(nth(x, 4, default = "x"), "x") +}) + +test_that("nth works with data frames and always returns a single row", { + x <- tibble(x = 1:3, y = 4:6) + + expect_identical(nth(x, 1), tibble(x = 1L, y = 4L)) + expect_identical(nth(x, 4), tibble(x = NA_integer_, y = NA_integer_)) + expect_identical(nth(x, 4, default = tibble(x = 0, y = 0)), tibble(x = 0L, y = 0L)) +}) + +test_that("nth works with rcrds", { + x <- new_rcrd(list(x = 1:3, y = 4:6)) + + expect_identical(nth(x, 1), vec_slice(x, 1)) + expect_identical(nth(x, 4), vec_init(x)) + expect_identical(nth(x, 4, default = x[2]), x[2]) +}) + +test_that("drops names, because it uses `vec_slice2()`", { + x <- c(a = 1, b = 2) + expect_named(nth(x, 2), NULL) +}) + +test_that("negative values index from end", { + x <- 1:5 + + expect_equal(nth(x, -1), 5L) + expect_equal(nth(x, -3), 3L) +}) + +test_that("indexing past ends returns default value", { + expect_equal(nth(1:4, 5), NA_integer_) + expect_equal(nth(1:4, -5), NA_integer_) + expect_equal(nth(1:4, -10), NA_integer_) + expect_equal(nth(1:4, -10, default = 6L), 6L) +}) + +test_that("gets corner case indexing correct", { + expect_identical(nth(1:4, -5), NA_integer_) + expect_identical(nth(1:4, -4), 1L) + expect_identical(nth(1:4, -3), 2L) + + expect_identical(nth(1:4, -1), 4L) + expect_identical(nth(1:4, 0), NA_integer_) + expect_identical(nth(1:4, 1), 1L) + + expect_identical(nth(1:4, 3), 3L) + expect_identical(nth(1:4, 4), 4L) + expect_identical(nth(1:4, 5), NA_integer_) +}) + +test_that("`order_by` can be used to alter the order", { + expect_identical(nth(1:5, n = 1L, order_by = 5:1), 5L) + expect_identical(nth(as.list(1:5), n = 1L, order_by = 5:1), 5L) +}) + +test_that("can use a data frame as `order_by`", { + x <- 1:3 + order_by <- tibble(a = c(1, 1, 2), b = c(2, 1, 0)) + + expect_identical(nth(x, 1, order_by = order_by), 2L) + expect_identical(nth(x, 2, order_by = order_by), 1L) +}) + +test_that("`na_rm` can be used to drop missings before selecting the value (#6242)", { + x <- c(NA, 4, 10, NA, 5, NA) + + expect_identical(nth(x, 1, na_rm = TRUE), 4) + expect_identical(nth(x, -1, na_rm = TRUE), 5) + expect_identical(nth(x, 3, na_rm = TRUE), 5) +}) + +test_that("`na_rm` removes `NULL` list elements", { + x <- list(1:3, NULL, 4, integer(), NULL, NULL) + + expect_identical(nth(x, 2, na_rm = TRUE), 4) + expect_identical(nth(x, -1, na_rm = TRUE), integer()) +}) + +test_that("`na_rm` can generate OOB selections, resulting in `default`", { + # Removes some values + x <- c(NA, FALSE, NA) + expect_identical(nth(x, 2, default = TRUE, na_rm = TRUE), TRUE) + + # Removes everything + x <- c(NA, NA, NA) + expect_identical(nth(x, 1, default = TRUE, na_rm = TRUE), TRUE) + expect_identical(nth(x, -2, default = TRUE, na_rm = TRUE), TRUE) +}) + +test_that("`na_rm` slices `order_by` as well", { + x <- c(NA, 4, 10, NA, 5, NA) + o <- c(2, 1, 3, 1, 1, 0) + + expect_identical(nth(x, 1, order_by = o, na_rm = TRUE), 4) + expect_identical(nth(x, -1, order_by = o, na_rm = TRUE), 10) + expect_identical(nth(x, 2, order_by = o, na_rm = TRUE), 5) + expect_identical(nth(x, 3, order_by = o, na_rm = TRUE), 10) +}) + +test_that("`na_rm` is validated", { + expect_snapshot(error = TRUE, { + nth(1, 1, na_rm = 1) + }) + expect_snapshot(error = TRUE, { + nth(1, 1, na_rm = c(TRUE, FALSE)) + }) +}) + +test_that("`default` must be size 1 (when not used with lists)", { + expect_snapshot(error = TRUE, { + nth(1L, n = 2L, default = 1:2) + }) +}) + +test_that("`default` is cast to the type of `x` (when not used with lists)", { + expect_snapshot(error = TRUE, { + nth("x", 2, default = 2) + }) +}) + +test_that("`n` is validated (#5466)", { + expect_snapshot(error = TRUE, { + nth(1:10, n = "x") + }) + expect_snapshot(error = TRUE, { + nth(1:10, n = 1:2) + }) + expect_snapshot(error = TRUE, { + nth(1:10, n = NA_integer_) + }) +}) + +test_that("`x` must be a vector", { + expect_snapshot(error = TRUE, { + nth(environment(), 1L) + }) +}) + +test_that("`order_by` must be the same size as `x`", { + expect_snapshot(error = TRUE, { + nth(1:5, n = 1L, order_by = 1:2) + }) + + # Ensure that this is checked before `default` is early returned + expect_snapshot(error = TRUE, { + nth(1:5, n = 6L, order_by = 1:2) + }) +}) + +# ------------------------------------------------------------------------------ +# first() + +test_that("`first()` selects the first value", { + expect_identical(first(1:5), 1L) +}) + +test_that("`first()` uses default value for 0 length vectors", { + expect_equal(first(logical()), NA) + expect_equal(first(integer()), NA_integer_) + expect_equal(first(numeric()), NA_real_) + expect_equal(first(character()), NA_character_) +}) + +test_that("`first()` uses `NULL` default for 0 length lists", { + expect_identical(first(list()), NULL) +}) + +test_that("`first()` uses default value for 0 length augmented vectors", { + fc <- factor("a")[0] + dt <- Sys.Date()[0] + tm <- Sys.time()[0] + + expect_equal(first(fc), vec_init(fc)) + expect_equal(first(dt), vec_init(dt)) + expect_equal(first(tm), vec_init(tm)) +}) + +test_that("`first()` returns list elements", { + expect_identical(first(list(2:3, 4:5)), 2:3) +}) + +test_that("`first()` respects `na_rm`", { + x <- c(NA, NA, 2, 3) + expect_identical(first(x, na_rm = TRUE), 2) +}) + +# ------------------------------------------------------------------------------ +# last() + +test_that("`last()` selects the last value", { + expect_identical(last(1:5), 5L) +}) + +test_that("`last()` returns list elements", { + expect_identical(last(list(2:3, 4:5)), 4:5) +}) + +test_that("`last()` respects `na_rm`", { + x <- c(2, 3, NA, NA) + expect_identical(last(x, na_rm = TRUE), 3) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-order-by.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-order-by.R new file mode 100644 index 000000000..d8c17e237 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-order-by.R @@ -0,0 +1,54 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("order_by() gives useful error messages", { + expect_snapshot({ + (expect_error(order_by(mtcars, 10))) + (expect_error(order_by(mtcars, cyl))) + }) +}) + +test_that("`with_order()` works with data frame `order_by` (#6334)", { + x <- 1:3 + order_by <- tibble(a = c(1, 1, 2), b = c(2, 1, 1)) + + expect_identical(with_order(order_by, lag, x), c(2L, NA, 1L)) +}) + +test_that("`with_order()` requires `order_by` and `x` to be the same size", { + expect_snapshot(error = TRUE, { + with_order(1:2, identity, 1:3) + }) +}) + + +test_that("order_by() returns correct value", { + expected <- int(15, 14, 12, 9, 5) + expect_identical(order_by(5:1, cumsum(1:5)), expected) + + x <- 5:1 + y <- 1:5 + expect_identical(order_by(x, cumsum(y)), expected) +}) + +test_that("order_by() works in arbitrary envs (#2297)", { + env <- child_env("base") + expect_equal( + with_env(env, dplyr::order_by(5:1, cumsum(1:5))), + rev(cumsum(rev(1:5))) + ) + expect_equal( + order_by(5:1, cumsum(1:5)), + rev(cumsum(rev(1:5))) + ) +}) + +test_that("order_by() give meaningful errors", { + expect_snapshot({ + (expect_error(order_by(NULL, 1L))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pick.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pick.R new file mode 100644 index 000000000..6aec2a51c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pick.R @@ -0,0 +1,575 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# ------------------------------------------------------------------------------ +# pick() + duckplyr_mutate() + +test_that("can pick columns from the data", { + df <- tibble(x1 = 1, y = 2, x2 = 3, z = 4) + + expect <- df[c("z", "x1", "x2")] + + out <- duckplyr_mutate(df, sel = pick(z, starts_with("x"))) + expect_identical(out$sel, expect) + + out <- duckplyr_mutate(df, sel = pick_wrapper(z, starts_with("x"))) + expect_identical(out$sel, expect) +}) + +test_that("can use namespaced call to `pick()`", { + df <- tibble(x = 1, y = "y") + + expect_identical( + duckplyr_mutate(df, z = dplyr::pick(where(is.character))), + duckplyr_mutate(df, z = pick(where(is.character))) + ) +}) + +test_that("returns separate data frames for each group", { + fn <- function(x) { + x[["x"]] + mean(x[["z"]]) + } + + df <- tibble(g = c(1, 1, 2, 2, 2), x = 1:5, z = 11:15) + gdf <- duckplyr_group_by(df, g) + + expect <- duckplyr_mutate(gdf, res = x + mean(z)) + + out <- duckplyr_mutate(gdf, res = fn(pick(x, z))) + expect_identical(out, expect) + + out <- duckplyr_mutate(gdf, res = fn(pick_wrapper(x, z))) + expect_identical(out, expect) +}) + +test_that("returns a tibble", { + df <- data.frame(x = 1) + + out <- duckplyr_mutate(df, y = pick(x)) + expect_s3_class(out$y, "tbl_df") + + out <- duckplyr_mutate(df, y = pick_wrapper(x)) + expect_s3_class(out$y, "tbl_df") +}) + +test_that("with `duckplyr_rowwise()` data, leaves list-cols unwrapped (#5951, #6264)", { + # Because this most closely mimics macro expansion of: + # pick(x) -> tibble(x = x) + df <- tibble(x = list(1, 2:3, 4:5), y = 1:3) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(rdf, z = pick(x, y)) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(rdf, z = pick_wrapper(x, y)) + }) +}) + +test_that("selectors won't select grouping columns", { + df <- tibble(g = 1, x = 2) + gdf <- duckplyr_group_by(df, g) + + out <- duckplyr_mutate(gdf, y = pick(everything())) + expect_named(out$y, "x") + + out <- duckplyr_mutate(gdf, y = pick_wrapper(everything())) + expect_named(out$y, "x") +}) + +test_that("selectors won't select rowwise 'grouping' columns", { + df <- tibble(g = 1, x = 2) + rdf <- duckplyr_rowwise(df, g) + + out <- duckplyr_mutate(rdf, y = pick(everything())) + expect_named(out$y, "x") + + out <- duckplyr_mutate(rdf, y = pick_wrapper(everything())) + expect_named(out$y, "x") +}) + +test_that("can't explicitly select grouping columns (#5460)", { + # Related to removing the mask layer from the quosure environments + df <- tibble(g = 1, x = 2) + gdf <- duckplyr_group_by(df, g) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, y = pick(g)) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, y = pick_wrapper(g)) + }) +}) + +test_that("`all_of()` is evaluated in the correct environment (#5460)", { + # Related to removing the mask layer from the quosure environments + df <- tibble(g = 1, x = 2, y = 3) + + # We expect an "object not found" error, but we don't control that + # so we aren't going to snapshot it, especially since the call reported + # by those kinds of errors changed in R 4.3. + expect_error(duckplyr_mutate(df, z = pick(all_of(y)))) + expect_error(duckplyr_mutate(df, z = pick_wrapper(all_of(y)))) + + y <- "x" + expect <- df["x"] + + out <- duckplyr_mutate(df, z = pick(all_of(y))) + expect_identical(out$z, expect) + + out <- duckplyr_mutate(df, z = pick_wrapper(all_of(y))) + expect_identical(out$z, expect) +}) + +test_that("empty selections create 1 row tibbles (#6685)", { + # This makes the result recyclable against other inputs, and ensures that + # a `pick(NULL)` call can be used in a `duckplyr_group_by()` wrapper to + # "group by nothing". It is a slight departure from viewing `pick()` as a + # pure macro expansion into `tibble()`. Instead it is more like an expansion + # into: + # size <- vctrs::vec_size_common(..., .absent = 1L) + # out <- vctrs::vec_recycle_common(..., .size = size) + # tibble::new_tibble(out, nrow = size) + + df <- tibble(g = c(1, 1, 2), x = c(2, 3, 4)) + gdf <- duckplyr_group_by(df, g) + + out <- duckplyr_mutate(gdf, y = pick(starts_with("foo"))) + expect_identical(out$y, new_tibble(list(), nrow = 3L)) + + out <- duckplyr_mutate(gdf, y = pick_wrapper(starts_with("foo"))) + expect_identical(out$y, new_tibble(list(), nrow = 3L)) +}) + +test_that("must supply at least one selector to `pick()`", { + df <- tibble(x = c(2, 3, 4)) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, y = pick()) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, y = pick_wrapper()) + }) +}) + +test_that("the tidyselection and column extraction are evaluated on the current data", { + # Because `pick()` is viewed as macro expansion, and the expansion inherits + # typical dplyr semantics + + df <- tibble(g = c(1, 2, 2), x = 1:3) + gdf <- duckplyr_group_by(df, g) + + expect_snapshot(error = TRUE, { + # Expands to `tibble(x = x)` + duckplyr_mutate(gdf, x = NULL, y = pick(x)) + }) + expect_snapshot(error = TRUE, { + # Does actual `eval_select()` call per group + duckplyr_mutate(gdf, x = NULL, y = pick_wrapper(x)) + }) + + # Can select newly created columns + out <- duckplyr_mutate(gdf, y = x + 1L, z = pick(x, y)) + expect_identical(out[c("x", "y")], out$z) + + out <- duckplyr_mutate(gdf, y = x + 1L, z = pick_wrapper(x, y)) + expect_identical(out[c("x", "y")], out$z) + + + df <- tibble(x = 1) + expect <- tibble(x = tibble(x = tibble(x = 1)), y = tibble(x = x)) + + out <- duckplyr_mutate(df, x = pick(x), x = pick(x), y = pick(x)) + expect_identical(out, expect) + + out <- duckplyr_mutate(df, x = pick_wrapper(x), x = pick_wrapper(x), y = pick_wrapper(x)) + expect_identical(out, expect) +}) + +test_that("can call different `pick()` expressions in different groups", { + df <- tibble(g = c(1, 2), x = 1:2, y = 3:4) + gdf <- duckplyr_group_by(df, g) + + expect <- tibble(x = c(1L, NA), y = c(NA, 4L)) + + out <- duckplyr_mutate(gdf, z = if (g == 1) pick(x) else pick(y)) + expect_identical(out$z, expect) + + out <- duckplyr_mutate(gdf, z = if (g == 1) pick_wrapper(x) else pick_wrapper(y)) + expect_identical(out$z, expect) +}) + +test_that("can call `pick()` from a user defined function", { + df <- tibble(a = 1, b = 2, c = 3) + gdf <- duckplyr_group_by(df, a) + + # Hardcoded variables in expression + my_pick <- function() pick(a, c) + out <- duckplyr_mutate(df, d = my_pick()) + expect_identical(out$d, df[c("a", "c")]) + + # Hardcoded `all_of()` using a local variable + my_pick <- function() { + x <- c("a", "c") + pick(all_of(x)) + } + out <- duckplyr_mutate(df, d = my_pick()) + expect_identical(out$d, df[c("a", "c")]) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, d = my_pick()) + }) + + # Dynamic `all_of()` using user supplied variable + my_pick <- function(x) { + pick(all_of(x)) + } + y <- c("a", "c") + out <- duckplyr_mutate(df, d = my_pick(y)) + expect_identical(out$d, df[c("a", "c")]) + + expect_snapshot(error = TRUE, { + duckplyr_mutate(gdf, d = my_pick(y)) + }) +}) + +test_that("wrapped `all_of()` and `where()` selections work", { + df <- tibble(a = 1, b = "x", c = 3) + + my_pick <- function(x) { + pick(all_of(x)) + } + out <- duckplyr_mutate(df, x = my_pick("a"), y = my_pick("b")) + expect_identical(out$x, df["a"]) + expect_identical(out$y, df["b"]) + + my_pick2 <- function(x) { + pick(all_of(x)) + } + out <- duckplyr_mutate(df, x = my_pick("a"), y = my_pick2("b")) + expect_identical(out$x, df["a"]) + expect_identical(out$y, df["b"]) + + my_where <- function(fn) { + pick(where(fn)) + } + out <- duckplyr_mutate(df, x = my_where(is.numeric), y = my_where(is.character)) + expect_identical(out$x, df[c("a", "c")]) + expect_identical(out$y, df["b"]) +}) + +test_that("`pick()` expansion evaluates on the full data", { + # To ensure tidyselection is consistent across groups + df <- tibble(g = c(1, 1, 2, 2), x = c(0, 0, 1, 1), y = c(1, 1, 0, 0)) + gdf <- duckplyr_group_by(df, g) + + # Doesn't select any columns. Returns a 1 row tibble per group (#6685). + out <- duckplyr_mutate(gdf, y = pick(where(~all(.x == 0)))) + expect_identical(out$y, new_tibble(list(), nrow = 4L)) + + # `pick()` evaluation fallback evaluates on the group specific data, + # forcing potentially different results per group. + out <- duckplyr_mutate(gdf, z = pick_wrapper(where(~all(.x == 0)))) + expect_named(out$z, c("x", "y")) + expect_identical(out$z$x, c(0, 0, NA, NA)) + expect_identical(out$z$y, c(NA, NA, 0, 0)) +}) + +test_that("`pick()` expansion/tidyselection happens outside the data mask", { + # `pick()` expressions are evaluated in the caller environment of the verb. + # This is intentional to avoid theoretical per-group differences in what + # `pick()` should return. + df <- tibble(x = 1, y = 2, z = 3) + + a <- "z" + expect <- df["z"] + + out <- duckplyr_mutate(df, foo = { + a <- "x" + pick(all_of(a)) + }) + expect_identical(out$foo, expect) + + # `pick()`'s evaluation fallback also performs the tidy-selection + # in the calling environment of the verb + out <- duckplyr_mutate(df, foo = { + a <- "x" + pick_wrapper(all_of(a)) + }) + expect_identical(out$foo, expect) +}) + +test_that("errors correctly outside mutate context", { + expect_snapshot(error = TRUE, { + pick() + }) + expect_snapshot(error = TRUE, { + pick(a, b) + }) +}) + +test_that("can assign `pick()` to new function", { + # Will run the evaluation version of `pick()` + pick2 <- pick + + df <- tibble(x = 1, y = 2) + + out <- duckplyr_mutate(df, z = pick2(y)) + expect_identical(out$z, df["y"]) +}) + +test_that("selection on rowwise data frames uses full list-cols, but actual evaluation unwraps them", { + df <- tibble(x = list(1:2, 2:4, 5)) + df <- duckplyr_rowwise(df) + + # i.e. can select based on list-ness of the column. + # Expands to `y = list(tibble(x = x))` where `x` is `1:2`, `2:4`, `5` like it + # would be if you called that directly. + out <- duckplyr_mutate(df, y = list(pick(where(is.list)))) + expect_identical(out$y, map(df$x, ~tibble(x = .x))) +}) + +test_that("when expansion occurs, error labels use the pre-expansion quosure", { + df <- tibble(g = c(1, 2, 2), x = c(1, 2, 3)) + + # Fails in common type casting of the group chunks, + # which references the auto-named column name + expect_snapshot(error = TRUE, { + duckplyr_mutate(df, if (cur_group_id() == 1L) pick(x) else "x", .by = g) + }) +}) + +test_that("doesn't allow renaming", { + expect_snapshot(error = TRUE, { + duckplyr_mutate(data.frame(x = 1), pick(y = x)) + }) + expect_snapshot(error = TRUE, { + duckplyr_mutate(data.frame(x = 1), pick_wrapper(y = x)) + }) +}) + +# ------------------------------------------------------------------------------ +# pick() + duckplyr_summarise()/duckplyr_reframe() + +test_that("can `pick()` inside `duckplyr_reframe()`", { + df <- tibble(g = c(1, 1, 2, 1, 2), x = c(1, 1, 1, 2, 2), y = c(1, 1, 1, 2, 1)) + gdf <- duckplyr_group_by(df, g) + + expect_key <- df[c(1, 4, 5), c("x", "y")] + expect_count <- c(3L, 1L, 1L) + + out <- duckplyr_reframe(df, vec_count(pick(x, y), sort = "count")) + expect_identical(out$key, expect_key) + expect_identical(out$count, expect_count) + + out <- duckplyr_reframe(df, vec_count(pick_wrapper(x, y), sort = "count")) + expect_identical(out$key, expect_key) + expect_identical(out$count, expect_count) + + + expect_key <- df[c(1, 4, 3, 5), c("x", "y")] + expect_count <- c(2L, 1L, 1L, 1L) + + out <- duckplyr_reframe(gdf, vec_count(pick(x, y), sort = "count")) + expect_identical(out$key, expect_key) + expect_identical(out$count, expect_count) + + out <- duckplyr_reframe(gdf, vec_count(pick_wrapper(x, y), sort = "count")) + expect_identical(out$key, expect_key) + expect_identical(out$count, expect_count) +}) + +test_that("empty selections recycle to the size of any other column", { + df <- tibble(x = 1:5) + + # Returns size 1 tibbles that stay the same size (#6685) + out <- duckplyr_summarise(df, sum = sum(x), y = pick(starts_with("foo"))) + expect_identical(out$sum, 15L) + expect_identical(out$y, new_tibble(list(), nrow = 1L)) + + out <- duckplyr_summarise(df, sum = sum(x), y = pick_wrapper(starts_with("foo"))) + expect_identical(out$sum, 15L) + expect_identical(out$y, new_tibble(list(), nrow = 1L)) + + # Returns size 1 tibbles that recycle to size 0 because of `empty` (#6685) + out <- duckplyr_reframe(df, empty = integer(), y = pick(starts_with("foo"))) + expect_identical(out$empty, integer()) + expect_identical(out$y, new_tibble(list(), nrow = 0L)) + + out <- duckplyr_reframe(df, empty = integer(), y = pick_wrapper(starts_with("foo"))) + expect_identical(out$empty, integer()) + expect_identical(out$y, new_tibble(list(), nrow = 0L)) +}) + +test_that("uses 'current' columns of `summarize()` and `duckplyr_reframe()`", { + df <- tibble(x = 1:5, y = 6:10) + + # Uses size of current version of `x` + expect_x <- 15L + expect_z <- tibble(x = 15L) + + out <- duckplyr_summarise(df, x = sum(x), z = pick(x)) + expect_identical(out$x, expect_x) + expect_identical(out$z, expect_z) + + out <- duckplyr_summarise(df, x = sum(x), z = pick_wrapper(x)) + expect_identical(out$x, expect_x) + expect_identical(out$z, expect_z) + + + # Adding in `y` forces recycling + expect_x <- vec_rep(15L, 5) + expect_z <- tibble(x = 15L, y = 6:10) + + out <- duckplyr_reframe(df, x = sum(x), z = pick(x, y)) + expect_identical(out$x, expect_x) + expect_identical(out$z, expect_z) + + out <- duckplyr_reframe(df, x = sum(x), z = pick_wrapper(x, y)) + expect_identical(out$x, expect_x) + expect_identical(out$z, expect_z) +}) + +test_that("can select completely new columns in `duckplyr_summarise()`", { + df <- tibble(x = 1:5) + + out <- duckplyr_mutate(df, y = x + 1, z = pick(y)) + expect_identical(out["y"], out$z) + + out <- duckplyr_mutate(df, y = x + 1, z = pick_wrapper(y)) + expect_identical(out["y"], out$z) +}) + +# ------------------------------------------------------------------------------ +# pick() + duckplyr_arrange() + +test_that("can `duckplyr_arrange()` with `pick()` selection", { + df <- tibble(x = c(2, 2, 1), y = c(3, 1, 3)) + + expect <- df[c(3, 2, 1),] + + expect_identical(duckplyr_arrange(df, pick(x, y)), expect) + expect_identical(duckplyr_arrange(df, pick_wrapper(x, y)), expect) + + expect_identical(duckplyr_arrange(df, pick(x), y), expect) + expect_identical(duckplyr_arrange(df, pick_wrapper(x), y), expect) +}) + +test_that("`pick()` errors in `duckplyr_arrange()` are useful", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_arrange(df, pick(y)) + }) + expect_snapshot(error = TRUE, { + duckplyr_arrange(df, foo(pick(x))) + }) +}) + +# ------------------------------------------------------------------------------ +# pick() + duckplyr_filter() + +test_that("can `pick()` inside `duckplyr_filter()`", { + df <- tibble(x = c(1, 2, NA, 3), y = c(2, NA, 5, 3)) + + out <- duckplyr_filter(df, vec_detect_complete(pick(x, y))) + expect_identical(out, df[c(1, 4),]) + + out <- duckplyr_filter(df, vec_detect_complete(pick_wrapper(x, y))) + expect_identical(out, df[c(1, 4),]) +}) + +test_that("`duckplyr_filter()` with `pick()` that uses invalid tidy-selection errors", { + df <- tibble(x = c(1, 2, NA, 3), y = c(2, NA, 5, 3)) + + expect_snapshot(error = TRUE, { + duckplyr_filter(df, pick(x, a)) + }) + expect_snapshot(error = TRUE, { + duckplyr_filter(df, pick_wrapper(x, a)) + }) +}) + +test_that("`duckplyr_filter()` that doesn't use `pick()` result correctly errors", { + df <- tibble(x = c(1, 2, NA, 3), y = c(2, NA, 5, 3)) + + # TODO: Can we improve on the `In argument:` expression in the expansion case? + expect_snapshot(error = TRUE, { + duckplyr_filter(df, pick(x, y)$x) + }) + expect_snapshot(error = TRUE, { + duckplyr_filter(df, pick_wrapper(x, y)$x) + }) +}) + +# ------------------------------------------------------------------------------ +# pick() + duckplyr_group_by() + +test_that("`pick()` can be used inside `duckplyr_group_by()` wrappers", { + df <- tibble(a = 1:3, b = 2:4, c = 3:5) + + tidyselect_group_by <- function(data, groups) { + duckplyr_group_by(data, pick({{ groups }})) + } + tidyselect_group_by_wrapper <- function(data, groups) { + duckplyr_group_by(data, pick_wrapper({{ groups }})) + } + + expect_identical( + tidyselect_group_by(df, c(a, c)), + duckplyr_group_by(df, a, c) + ) + expect_identical( + tidyselect_group_by_wrapper(df, c(a, c)), + duckplyr_group_by(df, a, c) + ) + + # Empty selections group by nothing (#6685) + expect_identical( + tidyselect_group_by(df, NULL), + df + ) + expect_identical( + tidyselect_group_by_wrapper(df, NULL), + df + ) +}) + +# ------------------------------------------------------------------------------ +# expand_pick() + +test_that("`pick()` doesn't expand across anonymous function boundaries", { + df <- tibble(x = 1, y = 2) + by <- compute_by(by = NULL, data = df, error_call = current_env()) + mask <- DataMask$new(df, by, verb = "mutate", error_call = current_env()) + + # With inline `function() { }` calls (this also handles native R anonymous functions) + quo <- dplyr_quosures(z = function() pick(y, x))$z + expect_identical(expand_pick(quo, mask), quo) + + # With `~` anonymous functions + quos <- dplyr_quosures(z = ~ pick(y, x))$z + expect_identical(expand_pick(quo, mask), quo) +}) + +test_that("`pick()` expands embedded quosures", { + df <- tibble(x = 1, y = 2) + by <- compute_by(by = NULL, data = df, error_call = current_env()) + mask <- DataMask$new(df, by, verb = "mutate", error_call = current_env()) + + wrapper <- function(x) { + dplyr_quosures(z = dense_rank({{x}})) + } + quo <- wrapper(pick(x, y))$z + + out <- expand_pick(quo, mask) + + expect_identical( + quo_get_expr(quo_get_expr(out)[[2L]]), + expr(asNamespace("dplyr")$dplyr_pick_tibble(x = x, y = y)) + ) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pull.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pull.R new file mode 100644 index 000000000..2ad721c30 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-pull.R @@ -0,0 +1,34 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("default extracts last var from data frame", { + df <- tibble(x = 1:10, y = 1:10) + expect_equal(duckplyr_pull(df), 1:10) +}) + +test_that("can extract by name, or positive/negative position", { + x <- 1:10 + df <- tibble(x = x, y = runif(10)) + + expect_equal(duckplyr_pull(df, x), x) + expect_equal(duckplyr_pull(df, 1L), x) + expect_equal(duckplyr_pull(df, 1), x) + expect_equal(duckplyr_pull(df, -2), x) + expect_equal(duckplyr_pull(df, -2L), x) +}) + +test_that("can extract named vectors", { + x <- 1:10 + y <- letters[x] + df <- tibble(x = x, y = y) + xn <- set_names(x, y) + + expect_equal(duckplyr_pull(df, x), x) + expect_equal(duckplyr_pull(df, x, y), xn) + expect_equal(duckplyr_pull(df, 1, 2), xn) + expect_equal(names(duckplyr_pull(df, x, y)), y) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rank.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rank.R new file mode 100644 index 000000000..b90554b78 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rank.R @@ -0,0 +1,121 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# ranking functions ------------------------------------------------------- + +test_that("ranking empty vector returns empty vector (#762)", { + x <- numeric() + + expect_equal(row_number(x), numeric()) + expect_equal(min_rank(x), numeric()) + expect_equal(dense_rank(x), numeric()) + expect_equal(percent_rank(x), numeric()) + expect_equal(cume_dist(x), numeric()) + expect_equal(ntile(x, 1), numeric()) +}) + +test_that("rank functions deal pass NA (and NaN) through (#774, #1132)", { + x <- c(1, 2, NA, 1, 0, NaN) + + expect_equal(percent_rank(x), c(1 / 3, 1, NA, 1 / 3, 0, NA)) + expect_equal(min_rank(x), c(2L, 4L, NA, 2L, 1L, NA)) + expect_equal(dense_rank(x), c(2L, 3L, NA, 2L, 1L, NA)) + expect_equal(cume_dist(x), c(.75, 1, NA, .75, .25, NA)) + expect_equal(row_number(x), c(2L, 4L, NA, 3L, 1L, NA)) +}) + +test_that("ranking functions can handle data frames", { + # Explicitly testing partially/fully incomplete rows + df <- tibble( + year = c(2020, 2020, 2021, 2020, 2020, NA), + month = c(3, 2, 1, 2, NA, NA) + ) + + expect_identical(row_number(df), c(3L, 1L, 4L, 2L, NA, NA)) + expect_identical(min_rank(df), c(3L, 1L, 4L, 1L, NA, NA)) + expect_identical(dense_rank(df), c(2L, 1L, 3L, 1L, NA, NA)) + + expect_identical(percent_rank(df), c(2/3, 0/3, 3/3, 0/3, NA, NA)) + expect_identical(cume_dist(df), c(3/4, 2/4, 4/4, 2/4, NA, NA)) + + expect_identical(ntile(df, 2), c(2L, 1L, 2L, 1L, NA, NA)) + expect_identical(ntile(df, 4), c(3L, 1L, 4L, 2L, NA, NA)) +}) + +# row_number() -------------------------------------------------------------- + +test_that("zero-arg row_number() works in mutate", { + n <- c(1, 5, 2, 9) + + df <- tibble(id = rep(letters[1:4], n)) + expect_equal(duckplyr_mutate(df, rn = row_number())$rn, 1:sum(n)) + + gf <- duckplyr_group_by(df, id) + expect_equal(duckplyr_mutate(gf, rn = row_number())$rn, sequence(n)) +}) + +# ntile() ------------------------------------------------------------------- + +test_that("ntile puts biggest groups first (#4995) ", { + expect_equal(ntile(1, 5), 1) + expect_equal(ntile(1:2, 5), 1:2) + expect_equal(ntile(1:3, 5), 1:3) + expect_equal(ntile(1:4, 5), 1:4) + expect_equal(ntile(1:5, 5), 1:5) + expect_equal(ntile(1:6, 5), c(1, 1:5)) + + expect_equal(ntile(1, 7), 1) + expect_equal(ntile(1:2, 7), 1:2) + expect_equal(ntile(1:3, 7), 1:3) + expect_equal(ntile(1:4, 7), 1:4) + expect_equal(ntile(1:5, 7), 1:5) + expect_equal(ntile(1:6, 7), 1:6) + expect_equal(ntile(1:7, 7), 1:7) + expect_equal(ntile(1:8, 7), c(1, 1:7)) +}) + +test_that("ntile ignores NAs", { + x <- c(1:3, NA, NA, NA) + expect_equal(ntile(x, 3), x) + + x1 <- c(1L, 1L, 1L, NA, NA, NA) + expect_equal(ntile(x, 1), x1) +}) + +test_that("ntile always returns an integer", { + expect_equal(ntile(numeric(), 3), integer()) + expect_equal(ntile(NA, 3), NA_integer_) +}) + +test_that("ntile() does not overflow (#4186)", { + out <- ntile(1:1e5, n = 1e5) + expect_equal(out, 1:1e5) +}) + +test_that("ntile() works with one argument (#3418)", { + df <- tibble(id = c(1, 1, 2, 2, 2), x = 1:5) + expect_equal(duckplyr_mutate(df, out = ntile(n = 3))$out, c(1, 1, 2, 2, 3)) + + gf <- duckplyr_group_by(df, id) + expect_equal(duckplyr_mutate(gf, out = ntile(n = 2))$out, c(1, 2, 1, 1, 2)) +}) + +test_that("ntile() validates `n`", { + expect_snapshot(error = TRUE, { + ntile(1, n = 1.5) + }) + expect_snapshot(error = TRUE, { + ntile(1, n = c(1, 2)) + }) + expect_snapshot(error = TRUE, { + ntile(1, n = NA_real_) + }) + expect_snapshot(error = TRUE, { + ntile(1, n = 0) + }) +}) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-reframe.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-reframe.R new file mode 100644 index 000000000..176f4e77d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-reframe.R @@ -0,0 +1,308 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("`duckplyr_reframe()` allows summaries", { + df <- tibble(g = c(1, 1, 1, 2, 2), x = 1:5) + + expect_identical( + duckplyr_reframe(df, x = mean(x)), + tibble(x = 3) + ) + expect_identical( + duckplyr_reframe(df, x = mean(x), .by = g), + tibble(g = c(1, 2), x = c(2, 4.5)) + ) +}) + +test_that("`duckplyr_reframe()` allows size 0 results", { + df <- tibble(g = c(1, 1, 1, 2, 2), x = 1:5) + gdf <- duckplyr_group_by(df, g) + + expect_identical( + duckplyr_reframe(df, x = which(x > 5)), + tibble(x = integer()) + ) + expect_identical( + duckplyr_reframe(df, x = which(x > 5), .by = g), + tibble(g = double(), x = integer()) + ) + expect_identical( + duckplyr_reframe(gdf, x = which(x > 5)), + tibble(g = double(), x = integer()) + ) +}) + +test_that("`duckplyr_reframe()` allows size >1 results", { + df <- tibble(g = c(1, 1, 1, 2, 2), x = 1:5) + gdf <- duckplyr_group_by(df, g) + + expect_identical( + duckplyr_reframe(df, x = which(x > 2)), + tibble(x = 3:5) + ) + expect_identical( + duckplyr_reframe(df, x = which(x > 2), .by = g), + tibble(g = c(1, 2, 2), x = c(3L, 1L, 2L)) + ) + expect_identical( + duckplyr_reframe(gdf, x = which(x > 2)), + tibble(g = c(1, 2, 2), x = c(3L, 1L, 2L)) + ) +}) + +test_that("`duckplyr_reframe()` recycles across columns", { + df <- tibble(g = c(1, 1, 2, 2, 2), x = 1:5) + + out <- duckplyr_reframe(df, a = 1:2, b = 1L, c = 2:3) + expect_identical(out$a, 1:2) + expect_identical(out$b, c(1L, 1L)) + expect_identical(out$c, 2:3) + + out <- duckplyr_reframe(df, a = 1:2, b = 1L, c = 2:3, .by = g) + expect_identical(out$g, c(1, 1, 2, 2)) + expect_identical(out$a, c(1:2, 1:2)) + expect_identical(out$b, c(1L, 1L, 1L, 1L)) + expect_identical(out$c, c(2:3, 2:3)) +}) + +test_that("`duckplyr_reframe()` can recycle across columns to size 0", { + df <- tibble(g = 1:2, x = 1:2) + gdf <- duckplyr_group_by(df, g) + + out <- duckplyr_reframe(df, y = mean(x), z = which(x > 3)) + expect_identical(out$y, double()) + expect_identical(out$z, integer()) + + out <- duckplyr_reframe(df, y = mean(x), z = which(x > 1), .by = g) + expect_identical(out$g, 2L) + expect_identical(out$y, 2) + expect_identical(out$z, 1L) + + out <- duckplyr_reframe(gdf, y = mean(x), z = which(x > 1)) + expect_identical(out$g, 2L) + expect_identical(out$y, 2) + expect_identical(out$z, 1L) +}) + +test_that("`duckplyr_reframe()` throws intelligent recycling errors", { + df <- tibble(g = 1:2, x = 1:2) + gdf <- duckplyr_group_by(df, g) + + expect_snapshot(error = TRUE, { + duckplyr_reframe(df, x = 1:2, y = 3:5) + }) + expect_snapshot(error = TRUE, { + duckplyr_reframe(df, x = 1:2, y = 3:5, .by = g) + }) + expect_snapshot(error = TRUE, { + duckplyr_reframe(gdf, x = 1:2, y = 3:5) + }) +}) + +test_that("`duckplyr_reframe()` can return more rows than the original data frame", { + df <- tibble(x = 1:2) + + expect_identical( + duckplyr_reframe(df, x = vec_rep_each(x, x)), + tibble(x = c(1L, 2L, 2L)) + ) +}) + +test_that("`duckplyr_reframe()` doesn't message about regrouping when multiple group columns are supplied", { + df <- tibble(a = c(1, 1, 2, 2, 2), b = c(1, 2, 1, 1, 2), x = 1:5) + gdf <- duckplyr_group_by(df, a, b) + + # Silence + expect_snapshot({ + out <- duckplyr_reframe(df, x = mean(x), .by = c(a, b)) + }) + expect_snapshot({ + out <- duckplyr_reframe(gdf, x = mean(x)) + }) +}) + +test_that("`duckplyr_reframe()` doesn't message about regrouping when >1 rows are returned per group", { + df <- tibble(g = c(1, 1, 2, 2, 2), x = 1:5) + gdf <- duckplyr_group_by(df, g) + + # Silence + expect_snapshot({ + out <- duckplyr_reframe(df, x = vec_rep_each(x, x), .by = g) + }) + expect_snapshot({ + out <- duckplyr_reframe(gdf, x = vec_rep_each(x, x)) + }) +}) + +test_that("`duckplyr_reframe()` allows sequential assignments", { + df <- tibble(g = 1:2, x = 1:2) + + expect_identical( + duckplyr_reframe(df, y = 3, z = mean(x) + y), + tibble(y = 3, z = 4.5) + ) + expect_identical( + duckplyr_reframe(df, y = 3, z = mean(x) + y, .by = g), + tibble(g = 1:2, y = c(3, 3), z = c(4, 5)) + ) +}) + +test_that("`duckplyr_reframe()` allows for overwriting existing columns", { + df <- tibble(g = c("a", "b"), x = 1:2) + + expect_identical( + duckplyr_reframe(df, x = 3, z = x), + tibble(x = 3, z = 3) + ) + expect_identical( + duckplyr_reframe(df, x = cur_group_id(), z = x, .by = g), + tibble(g = c("a", "b"), x = 1:2, z = 1:2) + ) +}) + +test_that("`duckplyr_reframe()` works with unquoted values", { + df <- tibble(x = 1:5) + expect_equal(duckplyr_reframe(df, out = !!1), tibble(out = 1)) + expect_equal(duckplyr_reframe(df, out = !!quo(1)), tibble(out = 1)) + expect_equal(duckplyr_reframe(df, out = !!(1:2)), tibble(out = 1:2)) +}) + +test_that("`duckplyr_reframe()` with bare data frames always returns a bare data frame", { + df <- data.frame(g = c(1, 1, 2, 1, 2), x = c(5, 2, 1, 2, 3)) + + out <- duckplyr_reframe(df, x = mean(x)) + expect_s3_class(out, class(df), exact = TRUE) + + out <- duckplyr_reframe(df, x = mean(x), .by = g) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("`duckplyr_reframe()` drops data frame attributes", { + # Because `duckplyr_reframe()` theoretically creates a "new" data frame + + # With data.frames + df <- data.frame(g = c(1, 1, 2), x = c(1, 2, 1)) + attr(df, "foo") <- "bar" + + out <- duckplyr_reframe(df, x = mean(x)) + expect_null(attr(out, "foo")) + + out <- duckplyr_reframe(df, x = mean(x), .by = g) + expect_null(attr(out, "foo")) + + # With tibbles + tbl <- as_tibble(df) + attr(tbl, "foo") <- "bar" + + out <- duckplyr_reframe(tbl, x = mean(x)) + expect_null(attr(out, "foo")) + + out <- duckplyr_reframe(tbl, x = mean(x), .by = g) + expect_null(attr(out, "foo")) + + # With grouped_df + gdf <- duckplyr_group_by(df, g) + attr(gdf, "foo") <- "bar" + + out <- duckplyr_reframe(gdf, x = mean(x)) + expect_null(attr(out, "foo")) +}) + +test_that("`duckplyr_reframe()` with `duckplyr_group_by()` sorts keys", { + df <- tibble(g = c(2, 1, 2, 0), x = c(4, 2, 8, 5)) + df <- duckplyr_group_by(df, g) + + out <- duckplyr_reframe(df, x = mean(x)) + + expect_identical(out$g, c(0, 1, 2)) + expect_identical(out$x, c(5, 2, 6)) +}) + +test_that("`duckplyr_reframe()` with `duckplyr_group_by()` respects `.drop = FALSE`", { + g <- factor(c("c", "a", "c"), levels = c("a", "b", "c")) + + df <- tibble(g = g, x = c(1, 4, 2)) + gdf <- duckplyr_group_by(df, g, .drop = FALSE) + + out <- duckplyr_reframe(gdf, x = mean(x)) + + expect_identical(out$g, factor(c("a", "b", "c"))) + expect_identical(out$x, c(4, NaN, 1.5)) +}) + +test_that("`duckplyr_reframe()` with `duckplyr_group_by()` always returns an ungrouped tibble", { + df <- tibble(a = c(1, 1, 2, 2, 2), b = c(1, 2, 1, 1, 2), x = 1:5) + gdf <- duckplyr_group_by(df, a, b) + + out <- duckplyr_reframe(gdf, x = mean(x)) + expect_identical(class(out), class(df)) +}) + +test_that("`duckplyr_reframe()` with `duckplyr_rowwise()` respects list-col element access", { + df <- tibble(x = list(1:2, 3:5, 6L)) + rdf <- duckplyr_rowwise(df) + + expect_identical( + duckplyr_reframe(rdf, x), + tibble(x = 1:6) + ) +}) + +test_that("`duckplyr_reframe()` with `duckplyr_rowwise()` respects rowwise group columns", { + df <- tibble(g = c(1, 1, 2), x = list(1:2, 3:5, 6L)) + rdf <- duckplyr_rowwise(df, g) + + out <- duckplyr_reframe(rdf, x) + expect_identical(out$g, c(rep(1, 5), 2)) + expect_identical(out$x, 1:6) +}) + +test_that("`duckplyr_reframe()` with `duckplyr_rowwise()` always returns an ungrouped tibble", { + df <- tibble(g = c(1, 1, 2), x = list(1:2, 3:5, 6L)) + rdf <- duckplyr_rowwise(df, g) + + expect_s3_class(duckplyr_reframe(rdf, x), class(df), exact = TRUE) +}) + +# .by ---------------------------------------------------------------------- + +test_that("can group transiently using `.by`", { + df <- tibble(g = c(1, 1, 2, 1, 2), x = c(5, 2, 1, 2, 3)) + + out <- duckplyr_reframe(df, x = mean(x), .by = g) + + expect_identical(out$g, c(1, 2)) + expect_identical(out$x, c(3, 2)) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping orders by first appearance", { + df <- tibble(g = c(2, 1, 2, 0), x = c(4, 2, 8, 5)) + + out <- duckplyr_reframe(df, x = mean(x), .by = g) + + expect_identical(out$g, c(2, 1, 0)) + expect_identical(out$x, c(6, 2, 5)) +}) + +test_that("catches `.by` with grouped-df", { + df <- tibble(x = 1) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + duckplyr_reframe(gdf, .by = x) + }) +}) + +test_that("catches `.by` with rowwise-df", { + df <- tibble(x = 1) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + duckplyr_reframe(rdf, .by = x) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-relocate.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-relocate.R new file mode 100644 index 000000000..a90fca696 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-relocate.R @@ -0,0 +1,212 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# ------------------------------------------------------------------------------ +# duckplyr_relocate() + +test_that(".before and .after relocate individual cols", { + df <- tibble(x = 1, y = 2) + expect_named(duckplyr_relocate(df, x, .after = y), c("y", "x")) + expect_named(duckplyr_relocate(df, y, .before = x), c("y", "x")) +}) + +test_that("can move blocks of variables", { + df <- tibble(x = 1, a = "a", y = 2, b = "a") + expect_named(duckplyr_relocate(df, where(is.character)), c("a", "b", "x", "y")) + expect_named(duckplyr_relocate(df, where(is.character), .after = where(is.numeric)), c("x", "y", "a", "b")) +}) + +test_that("don't lose non-contiguous variables", { + df <- tibble(a = 1, b = 1, c = 1, d = 1, e = 1) + expect_named(duckplyr_relocate(df, b, .after = c(a, c, e)), c("a", "c", "d", "e", "b")) + expect_named(duckplyr_relocate(df, e, .before = c(b, d)), c("a", "e", "b", "c", "d")) +}) + +test_that("no .before/.after moves to front", { + df <- tibble(x = 1, y = 2) + expect_named(duckplyr_relocate(df, y), c("y", "x")) +}) + +test_that("can only supply one of .before and .after", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_relocate(df, .before = 1, .after = 1) + }) +}) + +test_that("before and after are defused with context", { + local_fn <- identity + expect_identical( + names(duckplyr_relocate(mtcars, 3, .before = local_fn(5))), + names(duckplyr_relocate(mtcars, 3, .before = 5)) + ) + expect_identical( + names(duckplyr_relocate(mtcars, 3, .after = local_fn(5))), + names(duckplyr_relocate(mtcars, 3, .after = 5)) + ) +}) + +test_that("duckplyr_relocate() respects order specified by ... (#5328)", { + df <- tibble(a = 1, x = 1, b = 1, z = 1, y = 1) + + expect_equal( + names(duckplyr_relocate(df, x, y, z, .before = x)), + c("a", "x", "y", "z", "b") + ) + expect_equal( + names(duckplyr_relocate(df, x, y, z, .after = last_col())), + c("a", "b", "x", "y", "z") + ) + expect_equal( + names(duckplyr_relocate(df, x, a, z)), + c("x", "a", "z", "b", "y") + ) +}) + +test_that("duckplyr_relocate() can rename (#5569)", { + df <- tibble(a = 1, b = 1, c = 1, d = "a", e = "a", f = "a") + expect_equal( + duckplyr_relocate(df, ffff = f), + tibble(ffff = "a", a = 1, b = 1, c = 1, d = "a", e = "a") + ) + expect_equal( + duckplyr_relocate(df, ffff = f, .before = c), + tibble(a = 1, b = 1, ffff = "a", c = 1, d = "a", e = "a") + ) + expect_equal( + duckplyr_relocate(df, ffff = f, .after = c), + tibble(a = 1, b = 1, c = 1, ffff = "a", d = "a", e = "a") + ) +}) + +test_that("`duckplyr_relocate()` retains the last duplicate when renaming while moving (#6209)", { + # To enforce the invariant that `ncol(.data) == ncol(duckplyr_relocate(.data, ...))`. + # Also matches `duckplyr_rename()` behavior. + + df <- tibble(x = 1) + + expect_named(duckplyr_relocate(df, a = x, b = x), "b") + expect_identical( + duckplyr_relocate(df, a = x, b = x), + duckplyr_rename(df, a = x, b = x) + ) + + df <- tibble(x = 1, y = 2) + + expect_named(duckplyr_relocate(df, a = x, b = y, c = x), c("b", "c")) + expect_identical( + duckplyr_relocate(df, a = x, b = y, c = x), + duckplyr_select(duckplyr_rename(df, a = x, b = y, c = x), b, c) + ) +}) + +test_that("attributes of bare data frames are retained (#6341)", { + # We require `[` methods to be in charge of keeping extra attributes for all + # data frame subclasses (except for data.tables) + df <- vctrs::data_frame(x = 1, y = 2) + attr(df, "foo") <- "bar" + + out <- duckplyr_relocate(df, y, .before = x) + + expect_identical(attr(out, "foo"), "bar") +}) + +# ------------------------------------------------------------------------------ +# eval_relocate() + +test_that("works with zero column data frames (#6167)", { + data <- tibble() + expr <- expr(any_of("b")) + + expect_identical( + eval_relocate(expr, data), + set_names(integer()) + ) +}) + +test_that("works with `before` and `after` `everything()`", { + data <- tibble(w = 1, x = 2, y = 3, z = 4) + expr <- expr(c(y, z)) + expr_everything <- expr(everything()) + + expect_identical( + eval_relocate(expr, data, before = expr_everything), + c(y = 3L, z = 4L, w = 1L, x = 2L) + ) + expect_identical( + eval_relocate(expr, data, after = expr_everything), + c(w = 1L, x = 2L, y = 3L, z = 4L) + ) +}) + +test_that("moves columns to the front when neither `before` nor `after` are specified", { + data <- tibble(x = 1, y = 2, z = 3) + expr <- expr(c(z, y)) + + expect_identical( + eval_relocate(expr, data), + c(z = 3L, y = 2L, x = 1L) + ) +}) + +test_that("Empty `before` selection moves columns to front", { + data <- tibble(x = 1, y = 2, z = 3) + expr <- expr(y) + before <- expr(where(is.character)) + + expect_identical( + eval_relocate(expr, data, before = before), + c(y = 2L, x = 1L, z = 3L) + ) +}) + +test_that("Empty `after` selection moves columns to end", { + data <- tibble(x = 1, y = 2, z = 3) + expr <- expr(y) + after <- expr(where(is.character)) + + expect_identical( + eval_relocate(expr, data, after = after), + c(x = 1L, z = 3L, y = 2L) + ) +}) + +test_that("Empty `before` and `after` selections work with 0-col data frames", { + data <- tibble() + expr <- expr(any_of("a")) + expr_is_character <- expr(where(is.character)) + + expect_identical( + eval_relocate(expr, data, before = expr_is_character), + set_names(integer()) + ) + + expect_identical( + eval_relocate(expr, data, after = expr_is_character), + set_names(integer()) + ) +}) + +test_that("retains the last duplicate when renaming while moving (#6209)", { + # To enforce the invariant that relocating can't change the number of columns + data <- tibble(x = 1) + expr <- expr(c(a = x, b = x)) + + expect_identical( + eval_relocate(expr, data), + c(b = 1L) + ) + + data <- tibble(x = 1, y = 2) + expr <- expr(c(a = x, b = y, c = x)) + + expect_identical( + eval_relocate(expr, data), + c(b = 2L, c = 1L) + ) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rename.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rename.R new file mode 100644 index 000000000..1537d64f4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rename.R @@ -0,0 +1,104 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("duckplyr_rename() handles deprecated `.data` pronoun", { + withr::local_options(lifecycle_verbosity = "quiet") + expect_identical(duckplyr_rename(tibble(x = 1), y = .data$x), tibble(y = 1)) +}) + +test_that("arguments to duckplyr_rename() don't match vars_rename() arguments (#2861)", { + df <- tibble(a = 1) + expect_identical(duckplyr_rename(df, var = a), tibble(var = 1)) + expect_identical(duckplyr_rename(duckplyr_group_by(df, a), var = a), duckplyr_group_by(tibble(var = 1), var)) + expect_identical(duckplyr_rename(df, strict = a), tibble(strict = 1)) + expect_identical(duckplyr_rename(duckplyr_group_by(df, a), strict = a), duckplyr_group_by(tibble(strict = 1), strict)) +}) + +test_that("duckplyr_rename() to UTF-8 column names", { + skip_if_not(l10n_info()$"UTF-8") + + df <- tibble(a = 1) %>% duckplyr_rename("\u5e78" := a) + expect_equal(colnames(df), "\u5e78") +}) + +test_that("can duckplyr_rename() with strings and character vectors", { + vars <- c(foo = "cyl", bar = "am") + + expect_identical(duckplyr_rename(mtcars, !!!vars), duckplyr_rename(mtcars, foo = cyl, bar = am)) + expect_identical(duckplyr_rename(mtcars, !!vars), duckplyr_rename(mtcars, foo = cyl, bar = am)) +}) + +test_that("rename preserves grouping", { + gf <- duckplyr_group_by(tibble(g = 1:3, x = 3:1), g) + + i <- count_regroups(out <- duckplyr_rename(gf, h = g)) + expect_equal(i, 0) + expect_equal(duckplyr_group_vars(out), "h") +}) + +test_that("can rename with duplicate columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, x = 2, y = 1, .name_repair = "minimal") + expect_named(df %>% duckplyr_rename(x2 = 2), c("x", "x2", "y")) +}) + +test_that("duckplyr_rename() ignores duplicates", { + df <- tibble(x = 1) + expect_named(duckplyr_rename(df, a = x, b = x), "b") +}) + +# rename_with ------------------------------------------------------------- + +test_that("can select columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + expect_named(df %>% duckplyr_rename_with(toupper, 1), c("X", "y")) + + df <- tibble(x = 1, y = 2) + expect_named(df %>% duckplyr_rename_with(toupper, x), c("X", "y")) +}) + +test_that("passes ... along", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + expect_named(df %>% duckplyr_rename_with(gsub, 1, pattern = "x", replacement = "X"), c("X", "y")) +}) + +test_that("can't create duplicated names", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + expect_error(df %>% duckplyr_rename_with(~ rep_along(.x, "X")), class = "vctrs_error_names") +}) + +test_that("`.fn` result type is checked (#6561)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + fn <- function(x) 1L + + expect_snapshot(error = TRUE, { + duckplyr_rename_with(df, fn) + }) +}) + +test_that("`.fn` result size is checked (#6561)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + fn <- function(x) c("a", "b", "c") + + expect_snapshot(error = TRUE, { + duckplyr_rename_with(df, fn) + }) +}) + +test_that("can't rename in `.cols`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_rename_with(df, toupper, .cols = c(y = x)) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rows.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rows.R new file mode 100644 index 000000000..5351090fc --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rows.R @@ -0,0 +1,541 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# ------------------------------------------------------------------------------ +# duckplyr_rows_insert() + +test_that("duckplyr_rows_insert() works", { + data <- tibble(a = 1:3, b = letters[c(1:2, NA)], c = 0.5 + 0:2) + + expect_identical( + duckplyr_rows_insert(data, tibble(a = 4L, b = "z"), by = "a"), + tibble(a = 1:4, b = c("a", "b", NA, "z"), c = c(0.5, 1.5, 2.5, NA)) + ) +}) + +test_that("duckplyr_rows_insert() doesn't allow insertion of matched keys by default", { + x <- tibble(a = 1, b = 2) + + y <- tibble(a = 1, b = 3) + + expect_snapshot( + (expect_error(duckplyr_rows_insert(x, y, by = "a"))) + ) + + y <- tibble(a = c(1, 1, 1), b = c(3, 4, 5)) + + expect_snapshot( + (expect_error(duckplyr_rows_insert(x, y, by = "a"))) + ) +}) + +test_that("duckplyr_rows_insert() allows you to ignore matched keys with `conflict = 'ignore'`", { + x <- tibble(a = 1, b = 2) + + y <- tibble(a = 1, b = 3) + + expect_identical(duckplyr_rows_insert(x, y, by = "a", conflict = "ignore"), x) + + y <- tibble(a = c(1, 2, 1), b = c(3, 4, 5)) + + expect_identical( + duckplyr_rows_insert(x, y, by = "a", conflict = "ignore"), + duckplyr_rows_insert(x, y[2,], by = "a") + ) +}) + +test_that("duckplyr_rows_insert() allows `x` keys to be duplicated (#5553)", { + x <- tibble(a = c(1, 1), b = c(2, 3)) + y <- tibble(a = 2, b = 4) + + expect_identical( + duckplyr_rows_insert(x, y, by = "a"), + tibble(a = c(1, 1, 2), b = c(2, 3, 4)) + ) +}) + +test_that("duckplyr_rows_insert() allows `y` keys to be duplicated (#5553)", { + x <- tibble(a = 2, b = 4) + y <- tibble(a = c(1, 1), b = c(2, 3)) + + expect_identical( + duckplyr_rows_insert(x, y, by = "a"), + tibble(a = c(2, 1, 1), b = c(4, 2, 3)) + ) +}) + +test_that("duckplyr_rows_insert() casts keys to the type of `x`", { + x <- vctrs::data_frame(key = 1L, value = 2) + y <- vctrs::data_frame(key = 1.5, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_insert(x, y, "key"))) + }) +}) + +test_that("duckplyr_rows_insert() casts values to the type of `x`", { + x <- vctrs::data_frame(key = 1, value = 2L) + y <- vctrs::data_frame(key = 2, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_insert(x, y, "key"))) + }) +}) + +test_that("duckplyr_rows_insert() checks that `x` and `y` contain `by` (#6652)", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = 1) + + expect_snapshot({ + (expect_error(duckplyr_rows_insert(x, y, by = "c"))) + }) + expect_snapshot({ + (expect_error(duckplyr_rows_insert(x, y, by = c("a", "b")))) + }) +}) + +test_that("`conflict` is validated", { + x <- tibble(a = 1) + y <- tibble(a = 2) + + expect_snapshot({ + (expect_error(duckplyr_rows_insert(x, y, by = "a", conflict = "foo"))) + (expect_error(duckplyr_rows_insert(x, y, by = "a", conflict = 1))) + }) +}) + +# ------------------------------------------------------------------------------ +# duckplyr_rows_append() + +test_that("duckplyr_rows_append() allows you to insert unconditionally", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = 1, b = 3) + + expect_identical(duckplyr_rows_append(x, y), bind_rows(x, y)) + + y <- tibble(a = c(1, 2, 1), b = c(3, 4, 5)) + + expect_identical(duckplyr_rows_append(x, y), bind_rows(x, y)) +}) + +test_that("duckplyr_rows_append() casts to the type of `x`", { + x <- vctrs::data_frame(key = 1L, value = 2) + + y <- vctrs::data_frame(key = 1.5, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_append(x, y))) + }) + + y <- vctrs::data_frame(key = 2, value = 3L) + + out <- duckplyr_rows_append(x, y) + expect_identical(out$key, c(1L, 2L)) + expect_identical(out$value, c(2, 3)) +}) + +test_that("duckplyr_rows_append() requires that `y` columns be a subset of `x`", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = 1, b = 2, c = 3) + + expect_snapshot({ + (expect_error(duckplyr_rows_append(x, y))) + }) +}) + +test_that("duckplyr_rows_append() doesn't require that `x` columns be a subset of `y`", { + x <- tibble(a = 1, b = 2, c = 3) + y <- tibble(a = 1, b = 2) + + out <- duckplyr_rows_append(x, y) + expect_identical(out$c, c(3, NA)) +}) + +# ------------------------------------------------------------------------------ + +test_that("duckplyr_rows_update() works", { + data <- tibble(a = 1:3, b = letters[c(1:2, NA)], c = 0.5 + 0:2) + + expect_identical( + duckplyr_rows_update(data, tibble(a = 2:3, b = "z"), by = "a"), + tibble(a = 1:3, b = c("a", "z", "z"), c = data$c) + ) + + expect_silent( + expect_identical( + duckplyr_rows_update(data, tibble(b = "z", a = 2:3), by = "a"), + tibble(a = 1:3, b = c("a", "z", "z"), c = data$c) + ) + ) +}) + +test_that("duckplyr_rows_update() requires `y` keys to exist in `x` by default", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = c(2, 1, 3), b = c(1, 1, 1)) + + expect_snapshot((expect_error(duckplyr_rows_update(x, y, "a")))) +}) + +test_that("duckplyr_rows_update() allows `y` keys that don't exist in `x` to be ignored", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = c(2, 1, 3), b = c(1, 1, 1)) + + expect_identical( + duckplyr_rows_update(x, y, "a", unmatched = "ignore"), + tibble(a = 1, b = 1) + ) +}) + +test_that("duckplyr_rows_update() allows `x` keys to be duplicated (#5553)", { + x <- tibble(a = c(1, 2, 1, 3), b = c(2, 3, 4, 5), c = letters[1:4]) + y <- tibble(a = c(1, 3), b = c(99, 88)) + + expect_identical( + duckplyr_rows_update(x, y, by = "a"), + tibble(a = c(1, 2, 1, 3), b = c(99, 3, 99, 88), c = letters[1:4]) + ) +}) + +test_that("duckplyr_rows_update() doesn't allow `y` keys to be duplicated (#5553)", { + x <- tibble(a = 2, b = 4) + y <- tibble(a = c(1, 1), b = c(2, 3)) + + expect_snapshot((expect_error(duckplyr_rows_update(x, y, by = "a")))) +}) + +test_that("duckplyr_rows_update() avoids bare data.frame `drop = FALSE` issues", { + x <- vctrs::data_frame(x = c(1, 2, 3), y = I(list(1:2, 3:4, 5:6))) + y <- vctrs::data_frame(x = c(1, 3), y = I(list(0L, 100:101))) + + out <- duckplyr_rows_update(x, y, "x") + expect_identical(out$y, I(list(0L, 3:4, 100:101))) +}) + +test_that("duckplyr_rows_update() casts keys to their common type for matching but retains `x` type", { + x <- vctrs::data_frame(key = 1L, value = 2) + y <- vctrs::data_frame(key = 1, value = 1.5) + + out <- duckplyr_rows_update(x, y, "key") + expect_identical(out$key, x$key) + expect_identical(out$value, y$value) + + y <- vctrs::data_frame(key = "x", value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_update(x, y, "key"))) + }) +}) + +test_that("duckplyr_rows_update() casts values to the type of `x`", { + x <- vctrs::data_frame(key = 1, value = 2L) + y <- vctrs::data_frame(key = 1, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_update(x, y, "key"))) + }) + + out <- duckplyr_rows_update(y, x, "key") + expect_identical(out$value, 2) +}) + +test_that("`unmatched` is validated", { + x <- tibble(a = 1) + y <- tibble(a = 1) + + expect_snapshot({ + (expect_error(duckplyr_rows_update(x, y, by = "a", unmatched = "foo"))) + (expect_error(duckplyr_rows_update(x, y, by = "a", unmatched = 1))) + }) +}) + +# ------------------------------------------------------------------------------ + +test_that("duckplyr_rows_patch() works", { + data <- tibble(a = 1:3, b = letters[c(1:2, NA)], c = 0.5 + 0:2) + + expect_identical( + duckplyr_rows_patch(data, tibble(a = 2:3, b = "z"), by = "a"), + tibble(a = 1:3, b = c("a", "b", "z"), c = data$c) + ) + + expect_silent( + expect_identical( + duckplyr_rows_patch(data, tibble(b = "z", a = 2:3), by = "a"), + tibble(a = 1:3, b = c("a", "b", "z"), c = data$c) + ) + ) +}) + +test_that("duckplyr_rows_patch() requires `y` keys to exist in `x` by default", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = c(2, 1, 3), b = c(1, 1, 1)) + + expect_snapshot((expect_error(duckplyr_rows_patch(x, y, "a")))) +}) + +test_that("duckplyr_rows_patch() allows `y` keys that don't exist in `x` to be ignored", { + x <- tibble(a = 1, b = NA_real_) + y <- tibble(a = c(2, 1, 3), b = c(1, 1, 1)) + + expect_identical( + duckplyr_rows_patch(x, y, "a", unmatched = "ignore"), + tibble(a = 1, b = 1) + ) +}) + +test_that("duckplyr_rows_patch() allows `x` keys to be duplicated (#5553)", { + x <- tibble(a = c(1, 2, 1, 3), b = c(NA, 3, 4, NA), c = letters[1:4]) + y <- tibble(a = c(1, 3), b = c(99, 88)) + + expect_identical( + duckplyr_rows_patch(x, y, by = "a"), + tibble(a = c(1, 2, 1, 3), b = c(99, 3, 4, 88), c = letters[1:4]) + ) +}) + +test_that("duckplyr_rows_patch() doesn't allow `y` keys to be duplicated (#5553)", { + x <- tibble(a = 2, b = 4) + y <- tibble(a = c(1, 1), b = c(2, 3)) + + expect_snapshot((expect_error(duckplyr_rows_patch(x, y, by = "a")))) +}) + +test_that("duckplyr_rows_patch() avoids bare data.frame `drop = FALSE` issues", { + x <- vctrs::data_frame(x = c(1, 2, 3, 3), y = c(NA, 5, NA, 6)) + y <- vctrs::data_frame(x = c(1, 3), y = c(0, 100)) + + out <- duckplyr_rows_patch(x, y, "x") + expect_identical(out$y, c(0, 5, 100, 6)) +}) + +test_that("duckplyr_rows_patch() casts keys to their common type for matching but retains `x` type", { + x <- vctrs::data_frame(key = 1L, value = NA_real_) + y <- vctrs::data_frame(key = 1, value = 1.5) + + out <- duckplyr_rows_patch(x, y, "key") + expect_identical(out$key, x$key) + expect_identical(out$value, y$value) + + y <- vctrs::data_frame(key = "x", value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_patch(x, y, "key"))) + }) +}) + +test_that("duckplyr_rows_patch() casts values to the type of `x`", { + x <- vctrs::data_frame(key = 1, value = 2L) + y <- vctrs::data_frame(key = 1, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_patch(x, y, "key"))) + }) +}) + +# ------------------------------------------------------------------------------ + +test_that("duckplyr_rows_upsert() works", { + data <- tibble(a = 1:3, b = letters[c(1:2, NA)], c = 0.5 + 0:2) + + expect_identical( + duckplyr_rows_upsert(data, tibble(a = 2:4, b = "z"), by = "a"), + tibble(a = 1:4, b = c("a", "z", "z", "z"), c = c(data$c, NA)) + ) +}) + +test_that("duckplyr_rows_upsert() allows `x` keys to be duplicated (#5553)", { + x <- tibble(a = c(1, 2, 1, 3), b = c(NA, 3, 4, NA), c = letters[1:4]) + y <- tibble(a = c(1, 3, 4), b = c(99, 88, 100)) + + expect_identical( + duckplyr_rows_upsert(x, y, by = "a"), + tibble(a = c(1, 2, 1, 3, 4), b = c(99, 3, 99, 88, 100), c = c(letters[1:4], NA)) + ) +}) + +test_that("duckplyr_rows_upsert() doesn't allow `y` keys to be duplicated (#5553)", { + x <- tibble(a = 2, b = 4) + y <- tibble(a = c(1, 1), b = c(2, 3)) + + expect_snapshot((expect_error(duckplyr_rows_upsert(x, y, by = "a")))) +}) + +test_that("duckplyr_rows_upsert() avoids bare data.frame `drop = FALSE` issues", { + x <- vctrs::data_frame(x = c(1, 2, 3), y = I(list(1:2, 3:4, 5:6))) + y <- vctrs::data_frame(x = c(1, 3, 4), y = I(list(0L, 100:101, -1L))) + + out <- duckplyr_rows_upsert(x, y, "x") + expect_identical(out$y, I(list(0L, 3:4, 100:101, -1L))) +}) + +test_that("duckplyr_rows_upsert() casts keys to their common type for matching but retains `x` type", { + x <- vctrs::data_frame(key = 1L, value = 2) + y <- vctrs::data_frame(key = c(2, 1), value = c(1.5, 2.5)) + + out <- duckplyr_rows_upsert(x, y, "key") + expect_identical(out$key, c(1L, 2L)) + expect_identical(out$value, c(2.5, 1.5)) + + y <- vctrs::data_frame(key = "x", value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_upsert(x, y, "key"))) + }) +}) + +test_that("duckplyr_rows_upsert() casts keys to the type of `x`", { + x <- vctrs::data_frame(key = 1L, value = 2) + y <- vctrs::data_frame(key = 1.5, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_upsert(x, y, "key"))) + }) +}) + +test_that("duckplyr_rows_upsert() casts values to the type of `x`", { + x <- vctrs::data_frame(key = 1, value = 2L) + y <- vctrs::data_frame(key = 1, value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_upsert(x, y, "key"))) + }) +}) + +# ------------------------------------------------------------------------------ + +test_that("duckplyr_rows_delete() works", { + data <- tibble(a = 1:3, b = letters[c(1:2, NA)], c = 0.5 + 0:2) + + expect_identical( + duckplyr_rows_delete(data, tibble(a = 2:3), by = "a"), + data[1, ] + ) +}) + +test_that("duckplyr_rows_delete() ignores extra `y` columns, with a message", { + x <- tibble(a = 1) + y <- tibble(a = 1, b = 2) + + expect_snapshot({ + out <- duckplyr_rows_delete(x, y) + }) + + expect_identical(out, x[0,]) + + expect_snapshot({ + out <- duckplyr_rows_delete(x, y, by = "a") + }) + + expect_identical(out, x[0,]) +}) + +test_that("duckplyr_rows_delete() requires `y` keys to exist in `x` by default", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = c(2, 1, 3), b = c(1, 1, 1)) + + expect_snapshot((expect_error(duckplyr_rows_delete(x, y, "a")))) +}) + +test_that("duckplyr_rows_delete() allows `y` keys that don't exist in `x` to be ignored", { + x <- tibble(a = 1, b = 2) + y <- tibble(a = c(2, 1, 3)) + + expect_identical( + duckplyr_rows_delete(x, y, "a", unmatched = "ignore"), + tibble(a = double(), b = double()) + ) +}) + +test_that("duckplyr_rows_delete() allows `x` keys to be duplicated (#5553)", { + x <- tibble(a = c(1, 2, 1, 3), b = c(NA, 3, 4, NA), c = letters[1:4]) + y <- tibble(a = c(1, 3)) + + expect_identical( + duckplyr_rows_delete(x, y, by = "a"), + x[2,] + ) +}) + +test_that("duckplyr_rows_delete() allows `y` keys to be duplicated (#5553)", { + x <- tibble(a = c(1, 2, 3), b = c(4, 5, 6)) + y <- tibble(a = c(1, 1)) + + expect_identical( + duckplyr_rows_delete(x, y, by = "a"), + x[c(2, 3),] + ) +}) + +test_that("duckplyr_rows_delete() casts keys to their common type for matching but retains `x` type", { + x <- vctrs::data_frame(key = c(1L, 2L), value = c("x", "y")) + y <- vctrs::data_frame(key = 2) + + out <- duckplyr_rows_delete(x, y, "key") + expect_identical(out$key, 1L) + + y <- vctrs::data_frame(key = "x", value = 1.5) + + expect_snapshot({ + (expect_error(duckplyr_rows_delete(x, y, "key"))) + }) +}) + +# ------------------------------------------------------------------------------ +# Common errors + +test_that("rows_check_x_contains_y() checks that `y` columns are in `x`", { + x <- tibble(a = 1) + y <- tibble(a = 1, b = 2) + + expect_snapshot((expect_error(rows_check_x_contains_y(x, y)))) +}) + +test_that("rows_check_by() checks that `y` has at least 1 column before using it (#6061)", { + y <- tibble() + + expect_snapshot((expect_error(rows_check_by(by = NULL, y = y)))) +}) + +test_that("rows_check_by() uses the first column from `y` by default, with a message", { + y <- tibble(a = 1, b = 2) + + expect_snapshot( + by <- rows_check_by(by = NULL, y = y) + ) + + expect_identical(by, "a") +}) + +test_that("rows_check_by() validates `by`", { + y <- tibble(x = 1) + + expect_snapshot({ + (expect_error(rows_check_by(by = 1, y = y))) + (expect_error(rows_check_by(by = character(), y = y))) + (expect_error(rows_check_by(by = c(x = "y"), y = y))) + }) +}) + +test_that("rows_check_contains_by() checks that all `by` columns are in `x`", { + x <- tibble(x = 1) + + expect_snapshot({ + (expect_error(rows_check_contains_by(x, "y", arg = "x"))) + (expect_error(rows_check_contains_by(x, c("y", "x", "z"), arg = "y"))) + }) +}) + +test_that("rows_check_unique() requires uniqueness", { + x <- tibble(x = c(1, 1, 1), y = c(2, 3, 2), z = c(1, 2, 3)) + + expect_silent(rows_check_unique(x, "x")) + + expect_snapshot({ + (expect_error(rows_check_unique(x["x"], "x"))) + (expect_error(rows_check_unique(x[c("x", "y")], "y"))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rowwise.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rowwise.R new file mode 100644 index 000000000..16aa0ec33 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-rowwise.R @@ -0,0 +1,146 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("rowwise status preserved by major verbs", { + rf <- duckplyr_rowwise(tibble(x = 1:5, y = 5:1), "x") + + out <- duckplyr_arrange(rf, y) + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + out <- duckplyr_filter(rf, x < 3) + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + out <- duckplyr_mutate(rf, x = x + 1) + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + out <- duckplyr_rename(rf, X = x) + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "X") + + out <- duckplyr_select(rf, "x") + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + out <- duckplyr_slice(rf, c(1, 1)) + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + # Except for summarise + out <- duckplyr_summarise(rf, z = mean(x, y)) + expect_s3_class(out, "grouped_df") + expect_equal(duckplyr_group_vars(out), "x") +}) + +test_that("rowwise nature preserved by subsetting ops", { + rf <- duckplyr_rowwise(tibble(x = 1:5, y = 1:5), "x") + + out <- rf[1] + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + out[, "z"] <- 5:1 + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "x") + + names(out) <- toupper(names(out)) + expect_s3_class(out, "rowwise_df") + expect_equal(duckplyr_group_vars(out), "X") +}) + +test_that("except when it should be removed", { + rf <- duckplyr_rowwise(tibble(x = 1:5, y = 1:5), "x") + expect_equal(out <- rf[, 1, drop = TRUE], rf$x) +}) + +test_that("rowwise has decent print method", { + rf <- duckplyr_rowwise(tibble(x = 1:5), "x") + expect_snapshot(rf) +}) + +test_that("rowwise captures group_vars", { + df <- duckplyr_group_by(tibble(g = 1:2, x = 1:2), g) + rw <- duckplyr_rowwise(df) + expect_equal(duckplyr_group_vars(rw), "g") + + # but can't regroup + expect_error(duckplyr_rowwise(df, x), "Can't re-group") +}) + +test_that("can re-rowwise", { + rf1 <- duckplyr_rowwise(tibble(x = 1:5, y = 1:5), "x") + rf2 <- duckplyr_rowwise(rf1, y) + expect_equal(duckplyr_group_vars(rf2), "y") +}) + +test_that("new_rowwise_df() does not require `group_data=`", { + df <- new_rowwise_df(data.frame(x = 1:2)) + expect_s3_class(df, "rowwise_df") + expect_equal(attr(df, "groups"), tibble(".rows" := vctrs::list_of(1L, 2L))) +}) + +test_that("new_rowwise_df() can add class and attributes (#5918)", { + df <- new_rowwise_df(tibble(x = 1:4), tibble(), class = "custom_rowwise_df", a = "b") + expect_s3_class(df, "custom_rowwise_df") + expect_equal(attr(df, "a"), "b") +}) + +test_that("validate_rowwise_df() gives useful errors", { + df1 <- duckplyr_rowwise(tibble(x = 1:4, g = rep(1:2, each = 2)), g) + groups <- attr(df1, "groups") + groups[[2]] <- 4:1 + attr(df1, "groups") <- groups + + df2 <- duckplyr_rowwise(tibble(x = 1:4, g = rep(1:2, each = 2)), g) + groups <- attr(df2, "groups") + names(groups) <- c("g", "not.rows") + attr(df2, "groups") <- groups + + df3 <- df2 + attr(df3, "groups") <- tibble() + + df4 <- df3 + attr(df4, "groups") <- NA + + df7 <- duckplyr_rowwise(tibble(x = 1:10)) + attr(df7, "groups")$.rows <- 11:20 + + df8 <- duckplyr_rowwise(tibble(x = 1:10)) + + df10 <- df7 + attr(df10, "groups") <- tibble() + + df11 <- df7 + attr(df11, "groups") <- NULL + + expect_snapshot({ + (expect_error(validate_rowwise_df(df1))) + (expect_error(validate_rowwise_df(df2))) + (expect_error(validate_rowwise_df(df3))) + (expect_error(validate_rowwise_df(df4))) + (expect_error(validate_rowwise_df(df7))) + (expect_error(attr(df8, "groups")$.rows <- 1:8)) + (expect_error(validate_rowwise_df(df10))) + (expect_error(validate_rowwise_df(df11))) + + (expect_error( + new_rowwise_df( + tibble(x = 1:10), + tibble(".rows" := list(1:5, -1L)) + ) + )) + + (expect_error( + new_rowwise_df( + tibble(x = 1:10), + 1:10 + ) + )) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sample.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sample.R new file mode 100644 index 000000000..8b651f8c2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sample.R @@ -0,0 +1,156 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +# Basic behaviour ------------------------------------------------------------- + +test_that("sample preserves class", { + expect_s3_class(sample_n(mtcars, 1), "data.frame") + expect_s3_class(sample_n(as_tibble(mtcars), 1), "tbl_df") + + expect_s3_class(sample_frac(mtcars, 1), "data.frame") + expect_s3_class(sample_frac(as_tibble(mtcars), 1), "tbl_df") +}) + +# Ungrouped -------------------------------------------------------------------- + + +test_that("sample respects weight", { + df <- data.frame(x = 1:2, y = c(0, 1)) + expect_equal(sample_n(df, 1, weight = y)$x, 2) + expect_equal(sample_frac(df, 0.5, weight = y)$x, 2) +}) + +test_that("slice does not evaluate the expression in empty groups (#1438)", { + res <- mtcars %>% + duckplyr_group_by(cyl) %>% + duckplyr_filter(cyl==6) %>% + duckplyr_slice(1:2) + expect_equal(nrow(res), 2L) + + expect_error( + res <- mtcars %>% duckplyr_group_by(cyl) %>% duckplyr_filter(cyl==6) %>% sample_n(size=3), + NA + ) + expect_equal(nrow(res), 3L) +}) + +# Grouped ---------------------------------------------------------------------- + +test_that("sampling grouped tbl samples each group", { + sampled <- mtcars %>% duckplyr_group_by(cyl) %>% sample_n(2) + expect_s3_class(sampled, "grouped_df") + expect_equal(duckplyr_group_vars(sampled), "cyl") + expect_equal(nrow(sampled), 6) + expect_equal(map_int(group_rows(sampled), length), c(2,2,2)) +}) + +test_that("grouped sample respects weight", { + df2 <- tibble( + x = rep(1:2, 100), + y = rep(c(0, 1), 100), + g = rep(1:2, each = 100) + ) + + grp <- df2 %>% duckplyr_group_by(g) + + expect_equal(sample_n(grp, 1, weight = y)$x, c(2, 2)) + expect_equal(sample_frac(grp, 0.5, weight = y)$x, rep(2, nrow(df2) / 2)) +}) + +test_that("grouped sample accepts NULL weight from variable (for saeSim)", { + df <- tibble( + x = rep(1:2, 10), + y = rep(c(0, 1), 10), + g = rep(1:2, each = 10) + ) + + weight <- NULL + + expect_no_error(sample_n(df, nrow(df), weight = weight)) + expect_no_error(sample_frac(df, weight = weight)) + + grp <- df %>% duckplyr_group_by(g) + + expect_no_error(sample_n(grp, nrow(df) / 2, weight = weight)) + expect_no_error(sample_frac(grp, weight = weight)) +}) + +test_that("sample_n and sample_frac can call n() (#3413)", { + df <- tibble( + x = rep(1:2, 10), + y = rep(c(0, 1), 10), + g = rep(1:2, each = 10) + ) + gdf <- duckplyr_group_by(df, g) + + expect_equal(nrow(sample_n(df, n())), nrow(df)) + expect_equal(nrow(sample_n(gdf, n())), nrow(gdf)) + + expect_equal(nrow(sample_n(df, n() - 2L)), nrow(df) - 2) + expect_equal(nrow(sample_n(gdf, n() - 2L)), nrow(df) - 4) +}) + +test_that("sample_n and sample_frac handles lazy grouped data frames (#3380)", { + df1 <- data.frame(x = 1:10, y = rep(1:2, each=5)) + df2 <- data.frame(x = 6:15, z = 1:10) + res <- df1 %>% duckplyr_group_by(y) %>% duckplyr_anti_join(df2, by="x") %>% sample_n(1) + expect_equal(nrow(res), 1L) + + res <- df1 %>% duckplyr_group_by(y) %>% duckplyr_anti_join(df2, by="x") %>% sample_frac(0.2) + expect_equal(nrow(res), 1L) +}) + +# Errors -------------------------------------------- + +test_that("sample_*() gives meaningful error messages", { + expect_snapshot({ + df2 <- tibble( + x = rep(1:2, 100), + y = rep(c(0, 1), 100), + g = rep(1:2, each = 100) + ) + + grp <- df2 %>% duckplyr_group_by(g) + + # base R error messages + (expect_error( + sample_n(grp, nrow(df2) / 2, weight = y) + )) + (expect_error( + sample_frac(grp, 1, weight = y) + )) + + # can't sample more values than obs (without replacement) + (expect_error( + mtcars %>% duckplyr_group_by(cyl) %>% sample_n(10) + )) + + # unknown type + (expect_error( + sample_n(list()) + )) + (expect_error( + sample_frac(list()) + )) + + "# respects weight" + df <- data.frame(x = 1:2, y = c(0, 1)) + (expect_error( + sample_n(df, 2, weight = y) + )) + (expect_error( + sample_frac(df, 2) + )) + (expect_error( + sample_frac(df %>% duckplyr_group_by(y), 2) + )) + (expect_error( + sample_frac(df, 1, weight = y) + )) + }) + +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select-helpers.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select-helpers.R new file mode 100644 index 000000000..9cc1641de --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select-helpers.R @@ -0,0 +1,28 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("group_cols() selects grouping variables", { + df <- tibble(x = 1:3, y = 1:3) + gf <- duckplyr_group_by(df, x) + + expect_equal(df %>% duckplyr_select(group_cols()), df[integer()]) + expect_message( + expect_equal(gf %>% duckplyr_select(group_cols()), gf["x"]), + NA + ) +}) + +test_that("group_cols(vars) is deprecated", { + expect_warning(out <- group_cols("a"), "deprecated") + expect_equal(out, integer()) +}) + +test_that("group_cols() finds groups in scoped helpers", { + gf <- duckplyr_group_by(tibble(x = 1, y = 2), x) + out <- select_at(gf, vars(group_cols())) + expect_named(out, "x") +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select.R new file mode 100644 index 000000000..c03b356f4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-select.R @@ -0,0 +1,211 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("select preserves grouping", { + gf <- duckplyr_group_by(tibble(g = 1:3, x = 3:1), g) + + i <- count_regroups(out <- duckplyr_select(gf, h = g)) + expect_equal(i, 0) + expect_equal(duckplyr_group_vars(out), "h") +}) + +test_that("grouping variables preserved with a message, unless already selected (#1511, #5841)", { + df <- tibble(g = 1:3, x = 3:1) %>% duckplyr_group_by(g) + + expect_snapshot({ + res <- duckplyr_select(df, x) + }) + expect_named(res, c("g", "x")) + + df <- tibble(a = 1, b = 2, c = 3) %>% duckplyr_group_by(a) + expect_equal(df %>% duckplyr_select(a = b), tibble(a = 2)) + + df <- tibble(a = 1, b = 2, c = 3) %>% duckplyr_group_by(a, b) + expect_snapshot({ + expect_equal(df %>% duckplyr_select(a = c), tibble(b = 2, a = 3) %>% duckplyr_group_by(b)) + expect_equal(df %>% duckplyr_select(b = c), tibble(a = 1, b = 3) %>% duckplyr_group_by(a)) + }) +}) + +test_that("non-syntactic grouping variable is preserved (#1138)", { + expect_snapshot( + df <- tibble(`a b` = 1L) %>% duckplyr_group_by(`a b`) %>% duckplyr_select() + ) + expect_named(df, "a b") +}) + +test_that("select doesn't fail if some names missing", { + df1 <- data.frame(x = 1:10, y = 1:10, z = 1:10) + df2 <- setNames(df1, c("x", "y", "")) + # df3 <- setNames(df1, c("x", "", "")) + + expect_equal(duckplyr_select(df1, x), data.frame(x = 1:10)) + expect_equal(duckplyr_select(df2, x), data.frame(x = 1:10)) + # expect_equal(duckplyr_select(df3, x), data.frame(x = 1:10)) +}) + + +# Special cases ------------------------------------------------- + +test_that("select with no args returns nothing", { + empty <- duckplyr_select(mtcars) + expect_equal(df_n_col(empty), 0) + expect_equal(nrow(empty), 32) + + empty <- duckplyr_select(mtcars, !!!list()) + expect_equal(df_n_col(empty), 0) + expect_equal(nrow(empty), 32) +}) + +test_that("select excluding all vars returns nothing", { + expect_equal(dim(duckplyr_select(mtcars, -(mpg:carb))), c(32, 0)) + expect_equal(dim(duckplyr_select(mtcars, starts_with("x"))), c(32, 0)) + expect_equal(dim(duckplyr_select(mtcars, -matches("."))), c(32, 0)) +}) + +test_that("negating empty match returns everything", { + df <- data.frame(x = 1:3, y = 3:1) + expect_equal(duckplyr_select(df, -starts_with("xyz")), df) +}) + +test_that("can select with duplicate columns", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, x = 2, y = 1, .name_repair = "minimal") + + # can extract duplicate cols by position + expect_named(df %>% duckplyr_select(1, 3), c("x", "y")) + + # can select out non-duplicated columns + expect_named(df %>% duckplyr_select(y), "y") +}) + +# Select variables ----------------------------------------------- + +test_that("select can be before group_by (#309)", { + df <- data.frame( + id = c(1, 1, 2, 2, 2, 3, 3, 4, 4, 5), + year = c(2013, 2013, 2012, 2013, 2013, 2013, 2012, 2012, 2013, 2013), + var1 = rnorm(10) + ) + dfagg <- df %>% + duckplyr_group_by(id, year) %>% + duckplyr_select(id, year, var1) %>% + duckplyr_summarise(var1 = mean(var1)) + expect_equal(names(dfagg), c("id", "year", "var1")) +}) + + +test_that("select succeeds in presence of raw columns (#1803)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1:3, b = as.raw(1:3)) + expect_identical(duckplyr_select(df, a), df["a"]) + expect_identical(duckplyr_select(df, b), df["b"]) + expect_identical(duckplyr_select(df, -b), df["a"]) +}) + +test_that("arguments to duckplyr_select() don't match vars_select() arguments", { + df <- tibble(a = 1) + expect_identical(duckplyr_select(df, var = a), tibble(var = 1)) + expect_identical(duckplyr_select(duckplyr_group_by(df, a), var = a), duckplyr_group_by(tibble(var = 1), var)) + expect_identical(duckplyr_select(df, exclude = a), tibble(exclude = 1)) + expect_identical(duckplyr_select(df, include = a), tibble(include = 1)) + expect_identical(duckplyr_select(duckplyr_group_by(df, a), exclude = a), duckplyr_group_by(tibble(exclude = 1), exclude)) + expect_identical(duckplyr_select(duckplyr_group_by(df, a), include = a), duckplyr_group_by(tibble(include = 1), include)) +}) + +test_that("can duckplyr_select() with deprecated `.data` pronoun (#2715)", { + withr::local_options(lifecycle_verbosity = "quiet") + expect_identical(duckplyr_select(mtcars, .data$cyl), duckplyr_select(mtcars, cyl)) +}) + +test_that("can duckplyr_select() with character vectors", { + expect_identical(duckplyr_select(mtcars, "cyl", !!"disp", c("cyl", "am", "drat")), mtcars[c("cyl", "disp", "am", "drat")]) +}) + +test_that("duckplyr_select() treats NULL inputs as empty", { + expect_identical(duckplyr_select(mtcars, cyl), duckplyr_select(mtcars, NULL, cyl, NULL)) +}) + +test_that("can duckplyr_select() with strings and character vectors", { + vars <- c(foo = "cyl", bar = "am") + + expect_identical(duckplyr_select(mtcars, !!!vars), duckplyr_select(mtcars, foo = cyl, bar = am)) + expect_identical(duckplyr_select(mtcars, !!vars), duckplyr_select(mtcars, foo = cyl, bar = am)) +}) + +test_that("select works on empty names (#3601)", { + df <- data.frame(x=1, y=2, z=3) + colnames(df) <- c("x","y","") + expect_identical(duckplyr_select(df, x)$x, 1) + + colnames(df) <- c("","y","z") + expect_identical(duckplyr_select(df, y)$y, 2) +}) + +test_that("select works on NA names (#3601)", { + df <- data.frame(x=1, y=2, z=3) + colnames(df) <- c("x","y",NA) + expect_identical(duckplyr_select(df, x)$x, 1) + + colnames(df) <- c(NA,"y","z") + expect_identical(duckplyr_select(df, y)$y, 2) +}) + +test_that("duckplyr_select() keeps attributes of raw data frames (#5831)", { + df <- data.frame(x = 1) + attr(df, "a") <- "b" + expect_equal(attr(duckplyr_select(df, x), "a"), "b") +}) + +test_that("duckplyr_select() provides informative errors", { + expect_snapshot({ + (expect_error(duckplyr_select(mtcars, 1 + ""))) + }) +}) + + +# dplyr_col_select() ------------------------------------------------------ + +test_that("dplyr_col_select() aborts when `[` implementation is broken", { + local_methods( + "[.dplyr_test_broken_operator" = function(x, ...) { + unclass(x) + }, + "[.dplyr_test_operator_wrong_size" = function(x, ...) { + data.frame() + } + ) + df1 <- new_tibble(list(x = 1), nrow = 1L, class = "dplyr_test_broken_operator") + expect_snapshot({ + (expect_error( + duckplyr_select(df1, 1:2) + )) + (expect_error( + duckplyr_select(df1, 0) + )) + }) + df2 <- new_tibble(list(x = 1), nrow = 1L, class = "dplyr_test_operator_wrong_size") + expect_error(duckplyr_select(df2, 1:2)) + + expect_snapshot({ + # from vctrs + (expect_error( + duckplyr_select(df1, 2) + )) + + # not returning a data frame + (expect_error( + duckplyr_select(df1, 1) + )) + + # unexpected number of columns + (expect_error( + duckplyr_select(df2, 1) + )) + }) + +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sets.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sets.R new file mode 100644 index 000000000..3e9887a72 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-sets.R @@ -0,0 +1,149 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("x used as basis of output (#3839)", { + df1 <- tibble(x = 1:4, y = 1) + df2 <- tibble(y = 1, x = c(4, 2)) + + expect_equal(duckplyr_intersect(df1, df2), tibble(x = c(2, 4), y = 1)) + expect_equal(duckplyr_union(df1, df2), tibble(x = 1:4, y = 1)) + expect_equal(duckplyr_union_all(df1, df2), tibble(x = c(1:4, 4, 2), y = 1)) + expect_equal(duckplyr_setdiff(df1, df2), tibble(x = c(1, 3), y = 1)) + expect_equal(duckplyr_symdiff(df1, df2), tibble(x = c(1, 3), y = 1)) +}) + +test_that("set operations (apart from union_all) remove duplicates", { + df1 <- tibble(x = c(1, 1, 2)) + df2 <- tibble(x = 2) + + expect_equal(duckplyr_intersect(df1, df2), tibble(x = 2)) + expect_equal(duckplyr_union(df1, df2), tibble(x = c(1, 2))) + expect_equal(duckplyr_union_all(df1, df2), tibble(x = c(1, 1, 2, 2))) + expect_equal(duckplyr_setdiff(df1, df2), tibble(x = 1)) + expect_equal(duckplyr_symdiff(df1, df2), tibble(x = 1)) +}) + +test_that("standard coercion rules are used (#799)", { + df1 <- tibble(x = 1:2, y = c(1, 1)) + df2 <- tibble(x = 1:2, y = 1:2) + + expect_equal(nrow(duckplyr_intersect(df1, df2)), 1) + expect_equal(nrow(duckplyr_union(df1, df2)), 3) + expect_equal(nrow(duckplyr_union_all(df1, df2)), 4) + expect_equal(nrow(duckplyr_setdiff(df1, df2)), 1) + expect_equal(nrow(duckplyr_symdiff(df1, df2)), 2) +}) + +test_that("grouping metadata is reconstructed (#3587)", { + df1 <- tibble(x = 1:4, g = rep(1:2, each = 2)) %>% duckplyr_group_by(g) + df2 <- tibble(x = 3:6, g = rep(2:3, each = 2)) + + expect_equal(duckplyr_group_vars(duckplyr_intersect(df1, df2)), "g") + expect_equal(duckplyr_group_vars(duckplyr_union(df1, df2)), "g") + expect_equal(duckplyr_group_vars(duckplyr_union_all(df1, df2)), "g") + expect_equal(duckplyr_group_vars(duckplyr_setdiff(df1, df2)), "g") + expect_equal(duckplyr_group_vars(duckplyr_symdiff(df1, df2)), "g") +}) + +test_that("also work with vectors", { + expect_equal(duckplyr_intersect(1:3, 3:4), 3) + expect_equal(duckplyr_union(1:3, 3:4), 1:4) + expect_equal(duckplyr_union_all(1:3, 3:4), c(1:3, 3:4)) + expect_equal(duckplyr_setdiff(1:3, 3:4), 1:2) + expect_equal(duckplyr_symdiff(1:3, 3:4), c(1, 2, 4)) + # removes duplicates + expect_equal(duckplyr_symdiff(c(1, 1, 2), c(2, 2, 3)), c(1, 3)) +}) + +test_that("extra arguments in ... error (#5891)", { + df1 <- tibble(var = 1:3) + df2 <- tibble(var = 2:4) + + expect_snapshot(error = TRUE, { + duckplyr_intersect(df1, df2, z = 3) + duckplyr_union(df1, df2, z = 3) + duckplyr_union_all(df1, df2, z = 3) + duckplyr_setdiff(df1, df2, z = 3) + duckplyr_symdiff(df1, df2, z = 3) + }) +}) + +test_that("incompatible data frames error (#903)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1) + df2 <- tibble(x = 1, y = 1) + + expect_snapshot(error = TRUE, { + duckplyr_intersect(df1, df2) + duckplyr_union(df1, df2) + duckplyr_union_all(df1, df2) + duckplyr_setdiff(df1, df2) + duckplyr_symdiff(df1, df2) + }) +}) + +test_that("is_compatible generates useful messages for different cases", { + expect_snapshot({ + cat(is_compatible(tibble(x = 1), 1)) + cat(is_compatible(tibble(x = 1), tibble(x = 1, y = 2))) + cat(is_compatible(tibble(x = 1, y = 1), tibble(y = 1, x = 1), ignore_col_order = FALSE)) + cat(is_compatible(tibble(x = 1), tibble(y = 1))) + cat(is_compatible(tibble(x = 1), tibble(x = 1L), convert = FALSE)) + cat(is_compatible(tibble(x = 1), tibble(x = "a"))) + }) +}) + +# setequal ---------------------------------------------------------------- + +test_that("setequal ignores column and row order", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1:2, y = 3:4) + df2 <- df1[2:1, 2:1] + + expect_true(duckplyr_setequal(df1, df2)) + expect_true(duckplyr_setequal(df1, df2)) +}) + +test_that("setequal ignores duplicated rows (#6057)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1) + df2 <- df1[c(1, 1, 1), ] + + expect_true(duckplyr_setequal(df1, df2)) + expect_true(duckplyr_setequal(df2, df1)) +}) + +test_that("setequal uses coercion rules (#6114)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df1 <- tibble(x = 1) + df2 <- tibble(x = 1L) + + expect_true(duckplyr_setequal(df1, df2)) + expect_true(duckplyr_setequal(df2, df1)) +}) + +test_that("setequal tibbles must have same rows and columns", { + # Different rows are the definition of not equal + expect_false(duckplyr_setequal(tibble(x = 1:2), tibble(x = 2:3))) + + # Different or incompatible columns are an error, like the other set ops (#6786) + expect_snapshot(error = TRUE, { + duckplyr_setequal(tibble(x = 1:2), tibble(y = 1:2)) + }) + expect_snapshot(error = TRUE, { + duckplyr_setequal(tibble(x = 1:2), tibble(x = c("a", "b"))) + }) +}) + +test_that("setequal checks y is a data frame", { + expect_snapshot(duckplyr_setequal(mtcars, 1), error = TRUE) +}) + +test_that("setequal checks for extra arguments", { + expect_snapshot(duckplyr_setequal(mtcars, mtcars, z = 2), error = TRUE) +}) + diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-slice.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-slice.R new file mode 100644 index 000000000..8af1b6180 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-slice.R @@ -0,0 +1,667 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("empty slice drops all rows (#6573)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2), x = 1:3) + gdf <- duckplyr_group_by(df, g) + rdf <- duckplyr_rowwise(df) + + expect_identical(duckplyr_slice(df), df[integer(),]) + expect_identical(duckplyr_slice(gdf), gdf[integer(),]) + expect_identical(duckplyr_slice(rdf), rdf[integer(),]) +}) + +test_that("slicing data.frame yields data.frame", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1:3) + expect_equal(duckplyr_slice(df, 1), data.frame(x = 1L)) +}) + +test_that("slice keeps positive indices, ignoring out of range (#226)", { + gf <- duckplyr_group_by(tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6), g) + + out <- duckplyr_slice(gf, 1) + expect_equal(out$id, c(1, 2, 4)) + + out <- duckplyr_slice(gf, 2) + expect_equal(out$id, c(3, 5)) +}) + +test_that("slice drops negative indices, ignoring out of range (#3073)", { + gf <- duckplyr_group_by(tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6), g) + + out <- duckplyr_slice(gf, -1) + expect_equal(out$id, c(3, 5, 6)) + + out <- duckplyr_slice(gf, -(1:2)) + expect_equal(out$id, 6) +}) + +test_that("slice errors if positive and negative indices mixed", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot(error = TRUE, { + duckplyr_slice(tibble(), 1, -1) + }) +}) + +test_that("slice ignores 0 and NA (#3313, #1235)", { + gf <- duckplyr_group_by(tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6), g) + + out <- duckplyr_slice(gf, 0) + expect_equal(out$id, integer()) + out <- duckplyr_slice(gf, 0, 1) + expect_equal(out$id, c(1, 2, 4)) + + out <- duckplyr_slice(gf, NA) + expect_equal(out$id, integer()) + out <- duckplyr_slice(gf, NA, -1) + expect_equal(out$id, c(3, 5, 6)) +}) + +test_that("slicing with one-column matrix is deprecated", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6) + + expect_snapshot({ + out <- duckplyr_slice(df, matrix(c(1, 3))) + }) + expect_equal(out$id, c(1, 3)) +}) + +test_that("slice errors if index is not numeric", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot(error = TRUE, { + duckplyr_slice(tibble(), "a") + }) +}) + +test_that("slice preserves groups iff requested", { + gf <- duckplyr_group_by(tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6), g) + + out <- duckplyr_slice(gf, 2, 3) + expect_equal(duckplyr_group_keys(out), tibble(g = c(2, 3))) + expect_equal(group_rows(out), list_of(1, c(2, 3))) + + out <- duckplyr_slice(gf, 2, 3, .preserve = TRUE) + expect_equal(duckplyr_group_keys(out), tibble(g = c(1, 2, 3))) + expect_equal(group_rows(out), list_of(integer(), 1, c(2, 3))) +}) + +test_that("slice handles zero-row and zero-column inputs (#1219, #2490)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = numeric()) + expect_equal(duckplyr_slice(df, 1), df) + + df <- tibble(.rows = 10) + expect_equal(duckplyr_slice(df, 1), tibble(.rows = 1)) +}) + +test_that("user errors are correctly labelled", { + df <- tibble(x = 1:3) + expect_snapshot(error = TRUE, { + duckplyr_slice(df, 1 + "") + duckplyr_slice(duckplyr_group_by(df, x), 1 + "") + }) +}) + +test_that("`...` can't be named (#6554)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = 1, x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_slice(df, 1, foo = g) + }) +}) + +test_that("slice keeps zero length groups", { + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal(duckplyr_group_size(duckplyr_slice(df, 1)), c(1, 1, 0) ) +}) + +test_that("slicing retains labels for zero length groups", { + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal( + duckplyr_ungroup(duckplyr_count(duckplyr_slice(df, 1))), + tibble( + e = 1, + f = factor(1:3), + g = c(1, 2, NA), + n = c(1L, 1L, 0L) + ) + ) +}) + +test_that("can group transiently using `.by`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2), x = c(1, 2, 3)) + + out <- duckplyr_slice(df, n(), .by = g) + + expect_identical(out$g, c(1, 2)) + expect_identical(out$x, c(2, 3)) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains bare data.frame class", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2), x = c(1, 2, 3)) + out <- duckplyr_slice(df, n(), .by = g) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains data frame attributes", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # With data.frames or tibbles + df <- data.frame(g = c(1, 1, 2), x = c(1, 2, 3)) + tbl <- as_tibble(df) + + attr(df, "foo") <- "bar" + attr(tbl, "foo") <- "bar" + + out <- duckplyr_slice(df, n(), .by = g) + expect_identical(attr(out, "foo"), "bar") + + out <- duckplyr_slice(tbl, n(), .by = g) + expect_identical(attr(out, "foo"), "bar") +}) + +test_that("transient grouping orders by first appearance", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(2, 1, 2, 0), x = c(4, 2, 8, 5)) + + out <- duckplyr_slice(df, which(x == max(x)), .by = g) + + expect_identical(out$g, c(2, 1, 0)) + expect_identical(out$x, c(8, 2, 5)) +}) + +test_that("can't use `.by` with `.preserve`", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_slice(df, .by = x, .preserve = TRUE) + }) +}) + +test_that("catches `.by` with grouped-df", { + df <- tibble(x = 1) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + duckplyr_slice(gdf, .by = x) + }) +}) + +test_that("catches `.by` with rowwise-df", { + df <- tibble(x = 1) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + duckplyr_slice(rdf, .by = x) + }) +}) + +test_that("catches `by` typo (#6647)", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_slice(df, by = x) + }) +}) + +# Slice variants ---------------------------------------------------------- + +test_that("slice_helpers() call get_slice_size()", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_slice_head(df, n = "a") + duckplyr_slice_tail(df, n = "a") + slice_min(df, x, n = "a") + slice_max(df, x, n = "a") + duckplyr_slice_sample(df, n= "a") + }) +}) + +test_that("get_slice_size() validates its inputs", { + expect_snapshot(error = TRUE, { + get_slice_size(n = 1, prop = 1) + get_slice_size(n = "a") + get_slice_size(prop = "a") + }) +}) + +test_that("get_slice_size() snapshots", { + expect_snapshot({ + body(get_slice_size(prop = 0)) + + body(get_slice_size(prop = 0.4)) + body(get_slice_size(prop = 2)) + body(get_slice_size(prop = 2, allow_outsize = TRUE)) + + body(get_slice_size(prop = -0.4)) + body(get_slice_size(prop = -2)) + + body(get_slice_size(n = 0)) + + body(get_slice_size(n = 4)) + body(get_slice_size(n = 20)) + body(get_slice_size(n = 20, allow_outsize = TRUE)) + + body(get_slice_size(n = -4)) + body(get_slice_size(n = -20)) + }) +}) + +test_that("get_slice_size() standardises prop", { + expect_equal(get_slice_size(prop = 0)(10), 0) + + expect_equal(get_slice_size(prop = 0.4)(10), 4) + expect_equal(get_slice_size(prop = 2)(10), 10) + expect_equal(get_slice_size(prop = 2, allow_outsize = TRUE)(10), 20) + + expect_equal(get_slice_size(prop = -0.4)(10), 6) + expect_equal(get_slice_size(prop = -2)(10), 0) +}) + +test_that("get_slice_size() standardises n", { + expect_equal(get_slice_size(n = 0)(10), 0) + + expect_equal(get_slice_size(n = 4)(10), 4) + expect_equal(get_slice_size(n = 20)(10), 10) + expect_equal(get_slice_size(n = 20, allow_outsize = TRUE)(10), 20) + + expect_equal(get_slice_size(n = -4)(10), 6) + expect_equal(get_slice_size(n = -20)(10), 0) +}) + +test_that("get_slice_size() rounds prop in the right direction", { + expect_equal(get_slice_size(prop = 0.16)(10), 1) + expect_equal(get_slice_size(prop = -0.16)(10), 9) +}) + +test_that("n must be an integer", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:5) + expect_snapshot(duckplyr_slice_head(df, n = 1.1), error = TRUE) +}) + +test_that("functions silently truncate results", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # only test positive n because get_slice_size() converts all others + + df <- tibble(x = 1:5) + expect_equal(nrow(duckplyr_slice_head(df, n = 6)), 5) + expect_equal(nrow(duckplyr_slice_tail(df, n = 6)), 5) + expect_equal(nrow(slice_min(df, x, n = 6)), 5) + expect_equal(nrow(slice_max(df, x, n = 6)), 5) + expect_equal(nrow(duckplyr_slice_sample(df, n = 6)), 5) +}) + +test_that("slice helpers with n = 0 return no rows", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:5) + expect_equal(nrow(duckplyr_slice_head(df, n = 0)), 0) + expect_equal(nrow(duckplyr_slice_tail(df, n = 0)), 0) + expect_equal(nrow(slice_min(df, x, n = 0)), 0) + expect_equal(nrow(slice_max(df, x, n = 0)), 0) + expect_equal(nrow(duckplyr_slice_sample(df, n = 0)), 0) +}) + +test_that("slice_*() doesn't look for `n` in data (#6089)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1:10, n = 10:1, g = rep(1:2, each = 5)) + expect_error(slice_max(df, order_by = n), NA) + expect_error(slice_min(df, order_by = n), NA) + expect_error(duckplyr_slice_sample(df, weight_by = n, n = 1L), NA) + + df <- duckplyr_group_by(df, g) + expect_error(slice_max(df, order_by = n), NA) + expect_error(slice_min(df, order_by = n), NA) + expect_error(duckplyr_slice_sample(df, weight_by = n, n = 1L), NA) +}) + +test_that("slice_*() checks that `n=` is explicitly named and ... is empty", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # i.e. that every function calls check_slice_dots() + df <- data.frame(x = 1:10) + + expect_snapshot(error = TRUE, { + duckplyr_slice_head(df, 5) + duckplyr_slice_tail(df, 5) + slice_min(df, x, 5) + slice_max(df, x, 5) + duckplyr_slice_sample(df, 5) + }) + + # And works with namespace prefix (#6946) + expect_snapshot(error = TRUE, { + dplyr::duckplyr_slice_head(df, 5) + dplyr::duckplyr_slice_tail(df, 5) + dplyr::slice_min(df, x, 5) + dplyr::slice_max(df, x, 5) + dplyr::duckplyr_slice_sample(df, 5) + }) + + expect_snapshot(error = TRUE, { + duckplyr_slice_head(df, 5, 2) + duckplyr_slice_tail(df, 5, 2) + slice_min(df, x, 5, 2) + slice_max(df, x, 5, 2) + duckplyr_slice_sample(df, 5, 2) + }) +}) + +test_that("slice_helpers do call duckplyr_slice() and benefit from dispatch (#6084)", { + local_methods( + slice.noisy = function(.data, ..., .preserve = FALSE) { + warning("noisy") + NextMethod() + } + ) + + nf <- tibble(x = 1:10, g = rep(1:2, each = 5)) %>% duckplyr_group_by(g) + class(nf) <- c("noisy", class(nf)) + + expect_warning(duckplyr_slice(nf, 1:2), "noisy") + expect_warning(duckplyr_slice_sample(nf, n = 2), "noisy") + expect_warning(duckplyr_slice_head(nf, n = 2), "noisy") + expect_warning(duckplyr_slice_tail(nf, n = 2), "noisy") + expect_warning(slice_min(nf, x, n = 2), "noisy") + expect_warning(slice_max(nf, x, n = 2), "noisy") + expect_warning(sample_n(nf, 2), "noisy") + expect_warning(sample_frac(nf, .5), "noisy") +}) + +test_that("slice_helper `by` errors use correct error context and correct `by_arg`", { + df <- tibble(x = 1) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + duckplyr_slice_head(gdf, n = 1, by = x) + duckplyr_slice_tail(gdf, n = 1, by = x) + slice_min(gdf, order_by = x, by = x) + slice_max(gdf, order_by = x, by = x) + duckplyr_slice_sample(gdf, n = 1, by = x) + }) +}) + +test_that("slice_helper catches `.by` typo (#6647)", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_slice_head(df, n = 1, .by = x) + duckplyr_slice_tail(df, n = 1, .by = x) + slice_min(df, order_by = x, .by = x) + slice_max(df, order_by = x, .by = x) + duckplyr_slice_sample(df, n = 1, .by = x) + }) +}) + +# slice_min/slice_max ----------------------------------------------------- + +test_that("min and max return ties by default", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(id = 1:5, x = c(1, 1, 1, 2, 2)) + expect_equal(slice_min(df, x)$id, c(1, 2, 3)) + expect_equal(slice_max(df, x)$id, c(4, 5)) + + expect_equal(slice_min(df, x, with_ties = FALSE)$id, 1) + expect_equal(slice_max(df, x, with_ties = FALSE)$id, 4) +}) + +test_that("min and max reorder results", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(id = 1:4, x = c(2, 3, 1, 2)) + + expect_equal(slice_min(df, x, n = 2)$id, c(3, 1, 4)) + expect_equal(slice_max(df, x, n = 2)$id, c(2, 1, 4)) + + expect_equal(slice_min(df, x, n = 2, with_ties = FALSE)$id, c(3, 1)) + expect_equal(slice_max(df, x, n = 2, with_ties = FALSE)$id, c(2, 1)) +}) + +test_that("min and max include NAs when appropriate", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(id = 1:3, x = c(1, NA, NA)) + expect_equal(slice_min(df, x, n = 1)$id, 1) + expect_equal(slice_max(df, x, n = 1)$id, 1) + + expect_equal(slice_min(df, x, n = 2)$id, c(1, 2, 3)) + expect_equal(slice_min(df, x, n = 2, with_ties = FALSE)$id, c(1, 2)) + + df <- tibble(id = 1:4, x = NA) + expect_equal(slice_min(df, x, n = 2, na_rm = TRUE)$id, integer()) + expect_equal(slice_max(df, x, n = 2, na_rm = TRUE)$id, integer()) +}) + +test_that("min and max ignore NA's when requested (#4826)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(id = 1:4, x = c(2, NA, 1, 2)) + expect_equal(slice_min(df, x, n = 2, na_rm = TRUE)$id, c(3, 1, 4)) + expect_equal(slice_max(df, x, n = 2, na_rm = TRUE)$id, c(1, 4)) + + # Check with list to confirm use full vctrs support + df <- tibble(id = 1:4, x = list(NULL, 1, NULL, NULL)) + expect_equal(slice_min(df, x, n = 2, na_rm = TRUE)$id, 2) + expect_equal(slice_max(df, x, n = 2, na_rm = TRUE)$id, 2) + + # Drop when any element is missing + df <- tibble(id = 1:3, a = c(1, 2, NA), b = c(2, NA, NA)) + expect_equal(slice_min(df, tibble(a, b), n = 3, na_rm = TRUE)$id, 1) + expect_equal(slice_max(df, tibble(a, b), n = 3, na_rm = TRUE)$id, 1) +}) + +test_that("slice_min/max() count from back with negative n/prop", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(id = 1:4, x = c(2, 3, 1, 4)) + expect_equal(slice_min(df, x, n = -1), slice_min(df, x, n = 3)) + expect_equal(slice_max(df, x, n = -1), slice_max(df, x, n = 3)) + + # and can be larger than group size + expect_equal(slice_min(df, x, n = -10), df[0, ]) + expect_equal(slice_max(df, x, n = -10), df[0, ]) +}) + +test_that("slice_min/max() can order by multiple variables (#6176)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(id = 1:4, x = 1, y = c(1, 4, 2, 3)) + expect_equal(slice_min(df, tibble(x, y), n = 1)$id, 1) + expect_equal(slice_max(df, tibble(x, y), n = 1)$id, 2) +}) + +test_that("slice_min/max() work with `by`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(2, 2, 1, 1), x = c(1, 2, 3, 1)) + + expect_identical(slice_min(df, x, by = g), df[c(1, 4),]) + expect_identical(slice_max(df, x, by = g), df[c(2, 3),]) +}) + +test_that("slice_min/max() inject `with_ties` and `na_rm` (#6725)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # So columns named `with_ties` and `na_rm` don't mask those arguments + + df <- tibble(x = c(1, 1, 2, 2), with_ties = 1:4) + + expect_identical(slice_min(df, x, n = 1), vec_slice(df, 1:2)) + expect_identical(slice_min(df, x, n = 1, with_ties = FALSE), vec_slice(df, 1)) + + expect_identical(slice_max(df, x, n = 1), vec_slice(df, 3:4)) + expect_identical(slice_max(df, x, n = 1, with_ties = FALSE), vec_slice(df, 3)) + + df <- tibble(x = c(1, NA), na_rm = 1:2) + + expect_identical(slice_min(df, x, n = 2), df) + expect_identical(slice_min(df, x, n = 2, na_rm = TRUE), vec_slice(df, 1)) + + expect_identical(slice_max(df, x, n = 2), df) + expect_identical(slice_max(df, x, n = 2, na_rm = TRUE), vec_slice(df, 1)) +}) + +test_that("slice_min/max() check size of `order_by=` (#5922)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_snapshot(error = TRUE, { + slice_min(data.frame(x = 1:10), 1:6) + slice_max(data.frame(x = 1:10), 1:6) + }) +}) + +test_that("slice_min/max() validate simple arguments", { + expect_snapshot(error = TRUE, { + slice_min(data.frame(x = 1:10)) + slice_max(data.frame(x = 1:10)) + + slice_min(data.frame(x = 1:10), x, with_ties = 1) + slice_max(data.frame(x = 1:10), x, with_ties = 1) + + slice_min(data.frame(x = 1:10), x, na_rm = 1) + slice_max(data.frame(x = 1:10), x, na_rm = 1) + }) +}) + +# slice_sample ------------------------------------------------------------ + +test_that("duckplyr_slice_sample() respects weight_by and replaces", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:100, wt = c(1, rep(0, 99))) + + out <- duckplyr_slice_sample(df, n = 1, weight_by = wt) + expect_equal(out$x, 1) + + out <- duckplyr_slice_sample(df, n = 2, weight_by = wt, replace = TRUE) + expect_equal(out$x, c(1, 1)) +}) + +test_that("duckplyr_slice_sample() can increase rows iff replace = TRUE", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:10) + expect_equal(nrow(duckplyr_slice_sample(df, n = 20, replace = FALSE)), 10) + expect_equal(nrow(duckplyr_slice_sample(df, n = 20, replace = TRUE)), 20) +}) + +test_that("duckplyr_slice_sample() checks size of `weight_by=` (#5922)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:10) + expect_snapshot(duckplyr_slice_sample(df, n = 2, weight_by = 1:6), error = TRUE) +}) + +test_that("duckplyr_slice_sample() works with zero-row data frame (#5729)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = character(), w = numeric()) + out <- duckplyr_slice_sample(df, prop = 0.5, weight_by = w) + expect_equal(out, df) +}) + +test_that("`duckplyr_slice_sample()` validates `replace`", { + df <- tibble() + expect_snapshot(error = TRUE, { + duckplyr_slice_sample(df, replace = 1) + duckplyr_slice_sample(df, replace = NA) + }) +}) + +test_that("duckplyr_slice_sample() injects `replace` (#6725)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + # So a column named `replace` doesn't mask that argument + df <- tibble(replace = 1) + expect_identical(duckplyr_slice_sample(df, n = 2), df) + expect_identical(duckplyr_slice_sample(df, n = 2, replace = TRUE), vec_slice(df, c(1, 1))) +}) + +test_that("duckplyr_slice_sample() handles positive n= and prop=", { + gf <- duckplyr_group_by(tibble(a = 1, b = 1), a) + expect_equal(duckplyr_slice_sample(gf, n = 3, replace = TRUE), gf[c(1, 1, 1), ]) + expect_equal(duckplyr_slice_sample(gf, prop = 3, replace = TRUE), gf[c(1, 1, 1), ]) +}) + +test_that("duckplyr_slice_sample() handles negative n= and prop= (#6402)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1:2) + expect_equal(nrow(duckplyr_slice_sample(df, n = -1)), 1) + expect_equal(nrow(duckplyr_slice_sample(df, prop = -0.5)), 1) + + # even if larger than n + expect_equal(nrow(duckplyr_slice_sample(df, n = -3)), 0) + expect_equal(nrow(duckplyr_slice_sample(df, prop = -2)), 0) +}) + +test_that("duckplyr_slice_sample() works with `by`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(2, 2, 2, 1), x = c(1, 2, 3, 1)) + expect_identical(duckplyr_slice_sample(df, n = 2, by = g)$g, c(2, 2, 1)) +}) + +# slice_head/slice_tail --------------------------------------------------- + +test_that("slice_head/slice_tail keep positive values", { + gf <- duckplyr_group_by(tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6), g) + + expect_equal(duckplyr_slice_head(gf, n = 1)$id, c(1, 2, 4)) + expect_equal(duckplyr_slice_head(gf, n = 2)$id, c(1, 2, 3, 4, 5)) + + expect_equal(duckplyr_slice_tail(gf, n = 1)$id, c(1, 3, 6)) + expect_equal(duckplyr_slice_tail(gf, n = 2)$id, c(1, 2, 3, 5, 6)) +}) + +test_that("slice_head/tail() count from back with negative n/prop", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(id = 1:4, x = c(2, 3, 1, 4)) + expect_equal(duckplyr_slice_head(df, n = -1), duckplyr_slice_head(df, n = 3)) + expect_equal(duckplyr_slice_tail(df, n = -1), duckplyr_slice_tail(df, n = 3)) + + # and can be larger than group size + expect_equal(duckplyr_slice_head(df, n = -10), df[0, ]) + expect_equal(duckplyr_slice_tail(df, n = -10), df[0, ]) +}) + +test_that("slice_head/slice_tail drop from opposite end when n/prop negative", { + gf <- duckplyr_group_by(tibble(g = c(1, 2, 2, 3, 3, 3), id = 1:6), g) + + expect_equal(duckplyr_slice_head(gf, n = -1)$id, c(2, 4, 5)) + expect_equal(duckplyr_slice_head(gf, n = -2)$id, 4) + + expect_equal(duckplyr_slice_tail(gf, n = -1)$id, c(3, 5, 6)) + expect_equal(duckplyr_slice_tail(gf, n = -2)$id, 6) +}) + +test_that("slice_head/slice_tail handle infinite n/prop", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1) + expect_identical(duckplyr_slice_head(df, n = Inf), df) + expect_identical(duckplyr_slice_tail(df, n = Inf), df) + expect_identical(duckplyr_slice_head(df, n = -Inf), df[0, ]) + expect_identical(duckplyr_slice_tail(df, n = -Inf), df[0, ]) + + expect_identical(duckplyr_slice_head(df, prop = Inf), df) + expect_identical(duckplyr_slice_tail(df, prop = Inf), df) + expect_identical(duckplyr_slice_head(df, prop = -Inf), df[0, ]) + expect_identical(duckplyr_slice_tail(df, prop = -Inf), df[0, ]) +}) + +test_that("slice_head/slice_tail work with `by`", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(2, 2, 2, 1), x = c(1, 2, 3, 1)) + expect_identical(duckplyr_slice_head(df, n = 2, by = g), df[c(1, 2, 4),]) + expect_identical(duckplyr_slice_tail(df, n = 2, by = g), df[c(2, 3, 4),]) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-summarise.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-summarise.R new file mode 100644 index 000000000..4a25f4f28 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-summarise.R @@ -0,0 +1,592 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("can use freshly create variables (#138)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:10) + out <- duckplyr_summarise(df, y = mean(x), z = y + 1) + expect_equal(out$y, 5.5) + expect_equal(out$z, 6.5) +}) + +test_that("inputs are recycled (deprecated in 1.1.0)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "quiet") + + expect_equal( + tibble() %>% duckplyr_summarise(x = 1, y = 1:3, z = 1), + tibble(x = 1, y = 1:3, z = 1) + ) + + gf <- duckplyr_group_by(tibble(a = 1:2), a) + expect_equal( + gf %>% duckplyr_summarise(x = 1, y = 1:3, z = 1), + tibble(a = rep(1:2, each = 3), x = 1, y = c(1:3, 1:3), z = 1) %>% duckplyr_group_by(a) + ) + expect_equal( + gf %>% duckplyr_summarise(x = seq_len(a), y = 1), + tibble(a = c(1L, 2L, 2L), x = c(1L, 1L, 2L), y = 1) %>% duckplyr_group_by(a) + ) +}) + +test_that("works with empty data frames", { + skip("TODO duckdb") + # 0 rows + df <- tibble(x = integer()) + expect_equal(duckplyr_summarise(df), tibble(.rows = 1)) + expect_equal(duckplyr_summarise(df, n = n(), sum = sum(x)), tibble(n = 0, sum = 0)) + + # 0 cols + df <- tibble(.rows = 10) + expect_equal(duckplyr_summarise(df), tibble(.rows = 1)) + expect_equal(duckplyr_summarise(df, n = n()), tibble(n = 10)) +}) + +test_that("works with grouped empty data frames", { + df <- tibble(x = integer()) + + expect_equal( + df %>% duckplyr_group_by(x) %>% duckplyr_summarise(y = 1L), + tibble(x = integer(), y = integer()) + ) + expect_equal( + df %>% duckplyr_rowwise(x) %>% duckplyr_summarise(y = 1L), + duckplyr_group_by(tibble(x = integer(), y = integer()), x) + ) +}) + +test_that("no expressions yields grouping data", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1:2, y = 1:2) + gf <- duckplyr_group_by(df, x) + + expect_equal(duckplyr_summarise(df), tibble(.rows = 1)) + expect_equal(duckplyr_summarise(gf), tibble(x = 1:2)) + + expect_equal(duckplyr_summarise(df, !!!list()), tibble(.rows = 1)) + expect_equal(duckplyr_summarise(gf, !!!list()), tibble(x = 1:2)) +}) + +test_that("doesn't preserve attributes", { + df <- structure( + data.frame(x = 1:10, g1 = rep(1:2, each = 5), g2 = rep(1:5, 2)), + meta = "this is important" + ) + + out <- df %>% duckplyr_summarise(n = n()) + expect_null(attr(out, "res")) + + out <- df %>% duckplyr_group_by(g1) %>% duckplyr_summarise(n = n()) + expect_null(attr(out, "res")) +}) + +test_that("strips off subclass", { + # We consider the data frame returned by `duckplyr_summarise()` to be + # "fundamentally a new data frame" + + df <- new_data_frame(list(a = 1), class = "myclass") + out <- df %>% duckplyr_summarise(n = n()) + expect_s3_class(out, "data.frame", exact = TRUE) + out <- df %>% duckplyr_summarise(.by = a, n = n()) + expect_s3_class(out, "data.frame", exact = TRUE) + + df <- new_tibble(list(a = 1), class = "myclass") + out <- df %>% duckplyr_summarise(n = n()) + expect_s3_class(out, class(tibble()), exact = TRUE) + out <- df %>% duckplyr_summarise(.by = a, n = n()) + expect_s3_class(out, class(tibble()), exact = TRUE) + + gdf <- duckplyr_group_by(tibble(a = 1), a) + df <- gdf + class(df) <- c("myclass", class(gdf)) + out <- df %>% duckplyr_summarise(n = n(), .groups = "drop") + expect_s3_class(out, class(tibble()), exact = TRUE) + out <- df %>% duckplyr_summarise(n = n(), .groups = "keep") + expect_s3_class(out, class(gdf), exact = TRUE) +}) + +test_that("works with unquoted values", { + df <- tibble(g = c(1, 1, 2, 2, 2), x = 1:5) + expect_equal(duckplyr_summarise(df, out = !!1), tibble(out = 1)) + expect_equal(duckplyr_summarise(df, out = !!quo(1)), tibble(out = 1)) +}) + +test_that("formulas are evaluated in the right environment (#3019)", { + out <- mtcars %>% duckplyr_summarise(fn = list(rlang::as_function(~ list(~foo, environment())))) + out <- out$fn[[1]]() + expect_identical(environment(out[[1]]), out[[2]]) +}) + +test_that("unnamed data frame results with 0 columns are ignored (#5084)", { + df1 <- tibble(x = 1:2) + expect_equal(df1 %>% duckplyr_group_by(x) %>% duckplyr_summarise(data.frame()), df1) + expect_equal(df1 %>% duckplyr_group_by(x) %>% duckplyr_summarise(data.frame(), y = 65), duckplyr_mutate(df1, y = 65)) + expect_equal(df1 %>% duckplyr_group_by(x) %>% duckplyr_summarise(y = 65, data.frame()), duckplyr_mutate(df1, y = 65)) + + df2 <- tibble(x = 1:2, y = 3:4) + expect_equal(df2 %>% duckplyr_group_by(x) %>% duckplyr_summarise(data.frame()), df1) + expect_equal(df2 %>% duckplyr_group_by(x) %>% duckplyr_summarise(data.frame(), z = 98), duckplyr_mutate(df1, z = 98)) + expect_equal(df2 %>% duckplyr_group_by(x) %>% duckplyr_summarise(z = 98, data.frame()), duckplyr_mutate(df1, z = 98)) + + # This includes unnamed data frames that have 0 columns but >0 rows. + # Noted when working on (#6509). + empty3 <- new_tibble(list(), nrow = 3L) + expect_equal(df1 %>% duckplyr_summarise(empty3), new_tibble(list(), nrow = 1L)) + expect_equal(df1 %>% duckplyr_summarise(empty3, y = mean(x)), df1 %>% duckplyr_summarise(y = mean(x))) + expect_equal(df1 %>% duckplyr_group_by(x) %>% duckplyr_summarise(empty3), df1) + expect_equal(df1 %>% duckplyr_group_by(x) %>% duckplyr_summarise(empty3, y = x + 1), duckplyr_mutate(df1, y = x + 1)) +}) + +test_that("named data frame results with 0 columns participate in recycling (#6509)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "quiet") + + df <- tibble(x = 1:3) + gdf <- duckplyr_group_by(df, x) + + empty <- tibble() + expect_identical(duckplyr_summarise(df, empty = empty), tibble(empty = empty)) + expect_identical(duckplyr_summarise(df, x = sum(x), empty = empty), tibble(x = integer(), empty = empty)) + expect_identical(duckplyr_summarise(df, empty = empty, x = sum(x)), tibble(empty = empty, x = integer())) + + empty3 <- new_tibble(list(), nrow = 3L) + expect_identical(duckplyr_summarise(df, empty = empty3), tibble(empty = empty3)) + expect_identical(duckplyr_summarise(df, x = sum(x), empty = empty3), tibble(x = c(6L, 6L, 6L), empty = empty3)) + expect_identical(duckplyr_summarise(df, empty = empty3, x = sum(x)), tibble(empty = empty3, x = c(6L, 6L, 6L))) + + expect_identical( + duckplyr_summarise(gdf, empty = empty, .groups = "drop"), + tibble(x = integer(), empty = empty) + ) + expect_identical( + duckplyr_summarise(gdf, y = x + 1L, empty = empty, .groups = "drop"), + tibble(x = integer(), y = integer(), empty = empty) + ) + expect_identical( + duckplyr_summarise(gdf, empty = empty3, .groups = "drop"), + tibble(x = vec_rep_each(1:3, 3), empty = vec_rep(empty3, 3)) + ) + expect_identical( + duckplyr_summarise(gdf, y = x + 1L, empty = empty3, .groups = "drop"), + tibble(x = vec_rep_each(1:3, 3), y = vec_rep_each(2:4, 3), empty = vec_rep(empty3, 3)) + ) +}) + +test_that("can't overwrite column active bindings (#6666)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + skip_if(getRversion() < "3.6.3", message = "Active binding error changed") + + df <- tibble(g = c(1, 1, 2, 2), x = 1:4) + gdf <- duckplyr_group_by(df, g) + + # The error seen here comes from trying to `<-` to an active binding when + # the active binding function has 0 arguments. + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, y = { + x <<- x + 2L + mean(x) + }) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, .by = g, y = { + x <<- x + 2L + mean(x) + }) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(gdf, y = { + x <<- x + 2L + mean(x) + }) + }) +}) + +test_that("assigning with `<-` doesn't affect the mask (#6666)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(g = c(1, 1, 2, 2), x = 1:4) + gdf <- duckplyr_group_by(df, g) + + out <- duckplyr_summarise(df, .by = g, y = { + x <- x + 4L + mean(x) + }) + expect_identical(out$y, c(5.5, 7.5)) + + out <- duckplyr_summarise(gdf, y = { + x <- x + 4L + mean(x) + }) + expect_identical(out$y, c(5.5, 7.5)) +}) + +test_that("duckplyr_summarise() correctly auto-names expressions (#6741)", { + df <- tibble(a = 1:3) + expect_identical(duckplyr_summarise(df, min(-a)), tibble("min(-a)" = -3L)) +}) + +# grouping ---------------------------------------------------------------- + +test_that("peels off a single layer of grouping", { + df <- tibble(x = rep(1:4, each = 4), y = rep(1:2, each = 8), z = runif(16)) + gf <- df %>% duckplyr_group_by(x, y) + expect_equal(duckplyr_group_vars(duckplyr_summarise(gf)), "x") + expect_equal(duckplyr_group_vars(duckplyr_summarise(duckplyr_summarise(gf))), character()) +}) + +test_that("correctly reconstructs groups", { + d <- tibble(x = 1:4, g1 = rep(1:2, 2), g2 = 1:4) %>% + duckplyr_group_by(g1, g2) %>% + duckplyr_summarise(x = x + 1) + expect_equal(group_rows(d), list_of(1:2, 3:4)) +}) + +test_that("can modify grouping variables", { + df <- tibble(a = c(1, 2, 1, 2), b = c(1, 1, 2, 2)) + gf <- duckplyr_group_by(df, a, b) + + i <- count_regroups(out <- duckplyr_summarise(gf, a = a + 1)) + expect_equal(i, 1) + expect_equal(out$a, c(2, 2, 3, 3)) +}) + +test_that("summarise returns a row for zero length groups", { + df <- tibble( + e = 1, + f = factor(c(1, 1, 2, 2), levels = 1:3), + g = c(1, 1, 2, 2), + x = c(1, 2, 1, 4) + ) + df <- duckplyr_group_by(df, e, f, g, .drop = FALSE) + + expect_equal( nrow(duckplyr_summarise(df, z = n())), 3L) +}) + +test_that("summarise respects zero-length groups (#341)", { + df <- tibble(x = factor(rep(1:3, each = 10), levels = 1:4)) + + out <- df %>% + duckplyr_group_by(x, .drop = FALSE) %>% + duckplyr_summarise(n = n()) + + expect_equal(out$n, c(10L, 10L, 10L, 0L)) +}) + +# vector types ---------------------------------------------------------- + +test_that("summarise allows names (#2675)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + data <- tibble(a = 1:3) %>% duckplyr_summarise(b = c("1" = a[[1]])) + expect_equal(names(data$b), "1") + + data <- tibble(a = 1:3) %>% duckplyr_rowwise() %>% duckplyr_summarise(b = setNames(nm = a)) + expect_equal(names(data$b), c("1", "2", "3")) + + data <- tibble(a = c(1, 1, 2)) %>% duckplyr_group_by(a) %>% duckplyr_summarise(b = setNames(nm = a[[1]])) + expect_equal(names(data$b), c("1", "2")) + + res <- data.frame(x = c(1:3), y = letters[1:3]) %>% + duckplyr_group_by(y) %>% + duckplyr_summarise( + a = length(x), + b = quantile(x, 0.5) + ) + expect_equal(res$b, c("50%" = 1, "50%" = 2, "50%" = 3)) +}) + +test_that("summarise handles list output columns (#832)", { + df <- tibble(x = 1:10, g = rep(1:2, each = 5)) + res <- df %>% duckplyr_group_by(g) %>% duckplyr_summarise(y = list(x)) + expect_equal(res$y[[1]], 1:5) + + # preserving names + d <- tibble(x = rep(1:3, 1:3), y = 1:6, names = letters[1:6]) + res <- d %>% duckplyr_group_by(x) %>% duckplyr_summarise(y = list(setNames(y, names))) + expect_equal(names(res$y[[1]]), letters[[1]]) +}) + +test_that("summarise coerces types across groups", { + gf <- duckplyr_group_by(tibble(g = 1:2), g) + + out <- duckplyr_summarise(gf, x = if (g == 1) NA else "x") + expect_type(out$x, "character") + + out <- duckplyr_summarise(gf, x = if (g == 1L) NA else 2.5) + expect_type(out$x, "double") +}) + +test_that("unnamed tibbles are unpacked (#2326)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2) + out <- duckplyr_summarise(df, tibble(y = x * 2, z = 3)) + expect_equal(out$y, 4) + expect_equal(out$z, 3) +}) + +test_that("named tibbles are packed (#2326)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 2) + out <- duckplyr_summarise(df, df = tibble(y = x * 2, z = 3)) + expect_equal(out$df, tibble(y = 4, z = 3)) +}) + +test_that("duckplyr_summarise(.groups=) in global environment", { + skip("TODO duckdb") + expect_message(eval_bare( + expr(data.frame(x = 1, y = 2) %>% duckplyr_group_by(x, y) %>% duckplyr_summarise()), + env(global_env()) + )) + expect_message(eval_bare( + expr(data.frame(x = 1, y = 2) %>% duckplyr_rowwise(x, y) %>% duckplyr_summarise()), + env(global_env()) + )) +}) + +test_that("duckplyr_summarise(.groups=)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x = 1, y = 2) + expect_equal(df %>% duckplyr_summarise(z = 3, .groups= "rowwise"), duckplyr_rowwise(data.frame(z = 3))) + + gf <- df %>% duckplyr_group_by(x, y) + expect_equal(gf %>% duckplyr_summarise() %>% duckplyr_group_vars(), "x") + expect_equal(gf %>% duckplyr_summarise(.groups = "drop_last") %>% duckplyr_group_vars(), "x") + expect_equal(gf %>% duckplyr_summarise(.groups = "drop") %>% duckplyr_group_vars(), character()) + expect_equal(gf %>% duckplyr_summarise(.groups = "keep") %>% duckplyr_group_vars(), c("x", "y")) + + rf <- df %>% duckplyr_rowwise(x, y) + expect_equal(rf %>% duckplyr_summarise(.groups = "drop") %>% duckplyr_group_vars(), character()) + expect_equal(rf %>% duckplyr_summarise(.groups = "keep") %>% duckplyr_group_vars(), c("x", "y")) +}) + +test_that("duckplyr_summarise() casts data frame results to common type (#5646)", { + df <- data.frame(x = 1:2, g = 1:2) %>% duckplyr_group_by(g) + + res <- df %>% + duckplyr_summarise(if (g == 1) data.frame(y = 1) else data.frame(y = 1, z = 2), .groups = "drop") + expect_equal(res$z, c(NA, 2)) +}) + +test_that("duckplyr_summarise() silently skips when all results are NULL (#5708)", { + df <- data.frame(x = 1:2, g = 1:2) %>% duckplyr_group_by(g) + + expect_equal(duckplyr_summarise(df, x = NULL), duckplyr_summarise(df)) + expect_error(duckplyr_summarise(df, x = if(g == 1) 42)) +}) + +# .by ---------------------------------------------------------------------- + +test_that("can group transiently using `.by`", { + df <- tibble(g = c(1, 1, 2, 1, 2), x = c(5, 2, 1, 2, 3)) + + out <- duckplyr_summarise(df, x = mean(x), .by = g) + + expect_identical(out$g, c(1, 2)) + expect_identical(out$x, c(3, 2)) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping retains bare data.frame class", { + df <- data.frame(g = c(1, 1, 2, 1, 2), x = c(5, 2, 1, 2, 3)) + out <- duckplyr_summarise(df, x = mean(x), .by = g) + expect_s3_class(out, class(df), exact = TRUE) +}) + +test_that("transient grouping drops data frame attributes", { + # Because `duckplyr_summarise()` theoretically creates a "new" data frame + + # With data.frames or tibbles + df <- data.frame(g = c(1, 1, 2), x = c(1, 2, 1)) + tbl <- as_tibble(df) + + attr(df, "foo") <- "bar" + attr(tbl, "foo") <- "bar" + + out <- duckplyr_summarise(df, x = mean(x), .by = g) + expect_null(attr(out, "foo")) + + out <- duckplyr_summarise(tbl, x = mean(x), .by = g) + expect_null(attr(out, "foo")) +}) + +test_that("transient grouping orders by first appearance", { + df <- tibble(g = c(2, 1, 2, 0), x = c(4, 2, 8, 5)) + + out <- duckplyr_summarise(df, x = mean(x), .by = g) + + expect_identical(out$g, c(2, 1, 0)) + expect_identical(out$x, c(6, 2, 5)) +}) + +test_that("can't use `.by` with `.groups`", { + df <- tibble(x = 1) + + expect_snapshot(error = TRUE, { + duckplyr_summarise(df, .by = x, .groups = "drop") + }) +}) + +test_that("catches `.by` with grouped-df", { + df <- tibble(x = 1) + gdf <- duckplyr_group_by(df, x) + + expect_snapshot(error = TRUE, { + duckplyr_summarise(gdf, .by = x) + }) +}) + +test_that("catches `.by` with rowwise-df", { + df <- tibble(x = 1) + rdf <- duckplyr_rowwise(df) + + expect_snapshot(error = TRUE, { + duckplyr_summarise(rdf, .by = x) + }) +}) + +# errors ------------------------------------------------------------------- + +test_that("duckplyr_summarise() preserves the call stack on error (#5308)", { + foobar <- function() stop("foo") + + stack <- NULL + expect_error( + withCallingHandlers( + error = function(...) stack <<- sys.calls(), + duckplyr_summarise(mtcars, foobar()) + ) + ) + + expect_true(some(stack, is_call, "foobar")) +}) + +test_that("`duckplyr_summarise()` doesn't allow data frames with missing or empty names (#6758)", { + df1 <- new_data_frame(set_names(list(1), "")) + df2 <- new_data_frame(set_names(list(1), NA_character_)) + + expect_snapshot(error = TRUE, { + duckplyr_summarise(df1) + }) + expect_snapshot(error = TRUE, { + duckplyr_summarise(df2) + }) +}) + +test_that("duckplyr_summarise() gives meaningful errors", { + skip("TODO duckdb") + eval(envir = global_env(), expr({ + expect_snapshot({ + # Messages about .groups= + tibble(x = 1, y = 2) %>% duckplyr_group_by(x, y) %>% duckplyr_summarise() + tibble(x = 1, y = 2) %>% duckplyr_rowwise(x, y) %>% duckplyr_summarise() + tibble(x = 1, y = 2) %>% duckplyr_rowwise() %>% duckplyr_summarise() + }) + })) + + eval(envir = global_env(), expr({ + expect_snapshot({ + # unsupported type + (expect_error( + tibble(x = 1, y = c(1, 2, 2), z = runif(3)) %>% + duckplyr_summarise(a = rlang::env(a = 1)) + )) + (expect_error( + tibble(x = 1, y = c(1, 2, 2), z = runif(3)) %>% + duckplyr_group_by(x, y) %>% + duckplyr_summarise(a = rlang::env(a = 1)) + )) + (expect_error( + tibble(x = 1, y = c(1, 2, 2), z = runif(3)) %>% + duckplyr_rowwise() %>% + duckplyr_summarise(a = lm(y ~ x)) + )) + + # mixed types + (expect_error( + tibble(id = 1:2, a = list(1, "2")) %>% + duckplyr_group_by(id) %>% + duckplyr_summarise(a = a[[1]]) + )) + (expect_error( + tibble(id = 1:2, a = list(1, "2")) %>% + duckplyr_rowwise() %>% + duckplyr_summarise(a = a[[1]]) + )) + + # incompatible size + (expect_error( + tibble(z = 1) %>% + duckplyr_summarise(x = 1:3, y = 1:2) + )) + (expect_error( + tibble(z = 1:2) %>% + duckplyr_group_by(z) %>% + duckplyr_summarise(x = 1:3, y = 1:2) + )) + (expect_error( + tibble(z = c(1, 3)) %>% + duckplyr_group_by(z) %>% + duckplyr_summarise(x = seq_len(z), y = 1:2) + )) + + # mixed nulls + (expect_error( + data.frame(x = 1:2, g = 1:2) %>% duckplyr_group_by(g) %>% duckplyr_summarise(x = if(g == 1) 42) + )) + (expect_error( + data.frame(x = 1:2, g = 1:2) %>% duckplyr_group_by(g) %>% duckplyr_summarise(x = if(g == 2) 42) + )) + + # .data pronoun + (expect_error(duckplyr_summarise(tibble(a = 1), c = .data$b))) + (expect_error(duckplyr_summarise(duckplyr_group_by(tibble(a = 1:3), a), c = .data$b))) + + # Duplicate column names + (expect_error( + tibble(x = 1, x = 1, .name_repair = "minimal") %>% duckplyr_summarise(x) + )) + + # Not glue()ing + (expect_error(tibble() %>% duckplyr_summarise(stop("{")))) + (expect_error( + tibble(a = 1, b="{value:1, unit:a}") %>% duckplyr_group_by(b) %>% duckplyr_summarise(a = stop("!")) + )) + }) + })) + +}) + +test_that("non-summary results are deprecated in favor of `duckplyr_reframe()` (#6382)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + local_options(lifecycle_verbosity = "warning") + + df <- tibble(g = c(1, 1, 2), x = 1:3) + gdf <- duckplyr_group_by(df, g) + rdf <- duckplyr_rowwise(df) + + expect_snapshot({ + out <- duckplyr_summarise(df, x = which(x < 3)) + }) + expect_identical(out$x, 1:2) + + expect_snapshot({ + out <- duckplyr_summarise(df, x = which(x < 3), .by = g) + }) + expect_identical(out$g, c(1, 1)) + expect_identical(out$x, 1:2) + + # First group returns size 2 summary + expect_snapshot({ + out <- duckplyr_summarise(gdf, x = which(x < 3)) + }) + expect_identical(out$g, c(1, 1)) + expect_identical(out$x, 1:2) + + # Last row returns size 0 summary + expect_snapshot({ + out <- duckplyr_summarise(rdf, x = which(x < 3)) + }) + expect_identical(out$x, c(1L, 1L)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-transmute.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-transmute.R new file mode 100644 index 000000000..e736091e9 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr-transmute.R @@ -0,0 +1,102 @@ +# Generated by 04-dplyr-tests.R, do not edit by hand + +# Workaround for lazytest +test_that("Dummy", { expect_true(TRUE) }) + +skip_if(Sys.getenv("DUCKPLYR_SKIP_DPLYR_TESTS") == "TRUE") + +test_that("non-syntactic grouping variable is preserved (#1138)", { + df <- tibble(`a b` = 1L) %>% duckplyr_group_by(`a b`) %>% duckplyr_transmute() + expect_named(df, "a b") +}) + +test_that("transmute preserves grouping", { + gf <- duckplyr_group_by(tibble(x = 1:2, y = 2), x) + + i <- count_regroups(out <- duckplyr_transmute(gf, x = 1)) + expect_equal(i, 1L) + expect_equal(duckplyr_group_vars(out), "x") + expect_equal(nrow(group_data(out)), 1) + + i <- count_regroups(out <- duckplyr_transmute(gf, z = 1)) + expect_equal(i, 0) + expect_equal(group_data(out), group_data(gf)) +}) + +# Empty transmutes ------------------------------------------------- + +test_that("transmute with no args returns grouping vars", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(x = 1, y = 2) + gf <- duckplyr_group_by(df, x) + + expect_equal(df %>% duckplyr_transmute(), df[integer()]) + expect_equal(gf %>% duckplyr_transmute(), gf[1L]) +}) + +# transmute variables ----------------------------------------------- + +test_that("transmute succeeds in presence of raw columns (#1803)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- tibble(a = 1:3, b = as.raw(1:3)) + expect_identical(duckplyr_transmute(df, a), df["a"]) + expect_identical(duckplyr_transmute(df, b), df["b"]) +}) + +test_that("arguments to duckplyr_transmute() don't match vars_transmute() arguments", { + df <- tibble(a = 1) + expect_identical(duckplyr_transmute(df, var = a), tibble(var = 1)) + expect_identical(duckplyr_transmute(df, exclude = a), tibble(exclude = 1)) + expect_identical(duckplyr_transmute(df, include = a), tibble(include = 1)) +}) + +test_that("arguments to duckplyr_rename() don't match vars_rename() arguments (#2861)", { + df <- tibble(a = 1) + expect_identical(duckplyr_rename(df, var = a), tibble(var = 1)) + expect_identical(duckplyr_rename(duckplyr_group_by(df, a), var = a), duckplyr_group_by(tibble(var = 1), var)) + expect_identical(duckplyr_rename(df, strict = a), tibble(strict = 1)) + expect_identical(duckplyr_rename(duckplyr_group_by(df, a), strict = a), duckplyr_group_by(tibble(strict = 1), strict)) +}) + +test_that("can duckplyr_transmute() with .data pronoun (#2715)", { + expect_identical(duckplyr_transmute(mtcars, .data$cyl), duckplyr_transmute(mtcars, cyl)) +}) + +test_that("duckplyr_transmute() does not warn when a variable is removed with = NULL (#4609)", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + df <- data.frame(x=1) + expect_warning(duckplyr_transmute(df, y =x+1, z=y*2, y = NULL), NA) +}) + +test_that("duckplyr_transmute() can handle auto splicing", { + skip_if(Sys.getenv("DUCKPLYR_FORCE") == "TRUE") + expect_equal( + iris %>% duckplyr_transmute(tibble(Sepal.Length, Sepal.Width)), + iris %>% duckplyr_select(Sepal.Length, Sepal.Width) + ) +}) + +test_that("duckplyr_transmute() retains ordering supplied in `...`, even for pre-existing columns (#6086)", { + df <- tibble(x = 1:3, y = 4:6) + out <- duckplyr_transmute(df, x, z = x + 1, y) + expect_named(out, c("x", "z", "y")) +}) + +test_that("duckplyr_transmute() retains ordering supplied in `...`, even for group columns (#6086)", { + df <- tibble(x = 1:3, g1 = 1:3, g2 = 1:3, y = 4:6) + df <- duckplyr_group_by(df, g1, g2) + + out <- duckplyr_transmute(df, x, z = x + 1, y, g1) + + # - Untouched group variables are first + # - Following by ordering supplied through `...` + expect_named(out, c("g2", "x", "z", "y", "g1")) +}) + +test_that("duckplyr_transmute() error messages", { + expect_snapshot({ + (expect_error(duckplyr_transmute(mtcars, cyl2 = cyl, .keep = 'all'))) + (expect_error(duckplyr_transmute(mtcars, cyl2 = cyl, .before = disp))) + (expect_error(duckplyr_transmute(mtcars, cyl2 = cyl, .after = disp))) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr.R new file mode 100644 index 000000000..b684f0e92 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-dplyr.R @@ -0,0 +1,49 @@ +test_that("no homonyms", { + skip_if(identical(Sys.getenv("R_COVR"), "true")) + + dplyr <- asNamespace("dplyr") + duckplyr <- asNamespace("duckplyr") + + names_dplyr <- ls(dplyr) + names_duckplyr <- ls(duckplyr) + + purrr_names <- c( + # https://github.com/tidyverse/dplyr/pull/7029 + "join_ptype_common", + + # needs dplyr > 1.1.4 + "ncol", "df_n_col", "mat_n_col", "check_compatible", "is_compatible", + + # internal or deprecated functions + "all_exprs", "any_exprs", "compute_groups", "dplyr_legacy_locale", + "err_locs", "expand_pick", "group_labels_details", "is_sel_vars", + "list_flatten", "quo_is_variable_reference", "shift", "vec_case_match", + "vec_case_when", "add_rownames", "arrange_", "arrange_at", "arrange_if", + "arrange_all", "combine", "compat_lazy_dots", "count_", "cur_data", + "cur_data_all", "distinct_", "distinct_at", "distinct_if", "distinct_all", + "do_", "filter_", "filter_at", "filter_if", "filter_all", "group_by_", + "group_by_at", "group_by_if", "group_by_all", "mutate_", "mutate_at", + "mutate_if", "mutate_all", "mutate_each", "mutate_each_", "recode", + "recode_factor", "rename_", "rename_at", "rename_if", "rename_all", + "select_", "select_at", "select_if", "select_all", "select_vars_", + "slice_", "src_df", "summarise_", "summarize_", "summarise_at", + "summarise_if", "summarise_all", "summarise_each", "tbl_at_vars", + "top_frac", "transmute_", "transmute_at", "transmute_if", "transmute_all", + + "map", "walk", "map_lgl", "map_int", "map_dbl", "map_chr", + ".rlang_purrr_map_mold", "map2", "map2_lgl", "map2_int", + "map2_dbl", "map2_chr", "imap", "pmap", ".rlang_purrr_args_recycle", + "keep", "discard", "map_if", ".rlang_purrr_probe", "compact", + "transpose", "every", "some", "negate", "reduce", "reduce_right", + "accumulate", "accumulate_right", "detect", "detect_index", + ".rlang_purrr_index", "list_c" + ) + + names_common <- intersect(names_dplyr, names_duckplyr) + names_common <- setdiff(names_common, c("DataMask", "the", purrr_names)) + + objs_dplyr <- mget(names_common, dplyr) + objs_duckplyr <- mget(names_common, duckplyr) + + expect_identical(objs_duckplyr, objs_dplyr[names(objs_duckplyr)]) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckdb.R new file mode 100644 index 000000000..9bb8f9f82 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckdb.R @@ -0,0 +1,6 @@ +test_that("case-insensitive duplicates", { + out <- duckdb_tibble(a = 1:2) %>% + mutate(A = a + 1L, b = A - 1L) + + expect_identical(out$a, out$b) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr-across.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr-across.R new file mode 100644 index 000000000..d62f1421e --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr-across.R @@ -0,0 +1,96 @@ +test_that("duckplyr_expand_across() successful", { + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, mean) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, function(x) mean(x)) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(c(x_mean = x, y_mean = y), mean) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(c(x_mean = x, y_mean = y), mean, .names = "{.col}_{.fn}") + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, function(x) mean(x, na.rm = TRUE)) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y", "a"), + across(c(a, x), function(x) x + 1) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y", "a"), + across(c(a, x), function(x) x * 2 + 1) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y", "a"), + across(-a, function(x) x * x) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, base::mean) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, list(mean)) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, list(mean = mean)) + ) + }) + + expect_snapshot({ + test_duckplyr_expand_across( + c("x", "y"), + across(x:y, list(mean = mean, median = median)) + ) + }) +}) + +test_that("duckplyr_expand_across() failing", { + expect_null(test_duckplyr_expand_across( + c("x", "y"), + across(x:y, mean, .unpack = TRUE) + )) + expect_null(test_duckplyr_expand_across( + c("x", "y"), + across(x:y, mean, na.rm = TRUE) + )) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr.R new file mode 100644 index 000000000..3b4b755fc --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-duckplyr.R @@ -0,0 +1,43 @@ +test_that("same_src() works", { + x <- data.frame(a = 1) + y <- as_duckplyr_df_impl(x) + + expect_true(same_src(x, y)) + expect_true(same_src(y, x)) + expect_true(same_src(y, y)) +}) + +test_that("collect() works", { + x <- data.frame(a = 1) + y <- as_duckplyr_df_impl(x) + z <- select(y, a) + + n_calls <- 0 + local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + collect(z) + expect_equal(n_calls, 1) + + expect_snapshot({ + print(z) + }) + expect_equal(n_calls, 1) +}) + +test_that("tbl_vars() works", { + x <- data.frame(a = 1) + y <- as_duckplyr_df_impl(x) + z <- select(y, a) + + expect_identical(tbl_vars(z), tbl_vars(x)) + + n_calls <- 0 + local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + collect(z) + expect_equal(n_calls, 1) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-ducktbl.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-ducktbl.R new file mode 100644 index 000000000..dd133afc0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-ducktbl.R @@ -0,0 +1,85 @@ +test_that("Can construct", { + expect_identical( + duckdb_tibble(a = 1), + as_duckdb_tibble(tibble::tibble(a = 1)) + ) + + expect_identical(duckdb_tibble(a = 1)$a, 1) +}) + +test_that('.prudence = "stingy" forbids materialization', { + tbl <- duckdb_tibble(a = 1, .prudence = "stingy") + expect_error(length(tbl$a)) +}) + +test_that('.prudence = c(rows = ) forbids materialization', { + tbl <- duckdb_tibble(a = 1:10, .prudence = c(rows = 5)) + expect_error(length(tbl$a)) +}) + +test_that('.prudence = c(cells = ) forbids materialization', { + tbl <- duckdb_tibble(a = 1:10, b = 1, .prudence = c(cells = 10)) + expect_error(length(tbl$a)) +}) + +test_that('.prudence = "stingy" forbids materialization for as_duckdb_tibble', { + tbl <- as_duckdb_tibble(data.frame(a = 1), prudence = "stingy") + expect_error(length(tbl$a)) +}) + +test_that("as_duckdb_tibble() and grouped df", { + expect_snapshot(error = TRUE, { + as_duckdb_tibble(dplyr::group_by(mtcars, cyl)) + }) +}) + +test_that("as_duckdb_tibble() and rowwise df", { + expect_snapshot(error = TRUE, { + as_duckdb_tibble(dplyr::rowwise(mtcars)) + }) +}) + +test_that("as_duckdb_tibble() and readr data", { + skip_if_not_installed("readr") + + path <- withr::local_tempfile(fileext = ".csv") + readr::write_csv(data.frame(a = 1), path) + + expect_snapshot(error = TRUE, { + as_duckdb_tibble(readr::read_csv(path, show_col_types = FALSE)) + }) + + expect_equal( + as_duckdb_tibble(as_tibble(readr::read_csv(path, show_col_types = FALSE))), + duckdb_tibble(a = 1) + ) +}) + +test_that("as_duckdb_tibble() and dbplyr tables", { + skip_if_not_installed("dbplyr") + con <- withr::local_db_connection(DBI::dbConnect(duckdb::duckdb())) + + db_tbl <- + data.frame(a = 1) %>% + dplyr::copy_to(dest = con) + + duck <- db_tbl %>% + as_duckdb_tibble(prudence = "stingy") %>% + mutate(b = 2) + + expect_error(length(duck$b)) + + db <- db_tbl %>% + mutate(b = 2) %>% + as_duckdb_tibble(prudence = "stingy") + + expect_error(length(db$b)) + + expect_identical(collect(duck), collect(db)) +}) + +test_that("is_duckdb_tibble()", { + expect_true(is_duckdb_tibble(duckdb_tibble(a = 1))) + expect_false(is_duckdb_tibble(tibble::tibble(a = 1))) + expect_false(is_duckdb_tibble(data.frame(a = 1))) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-expr.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-expr.R new file mode 100644 index 000000000..bc6e01498 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-expr.R @@ -0,0 +1,18 @@ +test_that("can construct expressions", { + skip_on_cran() + + expect_snapshot({ + relexpr_reference("column") + relexpr_constant(42) + relexpr_function("+", list(relexpr_reference("column"), relexpr_constant(42, alias = "fortytwo"))) + }) +}) + +test_that(".env pronoun works", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + a <- 2 + data <- data.frame(a = 1) + out <- data %>% as_duckplyr_df_impl() %>% mutate(b = .env$a) + expect_equal(out, data.frame(a = 1, b = 2) %>% as_duckplyr_df_impl()) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-fallback.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-fallback.R new file mode 100644 index 000000000..76850d6e7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-fallback.R @@ -0,0 +1,313 @@ +test_that("fallback_sitrep() default", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "", + "DUCKPLYR_FALLBACK_INFO" = "", + "DUCKPLYR_FALLBACK_AUTOUPLOAD" = "", + "DUCKPLYR_FALLBACK_LOG_DIR" = "fallback/log/dir", + "DUCKPLYR_TELEMETRY_FALLBACK_LOGS" = "" + )) + + expect_snapshot({ + fallback_sitrep() + }) +}) + +test_that("fallback_sitrep() enabled", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_INFO" = "", + "DUCKPLYR_FALLBACK_AUTOUPLOAD" = "1", + "DUCKPLYR_FALLBACK_LOG_DIR" = "fallback/log/dir", + "DUCKPLYR_TELEMETRY_FALLBACK_LOGS" = "1,2,3" + )) + + expect_snapshot({ + fallback_sitrep() + }) +}) + +test_that("fallback_sitrep() enabled silent", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_INFO" = "TRUE", + "DUCKPLYR_FALLBACK_AUTOUPLOAD" = "1", + "DUCKPLYR_FALLBACK_LOG_DIR" = "fallback/log/dir", + "DUCKPLYR_TELEMETRY_FALLBACK_LOGS" = "1,2,3" + )) + + expect_snapshot({ + fallback_sitrep() + }) +}) + +test_that("fallback_sitrep() disabled", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "0", + "DUCKPLYR_FALLBACK_AUTOUPLOAD" = "0", + "DUCKPLYR_FALLBACK_LOG_DIR" = "fallback/log/dir", + "DUCKPLYR_TELEMETRY_FALLBACK_LOGS" = "1,2,3" + )) + + expect_snapshot({ + fallback_sitrep() + }) +}) + +test_that("summarize()", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot({ + duckdb_tibble(a = 1, b = 2, c = 3) %>% + summarize( + .by = a, + e = sum(b), + f = sum(e) + ) + }) +}) + +test_that("wday()", { + skip_on_ci() + + skip_if_not_installed("lubridate") + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot({ + duckdb_tibble(a = as.Date("2024-03-08")) %>% + mutate( + b = lubridate::wday(a, label = TRUE) + ) + }) + + local_options(lubridate.week.start = 1) + + expect_snapshot({ + duckdb_tibble(a = as.Date("2024-03-08")) %>% + mutate( + b = lubridate::wday(a) + ) + }) +}) + +test_that("strftime()", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot({ + duckdb_tibble(a = as.Date("2024-03-08")) %>% + mutate( + b = strftime(a, format = "%Y-%m-%d", tz = "CET") + ) + }) +}) + +test_that("$", { + skip_on_ci() + + skip_if_not(getRversion() >= "4.3") + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1, b = 2) %>% + mutate(c = .env$x) + }) +}) + +test_that("unknown function", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + foo <- function(...) 3 + + expect_snapshot({ + duckdb_tibble(a = 1, b = 2) %>% + mutate(c = foo(a, b)) + }) +}) + +test_that("row names", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot({ + mtcars[1:2, ] %>% + as_duckdb_tibble() %>% + select(mpg, cyl) + }) +}) + +test_that("named column", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = c(x = 1)) + }) +}) + +test_that("named column", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = matrix(1:4, ncol = 2)) + }) +}) + +test_that("list column", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir() + )) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1, b = 2, c = list(3)) + }) +}) + +test_that("__row_number", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir(), + "DUCKPLYR_OUTPUT_ORDER" = "TRUE" + )) + + expect_snapshot({ + duckdb_tibble(`___row_number` = 1, b = 2:3) %>% + arrange(b) + }) +}) + +test_that("rel_try()", { + skip_on_ci() + + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = "1", + "DUCKPLYR_FALLBACK_VERBOSE" = "TRUE", + "DUCKPLYR_FALLBACK_LOG_DIR" = tempdir(), + "DUCKPLYR_OUTPUT_ORDER" = "TRUE" + )) + + expect_snapshot({ + duckdb_tibble(a = 1) %>% + count(a, .drop = FALSE, name = "n") + }) +}) + +test_that("fallback_config()", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = NA_character_, + "DUCKPLYR_FALLBACK_INFO" = NA_character_, + "DUCKPLYR_FALLBACK_LOGGING" = NA_character_, + "DUCKPLYR_FALLBACK_AUTOUPLOAD" = NA_character_, + "DUCKPLYR_FALLBACK_LOG_DIR" = NA_character_, + "DUCKPLYR_FALLBACK_VERBOSE" = NA_character_ + )) + + config_path <- "fallback.dcf" + local_mocked_bindings(fallback_config_path = function() config_path) + + expect_identical(fallback_config_read(), list()) + + fallback_config(info = TRUE, logging = TRUE, autoupload = TRUE, log_dir = "/", verbose = TRUE) + expect_snapshot_file(config_path, "fallback.dcf") + + expect_snapshot({ + "No conflicts" + fallback_config_load() + }) + + expect_snapshot({ + "Reset and set config" + fallback_config(reset_all = TRUE, logging = FALSE) + }) + expect_snapshot_file(config_path, "fallback-2.dcf") + + withr::local_envvar(c( + DUCKPLYR_FALLBACK_LOGGING = 1 + )) + expect_snapshot({ + "Conflicts with environment variable and setting" + fallback_config_load() + }) + + expect_snapshot({ + "Reset config" + fallback_config(reset_all = TRUE) + }) + expect_false(file.exists(config_path)) + + expect_snapshot(error = TRUE, { + fallback_config(boo = FALSE) + }) +}) + +test_that("fallback_config() failure", { + withr::local_envvar(c( + "DUCKPLYR_FALLBACK_COLLECT" = NA_character_, + "DUCKPLYR_FALLBACK_INFO" = NA_character_, + "DUCKPLYR_FALLBACK_AUTOUPLOAD" = NA_character_, + "DUCKPLYR_FALLBACK_LOG_DIR" = NA_character_, + "DUCKPLYR_FALLBACK_VERBOSE" = NA_character_ + )) + + config_path <- withr::local_tempfile(lines = "boo") + local_mocked_bindings(fallback_config_path = function() config_path) + + writeLines("boo", config_path) + + expect_snapshot({ + fallback_config_load() + }) + + expect_false(file.exists(config_path)) + + expect_snapshot({ + fallback_config_load() + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-handle_desc.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-handle_desc.R new file mode 100644 index 000000000..a4ed857b8 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-handle_desc.R @@ -0,0 +1,33 @@ +test_that("desc() is handled without qualification", { + out <- + duckdb_tibble(a = 1:3, .prudence = "stingy") %>% + arrange(desc(a)) %>% + pull() + + expect_identical(out, 3:1) +}) + +test_that("desc() is handled with qualification", { + out <- + duckdb_tibble(a = 1:3, .prudence = "stingy") %>% + arrange(dplyr::desc(a)) %>% + pull() + + expect_identical(out, 3:1) +}) + +test_that("desc() fails if it points elsewhere", { + desc <- function(...) {} + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, .prudence = "stingy") %>% + arrange(desc(a)) + }) +}) + +test_that("desc() fails for more than one argument", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6, .prudence = "stingy") %>% + arrange(desc(a, b)) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-head.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-head.R new file mode 100644 index 000000000..5d45647e5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-head.R @@ -0,0 +1,29 @@ +test_that("head(2)", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + out <- + duckdb_tibble(a = 1:5) %>% + head(2) + + expect_identical(out$a, 1:2) +}) + +test_that("head(-2)", { + withr::local_envvar(DUCKPLYR_FORCE = FALSE) + + out <- + duckdb_tibble(a = 1:5) %>% + head(-2) + + expect_identical(out$a, 1:3) +}) + +test_that("head(0)", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + out <- + duckdb_tibble(a = 1:5) %>% + head(0) + + expect_identical(out$a, integer(0)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-csv.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-csv.R new file mode 100644 index 000000000..597581b50 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-csv.R @@ -0,0 +1,12 @@ +test_that("Roundtrip to CSV works", { + local_options(lifecycle_verbosity = "quiet") + + df <- tibble(a = 1:3, b = letters[4:6]) + + path_csv <- withr::local_tempfile(fileext = ".csv") + + write.csv(df, path_csv, row.names = FALSE) + out <- df_from_csv(path_csv) + + expect_equal(out, df) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-parquet.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-parquet.R new file mode 100644 index 000000000..7eeb21ff3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-io-parquet.R @@ -0,0 +1,40 @@ +test_that("Roundtrip to Parquet works", { + local_options(lifecycle_verbosity = "quiet") + + df <- tibble(a = 1:3, b = letters[4:6]) + + path_parquet <- withr::local_tempfile(fileext = ".parquet") + + df_to_parquet(df, path_parquet) + out <- df_from_parquet(path_parquet) + + expect_equal(out, df) +}) + +test_that("Writing to Parquet works without materialization", { + local_options(lifecycle_verbosity = "quiet") + + n_calls <- 0 + withr::local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + df <- tibble(a = 1:3, b = letters[4:6]) + path_parquet <- withr::local_tempfile(fileext = ".parquet") + + df %>% + as_duckplyr_df_impl() %>% + select(b, a) %>% + df_to_parquet(path_parquet) + + expect_equal(n_calls, 0) + + out <- df_from_parquet(path_parquet) + expect_equal(n_calls, 0) + + # Side effect + nrow(out) + expect_equal(n_calls, 1) + + expect_equal(out, df[2:1]) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-last.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-last.R new file mode 100644 index 000000000..0321a8982 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-last.R @@ -0,0 +1,12 @@ +test_that("last_rel() works", { + last_rel_store(NULL) + expect_null(last_rel()) + + out <- duckplyr_mutate(data.frame(a = 1), b = 2) + rel <- duckdb$rel_from_altrep_df(out) + + expect_null(last_rel()) + + expect_silent(nrow(out)) + expect_identical(last_rel(), rel) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-n_distinct.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-n_distinct.R new file mode 100644 index 000000000..c32fa0374 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-n_distinct.R @@ -0,0 +1,149 @@ +test_that("duckdb n_distinct() basic", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(1, 1, 2, 2, 2), + b = c(3, 3, NA, 3, 3) + ) + + out <- df %>% + summarise( n_distinct_a = n_distinct(a), + n_distinct_a_na_rm = n_distinct(a, na.rm = TRUE), + n_distinct_b = n_distinct(b, na.rm = FALSE), + n_distinct_b_na_rm = n_distinct(b, na.rm = TRUE) + ) + + expect_equal(out$n_distinct_a, 2) + expect_equal(out$n_distinct_a_na_rm, 2) + expect_equal(out$n_distinct_b, 2) + expect_equal(out$n_distinct_b_na_rm, 1) +}) + + +test_that("duckdb n_distinct() counts empty inputs", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = integer(), + b = double(), + c = logical(), + d = character() + ) + + out <- df %>% + summarise( n_distinct_a = n_distinct(a), + n_distinct_b = n_distinct(b), + n_distinct_c = n_distinct(c), + n_distinct_d = n_distinct(d), + ) + + expect_equal(out$n_distinct_a, 0) + expect_equal(out$n_distinct_b, 0) + expect_equal(out$n_distinct_c, 0) + expect_equal(out$n_distinct_d, 0) +}) + + +test_that("duckdb n_distinct() counts unique values in simple vectors", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(TRUE, FALSE, NA), + b = c(1, 2, NA), + c = c(1L, 2L, NA), + d = c("x", "y", NA) + ) + + out <- df %>% + summarise( n_distinct_a = n_distinct(a), + n_distinct_b = n_distinct(b), + n_distinct_c = n_distinct(c), + n_distinct_d = n_distinct(d), + ) + + expect_equal(out$n_distinct_a, 3) + expect_equal(out$n_distinct_b, 3) + expect_equal(out$n_distinct_c, 3) + expect_equal(out$n_distinct_d, 3) +}) + + +test_that("duckdb n_distinct() can drop missing values", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(NA), + b = c(NA, 0), + ) + + out <- df %>% + summarise( n_distinct_a = n_distinct(a, na.rm = TRUE), + n_distinct_b = n_distinct(b, na.rm = TRUE), + ) + + expect_equal(out$n_distinct_a, 0) + expect_equal(out$n_distinct_b, 1) +}) + + +test_that("duckdb n_distinct() counts NA correctly", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(1, NA, 1, NA, 2, NA, 2), + b = c(3, 3, NA, 3, NA, 4, 5) + ) + + out <- df %>% + summarise( n_distinct_a = n_distinct(a), + n_distinct_a_na_rm = n_distinct(a, na.rm = TRUE), + n_distinct_b = n_distinct(b, na.rm = FALSE), + n_distinct_b_na_rm = n_distinct(b, na.rm = TRUE) + ) + + expect_equal(out$n_distinct_a, 3) + expect_equal(out$n_distinct_a_na_rm, 2) + expect_equal(out$n_distinct_b, 4) + expect_equal(out$n_distinct_b_na_rm, 3) +}) + + +test_that("duckdb n_distinct() error with more than one argument", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(1, 1, 2, 2, 2), + b = c(3, 3, NA, 3, 3) + ) + + expect_snapshot( error = TRUE, { + df %>% summarise( dummy = n_distinct(a, b) ) + }) +}) + + +test_that("duckdb n_distinct() error with na.rm not being TRUE/FALSE", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(1, 2), + ) + + expect_snapshot( error = TRUE, { + df %>% summarise( dummy = n_distinct(a, na.rm = "b") ) + }) +}) + + +test_that("duckdb n_distinct() error with mutate", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + df <- duckdb_tibble( + a = c(1, 1, 2, 2, 2), + b = c(3, 3, NA, 3, 3) + ) + + expect_snapshot( error = TRUE, { + df %>% mutate( dummy = n_distinct(a) ) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-overwrite.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-overwrite.R new file mode 100644 index 000000000..9ecbc97a2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-overwrite.R @@ -0,0 +1,14 @@ +test_that("methods_overwrite() works", { + expect_snapshot({ + methods_overwrite() + }) + on.exit(expect_snapshot({ + methods_restore() + })) + + out <- + data.frame(a = 1) %>% + mutate(b = 2) + + expect_false(duckdb$df_is_materialized(out)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prom.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prom.R new file mode 100644 index 000000000..61345c095 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prom.R @@ -0,0 +1,15 @@ +test_that("promises work as expected", { + skip("Won't need") + + producer <- function(x) { + 42L + } + is_promise <- function(x) .Call(is_promise, x) + + expect_true(is_promise(.Call(promise, producer))) + expect_true(.Call(promise, producer) %>% is_promise()) + + p <- .Call(promise, producer) + expect_false(is_promise(p)) + +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prudence.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prudence.R new file mode 100644 index 000000000..df9b62a7c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-prudence.R @@ -0,0 +1,66 @@ +test_that("stingy duckplyr frames will collect", { + tbl <- duckdb_tibble(a = 1, .prudence = "stingy") + expect_identical( + collect(tbl), + tibble(a = 1) + ) +}) + +test_that("lavish duckplyr frames are converted to data frames", { + tbl <- duckdb_tibble(a = 1) + expect_identical( + as.data.frame(tbl), + data.frame(a = 1) + ) +}) + +test_that("stingy duckplyr frames are converted to data frames", { + tbl <- duckdb_tibble(a = 1, .prudence = "stingy") + expect_identical( + as.data.frame(tbl), + data.frame(a = 1) + ) +}) + +test_that("lavish duckplyr frames are converted to tibbles", { + tbl <- duckdb_tibble(a = 1) + expect_identical( + as_tibble(tbl), + tibble(a = 1) + ) +}) + +test_that("stingy duckplyr frames are converted to tibbles", { + tbl <- duckdb_tibble(a = 1, .prudence = "stingy") + expect_identical( + as_tibble(tbl), + tibble(a = 1) + ) +}) + +test_that("prudence after operation with failure", { + df <- duckdb_tibble(x = 1:10, .prudence = c(rows = 5)) + out <- df %>% + count(x) + + expect_identical(get_prudence_duckplyr_df(out), c(rows = 5)) + expect_error(nrow(out)) +}) + +test_that("prudence after operation with success", { + df <- duckdb_tibble(x = 1:10, .prudence = c(rows = 5)) + out <- df %>% + count() + + expect_identical(get_prudence_duckplyr_df(out), c(rows = 5)) + expect_error(nrow(out), NA) +}) + +test_that("prudence after summarise() with success", { + df <- duckdb_tibble(x = 1:10, .prudence = c(rows = 5)) + out <- df %>% + summarise(n()) + + expect_identical(get_prudence_duckplyr_df(out), c(rows = 5)) + expect_error(nrow(out), NA) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_csv_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_csv_duckdb.R new file mode 100644 index 000000000..20f0ca6ca --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_csv_duckdb.R @@ -0,0 +1,24 @@ +test_that("Roundtrip to CSV works", { + df <- tibble(a = 1:3, b = letters[4:6]) + + path_csv <- withr::local_tempfile(fileext = ".csv") + + write.csv(df, path_csv, row.names = FALSE) + out <- read_csv_duckdb(path_csv) + + expect_equal(collect(out), df) +}) + +test_that("Roundtrip to multiple CSV works", { + df1 <- tibble(a = 1:3, b = letters[4:6]) + path_csv1 <- withr::local_tempfile(fileext = ".csv") + write.csv(df1, path_csv1, row.names = FALSE) + + df2 <- tibble(a = 4:6, b = letters[7:9]) + path_csv2 <- withr::local_tempfile(fileext = ".csv") + write.csv(df2, path_csv2, row.names = FALSE) + + out <- read_csv_duckdb(c(path_csv1, path_csv2)) + + expect_equal(collect(out), bind_rows(df1, df2)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_json_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_json_duckdb.R new file mode 100644 index 000000000..c90b1513d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_json_duckdb.R @@ -0,0 +1,14 @@ +test_that("Reading from JSON works", { + skip_if_not(can_load_extension("json")) + + df <- tibble(a = 1:2, b = c("x", "y")) + + path_json <- withr::local_tempfile(fileext = ".json") + writeLines('[{"a": 1, "b": "x"}, {"a": 2, "b": "y"}]', path_json) + + db_exec("INSTALL json") + db_exec("LOAD json") + out <- read_json_duckdb(path_json) + + expect_equal(collect(out), df) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_parquet_duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_parquet_duckdb.R new file mode 100644 index 000000000..8a4c6c699 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-read_parquet_duckdb.R @@ -0,0 +1,61 @@ +test_that("Roundtrip to Parquet works", { + df <- tibble(a = 1:3, b = letters[4:6]) + + path_parquet <- withr::local_tempfile(fileext = ".parquet") + + compute_parquet(df, path_parquet) + out <- read_parquet_duckdb(path_parquet) + + expect_equal(collect(out), df) +}) + +test_that("Writing to Parquet works without materialization", { + n_calls <- 0 + withr::local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + df <- tibble(a = 1:3, b = letters[4:6]) + path_parquet <- withr::local_tempfile(fileext = ".parquet") + + out <- df %>% + as_duckplyr_df_impl() %>% + select(b, a) %>% + compute_parquet(path_parquet) + + expect_equal(n_calls, 0) + + # Side effect + nrow(out) + expect_equal(n_calls, 1) + + expect_equal(collect(out), df[2:1]) + expect_equal(n_calls, 1) +}) + +test_that("Reading from Parquet and collecting", { + n_calls <- 0 + withr::local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + df <- tibble(a = 1:3, b = letters[4:6]) + path_parquet <- withr::local_tempfile(fileext = ".parquet") + + df %>% + as_duckplyr_df_impl() %>% + select(b, a) %>% + compute_parquet(path_parquet) + + expect_equal(n_calls, 0) + + out <- read_parquet_duckdb(path_parquet) + expect_equal(n_calls, 0) + + # Side effect + nrow(out) + expect_equal(n_calls, 1) + + collected <- collect(out) + expect_equal(collected, df[2:1]) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel.R new file mode 100644 index 000000000..d33a81606 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel.R @@ -0,0 +1,11 @@ +test_that("new_relational() creates relational objects", { + expect_s3_class(new_relational(list()), "relational") +}) + +test_that("rel_from_df() creates a data frame", { + expect_s3_class(rel_from_df(data.frame(a = 1)), "relational") +}) + +test_that("rel_from_df() fails with bogus input", { + expect_error(rel_from_df("bogus")) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel_api.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel_api.R new file mode 100644 index 000000000..11561bc4c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-rel_api.R @@ -0,0 +1,15183 @@ +# Generated by duckplyr's 05-duckdb-tests.R, do not edit by hand + +# anti_join order-preserving ----------------------------------------------------------- + +test_that("relational anti_join(join_by(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "anti_join" + rel1 <- duckdb$rel_from_df(con, df1) + "anti_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "anti_join" + rel3 <- duckdb$rel_from_df(con, df2) + "anti_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "anti_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "anti_join" + rel6 <- duckdb$rel_join( + rel5, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel5), duckdb$expr_reference("a", rel4))) + ), + "anti" + ) + "anti_join" + rel7 <- duckdb$rel_order(rel6, list(duckdb$expr_reference("___row_number_x", rel5))) + "anti_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel8 + out <- duckdb$rel_to_altrep(rel8) + expect_identical( + out, + data.frame(a = 1L, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# anti_join order-enforcing ------------------------------------------------------------ + +test_that("relational anti_join(join_by(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "anti_join" + rel1 <- duckdb$rel_from_df(con, df1) + "anti_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "anti_join" + rel3 <- duckdb$rel_from_df(con, df2) + "anti_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "anti_join" + rel5 <- duckdb$rel_join( + rel2, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel2), duckdb$expr_reference("a", rel4))) + ), + "anti" + ) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = 1L, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# arrange order-preserving ------------------------------------------------------------- + +test_that("relational arrange() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + rel1 + out <- duckdb$rel_to_altrep(rel1) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("a"), duckdb$expr_reference("___row_number"))) + "arrange" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("g"), duckdb$expr_reference("___row_number"))) + "arrange" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(g, a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("g"), duckdb$expr_reference("a"), duckdb$expr_reference("___row_number")) + ) + "arrange" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(a, g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("g"), duckdb$expr_reference("___row_number")) + ) + "arrange" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# arrange order-enforcing -------------------------------------------------------------- + +test_that("relational arrange() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_from_df(con, df1) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_order(rel1, list(duckdb$expr_reference("a"))) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_order(rel1, list(duckdb$expr_reference("g"))) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(g, a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_order(rel1, list(duckdb$expr_reference("g"), duckdb$expr_reference("a"))) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational arrange(a, g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "arrange" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_order(rel1, list(duckdb$expr_reference("a"), duckdb$expr_reference("g"))) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# count order-preserving --------------------------------------------------------------- + +test_that("relational count() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(n = 6L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), n = 1L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(b = 2, n = 6L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = 1:3, n = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(g, a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = c(1L, 2L, 2L, 3L, 3L, 3L), a = seq(1, 6, by = 1), n = 1L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(b, g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(b = 2, g = 1:3, n = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# count order-enforcing ---------------------------------------------------------------- + +test_that("relational count() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("n"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(n = 6L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"), duckdb$expr_reference("n"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), n = 1L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("b"), duckdb$expr_reference("n"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(b = 2, n = 6L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("g"), duckdb$expr_reference("n"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(g = 1:3, n = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(g, a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("g"), duckdb$expr_reference("a"), duckdb$expr_reference("n")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(g = c(1L, 2L, 2L, 3L, 3L, 3L), a = seq(1, 6, by = 1), n = 1L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational count(b, g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "count" + rel1 <- duckdb$rel_from_df(con, df1) + "count" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list( + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "count" + rel3 <- duckdb$rel_order( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("n")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(b = 2, g = 1:3, n = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# distinct order-preserving ------------------------------------------------------------ + +test_that("relational distinct() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(a, b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(b, b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 3, g = 2L)) %>% distinct(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 3, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_filter( + rel9, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel11 <- duckdb$rel_order(rel10, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel12 <- duckdb$rel_project( + rel11, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel12 + out <- duckdb$rel_to_altrep(rel12) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 4, g = 2L)) %>% distinct(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 4, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_filter( + rel9, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel11 <- duckdb$rel_order(rel10, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel12 <- duckdb$rel_project( + rel11, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel12 + out <- duckdb$rel_to_altrep(rel12) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 5, g = 2L)) %>% distinct(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 5, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_filter( + rel9, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel11 <- duckdb$rel_order(rel10, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel12 <- duckdb$rel_project( + rel11, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel12 + out <- duckdb$rel_to_altrep(rel12) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 6, g = 2L)) %>% distinct(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 6, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_filter( + rel9, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel11 <- duckdb$rel_order(rel10, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel12 <- duckdb$rel_project( + rel11, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel12 + out <- duckdb$rel_to_altrep(rel12) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 7, g = 2L)) %>% distinct(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 7, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_filter( + rel9, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel11 <- duckdb$rel_order(rel10, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel12 <- duckdb$rel_project( + rel11, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel12 + out <- duckdb$rel_to_altrep(rel12) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(g, .keep_all = TRUE) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = c(1, 2, 4), b = 2, g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# distinct order-enforcing ------------------------------------------------------------- + +test_that("relational distinct() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_distinct(rel1) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(a, b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(b, b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("b"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("g"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 3, g = 2L)) %>% distinct(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 3, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "distinct" + rel4 <- duckdb$rel_project( + rel3, + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel5 <- duckdb$rel_distinct(rel4) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("g"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 4, g = 2L)) %>% distinct(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 4, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "distinct" + rel4 <- duckdb$rel_project( + rel3, + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel5 <- duckdb$rel_distinct(rel4) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("g"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 5, g = 2L)) %>% distinct(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 5, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "distinct" + rel4 <- duckdb$rel_project( + rel3, + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel5 <- duckdb$rel_distinct(rel4) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("g"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 6, g = 2L)) %>% distinct(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 6, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "distinct" + rel4 <- duckdb$rel_project( + rel3, + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel5 <- duckdb$rel_distinct(rel4) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("g"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational union_all(data.frame(a = 1L, b = 7, g = 2L)) %>% distinct(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 1L, b = 7, g = 2L) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "distinct" + rel4 <- duckdb$rel_project( + rel3, + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "distinct" + rel5 <- duckdb$rel_distinct(rel4) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("g"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational distinct(g, .keep_all = TRUE) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "distinct" + rel1 <- duckdb$rel_from_df(con, df1) + "distinct" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + g = { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel4 <- duckdb$rel_filter( + rel3, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel6 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel7 <- duckdb$rel_order( + rel6, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel7 + out <- duckdb$rel_to_altrep(rel7) + expect_identical( + out, + data.frame(a = c(1, 2, 4), b = 2, g = 1:3) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# filter order-preserving -------------------------------------------------------------- + +test_that("relational filter(a == 1) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "filter" + rel3 <- duckdb$rel_filter( + rel2, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + ) + ) + "filter" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "filter" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(a = 1, b = 2, g = 1L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational filter(a %in% 2:3, g == 2) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + invisible(DBI::dbExecute(con, 'CREATE MACRO "|"(x, y) AS (x OR y)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "filter" + rel3 <- duckdb$rel_filter( + rel2, + list( + duckdb$expr_function( + "___coalesce", + list( + duckdb$expr_function( + "|", + list( + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(2L))), + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(3L))) + ) + ), + duckdb$expr_constant(FALSE) + ) + ), + duckdb$expr_comparison("==", list(duckdb$expr_reference("g"), duckdb$expr_constant(2))) + ) + ) + "filter" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "filter" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(a = c(2, 3), b = 2, g = 2L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational filter(a %in% 2:3 & g == 2) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + invisible(DBI::dbExecute(con, 'CREATE MACRO "&"(x, y) AS (x AND y)')) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + invisible(DBI::dbExecute(con, 'CREATE MACRO "|"(x, y) AS (x OR y)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "filter" + rel3 <- duckdb$rel_filter( + rel2, + list( + duckdb$expr_function( + "&", + list( + duckdb$expr_function( + "___coalesce", + list( + duckdb$expr_function( + "|", + list( + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(2L))), + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(3L))) + ) + ), + duckdb$expr_constant(FALSE) + ) + ), + duckdb$expr_comparison("==", list(duckdb$expr_reference("g"), duckdb$expr_constant(2))) + ) + ) + ) + ) + "filter" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "filter" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(a = c(2, 3), b = 2, g = 2L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational filter(a != 2 | g != 2) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + invisible(DBI::dbExecute(con, 'CREATE MACRO "|"(x, y) AS (x OR y)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "filter" + rel3 <- duckdb$rel_filter( + rel2, + list( + duckdb$expr_function( + "|", + list( + duckdb$expr_function("r_base::!=", list(duckdb$expr_reference("a"), duckdb$expr_constant(2))), + duckdb$expr_function("r_base::!=", list(duckdb$expr_reference("g"), duckdb$expr_constant(2))) + ) + ) + ) + ) + "filter" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "filter" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(a = c(1, 3, 4, 5, 6), b = 2, g = c(1L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# filter order-enforcing --------------------------------------------------------------- + +test_that("relational filter(a == 1) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_filter( + rel1, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = 1, b = 2, g = 1L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational filter(a %in% 2:3, g == 2) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + invisible(DBI::dbExecute(con, 'CREATE MACRO "|"(x, y) AS (x OR y)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_filter( + rel1, + list( + duckdb$expr_function( + "___coalesce", + list( + duckdb$expr_function( + "|", + list( + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(2L))), + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(3L))) + ) + ), + duckdb$expr_constant(FALSE) + ) + ), + duckdb$expr_comparison("==", list(duckdb$expr_reference("g"), duckdb$expr_constant(2))) + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = c(2, 3), b = 2, g = 2L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational filter(a %in% 2:3 & g == 2) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + invisible(DBI::dbExecute(con, 'CREATE MACRO "&"(x, y) AS (x AND y)')) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + invisible(DBI::dbExecute(con, 'CREATE MACRO "|"(x, y) AS (x OR y)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_filter( + rel1, + list( + duckdb$expr_function( + "&", + list( + duckdb$expr_function( + "___coalesce", + list( + duckdb$expr_function( + "|", + list( + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(2L))), + duckdb$expr_function("r_base::==", list(duckdb$expr_reference("a"), duckdb$expr_constant(3L))) + ) + ), + duckdb$expr_constant(FALSE) + ) + ), + duckdb$expr_comparison("==", list(duckdb$expr_reference("g"), duckdb$expr_constant(2))) + ) + ) + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = c(2, 3), b = 2, g = 2L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational filter(a != 2 | g != 2) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + invisible(DBI::dbExecute(con, 'CREATE MACRO "|"(x, y) AS (x OR y)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "filter" + rel1 <- duckdb$rel_from_df(con, df1) + "filter" + rel2 <- duckdb$rel_filter( + rel1, + list( + duckdb$expr_function( + "|", + list( + duckdb$expr_function("r_base::!=", list(duckdb$expr_reference("a"), duckdb$expr_constant(2))), + duckdb$expr_function("r_base::!=", list(duckdb$expr_reference("g"), duckdb$expr_constant(2))) + ) + ) + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = c(1, 3, 4, 5, 6), b = 2, g = c(1L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# full_join order-preserving ----------------------------------------------------------- + +test_that("relational full_join(join_by(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + df1 <- data.frame(a = 1:4, b = 2) + + "full_join" + rel1 <- duckdb$rel_from_df(con, df1) + "full_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "full_join" + rel3 <- duckdb$rel_from_df(con, df2) + "full_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "full_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "full_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "full_join" + rel7 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a_x") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "full_join" + rel8 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a_y") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "full_join" + rel9 <- duckdb$rel_join( + rel7, + rel8, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + ), + "outer" + ) + "full_join" + rel10 <- duckdb$rel_order( + rel9, + list(duckdb$expr_reference("___row_number_x", rel7), duckdb$expr_reference("___row_number_y", rel8)) + ) + "full_join" + rel11 <- duckdb$rel_project( + rel10, + list( + { + tmp_expr <- duckdb$expr_function("___coalesce", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + rel11 + out <- duckdb$rel_to_altrep(rel11) + expect_identical( + out, + data.frame(a = 1:5, b.x = rep(c(2, NA), c(4L, 1L)), b.y = rep(c(NA, 2), c(1L, 4L))) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# full_join order-enforcing ------------------------------------------------------------ + +test_that("relational full_join(join_by(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + df1 <- data.frame(a = 1:4, b = 2) + + "full_join" + rel1 <- duckdb$rel_from_df(con, df1) + "full_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "full_join" + rel3 <- duckdb$rel_from_df(con, df2) + "full_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "full_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "full_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "full_join" + rel7 <- duckdb$rel_join( + rel5, + rel6, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + ), + "outer" + ) + "full_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_function("___coalesce", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + "arrange" + rel9 <- duckdb$rel_order( + rel8, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b.x"), duckdb$expr_reference("b.y")) + ) + rel9 + out <- duckdb$rel_to_altrep(rel9) + expect_identical( + out, + data.frame(a = 1:5, b.x = rep(c(2, NA), c(4L, 1L)), b.y = rep(c(NA, 2), c(1L, 4L))) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# inner_join order-preserving ---------------------------------------------------------- + +test_that("relational inner_join(join_by(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + df1 <- data.frame(a = 1:4, b = 2) + + "inner_join" + rel1 <- duckdb$rel_from_df(con, df1) + "inner_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "inner_join" + rel3 <- duckdb$rel_from_df(con, df2) + "inner_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "inner_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "inner_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "inner_join" + rel7 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a_x") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "inner_join" + rel8 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a_y") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "inner_join" + rel9 <- duckdb$rel_join( + rel7, + rel8, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + ), + "inner" + ) + "inner_join" + rel10 <- duckdb$rel_order( + rel9, + list(duckdb$expr_reference("___row_number_x", rel7), duckdb$expr_reference("___row_number_y", rel8)) + ) + "inner_join" + rel11 <- duckdb$rel_project( + rel10, + list( + { + tmp_expr <- duckdb$expr_function("___coalesce", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + rel11 + out <- duckdb$rel_to_altrep(rel11) + expect_identical( + out, + data.frame(a = 2:4, b.x = 2, b.y = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# inner_join order-enforcing ----------------------------------------------------------- + +test_that("relational inner_join(join_by(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + df1 <- data.frame(a = 1:4, b = 2) + + "inner_join" + rel1 <- duckdb$rel_from_df(con, df1) + "inner_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "inner_join" + rel3 <- duckdb$rel_from_df(con, df2) + "inner_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "inner_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "inner_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "inner_join" + rel7 <- duckdb$rel_join( + rel5, + rel6, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + ), + "inner" + ) + "inner_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_function("___coalesce", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + "arrange" + rel9 <- duckdb$rel_order( + rel8, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b.x"), duckdb$expr_reference("b.y")) + ) + rel9 + out <- duckdb$rel_to_altrep(rel9) + expect_identical( + out, + data.frame(a = 2:4, b.x = 2, b.y = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# intersect order-preserving ----------------------------------------------------------- + +test_that("relational intersect() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "semi_join" + rel1 <- duckdb$rel_from_df(con, df1) + "semi_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "semi_join" + rel3 <- duckdb$rel_from_df(con, df2) + "semi_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "semi_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "semi_join" + rel6 <- duckdb$rel_join( + rel5, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel5), duckdb$expr_reference("a", rel4))), + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("b", rel5), duckdb$expr_reference("b", rel4))) + ), + "semi" + ) + "semi_join" + rel7 <- duckdb$rel_order(rel6, list(duckdb$expr_reference("___row_number_x", rel5))) + "semi_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_project( + rel9, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel11 <- duckdb$rel_filter( + rel10, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel12 <- duckdb$rel_order(rel11, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel13 <- duckdb$rel_project( + rel12, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel13 + out <- duckdb$rel_to_altrep(rel13) + expect_identical( + out, + data.frame(a = 2:4, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# intersect order-enforcing ------------------------------------------------------------ + +test_that("relational intersect() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "intersect" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "intersect" + rel2 <- duckdb$rel_from_df(con, df2) + "intersect" + rel3 <- duckdb$rel_set_intersect(rel1, rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = 2:4, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# left_join order-preserving ----------------------------------------------------------- + +test_that("relational left_join(join_by(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + df1 <- data.frame(a = 1:4, b = 2) + + "left_join" + rel1 <- duckdb$rel_from_df(con, df1) + "left_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "left_join" + rel3 <- duckdb$rel_from_df(con, df2) + "left_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "left_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "left_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "left_join" + rel7 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a_x") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "left_join" + rel8 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a_y") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "left_join" + rel9 <- duckdb$rel_join( + rel7, + rel8, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + ), + "left" + ) + "left_join" + rel10 <- duckdb$rel_order( + rel9, + list(duckdb$expr_reference("___row_number_x", rel7), duckdb$expr_reference("___row_number_y", rel8)) + ) + "left_join" + rel11 <- duckdb$rel_project( + rel10, + list( + { + tmp_expr <- duckdb$expr_function("___coalesce", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + rel11 + out <- duckdb$rel_to_altrep(rel11) + expect_identical( + out, + data.frame(a = 1:4, b.x = 2, b.y = c(NA, 2, 2, 2)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# left_join order-enforcing ------------------------------------------------------------ + +test_that("relational left_join(join_by(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___coalesce"(x, y) AS COALESCE(x, y)')) + df1 <- data.frame(a = 1:4, b = 2) + + "left_join" + rel1 <- duckdb$rel_from_df(con, df1) + "left_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "left_join" + rel3 <- duckdb$rel_from_df(con, df2) + "left_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "left_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "left_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "left_join" + rel7 <- duckdb$rel_join( + rel5, + rel6, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + ), + "left" + ) + "left_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_function("___coalesce", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + "arrange" + rel9 <- duckdb$rel_order( + rel8, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b.x"), duckdb$expr_reference("b.y")) + ) + rel9 + out <- duckdb$rel_to_altrep(rel9) + expect_identical( + out, + data.frame(a = 1:4, b.x = 2, b.y = c(NA, 2, 2, 2)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# mutate order-preserving -------------------------------------------------------------- + +test_that("relational mutate() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + rel1 + out <- duckdb$rel_to_altrep(rel1) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(a + 1) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "a + 1") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `a + 1` = seq(2, 7, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(a + 1, .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "a + 1") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a + 1") + duckdb$expr_set_alias(tmp_expr, "a + 1") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `a + 1` = seq(2, 7, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = a + 1) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = seq(2, 7, by = 1) + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(`if` = a + 1) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "if") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `if` = seq(2, 7, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sum(a, na.rm = TRUE)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("sum", list(duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sum(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sum(a, na.rm = TRUE)` = 21, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sum(a, na.rm = TRUE), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("sum", list(duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sum(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("sum(a, na.rm = TRUE)") + duckdb$expr_set_alias(tmp_expr, "sum(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sum(a, na.rm = TRUE)` = c(1, 5, 5, 15, 15, 15), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(mean(a, na.rm = TRUE)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("mean", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "mean(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `mean(a, na.rm = TRUE)` = 3.5, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(mean(a, na.rm = TRUE), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("mean", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "mean(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("mean(a, na.rm = TRUE)") + duckdb$expr_set_alias(tmp_expr, "mean(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `mean(a, na.rm = TRUE)` = c(1, 2.5, 2.5, 5, 5, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sd(a, na.rm = TRUE)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("stddev", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sd(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sd(a, na.rm = TRUE)` = 0x1.deeea11683f49p+0, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sd(a, na.rm = TRUE), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("stddev", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sd(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("sd(a, na.rm = TRUE)") + duckdb$expr_set_alias(tmp_expr, "sd(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sd(a, na.rm = TRUE)` = c(NA, 0.7071067811865476, 0.7071067811865476, 1, 1, 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a)` = c(NA, 1, 2, 3, 4, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lag(a)") + duckdb$expr_set_alias(tmp_expr, "lag(a)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a)` = c(NA, NA, 2, NA, 4, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a)` = c(2, 3, 4, 5, 6, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lead(a)") + duckdb$expr_set_alias(tmp_expr, "lead(a)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a)` = c(NA, 3, NA, 5, 6, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 2)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 2)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 2)` = c(NA, NA, 1, 2, 3, 4), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 2), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 2)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lag(a, 2)") + duckdb$expr_set_alias(tmp_expr, "lag(a, 2)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 2)` = rep(c(NA, 4), c(5L, 1L)), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 2)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 2)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 2)` = c(3, 4, 5, 6, NA, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 2), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 2)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lead(a, 2)") + duckdb$expr_set_alias(tmp_expr, "lead(a, 2)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 2)` = c(NA, NA, NA, 6, NA, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 4)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 4)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 4)` = c(NA, NA, NA, NA, 1, 2), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 4), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 4)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lag(a, 4)") + duckdb$expr_set_alias(tmp_expr, "lag(a, 4)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 4)` = NA_real_, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 4)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 4)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 4)` = c(5, 6, NA, NA, NA, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 4), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 4)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lead(a, 4)") + duckdb$expr_set_alias(tmp_expr, "lead(a, 4)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 4)` = NA_real_, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, default = 0)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(0)) + duckdb$expr_set_alias(tmp_expr, "lag(a, default = 0)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, default = 0)` = seq(0, 5, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, default = 0), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(0)) + duckdb$expr_set_alias(tmp_expr, "lag(a, default = 0)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lag(a, default = 0)") + duckdb$expr_set_alias(tmp_expr, "lag(a, default = 0)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, default = 0)` = c(0, 0, 2, 0, 4, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, default = 1000)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(1000)) + duckdb$expr_set_alias(tmp_expr, "lead(a, default = 1000)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, default = 1000)` = c(2, 3, 4, 5, 6, 1000), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, default = 1000), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(1000)) + duckdb$expr_set_alias(tmp_expr, "lead(a, default = 1000)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("lead(a, default = 1000)") + duckdb$expr_set_alias(tmp_expr, "lead(a, default = 1000)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, default = 1000)` = c(1000, 3, 1000, 5, 6, 1000), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(min(a, na.rm = TRUE)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("min", list(duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "min(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `min(a, na.rm = TRUE)` = 1, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(min(a, na.rm = TRUE), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("min", list(duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "min(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("min(a, na.rm = TRUE)") + duckdb$expr_set_alias(tmp_expr, "min(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `min(a, na.rm = TRUE)` = c(1, 2, 2, 4, 4, 4), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(max(a, na.rm = TRUE)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("max", list(duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "max(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `max(a, na.rm = TRUE)` = 6, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(max(a, na.rm = TRUE), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("max", list(duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "max(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("max(a, na.rm = TRUE)") + duckdb$expr_set_alias(tmp_expr, "max(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `max(a, na.rm = TRUE)` = c(1, 3, 3, 6, 6, 6), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(a / b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___divide"(x, y) AS CASE WHEN y = 0 THEN CASE WHEN x = 0 THEN CAST('NaN' AS double) WHEN x > 0 THEN CAST('+Infinity' AS double) ELSE CAST('-Infinity' AS double) END ELSE CAST(x AS double) / y END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___divide", list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + duckdb$expr_set_alias(tmp_expr, "a/b") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `a/b` = seq(0.5, 3, by = 0.5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = 0, e = 1 / d, f = 0 / d, g = -1 / d) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___divide"(x, y) AS CASE WHEN y = 0 THEN CASE WHEN x = 0 THEN CAST('NaN' AS double) WHEN x > 0 THEN CAST('+Infinity' AS double) ELSE CAST('-Infinity' AS double) END ELSE CAST(x AS double) / y END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(0) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___divide", list(duckdb$expr_constant(1), duckdb$expr_reference("d"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___divide", list(duckdb$expr_constant(0), duckdb$expr_reference("d"))) + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "___divide", + list(duckdb$expr_function("-", list(duckdb$expr_constant(1))), duckdb$expr_reference("d")) + ) + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("f") + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = -Inf, d = 0, e = Inf, f = NaN) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 0, d = -1, e = log(c), f = suppressWarnings(log(d))) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE ln(x) END]" + ) + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "suppressWarnings"(x) AS (x)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(0) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("-", list(duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("suppressWarnings", list(duckdb$expr_function("___log", list(duckdb$expr_reference("d"))))) + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = 0, + d = -1, + e = -Inf, + f = NaN + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 0, d = -1, e = log10(c), f = suppressWarnings(log10(d))) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log10"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE log10(x) END]" + ) + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "suppressWarnings"(x) AS (x)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(0) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("-", list(duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log10", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("suppressWarnings", list(duckdb$expr_function("___log10", list(duckdb$expr_reference("d"))))) + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = 0, + d = -1, + e = -Inf, + f = NaN + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 10, d = log(c)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE ln(x) END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(10) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = 10, + d = 2.302585092994046 + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 10, d = log10(c)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log10"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE log10(x) END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(10) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log10", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), c = 10, d = 1) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = NA_character_, d = grepl('.', c)) order-preserving", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "grepl"(pattern, x) AS (CASE WHEN x IS NULL THEN FALSE ELSE regexp_matches(x, pattern) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("grepl", list(duckdb$expr_constant("."), duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = NA_character_, + d = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 'abbc', d = gsub('(b|c)', 'z' , c)) order-preserving", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "gsub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement, 'g'))]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant("abbc") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "gsub", + list(duckdb$expr_constant("(b|c)"), duckdb$expr_constant("z"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = "abbc", + d = "azzz" + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 'abbc', d = sub('(b|c)', 'z' , c)) order-preserving", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "sub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement))' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant("abbc") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "sub", + list(duckdb$expr_constant("(b|c)"), duckdb$expr_constant("z"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = "abbc", + d = "azbc" + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = NA_character_, d = gsub('.', '-' , c)) order-preserving", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "gsub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement, 'g'))]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "gsub", + list(duckdb$expr_constant("."), duckdb$expr_constant("-"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = NA_character_, + d = NA_character_ + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = NA_character_, d = sub('.', '-' , c)) order-preserving", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "sub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement))' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "sub", + list(duckdb$expr_constant("."), duckdb$expr_constant("-"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = NA_character_, + d = NA_character_ + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = a %in% NA_real_) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "is.na"(x) AS (x IS NULL)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("is.na", list(duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = FALSE) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = a %in% NULL) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(FALSE) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = FALSE) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = a %in% integer()) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(FALSE) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = FALSE) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_real_, e = is.na(d)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "is.na"(x) AS (x IS NULL)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_real_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("is.na", list(duckdb$expr_reference("d"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + d = NA_real_, + e = TRUE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = row_number()) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "r_base::as.integer", + list( + duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + ) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = 1:6) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = row_number(), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("___row_number") + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "r_base::as.integer", + list( + duckdb$expr_window(duckdb$expr_function("row_number", list()), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + ) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + d = c(1L, 1L, 2L, 1L, 2L, 3L) + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = .data$b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), c = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___null"() AS CAST(NULL AS BOOLEAN)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___null", list()) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_integer_) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA_integer_) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_real_) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_real_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA_real_) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_character_) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA_character_) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = if_else(a > 1, \"ok\", NA)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "if_else"(test, yes, no) AS (CASE WHEN test IS NULL THEN NULL ELSE CASE WHEN test THEN yes ELSE no END END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "if_else", + list( + duckdb$expr_comparison(">", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))), + duckdb$expr_constant("ok"), + duckdb$expr_constant(NA) + ) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + d = rep(c(NA, "ok"), c(1L, 5L)) + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# mutate order-enforcing --------------------------------------------------------------- + +test_that("relational mutate() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "arrange" + rel2 <- duckdb$rel_order( + rel1, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(a + 1) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "a + 1") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("a + 1")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `a + 1` = seq(2, 7, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(a + 1, .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "a + 1") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("a + 1")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `a + 1` = seq(2, 7, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = a + 1) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = seq(2, 7, by = 1) + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(`if` = a + 1) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "if") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("if")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `if` = seq(2, 7, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sum(a, na.rm = TRUE)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("sum", list(duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sum(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("sum(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sum(a, na.rm = TRUE)` = 21, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sum(a, na.rm = TRUE), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("sum", list(duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sum(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("sum(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sum(a, na.rm = TRUE)` = c(1, 5, 5, 15, 15, 15), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(mean(a, na.rm = TRUE)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("mean", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "mean(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("mean(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `mean(a, na.rm = TRUE)` = 3.5, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(mean(a, na.rm = TRUE), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("mean", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "mean(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("mean(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `mean(a, na.rm = TRUE)` = c(1, 2.5, 2.5, 5, 5, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sd(a, na.rm = TRUE)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("stddev", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sd(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("sd(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sd(a, na.rm = TRUE)` = 0x1.deeea11683f49p+0, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(sd(a, na.rm = TRUE), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("stddev", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "sd(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("sd(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `sd(a, na.rm = TRUE)` = c(NA, 0.7071067811865476, 0.7071067811865476, 1, 1, 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a)` = c(NA, 1, 2, 3, 4, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a)` = c(NA, NA, 2, NA, 4, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a)` = c(2, 3, 4, 5, 6, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a)` = c(NA, 3, NA, 5, 6, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 2)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 2)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a, 2)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 2)` = c(NA, NA, 1, 2, 3, 4), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 2), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 2)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a, 2)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 2)` = rep(c(NA, 4), c(5L, 1L)), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 2)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 2)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a, 2)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 2)` = c(3, 4, 5, 6, NA, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 2), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(2), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 2)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a, 2)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 2)` = c(NA, NA, NA, 6, NA, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 4)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 4)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a, 4)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 4)` = c(NA, NA, NA, NA, 1, 2), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, 4), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lag(a, 4)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a, 4)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, 4)` = NA_real_, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 4)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 4)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a, 4)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 4)` = c(5, 6, NA, NA, NA, NA), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, 4), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(4), default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "lead(a, 4)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a, 4)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, 4)` = NA_real_, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, default = 0)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(0)) + duckdb$expr_set_alias(tmp_expr, "lag(a, default = 0)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a, default = 0)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, default = 0)` = seq(0, 5, by = 1), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lag(a, default = 0), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lag", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(0)) + duckdb$expr_set_alias(tmp_expr, "lag(a, default = 0)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lag(a, default = 0)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lag(a, default = 0)` = c(0, 0, 2, 0, 4, 5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, default = 1000)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(1000)) + duckdb$expr_set_alias(tmp_expr, "lead(a, default = 1000)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a, default = 1000)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, default = 1000)` = c(2, 3, 4, 5, 6, 1000), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(lead(a, default = 1000), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("lead", list(x = duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = duckdb$expr_constant(1L), default_expr = duckdb$expr_constant(1000)) + duckdb$expr_set_alias(tmp_expr, "lead(a, default = 1000)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("lead(a, default = 1000)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `lead(a, default = 1000)` = c(1000, 3, 1000, 5, 6, 1000), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(min(a, na.rm = TRUE)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("min", list(duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "min(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("min(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `min(a, na.rm = TRUE)` = 1, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(min(a, na.rm = TRUE), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("min", list(duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "min(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("min(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `min(a, na.rm = TRUE)` = c(1, 2, 2, 4, 4, 4), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(max(a, na.rm = TRUE)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("max", list(duckdb$expr_reference("a"))), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "max(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("max(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `max(a, na.rm = TRUE)` = 6, + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(max(a, na.rm = TRUE), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("max", list(duckdb$expr_reference("a"))), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "max(a, na.rm = TRUE)") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("max(a, na.rm = TRUE)")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `max(a, na.rm = TRUE)` = c(1, 3, 3, 6, 6, 6), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(a / b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___divide"(x, y) AS CASE WHEN y = 0 THEN CASE WHEN x = 0 THEN CAST('NaN' AS double) WHEN x > 0 THEN CAST('+Infinity' AS double) ELSE CAST('-Infinity' AS double) END ELSE CAST(x AS double) / y END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___divide", list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + duckdb$expr_set_alias(tmp_expr, "a/b") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("a/b")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + `a/b` = seq(0.5, 3, by = 0.5), + check.names = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = 0, e = 1 / d, f = 0 / d, g = -1 / d) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___divide"(x, y) AS CASE WHEN y = 0 THEN CASE WHEN x = 0 THEN CAST('NaN' AS double) WHEN x > 0 THEN CAST('+Infinity' AS double) ELSE CAST('-Infinity' AS double) END ELSE CAST(x AS double) / y END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(0) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___divide", list(duckdb$expr_constant(1), duckdb$expr_reference("d"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___divide", list(duckdb$expr_constant(0), duckdb$expr_reference("d"))) + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "___divide", + list(duckdb$expr_function("-", list(duckdb$expr_constant(1))), duckdb$expr_reference("d")) + ) + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("f") + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + "arrange" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d"), duckdb$expr_reference("e"), duckdb$expr_reference("f")) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = -Inf, d = 0, e = Inf, f = NaN) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 0, d = -1, e = log(c), f = suppressWarnings(log(d))) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE ln(x) END]" + ) + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "suppressWarnings"(x) AS (x)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(0) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("-", list(duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("suppressWarnings", list(duckdb$expr_function("___log", list(duckdb$expr_reference("d"))))) + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + "arrange" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d"), duckdb$expr_reference("e"), duckdb$expr_reference("f")) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = 0, + d = -1, + e = -Inf, + f = NaN + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 0, d = -1, e = log10(c), f = suppressWarnings(log10(d))) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log10"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE log10(x) END]" + ) + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "suppressWarnings"(x) AS (x)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(0) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("-", list(duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel4 <- duckdb$rel_project( + rel3, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log10", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "mutate" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("e") + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("suppressWarnings", list(duckdb$expr_function("___log10", list(duckdb$expr_reference("d"))))) + duckdb$expr_set_alias(tmp_expr, "f") + tmp_expr + } + ) + ) + "arrange" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d"), duckdb$expr_reference("e"), duckdb$expr_reference("f")) + ) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = 0, + d = -1, + e = -Inf, + f = NaN + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 10, d = log(c)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE ln(x) END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(10) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = 10, + d = 2.302585092994046 + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 10, d = log10(c)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "___log10"(x) AS CASE WHEN x < 0 THEN CAST('NaN' AS double) WHEN x = 0 THEN CAST('-Inf' AS double) ELSE log10(x) END]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(10) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___log10", list(duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), c = 10, d = 1) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = NA_character_, d = grepl('.', c)) order-enforcing", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "grepl"(pattern, x) AS (CASE WHEN x IS NULL THEN FALSE ELSE regexp_matches(x, pattern) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("grepl", list(duckdb$expr_constant("."), duckdb$expr_reference("c"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = NA_character_, + d = FALSE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 'abbc', d = gsub('(b|c)', 'z' , c)) order-enforcing", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "gsub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement, 'g'))]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant("abbc") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "gsub", + list(duckdb$expr_constant("(b|c)"), duckdb$expr_constant("z"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = "abbc", + d = "azzz" + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = 'abbc', d = sub('(b|c)', 'z' , c)) order-enforcing", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "sub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement))' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant("abbc") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "sub", + list(duckdb$expr_constant("(b|c)"), duckdb$expr_constant("z"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = "abbc", + d = "azbc" + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = NA_character_, d = gsub('.', '-' , c)) order-enforcing", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + r"[CREATE MACRO "gsub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement, 'g'))]" + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "gsub", + list(duckdb$expr_constant("."), duckdb$expr_constant("-"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = NA_character_, + d = NA_character_ + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = NA_character_, d = sub('.', '-' , c)) order-enforcing", { + # skip_if_not(TEST_RE2) + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "sub"(pattern, replacement, x) AS (regexp_replace(x, pattern, replacement))' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "sub", + list(duckdb$expr_constant("."), duckdb$expr_constant("-"), duckdb$expr_reference("c")) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c"), duckdb$expr_reference("d")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + c = NA_character_, + d = NA_character_ + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = a %in% NA_real_) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "is.na"(x) AS (x IS NULL)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("is.na", list(duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = FALSE) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = a %in% NULL) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(FALSE) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = FALSE) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = a %in% integer()) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(FALSE) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = FALSE) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_real_, e = is.na(d)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "is.na"(x) AS (x IS NULL)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_real_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "mutate" + rel3 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("d") + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("is.na", list(duckdb$expr_reference("d"))) + duckdb$expr_set_alias(tmp_expr, "e") + tmp_expr + } + ) + ) + "arrange" + rel4 <- duckdb$rel_order( + rel3, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d"), duckdb$expr_reference("e")) + ) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + d = NA_real_, + e = TRUE + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = row_number()) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "r_base::as.integer", + list( + duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + ) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = 1:6) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = row_number(), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(duckdb$rapi_load_rfuns(drv@database_ref)) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "r_base::as.integer", + list( + duckdb$expr_window(duckdb$expr_function("row_number", list()), list(duckdb$expr_reference("g")), list(), offset_expr = NULL, default_expr = NULL) + ) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + d = c(1L, 1L, 2L, 1L, 2L, 3L) + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(c = .data$b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("c")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), c = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "___null"() AS CAST(NULL AS BOOLEAN)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___null", list()) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_integer_) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA_integer_) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_real_) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_real_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA_real_) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = NA_character_) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_character_) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L), d = NA_character_) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational mutate(d = if_else(a > 1, \"ok\", NA)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "if_else"(test, yes, no) AS (CASE WHEN test IS NULL THEN NULL ELSE CASE WHEN test THEN yes ELSE no END END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "mutate" + rel1 <- duckdb$rel_from_df(con, df1) + "mutate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function( + "if_else", + list( + duckdb$expr_comparison(">", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))), + duckdb$expr_constant("ok"), + duckdb$expr_constant(NA) + ) + ) + duckdb$expr_set_alias(tmp_expr, "d") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"), duckdb$expr_reference("d")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame( + a = seq(1, 6, by = 1), + b = 2, + g = c(1L, 2L, 2L, 3L, 3L, 3L), + d = rep(c(NA, "ok"), c(1L, 5L)) + ) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# relocate order-preserving ------------------------------------------------------------ + +test_that("relational relocate(g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(g = c(1L, 2L, 2L, 3L, 3L, 3L), a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational relocate(a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational relocate(g, .before = b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), g = c(1L, 2L, 2L, 3L, 3L, 3L), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational relocate(a:b, .after = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(g = c(1L, 2L, 2L, 3L, 3L, 3L), a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# relocate order-enforcing ------------------------------------------------------------- + +test_that("relational relocate(g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("g"), duckdb$expr_reference("a"), duckdb$expr_reference("b")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = c(1L, 2L, 2L, 3L, 3L, 3L), a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational relocate(a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational relocate(g, .before = b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("g"), duckdb$expr_reference("b")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), g = c(1L, 2L, 2L, 3L, 3L, 3L), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational relocate(a:b, .after = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "relocate" + rel1 <- duckdb$rel_from_df(con, df1) + "relocate" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("g"), duckdb$expr_reference("a"), duckdb$expr_reference("b")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = c(1L, 2L, 2L, 3L, 3L, 3L), a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# rename order-preserving -------------------------------------------------------------- + +test_that("relational rename() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "rename" + rel1 <- duckdb$rel_from_df(con, df1) + "rename" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational rename(c = a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "rename" + rel1 <- duckdb$rel_from_df(con, df1) + "rename" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(c = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# rename order-enforcing --------------------------------------------------------------- + +test_that("relational rename() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "rename" + rel1 <- duckdb$rel_from_df(con, df1) + "rename" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational rename(c = a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "rename" + rel1 <- duckdb$rel_from_df(con, df1) + "rename" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("c"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(c = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# right_join order-preserving ---------------------------------------------------------- + +test_that("relational right_join(join_by(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "right_join" + rel1 <- duckdb$rel_from_df(con, df1) + "right_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "right_join" + rel3 <- duckdb$rel_from_df(con, df2) + "right_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "right_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "right_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "right_join" + rel7 <- duckdb$rel_project( + rel5, + list( + { + tmp_expr <- duckdb$expr_reference("a_x") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "right_join" + rel8 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a_y") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "right_join" + rel9 <- duckdb$rel_join( + rel7, + rel8, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel7), duckdb$expr_reference("a_y", rel8))) + ), + "right" + ) + "right_join" + rel10 <- duckdb$rel_order( + rel9, + list(duckdb$expr_reference("___row_number_x", rel7), duckdb$expr_reference("___row_number_y", rel8)) + ) + "right_join" + rel11 <- duckdb$rel_project( + rel10, + list( + { + tmp_expr <- duckdb$expr_reference("a_y", rel8) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + rel11 + out <- duckdb$rel_to_altrep(rel11) + expect_identical( + out, + data.frame(a = 2:5, b.x = c(2, 2, 2, NA), b.y = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# right_join order-enforcing ----------------------------------------------------------- + +test_that("relational right_join(join_by(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "right_join" + rel1 <- duckdb$rel_from_df(con, df1) + "right_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "right_join" + rel3 <- duckdb$rel_from_df(con, df2) + "right_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "right_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_x") + tmp_expr + } + ) + ) + "right_join" + rel6 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a_y") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b_y") + tmp_expr + } + ) + ) + "right_join" + rel7 <- duckdb$rel_join( + rel5, + rel6, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a_x", rel5), duckdb$expr_reference("a_y", rel6))) + ), + "right" + ) + "right_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a_y", rel6) + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_x") + duckdb$expr_set_alias(tmp_expr, "b.x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b_y") + duckdb$expr_set_alias(tmp_expr, "b.y") + tmp_expr + } + ) + ) + "arrange" + rel9 <- duckdb$rel_order( + rel8, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b.x"), duckdb$expr_reference("b.y")) + ) + rel9 + out <- duckdb$rel_to_altrep(rel9) + expect_identical( + out, + data.frame(a = 2:5, b.x = c(2, 2, 2, NA), b.y = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# select order-preserving -------------------------------------------------------------- + +test_that("relational select(a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "select" + rel1 <- duckdb$rel_from_df(con, df1) + "select" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational select(-g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "select" + rel1 <- duckdb$rel_from_df(con, df1) + "select" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational select(everything()) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "select" + rel1 <- duckdb$rel_from_df(con, df1) + "select" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# select order-enforcing --------------------------------------------------------------- + +test_that("relational select(a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "select" + rel1 <- duckdb$rel_from_df(con, df1) + "select" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("a"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational select(-g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "select" + rel1 <- duckdb$rel_from_df(con, df1) + "select" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational select(everything()) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "select" + rel1 <- duckdb$rel_from_df(con, df1) + "select" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# semi_join order-preserving ----------------------------------------------------------- + +test_that("relational semi_join(join_by(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "semi_join" + rel1 <- duckdb$rel_from_df(con, df1) + "semi_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "semi_join" + rel3 <- duckdb$rel_from_df(con, df2) + "semi_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "semi_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "semi_join" + rel6 <- duckdb$rel_join( + rel5, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel5), duckdb$expr_reference("a", rel4))) + ), + "semi" + ) + "semi_join" + rel7 <- duckdb$rel_order(rel6, list(duckdb$expr_reference("___row_number_x", rel5))) + "semi_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel8 + out <- duckdb$rel_to_altrep(rel8) + expect_identical( + out, + data.frame(a = 2:4, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# semi_join order-enforcing ------------------------------------------------------------ + +test_that("relational semi_join(join_by(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "semi_join" + rel1 <- duckdb$rel_from_df(con, df1) + "semi_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "semi_join" + rel3 <- duckdb$rel_from_df(con, df2) + "semi_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "semi_join" + rel5 <- duckdb$rel_join( + rel2, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel2), duckdb$expr_reference("a", rel4))) + ), + "semi" + ) + "arrange" + rel6 <- duckdb$rel_order(rel5, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel6 + out <- duckdb$rel_to_altrep(rel6) + expect_identical( + out, + data.frame(a = 2:4, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# setdiff order-preserving ------------------------------------------------------------- + +test_that("relational setdiff() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "anti_join" + rel1 <- duckdb$rel_from_df(con, df1) + "anti_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "anti_join" + rel3 <- duckdb$rel_from_df(con, df2) + "anti_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "anti_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "anti_join" + rel6 <- duckdb$rel_join( + rel5, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel5), duckdb$expr_reference("a", rel4))), + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("b", rel5), duckdb$expr_reference("b", rel4))) + ), + "anti" + ) + "anti_join" + rel7 <- duckdb$rel_order(rel6, list(duckdb$expr_reference("___row_number_x", rel5))) + "anti_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_project( + rel9, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel11 <- duckdb$rel_filter( + rel10, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel12 <- duckdb$rel_order(rel11, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel13 <- duckdb$rel_project( + rel12, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel13 + out <- duckdb$rel_to_altrep(rel13) + expect_identical( + out, + data.frame(a = 1L, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# setdiff order-enforcing -------------------------------------------------------------- + +test_that("relational setdiff() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "setdiff" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "setdiff" + rel2 <- duckdb$rel_from_df(con, df2) + "setdiff" + rel3 <- duckdb$rel_set_diff(rel1, rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = 1L, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# slice_head order-preserving ---------------------------------------------------------- + +test_that("relational slice_head(n = 2) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "slice_head" + rel1 <- duckdb$rel_from_df(con, df1) + "slice_head" + rel2 <- duckdb$rel_limit(rel1, 2) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(a = c(1, 2), b = 2, g = 1:2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# slice_head order-enforcing ----------------------------------------------------------- + +test_that("relational slice_head(n = 2) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "slice_head" + rel1 <- duckdb$rel_from_df(con, df1) + "slice_head" + rel2 <- duckdb$rel_limit(rel1, 2) + "arrange" + rel3 <- duckdb$rel_order( + rel2, + list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g")) + ) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(a = c(1, 2), b = 2, g = 1:2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# summarise order-preserving ----------------------------------------------------------- + +test_that("relational summarise(c = mean(a)) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___mean_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___mean_na", list(x = duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(c = 3.5) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = mean(a), .by = b) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___min_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE MIN(x) END)' + ) + ) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___mean_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_aggregate( + rel2, + groups = list(duckdb$expr_reference("b")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___min_na", list(duckdb$expr_reference("___row_number"))) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___mean_na", list(x = duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "summarise" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(b = 2, c = 3.5) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = mean(a), .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___min_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE MIN(x) END)' + ) + ) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___mean_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_aggregate( + rel2, + groups = list(duckdb$expr_reference("g")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___min_na", list(duckdb$expr_reference("___row_number"))) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("___mean_na", list(x = duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "summarise" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(g = 1:3, c = c(1, 2.5, 5)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = 1) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_constant(1) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(c = 1) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = 1, .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___min_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE MIN(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_aggregate( + rel2, + groups = list(duckdb$expr_reference("g")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___min_na", list(duckdb$expr_reference("___row_number"))) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(1) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "summarise" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("c") + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(g = 1:3, c = 1) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(n = n(), n = n() + 1L, .by = g) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___min_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE MIN(x) END)' + ) + ) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_aggregate( + rel2, + groups = list(duckdb$expr_reference("g")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___min_na", list(duckdb$expr_reference("___row_number"))) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_function("n", list()), duckdb$expr_constant(1L))) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "summarise" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("___row_number"))) + "summarise" + rel5 <- duckdb$rel_project( + rel4, + list( + { + tmp_expr <- duckdb$expr_reference("g") + duckdb$expr_set_alias(tmp_expr, "g") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("n") + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(g = 1:3, n = 2:4) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(n = n(), n = n() + 1L) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_function("n", list()), duckdb$expr_constant(1L))) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(n = 7L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# summarise order-enforcing ------------------------------------------------------------ + +test_that("relational summarise(c = mean(a)) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___mean_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___mean_na", list(x = duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("c"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(c = 3.5) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = mean(a), .by = b) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___mean_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(duckdb$expr_reference("b")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___mean_na", list(x = duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("b"), duckdb$expr_reference("c"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(b = 2, c = 3.5) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = mean(a), .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute( + con, + 'CREATE MACRO "___mean_na"(x) AS (CASE WHEN SUM(CASE WHEN x IS NULL THEN 1 ELSE 0 END) > 0 THEN NULL ELSE AVG(x) END)' + ) + ) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(duckdb$expr_reference("g")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("___mean_na", list(x = duckdb$expr_reference("a"))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("g"), duckdb$expr_reference("c"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = 1:3, c = c(1, 2.5, 5)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = 1) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_constant(1) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("c"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(c = 1) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(c = 1, .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(duckdb$expr_reference("g")), + aggregates = list( + { + tmp_expr <- duckdb$expr_constant(1) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("g"), duckdb$expr_reference("c"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = 1:3, c = 1) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(n = n(), n = n() + 1L, .by = g) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(duckdb$expr_reference("g")), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_function("n", list()), duckdb$expr_constant(1L))) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("g"), duckdb$expr_reference("n"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(g = 1:3, n = 2:4) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational summarise(n = n(), n = n() + 1L) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_function("n", list()), duckdb$expr_constant(1L))) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("n"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(n = 7L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# symdiff order-preserving ------------------------------------------------------------- + +test_that("relational symdiff() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible( + DBI::dbExecute(con, 'CREATE MACRO "___eq_na_matches_na"(x, y) AS (x IS NOT DISTINCT FROM y)') + ) + df1 <- data.frame(a = 1:4, b = 2) + + "anti_join" + rel1 <- duckdb$rel_from_df(con, df1) + "anti_join" + rel2 <- duckdb$rel_set_alias(rel1, "lhs") + df2 <- data.frame(a = 2:5, b = 2) + + "anti_join" + rel3 <- duckdb$rel_from_df(con, df2) + "anti_join" + rel4 <- duckdb$rel_set_alias(rel3, "rhs") + "anti_join" + rel5 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + } + ) + ) + "anti_join" + rel6 <- duckdb$rel_join( + rel5, + rel4, + list( + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("a", rel5), duckdb$expr_reference("a", rel4))), + duckdb$expr_function("___eq_na_matches_na", list(duckdb$expr_reference("b", rel5), duckdb$expr_reference("b", rel4))) + ), + "anti" + ) + "anti_join" + rel7 <- duckdb$rel_order(rel6, list(duckdb$expr_reference("___row_number_x", rel5))) + "anti_join" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + df3 <- data.frame(a = 5L, b = 2) + + "union_all" + rel9 <- duckdb$rel_from_df(con, df3) + "union_all" + rel10 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel11 <- duckdb$rel_project( + rel9, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel12 <- duckdb$rel_union_all(rel10, rel11) + "union_all" + rel13 <- duckdb$rel_order( + rel12, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel14 <- duckdb$rel_project( + rel13, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "distinct" + rel15 <- duckdb$rel_project( + rel14, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel16 <- duckdb$rel_project( + rel15, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel17 <- duckdb$rel_filter( + rel16, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel18 <- duckdb$rel_order(rel17, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel19 <- duckdb$rel_project( + rel18, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel19 + out <- duckdb$rel_to_altrep(rel19) + expect_identical( + out, + data.frame(a = c(1L, 5L), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# symdiff order-enforcing -------------------------------------------------------------- + +test_that("relational symdiff() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "symdiff" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "symdiff" + rel2 <- duckdb$rel_from_df(con, df2) + "symdiff" + rel3 <- duckdb$rel_set_symdiff(rel1, rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = c(1L, 5L), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# tally order-preserving --------------------------------------------------------------- + +test_that("relational tally() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(n = 6L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# tally order-enforcing ---------------------------------------------------------------- + +test_that("relational tally() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + invisible(DBI::dbExecute(con, 'CREATE MACRO "n"() AS CAST(COUNT(*) AS int32)')) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "summarise" + rel1 <- duckdb$rel_from_df(con, df1) + "summarise" + rel2 <- duckdb$rel_aggregate( + rel1, + groups = list(), + aggregates = list( + { + tmp_expr <- duckdb$expr_function("n", list()) + duckdb$expr_set_alias(tmp_expr, "n") + tmp_expr + } + ) + ) + "summarise" + rel3 <- duckdb$rel_distinct(rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("n"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(n = 6L) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# transmute order-preserving ----------------------------------------------------------- + +test_that("relational transmute(c = a + 1) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "transmute" + rel1 <- duckdb$rel_from_df(con, df1) + "transmute" + rel2 <- duckdb$rel_project( + rel1, + list( + c = { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(c = seq(2, 7, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational transmute(row = a) order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "transmute" + rel1 <- duckdb$rel_from_df(con, df1) + "transmute" + rel2 <- duckdb$rel_project( + rel1, + list( + row = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "row") + tmp_expr + } + ) + ) + rel2 + out <- duckdb$rel_to_altrep(rel2) + expect_identical( + out, + data.frame(row = seq(1, 6, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# transmute order-enforcing ------------------------------------------------------------ + +test_that("relational transmute(c = a + 1) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "transmute" + rel1 <- duckdb$rel_from_df(con, df1) + "transmute" + rel2 <- duckdb$rel_project( + rel1, + list( + c = { + tmp_expr <- duckdb$expr_function("+", list(duckdb$expr_reference("a"), duckdb$expr_constant(1))) + duckdb$expr_set_alias(tmp_expr, "c") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("c"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(c = seq(2, 7, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +test_that("relational transmute(row = a) order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L)) + + "transmute" + rel1 <- duckdb$rel_from_df(con, df1) + "transmute" + rel2 <- duckdb$rel_project( + rel1, + list( + row = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "row") + tmp_expr + } + ) + ) + "arrange" + rel3 <- duckdb$rel_order(rel2, list(duckdb$expr_reference("row"))) + rel3 + out <- duckdb$rel_to_altrep(rel3) + expect_identical( + out, + data.frame(row = seq(1, 6, by = 1)) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# union order-preserving --------------------------------------------------------------- + +test_that("relational union() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + "distinct" + rel8 <- duckdb$rel_project( + rel7, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number") + tmp_expr + } + ) + ) + "distinct" + rel9 <- duckdb$rel_project( + rel8, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + duckdb$expr_reference("___row_number"), + { + tmp_expr <- duckdb$expr_window( + duckdb$expr_function("row_number", list()), + list( + a = { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + b = { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ), + list(duckdb$expr_reference("___row_number")), + offset_expr = NULL, + default_expr = NULL + ) + duckdb$expr_set_alias(tmp_expr, "___row_number_by") + tmp_expr + } + ) + ) + "distinct" + rel10 <- duckdb$rel_filter( + rel9, + list( + duckdb$expr_comparison("==", list(duckdb$expr_reference("___row_number_by"), duckdb$expr_constant(1L))) + ) + ) + "distinct" + rel11 <- duckdb$rel_order(rel10, list(duckdb$expr_reference("___row_number"))) + "distinct" + rel12 <- duckdb$rel_project( + rel11, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel12 + out <- duckdb$rel_to_altrep(rel12) + expect_identical( + out, + data.frame(a = 1:5, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# union order-enforcing ---------------------------------------------------------------- + +test_that("relational union() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "distinct" + rel4 <- duckdb$rel_distinct(rel3) + "arrange" + rel5 <- duckdb$rel_order(rel4, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel5 + out <- duckdb$rel_to_altrep(rel5) + expect_identical( + out, + data.frame(a = 1:5, b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# union_all order-preserving ----------------------------------------------------------- + +test_that("relational union_all() order-preserving", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_project( + rel1, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel4 <- duckdb$rel_project( + rel2, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_constant(NA_integer_) + duckdb$expr_set_alias(tmp_expr, "___row_number_x") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_window(duckdb$expr_function("row_number", list()), list(), list(), offset_expr = NULL, default_expr = NULL) + duckdb$expr_set_alias(tmp_expr, "___row_number_y") + tmp_expr + } + ) + ) + "union_all" + rel5 <- duckdb$rel_union_all(rel3, rel4) + "union_all" + rel6 <- duckdb$rel_order( + rel5, + list(duckdb$expr_reference("___row_number_x"), duckdb$expr_reference("___row_number_y")) + ) + "union_all" + rel7 <- duckdb$rel_project( + rel6, + list( + { + tmp_expr <- duckdb$expr_reference("a") + duckdb$expr_set_alias(tmp_expr, "a") + tmp_expr + }, + { + tmp_expr <- duckdb$expr_reference("b") + duckdb$expr_set_alias(tmp_expr, "b") + tmp_expr + } + ) + ) + rel7 + out <- duckdb$rel_to_altrep(rel7) + expect_identical( + out, + data.frame(a = c(1L, 2L, 3L, 4L, 2L, 3L, 4L, 5L), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) + +# union_all order-enforcing ------------------------------------------------------------ + +test_that("relational union_all() order-enforcing", { + # Autogenerated + duckdb <- asNamespace("duckdb") + drv <- duckdb::duckdb() + con <- DBI::dbConnect(drv) + df1 <- data.frame(a = 1:4, b = 2) + + "union_all" + rel1 <- duckdb$rel_from_df(con, df1) + df2 <- data.frame(a = 2:5, b = 2) + + "union_all" + rel2 <- duckdb$rel_from_df(con, df2) + "union_all" + rel3 <- duckdb$rel_union_all(rel1, rel2) + "arrange" + rel4 <- duckdb$rel_order(rel3, list(duckdb$expr_reference("a"), duckdb$expr_reference("b"))) + rel4 + out <- duckdb$rel_to_altrep(rel4) + expect_identical( + out, + data.frame(a = c(1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L), b = 2) + ) + DBI::dbDisconnect(con, shutdown = TRUE) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-duckdb.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-duckdb.R new file mode 100644 index 000000000..4df078a3a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-duckdb.R @@ -0,0 +1,140 @@ +test_that("duckdb_rel_from_df()", { + df <- vctrs::data_frame( + LOGICAL = TRUE, + INTEGER = 2L, + NUMERIC = 3, + STRING = "four", + # FACTOR = factor("five"), # https://github.com/duckdb/duckdb/issues/8561 + DATE = as.Date("2023-08-05"), + DATE_INTEGER = structure(19574L, class = "Date"), + TIMESTAMP = structure(1691210978.25436, class = c("POSIXct", "POSIXt"), tzone = "UTC"), + TIME_SECONDS = structure(6, class = "difftime", units = "secs"), + # TIME_MINUTES etc.: Loss ok + TIME_SECONDS_INTEGER = structure(11L, class = "difftime", units = "secs"), + # TIME_MINUTES_INTEGER etc.: Loss ok + + # INTEGER64 = structure(1, class = "integer64"), + # LIST_OF_NULLS = list(NULL), + # BLOB = list(raw(16)), + + `NA` = NA, + ) + + if (Sys.getenv("DUCKPLYR_CHECK_ROUNDTRIP") == "TRUE") { + # Ingestion only + df$DATE_INTEGER <- NULL + df$TIME_SECONDS_INTEGER <- NULL + } + + expect_silent(duckdb_rel_from_df(df)) + + expect_identical( + map(df, vec_ptype_safe), + map(df, vec_ptype) + ) + + # FIXME: Test that vec_ptype_safe() does not materialize (#149), + # remove test-altrep.R + + skip_if(Sys.getenv("DUCKPLYR_CHECK_ROUNDTRIP") == "TRUE") + + # If this is no longer an eror, we need to make sure that subsetting + # forwards to the vector class + expect_snapshot(error = TRUE, { + data.frame(a = vctrs::new_vctr(1:3)) %>% + duckdb_rel_from_df() + }) +}) + +test_that("duckdb_rel_from_df() and changing column names", { + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + + x <- + data.frame(a = 1) %>% + duckplyr_select(a) + + names(x) <- "b" + expect_equal(x %>% duckplyr_filter(b == 1), data.frame(b = 1)) +}) + +test_that("duckdb_rel_from_df() error call", { + expect_snapshot(error = TRUE, { + as_duckdb_tibble(data.frame(a = factor(letters))) + }) +}) + +test_that("rel_aggregate()", { + skip_if_not_installed("palmerpenguins") + + expr_species <- relexpr_reference("species") + expr_aggregate <- relexpr_function(alias = "mean_bill_length_mm", "avg", list( + relexpr_reference("bill_length_mm") + )) + + grouped <- + palmerpenguins::penguins %>% + # https://github.com/duckdb/duckdb/issues/8561 + mutate(species = as.character(species)) %>% + mutate(island = as.character(island)) %>% + mutate(sex = as.character(sex)) %>% + as_duckdb_tibble() %>% + duckdb_rel_from_df() %>% + rel_aggregate(list(expr_species), list(expr_aggregate)) + + ungrouped <- + palmerpenguins::penguins %>% + # https://github.com/duckdb/duckdb/issues/8561 + mutate(species = as.character(species)) %>% + mutate(island = as.character(island)) %>% + mutate(sex = as.character(sex)) %>% + as_duckdb_tibble() %>% + duckdb_rel_from_df() %>% + rel_aggregate(list(), list(expr_aggregate)) + + expect_snapshot({ + grouped %>% + rel_to_df(prudence = "lavish") %>% + arrange(species) + ungrouped %>% + rel_to_df(prudence = "lavish") + }) +}) + +test_that("duckdb_rel_from_df() uses materialized results", { + skip_if(identical(Sys.getenv("R_COVR"), "true")) + + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + n_calls <- 0 + withr::local_options(duckdb.materialize_callback = function(...) { + n_calls <<- n_calls + 1 + }) + + df <- + data.frame(a = 1) %>% + duckplyr_filter(a == 1) + + transform <- function(x) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x + } + + expect_equal(n_calls, 0) + + expect_snapshot(transform = transform, { + duckdb_rel_from_df(df) + }) + + expect_equal(n_calls, 0) + + expect_snapshot(transform = transform, { + nrow(df) + }) + + expect_equal(n_calls, 1) + + expect_snapshot(transform = transform, { + duckdb_rel_from_df(df) + }) + + expect_equal(n_calls, 1) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-rel.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-rel.R new file mode 100644 index 000000000..02f7b95ad --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational-rel.R @@ -0,0 +1,5 @@ +test_that("new_relational()", { + expect_snapshot({ + new_relational(list()) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational.R new file mode 100644 index 000000000..db415c659 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-relational.R @@ -0,0 +1,12 @@ +test_that("rel_try() with reason", { + withr::local_envvar(DUCKPLYR_FALLBACK_INFO = TRUE, DUCKPLYR_FORCE = FALSE) + + expect_snapshot({ + rel_try(NULL, + "Not affected: {.code FALSE}" = FALSE, + "Affected: {.code TRUE}" = TRUE, + { + } + ) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sets.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sets.R new file mode 100644 index 000000000..6249c40a3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sets.R @@ -0,0 +1,5 @@ +test_that("stingy union_all()", { + data <- duckdb_tibble(x = 1, .prudence = "stingy") + out <- collect(union_all(data, tibble(x = 2))) + expect_equal(out, tibble(x = c(1, 2))) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sql.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sql.R new file mode 100644 index 000000000..7b79eedc2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-sql.R @@ -0,0 +1,8 @@ +test_that("read_sql_duckdb() works", { + con <- withr::local_db_connection(DBI::dbConnect(duckdb::duckdb())) + + expect_identical( + read_sql_duckdb("SELECT 1 AS a", con = con, prudence = "lavish"), + duckdb_tibble(a = 1L) + ) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-telemetry.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-telemetry.R new file mode 100644 index 000000000..550c3281d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-telemetry.R @@ -0,0 +1,300 @@ +withr::local_envvar(DUCKPLYR_TELEMETRY_TEST = TRUE) + +test_that("telemetry and anti_join()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + anti_join(tibble(a = 1:3, b = 4:6)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + anti_join( + tibble(a = 1:3, b = 4:6), + by = "a", + copy = FALSE, + na_matches = "na" + ) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + anti_join( + tibble(a = 1:3, b = 4:6), + by = c("a" = "b"), + copy = FALSE, + na_matches = "na" + ) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + anti_join( + tibble(a = 1:3, b = 4:6), + by = join_by(a == b), + copy = FALSE, + na_matches = "never" + ) + }) +}) + +test_that("telemetry and arrange()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + arrange(a) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + arrange(a, .by_group = TRUE) + }) +}) + +test_that("telemetry and count()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + count(a) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + count(a, wt = b, sort = TRUE, name = "nn", .drop = FALSE) + }) +}) + +test_that("telemetry and distinct()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + distinct(a) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + distinct(a, b, .keep_all = TRUE) + }) +}) + +test_that("telemetry and filter()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + filter(a > 1) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + filter(a > 1, .by = b) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + filter(a > 1, .preserve = TRUE) + }) +}) + +test_that("telemetry and full_join()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + full_join(tibble(a = 1:3, b = 4:6)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + full_join( + tibble(a = 1:3, b = 4:6), + by = "a", + copy = TRUE, + suffix = c("x", "y"), + keep = TRUE, + na_matches = "na", + multiple = "all", + relationship = "one-to-one" + ) + }) +}) + +test_that("telemetry and inner_join()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + inner_join(tibble(a = 1:3, b = 4:6)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + inner_join( + tibble(a = 1:3, b = 4:6), + by = "a", + copy = TRUE, + suffix = c("x", "y"), + keep = TRUE, + na_matches = "na", + multiple = "all", + unmatched = "error", + relationship = "one-to-one" + ) + }) +}) + +test_that("telemetry and intersect()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + intersect(tibble(a = 1:3, b = 4:6)) + }) +}) + +test_that("telemetry and left_join()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + left_join(tibble(a = 1:3, b = 4:6)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + left_join( + tibble(a = 1:3, b = 4:6), + by = "a", + copy = TRUE, + suffix = c("x", "y"), + keep = TRUE, + na_matches = "na", + multiple = "all", + unmatched = "error", + relationship = "one-to-one" + ) + }) +}) + +test_that("telemetry and mutate()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + mutate(c = a + b) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + mutate(c = a + b, .by = a, .keep = "unused", ) + }) +}) + +test_that("telemetry and relocate()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + relocate(b) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + relocate(b, .before = a) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + relocate(a, .after = b) + }) +}) + +test_that("telemetry and rename()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + rename(c = a) + }) +}) + +test_that("telemetry and right_join()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + right_join(tibble(a = 1:3, b = 4:6)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + right_join( + tibble(a = 1:3, b = 4:6), + by = "a", + copy = TRUE, + suffix = c("x", "y"), + keep = TRUE, + na_matches = "na", + multiple = "all", + unmatched = "error", + relationship = "one-to-one" + ) + }) +}) + +test_that("telemetry and select()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + select(c = b) + }) +}) + +test_that("telemetry and semi_join()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + semi_join(tibble(a = 1:3, b = 4:6)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + semi_join( + tibble(a = 1:3, b = 4:6), + by = "a", + copy = TRUE, + na_matches = "na" + ) + }) +}) + +test_that("telemetry and setdiff()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + setdiff(tibble(a = 1:3, b = 4:6)) + }) +}) + +test_that("telemetry and summarise()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + summarise(c = sum(b)) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + summarise(c = sum(b), .by = a) + }) + + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + summarise(c = sum(b), .groups = "rowwise") + }) +}) + +test_that("telemetry and symdiff()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + symdiff(tibble(a = 1:3, b = 4:6)) + }) +}) + +test_that("telemetry and transmute()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + transmute(c = a + b) + }) +}) + +test_that("telemetry and union_all()", { + expect_snapshot(error = TRUE, { + duckdb_tibble(a = 1:3, b = 4:6) %>% + union_all(tibble(a = 1:3, b = 4:6)) + }) +}) + +test_that("scrubbing function declarations", { + expect_snapshot({ + expr <- expr( + across(x:y, function(arg) mean(arg, na.rm = TRUE)) + ) + + expr_scrub(expr) + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-tpch.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-tpch.R new file mode 100644 index 000000000..37326c029 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-tpch.R @@ -0,0 +1,271 @@ +test_that("TPCH queries can be parsed and run", { + customer <- duckdb_tibble( + c_custkey = integer(0), + c_name = character(0), + c_address = character(0), + c_nationkey = integer(0), + c_phone = character(0), + c_acctbal = numeric(0), + c_mktsegment = character(0), + c_comment = character(0), + .prudence = "stingy" + ) + + lineitem <- duckdb_tibble( + l_orderkey = integer(0), + l_partkey = integer(0), + l_suppkey = integer(0), + l_linenumber = integer(0), + l_quantity = numeric(0), + l_extendedprice = numeric(0), + l_discount = numeric(0), + l_tax = numeric(0), + l_returnflag = character(0), + l_linestatus = character(0), + l_shipdate = as.Date(character(0)), + l_commitdate = as.Date(character(0)), + l_receiptdate = as.Date(character(0)), + l_shipinstruct = character(0), + l_shipmode = character(0), + l_comment = character(0), + .prudence = "stingy" + ) + + nation <- duckdb_tibble( + n_nationkey = integer(0), + n_name = character(0), + n_regionkey = integer(0), + n_comment = character(0), + .prudence = "stingy" + ) + + orders <- duckdb_tibble( + o_orderkey = integer(0), + o_custkey = integer(0), + o_orderstatus = character(0), + o_totalprice = numeric(0), + o_orderdate = as.Date(character(0)), + o_orderpriority = character(0), + o_clerk = character(0), + o_shippriority = integer(0), + o_comment = character(0), + .prudence = "stingy" + ) + + part <- duckdb_tibble( + p_partkey = integer(0), + p_name = character(0), + p_mfgr = character(0), + p_brand = character(0), + p_type = character(0), + p_size = integer(0), + p_container = character(0), + p_retailprice = numeric(0), + p_comment = character(0), + .prudence = "stingy" + ) + + partsupp <- duckdb_tibble( + ps_partkey = integer(0), + ps_suppkey = integer(0), + ps_availqty = integer(0), + ps_supplycost = numeric(0), + ps_comment = character(0), + .prudence = "stingy" + ) + + region <- duckdb_tibble( + r_regionkey = integer(0), + r_name = character(0), + r_comment = character(0), + .prudence = "stingy" + ) + + supplier <- duckdb_tibble( + s_suppkey = integer(0), + s_name = character(0), + s_address = character(0), + s_nationkey = integer(0), + s_phone = character(0), + s_acctbal = numeric(0), + s_comment = character(0), + .prudence = "stingy" + ) + + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + local_options(duckdb.materialize_callback = NULL) + + local_bindings( + customer = customer, + lineitem = lineitem, + nation = nation, + orders = orders, + part = part, + partsupp = partsupp, + region = region, + supplier = supplier, + .env = .GlobalEnv + ) + + expect_snapshot({ + tpch_01() + tpch_02() + tpch_03() + tpch_04() + tpch_05() + tpch_06() + tpch_07() + tpch_08() + tpch_09() + tpch_10() + tpch_11() + tpch_12() + tpch_13() + tpch_14() + tpch_15() + tpch_16() + tpch_17() + tpch_18() + tpch_19() + tpch_20() + tpch_21() + tpch_22() + }) +}) + +test_that("TPCH queries can be parsed and run with overwriting", { + customer <- data.frame( + c_custkey = integer(0), + c_name = character(0), + c_address = character(0), + c_nationkey = integer(0), + c_phone = character(0), + c_acctbal = numeric(0), + c_mktsegment = character(0), + c_comment = character(0) + ) + + lineitem <- data.frame( + l_orderkey = integer(0), + l_partkey = integer(0), + l_suppkey = integer(0), + l_linenumber = integer(0), + l_quantity = numeric(0), + l_extendedprice = numeric(0), + l_discount = numeric(0), + l_tax = numeric(0), + l_returnflag = character(0), + l_linestatus = character(0), + l_shipdate = as.Date(character(0)), + l_commitdate = as.Date(character(0)), + l_receiptdate = as.Date(character(0)), + l_shipinstruct = character(0), + l_shipmode = character(0), + l_comment = character(0) + ) + + nation <- data.frame( + n_nationkey = integer(0), + n_name = character(0), + n_regionkey = integer(0), + n_comment = character(0) + ) + + orders <- data.frame( + o_orderkey = integer(0), + o_custkey = integer(0), + o_orderstatus = character(0), + o_totalprice = numeric(0), + o_orderdate = as.Date(character(0)), + o_orderpriority = character(0), + o_clerk = character(0), + o_shippriority = integer(0), + o_comment = character(0) + ) + + part <- data.frame( + p_partkey = integer(0), + p_name = character(0), + p_mfgr = character(0), + p_brand = character(0), + p_type = character(0), + p_size = integer(0), + p_container = character(0), + p_retailprice = numeric(0), + p_comment = character(0) + ) + + partsupp <- data.frame( + ps_partkey = integer(0), + ps_suppkey = integer(0), + ps_availqty = integer(0), + ps_supplycost = numeric(0), + ps_comment = character(0) + ) + + region <- data.frame( + r_regionkey = integer(0), + r_name = character(0), + r_comment = character(0) + ) + + supplier <- data.frame( + s_suppkey = integer(0), + s_name = character(0), + s_address = character(0), + s_nationkey = integer(0), + s_phone = character(0), + s_acctbal = numeric(0), + s_comment = character(0) + ) + + withr::local_envvar(DUCKPLYR_FORCE = TRUE) + local_options(duckdb.materialize_callback = NULL) + + local_bindings( + customer = customer, + lineitem = lineitem, + nation = nation, + orders = orders, + part = part, + partsupp = partsupp, + region = region, + supplier = supplier, + .env = .GlobalEnv + ) + + suppressMessages(methods_overwrite()) + withr::defer({ + suppressMessages(methods_restore()) + }) + + local_bindings( + default_df_prudence = "stingy", + .env = duckplyr_the + ) + + expect_error(regexp = NA, { + tpch_01() + tpch_02() + tpch_03() + tpch_04() + tpch_05() + tpch_06() + tpch_07() + tpch_08() + tpch_09() + tpch_10() + tpch_11() + tpch_12() + tpch_13() + tpch_14() + tpch_15() + tpch_16() + tpch_17() + tpch_18() + tpch_19() + tpch_20() + tpch_21() + tpch_22() + }) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-translate.R b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-translate.R new file mode 100644 index 000000000..b8f526ef1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/test-translate.R @@ -0,0 +1,237 @@ +test_that("inline parens (3)", { + df <- data.frame(a = 1, b = 2) + expect_identical( + rel_translate(quo((a) * (b)), df), + rel_translate(quo((a * b)), df), + ) +}) + +test_that("call with named argument", { + expect_snapshot(error = TRUE, { + rel_translate(quo(c(1, b = 2))) + }) +}) + +test_that("a %in% b", { + expect_snapshot(error = TRUE, { + rel_translate(quo(a %in% b), data.frame(a = 1:3, b = 2:4)) + }) +}) + +test_that("comparison expression translated", { + df <- data.frame(a = 1L, b = 2L, c = 3) + expect_snapshot({ + rel_translate(quo(a > 123L), df) + }) + + expect_snapshot({ + rel_translate(quo(a > 123.0), df) + }) + + expect_snapshot({ + rel_translate(quo(a == b), df) + }) + + expect_snapshot({ + rel_translate(quo(a <= c), df) + }) +}) + +test_that("aggregation primitives", { + df <- data.frame(a = 1L, b = TRUE) + + expect_snapshot({ + rel_translate(expr(sum(a)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(sum(a, b)), df) + }) + + expect_snapshot({ + rel_translate(expr(sum(a, na.rm = TRUE)), df) + }) + + expect_snapshot({ + rel_translate(expr(sum(a, na.rm = FALSE)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(sum(a, na.rm = b)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(sum(a, na.rm = 1)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(sum(a)), df, need_window = TRUE) + }) + + expect_snapshot({ + rel_translate(expr(min(a)), df) + }) + + expect_snapshot({ + rel_translate(expr(min(a, na.rm = TRUE)), df) + }) + + expect_snapshot({ + rel_translate(expr(max(a)), df) + }) + + expect_snapshot({ + rel_translate(expr(max(a, na.rm = TRUE)), df) + }) + + expect_snapshot({ + rel_translate(expr(any(a)), df) + }) + + expect_snapshot({ + rel_translate(expr(any(a, na.rm = TRUE)), df) + }) + + expect_snapshot({ + rel_translate(expr(all(a)), df) + }) + + expect_snapshot({ + rel_translate(expr(all(a, na.rm = TRUE)), df) + }) + + expect_snapshot({ + rel_translate(expr(mean(a)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(mean(a, b)), df) + }) + + expect_snapshot({ + rel_translate(expr(mean(a, na.rm = TRUE)), df) + }) + + expect_snapshot({ + rel_translate(expr(mean(a, na.rm = FALSE)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(mean(a, na.rm = b)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(mean(a, na.rm = 1)), df) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(mean(a)), df, need_window = TRUE) + }) +}) + +test_that("aggregation primitives with na.rm and window functions", { + df <- data.frame(a = 1L, b = TRUE) + + expect_snapshot({ + "Test aggregation primitives with na.rm = TRUE in window functions" + rel_translate(expr(sum(a, na.rm = TRUE)), df, need_window = TRUE) + }) + + expect_snapshot({ + rel_translate(expr(min(a, na.rm = TRUE)), df, need_window = TRUE) + }) + + expect_snapshot({ + rel_translate(expr(max(a, na.rm = TRUE)), df, need_window = TRUE) + }) + + expect_snapshot({ + rel_translate(expr(mean(a, na.rm = TRUE)), df, need_window = TRUE) + }) + + expect_snapshot(error = TRUE, { + "Test error when na.rm = FALSE in window functions" + rel_translate(expr(sum(a, na.rm = FALSE)), df, need_window = TRUE) + }) + + expect_snapshot(error = TRUE, { + rel_translate(expr(mean(a, na.rm = FALSE)), df, need_window = TRUE) + }) +}) + +test_that("rel_find_call() success paths", { + env <- baseenv() + + expect_snapshot({ + "Success: Translate base function" + rel_find_call(quote(mean), env) + }) + + expect_snapshot({ + "Success: Translate dplyr::n() function" + "https://github.com/tidyverse/dplyr/pull/7046" + rel_find_call(quote(n), env) + }) + + expect_snapshot({ + "Success: Translate DuckDB function with 'dd$' prefix" + rel_find_call(quote(dd$ROW), env) + }) + + expect_snapshot({ + "Success: Translate stats function when stats is available" + rel_find_call(quote(sd), new_environment(parent = asNamespace("stats"))) + }) + + skip_if_not_installed("lubridate") + + expect_snapshot({ + "Success: Translate lubridate function" + rel_find_call(quote(lubridate::wday), env) + }) + + expect_snapshot({ + "Success: Translate lubridate function when exported" + rel_find_call(quote(wday), new_environment(list(wday = lubridate::wday))) + }) +}) + +test_that("rel_find_call() error paths", { + env <- baseenv() + + expect_snapshot(error = TRUE, { + "Error: Can't translate function with invalid '::' structure" + rel_find_call(quote(pkg::""), env) + }) + + expect_snapshot(error = TRUE, { + "Error: Can't translate function with invalid '::' components" + rel_find_call(call("::", "pkg", 123), env, env) + }) + + expect_snapshot(error = TRUE, { + "Error: No translation for unknown function" + rel_find_call(quote(unknown_function), env) + }) + + expect_snapshot(error = TRUE, { + "Error: No translation for unknown function from some package" + rel_find_call(quote(somepkg::unknown_function), env) + }) + + expect_snapshot(error = TRUE, { + "Error: Function not found in the environment" + rel_find_call(quote(mean), new_environment()) + }) + + expect_snapshot(error = TRUE, { + "Error: Function does not map to the corresponding package" + rel_find_call(quote(mean), new_environment(list(mean = stats::sd))) + }) +}) + +test_that("dd$ prefix", { + out <- duckdb_tibble(a = "duckdb") |> + mutate(b = dd$ascii(a)) + expect_equal(out, duckdb_tibble(a = "duckdb", b = 100L)) +}) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q01.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q01.csv new file mode 100644 index 000000000..84965cd33 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q01.csv @@ -0,0 +1,5 @@ +l_returnflag|l_linestatus|sum_qty|sum_base_price|sum_disc_price|sum_charge|avg_qty|avg_price|avg_disc|count_order +A|F|380456|532348211.65|505822441.4861|526165934.000839|25.5751546114546921|35785.709306937349|0.05008133906964237698|14876 +N|F|8971|12384801.37|11798257.2080|12282485.056933|25.7787356321839080|35588.509683908046|0.04775862068965517241|348 +N|O|742802|1041502841.45|989737518.6346|1029418531.523350|25.4549878345498783|35691.129209074398|0.04993111956409992804|29181 +R|F|381449|534594445.35|507996454.4067|528524219.358903|25.5971681653469333|35874.006532680177|0.04982753992752650651|14902 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q02.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q02.csv new file mode 100644 index 000000000..195cd4a60 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q02.csv @@ -0,0 +1,5 @@ +s_acctbal|s_name|n_name|p_partkey|p_mfgr|s_address|s_phone|s_comment +4186.95|Supplier#000000077|GERMANY|249|Manufacturer#4|w5yO 0yjXou 8I4ffzADq,R8tD06x1vbeMpLJF2|17-281-345-4863|ainst the blithely ironic packages poach at the regul +1883.37|Supplier#000000086|ROMANIA|1015|Manufacturer#4|iZLKKWaQADe|29-903-665-7065| foxes boost after the carefully silent asymptotes. slyl +1687.81|Supplier#000000017|ROMANIA|1634|Manufacturer#2|PYN0m9j98GhX42DvBKvURcAd,B|29-601-884-9219|se slyly furiously even notornis. furiously regular packa +287.16|Supplier#000000052|ROMANIA|323|Manufacturer#4|5oGr3pj2sprZNwho8CFW2haaObd0|29-974-934-4713|arefully silent pinto beans use furiously furiously even deposits. regular packages are furious diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q03.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q03.csv new file mode 100644 index 000000000..3f9b77cb4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q03.csv @@ -0,0 +1,11 @@ +l_orderkey|revenue|o_orderdate|o_shippriority +47714|267010.5894|1995-03-11|0 +22276|266351.5562|1995-01-29|0 +32965|263768.3414|1995-02-25|0 +21956|254541.1285|1995-02-02|0 +1637|243512.7981|1995-02-08|0 +10916|241320.0814|1995-03-11|0 +30497|208566.6969|1995-02-07|0 +450|205447.4232|1995-03-05|0 +47204|204478.5213|1995-03-13|0 +9696|201502.2188|1995-02-20|0 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q04.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q04.csv new file mode 100644 index 000000000..b2909a5b2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q04.csv @@ -0,0 +1,6 @@ +o_orderpriority|order_count +1-URGENT|93 +2-HIGH|103 +3-MEDIUM|109 +4-NOT SPECIFIED|102 +5-LOW|128 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q05.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q05.csv new file mode 100644 index 000000000..1e37d508d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q05.csv @@ -0,0 +1,6 @@ +n_name|revenue +VIETNAM|1000926.6999 +CHINA|740210.7570 +JAPAN|660651.2425 +INDONESIA|566379.5276 +INDIA|422874.6844 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q06.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q06.csv new file mode 100644 index 000000000..04561d74f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q06.csv @@ -0,0 +1,2 @@ +revenue +1193053.2253 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q07.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q07.csv new file mode 100644 index 000000000..1a259a28f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q07.csv @@ -0,0 +1,5 @@ +supp_nation|cust_nation|l_year|revenue +FRANCE|GERMANY|1995|268068.5774 +FRANCE|GERMANY|1996|303862.2980 +GERMANY|FRANCE|1995|621159.4882 +GERMANY|FRANCE|1996|379095.8854 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q08.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q08.csv new file mode 100644 index 000000000..a588ba658 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q08.csv @@ -0,0 +1,3 @@ +o_year|mkt_share +1995|0.000000000000000000000000 +1996|0.000000000000000000000000 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q09.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q09.csv new file mode 100644 index 000000000..560fc8fd6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q09.csv @@ -0,0 +1,174 @@ +nation|o_year|sum_profit +ALGERIA|1998|97864.5682 +ALGERIA|1997|368231.6695 +ALGERIA|1996|196525.8046 +ALGERIA|1995|341438.6885 +ALGERIA|1994|677444.0160 +ALGERIA|1993|458756.9157 +ALGERIA|1992|549243.9511 +ARGENTINA|1998|80448.7680 +ARGENTINA|1997|186279.1618 +ARGENTINA|1996|154041.8822 +ARGENTINA|1995|113143.3119 +ARGENTINA|1994|169680.4239 +ARGENTINA|1993|116513.8141 +ARGENTINA|1992|202404.7608 +BRAZIL|1998|75952.5946 +BRAZIL|1997|190548.1104 +BRAZIL|1996|219059.0692 +BRAZIL|1995|186435.2023 +BRAZIL|1994|96835.1870 +BRAZIL|1993|186365.4109 +BRAZIL|1992|152546.4439 +CANADA|1998|101030.3336 +CANADA|1997|101197.3441 +CANADA|1996|257697.1355 +CANADA|1995|91474.8820 +CANADA|1994|249182.7548 +CANADA|1993|185737.8379 +CANADA|1992|143371.7465 +CHINA|1998|508364.5444 +CHINA|1997|650235.1646 +CHINA|1996|911366.0698 +CHINA|1995|797268.4076 +CHINA|1994|529989.3095 +CHINA|1993|573864.3972 +CHINA|1992|751688.7613 +EGYPT|1998|306325.2842 +EGYPT|1997|568461.6699 +EGYPT|1996|465081.9232 +EGYPT|1995|542886.5087 +EGYPT|1994|745807.8123 +EGYPT|1993|381503.2008 +EGYPT|1992|641866.4367 +ETHIOPIA|1998|226054.5716 +ETHIOPIA|1997|585193.2802 +ETHIOPIA|1996|405412.7741 +ETHIOPIA|1995|270455.7637 +ETHIOPIA|1994|567875.4279 +ETHIOPIA|1993|412302.2871 +ETHIOPIA|1992|551284.5821 +FRANCE|1998|135723.4050 +FRANCE|1997|249664.7578 +FRANCE|1996|175882.8934 +FRANCE|1995|116394.7866 +FRANCE|1994|197695.2438 +FRANCE|1993|231878.6201 +FRANCE|1992|199131.2037 +GERMANY|1998|172741.1024 +GERMANY|1997|393833.4660 +GERMANY|1996|335634.5936 +GERMANY|1995|378106.0763 +GERMANY|1994|250107.6653 +GERMANY|1993|327154.9365 +GERMANY|1992|387240.0885 +INDIA|1998|347548.7604 +INDIA|1997|656797.9670 +INDIA|1996|522759.3529 +INDIA|1995|574428.6693 +INDIA|1994|741983.7846 +INDIA|1993|729948.5341 +INDIA|1992|661061.1415 +INDONESIA|1998|91791.5096 +INDONESIA|1997|183956.4613 +INDONESIA|1996|415234.7848 +INDONESIA|1995|427155.3804 +INDONESIA|1994|286271.2875 +INDONESIA|1993|551178.8823 +INDONESIA|1992|274513.2685 +IRAN|1998|47959.8219 +IRAN|1997|184335.0615 +IRAN|1996|223115.2464 +IRAN|1995|125339.0927 +IRAN|1994|117228.3122 +IRAN|1993|208030.3229 +IRAN|1992|161835.5475 +IRAQ|1998|161797.4924 +IRAQ|1997|224876.5436 +IRAQ|1996|145277.8980 +IRAQ|1995|467955.2505 +IRAQ|1994|97455.2990 +IRAQ|1993|114821.6440 +IRAQ|1992|213307.1574 +JAPAN|1998|307594.5980 +JAPAN|1997|339018.1488 +JAPAN|1996|649578.3368 +JAPAN|1995|671644.0911 +JAPAN|1994|576266.2386 +JAPAN|1993|514190.8437 +JAPAN|1992|534914.9339 +JORDAN|1996|33460.2447 +JORDAN|1995|20364.1623 +JORDAN|1994|15528.6088 +JORDAN|1993|14640.9889 +JORDAN|1992|10904.2931 +KENYA|1998|521926.5198 +KENYA|1997|559632.3408 +KENYA|1996|772855.7939 +KENYA|1995|516452.5067 +KENYA|1994|543665.8154 +KENYA|1993|866924.8754 +KENYA|1992|567410.5502 +MOROCCO|1998|217794.4973 +MOROCCO|1997|439240.9287 +MOROCCO|1996|399969.4680 +MOROCCO|1995|258131.9398 +MOROCCO|1994|386972.1424 +MOROCCO|1993|145468.0381 +MOROCCO|1992|284314.2813 +MOZAMBIQUE|1998|518693.2238 +MOZAMBIQUE|1997|613873.2961 +MOZAMBIQUE|1996|936793.5612 +MOZAMBIQUE|1995|727204.7718 +MOZAMBIQUE|1994|1104618.1807 +MOZAMBIQUE|1993|893266.0530 +MOZAMBIQUE|1992|1062432.0884 +PERU|1998|287242.9797 +PERU|1997|532358.3660 +PERU|1996|398435.7507 +PERU|1995|462031.6251 +PERU|1994|304235.4118 +PERU|1993|505885.4890 +PERU|1992|382290.0947 +ROMANIA|1998|357824.5528 +ROMANIA|1997|569806.5564 +ROMANIA|1996|732001.5568 +ROMANIA|1995|408657.1154 +ROMANIA|1994|540702.5463 +ROMANIA|1993|883158.5056 +ROMANIA|1992|505488.9501 +RUSSIA|1998|34448.6357 +RUSSIA|1997|314972.0446 +RUSSIA|1996|430049.5821 +RUSSIA|1995|360538.0586 +RUSSIA|1994|301791.0114 +RUSSIA|1993|308993.9622 +RUSSIA|1992|289868.6564 +SAUDI ARABIA|1998|16502.4100 +SAUDI ARABIA|1997|61830.9556 +SAUDI ARABIA|1996|213650.2809 +SAUDI ARABIA|1995|62668.7250 +SAUDI ARABIA|1994|94629.1538 +SAUDI ARABIA|1993|57768.3071 +SAUDI ARABIA|1992|66520.1093 +UNITED KINGDOM|1998|80437.6523 +UNITED KINGDOM|1997|252509.7351 +UNITED KINGDOM|1996|231152.8582 +UNITED KINGDOM|1995|181310.8808 +UNITED KINGDOM|1994|239161.2061 +UNITED KINGDOM|1993|122103.1142 +UNITED KINGDOM|1992|60882.3080 +UNITED STATES|1998|440347.6658 +UNITED STATES|1997|652958.9371 +UNITED STATES|1996|1004593.8282 +UNITED STATES|1995|860144.1029 +UNITED STATES|1994|807797.4877 +UNITED STATES|1993|736669.4711 +UNITED STATES|1992|877851.4103 +VIETNAM|1998|358248.0159 +VIETNAM|1997|394817.2842 +VIETNAM|1996|439390.0836 +VIETNAM|1995|418626.6325 +VIETNAM|1994|422644.8168 +VIETNAM|1993|309063.4020 +VIETNAM|1992|716126.5378 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q10.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q10.csv new file mode 100644 index 000000000..0d779a8fe --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q10.csv @@ -0,0 +1,21 @@ +c_custkey|c_name|revenue|c_acctbal|n_name|c_address|c_phone|c_comment +679|Customer#000000679|378211.3252|1394.44|IRAN|jhLZlG1fRiSE0 wDLOCguVB96dH6a5|20-146-696-9508|oys. furiously silent pinto beans +1201|Customer#000001201|374331.534|5165.39|IRAN|gLo85h4Cst5ckn40ZKR6403KjW26YCWAFZsq d|20-825-400-1187|r foxes boost blithely final accounts. furiously silent ideas unwind. blithely even dependencies wake slyly; sly +422|Customer#000000422|366451.0126|-272.14|INDONESIA|rtds1qwEinDWYhyIbRusRgXBDxGJraP,C,S zDwA|19-299-247-2444| are carefully. slyly regular requests hag +334|Customer#000000334|360370.755|-405.91|EGYPT|dbdZdUyWQaZX7DoCO|14-947-291-5002|r, bold pinto beans according to the blithely final theodolites can snooze slyly even packages. carefully ironic acc +805|Customer#000000805|359448.9036|511.69|IRAN|woguVtOjwvC8LLtPRALISNwAOFRf70Pfqk|20-732-989-5653| quickly unusual accounts haggle carefully after the even accounts. deposits cajole carefully bravely spec +932|Customer#000000932|341608.2753|6553.37|JORDAN|kdRqB0ezhkUePSc|23-300-708-7927|ound the regular accounts. quickly ironic requests try to +853|Customer#000000853|341236.6246|-444.73|BRAZIL|7,rRbAvqK4hSqm,ljEDolyjRp8Mu44xSTG|12-869-161-3468|l foxes affix. furiously regular foxes +872|Customer#000000872|338328.7808|-858.61|PERU|xfbTIe1pgWq0jqdm7gQP6iXqcs2RcSk|27-357-139-7164|unts believe across the ironic instructions. packages nag re +737|Customer#000000737|338185.3365|2501.74|CHINA|eNHkZHYWXIpf8Ct,F2Aej BwxkZ6LTW|28-658-938-1102|ackages. blithely daring packages detect slyly around the carefully busy excuses. f +1118|Customer#000001118|319875.728|4130.18|IRAQ|bjK0newm53R2CoNAtvQsIqh|21-583-715-8627|ecial sentiments. express deposits across the blithely express packages wake quickly across the attainments. platele +223|Customer#000000223|319564.275|7476.2|SAUDI ARABIA|MyQxUcG0P QCetmG00GlF|30-193-643-1517|xcuses. silent theodolites across the carefully bold excuses sleep ironic, final courts. regular excuses +808|Customer#000000808|314774.6167|5561.93|ROMANIA|9Y4G8hokyDQJOlcCUe4OypX smFlME6d Kr|29-531-319-7726|usual pinto beans use carefully. express ideas boost. ironic, ironic Tir +478|Customer#000000478|299651.8026|-210.4|ARGENTINA|PFtAWWSniG43yWAgtjFPN0DrhyMCDIm|11-655-291-2694|even asymptotes haggle carefully slyl +1441|Customer#000001441|294705.3935|9465.15|UNITED KINGDOM|y,221QWUv0CvgCVkRwsrOUpRshWpcjKkrHu|33-681-334-4499|ccounts are furiously above the slyly even foxes. furiously even requests hagg +1478|Customer#000001478|294431.9178|9701.54|GERMANY|vTjnwinnB9YeA1VxKYoQELaZJm,HW|17-420-484-5959|nal instructions. quickly regular excuses haggle after the sl +211|Customer#000000211|287905.6368|4198.72|JORDAN|79JF5cssWlB3ME|23-965-335-9471|unusual requests haggle after the unusual, ironic ideas. ironic orbits hinder carefully quickly final p +197|Customer#000000197|283190.4807|9860.22|ARGENTINA|7M5Az MBdx4Ey1XTn|11-107-312-6585|latelets doze fluffily blithely final dep +1030|Customer#000001030|282557.3566|6359.27|INDIA|4ByZpJpVJRD|18-759-877-1870| the final accounts. quick platelets are carefully. instructions a +1049|Customer#000001049|281134.1117|8747.99|INDONESIA|Q1ZcOmJjQi2VKeIk|19-499-258-2851|sleep carefully final deposits. furiously bold depo +1094|Customer#000001094|274877.444|2544.49|BRAZIL|dls,MNN7EbF3GYrY3eRwSeOBZYd5o,cg|12-234-721-9871|structions among the furiously final pinto beans haggle fluffily diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q11.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q11.csv new file mode 100644 index 000000000..a3b289eca --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q11.csv @@ -0,0 +1,360 @@ +ps_partkey|value +1376|13271249.89 +788|9498648.06 +1071|9388264.40 +1768|9207199.75 +1168|8881908.96 +1084|8709494.16 +1415|8471489.56 +1338|8293841.12 +124|8203209.30 +1232|8111663.34 +1643|7975862.75 +1952|7936947.61 +1944|7880018.60 +1884|7513422.84 +942|7511018.76 +670|7299956.80 +1532|7222347.20 +1052|7158586.00 +455|7064285.84 +1176|7060670.89 +143|7037648.64 +1653|6949533.70 +1140|6929464.08 +1076|6877472.96 +2000|6720009.38 +348|6681307.34 +810|6576640.95 +943|6458641.70 +720|6391330.27 +1748|6341530.40 +1241|6304944.66 +1384|6279261.12 +1784|6247863.25 +984|6136927.00 +445|6127784.28 +1976|6079237.08 +1609|6022720.80 +1563|5978195.08 +452|5838052.00 +222|5737162.24 +1629|5703117.12 +1454|5694804.18 +1082|5681981.25 +691|5633589.72 +1474|5614673.64 +1900|5591905.36 +262|5553285.32 +1876|5517997.59 +1027|5490916.00 +1833|5451495.00 +513|5374426.22 +752|5358919.70 +1367|5352773.25 +543|5189101.68 +1144|5174388.56 +403|5126118.15 +1406|5121886.44 +320|5072099.76 +1940|5069178.40 +1503|5050895.50 +1437|5039590.60 +743|5039271.42 +82|4995939.00 +916|4994730.10 +732|4932809.82 +356|4879860.09 +1592|4831242.60 +1043|4825921.31 +132|4781984.14 +1006|4733954.64 +497|4711173.60 +1008|4565588.85 +1370|4563830.10 +216|4561143.80 +34|4501982.71 +1908|4417931.80 +982|4391495.46 +1652|4358793.14 +614|4356657.45 +1552|4355541.70 +359|4353566.87 +1104|4347515.90 +198|4315049.00 +998|4167784.88 +1543|4159568.16 +1308|4153124.95 +474|4123819.20 +1394|4122729.33 +271|4095180.96 +908|4088856.20 +1135|4045014.13 +1632|4010794.90 +1362|3982060.16 +158|3941881.65 +1852|3923035.02 +1556|3896709.54 +584|3843848.30 +885|3826021.16 +376|3781201.96 +712|3749696.80 +2|3743241.43 +676|3735715.20 +1832|3709008.60 +1955|3702794.70 +68|3690702.41 +1435|3659114.10 +1443|3656762.84 +1278|3653100.66 +1920|3647892.54 +423|3602031.80 +818|3589047.60 +779|3559597.53 +485|3558511.44 +552|3555470.10 +1269|3510427.65 +1602|3492117.70 +426|3486888.02 +1452|3480825.60 +756|3469373.70 +832|3447746.46 +1493|3446867.40 +1650|3417752.58 +205|3403046.25 +93|3361425.89 +76|3342081.82 +1759|3303050.40 +886|3302180.70 +1544|3288573.16 +1932|3270900.40 +489|3253368.30 +594|3177408.57 +184|3177162.05 +950|3165213.01 +1124|3143279.36 +106|3099021.98 +1964|3016553.10 +384|2964262.77 +974|2959497.10 +964|2951329.45 +1984|2907345.36 +200|2895688.32 +683|2829476.95 +1564|2816506.56 +546|2788059.64 +502|2780828.64 +396|2778421.39 +203|2761439.88 +866|2753031.20 +1743|2743889.49 +1041|2738083.92 +1432|2713412.16 +43|2587359.58 +941|2587091.52 +1890|2558739.69 +1866|2545838.40 +747|2511745.32 +776|2506489.89 +554|2505417.25 +1210|2490820.92 +1239|2405206.30 +443|2382150.05 +1661|2370574.16 +1079|2363505.11 +1329|2305870.42 +1691|2261159.92 +1247|2239553.28 +1752|2230055.76 +150|2217043.59 +1814|2213635.20 +289|2187160.45 +1400|2139845.10 +1898|2130114.96 +1809|2122758.72 +884|2107479.56 +1038|2096868.97 +1318|2051302.44 +524|2035262.22 +414|2029692.45 +298|2026981.74 +1996|2020953.54 +1742|2019190.80 +1620|2010112.00 +877|1956429.18 +1332|1919029.56 +1536|1859318.15 +1116|1852588.28 +447|1817951.32 +1676|1802306.08 +1911|1779646.44 +1459|1767602.30 +576|1761838.75 +1273|1754235.01 +583|1725649.92 +532|1682311.48 +1732|1652831.20 +1572|1650953.52 +1889|1638443.72 +476|1631154.06 +1221|1629883.46 +1792|1606346.10 +243|1603235.16 +328|1569826.72 +1999|1553706.00 +1611|1529857.01 +643|1512838.80 +1276|1467567.28 +1823|1462293.00 +1|1456050.96 +27|1425832.40 +632|1408087.26 +1184|1406101.78 +252|1379186.35 +392|1354813.18 +1215|1344383.20 +26|1337002.89 +84|1334146.71 +784|1327297.01 +1803|1327045.06 +352|1326102.34 +165|1289075.76 +176|1285866.20 +1314|1244173.26 +1701|1239095.44 +844|1225696.05 +1988|1216798.33 +1847|1202012.13 +1706|1184125.10 +744|1182820.80 +230|1165932.30 +418|1078321.44 +174|1060584.80 +1073|1028449.89 +1726|1018673.04 +1206|1002319.49 +1343|998105.76 +952|997684.24 +484|991530.93 +932|980620.68 +843|978862.92 +1841|962131.86 +494|957575.34 +659|954291.05 +251|939764.70 +1413|936951.94 +572|906111.99 +32|894484.09 +9|893905.92 +1498|890887.85 +1790|878923.64 +1670|854046.43 +876|842245.67 +1758|841275.42 +930|832963.68 +284|826642.60 +1710|811504.38 +1047|791214.45 +653|788974.21 +315|770526.05 +1734|763569.40 +1017|715302.72 +1305|713351.43 +77|688865.82 +1512|682434.15 +276|680239.04 +1284|671225.94 +1356|665716.83 +800|663414.65 +117|639650.88 +652|635629.28 +57|630987.44 +1426|628241.25 +1196|622427.16 +51|622249.54 +1846|621068.80 +601|615942.60 +645|607985.84 +684|571490.70 +465|570337.40 +562|567651.24 +387|556634.76 +1152|555989.28 +1202|553818.18 +1112|552658.68 +304|535868.16 +368|526995.84 +1800|526711.11 +1148|515702.16 +225|513587.57 +324|500954.58 +586|499475.58 +1576|494401.05 +1484|462396.27 +126|461263.74 +1132|455492.24 +622|449685.60 +1160|448183.06 +1352|439967.04 +18|426442.08 +7|414558.20 +833|398540.87 +1694|376443.98 +650|370900.99 +1504|370815.90 +432|370528.52 +612|367894.50 +542|367653.66 +456|360911.32 +52|358792.36 +1346|350637.43 +59|342221.48 +1107|341805.20 +1171|334938.04 +1062|326445.90 +592|313081.75 +1750|312229.33 +1843|309456.95 +180|308539.84 +899|301989.50 +1180|293452.50 +522|291601.75 +249|282520.32 +1584|278559.38 +1404|276057.90 +1265|271079.76 +154|269641.42 +1295|265566.56 +1523|263158.90 +1635|254834.56 +1776|234181.20 +1097|234113.55 +1258|233500.61 +621|233431.30 +152|229781.60 +278|216372.84 +232|211879.92 +1684|201386.22 +1243|199587.54 +976|197432.10 +819|191475.90 +1943|191247.76 +853|189232.64 +400|188941.20 +639|186533.28 +851|184103.16 +909|175099.00 +257|169033.44 +1445|164888.68 +1855|164614.81 +1252|158680.90 +1014|156465.82 +1717|148325.75 +1032|146408.40 +780|136296.26 +918|135268.32 +690|133826.88 +711|113268.84 +332|112181.30 +1596|110565.00 +295|97604.25 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q12.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q12.csv new file mode 100644 index 000000000..43e005435 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q12.csv @@ -0,0 +1,3 @@ +l_shipmode|high_line_count|low_line_count +MAIL|64|86 +SHIP|61|96 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q13.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q13.csv new file mode 100644 index 000000000..dee64b4bd --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q13.csv @@ -0,0 +1,33 @@ +c_count|custdist +0|500 +10|69 +9|66 +11|63 +12|60 +8|60 +14|56 +13|55 +21|48 +20|45 +7|45 +18|43 +19|42 +17|42 +16|42 +15|41 +22|37 +24|30 +6|30 +23|26 +25|20 +5|18 +26|17 +27|13 +29|7 +28|6 +4|6 +31|4 +3|3 +32|2 +30|2 +2|2 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q14.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q14.csv new file mode 100644 index 000000000..f5ac558f0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q14.csv @@ -0,0 +1,2 @@ +promo_revenue +15.4865458122840715 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q15.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q15.csv new file mode 100644 index 000000000..bd4712447 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q15.csv @@ -0,0 +1,2 @@ +s_suppkey|s_name|s_address|s_phone|total_revenue +21|Supplier#000000021|TZoQwNFFO i,baXpbpin02,hvuhE,GRVIKm |12-253-590-5816|1161099.4636 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q16.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q16.csv new file mode 100644 index 000000000..c1d12ed44 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q16.csv @@ -0,0 +1,297 @@ +p_brand|p_type|p_size|supplier_cnt +Brand#14|PROMO BRUSHED STEEL|9|8 +Brand#35|SMALL POLISHED COPPER|14|8 +Brand#22|LARGE BURNISHED TIN|36|6 +Brand#11|ECONOMY BURNISHED NICKEL|49|4 +Brand#11|LARGE PLATED TIN|23|4 +Brand#11|MEDIUM ANODIZED BRASS|45|4 +Brand#11|MEDIUM BRUSHED BRASS|45|4 +Brand#11|PROMO ANODIZED BRASS|3|4 +Brand#11|PROMO ANODIZED BRASS|49|4 +Brand#11|PROMO ANODIZED TIN|45|4 +Brand#11|PROMO BURNISHED BRASS|36|4 +Brand#11|SMALL ANODIZED TIN|45|4 +Brand#11|SMALL PLATED COPPER|45|4 +Brand#11|STANDARD POLISHED NICKEL|45|4 +Brand#11|STANDARD POLISHED TIN|45|4 +Brand#12|ECONOMY BURNISHED COPPER|45|4 +Brand#12|LARGE ANODIZED TIN|45|4 +Brand#12|LARGE BURNISHED BRASS|19|4 +Brand#12|LARGE PLATED STEEL|36|4 +Brand#12|MEDIUM PLATED BRASS|23|4 +Brand#12|PROMO BRUSHED COPPER|14|4 +Brand#12|PROMO BURNISHED BRASS|49|4 +Brand#12|SMALL ANODIZED COPPER|23|4 +Brand#12|STANDARD ANODIZED BRASS|3|4 +Brand#12|STANDARD BURNISHED TIN|23|4 +Brand#12|STANDARD PLATED STEEL|36|4 +Brand#13|ECONOMY PLATED STEEL|23|4 +Brand#13|ECONOMY POLISHED BRASS|9|4 +Brand#13|ECONOMY POLISHED COPPER|9|4 +Brand#13|LARGE ANODIZED TIN|19|4 +Brand#13|LARGE BURNISHED TIN|49|4 +Brand#13|LARGE POLISHED BRASS|3|4 +Brand#13|MEDIUM ANODIZED STEEL|36|4 +Brand#13|MEDIUM PLATED COPPER|19|4 +Brand#13|PROMO BRUSHED COPPER|49|4 +Brand#13|PROMO PLATED TIN|19|4 +Brand#13|SMALL BRUSHED NICKEL|19|4 +Brand#13|SMALL BURNISHED BRASS|45|4 +Brand#14|ECONOMY ANODIZED STEEL|19|4 +Brand#14|ECONOMY BURNISHED TIN|23|4 +Brand#14|ECONOMY PLATED STEEL|45|4 +Brand#14|ECONOMY PLATED TIN|9|4 +Brand#14|LARGE ANODIZED NICKEL|9|4 +Brand#14|LARGE BRUSHED NICKEL|45|4 +Brand#14|SMALL ANODIZED NICKEL|45|4 +Brand#14|SMALL BURNISHED COPPER|14|4 +Brand#14|SMALL BURNISHED TIN|23|4 +Brand#15|ECONOMY ANODIZED STEEL|36|4 +Brand#15|ECONOMY BRUSHED BRASS|36|4 +Brand#15|ECONOMY BURNISHED BRASS|14|4 +Brand#15|ECONOMY PLATED STEEL|45|4 +Brand#15|LARGE ANODIZED BRASS|45|4 +Brand#15|LARGE ANODIZED COPPER|3|4 +Brand#15|MEDIUM ANODIZED COPPER|9|4 +Brand#15|MEDIUM PLATED TIN|9|4 +Brand#15|PROMO POLISHED TIN|49|4 +Brand#15|SMALL POLISHED STEEL|19|4 +Brand#15|STANDARD BURNISHED STEEL|45|4 +Brand#15|STANDARD PLATED NICKEL|19|4 +Brand#15|STANDARD PLATED TIN|3|4 +Brand#21|ECONOMY ANODIZED STEEL|19|4 +Brand#21|ECONOMY BRUSHED TIN|49|4 +Brand#21|LARGE BURNISHED COPPER|19|4 +Brand#21|MEDIUM ANODIZED TIN|9|4 +Brand#21|MEDIUM BURNISHED STEEL|23|4 +Brand#21|PROMO BRUSHED STEEL|23|4 +Brand#21|PROMO BURNISHED COPPER|19|4 +Brand#21|STANDARD PLATED BRASS|49|4 +Brand#21|STANDARD POLISHED TIN|36|4 +Brand#22|ECONOMY BURNISHED NICKEL|19|4 +Brand#22|LARGE ANODIZED STEEL|3|4 +Brand#22|LARGE BURNISHED STEEL|23|4 +Brand#22|LARGE BURNISHED STEEL|45|4 +Brand#22|LARGE BURNISHED TIN|45|4 +Brand#22|LARGE POLISHED NICKEL|19|4 +Brand#22|MEDIUM ANODIZED TIN|9|4 +Brand#22|MEDIUM BRUSHED BRASS|14|4 +Brand#22|MEDIUM BRUSHED COPPER|3|4 +Brand#22|MEDIUM BRUSHED COPPER|45|4 +Brand#22|MEDIUM BURNISHED TIN|19|4 +Brand#22|MEDIUM BURNISHED TIN|23|4 +Brand#22|MEDIUM PLATED BRASS|49|4 +Brand#22|PROMO BRUSHED BRASS|9|4 +Brand#22|PROMO BRUSHED STEEL|36|4 +Brand#22|SMALL BRUSHED NICKEL|3|4 +Brand#22|SMALL BURNISHED STEEL|23|4 +Brand#22|STANDARD PLATED NICKEL|3|4 +Brand#22|STANDARD PLATED TIN|19|4 +Brand#23|ECONOMY BRUSHED COPPER|9|4 +Brand#23|LARGE ANODIZED COPPER|14|4 +Brand#23|LARGE PLATED BRASS|49|4 +Brand#23|MEDIUM BRUSHED NICKEL|3|4 +Brand#23|PROMO ANODIZED COPPER|19|4 +Brand#23|PROMO BURNISHED COPPER|14|4 +Brand#23|PROMO POLISHED BRASS|14|4 +Brand#23|SMALL BRUSHED BRASS|49|4 +Brand#23|SMALL BRUSHED COPPER|45|4 +Brand#23|SMALL BURNISHED COPPER|49|4 +Brand#23|SMALL PLATED BRASS|36|4 +Brand#23|SMALL POLISHED BRASS|9|4 +Brand#23|STANDARD BRUSHED TIN|3|4 +Brand#23|STANDARD PLATED BRASS|9|4 +Brand#23|STANDARD PLATED STEEL|36|4 +Brand#23|STANDARD PLATED TIN|19|4 +Brand#24|ECONOMY BRUSHED BRASS|36|4 +Brand#24|ECONOMY PLATED COPPER|36|4 +Brand#24|LARGE PLATED NICKEL|36|4 +Brand#24|MEDIUM PLATED STEEL|19|4 +Brand#24|PROMO POLISHED BRASS|14|4 +Brand#24|SMALL ANODIZED COPPER|3|4 +Brand#24|STANDARD BRUSHED BRASS|14|4 +Brand#24|STANDARD BRUSHED STEEL|14|4 +Brand#24|STANDARD POLISHED NICKEL|14|4 +Brand#25|ECONOMY BURNISHED TIN|19|4 +Brand#25|ECONOMY PLATED NICKEL|23|4 +Brand#25|LARGE ANODIZED NICKEL|23|4 +Brand#25|LARGE BRUSHED NICKEL|19|4 +Brand#25|LARGE BURNISHED TIN|49|4 +Brand#25|MEDIUM BURNISHED NICKEL|49|4 +Brand#25|MEDIUM PLATED BRASS|45|4 +Brand#25|PROMO ANODIZED TIN|3|4 +Brand#25|PROMO BURNISHED COPPER|45|4 +Brand#25|PROMO PLATED NICKEL|3|4 +Brand#25|SMALL BURNISHED COPPER|3|4 +Brand#25|SMALL PLATED TIN|36|4 +Brand#25|STANDARD ANODIZED TIN|9|4 +Brand#25|STANDARD PLATED NICKEL|36|4 +Brand#31|ECONOMY BURNISHED COPPER|36|4 +Brand#31|ECONOMY PLATED STEEL|23|4 +Brand#31|LARGE PLATED NICKEL|14|4 +Brand#31|MEDIUM BURNISHED COPPER|3|4 +Brand#31|MEDIUM PLATED TIN|36|4 +Brand#31|PROMO ANODIZED NICKEL|9|4 +Brand#31|PROMO POLISHED TIN|23|4 +Brand#31|SMALL ANODIZED COPPER|3|4 +Brand#31|SMALL ANODIZED COPPER|45|4 +Brand#31|SMALL BRUSHED NICKEL|23|4 +Brand#31|SMALL PLATED COPPER|36|4 +Brand#32|ECONOMY ANODIZED COPPER|36|4 +Brand#32|ECONOMY PLATED COPPER|9|4 +Brand#32|LARGE ANODIZED STEEL|14|4 +Brand#32|MEDIUM ANODIZED STEEL|49|4 +Brand#32|MEDIUM BURNISHED BRASS|9|4 +Brand#32|MEDIUM BURNISHED BRASS|49|4 +Brand#32|PROMO BRUSHED STEEL|23|4 +Brand#32|PROMO BURNISHED TIN|45|4 +Brand#32|SMALL ANODIZED TIN|9|4 +Brand#32|SMALL BRUSHED COPPER|3|4 +Brand#32|SMALL PLATED COPPER|45|4 +Brand#32|SMALL POLISHED STEEL|36|4 +Brand#32|SMALL POLISHED TIN|45|4 +Brand#32|STANDARD PLATED STEEL|36|4 +Brand#33|ECONOMY BURNISHED COPPER|14|4 +Brand#33|ECONOMY POLISHED BRASS|14|4 +Brand#33|LARGE BRUSHED TIN|36|4 +Brand#33|MEDIUM ANODIZED BRASS|3|4 +Brand#33|MEDIUM BURNISHED COPPER|14|4 +Brand#33|MEDIUM PLATED STEEL|49|4 +Brand#33|PROMO PLATED STEEL|49|4 +Brand#33|PROMO PLATED TIN|49|4 +Brand#33|PROMO POLISHED STEEL|9|4 +Brand#33|SMALL ANODIZED COPPER|23|4 +Brand#33|SMALL BRUSHED STEEL|3|4 +Brand#33|SMALL BURNISHED NICKEL|3|4 +Brand#33|STANDARD PLATED NICKEL|36|4 +Brand#34|ECONOMY ANODIZED TIN|49|4 +Brand#34|LARGE ANODIZED BRASS|23|4 +Brand#34|LARGE BRUSHED COPPER|23|4 +Brand#34|LARGE BURNISHED TIN|49|4 +Brand#34|LARGE PLATED BRASS|45|4 +Brand#34|MEDIUM BRUSHED COPPER|9|4 +Brand#34|MEDIUM BRUSHED TIN|14|4 +Brand#34|MEDIUM BURNISHED NICKEL|3|4 +Brand#34|SMALL ANODIZED STEEL|23|4 +Brand#34|SMALL BRUSHED TIN|9|4 +Brand#34|SMALL PLATED BRASS|14|4 +Brand#34|STANDARD ANODIZED NICKEL|36|4 +Brand#34|STANDARD BRUSHED TIN|19|4 +Brand#34|STANDARD BURNISHED TIN|23|4 +Brand#34|STANDARD PLATED NICKEL|36|4 +Brand#35|PROMO BURNISHED BRASS|3|4 +Brand#35|PROMO BURNISHED STEEL|14|4 +Brand#35|PROMO PLATED BRASS|19|4 +Brand#35|STANDARD ANODIZED NICKEL|14|4 +Brand#35|STANDARD ANODIZED STEEL|23|4 +Brand#35|STANDARD BRUSHED BRASS|3|4 +Brand#35|STANDARD BRUSHED NICKEL|49|4 +Brand#35|STANDARD PLATED STEEL|14|4 +Brand#41|MEDIUM ANODIZED NICKEL|9|4 +Brand#41|MEDIUM BRUSHED TIN|9|4 +Brand#41|MEDIUM PLATED STEEL|19|4 +Brand#41|PROMO ANODIZED NICKEL|9|4 +Brand#41|SMALL ANODIZED STEEL|45|4 +Brand#41|SMALL POLISHED COPPER|14|4 +Brand#41|STANDARD ANODIZED NICKEL|9|4 +Brand#41|STANDARD ANODIZED TIN|36|4 +Brand#41|STANDARD ANODIZED TIN|49|4 +Brand#41|STANDARD BRUSHED TIN|45|4 +Brand#41|STANDARD PLATED TIN|49|4 +Brand#42|ECONOMY BRUSHED COPPER|14|4 +Brand#42|LARGE ANODIZED NICKEL|49|4 +Brand#42|MEDIUM PLATED TIN|45|4 +Brand#42|PROMO BRUSHED STEEL|19|4 +Brand#42|PROMO BURNISHED TIN|49|4 +Brand#42|PROMO PLATED STEEL|19|4 +Brand#42|PROMO PLATED STEEL|45|4 +Brand#42|STANDARD BURNISHED NICKEL|49|4 +Brand#42|STANDARD PLATED COPPER|19|4 +Brand#43|ECONOMY ANODIZED COPPER|19|4 +Brand#43|ECONOMY ANODIZED NICKEL|49|4 +Brand#43|ECONOMY PLATED TIN|19|4 +Brand#43|ECONOMY POLISHED TIN|45|4 +Brand#43|LARGE BURNISHED COPPER|3|4 +Brand#43|LARGE POLISHED TIN|45|4 +Brand#43|MEDIUM ANODIZED BRASS|14|4 +Brand#43|MEDIUM ANODIZED COPPER|36|4 +Brand#43|MEDIUM ANODIZED COPPER|49|4 +Brand#43|MEDIUM BURNISHED TIN|23|4 +Brand#43|PROMO BRUSHED BRASS|36|4 +Brand#43|PROMO BURNISHED STEEL|3|4 +Brand#43|PROMO POLISHED BRASS|19|4 +Brand#43|SMALL BRUSHED NICKEL|9|4 +Brand#43|SMALL POLISHED STEEL|19|4 +Brand#43|STANDARD ANODIZED BRASS|3|4 +Brand#43|STANDARD PLATED TIN|14|4 +Brand#44|ECONOMY ANODIZED NICKEL|36|4 +Brand#44|ECONOMY POLISHED NICKEL|23|4 +Brand#44|LARGE ANODIZED BRASS|19|4 +Brand#44|LARGE BRUSHED TIN|3|4 +Brand#44|MEDIUM BRUSHED STEEL|19|4 +Brand#44|MEDIUM BURNISHED COPPER|45|4 +Brand#44|MEDIUM BURNISHED NICKEL|23|4 +Brand#44|MEDIUM PLATED COPPER|14|4 +Brand#44|SMALL ANODIZED COPPER|23|4 +Brand#44|SMALL ANODIZED TIN|45|4 +Brand#44|SMALL PLATED COPPER|19|4 +Brand#44|STANDARD ANODIZED COPPER|3|4 +Brand#44|STANDARD ANODIZED NICKEL|36|4 +Brand#51|ECONOMY ANODIZED STEEL|9|4 +Brand#51|ECONOMY PLATED NICKEL|49|4 +Brand#51|ECONOMY POLISHED COPPER|9|4 +Brand#51|ECONOMY POLISHED STEEL|49|4 +Brand#51|LARGE BURNISHED BRASS|19|4 +Brand#51|LARGE POLISHED STEEL|19|4 +Brand#51|MEDIUM ANODIZED TIN|14|4 +Brand#51|PROMO BRUSHED BRASS|23|4 +Brand#51|PROMO POLISHED STEEL|49|4 +Brand#51|SMALL BRUSHED TIN|36|4 +Brand#51|SMALL POLISHED STEEL|49|4 +Brand#51|STANDARD BRUSHED COPPER|3|4 +Brand#51|STANDARD BRUSHED NICKEL|19|4 +Brand#51|STANDARD BURNISHED COPPER|19|4 +Brand#52|ECONOMY ANODIZED BRASS|14|4 +Brand#52|ECONOMY ANODIZED COPPER|36|4 +Brand#52|ECONOMY BURNISHED NICKEL|19|4 +Brand#52|ECONOMY BURNISHED STEEL|36|4 +Brand#52|ECONOMY PLATED TIN|23|4 +Brand#52|LARGE BRUSHED NICKEL|19|4 +Brand#52|LARGE BURNISHED TIN|45|4 +Brand#52|LARGE PLATED STEEL|9|4 +Brand#52|LARGE PLATED TIN|9|4 +Brand#52|LARGE POLISHED NICKEL|36|4 +Brand#52|MEDIUM BURNISHED TIN|45|4 +Brand#52|SMALL ANODIZED NICKEL|36|4 +Brand#52|SMALL ANODIZED STEEL|9|4 +Brand#52|SMALL BRUSHED STEEL|23|4 +Brand#52|SMALL BURNISHED NICKEL|14|4 +Brand#52|STANDARD POLISHED STEEL|19|4 +Brand#53|LARGE BURNISHED NICKEL|23|4 +Brand#53|LARGE PLATED BRASS|9|4 +Brand#53|LARGE PLATED STEEL|49|4 +Brand#53|MEDIUM BRUSHED COPPER|3|4 +Brand#53|MEDIUM BRUSHED STEEL|45|4 +Brand#53|SMALL BRUSHED BRASS|36|4 +Brand#53|STANDARD PLATED STEEL|45|4 +Brand#54|ECONOMY ANODIZED BRASS|9|4 +Brand#54|ECONOMY BRUSHED TIN|19|4 +Brand#54|ECONOMY POLISHED BRASS|49|4 +Brand#54|LARGE ANODIZED BRASS|49|4 +Brand#54|LARGE BURNISHED BRASS|49|4 +Brand#54|LARGE BURNISHED TIN|14|4 +Brand#54|LARGE POLISHED BRASS|19|4 +Brand#54|MEDIUM BURNISHED STEEL|3|4 +Brand#54|SMALL BURNISHED STEEL|19|4 +Brand#54|SMALL PLATED BRASS|23|4 +Brand#54|SMALL PLATED TIN|14|4 +Brand#55|LARGE BRUSHED NICKEL|9|4 +Brand#55|LARGE PLATED TIN|9|4 +Brand#55|LARGE POLISHED STEEL|36|4 +Brand#55|MEDIUM BRUSHED TIN|45|4 +Brand#55|PROMO BRUSHED STEEL|36|4 +Brand#55|PROMO BURNISHED STEEL|14|4 +Brand#55|SMALL PLATED COPPER|45|4 +Brand#55|STANDARD ANODIZED BRASS|36|4 +Brand#55|STANDARD BRUSHED COPPER|3|4 +Brand#55|STANDARD BRUSHED STEEL|19|4 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q17.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q17.csv new file mode 100644 index 000000000..1726063c1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q17.csv @@ -0,0 +1,2 @@ +avg_yearly +NA diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q18.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q18.csv new file mode 100644 index 000000000..9ed5fdcb6 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q18.csv @@ -0,0 +1,3 @@ +c_name|c_custkey|o_orderkey|o_orderdate|o_totalprice|sum +Customer#000000667|667|29158|1995-10-21|439687.23|305 +Customer#000000178|178|6882|1997-04-09|422359.65|303 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q19.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q19.csv new file mode 100644 index 000000000..6e8168d47 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q19.csv @@ -0,0 +1,2 @@ +revenue +22923.0280 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q20.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q20.csv new file mode 100644 index 000000000..c0852d31c --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q20.csv @@ -0,0 +1,2 @@ +s_name|s_address +Supplier#000000013|kgTZjbt4CAa4c3SlirlBLqIL41YbCj diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q21.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q21.csv new file mode 100644 index 000000000..6d7955274 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q21.csv @@ -0,0 +1,2 @@ +s_name|numwait +Supplier#000000074|9 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q22.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q22.csv new file mode 100644 index 000000000..63c36ded7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf0.01/q22.csv @@ -0,0 +1,8 @@ +cntrycode|numcust|totacctbal +13|10|75359.29 +17|8|62288.98 +18|14|111072.45 +23|5|40458.86 +29|11|88722.85 +30|17|122189.33 +31|8|66313.16 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q01.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q01.csv new file mode 100644 index 000000000..e3b1a6d8a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q01.csv @@ -0,0 +1,5 @@ +l_returnflag|l_linestatus|sum_qty|sum_base_price|sum_disc_price|sum_charge|avg_qty|avg_price|avg_disc|count_order +A|F|37734107|56586554400.73|53758257134.8700|55909065222.827692|25.5220058532573|38273.1297346216|0.0499852958382544|1478493 +N|F|991417|1487504710.38|1413082168.0541|1469649223.194375|25.516471920523|38284.4677608482|0.0500934266741932|38854 +N|O|74476040|111701729697.74|106118230307.6056|110367043872.497010|25.502226769585|38249.1179889067|0.0499965860536267|2920374 +R|F|37719753|56568041380.90|53741292684.6040|55889619119.831932|25.5057936126908|38250.8546261027|0.0500094058299836|1478870 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q02.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q02.csv new file mode 100644 index 000000000..30196d883 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q02.csv @@ -0,0 +1,101 @@ +s_acctbal|s_name|n_name|p_partkey|p_mfgr|s_address|s_phone|s_comment +9938.53|Supplier#000005359|UNITED KINGDOM|185358|Manufacturer#4|bgxj2K0w1kJvxYl5mhCfou,W|33-429-790-6131|l, ironic instructions cajole +9937.84|Supplier#000005969|ROMANIA|108438|Manufacturer#1|rdnmd9c8EG1EIAYY3LPVa4yUNx6OwyVaQ|29-520-692-3537|es. furiously silent deposits among the deposits haggle furiously a +9936.22|Supplier#000005250|UNITED KINGDOM|249|Manufacturer#4|qX AB0vP8mJEWeBuY9jri|33-320-228-2957|ar, regular requests nag blithely special accounts. final deposits impress carefully. ironic, +9923.77|Supplier#000002324|GERMANY|29821|Manufacturer#4|uXcnR7tv87dG|17-779-299-1839|s sleep according to the quick requests. carefully +9871.22|Supplier#000006373|GERMANY|43868|Manufacturer#5|iSLO35z7Ae|17-813-485-8637|against the slyly daring requests. unusual accounts wake atop the blithely spe +9870.78|Supplier#000001286|GERMANY|81285|Manufacturer#2|3gq0mZLHI5OTM6 tBYmLTHZaulCYnlECzQ7nj|17-516-924-4574|into beans haggle at the quickly final asymptotes. unusu +9870.78|Supplier#000001286|GERMANY|181285|Manufacturer#4|3gq0mZLHI5OTM6 tBYmLTHZaulCYnlECzQ7nj|17-516-924-4574|into beans haggle at the quickly final asymptotes. unusu +9852.52|Supplier#000008973|RUSSIA|18972|Manufacturer#2|zVfUT3Np22kUC05tYWHBotaR|32-188-594-7038|ly daring excuses unwind carefully above the fu +9847.83|Supplier#000008097|RUSSIA|130557|Manufacturer#2|veMRTQBmUResNvfD3|32-375-640-3593| slyly ironic, special requests. final instructions above the qu +9847.57|Supplier#000006345|FRANCE|86344|Manufacturer#1|68yX tGXAkVRSxUGNSjJdptw 8O878xaFnaoQK|16-886-766-7945|odolites. blithely special requests above the regular foxes sleep unusual sauternes. care +9847.57|Supplier#000006345|FRANCE|173827|Manufacturer#2|68yX tGXAkVRSxUGNSjJdptw 8O878xaFnaoQK|16-886-766-7945|odolites. blithely special requests above the regular foxes sleep unusual sauternes. care +9836.93|Supplier#000007342|RUSSIA|4841|Manufacturer#4|icFgTpZ0TuAm188dv|32-399-414-5385| packages are blithely about the quickly +9817.1|Supplier#000002352|RUSSIA|124815|Manufacturer#2|XfLCj71HKHnPqgvs7KNgPKcOWoWxo2w|32-551-831-1437|al packages doze always according to the quickly f +9817.1|Supplier#000002352|RUSSIA|152351|Manufacturer#3|XfLCj71HKHnPqgvs7KNgPKcOWoWxo2w|32-551-831-1437|al packages doze always according to the quickly f +9739.86|Supplier#000003384|FRANCE|138357|Manufacturer#2|D01XwXbcILNwmrGS6ZPrVhZxO40i|16-494-913-5925|es. carefully regular ideas cajole. quickly ironic requests haggle. pending sentiment +9721.95|Supplier#000008757|UNITED KINGDOM|156241|Manufacturer#3|ryKUkEeWN7Z|33-821-407-2995| the instructions breach slyly +9681.33|Supplier#000008406|RUSSIA|78405|Manufacturer#1|1A6x3PLy6F|32-139-873-8571|ons sleep express deposits. epitap +9643.55|Supplier#000005148|ROMANIA|107617|Manufacturer#1|H7WOI6lzFuSsWzTSBrhzTYV|29-252-617-4850|carefully platelets. packages sleep special ideas. quick +9624.82|Supplier#000001816|FRANCE|34306|Manufacturer#3|NTwQPSZwfhc4uu1EMvEDopBnEv2j P|16-392-237-6726| the express, regular accounts. regular decoys boost alongside of +9624.78|Supplier#000009658|ROMANIA|189657|Manufacturer#1|DmRxpLmL88XCBiONB3tq3e0u|29-748-876-2014|inst the blithely brave frays. brav +9612.94|Supplier#000003228|ROMANIA|120715|Manufacturer#2|hnNBdhdXO4yT18 QNABTrL8fuv0A4p|29-325-784-8187|furiously foxes. express packages nag blithely express, pending ideas. fluffily ironi +9612.94|Supplier#000003228|ROMANIA|198189|Manufacturer#4|hnNBdhdXO4yT18 QNABTrL8fuv0A4p|29-325-784-8187|furiously foxes. express packages nag blithely express, pending ideas. fluffily ironi +9571.83|Supplier#000004305|ROMANIA|179270|Manufacturer#2|Bdj1T5EostLveb9ocRbz|29-973-481-1831|fully: fluffily special deposits use fur +9558.1|Supplier#000003532|UNITED KINGDOM|88515|Manufacturer#4|ncMxIJcDYZd5B7FlKxxLmnlzPeZB,FKBujB|33-152-301-2164|against the final pinto beans. carefully bold asymptotes use +9492.79|Supplier#000005975|GERMANY|25974|Manufacturer#5|9UEiIp7uSYtTF5|17-992-579-4839|fluffily ironic instructions haggle against the even, special accounts. quickly final +9461.05|Supplier#000002536|UNITED KINGDOM|20033|Manufacturer#1|TEEkPusQ6rU18YvixE7IQtBDOyRBdGoOWl2r|33-556-973-5522|inal ideas cajole furiously. blithely special Tiresias against the b +9453.01|Supplier#000000802|ROMANIA|175767|Manufacturer#1|1Uj23QWxQjj7EyeqHWqGWTbN|29-342-882-6463|s according to the even deposits integrate express packages. express +9408.65|Supplier#000007772|UNITED KINGDOM|117771|Manufacturer#4|rIoV2rj0KNy,IT|33-152-491-1126|s nag quickly regular packages. carefully express pinto beans about th +9359.61|Supplier#000004856|ROMANIA|62349|Manufacturer#5|k2CKOmXhPruJZ|29-334-870-9731|es. final asymptotes wake carefully +9357.45|Supplier#000006188|UNITED KINGDOM|138648|Manufacturer#1|LS,Z0 zbSvC7GWjF|33-583-607-1633| somas cajole around the even, ironic deposits. pending theodolites according to the b +9352.04|Supplier#000003439|GERMANY|170921|Manufacturer#4|B2bnKDIpkJp2uHKp|17-128-996-4650|nusual frets cajole carefully beneath +9312.97|Supplier#000007807|RUSSIA|90279|Manufacturer#5|Dk2ebpGR3jlpYbpMg9Djr|32-673-872-5854|. silent asymptotes boost. quickly ironic accounts for the +9312.97|Supplier#000007807|RUSSIA|100276|Manufacturer#5|Dk2ebpGR3jlpYbpMg9Djr|32-673-872-5854|. silent asymptotes boost. quickly ironic accounts for the +9280.27|Supplier#000007194|ROMANIA|47193|Manufacturer#3|tJ96aHp8 l3uiq38LiDHswtk9bHMg|29-318-454-2133|tes. carefully regular accounts are carefully since the waters. accounts cajole? carefully bold +9274.8|Supplier#000008854|RUSSIA|76346|Manufacturer#3|,uJfCd6eTiYE1ZEhDM vsc8ANQPWaPlQ|32-524-148-5221|onic, final ideas. blithely regular platelets boost final, ironic pinto beans. fluffil +9249.35|Supplier#000003973|FRANCE|26466|Manufacturer#1|OZSkIozfU4FYizk4e091MZHozL1qcHe257J89bW|16-722-866-1658|beans. slyly ironic dependencies cajole furiously furiously regular ideas. boldly even requests hagg +9249.35|Supplier#000003973|FRANCE|33972|Manufacturer#1|OZSkIozfU4FYizk4e091MZHozL1qcHe257J89bW|16-722-866-1658|beans. slyly ironic dependencies cajole furiously furiously regular ideas. boldly even requests hagg +9208.7|Supplier#000007769|ROMANIA|40256|Manufacturer#5|AzIENtMrVCSbrjyUu8|29-964-424-9649| ironic requests among the deposits affix busily ironic accounts. slow pinto beans are blithely fi +9201.47|Supplier#000009690|UNITED KINGDOM|67183|Manufacturer#5|pprpD77FEIWsNMmGT9T|33-121-267-9529|uriously bold packages integrate blithely ironic theodolites. carefully unusual escap +9192.1|Supplier#000000115|UNITED KINGDOM|85098|Manufacturer#3|EhrYy0MT5M1vfZ0V4skpifdp6pgFz5|33-597-248-1220|onic instructions. ironic, regular deposits haggle f +9189.98|Supplier#000001226|GERMANY|21225|Manufacturer#4|BzfoA9wft1Mx3iBIs|17-725-903-1381|luffily across the slyly special instructions. bold, ironic deposi +9128.97|Supplier#000004311|RUSSIA|146768|Manufacturer#5|jSiHD4NTd8i9zVRX9uz9a,|32-155-440-7120|theodolites. furiously even pinto beans abou +9104.83|Supplier#000008520|GERMANY|150974|Manufacturer#4|aA95nLn,m9shRrPXZw9Y1X|17-728-804-1793|nstructions. carefully regular requests use fluffily against the quickly final deposits. blithel +9101|Supplier#000005791|ROMANIA|128254|Manufacturer#5|txPYsp50HJkbbaAJ0bYieqHmZtirDUVOcmC4lk|29-549-251-5384| regular foxes use carefully final packages. fluffily stealthy deposits toward the sp +9094.57|Supplier#000004582|RUSSIA|39575|Manufacturer#1|5p,3Gp8kX 1EDarE0JR5juHH Sq9jlxgKenM|32-587-577-1351|ages affix quickly after the carefully regular accounts. regular, regular foxes kindle. +8996.87|Supplier#000004702|FRANCE|102191|Manufacturer#5|T35OahYXQGC|16-811-269-8946|ily regular grouches wake quickly ironic de +8996.14|Supplier#000009814|ROMANIA|139813|Manufacturer#2|RL,cVCKSXFc6Win6EmtF415A22as8nG2fqEa|29-995-571-8781| regular requests haggle carefully above the carefully regular deposits. ironic pearls in p +8968.42|Supplier#000010000|ROMANIA|119999|Manufacturer#5|R7kfmyzoIfXlrbnqNwUUW3phJctocp0J|29-578-432-2146|ular, quick foxes sleep quickly according to the blithely fluffy theodolit +8936.82|Supplier#000007043|UNITED KINGDOM|109512|Manufacturer#1|m5QHON1iD1OPhmU2R3z97u 6mCIvjnAc3I0,9s|33-784-177-8208| final dependencies. deposits a +8929.42|Supplier#000008770|FRANCE|173735|Manufacturer#4|aTOkYV7y3 kqbRrkOGJLaI|16-242-746-9248|ns haggle quickly silent theodolites. bold, final requests along t +8920.59|Supplier#000003967|ROMANIA|26460|Manufacturer#1|NjCq3NUY82S|29-194-731-3944|ts. daringly regular theodolites affix silently. reg +8920.59|Supplier#000003967|ROMANIA|173966|Manufacturer#2|NjCq3NUY82S|29-194-731-3944|ts. daringly regular theodolites affix silently. reg +8913.96|Supplier#000004603|UNITED KINGDOM|137063|Manufacturer#2|d6sFwf6 TD1xyfuFbdM2h8LX7ZWc3zHupV|33-789-255-7342|lithely whithout the furiously ironic sheaves. ironic reques +8877.82|Supplier#000007967|FRANCE|167966|Manufacturer#5|rXBIZqq9eWEuU90B vlCab6|16-442-147-9345|ckages-- evenly even requests boost blit +8862.24|Supplier#000003323|ROMANIA|73322|Manufacturer#3|5RrF2PzoRlwpAGXjyf|29-736-951-3710|regular ideas haggle blithely packages. regula +8841.59|Supplier#000005750|ROMANIA|100729|Manufacturer#5|n uXFrKx,KVYIQjmRuV,yejWmLMdRJnk|29-344-502-5481|leep finally furiously express packages. slyly unusual packages cajole unusual, +8781.71|Supplier#000003121|ROMANIA|13120|Manufacturer#5|wdA7CLuYXS22oQEmP0V,x0PHrXiPdl5Rpwv,ub|29-707-291-5144|ies. final foxes are furiou +8754.24|Supplier#000009407|UNITED KINGDOM|179406|Manufacturer#4|pj9oPHQ4OLWp|33-903-970-9604|ng asymptotes hang across the blithely special deposits. +8691.06|Supplier#000004429|UNITED KINGDOM|126892|Manufacturer#2|H0paE V6JCrlZpYrzI0LgIP|33-964-337-5038| sly requests might sleep. final dolphins sleep. furiousl +8655.99|Supplier#000006330|RUSSIA|193810|Manufacturer#2|7CsFQnd ,tzgMYvVoMim5l4DrJcX8SaQMTcy|32-561-198-3705|ideas wake across the regular, unusual instructions; furiously final deposits wake near the s +8638.36|Supplier#000002920|RUSSIA|75398|Manufacturer#1|iMYQSQzsLXg|32-122-621-7549|ickly dolphins. furiously careful asymptotes sublate +8638.36|Supplier#000002920|RUSSIA|170402|Manufacturer#3|iMYQSQzsLXg|32-122-621-7549|ickly dolphins. furiously careful asymptotes sublate +8607.69|Supplier#000006003|UNITED KINGDOM|76002|Manufacturer#2|njRvqoOmIxNDe,da,SsnweINv1VY2YatifmJq|33-416-807-5206|braids sleep carefully along the iron +8569.52|Supplier#000005936|RUSSIA|5935|Manufacturer#5|I3Qd1VwvDm5hYGzg1hBHzKy,P3YQXq7|32-644-251-7916|s about the carefully final accounts use always even requests. furiously express dependenc +8564.12|Supplier#000000033|GERMANY|110032|Manufacturer#1|LLMgB3vXW,0g,8nuv3qU3QZaEBZvU2qRLX9|17-138-897-9374|l packages cajole unusual, final packages. slyly express requests +8553.82|Supplier#000003979|ROMANIA|143978|Manufacturer#4|qLE5JpqDoe3XHsBI6etWpd4zRsjsBNb9Tidi6|29-124-646-4897|counts are quickly carefully ironic instructions. platelets wake f +8517.23|Supplier#000009529|RUSSIA|37025|Manufacturer#5|NWW9SDThqi9RIeOA|32-565-297-8775|ial requests use stealthily along the carefully u +8517.23|Supplier#000009529|RUSSIA|59528|Manufacturer#2|NWW9SDThqi9RIeOA|32-565-297-8775|ial requests use stealthily along the carefully u +8503.7|Supplier#000006830|RUSSIA|44325|Manufacturer#4|qoW4lp2961uQiKOK6rW8|32-147-878-5069|atelets sleep furiously pending asymptotes. even requests for the blithely unusual packages +8457.09|Supplier#000009456|UNITED KINGDOM|19455|Manufacturer#1|U8pJ1 SKbZPhH7,bLWXX3pG|33-858-440-4349|ounts sleep about the bold, even ideas. slyly unusual accounts after the asymptotes +8441.4|Supplier#000003817|FRANCE|141302|Manufacturer#2|K6XLsYufTS|16-339-356-5115|sly fluffily regular pinto beans. slyly even deposits snooze fluffily along the fluff +8432.89|Supplier#000003990|RUSSIA|191470|Manufacturer#1|wMJppCZ9aPMuq2nr88TVfztvE gj95OG wdNUE|32-839-509-9301|. express pinto beans use slyly. regular platelets sleep quickly busy deposits. final +8431.4|Supplier#000002675|ROMANIA|5174|Manufacturer#1|khl8ydxR9VekbcMLgJKPtpNtwAkYtJTv|29-474-643-1443|regular, express platelets are. carefully ironic forges since the requests affix +8407.04|Supplier#000005406|RUSSIA|162889|Manufacturer#4|ITrK2mV94SooV6 Igo|32-626-152-4621| even theodolites. quickly bold deposits after the pen +8386.08|Supplier#000008518|FRANCE|36014|Manufacturer#3|ZHAsABq5MRP e5kc0DRD8za3xGdf763ChHmoOA45|16-618-780-7481|g alongside of the slyly unusual platelets! blithely regular asymptotes cajole. quickly regular +8376.52|Supplier#000005306|UNITED KINGDOM|190267|Manufacturer#5|SyS2SsaA8i CqnbzUdfNH07bVtt9uW,Cp6FLCkOR|33-632-514-7931|pendencies affix furiously against the special, blithe packages. qui +8348.74|Supplier#000008851|FRANCE|66344|Manufacturer#4|E4uITlvmPHKvZ|16-796-240-2472|s packages haggle above the express pinto beans. stealthy, ironic theodolites sleep quickly +8338.58|Supplier#000007269|FRANCE|17268|Manufacturer#4|2vJh8wqp6CJp,W0Y|16-267-277-4365|lithely through the accounts. express, ironic asymptotes wou +8328.46|Supplier#000001744|ROMANIA|69237|Manufacturer#5|DfCXL6UWAY1lgjQYB0AjE8T2sx0BzS|29-330-728-5873| regular, special dolphins haggle carefully special asy +8307.93|Supplier#000003142|GERMANY|18139|Manufacturer#1|OAPFw6SNodrC kFi|17-595-447-6026|usly express packages sleep finally regular ideas. carefu +8231.61|Supplier#000009558|RUSSIA|192000|Manufacturer#2|FONKME0t7ZJhnjn9VL5|32-762-137-5858|g to the carefully even brai +8152.61|Supplier#000002731|ROMANIA|15227|Manufacturer#4|sDFx3iox2Zzx|29-805-463-2030|ly above the packages. final accounts sleep furiously. fluffily iro +8109.09|Supplier#000009186|FRANCE|99185|Manufacturer#1|wKLCzA5bMuGRBm35tvQAGpen23L|16-668-570-1402|ts cajole daringly. pinto beans +8102.62|Supplier#000003347|UNITED KINGDOM|18344|Manufacturer#5|Froy39Y8ZUJ|33-454-274-8532|y daring requests. unusual accounts wake atop the blithely special packages. sly +8046.07|Supplier#000008780|FRANCE|191222|Manufacturer#3|rOssxn,6gRDzHr0gu,hEK|16-473-215-6395|he regular foxes cajole ruthlessly among the sometimes final grouches. blithel +8042.09|Supplier#000003245|RUSSIA|135705|Manufacturer#4|oJSiGLXRCDAPcfWot7LkwSQRCh63XNS2|32-836-132-8872| use slyly. furiously regular deposits sleep according to the requests. +8042.09|Supplier#000003245|RUSSIA|150729|Manufacturer#1|oJSiGLXRCDAPcfWot7LkwSQRCh63XNS2|32-836-132-8872| use slyly. furiously regular deposits sleep according to the requests. +7992.4|Supplier#000006108|FRANCE|118574|Manufacturer#1|TyptNE7nv6BLpLl6WFX|16-974-998-8937|theodolites among the furiously unusual accounts must x +7980.65|Supplier#000001288|FRANCE|13784|Manufacturer#4|tm0TjL5b oE|16-646-464-8247|gular pains? fluffily bold warhorses affix? blithe instruction +7950.37|Supplier#000008101|GERMANY|33094|Manufacturer#5|HG2wfVixwCIhK7dlrigGR3an2LuSifDJH|17-627-663-8014|ly alongside of the furiously unusual requests! bold, express foxe +7937.93|Supplier#000009012|ROMANIA|83995|Manufacturer#2|J6I7sJj0mGYIWFv9KxD3fK O7tvNP|29-250-925-9690| use slyly against the slyly bold theod +7914.45|Supplier#000001013|RUSSIA|125988|Manufacturer#2|AI9ODzBzWgnny28PHBei5M2lUFdD9|32-194-698-3365| the blithely silent accounts. q +7912.91|Supplier#000004211|GERMANY|159180|Manufacturer#5|Zva95Dwj EY0w,XjgsL7O0Zb2 l3almck|17-266-947-7315| slyly silent requests; fluffily fi +7912.91|Supplier#000004211|GERMANY|184210|Manufacturer#4|Zva95Dwj EY0w,XjgsL7O0Zb2 l3almck|17-266-947-7315| slyly silent requests; fluffily fi +7894.56|Supplier#000007981|GERMANY|85472|Manufacturer#4|e8hRUxe9cqQM3b|17-963-404-3760|ly final courts. unusual, quiet dolphi +7887.08|Supplier#000009792|GERMANY|164759|Manufacturer#3|3YSi76M2 I8XGikO5YgSM81r5Z6A7VkZcys|17-988-938-4296| the regular ideas. furiously bold deposits boost above the bli +7871.5|Supplier#000007206|RUSSIA|104695|Manufacturer#1|YvrLdpD 5ExhHmRWzK41tw4|32-432-452-7731|ording to the furious theodolites cajole carefully according to the busily express asymptotes. +7852.45|Supplier#000005864|RUSSIA|8363|Manufacturer#4|5odLpc1M83KXJ0O|32-454-883-3821|egular, regular ideas. requests are carefully. furiously final dolp +7850.66|Supplier#000001518|UNITED KINGDOM|86501|Manufacturer#1|ddNQX3hIjgico|33-730-383-3892|ccounts. special, final deposits +7843.52|Supplier#000006683|FRANCE|11680|Manufacturer#4|Z1,hkHIw,Z3,,Comv6kLxIiPJtoNt|16-464-517-8943|sits. blithely regular requests above the pending, regular ideas boo diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q03.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q03.csv new file mode 100644 index 000000000..75e09ea04 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q03.csv @@ -0,0 +1,11 @@ +l_orderkey|revenue|o_orderdate|o_shippriority +2456423|406181.0111|1995-03-05|0 +3459808|405838.6989|1995-03-04|0 +492164|390324.0610|1995-02-19|0 +1188320|384537.9359|1995-03-09|0 +2435712|378673.0558|1995-02-26|0 +4878020|378376.7952|1995-03-12|0 +5521732|375153.9215|1995-03-13|0 +2628192|373133.3094|1995-02-22|0 +993600|371407.4595|1995-03-05|0 +2300070|367371.1452|1995-03-13|0 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q04.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q04.csv new file mode 100644 index 000000000..845a19929 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q04.csv @@ -0,0 +1,6 @@ +o_orderpriority|order_count +1-URGENT|10594 +2-HIGH|10476 +3-MEDIUM|10410 +4-NOT SPECIFIED|10556 +5-LOW|10487 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q05.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q05.csv new file mode 100644 index 000000000..a00890db5 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q05.csv @@ -0,0 +1,6 @@ +n_name|revenue +INDONESIA|55502041.1697 +VIETNAM|55295086.9967 +CHINA|53724494.2566 +INDIA|52035512.0002 +JAPAN|45410175.6954 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q06.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q06.csv new file mode 100644 index 000000000..3e29ce500 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q06.csv @@ -0,0 +1,2 @@ +revenue +123141078.2283 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q07.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q07.csv new file mode 100644 index 000000000..dc42bb9d3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q07.csv @@ -0,0 +1,5 @@ +supp_nation|cust_nation|l_year|revenue +FRANCE|GERMANY|1995|54639732.7336 +FRANCE|GERMANY|1996|54633083.3076 +GERMANY|FRANCE|1995|52531746.6697 +GERMANY|FRANCE|1996|52520549.0224 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q08.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q08.csv new file mode 100644 index 000000000..c70f6ad52 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q08.csv @@ -0,0 +1,3 @@ +o_year|mkt_share +1995|0.0344358904066549 +1996|0.0414855212935303 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q09.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q09.csv new file mode 100644 index 000000000..8c6100b92 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q09.csv @@ -0,0 +1,176 @@ +nation|o_year|sum_profit +ALGERIA|1998|27136900.1803 +ALGERIA|1997|48611833.4962 +ALGERIA|1996|48285482.6782 +ALGERIA|1995|44402273.5999 +ALGERIA|1994|48694008.0668 +ALGERIA|1993|46044207.7838 +ALGERIA|1992|45636849.4881 +ARGENTINA|1998|28341663.7848 +ARGENTINA|1997|47143964.1176 +ARGENTINA|1996|45255278.6021 +ARGENTINA|1995|45631769.2054 +ARGENTINA|1994|48268856.3547 +ARGENTINA|1993|48605593.6162 +ARGENTINA|1992|46654240.7487 +BRAZIL|1998|26527736.3960 +BRAZIL|1997|45640660.7677 +BRAZIL|1996|45090647.1630 +BRAZIL|1995|44015888.5132 +BRAZIL|1994|44854218.8932 +BRAZIL|1993|45766603.7379 +BRAZIL|1992|45280216.8027 +CANADA|1998|26828985.3944 +CANADA|1997|44849954.3186 +CANADA|1996|46307936.1108 +CANADA|1995|47311993.0441 +CANADA|1994|46691491.9596 +CANADA|1993|46634791.1121 +CANADA|1992|45873849.6882 +CHINA|1998|27510180.1657 +CHINA|1997|46123865.4097 +CHINA|1996|49532807.0601 +CHINA|1995|46734651.4838 +CHINA|1994|46397896.6097 +CHINA|1993|49634673.9463 +CHINA|1992|46949457.6426 +EGYPT|1998|28401491.7968 +EGYPT|1997|47674857.6783 +EGYPT|1996|47745727.5450 +EGYPT|1995|45897160.6783 +EGYPT|1994|47194895.2280 +EGYPT|1993|49133627.6471 +EGYPT|1992|47000574.5027 +ETHIOPIA|1998|25135046.1377 +ETHIOPIA|1997|43010596.0838 +ETHIOPIA|1996|43636287.1922 +ETHIOPIA|1995|43575757.3343 +ETHIOPIA|1994|41597208.5283 +ETHIOPIA|1993|42622804.1616 +ETHIOPIA|1992|44385735.6813 +FRANCE|1998|26210392.2804 +FRANCE|1997|42392969.4731 +FRANCE|1996|43306317.9749 +FRANCE|1995|46377408.4328 +FRANCE|1994|43447352.9922 +FRANCE|1993|43729961.0639 +FRANCE|1992|44052308.4290 +GERMANY|1998|25991257.1071 +GERMANY|1997|43968355.8079 +GERMANY|1996|45882074.8049 +GERMANY|1995|43314338.3077 +GERMANY|1994|44616995.4369 +GERMANY|1993|45126645.9113 +GERMANY|1992|44361141.2107 +INDIA|1998|29626417.2379 +INDIA|1997|51386111.3448 +INDIA|1996|47571018.5122 +INDIA|1995|49344062.2829 +INDIA|1994|50106952.4261 +INDIA|1993|48112766.6987 +INDIA|1992|47914303.1234 +INDONESIA|1998|27734909.6763 +INDONESIA|1997|44593812.9863 +INDONESIA|1996|44746729.8078 +INDONESIA|1995|45593622.6993 +INDONESIA|1994|45988483.8772 +INDONESIA|1993|46147963.7895 +INDONESIA|1992|45185777.0688 +IRAN|1998|26661608.9301 +IRAN|1997|45019114.1696 +IRAN|1996|45891397.0992 +IRAN|1995|44414285.2348 +IRAN|1994|43696360.4795 +IRAN|1993|45362775.8094 +IRAN|1992|43052338.4143 +IRAQ|1998|31188498.1914 +IRAQ|1997|48585307.5222 +IRAQ|1996|50036593.8404 +IRAQ|1995|48774801.7275 +IRAQ|1994|48795847.2310 +IRAQ|1993|47435691.5082 +IRAQ|1992|47562355.6571 +JAPAN|1998|24694102.1720 +JAPAN|1997|42377052.3454 +JAPAN|1996|40267778.9094 +JAPAN|1995|40925317.4650 +JAPAN|1994|41159518.3058 +JAPAN|1993|39589074.2771 +JAPAN|1992|39113493.9052 +JORDAN|1998|23489867.7893 +JORDAN|1997|41615962.6619 +JORDAN|1996|41860855.4684 +JORDAN|1995|39931672.0908 +JORDAN|1994|40707555.4638 +JORDAN|1993|39060405.4658 +JORDAN|1992|41657604.2684 +KENYA|1998|25566337.4303 +KENYA|1997|43108847.9024 +KENYA|1996|43482953.5430 +KENYA|1995|42517988.9814 +KENYA|1994|43612479.4523 +KENYA|1993|42724038.7571 +KENYA|1992|43217106.2068 +MOROCCO|1998|24915496.8756 +MOROCCO|1997|42698382.8550 +MOROCCO|1996|42986113.5049 +MOROCCO|1995|42316089.1593 +MOROCCO|1994|43458604.6029 +MOROCCO|1993|42672288.0699 +MOROCCO|1992|42800781.6415 +MOZAMBIQUE|1998|28279876.0301 +MOZAMBIQUE|1997|51159216.2298 +MOZAMBIQUE|1996|48072525.0645 +MOZAMBIQUE|1995|48905200.6007 +MOZAMBIQUE|1994|46092076.2805 +MOZAMBIQUE|1993|48555926.2669 +MOZAMBIQUE|1992|47809075.1192 +PERU|1998|26713966.2678 +PERU|1997|48324008.6011 +PERU|1996|50310008.8629 +PERU|1995|49647080.9629 +PERU|1994|46420910.2773 +PERU|1993|51536906.2487 +PERU|1992|47711665.3137 +ROMANIA|1998|27271993.1010 +ROMANIA|1997|45063059.1953 +ROMANIA|1996|47492335.0323 +ROMANIA|1995|45710636.2909 +ROMANIA|1994|46088041.1066 +ROMANIA|1993|47515092.5613 +ROMANIA|1992|44111439.8044 +RUSSIA|1998|27935323.7271 +RUSSIA|1997|48222347.2924 +RUSSIA|1996|47553559.4932 +RUSSIA|1995|46755990.0976 +RUSSIA|1994|48000515.6191 +RUSSIA|1993|48569624.5082 +RUSSIA|1992|47672831.5329 +SAUDI ARABIA|1998|27113516.8424 +SAUDI ARABIA|1997|46690468.9649 +SAUDI ARABIA|1996|47775782.6670 +SAUDI ARABIA|1995|46657107.8287 +SAUDI ARABIA|1994|48181672.8100 +SAUDI ARABIA|1993|45692556.4438 +SAUDI ARABIA|1992|48924913.2717 +UNITED KINGDOM|1998|26366682.8786 +UNITED KINGDOM|1997|44518130.1851 +UNITED KINGDOM|1996|45539729.6166 +UNITED KINGDOM|1995|46845879.3390 +UNITED KINGDOM|1994|43081609.5737 +UNITED KINGDOM|1993|44770146.7555 +UNITED KINGDOM|1992|44123402.5484 +UNITED STATES|1998|27826593.6825 +UNITED STATES|1997|46638572.3648 +UNITED STATES|1996|46688280.5474 +UNITED STATES|1995|48951591.6156 +UNITED STATES|1994|45099092.0598 +UNITED STATES|1993|46181600.5278 +UNITED STATES|1992|46168214.0901 +VIETNAM|1998|27281931.0011 +VIETNAM|1997|48735914.1796 +VIETNAM|1996|47824595.9040 +VIETNAM|1995|48235135.8016 +VIETNAM|1994|47729256.3324 +VIETNAM|1993|45352676.8672 +VIETNAM|1992|47846355.6485 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q10.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q10.csv new file mode 100644 index 000000000..3dbde947f --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q10.csv @@ -0,0 +1,21 @@ +c_custkey|c_name|revenue|c_acctbal|n_name|c_address|c_phone|c_comment +57040|Customer#000057040|734235.2455|632.87|JAPAN|nICtsILWBB|22-895-641-3466|ep. blithely regular foxes promise slyly furiously ironic depend +143347|Customer#000143347|721002.6948|2557.47|EGYPT|,Q9Ml3w0gvX|14-742-935-3718|endencies sleep. slyly express deposits nag carefully around the even tithes. slyly regular +60838|Customer#000060838|679127.3077|2454.77|BRAZIL|VWmQhWweqj5hFpcvhGFBeOY9hJ4m|12-913-494-9813|tes. final instructions nag quickly according to +101998|Customer#000101998|637029.5667|3790.89|UNITED KINGDOM|0,ORojfDdyMca2E2H|33-593-865-6378|ost carefully. slyly regular packages cajole about the blithely final ideas. permanently daring deposit +125341|Customer#000125341|633508.086|4983.51|GERMANY|9YRcnoUPOM7Sa8xymhsDHdQg|17-582-695-5962|ly furiously brave packages. quickly regular dugouts kindle furiously carefully bold theodolites. +25501|Customer#000025501|620269.7849|7725.04|ETHIOPIA|sr4VVVe3xCJQ2oo2QEhi19D,pXqo6kOGaSn2|15-874-808-6793|y ironic foxes hinder according to the furiously permanent dolphins. pending ideas integrate blithely from +115831|Customer#000115831|596423.8672|5098.1|FRANCE|AlMpPnmtGrOFrDMUs5VLo EIA,Cg,Rw5TBuBoKiO|16-715-386-3788|unts nag carefully final packages. express theodolites are regular ac +84223|Customer#000084223|594998.0239|528.65|UNITED KINGDOM|Eq51o UpQ4RBr fYTdrZApDsPV4pQyuPq|33-442-824-8191|longside of the slyly final deposits. blithely final platelets about the blithely i +54289|Customer#000054289|585603.3918|5583.02|IRAN|x3ouCpz6,pRNVhajr0CCQG1|20-834-292-4707| cajole furiously after the quickly unusual fo +39922|Customer#000039922|584878.1134|7321.11|GERMANY|2KtWzW,FYkhdWBfobp6SFXWYKjvU9|17-147-757-8036|ironic deposits sublate furiously. carefully regular theodolites along the b +6226|Customer#000006226|576783.7606|2230.09|UNITED KINGDOM|TKbxS1dbkGMtaa,KOi26lbip4P0tPbWK0|33-657-701-3391|nal packages are alongside of the quickly bold deposits. carefully +922|Customer#000000922|576767.5333|3869.25|GERMANY|rsR9lRxyTdHbDOVt8nYbwjK5vAWH9sB|17-945-916-9648|cuses cajole carefully regular idea +147946|Customer#000147946|576455.132|2030.13|ALGERIA|Jqdt1kHAJtuTqHQK,B7 3tJh|10-886-956-3143|ly pending platelets. ironic requests haggle alongside of the furiou +115640|Customer#000115640|569341.1933|6436.1|ARGENTINA|6yKLIRRAirUmBjKNO6Z3|11-411-543-4901|ffily ironic deposits. blithely specia +73606|Customer#000073606|568656.8578|1785.67|JAPAN|vx9,7ACVtoKnLcoAHGNYDF|22-437-653-6966|uests cajole according to the foxe +110246|Customer#000110246|566842.9815|7763.35|VIETNAM|UgsLFL3rendATzcHi|31-943-426-9837|ow carefully. blithely careful packages hag +142549|Customer#000142549|563537.2368|5085.99|INDONESIA|pJAmChWXct HNjPzgoBUOgAHduwwIR|19-955-562-2398|. slyly bold packages nag quickly against the unusual deposits. express asymptotes detect furiously pending, eve +146149|Customer#000146149|557254.9865|1791.55|ROMANIA| STLwtlaB6|29-744-164-6487|nic, special instructions. multipliers run carefully blithely iro +52528|Customer#000052528|556397.3509|551.79|ARGENTINA|elsyt8c9Z,7ch|11-208-192-3205|olphins. blithely silent platelets affix carefully even platelets. ca +23431|Customer#000023431|554269.536|3381.86|ROMANIA|kKI5,CJAJQjQRQtOdCiFQ|29-915-458-2654|the final sentiments. carefully ironic packages diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q11.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q11.csv new file mode 100644 index 000000000..593ac8894 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q11.csv @@ -0,0 +1,1049 @@ +ps_partkey|value +129760|17538456.86 +166726|16503353.92 +191287|16474801.97 +161758|16101755.54 +34452|15983844.72 +139035|15907078.34 +9403|15451755.62 +154358|15212937.88 +38823|15064802.86 +85606|15053957.15 +33354|14408297.40 +154747|14407580.68 +82865|14235489.78 +76094|14094247.04 +222|13937777.74 +121271|13908336.00 +55221|13716120.47 +22819|13666434.28 +76281|13646853.68 +85298|13581154.93 +85158|13554904.00 +139684|13535538.72 +31034|13498025.25 +87305|13482847.04 +10181|13445148.75 +62323|13411824.30 +26489|13377256.38 +96493|13339057.83 +56548|13329014.97 +55576|13306843.35 +159751|13306614.48 +92406|13287414.50 +182636|13223726.74 +199969|13135288.21 +62865|13001926.94 +7284|12945298.19 +197867|12944510.52 +11562|12931575.51 +75165|12916918.12 +97175|12911283.50 +140840|12896562.23 +65241|12890600.46 +166120|12876927.22 +9035|12863828.70 +144616|12853549.30 +176723|12832309.74 +170884|12792136.58 +29790|12723300.33 +95213|12555483.73 +183873|12550533.05 +171235|12476538.30 +21533|12437821.32 +17290|12432159.50 +156397|12260623.50 +122611|12222812.98 +139155|12220319.25 +146316|12215800.61 +171381|12199734.52 +198633|12078226.95 +167417|12046637.62 +59512|12043468.76 +31688|12034893.64 +159586|12001505.84 +8993|11963814.30 +120302|11857707.55 +43536|11779340.52 +9552|11776909.16 +86223|11772205.08 +53776|11758669.65 +131285|11616953.74 +91628|11611114.83 +169644|11567959.72 +182299|11567462.05 +33107|11453818.76 +104184|11436657.44 +67027|11419127.14 +176869|11371451.71 +30885|11369674.79 +54420|11345076.88 +72240|11313951.05 +178708|11294635.17 +81298|11273686.13 +158324|11243442.72 +117095|11242535.24 +176793|11237733.38 +86091|11177793.79 +116033|11145434.36 +129058|11119112.20 +193714|11104706.39 +117195|11077217.96 +49851|11043701.78 +19791|11030662.62 +75800|11012401.62 +161562|10996371.69 +10119|10980015.75 +39185|10970042.56 +47223|10950022.13 +175594|10942923.05 +111295|10893675.61 +155446|10852764.57 +156391|10839810.38 +40884|10837234.19 +141288|10837130.21 +152388|10830977.82 +33449|10830858.72 +149035|10826130.02 +162620|10814275.68 +118324|10791788.10 +38932|10777541.75 +121294|10764225.22 +48721|10762582.49 +63342|10740132.60 +5614|10724668.80 +62266|10711143.10 +100202|10696675.55 +197741|10688560.72 +169178|10648522.80 +5271|10639392.65 +34499|10584177.10 +71108|10569117.56 +137132|10539880.47 +78451|10524873.24 +150827|10503810.48 +107237|10488030.84 +101727|10473558.10 +58708|10466280.44 +89768|10465477.22 +146493|10444291.58 +55424|10444006.48 +16560|10425574.74 +133114|10415097.90 +195810|10413625.20 +76673|10391977.18 +97305|10390890.57 +134210|10387210.02 +188536|10386529.92 +122255|10335760.32 +2682|10312966.10 +43814|10303086.61 +34767|10290405.18 +165584|10273705.89 +2231|10270415.55 +111259|10263256.56 +195578|10239795.82 +21093|10217531.30 +29856|10216932.54 +133686|10213345.76 +87745|10185509.40 +135153|10179379.70 +11773|10167410.84 +76316|10165151.70 +123076|10161225.78 +91894|10130462.19 +39741|10128387.52 +111753|10119780.98 +142729|10104748.89 +116775|10097750.42 +102589|10034784.36 +186268|10012181.57 +44545|10000286.48 +23307|9966577.50 +124281|9930018.90 +69604|9925730.64 +21971|9908982.03 +58148|9895894.40 +16532|9886529.90 +159180|9883744.43 +74733|9877582.88 +35173|9858275.92 +7116|9856881.02 +124620|9838589.14 +122108|9829949.35 +67200|9828690.69 +164775|9821424.44 +9039|9816447.72 +14912|9803102.20 +190906|9791315.70 +130398|9781674.27 +119310|9776927.21 +10132|9770930.78 +107211|9757586.25 +113958|9757065.50 +37009|9748362.69 +66746|9743528.76 +134486|9731922.00 +15945|9731096.45 +55307|9717745.80 +56362|9714922.83 +57726|9711792.10 +57256|9708621.00 +112292|9701653.08 +87514|9699492.53 +174206|9680562.02 +72865|9679043.34 +114357|9671017.44 +112807|9665019.21 +115203|9661018.73 +177454|9658906.35 +161275|9634313.71 +61893|9617095.44 +122219|9604888.20 +183427|9601362.58 +59158|9599705.96 +61931|9584918.98 +5532|9579964.14 +20158|9576714.38 +167199|9557413.08 +38869|9550279.53 +86949|9541943.70 +198544|9538613.92 +193762|9538238.94 +108807|9536247.16 +168324|9535647.99 +115588|9532195.04 +141372|9529702.14 +175120|9526068.66 +163851|9522808.83 +160954|9520359.45 +117757|9517882.80 +52594|9508325.76 +60960|9498843.06 +70272|9495775.62 +44050|9495515.36 +152213|9494756.96 +121203|9492601.30 +70114|9491012.30 +167588|9484741.11 +136455|9476241.78 +4357|9464355.64 +6786|9463632.57 +61345|9455336.70 +160826|9446754.84 +71275|9440138.40 +77746|9439118.35 +91289|9437472.00 +56723|9435102.16 +86647|9434604.18 +131234|9432120.00 +198129|9427651.36 +165530|9426193.68 +69233|9425053.92 +6243|9423304.66 +90110|9420422.70 +191980|9419368.36 +38461|9419316.07 +167873|9419024.49 +159373|9416950.15 +128707|9413428.50 +45267|9410863.78 +48460|9409793.93 +197672|9406887.68 +60884|9403442.40 +15209|9403245.31 +138049|9401262.10 +199286|9391770.70 +19629|9391236.40 +134019|9390615.15 +169475|9387639.58 +165918|9379510.44 +135602|9374251.54 +162323|9367566.51 +96277|9360850.68 +98336|9359671.29 +119781|9356395.73 +34440|9355365.00 +57362|9355180.10 +167236|9352973.84 +38463|9347530.94 +86749|9346826.44 +170007|9345699.90 +193087|9343744.00 +150383|9332576.75 +60932|9329582.02 +128420|9328206.35 +162145|9327722.88 +55686|9320304.40 +163080|9304916.96 +160583|9303515.92 +118153|9298606.56 +152634|9282184.57 +84731|9276586.92 +119989|9273814.20 +114584|9269698.65 +131817|9268570.08 +29068|9256583.88 +44116|9255922.00 +115818|9253311.91 +103388|9239218.08 +186118|9236209.12 +155809|9235410.84 +147003|9234847.99 +27769|9232511.64 +112779|9231927.36 +124851|9228982.68 +158488|9227216.40 +83328|9224792.20 +136797|9222927.09 +141730|9216370.68 +87304|9215695.50 +156004|9215557.90 +140740|9215329.20 +100648|9212185.08 +174774|9211718.00 +37644|9211578.60 +48807|9209496.24 +95940|9207948.40 +141586|9206699.22 +147248|9205654.95 +61372|9205228.76 +52970|9204415.95 +26430|9203710.51 +28504|9201669.20 +25810|9198878.50 +125329|9198688.50 +167867|9194022.72 +134767|9191444.72 +127745|9191271.56 +69208|9187110.00 +155222|9186469.16 +196916|9182995.82 +195590|9176353.12 +169155|9175176.09 +81558|9171946.50 +185136|9171293.04 +114790|9168509.10 +194142|9165836.61 +167639|9161165.00 +11241|9160789.46 +82628|9160155.54 +41399|9148338.00 +30755|9146196.84 +6944|9143574.58 +6326|9138803.16 +101296|9135657.62 +181479|9121093.30 +76898|9120983.10 +64274|9118745.25 +175826|9117387.99 +142215|9116876.88 +103415|9113128.62 +119765|9110768.79 +107624|9108837.45 +84215|9105257.36 +73774|9102651.92 +173972|9102069.00 +69817|9095513.88 +86943|9092253.00 +138859|9087719.30 +162273|9085296.48 +175945|9080401.21 +16836|9075715.44 +70224|9075265.95 +139765|9074755.89 +30319|9073233.10 +3851|9072657.24 +181271|9070631.52 +162184|9068835.78 +81683|9067258.47 +153028|9067010.51 +123324|9061870.95 +186481|9058608.30 +167680|9052908.76 +165293|9050545.70 +122148|9046298.17 +138604|9045840.80 +78851|9044822.60 +137280|9042355.34 +8823|9040855.10 +163900|9040848.48 +75600|9035392.45 +81676|9031999.40 +46033|9031460.58 +194917|9028500.00 +133936|9026949.02 +33182|9024971.10 +34220|9021485.39 +20118|9019942.60 +178258|9019881.66 +15560|9017687.28 +111425|9016198.56 +95942|9015585.12 +132709|9015240.15 +39731|9014746.95 +154307|9012571.20 +23769|9008157.60 +93328|9007211.20 +142826|8998297.44 +188792|8996014.00 +68703|8994982.22 +145280|8990941.05 +150725|8985686.16 +172046|8982469.52 +70476|8967629.50 +124988|8966805.22 +17937|8963319.76 +177372|8954873.64 +137994|8950916.79 +84019|8950039.98 +40389|8946158.20 +69187|8941054.14 +4863|8939044.92 +50465|8930503.14 +43686|8915543.84 +131352|8909053.59 +198916|8906940.03 +135932|8905282.95 +104673|8903682.00 +152308|8903244.08 +135298|8900323.20 +156873|8899429.10 +157454|8897339.20 +75415|8897068.09 +46325|8895569.09 +1966|8895117.06 +24576|8895034.75 +19425|8890156.60 +169735|8890085.56 +32225|8889829.28 +124537|8889770.71 +146327|8887836.23 +121562|8887740.40 +44731|8882444.95 +93141|8881850.88 +187871|8873506.18 +71709|8873057.28 +151913|8869321.17 +33786|8868955.39 +35902|8868126.06 +23588|8867769.90 +24508|8867616.00 +161282|8866661.43 +188061|8862304.00 +132847|8862082.00 +166843|8861200.80 +30609|8860214.73 +56191|8856546.96 +160740|8852685.43 +71229|8846106.99 +91208|8845541.28 +10995|8845306.56 +78094|8839938.29 +36489|8838538.10 +198437|8836494.84 +151693|8833807.64 +185367|8829791.37 +65682|8820622.89 +65421|8819329.24 +122225|8816821.86 +85330|8811013.16 +64555|8810643.12 +104188|8808211.02 +54411|8805703.40 +39438|8805282.56 +70795|8800060.92 +20383|8799073.28 +21952|8798624.19 +63584|8796590.00 +158768|8796422.95 +166588|8796214.38 +120600|8793558.06 +157202|8788287.88 +55358|8786820.75 +168322|8786670.73 +25143|8786324.80 +5368|8786274.14 +114025|8786201.12 +97744|8785315.94 +164327|8784503.86 +76542|8782613.28 +4731|8772846.70 +157590|8772006.45 +154276|8771733.91 +28705|8771576.64 +100226|8769455.00 +179195|8769185.16 +184355|8768118.05 +120408|8768011.12 +63145|8761991.96 +53135|8753491.80 +173071|8750508.80 +41087|8749436.79 +194830|8747438.40 +43496|8743359.30 +30235|8741611.00 +26391|8741399.64 +191816|8740258.72 +47616|8737229.68 +152101|8734432.76 +163784|8730514.34 +5134|8728424.64 +155241|8725429.86 +188814|8724182.40 +140782|8720378.75 +153141|8719407.51 +169373|8718609.06 +41335|8714773.80 +197450|8714617.32 +87004|8714017.79 +181804|8712257.76 +122814|8711119.14 +109939|8709193.16 +98094|8708780.04 +74630|8708040.75 +197291|8706519.09 +184173|8705467.45 +192175|8705411.12 +19471|8702536.12 +18052|8702155.70 +135560|8698137.72 +152791|8697325.80 +170953|8696909.19 +116137|8696687.17 +7722|8696589.40 +49788|8694846.71 +13252|8694822.42 +12633|8694559.36 +193438|8690426.72 +17326|8689329.16 +96124|8679794.58 +143802|8676626.48 +30389|8675826.60 +75250|8675257.14 +72613|8673524.94 +123520|8672456.25 +325|8667741.28 +167291|8667556.18 +150119|8663403.54 +88420|8663355.40 +179784|8653021.34 +130884|8651970.00 +172611|8648217.00 +85373|8647796.22 +122717|8646758.54 +113431|8646348.34 +66015|8643349.40 +33141|8643243.18 +69786|8637396.92 +181857|8637393.28 +122939|8636378.00 +196223|8635391.02 +50532|8632648.24 +58102|8632614.54 +93581|8632372.36 +52804|8632109.25 +755|8627091.68 +16597|8623357.05 +119041|8622397.00 +89050|8621185.98 +98696|8620784.82 +94399|8620524.00 +151295|8616671.02 +56417|8613450.35 +121322|8612948.23 +126883|8611373.42 +29155|8610163.64 +114530|8608471.74 +131007|8607394.82 +128715|8606833.62 +72522|8601479.98 +144061|8595718.74 +83503|8595034.20 +112199|8590717.44 +9227|8587350.42 +116318|8585910.66 +41248|8585559.64 +159398|8584821.00 +105966|8582308.79 +137876|8580641.30 +122272|8580400.77 +195717|8577278.10 +165295|8571121.92 +5840|8570728.74 +120860|8570610.44 +66692|8567540.52 +135596|8563276.31 +150576|8562794.10 +7500|8562393.84 +107716|8561541.56 +100611|8559995.85 +171192|8557390.08 +107660|8556696.60 +13461|8556545.12 +90310|8555131.51 +141493|8553782.93 +71286|8552682.00 +136423|8551300.76 +54241|8550785.25 +120325|8549976.60 +424|8547527.10 +196543|8545907.09 +13042|8542717.18 +58332|8536074.69 +9191|8535663.92 +134357|8535429.90 +96207|8534900.60 +92292|8530618.78 +181093|8528303.52 +105064|8527491.60 +59635|8526854.08 +136974|8524351.56 +126694|8522783.37 +6247|8522606.90 +139447|8522521.92 +96313|8520949.92 +108454|8520916.25 +181254|8519496.10 +71117|8519223.00 +131703|8517215.28 +59312|8510568.36 +2903|8509960.35 +102838|8509527.69 +162806|8508906.05 +41527|8508222.36 +118416|8505858.36 +180203|8505024.16 +14773|8500598.28 +140446|8499514.24 +199641|8497362.59 +109240|8494617.12 +150268|8494188.38 +45310|8492380.65 +36552|8490733.60 +199690|8490145.80 +185353|8488726.68 +163615|8484985.01 +196520|8483545.04 +133438|8483482.35 +77285|8481442.32 +55824|8476893.90 +76753|8475522.12 +46129|8472717.96 +28358|8472515.50 +9317|8472145.32 +33823|8469721.44 +39055|8469145.07 +91471|8468874.56 +142299|8466039.55 +97672|8464119.80 +134712|8461781.79 +157988|8460123.20 +102284|8458652.44 +73533|8458453.32 +90599|8457874.86 +112160|8457863.36 +124792|8457633.70 +66097|8457573.15 +165271|8456969.01 +146925|8454887.91 +164277|8454838.50 +131290|8454811.20 +179386|8450909.90 +90486|8447873.86 +175924|8444421.66 +185922|8442394.88 +38492|8436438.32 +172511|8436287.34 +139539|8434180.29 +11926|8433199.52 +55889|8431449.88 +163068|8431116.40 +138772|8428406.36 +126821|8425180.68 +22091|8420687.88 +55981|8419434.38 +100960|8419403.46 +172568|8417955.21 +63135|8415945.53 +137651|8413170.35 +191353|8413039.84 +62988|8411571.48 +103417|8411541.12 +12052|8411519.28 +104260|8408516.55 +157129|8405730.08 +77254|8405537.22 +112966|8403512.89 +168114|8402764.56 +49940|8402328.20 +52017|8398753.60 +176179|8398087.00 +100215|8395906.61 +61256|8392811.20 +15366|8388907.80 +109479|8388027.20 +66202|8386522.83 +81707|8385761.19 +51727|8385426.40 +9980|8382754.62 +174403|8378575.73 +54558|8378041.92 +3141|8377378.22 +134829|8377105.52 +145056|8376920.76 +194020|8375157.64 +7117|8373982.27 +120146|8373796.20 +126843|8370761.28 +62117|8369493.44 +111221|8367525.81 +159337|8366092.26 +173903|8365428.48 +136438|8364065.45 +56684|8363198.00 +137597|8363185.94 +20039|8361138.24 +121326|8359635.52 +48435|8352863.10 +1712|8349107.00 +167190|8347238.70 +32113|8346452.04 +40580|8342983.32 +74785|8342519.13 +14799|8342236.75 +177291|8341736.83 +198956|8340370.65 +69179|8338465.99 +118764|8337616.56 +128814|8336435.56 +82729|8331766.88 +152048|8330638.99 +171085|8326259.50 +126730|8325974.40 +77525|8323282.50 +170653|8322840.50 +5257|8320350.78 +67350|8318987.56 +109008|8317836.54 +199043|8316603.54 +139969|8316551.54 +22634|8316531.24 +173309|8315750.25 +10887|8315019.36 +42392|8312895.96 +126040|8312623.20 +101590|8304555.42 +46891|8302192.12 +138721|8301745.62 +113715|8301533.20 +78778|8299685.64 +142908|8299447.77 +64419|8297631.80 +21396|8296272.27 +4180|8295646.92 +63534|8295383.67 +135957|8294389.86 +30126|8291920.32 +158427|8288938.00 +14545|8288395.92 +75548|8288287.20 +64473|8286137.44 +149553|8285714.88 +151284|8283526.65 +171091|8282934.36 +194256|8278985.34 +952|8276136.00 +121541|8275390.26 +177664|8275315.20 +51117|8274504.30 +66770|8273407.80 +37238|8272728.06 +46679|8270486.55 +165852|8268312.60 +99458|8266564.47 +114519|8265493.54 +7231|8264881.50 +19033|8264826.56 +125123|8262732.65 +18642|8261578.99 +50386|8261380.05 +193770|8259578.82 +7276|8258101.60 +178045|8253904.15 +49033|8253696.23 +187195|8251334.58 +10590|8249227.40 +143779|8247057.70 +35205|8245675.17 +19729|8245081.60 +144946|8240479.80 +123786|8239581.24 +70843|8237973.20 +112437|8236907.52 +5436|8236039.57 +163754|8235471.16 +115945|8234811.36 +27918|8233957.88 +105712|8233571.86 +41007|8229431.79 +40476|8226640.41 +145620|8221371.60 +7771|8220413.33 +86424|8215572.61 +129137|8215478.40 +76020|8210495.36 +140213|8209831.80 +32379|8208338.88 +130616|8207715.75 +195469|8206609.80 +191805|8205147.75 +90906|8200951.20 +170910|8195558.01 +105399|8193122.63 +123798|8192385.97 +90218|8191689.16 +114766|8189339.54 +11289|8187354.72 +178308|8185750.50 +71271|8185519.24 +1115|8184903.38 +152636|8184530.72 +151619|8182909.05 +116943|8181072.69 +28891|8181051.54 +47049|8180955.00 +158827|8180470.90 +92620|8179671.55 +20814|8176953.54 +179323|8176795.55 +193453|8174343.94 +56888|8173342.00 +28087|8169876.30 +164254|8169632.35 +57661|8168848.16 +7363|8167538.05 +164499|8167512.08 +197557|8165940.45 +5495|8164805.22 +966|8163824.79 +98435|8161771.45 +127227|8161344.92 +194100|8160978.78 +40134|8160358.08 +107341|8159952.05 +6790|8158792.66 +43851|8157101.40 +51295|8156419.20 +69512|8151537.00 +164274|8149869.93 +130854|8145338.85 +186865|8143586.82 +176629|8141411.20 +193739|8141377.77 +6810|8139822.60 +27732|8136724.96 +50616|8134089.82 +123908|8128920.54 +140994|8128470.82 +99039|8128290.78 +62735|8124940.50 +47829|8122796.50 +192635|8122687.57 +192429|8119268.00 +145812|8119165.63 +42896|8118529.80 +146877|8118266.16 +60882|8116095.04 +18254|8114783.04 +165464|8114571.80 +57936|8111927.25 +52226|8110723.32 +128571|8106788.80 +100308|8105837.04 +8872|8102395.62 +58867|8102033.19 +145153|8100222.84 +172088|8098138.20 +59398|8095845.45 +89395|8093576.10 +171961|8093538.00 +88736|8090762.16 +174053|8090350.11 +102237|8089103.22 +43041|8086537.90 +110219|8085296.90 +126738|8084199.20 +44787|8083628.40 +31277|8083580.76 +93595|8082188.80 +189040|8080257.21 +59851|8079024.24 +175100|8077904.01 +43429|8076729.96 +154199|8074940.76 +60963|8073894.40 +8768|8072760.96 +66095|8071421.70 +111552|8068184.48 +24563|8067500.40 +16167|8067495.24 +12662|8067248.85 +94540|8063727.16 +23308|8063463.18 +27390|8062823.25 +130660|8062787.48 +8608|8062411.16 +181552|8062008.30 +199319|8060248.56 +55475|8058850.92 +142711|8057926.58 +103499|8056978.00 +105943|8056698.75 +8432|8053052.16 +149392|8049675.69 +101248|8048855.49 +140962|8047260.70 +87101|8046651.83 +133107|8046476.73 +45126|8045924.40 +87508|8042966.39 +124711|8042722.72 +173169|8042224.41 +175161|8041331.98 +167787|8040075.78 +3242|8038855.53 +114789|8038628.35 +43833|8038545.83 +141198|8035110.72 +137248|8034109.35 +96673|8033491.20 +32180|8032380.72 +166493|8031902.40 +66959|8031839.40 +85628|8029693.44 +110971|8029469.70 +130395|8027463.92 +7757|8026840.37 +178446|8025379.09 +41295|8024785.53 +100956|8024179.30 +131917|8021604.78 +24224|8020463.52 +2073|8020009.64 +121622|8018462.17 +14357|8016906.30 +135601|8016209.44 +58458|8016192.52 +73036|8015799.00 +184722|8015680.31 +151664|8014821.96 +195090|8012680.20 +162609|8011241.00 +83532|8009753.85 +50166|8007137.89 +181562|8006805.96 +175165|8005319.76 +62500|8005316.28 +36342|8004333.40 +128435|8004242.88 +92516|8003836.80 +30802|8003710.88 +107418|8000430.30 +46620|7999778.35 +191803|7994734.15 +106343|7993087.76 +59362|7990397.46 +8329|7990052.90 +75133|7988244.00 +179023|7986829.62 +135899|7985726.64 +5824|7985340.02 +148579|7984889.56 +95888|7984735.72 +9791|7982699.79 +170437|7982370.72 +39782|7977858.24 +20605|7977556.00 +28682|7976960.00 +42172|7973399.00 +56137|7971405.40 +64729|7970769.72 +98643|7968603.73 +153787|7967535.58 +8932|7967222.19 +20134|7965713.28 +197635|7963507.58 +80408|7963312.17 +37728|7961875.68 +26624|7961772.31 +44736|7961144.10 +29763|7960605.03 +36147|7959463.68 +146040|7957587.66 +115469|7957485.14 +142276|7956790.63 +181280|7954037.35 +115096|7953047.55 +109650|7952258.73 +93862|7951992.24 +158325|7950728.30 +55952|7950387.06 +122397|7947106.27 +28114|7946945.72 +11966|7945197.48 +47814|7944083.00 +85096|7943691.06 +51657|7943593.77 +196680|7943578.89 +13141|7942730.34 +193327|7941036.25 +152612|7940663.71 +139680|7939242.36 +31134|7938318.30 +45636|7937240.85 +56694|7936015.95 +8114|7933921.88 +71518|7932261.69 +72922|7930400.64 +146699|7929167.40 +92387|7928972.67 +186289|7928786.19 +95952|7927972.78 +196514|7927180.70 +4403|7925729.04 +2267|7925649.37 +45924|7925047.68 +11493|7916722.23 +104478|7916253.60 +166794|7913842.00 +161995|7910874.27 +23538|7909752.06 +41093|7909579.92 +112073|7908617.57 +92814|7908262.50 +88919|7907992.50 +79753|7907933.88 +108765|7905338.98 +146530|7905336.60 +71475|7903367.58 +36289|7901946.50 +61739|7900794.00 +52338|7898638.08 +194299|7898421.24 +105235|7897829.94 +77207|7897752.72 +96712|7897575.27 +10157|7897046.25 +171154|7896814.50 +79373|7896186.00 +113808|7893353.88 +27901|7892952.00 +128820|7892882.72 +25891|7890511.20 +122819|7888881.02 +154731|7888301.33 +101674|7879324.60 +51968|7879102.21 +72073|7877736.11 +5182|7874521.73 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q12.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q12.csv new file mode 100644 index 000000000..172403526 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q12.csv @@ -0,0 +1,3 @@ +l_shipmode|high_line_count|low_line_count +MAIL|6202|9324 +SHIP|6200|9262 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q13.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q13.csv new file mode 100644 index 000000000..4b037d50a --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q13.csv @@ -0,0 +1,43 @@ +c_count|custdist +0|50004 +10|6668 +9|6563 +11|6004 +8|5890 +12|5600 +13|5029 +19|4805 +7|4680 +18|4531 +20|4507 +14|4473 +15|4463 +17|4445 +16|4410 +21|4168 +22|3742 +6|3273 +23|3189 +24|2700 +25|2090 +5|1957 +26|1653 +27|1177 +4|1010 +28|901 +29|564 +3|408 +30|378 +31|242 +32|133 +2|128 +33|72 +34|52 +35|32 +36|20 +1|20 +37|8 +38|4 +41|3 +40|3 +39|1 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q14.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q14.csv new file mode 100644 index 000000000..6f74943a0 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q14.csv @@ -0,0 +1,2 @@ +promo_revenue +16.3807786263956 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q15.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q15.csv new file mode 100644 index 000000000..de736e34d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q15.csv @@ -0,0 +1,2 @@ +s_suppkey|s_name|s_address|s_phone|total_revenue +8449|Supplier#000008449|5BXWsJERA2mP5OyO4|20-469-856-8873|1772627.2087 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q16.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q16.csv new file mode 100644 index 000000000..56338f2b2 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q16.csv @@ -0,0 +1,18315 @@ +p_brand|p_type|p_size|supplier_cnt +Brand#41|MEDIUM BRUSHED TIN|3|28 +Brand#54|STANDARD BRUSHED COPPER|14|27 +Brand#11|STANDARD BRUSHED TIN|23|24 +Brand#11|STANDARD BURNISHED BRASS|36|24 +Brand#15|MEDIUM ANODIZED NICKEL|3|24 +Brand#15|SMALL ANODIZED BRASS|45|24 +Brand#15|SMALL BURNISHED NICKEL|19|24 +Brand#21|MEDIUM ANODIZED COPPER|3|24 +Brand#22|SMALL BRUSHED NICKEL|3|24 +Brand#22|SMALL BURNISHED BRASS|19|24 +Brand#25|MEDIUM BURNISHED COPPER|36|24 +Brand#31|PROMO POLISHED COPPER|36|24 +Brand#33|LARGE POLISHED TIN|23|24 +Brand#33|PROMO POLISHED STEEL|14|24 +Brand#35|PROMO BRUSHED NICKEL|14|24 +Brand#41|ECONOMY BRUSHED STEEL|9|24 +Brand#41|ECONOMY POLISHED TIN|19|24 +Brand#41|LARGE PLATED COPPER|36|24 +Brand#42|ECONOMY PLATED BRASS|3|24 +Brand#42|STANDARD POLISHED TIN|49|24 +Brand#43|PROMO BRUSHED TIN|3|24 +Brand#43|SMALL ANODIZED COPPER|36|24 +Brand#44|STANDARD POLISHED NICKEL|3|24 +Brand#52|ECONOMY PLATED TIN|14|24 +Brand#52|STANDARD BURNISHED NICKEL|3|24 +Brand#53|MEDIUM ANODIZED STEEL|14|24 +Brand#14|PROMO ANODIZED NICKEL|45|23 +Brand#32|ECONOMY PLATED BRASS|9|23 +Brand#52|SMALL ANODIZED COPPER|3|23 +Brand#11|ECONOMY BRUSHED COPPER|45|20 +Brand#11|ECONOMY PLATED BRASS|23|20 +Brand#11|LARGE BRUSHED COPPER|49|20 +Brand#11|LARGE POLISHED COPPER|49|20 +Brand#12|STANDARD ANODIZED TIN|49|20 +Brand#12|STANDARD PLATED BRASS|19|20 +Brand#13|ECONOMY BRUSHED BRASS|9|20 +Brand#13|ECONOMY BURNISHED STEEL|14|20 +Brand#13|LARGE BURNISHED NICKEL|19|20 +Brand#13|MEDIUM BURNISHED COPPER|36|20 +Brand#13|SMALL BRUSHED TIN|45|20 +Brand#13|STANDARD ANODIZED COPPER|3|20 +Brand#13|STANDARD PLATED NICKEL|23|20 +Brand#14|ECONOMY ANODIZED COPPER|14|20 +Brand#14|ECONOMY PLATED TIN|36|20 +Brand#14|ECONOMY POLISHED NICKEL|3|20 +Brand#14|MEDIUM ANODIZED NICKEL|3|20 +Brand#14|SMALL POLISHED TIN|14|20 +Brand#15|MEDIUM ANODIZED COPPER|9|20 +Brand#15|MEDIUM PLATED TIN|23|20 +Brand#15|PROMO PLATED BRASS|14|20 +Brand#15|SMALL ANODIZED COPPER|45|20 +Brand#15|SMALL PLATED COPPER|49|20 +Brand#15|STANDARD PLATED TIN|3|20 +Brand#21|LARGE ANODIZED COPPER|36|20 +Brand#21|LARGE BRUSHED TIN|3|20 +Brand#21|MEDIUM ANODIZED COPPER|14|20 +Brand#21|PROMO BRUSHED TIN|36|20 +Brand#21|PROMO POLISHED NICKEL|45|20 +Brand#21|SMALL ANODIZED COPPER|9|20 +Brand#21|SMALL POLISHED NICKEL|23|20 +Brand#22|LARGE ANODIZED COPPER|36|20 +Brand#22|LARGE BRUSHED COPPER|49|20 +Brand#22|PROMO ANODIZED TIN|49|20 +Brand#22|PROMO POLISHED BRASS|45|20 +Brand#22|SMALL BURNISHED STEEL|45|20 +Brand#23|MEDIUM ANODIZED STEEL|45|20 +Brand#23|PROMO POLISHED STEEL|23|20 +Brand#23|STANDARD BRUSHED TIN|14|20 +Brand#23|STANDARD PLATED NICKEL|36|20 +Brand#24|PROMO PLATED COPPER|49|20 +Brand#24|PROMO PLATED STEEL|49|20 +Brand#24|PROMO POLISHED STEEL|9|20 +Brand#24|STANDARD BRUSHED TIN|36|20 +Brand#25|LARGE ANODIZED BRASS|3|20 +Brand#25|PROMO BURNISHED TIN|3|20 +Brand#31|ECONOMY POLISHED NICKEL|3|20 +Brand#31|MEDIUM PLATED TIN|45|20 +Brand#31|SMALL ANODIZED STEEL|14|20 +Brand#32|ECONOMY ANODIZED COPPER|36|20 +Brand#32|ECONOMY BRUSHED NICKEL|49|20 +Brand#32|LARGE ANODIZED TIN|19|20 +Brand#32|MEDIUM BURNISHED COPPER|19|20 +Brand#32|SMALL ANODIZED STEEL|45|20 +Brand#33|ECONOMY POLISHED COPPER|19|20 +Brand#33|PROMO PLATED NICKEL|14|20 +Brand#33|SMALL POLISHED TIN|9|20 +Brand#33|STANDARD ANODIZED BRASS|49|20 +Brand#33|STANDARD BURNISHED BRASS|45|20 +Brand#34|ECONOMY BRUSHED NICKEL|49|20 +Brand#34|LARGE BRUSHED BRASS|19|20 +Brand#34|SMALL BRUSHED TIN|3|20 +Brand#34|STANDARD PLATED COPPER|9|20 +Brand#35|LARGE ANODIZED NICKEL|3|20 +Brand#35|MEDIUM ANODIZED BRASS|45|20 +Brand#35|MEDIUM ANODIZED STEEL|23|20 +Brand#35|PROMO ANODIZED COPPER|49|20 +Brand#35|SMALL POLISHED COPPER|14|20 +Brand#41|LARGE ANODIZED STEEL|3|20 +Brand#41|LARGE BRUSHED NICKEL|23|20 +Brand#41|LARGE BURNISHED COPPER|3|20 +Brand#41|MEDIUM PLATED STEEL|19|20 +Brand#41|SMALL BURNISHED COPPER|23|20 +Brand#42|MEDIUM BURNISHED BRASS|14|20 +Brand#42|SMALL BURNISHED COPPER|3|20 +Brand#43|ECONOMY POLISHED COPPER|9|20 +Brand#43|SMALL PLATED STEEL|3|20 +Brand#43|STANDARD BURNISHED TIN|23|20 +Brand#44|LARGE ANODIZED STEEL|23|20 +Brand#44|PROMO ANODIZED TIN|23|20 +Brand#51|ECONOMY BRUSHED BRASS|49|20 +Brand#51|ECONOMY POLISHED NICKEL|9|20 +Brand#51|MEDIUM BRUSHED TIN|9|20 +Brand#51|MEDIUM PLATED BRASS|9|20 +Brand#51|PROMO BURNISHED BRASS|9|20 +Brand#51|SMALL PLATED NICKEL|49|20 +Brand#51|STANDARD ANODIZED NICKEL|49|20 +Brand#51|STANDARD BRUSHED COPPER|3|20 +Brand#52|ECONOMY ANODIZED BRASS|3|20 +Brand#52|ECONOMY BRUSHED COPPER|49|20 +Brand#52|LARGE ANODIZED NICKEL|45|20 +Brand#52|MEDIUM ANODIZED TIN|23|20 +Brand#52|MEDIUM BURNISHED TIN|45|20 +Brand#52|SMALL PLATED COPPER|36|20 +Brand#52|STANDARD ANODIZED BRASS|45|20 +Brand#53|ECONOMY PLATED COPPER|45|20 +Brand#53|PROMO ANODIZED COPPER|49|20 +Brand#53|PROMO BRUSHED COPPER|23|20 +Brand#53|PROMO PLATED TIN|19|20 +Brand#53|PROMO POLISHED NICKEL|3|20 +Brand#53|SMALL ANODIZED STEEL|9|20 +Brand#53|SMALL BRUSHED COPPER|3|20 +Brand#53|SMALL BRUSHED NICKEL|3|20 +Brand#54|ECONOMY PLATED STEEL|9|20 +Brand#54|ECONOMY POLISHED TIN|3|20 +Brand#54|SMALL BRUSHED BRASS|19|20 +Brand#55|MEDIUM ANODIZED COPPER|3|20 +Brand#55|PROMO BURNISHED STEEL|14|20 +Brand#55|PROMO POLISHED NICKEL|49|20 +Brand#55|STANDARD ANODIZED BRASS|19|20 +Brand#55|STANDARD BURNISHED COPPER|45|20 +Brand#43|ECONOMY ANODIZED TIN|3|19 +Brand#11|ECONOMY ANODIZED BRASS|14|16 +Brand#11|ECONOMY ANODIZED BRASS|23|16 +Brand#11|ECONOMY ANODIZED COPPER|14|16 +Brand#11|ECONOMY BRUSHED BRASS|49|16 +Brand#11|ECONOMY BRUSHED STEEL|19|16 +Brand#11|ECONOMY BURNISHED NICKEL|23|16 +Brand#11|LARGE ANODIZED COPPER|14|16 +Brand#11|LARGE BRUSHED TIN|45|16 +Brand#11|LARGE BURNISHED COPPER|23|16 +Brand#11|LARGE BURNISHED NICKEL|36|16 +Brand#11|LARGE PLATED STEEL|14|16 +Brand#11|MEDIUM BRUSHED NICKEL|14|16 +Brand#11|MEDIUM BRUSHED STEEL|49|16 +Brand#11|MEDIUM BURNISHED NICKEL|49|16 +Brand#11|MEDIUM BURNISHED TIN|3|16 +Brand#11|MEDIUM PLATED COPPER|9|16 +Brand#11|PROMO ANODIZED BRASS|19|16 +Brand#11|PROMO ANODIZED BRASS|49|16 +Brand#11|PROMO ANODIZED STEEL|45|16 +Brand#11|PROMO PLATED BRASS|45|16 +Brand#11|SMALL ANODIZED TIN|45|16 +Brand#11|SMALL BRUSHED STEEL|49|16 +Brand#11|SMALL BURNISHED COPPER|19|16 +Brand#11|SMALL BURNISHED COPPER|45|16 +Brand#11|SMALL BURNISHED NICKEL|14|16 +Brand#11|SMALL POLISHED NICKEL|36|16 +Brand#11|STANDARD ANODIZED BRASS|19|16 +Brand#11|STANDARD ANODIZED COPPER|14|16 +Brand#11|STANDARD BRUSHED STEEL|45|16 +Brand#11|STANDARD POLISHED NICKEL|23|16 +Brand#12|ECONOMY ANODIZED TIN|14|16 +Brand#12|ECONOMY BRUSHED COPPER|9|16 +Brand#12|ECONOMY BRUSHED COPPER|36|16 +Brand#12|ECONOMY BURNISHED BRASS|9|16 +Brand#12|ECONOMY BURNISHED NICKEL|36|16 +Brand#12|LARGE ANODIZED BRASS|14|16 +Brand#12|LARGE ANODIZED COPPER|9|16 +Brand#12|LARGE ANODIZED STEEL|23|16 +Brand#12|LARGE BURNISHED TIN|36|16 +Brand#12|LARGE PLATED COPPER|49|16 +Brand#12|LARGE POLISHED COPPER|49|16 +Brand#12|MEDIUM PLATED COPPER|19|16 +Brand#12|MEDIUM PLATED NICKEL|23|16 +Brand#12|PROMO ANODIZED BRASS|45|16 +Brand#12|PROMO ANODIZED STEEL|49|16 +Brand#12|PROMO BURNISHED STEEL|9|16 +Brand#12|SMALL BRUSHED NICKEL|36|16 +Brand#12|SMALL BRUSHED TIN|45|16 +Brand#12|STANDARD ANODIZED BRASS|3|16 +Brand#12|STANDARD ANODIZED NICKEL|14|16 +Brand#12|STANDARD BRUSHED BRASS|3|16 +Brand#12|STANDARD BRUSHED TIN|9|16 +Brand#12|STANDARD BRUSHED TIN|36|16 +Brand#12|STANDARD POLISHED COPPER|9|16 +Brand#13|ECONOMY ANODIZED STEEL|45|16 +Brand#13|ECONOMY POLISHED BRASS|3|16 +Brand#13|LARGE BRUSHED NICKEL|23|16 +Brand#13|LARGE BURNISHED NICKEL|9|16 +Brand#13|MEDIUM BRUSHED STEEL|49|16 +Brand#13|MEDIUM BURNISHED NICKEL|49|16 +Brand#13|MEDIUM PLATED BRASS|49|16 +Brand#13|PROMO ANODIZED BRASS|14|16 +Brand#13|PROMO ANODIZED COPPER|3|16 +Brand#13|SMALL ANODIZED STEEL|45|16 +Brand#13|SMALL BURNISHED STEEL|19|16 +Brand#13|SMALL PLATED BRASS|36|16 +Brand#13|STANDARD ANODIZED BRASS|23|16 +Brand#13|STANDARD ANODIZED STEEL|23|16 +Brand#13|STANDARD BURNISHED BRASS|9|16 +Brand#13|STANDARD PLATED NICKEL|9|16 +Brand#13|STANDARD PLATED TIN|23|16 +Brand#14|ECONOMY BRUSHED STEEL|3|16 +Brand#14|ECONOMY PLATED NICKEL|9|16 +Brand#14|ECONOMY PLATED STEEL|9|16 +Brand#14|ECONOMY POLISHED NICKEL|19|16 +Brand#14|LARGE ANODIZED COPPER|14|16 +Brand#14|LARGE BRUSHED NICKEL|19|16 +Brand#14|LARGE POLISHED STEEL|3|16 +Brand#14|LARGE POLISHED TIN|23|16 +Brand#14|MEDIUM BURNISHED COPPER|3|16 +Brand#14|PROMO ANODIZED STEEL|36|16 +Brand#14|PROMO PLATED BRASS|9|16 +Brand#14|PROMO PLATED NICKEL|49|16 +Brand#14|PROMO POLISHED BRASS|19|16 +Brand#14|PROMO POLISHED STEEL|19|16 +Brand#14|PROMO POLISHED TIN|45|16 +Brand#14|SMALL BRUSHED BRASS|14|16 +Brand#14|SMALL BURNISHED COPPER|45|16 +Brand#14|STANDARD BRUSHED TIN|19|16 +Brand#14|STANDARD PLATED COPPER|45|16 +Brand#14|STANDARD PLATED TIN|9|16 +Brand#14|STANDARD POLISHED TIN|49|16 +Brand#15|ECONOMY BRUSHED STEEL|19|16 +Brand#15|LARGE BRUSHED BRASS|14|16 +Brand#15|LARGE BRUSHED STEEL|14|16 +Brand#15|LARGE BURNISHED NICKEL|3|16 +Brand#15|LARGE PLATED COPPER|49|16 +Brand#15|PROMO ANODIZED NICKEL|3|16 +Brand#15|PROMO BURNISHED TIN|49|16 +Brand#15|PROMO PLATED STEEL|3|16 +Brand#15|PROMO POLISHED STEEL|49|16 +Brand#15|SMALL BRUSHED COPPER|9|16 +Brand#15|SMALL BRUSHED NICKEL|23|16 +Brand#15|SMALL PLATED BRASS|49|16 +Brand#15|STANDARD ANODIZED COPPER|45|16 +Brand#15|STANDARD BRUSHED COPPER|14|16 +Brand#15|STANDARD PLATED TIN|36|16 +Brand#21|ECONOMY ANODIZED STEEL|45|16 +Brand#21|ECONOMY BRUSHED COPPER|9|16 +Brand#21|ECONOMY POLISHED STEEL|19|16 +Brand#21|LARGE ANODIZED STEEL|14|16 +Brand#21|MEDIUM ANODIZED STEEL|36|16 +Brand#21|PROMO POLISHED BRASS|14|16 +Brand#21|PROMO POLISHED TIN|49|16 +Brand#21|SMALL BRUSHED COPPER|3|16 +Brand#21|SMALL PLATED STEEL|45|16 +Brand#21|SMALL PLATED TIN|45|16 +Brand#21|STANDARD POLISHED STEEL|36|16 +Brand#22|ECONOMY BRUSHED BRASS|9|16 +Brand#22|ECONOMY BRUSHED NICKEL|36|16 +Brand#22|ECONOMY POLISHED TIN|36|16 +Brand#22|LARGE BRUSHED COPPER|19|16 +Brand#22|LARGE BRUSHED TIN|36|16 +Brand#22|LARGE POLISHED COPPER|19|16 +Brand#22|MEDIUM ANODIZED BRASS|23|16 +Brand#22|MEDIUM ANODIZED NICKEL|9|16 +Brand#22|MEDIUM BRUSHED NICKEL|14|16 +Brand#22|MEDIUM PLATED NICKEL|23|16 +Brand#22|PROMO ANODIZED TIN|45|16 +Brand#22|PROMO POLISHED STEEL|49|16 +Brand#22|SMALL BRUSHED NICKEL|45|16 +Brand#22|SMALL POLISHED BRASS|36|16 +Brand#22|SMALL POLISHED STEEL|9|16 +Brand#22|STANDARD BURNISHED BRASS|45|16 +Brand#22|STANDARD BURNISHED NICKEL|3|16 +Brand#22|STANDARD PLATED BRASS|9|16 +Brand#23|ECONOMY BRUSHED TIN|49|16 +Brand#23|ECONOMY BURNISHED COPPER|45|16 +Brand#23|ECONOMY BURNISHED NICKEL|19|16 +Brand#23|ECONOMY BURNISHED TIN|9|16 +Brand#23|ECONOMY PLATED BRASS|9|16 +Brand#23|ECONOMY PLATED COPPER|14|16 +Brand#23|LARGE ANODIZED STEEL|23|16 +Brand#23|LARGE ANODIZED STEEL|49|16 +Brand#23|LARGE BURNISHED COPPER|23|16 +Brand#23|LARGE POLISHED NICKEL|9|16 +Brand#23|MEDIUM BRUSHED STEEL|3|16 +Brand#23|PROMO ANODIZED COPPER|19|16 +Brand#23|PROMO ANODIZED TIN|3|16 +Brand#23|PROMO BURNISHED COPPER|14|16 +Brand#23|PROMO PLATED BRASS|3|16 +Brand#23|SMALL ANODIZED BRASS|23|16 +Brand#23|SMALL BRUSHED BRASS|45|16 +Brand#23|SMALL POLISHED TIN|3|16 +Brand#23|STANDARD BURNISHED COPPER|19|16 +Brand#23|STANDARD BURNISHED NICKEL|49|16 +Brand#23|STANDARD PLATED BRASS|9|16 +Brand#23|STANDARD PLATED COPPER|45|16 +Brand#23|STANDARD POLISHED BRASS|9|16 +Brand#24|ECONOMY ANODIZED BRASS|3|16 +Brand#24|ECONOMY BRUSHED COPPER|36|16 +Brand#24|ECONOMY BRUSHED STEEL|14|16 +Brand#24|ECONOMY POLISHED COPPER|36|16 +Brand#24|ECONOMY POLISHED NICKEL|3|16 +Brand#24|LARGE ANODIZED BRASS|23|16 +Brand#24|LARGE BURNISHED BRASS|45|16 +Brand#24|LARGE BURNISHED STEEL|14|16 +Brand#24|LARGE PLATED TIN|9|16 +Brand#24|MEDIUM BRUSHED NICKEL|49|16 +Brand#24|MEDIUM BURNISHED STEEL|3|16 +Brand#24|PROMO BURNISHED COPPER|49|16 +Brand#24|PROMO BURNISHED STEEL|49|16 +Brand#24|PROMO POLISHED STEEL|23|16 +Brand#24|SMALL ANODIZED NICKEL|19|16 +Brand#24|STANDARD BURNISHED COPPER|19|16 +Brand#24|STANDARD BURNISHED STEEL|36|16 +Brand#24|STANDARD PLATED NICKEL|23|16 +Brand#24|STANDARD PLATED TIN|49|16 +Brand#25|ECONOMY ANODIZED COPPER|14|16 +Brand#25|ECONOMY BURNISHED NICKEL|9|16 +Brand#25|ECONOMY PLATED TIN|14|16 +Brand#25|ECONOMY POLISHED TIN|45|16 +Brand#25|LARGE ANODIZED STEEL|9|16 +Brand#25|LARGE ANODIZED TIN|45|16 +Brand#25|LARGE BRUSHED NICKEL|36|16 +Brand#25|LARGE BURNISHED NICKEL|14|16 +Brand#25|LARGE POLISHED STEEL|19|16 +Brand#25|MEDIUM BRUSHED COPPER|9|16 +Brand#25|MEDIUM BURNISHED COPPER|49|16 +Brand#25|MEDIUM BURNISHED TIN|3|16 +Brand#25|MEDIUM PLATED STEEL|9|16 +Brand#25|PROMO ANODIZED BRASS|49|16 +Brand#25|PROMO ANODIZED STEEL|19|16 +Brand#25|PROMO ANODIZED TIN|23|16 +Brand#25|PROMO BURNISHED COPPER|49|16 +Brand#25|PROMO POLISHED COPPER|14|16 +Brand#25|SMALL ANODIZED COPPER|23|16 +Brand#25|SMALL BRUSHED STEEL|23|16 +Brand#25|SMALL POLISHED COPPER|23|16 +Brand#25|STANDARD BURNISHED STEEL|23|16 +Brand#25|STANDARD BURNISHED TIN|3|16 +Brand#25|STANDARD BURNISHED TIN|36|16 +Brand#25|STANDARD PLATED BRASS|45|16 +Brand#25|STANDARD PLATED COPPER|49|16 +Brand#31|ECONOMY ANODIZED BRASS|45|16 +Brand#31|ECONOMY BRUSHED COPPER|14|16 +Brand#31|ECONOMY BRUSHED COPPER|36|16 +Brand#31|LARGE ANODIZED STEEL|45|16 +Brand#31|LARGE BURNISHED NICKEL|45|16 +Brand#31|LARGE PLATED TIN|14|16 +Brand#31|LARGE POLISHED COPPER|49|16 +Brand#31|MEDIUM ANODIZED NICKEL|49|16 +Brand#31|MEDIUM BURNISHED BRASS|19|16 +Brand#31|PROMO ANODIZED NICKEL|14|16 +Brand#31|PROMO BRUSHED TIN|45|16 +Brand#31|PROMO BURNISHED STEEL|36|16 +Brand#31|SMALL ANODIZED NICKEL|23|16 +Brand#31|SMALL BRUSHED NICKEL|14|16 +Brand#31|SMALL BRUSHED TIN|19|16 +Brand#31|SMALL PLATED NICKEL|23|16 +Brand#31|SMALL POLISHED BRASS|23|16 +Brand#31|SMALL POLISHED TIN|14|16 +Brand#31|SMALL POLISHED TIN|45|16 +Brand#31|STANDARD BRUSHED COPPER|45|16 +Brand#31|STANDARD POLISHED STEEL|36|16 +Brand#32|ECONOMY BRUSHED STEEL|9|16 +Brand#32|ECONOMY PLATED STEEL|14|16 +Brand#32|LARGE ANODIZED BRASS|36|16 +Brand#32|LARGE BURNISHED NICKEL|36|16 +Brand#32|LARGE PLATED BRASS|36|16 +Brand#32|LARGE PLATED STEEL|23|16 +Brand#32|MEDIUM BRUSHED BRASS|49|16 +Brand#32|MEDIUM BRUSHED TIN|9|16 +Brand#32|MEDIUM PLATED COPPER|36|16 +Brand#32|PROMO ANODIZED TIN|36|16 +Brand#32|PROMO BRUSHED BRASS|9|16 +Brand#32|PROMO BURNISHED STEEL|36|16 +Brand#32|PROMO PLATED STEEL|3|16 +Brand#32|PROMO PLATED TIN|45|16 +Brand#32|SMALL BURNISHED TIN|49|16 +Brand#32|SMALL PLATED NICKEL|36|16 +Brand#32|SMALL POLISHED NICKEL|36|16 +Brand#32|SMALL POLISHED STEEL|9|16 +Brand#32|SMALL POLISHED TIN|36|16 +Brand#32|STANDARD ANODIZED COPPER|14|16 +Brand#32|STANDARD ANODIZED TIN|9|16 +Brand#32|STANDARD BURNISHED COPPER|45|16 +Brand#32|STANDARD BURNISHED COPPER|49|16 +Brand#32|STANDARD POLISHED BRASS|14|16 +Brand#32|STANDARD POLISHED STEEL|14|16 +Brand#33|ECONOMY ANODIZED STEEL|49|16 +Brand#33|ECONOMY PLATED BRASS|36|16 +Brand#33|ECONOMY PLATED COPPER|19|16 +Brand#33|ECONOMY POLISHED NICKEL|19|16 +Brand#33|LARGE ANODIZED STEEL|45|16 +Brand#33|LARGE ANODIZED TIN|45|16 +Brand#33|LARGE BURNISHED COPPER|45|16 +Brand#33|LARGE POLISHED STEEL|3|16 +Brand#33|MEDIUM ANODIZED BRASS|23|16 +Brand#33|MEDIUM ANODIZED NICKEL|3|16 +Brand#33|MEDIUM ANODIZED TIN|14|16 +Brand#33|MEDIUM BRUSHED COPPER|49|16 +Brand#33|MEDIUM BURNISHED COPPER|9|16 +Brand#33|PROMO BURNISHED BRASS|9|16 +Brand#33|PROMO BURNISHED BRASS|19|16 +Brand#33|PROMO PLATED STEEL|49|16 +Brand#33|SMALL ANODIZED BRASS|36|16 +Brand#33|SMALL BRUSHED BRASS|3|16 +Brand#33|SMALL BRUSHED STEEL|9|16 +Brand#33|SMALL POLISHED BRASS|14|16 +Brand#33|SMALL POLISHED COPPER|36|16 +Brand#33|SMALL POLISHED NICKEL|19|16 +Brand#33|STANDARD ANODIZED BRASS|9|16 +Brand#33|STANDARD ANODIZED TIN|3|16 +Brand#33|STANDARD BURNISHED NICKEL|49|16 +Brand#33|STANDARD PLATED NICKEL|49|16 +Brand#33|STANDARD POLISHED BRASS|9|16 +Brand#33|STANDARD POLISHED BRASS|14|16 +Brand#33|STANDARD POLISHED COPPER|49|16 +Brand#33|STANDARD POLISHED STEEL|3|16 +Brand#34|ECONOMY BURNISHED BRASS|14|16 +Brand#34|ECONOMY POLISHED STEEL|36|16 +Brand#34|LARGE BRUSHED BRASS|23|16 +Brand#34|LARGE PLATED BRASS|36|16 +Brand#34|LARGE PLATED TIN|3|16 +Brand#34|LARGE POLISHED COPPER|14|16 +Brand#34|MEDIUM ANODIZED COPPER|36|16 +Brand#34|MEDIUM BRUSHED STEEL|23|16 +Brand#34|MEDIUM PLATED NICKEL|23|16 +Brand#34|PROMO BRUSHED NICKEL|45|16 +Brand#34|PROMO POLISHED TIN|3|16 +Brand#34|SMALL ANODIZED NICKEL|14|16 +Brand#34|SMALL BURNISHED TIN|3|16 +Brand#34|SMALL POLISHED NICKEL|36|16 +Brand#34|STANDARD ANODIZED STEEL|9|16 +Brand#34|STANDARD BURNISHED NICKEL|19|16 +Brand#34|STANDARD BURNISHED NICKEL|23|16 +Brand#34|STANDARD POLISHED COPPER|23|16 +Brand#35|ECONOMY ANODIZED COPPER|36|16 +Brand#35|ECONOMY BURNISHED NICKEL|19|16 +Brand#35|ECONOMY BURNISHED TIN|9|16 +Brand#35|ECONOMY PLATED STEEL|14|16 +Brand#35|LARGE ANODIZED BRASS|9|16 +Brand#35|LARGE ANODIZED COPPER|49|16 +Brand#35|LARGE ANODIZED NICKEL|9|16 +Brand#35|LARGE BRUSHED TIN|49|16 +Brand#35|LARGE BURNISHED COPPER|23|16 +Brand#35|LARGE BURNISHED NICKEL|9|16 +Brand#35|LARGE BURNISHED STEEL|3|16 +Brand#35|LARGE PLATED COPPER|19|16 +Brand#35|MEDIUM BRUSHED STEEL|23|16 +Brand#35|MEDIUM PLATED NICKEL|23|16 +Brand#35|PROMO BRUSHED NICKEL|19|16 +Brand#35|SMALL ANODIZED BRASS|45|16 +Brand#35|SMALL BRUSHED TIN|49|16 +Brand#41|ECONOMY ANODIZED STEEL|49|16 +Brand#41|ECONOMY PLATED STEEL|3|16 +Brand#41|ECONOMY PLATED TIN|3|16 +Brand#41|ECONOMY POLISHED STEEL|19|16 +Brand#41|ECONOMY POLISHED STEEL|45|16 +Brand#41|LARGE ANODIZED BRASS|36|16 +Brand#41|LARGE BURNISHED BRASS|23|16 +Brand#41|LARGE POLISHED BRASS|36|16 +Brand#41|LARGE POLISHED NICKEL|3|16 +Brand#41|MEDIUM BURNISHED TIN|3|16 +Brand#41|MEDIUM PLATED STEEL|3|16 +Brand#41|PROMO PLATED BRASS|9|16 +Brand#41|PROMO PLATED STEEL|36|16 +Brand#41|PROMO POLISHED STEEL|36|16 +Brand#41|PROMO POLISHED TIN|19|16 +Brand#41|SMALL ANODIZED COPPER|23|16 +Brand#41|SMALL ANODIZED STEEL|45|16 +Brand#41|SMALL BRUSHED NICKEL|45|16 +Brand#41|SMALL BURNISHED NICKEL|36|16 +Brand#41|SMALL POLISHED NICKEL|9|16 +Brand#41|SMALL POLISHED STEEL|45|16 +Brand#41|SMALL POLISHED TIN|14|16 +Brand#41|STANDARD BRUSHED NICKEL|45|16 +Brand#42|ECONOMY BRUSHED STEEL|14|16 +Brand#42|ECONOMY BURNISHED STEEL|9|16 +Brand#42|ECONOMY BURNISHED STEEL|45|16 +Brand#42|LARGE ANODIZED TIN|23|16 +Brand#42|LARGE BRUSHED STEEL|14|16 +Brand#42|LARGE BURNISHED NICKEL|19|16 +Brand#42|LARGE PLATED STEEL|45|16 +Brand#42|LARGE POLISHED STEEL|14|16 +Brand#42|MEDIUM ANODIZED STEEL|14|16 +Brand#42|MEDIUM ANODIZED TIN|19|16 +Brand#42|MEDIUM BRUSHED COPPER|9|16 +Brand#42|MEDIUM BRUSHED STEEL|14|16 +Brand#42|MEDIUM BURNISHED COPPER|49|16 +Brand#42|MEDIUM BURNISHED NICKEL|23|16 +Brand#42|MEDIUM BURNISHED TIN|49|16 +Brand#42|PROMO ANODIZED NICKEL|49|16 +Brand#42|PROMO ANODIZED STEEL|49|16 +Brand#42|PROMO BURNISHED TIN|49|16 +Brand#42|SMALL ANODIZED BRASS|23|16 +Brand#42|SMALL ANODIZED NICKEL|19|16 +Brand#42|SMALL ANODIZED TIN|49|16 +Brand#42|SMALL PLATED COPPER|23|16 +Brand#42|STANDARD ANODIZED BRASS|9|16 +Brand#42|STANDARD ANODIZED NICKEL|9|16 +Brand#42|STANDARD BRUSHED STEEL|49|16 +Brand#42|STANDARD BRUSHED TIN|45|16 +Brand#42|STANDARD PLATED TIN|23|16 +Brand#43|ECONOMY BRUSHED STEEL|23|16 +Brand#43|ECONOMY PLATED TIN|49|16 +Brand#43|ECONOMY POLISHED TIN|14|16 +Brand#43|LARGE BRUSHED COPPER|9|16 +Brand#43|LARGE BURNISHED STEEL|9|16 +Brand#43|LARGE PLATED BRASS|14|16 +Brand#43|LARGE PLATED BRASS|19|16 +Brand#43|LARGE PLATED NICKEL|45|16 +Brand#43|MEDIUM ANODIZED COPPER|49|16 +Brand#43|PROMO BRUSHED BRASS|36|16 +Brand#43|PROMO BRUSHED STEEL|49|16 +Brand#43|PROMO PLATED BRASS|45|16 +Brand#43|SMALL BURNISHED COPPER|19|16 +Brand#43|SMALL BURNISHED TIN|23|16 +Brand#43|SMALL BURNISHED TIN|45|16 +Brand#43|SMALL PLATED COPPER|23|16 +Brand#43|SMALL POLISHED STEEL|19|16 +Brand#43|STANDARD ANODIZED TIN|45|16 +Brand#43|STANDARD PLATED BRASS|3|16 +Brand#44|ECONOMY ANODIZED BRASS|45|16 +Brand#44|ECONOMY BRUSHED TIN|45|16 +Brand#44|ECONOMY PLATED COPPER|23|16 +Brand#44|ECONOMY PLATED STEEL|3|16 +Brand#44|LARGE BRUSHED BRASS|9|16 +Brand#44|LARGE PLATED BRASS|49|16 +Brand#44|LARGE PLATED STEEL|14|16 +Brand#44|LARGE POLISHED TIN|19|16 +Brand#44|MEDIUM ANODIZED NICKEL|9|16 +Brand#44|MEDIUM ANODIZED TIN|49|16 +Brand#44|MEDIUM BRUSHED NICKEL|36|16 +Brand#44|MEDIUM BURNISHED NICKEL|23|16 +Brand#44|MEDIUM BURNISHED NICKEL|45|16 +Brand#44|MEDIUM PLATED BRASS|9|16 +Brand#44|MEDIUM PLATED STEEL|49|16 +Brand#44|PROMO BURNISHED TIN|3|16 +Brand#44|SMALL ANODIZED COPPER|9|16 +Brand#44|SMALL ANODIZED STEEL|14|16 +Brand#44|SMALL BRUSHED STEEL|19|16 +Brand#44|SMALL BRUSHED TIN|14|16 +Brand#44|SMALL BURNISHED STEEL|23|16 +Brand#44|SMALL PLATED STEEL|19|16 +Brand#44|STANDARD ANODIZED NICKEL|45|16 +Brand#44|STANDARD ANODIZED STEEL|19|16 +Brand#44|STANDARD BRUSHED COPPER|36|16 +Brand#44|STANDARD PLATED BRASS|49|16 +Brand#44|STANDARD PLATED NICKEL|45|16 +Brand#44|STANDARD PLATED STEEL|36|16 +Brand#51|ECONOMY ANODIZED STEEL|9|16 +Brand#51|ECONOMY BRUSHED STEEL|23|16 +Brand#51|ECONOMY PLATED STEEL|9|16 +Brand#51|LARGE BURNISHED COPPER|14|16 +Brand#51|LARGE PLATED BRASS|3|16 +Brand#51|LARGE PLATED BRASS|36|16 +Brand#51|LARGE PLATED BRASS|49|16 +Brand#51|LARGE POLISHED BRASS|3|16 +Brand#51|LARGE POLISHED NICKEL|19|16 +Brand#51|MEDIUM ANODIZED BRASS|9|16 +Brand#51|MEDIUM ANODIZED TIN|9|16 +Brand#51|MEDIUM PLATED BRASS|14|16 +Brand#51|PROMO BURNISHED NICKEL|14|16 +Brand#51|PROMO BURNISHED TIN|9|16 +Brand#51|PROMO PLATED NICKEL|14|16 +Brand#51|SMALL ANODIZED COPPER|45|16 +Brand#51|SMALL BURNISHED COPPER|36|16 +Brand#51|SMALL BURNISHED TIN|9|16 +Brand#51|STANDARD BURNISHED STEEL|45|16 +Brand#51|STANDARD BURNISHED TIN|9|16 +Brand#51|STANDARD PLATED BRASS|36|16 +Brand#51|STANDARD PLATED STEEL|45|16 +Brand#52|ECONOMY BRUSHED NICKEL|3|16 +Brand#52|ECONOMY BURNISHED COPPER|9|16 +Brand#52|ECONOMY BURNISHED STEEL|14|16 +Brand#52|LARGE ANODIZED BRASS|23|16 +Brand#52|LARGE BRUSHED BRASS|14|16 +Brand#52|LARGE BURNISHED TIN|23|16 +Brand#52|MEDIUM ANODIZED COPPER|23|16 +Brand#52|PROMO BRUSHED STEEL|36|16 +Brand#52|PROMO PLATED COPPER|14|16 +Brand#52|SMALL PLATED COPPER|3|16 +Brand#52|STANDARD BRUSHED COPPER|14|16 +Brand#52|STANDARD BURNISHED BRASS|14|16 +Brand#52|STANDARD BURNISHED BRASS|19|16 +Brand#52|STANDARD POLISHED NICKEL|36|16 +Brand#53|ECONOMY ANODIZED BRASS|19|16 +Brand#53|LARGE BRUSHED COPPER|14|16 +Brand#53|LARGE BRUSHED NICKEL|45|16 +Brand#53|LARGE BURNISHED COPPER|36|16 +Brand#53|LARGE PLATED COPPER|36|16 +Brand#53|LARGE PLATED STEEL|36|16 +Brand#53|LARGE PLATED TIN|14|16 +Brand#53|LARGE POLISHED BRASS|14|16 +Brand#53|LARGE POLISHED STEEL|49|16 +Brand#53|MEDIUM BRUSHED NICKEL|49|16 +Brand#53|MEDIUM BURNISHED BRASS|3|16 +Brand#53|MEDIUM BURNISHED COPPER|49|16 +Brand#53|PROMO ANODIZED COPPER|36|16 +Brand#53|PROMO ANODIZED NICKEL|3|16 +Brand#53|PROMO BURNISHED STEEL|9|16 +Brand#53|PROMO PLATED COPPER|3|16 +Brand#53|SMALL ANODIZED TIN|9|16 +Brand#53|STANDARD PLATED BRASS|23|16 +Brand#54|ECONOMY BRUSHED BRASS|45|16 +Brand#54|ECONOMY BRUSHED COPPER|14|16 +Brand#54|LARGE ANODIZED NICKEL|49|16 +Brand#54|LARGE BURNISHED BRASS|49|16 +Brand#54|LARGE BURNISHED COPPER|19|16 +Brand#54|LARGE POLISHED NICKEL|36|16 +Brand#54|PROMO BURNISHED TIN|19|16 +Brand#54|PROMO PLATED BRASS|49|16 +Brand#54|PROMO POLISHED TIN|23|16 +Brand#54|SMALL ANODIZED COPPER|14|16 +Brand#54|SMALL BRUSHED COPPER|9|16 +Brand#54|SMALL PLATED NICKEL|9|16 +Brand#54|STANDARD ANODIZED COPPER|49|16 +Brand#54|STANDARD ANODIZED TIN|14|16 +Brand#54|STANDARD BRUSHED COPPER|45|16 +Brand#54|STANDARD PLATED COPPER|23|16 +Brand#54|STANDARD PLATED COPPER|45|16 +Brand#54|STANDARD POLISHED BRASS|19|16 +Brand#54|STANDARD POLISHED STEEL|14|16 +Brand#55|ECONOMY BRUSHED TIN|36|16 +Brand#55|ECONOMY POLISHED TIN|14|16 +Brand#55|LARGE PLATED BRASS|9|16 +Brand#55|LARGE POLISHED STEEL|9|16 +Brand#55|MEDIUM BURNISHED TIN|36|16 +Brand#55|PROMO ANODIZED BRASS|14|16 +Brand#55|PROMO ANODIZED COPPER|14|16 +Brand#55|SMALL BURNISHED STEEL|9|16 +Brand#55|STANDARD POLISHED COPPER|19|16 +Brand#23|PROMO POLISHED COPPER|36|15 +Brand#33|PROMO POLISHED STEEL|9|15 +Brand#34|LARGE BURNISHED BRASS|23|15 +Brand#41|PROMO ANODIZED BRASS|49|15 +Brand#11|ECONOMY ANODIZED NICKEL|14|12 +Brand#11|ECONOMY ANODIZED NICKEL|23|12 +Brand#11|ECONOMY ANODIZED STEEL|36|12 +Brand#11|ECONOMY ANODIZED TIN|14|12 +Brand#11|ECONOMY BRUSHED COPPER|14|12 +Brand#11|ECONOMY BURNISHED BRASS|36|12 +Brand#11|ECONOMY BURNISHED COPPER|3|12 +Brand#11|ECONOMY BURNISHED COPPER|49|12 +Brand#11|ECONOMY PLATED COPPER|3|12 +Brand#11|ECONOMY PLATED COPPER|19|12 +Brand#11|ECONOMY PLATED NICKEL|14|12 +Brand#11|ECONOMY POLISHED COPPER|14|12 +Brand#11|ECONOMY POLISHED TIN|23|12 +Brand#11|LARGE ANODIZED NICKEL|9|12 +Brand#11|LARGE ANODIZED STEEL|23|12 +Brand#11|LARGE ANODIZED TIN|36|12 +Brand#11|LARGE BRUSHED BRASS|19|12 +Brand#11|LARGE BRUSHED STEEL|19|12 +Brand#11|LARGE BRUSHED STEEL|36|12 +Brand#11|LARGE BURNISHED BRASS|3|12 +Brand#11|LARGE PLATED TIN|19|12 +Brand#11|MEDIUM ANODIZED BRASS|45|12 +Brand#11|MEDIUM BRUSHED BRASS|3|12 +Brand#11|MEDIUM BRUSHED BRASS|23|12 +Brand#11|MEDIUM BRUSHED BRASS|45|12 +Brand#11|MEDIUM BRUSHED NICKEL|36|12 +Brand#11|MEDIUM BRUSHED STEEL|19|12 +Brand#11|MEDIUM BRUSHED STEEL|23|12 +Brand#11|MEDIUM BURNISHED NICKEL|23|12 +Brand#11|MEDIUM BURNISHED STEEL|9|12 +Brand#11|MEDIUM PLATED BRASS|14|12 +Brand#11|MEDIUM PLATED COPPER|3|12 +Brand#11|MEDIUM PLATED STEEL|14|12 +Brand#11|PROMO ANODIZED BRASS|45|12 +Brand#11|PROMO BRUSHED NICKEL|9|12 +Brand#11|PROMO BRUSHED STEEL|45|12 +Brand#11|PROMO BURNISHED BRASS|23|12 +Brand#11|PROMO BURNISHED COPPER|23|12 +Brand#11|PROMO BURNISHED NICKEL|36|12 +Brand#11|PROMO PLATED BRASS|14|12 +Brand#11|PROMO PLATED COPPER|14|12 +Brand#11|PROMO PLATED STEEL|49|12 +Brand#11|PROMO PLATED TIN|3|12 +Brand#11|PROMO POLISHED COPPER|14|12 +Brand#11|PROMO POLISHED NICKEL|3|12 +Brand#11|PROMO POLISHED STEEL|3|12 +Brand#11|PROMO POLISHED STEEL|23|12 +Brand#11|PROMO POLISHED TIN|14|12 +Brand#11|SMALL ANODIZED BRASS|49|12 +Brand#11|SMALL ANODIZED COPPER|49|12 +Brand#11|SMALL ANODIZED NICKEL|9|12 +Brand#11|SMALL ANODIZED STEEL|45|12 +Brand#11|SMALL BURNISHED BRASS|19|12 +Brand#11|SMALL BURNISHED BRASS|49|12 +Brand#11|SMALL BURNISHED NICKEL|9|12 +Brand#11|SMALL BURNISHED NICKEL|49|12 +Brand#11|SMALL PLATED COPPER|45|12 +Brand#11|SMALL PLATED NICKEL|45|12 +Brand#11|SMALL PLATED TIN|36|12 +Brand#11|SMALL POLISHED BRASS|14|12 +Brand#11|SMALL POLISHED BRASS|19|12 +Brand#11|SMALL POLISHED STEEL|3|12 +Brand#11|SMALL POLISHED STEEL|36|12 +Brand#11|STANDARD ANODIZED COPPER|49|12 +Brand#11|STANDARD BRUSHED COPPER|23|12 +Brand#11|STANDARD BRUSHED NICKEL|9|12 +Brand#11|STANDARD BURNISHED BRASS|19|12 +Brand#11|STANDARD BURNISHED COPPER|9|12 +Brand#11|STANDARD PLATED STEEL|19|12 +Brand#11|STANDARD PLATED TIN|45|12 +Brand#11|STANDARD POLISHED STEEL|9|12 +Brand#11|STANDARD POLISHED STEEL|19|12 +Brand#11|STANDARD POLISHED TIN|14|12 +Brand#12|ECONOMY ANODIZED BRASS|49|12 +Brand#12|ECONOMY ANODIZED COPPER|14|12 +Brand#12|ECONOMY ANODIZED NICKEL|19|12 +Brand#12|ECONOMY ANODIZED NICKEL|45|12 +Brand#12|ECONOMY BRUSHED BRASS|23|12 +Brand#12|ECONOMY BRUSHED STEEL|9|12 +Brand#12|ECONOMY BRUSHED TIN|3|12 +Brand#12|ECONOMY BRUSHED TIN|19|12 +Brand#12|ECONOMY BURNISHED BRASS|19|12 +Brand#12|ECONOMY BURNISHED COPPER|49|12 +Brand#12|ECONOMY BURNISHED STEEL|9|12 +Brand#12|ECONOMY BURNISHED STEEL|36|12 +Brand#12|ECONOMY PLATED BRASS|3|12 +Brand#12|ECONOMY PLATED NICKEL|9|12 +Brand#12|ECONOMY PLATED TIN|45|12 +Brand#12|ECONOMY POLISHED NICKEL|45|12 +Brand#12|ECONOMY POLISHED STEEL|9|12 +Brand#12|ECONOMY POLISHED STEEL|19|12 +Brand#12|ECONOMY POLISHED TIN|14|12 +Brand#12|LARGE ANODIZED COPPER|19|12 +Brand#12|LARGE ANODIZED NICKEL|49|12 +Brand#12|LARGE ANODIZED TIN|49|12 +Brand#12|LARGE BRUSHED BRASS|9|12 +Brand#12|LARGE BRUSHED BRASS|23|12 +Brand#12|LARGE BRUSHED BRASS|49|12 +Brand#12|LARGE BURNISHED NICKEL|45|12 +Brand#12|LARGE PLATED BRASS|3|12 +Brand#12|LARGE POLISHED BRASS|23|12 +Brand#12|LARGE POLISHED COPPER|19|12 +Brand#12|MEDIUM ANODIZED BRASS|3|12 +Brand#12|MEDIUM ANODIZED COPPER|9|12 +Brand#12|MEDIUM BRUSHED BRASS|14|12 +Brand#12|MEDIUM BRUSHED BRASS|23|12 +Brand#12|MEDIUM BRUSHED BRASS|45|12 +Brand#12|MEDIUM BRUSHED COPPER|23|12 +Brand#12|MEDIUM BRUSHED NICKEL|14|12 +Brand#12|MEDIUM BRUSHED TIN|14|12 +Brand#12|MEDIUM BRUSHED TIN|36|12 +Brand#12|MEDIUM BURNISHED BRASS|19|12 +Brand#12|MEDIUM PLATED BRASS|23|12 +Brand#12|MEDIUM PLATED NICKEL|45|12 +Brand#12|MEDIUM PLATED STEEL|19|12 +Brand#12|MEDIUM PLATED TIN|23|12 +Brand#12|PROMO BRUSHED COPPER|36|12 +Brand#12|PROMO BRUSHED STEEL|19|12 +Brand#12|PROMO BRUSHED STEEL|45|12 +Brand#12|PROMO PLATED COPPER|14|12 +Brand#12|PROMO PLATED STEEL|19|12 +Brand#12|PROMO POLISHED COPPER|45|12 +Brand#12|PROMO POLISHED STEEL|45|12 +Brand#12|PROMO POLISHED TIN|3|12 +Brand#12|PROMO POLISHED TIN|14|12 +Brand#12|SMALL ANODIZED BRASS|9|12 +Brand#12|SMALL ANODIZED STEEL|14|12 +Brand#12|SMALL BRUSHED BRASS|36|12 +Brand#12|SMALL BRUSHED NICKEL|3|12 +Brand#12|SMALL BRUSHED NICKEL|9|12 +Brand#12|SMALL BURNISHED BRASS|14|12 +Brand#12|SMALL BURNISHED BRASS|23|12 +Brand#12|SMALL BURNISHED TIN|14|12 +Brand#12|SMALL POLISHED NICKEL|23|12 +Brand#12|STANDARD ANODIZED COPPER|45|12 +Brand#12|STANDARD BRUSHED COPPER|3|12 +Brand#12|STANDARD BRUSHED NICKEL|23|12 +Brand#12|STANDARD BRUSHED STEEL|3|12 +Brand#12|STANDARD BRUSHED TIN|45|12 +Brand#12|STANDARD BURNISHED BRASS|14|12 +Brand#12|STANDARD BURNISHED COPPER|3|12 +Brand#12|STANDARD BURNISHED COPPER|45|12 +Brand#12|STANDARD BURNISHED STEEL|9|12 +Brand#12|STANDARD BURNISHED TIN|3|12 +Brand#12|STANDARD PLATED COPPER|49|12 +Brand#12|STANDARD PLATED NICKEL|19|12 +Brand#12|STANDARD PLATED NICKEL|45|12 +Brand#12|STANDARD PLATED STEEL|19|12 +Brand#12|STANDARD PLATED STEEL|36|12 +Brand#12|STANDARD POLISHED BRASS|45|12 +Brand#13|ECONOMY ANODIZED BRASS|36|12 +Brand#13|ECONOMY ANODIZED BRASS|45|12 +Brand#13|ECONOMY ANODIZED COPPER|14|12 +Brand#13|ECONOMY ANODIZED NICKEL|14|12 +Brand#13|ECONOMY ANODIZED NICKEL|19|12 +Brand#13|ECONOMY ANODIZED TIN|23|12 +Brand#13|ECONOMY BRUSHED BRASS|45|12 +Brand#13|ECONOMY BRUSHED NICKEL|45|12 +Brand#13|ECONOMY BURNISHED BRASS|3|12 +Brand#13|ECONOMY BURNISHED COPPER|19|12 +Brand#13|ECONOMY BURNISHED NICKEL|36|12 +Brand#13|ECONOMY PLATED COPPER|49|12 +Brand#13|ECONOMY PLATED NICKEL|3|12 +Brand#13|ECONOMY PLATED NICKEL|19|12 +Brand#13|ECONOMY PLATED STEEL|23|12 +Brand#13|ECONOMY POLISHED STEEL|19|12 +Brand#13|ECONOMY POLISHED STEEL|36|12 +Brand#13|LARGE ANODIZED BRASS|49|12 +Brand#13|LARGE ANODIZED TIN|9|12 +Brand#13|LARGE ANODIZED TIN|19|12 +Brand#13|LARGE BRUSHED BRASS|3|12 +Brand#13|LARGE BRUSHED COPPER|9|12 +Brand#13|LARGE BRUSHED NICKEL|3|12 +Brand#13|LARGE BURNISHED COPPER|45|12 +Brand#13|LARGE PLATED COPPER|23|12 +Brand#13|LARGE PLATED COPPER|36|12 +Brand#13|LARGE PLATED NICKEL|23|12 +Brand#13|LARGE PLATED NICKEL|49|12 +Brand#13|LARGE PLATED STEEL|14|12 +Brand#13|LARGE PLATED TIN|9|12 +Brand#13|LARGE POLISHED BRASS|49|12 +Brand#13|LARGE POLISHED STEEL|9|12 +Brand#13|MEDIUM ANODIZED NICKEL|3|12 +Brand#13|MEDIUM ANODIZED NICKEL|36|12 +Brand#13|MEDIUM ANODIZED NICKEL|45|12 +Brand#13|MEDIUM ANODIZED STEEL|9|12 +Brand#13|MEDIUM ANODIZED STEEL|14|12 +Brand#13|MEDIUM BRUSHED BRASS|9|12 +Brand#13|MEDIUM BRUSHED COPPER|3|12 +Brand#13|MEDIUM BRUSHED COPPER|14|12 +Brand#13|MEDIUM BRUSHED STEEL|19|12 +Brand#13|MEDIUM BRUSHED TIN|19|12 +Brand#13|MEDIUM BURNISHED NICKEL|36|12 +Brand#13|MEDIUM PLATED BRASS|9|12 +Brand#13|PROMO ANODIZED COPPER|45|12 +Brand#13|PROMO BRUSHED NICKEL|23|12 +Brand#13|PROMO BRUSHED STEEL|45|12 +Brand#13|PROMO BRUSHED TIN|3|12 +Brand#13|PROMO BURNISHED BRASS|19|12 +Brand#13|PROMO BURNISHED COPPER|19|12 +Brand#13|PROMO BURNISHED NICKEL|3|12 +Brand#13|PROMO BURNISHED NICKEL|49|12 +Brand#13|PROMO PLATED COPPER|3|12 +Brand#13|PROMO PLATED NICKEL|3|12 +Brand#13|PROMO PLATED STEEL|45|12 +Brand#13|PROMO POLISHED NICKEL|3|12 +Brand#13|PROMO POLISHED STEEL|14|12 +Brand#13|SMALL ANODIZED BRASS|49|12 +Brand#13|SMALL ANODIZED COPPER|36|12 +Brand#13|SMALL ANODIZED TIN|9|12 +Brand#13|SMALL ANODIZED TIN|23|12 +Brand#13|SMALL BRUSHED COPPER|14|12 +Brand#13|SMALL BRUSHED COPPER|45|12 +Brand#13|SMALL BURNISHED NICKEL|3|12 +Brand#13|SMALL PLATED BRASS|45|12 +Brand#13|SMALL PLATED NICKEL|45|12 +Brand#13|SMALL PLATED TIN|14|12 +Brand#13|SMALL POLISHED BRASS|49|12 +Brand#13|SMALL POLISHED NICKEL|19|12 +Brand#13|STANDARD BRUSHED BRASS|14|12 +Brand#13|STANDARD BRUSHED COPPER|23|12 +Brand#13|STANDARD BURNISHED COPPER|3|12 +Brand#13|STANDARD BURNISHED COPPER|23|12 +Brand#13|STANDARD BURNISHED COPPER|45|12 +Brand#13|STANDARD BURNISHED STEEL|3|12 +Brand#13|STANDARD BURNISHED STEEL|19|12 +Brand#13|STANDARD BURNISHED TIN|23|12 +Brand#13|STANDARD PLATED BRASS|14|12 +Brand#13|STANDARD PLATED COPPER|45|12 +Brand#13|STANDARD PLATED NICKEL|45|12 +Brand#13|STANDARD PLATED STEEL|9|12 +Brand#13|STANDARD POLISHED BRASS|19|12 +Brand#13|STANDARD POLISHED NICKEL|19|12 +Brand#14|ECONOMY ANODIZED COPPER|9|12 +Brand#14|ECONOMY ANODIZED NICKEL|49|12 +Brand#14|ECONOMY ANODIZED STEEL|45|12 +Brand#14|ECONOMY BRUSHED BRASS|23|12 +Brand#14|ECONOMY BRUSHED COPPER|19|12 +Brand#14|ECONOMY BRUSHED COPPER|45|12 +Brand#14|ECONOMY BRUSHED NICKEL|36|12 +Brand#14|ECONOMY BRUSHED TIN|14|12 +Brand#14|ECONOMY BURNISHED COPPER|9|12 +Brand#14|ECONOMY BURNISHED COPPER|23|12 +Brand#14|ECONOMY BURNISHED STEEL|9|12 +Brand#14|ECONOMY BURNISHED STEEL|14|12 +Brand#14|ECONOMY PLATED BRASS|9|12 +Brand#14|ECONOMY POLISHED BRASS|19|12 +Brand#14|ECONOMY POLISHED COPPER|23|12 +Brand#14|ECONOMY POLISHED STEEL|45|12 +Brand#14|LARGE ANODIZED COPPER|49|12 +Brand#14|LARGE ANODIZED NICKEL|23|12 +Brand#14|LARGE ANODIZED NICKEL|45|12 +Brand#14|LARGE ANODIZED STEEL|9|12 +Brand#14|LARGE BRUSHED COPPER|14|12 +Brand#14|LARGE BRUSHED TIN|3|12 +Brand#14|LARGE BRUSHED TIN|45|12 +Brand#14|LARGE BURNISHED COPPER|49|12 +Brand#14|LARGE PLATED BRASS|19|12 +Brand#14|LARGE PLATED COPPER|3|12 +Brand#14|LARGE PLATED NICKEL|36|12 +Brand#14|MEDIUM ANODIZED STEEL|36|12 +Brand#14|MEDIUM BRUSHED BRASS|9|12 +Brand#14|MEDIUM BRUSHED TIN|19|12 +Brand#14|MEDIUM BURNISHED BRASS|49|12 +Brand#14|MEDIUM BURNISHED COPPER|14|12 +Brand#14|MEDIUM BURNISHED NICKEL|36|12 +Brand#14|MEDIUM BURNISHED STEEL|3|12 +Brand#14|MEDIUM BURNISHED STEEL|19|12 +Brand#14|MEDIUM PLATED COPPER|36|12 +Brand#14|MEDIUM PLATED TIN|49|12 +Brand#14|PROMO ANODIZED NICKEL|36|12 +Brand#14|PROMO BRUSHED COPPER|14|12 +Brand#14|PROMO BURNISHED NICKEL|14|12 +Brand#14|PROMO PLATED COPPER|45|12 +Brand#14|PROMO PLATED NICKEL|36|12 +Brand#14|PROMO PLATED STEEL|9|12 +Brand#14|PROMO PLATED TIN|19|12 +Brand#14|PROMO PLATED TIN|45|12 +Brand#14|PROMO PLATED TIN|49|12 +Brand#14|PROMO POLISHED BRASS|9|12 +Brand#14|PROMO POLISHED COPPER|14|12 +Brand#14|PROMO POLISHED NICKEL|9|12 +Brand#14|SMALL ANODIZED NICKEL|45|12 +Brand#14|SMALL ANODIZED TIN|45|12 +Brand#14|SMALL BRUSHED NICKEL|19|12 +Brand#14|SMALL BRUSHED TIN|19|12 +Brand#14|SMALL BURNISHED STEEL|9|12 +Brand#14|SMALL BURNISHED STEEL|36|12 +Brand#14|SMALL PLATED BRASS|23|12 +Brand#14|SMALL PLATED COPPER|9|12 +Brand#14|SMALL PLATED STEEL|23|12 +Brand#14|SMALL POLISHED BRASS|3|12 +Brand#14|SMALL POLISHED BRASS|9|12 +Brand#14|SMALL POLISHED COPPER|36|12 +Brand#14|SMALL POLISHED NICKEL|49|12 +Brand#14|SMALL POLISHED STEEL|14|12 +Brand#14|SMALL POLISHED TIN|49|12 +Brand#14|STANDARD ANODIZED STEEL|49|12 +Brand#14|STANDARD BRUSHED BRASS|3|12 +Brand#14|STANDARD BRUSHED STEEL|49|12 +Brand#14|STANDARD BURNISHED BRASS|23|12 +Brand#14|STANDARD PLATED NICKEL|49|12 +Brand#14|STANDARD POLISHED COPPER|36|12 +Brand#14|STANDARD POLISHED COPPER|45|12 +Brand#15|ECONOMY ANODIZED TIN|19|12 +Brand#15|ECONOMY BRUSHED NICKEL|14|12 +Brand#15|ECONOMY BURNISHED STEEL|19|12 +Brand#15|ECONOMY PLATED NICKEL|9|12 +Brand#15|ECONOMY PLATED STEEL|3|12 +Brand#15|ECONOMY PLATED STEEL|19|12 +Brand#15|ECONOMY PLATED TIN|9|12 +Brand#15|ECONOMY POLISHED COPPER|36|12 +Brand#15|ECONOMY POLISHED NICKEL|45|12 +Brand#15|LARGE ANODIZED BRASS|19|12 +Brand#15|LARGE ANODIZED STEEL|14|12 +Brand#15|LARGE ANODIZED TIN|23|12 +Brand#15|LARGE BRUSHED BRASS|19|12 +Brand#15|LARGE BRUSHED BRASS|49|12 +Brand#15|LARGE BURNISHED BRASS|3|12 +Brand#15|LARGE BURNISHED BRASS|23|12 +Brand#15|LARGE BURNISHED COPPER|9|12 +Brand#15|LARGE BURNISHED COPPER|49|12 +Brand#15|LARGE BURNISHED STEEL|9|12 +Brand#15|LARGE PLATED BRASS|9|12 +Brand#15|MEDIUM BRUSHED BRASS|14|12 +Brand#15|MEDIUM BRUSHED NICKEL|14|12 +Brand#15|MEDIUM BRUSHED NICKEL|19|12 +Brand#15|MEDIUM BRUSHED STEEL|36|12 +Brand#15|MEDIUM BRUSHED TIN|14|12 +Brand#15|MEDIUM BURNISHED STEEL|3|12 +Brand#15|MEDIUM PLATED TIN|9|12 +Brand#15|MEDIUM PLATED TIN|45|12 +Brand#15|PROMO BRUSHED BRASS|36|12 +Brand#15|PROMO BRUSHED STEEL|9|12 +Brand#15|PROMO BURNISHED NICKEL|9|12 +Brand#15|PROMO PLATED COPPER|36|12 +Brand#15|PROMO POLISHED BRASS|14|12 +Brand#15|PROMO POLISHED COPPER|9|12 +Brand#15|PROMO POLISHED NICKEL|36|12 +Brand#15|PROMO POLISHED TIN|49|12 +Brand#15|SMALL ANODIZED STEEL|45|12 +Brand#15|SMALL BRUSHED BRASS|45|12 +Brand#15|SMALL BRUSHED COPPER|14|12 +Brand#15|SMALL BRUSHED COPPER|19|12 +Brand#15|SMALL BRUSHED NICKEL|36|12 +Brand#15|SMALL BURNISHED BRASS|3|12 +Brand#15|SMALL PLATED COPPER|19|12 +Brand#15|SMALL PLATED COPPER|23|12 +Brand#15|SMALL PLATED NICKEL|19|12 +Brand#15|SMALL POLISHED BRASS|45|12 +Brand#15|SMALL POLISHED NICKEL|19|12 +Brand#15|SMALL POLISHED NICKEL|23|12 +Brand#15|SMALL POLISHED TIN|3|12 +Brand#15|SMALL POLISHED TIN|49|12 +Brand#15|STANDARD ANODIZED NICKEL|3|12 +Brand#15|STANDARD ANODIZED STEEL|19|12 +Brand#15|STANDARD ANODIZED TIN|36|12 +Brand#15|STANDARD BRUSHED BRASS|49|12 +Brand#15|STANDARD BRUSHED COPPER|49|12 +Brand#15|STANDARD BRUSHED NICKEL|3|12 +Brand#15|STANDARD BRUSHED STEEL|19|12 +Brand#15|STANDARD BURNISHED BRASS|19|12 +Brand#15|STANDARD BURNISHED COPPER|14|12 +Brand#15|STANDARD BURNISHED COPPER|36|12 +Brand#15|STANDARD BURNISHED TIN|49|12 +Brand#15|STANDARD PLATED COPPER|14|12 +Brand#15|STANDARD PLATED STEEL|3|12 +Brand#15|STANDARD PLATED TIN|9|12 +Brand#15|STANDARD PLATED TIN|45|12 +Brand#15|STANDARD POLISHED TIN|14|12 +Brand#21|ECONOMY ANODIZED STEEL|19|12 +Brand#21|ECONOMY BRUSHED COPPER|14|12 +Brand#21|ECONOMY BRUSHED NICKEL|23|12 +Brand#21|ECONOMY BRUSHED STEEL|45|12 +Brand#21|ECONOMY BRUSHED TIN|19|12 +Brand#21|ECONOMY BURNISHED BRASS|19|12 +Brand#21|ECONOMY BURNISHED COPPER|45|12 +Brand#21|ECONOMY BURNISHED STEEL|9|12 +Brand#21|ECONOMY BURNISHED STEEL|14|12 +Brand#21|ECONOMY BURNISHED TIN|49|12 +Brand#21|ECONOMY PLATED BRASS|49|12 +Brand#21|ECONOMY PLATED COPPER|14|12 +Brand#21|ECONOMY PLATED NICKEL|3|12 +Brand#21|ECONOMY PLATED STEEL|9|12 +Brand#21|ECONOMY PLATED TIN|19|12 +Brand#21|ECONOMY PLATED TIN|23|12 +Brand#21|ECONOMY POLISHED BRASS|9|12 +Brand#21|ECONOMY POLISHED STEEL|14|12 +Brand#21|LARGE ANODIZED COPPER|3|12 +Brand#21|LARGE ANODIZED TIN|3|12 +Brand#21|LARGE ANODIZED TIN|14|12 +Brand#21|LARGE ANODIZED TIN|45|12 +Brand#21|LARGE BRUSHED COPPER|23|12 +Brand#21|LARGE BRUSHED NICKEL|36|12 +Brand#21|LARGE BRUSHED STEEL|23|12 +Brand#21|LARGE BRUSHED TIN|45|12 +Brand#21|LARGE BRUSHED TIN|49|12 +Brand#21|LARGE BURNISHED BRASS|14|12 +Brand#21|LARGE BURNISHED NICKEL|14|12 +Brand#21|LARGE BURNISHED STEEL|19|12 +Brand#21|LARGE PLATED BRASS|14|12 +Brand#21|LARGE PLATED COPPER|19|12 +Brand#21|LARGE PLATED COPPER|49|12 +Brand#21|LARGE POLISHED COPPER|14|12 +Brand#21|LARGE POLISHED STEEL|45|12 +Brand#21|MEDIUM ANODIZED NICKEL|3|12 +Brand#21|MEDIUM ANODIZED STEEL|14|12 +Brand#21|MEDIUM BRUSHED BRASS|23|12 +Brand#21|MEDIUM BURNISHED COPPER|49|12 +Brand#21|MEDIUM BURNISHED NICKEL|9|12 +Brand#21|MEDIUM BURNISHED TIN|9|12 +Brand#21|MEDIUM PLATED BRASS|36|12 +Brand#21|MEDIUM PLATED NICKEL|36|12 +Brand#21|MEDIUM PLATED STEEL|36|12 +Brand#21|MEDIUM PLATED TIN|9|12 +Brand#21|PROMO ANODIZED BRASS|9|12 +Brand#21|PROMO ANODIZED COPPER|9|12 +Brand#21|PROMO ANODIZED NICKEL|19|12 +Brand#21|PROMO ANODIZED STEEL|36|12 +Brand#21|PROMO ANODIZED TIN|45|12 +Brand#21|PROMO BRUSHED NICKEL|9|12 +Brand#21|PROMO BRUSHED STEEL|14|12 +Brand#21|PROMO BRUSHED STEEL|19|12 +Brand#21|PROMO BRUSHED STEEL|45|12 +Brand#21|PROMO BRUSHED TIN|14|12 +Brand#21|PROMO BURNISHED COPPER|3|12 +Brand#21|PROMO BURNISHED STEEL|14|12 +Brand#21|PROMO PLATED BRASS|36|12 +Brand#21|PROMO PLATED COPPER|49|12 +Brand#21|PROMO PLATED TIN|45|12 +Brand#21|PROMO POLISHED COPPER|9|12 +Brand#21|PROMO POLISHED COPPER|19|12 +Brand#21|PROMO POLISHED NICKEL|23|12 +Brand#21|PROMO POLISHED STEEL|3|12 +Brand#21|PROMO POLISHED STEEL|9|12 +Brand#21|PROMO POLISHED TIN|9|12 +Brand#21|PROMO POLISHED TIN|14|12 +Brand#21|PROMO POLISHED TIN|19|12 +Brand#21|SMALL BRUSHED NICKEL|9|12 +Brand#21|SMALL BRUSHED NICKEL|45|12 +Brand#21|SMALL BRUSHED STEEL|3|12 +Brand#21|SMALL BRUSHED STEEL|9|12 +Brand#21|SMALL BRUSHED TIN|14|12 +Brand#21|SMALL PLATED BRASS|36|12 +Brand#21|SMALL PLATED COPPER|14|12 +Brand#21|SMALL PLATED COPPER|23|12 +Brand#21|SMALL POLISHED NICKEL|9|12 +Brand#21|SMALL POLISHED STEEL|3|12 +Brand#21|STANDARD ANODIZED NICKEL|3|12 +Brand#21|STANDARD ANODIZED NICKEL|19|12 +Brand#21|STANDARD BRUSHED BRASS|9|12 +Brand#21|STANDARD BRUSHED NICKEL|23|12 +Brand#21|STANDARD BRUSHED NICKEL|45|12 +Brand#21|STANDARD BURNISHED BRASS|49|12 +Brand#21|STANDARD PLATED COPPER|45|12 +Brand#21|STANDARD PLATED NICKEL|49|12 +Brand#21|STANDARD PLATED STEEL|36|12 +Brand#21|STANDARD PLATED TIN|9|12 +Brand#21|STANDARD POLISHED COPPER|49|12 +Brand#22|ECONOMY ANODIZED COPPER|36|12 +Brand#22|ECONOMY ANODIZED COPPER|45|12 +Brand#22|ECONOMY ANODIZED NICKEL|45|12 +Brand#22|ECONOMY ANODIZED STEEL|45|12 +Brand#22|ECONOMY ANODIZED TIN|49|12 +Brand#22|ECONOMY BRUSHED STEEL|45|12 +Brand#22|ECONOMY BRUSHED TIN|49|12 +Brand#22|ECONOMY BURNISHED BRASS|19|12 +Brand#22|ECONOMY BURNISHED BRASS|23|12 +Brand#22|ECONOMY BURNISHED BRASS|45|12 +Brand#22|ECONOMY BURNISHED COPPER|3|12 +Brand#22|ECONOMY BURNISHED COPPER|9|12 +Brand#22|ECONOMY BURNISHED COPPER|49|12 +Brand#22|ECONOMY BURNISHED NICKEL|14|12 +Brand#22|ECONOMY BURNISHED NICKEL|23|12 +Brand#22|ECONOMY BURNISHED STEEL|23|12 +Brand#22|ECONOMY BURNISHED STEEL|45|12 +Brand#22|ECONOMY BURNISHED STEEL|49|12 +Brand#22|ECONOMY BURNISHED TIN|9|12 +Brand#22|ECONOMY BURNISHED TIN|19|12 +Brand#22|ECONOMY PLATED BRASS|36|12 +Brand#22|ECONOMY PLATED COPPER|3|12 +Brand#22|ECONOMY PLATED STEEL|23|12 +Brand#22|ECONOMY POLISHED COPPER|14|12 +Brand#22|ECONOMY POLISHED TIN|49|12 +Brand#22|LARGE ANODIZED NICKEL|14|12 +Brand#22|LARGE ANODIZED TIN|14|12 +Brand#22|LARGE BRUSHED BRASS|9|12 +Brand#22|LARGE BRUSHED BRASS|49|12 +Brand#22|LARGE BRUSHED COPPER|14|12 +Brand#22|LARGE BRUSHED STEEL|19|12 +Brand#22|LARGE BRUSHED TIN|23|12 +Brand#22|LARGE BURNISHED BRASS|14|12 +Brand#22|LARGE BURNISHED TIN|36|12 +Brand#22|LARGE PLATED STEEL|9|12 +Brand#22|LARGE PLATED TIN|49|12 +Brand#22|LARGE POLISHED COPPER|23|12 +Brand#22|LARGE POLISHED NICKEL|19|12 +Brand#22|LARGE POLISHED NICKEL|23|12 +Brand#22|LARGE POLISHED STEEL|3|12 +Brand#22|MEDIUM ANODIZED COPPER|19|12 +Brand#22|MEDIUM ANODIZED NICKEL|45|12 +Brand#22|MEDIUM BRUSHED NICKEL|9|12 +Brand#22|MEDIUM BRUSHED STEEL|3|12 +Brand#22|MEDIUM PLATED BRASS|36|12 +Brand#22|MEDIUM PLATED NICKEL|14|12 +Brand#22|PROMO ANODIZED COPPER|45|12 +Brand#22|PROMO ANODIZED STEEL|36|12 +Brand#22|PROMO BURNISHED BRASS|3|12 +Brand#22|PROMO BURNISHED BRASS|23|12 +Brand#22|PROMO BURNISHED STEEL|3|12 +Brand#22|PROMO PLATED BRASS|14|12 +Brand#22|PROMO POLISHED BRASS|14|12 +Brand#22|PROMO POLISHED COPPER|3|12 +Brand#22|PROMO POLISHED COPPER|23|12 +Brand#22|PROMO POLISHED NICKEL|19|12 +Brand#22|PROMO POLISHED NICKEL|36|12 +Brand#22|PROMO POLISHED STEEL|36|12 +Brand#22|SMALL ANODIZED COPPER|9|12 +Brand#22|SMALL ANODIZED STEEL|19|12 +Brand#22|SMALL ANODIZED TIN|19|12 +Brand#22|SMALL ANODIZED TIN|49|12 +Brand#22|SMALL BRUSHED COPPER|36|12 +Brand#22|SMALL BRUSHED TIN|45|12 +Brand#22|SMALL BURNISHED COPPER|49|12 +Brand#22|SMALL BURNISHED NICKEL|9|12 +Brand#22|SMALL PLATED BRASS|9|12 +Brand#22|SMALL PLATED COPPER|3|12 +Brand#22|SMALL POLISHED NICKEL|9|12 +Brand#22|SMALL POLISHED NICKEL|49|12 +Brand#22|SMALL POLISHED STEEL|49|12 +Brand#22|STANDARD ANODIZED BRASS|23|12 +Brand#22|STANDARD ANODIZED STEEL|49|12 +Brand#22|STANDARD BRUSHED BRASS|36|12 +Brand#22|STANDARD BRUSHED TIN|19|12 +Brand#22|STANDARD BRUSHED TIN|49|12 +Brand#22|STANDARD BURNISHED TIN|14|12 +Brand#22|STANDARD PLATED BRASS|45|12 +Brand#22|STANDARD PLATED COPPER|36|12 +Brand#22|STANDARD PLATED NICKEL|9|12 +Brand#22|STANDARD PLATED STEEL|36|12 +Brand#22|STANDARD PLATED STEEL|49|12 +Brand#22|STANDARD PLATED TIN|3|12 +Brand#22|STANDARD PLATED TIN|36|12 +Brand#22|STANDARD PLATED TIN|49|12 +Brand#22|STANDARD POLISHED BRASS|19|12 +Brand#22|STANDARD POLISHED COPPER|9|12 +Brand#22|STANDARD POLISHED NICKEL|19|12 +Brand#22|STANDARD POLISHED STEEL|9|12 +Brand#22|STANDARD POLISHED TIN|45|12 +Brand#23|ECONOMY ANODIZED BRASS|36|12 +Brand#23|ECONOMY ANODIZED NICKEL|9|12 +Brand#23|ECONOMY ANODIZED STEEL|49|12 +Brand#23|ECONOMY BRUSHED COPPER|3|12 +Brand#23|ECONOMY BRUSHED COPPER|49|12 +Brand#23|ECONOMY BRUSHED NICKEL|23|12 +Brand#23|ECONOMY BURNISHED STEEL|49|12 +Brand#23|ECONOMY BURNISHED TIN|3|12 +Brand#23|ECONOMY PLATED STEEL|14|12 +Brand#23|ECONOMY PLATED TIN|49|12 +Brand#23|ECONOMY POLISHED COPPER|23|12 +Brand#23|ECONOMY POLISHED NICKEL|36|12 +Brand#23|ECONOMY POLISHED TIN|3|12 +Brand#23|LARGE ANODIZED TIN|14|12 +Brand#23|LARGE BURNISHED STEEL|23|12 +Brand#23|LARGE BURNISHED TIN|19|12 +Brand#23|LARGE PLATED COPPER|14|12 +Brand#23|LARGE PLATED STEEL|9|12 +Brand#23|LARGE POLISHED BRASS|19|12 +Brand#23|LARGE POLISHED COPPER|45|12 +Brand#23|LARGE POLISHED COPPER|49|12 +Brand#23|LARGE POLISHED TIN|3|12 +Brand#23|MEDIUM BRUSHED BRASS|9|12 +Brand#23|MEDIUM BRUSHED COPPER|3|12 +Brand#23|MEDIUM BRUSHED NICKEL|23|12 +Brand#23|MEDIUM BRUSHED NICKEL|36|12 +Brand#23|MEDIUM BURNISHED COPPER|9|12 +Brand#23|MEDIUM BURNISHED COPPER|19|12 +Brand#23|MEDIUM PLATED COPPER|19|12 +Brand#23|MEDIUM PLATED STEEL|14|12 +Brand#23|PROMO ANODIZED BRASS|9|12 +Brand#23|PROMO ANODIZED BRASS|19|12 +Brand#23|PROMO ANODIZED NICKEL|3|12 +Brand#23|PROMO ANODIZED STEEL|36|12 +Brand#23|PROMO BRUSHED COPPER|36|12 +Brand#23|PROMO BURNISHED BRASS|9|12 +Brand#23|PROMO BURNISHED STEEL|9|12 +Brand#23|PROMO BURNISHED TIN|3|12 +Brand#23|PROMO BURNISHED TIN|45|12 +Brand#23|PROMO PLATED BRASS|19|12 +Brand#23|PROMO PLATED BRASS|23|12 +Brand#23|PROMO PLATED BRASS|49|12 +Brand#23|PROMO PLATED NICKEL|3|12 +Brand#23|PROMO PLATED TIN|14|12 +Brand#23|PROMO POLISHED TIN|45|12 +Brand#23|SMALL ANODIZED STEEL|3|12 +Brand#23|SMALL ANODIZED TIN|45|12 +Brand#23|SMALL BRUSHED BRASS|19|12 +Brand#23|SMALL BRUSHED STEEL|3|12 +Brand#23|SMALL BURNISHED BRASS|14|12 +Brand#23|SMALL BURNISHED COPPER|36|12 +Brand#23|SMALL BURNISHED STEEL|45|12 +Brand#23|SMALL PLATED BRASS|49|12 +Brand#23|SMALL PLATED STEEL|23|12 +Brand#23|SMALL PLATED TIN|14|12 +Brand#23|SMALL POLISHED COPPER|49|12 +Brand#23|SMALL POLISHED TIN|23|12 +Brand#23|STANDARD ANODIZED BRASS|23|12 +Brand#23|STANDARD ANODIZED TIN|3|12 +Brand#23|STANDARD ANODIZED TIN|45|12 +Brand#23|STANDARD BRUSHED BRASS|3|12 +Brand#23|STANDARD BRUSHED STEEL|9|12 +Brand#23|STANDARD BRUSHED TIN|19|12 +Brand#23|STANDARD PLATED BRASS|3|12 +Brand#23|STANDARD PLATED NICKEL|49|12 +Brand#23|STANDARD PLATED TIN|9|12 +Brand#23|STANDARD PLATED TIN|19|12 +Brand#23|STANDARD POLISHED STEEL|23|12 +Brand#23|STANDARD POLISHED TIN|23|12 +Brand#24|ECONOMY ANODIZED BRASS|19|12 +Brand#24|ECONOMY ANODIZED COPPER|36|12 +Brand#24|ECONOMY ANODIZED COPPER|49|12 +Brand#24|ECONOMY ANODIZED NICKEL|3|12 +Brand#24|ECONOMY ANODIZED STEEL|23|12 +Brand#24|ECONOMY ANODIZED STEEL|45|12 +Brand#24|ECONOMY BRUSHED STEEL|9|12 +Brand#24|ECONOMY BRUSHED TIN|49|12 +Brand#24|ECONOMY BURNISHED BRASS|14|12 +Brand#24|ECONOMY BURNISHED COPPER|3|12 +Brand#24|ECONOMY BURNISHED COPPER|19|12 +Brand#24|ECONOMY BURNISHED STEEL|45|12 +Brand#24|ECONOMY PLATED COPPER|49|12 +Brand#24|ECONOMY PLATED STEEL|45|12 +Brand#24|ECONOMY POLISHED BRASS|23|12 +Brand#24|ECONOMY POLISHED STEEL|14|12 +Brand#24|ECONOMY POLISHED TIN|14|12 +Brand#24|ECONOMY POLISHED TIN|45|12 +Brand#24|ECONOMY POLISHED TIN|49|12 +Brand#24|LARGE ANODIZED BRASS|3|12 +Brand#24|LARGE ANODIZED BRASS|45|12 +Brand#24|LARGE BRUSHED BRASS|14|12 +Brand#24|LARGE BRUSHED BRASS|45|12 +Brand#24|LARGE BRUSHED STEEL|23|12 +Brand#24|LARGE BRUSHED STEEL|45|12 +Brand#24|LARGE BURNISHED STEEL|3|12 +Brand#24|LARGE BURNISHED TIN|23|12 +Brand#24|LARGE PLATED COPPER|23|12 +Brand#24|LARGE PLATED STEEL|3|12 +Brand#24|LARGE POLISHED COPPER|9|12 +Brand#24|LARGE POLISHED TIN|14|12 +Brand#24|MEDIUM ANODIZED BRASS|14|12 +Brand#24|MEDIUM BRUSHED NICKEL|9|12 +Brand#24|MEDIUM BRUSHED NICKEL|36|12 +Brand#24|MEDIUM BRUSHED STEEL|23|12 +Brand#24|MEDIUM BRUSHED STEEL|49|12 +Brand#24|MEDIUM BURNISHED BRASS|36|12 +Brand#24|MEDIUM BURNISHED STEEL|49|12 +Brand#24|MEDIUM BURNISHED TIN|23|12 +Brand#24|MEDIUM PLATED BRASS|3|12 +Brand#24|MEDIUM PLATED NICKEL|36|12 +Brand#24|PROMO ANODIZED NICKEL|19|12 +Brand#24|PROMO ANODIZED NICKEL|45|12 +Brand#24|PROMO ANODIZED TIN|14|12 +Brand#24|PROMO BRUSHED COPPER|23|12 +Brand#24|PROMO BRUSHED COPPER|49|12 +Brand#24|PROMO BRUSHED NICKEL|3|12 +Brand#24|PROMO BURNISHED BRASS|36|12 +Brand#24|PROMO BURNISHED STEEL|14|12 +Brand#24|PROMO BURNISHED TIN|14|12 +Brand#24|PROMO PLATED STEEL|3|12 +Brand#24|PROMO POLISHED BRASS|3|12 +Brand#24|PROMO POLISHED BRASS|14|12 +Brand#24|PROMO POLISHED COPPER|45|12 +Brand#24|SMALL ANODIZED COPPER|3|12 +Brand#24|SMALL ANODIZED NICKEL|23|12 +Brand#24|SMALL BRUSHED BRASS|45|12 +Brand#24|SMALL BRUSHED COPPER|9|12 +Brand#24|SMALL BRUSHED NICKEL|49|12 +Brand#24|SMALL BURNISHED BRASS|3|12 +Brand#24|SMALL BURNISHED BRASS|14|12 +Brand#24|SMALL BURNISHED COPPER|19|12 +Brand#24|SMALL BURNISHED NICKEL|9|12 +Brand#24|SMALL PLATED BRASS|3|12 +Brand#24|SMALL PLATED BRASS|14|12 +Brand#24|SMALL PLATED NICKEL|14|12 +Brand#24|SMALL POLISHED BRASS|3|12 +Brand#24|SMALL POLISHED NICKEL|19|12 +Brand#24|SMALL POLISHED TIN|9|12 +Brand#24|STANDARD ANODIZED TIN|49|12 +Brand#24|STANDARD BRUSHED BRASS|14|12 +Brand#24|STANDARD BRUSHED BRASS|23|12 +Brand#24|STANDARD BRUSHED NICKEL|19|12 +Brand#24|STANDARD BRUSHED STEEL|23|12 +Brand#24|STANDARD PLATED BRASS|36|12 +Brand#24|STANDARD PLATED COPPER|49|12 +Brand#24|STANDARD PLATED NICKEL|36|12 +Brand#24|STANDARD POLISHED BRASS|9|12 +Brand#24|STANDARD POLISHED COPPER|9|12 +Brand#25|ECONOMY ANODIZED STEEL|14|12 +Brand#25|ECONOMY ANODIZED STEEL|45|12 +Brand#25|ECONOMY BRUSHED NICKEL|9|12 +Brand#25|ECONOMY BRUSHED STEEL|3|12 +Brand#25|ECONOMY BRUSHED TIN|14|12 +Brand#25|ECONOMY PLATED COPPER|3|12 +Brand#25|ECONOMY PLATED NICKEL|19|12 +Brand#25|ECONOMY PLATED STEEL|9|12 +Brand#25|ECONOMY POLISHED BRASS|3|12 +Brand#25|ECONOMY POLISHED BRASS|9|12 +Brand#25|ECONOMY POLISHED NICKEL|3|12 +Brand#25|LARGE ANODIZED BRASS|14|12 +Brand#25|LARGE ANODIZED BRASS|23|12 +Brand#25|LARGE ANODIZED COPPER|19|12 +Brand#25|LARGE ANODIZED COPPER|36|12 +Brand#25|LARGE BRUSHED BRASS|19|12 +Brand#25|LARGE BRUSHED NICKEL|49|12 +Brand#25|LARGE BRUSHED STEEL|36|12 +Brand#25|LARGE BRUSHED TIN|3|12 +Brand#25|LARGE BRUSHED TIN|9|12 +Brand#25|LARGE BURNISHED BRASS|23|12 +Brand#25|LARGE BURNISHED STEEL|36|12 +Brand#25|LARGE BURNISHED TIN|14|12 +Brand#25|LARGE BURNISHED TIN|36|12 +Brand#25|LARGE PLATED NICKEL|45|12 +Brand#25|LARGE PLATED TIN|23|12 +Brand#25|MEDIUM ANODIZED BRASS|3|12 +Brand#25|MEDIUM ANODIZED BRASS|9|12 +Brand#25|MEDIUM ANODIZED BRASS|14|12 +Brand#25|MEDIUM ANODIZED BRASS|19|12 +Brand#25|MEDIUM ANODIZED STEEL|36|12 +Brand#25|MEDIUM ANODIZED TIN|3|12 +Brand#25|MEDIUM BRUSHED BRASS|14|12 +Brand#25|MEDIUM BRUSHED BRASS|49|12 +Brand#25|MEDIUM BRUSHED TIN|9|12 +Brand#25|MEDIUM BRUSHED TIN|49|12 +Brand#25|MEDIUM BURNISHED STEEL|36|12 +Brand#25|MEDIUM PLATED COPPER|14|12 +Brand#25|MEDIUM PLATED COPPER|23|12 +Brand#25|MEDIUM PLATED STEEL|36|12 +Brand#25|MEDIUM PLATED TIN|14|12 +Brand#25|PROMO ANODIZED COPPER|3|12 +Brand#25|PROMO ANODIZED NICKEL|23|12 +Brand#25|PROMO ANODIZED TIN|36|12 +Brand#25|PROMO BURNISHED COPPER|19|12 +Brand#25|PROMO BURNISHED COPPER|36|12 +Brand#25|PROMO BURNISHED COPPER|45|12 +Brand#25|PROMO BURNISHED STEEL|9|12 +Brand#25|PROMO PLATED BRASS|9|12 +Brand#25|PROMO POLISHED BRASS|3|12 +Brand#25|PROMO POLISHED BRASS|49|12 +Brand#25|PROMO POLISHED NICKEL|36|12 +Brand#25|PROMO POLISHED STEEL|45|12 +Brand#25|SMALL ANODIZED COPPER|45|12 +Brand#25|SMALL ANODIZED TIN|14|12 +Brand#25|SMALL BRUSHED COPPER|14|12 +Brand#25|SMALL BURNISHED BRASS|3|12 +Brand#25|SMALL BURNISHED NICKEL|45|12 +Brand#25|SMALL BURNISHED STEEL|14|12 +Brand#25|SMALL PLATED BRASS|19|12 +Brand#25|SMALL PLATED BRASS|49|12 +Brand#25|SMALL PLATED COPPER|23|12 +Brand#25|SMALL PLATED TIN|3|12 +Brand#25|SMALL POLISHED COPPER|9|12 +Brand#25|STANDARD BRUSHED TIN|45|12 +Brand#25|STANDARD BURNISHED BRASS|3|12 +Brand#25|STANDARD BURNISHED BRASS|14|12 +Brand#25|STANDARD BURNISHED NICKEL|36|12 +Brand#25|STANDARD PLATED COPPER|9|12 +Brand#25|STANDARD PLATED COPPER|23|12 +Brand#25|STANDARD PLATED NICKEL|36|12 +Brand#25|STANDARD PLATED NICKEL|49|12 +Brand#25|STANDARD PLATED TIN|36|12 +Brand#25|STANDARD POLISHED COPPER|23|12 +Brand#25|STANDARD POLISHED NICKEL|45|12 +Brand#25|STANDARD POLISHED TIN|3|12 +Brand#31|ECONOMY ANODIZED BRASS|19|12 +Brand#31|ECONOMY ANODIZED TIN|36|12 +Brand#31|ECONOMY BRUSHED NICKEL|14|12 +Brand#31|ECONOMY BURNISHED COPPER|14|12 +Brand#31|ECONOMY BURNISHED NICKEL|19|12 +Brand#31|ECONOMY PLATED NICKEL|9|12 +Brand#31|ECONOMY POLISHED COPPER|3|12 +Brand#31|ECONOMY POLISHED TIN|36|12 +Brand#31|LARGE ANODIZED COPPER|3|12 +Brand#31|LARGE ANODIZED COPPER|14|12 +Brand#31|LARGE ANODIZED STEEL|36|12 +Brand#31|LARGE ANODIZED TIN|3|12 +Brand#31|LARGE BRUSHED BRASS|36|12 +Brand#31|LARGE BRUSHED NICKEL|19|12 +Brand#31|LARGE BRUSHED STEEL|36|12 +Brand#31|LARGE BRUSHED TIN|14|12 +Brand#31|LARGE BURNISHED BRASS|36|12 +Brand#31|LARGE BURNISHED NICKEL|14|12 +Brand#31|LARGE PLATED STEEL|23|12 +Brand#31|LARGE POLISHED BRASS|9|12 +Brand#31|LARGE POLISHED STEEL|45|12 +Brand#31|MEDIUM ANODIZED STEEL|14|12 +Brand#31|MEDIUM ANODIZED TIN|9|12 +Brand#31|MEDIUM ANODIZED TIN|23|12 +Brand#31|MEDIUM BRUSHED BRASS|23|12 +Brand#31|MEDIUM BRUSHED STEEL|3|12 +Brand#31|MEDIUM BURNISHED BRASS|14|12 +Brand#31|MEDIUM BURNISHED STEEL|9|12 +Brand#31|PROMO ANODIZED COPPER|14|12 +Brand#31|PROMO ANODIZED TIN|36|12 +Brand#31|PROMO BRUSHED BRASS|3|12 +Brand#31|PROMO BRUSHED COPPER|23|12 +Brand#31|PROMO BRUSHED STEEL|23|12 +Brand#31|PROMO BURNISHED BRASS|49|12 +Brand#31|PROMO BURNISHED STEEL|3|12 +Brand#31|PROMO PLATED BRASS|36|12 +Brand#31|PROMO POLISHED NICKEL|49|12 +Brand#31|SMALL ANODIZED COPPER|3|12 +Brand#31|SMALL ANODIZED NICKEL|9|12 +Brand#31|SMALL ANODIZED TIN|3|12 +Brand#31|SMALL BRUSHED COPPER|14|12 +Brand#31|SMALL BRUSHED COPPER|19|12 +Brand#31|SMALL BRUSHED NICKEL|3|12 +Brand#31|SMALL BRUSHED NICKEL|23|12 +Brand#31|SMALL BRUSHED NICKEL|36|12 +Brand#31|SMALL BURNISHED BRASS|3|12 +Brand#31|SMALL BURNISHED NICKEL|9|12 +Brand#31|SMALL BURNISHED TIN|23|12 +Brand#31|SMALL PLATED STEEL|19|12 +Brand#31|SMALL PLATED STEEL|23|12 +Brand#31|SMALL POLISHED STEEL|3|12 +Brand#31|STANDARD ANODIZED BRASS|45|12 +Brand#31|STANDARD ANODIZED NICKEL|3|12 +Brand#31|STANDARD BRUSHED COPPER|3|12 +Brand#31|STANDARD BURNISHED STEEL|45|12 +Brand#31|STANDARD PLATED BRASS|3|12 +Brand#31|STANDARD PLATED BRASS|19|12 +Brand#31|STANDARD PLATED STEEL|19|12 +Brand#31|STANDARD POLISHED BRASS|23|12 +Brand#31|STANDARD POLISHED COPPER|45|12 +Brand#32|ECONOMY ANODIZED BRASS|14|12 +Brand#32|ECONOMY ANODIZED STEEL|23|12 +Brand#32|ECONOMY ANODIZED STEEL|49|12 +Brand#32|ECONOMY ANODIZED TIN|23|12 +Brand#32|ECONOMY BRUSHED NICKEL|3|12 +Brand#32|ECONOMY BRUSHED STEEL|36|12 +Brand#32|ECONOMY BRUSHED TIN|19|12 +Brand#32|ECONOMY BURNISHED TIN|19|12 +Brand#32|ECONOMY PLATED BRASS|19|12 +Brand#32|ECONOMY PLATED NICKEL|23|12 +Brand#32|ECONOMY PLATED TIN|45|12 +Brand#32|LARGE ANODIZED NICKEL|3|12 +Brand#32|LARGE ANODIZED STEEL|14|12 +Brand#32|LARGE BRUSHED BRASS|45|12 +Brand#32|LARGE BRUSHED NICKEL|3|12 +Brand#32|LARGE BRUSHED STEEL|45|12 +Brand#32|LARGE BRUSHED TIN|19|12 +Brand#32|LARGE PLATED BRASS|3|12 +Brand#32|LARGE PLATED BRASS|9|12 +Brand#32|LARGE POLISHED COPPER|19|12 +Brand#32|LARGE POLISHED NICKEL|3|12 +Brand#32|MEDIUM ANODIZED COPPER|45|12 +Brand#32|MEDIUM ANODIZED STEEL|19|12 +Brand#32|MEDIUM ANODIZED STEEL|49|12 +Brand#32|MEDIUM ANODIZED TIN|45|12 +Brand#32|MEDIUM ANODIZED TIN|49|12 +Brand#32|MEDIUM BURNISHED BRASS|23|12 +Brand#32|MEDIUM BURNISHED NICKEL|23|12 +Brand#32|MEDIUM PLATED BRASS|49|12 +Brand#32|MEDIUM PLATED TIN|3|12 +Brand#32|PROMO ANODIZED NICKEL|49|12 +Brand#32|PROMO BRUSHED COPPER|45|12 +Brand#32|PROMO BRUSHED STEEL|23|12 +Brand#32|PROMO BRUSHED STEEL|49|12 +Brand#32|PROMO BRUSHED TIN|14|12 +Brand#32|PROMO BRUSHED TIN|36|12 +Brand#32|PROMO BURNISHED NICKEL|45|12 +Brand#32|PROMO BURNISHED TIN|49|12 +Brand#32|PROMO PLATED COPPER|49|12 +Brand#32|PROMO PLATED STEEL|49|12 +Brand#32|PROMO POLISHED STEEL|49|12 +Brand#32|PROMO POLISHED TIN|19|12 +Brand#32|PROMO POLISHED TIN|23|12 +Brand#32|PROMO POLISHED TIN|45|12 +Brand#32|SMALL ANODIZED NICKEL|9|12 +Brand#32|SMALL BRUSHED TIN|3|12 +Brand#32|SMALL BRUSHED TIN|9|12 +Brand#32|SMALL BURNISHED TIN|23|12 +Brand#32|SMALL BURNISHED TIN|36|12 +Brand#32|SMALL PLATED BRASS|36|12 +Brand#32|SMALL PLATED COPPER|14|12 +Brand#32|SMALL PLATED COPPER|45|12 +Brand#32|SMALL PLATED STEEL|36|12 +Brand#32|SMALL PLATED TIN|14|12 +Brand#32|SMALL POLISHED NICKEL|45|12 +Brand#32|SMALL POLISHED STEEL|23|12 +Brand#32|SMALL POLISHED STEEL|36|12 +Brand#32|STANDARD ANODIZED NICKEL|9|12 +Brand#32|STANDARD ANODIZED STEEL|3|12 +Brand#32|STANDARD ANODIZED TIN|14|12 +Brand#32|STANDARD ANODIZED TIN|19|12 +Brand#32|STANDARD BRUSHED BRASS|14|12 +Brand#32|STANDARD BRUSHED STEEL|14|12 +Brand#32|STANDARD BRUSHED TIN|9|12 +Brand#32|STANDARD BURNISHED BRASS|45|12 +Brand#32|STANDARD BURNISHED COPPER|3|12 +Brand#32|STANDARD BURNISHED NICKEL|3|12 +Brand#32|STANDARD PLATED STEEL|9|12 +Brand#32|STANDARD PLATED STEEL|49|12 +Brand#32|STANDARD POLISHED COPPER|36|12 +Brand#33|ECONOMY ANODIZED NICKEL|36|12 +Brand#33|ECONOMY ANODIZED STEEL|23|12 +Brand#33|ECONOMY ANODIZED STEEL|45|12 +Brand#33|ECONOMY BURNISHED NICKEL|14|12 +Brand#33|ECONOMY BURNISHED TIN|45|12 +Brand#33|ECONOMY PLATED STEEL|3|12 +Brand#33|ECONOMY PLATED TIN|3|12 +Brand#33|ECONOMY PLATED TIN|9|12 +Brand#33|ECONOMY POLISHED BRASS|3|12 +Brand#33|ECONOMY POLISHED BRASS|14|12 +Brand#33|LARGE ANODIZED BRASS|3|12 +Brand#33|LARGE ANODIZED BRASS|36|12 +Brand#33|LARGE ANODIZED NICKEL|23|12 +Brand#33|LARGE ANODIZED STEEL|3|12 +Brand#33|LARGE ANODIZED TIN|36|12 +Brand#33|LARGE BRUSHED BRASS|23|12 +Brand#33|LARGE BRUSHED STEEL|3|12 +Brand#33|LARGE BRUSHED TIN|36|12 +Brand#33|LARGE BURNISHED BRASS|19|12 +Brand#33|LARGE BURNISHED BRASS|49|12 +Brand#33|LARGE PLATED NICKEL|9|12 +Brand#33|LARGE PLATED NICKEL|19|12 +Brand#33|LARGE POLISHED BRASS|9|12 +Brand#33|LARGE POLISHED NICKEL|45|12 +Brand#33|MEDIUM ANODIZED NICKEL|19|12 +Brand#33|MEDIUM ANODIZED TIN|49|12 +Brand#33|MEDIUM BRUSHED BRASS|45|12 +Brand#33|MEDIUM BRUSHED NICKEL|14|12 +Brand#33|MEDIUM BRUSHED STEEL|14|12 +Brand#33|MEDIUM BRUSHED STEEL|36|12 +Brand#33|MEDIUM BURNISHED BRASS|49|12 +Brand#33|MEDIUM BURNISHED TIN|3|12 +Brand#33|MEDIUM BURNISHED TIN|49|12 +Brand#33|MEDIUM PLATED STEEL|3|12 +Brand#33|MEDIUM PLATED TIN|23|12 +Brand#33|PROMO ANODIZED STEEL|23|12 +Brand#33|PROMO ANODIZED TIN|9|12 +Brand#33|PROMO ANODIZED TIN|49|12 +Brand#33|PROMO BRUSHED BRASS|3|12 +Brand#33|PROMO BRUSHED BRASS|19|12 +Brand#33|PROMO BRUSHED TIN|49|12 +Brand#33|PROMO BURNISHED NICKEL|23|12 +Brand#33|PROMO BURNISHED TIN|3|12 +Brand#33|PROMO BURNISHED TIN|19|12 +Brand#33|PROMO BURNISHED TIN|23|12 +Brand#33|PROMO BURNISHED TIN|36|12 +Brand#33|PROMO BURNISHED TIN|49|12 +Brand#33|PROMO PLATED BRASS|23|12 +Brand#33|PROMO PLATED BRASS|36|12 +Brand#33|PROMO POLISHED COPPER|3|12 +Brand#33|PROMO POLISHED NICKEL|3|12 +Brand#33|PROMO POLISHED STEEL|23|12 +Brand#33|SMALL ANODIZED STEEL|14|12 +Brand#33|SMALL ANODIZED STEEL|49|12 +Brand#33|SMALL ANODIZED TIN|19|12 +Brand#33|SMALL BRUSHED BRASS|36|12 +Brand#33|SMALL BRUSHED NICKEL|19|12 +Brand#33|SMALL BRUSHED NICKEL|45|12 +Brand#33|SMALL BURNISHED BRASS|36|12 +Brand#33|SMALL BURNISHED TIN|9|12 +Brand#33|SMALL PLATED BRASS|14|12 +Brand#33|SMALL PLATED NICKEL|49|12 +Brand#33|SMALL PLATED STEEL|3|12 +Brand#33|SMALL POLISHED NICKEL|9|12 +Brand#33|STANDARD ANODIZED STEEL|14|12 +Brand#33|STANDARD ANODIZED STEEL|45|12 +Brand#33|STANDARD ANODIZED TIN|9|12 +Brand#33|STANDARD BRUSHED BRASS|19|12 +Brand#33|STANDARD BRUSHED NICKEL|14|12 +Brand#33|STANDARD BURNISHED BRASS|9|12 +Brand#33|STANDARD BURNISHED TIN|23|12 +Brand#33|STANDARD POLISHED STEEL|45|12 +Brand#34|ECONOMY ANODIZED NICKEL|9|12 +Brand#34|ECONOMY ANODIZED NICKEL|49|12 +Brand#34|ECONOMY ANODIZED STEEL|45|12 +Brand#34|ECONOMY BURNISHED COPPER|9|12 +Brand#34|ECONOMY BURNISHED COPPER|23|12 +Brand#34|ECONOMY BURNISHED COPPER|36|12 +Brand#34|ECONOMY BURNISHED NICKEL|19|12 +Brand#34|ECONOMY BURNISHED NICKEL|49|12 +Brand#34|ECONOMY BURNISHED STEEL|9|12 +Brand#34|ECONOMY BURNISHED TIN|14|12 +Brand#34|ECONOMY PLATED BRASS|3|12 +Brand#34|ECONOMY PLATED COPPER|3|12 +Brand#34|ECONOMY PLATED TIN|3|12 +Brand#34|ECONOMY PLATED TIN|14|12 +Brand#34|ECONOMY POLISHED TIN|36|12 +Brand#34|LARGE ANODIZED COPPER|3|12 +Brand#34|LARGE ANODIZED NICKEL|3|12 +Brand#34|LARGE ANODIZED NICKEL|49|12 +Brand#34|LARGE BRUSHED COPPER|36|12 +Brand#34|LARGE BRUSHED NICKEL|19|12 +Brand#34|LARGE BRUSHED NICKEL|49|12 +Brand#34|LARGE BURNISHED COPPER|23|12 +Brand#34|LARGE BURNISHED NICKEL|23|12 +Brand#34|LARGE BURNISHED TIN|14|12 +Brand#34|LARGE BURNISHED TIN|23|12 +Brand#34|LARGE BURNISHED TIN|49|12 +Brand#34|LARGE PLATED COPPER|9|12 +Brand#34|LARGE PLATED TIN|14|12 +Brand#34|LARGE POLISHED BRASS|3|12 +Brand#34|LARGE POLISHED BRASS|45|12 +Brand#34|LARGE POLISHED COPPER|3|12 +Brand#34|LARGE POLISHED NICKEL|3|12 +Brand#34|LARGE POLISHED NICKEL|49|12 +Brand#34|MEDIUM ANODIZED BRASS|45|12 +Brand#34|MEDIUM BRUSHED BRASS|49|12 +Brand#34|MEDIUM BRUSHED COPPER|9|12 +Brand#34|MEDIUM BRUSHED COPPER|23|12 +Brand#34|MEDIUM BRUSHED NICKEL|9|12 +Brand#34|MEDIUM BRUSHED STEEL|45|12 +Brand#34|MEDIUM BRUSHED TIN|36|12 +Brand#34|MEDIUM BURNISHED BRASS|14|12 +Brand#34|MEDIUM BURNISHED NICKEL|3|12 +Brand#34|MEDIUM PLATED BRASS|23|12 +Brand#34|PROMO ANODIZED NICKEL|3|12 +Brand#34|PROMO BRUSHED COPPER|49|12 +Brand#34|PROMO BRUSHED NICKEL|49|12 +Brand#34|PROMO BURNISHED STEEL|14|12 +Brand#34|PROMO PLATED BRASS|3|12 +Brand#34|PROMO PLATED BRASS|36|12 +Brand#34|PROMO PLATED TIN|49|12 +Brand#34|PROMO POLISHED BRASS|14|12 +Brand#34|PROMO POLISHED COPPER|23|12 +Brand#34|PROMO POLISHED NICKEL|49|12 +Brand#34|SMALL ANODIZED BRASS|19|12 +Brand#34|SMALL ANODIZED COPPER|14|12 +Brand#34|SMALL ANODIZED STEEL|19|12 +Brand#34|SMALL ANODIZED TIN|9|12 +Brand#34|SMALL BRUSHED COPPER|14|12 +Brand#34|SMALL BURNISHED BRASS|9|12 +Brand#34|SMALL BURNISHED BRASS|23|12 +Brand#34|SMALL BURNISHED COPPER|9|12 +Brand#34|SMALL BURNISHED COPPER|36|12 +Brand#34|SMALL BURNISHED NICKEL|9|12 +Brand#34|SMALL BURNISHED NICKEL|14|12 +Brand#34|SMALL BURNISHED NICKEL|36|12 +Brand#34|SMALL BURNISHED STEEL|14|12 +Brand#34|SMALL PLATED BRASS|14|12 +Brand#34|SMALL PLATED TIN|45|12 +Brand#34|SMALL POLISHED STEEL|19|12 +Brand#34|STANDARD ANODIZED BRASS|36|12 +Brand#34|STANDARD ANODIZED TIN|3|12 +Brand#34|STANDARD ANODIZED TIN|14|12 +Brand#34|STANDARD BRUSHED BRASS|36|12 +Brand#34|STANDARD BRUSHED COPPER|3|12 +Brand#34|STANDARD BRUSHED STEEL|23|12 +Brand#34|STANDARD BRUSHED TIN|45|12 +Brand#34|STANDARD BURNISHED STEEL|14|12 +Brand#34|STANDARD BURNISHED TIN|45|12 +Brand#34|STANDARD POLISHED COPPER|14|12 +Brand#35|ECONOMY ANODIZED BRASS|14|12 +Brand#35|ECONOMY ANODIZED COPPER|19|12 +Brand#35|ECONOMY ANODIZED NICKEL|14|12 +Brand#35|ECONOMY ANODIZED STEEL|14|12 +Brand#35|ECONOMY ANODIZED STEEL|45|12 +Brand#35|ECONOMY BRUSHED BRASS|36|12 +Brand#35|ECONOMY BRUSHED NICKEL|49|12 +Brand#35|ECONOMY BURNISHED BRASS|19|12 +Brand#35|ECONOMY BURNISHED BRASS|36|12 +Brand#35|ECONOMY BURNISHED STEEL|36|12 +Brand#35|ECONOMY PLATED TIN|45|12 +Brand#35|ECONOMY PLATED TIN|49|12 +Brand#35|ECONOMY POLISHED COPPER|9|12 +Brand#35|ECONOMY POLISHED NICKEL|23|12 +Brand#35|ECONOMY POLISHED STEEL|9|12 +Brand#35|ECONOMY POLISHED TIN|23|12 +Brand#35|LARGE ANODIZED BRASS|3|12 +Brand#35|LARGE ANODIZED BRASS|45|12 +Brand#35|LARGE ANODIZED COPPER|19|12 +Brand#35|LARGE ANODIZED COPPER|36|12 +Brand#35|LARGE ANODIZED STEEL|45|12 +Brand#35|LARGE ANODIZED TIN|45|12 +Brand#35|LARGE BRUSHED COPPER|23|12 +Brand#35|LARGE BRUSHED NICKEL|36|12 +Brand#35|LARGE BRUSHED STEEL|3|12 +Brand#35|LARGE BRUSHED TIN|36|12 +Brand#35|LARGE BURNISHED BRASS|45|12 +Brand#35|LARGE BURNISHED STEEL|9|12 +Brand#35|LARGE BURNISHED STEEL|45|12 +Brand#35|LARGE BURNISHED TIN|49|12 +Brand#35|LARGE PLATED BRASS|3|12 +Brand#35|LARGE PLATED BRASS|23|12 +Brand#35|LARGE PLATED STEEL|19|12 +Brand#35|LARGE PLATED STEEL|49|12 +Brand#35|MEDIUM ANODIZED TIN|3|12 +Brand#35|MEDIUM BRUSHED BRASS|49|12 +Brand#35|MEDIUM BRUSHED COPPER|14|12 +Brand#35|MEDIUM BRUSHED NICKEL|3|12 +Brand#35|MEDIUM BRUSHED STEEL|45|12 +Brand#35|MEDIUM BURNISHED STEEL|19|12 +Brand#35|MEDIUM PLATED NICKEL|45|12 +Brand#35|MEDIUM PLATED STEEL|3|12 +Brand#35|MEDIUM PLATED TIN|36|12 +Brand#35|PROMO ANODIZED BRASS|14|12 +Brand#35|PROMO ANODIZED STEEL|3|12 +Brand#35|PROMO ANODIZED STEEL|23|12 +Brand#35|PROMO ANODIZED TIN|49|12 +Brand#35|PROMO BRUSHED COPPER|9|12 +Brand#35|PROMO BRUSHED COPPER|23|12 +Brand#35|PROMO BRUSHED STEEL|36|12 +Brand#35|PROMO BURNISHED NICKEL|19|12 +Brand#35|PROMO BURNISHED STEEL|3|12 +Brand#35|PROMO BURNISHED STEEL|14|12 +Brand#35|PROMO BURNISHED STEEL|49|12 +Brand#35|PROMO BURNISHED TIN|9|12 +Brand#35|PROMO BURNISHED TIN|14|12 +Brand#35|PROMO POLISHED BRASS|19|12 +Brand#35|PROMO POLISHED COPPER|49|12 +Brand#35|PROMO POLISHED NICKEL|49|12 +Brand#35|PROMO POLISHED STEEL|9|12 +Brand#35|PROMO POLISHED TIN|36|12 +Brand#35|SMALL ANODIZED BRASS|9|12 +Brand#35|SMALL ANODIZED BRASS|19|12 +Brand#35|SMALL BRUSHED NICKEL|19|12 +Brand#35|SMALL BRUSHED STEEL|45|12 +Brand#35|SMALL BRUSHED TIN|45|12 +Brand#35|SMALL BURNISHED BRASS|9|12 +Brand#35|SMALL BURNISHED BRASS|23|12 +Brand#35|SMALL BURNISHED BRASS|36|12 +Brand#35|SMALL BURNISHED BRASS|49|12 +Brand#35|SMALL BURNISHED COPPER|45|12 +Brand#35|SMALL PLATED BRASS|9|12 +Brand#35|SMALL PLATED BRASS|36|12 +Brand#35|SMALL PLATED TIN|36|12 +Brand#35|STANDARD ANODIZED TIN|3|12 +Brand#35|STANDARD ANODIZED TIN|9|12 +Brand#35|STANDARD BURNISHED BRASS|36|12 +Brand#35|STANDARD BURNISHED STEEL|49|12 +Brand#35|STANDARD PLATED BRASS|49|12 +Brand#35|STANDARD PLATED COPPER|9|12 +Brand#35|STANDARD PLATED NICKEL|23|12 +Brand#35|STANDARD PLATED NICKEL|49|12 +Brand#35|STANDARD PLATED STEEL|23|12 +Brand#35|STANDARD PLATED TIN|45|12 +Brand#35|STANDARD POLISHED STEEL|23|12 +Brand#35|STANDARD POLISHED TIN|3|12 +Brand#41|ECONOMY ANODIZED BRASS|45|12 +Brand#41|ECONOMY ANODIZED TIN|14|12 +Brand#41|ECONOMY BRUSHED BRASS|23|12 +Brand#41|ECONOMY BRUSHED NICKEL|49|12 +Brand#41|ECONOMY BRUSHED STEEL|36|12 +Brand#41|ECONOMY BRUSHED TIN|45|12 +Brand#41|ECONOMY BURNISHED COPPER|3|12 +Brand#41|ECONOMY BURNISHED COPPER|45|12 +Brand#41|ECONOMY PLATED NICKEL|23|12 +Brand#41|ECONOMY PLATED STEEL|36|12 +Brand#41|ECONOMY PLATED TIN|23|12 +Brand#41|ECONOMY POLISHED BRASS|36|12 +Brand#41|ECONOMY POLISHED COPPER|49|12 +Brand#41|ECONOMY POLISHED NICKEL|9|12 +Brand#41|ECONOMY POLISHED NICKEL|19|12 +Brand#41|ECONOMY POLISHED NICKEL|23|12 +Brand#41|ECONOMY POLISHED STEEL|49|12 +Brand#41|LARGE ANODIZED BRASS|14|12 +Brand#41|LARGE ANODIZED BRASS|23|12 +Brand#41|LARGE ANODIZED COPPER|36|12 +Brand#41|LARGE ANODIZED STEEL|23|12 +Brand#41|LARGE BRUSHED BRASS|9|12 +Brand#41|LARGE BRUSHED COPPER|23|12 +Brand#41|LARGE BURNISHED BRASS|36|12 +Brand#41|LARGE BURNISHED STEEL|23|12 +Brand#41|LARGE PLATED NICKEL|14|12 +Brand#41|LARGE POLISHED BRASS|45|12 +Brand#41|LARGE POLISHED COPPER|23|12 +Brand#41|LARGE POLISHED COPPER|36|12 +Brand#41|LARGE POLISHED STEEL|3|12 +Brand#41|LARGE POLISHED STEEL|9|12 +Brand#41|MEDIUM ANODIZED NICKEL|3|12 +Brand#41|MEDIUM ANODIZED TIN|3|12 +Brand#41|MEDIUM BURNISHED COPPER|23|12 +Brand#41|MEDIUM BURNISHED TIN|14|12 +Brand#41|MEDIUM BURNISHED TIN|45|12 +Brand#41|MEDIUM PLATED BRASS|19|12 +Brand#41|MEDIUM PLATED COPPER|19|12 +Brand#41|MEDIUM PLATED COPPER|45|12 +Brand#41|PROMO ANODIZED BRASS|14|12 +Brand#41|PROMO ANODIZED NICKEL|49|12 +Brand#41|PROMO ANODIZED TIN|9|12 +Brand#41|PROMO BURNISHED COPPER|49|12 +Brand#41|PROMO BURNISHED TIN|14|12 +Brand#41|PROMO PLATED NICKEL|14|12 +Brand#41|PROMO PLATED STEEL|45|12 +Brand#41|PROMO PLATED TIN|3|12 +Brand#41|PROMO PLATED TIN|36|12 +Brand#41|PROMO POLISHED COPPER|23|12 +Brand#41|PROMO POLISHED NICKEL|19|12 +Brand#41|SMALL ANODIZED BRASS|3|12 +Brand#41|SMALL ANODIZED COPPER|14|12 +Brand#41|SMALL ANODIZED NICKEL|36|12 +Brand#41|SMALL BRUSHED STEEL|36|12 +Brand#41|SMALL BRUSHED TIN|14|12 +Brand#41|SMALL BURNISHED TIN|3|12 +Brand#41|SMALL PLATED BRASS|14|12 +Brand#41|SMALL PLATED STEEL|14|12 +Brand#41|SMALL POLISHED COPPER|36|12 +Brand#41|SMALL POLISHED TIN|36|12 +Brand#41|STANDARD ANODIZED BRASS|3|12 +Brand#41|STANDARD ANODIZED BRASS|36|12 +Brand#41|STANDARD ANODIZED COPPER|14|12 +Brand#41|STANDARD ANODIZED NICKEL|36|12 +Brand#41|STANDARD BURNISHED STEEL|9|12 +Brand#41|STANDARD BURNISHED TIN|3|12 +Brand#41|STANDARD PLATED BRASS|45|12 +Brand#41|STANDARD PLATED COPPER|49|12 +Brand#41|STANDARD POLISHED COPPER|23|12 +Brand#41|STANDARD POLISHED NICKEL|3|12 +Brand#42|ECONOMY ANODIZED BRASS|36|12 +Brand#42|ECONOMY ANODIZED STEEL|9|12 +Brand#42|ECONOMY BRUSHED NICKEL|45|12 +Brand#42|ECONOMY BRUSHED TIN|14|12 +Brand#42|ECONOMY BURNISHED NICKEL|49|12 +Brand#42|ECONOMY BURNISHED STEEL|49|12 +Brand#42|ECONOMY BURNISHED TIN|19|12 +Brand#42|ECONOMY PLATED COPPER|14|12 +Brand#42|ECONOMY PLATED NICKEL|9|12 +Brand#42|ECONOMY POLISHED COPPER|9|12 +Brand#42|LARGE ANODIZED BRASS|49|12 +Brand#42|LARGE ANODIZED COPPER|36|12 +Brand#42|LARGE BURNISHED COPPER|9|12 +Brand#42|LARGE BURNISHED COPPER|19|12 +Brand#42|LARGE BURNISHED TIN|9|12 +Brand#42|LARGE PLATED BRASS|23|12 +Brand#42|LARGE PLATED BRASS|36|12 +Brand#42|LARGE PLATED NICKEL|23|12 +Brand#42|LARGE PLATED TIN|9|12 +Brand#42|LARGE PLATED TIN|19|12 +Brand#42|LARGE POLISHED BRASS|36|12 +Brand#42|LARGE POLISHED STEEL|9|12 +Brand#42|LARGE POLISHED STEEL|45|12 +Brand#42|LARGE POLISHED TIN|14|12 +Brand#42|MEDIUM ANODIZED NICKEL|19|12 +Brand#42|MEDIUM ANODIZED STEEL|23|12 +Brand#42|MEDIUM ANODIZED TIN|49|12 +Brand#42|MEDIUM BRUSHED NICKEL|9|12 +Brand#42|MEDIUM BRUSHED STEEL|19|12 +Brand#42|MEDIUM BRUSHED TIN|14|12 +Brand#42|MEDIUM BURNISHED BRASS|36|12 +Brand#42|MEDIUM BURNISHED NICKEL|36|12 +Brand#42|MEDIUM BURNISHED STEEL|49|12 +Brand#42|MEDIUM PLATED BRASS|36|12 +Brand#42|MEDIUM PLATED COPPER|36|12 +Brand#42|MEDIUM PLATED COPPER|45|12 +Brand#42|MEDIUM PLATED STEEL|3|12 +Brand#42|MEDIUM PLATED TIN|45|12 +Brand#42|PROMO ANODIZED TIN|23|12 +Brand#42|PROMO BRUSHED BRASS|19|12 +Brand#42|PROMO BRUSHED NICKEL|3|12 +Brand#42|PROMO BRUSHED TIN|45|12 +Brand#42|PROMO BURNISHED BRASS|19|12 +Brand#42|PROMO BURNISHED NICKEL|3|12 +Brand#42|PROMO BURNISHED TIN|9|12 +Brand#42|PROMO PLATED BRASS|14|12 +Brand#42|PROMO PLATED BRASS|23|12 +Brand#42|PROMO PLATED STEEL|19|12 +Brand#42|PROMO POLISHED STEEL|45|12 +Brand#42|SMALL ANODIZED BRASS|36|12 +Brand#42|SMALL BRUSHED BRASS|36|12 +Brand#42|SMALL BURNISHED BRASS|3|12 +Brand#42|SMALL BURNISHED BRASS|36|12 +Brand#42|SMALL BURNISHED STEEL|23|12 +Brand#42|SMALL BURNISHED TIN|9|12 +Brand#42|SMALL BURNISHED TIN|49|12 +Brand#42|SMALL PLATED COPPER|9|12 +Brand#42|SMALL PLATED COPPER|19|12 +Brand#42|SMALL POLISHED BRASS|3|12 +Brand#42|SMALL POLISHED COPPER|36|12 +Brand#42|SMALL POLISHED NICKEL|23|12 +Brand#42|STANDARD ANODIZED BRASS|23|12 +Brand#42|STANDARD ANODIZED COPPER|45|12 +Brand#42|STANDARD ANODIZED STEEL|23|12 +Brand#42|STANDARD ANODIZED TIN|23|12 +Brand#42|STANDARD BRUSHED TIN|3|12 +Brand#42|STANDARD BURNISHED COPPER|36|12 +Brand#42|STANDARD BURNISHED TIN|23|12 +Brand#42|STANDARD PLATED COPPER|9|12 +Brand#42|STANDARD PLATED TIN|3|12 +Brand#42|STANDARD POLISHED NICKEL|9|12 +Brand#42|STANDARD POLISHED STEEL|14|12 +Brand#43|ECONOMY ANODIZED BRASS|14|12 +Brand#43|ECONOMY ANODIZED COPPER|9|12 +Brand#43|ECONOMY ANODIZED COPPER|19|12 +Brand#43|ECONOMY ANODIZED COPPER|45|12 +Brand#43|ECONOMY BRUSHED STEEL|9|12 +Brand#43|ECONOMY BRUSHED STEEL|14|12 +Brand#43|ECONOMY BRUSHED STEEL|36|12 +Brand#43|ECONOMY BRUSHED STEEL|45|12 +Brand#43|ECONOMY BRUSHED TIN|49|12 +Brand#43|ECONOMY BURNISHED BRASS|3|12 +Brand#43|ECONOMY BURNISHED BRASS|49|12 +Brand#43|ECONOMY BURNISHED NICKEL|3|12 +Brand#43|ECONOMY BURNISHED NICKEL|36|12 +Brand#43|ECONOMY BURNISHED STEEL|9|12 +Brand#43|ECONOMY BURNISHED TIN|19|12 +Brand#43|ECONOMY PLATED COPPER|3|12 +Brand#43|ECONOMY PLATED STEEL|3|12 +Brand#43|ECONOMY POLISHED BRASS|45|12 +Brand#43|ECONOMY POLISHED NICKEL|45|12 +Brand#43|ECONOMY POLISHED TIN|49|12 +Brand#43|LARGE ANODIZED TIN|14|12 +Brand#43|LARGE BRUSHED NICKEL|23|12 +Brand#43|LARGE BRUSHED STEEL|45|12 +Brand#43|LARGE BURNISHED COPPER|14|12 +Brand#43|LARGE BURNISHED NICKEL|3|12 +Brand#43|LARGE BURNISHED STEEL|3|12 +Brand#43|LARGE BURNISHED TIN|45|12 +Brand#43|LARGE PLATED TIN|9|12 +Brand#43|LARGE POLISHED BRASS|9|12 +Brand#43|LARGE POLISHED COPPER|23|12 +Brand#43|LARGE POLISHED NICKEL|9|12 +Brand#43|LARGE POLISHED TIN|45|12 +Brand#43|MEDIUM ANODIZED BRASS|14|12 +Brand#43|MEDIUM ANODIZED BRASS|19|12 +Brand#43|MEDIUM ANODIZED BRASS|36|12 +Brand#43|MEDIUM ANODIZED COPPER|45|12 +Brand#43|MEDIUM ANODIZED NICKEL|36|12 +Brand#43|MEDIUM BRUSHED BRASS|45|12 +Brand#43|MEDIUM BURNISHED BRASS|36|12 +Brand#43|MEDIUM BURNISHED BRASS|45|12 +Brand#43|MEDIUM BURNISHED BRASS|49|12 +Brand#43|MEDIUM BURNISHED COPPER|3|12 +Brand#43|MEDIUM BURNISHED COPPER|14|12 +Brand#43|MEDIUM PLATED BRASS|3|12 +Brand#43|MEDIUM PLATED BRASS|49|12 +Brand#43|MEDIUM PLATED COPPER|19|12 +Brand#43|PROMO ANODIZED NICKEL|19|12 +Brand#43|PROMO ANODIZED STEEL|9|12 +Brand#43|PROMO ANODIZED TIN|9|12 +Brand#43|PROMO BRUSHED NICKEL|23|12 +Brand#43|PROMO BRUSHED TIN|49|12 +Brand#43|PROMO BURNISHED STEEL|36|12 +Brand#43|PROMO BURNISHED STEEL|45|12 +Brand#43|PROMO BURNISHED TIN|14|12 +Brand#43|PROMO PLATED NICKEL|9|12 +Brand#43|PROMO PLATED NICKEL|14|12 +Brand#43|PROMO PLATED STEEL|9|12 +Brand#43|PROMO POLISHED COPPER|23|12 +Brand#43|PROMO POLISHED NICKEL|3|12 +Brand#43|PROMO POLISHED STEEL|3|12 +Brand#43|PROMO POLISHED STEEL|36|12 +Brand#43|SMALL ANODIZED NICKEL|3|12 +Brand#43|SMALL ANODIZED NICKEL|23|12 +Brand#43|SMALL BRUSHED BRASS|49|12 +Brand#43|SMALL BRUSHED COPPER|36|12 +Brand#43|SMALL BRUSHED NICKEL|36|12 +Brand#43|SMALL BRUSHED STEEL|9|12 +Brand#43|SMALL BURNISHED COPPER|49|12 +Brand#43|SMALL BURNISHED NICKEL|45|12 +Brand#43|SMALL PLATED BRASS|36|12 +Brand#43|SMALL PLATED COPPER|9|12 +Brand#43|SMALL PLATED COPPER|49|12 +Brand#43|SMALL POLISHED NICKEL|14|12 +Brand#43|SMALL POLISHED TIN|49|12 +Brand#43|STANDARD ANODIZED BRASS|36|12 +Brand#43|STANDARD ANODIZED NICKEL|14|12 +Brand#43|STANDARD ANODIZED TIN|9|12 +Brand#43|STANDARD ANODIZED TIN|49|12 +Brand#43|STANDARD BRUSHED BRASS|3|12 +Brand#43|STANDARD BRUSHED COPPER|19|12 +Brand#43|STANDARD BURNISHED STEEL|23|12 +Brand#43|STANDARD BURNISHED TIN|14|12 +Brand#43|STANDARD PLATED BRASS|19|12 +Brand#43|STANDARD PLATED NICKEL|14|12 +Brand#43|STANDARD PLATED NICKEL|23|12 +Brand#43|STANDARD PLATED NICKEL|36|12 +Brand#43|STANDARD POLISHED COPPER|3|12 +Brand#43|STANDARD POLISHED STEEL|36|12 +Brand#43|STANDARD POLISHED TIN|9|12 +Brand#44|ECONOMY ANODIZED COPPER|9|12 +Brand#44|ECONOMY ANODIZED NICKEL|36|12 +Brand#44|ECONOMY ANODIZED STEEL|14|12 +Brand#44|ECONOMY BRUSHED COPPER|19|12 +Brand#44|ECONOMY BURNISHED STEEL|45|12 +Brand#44|ECONOMY POLISHED TIN|36|12 +Brand#44|ECONOMY POLISHED TIN|49|12 +Brand#44|LARGE ANODIZED TIN|3|12 +Brand#44|LARGE BRUSHED COPPER|36|12 +Brand#44|LARGE BRUSHED STEEL|36|12 +Brand#44|LARGE BRUSHED TIN|3|12 +Brand#44|LARGE BRUSHED TIN|19|12 +Brand#44|LARGE BURNISHED BRASS|19|12 +Brand#44|LARGE BURNISHED BRASS|49|12 +Brand#44|LARGE BURNISHED NICKEL|9|12 +Brand#44|LARGE PLATED BRASS|9|12 +Brand#44|LARGE PLATED NICKEL|3|12 +Brand#44|LARGE PLATED NICKEL|14|12 +Brand#44|LARGE PLATED NICKEL|36|12 +Brand#44|MEDIUM ANODIZED BRASS|23|12 +Brand#44|MEDIUM ANODIZED COPPER|45|12 +Brand#44|MEDIUM ANODIZED TIN|9|12 +Brand#44|MEDIUM BRUSHED BRASS|49|12 +Brand#44|MEDIUM BRUSHED COPPER|3|12 +Brand#44|MEDIUM BRUSHED COPPER|9|12 +Brand#44|MEDIUM BRUSHED COPPER|36|12 +Brand#44|MEDIUM BURNISHED COPPER|36|12 +Brand#44|MEDIUM BURNISHED NICKEL|36|12 +Brand#44|MEDIUM PLATED STEEL|19|12 +Brand#44|MEDIUM PLATED TIN|23|12 +Brand#44|MEDIUM PLATED TIN|36|12 +Brand#44|PROMO ANODIZED BRASS|9|12 +Brand#44|PROMO ANODIZED COPPER|19|12 +Brand#44|PROMO ANODIZED NICKEL|19|12 +Brand#44|PROMO ANODIZED STEEL|36|12 +Brand#44|PROMO BRUSHED NICKEL|3|12 +Brand#44|PROMO BURNISHED BRASS|19|12 +Brand#44|PROMO BURNISHED NICKEL|49|12 +Brand#44|PROMO PLATED BRASS|19|12 +Brand#44|PROMO PLATED STEEL|14|12 +Brand#44|PROMO PLATED STEEL|36|12 +Brand#44|PROMO POLISHED COPPER|14|12 +Brand#44|PROMO POLISHED COPPER|23|12 +Brand#44|PROMO POLISHED COPPER|45|12 +Brand#44|PROMO POLISHED STEEL|36|12 +Brand#44|SMALL ANODIZED STEEL|36|12 +Brand#44|SMALL BRUSHED COPPER|19|12 +Brand#44|SMALL BRUSHED COPPER|45|12 +Brand#44|SMALL BRUSHED NICKEL|3|12 +Brand#44|SMALL BRUSHED NICKEL|9|12 +Brand#44|SMALL BURNISHED COPPER|14|12 +Brand#44|SMALL BURNISHED NICKEL|3|12 +Brand#44|SMALL BURNISHED TIN|3|12 +Brand#44|SMALL BURNISHED TIN|36|12 +Brand#44|SMALL PLATED BRASS|23|12 +Brand#44|SMALL PLATED BRASS|49|12 +Brand#44|SMALL PLATED STEEL|3|12 +Brand#44|SMALL PLATED STEEL|45|12 +Brand#44|SMALL POLISHED BRASS|3|12 +Brand#44|SMALL POLISHED COPPER|14|12 +Brand#44|STANDARD ANODIZED BRASS|3|12 +Brand#44|STANDARD ANODIZED BRASS|14|12 +Brand#44|STANDARD ANODIZED COPPER|45|12 +Brand#44|STANDARD ANODIZED NICKEL|9|12 +Brand#44|STANDARD ANODIZED NICKEL|36|12 +Brand#44|STANDARD ANODIZED TIN|9|12 +Brand#44|STANDARD BRUSHED BRASS|9|12 +Brand#44|STANDARD BRUSHED COPPER|23|12 +Brand#44|STANDARD BRUSHED TIN|49|12 +Brand#44|STANDARD BURNISHED COPPER|3|12 +Brand#44|STANDARD BURNISHED COPPER|49|12 +Brand#44|STANDARD BURNISHED STEEL|23|12 +Brand#44|STANDARD BURNISHED TIN|36|12 +Brand#44|STANDARD PLATED COPPER|14|12 +Brand#44|STANDARD PLATED COPPER|45|12 +Brand#44|STANDARD PLATED TIN|9|12 +Brand#44|STANDARD PLATED TIN|23|12 +Brand#44|STANDARD POLISHED BRASS|14|12 +Brand#44|STANDARD POLISHED NICKEL|19|12 +Brand#51|ECONOMY ANODIZED BRASS|9|12 +Brand#51|ECONOMY ANODIZED BRASS|36|12 +Brand#51|ECONOMY ANODIZED BRASS|45|12 +Brand#51|ECONOMY ANODIZED COPPER|19|12 +Brand#51|ECONOMY ANODIZED NICKEL|14|12 +Brand#51|ECONOMY ANODIZED TIN|9|12 +Brand#51|ECONOMY BRUSHED STEEL|36|12 +Brand#51|ECONOMY BRUSHED STEEL|45|12 +Brand#51|ECONOMY BRUSHED TIN|36|12 +Brand#51|ECONOMY BURNISHED COPPER|45|12 +Brand#51|ECONOMY PLATED STEEL|19|12 +Brand#51|ECONOMY PLATED STEEL|23|12 +Brand#51|ECONOMY PLATED TIN|45|12 +Brand#51|LARGE ANODIZED COPPER|19|12 +Brand#51|LARGE BRUSHED COPPER|36|12 +Brand#51|LARGE BRUSHED NICKEL|49|12 +Brand#51|LARGE BURNISHED STEEL|3|12 +Brand#51|LARGE PLATED COPPER|9|12 +Brand#51|LARGE PLATED NICKEL|45|12 +Brand#51|LARGE PLATED TIN|19|12 +Brand#51|LARGE PLATED TIN|23|12 +Brand#51|LARGE POLISHED COPPER|3|12 +Brand#51|LARGE POLISHED COPPER|23|12 +Brand#51|MEDIUM ANODIZED NICKEL|3|12 +Brand#51|MEDIUM ANODIZED NICKEL|19|12 +Brand#51|MEDIUM ANODIZED NICKEL|23|12 +Brand#51|MEDIUM ANODIZED STEEL|14|12 +Brand#51|MEDIUM ANODIZED TIN|14|12 +Brand#51|MEDIUM BRUSHED COPPER|49|12 +Brand#51|MEDIUM BRUSHED TIN|49|12 +Brand#51|MEDIUM BURNISHED BRASS|36|12 +Brand#51|MEDIUM BURNISHED NICKEL|14|12 +Brand#51|MEDIUM BURNISHED NICKEL|49|12 +Brand#51|MEDIUM PLATED NICKEL|45|12 +Brand#51|PROMO ANODIZED BRASS|3|12 +Brand#51|PROMO ANODIZED COPPER|23|12 +Brand#51|PROMO ANODIZED NICKEL|9|12 +Brand#51|PROMO ANODIZED NICKEL|14|12 +Brand#51|PROMO ANODIZED TIN|23|12 +Brand#51|PROMO ANODIZED TIN|49|12 +Brand#51|PROMO BRUSHED BRASS|23|12 +Brand#51|PROMO BRUSHED COPPER|19|12 +Brand#51|PROMO BRUSHED STEEL|36|12 +Brand#51|PROMO BRUSHED TIN|3|12 +Brand#51|PROMO BURNISHED COPPER|3|12 +Brand#51|PROMO BURNISHED COPPER|19|12 +Brand#51|PROMO PLATED COPPER|9|12 +Brand#51|PROMO PLATED STEEL|45|12 +Brand#51|PROMO PLATED TIN|14|12 +Brand#51|SMALL ANODIZED NICKEL|9|12 +Brand#51|SMALL BRUSHED BRASS|19|12 +Brand#51|SMALL BRUSHED NICKEL|3|12 +Brand#51|SMALL BRUSHED TIN|19|12 +Brand#51|SMALL BURNISHED NICKEL|14|12 +Brand#51|SMALL BURNISHED NICKEL|23|12 +Brand#51|SMALL BURNISHED STEEL|45|12 +Brand#51|SMALL BURNISHED STEEL|49|12 +Brand#51|SMALL BURNISHED TIN|23|12 +Brand#51|SMALL PLATED COPPER|14|12 +Brand#51|SMALL PLATED COPPER|36|12 +Brand#51|SMALL PLATED NICKEL|14|12 +Brand#51|SMALL PLATED STEEL|9|12 +Brand#51|SMALL POLISHED COPPER|23|12 +Brand#51|SMALL POLISHED NICKEL|19|12 +Brand#51|SMALL POLISHED NICKEL|23|12 +Brand#51|SMALL POLISHED STEEL|3|12 +Brand#51|SMALL POLISHED TIN|36|12 +Brand#51|STANDARD ANODIZED BRASS|49|12 +Brand#51|STANDARD ANODIZED COPPER|14|12 +Brand#51|STANDARD ANODIZED NICKEL|23|12 +Brand#51|STANDARD ANODIZED NICKEL|45|12 +Brand#51|STANDARD ANODIZED STEEL|49|12 +Brand#51|STANDARD ANODIZED TIN|19|12 +Brand#51|STANDARD BRUSHED BRASS|19|12 +Brand#51|STANDARD BRUSHED STEEL|23|12 +Brand#51|STANDARD BRUSHED STEEL|36|12 +Brand#51|STANDARD BRUSHED TIN|36|12 +Brand#51|STANDARD BURNISHED STEEL|23|12 +Brand#51|STANDARD BURNISHED STEEL|36|12 +Brand#51|STANDARD PLATED BRASS|3|12 +Brand#51|STANDARD POLISHED COPPER|45|12 +Brand#51|STANDARD POLISHED STEEL|36|12 +Brand#51|STANDARD POLISHED STEEL|45|12 +Brand#51|STANDARD POLISHED TIN|3|12 +Brand#52|ECONOMY ANODIZED COPPER|19|12 +Brand#52|ECONOMY ANODIZED STEEL|14|12 +Brand#52|ECONOMY ANODIZED TIN|9|12 +Brand#52|ECONOMY ANODIZED TIN|19|12 +Brand#52|ECONOMY BURNISHED COPPER|14|12 +Brand#52|ECONOMY BURNISHED COPPER|19|12 +Brand#52|ECONOMY BURNISHED NICKEL|19|12 +Brand#52|ECONOMY PLATED STEEL|45|12 +Brand#52|ECONOMY POLISHED BRASS|14|12 +Brand#52|ECONOMY POLISHED BRASS|19|12 +Brand#52|ECONOMY POLISHED COPPER|3|12 +Brand#52|ECONOMY POLISHED COPPER|14|12 +Brand#52|ECONOMY POLISHED COPPER|19|12 +Brand#52|LARGE ANODIZED COPPER|14|12 +Brand#52|LARGE ANODIZED NICKEL|3|12 +Brand#52|LARGE BRUSHED BRASS|23|12 +Brand#52|LARGE BRUSHED STEEL|23|12 +Brand#52|LARGE BURNISHED BRASS|14|12 +Brand#52|LARGE BURNISHED NICKEL|23|12 +Brand#52|LARGE PLATED BRASS|23|12 +Brand#52|LARGE PLATED COPPER|19|12 +Brand#52|LARGE PLATED NICKEL|19|12 +Brand#52|LARGE PLATED NICKEL|45|12 +Brand#52|LARGE PLATED STEEL|49|12 +Brand#52|LARGE PLATED TIN|3|12 +Brand#52|LARGE PLATED TIN|19|12 +Brand#52|LARGE POLISHED BRASS|3|12 +Brand#52|LARGE POLISHED BRASS|9|12 +Brand#52|LARGE POLISHED BRASS|23|12 +Brand#52|MEDIUM ANODIZED COPPER|19|12 +Brand#52|MEDIUM ANODIZED STEEL|9|12 +Brand#52|MEDIUM ANODIZED TIN|3|12 +Brand#52|MEDIUM BRUSHED BRASS|3|12 +Brand#52|MEDIUM BRUSHED BRASS|36|12 +Brand#52|MEDIUM BRUSHED COPPER|36|12 +Brand#52|MEDIUM BURNISHED BRASS|49|12 +Brand#52|MEDIUM BURNISHED COPPER|3|12 +Brand#52|MEDIUM BURNISHED COPPER|23|12 +Brand#52|MEDIUM BURNISHED NICKEL|45|12 +Brand#52|MEDIUM BURNISHED TIN|23|12 +Brand#52|MEDIUM PLATED BRASS|14|12 +Brand#52|MEDIUM PLATED TIN|36|12 +Brand#52|MEDIUM PLATED TIN|49|12 +Brand#52|PROMO ANODIZED BRASS|9|12 +Brand#52|PROMO ANODIZED BRASS|23|12 +Brand#52|PROMO ANODIZED COPPER|14|12 +Brand#52|PROMO ANODIZED COPPER|49|12 +Brand#52|PROMO ANODIZED STEEL|36|12 +Brand#52|PROMO ANODIZED TIN|3|12 +Brand#52|PROMO BRUSHED COPPER|49|12 +Brand#52|PROMO BRUSHED NICKEL|3|12 +Brand#52|PROMO BRUSHED TIN|36|12 +Brand#52|PROMO BURNISHED NICKEL|36|12 +Brand#52|PROMO BURNISHED STEEL|19|12 +Brand#52|PROMO BURNISHED STEEL|45|12 +Brand#52|PROMO BURNISHED TIN|19|12 +Brand#52|PROMO BURNISHED TIN|45|12 +Brand#52|PROMO PLATED BRASS|14|12 +Brand#52|PROMO PLATED NICKEL|14|12 +Brand#52|PROMO PLATED NICKEL|49|12 +Brand#52|PROMO PLATED STEEL|9|12 +Brand#52|PROMO PLATED TIN|3|12 +Brand#52|PROMO POLISHED BRASS|23|12 +Brand#52|PROMO POLISHED COPPER|45|12 +Brand#52|PROMO POLISHED NICKEL|49|12 +Brand#52|SMALL ANODIZED COPPER|36|12 +Brand#52|SMALL ANODIZED NICKEL|19|12 +Brand#52|SMALL ANODIZED NICKEL|36|12 +Brand#52|SMALL BRUSHED BRASS|14|12 +Brand#52|SMALL BRUSHED BRASS|19|12 +Brand#52|SMALL BRUSHED COPPER|9|12 +Brand#52|SMALL BRUSHED STEEL|45|12 +Brand#52|SMALL BURNISHED BRASS|14|12 +Brand#52|SMALL BURNISHED COPPER|23|12 +Brand#52|SMALL BURNISHED NICKEL|9|12 +Brand#52|SMALL BURNISHED NICKEL|36|12 +Brand#52|SMALL BURNISHED NICKEL|49|12 +Brand#52|SMALL BURNISHED STEEL|23|12 +Brand#52|SMALL BURNISHED TIN|3|12 +Brand#52|SMALL PLATED BRASS|36|12 +Brand#52|SMALL PLATED NICKEL|19|12 +Brand#52|SMALL PLATED NICKEL|23|12 +Brand#52|SMALL POLISHED NICKEL|9|12 +Brand#52|SMALL POLISHED NICKEL|19|12 +Brand#52|STANDARD ANODIZED TIN|14|12 +Brand#52|STANDARD BRUSHED BRASS|19|12 +Brand#52|STANDARD BRUSHED COPPER|19|12 +Brand#52|STANDARD BRUSHED TIN|36|12 +Brand#52|STANDARD BRUSHED TIN|49|12 +Brand#52|STANDARD BURNISHED STEEL|9|12 +Brand#52|STANDARD BURNISHED TIN|9|12 +Brand#52|STANDARD PLATED COPPER|45|12 +Brand#52|STANDARD PLATED NICKEL|3|12 +Brand#52|STANDARD PLATED NICKEL|45|12 +Brand#52|STANDARD PLATED STEEL|9|12 +Brand#52|STANDARD PLATED TIN|23|12 +Brand#52|STANDARD POLISHED BRASS|36|12 +Brand#52|STANDARD POLISHED NICKEL|3|12 +Brand#53|ECONOMY ANODIZED COPPER|23|12 +Brand#53|ECONOMY ANODIZED COPPER|36|12 +Brand#53|ECONOMY ANODIZED STEEL|9|12 +Brand#53|ECONOMY BRUSHED BRASS|3|12 +Brand#53|ECONOMY BRUSHED BRASS|23|12 +Brand#53|ECONOMY BRUSHED COPPER|45|12 +Brand#53|ECONOMY BRUSHED STEEL|19|12 +Brand#53|ECONOMY BURNISHED BRASS|49|12 +Brand#53|ECONOMY BURNISHED COPPER|45|12 +Brand#53|ECONOMY BURNISHED TIN|14|12 +Brand#53|ECONOMY PLATED BRASS|36|12 +Brand#53|ECONOMY PLATED BRASS|45|12 +Brand#53|ECONOMY PLATED STEEL|36|12 +Brand#53|ECONOMY PLATED TIN|3|12 +Brand#53|ECONOMY PLATED TIN|23|12 +Brand#53|ECONOMY POLISHED STEEL|14|12 +Brand#53|ECONOMY POLISHED STEEL|36|12 +Brand#53|ECONOMY POLISHED STEEL|45|12 +Brand#53|ECONOMY POLISHED STEEL|49|12 +Brand#53|ECONOMY POLISHED TIN|19|12 +Brand#53|ECONOMY POLISHED TIN|36|12 +Brand#53|LARGE ANODIZED COPPER|45|12 +Brand#53|LARGE ANODIZED NICKEL|9|12 +Brand#53|LARGE ANODIZED STEEL|19|12 +Brand#53|LARGE BRUSHED BRASS|9|12 +Brand#53|LARGE BRUSHED BRASS|19|12 +Brand#53|LARGE BRUSHED NICKEL|23|12 +Brand#53|LARGE BRUSHED STEEL|19|12 +Brand#53|LARGE BURNISHED BRASS|9|12 +Brand#53|LARGE BURNISHED STEEL|14|12 +Brand#53|LARGE PLATED COPPER|3|12 +Brand#53|LARGE PLATED NICKEL|45|12 +Brand#53|LARGE POLISHED COPPER|49|12 +Brand#53|LARGE POLISHED STEEL|36|12 +Brand#53|MEDIUM ANODIZED COPPER|14|12 +Brand#53|MEDIUM ANODIZED NICKEL|14|12 +Brand#53|MEDIUM ANODIZED TIN|23|12 +Brand#53|MEDIUM ANODIZED TIN|36|12 +Brand#53|MEDIUM BRUSHED BRASS|3|12 +Brand#53|MEDIUM BRUSHED BRASS|23|12 +Brand#53|MEDIUM BURNISHED BRASS|14|12 +Brand#53|MEDIUM BURNISHED BRASS|49|12 +Brand#53|MEDIUM BURNISHED NICKEL|23|12 +Brand#53|MEDIUM PLATED BRASS|49|12 +Brand#53|MEDIUM PLATED COPPER|14|12 +Brand#53|MEDIUM PLATED COPPER|23|12 +Brand#53|MEDIUM PLATED STEEL|14|12 +Brand#53|MEDIUM PLATED TIN|45|12 +Brand#53|PROMO ANODIZED COPPER|14|12 +Brand#53|PROMO BRUSHED COPPER|3|12 +Brand#53|PROMO BURNISHED COPPER|36|12 +Brand#53|PROMO BURNISHED NICKEL|36|12 +Brand#53|PROMO BURNISHED STEEL|36|12 +Brand#53|PROMO BURNISHED STEEL|49|12 +Brand#53|PROMO PLATED COPPER|14|12 +Brand#53|PROMO PLATED TIN|3|12 +Brand#53|PROMO PLATED TIN|23|12 +Brand#53|PROMO POLISHED COPPER|49|12 +Brand#53|PROMO POLISHED NICKEL|9|12 +Brand#53|PROMO POLISHED TIN|14|12 +Brand#53|SMALL ANODIZED COPPER|36|12 +Brand#53|SMALL ANODIZED NICKEL|36|12 +Brand#53|SMALL ANODIZED STEEL|19|12 +Brand#53|SMALL BRUSHED COPPER|14|12 +Brand#53|SMALL BURNISHED BRASS|9|12 +Brand#53|SMALL BURNISHED COPPER|9|12 +Brand#53|SMALL BURNISHED NICKEL|36|12 +Brand#53|SMALL BURNISHED STEEL|19|12 +Brand#53|SMALL PLATED COPPER|3|12 +Brand#53|SMALL POLISHED BRASS|3|12 +Brand#53|SMALL POLISHED BRASS|9|12 +Brand#53|SMALL POLISHED STEEL|36|12 +Brand#53|STANDARD ANODIZED STEEL|23|12 +Brand#53|STANDARD ANODIZED STEEL|49|12 +Brand#53|STANDARD BRUSHED COPPER|3|12 +Brand#53|STANDARD BRUSHED STEEL|45|12 +Brand#53|STANDARD BRUSHED TIN|14|12 +Brand#53|STANDARD BRUSHED TIN|19|12 +Brand#53|STANDARD BURNISHED BRASS|9|12 +Brand#53|STANDARD BURNISHED NICKEL|23|12 +Brand#53|STANDARD PLATED BRASS|3|12 +Brand#53|STANDARD PLATED BRASS|36|12 +Brand#53|STANDARD PLATED COPPER|36|12 +Brand#53|STANDARD PLATED COPPER|45|12 +Brand#53|STANDARD POLISHED BRASS|19|12 +Brand#53|STANDARD POLISHED COPPER|14|12 +Brand#53|STANDARD POLISHED TIN|19|12 +Brand#54|ECONOMY ANODIZED COPPER|19|12 +Brand#54|ECONOMY BRUSHED STEEL|19|12 +Brand#54|ECONOMY BRUSHED STEEL|45|12 +Brand#54|ECONOMY BRUSHED TIN|45|12 +Brand#54|ECONOMY BURNISHED BRASS|19|12 +Brand#54|ECONOMY BURNISHED BRASS|45|12 +Brand#54|ECONOMY BURNISHED COPPER|14|12 +Brand#54|ECONOMY BURNISHED NICKEL|9|12 +Brand#54|ECONOMY POLISHED NICKEL|14|12 +Brand#54|ECONOMY POLISHED NICKEL|45|12 +Brand#54|ECONOMY POLISHED TIN|23|12 +Brand#54|LARGE ANODIZED TIN|36|12 +Brand#54|LARGE BRUSHED COPPER|9|12 +Brand#54|LARGE BRUSHED COPPER|23|12 +Brand#54|LARGE BURNISHED BRASS|45|12 +Brand#54|LARGE BURNISHED COPPER|3|12 +Brand#54|LARGE BURNISHED COPPER|45|12 +Brand#54|LARGE BURNISHED NICKEL|14|12 +Brand#54|LARGE PLATED COPPER|9|12 +Brand#54|LARGE PLATED COPPER|45|12 +Brand#54|LARGE PLATED STEEL|49|12 +Brand#54|LARGE POLISHED BRASS|23|12 +Brand#54|LARGE POLISHED COPPER|3|12 +Brand#54|MEDIUM ANODIZED STEEL|19|12 +Brand#54|MEDIUM BRUSHED BRASS|49|12 +Brand#54|MEDIUM BURNISHED COPPER|23|12 +Brand#54|MEDIUM BURNISHED STEEL|3|12 +Brand#54|MEDIUM BURNISHED STEEL|49|12 +Brand#54|PROMO ANODIZED COPPER|49|12 +Brand#54|PROMO ANODIZED STEEL|19|12 +Brand#54|PROMO BRUSHED BRASS|14|12 +Brand#54|PROMO BRUSHED COPPER|14|12 +Brand#54|PROMO BRUSHED STEEL|14|12 +Brand#54|PROMO BRUSHED STEEL|45|12 +Brand#54|PROMO BRUSHED TIN|14|12 +Brand#54|PROMO BURNISHED BRASS|9|12 +Brand#54|PROMO BURNISHED COPPER|49|12 +Brand#54|PROMO BURNISHED NICKEL|23|12 +Brand#54|PROMO BURNISHED NICKEL|36|12 +Brand#54|PROMO BURNISHED STEEL|23|12 +Brand#54|PROMO BURNISHED TIN|9|12 +Brand#54|PROMO BURNISHED TIN|23|12 +Brand#54|PROMO PLATED BRASS|23|12 +Brand#54|PROMO PLATED STEEL|9|12 +Brand#54|PROMO PLATED TIN|3|12 +Brand#54|PROMO PLATED TIN|49|12 +Brand#54|PROMO POLISHED STEEL|19|12 +Brand#54|PROMO POLISHED STEEL|45|12 +Brand#54|PROMO POLISHED TIN|19|12 +Brand#54|SMALL ANODIZED COPPER|49|12 +Brand#54|SMALL BRUSHED BRASS|23|12 +Brand#54|SMALL BRUSHED BRASS|36|12 +Brand#54|SMALL BRUSHED COPPER|19|12 +Brand#54|SMALL BRUSHED TIN|14|12 +Brand#54|SMALL BURNISHED BRASS|3|12 +Brand#54|SMALL BURNISHED COPPER|49|12 +Brand#54|SMALL BURNISHED NICKEL|14|12 +Brand#54|SMALL BURNISHED STEEL|19|12 +Brand#54|SMALL BURNISHED TIN|9|12 +Brand#54|SMALL PLATED BRASS|23|12 +Brand#54|SMALL PLATED COPPER|36|12 +Brand#54|SMALL PLATED NICKEL|36|12 +Brand#54|STANDARD ANODIZED BRASS|3|12 +Brand#54|STANDARD ANODIZED STEEL|49|12 +Brand#54|STANDARD BRUSHED BRASS|14|12 +Brand#54|STANDARD BRUSHED COPPER|19|12 +Brand#54|STANDARD BURNISHED BRASS|9|12 +Brand#54|STANDARD BURNISHED NICKEL|14|12 +Brand#54|STANDARD PLATED BRASS|45|12 +Brand#54|STANDARD PLATED COPPER|9|12 +Brand#54|STANDARD PLATED COPPER|19|12 +Brand#54|STANDARD PLATED NICKEL|49|12 +Brand#54|STANDARD PLATED TIN|45|12 +Brand#54|STANDARD POLISHED STEEL|49|12 +Brand#55|ECONOMY BRUSHED BRASS|3|12 +Brand#55|ECONOMY BRUSHED COPPER|9|12 +Brand#55|ECONOMY BRUSHED COPPER|14|12 +Brand#55|ECONOMY BRUSHED NICKEL|19|12 +Brand#55|ECONOMY BRUSHED STEEL|3|12 +Brand#55|ECONOMY BURNISHED COPPER|9|12 +Brand#55|ECONOMY PLATED STEEL|9|12 +Brand#55|ECONOMY POLISHED STEEL|3|12 +Brand#55|LARGE ANODIZED NICKEL|9|12 +Brand#55|LARGE BRUSHED COPPER|14|12 +Brand#55|LARGE BRUSHED COPPER|23|12 +Brand#55|LARGE BRUSHED COPPER|49|12 +Brand#55|LARGE BURNISHED COPPER|14|12 +Brand#55|LARGE BURNISHED NICKEL|14|12 +Brand#55|LARGE PLATED BRASS|45|12 +Brand#55|LARGE PLATED NICKEL|14|12 +Brand#55|LARGE PLATED STEEL|23|12 +Brand#55|LARGE POLISHED NICKEL|3|12 +Brand#55|LARGE POLISHED STEEL|45|12 +Brand#55|MEDIUM ANODIZED NICKEL|36|12 +Brand#55|MEDIUM ANODIZED TIN|49|12 +Brand#55|MEDIUM BRUSHED BRASS|19|12 +Brand#55|MEDIUM BRUSHED COPPER|49|12 +Brand#55|MEDIUM BRUSHED NICKEL|23|12 +Brand#55|MEDIUM BRUSHED NICKEL|45|12 +Brand#55|MEDIUM BRUSHED STEEL|45|12 +Brand#55|MEDIUM BURNISHED COPPER|36|12 +Brand#55|MEDIUM PLATED NICKEL|23|12 +Brand#55|MEDIUM PLATED STEEL|3|12 +Brand#55|MEDIUM PLATED TIN|19|12 +Brand#55|PROMO ANODIZED TIN|19|12 +Brand#55|PROMO BRUSHED BRASS|23|12 +Brand#55|PROMO BRUSHED BRASS|45|12 +Brand#55|PROMO BRUSHED NICKEL|23|12 +Brand#55|PROMO BRUSHED TIN|9|12 +Brand#55|PROMO BURNISHED STEEL|23|12 +Brand#55|PROMO POLISHED BRASS|45|12 +Brand#55|SMALL ANODIZED STEEL|23|12 +Brand#55|SMALL ANODIZED STEEL|45|12 +Brand#55|SMALL BRUSHED STEEL|36|12 +Brand#55|SMALL BRUSHED TIN|3|12 +Brand#55|SMALL BURNISHED BRASS|49|12 +Brand#55|SMALL BURNISHED TIN|49|12 +Brand#55|SMALL PLATED NICKEL|36|12 +Brand#55|SMALL PLATED NICKEL|45|12 +Brand#55|SMALL PLATED STEEL|9|12 +Brand#55|SMALL PLATED STEEL|19|12 +Brand#55|SMALL POLISHED STEEL|14|12 +Brand#55|STANDARD ANODIZED BRASS|3|12 +Brand#55|STANDARD ANODIZED STEEL|19|12 +Brand#55|STANDARD ANODIZED TIN|9|12 +Brand#55|STANDARD BRUSHED COPPER|9|12 +Brand#55|STANDARD BRUSHED NICKEL|9|12 +Brand#55|STANDARD BRUSHED TIN|36|12 +Brand#55|STANDARD BRUSHED TIN|45|12 +Brand#55|STANDARD BURNISHED BRASS|3|12 +Brand#55|STANDARD BURNISHED COPPER|49|12 +Brand#55|STANDARD BURNISHED TIN|3|12 +Brand#55|STANDARD PLATED BRASS|3|12 +Brand#55|STANDARD PLATED COPPER|3|12 +Brand#55|STANDARD PLATED COPPER|19|12 +Brand#55|STANDARD PLATED NICKEL|9|12 +Brand#55|STANDARD PLATED TIN|19|12 +Brand#55|STANDARD POLISHED NICKEL|14|12 +Brand#11|ECONOMY POLISHED BRASS|14|11 +Brand#11|SMALL PLATED BRASS|14|11 +Brand#12|MEDIUM BURNISHED TIN|45|11 +Brand#12|SMALL BURNISHED COPPER|23|11 +Brand#15|SMALL PLATED NICKEL|45|11 +Brand#21|ECONOMY PLATED COPPER|3|11 +Brand#21|SMALL BRUSHED TIN|19|11 +Brand#23|LARGE BRUSHED NICKEL|23|11 +Brand#24|PROMO BRUSHED NICKEL|9|11 +Brand#25|SMALL PLATED TIN|23|11 +Brand#31|ECONOMY POLISHED COPPER|14|11 +Brand#32|SMALL PLATED NICKEL|45|11 +Brand#33|PROMO ANODIZED TIN|19|11 +Brand#43|PROMO BRUSHED NICKEL|9|11 +Brand#44|LARGE PLATED STEEL|3|11 +Brand#52|ECONOMY ANODIZED COPPER|36|11 +Brand#52|SMALL POLISHED BRASS|49|11 +Brand#53|MEDIUM BRUSHED BRASS|49|11 +Brand#53|PROMO BRUSHED NICKEL|3|11 +Brand#54|LARGE PLATED BRASS|19|11 +Brand#54|LARGE POLISHED NICKEL|3|11 +Brand#55|PROMO ANODIZED STEEL|45|11 +Brand#55|STANDARD POLISHED STEEL|19|11 +Brand#11|ECONOMY ANODIZED BRASS|19|8 +Brand#11|ECONOMY ANODIZED NICKEL|9|8 +Brand#11|ECONOMY ANODIZED NICKEL|19|8 +Brand#11|ECONOMY ANODIZED NICKEL|36|8 +Brand#11|ECONOMY ANODIZED NICKEL|45|8 +Brand#11|ECONOMY ANODIZED TIN|36|8 +Brand#11|ECONOMY BRUSHED COPPER|9|8 +Brand#11|ECONOMY BRUSHED COPPER|49|8 +Brand#11|ECONOMY BRUSHED NICKEL|49|8 +Brand#11|ECONOMY BRUSHED STEEL|9|8 +Brand#11|ECONOMY BRUSHED STEEL|14|8 +Brand#11|ECONOMY BRUSHED STEEL|23|8 +Brand#11|ECONOMY BRUSHED TIN|19|8 +Brand#11|ECONOMY BRUSHED TIN|36|8 +Brand#11|ECONOMY BRUSHED TIN|49|8 +Brand#11|ECONOMY BURNISHED BRASS|23|8 +Brand#11|ECONOMY BURNISHED COPPER|9|8 +Brand#11|ECONOMY BURNISHED NICKEL|14|8 +Brand#11|ECONOMY BURNISHED NICKEL|19|8 +Brand#11|ECONOMY BURNISHED TIN|9|8 +Brand#11|ECONOMY BURNISHED TIN|14|8 +Brand#11|ECONOMY BURNISHED TIN|49|8 +Brand#11|ECONOMY PLATED COPPER|14|8 +Brand#11|ECONOMY PLATED COPPER|49|8 +Brand#11|ECONOMY PLATED NICKEL|23|8 +Brand#11|ECONOMY PLATED NICKEL|36|8 +Brand#11|ECONOMY PLATED NICKEL|45|8 +Brand#11|ECONOMY PLATED STEEL|23|8 +Brand#11|ECONOMY PLATED TIN|49|8 +Brand#11|ECONOMY POLISHED BRASS|3|8 +Brand#11|ECONOMY POLISHED COPPER|45|8 +Brand#11|ECONOMY POLISHED COPPER|49|8 +Brand#11|ECONOMY POLISHED NICKEL|3|8 +Brand#11|ECONOMY POLISHED NICKEL|9|8 +Brand#11|ECONOMY POLISHED NICKEL|14|8 +Brand#11|ECONOMY POLISHED NICKEL|23|8 +Brand#11|ECONOMY POLISHED STEEL|19|8 +Brand#11|ECONOMY POLISHED TIN|3|8 +Brand#11|ECONOMY POLISHED TIN|14|8 +Brand#11|ECONOMY POLISHED TIN|36|8 +Brand#11|LARGE ANODIZED BRASS|49|8 +Brand#11|LARGE ANODIZED COPPER|23|8 +Brand#11|LARGE ANODIZED NICKEL|36|8 +Brand#11|LARGE ANODIZED NICKEL|45|8 +Brand#11|LARGE ANODIZED NICKEL|49|8 +Brand#11|LARGE ANODIZED STEEL|9|8 +Brand#11|LARGE ANODIZED TIN|23|8 +Brand#11|LARGE ANODIZED TIN|45|8 +Brand#11|LARGE BRUSHED BRASS|14|8 +Brand#11|LARGE BRUSHED BRASS|23|8 +Brand#11|LARGE BRUSHED COPPER|19|8 +Brand#11|LARGE BRUSHED COPPER|23|8 +Brand#11|LARGE BRUSHED COPPER|36|8 +Brand#11|LARGE BRUSHED NICKEL|3|8 +Brand#11|LARGE BRUSHED NICKEL|14|8 +Brand#11|LARGE BRUSHED NICKEL|19|8 +Brand#11|LARGE BRUSHED STEEL|49|8 +Brand#11|LARGE BRUSHED TIN|14|8 +Brand#11|LARGE BRUSHED TIN|23|8 +Brand#11|LARGE BURNISHED BRASS|14|8 +Brand#11|LARGE BURNISHED BRASS|23|8 +Brand#11|LARGE BURNISHED BRASS|45|8 +Brand#11|LARGE BURNISHED BRASS|49|8 +Brand#11|LARGE BURNISHED COPPER|9|8 +Brand#11|LARGE BURNISHED COPPER|36|8 +Brand#11|LARGE BURNISHED NICKEL|45|8 +Brand#11|LARGE BURNISHED STEEL|36|8 +Brand#11|LARGE BURNISHED STEEL|49|8 +Brand#11|LARGE BURNISHED TIN|14|8 +Brand#11|LARGE BURNISHED TIN|23|8 +Brand#11|LARGE PLATED BRASS|14|8 +Brand#11|LARGE PLATED BRASS|23|8 +Brand#11|LARGE PLATED NICKEL|3|8 +Brand#11|LARGE PLATED NICKEL|36|8 +Brand#11|LARGE PLATED STEEL|3|8 +Brand#11|LARGE PLATED STEEL|23|8 +Brand#11|LARGE PLATED STEEL|36|8 +Brand#11|LARGE PLATED TIN|9|8 +Brand#11|LARGE PLATED TIN|14|8 +Brand#11|LARGE POLISHED BRASS|49|8 +Brand#11|LARGE POLISHED COPPER|14|8 +Brand#11|LARGE POLISHED NICKEL|14|8 +Brand#11|LARGE POLISHED STEEL|36|8 +Brand#11|LARGE POLISHED TIN|3|8 +Brand#11|MEDIUM ANODIZED BRASS|14|8 +Brand#11|MEDIUM ANODIZED BRASS|49|8 +Brand#11|MEDIUM ANODIZED COPPER|23|8 +Brand#11|MEDIUM ANODIZED NICKEL|9|8 +Brand#11|MEDIUM ANODIZED NICKEL|14|8 +Brand#11|MEDIUM ANODIZED NICKEL|36|8 +Brand#11|MEDIUM ANODIZED NICKEL|45|8 +Brand#11|MEDIUM ANODIZED STEEL|9|8 +Brand#11|MEDIUM ANODIZED TIN|23|8 +Brand#11|MEDIUM ANODIZED TIN|49|8 +Brand#11|MEDIUM BRUSHED COPPER|23|8 +Brand#11|MEDIUM BRUSHED NICKEL|23|8 +Brand#11|MEDIUM BURNISHED BRASS|3|8 +Brand#11|MEDIUM BURNISHED BRASS|19|8 +Brand#11|MEDIUM BURNISHED BRASS|45|8 +Brand#11|MEDIUM BURNISHED COPPER|9|8 +Brand#11|MEDIUM BURNISHED COPPER|14|8 +Brand#11|MEDIUM BURNISHED COPPER|49|8 +Brand#11|MEDIUM BURNISHED STEEL|19|8 +Brand#11|MEDIUM BURNISHED TIN|19|8 +Brand#11|MEDIUM BURNISHED TIN|36|8 +Brand#11|MEDIUM PLATED BRASS|3|8 +Brand#11|MEDIUM PLATED BRASS|36|8 +Brand#11|MEDIUM PLATED NICKEL|14|8 +Brand#11|MEDIUM PLATED NICKEL|45|8 +Brand#11|MEDIUM PLATED STEEL|3|8 +Brand#11|MEDIUM PLATED STEEL|9|8 +Brand#11|MEDIUM PLATED STEEL|23|8 +Brand#11|MEDIUM PLATED STEEL|36|8 +Brand#11|MEDIUM PLATED TIN|3|8 +Brand#11|MEDIUM PLATED TIN|19|8 +Brand#11|MEDIUM PLATED TIN|23|8 +Brand#11|MEDIUM PLATED TIN|45|8 +Brand#11|PROMO ANODIZED COPPER|14|8 +Brand#11|PROMO ANODIZED NICKEL|3|8 +Brand#11|PROMO ANODIZED NICKEL|45|8 +Brand#11|PROMO ANODIZED STEEL|23|8 +Brand#11|PROMO ANODIZED STEEL|49|8 +Brand#11|PROMO ANODIZED TIN|36|8 +Brand#11|PROMO BRUSHED BRASS|3|8 +Brand#11|PROMO BRUSHED BRASS|36|8 +Brand#11|PROMO BRUSHED COPPER|14|8 +Brand#11|PROMO BRUSHED COPPER|19|8 +Brand#11|PROMO BRUSHED NICKEL|19|8 +Brand#11|PROMO BRUSHED STEEL|49|8 +Brand#11|PROMO BRUSHED TIN|19|8 +Brand#11|PROMO BRUSHED TIN|36|8 +Brand#11|PROMO BURNISHED BRASS|3|8 +Brand#11|PROMO BURNISHED BRASS|19|8 +Brand#11|PROMO BURNISHED BRASS|36|8 +Brand#11|PROMO BURNISHED BRASS|49|8 +Brand#11|PROMO BURNISHED COPPER|14|8 +Brand#11|PROMO BURNISHED NICKEL|3|8 +Brand#11|PROMO BURNISHED NICKEL|14|8 +Brand#11|PROMO BURNISHED STEEL|14|8 +Brand#11|PROMO BURNISHED STEEL|19|8 +Brand#11|PROMO BURNISHED STEEL|36|8 +Brand#11|PROMO BURNISHED STEEL|49|8 +Brand#11|PROMO PLATED BRASS|23|8 +Brand#11|PROMO PLATED NICKEL|14|8 +Brand#11|PROMO PLATED NICKEL|49|8 +Brand#11|PROMO PLATED STEEL|19|8 +Brand#11|PROMO PLATED STEEL|23|8 +Brand#11|PROMO POLISHED BRASS|3|8 +Brand#11|PROMO POLISHED BRASS|19|8 +Brand#11|PROMO POLISHED BRASS|36|8 +Brand#11|PROMO POLISHED COPPER|45|8 +Brand#11|PROMO POLISHED TIN|3|8 +Brand#11|PROMO POLISHED TIN|9|8 +Brand#11|PROMO POLISHED TIN|49|8 +Brand#11|SMALL ANODIZED COPPER|19|8 +Brand#11|SMALL ANODIZED NICKEL|49|8 +Brand#11|SMALL ANODIZED STEEL|3|8 +Brand#11|SMALL ANODIZED STEEL|14|8 +Brand#11|SMALL ANODIZED TIN|9|8 +Brand#11|SMALL ANODIZED TIN|19|8 +Brand#11|SMALL BRUSHED BRASS|45|8 +Brand#11|SMALL BRUSHED BRASS|49|8 +Brand#11|SMALL BRUSHED COPPER|14|8 +Brand#11|SMALL BRUSHED COPPER|19|8 +Brand#11|SMALL BRUSHED NICKEL|3|8 +Brand#11|SMALL BRUSHED NICKEL|45|8 +Brand#11|SMALL BRUSHED NICKEL|49|8 +Brand#11|SMALL BRUSHED TIN|14|8 +Brand#11|SMALL BURNISHED COPPER|23|8 +Brand#11|SMALL BURNISHED COPPER|36|8 +Brand#11|SMALL BURNISHED COPPER|49|8 +Brand#11|SMALL BURNISHED STEEL|3|8 +Brand#11|SMALL BURNISHED STEEL|9|8 +Brand#11|SMALL BURNISHED STEEL|36|8 +Brand#11|SMALL BURNISHED STEEL|45|8 +Brand#11|SMALL BURNISHED TIN|3|8 +Brand#11|SMALL BURNISHED TIN|19|8 +Brand#11|SMALL BURNISHED TIN|45|8 +Brand#11|SMALL PLATED BRASS|3|8 +Brand#11|SMALL PLATED BRASS|19|8 +Brand#11|SMALL PLATED BRASS|36|8 +Brand#11|SMALL PLATED COPPER|49|8 +Brand#11|SMALL PLATED NICKEL|9|8 +Brand#11|SMALL PLATED NICKEL|49|8 +Brand#11|SMALL PLATED STEEL|9|8 +Brand#11|SMALL PLATED TIN|9|8 +Brand#11|SMALL PLATED TIN|19|8 +Brand#11|SMALL PLATED TIN|45|8 +Brand#11|SMALL POLISHED BRASS|23|8 +Brand#11|SMALL POLISHED COPPER|36|8 +Brand#11|SMALL POLISHED NICKEL|45|8 +Brand#11|SMALL POLISHED STEEL|14|8 +Brand#11|SMALL POLISHED STEEL|23|8 +Brand#11|STANDARD ANODIZED BRASS|23|8 +Brand#11|STANDARD ANODIZED COPPER|9|8 +Brand#11|STANDARD ANODIZED STEEL|19|8 +Brand#11|STANDARD BRUSHED COPPER|14|8 +Brand#11|STANDARD BRUSHED NICKEL|14|8 +Brand#11|STANDARD BRUSHED NICKEL|45|8 +Brand#11|STANDARD BRUSHED TIN|19|8 +Brand#11|STANDARD BURNISHED BRASS|49|8 +Brand#11|STANDARD BURNISHED COPPER|14|8 +Brand#11|STANDARD BURNISHED COPPER|23|8 +Brand#11|STANDARD BURNISHED STEEL|23|8 +Brand#11|STANDARD BURNISHED TIN|49|8 +Brand#11|STANDARD PLATED BRASS|19|8 +Brand#11|STANDARD PLATED BRASS|23|8 +Brand#11|STANDARD PLATED BRASS|49|8 +Brand#11|STANDARD PLATED NICKEL|36|8 +Brand#11|STANDARD PLATED NICKEL|45|8 +Brand#11|STANDARD PLATED STEEL|23|8 +Brand#11|STANDARD PLATED STEEL|45|8 +Brand#11|STANDARD PLATED TIN|36|8 +Brand#11|STANDARD POLISHED BRASS|9|8 +Brand#11|STANDARD POLISHED NICKEL|19|8 +Brand#11|STANDARD POLISHED STEEL|49|8 +Brand#12|ECONOMY ANODIZED STEEL|3|8 +Brand#12|ECONOMY ANODIZED STEEL|19|8 +Brand#12|ECONOMY ANODIZED STEEL|23|8 +Brand#12|ECONOMY ANODIZED TIN|23|8 +Brand#12|ECONOMY BRUSHED COPPER|3|8 +Brand#12|ECONOMY BRUSHED COPPER|14|8 +Brand#12|ECONOMY BRUSHED COPPER|19|8 +Brand#12|ECONOMY BRUSHED COPPER|49|8 +Brand#12|ECONOMY BRUSHED NICKEL|3|8 +Brand#12|ECONOMY BRUSHED STEEL|3|8 +Brand#12|ECONOMY BRUSHED STEEL|49|8 +Brand#12|ECONOMY BRUSHED TIN|9|8 +Brand#12|ECONOMY BRUSHED TIN|49|8 +Brand#12|ECONOMY BURNISHED BRASS|49|8 +Brand#12|ECONOMY BURNISHED COPPER|3|8 +Brand#12|ECONOMY BURNISHED COPPER|19|8 +Brand#12|ECONOMY BURNISHED NICKEL|3|8 +Brand#12|ECONOMY BURNISHED NICKEL|23|8 +Brand#12|ECONOMY BURNISHED STEEL|3|8 +Brand#12|ECONOMY BURNISHED TIN|14|8 +Brand#12|ECONOMY BURNISHED TIN|19|8 +Brand#12|ECONOMY PLATED BRASS|19|8 +Brand#12|ECONOMY PLATED BRASS|49|8 +Brand#12|ECONOMY PLATED COPPER|23|8 +Brand#12|ECONOMY PLATED STEEL|23|8 +Brand#12|ECONOMY PLATED TIN|36|8 +Brand#12|ECONOMY PLATED TIN|49|8 +Brand#12|ECONOMY POLISHED BRASS|9|8 +Brand#12|ECONOMY POLISHED BRASS|14|8 +Brand#12|ECONOMY POLISHED COPPER|9|8 +Brand#12|ECONOMY POLISHED COPPER|49|8 +Brand#12|ECONOMY POLISHED TIN|3|8 +Brand#12|ECONOMY POLISHED TIN|19|8 +Brand#12|ECONOMY POLISHED TIN|36|8 +Brand#12|LARGE ANODIZED BRASS|23|8 +Brand#12|LARGE ANODIZED BRASS|36|8 +Brand#12|LARGE ANODIZED COPPER|14|8 +Brand#12|LARGE ANODIZED COPPER|45|8 +Brand#12|LARGE ANODIZED NICKEL|9|8 +Brand#12|LARGE ANODIZED STEEL|9|8 +Brand#12|LARGE ANODIZED STEEL|49|8 +Brand#12|LARGE ANODIZED TIN|14|8 +Brand#12|LARGE BRUSHED BRASS|14|8 +Brand#12|LARGE BRUSHED NICKEL|9|8 +Brand#12|LARGE BRUSHED STEEL|3|8 +Brand#12|LARGE BRUSHED STEEL|23|8 +Brand#12|LARGE BRUSHED STEEL|49|8 +Brand#12|LARGE BRUSHED TIN|9|8 +Brand#12|LARGE BRUSHED TIN|45|8 +Brand#12|LARGE BURNISHED BRASS|14|8 +Brand#12|LARGE BURNISHED BRASS|45|8 +Brand#12|LARGE BURNISHED COPPER|19|8 +Brand#12|LARGE BURNISHED COPPER|49|8 +Brand#12|LARGE BURNISHED NICKEL|3|8 +Brand#12|LARGE BURNISHED NICKEL|14|8 +Brand#12|LARGE BURNISHED STEEL|3|8 +Brand#12|LARGE BURNISHED STEEL|45|8 +Brand#12|LARGE BURNISHED TIN|9|8 +Brand#12|LARGE BURNISHED TIN|14|8 +Brand#12|LARGE BURNISHED TIN|45|8 +Brand#12|LARGE BURNISHED TIN|49|8 +Brand#12|LARGE PLATED BRASS|49|8 +Brand#12|LARGE PLATED COPPER|3|8 +Brand#12|LARGE PLATED COPPER|36|8 +Brand#12|LARGE PLATED COPPER|45|8 +Brand#12|LARGE PLATED NICKEL|49|8 +Brand#12|LARGE PLATED STEEL|3|8 +Brand#12|LARGE PLATED STEEL|36|8 +Brand#12|LARGE PLATED TIN|14|8 +Brand#12|LARGE POLISHED BRASS|9|8 +Brand#12|LARGE POLISHED BRASS|19|8 +Brand#12|LARGE POLISHED COPPER|9|8 +Brand#12|LARGE POLISHED COPPER|36|8 +Brand#12|LARGE POLISHED NICKEL|23|8 +Brand#12|LARGE POLISHED NICKEL|36|8 +Brand#12|LARGE POLISHED NICKEL|49|8 +Brand#12|LARGE POLISHED STEEL|49|8 +Brand#12|MEDIUM ANODIZED BRASS|23|8 +Brand#12|MEDIUM ANODIZED NICKEL|9|8 +Brand#12|MEDIUM ANODIZED STEEL|19|8 +Brand#12|MEDIUM ANODIZED TIN|9|8 +Brand#12|MEDIUM BRUSHED COPPER|3|8 +Brand#12|MEDIUM BRUSHED COPPER|9|8 +Brand#12|MEDIUM BRUSHED COPPER|36|8 +Brand#12|MEDIUM BRUSHED NICKEL|49|8 +Brand#12|MEDIUM BRUSHED STEEL|3|8 +Brand#12|MEDIUM BRUSHED STEEL|36|8 +Brand#12|MEDIUM BURNISHED BRASS|23|8 +Brand#12|MEDIUM BURNISHED COPPER|49|8 +Brand#12|MEDIUM BURNISHED NICKEL|3|8 +Brand#12|MEDIUM BURNISHED NICKEL|9|8 +Brand#12|MEDIUM BURNISHED NICKEL|49|8 +Brand#12|MEDIUM BURNISHED STEEL|3|8 +Brand#12|MEDIUM BURNISHED STEEL|9|8 +Brand#12|MEDIUM BURNISHED STEEL|14|8 +Brand#12|MEDIUM BURNISHED STEEL|19|8 +Brand#12|MEDIUM BURNISHED TIN|14|8 +Brand#12|MEDIUM PLATED BRASS|14|8 +Brand#12|MEDIUM PLATED BRASS|49|8 +Brand#12|MEDIUM PLATED NICKEL|9|8 +Brand#12|MEDIUM PLATED NICKEL|36|8 +Brand#12|MEDIUM PLATED NICKEL|49|8 +Brand#12|MEDIUM PLATED STEEL|14|8 +Brand#12|MEDIUM PLATED STEEL|23|8 +Brand#12|MEDIUM PLATED STEEL|45|8 +Brand#12|MEDIUM PLATED TIN|14|8 +Brand#12|MEDIUM PLATED TIN|19|8 +Brand#12|MEDIUM PLATED TIN|45|8 +Brand#12|PROMO ANODIZED BRASS|49|8 +Brand#12|PROMO ANODIZED COPPER|3|8 +Brand#12|PROMO ANODIZED COPPER|36|8 +Brand#12|PROMO ANODIZED COPPER|45|8 +Brand#12|PROMO ANODIZED COPPER|49|8 +Brand#12|PROMO ANODIZED NICKEL|14|8 +Brand#12|PROMO ANODIZED NICKEL|23|8 +Brand#12|PROMO ANODIZED TIN|19|8 +Brand#12|PROMO ANODIZED TIN|36|8 +Brand#12|PROMO BRUSHED BRASS|3|8 +Brand#12|PROMO BRUSHED BRASS|23|8 +Brand#12|PROMO BRUSHED BRASS|49|8 +Brand#12|PROMO BRUSHED COPPER|9|8 +Brand#12|PROMO BRUSHED COPPER|23|8 +Brand#12|PROMO BRUSHED NICKEL|23|8 +Brand#12|PROMO BRUSHED STEEL|23|8 +Brand#12|PROMO BURNISHED BRASS|3|8 +Brand#12|PROMO BURNISHED COPPER|3|8 +Brand#12|PROMO BURNISHED NICKEL|9|8 +Brand#12|PROMO BURNISHED NICKEL|49|8 +Brand#12|PROMO BURNISHED TIN|9|8 +Brand#12|PROMO BURNISHED TIN|14|8 +Brand#12|PROMO BURNISHED TIN|45|8 +Brand#12|PROMO PLATED BRASS|36|8 +Brand#12|PROMO PLATED BRASS|45|8 +Brand#12|PROMO PLATED BRASS|49|8 +Brand#12|PROMO PLATED COPPER|23|8 +Brand#12|PROMO PLATED COPPER|36|8 +Brand#12|PROMO PLATED NICKEL|14|8 +Brand#12|PROMO PLATED STEEL|3|8 +Brand#12|PROMO PLATED STEEL|49|8 +Brand#12|PROMO PLATED TIN|3|8 +Brand#12|PROMO PLATED TIN|9|8 +Brand#12|PROMO PLATED TIN|23|8 +Brand#12|PROMO PLATED TIN|36|8 +Brand#12|PROMO POLISHED BRASS|3|8 +Brand#12|PROMO POLISHED BRASS|9|8 +Brand#12|PROMO POLISHED BRASS|19|8 +Brand#12|PROMO POLISHED COPPER|19|8 +Brand#12|PROMO POLISHED COPPER|23|8 +Brand#12|PROMO POLISHED NICKEL|3|8 +Brand#12|PROMO POLISHED NICKEL|19|8 +Brand#12|PROMO POLISHED STEEL|3|8 +Brand#12|PROMO POLISHED STEEL|19|8 +Brand#12|PROMO POLISHED STEEL|36|8 +Brand#12|PROMO POLISHED TIN|23|8 +Brand#12|PROMO POLISHED TIN|36|8 +Brand#12|SMALL ANODIZED BRASS|36|8 +Brand#12|SMALL ANODIZED COPPER|9|8 +Brand#12|SMALL ANODIZED STEEL|9|8 +Brand#12|SMALL ANODIZED STEEL|45|8 +Brand#12|SMALL ANODIZED STEEL|49|8 +Brand#12|SMALL ANODIZED TIN|14|8 +Brand#12|SMALL ANODIZED TIN|19|8 +Brand#12|SMALL ANODIZED TIN|23|8 +Brand#12|SMALL BRUSHED BRASS|14|8 +Brand#12|SMALL BRUSHED BRASS|45|8 +Brand#12|SMALL BRUSHED COPPER|36|8 +Brand#12|SMALL BRUSHED NICKEL|14|8 +Brand#12|SMALL BRUSHED NICKEL|19|8 +Brand#12|SMALL BRUSHED NICKEL|23|8 +Brand#12|SMALL BRUSHED NICKEL|45|8 +Brand#12|SMALL BRUSHED STEEL|49|8 +Brand#12|SMALL BRUSHED TIN|9|8 +Brand#12|SMALL BURNISHED BRASS|9|8 +Brand#12|SMALL BURNISHED COPPER|36|8 +Brand#12|SMALL BURNISHED COPPER|49|8 +Brand#12|SMALL BURNISHED NICKEL|3|8 +Brand#12|SMALL BURNISHED NICKEL|9|8 +Brand#12|SMALL BURNISHED NICKEL|19|8 +Brand#12|SMALL BURNISHED NICKEL|36|8 +Brand#12|SMALL BURNISHED STEEL|9|8 +Brand#12|SMALL BURNISHED TIN|23|8 +Brand#12|SMALL BURNISHED TIN|45|8 +Brand#12|SMALL PLATED BRASS|45|8 +Brand#12|SMALL PLATED COPPER|19|8 +Brand#12|SMALL PLATED NICKEL|23|8 +Brand#12|SMALL PLATED TIN|45|8 +Brand#12|SMALL POLISHED BRASS|36|8 +Brand#12|SMALL POLISHED BRASS|45|8 +Brand#12|SMALL POLISHED COPPER|45|8 +Brand#12|SMALL POLISHED COPPER|49|8 +Brand#12|SMALL POLISHED TIN|36|8 +Brand#12|SMALL POLISHED TIN|45|8 +Brand#12|STANDARD ANODIZED BRASS|14|8 +Brand#12|STANDARD ANODIZED BRASS|49|8 +Brand#12|STANDARD ANODIZED NICKEL|19|8 +Brand#12|STANDARD ANODIZED NICKEL|49|8 +Brand#12|STANDARD ANODIZED STEEL|19|8 +Brand#12|STANDARD ANODIZED STEEL|36|8 +Brand#12|STANDARD ANODIZED TIN|36|8 +Brand#12|STANDARD BRUSHED BRASS|36|8 +Brand#12|STANDARD BRUSHED BRASS|45|8 +Brand#12|STANDARD BRUSHED COPPER|9|8 +Brand#12|STANDARD BRUSHED COPPER|36|8 +Brand#12|STANDARD BRUSHED NICKEL|3|8 +Brand#12|STANDARD BRUSHED NICKEL|14|8 +Brand#12|STANDARD BRUSHED NICKEL|19|8 +Brand#12|STANDARD BRUSHED NICKEL|36|8 +Brand#12|STANDARD BRUSHED NICKEL|45|8 +Brand#12|STANDARD BRUSHED STEEL|45|8 +Brand#12|STANDARD BRUSHED TIN|14|8 +Brand#12|STANDARD BRUSHED TIN|23|8 +Brand#12|STANDARD BURNISHED BRASS|23|8 +Brand#12|STANDARD BURNISHED BRASS|36|8 +Brand#12|STANDARD BURNISHED BRASS|49|8 +Brand#12|STANDARD BURNISHED COPPER|14|8 +Brand#12|STANDARD BURNISHED NICKEL|14|8 +Brand#12|STANDARD BURNISHED STEEL|14|8 +Brand#12|STANDARD BURNISHED TIN|14|8 +Brand#12|STANDARD BURNISHED TIN|23|8 +Brand#12|STANDARD BURNISHED TIN|36|8 +Brand#12|STANDARD PLATED BRASS|49|8 +Brand#12|STANDARD PLATED COPPER|14|8 +Brand#12|STANDARD PLATED STEEL|45|8 +Brand#12|STANDARD PLATED TIN|9|8 +Brand#12|STANDARD PLATED TIN|45|8 +Brand#12|STANDARD POLISHED COPPER|3|8 +Brand#12|STANDARD POLISHED COPPER|23|8 +Brand#12|STANDARD POLISHED COPPER|36|8 +Brand#12|STANDARD POLISHED NICKEL|36|8 +Brand#12|STANDARD POLISHED STEEL|3|8 +Brand#12|STANDARD POLISHED STEEL|14|8 +Brand#12|STANDARD POLISHED STEEL|19|8 +Brand#12|STANDARD POLISHED STEEL|45|8 +Brand#13|ECONOMY ANODIZED COPPER|9|8 +Brand#13|ECONOMY ANODIZED COPPER|23|8 +Brand#13|ECONOMY ANODIZED NICKEL|3|8 +Brand#13|ECONOMY ANODIZED STEEL|3|8 +Brand#13|ECONOMY BRUSHED COPPER|3|8 +Brand#13|ECONOMY BRUSHED COPPER|9|8 +Brand#13|ECONOMY BRUSHED COPPER|49|8 +Brand#13|ECONOMY BRUSHED NICKEL|49|8 +Brand#13|ECONOMY BRUSHED TIN|14|8 +Brand#13|ECONOMY BRUSHED TIN|23|8 +Brand#13|ECONOMY BURNISHED BRASS|45|8 +Brand#13|ECONOMY BURNISHED NICKEL|9|8 +Brand#13|ECONOMY BURNISHED STEEL|3|8 +Brand#13|ECONOMY BURNISHED STEEL|36|8 +Brand#13|ECONOMY BURNISHED TIN|49|8 +Brand#13|ECONOMY PLATED BRASS|36|8 +Brand#13|ECONOMY PLATED COPPER|3|8 +Brand#13|ECONOMY PLATED COPPER|9|8 +Brand#13|ECONOMY PLATED COPPER|19|8 +Brand#13|ECONOMY PLATED NICKEL|14|8 +Brand#13|ECONOMY PLATED STEEL|45|8 +Brand#13|ECONOMY PLATED TIN|3|8 +Brand#13|ECONOMY PLATED TIN|23|8 +Brand#13|ECONOMY POLISHED BRASS|9|8 +Brand#13|ECONOMY POLISHED BRASS|36|8 +Brand#13|ECONOMY POLISHED COPPER|9|8 +Brand#13|ECONOMY POLISHED COPPER|49|8 +Brand#13|ECONOMY POLISHED STEEL|3|8 +Brand#13|ECONOMY POLISHED STEEL|23|8 +Brand#13|ECONOMY POLISHED STEEL|45|8 +Brand#13|ECONOMY POLISHED STEEL|49|8 +Brand#13|ECONOMY POLISHED TIN|3|8 +Brand#13|ECONOMY POLISHED TIN|36|8 +Brand#13|LARGE ANODIZED COPPER|3|8 +Brand#13|LARGE ANODIZED COPPER|19|8 +Brand#13|LARGE ANODIZED STEEL|19|8 +Brand#13|LARGE ANODIZED STEEL|45|8 +Brand#13|LARGE ANODIZED TIN|45|8 +Brand#13|LARGE BRUSHED BRASS|9|8 +Brand#13|LARGE BRUSHED BRASS|19|8 +Brand#13|LARGE BRUSHED BRASS|45|8 +Brand#13|LARGE BRUSHED BRASS|49|8 +Brand#13|LARGE BRUSHED COPPER|45|8 +Brand#13|LARGE BRUSHED COPPER|49|8 +Brand#13|LARGE BRUSHED NICKEL|9|8 +Brand#13|LARGE BRUSHED STEEL|19|8 +Brand#13|LARGE BRUSHED STEEL|36|8 +Brand#13|LARGE BRUSHED TIN|9|8 +Brand#13|LARGE BURNISHED BRASS|3|8 +Brand#13|LARGE BURNISHED COPPER|3|8 +Brand#13|LARGE BURNISHED COPPER|23|8 +Brand#13|LARGE BURNISHED NICKEL|14|8 +Brand#13|LARGE BURNISHED STEEL|14|8 +Brand#13|LARGE BURNISHED STEEL|45|8 +Brand#13|LARGE PLATED BRASS|9|8 +Brand#13|LARGE PLATED COPPER|14|8 +Brand#13|LARGE PLATED NICKEL|19|8 +Brand#13|LARGE PLATED STEEL|3|8 +Brand#13|LARGE PLATED STEEL|36|8 +Brand#13|LARGE PLATED TIN|14|8 +Brand#13|LARGE PLATED TIN|45|8 +Brand#13|LARGE POLISHED BRASS|23|8 +Brand#13|LARGE POLISHED NICKEL|45|8 +Brand#13|LARGE POLISHED STEEL|36|8 +Brand#13|LARGE POLISHED TIN|3|8 +Brand#13|LARGE POLISHED TIN|9|8 +Brand#13|LARGE POLISHED TIN|14|8 +Brand#13|LARGE POLISHED TIN|45|8 +Brand#13|MEDIUM ANODIZED STEEL|23|8 +Brand#13|MEDIUM ANODIZED TIN|9|8 +Brand#13|MEDIUM ANODIZED TIN|45|8 +Brand#13|MEDIUM BRUSHED BRASS|14|8 +Brand#13|MEDIUM BRUSHED BRASS|36|8 +Brand#13|MEDIUM BRUSHED BRASS|49|8 +Brand#13|MEDIUM BRUSHED COPPER|23|8 +Brand#13|MEDIUM BRUSHED COPPER|49|8 +Brand#13|MEDIUM BRUSHED NICKEL|19|8 +Brand#13|MEDIUM BRUSHED STEEL|14|8 +Brand#13|MEDIUM BRUSHED TIN|9|8 +Brand#13|MEDIUM BURNISHED BRASS|19|8 +Brand#13|MEDIUM BURNISHED COPPER|3|8 +Brand#13|MEDIUM BURNISHED COPPER|19|8 +Brand#13|MEDIUM BURNISHED COPPER|23|8 +Brand#13|MEDIUM BURNISHED NICKEL|9|8 +Brand#13|MEDIUM BURNISHED NICKEL|23|8 +Brand#13|MEDIUM BURNISHED STEEL|14|8 +Brand#13|MEDIUM BURNISHED STEEL|19|8 +Brand#13|MEDIUM BURNISHED STEEL|45|8 +Brand#13|MEDIUM BURNISHED STEEL|49|8 +Brand#13|MEDIUM BURNISHED TIN|45|8 +Brand#13|MEDIUM BURNISHED TIN|49|8 +Brand#13|MEDIUM PLATED BRASS|19|8 +Brand#13|MEDIUM PLATED BRASS|23|8 +Brand#13|MEDIUM PLATED COPPER|14|8 +Brand#13|MEDIUM PLATED COPPER|19|8 +Brand#13|MEDIUM PLATED NICKEL|3|8 +Brand#13|MEDIUM PLATED NICKEL|36|8 +Brand#13|MEDIUM PLATED STEEL|3|8 +Brand#13|MEDIUM PLATED STEEL|9|8 +Brand#13|MEDIUM PLATED STEEL|19|8 +Brand#13|MEDIUM PLATED STEEL|36|8 +Brand#13|MEDIUM PLATED TIN|36|8 +Brand#13|PROMO ANODIZED BRASS|3|8 +Brand#13|PROMO ANODIZED COPPER|9|8 +Brand#13|PROMO ANODIZED COPPER|14|8 +Brand#13|PROMO ANODIZED COPPER|23|8 +Brand#13|PROMO ANODIZED NICKEL|3|8 +Brand#13|PROMO ANODIZED NICKEL|9|8 +Brand#13|PROMO ANODIZED NICKEL|45|8 +Brand#13|PROMO ANODIZED STEEL|19|8 +Brand#13|PROMO ANODIZED TIN|36|8 +Brand#13|PROMO ANODIZED TIN|49|8 +Brand#13|PROMO BRUSHED BRASS|3|8 +Brand#13|PROMO BRUSHED BRASS|23|8 +Brand#13|PROMO BRUSHED BRASS|49|8 +Brand#13|PROMO BRUSHED COPPER|14|8 +Brand#13|PROMO BRUSHED COPPER|19|8 +Brand#13|PROMO BRUSHED COPPER|49|8 +Brand#13|PROMO BRUSHED NICKEL|14|8 +Brand#13|PROMO BRUSHED TIN|45|8 +Brand#13|PROMO BURNISHED BRASS|9|8 +Brand#13|PROMO BURNISHED BRASS|23|8 +Brand#13|PROMO BURNISHED BRASS|36|8 +Brand#13|PROMO BURNISHED COPPER|9|8 +Brand#13|PROMO BURNISHED COPPER|23|8 +Brand#13|PROMO BURNISHED COPPER|45|8 +Brand#13|PROMO BURNISHED NICKEL|9|8 +Brand#13|PROMO BURNISHED STEEL|9|8 +Brand#13|PROMO BURNISHED STEEL|14|8 +Brand#13|PROMO BURNISHED STEEL|23|8 +Brand#13|PROMO BURNISHED STEEL|45|8 +Brand#13|PROMO BURNISHED TIN|9|8 +Brand#13|PROMO BURNISHED TIN|14|8 +Brand#13|PROMO BURNISHED TIN|19|8 +Brand#13|PROMO PLATED BRASS|14|8 +Brand#13|PROMO PLATED BRASS|49|8 +Brand#13|PROMO PLATED NICKEL|14|8 +Brand#13|PROMO PLATED NICKEL|36|8 +Brand#13|PROMO PLATED STEEL|9|8 +Brand#13|PROMO PLATED TIN|14|8 +Brand#13|PROMO PLATED TIN|23|8 +Brand#13|PROMO POLISHED BRASS|3|8 +Brand#13|PROMO POLISHED BRASS|19|8 +Brand#13|PROMO POLISHED BRASS|45|8 +Brand#13|PROMO POLISHED BRASS|49|8 +Brand#13|PROMO POLISHED NICKEL|23|8 +Brand#13|PROMO POLISHED STEEL|36|8 +Brand#13|PROMO POLISHED STEEL|45|8 +Brand#13|PROMO POLISHED TIN|19|8 +Brand#13|PROMO POLISHED TIN|36|8 +Brand#13|SMALL ANODIZED BRASS|14|8 +Brand#13|SMALL ANODIZED COPPER|9|8 +Brand#13|SMALL ANODIZED COPPER|23|8 +Brand#13|SMALL ANODIZED NICKEL|14|8 +Brand#13|SMALL ANODIZED NICKEL|45|8 +Brand#13|SMALL ANODIZED STEEL|14|8 +Brand#13|SMALL ANODIZED STEEL|23|8 +Brand#13|SMALL ANODIZED TIN|45|8 +Brand#13|SMALL BRUSHED BRASS|9|8 +Brand#13|SMALL BRUSHED BRASS|14|8 +Brand#13|SMALL BRUSHED BRASS|36|8 +Brand#13|SMALL BRUSHED BRASS|49|8 +Brand#13|SMALL BRUSHED COPPER|23|8 +Brand#13|SMALL BRUSHED COPPER|36|8 +Brand#13|SMALL BRUSHED NICKEL|9|8 +Brand#13|SMALL BRUSHED NICKEL|19|8 +Brand#13|SMALL BRUSHED STEEL|23|8 +Brand#13|SMALL BRUSHED STEEL|49|8 +Brand#13|SMALL BRUSHED TIN|9|8 +Brand#13|SMALL BRUSHED TIN|49|8 +Brand#13|SMALL BURNISHED BRASS|19|8 +Brand#13|SMALL BURNISHED BRASS|45|8 +Brand#13|SMALL BURNISHED COPPER|9|8 +Brand#13|SMALL BURNISHED NICKEL|19|8 +Brand#13|SMALL BURNISHED NICKEL|45|8 +Brand#13|SMALL BURNISHED STEEL|23|8 +Brand#13|SMALL BURNISHED TIN|9|8 +Brand#13|SMALL BURNISHED TIN|14|8 +Brand#13|SMALL BURNISHED TIN|36|8 +Brand#13|SMALL PLATED BRASS|9|8 +Brand#13|SMALL PLATED BRASS|49|8 +Brand#13|SMALL PLATED COPPER|3|8 +Brand#13|SMALL PLATED COPPER|23|8 +Brand#13|SMALL PLATED COPPER|36|8 +Brand#13|SMALL PLATED NICKEL|14|8 +Brand#13|SMALL PLATED STEEL|19|8 +Brand#13|SMALL PLATED TIN|19|8 +Brand#13|SMALL POLISHED BRASS|45|8 +Brand#13|SMALL POLISHED COPPER|3|8 +Brand#13|SMALL POLISHED COPPER|49|8 +Brand#13|SMALL POLISHED NICKEL|3|8 +Brand#13|SMALL POLISHED STEEL|36|8 +Brand#13|SMALL POLISHED STEEL|45|8 +Brand#13|SMALL POLISHED STEEL|49|8 +Brand#13|STANDARD ANODIZED BRASS|49|8 +Brand#13|STANDARD ANODIZED COPPER|14|8 +Brand#13|STANDARD ANODIZED COPPER|36|8 +Brand#13|STANDARD ANODIZED NICKEL|19|8 +Brand#13|STANDARD ANODIZED STEEL|14|8 +Brand#13|STANDARD ANODIZED TIN|9|8 +Brand#13|STANDARD ANODIZED TIN|36|8 +Brand#13|STANDARD BRUSHED BRASS|36|8 +Brand#13|STANDARD BRUSHED COPPER|3|8 +Brand#13|STANDARD BRUSHED NICKEL|14|8 +Brand#13|STANDARD BRUSHED NICKEL|49|8 +Brand#13|STANDARD BRUSHED STEEL|9|8 +Brand#13|STANDARD BRUSHED STEEL|49|8 +Brand#13|STANDARD BRUSHED TIN|9|8 +Brand#13|STANDARD BURNISHED BRASS|23|8 +Brand#13|STANDARD BURNISHED BRASS|36|8 +Brand#13|STANDARD BURNISHED COPPER|49|8 +Brand#13|STANDARD BURNISHED NICKEL|3|8 +Brand#13|STANDARD BURNISHED NICKEL|14|8 +Brand#13|STANDARD PLATED BRASS|9|8 +Brand#13|STANDARD PLATED BRASS|23|8 +Brand#13|STANDARD PLATED BRASS|49|8 +Brand#13|STANDARD PLATED COPPER|36|8 +Brand#13|STANDARD PLATED NICKEL|3|8 +Brand#13|STANDARD PLATED STEEL|14|8 +Brand#13|STANDARD PLATED STEEL|49|8 +Brand#13|STANDARD PLATED TIN|14|8 +Brand#13|STANDARD PLATED TIN|36|8 +Brand#13|STANDARD POLISHED COPPER|14|8 +Brand#13|STANDARD POLISHED NICKEL|3|8 +Brand#13|STANDARD POLISHED NICKEL|36|8 +Brand#13|STANDARD POLISHED STEEL|14|8 +Brand#13|STANDARD POLISHED STEEL|23|8 +Brand#13|STANDARD POLISHED STEEL|36|8 +Brand#13|STANDARD POLISHED TIN|23|8 +Brand#14|ECONOMY ANODIZED BRASS|36|8 +Brand#14|ECONOMY ANODIZED COPPER|36|8 +Brand#14|ECONOMY ANODIZED COPPER|45|8 +Brand#14|ECONOMY ANODIZED NICKEL|23|8 +Brand#14|ECONOMY ANODIZED NICKEL|45|8 +Brand#14|ECONOMY ANODIZED STEEL|3|8 +Brand#14|ECONOMY ANODIZED STEEL|9|8 +Brand#14|ECONOMY ANODIZED STEEL|19|8 +Brand#14|ECONOMY ANODIZED TIN|19|8 +Brand#14|ECONOMY BRUSHED BRASS|3|8 +Brand#14|ECONOMY BRUSHED BRASS|14|8 +Brand#14|ECONOMY BRUSHED BRASS|49|8 +Brand#14|ECONOMY BRUSHED NICKEL|14|8 +Brand#14|ECONOMY BRUSHED STEEL|49|8 +Brand#14|ECONOMY BRUSHED TIN|3|8 +Brand#14|ECONOMY BRUSHED TIN|49|8 +Brand#14|ECONOMY BURNISHED BRASS|14|8 +Brand#14|ECONOMY BURNISHED COPPER|36|8 +Brand#14|ECONOMY BURNISHED STEEL|23|8 +Brand#14|ECONOMY BURNISHED STEEL|36|8 +Brand#14|ECONOMY BURNISHED TIN|19|8 +Brand#14|ECONOMY BURNISHED TIN|23|8 +Brand#14|ECONOMY BURNISHED TIN|36|8 +Brand#14|ECONOMY PLATED BRASS|36|8 +Brand#14|ECONOMY PLATED COPPER|3|8 +Brand#14|ECONOMY PLATED COPPER|9|8 +Brand#14|ECONOMY PLATED NICKEL|3|8 +Brand#14|ECONOMY PLATED STEEL|36|8 +Brand#14|ECONOMY PLATED TIN|9|8 +Brand#14|ECONOMY POLISHED BRASS|3|8 +Brand#14|ECONOMY POLISHED BRASS|23|8 +Brand#14|ECONOMY POLISHED BRASS|36|8 +Brand#14|ECONOMY POLISHED COPPER|14|8 +Brand#14|ECONOMY POLISHED STEEL|3|8 +Brand#14|ECONOMY POLISHED TIN|19|8 +Brand#14|LARGE ANODIZED BRASS|19|8 +Brand#14|LARGE ANODIZED COPPER|23|8 +Brand#14|LARGE ANODIZED NICKEL|9|8 +Brand#14|LARGE ANODIZED NICKEL|49|8 +Brand#14|LARGE ANODIZED STEEL|3|8 +Brand#14|LARGE ANODIZED STEEL|45|8 +Brand#14|LARGE ANODIZED TIN|9|8 +Brand#14|LARGE ANODIZED TIN|19|8 +Brand#14|LARGE ANODIZED TIN|23|8 +Brand#14|LARGE BRUSHED BRASS|23|8 +Brand#14|LARGE BRUSHED BRASS|45|8 +Brand#14|LARGE BRUSHED COPPER|49|8 +Brand#14|LARGE BRUSHED NICKEL|23|8 +Brand#14|LARGE BRUSHED NICKEL|45|8 +Brand#14|LARGE BRUSHED TIN|9|8 +Brand#14|LARGE BURNISHED BRASS|14|8 +Brand#14|LARGE BURNISHED COPPER|19|8 +Brand#14|LARGE BURNISHED NICKEL|3|8 +Brand#14|LARGE BURNISHED NICKEL|49|8 +Brand#14|LARGE BURNISHED STEEL|3|8 +Brand#14|LARGE BURNISHED STEEL|9|8 +Brand#14|LARGE BURNISHED STEEL|14|8 +Brand#14|LARGE BURNISHED STEEL|19|8 +Brand#14|LARGE BURNISHED STEEL|45|8 +Brand#14|LARGE BURNISHED TIN|19|8 +Brand#14|LARGE BURNISHED TIN|23|8 +Brand#14|LARGE BURNISHED TIN|45|8 +Brand#14|LARGE PLATED BRASS|23|8 +Brand#14|LARGE PLATED COPPER|36|8 +Brand#14|LARGE PLATED NICKEL|23|8 +Brand#14|LARGE PLATED NICKEL|49|8 +Brand#14|LARGE PLATED STEEL|49|8 +Brand#14|LARGE POLISHED BRASS|3|8 +Brand#14|LARGE POLISHED BRASS|9|8 +Brand#14|LARGE POLISHED BRASS|14|8 +Brand#14|LARGE POLISHED BRASS|19|8 +Brand#14|LARGE POLISHED BRASS|36|8 +Brand#14|LARGE POLISHED COPPER|9|8 +Brand#14|LARGE POLISHED COPPER|23|8 +Brand#14|LARGE POLISHED NICKEL|14|8 +Brand#14|LARGE POLISHED NICKEL|36|8 +Brand#14|LARGE POLISHED STEEL|23|8 +Brand#14|LARGE POLISHED TIN|36|8 +Brand#14|LARGE POLISHED TIN|45|8 +Brand#14|LARGE POLISHED TIN|49|8 +Brand#14|MEDIUM ANODIZED BRASS|14|8 +Brand#14|MEDIUM ANODIZED COPPER|9|8 +Brand#14|MEDIUM ANODIZED COPPER|19|8 +Brand#14|MEDIUM ANODIZED COPPER|36|8 +Brand#14|MEDIUM ANODIZED COPPER|49|8 +Brand#14|MEDIUM ANODIZED NICKEL|9|8 +Brand#14|MEDIUM ANODIZED NICKEL|36|8 +Brand#14|MEDIUM BRUSHED COPPER|9|8 +Brand#14|MEDIUM BRUSHED COPPER|23|8 +Brand#14|MEDIUM BRUSHED STEEL|49|8 +Brand#14|MEDIUM BRUSHED TIN|3|8 +Brand#14|MEDIUM BRUSHED TIN|9|8 +Brand#14|MEDIUM BURNISHED BRASS|19|8 +Brand#14|MEDIUM BURNISHED BRASS|23|8 +Brand#14|MEDIUM BURNISHED NICKEL|9|8 +Brand#14|MEDIUM BURNISHED NICKEL|19|8 +Brand#14|MEDIUM BURNISHED NICKEL|23|8 +Brand#14|MEDIUM BURNISHED NICKEL|49|8 +Brand#14|MEDIUM BURNISHED STEEL|36|8 +Brand#14|MEDIUM BURNISHED STEEL|49|8 +Brand#14|MEDIUM BURNISHED TIN|49|8 +Brand#14|MEDIUM PLATED BRASS|9|8 +Brand#14|MEDIUM PLATED BRASS|23|8 +Brand#14|MEDIUM PLATED BRASS|36|8 +Brand#14|MEDIUM PLATED BRASS|45|8 +Brand#14|MEDIUM PLATED BRASS|49|8 +Brand#14|MEDIUM PLATED NICKEL|23|8 +Brand#14|MEDIUM PLATED STEEL|36|8 +Brand#14|MEDIUM PLATED STEEL|49|8 +Brand#14|MEDIUM PLATED TIN|3|8 +Brand#14|MEDIUM PLATED TIN|14|8 +Brand#14|MEDIUM PLATED TIN|45|8 +Brand#14|PROMO ANODIZED BRASS|23|8 +Brand#14|PROMO ANODIZED BRASS|36|8 +Brand#14|PROMO ANODIZED COPPER|19|8 +Brand#14|PROMO ANODIZED COPPER|36|8 +Brand#14|PROMO ANODIZED COPPER|45|8 +Brand#14|PROMO ANODIZED STEEL|45|8 +Brand#14|PROMO ANODIZED TIN|14|8 +Brand#14|PROMO ANODIZED TIN|19|8 +Brand#14|PROMO BRUSHED BRASS|14|8 +Brand#14|PROMO BRUSHED BRASS|19|8 +Brand#14|PROMO BRUSHED BRASS|36|8 +Brand#14|PROMO BRUSHED BRASS|45|8 +Brand#14|PROMO BRUSHED COPPER|23|8 +Brand#14|PROMO BRUSHED COPPER|49|8 +Brand#14|PROMO BRUSHED NICKEL|19|8 +Brand#14|PROMO BRUSHED NICKEL|36|8 +Brand#14|PROMO BRUSHED STEEL|9|8 +Brand#14|PROMO BRUSHED STEEL|36|8 +Brand#14|PROMO BRUSHED STEEL|49|8 +Brand#14|PROMO BURNISHED BRASS|9|8 +Brand#14|PROMO BURNISHED BRASS|23|8 +Brand#14|PROMO BURNISHED BRASS|36|8 +Brand#14|PROMO BURNISHED BRASS|45|8 +Brand#14|PROMO BURNISHED NICKEL|9|8 +Brand#14|PROMO BURNISHED STEEL|36|8 +Brand#14|PROMO BURNISHED TIN|49|8 +Brand#14|PROMO PLATED BRASS|14|8 +Brand#14|PROMO PLATED BRASS|45|8 +Brand#14|PROMO PLATED COPPER|23|8 +Brand#14|PROMO PLATED NICKEL|9|8 +Brand#14|PROMO PLATED STEEL|3|8 +Brand#14|PROMO PLATED STEEL|14|8 +Brand#14|PROMO PLATED STEEL|19|8 +Brand#14|PROMO PLATED STEEL|49|8 +Brand#14|PROMO PLATED TIN|3|8 +Brand#14|PROMO PLATED TIN|9|8 +Brand#14|PROMO POLISHED BRASS|36|8 +Brand#14|PROMO POLISHED COPPER|3|8 +Brand#14|PROMO POLISHED NICKEL|3|8 +Brand#14|PROMO POLISHED NICKEL|45|8 +Brand#14|PROMO POLISHED TIN|9|8 +Brand#14|PROMO POLISHED TIN|49|8 +Brand#14|SMALL ANODIZED BRASS|9|8 +Brand#14|SMALL ANODIZED BRASS|14|8 +Brand#14|SMALL ANODIZED COPPER|14|8 +Brand#14|SMALL ANODIZED NICKEL|36|8 +Brand#14|SMALL ANODIZED STEEL|23|8 +Brand#14|SMALL ANODIZED TIN|19|8 +Brand#14|SMALL BRUSHED BRASS|19|8 +Brand#14|SMALL BRUSHED BRASS|45|8 +Brand#14|SMALL BRUSHED COPPER|36|8 +Brand#14|SMALL BRUSHED COPPER|49|8 +Brand#14|SMALL BRUSHED TIN|9|8 +Brand#14|SMALL BRUSHED TIN|14|8 +Brand#14|SMALL BRUSHED TIN|36|8 +Brand#14|SMALL BURNISHED BRASS|19|8 +Brand#14|SMALL BURNISHED BRASS|45|8 +Brand#14|SMALL BURNISHED COPPER|14|8 +Brand#14|SMALL BURNISHED COPPER|36|8 +Brand#14|SMALL BURNISHED NICKEL|36|8 +Brand#14|SMALL BURNISHED NICKEL|45|8 +Brand#14|SMALL BURNISHED STEEL|14|8 +Brand#14|SMALL BURNISHED STEEL|45|8 +Brand#14|SMALL BURNISHED TIN|19|8 +Brand#14|SMALL BURNISHED TIN|23|8 +Brand#14|SMALL PLATED BRASS|14|8 +Brand#14|SMALL PLATED COPPER|23|8 +Brand#14|SMALL PLATED NICKEL|19|8 +Brand#14|SMALL PLATED STEEL|14|8 +Brand#14|SMALL PLATED STEEL|36|8 +Brand#14|SMALL PLATED TIN|9|8 +Brand#14|SMALL PLATED TIN|49|8 +Brand#14|SMALL POLISHED BRASS|19|8 +Brand#14|SMALL POLISHED BRASS|36|8 +Brand#14|SMALL POLISHED BRASS|45|8 +Brand#14|SMALL POLISHED COPPER|3|8 +Brand#14|SMALL POLISHED NICKEL|9|8 +Brand#14|SMALL POLISHED NICKEL|19|8 +Brand#14|SMALL POLISHED STEEL|49|8 +Brand#14|SMALL POLISHED TIN|3|8 +Brand#14|SMALL POLISHED TIN|36|8 +Brand#14|STANDARD ANODIZED BRASS|3|8 +Brand#14|STANDARD ANODIZED COPPER|3|8 +Brand#14|STANDARD ANODIZED COPPER|23|8 +Brand#14|STANDARD ANODIZED STEEL|9|8 +Brand#14|STANDARD BRUSHED BRASS|19|8 +Brand#14|STANDARD BRUSHED COPPER|3|8 +Brand#14|STANDARD BRUSHED NICKEL|3|8 +Brand#14|STANDARD BRUSHED STEEL|23|8 +Brand#14|STANDARD BRUSHED TIN|9|8 +Brand#14|STANDARD BRUSHED TIN|45|8 +Brand#14|STANDARD BRUSHED TIN|49|8 +Brand#14|STANDARD BURNISHED BRASS|9|8 +Brand#14|STANDARD BURNISHED BRASS|49|8 +Brand#14|STANDARD BURNISHED COPPER|14|8 +Brand#14|STANDARD BURNISHED NICKEL|3|8 +Brand#14|STANDARD BURNISHED NICKEL|19|8 +Brand#14|STANDARD BURNISHED NICKEL|23|8 +Brand#14|STANDARD BURNISHED STEEL|14|8 +Brand#14|STANDARD BURNISHED STEEL|19|8 +Brand#14|STANDARD BURNISHED STEEL|23|8 +Brand#14|STANDARD BURNISHED TIN|3|8 +Brand#14|STANDARD BURNISHED TIN|9|8 +Brand#14|STANDARD PLATED BRASS|9|8 +Brand#14|STANDARD PLATED BRASS|45|8 +Brand#14|STANDARD PLATED COPPER|14|8 +Brand#14|STANDARD PLATED NICKEL|14|8 +Brand#14|STANDARD PLATED STEEL|23|8 +Brand#14|STANDARD PLATED TIN|3|8 +Brand#14|STANDARD POLISHED BRASS|19|8 +Brand#14|STANDARD POLISHED COPPER|3|8 +Brand#14|STANDARD POLISHED COPPER|49|8 +Brand#14|STANDARD POLISHED NICKEL|3|8 +Brand#14|STANDARD POLISHED NICKEL|9|8 +Brand#14|STANDARD POLISHED NICKEL|19|8 +Brand#15|ECONOMY ANODIZED BRASS|3|8 +Brand#15|ECONOMY ANODIZED BRASS|9|8 +Brand#15|ECONOMY ANODIZED STEEL|3|8 +Brand#15|ECONOMY ANODIZED STEEL|14|8 +Brand#15|ECONOMY ANODIZED STEEL|36|8 +Brand#15|ECONOMY ANODIZED TIN|36|8 +Brand#15|ECONOMY BRUSHED BRASS|19|8 +Brand#15|ECONOMY BRUSHED NICKEL|3|8 +Brand#15|ECONOMY BRUSHED NICKEL|36|8 +Brand#15|ECONOMY BRUSHED NICKEL|45|8 +Brand#15|ECONOMY BRUSHED STEEL|23|8 +Brand#15|ECONOMY BRUSHED STEEL|45|8 +Brand#15|ECONOMY BRUSHED TIN|36|8 +Brand#15|ECONOMY BRUSHED TIN|49|8 +Brand#15|ECONOMY BURNISHED BRASS|9|8 +Brand#15|ECONOMY BURNISHED BRASS|14|8 +Brand#15|ECONOMY BURNISHED COPPER|23|8 +Brand#15|ECONOMY BURNISHED COPPER|45|8 +Brand#15|ECONOMY BURNISHED NICKEL|49|8 +Brand#15|ECONOMY PLATED BRASS|14|8 +Brand#15|ECONOMY PLATED COPPER|36|8 +Brand#15|ECONOMY PLATED COPPER|45|8 +Brand#15|ECONOMY POLISHED COPPER|49|8 +Brand#15|ECONOMY POLISHED NICKEL|9|8 +Brand#15|ECONOMY POLISHED NICKEL|14|8 +Brand#15|ECONOMY POLISHED STEEL|49|8 +Brand#15|LARGE ANODIZED BRASS|9|8 +Brand#15|LARGE ANODIZED BRASS|36|8 +Brand#15|LARGE ANODIZED COPPER|23|8 +Brand#15|LARGE ANODIZED COPPER|36|8 +Brand#15|LARGE ANODIZED COPPER|45|8 +Brand#15|LARGE ANODIZED NICKEL|23|8 +Brand#15|LARGE ANODIZED NICKEL|49|8 +Brand#15|LARGE ANODIZED TIN|14|8 +Brand#15|LARGE BRUSHED BRASS|9|8 +Brand#15|LARGE BRUSHED COPPER|3|8 +Brand#15|LARGE BRUSHED COPPER|14|8 +Brand#15|LARGE BRUSHED STEEL|19|8 +Brand#15|LARGE BRUSHED STEEL|23|8 +Brand#15|LARGE BRUSHED TIN|3|8 +Brand#15|LARGE BRUSHED TIN|9|8 +Brand#15|LARGE BRUSHED TIN|19|8 +Brand#15|LARGE BRUSHED TIN|49|8 +Brand#15|LARGE BURNISHED BRASS|9|8 +Brand#15|LARGE BURNISHED BRASS|14|8 +Brand#15|LARGE BURNISHED BRASS|19|8 +Brand#15|LARGE BURNISHED COPPER|23|8 +Brand#15|LARGE BURNISHED COPPER|45|8 +Brand#15|LARGE BURNISHED NICKEL|36|8 +Brand#15|LARGE BURNISHED STEEL|36|8 +Brand#15|LARGE BURNISHED STEEL|49|8 +Brand#15|LARGE BURNISHED TIN|49|8 +Brand#15|LARGE PLATED COPPER|19|8 +Brand#15|LARGE PLATED COPPER|45|8 +Brand#15|LARGE PLATED NICKEL|14|8 +Brand#15|LARGE PLATED STEEL|9|8 +Brand#15|LARGE PLATED TIN|49|8 +Brand#15|LARGE POLISHED BRASS|23|8 +Brand#15|LARGE POLISHED STEEL|36|8 +Brand#15|LARGE POLISHED STEEL|49|8 +Brand#15|LARGE POLISHED TIN|19|8 +Brand#15|MEDIUM ANODIZED BRASS|3|8 +Brand#15|MEDIUM ANODIZED BRASS|9|8 +Brand#15|MEDIUM ANODIZED BRASS|19|8 +Brand#15|MEDIUM ANODIZED BRASS|23|8 +Brand#15|MEDIUM ANODIZED COPPER|36|8 +Brand#15|MEDIUM ANODIZED NICKEL|45|8 +Brand#15|MEDIUM ANODIZED STEEL|23|8 +Brand#15|MEDIUM ANODIZED TIN|14|8 +Brand#15|MEDIUM ANODIZED TIN|19|8 +Brand#15|MEDIUM ANODIZED TIN|23|8 +Brand#15|MEDIUM BRUSHED BRASS|3|8 +Brand#15|MEDIUM BRUSHED BRASS|23|8 +Brand#15|MEDIUM BRUSHED COPPER|49|8 +Brand#15|MEDIUM BRUSHED NICKEL|9|8 +Brand#15|MEDIUM BRUSHED TIN|9|8 +Brand#15|MEDIUM BRUSHED TIN|23|8 +Brand#15|MEDIUM BURNISHED BRASS|45|8 +Brand#15|MEDIUM BURNISHED COPPER|3|8 +Brand#15|MEDIUM BURNISHED COPPER|49|8 +Brand#15|MEDIUM BURNISHED NICKEL|19|8 +Brand#15|MEDIUM BURNISHED NICKEL|36|8 +Brand#15|MEDIUM BURNISHED NICKEL|49|8 +Brand#15|MEDIUM BURNISHED STEEL|23|8 +Brand#15|MEDIUM BURNISHED STEEL|49|8 +Brand#15|MEDIUM BURNISHED TIN|45|8 +Brand#15|MEDIUM PLATED BRASS|36|8 +Brand#15|MEDIUM PLATED NICKEL|23|8 +Brand#15|MEDIUM PLATED NICKEL|49|8 +Brand#15|MEDIUM PLATED STEEL|9|8 +Brand#15|MEDIUM PLATED STEEL|19|8 +Brand#15|MEDIUM PLATED STEEL|49|8 +Brand#15|MEDIUM PLATED TIN|19|8 +Brand#15|MEDIUM PLATED TIN|49|8 +Brand#15|PROMO ANODIZED BRASS|14|8 +Brand#15|PROMO ANODIZED BRASS|36|8 +Brand#15|PROMO ANODIZED COPPER|45|8 +Brand#15|PROMO ANODIZED NICKEL|36|8 +Brand#15|PROMO ANODIZED NICKEL|49|8 +Brand#15|PROMO ANODIZED STEEL|14|8 +Brand#15|PROMO BRUSHED BRASS|14|8 +Brand#15|PROMO BRUSHED COPPER|9|8 +Brand#15|PROMO BRUSHED COPPER|19|8 +Brand#15|PROMO BRUSHED NICKEL|9|8 +Brand#15|PROMO BRUSHED NICKEL|19|8 +Brand#15|PROMO BRUSHED NICKEL|23|8 +Brand#15|PROMO BRUSHED STEEL|14|8 +Brand#15|PROMO BRUSHED STEEL|23|8 +Brand#15|PROMO BRUSHED STEEL|49|8 +Brand#15|PROMO BRUSHED TIN|3|8 +Brand#15|PROMO BRUSHED TIN|23|8 +Brand#15|PROMO BRUSHED TIN|36|8 +Brand#15|PROMO BURNISHED BRASS|23|8 +Brand#15|PROMO BURNISHED BRASS|36|8 +Brand#15|PROMO BURNISHED COPPER|3|8 +Brand#15|PROMO BURNISHED COPPER|9|8 +Brand#15|PROMO BURNISHED COPPER|19|8 +Brand#15|PROMO BURNISHED NICKEL|23|8 +Brand#15|PROMO BURNISHED NICKEL|36|8 +Brand#15|PROMO BURNISHED NICKEL|49|8 +Brand#15|PROMO BURNISHED STEEL|9|8 +Brand#15|PROMO BURNISHED TIN|14|8 +Brand#15|PROMO BURNISHED TIN|45|8 +Brand#15|PROMO PLATED BRASS|36|8 +Brand#15|PROMO PLATED BRASS|49|8 +Brand#15|PROMO PLATED COPPER|3|8 +Brand#15|PROMO PLATED COPPER|9|8 +Brand#15|PROMO PLATED COPPER|14|8 +Brand#15|PROMO PLATED NICKEL|36|8 +Brand#15|PROMO PLATED NICKEL|45|8 +Brand#15|PROMO PLATED STEEL|14|8 +Brand#15|PROMO PLATED TIN|3|8 +Brand#15|PROMO PLATED TIN|9|8 +Brand#15|PROMO PLATED TIN|19|8 +Brand#15|PROMO POLISHED COPPER|3|8 +Brand#15|PROMO POLISHED COPPER|14|8 +Brand#15|PROMO POLISHED COPPER|19|8 +Brand#15|PROMO POLISHED COPPER|49|8 +Brand#15|PROMO POLISHED NICKEL|19|8 +Brand#15|PROMO POLISHED STEEL|3|8 +Brand#15|PROMO POLISHED STEEL|14|8 +Brand#15|PROMO POLISHED STEEL|19|8 +Brand#15|PROMO POLISHED TIN|23|8 +Brand#15|SMALL ANODIZED BRASS|14|8 +Brand#15|SMALL ANODIZED BRASS|19|8 +Brand#15|SMALL ANODIZED NICKEL|3|8 +Brand#15|SMALL ANODIZED NICKEL|14|8 +Brand#15|SMALL ANODIZED NICKEL|36|8 +Brand#15|SMALL ANODIZED STEEL|3|8 +Brand#15|SMALL ANODIZED TIN|45|8 +Brand#15|SMALL BRUSHED BRASS|3|8 +Brand#15|SMALL BRUSHED BRASS|9|8 +Brand#15|SMALL BRUSHED BRASS|19|8 +Brand#15|SMALL BRUSHED NICKEL|9|8 +Brand#15|SMALL BRUSHED NICKEL|49|8 +Brand#15|SMALL BRUSHED STEEL|14|8 +Brand#15|SMALL BRUSHED STEEL|23|8 +Brand#15|SMALL BRUSHED TIN|9|8 +Brand#15|SMALL BRUSHED TIN|23|8 +Brand#15|SMALL BRUSHED TIN|36|8 +Brand#15|SMALL BRUSHED TIN|45|8 +Brand#15|SMALL BURNISHED BRASS|19|8 +Brand#15|SMALL BURNISHED COPPER|14|8 +Brand#15|SMALL BURNISHED COPPER|49|8 +Brand#15|SMALL BURNISHED NICKEL|3|8 +Brand#15|SMALL BURNISHED NICKEL|9|8 +Brand#15|SMALL BURNISHED NICKEL|36|8 +Brand#15|SMALL BURNISHED STEEL|9|8 +Brand#15|SMALL BURNISHED STEEL|19|8 +Brand#15|SMALL BURNISHED TIN|14|8 +Brand#15|SMALL BURNISHED TIN|19|8 +Brand#15|SMALL BURNISHED TIN|23|8 +Brand#15|SMALL PLATED STEEL|3|8 +Brand#15|SMALL PLATED STEEL|9|8 +Brand#15|SMALL PLATED TIN|9|8 +Brand#15|SMALL POLISHED COPPER|3|8 +Brand#15|SMALL POLISHED COPPER|9|8 +Brand#15|SMALL POLISHED NICKEL|14|8 +Brand#15|SMALL POLISHED STEEL|3|8 +Brand#15|SMALL POLISHED STEEL|9|8 +Brand#15|SMALL POLISHED STEEL|23|8 +Brand#15|SMALL POLISHED STEEL|36|8 +Brand#15|SMALL POLISHED TIN|9|8 +Brand#15|SMALL POLISHED TIN|19|8 +Brand#15|SMALL POLISHED TIN|45|8 +Brand#15|STANDARD ANODIZED BRASS|19|8 +Brand#15|STANDARD ANODIZED BRASS|23|8 +Brand#15|STANDARD ANODIZED COPPER|3|8 +Brand#15|STANDARD ANODIZED COPPER|23|8 +Brand#15|STANDARD ANODIZED COPPER|36|8 +Brand#15|STANDARD BRUSHED COPPER|23|8 +Brand#15|STANDARD BRUSHED NICKEL|9|8 +Brand#15|STANDARD BRUSHED NICKEL|19|8 +Brand#15|STANDARD BRUSHED STEEL|49|8 +Brand#15|STANDARD BRUSHED TIN|45|8 +Brand#15|STANDARD BURNISHED BRASS|23|8 +Brand#15|STANDARD BURNISHED NICKEL|9|8 +Brand#15|STANDARD BURNISHED NICKEL|14|8 +Brand#15|STANDARD BURNISHED NICKEL|49|8 +Brand#15|STANDARD BURNISHED STEEL|45|8 +Brand#15|STANDARD PLATED BRASS|14|8 +Brand#15|STANDARD PLATED BRASS|36|8 +Brand#15|STANDARD PLATED COPPER|9|8 +Brand#15|STANDARD PLATED NICKEL|9|8 +Brand#15|STANDARD PLATED STEEL|23|8 +Brand#15|STANDARD POLISHED BRASS|3|8 +Brand#15|STANDARD POLISHED BRASS|9|8 +Brand#15|STANDARD POLISHED BRASS|14|8 +Brand#15|STANDARD POLISHED COPPER|3|8 +Brand#15|STANDARD POLISHED COPPER|23|8 +Brand#15|STANDARD POLISHED NICKEL|14|8 +Brand#15|STANDARD POLISHED NICKEL|36|8 +Brand#15|STANDARD POLISHED NICKEL|45|8 +Brand#15|STANDARD POLISHED TIN|3|8 +Brand#15|STANDARD POLISHED TIN|36|8 +Brand#21|ECONOMY ANODIZED BRASS|14|8 +Brand#21|ECONOMY ANODIZED COPPER|3|8 +Brand#21|ECONOMY ANODIZED COPPER|14|8 +Brand#21|ECONOMY ANODIZED COPPER|36|8 +Brand#21|ECONOMY ANODIZED NICKEL|14|8 +Brand#21|ECONOMY ANODIZED NICKEL|36|8 +Brand#21|ECONOMY ANODIZED NICKEL|45|8 +Brand#21|ECONOMY ANODIZED STEEL|36|8 +Brand#21|ECONOMY ANODIZED STEEL|49|8 +Brand#21|ECONOMY ANODIZED TIN|9|8 +Brand#21|ECONOMY BRUSHED BRASS|14|8 +Brand#21|ECONOMY BRUSHED BRASS|36|8 +Brand#21|ECONOMY BRUSHED COPPER|45|8 +Brand#21|ECONOMY BRUSHED NICKEL|36|8 +Brand#21|ECONOMY BURNISHED BRASS|3|8 +Brand#21|ECONOMY BURNISHED NICKEL|3|8 +Brand#21|ECONOMY BURNISHED NICKEL|49|8 +Brand#21|ECONOMY BURNISHED STEEL|23|8 +Brand#21|ECONOMY BURNISHED STEEL|36|8 +Brand#21|ECONOMY BURNISHED TIN|14|8 +Brand#21|ECONOMY BURNISHED TIN|19|8 +Brand#21|ECONOMY BURNISHED TIN|45|8 +Brand#21|ECONOMY PLATED BRASS|9|8 +Brand#21|ECONOMY PLATED NICKEL|49|8 +Brand#21|ECONOMY PLATED STEEL|19|8 +Brand#21|ECONOMY PLATED STEEL|23|8 +Brand#21|ECONOMY POLISHED BRASS|23|8 +Brand#21|ECONOMY POLISHED COPPER|3|8 +Brand#21|ECONOMY POLISHED NICKEL|3|8 +Brand#21|ECONOMY POLISHED NICKEL|19|8 +Brand#21|ECONOMY POLISHED NICKEL|36|8 +Brand#21|ECONOMY POLISHED STEEL|36|8 +Brand#21|ECONOMY POLISHED STEEL|49|8 +Brand#21|ECONOMY POLISHED TIN|3|8 +Brand#21|ECONOMY POLISHED TIN|45|8 +Brand#21|LARGE ANODIZED BRASS|45|8 +Brand#21|LARGE ANODIZED NICKEL|9|8 +Brand#21|LARGE ANODIZED NICKEL|19|8 +Brand#21|LARGE ANODIZED NICKEL|49|8 +Brand#21|LARGE ANODIZED STEEL|3|8 +Brand#21|LARGE ANODIZED STEEL|36|8 +Brand#21|LARGE BRUSHED BRASS|19|8 +Brand#21|LARGE BRUSHED BRASS|23|8 +Brand#21|LARGE BRUSHED BRASS|45|8 +Brand#21|LARGE BRUSHED COPPER|19|8 +Brand#21|LARGE BRUSHED NICKEL|14|8 +Brand#21|LARGE BRUSHED NICKEL|45|8 +Brand#21|LARGE BRUSHED STEEL|45|8 +Brand#21|LARGE BRUSHED TIN|9|8 +Brand#21|LARGE BRUSHED TIN|19|8 +Brand#21|LARGE BRUSHED TIN|36|8 +Brand#21|LARGE BURNISHED COPPER|3|8 +Brand#21|LARGE BURNISHED COPPER|9|8 +Brand#21|LARGE BURNISHED COPPER|14|8 +Brand#21|LARGE BURNISHED COPPER|19|8 +Brand#21|LARGE BURNISHED COPPER|23|8 +Brand#21|LARGE BURNISHED NICKEL|9|8 +Brand#21|LARGE BURNISHED NICKEL|36|8 +Brand#21|LARGE BURNISHED STEEL|14|8 +Brand#21|LARGE BURNISHED STEEL|45|8 +Brand#21|LARGE BURNISHED STEEL|49|8 +Brand#21|LARGE BURNISHED TIN|14|8 +Brand#21|LARGE BURNISHED TIN|49|8 +Brand#21|LARGE PLATED BRASS|19|8 +Brand#21|LARGE PLATED BRASS|23|8 +Brand#21|LARGE PLATED NICKEL|23|8 +Brand#21|LARGE PLATED STEEL|3|8 +Brand#21|LARGE PLATED STEEL|19|8 +Brand#21|LARGE PLATED STEEL|45|8 +Brand#21|LARGE PLATED TIN|9|8 +Brand#21|LARGE PLATED TIN|23|8 +Brand#21|LARGE POLISHED BRASS|36|8 +Brand#21|LARGE POLISHED BRASS|49|8 +Brand#21|LARGE POLISHED COPPER|23|8 +Brand#21|LARGE POLISHED NICKEL|3|8 +Brand#21|LARGE POLISHED NICKEL|23|8 +Brand#21|LARGE POLISHED NICKEL|45|8 +Brand#21|LARGE POLISHED STEEL|3|8 +Brand#21|LARGE POLISHED STEEL|9|8 +Brand#21|LARGE POLISHED STEEL|23|8 +Brand#21|LARGE POLISHED TIN|3|8 +Brand#21|LARGE POLISHED TIN|19|8 +Brand#21|LARGE POLISHED TIN|45|8 +Brand#21|MEDIUM ANODIZED BRASS|3|8 +Brand#21|MEDIUM ANODIZED BRASS|14|8 +Brand#21|MEDIUM ANODIZED BRASS|23|8 +Brand#21|MEDIUM ANODIZED NICKEL|36|8 +Brand#21|MEDIUM ANODIZED TIN|9|8 +Brand#21|MEDIUM ANODIZED TIN|14|8 +Brand#21|MEDIUM ANODIZED TIN|23|8 +Brand#21|MEDIUM ANODIZED TIN|45|8 +Brand#21|MEDIUM BRUSHED BRASS|45|8 +Brand#21|MEDIUM BRUSHED BRASS|49|8 +Brand#21|MEDIUM BRUSHED COPPER|3|8 +Brand#21|MEDIUM BRUSHED COPPER|14|8 +Brand#21|MEDIUM BRUSHED NICKEL|14|8 +Brand#21|MEDIUM BRUSHED STEEL|23|8 +Brand#21|MEDIUM BRUSHED STEEL|45|8 +Brand#21|MEDIUM BURNISHED BRASS|36|8 +Brand#21|MEDIUM BURNISHED NICKEL|14|8 +Brand#21|MEDIUM BURNISHED STEEL|23|8 +Brand#21|MEDIUM PLATED BRASS|45|8 +Brand#21|MEDIUM PLATED COPPER|23|8 +Brand#21|MEDIUM PLATED COPPER|49|8 +Brand#21|MEDIUM PLATED TIN|36|8 +Brand#21|PROMO ANODIZED BRASS|14|8 +Brand#21|PROMO ANODIZED BRASS|19|8 +Brand#21|PROMO ANODIZED COPPER|14|8 +Brand#21|PROMO ANODIZED COPPER|23|8 +Brand#21|PROMO ANODIZED COPPER|45|8 +Brand#21|PROMO ANODIZED NICKEL|14|8 +Brand#21|PROMO ANODIZED NICKEL|23|8 +Brand#21|PROMO ANODIZED STEEL|3|8 +Brand#21|PROMO ANODIZED STEEL|9|8 +Brand#21|PROMO ANODIZED TIN|23|8 +Brand#21|PROMO BRUSHED BRASS|23|8 +Brand#21|PROMO BRUSHED BRASS|45|8 +Brand#21|PROMO BRUSHED BRASS|49|8 +Brand#21|PROMO BRUSHED NICKEL|45|8 +Brand#21|PROMO BRUSHED STEEL|23|8 +Brand#21|PROMO BURNISHED BRASS|19|8 +Brand#21|PROMO BURNISHED BRASS|23|8 +Brand#21|PROMO BURNISHED COPPER|9|8 +Brand#21|PROMO BURNISHED COPPER|49|8 +Brand#21|PROMO BURNISHED NICKEL|19|8 +Brand#21|PROMO BURNISHED NICKEL|23|8 +Brand#21|PROMO BURNISHED STEEL|23|8 +Brand#21|PROMO PLATED BRASS|14|8 +Brand#21|PROMO PLATED BRASS|23|8 +Brand#21|PROMO PLATED COPPER|3|8 +Brand#21|PROMO PLATED NICKEL|3|8 +Brand#21|PROMO PLATED STEEL|9|8 +Brand#21|PROMO PLATED STEEL|23|8 +Brand#21|PROMO PLATED STEEL|49|8 +Brand#21|PROMO PLATED TIN|3|8 +Brand#21|PROMO POLISHED COPPER|14|8 +Brand#21|PROMO POLISHED STEEL|19|8 +Brand#21|PROMO POLISHED STEEL|23|8 +Brand#21|PROMO POLISHED STEEL|45|8 +Brand#21|SMALL ANODIZED BRASS|45|8 +Brand#21|SMALL ANODIZED COPPER|49|8 +Brand#21|SMALL ANODIZED NICKEL|9|8 +Brand#21|SMALL ANODIZED NICKEL|14|8 +Brand#21|SMALL ANODIZED NICKEL|19|8 +Brand#21|SMALL ANODIZED NICKEL|23|8 +Brand#21|SMALL ANODIZED NICKEL|45|8 +Brand#21|SMALL ANODIZED STEEL|49|8 +Brand#21|SMALL ANODIZED TIN|9|8 +Brand#21|SMALL ANODIZED TIN|14|8 +Brand#21|SMALL ANODIZED TIN|19|8 +Brand#21|SMALL ANODIZED TIN|36|8 +Brand#21|SMALL ANODIZED TIN|49|8 +Brand#21|SMALL BRUSHED BRASS|36|8 +Brand#21|SMALL BRUSHED NICKEL|36|8 +Brand#21|SMALL BRUSHED STEEL|14|8 +Brand#21|SMALL BRUSHED TIN|45|8 +Brand#21|SMALL BURNISHED BRASS|36|8 +Brand#21|SMALL BURNISHED COPPER|3|8 +Brand#21|SMALL BURNISHED COPPER|14|8 +Brand#21|SMALL BURNISHED COPPER|19|8 +Brand#21|SMALL BURNISHED COPPER|45|8 +Brand#21|SMALL BURNISHED NICKEL|14|8 +Brand#21|SMALL BURNISHED STEEL|49|8 +Brand#21|SMALL PLATED BRASS|14|8 +Brand#21|SMALL PLATED COPPER|3|8 +Brand#21|SMALL PLATED COPPER|9|8 +Brand#21|SMALL PLATED COPPER|45|8 +Brand#21|SMALL PLATED NICKEL|3|8 +Brand#21|SMALL PLATED STEEL|3|8 +Brand#21|SMALL PLATED STEEL|9|8 +Brand#21|SMALL PLATED TIN|19|8 +Brand#21|SMALL POLISHED STEEL|36|8 +Brand#21|STANDARD ANODIZED BRASS|3|8 +Brand#21|STANDARD ANODIZED BRASS|36|8 +Brand#21|STANDARD ANODIZED BRASS|45|8 +Brand#21|STANDARD ANODIZED COPPER|23|8 +Brand#21|STANDARD ANODIZED STEEL|36|8 +Brand#21|STANDARD ANODIZED STEEL|49|8 +Brand#21|STANDARD ANODIZED TIN|9|8 +Brand#21|STANDARD ANODIZED TIN|49|8 +Brand#21|STANDARD BRUSHED BRASS|14|8 +Brand#21|STANDARD BRUSHED BRASS|45|8 +Brand#21|STANDARD BRUSHED COPPER|23|8 +Brand#21|STANDARD BRUSHED NICKEL|19|8 +Brand#21|STANDARD BRUSHED STEEL|9|8 +Brand#21|STANDARD BRUSHED STEEL|49|8 +Brand#21|STANDARD BRUSHED TIN|23|8 +Brand#21|STANDARD BRUSHED TIN|36|8 +Brand#21|STANDARD BRUSHED TIN|45|8 +Brand#21|STANDARD BURNISHED BRASS|36|8 +Brand#21|STANDARD BURNISHED COPPER|9|8 +Brand#21|STANDARD BURNISHED COPPER|19|8 +Brand#21|STANDARD BURNISHED NICKEL|9|8 +Brand#21|STANDARD BURNISHED NICKEL|23|8 +Brand#21|STANDARD BURNISHED NICKEL|45|8 +Brand#21|STANDARD BURNISHED STEEL|14|8 +Brand#21|STANDARD BURNISHED STEEL|36|8 +Brand#21|STANDARD BURNISHED STEEL|45|8 +Brand#21|STANDARD BURNISHED TIN|19|8 +Brand#21|STANDARD BURNISHED TIN|36|8 +Brand#21|STANDARD PLATED BRASS|14|8 +Brand#21|STANDARD PLATED BRASS|19|8 +Brand#21|STANDARD PLATED BRASS|49|8 +Brand#21|STANDARD PLATED COPPER|19|8 +Brand#21|STANDARD PLATED COPPER|23|8 +Brand#21|STANDARD PLATED COPPER|49|8 +Brand#21|STANDARD PLATED NICKEL|3|8 +Brand#21|STANDARD PLATED NICKEL|45|8 +Brand#21|STANDARD PLATED TIN|14|8 +Brand#21|STANDARD PLATED TIN|49|8 +Brand#21|STANDARD POLISHED BRASS|9|8 +Brand#21|STANDARD POLISHED BRASS|19|8 +Brand#21|STANDARD POLISHED BRASS|45|8 +Brand#21|STANDARD POLISHED COPPER|23|8 +Brand#21|STANDARD POLISHED NICKEL|14|8 +Brand#21|STANDARD POLISHED NICKEL|23|8 +Brand#21|STANDARD POLISHED STEEL|19|8 +Brand#21|STANDARD POLISHED TIN|36|8 +Brand#22|ECONOMY ANODIZED BRASS|3|8 +Brand#22|ECONOMY ANODIZED BRASS|36|8 +Brand#22|ECONOMY ANODIZED COPPER|14|8 +Brand#22|ECONOMY ANODIZED STEEL|23|8 +Brand#22|ECONOMY ANODIZED TIN|36|8 +Brand#22|ECONOMY BRUSHED BRASS|14|8 +Brand#22|ECONOMY BRUSHED COPPER|3|8 +Brand#22|ECONOMY BRUSHED COPPER|9|8 +Brand#22|ECONOMY BRUSHED COPPER|23|8 +Brand#22|ECONOMY BRUSHED NICKEL|3|8 +Brand#22|ECONOMY BRUSHED NICKEL|9|8 +Brand#22|ECONOMY BRUSHED NICKEL|14|8 +Brand#22|ECONOMY BRUSHED NICKEL|45|8 +Brand#22|ECONOMY BRUSHED STEEL|49|8 +Brand#22|ECONOMY BRUSHED TIN|45|8 +Brand#22|ECONOMY BURNISHED NICKEL|3|8 +Brand#22|ECONOMY BURNISHED NICKEL|49|8 +Brand#22|ECONOMY BURNISHED STEEL|9|8 +Brand#22|ECONOMY BURNISHED TIN|23|8 +Brand#22|ECONOMY PLATED BRASS|3|8 +Brand#22|ECONOMY PLATED STEEL|3|8 +Brand#22|ECONOMY PLATED TIN|9|8 +Brand#22|ECONOMY POLISHED COPPER|49|8 +Brand#22|ECONOMY POLISHED NICKEL|45|8 +Brand#22|ECONOMY POLISHED STEEL|9|8 +Brand#22|ECONOMY POLISHED STEEL|14|8 +Brand#22|ECONOMY POLISHED TIN|14|8 +Brand#22|ECONOMY POLISHED TIN|45|8 +Brand#22|LARGE ANODIZED BRASS|14|8 +Brand#22|LARGE ANODIZED NICKEL|23|8 +Brand#22|LARGE ANODIZED TIN|19|8 +Brand#22|LARGE ANODIZED TIN|23|8 +Brand#22|LARGE ANODIZED TIN|49|8 +Brand#22|LARGE BRUSHED BRASS|23|8 +Brand#22|LARGE BRUSHED BRASS|36|8 +Brand#22|LARGE BRUSHED COPPER|9|8 +Brand#22|LARGE BRUSHED NICKEL|23|8 +Brand#22|LARGE BRUSHED NICKEL|45|8 +Brand#22|LARGE BRUSHED STEEL|23|8 +Brand#22|LARGE BURNISHED BRASS|23|8 +Brand#22|LARGE BURNISHED COPPER|9|8 +Brand#22|LARGE BURNISHED COPPER|19|8 +Brand#22|LARGE BURNISHED NICKEL|36|8 +Brand#22|LARGE BURNISHED NICKEL|49|8 +Brand#22|LARGE BURNISHED STEEL|9|8 +Brand#22|LARGE BURNISHED TIN|45|8 +Brand#22|LARGE PLATED BRASS|45|8 +Brand#22|LARGE PLATED COPPER|45|8 +Brand#22|LARGE PLATED NICKEL|9|8 +Brand#22|LARGE PLATED NICKEL|19|8 +Brand#22|LARGE PLATED NICKEL|23|8 +Brand#22|LARGE PLATED STEEL|14|8 +Brand#22|LARGE PLATED STEEL|19|8 +Brand#22|LARGE PLATED STEEL|23|8 +Brand#22|LARGE PLATED TIN|14|8 +Brand#22|LARGE POLISHED BRASS|23|8 +Brand#22|LARGE POLISHED BRASS|45|8 +Brand#22|LARGE POLISHED BRASS|49|8 +Brand#22|LARGE POLISHED COPPER|9|8 +Brand#22|LARGE POLISHED COPPER|49|8 +Brand#22|LARGE POLISHED NICKEL|45|8 +Brand#22|LARGE POLISHED NICKEL|49|8 +Brand#22|LARGE POLISHED STEEL|49|8 +Brand#22|LARGE POLISHED TIN|49|8 +Brand#22|MEDIUM ANODIZED BRASS|14|8 +Brand#22|MEDIUM ANODIZED BRASS|49|8 +Brand#22|MEDIUM ANODIZED COPPER|3|8 +Brand#22|MEDIUM ANODIZED COPPER|14|8 +Brand#22|MEDIUM ANODIZED COPPER|45|8 +Brand#22|MEDIUM ANODIZED NICKEL|3|8 +Brand#22|MEDIUM ANODIZED NICKEL|36|8 +Brand#22|MEDIUM ANODIZED STEEL|36|8 +Brand#22|MEDIUM ANODIZED TIN|45|8 +Brand#22|MEDIUM BRUSHED BRASS|45|8 +Brand#22|MEDIUM BRUSHED BRASS|49|8 +Brand#22|MEDIUM BRUSHED COPPER|3|8 +Brand#22|MEDIUM BRUSHED COPPER|45|8 +Brand#22|MEDIUM BRUSHED COPPER|49|8 +Brand#22|MEDIUM BRUSHED STEEL|36|8 +Brand#22|MEDIUM BURNISHED BRASS|9|8 +Brand#22|MEDIUM BURNISHED BRASS|45|8 +Brand#22|MEDIUM BURNISHED BRASS|49|8 +Brand#22|MEDIUM BURNISHED COPPER|14|8 +Brand#22|MEDIUM BURNISHED COPPER|23|8 +Brand#22|MEDIUM BURNISHED COPPER|36|8 +Brand#22|MEDIUM BURNISHED COPPER|45|8 +Brand#22|MEDIUM BURNISHED COPPER|49|8 +Brand#22|MEDIUM BURNISHED NICKEL|3|8 +Brand#22|MEDIUM BURNISHED NICKEL|14|8 +Brand#22|MEDIUM BURNISHED STEEL|3|8 +Brand#22|MEDIUM BURNISHED STEEL|9|8 +Brand#22|MEDIUM BURNISHED STEEL|45|8 +Brand#22|MEDIUM BURNISHED TIN|3|8 +Brand#22|MEDIUM BURNISHED TIN|9|8 +Brand#22|MEDIUM BURNISHED TIN|19|8 +Brand#22|MEDIUM BURNISHED TIN|49|8 +Brand#22|MEDIUM PLATED COPPER|19|8 +Brand#22|MEDIUM PLATED NICKEL|3|8 +Brand#22|MEDIUM PLATED NICKEL|45|8 +Brand#22|MEDIUM PLATED NICKEL|49|8 +Brand#22|MEDIUM PLATED STEEL|3|8 +Brand#22|MEDIUM PLATED STEEL|9|8 +Brand#22|MEDIUM PLATED STEEL|19|8 +Brand#22|MEDIUM PLATED TIN|49|8 +Brand#22|PROMO ANODIZED BRASS|3|8 +Brand#22|PROMO ANODIZED BRASS|9|8 +Brand#22|PROMO ANODIZED BRASS|45|8 +Brand#22|PROMO ANODIZED COPPER|3|8 +Brand#22|PROMO ANODIZED COPPER|9|8 +Brand#22|PROMO ANODIZED COPPER|23|8 +Brand#22|PROMO ANODIZED NICKEL|9|8 +Brand#22|PROMO ANODIZED NICKEL|45|8 +Brand#22|PROMO ANODIZED STEEL|19|8 +Brand#22|PROMO ANODIZED TIN|14|8 +Brand#22|PROMO ANODIZED TIN|23|8 +Brand#22|PROMO BRUSHED BRASS|3|8 +Brand#22|PROMO BRUSHED BRASS|19|8 +Brand#22|PROMO BRUSHED BRASS|23|8 +Brand#22|PROMO BRUSHED BRASS|36|8 +Brand#22|PROMO BRUSHED NICKEL|19|8 +Brand#22|PROMO BRUSHED NICKEL|45|8 +Brand#22|PROMO BRUSHED STEEL|36|8 +Brand#22|PROMO BRUSHED TIN|3|8 +Brand#22|PROMO BURNISHED COPPER|14|8 +Brand#22|PROMO BURNISHED COPPER|36|8 +Brand#22|PROMO BURNISHED STEEL|14|8 +Brand#22|PROMO BURNISHED STEEL|36|8 +Brand#22|PROMO BURNISHED TIN|3|8 +Brand#22|PROMO PLATED BRASS|36|8 +Brand#22|PROMO PLATED BRASS|45|8 +Brand#22|PROMO PLATED COPPER|36|8 +Brand#22|PROMO PLATED NICKEL|9|8 +Brand#22|PROMO PLATED NICKEL|19|8 +Brand#22|PROMO PLATED NICKEL|49|8 +Brand#22|PROMO PLATED STEEL|45|8 +Brand#22|PROMO PLATED TIN|3|8 +Brand#22|PROMO PLATED TIN|23|8 +Brand#22|PROMO POLISHED BRASS|3|8 +Brand#22|PROMO POLISHED BRASS|9|8 +Brand#22|PROMO POLISHED NICKEL|9|8 +Brand#22|PROMO POLISHED STEEL|14|8 +Brand#22|PROMO POLISHED TIN|3|8 +Brand#22|PROMO POLISHED TIN|23|8 +Brand#22|PROMO POLISHED TIN|49|8 +Brand#22|SMALL ANODIZED BRASS|14|8 +Brand#22|SMALL ANODIZED COPPER|3|8 +Brand#22|SMALL ANODIZED NICKEL|3|8 +Brand#22|SMALL ANODIZED NICKEL|36|8 +Brand#22|SMALL ANODIZED STEEL|23|8 +Brand#22|SMALL BRUSHED BRASS|36|8 +Brand#22|SMALL BRUSHED TIN|19|8 +Brand#22|SMALL BRUSHED TIN|23|8 +Brand#22|SMALL BURNISHED BRASS|3|8 +Brand#22|SMALL BURNISHED NICKEL|3|8 +Brand#22|SMALL BURNISHED NICKEL|49|8 +Brand#22|SMALL BURNISHED STEEL|9|8 +Brand#22|SMALL BURNISHED STEEL|23|8 +Brand#22|SMALL BURNISHED STEEL|49|8 +Brand#22|SMALL BURNISHED TIN|45|8 +Brand#22|SMALL PLATED BRASS|23|8 +Brand#22|SMALL PLATED COPPER|14|8 +Brand#22|SMALL PLATED COPPER|36|8 +Brand#22|SMALL PLATED NICKEL|3|8 +Brand#22|SMALL PLATED NICKEL|19|8 +Brand#22|SMALL PLATED STEEL|3|8 +Brand#22|SMALL PLATED STEEL|45|8 +Brand#22|SMALL POLISHED COPPER|9|8 +Brand#22|SMALL POLISHED COPPER|23|8 +Brand#22|SMALL POLISHED NICKEL|14|8 +Brand#22|SMALL POLISHED NICKEL|19|8 +Brand#22|SMALL POLISHED STEEL|14|8 +Brand#22|SMALL POLISHED TIN|14|8 +Brand#22|SMALL POLISHED TIN|19|8 +Brand#22|STANDARD ANODIZED BRASS|14|8 +Brand#22|STANDARD ANODIZED BRASS|19|8 +Brand#22|STANDARD ANODIZED COPPER|9|8 +Brand#22|STANDARD ANODIZED COPPER|23|8 +Brand#22|STANDARD ANODIZED COPPER|36|8 +Brand#22|STANDARD ANODIZED NICKEL|3|8 +Brand#22|STANDARD ANODIZED NICKEL|14|8 +Brand#22|STANDARD ANODIZED TIN|3|8 +Brand#22|STANDARD ANODIZED TIN|49|8 +Brand#22|STANDARD BRUSHED BRASS|14|8 +Brand#22|STANDARD BRUSHED BRASS|19|8 +Brand#22|STANDARD BRUSHED COPPER|19|8 +Brand#22|STANDARD BRUSHED COPPER|36|8 +Brand#22|STANDARD BRUSHED NICKEL|9|8 +Brand#22|STANDARD BRUSHED NICKEL|19|8 +Brand#22|STANDARD BRUSHED NICKEL|23|8 +Brand#22|STANDARD BRUSHED STEEL|9|8 +Brand#22|STANDARD BRUSHED STEEL|14|8 +Brand#22|STANDARD BRUSHED STEEL|19|8 +Brand#22|STANDARD BRUSHED STEEL|36|8 +Brand#22|STANDARD BRUSHED TIN|3|8 +Brand#22|STANDARD BRUSHED TIN|45|8 +Brand#22|STANDARD BURNISHED BRASS|14|8 +Brand#22|STANDARD BURNISHED NICKEL|14|8 +Brand#22|STANDARD BURNISHED NICKEL|45|8 +Brand#22|STANDARD BURNISHED STEEL|36|8 +Brand#22|STANDARD BURNISHED STEEL|45|8 +Brand#22|STANDARD PLATED BRASS|14|8 +Brand#22|STANDARD PLATED COPPER|19|8 +Brand#22|STANDARD PLATED NICKEL|19|8 +Brand#22|STANDARD PLATED NICKEL|36|8 +Brand#22|STANDARD PLATED STEEL|9|8 +Brand#22|STANDARD PLATED STEEL|14|8 +Brand#22|STANDARD POLISHED BRASS|14|8 +Brand#22|STANDARD POLISHED NICKEL|23|8 +Brand#22|STANDARD POLISHED NICKEL|45|8 +Brand#22|STANDARD POLISHED STEEL|36|8 +Brand#22|STANDARD POLISHED STEEL|45|8 +Brand#22|STANDARD POLISHED STEEL|49|8 +Brand#22|STANDARD POLISHED TIN|9|8 +Brand#22|STANDARD POLISHED TIN|19|8 +Brand#23|ECONOMY ANODIZED COPPER|3|8 +Brand#23|ECONOMY ANODIZED NICKEL|3|8 +Brand#23|ECONOMY ANODIZED NICKEL|49|8 +Brand#23|ECONOMY ANODIZED STEEL|14|8 +Brand#23|ECONOMY ANODIZED TIN|14|8 +Brand#23|ECONOMY ANODIZED TIN|19|8 +Brand#23|ECONOMY ANODIZED TIN|45|8 +Brand#23|ECONOMY ANODIZED TIN|49|8 +Brand#23|ECONOMY BRUSHED BRASS|3|8 +Brand#23|ECONOMY BRUSHED BRASS|36|8 +Brand#23|ECONOMY BRUSHED COPPER|9|8 +Brand#23|ECONOMY BRUSHED TIN|9|8 +Brand#23|ECONOMY BRUSHED TIN|19|8 +Brand#23|ECONOMY BRUSHED TIN|23|8 +Brand#23|ECONOMY BURNISHED BRASS|9|8 +Brand#23|ECONOMY BURNISHED BRASS|14|8 +Brand#23|ECONOMY BURNISHED COPPER|14|8 +Brand#23|ECONOMY BURNISHED NICKEL|9|8 +Brand#23|ECONOMY BURNISHED NICKEL|23|8 +Brand#23|ECONOMY BURNISHED STEEL|45|8 +Brand#23|ECONOMY PLATED BRASS|23|8 +Brand#23|ECONOMY PLATED COPPER|23|8 +Brand#23|ECONOMY PLATED NICKEL|3|8 +Brand#23|ECONOMY PLATED NICKEL|23|8 +Brand#23|ECONOMY PLATED STEEL|19|8 +Brand#23|ECONOMY PLATED TIN|3|8 +Brand#23|ECONOMY PLATED TIN|19|8 +Brand#23|ECONOMY PLATED TIN|23|8 +Brand#23|ECONOMY PLATED TIN|36|8 +Brand#23|ECONOMY POLISHED BRASS|36|8 +Brand#23|ECONOMY POLISHED COPPER|3|8 +Brand#23|ECONOMY POLISHED COPPER|14|8 +Brand#23|ECONOMY POLISHED COPPER|49|8 +Brand#23|ECONOMY POLISHED STEEL|3|8 +Brand#23|ECONOMY POLISHED STEEL|23|8 +Brand#23|ECONOMY POLISHED STEEL|36|8 +Brand#23|ECONOMY POLISHED TIN|45|8 +Brand#23|LARGE ANODIZED BRASS|9|8 +Brand#23|LARGE ANODIZED BRASS|14|8 +Brand#23|LARGE ANODIZED COPPER|9|8 +Brand#23|LARGE ANODIZED COPPER|45|8 +Brand#23|LARGE ANODIZED COPPER|49|8 +Brand#23|LARGE ANODIZED STEEL|19|8 +Brand#23|LARGE ANODIZED STEEL|36|8 +Brand#23|LARGE BRUSHED BRASS|9|8 +Brand#23|LARGE BRUSHED NICKEL|3|8 +Brand#23|LARGE BRUSHED NICKEL|45|8 +Brand#23|LARGE BURNISHED COPPER|3|8 +Brand#23|LARGE BURNISHED COPPER|9|8 +Brand#23|LARGE BURNISHED NICKEL|9|8 +Brand#23|LARGE BURNISHED NICKEL|19|8 +Brand#23|LARGE BURNISHED STEEL|3|8 +Brand#23|LARGE BURNISHED STEEL|9|8 +Brand#23|LARGE BURNISHED STEEL|14|8 +Brand#23|LARGE BURNISHED STEEL|49|8 +Brand#23|LARGE PLATED BRASS|3|8 +Brand#23|LARGE PLATED BRASS|9|8 +Brand#23|LARGE PLATED BRASS|14|8 +Brand#23|LARGE PLATED COPPER|19|8 +Brand#23|LARGE PLATED NICKEL|23|8 +Brand#23|LARGE PLATED NICKEL|49|8 +Brand#23|LARGE PLATED STEEL|3|8 +Brand#23|LARGE PLATED STEEL|14|8 +Brand#23|LARGE PLATED STEEL|45|8 +Brand#23|LARGE POLISHED NICKEL|3|8 +Brand#23|LARGE POLISHED NICKEL|49|8 +Brand#23|LARGE POLISHED TIN|9|8 +Brand#23|LARGE POLISHED TIN|14|8 +Brand#23|LARGE POLISHED TIN|36|8 +Brand#23|LARGE POLISHED TIN|49|8 +Brand#23|MEDIUM ANODIZED COPPER|45|8 +Brand#23|MEDIUM ANODIZED NICKEL|3|8 +Brand#23|MEDIUM ANODIZED NICKEL|14|8 +Brand#23|MEDIUM ANODIZED STEEL|3|8 +Brand#23|MEDIUM ANODIZED STEEL|19|8 +Brand#23|MEDIUM ANODIZED STEEL|49|8 +Brand#23|MEDIUM ANODIZED TIN|14|8 +Brand#23|MEDIUM ANODIZED TIN|23|8 +Brand#23|MEDIUM ANODIZED TIN|45|8 +Brand#23|MEDIUM BRUSHED BRASS|45|8 +Brand#23|MEDIUM BRUSHED COPPER|19|8 +Brand#23|MEDIUM BRUSHED COPPER|23|8 +Brand#23|MEDIUM BRUSHED NICKEL|3|8 +Brand#23|MEDIUM BRUSHED NICKEL|14|8 +Brand#23|MEDIUM BRUSHED TIN|14|8 +Brand#23|MEDIUM BRUSHED TIN|45|8 +Brand#23|MEDIUM BURNISHED BRASS|3|8 +Brand#23|MEDIUM BURNISHED BRASS|9|8 +Brand#23|MEDIUM BURNISHED BRASS|14|8 +Brand#23|MEDIUM BURNISHED COPPER|14|8 +Brand#23|MEDIUM BURNISHED COPPER|23|8 +Brand#23|MEDIUM BURNISHED COPPER|36|8 +Brand#23|MEDIUM BURNISHED STEEL|9|8 +Brand#23|MEDIUM BURNISHED STEEL|14|8 +Brand#23|MEDIUM BURNISHED TIN|9|8 +Brand#23|MEDIUM BURNISHED TIN|14|8 +Brand#23|MEDIUM PLATED BRASS|9|8 +Brand#23|MEDIUM PLATED BRASS|14|8 +Brand#23|MEDIUM PLATED BRASS|19|8 +Brand#23|MEDIUM PLATED NICKEL|3|8 +Brand#23|MEDIUM PLATED NICKEL|9|8 +Brand#23|MEDIUM PLATED NICKEL|23|8 +Brand#23|MEDIUM PLATED NICKEL|36|8 +Brand#23|MEDIUM PLATED STEEL|23|8 +Brand#23|MEDIUM PLATED TIN|49|8 +Brand#23|PROMO ANODIZED COPPER|3|8 +Brand#23|PROMO ANODIZED COPPER|36|8 +Brand#23|PROMO ANODIZED COPPER|45|8 +Brand#23|PROMO ANODIZED NICKEL|45|8 +Brand#23|PROMO ANODIZED TIN|14|8 +Brand#23|PROMO BRUSHED BRASS|19|8 +Brand#23|PROMO BRUSHED BRASS|36|8 +Brand#23|PROMO BRUSHED COPPER|14|8 +Brand#23|PROMO BRUSHED NICKEL|3|8 +Brand#23|PROMO BRUSHED NICKEL|49|8 +Brand#23|PROMO BRUSHED TIN|9|8 +Brand#23|PROMO BRUSHED TIN|49|8 +Brand#23|PROMO BURNISHED BRASS|14|8 +Brand#23|PROMO BURNISHED BRASS|45|8 +Brand#23|PROMO BURNISHED COPPER|49|8 +Brand#23|PROMO BURNISHED NICKEL|9|8 +Brand#23|PROMO BURNISHED NICKEL|23|8 +Brand#23|PROMO BURNISHED STEEL|14|8 +Brand#23|PROMO BURNISHED TIN|14|8 +Brand#23|PROMO BURNISHED TIN|49|8 +Brand#23|PROMO PLATED BRASS|14|8 +Brand#23|PROMO PLATED COPPER|14|8 +Brand#23|PROMO PLATED NICKEL|23|8 +Brand#23|PROMO PLATED NICKEL|45|8 +Brand#23|PROMO PLATED STEEL|3|8 +Brand#23|PROMO PLATED STEEL|49|8 +Brand#23|PROMO PLATED TIN|3|8 +Brand#23|PROMO PLATED TIN|23|8 +Brand#23|PROMO PLATED TIN|36|8 +Brand#23|PROMO PLATED TIN|45|8 +Brand#23|PROMO POLISHED BRASS|14|8 +Brand#23|PROMO POLISHED COPPER|23|8 +Brand#23|PROMO POLISHED NICKEL|19|8 +Brand#23|PROMO POLISHED NICKEL|23|8 +Brand#23|PROMO POLISHED NICKEL|36|8 +Brand#23|PROMO POLISHED STEEL|3|8 +Brand#23|PROMO POLISHED STEEL|14|8 +Brand#23|PROMO POLISHED TIN|23|8 +Brand#23|PROMO POLISHED TIN|49|8 +Brand#23|SMALL ANODIZED BRASS|36|8 +Brand#23|SMALL ANODIZED BRASS|49|8 +Brand#23|SMALL ANODIZED COPPER|14|8 +Brand#23|SMALL ANODIZED STEEL|14|8 +Brand#23|SMALL ANODIZED STEEL|23|8 +Brand#23|SMALL ANODIZED TIN|3|8 +Brand#23|SMALL BRUSHED BRASS|49|8 +Brand#23|SMALL BRUSHED COPPER|23|8 +Brand#23|SMALL BRUSHED COPPER|45|8 +Brand#23|SMALL BRUSHED NICKEL|3|8 +Brand#23|SMALL BRUSHED STEEL|23|8 +Brand#23|SMALL BRUSHED STEEL|45|8 +Brand#23|SMALL BRUSHED STEEL|49|8 +Brand#23|SMALL BRUSHED TIN|3|8 +Brand#23|SMALL BRUSHED TIN|14|8 +Brand#23|SMALL BURNISHED BRASS|3|8 +Brand#23|SMALL BURNISHED BRASS|9|8 +Brand#23|SMALL BURNISHED BRASS|49|8 +Brand#23|SMALL BURNISHED COPPER|45|8 +Brand#23|SMALL BURNISHED NICKEL|3|8 +Brand#23|SMALL BURNISHED NICKEL|49|8 +Brand#23|SMALL BURNISHED STEEL|19|8 +Brand#23|SMALL BURNISHED STEEL|49|8 +Brand#23|SMALL PLATED BRASS|3|8 +Brand#23|SMALL PLATED BRASS|45|8 +Brand#23|SMALL PLATED COPPER|14|8 +Brand#23|SMALL PLATED COPPER|36|8 +Brand#23|SMALL PLATED COPPER|45|8 +Brand#23|SMALL PLATED NICKEL|23|8 +Brand#23|SMALL PLATED STEEL|19|8 +Brand#23|SMALL PLATED STEEL|36|8 +Brand#23|SMALL PLATED STEEL|49|8 +Brand#23|SMALL PLATED TIN|19|8 +Brand#23|SMALL PLATED TIN|23|8 +Brand#23|SMALL PLATED TIN|45|8 +Brand#23|SMALL PLATED TIN|49|8 +Brand#23|SMALL POLISHED BRASS|19|8 +Brand#23|SMALL POLISHED BRASS|49|8 +Brand#23|SMALL POLISHED COPPER|9|8 +Brand#23|SMALL POLISHED NICKEL|3|8 +Brand#23|SMALL POLISHED NICKEL|23|8 +Brand#23|SMALL POLISHED NICKEL|49|8 +Brand#23|STANDARD ANODIZED BRASS|19|8 +Brand#23|STANDARD ANODIZED COPPER|19|8 +Brand#23|STANDARD ANODIZED COPPER|23|8 +Brand#23|STANDARD ANODIZED NICKEL|9|8 +Brand#23|STANDARD ANODIZED NICKEL|23|8 +Brand#23|STANDARD ANODIZED STEEL|9|8 +Brand#23|STANDARD ANODIZED STEEL|14|8 +Brand#23|STANDARD ANODIZED STEEL|45|8 +Brand#23|STANDARD ANODIZED STEEL|49|8 +Brand#23|STANDARD BRUSHED BRASS|19|8 +Brand#23|STANDARD BRUSHED BRASS|36|8 +Brand#23|STANDARD BRUSHED COPPER|36|8 +Brand#23|STANDARD BRUSHED NICKEL|9|8 +Brand#23|STANDARD BRUSHED NICKEL|19|8 +Brand#23|STANDARD BRUSHED STEEL|14|8 +Brand#23|STANDARD BRUSHED STEEL|49|8 +Brand#23|STANDARD BRUSHED TIN|3|8 +Brand#23|STANDARD BRUSHED TIN|36|8 +Brand#23|STANDARD BURNISHED BRASS|45|8 +Brand#23|STANDARD BURNISHED COPPER|3|8 +Brand#23|STANDARD BURNISHED COPPER|36|8 +Brand#23|STANDARD BURNISHED NICKEL|45|8 +Brand#23|STANDARD BURNISHED TIN|49|8 +Brand#23|STANDARD PLATED BRASS|23|8 +Brand#23|STANDARD PLATED COPPER|3|8 +Brand#23|STANDARD PLATED COPPER|14|8 +Brand#23|STANDARD PLATED COPPER|23|8 +Brand#23|STANDARD PLATED COPPER|36|8 +Brand#23|STANDARD PLATED STEEL|3|8 +Brand#23|STANDARD PLATED STEEL|19|8 +Brand#23|STANDARD PLATED STEEL|36|8 +Brand#23|STANDARD PLATED STEEL|49|8 +Brand#23|STANDARD PLATED TIN|3|8 +Brand#23|STANDARD PLATED TIN|23|8 +Brand#23|STANDARD POLISHED BRASS|36|8 +Brand#23|STANDARD POLISHED BRASS|49|8 +Brand#23|STANDARD POLISHED COPPER|14|8 +Brand#23|STANDARD POLISHED COPPER|45|8 +Brand#23|STANDARD POLISHED TIN|3|8 +Brand#24|ECONOMY ANODIZED BRASS|23|8 +Brand#24|ECONOMY ANODIZED COPPER|9|8 +Brand#24|ECONOMY ANODIZED COPPER|14|8 +Brand#24|ECONOMY ANODIZED NICKEL|9|8 +Brand#24|ECONOMY ANODIZED NICKEL|14|8 +Brand#24|ECONOMY ANODIZED NICKEL|19|8 +Brand#24|ECONOMY ANODIZED NICKEL|36|8 +Brand#24|ECONOMY ANODIZED STEEL|3|8 +Brand#24|ECONOMY ANODIZED STEEL|36|8 +Brand#24|ECONOMY ANODIZED TIN|19|8 +Brand#24|ECONOMY ANODIZED TIN|36|8 +Brand#24|ECONOMY BRUSHED BRASS|19|8 +Brand#24|ECONOMY BRUSHED COPPER|23|8 +Brand#24|ECONOMY BRUSHED NICKEL|9|8 +Brand#24|ECONOMY BRUSHED NICKEL|45|8 +Brand#24|ECONOMY BRUSHED NICKEL|49|8 +Brand#24|ECONOMY BRUSHED STEEL|36|8 +Brand#24|ECONOMY BRUSHED TIN|9|8 +Brand#24|ECONOMY BRUSHED TIN|14|8 +Brand#24|ECONOMY BRUSHED TIN|36|8 +Brand#24|ECONOMY BURNISHED BRASS|23|8 +Brand#24|ECONOMY BURNISHED BRASS|49|8 +Brand#24|ECONOMY BURNISHED COPPER|45|8 +Brand#24|ECONOMY BURNISHED NICKEL|14|8 +Brand#24|ECONOMY BURNISHED NICKEL|19|8 +Brand#24|ECONOMY BURNISHED STEEL|9|8 +Brand#24|ECONOMY BURNISHED STEEL|36|8 +Brand#24|ECONOMY BURNISHED STEEL|49|8 +Brand#24|ECONOMY PLATED BRASS|49|8 +Brand#24|ECONOMY PLATED COPPER|36|8 +Brand#24|ECONOMY PLATED COPPER|45|8 +Brand#24|ECONOMY PLATED NICKEL|9|8 +Brand#24|ECONOMY PLATED NICKEL|36|8 +Brand#24|ECONOMY PLATED STEEL|14|8 +Brand#24|ECONOMY POLISHED BRASS|3|8 +Brand#24|ECONOMY POLISHED BRASS|9|8 +Brand#24|ECONOMY POLISHED BRASS|14|8 +Brand#24|ECONOMY POLISHED BRASS|36|8 +Brand#24|ECONOMY POLISHED COPPER|23|8 +Brand#24|ECONOMY POLISHED NICKEL|23|8 +Brand#24|ECONOMY POLISHED NICKEL|36|8 +Brand#24|ECONOMY POLISHED STEEL|23|8 +Brand#24|ECONOMY POLISHED STEEL|36|8 +Brand#24|ECONOMY POLISHED TIN|9|8 +Brand#24|ECONOMY POLISHED TIN|23|8 +Brand#24|LARGE ANODIZED COPPER|23|8 +Brand#24|LARGE ANODIZED NICKEL|3|8 +Brand#24|LARGE ANODIZED NICKEL|23|8 +Brand#24|LARGE ANODIZED NICKEL|49|8 +Brand#24|LARGE ANODIZED STEEL|14|8 +Brand#24|LARGE ANODIZED STEEL|49|8 +Brand#24|LARGE ANODIZED TIN|9|8 +Brand#24|LARGE BRUSHED COPPER|19|8 +Brand#24|LARGE BRUSHED COPPER|49|8 +Brand#24|LARGE BRUSHED NICKEL|36|8 +Brand#24|LARGE BRUSHED STEEL|9|8 +Brand#24|LARGE BRUSHED STEEL|19|8 +Brand#24|LARGE BRUSHED TIN|45|8 +Brand#24|LARGE BRUSHED TIN|49|8 +Brand#24|LARGE BURNISHED BRASS|3|8 +Brand#24|LARGE BURNISHED BRASS|23|8 +Brand#24|LARGE BURNISHED COPPER|3|8 +Brand#24|LARGE BURNISHED NICKEL|14|8 +Brand#24|LARGE BURNISHED NICKEL|19|8 +Brand#24|LARGE BURNISHED TIN|45|8 +Brand#24|LARGE PLATED BRASS|9|8 +Brand#24|LARGE PLATED BRASS|23|8 +Brand#24|LARGE PLATED COPPER|45|8 +Brand#24|LARGE PLATED COPPER|49|8 +Brand#24|LARGE PLATED NICKEL|14|8 +Brand#24|LARGE PLATED NICKEL|49|8 +Brand#24|LARGE PLATED STEEL|19|8 +Brand#24|LARGE PLATED STEEL|36|8 +Brand#24|LARGE PLATED TIN|19|8 +Brand#24|LARGE POLISHED BRASS|3|8 +Brand#24|LARGE POLISHED BRASS|14|8 +Brand#24|LARGE POLISHED BRASS|36|8 +Brand#24|LARGE POLISHED NICKEL|9|8 +Brand#24|LARGE POLISHED NICKEL|19|8 +Brand#24|LARGE POLISHED NICKEL|36|8 +Brand#24|LARGE POLISHED STEEL|23|8 +Brand#24|LARGE POLISHED STEEL|49|8 +Brand#24|MEDIUM ANODIZED BRASS|45|8 +Brand#24|MEDIUM ANODIZED BRASS|49|8 +Brand#24|MEDIUM ANODIZED COPPER|45|8 +Brand#24|MEDIUM ANODIZED NICKEL|36|8 +Brand#24|MEDIUM ANODIZED STEEL|9|8 +Brand#24|MEDIUM ANODIZED STEEL|23|8 +Brand#24|MEDIUM ANODIZED STEEL|49|8 +Brand#24|MEDIUM BRUSHED BRASS|3|8 +Brand#24|MEDIUM BRUSHED COPPER|14|8 +Brand#24|MEDIUM BRUSHED TIN|49|8 +Brand#24|MEDIUM BURNISHED BRASS|9|8 +Brand#24|MEDIUM BURNISHED COPPER|3|8 +Brand#24|MEDIUM BURNISHED COPPER|9|8 +Brand#24|MEDIUM BURNISHED NICKEL|36|8 +Brand#24|MEDIUM BURNISHED NICKEL|45|8 +Brand#24|MEDIUM BURNISHED STEEL|19|8 +Brand#24|MEDIUM BURNISHED STEEL|36|8 +Brand#24|MEDIUM PLATED BRASS|19|8 +Brand#24|MEDIUM PLATED BRASS|23|8 +Brand#24|MEDIUM PLATED COPPER|3|8 +Brand#24|MEDIUM PLATED COPPER|9|8 +Brand#24|MEDIUM PLATED COPPER|23|8 +Brand#24|MEDIUM PLATED COPPER|45|8 +Brand#24|MEDIUM PLATED NICKEL|3|8 +Brand#24|MEDIUM PLATED NICKEL|19|8 +Brand#24|MEDIUM PLATED STEEL|14|8 +Brand#24|MEDIUM PLATED STEEL|19|8 +Brand#24|PROMO ANODIZED BRASS|3|8 +Brand#24|PROMO ANODIZED NICKEL|14|8 +Brand#24|PROMO ANODIZED STEEL|9|8 +Brand#24|PROMO ANODIZED STEEL|45|8 +Brand#24|PROMO BRUSHED BRASS|19|8 +Brand#24|PROMO BRUSHED COPPER|3|8 +Brand#24|PROMO BRUSHED COPPER|45|8 +Brand#24|PROMO BRUSHED NICKEL|19|8 +Brand#24|PROMO BRUSHED NICKEL|45|8 +Brand#24|PROMO BRUSHED NICKEL|49|8 +Brand#24|PROMO BRUSHED STEEL|19|8 +Brand#24|PROMO BRUSHED TIN|14|8 +Brand#24|PROMO BURNISHED BRASS|49|8 +Brand#24|PROMO BURNISHED STEEL|3|8 +Brand#24|PROMO PLATED BRASS|3|8 +Brand#24|PROMO PLATED BRASS|9|8 +Brand#24|PROMO PLATED BRASS|19|8 +Brand#24|PROMO PLATED BRASS|49|8 +Brand#24|PROMO PLATED COPPER|9|8 +Brand#24|PROMO PLATED NICKEL|9|8 +Brand#24|PROMO PLATED STEEL|36|8 +Brand#24|PROMO PLATED TIN|23|8 +Brand#24|PROMO PLATED TIN|49|8 +Brand#24|PROMO POLISHED BRASS|45|8 +Brand#24|PROMO POLISHED COPPER|49|8 +Brand#24|PROMO POLISHED NICKEL|45|8 +Brand#24|PROMO POLISHED NICKEL|49|8 +Brand#24|PROMO POLISHED STEEL|14|8 +Brand#24|PROMO POLISHED STEEL|36|8 +Brand#24|PROMO POLISHED TIN|3|8 +Brand#24|PROMO POLISHED TIN|14|8 +Brand#24|PROMO POLISHED TIN|45|8 +Brand#24|SMALL ANODIZED BRASS|19|8 +Brand#24|SMALL ANODIZED BRASS|23|8 +Brand#24|SMALL ANODIZED COPPER|36|8 +Brand#24|SMALL ANODIZED NICKEL|9|8 +Brand#24|SMALL ANODIZED NICKEL|45|8 +Brand#24|SMALL ANODIZED NICKEL|49|8 +Brand#24|SMALL ANODIZED STEEL|45|8 +Brand#24|SMALL ANODIZED TIN|9|8 +Brand#24|SMALL ANODIZED TIN|23|8 +Brand#24|SMALL ANODIZED TIN|36|8 +Brand#24|SMALL BRUSHED BRASS|9|8 +Brand#24|SMALL BRUSHED COPPER|19|8 +Brand#24|SMALL BRUSHED NICKEL|36|8 +Brand#24|SMALL BRUSHED STEEL|9|8 +Brand#24|SMALL BRUSHED STEEL|19|8 +Brand#24|SMALL BRUSHED STEEL|36|8 +Brand#24|SMALL BRUSHED TIN|3|8 +Brand#24|SMALL BRUSHED TIN|14|8 +Brand#24|SMALL BRUSHED TIN|36|8 +Brand#24|SMALL BRUSHED TIN|49|8 +Brand#24|SMALL BURNISHED BRASS|19|8 +Brand#24|SMALL BURNISHED BRASS|36|8 +Brand#24|SMALL BURNISHED BRASS|49|8 +Brand#24|SMALL BURNISHED NICKEL|19|8 +Brand#24|SMALL BURNISHED NICKEL|23|8 +Brand#24|SMALL BURNISHED NICKEL|36|8 +Brand#24|SMALL BURNISHED TIN|9|8 +Brand#24|SMALL PLATED BRASS|23|8 +Brand#24|SMALL PLATED BRASS|36|8 +Brand#24|SMALL PLATED COPPER|3|8 +Brand#24|SMALL PLATED COPPER|23|8 +Brand#24|SMALL PLATED NICKEL|49|8 +Brand#24|SMALL PLATED STEEL|3|8 +Brand#24|SMALL PLATED STEEL|14|8 +Brand#24|SMALL PLATED STEEL|49|8 +Brand#24|SMALL PLATED TIN|3|8 +Brand#24|SMALL PLATED TIN|14|8 +Brand#24|SMALL POLISHED BRASS|14|8 +Brand#24|SMALL POLISHED BRASS|23|8 +Brand#24|SMALL POLISHED NICKEL|3|8 +Brand#24|SMALL POLISHED NICKEL|9|8 +Brand#24|SMALL POLISHED NICKEL|36|8 +Brand#24|SMALL POLISHED NICKEL|45|8 +Brand#24|SMALL POLISHED STEEL|9|8 +Brand#24|SMALL POLISHED TIN|3|8 +Brand#24|STANDARD ANODIZED BRASS|14|8 +Brand#24|STANDARD ANODIZED BRASS|23|8 +Brand#24|STANDARD ANODIZED BRASS|49|8 +Brand#24|STANDARD ANODIZED COPPER|14|8 +Brand#24|STANDARD ANODIZED NICKEL|36|8 +Brand#24|STANDARD ANODIZED STEEL|3|8 +Brand#24|STANDARD ANODIZED STEEL|14|8 +Brand#24|STANDARD BRUSHED BRASS|3|8 +Brand#24|STANDARD BRUSHED BRASS|36|8 +Brand#24|STANDARD BRUSHED COPPER|9|8 +Brand#24|STANDARD BRUSHED COPPER|23|8 +Brand#24|STANDARD BRUSHED NICKEL|45|8 +Brand#24|STANDARD BRUSHED STEEL|19|8 +Brand#24|STANDARD BRUSHED TIN|14|8 +Brand#24|STANDARD BURNISHED NICKEL|9|8 +Brand#24|STANDARD BURNISHED NICKEL|23|8 +Brand#24|STANDARD BURNISHED NICKEL|36|8 +Brand#24|STANDARD BURNISHED STEEL|9|8 +Brand#24|STANDARD BURNISHED STEEL|45|8 +Brand#24|STANDARD BURNISHED TIN|14|8 +Brand#24|STANDARD BURNISHED TIN|23|8 +Brand#24|STANDARD PLATED BRASS|14|8 +Brand#24|STANDARD PLATED COPPER|14|8 +Brand#24|STANDARD PLATED NICKEL|3|8 +Brand#24|STANDARD PLATED NICKEL|14|8 +Brand#24|STANDARD PLATED NICKEL|45|8 +Brand#24|STANDARD PLATED STEEL|19|8 +Brand#24|STANDARD PLATED STEEL|49|8 +Brand#24|STANDARD PLATED TIN|36|8 +Brand#24|STANDARD PLATED TIN|45|8 +Brand#24|STANDARD POLISHED BRASS|49|8 +Brand#24|STANDARD POLISHED COPPER|23|8 +Brand#24|STANDARD POLISHED COPPER|45|8 +Brand#24|STANDARD POLISHED NICKEL|3|8 +Brand#24|STANDARD POLISHED NICKEL|14|8 +Brand#24|STANDARD POLISHED STEEL|3|8 +Brand#24|STANDARD POLISHED STEEL|9|8 +Brand#24|STANDARD POLISHED STEEL|19|8 +Brand#24|STANDARD POLISHED STEEL|23|8 +Brand#25|ECONOMY ANODIZED BRASS|49|8 +Brand#25|ECONOMY ANODIZED COPPER|9|8 +Brand#25|ECONOMY ANODIZED COPPER|23|8 +Brand#25|ECONOMY ANODIZED NICKEL|9|8 +Brand#25|ECONOMY ANODIZED NICKEL|19|8 +Brand#25|ECONOMY ANODIZED NICKEL|45|8 +Brand#25|ECONOMY ANODIZED STEEL|3|8 +Brand#25|ECONOMY ANODIZED STEEL|19|8 +Brand#25|ECONOMY ANODIZED TIN|49|8 +Brand#25|ECONOMY BRUSHED BRASS|36|8 +Brand#25|ECONOMY BRUSHED NICKEL|36|8 +Brand#25|ECONOMY BRUSHED STEEL|49|8 +Brand#25|ECONOMY BURNISHED COPPER|9|8 +Brand#25|ECONOMY BURNISHED COPPER|14|8 +Brand#25|ECONOMY BURNISHED COPPER|45|8 +Brand#25|ECONOMY BURNISHED NICKEL|36|8 +Brand#25|ECONOMY BURNISHED STEEL|9|8 +Brand#25|ECONOMY PLATED NICKEL|45|8 +Brand#25|ECONOMY PLATED STEEL|49|8 +Brand#25|ECONOMY PLATED TIN|3|8 +Brand#25|ECONOMY PLATED TIN|19|8 +Brand#25|ECONOMY PLATED TIN|36|8 +Brand#25|ECONOMY POLISHED BRASS|36|8 +Brand#25|ECONOMY POLISHED BRASS|45|8 +Brand#25|ECONOMY POLISHED COPPER|9|8 +Brand#25|ECONOMY POLISHED TIN|36|8 +Brand#25|LARGE ANODIZED BRASS|45|8 +Brand#25|LARGE ANODIZED NICKEL|36|8 +Brand#25|LARGE ANODIZED STEEL|3|8 +Brand#25|LARGE BRUSHED BRASS|3|8 +Brand#25|LARGE BRUSHED NICKEL|19|8 +Brand#25|LARGE BURNISHED BRASS|9|8 +Brand#25|LARGE BURNISHED BRASS|49|8 +Brand#25|LARGE BURNISHED COPPER|3|8 +Brand#25|LARGE BURNISHED COPPER|14|8 +Brand#25|LARGE BURNISHED COPPER|19|8 +Brand#25|LARGE BURNISHED COPPER|45|8 +Brand#25|LARGE BURNISHED TIN|3|8 +Brand#25|LARGE BURNISHED TIN|9|8 +Brand#25|LARGE PLATED COPPER|36|8 +Brand#25|LARGE PLATED NICKEL|36|8 +Brand#25|LARGE PLATED STEEL|9|8 +Brand#25|LARGE PLATED STEEL|23|8 +Brand#25|LARGE PLATED STEEL|49|8 +Brand#25|LARGE PLATED TIN|3|8 +Brand#25|LARGE PLATED TIN|9|8 +Brand#25|LARGE PLATED TIN|19|8 +Brand#25|LARGE PLATED TIN|45|8 +Brand#25|LARGE POLISHED BRASS|9|8 +Brand#25|LARGE POLISHED BRASS|19|8 +Brand#25|LARGE POLISHED COPPER|14|8 +Brand#25|LARGE POLISHED COPPER|23|8 +Brand#25|LARGE POLISHED COPPER|36|8 +Brand#25|LARGE POLISHED NICKEL|14|8 +Brand#25|LARGE POLISHED STEEL|9|8 +Brand#25|LARGE POLISHED STEEL|36|8 +Brand#25|LARGE POLISHED STEEL|45|8 +Brand#25|MEDIUM ANODIZED COPPER|9|8 +Brand#25|MEDIUM ANODIZED COPPER|36|8 +Brand#25|MEDIUM ANODIZED NICKEL|9|8 +Brand#25|MEDIUM ANODIZED NICKEL|36|8 +Brand#25|MEDIUM ANODIZED STEEL|3|8 +Brand#25|MEDIUM ANODIZED TIN|9|8 +Brand#25|MEDIUM ANODIZED TIN|49|8 +Brand#25|MEDIUM BRUSHED COPPER|14|8 +Brand#25|MEDIUM BRUSHED COPPER|45|8 +Brand#25|MEDIUM BRUSHED COPPER|49|8 +Brand#25|MEDIUM BRUSHED NICKEL|49|8 +Brand#25|MEDIUM BRUSHED STEEL|45|8 +Brand#25|MEDIUM BURNISHED BRASS|3|8 +Brand#25|MEDIUM BURNISHED BRASS|19|8 +Brand#25|MEDIUM BURNISHED BRASS|36|8 +Brand#25|MEDIUM BURNISHED BRASS|45|8 +Brand#25|MEDIUM BURNISHED COPPER|14|8 +Brand#25|MEDIUM BURNISHED COPPER|19|8 +Brand#25|MEDIUM BURNISHED COPPER|45|8 +Brand#25|MEDIUM BURNISHED NICKEL|3|8 +Brand#25|MEDIUM BURNISHED NICKEL|9|8 +Brand#25|MEDIUM BURNISHED STEEL|3|8 +Brand#25|MEDIUM BURNISHED STEEL|45|8 +Brand#25|MEDIUM BURNISHED STEEL|49|8 +Brand#25|MEDIUM BURNISHED TIN|9|8 +Brand#25|MEDIUM PLATED BRASS|14|8 +Brand#25|MEDIUM PLATED BRASS|45|8 +Brand#25|MEDIUM PLATED COPPER|49|8 +Brand#25|MEDIUM PLATED NICKEL|9|8 +Brand#25|MEDIUM PLATED NICKEL|19|8 +Brand#25|MEDIUM PLATED NICKEL|23|8 +Brand#25|MEDIUM PLATED NICKEL|36|8 +Brand#25|MEDIUM PLATED NICKEL|45|8 +Brand#25|MEDIUM PLATED TIN|3|8 +Brand#25|PROMO ANODIZED BRASS|23|8 +Brand#25|PROMO ANODIZED BRASS|45|8 +Brand#25|PROMO ANODIZED COPPER|19|8 +Brand#25|PROMO ANODIZED COPPER|45|8 +Brand#25|PROMO ANODIZED TIN|3|8 +Brand#25|PROMO ANODIZED TIN|14|8 +Brand#25|PROMO ANODIZED TIN|19|8 +Brand#25|PROMO BRUSHED COPPER|49|8 +Brand#25|PROMO BRUSHED NICKEL|3|8 +Brand#25|PROMO BRUSHED NICKEL|19|8 +Brand#25|PROMO BRUSHED TIN|14|8 +Brand#25|PROMO BRUSHED TIN|19|8 +Brand#25|PROMO BURNISHED COPPER|14|8 +Brand#25|PROMO BURNISHED NICKEL|3|8 +Brand#25|PROMO BURNISHED NICKEL|36|8 +Brand#25|PROMO BURNISHED STEEL|19|8 +Brand#25|PROMO BURNISHED TIN|36|8 +Brand#25|PROMO PLATED BRASS|14|8 +Brand#25|PROMO PLATED BRASS|19|8 +Brand#25|PROMO PLATED NICKEL|3|8 +Brand#25|PROMO PLATED NICKEL|9|8 +Brand#25|PROMO PLATED NICKEL|49|8 +Brand#25|PROMO PLATED STEEL|23|8 +Brand#25|PROMO PLATED STEEL|36|8 +Brand#25|PROMO POLISHED BRASS|14|8 +Brand#25|PROMO POLISHED COPPER|9|8 +Brand#25|PROMO POLISHED COPPER|36|8 +Brand#25|PROMO POLISHED NICKEL|19|8 +Brand#25|PROMO POLISHED NICKEL|45|8 +Brand#25|PROMO POLISHED TIN|19|8 +Brand#25|SMALL ANODIZED BRASS|3|8 +Brand#25|SMALL ANODIZED BRASS|9|8 +Brand#25|SMALL ANODIZED BRASS|14|8 +Brand#25|SMALL ANODIZED BRASS|23|8 +Brand#25|SMALL ANODIZED NICKEL|14|8 +Brand#25|SMALL ANODIZED NICKEL|19|8 +Brand#25|SMALL ANODIZED NICKEL|49|8 +Brand#25|SMALL ANODIZED STEEL|23|8 +Brand#25|SMALL ANODIZED TIN|3|8 +Brand#25|SMALL ANODIZED TIN|45|8 +Brand#25|SMALL BRUSHED BRASS|3|8 +Brand#25|SMALL BRUSHED BRASS|49|8 +Brand#25|SMALL BRUSHED COPPER|36|8 +Brand#25|SMALL BRUSHED NICKEL|9|8 +Brand#25|SMALL BRUSHED NICKEL|49|8 +Brand#25|SMALL BRUSHED STEEL|3|8 +Brand#25|SMALL BRUSHED TIN|36|8 +Brand#25|SMALL BRUSHED TIN|49|8 +Brand#25|SMALL BURNISHED BRASS|23|8 +Brand#25|SMALL BURNISHED COPPER|45|8 +Brand#25|SMALL BURNISHED NICKEL|23|8 +Brand#25|SMALL BURNISHED STEEL|3|8 +Brand#25|SMALL BURNISHED TIN|3|8 +Brand#25|SMALL PLATED COPPER|36|8 +Brand#25|SMALL PLATED COPPER|49|8 +Brand#25|SMALL PLATED STEEL|23|8 +Brand#25|SMALL PLATED STEEL|36|8 +Brand#25|SMALL PLATED STEEL|45|8 +Brand#25|SMALL PLATED TIN|49|8 +Brand#25|SMALL POLISHED BRASS|3|8 +Brand#25|SMALL POLISHED BRASS|19|8 +Brand#25|SMALL POLISHED BRASS|23|8 +Brand#25|SMALL POLISHED BRASS|45|8 +Brand#25|SMALL POLISHED COPPER|3|8 +Brand#25|SMALL POLISHED NICKEL|9|8 +Brand#25|SMALL POLISHED NICKEL|14|8 +Brand#25|SMALL POLISHED NICKEL|19|8 +Brand#25|SMALL POLISHED STEEL|14|8 +Brand#25|SMALL POLISHED TIN|45|8 +Brand#25|SMALL POLISHED TIN|49|8 +Brand#25|STANDARD ANODIZED BRASS|9|8 +Brand#25|STANDARD ANODIZED BRASS|45|8 +Brand#25|STANDARD ANODIZED NICKEL|14|8 +Brand#25|STANDARD ANODIZED NICKEL|23|8 +Brand#25|STANDARD ANODIZED NICKEL|49|8 +Brand#25|STANDARD ANODIZED STEEL|3|8 +Brand#25|STANDARD ANODIZED STEEL|9|8 +Brand#25|STANDARD ANODIZED TIN|9|8 +Brand#25|STANDARD ANODIZED TIN|14|8 +Brand#25|STANDARD ANODIZED TIN|23|8 +Brand#25|STANDARD ANODIZED TIN|49|8 +Brand#25|STANDARD BRUSHED COPPER|3|8 +Brand#25|STANDARD BRUSHED COPPER|36|8 +Brand#25|STANDARD BRUSHED NICKEL|14|8 +Brand#25|STANDARD BRUSHED NICKEL|19|8 +Brand#25|STANDARD BRUSHED TIN|36|8 +Brand#25|STANDARD BURNISHED NICKEL|9|8 +Brand#25|STANDARD BURNISHED NICKEL|19|8 +Brand#25|STANDARD BURNISHED STEEL|14|8 +Brand#25|STANDARD BURNISHED STEEL|36|8 +Brand#25|STANDARD BURNISHED STEEL|45|8 +Brand#25|STANDARD BURNISHED TIN|14|8 +Brand#25|STANDARD BURNISHED TIN|19|8 +Brand#25|STANDARD PLATED BRASS|19|8 +Brand#25|STANDARD PLATED COPPER|14|8 +Brand#25|STANDARD PLATED COPPER|36|8 +Brand#25|STANDARD PLATED COPPER|45|8 +Brand#25|STANDARD PLATED STEEL|45|8 +Brand#25|STANDARD PLATED TIN|49|8 +Brand#25|STANDARD POLISHED BRASS|19|8 +Brand#25|STANDARD POLISHED BRASS|49|8 +Brand#25|STANDARD POLISHED NICKEL|3|8 +Brand#25|STANDARD POLISHED STEEL|19|8 +Brand#25|STANDARD POLISHED TIN|36|8 +Brand#25|STANDARD POLISHED TIN|45|8 +Brand#25|STANDARD POLISHED TIN|49|8 +Brand#31|ECONOMY ANODIZED COPPER|23|8 +Brand#31|ECONOMY ANODIZED NICKEL|9|8 +Brand#31|ECONOMY ANODIZED NICKEL|14|8 +Brand#31|ECONOMY ANODIZED STEEL|3|8 +Brand#31|ECONOMY ANODIZED TIN|3|8 +Brand#31|ECONOMY ANODIZED TIN|19|8 +Brand#31|ECONOMY BRUSHED COPPER|3|8 +Brand#31|ECONOMY BRUSHED COPPER|9|8 +Brand#31|ECONOMY BRUSHED NICKEL|9|8 +Brand#31|ECONOMY BRUSHED STEEL|3|8 +Brand#31|ECONOMY BRUSHED STEEL|19|8 +Brand#31|ECONOMY BRUSHED TIN|23|8 +Brand#31|ECONOMY BURNISHED COPPER|45|8 +Brand#31|ECONOMY BURNISHED STEEL|3|8 +Brand#31|ECONOMY BURNISHED STEEL|14|8 +Brand#31|ECONOMY BURNISHED STEEL|19|8 +Brand#31|ECONOMY BURNISHED TIN|3|8 +Brand#31|ECONOMY BURNISHED TIN|45|8 +Brand#31|ECONOMY BURNISHED TIN|49|8 +Brand#31|ECONOMY PLATED BRASS|36|8 +Brand#31|ECONOMY PLATED COPPER|19|8 +Brand#31|ECONOMY PLATED COPPER|49|8 +Brand#31|ECONOMY PLATED NICKEL|36|8 +Brand#31|ECONOMY PLATED STEEL|23|8 +Brand#31|ECONOMY PLATED TIN|3|8 +Brand#31|ECONOMY PLATED TIN|36|8 +Brand#31|ECONOMY POLISHED BRASS|9|8 +Brand#31|ECONOMY POLISHED BRASS|23|8 +Brand#31|ECONOMY POLISHED COPPER|49|8 +Brand#31|ECONOMY POLISHED NICKEL|9|8 +Brand#31|ECONOMY POLISHED NICKEL|45|8 +Brand#31|ECONOMY POLISHED STEEL|49|8 +Brand#31|ECONOMY POLISHED TIN|45|8 +Brand#31|LARGE ANODIZED BRASS|3|8 +Brand#31|LARGE ANODIZED BRASS|14|8 +Brand#31|LARGE ANODIZED BRASS|36|8 +Brand#31|LARGE ANODIZED COPPER|23|8 +Brand#31|LARGE ANODIZED COPPER|45|8 +Brand#31|LARGE ANODIZED NICKEL|49|8 +Brand#31|LARGE ANODIZED STEEL|3|8 +Brand#31|LARGE ANODIZED STEEL|9|8 +Brand#31|LARGE ANODIZED TIN|9|8 +Brand#31|LARGE BRUSHED BRASS|45|8 +Brand#31|LARGE BRUSHED BRASS|49|8 +Brand#31|LARGE BRUSHED COPPER|19|8 +Brand#31|LARGE BRUSHED NICKEL|14|8 +Brand#31|LARGE BRUSHED NICKEL|23|8 +Brand#31|LARGE BRUSHED STEEL|14|8 +Brand#31|LARGE BRUSHED TIN|45|8 +Brand#31|LARGE BURNISHED BRASS|19|8 +Brand#31|LARGE BURNISHED BRASS|23|8 +Brand#31|LARGE BURNISHED COPPER|23|8 +Brand#31|LARGE BURNISHED COPPER|45|8 +Brand#31|LARGE BURNISHED NICKEL|9|8 +Brand#31|LARGE BURNISHED NICKEL|19|8 +Brand#31|LARGE BURNISHED STEEL|9|8 +Brand#31|LARGE BURNISHED STEEL|36|8 +Brand#31|LARGE BURNISHED TIN|14|8 +Brand#31|LARGE BURNISHED TIN|23|8 +Brand#31|LARGE BURNISHED TIN|49|8 +Brand#31|LARGE PLATED BRASS|14|8 +Brand#31|LARGE PLATED COPPER|19|8 +Brand#31|LARGE PLATED TIN|9|8 +Brand#31|LARGE PLATED TIN|36|8 +Brand#31|LARGE POLISHED BRASS|45|8 +Brand#31|LARGE POLISHED COPPER|3|8 +Brand#31|LARGE POLISHED COPPER|9|8 +Brand#31|LARGE POLISHED COPPER|19|8 +Brand#31|LARGE POLISHED NICKEL|14|8 +Brand#31|LARGE POLISHED NICKEL|19|8 +Brand#31|LARGE POLISHED TIN|14|8 +Brand#31|LARGE POLISHED TIN|19|8 +Brand#31|LARGE POLISHED TIN|23|8 +Brand#31|MEDIUM ANODIZED BRASS|19|8 +Brand#31|MEDIUM ANODIZED BRASS|23|8 +Brand#31|MEDIUM ANODIZED COPPER|14|8 +Brand#31|MEDIUM ANODIZED COPPER|19|8 +Brand#31|MEDIUM ANODIZED STEEL|49|8 +Brand#31|MEDIUM ANODIZED TIN|19|8 +Brand#31|MEDIUM BRUSHED BRASS|19|8 +Brand#31|MEDIUM BRUSHED BRASS|36|8 +Brand#31|MEDIUM BRUSHED COPPER|9|8 +Brand#31|MEDIUM BRUSHED COPPER|23|8 +Brand#31|MEDIUM BRUSHED COPPER|49|8 +Brand#31|MEDIUM BRUSHED STEEL|23|8 +Brand#31|MEDIUM BRUSHED TIN|49|8 +Brand#31|MEDIUM BURNISHED BRASS|49|8 +Brand#31|MEDIUM BURNISHED NICKEL|9|8 +Brand#31|MEDIUM BURNISHED NICKEL|19|8 +Brand#31|MEDIUM BURNISHED NICKEL|45|8 +Brand#31|MEDIUM BURNISHED STEEL|19|8 +Brand#31|MEDIUM BURNISHED TIN|3|8 +Brand#31|MEDIUM BURNISHED TIN|14|8 +Brand#31|MEDIUM BURNISHED TIN|23|8 +Brand#31|MEDIUM PLATED BRASS|3|8 +Brand#31|MEDIUM PLATED COPPER|14|8 +Brand#31|MEDIUM PLATED COPPER|19|8 +Brand#31|MEDIUM PLATED TIN|19|8 +Brand#31|PROMO ANODIZED BRASS|3|8 +Brand#31|PROMO ANODIZED BRASS|9|8 +Brand#31|PROMO ANODIZED BRASS|14|8 +Brand#31|PROMO ANODIZED BRASS|23|8 +Brand#31|PROMO ANODIZED COPPER|23|8 +Brand#31|PROMO ANODIZED NICKEL|3|8 +Brand#31|PROMO ANODIZED NICKEL|36|8 +Brand#31|PROMO ANODIZED NICKEL|45|8 +Brand#31|PROMO ANODIZED STEEL|9|8 +Brand#31|PROMO ANODIZED STEEL|49|8 +Brand#31|PROMO BRUSHED BRASS|19|8 +Brand#31|PROMO BRUSHED BRASS|23|8 +Brand#31|PROMO BRUSHED BRASS|36|8 +Brand#31|PROMO BRUSHED COPPER|45|8 +Brand#31|PROMO BRUSHED NICKEL|23|8 +Brand#31|PROMO BRUSHED NICKEL|49|8 +Brand#31|PROMO BRUSHED STEEL|49|8 +Brand#31|PROMO BRUSHED TIN|9|8 +Brand#31|PROMO BRUSHED TIN|36|8 +Brand#31|PROMO BURNISHED COPPER|3|8 +Brand#31|PROMO BURNISHED COPPER|14|8 +Brand#31|PROMO BURNISHED COPPER|19|8 +Brand#31|PROMO BURNISHED COPPER|36|8 +Brand#31|PROMO BURNISHED NICKEL|45|8 +Brand#31|PROMO BURNISHED STEEL|19|8 +Brand#31|PROMO PLATED COPPER|19|8 +Brand#31|PROMO PLATED COPPER|36|8 +Brand#31|PROMO PLATED COPPER|49|8 +Brand#31|PROMO PLATED NICKEL|36|8 +Brand#31|PROMO PLATED STEEL|19|8 +Brand#31|PROMO PLATED STEEL|23|8 +Brand#31|PROMO PLATED TIN|3|8 +Brand#31|PROMO PLATED TIN|49|8 +Brand#31|PROMO POLISHED BRASS|3|8 +Brand#31|PROMO POLISHED BRASS|49|8 +Brand#31|PROMO POLISHED NICKEL|3|8 +Brand#31|PROMO POLISHED TIN|9|8 +Brand#31|PROMO POLISHED TIN|45|8 +Brand#31|SMALL ANODIZED BRASS|9|8 +Brand#31|SMALL ANODIZED BRASS|36|8 +Brand#31|SMALL ANODIZED COPPER|36|8 +Brand#31|SMALL ANODIZED COPPER|45|8 +Brand#31|SMALL ANODIZED NICKEL|14|8 +Brand#31|SMALL ANODIZED NICKEL|49|8 +Brand#31|SMALL ANODIZED STEEL|9|8 +Brand#31|SMALL ANODIZED STEEL|45|8 +Brand#31|SMALL ANODIZED TIN|23|8 +Brand#31|SMALL BRUSHED BRASS|23|8 +Brand#31|SMALL BRUSHED NICKEL|19|8 +Brand#31|SMALL BRUSHED NICKEL|49|8 +Brand#31|SMALL BRUSHED STEEL|36|8 +Brand#31|SMALL BRUSHED STEEL|49|8 +Brand#31|SMALL BRUSHED TIN|9|8 +Brand#31|SMALL BRUSHED TIN|45|8 +Brand#31|SMALL BURNISHED NICKEL|23|8 +Brand#31|SMALL BURNISHED STEEL|3|8 +Brand#31|SMALL BURNISHED STEEL|9|8 +Brand#31|SMALL BURNISHED STEEL|19|8 +Brand#31|SMALL BURNISHED STEEL|23|8 +Brand#31|SMALL BURNISHED STEEL|36|8 +Brand#31|SMALL BURNISHED STEEL|49|8 +Brand#31|SMALL BURNISHED TIN|36|8 +Brand#31|SMALL PLATED BRASS|23|8 +Brand#31|SMALL PLATED COPPER|14|8 +Brand#31|SMALL PLATED COPPER|19|8 +Brand#31|SMALL PLATED NICKEL|36|8 +Brand#31|SMALL PLATED STEEL|14|8 +Brand#31|SMALL PLATED STEEL|36|8 +Brand#31|SMALL PLATED TIN|3|8 +Brand#31|SMALL PLATED TIN|36|8 +Brand#31|SMALL POLISHED BRASS|3|8 +Brand#31|SMALL POLISHED BRASS|14|8 +Brand#31|SMALL POLISHED BRASS|19|8 +Brand#31|SMALL POLISHED COPPER|3|8 +Brand#31|SMALL POLISHED COPPER|36|8 +Brand#31|SMALL POLISHED NICKEL|14|8 +Brand#31|SMALL POLISHED STEEL|49|8 +Brand#31|SMALL POLISHED TIN|23|8 +Brand#31|SMALL POLISHED TIN|49|8 +Brand#31|STANDARD ANODIZED NICKEL|9|8 +Brand#31|STANDARD ANODIZED NICKEL|14|8 +Brand#31|STANDARD ANODIZED NICKEL|45|8 +Brand#31|STANDARD ANODIZED STEEL|19|8 +Brand#31|STANDARD ANODIZED STEEL|45|8 +Brand#31|STANDARD ANODIZED TIN|3|8 +Brand#31|STANDARD ANODIZED TIN|9|8 +Brand#31|STANDARD ANODIZED TIN|19|8 +Brand#31|STANDARD ANODIZED TIN|36|8 +Brand#31|STANDARD BRUSHED BRASS|9|8 +Brand#31|STANDARD BRUSHED BRASS|36|8 +Brand#31|STANDARD BRUSHED COPPER|36|8 +Brand#31|STANDARD BRUSHED NICKEL|36|8 +Brand#31|STANDARD BRUSHED STEEL|19|8 +Brand#31|STANDARD BRUSHED STEEL|45|8 +Brand#31|STANDARD BRUSHED TIN|23|8 +Brand#31|STANDARD BURNISHED BRASS|9|8 +Brand#31|STANDARD BURNISHED BRASS|36|8 +Brand#31|STANDARD BURNISHED COPPER|9|8 +Brand#31|STANDARD BURNISHED COPPER|14|8 +Brand#31|STANDARD BURNISHED NICKEL|45|8 +Brand#31|STANDARD BURNISHED STEEL|9|8 +Brand#31|STANDARD BURNISHED STEEL|23|8 +Brand#31|STANDARD BURNISHED TIN|3|8 +Brand#31|STANDARD BURNISHED TIN|9|8 +Brand#31|STANDARD BURNISHED TIN|36|8 +Brand#31|STANDARD PLATED COPPER|3|8 +Brand#31|STANDARD PLATED COPPER|23|8 +Brand#31|STANDARD PLATED COPPER|49|8 +Brand#31|STANDARD PLATED NICKEL|3|8 +Brand#31|STANDARD PLATED NICKEL|9|8 +Brand#31|STANDARD PLATED NICKEL|36|8 +Brand#31|STANDARD PLATED TIN|14|8 +Brand#31|STANDARD PLATED TIN|19|8 +Brand#31|STANDARD POLISHED BRASS|14|8 +Brand#31|STANDARD POLISHED COPPER|3|8 +Brand#31|STANDARD POLISHED COPPER|23|8 +Brand#31|STANDARD POLISHED NICKEL|19|8 +Brand#31|STANDARD POLISHED STEEL|14|8 +Brand#31|STANDARD POLISHED TIN|36|8 +Brand#31|STANDARD POLISHED TIN|45|8 +Brand#32|ECONOMY ANODIZED BRASS|19|8 +Brand#32|ECONOMY ANODIZED BRASS|45|8 +Brand#32|ECONOMY ANODIZED COPPER|49|8 +Brand#32|ECONOMY ANODIZED NICKEL|14|8 +Brand#32|ECONOMY ANODIZED NICKEL|19|8 +Brand#32|ECONOMY ANODIZED STEEL|3|8 +Brand#32|ECONOMY ANODIZED STEEL|45|8 +Brand#32|ECONOMY ANODIZED TIN|49|8 +Brand#32|ECONOMY BRUSHED COPPER|23|8 +Brand#32|ECONOMY BRUSHED NICKEL|36|8 +Brand#32|ECONOMY BRUSHED STEEL|14|8 +Brand#32|ECONOMY BRUSHED TIN|14|8 +Brand#32|ECONOMY BRUSHED TIN|45|8 +Brand#32|ECONOMY BURNISHED COPPER|9|8 +Brand#32|ECONOMY BURNISHED NICKEL|9|8 +Brand#32|ECONOMY BURNISHED NICKEL|14|8 +Brand#32|ECONOMY BURNISHED NICKEL|19|8 +Brand#32|ECONOMY BURNISHED NICKEL|23|8 +Brand#32|ECONOMY BURNISHED STEEL|14|8 +Brand#32|ECONOMY BURNISHED STEEL|19|8 +Brand#32|ECONOMY BURNISHED STEEL|36|8 +Brand#32|ECONOMY BURNISHED TIN|9|8 +Brand#32|ECONOMY BURNISHED TIN|45|8 +Brand#32|ECONOMY BURNISHED TIN|49|8 +Brand#32|ECONOMY PLATED NICKEL|3|8 +Brand#32|ECONOMY PLATED NICKEL|14|8 +Brand#32|ECONOMY PLATED STEEL|49|8 +Brand#32|ECONOMY PLATED TIN|9|8 +Brand#32|ECONOMY POLISHED BRASS|14|8 +Brand#32|ECONOMY POLISHED BRASS|19|8 +Brand#32|ECONOMY POLISHED BRASS|49|8 +Brand#32|ECONOMY POLISHED COPPER|3|8 +Brand#32|ECONOMY POLISHED COPPER|23|8 +Brand#32|ECONOMY POLISHED NICKEL|9|8 +Brand#32|ECONOMY POLISHED NICKEL|23|8 +Brand#32|ECONOMY POLISHED NICKEL|49|8 +Brand#32|ECONOMY POLISHED STEEL|19|8 +Brand#32|ECONOMY POLISHED STEEL|36|8 +Brand#32|ECONOMY POLISHED TIN|9|8 +Brand#32|LARGE ANODIZED BRASS|9|8 +Brand#32|LARGE ANODIZED NICKEL|45|8 +Brand#32|LARGE ANODIZED STEEL|3|8 +Brand#32|LARGE ANODIZED STEEL|49|8 +Brand#32|LARGE ANODIZED TIN|9|8 +Brand#32|LARGE BRUSHED BRASS|3|8 +Brand#32|LARGE BRUSHED COPPER|9|8 +Brand#32|LARGE BRUSHED COPPER|14|8 +Brand#32|LARGE BRUSHED NICKEL|45|8 +Brand#32|LARGE BRUSHED TIN|36|8 +Brand#32|LARGE BURNISHED BRASS|9|8 +Brand#32|LARGE BURNISHED BRASS|23|8 +Brand#32|LARGE BURNISHED BRASS|36|8 +Brand#32|LARGE BURNISHED NICKEL|3|8 +Brand#32|LARGE BURNISHED STEEL|49|8 +Brand#32|LARGE BURNISHED TIN|23|8 +Brand#32|LARGE BURNISHED TIN|45|8 +Brand#32|LARGE BURNISHED TIN|49|8 +Brand#32|LARGE PLATED BRASS|14|8 +Brand#32|LARGE PLATED BRASS|45|8 +Brand#32|LARGE PLATED BRASS|49|8 +Brand#32|LARGE PLATED NICKEL|14|8 +Brand#32|LARGE PLATED STEEL|19|8 +Brand#32|LARGE PLATED TIN|14|8 +Brand#32|LARGE POLISHED BRASS|45|8 +Brand#32|LARGE POLISHED COPPER|3|8 +Brand#32|LARGE POLISHED COPPER|9|8 +Brand#32|LARGE POLISHED STEEL|49|8 +Brand#32|LARGE POLISHED TIN|14|8 +Brand#32|LARGE POLISHED TIN|49|8 +Brand#32|MEDIUM ANODIZED BRASS|3|8 +Brand#32|MEDIUM ANODIZED BRASS|23|8 +Brand#32|MEDIUM ANODIZED COPPER|3|8 +Brand#32|MEDIUM ANODIZED STEEL|9|8 +Brand#32|MEDIUM ANODIZED TIN|9|8 +Brand#32|MEDIUM BRUSHED BRASS|3|8 +Brand#32|MEDIUM BRUSHED BRASS|36|8 +Brand#32|MEDIUM BRUSHED COPPER|23|8 +Brand#32|MEDIUM BRUSHED TIN|3|8 +Brand#32|MEDIUM BRUSHED TIN|23|8 +Brand#32|MEDIUM BURNISHED BRASS|19|8 +Brand#32|MEDIUM BURNISHED BRASS|45|8 +Brand#32|MEDIUM BURNISHED BRASS|49|8 +Brand#32|MEDIUM BURNISHED COPPER|9|8 +Brand#32|MEDIUM BURNISHED COPPER|36|8 +Brand#32|MEDIUM BURNISHED NICKEL|49|8 +Brand#32|MEDIUM BURNISHED STEEL|9|8 +Brand#32|MEDIUM BURNISHED TIN|9|8 +Brand#32|MEDIUM PLATED BRASS|3|8 +Brand#32|MEDIUM PLATED COPPER|3|8 +Brand#32|MEDIUM PLATED COPPER|9|8 +Brand#32|MEDIUM PLATED COPPER|23|8 +Brand#32|MEDIUM PLATED NICKEL|23|8 +Brand#32|MEDIUM PLATED STEEL|3|8 +Brand#32|MEDIUM PLATED STEEL|9|8 +Brand#32|PROMO ANODIZED BRASS|3|8 +Brand#32|PROMO ANODIZED BRASS|9|8 +Brand#32|PROMO ANODIZED COPPER|19|8 +Brand#32|PROMO ANODIZED NICKEL|9|8 +Brand#32|PROMO ANODIZED NICKEL|14|8 +Brand#32|PROMO ANODIZED NICKEL|19|8 +Brand#32|PROMO ANODIZED STEEL|3|8 +Brand#32|PROMO ANODIZED STEEL|23|8 +Brand#32|PROMO BRUSHED BRASS|36|8 +Brand#32|PROMO BRUSHED COPPER|23|8 +Brand#32|PROMO BRUSHED NICKEL|23|8 +Brand#32|PROMO BRUSHED NICKEL|36|8 +Brand#32|PROMO BRUSHED STEEL|3|8 +Brand#32|PROMO BRUSHED TIN|23|8 +Brand#32|PROMO BURNISHED BRASS|14|8 +Brand#32|PROMO BURNISHED BRASS|45|8 +Brand#32|PROMO BURNISHED COPPER|3|8 +Brand#32|PROMO BURNISHED COPPER|19|8 +Brand#32|PROMO BURNISHED COPPER|49|8 +Brand#32|PROMO BURNISHED NICKEL|3|8 +Brand#32|PROMO BURNISHED NICKEL|19|8 +Brand#32|PROMO BURNISHED NICKEL|49|8 +Brand#32|PROMO BURNISHED TIN|3|8 +Brand#32|PROMO BURNISHED TIN|14|8 +Brand#32|PROMO BURNISHED TIN|45|8 +Brand#32|PROMO PLATED BRASS|9|8 +Brand#32|PROMO PLATED COPPER|19|8 +Brand#32|PROMO PLATED NICKEL|49|8 +Brand#32|PROMO PLATED STEEL|14|8 +Brand#32|PROMO PLATED TIN|19|8 +Brand#32|PROMO POLISHED BRASS|3|8 +Brand#32|PROMO POLISHED BRASS|23|8 +Brand#32|PROMO POLISHED BRASS|49|8 +Brand#32|PROMO POLISHED NICKEL|3|8 +Brand#32|PROMO POLISHED NICKEL|36|8 +Brand#32|PROMO POLISHED STEEL|3|8 +Brand#32|PROMO POLISHED TIN|3|8 +Brand#32|PROMO POLISHED TIN|9|8 +Brand#32|PROMO POLISHED TIN|14|8 +Brand#32|PROMO POLISHED TIN|36|8 +Brand#32|SMALL ANODIZED BRASS|3|8 +Brand#32|SMALL ANODIZED BRASS|49|8 +Brand#32|SMALL ANODIZED COPPER|23|8 +Brand#32|SMALL ANODIZED STEEL|3|8 +Brand#32|SMALL ANODIZED STEEL|23|8 +Brand#32|SMALL ANODIZED TIN|49|8 +Brand#32|SMALL BRUSHED BRASS|36|8 +Brand#32|SMALL BRUSHED COPPER|14|8 +Brand#32|SMALL BRUSHED COPPER|23|8 +Brand#32|SMALL BRUSHED NICKEL|3|8 +Brand#32|SMALL BRUSHED NICKEL|36|8 +Brand#32|SMALL BRUSHED STEEL|9|8 +Brand#32|SMALL BURNISHED BRASS|9|8 +Brand#32|SMALL BURNISHED BRASS|36|8 +Brand#32|SMALL BURNISHED BRASS|49|8 +Brand#32|SMALL BURNISHED COPPER|45|8 +Brand#32|SMALL BURNISHED NICKEL|9|8 +Brand#32|SMALL BURNISHED STEEL|3|8 +Brand#32|SMALL BURNISHED STEEL|9|8 +Brand#32|SMALL BURNISHED STEEL|14|8 +Brand#32|SMALL BURNISHED STEEL|23|8 +Brand#32|SMALL BURNISHED TIN|19|8 +Brand#32|SMALL BURNISHED TIN|45|8 +Brand#32|SMALL PLATED COPPER|23|8 +Brand#32|SMALL PLATED COPPER|36|8 +Brand#32|SMALL PLATED NICKEL|9|8 +Brand#32|SMALL PLATED STEEL|3|8 +Brand#32|SMALL PLATED STEEL|19|8 +Brand#32|SMALL PLATED TIN|23|8 +Brand#32|SMALL PLATED TIN|36|8 +Brand#32|SMALL PLATED TIN|45|8 +Brand#32|SMALL POLISHED BRASS|3|8 +Brand#32|SMALL POLISHED BRASS|45|8 +Brand#32|SMALL POLISHED COPPER|9|8 +Brand#32|SMALL POLISHED COPPER|14|8 +Brand#32|SMALL POLISHED NICKEL|49|8 +Brand#32|SMALL POLISHED STEEL|3|8 +Brand#32|SMALL POLISHED STEEL|14|8 +Brand#32|SMALL POLISHED STEEL|45|8 +Brand#32|SMALL POLISHED TIN|19|8 +Brand#32|SMALL POLISHED TIN|45|8 +Brand#32|STANDARD ANODIZED BRASS|19|8 +Brand#32|STANDARD ANODIZED NICKEL|19|8 +Brand#32|STANDARD ANODIZED STEEL|9|8 +Brand#32|STANDARD ANODIZED STEEL|45|8 +Brand#32|STANDARD ANODIZED STEEL|49|8 +Brand#32|STANDARD ANODIZED TIN|36|8 +Brand#32|STANDARD BRUSHED COPPER|36|8 +Brand#32|STANDARD BRUSHED NICKEL|14|8 +Brand#32|STANDARD BRUSHED NICKEL|19|8 +Brand#32|STANDARD BRUSHED STEEL|3|8 +Brand#32|STANDARD BRUSHED TIN|45|8 +Brand#32|STANDARD BURNISHED BRASS|14|8 +Brand#32|STANDARD BURNISHED NICKEL|14|8 +Brand#32|STANDARD BURNISHED NICKEL|23|8 +Brand#32|STANDARD BURNISHED NICKEL|49|8 +Brand#32|STANDARD BURNISHED STEEL|14|8 +Brand#32|STANDARD BURNISHED STEEL|45|8 +Brand#32|STANDARD BURNISHED STEEL|49|8 +Brand#32|STANDARD BURNISHED TIN|14|8 +Brand#32|STANDARD BURNISHED TIN|23|8 +Brand#32|STANDARD PLATED BRASS|3|8 +Brand#32|STANDARD PLATED BRASS|9|8 +Brand#32|STANDARD PLATED COPPER|9|8 +Brand#32|STANDARD PLATED COPPER|14|8 +Brand#32|STANDARD PLATED NICKEL|19|8 +Brand#32|STANDARD PLATED TIN|9|8 +Brand#32|STANDARD POLISHED COPPER|3|8 +Brand#32|STANDARD POLISHED COPPER|23|8 +Brand#32|STANDARD POLISHED NICKEL|9|8 +Brand#32|STANDARD POLISHED NICKEL|14|8 +Brand#32|STANDARD POLISHED NICKEL|49|8 +Brand#32|STANDARD POLISHED STEEL|23|8 +Brand#33|ECONOMY ANODIZED BRASS|3|8 +Brand#33|ECONOMY ANODIZED BRASS|19|8 +Brand#33|ECONOMY ANODIZED COPPER|3|8 +Brand#33|ECONOMY ANODIZED COPPER|9|8 +Brand#33|ECONOMY ANODIZED COPPER|23|8 +Brand#33|ECONOMY ANODIZED NICKEL|3|8 +Brand#33|ECONOMY ANODIZED NICKEL|23|8 +Brand#33|ECONOMY ANODIZED STEEL|19|8 +Brand#33|ECONOMY BRUSHED BRASS|14|8 +Brand#33|ECONOMY BRUSHED BRASS|45|8 +Brand#33|ECONOMY BRUSHED COPPER|9|8 +Brand#33|ECONOMY BRUSHED COPPER|23|8 +Brand#33|ECONOMY BRUSHED COPPER|45|8 +Brand#33|ECONOMY BRUSHED NICKEL|14|8 +Brand#33|ECONOMY BRUSHED NICKEL|45|8 +Brand#33|ECONOMY BRUSHED STEEL|19|8 +Brand#33|ECONOMY BRUSHED TIN|3|8 +Brand#33|ECONOMY BURNISHED BRASS|3|8 +Brand#33|ECONOMY BURNISHED BRASS|45|8 +Brand#33|ECONOMY BURNISHED BRASS|49|8 +Brand#33|ECONOMY BURNISHED COPPER|3|8 +Brand#33|ECONOMY BURNISHED COPPER|14|8 +Brand#33|ECONOMY BURNISHED COPPER|19|8 +Brand#33|ECONOMY BURNISHED STEEL|9|8 +Brand#33|ECONOMY BURNISHED STEEL|36|8 +Brand#33|ECONOMY BURNISHED TIN|9|8 +Brand#33|ECONOMY PLATED BRASS|3|8 +Brand#33|ECONOMY PLATED BRASS|9|8 +Brand#33|ECONOMY PLATED BRASS|19|8 +Brand#33|ECONOMY PLATED COPPER|9|8 +Brand#33|ECONOMY PLATED COPPER|14|8 +Brand#33|ECONOMY PLATED COPPER|23|8 +Brand#33|ECONOMY PLATED COPPER|49|8 +Brand#33|ECONOMY PLATED NICKEL|3|8 +Brand#33|ECONOMY PLATED NICKEL|36|8 +Brand#33|ECONOMY PLATED STEEL|9|8 +Brand#33|ECONOMY PLATED STEEL|19|8 +Brand#33|ECONOMY PLATED STEEL|23|8 +Brand#33|ECONOMY POLISHED BRASS|19|8 +Brand#33|ECONOMY POLISHED NICKEL|14|8 +Brand#33|ECONOMY POLISHED STEEL|9|8 +Brand#33|LARGE ANODIZED BRASS|9|8 +Brand#33|LARGE ANODIZED BRASS|49|8 +Brand#33|LARGE ANODIZED COPPER|3|8 +Brand#33|LARGE ANODIZED COPPER|19|8 +Brand#33|LARGE ANODIZED NICKEL|19|8 +Brand#33|LARGE ANODIZED NICKEL|45|8 +Brand#33|LARGE ANODIZED NICKEL|49|8 +Brand#33|LARGE BRUSHED BRASS|3|8 +Brand#33|LARGE BRUSHED BRASS|14|8 +Brand#33|LARGE BRUSHED BRASS|19|8 +Brand#33|LARGE BRUSHED BRASS|49|8 +Brand#33|LARGE BRUSHED COPPER|19|8 +Brand#33|LARGE BRUSHED COPPER|49|8 +Brand#33|LARGE BRUSHED NICKEL|9|8 +Brand#33|LARGE BRUSHED NICKEL|14|8 +Brand#33|LARGE BRUSHED NICKEL|49|8 +Brand#33|LARGE BRUSHED STEEL|36|8 +Brand#33|LARGE BRUSHED TIN|3|8 +Brand#33|LARGE BRUSHED TIN|23|8 +Brand#33|LARGE BURNISHED BRASS|3|8 +Brand#33|LARGE BURNISHED BRASS|9|8 +Brand#33|LARGE BURNISHED COPPER|14|8 +Brand#33|LARGE BURNISHED NICKEL|3|8 +Brand#33|LARGE BURNISHED TIN|9|8 +Brand#33|LARGE BURNISHED TIN|14|8 +Brand#33|LARGE BURNISHED TIN|45|8 +Brand#33|LARGE PLATED BRASS|3|8 +Brand#33|LARGE PLATED BRASS|45|8 +Brand#33|LARGE PLATED NICKEL|3|8 +Brand#33|LARGE PLATED STEEL|3|8 +Brand#33|LARGE PLATED STEEL|14|8 +Brand#33|LARGE PLATED TIN|36|8 +Brand#33|LARGE POLISHED BRASS|23|8 +Brand#33|LARGE POLISHED NICKEL|3|8 +Brand#33|LARGE POLISHED NICKEL|14|8 +Brand#33|LARGE POLISHED STEEL|36|8 +Brand#33|LARGE POLISHED TIN|9|8 +Brand#33|LARGE POLISHED TIN|36|8 +Brand#33|MEDIUM ANODIZED BRASS|3|8 +Brand#33|MEDIUM ANODIZED COPPER|3|8 +Brand#33|MEDIUM ANODIZED COPPER|9|8 +Brand#33|MEDIUM ANODIZED STEEL|3|8 +Brand#33|MEDIUM ANODIZED STEEL|9|8 +Brand#33|MEDIUM ANODIZED TIN|9|8 +Brand#33|MEDIUM ANODIZED TIN|23|8 +Brand#33|MEDIUM ANODIZED TIN|36|8 +Brand#33|MEDIUM BRUSHED BRASS|19|8 +Brand#33|MEDIUM BRUSHED BRASS|23|8 +Brand#33|MEDIUM BRUSHED COPPER|14|8 +Brand#33|MEDIUM BRUSHED NICKEL|23|8 +Brand#33|MEDIUM BRUSHED NICKEL|45|8 +Brand#33|MEDIUM BURNISHED BRASS|3|8 +Brand#33|MEDIUM BURNISHED COPPER|14|8 +Brand#33|MEDIUM BURNISHED COPPER|45|8 +Brand#33|MEDIUM BURNISHED COPPER|49|8 +Brand#33|MEDIUM BURNISHED NICKEL|9|8 +Brand#33|MEDIUM BURNISHED NICKEL|19|8 +Brand#33|MEDIUM BURNISHED STEEL|14|8 +Brand#33|MEDIUM BURNISHED TIN|36|8 +Brand#33|MEDIUM PLATED BRASS|3|8 +Brand#33|MEDIUM PLATED BRASS|19|8 +Brand#33|MEDIUM PLATED NICKEL|3|8 +Brand#33|MEDIUM PLATED NICKEL|9|8 +Brand#33|MEDIUM PLATED NICKEL|23|8 +Brand#33|MEDIUM PLATED NICKEL|36|8 +Brand#33|MEDIUM PLATED NICKEL|45|8 +Brand#33|MEDIUM PLATED STEEL|14|8 +Brand#33|MEDIUM PLATED TIN|14|8 +Brand#33|PROMO ANODIZED BRASS|3|8 +Brand#33|PROMO ANODIZED BRASS|9|8 +Brand#33|PROMO ANODIZED BRASS|49|8 +Brand#33|PROMO ANODIZED COPPER|14|8 +Brand#33|PROMO ANODIZED COPPER|19|8 +Brand#33|PROMO ANODIZED NICKEL|45|8 +Brand#33|PROMO ANODIZED STEEL|9|8 +Brand#33|PROMO ANODIZED TIN|45|8 +Brand#33|PROMO BRUSHED COPPER|3|8 +Brand#33|PROMO BRUSHED COPPER|9|8 +Brand#33|PROMO BRUSHED COPPER|45|8 +Brand#33|PROMO BRUSHED COPPER|49|8 +Brand#33|PROMO BRUSHED NICKEL|14|8 +Brand#33|PROMO BRUSHED NICKEL|36|8 +Brand#33|PROMO BRUSHED NICKEL|49|8 +Brand#33|PROMO BRUSHED STEEL|9|8 +Brand#33|PROMO BRUSHED STEEL|49|8 +Brand#33|PROMO BRUSHED TIN|3|8 +Brand#33|PROMO BRUSHED TIN|9|8 +Brand#33|PROMO BURNISHED BRASS|45|8 +Brand#33|PROMO BURNISHED BRASS|49|8 +Brand#33|PROMO BURNISHED COPPER|3|8 +Brand#33|PROMO BURNISHED COPPER|23|8 +Brand#33|PROMO BURNISHED NICKEL|3|8 +Brand#33|PROMO BURNISHED NICKEL|14|8 +Brand#33|PROMO BURNISHED NICKEL|19|8 +Brand#33|PROMO BURNISHED STEEL|3|8 +Brand#33|PROMO BURNISHED STEEL|14|8 +Brand#33|PROMO BURNISHED STEEL|19|8 +Brand#33|PROMO BURNISHED STEEL|36|8 +Brand#33|PROMO BURNISHED STEEL|45|8 +Brand#33|PROMO PLATED BRASS|3|8 +Brand#33|PROMO PLATED BRASS|9|8 +Brand#33|PROMO PLATED BRASS|45|8 +Brand#33|PROMO PLATED COPPER|14|8 +Brand#33|PROMO PLATED COPPER|19|8 +Brand#33|PROMO PLATED COPPER|45|8 +Brand#33|PROMO PLATED NICKEL|45|8 +Brand#33|PROMO PLATED STEEL|9|8 +Brand#33|PROMO POLISHED BRASS|3|8 +Brand#33|PROMO POLISHED BRASS|9|8 +Brand#33|PROMO POLISHED BRASS|14|8 +Brand#33|PROMO POLISHED BRASS|36|8 +Brand#33|PROMO POLISHED BRASS|49|8 +Brand#33|PROMO POLISHED COPPER|45|8 +Brand#33|PROMO POLISHED NICKEL|9|8 +Brand#33|PROMO POLISHED NICKEL|49|8 +Brand#33|PROMO POLISHED STEEL|3|8 +Brand#33|PROMO POLISHED STEEL|19|8 +Brand#33|PROMO POLISHED TIN|14|8 +Brand#33|PROMO POLISHED TIN|45|8 +Brand#33|PROMO POLISHED TIN|49|8 +Brand#33|SMALL ANODIZED BRASS|23|8 +Brand#33|SMALL ANODIZED COPPER|3|8 +Brand#33|SMALL ANODIZED COPPER|14|8 +Brand#33|SMALL ANODIZED COPPER|45|8 +Brand#33|SMALL ANODIZED COPPER|49|8 +Brand#33|SMALL ANODIZED NICKEL|3|8 +Brand#33|SMALL ANODIZED NICKEL|45|8 +Brand#33|SMALL ANODIZED STEEL|3|8 +Brand#33|SMALL ANODIZED STEEL|9|8 +Brand#33|SMALL ANODIZED TIN|49|8 +Brand#33|SMALL BRUSHED BRASS|9|8 +Brand#33|SMALL BRUSHED BRASS|23|8 +Brand#33|SMALL BRUSHED BRASS|49|8 +Brand#33|SMALL BRUSHED STEEL|3|8 +Brand#33|SMALL BRUSHED TIN|9|8 +Brand#33|SMALL BRUSHED TIN|19|8 +Brand#33|SMALL BURNISHED BRASS|9|8 +Brand#33|SMALL BURNISHED BRASS|14|8 +Brand#33|SMALL BURNISHED BRASS|23|8 +Brand#33|SMALL BURNISHED COPPER|36|8 +Brand#33|SMALL BURNISHED STEEL|9|8 +Brand#33|SMALL BURNISHED STEEL|14|8 +Brand#33|SMALL BURNISHED TIN|23|8 +Brand#33|SMALL BURNISHED TIN|36|8 +Brand#33|SMALL BURNISHED TIN|45|8 +Brand#33|SMALL PLATED BRASS|9|8 +Brand#33|SMALL PLATED BRASS|49|8 +Brand#33|SMALL PLATED NICKEL|14|8 +Brand#33|SMALL PLATED NICKEL|19|8 +Brand#33|SMALL PLATED NICKEL|36|8 +Brand#33|SMALL PLATED STEEL|14|8 +Brand#33|SMALL PLATED STEEL|23|8 +Brand#33|SMALL PLATED TIN|23|8 +Brand#33|SMALL PLATED TIN|36|8 +Brand#33|SMALL PLATED TIN|49|8 +Brand#33|SMALL POLISHED BRASS|9|8 +Brand#33|SMALL POLISHED BRASS|23|8 +Brand#33|SMALL POLISHED BRASS|45|8 +Brand#33|SMALL POLISHED COPPER|3|8 +Brand#33|SMALL POLISHED STEEL|23|8 +Brand#33|SMALL POLISHED STEEL|49|8 +Brand#33|SMALL POLISHED TIN|19|8 +Brand#33|SMALL POLISHED TIN|23|8 +Brand#33|SMALL POLISHED TIN|45|8 +Brand#33|STANDARD ANODIZED COPPER|3|8 +Brand#33|STANDARD ANODIZED COPPER|19|8 +Brand#33|STANDARD ANODIZED COPPER|23|8 +Brand#33|STANDARD ANODIZED COPPER|49|8 +Brand#33|STANDARD ANODIZED NICKEL|45|8 +Brand#33|STANDARD ANODIZED STEEL|19|8 +Brand#33|STANDARD ANODIZED STEEL|36|8 +Brand#33|STANDARD ANODIZED STEEL|49|8 +Brand#33|STANDARD ANODIZED TIN|23|8 +Brand#33|STANDARD ANODIZED TIN|49|8 +Brand#33|STANDARD BRUSHED BRASS|9|8 +Brand#33|STANDARD BRUSHED COPPER|3|8 +Brand#33|STANDARD BRUSHED COPPER|19|8 +Brand#33|STANDARD BRUSHED COPPER|36|8 +Brand#33|STANDARD BRUSHED NICKEL|23|8 +Brand#33|STANDARD BRUSHED NICKEL|49|8 +Brand#33|STANDARD BRUSHED STEEL|9|8 +Brand#33|STANDARD BRUSHED TIN|19|8 +Brand#33|STANDARD BURNISHED BRASS|14|8 +Brand#33|STANDARD BURNISHED BRASS|23|8 +Brand#33|STANDARD BURNISHED BRASS|49|8 +Brand#33|STANDARD BURNISHED COPPER|19|8 +Brand#33|STANDARD BURNISHED NICKEL|36|8 +Brand#33|STANDARD BURNISHED STEEL|36|8 +Brand#33|STANDARD PLATED BRASS|14|8 +Brand#33|STANDARD PLATED BRASS|36|8 +Brand#33|STANDARD PLATED BRASS|45|8 +Brand#33|STANDARD PLATED BRASS|49|8 +Brand#33|STANDARD PLATED COPPER|14|8 +Brand#33|STANDARD PLATED COPPER|19|8 +Brand#33|STANDARD PLATED COPPER|45|8 +Brand#33|STANDARD PLATED COPPER|49|8 +Brand#33|STANDARD PLATED NICKEL|36|8 +Brand#33|STANDARD PLATED STEEL|3|8 +Brand#33|STANDARD PLATED STEEL|9|8 +Brand#33|STANDARD PLATED STEEL|23|8 +Brand#33|STANDARD PLATED STEEL|49|8 +Brand#33|STANDARD PLATED TIN|14|8 +Brand#33|STANDARD PLATED TIN|49|8 +Brand#33|STANDARD POLISHED BRASS|19|8 +Brand#33|STANDARD POLISHED COPPER|3|8 +Brand#33|STANDARD POLISHED COPPER|9|8 +Brand#33|STANDARD POLISHED COPPER|23|8 +Brand#33|STANDARD POLISHED NICKEL|14|8 +Brand#33|STANDARD POLISHED STEEL|14|8 +Brand#33|STANDARD POLISHED STEEL|19|8 +Brand#33|STANDARD POLISHED STEEL|49|8 +Brand#34|ECONOMY ANODIZED BRASS|14|8 +Brand#34|ECONOMY ANODIZED COPPER|9|8 +Brand#34|ECONOMY ANODIZED COPPER|14|8 +Brand#34|ECONOMY ANODIZED COPPER|45|8 +Brand#34|ECONOMY ANODIZED STEEL|49|8 +Brand#34|ECONOMY ANODIZED TIN|19|8 +Brand#34|ECONOMY ANODIZED TIN|23|8 +Brand#34|ECONOMY ANODIZED TIN|36|8 +Brand#34|ECONOMY ANODIZED TIN|49|8 +Brand#34|ECONOMY BRUSHED BRASS|9|8 +Brand#34|ECONOMY BRUSHED BRASS|14|8 +Brand#34|ECONOMY BRUSHED BRASS|36|8 +Brand#34|ECONOMY BRUSHED COPPER|3|8 +Brand#34|ECONOMY BRUSHED NICKEL|23|8 +Brand#34|ECONOMY BRUSHED STEEL|3|8 +Brand#34|ECONOMY BRUSHED STEEL|19|8 +Brand#34|ECONOMY BRUSHED TIN|14|8 +Brand#34|ECONOMY BURNISHED NICKEL|45|8 +Brand#34|ECONOMY BURNISHED TIN|3|8 +Brand#34|ECONOMY BURNISHED TIN|9|8 +Brand#34|ECONOMY BURNISHED TIN|19|8 +Brand#34|ECONOMY PLATED BRASS|9|8 +Brand#34|ECONOMY PLATED BRASS|14|8 +Brand#34|ECONOMY PLATED BRASS|45|8 +Brand#34|ECONOMY PLATED COPPER|49|8 +Brand#34|ECONOMY PLATED NICKEL|23|8 +Brand#34|ECONOMY PLATED NICKEL|36|8 +Brand#34|ECONOMY PLATED NICKEL|45|8 +Brand#34|ECONOMY PLATED STEEL|3|8 +Brand#34|ECONOMY PLATED STEEL|9|8 +Brand#34|ECONOMY PLATED TIN|45|8 +Brand#34|ECONOMY POLISHED BRASS|14|8 +Brand#34|ECONOMY POLISHED BRASS|19|8 +Brand#34|ECONOMY POLISHED BRASS|36|8 +Brand#34|ECONOMY POLISHED COPPER|14|8 +Brand#34|ECONOMY POLISHED COPPER|19|8 +Brand#34|ECONOMY POLISHED COPPER|45|8 +Brand#34|ECONOMY POLISHED STEEL|14|8 +Brand#34|ECONOMY POLISHED STEEL|23|8 +Brand#34|ECONOMY POLISHED STEEL|45|8 +Brand#34|LARGE ANODIZED TIN|36|8 +Brand#34|LARGE BRUSHED BRASS|14|8 +Brand#34|LARGE BRUSHED BRASS|49|8 +Brand#34|LARGE BRUSHED STEEL|19|8 +Brand#34|LARGE BRUSHED STEEL|49|8 +Brand#34|LARGE BRUSHED TIN|9|8 +Brand#34|LARGE BURNISHED BRASS|36|8 +Brand#34|LARGE BURNISHED BRASS|45|8 +Brand#34|LARGE BURNISHED COPPER|3|8 +Brand#34|LARGE BURNISHED COPPER|14|8 +Brand#34|LARGE BURNISHED COPPER|36|8 +Brand#34|LARGE BURNISHED NICKEL|3|8 +Brand#34|LARGE BURNISHED STEEL|19|8 +Brand#34|LARGE BURNISHED TIN|3|8 +Brand#34|LARGE PLATED COPPER|3|8 +Brand#34|LARGE PLATED COPPER|14|8 +Brand#34|LARGE PLATED COPPER|36|8 +Brand#34|LARGE PLATED COPPER|49|8 +Brand#34|LARGE PLATED NICKEL|14|8 +Brand#34|LARGE PLATED STEEL|23|8 +Brand#34|LARGE PLATED TIN|19|8 +Brand#34|LARGE POLISHED BRASS|23|8 +Brand#34|LARGE POLISHED COPPER|9|8 +Brand#34|LARGE POLISHED NICKEL|19|8 +Brand#34|LARGE POLISHED STEEL|3|8 +Brand#34|LARGE POLISHED STEEL|23|8 +Brand#34|LARGE POLISHED STEEL|36|8 +Brand#34|LARGE POLISHED TIN|19|8 +Brand#34|LARGE POLISHED TIN|36|8 +Brand#34|LARGE POLISHED TIN|45|8 +Brand#34|MEDIUM ANODIZED BRASS|9|8 +Brand#34|MEDIUM ANODIZED BRASS|23|8 +Brand#34|MEDIUM ANODIZED BRASS|49|8 +Brand#34|MEDIUM ANODIZED STEEL|49|8 +Brand#34|MEDIUM ANODIZED TIN|9|8 +Brand#34|MEDIUM ANODIZED TIN|14|8 +Brand#34|MEDIUM BRUSHED COPPER|19|8 +Brand#34|MEDIUM BRUSHED COPPER|45|8 +Brand#34|MEDIUM BRUSHED COPPER|49|8 +Brand#34|MEDIUM BRUSHED NICKEL|23|8 +Brand#34|MEDIUM BRUSHED STEEL|36|8 +Brand#34|MEDIUM BRUSHED TIN|9|8 +Brand#34|MEDIUM BURNISHED BRASS|49|8 +Brand#34|MEDIUM BURNISHED COPPER|3|8 +Brand#34|MEDIUM BURNISHED NICKEL|36|8 +Brand#34|MEDIUM BURNISHED TIN|3|8 +Brand#34|MEDIUM PLATED BRASS|19|8 +Brand#34|MEDIUM PLATED COPPER|14|8 +Brand#34|MEDIUM PLATED COPPER|49|8 +Brand#34|MEDIUM PLATED STEEL|14|8 +Brand#34|MEDIUM PLATED STEEL|23|8 +Brand#34|MEDIUM PLATED TIN|14|8 +Brand#34|MEDIUM PLATED TIN|19|8 +Brand#34|MEDIUM PLATED TIN|36|8 +Brand#34|PROMO ANODIZED BRASS|3|8 +Brand#34|PROMO ANODIZED COPPER|14|8 +Brand#34|PROMO ANODIZED COPPER|45|8 +Brand#34|PROMO ANODIZED NICKEL|14|8 +Brand#34|PROMO ANODIZED NICKEL|19|8 +Brand#34|PROMO ANODIZED STEEL|14|8 +Brand#34|PROMO ANODIZED STEEL|23|8 +Brand#34|PROMO ANODIZED TIN|3|8 +Brand#34|PROMO ANODIZED TIN|9|8 +Brand#34|PROMO ANODIZED TIN|14|8 +Brand#34|PROMO BRUSHED BRASS|9|8 +Brand#34|PROMO BRUSHED BRASS|19|8 +Brand#34|PROMO BRUSHED BRASS|23|8 +Brand#34|PROMO BRUSHED BRASS|45|8 +Brand#34|PROMO BRUSHED COPPER|14|8 +Brand#34|PROMO BRUSHED STEEL|36|8 +Brand#34|PROMO BRUSHED TIN|3|8 +Brand#34|PROMO BRUSHED TIN|45|8 +Brand#34|PROMO BURNISHED BRASS|14|8 +Brand#34|PROMO BURNISHED BRASS|36|8 +Brand#34|PROMO BURNISHED NICKEL|19|8 +Brand#34|PROMO BURNISHED STEEL|9|8 +Brand#34|PROMO BURNISHED STEEL|45|8 +Brand#34|PROMO BURNISHED STEEL|49|8 +Brand#34|PROMO BURNISHED TIN|14|8 +Brand#34|PROMO BURNISHED TIN|36|8 +Brand#34|PROMO PLATED BRASS|9|8 +Brand#34|PROMO PLATED BRASS|23|8 +Brand#34|PROMO PLATED BRASS|49|8 +Brand#34|PROMO PLATED NICKEL|23|8 +Brand#34|PROMO PLATED STEEL|9|8 +Brand#34|PROMO PLATED STEEL|14|8 +Brand#34|PROMO POLISHED BRASS|23|8 +Brand#34|PROMO POLISHED COPPER|14|8 +Brand#34|PROMO POLISHED NICKEL|19|8 +Brand#34|PROMO POLISHED STEEL|9|8 +Brand#34|PROMO POLISHED STEEL|19|8 +Brand#34|PROMO POLISHED STEEL|49|8 +Brand#34|PROMO POLISHED TIN|9|8 +Brand#34|PROMO POLISHED TIN|45|8 +Brand#34|SMALL ANODIZED BRASS|49|8 +Brand#34|SMALL ANODIZED NICKEL|23|8 +Brand#34|SMALL ANODIZED NICKEL|36|8 +Brand#34|SMALL ANODIZED NICKEL|49|8 +Brand#34|SMALL ANODIZED STEEL|49|8 +Brand#34|SMALL ANODIZED TIN|49|8 +Brand#34|SMALL BRUSHED BRASS|14|8 +Brand#34|SMALL BRUSHED NICKEL|14|8 +Brand#34|SMALL BRUSHED NICKEL|45|8 +Brand#34|SMALL BRUSHED STEEL|9|8 +Brand#34|SMALL BRUSHED STEEL|14|8 +Brand#34|SMALL BURNISHED BRASS|19|8 +Brand#34|SMALL BURNISHED BRASS|45|8 +Brand#34|SMALL BURNISHED COPPER|14|8 +Brand#34|SMALL BURNISHED COPPER|23|8 +Brand#34|SMALL BURNISHED NICKEL|3|8 +Brand#34|SMALL BURNISHED NICKEL|49|8 +Brand#34|SMALL BURNISHED TIN|36|8 +Brand#34|SMALL BURNISHED TIN|49|8 +Brand#34|SMALL PLATED BRASS|23|8 +Brand#34|SMALL PLATED COPPER|19|8 +Brand#34|SMALL PLATED COPPER|23|8 +Brand#34|SMALL PLATED COPPER|49|8 +Brand#34|SMALL PLATED NICKEL|3|8 +Brand#34|SMALL PLATED NICKEL|9|8 +Brand#34|SMALL PLATED NICKEL|23|8 +Brand#34|SMALL PLATED STEEL|9|8 +Brand#34|SMALL PLATED STEEL|45|8 +Brand#34|SMALL PLATED TIN|14|8 +Brand#34|SMALL PLATED TIN|19|8 +Brand#34|SMALL POLISHED BRASS|14|8 +Brand#34|SMALL POLISHED COPPER|9|8 +Brand#34|SMALL POLISHED COPPER|45|8 +Brand#34|STANDARD ANODIZED BRASS|14|8 +Brand#34|STANDARD ANODIZED BRASS|23|8 +Brand#34|STANDARD ANODIZED COPPER|3|8 +Brand#34|STANDARD ANODIZED COPPER|45|8 +Brand#34|STANDARD ANODIZED NICKEL|3|8 +Brand#34|STANDARD ANODIZED NICKEL|9|8 +Brand#34|STANDARD ANODIZED NICKEL|23|8 +Brand#34|STANDARD ANODIZED NICKEL|36|8 +Brand#34|STANDARD ANODIZED STEEL|3|8 +Brand#34|STANDARD ANODIZED STEEL|45|8 +Brand#34|STANDARD ANODIZED TIN|36|8 +Brand#34|STANDARD BRUSHED COPPER|49|8 +Brand#34|STANDARD BRUSHED NICKEL|19|8 +Brand#34|STANDARD BRUSHED NICKEL|45|8 +Brand#34|STANDARD BRUSHED TIN|49|8 +Brand#34|STANDARD BURNISHED BRASS|14|8 +Brand#34|STANDARD BURNISHED BRASS|19|8 +Brand#34|STANDARD BURNISHED BRASS|49|8 +Brand#34|STANDARD BURNISHED COPPER|9|8 +Brand#34|STANDARD BURNISHED COPPER|45|8 +Brand#34|STANDARD BURNISHED NICKEL|14|8 +Brand#34|STANDARD BURNISHED NICKEL|49|8 +Brand#34|STANDARD BURNISHED STEEL|9|8 +Brand#34|STANDARD BURNISHED TIN|9|8 +Brand#34|STANDARD BURNISHED TIN|23|8 +Brand#34|STANDARD PLATED NICKEL|3|8 +Brand#34|STANDARD PLATED NICKEL|19|8 +Brand#34|STANDARD PLATED NICKEL|36|8 +Brand#34|STANDARD PLATED STEEL|23|8 +Brand#34|STANDARD PLATED STEEL|49|8 +Brand#34|STANDARD PLATED TIN|14|8 +Brand#34|STANDARD PLATED TIN|19|8 +Brand#34|STANDARD PLATED TIN|45|8 +Brand#34|STANDARD POLISHED BRASS|9|8 +Brand#34|STANDARD POLISHED BRASS|19|8 +Brand#34|STANDARD POLISHED COPPER|36|8 +Brand#34|STANDARD POLISHED NICKEL|36|8 +Brand#34|STANDARD POLISHED STEEL|3|8 +Brand#34|STANDARD POLISHED STEEL|9|8 +Brand#34|STANDARD POLISHED STEEL|23|8 +Brand#34|STANDARD POLISHED TIN|3|8 +Brand#35|ECONOMY ANODIZED BRASS|23|8 +Brand#35|ECONOMY ANODIZED COPPER|3|8 +Brand#35|ECONOMY ANODIZED COPPER|49|8 +Brand#35|ECONOMY ANODIZED NICKEL|3|8 +Brand#35|ECONOMY ANODIZED NICKEL|9|8 +Brand#35|ECONOMY ANODIZED NICKEL|49|8 +Brand#35|ECONOMY ANODIZED STEEL|36|8 +Brand#35|ECONOMY ANODIZED TIN|19|8 +Brand#35|ECONOMY ANODIZED TIN|23|8 +Brand#35|ECONOMY BRUSHED BRASS|3|8 +Brand#35|ECONOMY BRUSHED COPPER|23|8 +Brand#35|ECONOMY BRUSHED NICKEL|14|8 +Brand#35|ECONOMY BRUSHED STEEL|23|8 +Brand#35|ECONOMY BRUSHED STEEL|36|8 +Brand#35|ECONOMY BRUSHED STEEL|45|8 +Brand#35|ECONOMY BRUSHED TIN|3|8 +Brand#35|ECONOMY BRUSHED TIN|9|8 +Brand#35|ECONOMY BRUSHED TIN|23|8 +Brand#35|ECONOMY BRUSHED TIN|36|8 +Brand#35|ECONOMY BURNISHED BRASS|3|8 +Brand#35|ECONOMY BURNISHED COPPER|19|8 +Brand#35|ECONOMY BURNISHED COPPER|23|8 +Brand#35|ECONOMY BURNISHED NICKEL|23|8 +Brand#35|ECONOMY BURNISHED TIN|3|8 +Brand#35|ECONOMY BURNISHED TIN|23|8 +Brand#35|ECONOMY BURNISHED TIN|45|8 +Brand#35|ECONOMY PLATED BRASS|45|8 +Brand#35|ECONOMY PLATED BRASS|49|8 +Brand#35|ECONOMY PLATED COPPER|19|8 +Brand#35|ECONOMY PLATED NICKEL|3|8 +Brand#35|ECONOMY PLATED NICKEL|23|8 +Brand#35|ECONOMY PLATED STEEL|19|8 +Brand#35|ECONOMY PLATED STEEL|23|8 +Brand#35|ECONOMY PLATED TIN|36|8 +Brand#35|ECONOMY POLISHED BRASS|14|8 +Brand#35|ECONOMY POLISHED BRASS|36|8 +Brand#35|ECONOMY POLISHED COPPER|3|8 +Brand#35|ECONOMY POLISHED COPPER|14|8 +Brand#35|ECONOMY POLISHED NICKEL|9|8 +Brand#35|ECONOMY POLISHED TIN|19|8 +Brand#35|LARGE ANODIZED BRASS|23|8 +Brand#35|LARGE ANODIZED BRASS|36|8 +Brand#35|LARGE ANODIZED COPPER|9|8 +Brand#35|LARGE ANODIZED COPPER|14|8 +Brand#35|LARGE ANODIZED STEEL|9|8 +Brand#35|LARGE ANODIZED STEEL|19|8 +Brand#35|LARGE ANODIZED STEEL|23|8 +Brand#35|LARGE ANODIZED TIN|9|8 +Brand#35|LARGE ANODIZED TIN|14|8 +Brand#35|LARGE BRUSHED BRASS|23|8 +Brand#35|LARGE BRUSHED COPPER|9|8 +Brand#35|LARGE BRUSHED COPPER|19|8 +Brand#35|LARGE BRUSHED STEEL|14|8 +Brand#35|LARGE BRUSHED STEEL|19|8 +Brand#35|LARGE BRUSHED TIN|23|8 +Brand#35|LARGE BRUSHED TIN|45|8 +Brand#35|LARGE BURNISHED BRASS|14|8 +Brand#35|LARGE BURNISHED BRASS|19|8 +Brand#35|LARGE BURNISHED BRASS|36|8 +Brand#35|LARGE BURNISHED COPPER|3|8 +Brand#35|LARGE BURNISHED NICKEL|14|8 +Brand#35|LARGE BURNISHED NICKEL|23|8 +Brand#35|LARGE BURNISHED TIN|3|8 +Brand#35|LARGE BURNISHED TIN|9|8 +Brand#35|LARGE BURNISHED TIN|19|8 +Brand#35|LARGE BURNISHED TIN|23|8 +Brand#35|LARGE BURNISHED TIN|36|8 +Brand#35|LARGE PLATED BRASS|9|8 +Brand#35|LARGE PLATED BRASS|45|8 +Brand#35|LARGE PLATED COPPER|23|8 +Brand#35|LARGE PLATED NICKEL|3|8 +Brand#35|LARGE PLATED NICKEL|19|8 +Brand#35|LARGE PLATED STEEL|23|8 +Brand#35|LARGE PLATED TIN|9|8 +Brand#35|LARGE PLATED TIN|45|8 +Brand#35|LARGE POLISHED BRASS|19|8 +Brand#35|LARGE POLISHED BRASS|49|8 +Brand#35|LARGE POLISHED STEEL|14|8 +Brand#35|LARGE POLISHED STEEL|36|8 +Brand#35|LARGE POLISHED TIN|9|8 +Brand#35|LARGE POLISHED TIN|14|8 +Brand#35|MEDIUM ANODIZED COPPER|9|8 +Brand#35|MEDIUM ANODIZED STEEL|3|8 +Brand#35|MEDIUM ANODIZED TIN|14|8 +Brand#35|MEDIUM ANODIZED TIN|45|8 +Brand#35|MEDIUM ANODIZED TIN|49|8 +Brand#35|MEDIUM BRUSHED BRASS|19|8 +Brand#35|MEDIUM BRUSHED BRASS|23|8 +Brand#35|MEDIUM BRUSHED COPPER|19|8 +Brand#35|MEDIUM BRUSHED COPPER|36|8 +Brand#35|MEDIUM BRUSHED NICKEL|9|8 +Brand#35|MEDIUM BRUSHED STEEL|3|8 +Brand#35|MEDIUM BRUSHED TIN|14|8 +Brand#35|MEDIUM BRUSHED TIN|19|8 +Brand#35|MEDIUM BURNISHED BRASS|49|8 +Brand#35|MEDIUM BURNISHED STEEL|45|8 +Brand#35|MEDIUM BURNISHED TIN|9|8 +Brand#35|MEDIUM BURNISHED TIN|19|8 +Brand#35|MEDIUM BURNISHED TIN|23|8 +Brand#35|MEDIUM BURNISHED TIN|36|8 +Brand#35|MEDIUM BURNISHED TIN|45|8 +Brand#35|MEDIUM PLATED BRASS|3|8 +Brand#35|MEDIUM PLATED BRASS|23|8 +Brand#35|MEDIUM PLATED BRASS|36|8 +Brand#35|MEDIUM PLATED COPPER|3|8 +Brand#35|MEDIUM PLATED COPPER|9|8 +Brand#35|MEDIUM PLATED COPPER|19|8 +Brand#35|MEDIUM PLATED NICKEL|49|8 +Brand#35|MEDIUM PLATED STEEL|14|8 +Brand#35|MEDIUM PLATED STEEL|23|8 +Brand#35|MEDIUM PLATED STEEL|36|8 +Brand#35|MEDIUM PLATED TIN|23|8 +Brand#35|PROMO ANODIZED BRASS|3|8 +Brand#35|PROMO ANODIZED COPPER|3|8 +Brand#35|PROMO ANODIZED COPPER|36|8 +Brand#35|PROMO ANODIZED NICKEL|36|8 +Brand#35|PROMO ANODIZED NICKEL|45|8 +Brand#35|PROMO ANODIZED NICKEL|49|8 +Brand#35|PROMO ANODIZED STEEL|45|8 +Brand#35|PROMO ANODIZED TIN|14|8 +Brand#35|PROMO BRUSHED BRASS|14|8 +Brand#35|PROMO BRUSHED BRASS|45|8 +Brand#35|PROMO BRUSHED COPPER|3|8 +Brand#35|PROMO BRUSHED COPPER|14|8 +Brand#35|PROMO BRUSHED NICKEL|9|8 +Brand#35|PROMO BRUSHED STEEL|9|8 +Brand#35|PROMO BRUSHED TIN|19|8 +Brand#35|PROMO BRUSHED TIN|45|8 +Brand#35|PROMO BURNISHED BRASS|3|8 +Brand#35|PROMO BURNISHED BRASS|19|8 +Brand#35|PROMO BURNISHED COPPER|9|8 +Brand#35|PROMO BURNISHED COPPER|14|8 +Brand#35|PROMO BURNISHED COPPER|19|8 +Brand#35|PROMO BURNISHED NICKEL|14|8 +Brand#35|PROMO BURNISHED TIN|3|8 +Brand#35|PROMO BURNISHED TIN|45|8 +Brand#35|PROMO PLATED BRASS|19|8 +Brand#35|PROMO PLATED COPPER|23|8 +Brand#35|PROMO PLATED NICKEL|9|8 +Brand#35|PROMO PLATED NICKEL|23|8 +Brand#35|PROMO PLATED NICKEL|45|8 +Brand#35|PROMO PLATED STEEL|9|8 +Brand#35|PROMO PLATED STEEL|23|8 +Brand#35|PROMO PLATED STEEL|36|8 +Brand#35|PROMO PLATED TIN|3|8 +Brand#35|PROMO PLATED TIN|9|8 +Brand#35|PROMO PLATED TIN|19|8 +Brand#35|PROMO PLATED TIN|36|8 +Brand#35|PROMO PLATED TIN|45|8 +Brand#35|PROMO POLISHED BRASS|3|8 +Brand#35|PROMO POLISHED BRASS|9|8 +Brand#35|PROMO POLISHED BRASS|23|8 +Brand#35|PROMO POLISHED NICKEL|9|8 +Brand#35|PROMO POLISHED NICKEL|23|8 +Brand#35|PROMO POLISHED TIN|3|8 +Brand#35|PROMO POLISHED TIN|23|8 +Brand#35|PROMO POLISHED TIN|45|8 +Brand#35|SMALL ANODIZED BRASS|49|8 +Brand#35|SMALL ANODIZED NICKEL|9|8 +Brand#35|SMALL ANODIZED NICKEL|19|8 +Brand#35|SMALL ANODIZED STEEL|19|8 +Brand#35|SMALL ANODIZED TIN|14|8 +Brand#35|SMALL ANODIZED TIN|36|8 +Brand#35|SMALL BRUSHED BRASS|14|8 +Brand#35|SMALL BRUSHED COPPER|49|8 +Brand#35|SMALL BRUSHED NICKEL|3|8 +Brand#35|SMALL BRUSHED NICKEL|9|8 +Brand#35|SMALL BRUSHED NICKEL|49|8 +Brand#35|SMALL BRUSHED STEEL|9|8 +Brand#35|SMALL BRUSHED STEEL|23|8 +Brand#35|SMALL BRUSHED STEEL|36|8 +Brand#35|SMALL BRUSHED STEEL|49|8 +Brand#35|SMALL BRUSHED TIN|19|8 +Brand#35|SMALL BRUSHED TIN|23|8 +Brand#35|SMALL BURNISHED COPPER|49|8 +Brand#35|SMALL BURNISHED NICKEL|9|8 +Brand#35|SMALL BURNISHED STEEL|3|8 +Brand#35|SMALL BURNISHED STEEL|14|8 +Brand#35|SMALL BURNISHED STEEL|23|8 +Brand#35|SMALL BURNISHED STEEL|36|8 +Brand#35|SMALL PLATED COPPER|45|8 +Brand#35|SMALL PLATED NICKEL|9|8 +Brand#35|SMALL PLATED NICKEL|23|8 +Brand#35|SMALL PLATED NICKEL|36|8 +Brand#35|SMALL PLATED NICKEL|45|8 +Brand#35|SMALL PLATED STEEL|3|8 +Brand#35|SMALL PLATED STEEL|14|8 +Brand#35|SMALL PLATED TIN|9|8 +Brand#35|SMALL POLISHED BRASS|9|8 +Brand#35|SMALL POLISHED BRASS|23|8 +Brand#35|SMALL POLISHED BRASS|36|8 +Brand#35|SMALL POLISHED COPPER|3|8 +Brand#35|SMALL POLISHED COPPER|23|8 +Brand#35|SMALL POLISHED COPPER|45|8 +Brand#35|SMALL POLISHED COPPER|49|8 +Brand#35|SMALL POLISHED NICKEL|14|8 +Brand#35|SMALL POLISHED NICKEL|19|8 +Brand#35|SMALL POLISHED STEEL|23|8 +Brand#35|SMALL POLISHED STEEL|49|8 +Brand#35|SMALL POLISHED TIN|9|8 +Brand#35|SMALL POLISHED TIN|23|8 +Brand#35|SMALL POLISHED TIN|45|8 +Brand#35|SMALL POLISHED TIN|49|8 +Brand#35|STANDARD ANODIZED BRASS|14|8 +Brand#35|STANDARD ANODIZED BRASS|19|8 +Brand#35|STANDARD ANODIZED COPPER|14|8 +Brand#35|STANDARD ANODIZED COPPER|36|8 +Brand#35|STANDARD ANODIZED COPPER|45|8 +Brand#35|STANDARD ANODIZED NICKEL|14|8 +Brand#35|STANDARD ANODIZED NICKEL|49|8 +Brand#35|STANDARD ANODIZED STEEL|14|8 +Brand#35|STANDARD ANODIZED TIN|23|8 +Brand#35|STANDARD ANODIZED TIN|45|8 +Brand#35|STANDARD ANODIZED TIN|49|8 +Brand#35|STANDARD BRUSHED BRASS|19|8 +Brand#35|STANDARD BRUSHED BRASS|23|8 +Brand#35|STANDARD BRUSHED BRASS|36|8 +Brand#35|STANDARD BRUSHED COPPER|14|8 +Brand#35|STANDARD BRUSHED COPPER|23|8 +Brand#35|STANDARD BRUSHED COPPER|36|8 +Brand#35|STANDARD BRUSHED NICKEL|14|8 +Brand#35|STANDARD BRUSHED NICKEL|49|8 +Brand#35|STANDARD BRUSHED TIN|3|8 +Brand#35|STANDARD BURNISHED BRASS|45|8 +Brand#35|STANDARD BURNISHED COPPER|36|8 +Brand#35|STANDARD BURNISHED NICKEL|9|8 +Brand#35|STANDARD BURNISHED NICKEL|14|8 +Brand#35|STANDARD BURNISHED NICKEL|49|8 +Brand#35|STANDARD BURNISHED STEEL|14|8 +Brand#35|STANDARD BURNISHED TIN|36|8 +Brand#35|STANDARD PLATED BRASS|23|8 +Brand#35|STANDARD PLATED COPPER|3|8 +Brand#35|STANDARD PLATED COPPER|19|8 +Brand#35|STANDARD PLATED COPPER|36|8 +Brand#35|STANDARD PLATED NICKEL|14|8 +Brand#35|STANDARD PLATED TIN|19|8 +Brand#35|STANDARD PLATED TIN|23|8 +Brand#35|STANDARD PLATED TIN|49|8 +Brand#35|STANDARD POLISHED BRASS|19|8 +Brand#35|STANDARD POLISHED BRASS|36|8 +Brand#35|STANDARD POLISHED NICKEL|23|8 +Brand#35|STANDARD POLISHED STEEL|14|8 +Brand#35|STANDARD POLISHED STEEL|45|8 +Brand#35|STANDARD POLISHED STEEL|49|8 +Brand#35|STANDARD POLISHED TIN|45|8 +Brand#41|ECONOMY ANODIZED BRASS|14|8 +Brand#41|ECONOMY ANODIZED BRASS|19|8 +Brand#41|ECONOMY ANODIZED COPPER|23|8 +Brand#41|ECONOMY ANODIZED NICKEL|19|8 +Brand#41|ECONOMY ANODIZED NICKEL|45|8 +Brand#41|ECONOMY ANODIZED STEEL|45|8 +Brand#41|ECONOMY BRUSHED BRASS|3|8 +Brand#41|ECONOMY BRUSHED BRASS|14|8 +Brand#41|ECONOMY BRUSHED BRASS|36|8 +Brand#41|ECONOMY BRUSHED COPPER|3|8 +Brand#41|ECONOMY BRUSHED COPPER|14|8 +Brand#41|ECONOMY BRUSHED COPPER|19|8 +Brand#41|ECONOMY BRUSHED NICKEL|19|8 +Brand#41|ECONOMY BRUSHED NICKEL|36|8 +Brand#41|ECONOMY BRUSHED NICKEL|45|8 +Brand#41|ECONOMY BRUSHED STEEL|3|8 +Brand#41|ECONOMY BRUSHED STEEL|45|8 +Brand#41|ECONOMY BRUSHED TIN|14|8 +Brand#41|ECONOMY BRUSHED TIN|36|8 +Brand#41|ECONOMY BURNISHED BRASS|3|8 +Brand#41|ECONOMY BURNISHED BRASS|45|8 +Brand#41|ECONOMY BURNISHED COPPER|9|8 +Brand#41|ECONOMY BURNISHED NICKEL|45|8 +Brand#41|ECONOMY BURNISHED NICKEL|49|8 +Brand#41|ECONOMY BURNISHED STEEL|23|8 +Brand#41|ECONOMY BURNISHED TIN|3|8 +Brand#41|ECONOMY PLATED BRASS|49|8 +Brand#41|ECONOMY PLATED COPPER|14|8 +Brand#41|ECONOMY PLATED NICKEL|14|8 +Brand#41|ECONOMY PLATED NICKEL|45|8 +Brand#41|ECONOMY PLATED STEEL|9|8 +Brand#41|ECONOMY PLATED STEEL|23|8 +Brand#41|ECONOMY PLATED STEEL|45|8 +Brand#41|ECONOMY PLATED TIN|19|8 +Brand#41|ECONOMY PLATED TIN|49|8 +Brand#41|ECONOMY POLISHED BRASS|14|8 +Brand#41|ECONOMY POLISHED BRASS|23|8 +Brand#41|ECONOMY POLISHED BRASS|49|8 +Brand#41|ECONOMY POLISHED COPPER|14|8 +Brand#41|ECONOMY POLISHED NICKEL|49|8 +Brand#41|ECONOMY POLISHED TIN|45|8 +Brand#41|ECONOMY POLISHED TIN|49|8 +Brand#41|LARGE ANODIZED BRASS|3|8 +Brand#41|LARGE ANODIZED BRASS|45|8 +Brand#41|LARGE ANODIZED COPPER|14|8 +Brand#41|LARGE ANODIZED NICKEL|3|8 +Brand#41|LARGE ANODIZED STEEL|14|8 +Brand#41|LARGE ANODIZED STEEL|36|8 +Brand#41|LARGE ANODIZED TIN|45|8 +Brand#41|LARGE BRUSHED BRASS|23|8 +Brand#41|LARGE BRUSHED COPPER|49|8 +Brand#41|LARGE BRUSHED TIN|14|8 +Brand#41|LARGE BRUSHED TIN|19|8 +Brand#41|LARGE BRUSHED TIN|49|8 +Brand#41|LARGE BURNISHED BRASS|19|8 +Brand#41|LARGE BURNISHED COPPER|14|8 +Brand#41|LARGE BURNISHED COPPER|49|8 +Brand#41|LARGE BURNISHED NICKEL|14|8 +Brand#41|LARGE BURNISHED STEEL|3|8 +Brand#41|LARGE BURNISHED STEEL|14|8 +Brand#41|LARGE BURNISHED STEEL|45|8 +Brand#41|LARGE BURNISHED STEEL|49|8 +Brand#41|LARGE BURNISHED TIN|3|8 +Brand#41|LARGE BURNISHED TIN|9|8 +Brand#41|LARGE BURNISHED TIN|36|8 +Brand#41|LARGE PLATED BRASS|3|8 +Brand#41|LARGE PLATED BRASS|14|8 +Brand#41|LARGE PLATED BRASS|19|8 +Brand#41|LARGE PLATED BRASS|45|8 +Brand#41|LARGE PLATED COPPER|14|8 +Brand#41|LARGE PLATED COPPER|23|8 +Brand#41|LARGE PLATED NICKEL|3|8 +Brand#41|LARGE PLATED NICKEL|9|8 +Brand#41|LARGE PLATED NICKEL|36|8 +Brand#41|LARGE PLATED STEEL|3|8 +Brand#41|LARGE PLATED STEEL|23|8 +Brand#41|LARGE PLATED STEEL|36|8 +Brand#41|LARGE PLATED STEEL|49|8 +Brand#41|LARGE PLATED TIN|3|8 +Brand#41|LARGE POLISHED BRASS|19|8 +Brand#41|LARGE POLISHED COPPER|3|8 +Brand#41|LARGE POLISHED COPPER|19|8 +Brand#41|LARGE POLISHED COPPER|49|8 +Brand#41|LARGE POLISHED NICKEL|23|8 +Brand#41|LARGE POLISHED STEEL|14|8 +Brand#41|LARGE POLISHED TIN|9|8 +Brand#41|LARGE POLISHED TIN|14|8 +Brand#41|MEDIUM ANODIZED BRASS|3|8 +Brand#41|MEDIUM ANODIZED BRASS|9|8 +Brand#41|MEDIUM ANODIZED BRASS|36|8 +Brand#41|MEDIUM ANODIZED COPPER|23|8 +Brand#41|MEDIUM ANODIZED NICKEL|19|8 +Brand#41|MEDIUM ANODIZED NICKEL|36|8 +Brand#41|MEDIUM ANODIZED STEEL|23|8 +Brand#41|MEDIUM ANODIZED STEEL|45|8 +Brand#41|MEDIUM ANODIZED TIN|9|8 +Brand#41|MEDIUM ANODIZED TIN|19|8 +Brand#41|MEDIUM ANODIZED TIN|45|8 +Brand#41|MEDIUM BRUSHED BRASS|3|8 +Brand#41|MEDIUM BRUSHED BRASS|14|8 +Brand#41|MEDIUM BRUSHED BRASS|45|8 +Brand#41|MEDIUM BRUSHED COPPER|3|8 +Brand#41|MEDIUM BRUSHED COPPER|14|8 +Brand#41|MEDIUM BRUSHED STEEL|45|8 +Brand#41|MEDIUM BRUSHED STEEL|49|8 +Brand#41|MEDIUM BRUSHED TIN|9|8 +Brand#41|MEDIUM BRUSHED TIN|23|8 +Brand#41|MEDIUM BRUSHED TIN|49|8 +Brand#41|MEDIUM BURNISHED BRASS|36|8 +Brand#41|MEDIUM BURNISHED COPPER|9|8 +Brand#41|MEDIUM BURNISHED STEEL|3|8 +Brand#41|MEDIUM BURNISHED STEEL|45|8 +Brand#41|MEDIUM PLATED BRASS|45|8 +Brand#41|MEDIUM PLATED COPPER|9|8 +Brand#41|MEDIUM PLATED COPPER|49|8 +Brand#41|MEDIUM PLATED NICKEL|19|8 +Brand#41|MEDIUM PLATED NICKEL|45|8 +Brand#41|MEDIUM PLATED STEEL|9|8 +Brand#41|MEDIUM PLATED STEEL|23|8 +Brand#41|PROMO ANODIZED COPPER|14|8 +Brand#41|PROMO ANODIZED NICKEL|3|8 +Brand#41|PROMO ANODIZED NICKEL|19|8 +Brand#41|PROMO ANODIZED STEEL|9|8 +Brand#41|PROMO ANODIZED TIN|36|8 +Brand#41|PROMO BRUSHED BRASS|9|8 +Brand#41|PROMO BRUSHED BRASS|14|8 +Brand#41|PROMO BRUSHED BRASS|19|8 +Brand#41|PROMO BRUSHED BRASS|23|8 +Brand#41|PROMO BRUSHED BRASS|36|8 +Brand#41|PROMO BRUSHED COPPER|36|8 +Brand#41|PROMO BRUSHED STEEL|9|8 +Brand#41|PROMO BRUSHED STEEL|36|8 +Brand#41|PROMO BRUSHED STEEL|49|8 +Brand#41|PROMO BRUSHED TIN|9|8 +Brand#41|PROMO BRUSHED TIN|49|8 +Brand#41|PROMO BURNISHED BRASS|9|8 +Brand#41|PROMO BURNISHED BRASS|14|8 +Brand#41|PROMO BURNISHED COPPER|36|8 +Brand#41|PROMO BURNISHED COPPER|45|8 +Brand#41|PROMO BURNISHED NICKEL|36|8 +Brand#41|PROMO BURNISHED STEEL|14|8 +Brand#41|PROMO BURNISHED STEEL|36|8 +Brand#41|PROMO BURNISHED TIN|3|8 +Brand#41|PROMO BURNISHED TIN|23|8 +Brand#41|PROMO PLATED BRASS|14|8 +Brand#41|PROMO PLATED BRASS|36|8 +Brand#41|PROMO PLATED COPPER|14|8 +Brand#41|PROMO PLATED COPPER|23|8 +Brand#41|PROMO PLATED NICKEL|49|8 +Brand#41|PROMO PLATED STEEL|3|8 +Brand#41|PROMO PLATED STEEL|14|8 +Brand#41|PROMO PLATED TIN|45|8 +Brand#41|PROMO POLISHED BRASS|9|8 +Brand#41|PROMO POLISHED COPPER|3|8 +Brand#41|PROMO POLISHED COPPER|19|8 +Brand#41|PROMO POLISHED COPPER|49|8 +Brand#41|PROMO POLISHED NICKEL|3|8 +Brand#41|PROMO POLISHED STEEL|49|8 +Brand#41|PROMO POLISHED TIN|14|8 +Brand#41|PROMO POLISHED TIN|45|8 +Brand#41|SMALL ANODIZED BRASS|14|8 +Brand#41|SMALL ANODIZED BRASS|36|8 +Brand#41|SMALL ANODIZED COPPER|49|8 +Brand#41|SMALL ANODIZED NICKEL|14|8 +Brand#41|SMALL ANODIZED NICKEL|19|8 +Brand#41|SMALL ANODIZED TIN|3|8 +Brand#41|SMALL ANODIZED TIN|9|8 +Brand#41|SMALL ANODIZED TIN|23|8 +Brand#41|SMALL BRUSHED BRASS|9|8 +Brand#41|SMALL BRUSHED BRASS|23|8 +Brand#41|SMALL BRUSHED COPPER|45|8 +Brand#41|SMALL BRUSHED COPPER|49|8 +Brand#41|SMALL BRUSHED NICKEL|14|8 +Brand#41|SMALL BRUSHED NICKEL|36|8 +Brand#41|SMALL BRUSHED STEEL|19|8 +Brand#41|SMALL BRUSHED TIN|3|8 +Brand#41|SMALL BRUSHED TIN|19|8 +Brand#41|SMALL BURNISHED BRASS|14|8 +Brand#41|SMALL BURNISHED BRASS|19|8 +Brand#41|SMALL BURNISHED COPPER|9|8 +Brand#41|SMALL BURNISHED COPPER|19|8 +Brand#41|SMALL BURNISHED NICKEL|3|8 +Brand#41|SMALL BURNISHED NICKEL|19|8 +Brand#41|SMALL BURNISHED NICKEL|45|8 +Brand#41|SMALL BURNISHED STEEL|9|8 +Brand#41|SMALL BURNISHED STEEL|23|8 +Brand#41|SMALL BURNISHED STEEL|45|8 +Brand#41|SMALL BURNISHED STEEL|49|8 +Brand#41|SMALL BURNISHED TIN|14|8 +Brand#41|SMALL PLATED BRASS|3|8 +Brand#41|SMALL PLATED COPPER|9|8 +Brand#41|SMALL PLATED COPPER|14|8 +Brand#41|SMALL PLATED NICKEL|3|8 +Brand#41|SMALL PLATED NICKEL|36|8 +Brand#41|SMALL PLATED STEEL|9|8 +Brand#41|SMALL PLATED STEEL|36|8 +Brand#41|SMALL PLATED TIN|19|8 +Brand#41|SMALL PLATED TIN|49|8 +Brand#41|SMALL POLISHED BRASS|45|8 +Brand#41|SMALL POLISHED COPPER|3|8 +Brand#41|SMALL POLISHED COPPER|14|8 +Brand#41|SMALL POLISHED COPPER|23|8 +Brand#41|SMALL POLISHED NICKEL|3|8 +Brand#41|SMALL POLISHED STEEL|49|8 +Brand#41|SMALL POLISHED TIN|9|8 +Brand#41|SMALL POLISHED TIN|45|8 +Brand#41|STANDARD ANODIZED COPPER|3|8 +Brand#41|STANDARD ANODIZED COPPER|23|8 +Brand#41|STANDARD ANODIZED NICKEL|3|8 +Brand#41|STANDARD ANODIZED NICKEL|9|8 +Brand#41|STANDARD ANODIZED STEEL|45|8 +Brand#41|STANDARD ANODIZED STEEL|49|8 +Brand#41|STANDARD ANODIZED TIN|19|8 +Brand#41|STANDARD ANODIZED TIN|23|8 +Brand#41|STANDARD BRUSHED BRASS|9|8 +Brand#41|STANDARD BRUSHED NICKEL|3|8 +Brand#41|STANDARD BRUSHED NICKEL|9|8 +Brand#41|STANDARD BRUSHED STEEL|45|8 +Brand#41|STANDARD BRUSHED TIN|9|8 +Brand#41|STANDARD BRUSHED TIN|19|8 +Brand#41|STANDARD BRUSHED TIN|45|8 +Brand#41|STANDARD BRUSHED TIN|49|8 +Brand#41|STANDARD BURNISHED BRASS|14|8 +Brand#41|STANDARD BURNISHED BRASS|36|8 +Brand#41|STANDARD BURNISHED COPPER|9|8 +Brand#41|STANDARD BURNISHED COPPER|14|8 +Brand#41|STANDARD BURNISHED NICKEL|19|8 +Brand#41|STANDARD BURNISHED STEEL|3|8 +Brand#41|STANDARD BURNISHED STEEL|49|8 +Brand#41|STANDARD BURNISHED TIN|19|8 +Brand#41|STANDARD BURNISHED TIN|45|8 +Brand#41|STANDARD PLATED BRASS|19|8 +Brand#41|STANDARD PLATED NICKEL|14|8 +Brand#41|STANDARD PLATED NICKEL|19|8 +Brand#41|STANDARD PLATED NICKEL|49|8 +Brand#41|STANDARD PLATED STEEL|3|8 +Brand#41|STANDARD PLATED STEEL|19|8 +Brand#41|STANDARD PLATED STEEL|49|8 +Brand#41|STANDARD PLATED TIN|45|8 +Brand#41|STANDARD PLATED TIN|49|8 +Brand#41|STANDARD POLISHED BRASS|14|8 +Brand#41|STANDARD POLISHED BRASS|36|8 +Brand#41|STANDARD POLISHED COPPER|14|8 +Brand#41|STANDARD POLISHED NICKEL|36|8 +Brand#41|STANDARD POLISHED STEEL|3|8 +Brand#41|STANDARD POLISHED STEEL|36|8 +Brand#41|STANDARD POLISHED TIN|19|8 +Brand#41|STANDARD POLISHED TIN|45|8 +Brand#42|ECONOMY ANODIZED BRASS|9|8 +Brand#42|ECONOMY ANODIZED BRASS|19|8 +Brand#42|ECONOMY ANODIZED BRASS|23|8 +Brand#42|ECONOMY ANODIZED COPPER|23|8 +Brand#42|ECONOMY ANODIZED COPPER|49|8 +Brand#42|ECONOMY ANODIZED NICKEL|19|8 +Brand#42|ECONOMY ANODIZED NICKEL|36|8 +Brand#42|ECONOMY ANODIZED STEEL|49|8 +Brand#42|ECONOMY BRUSHED COPPER|3|8 +Brand#42|ECONOMY BRUSHED NICKEL|14|8 +Brand#42|ECONOMY BRUSHED STEEL|23|8 +Brand#42|ECONOMY BRUSHED STEEL|49|8 +Brand#42|ECONOMY BRUSHED TIN|9|8 +Brand#42|ECONOMY BRUSHED TIN|19|8 +Brand#42|ECONOMY BRUSHED TIN|49|8 +Brand#42|ECONOMY BURNISHED COPPER|3|8 +Brand#42|ECONOMY BURNISHED COPPER|49|8 +Brand#42|ECONOMY BURNISHED NICKEL|3|8 +Brand#42|ECONOMY BURNISHED TIN|14|8 +Brand#42|ECONOMY BURNISHED TIN|45|8 +Brand#42|ECONOMY PLATED BRASS|9|8 +Brand#42|ECONOMY PLATED COPPER|23|8 +Brand#42|ECONOMY PLATED COPPER|36|8 +Brand#42|ECONOMY PLATED NICKEL|19|8 +Brand#42|ECONOMY PLATED NICKEL|49|8 +Brand#42|ECONOMY PLATED STEEL|49|8 +Brand#42|ECONOMY PLATED TIN|3|8 +Brand#42|ECONOMY POLISHED BRASS|9|8 +Brand#42|ECONOMY POLISHED NICKEL|49|8 +Brand#42|ECONOMY POLISHED STEEL|9|8 +Brand#42|ECONOMY POLISHED STEEL|36|8 +Brand#42|ECONOMY POLISHED TIN|36|8 +Brand#42|LARGE ANODIZED BRASS|3|8 +Brand#42|LARGE ANODIZED BRASS|23|8 +Brand#42|LARGE ANODIZED COPPER|3|8 +Brand#42|LARGE ANODIZED COPPER|14|8 +Brand#42|LARGE ANODIZED COPPER|49|8 +Brand#42|LARGE ANODIZED NICKEL|9|8 +Brand#42|LARGE ANODIZED NICKEL|45|8 +Brand#42|LARGE ANODIZED NICKEL|49|8 +Brand#42|LARGE ANODIZED STEEL|3|8 +Brand#42|LARGE ANODIZED STEEL|9|8 +Brand#42|LARGE ANODIZED TIN|14|8 +Brand#42|LARGE ANODIZED TIN|45|8 +Brand#42|LARGE BRUSHED BRASS|49|8 +Brand#42|LARGE BRUSHED COPPER|9|8 +Brand#42|LARGE BRUSHED NICKEL|19|8 +Brand#42|LARGE BRUSHED NICKEL|36|8 +Brand#42|LARGE BRUSHED NICKEL|49|8 +Brand#42|LARGE BRUSHED TIN|23|8 +Brand#42|LARGE BRUSHED TIN|49|8 +Brand#42|LARGE BURNISHED BRASS|3|8 +Brand#42|LARGE BURNISHED BRASS|49|8 +Brand#42|LARGE BURNISHED TIN|45|8 +Brand#42|LARGE PLATED COPPER|9|8 +Brand#42|LARGE PLATED COPPER|45|8 +Brand#42|LARGE PLATED NICKEL|45|8 +Brand#42|LARGE PLATED TIN|3|8 +Brand#42|LARGE PLATED TIN|45|8 +Brand#42|LARGE POLISHED COPPER|49|8 +Brand#42|LARGE POLISHED NICKEL|23|8 +Brand#42|LARGE POLISHED NICKEL|36|8 +Brand#42|LARGE POLISHED STEEL|3|8 +Brand#42|LARGE POLISHED TIN|3|8 +Brand#42|LARGE POLISHED TIN|19|8 +Brand#42|LARGE POLISHED TIN|45|8 +Brand#42|MEDIUM ANODIZED BRASS|9|8 +Brand#42|MEDIUM ANODIZED BRASS|49|8 +Brand#42|MEDIUM ANODIZED COPPER|3|8 +Brand#42|MEDIUM ANODIZED COPPER|19|8 +Brand#42|MEDIUM ANODIZED COPPER|49|8 +Brand#42|MEDIUM ANODIZED NICKEL|36|8 +Brand#42|MEDIUM ANODIZED STEEL|3|8 +Brand#42|MEDIUM ANODIZED TIN|14|8 +Brand#42|MEDIUM ANODIZED TIN|36|8 +Brand#42|MEDIUM ANODIZED TIN|45|8 +Brand#42|MEDIUM BRUSHED COPPER|14|8 +Brand#42|MEDIUM BRUSHED COPPER|49|8 +Brand#42|MEDIUM BRUSHED NICKEL|14|8 +Brand#42|MEDIUM BRUSHED STEEL|36|8 +Brand#42|MEDIUM BRUSHED STEEL|49|8 +Brand#42|MEDIUM BURNISHED BRASS|45|8 +Brand#42|MEDIUM BURNISHED COPPER|3|8 +Brand#42|MEDIUM BURNISHED NICKEL|14|8 +Brand#42|MEDIUM BURNISHED STEEL|9|8 +Brand#42|MEDIUM BURNISHED STEEL|14|8 +Brand#42|MEDIUM BURNISHED STEEL|36|8 +Brand#42|MEDIUM BURNISHED TIN|3|8 +Brand#42|MEDIUM PLATED BRASS|49|8 +Brand#42|MEDIUM PLATED COPPER|3|8 +Brand#42|MEDIUM PLATED COPPER|49|8 +Brand#42|MEDIUM PLATED NICKEL|9|8 +Brand#42|MEDIUM PLATED STEEL|9|8 +Brand#42|MEDIUM PLATED STEEL|14|8 +Brand#42|MEDIUM PLATED STEEL|36|8 +Brand#42|MEDIUM PLATED TIN|9|8 +Brand#42|MEDIUM PLATED TIN|14|8 +Brand#42|PROMO ANODIZED BRASS|9|8 +Brand#42|PROMO ANODIZED BRASS|36|8 +Brand#42|PROMO ANODIZED BRASS|45|8 +Brand#42|PROMO ANODIZED COPPER|3|8 +Brand#42|PROMO ANODIZED COPPER|23|8 +Brand#42|PROMO ANODIZED COPPER|45|8 +Brand#42|PROMO ANODIZED NICKEL|9|8 +Brand#42|PROMO ANODIZED TIN|3|8 +Brand#42|PROMO BRUSHED COPPER|14|8 +Brand#42|PROMO BRUSHED STEEL|19|8 +Brand#42|PROMO BRUSHED STEEL|23|8 +Brand#42|PROMO BRUSHED STEEL|45|8 +Brand#42|PROMO BURNISHED BRASS|14|8 +Brand#42|PROMO BURNISHED BRASS|45|8 +Brand#42|PROMO BURNISHED BRASS|49|8 +Brand#42|PROMO BURNISHED COPPER|45|8 +Brand#42|PROMO BURNISHED NICKEL|36|8 +Brand#42|PROMO PLATED NICKEL|23|8 +Brand#42|PROMO PLATED STEEL|45|8 +Brand#42|PROMO PLATED TIN|9|8 +Brand#42|PROMO PLATED TIN|19|8 +Brand#42|PROMO PLATED TIN|23|8 +Brand#42|PROMO PLATED TIN|36|8 +Brand#42|PROMO PLATED TIN|45|8 +Brand#42|PROMO POLISHED BRASS|19|8 +Brand#42|PROMO POLISHED BRASS|23|8 +Brand#42|PROMO POLISHED BRASS|45|8 +Brand#42|PROMO POLISHED COPPER|36|8 +Brand#42|PROMO POLISHED NICKEL|3|8 +Brand#42|PROMO POLISHED NICKEL|9|8 +Brand#42|PROMO POLISHED STEEL|9|8 +Brand#42|PROMO POLISHED STEEL|23|8 +Brand#42|PROMO POLISHED TIN|3|8 +Brand#42|PROMO POLISHED TIN|9|8 +Brand#42|SMALL ANODIZED BRASS|19|8 +Brand#42|SMALL ANODIZED COPPER|14|8 +Brand#42|SMALL ANODIZED COPPER|19|8 +Brand#42|SMALL ANODIZED COPPER|36|8 +Brand#42|SMALL ANODIZED NICKEL|14|8 +Brand#42|SMALL ANODIZED NICKEL|23|8 +Brand#42|SMALL ANODIZED NICKEL|45|8 +Brand#42|SMALL ANODIZED STEEL|3|8 +Brand#42|SMALL ANODIZED STEEL|9|8 +Brand#42|SMALL ANODIZED STEEL|36|8 +Brand#42|SMALL ANODIZED TIN|3|8 +Brand#42|SMALL ANODIZED TIN|19|8 +Brand#42|SMALL BRUSHED COPPER|9|8 +Brand#42|SMALL BRUSHED COPPER|36|8 +Brand#42|SMALL BRUSHED NICKEL|23|8 +Brand#42|SMALL BRUSHED STEEL|3|8 +Brand#42|SMALL BRUSHED STEEL|9|8 +Brand#42|SMALL BRUSHED STEEL|14|8 +Brand#42|SMALL BRUSHED STEEL|36|8 +Brand#42|SMALL BRUSHED STEEL|45|8 +Brand#42|SMALL BRUSHED TIN|9|8 +Brand#42|SMALL BRUSHED TIN|14|8 +Brand#42|SMALL BRUSHED TIN|45|8 +Brand#42|SMALL BRUSHED TIN|49|8 +Brand#42|SMALL BURNISHED BRASS|23|8 +Brand#42|SMALL BURNISHED NICKEL|19|8 +Brand#42|SMALL BURNISHED STEEL|14|8 +Brand#42|SMALL PLATED BRASS|19|8 +Brand#42|SMALL PLATED COPPER|36|8 +Brand#42|SMALL PLATED STEEL|3|8 +Brand#42|SMALL PLATED STEEL|23|8 +Brand#42|SMALL PLATED STEEL|36|8 +Brand#42|SMALL PLATED TIN|14|8 +Brand#42|SMALL PLATED TIN|19|8 +Brand#42|SMALL PLATED TIN|36|8 +Brand#42|SMALL POLISHED BRASS|23|8 +Brand#42|SMALL POLISHED BRASS|45|8 +Brand#42|SMALL POLISHED COPPER|23|8 +Brand#42|SMALL POLISHED COPPER|45|8 +Brand#42|SMALL POLISHED NICKEL|14|8 +Brand#42|SMALL POLISHED NICKEL|19|8 +Brand#42|SMALL POLISHED NICKEL|45|8 +Brand#42|SMALL POLISHED STEEL|49|8 +Brand#42|SMALL POLISHED TIN|14|8 +Brand#42|SMALL POLISHED TIN|36|8 +Brand#42|SMALL POLISHED TIN|49|8 +Brand#42|STANDARD ANODIZED BRASS|36|8 +Brand#42|STANDARD ANODIZED COPPER|14|8 +Brand#42|STANDARD ANODIZED STEEL|3|8 +Brand#42|STANDARD ANODIZED STEEL|9|8 +Brand#42|STANDARD ANODIZED STEEL|45|8 +Brand#42|STANDARD ANODIZED TIN|3|8 +Brand#42|STANDARD BRUSHED BRASS|3|8 +Brand#42|STANDARD BRUSHED BRASS|9|8 +Brand#42|STANDARD BRUSHED BRASS|23|8 +Brand#42|STANDARD BRUSHED COPPER|36|8 +Brand#42|STANDARD BRUSHED COPPER|49|8 +Brand#42|STANDARD BRUSHED NICKEL|23|8 +Brand#42|STANDARD BRUSHED NICKEL|49|8 +Brand#42|STANDARD BRUSHED STEEL|23|8 +Brand#42|STANDARD BRUSHED TIN|49|8 +Brand#42|STANDARD BURNISHED BRASS|9|8 +Brand#42|STANDARD BURNISHED BRASS|14|8 +Brand#42|STANDARD BURNISHED BRASS|49|8 +Brand#42|STANDARD BURNISHED NICKEL|14|8 +Brand#42|STANDARD BURNISHED NICKEL|49|8 +Brand#42|STANDARD BURNISHED STEEL|36|8 +Brand#42|STANDARD BURNISHED TIN|9|8 +Brand#42|STANDARD PLATED COPPER|49|8 +Brand#42|STANDARD PLATED NICKEL|14|8 +Brand#42|STANDARD PLATED NICKEL|45|8 +Brand#42|STANDARD PLATED STEEL|14|8 +Brand#42|STANDARD PLATED STEEL|19|8 +Brand#42|STANDARD PLATED STEEL|36|8 +Brand#42|STANDARD PLATED STEEL|45|8 +Brand#42|STANDARD PLATED TIN|9|8 +Brand#42|STANDARD PLATED TIN|14|8 +Brand#42|STANDARD POLISHED BRASS|19|8 +Brand#42|STANDARD POLISHED BRASS|36|8 +Brand#42|STANDARD POLISHED COPPER|14|8 +Brand#42|STANDARD POLISHED COPPER|19|8 +Brand#42|STANDARD POLISHED COPPER|49|8 +Brand#42|STANDARD POLISHED NICKEL|14|8 +Brand#42|STANDARD POLISHED NICKEL|23|8 +Brand#42|STANDARD POLISHED STEEL|23|8 +Brand#42|STANDARD POLISHED TIN|14|8 +Brand#42|STANDARD POLISHED TIN|23|8 +Brand#42|STANDARD POLISHED TIN|36|8 +Brand#43|ECONOMY ANODIZED BRASS|3|8 +Brand#43|ECONOMY ANODIZED NICKEL|3|8 +Brand#43|ECONOMY ANODIZED NICKEL|49|8 +Brand#43|ECONOMY ANODIZED STEEL|23|8 +Brand#43|ECONOMY ANODIZED STEEL|36|8 +Brand#43|ECONOMY ANODIZED TIN|49|8 +Brand#43|ECONOMY BRUSHED COPPER|45|8 +Brand#43|ECONOMY BRUSHED NICKEL|9|8 +Brand#43|ECONOMY BRUSHED NICKEL|14|8 +Brand#43|ECONOMY BRUSHED NICKEL|19|8 +Brand#43|ECONOMY BRUSHED NICKEL|49|8 +Brand#43|ECONOMY BRUSHED TIN|36|8 +Brand#43|ECONOMY BRUSHED TIN|45|8 +Brand#43|ECONOMY BURNISHED BRASS|19|8 +Brand#43|ECONOMY BURNISHED COPPER|14|8 +Brand#43|ECONOMY BURNISHED COPPER|36|8 +Brand#43|ECONOMY BURNISHED NICKEL|9|8 +Brand#43|ECONOMY BURNISHED NICKEL|14|8 +Brand#43|ECONOMY BURNISHED NICKEL|23|8 +Brand#43|ECONOMY BURNISHED NICKEL|45|8 +Brand#43|ECONOMY BURNISHED STEEL|3|8 +Brand#43|ECONOMY BURNISHED STEEL|36|8 +Brand#43|ECONOMY BURNISHED TIN|3|8 +Brand#43|ECONOMY BURNISHED TIN|49|8 +Brand#43|ECONOMY PLATED COPPER|19|8 +Brand#43|ECONOMY PLATED NICKEL|9|8 +Brand#43|ECONOMY PLATED STEEL|19|8 +Brand#43|ECONOMY PLATED TIN|9|8 +Brand#43|ECONOMY PLATED TIN|19|8 +Brand#43|ECONOMY POLISHED BRASS|19|8 +Brand#43|ECONOMY POLISHED COPPER|19|8 +Brand#43|ECONOMY POLISHED COPPER|36|8 +Brand#43|ECONOMY POLISHED NICKEL|19|8 +Brand#43|ECONOMY POLISHED NICKEL|36|8 +Brand#43|ECONOMY POLISHED STEEL|3|8 +Brand#43|ECONOMY POLISHED TIN|9|8 +Brand#43|ECONOMY POLISHED TIN|36|8 +Brand#43|ECONOMY POLISHED TIN|45|8 +Brand#43|LARGE ANODIZED BRASS|14|8 +Brand#43|LARGE ANODIZED BRASS|36|8 +Brand#43|LARGE ANODIZED COPPER|19|8 +Brand#43|LARGE ANODIZED NICKEL|3|8 +Brand#43|LARGE ANODIZED NICKEL|23|8 +Brand#43|LARGE ANODIZED NICKEL|36|8 +Brand#43|LARGE ANODIZED STEEL|23|8 +Brand#43|LARGE ANODIZED STEEL|49|8 +Brand#43|LARGE ANODIZED TIN|19|8 +Brand#43|LARGE BRUSHED BRASS|23|8 +Brand#43|LARGE BRUSHED COPPER|19|8 +Brand#43|LARGE BRUSHED COPPER|36|8 +Brand#43|LARGE BRUSHED NICKEL|14|8 +Brand#43|LARGE BRUSHED NICKEL|19|8 +Brand#43|LARGE BRUSHED NICKEL|36|8 +Brand#43|LARGE BRUSHED NICKEL|49|8 +Brand#43|LARGE BRUSHED STEEL|3|8 +Brand#43|LARGE BRUSHED TIN|23|8 +Brand#43|LARGE BURNISHED BRASS|9|8 +Brand#43|LARGE BURNISHED BRASS|14|8 +Brand#43|LARGE BURNISHED BRASS|49|8 +Brand#43|LARGE BURNISHED COPPER|3|8 +Brand#43|LARGE BURNISHED NICKEL|36|8 +Brand#43|LARGE BURNISHED TIN|23|8 +Brand#43|LARGE PLATED BRASS|9|8 +Brand#43|LARGE PLATED BRASS|45|8 +Brand#43|LARGE PLATED COPPER|36|8 +Brand#43|LARGE PLATED NICKEL|3|8 +Brand#43|LARGE PLATED NICKEL|14|8 +Brand#43|LARGE PLATED NICKEL|49|8 +Brand#43|LARGE PLATED STEEL|3|8 +Brand#43|LARGE PLATED STEEL|14|8 +Brand#43|LARGE PLATED STEEL|49|8 +Brand#43|LARGE PLATED TIN|23|8 +Brand#43|LARGE PLATED TIN|36|8 +Brand#43|LARGE PLATED TIN|45|8 +Brand#43|LARGE POLISHED BRASS|36|8 +Brand#43|LARGE POLISHED COPPER|3|8 +Brand#43|LARGE POLISHED COPPER|14|8 +Brand#43|LARGE POLISHED COPPER|36|8 +Brand#43|LARGE POLISHED NICKEL|3|8 +Brand#43|LARGE POLISHED STEEL|9|8 +Brand#43|LARGE POLISHED STEEL|14|8 +Brand#43|LARGE POLISHED STEEL|19|8 +Brand#43|MEDIUM ANODIZED BRASS|49|8 +Brand#43|MEDIUM ANODIZED COPPER|19|8 +Brand#43|MEDIUM ANODIZED COPPER|23|8 +Brand#43|MEDIUM ANODIZED NICKEL|3|8 +Brand#43|MEDIUM ANODIZED STEEL|9|8 +Brand#43|MEDIUM ANODIZED STEEL|19|8 +Brand#43|MEDIUM ANODIZED STEEL|36|8 +Brand#43|MEDIUM BRUSHED BRASS|9|8 +Brand#43|MEDIUM BRUSHED BRASS|14|8 +Brand#43|MEDIUM BRUSHED COPPER|45|8 +Brand#43|MEDIUM BRUSHED STEEL|19|8 +Brand#43|MEDIUM BRUSHED STEEL|49|8 +Brand#43|MEDIUM BRUSHED TIN|49|8 +Brand#43|MEDIUM BURNISHED BRASS|19|8 +Brand#43|MEDIUM BURNISHED NICKEL|19|8 +Brand#43|MEDIUM BURNISHED NICKEL|36|8 +Brand#43|MEDIUM BURNISHED STEEL|23|8 +Brand#43|MEDIUM BURNISHED TIN|14|8 +Brand#43|MEDIUM BURNISHED TIN|36|8 +Brand#43|MEDIUM PLATED BRASS|19|8 +Brand#43|MEDIUM PLATED BRASS|36|8 +Brand#43|MEDIUM PLATED COPPER|3|8 +Brand#43|MEDIUM PLATED COPPER|49|8 +Brand#43|MEDIUM PLATED NICKEL|36|8 +Brand#43|MEDIUM PLATED NICKEL|45|8 +Brand#43|MEDIUM PLATED TIN|45|8 +Brand#43|PROMO ANODIZED BRASS|3|8 +Brand#43|PROMO ANODIZED BRASS|9|8 +Brand#43|PROMO ANODIZED BRASS|45|8 +Brand#43|PROMO ANODIZED NICKEL|14|8 +Brand#43|PROMO ANODIZED NICKEL|45|8 +Brand#43|PROMO ANODIZED STEEL|49|8 +Brand#43|PROMO ANODIZED TIN|3|8 +Brand#43|PROMO ANODIZED TIN|14|8 +Brand#43|PROMO ANODIZED TIN|19|8 +Brand#43|PROMO ANODIZED TIN|49|8 +Brand#43|PROMO BRUSHED BRASS|3|8 +Brand#43|PROMO BRUSHED BRASS|45|8 +Brand#43|PROMO BRUSHED COPPER|23|8 +Brand#43|PROMO BRUSHED NICKEL|14|8 +Brand#43|PROMO BRUSHED NICKEL|19|8 +Brand#43|PROMO BRUSHED STEEL|14|8 +Brand#43|PROMO BURNISHED BRASS|3|8 +Brand#43|PROMO BURNISHED BRASS|49|8 +Brand#43|PROMO BURNISHED COPPER|14|8 +Brand#43|PROMO BURNISHED NICKEL|49|8 +Brand#43|PROMO BURNISHED STEEL|49|8 +Brand#43|PROMO BURNISHED TIN|9|8 +Brand#43|PROMO BURNISHED TIN|36|8 +Brand#43|PROMO BURNISHED TIN|49|8 +Brand#43|PROMO PLATED BRASS|14|8 +Brand#43|PROMO PLATED COPPER|45|8 +Brand#43|PROMO PLATED NICKEL|45|8 +Brand#43|PROMO PLATED STEEL|45|8 +Brand#43|PROMO PLATED TIN|23|8 +Brand#43|PROMO POLISHED BRASS|23|8 +Brand#43|PROMO POLISHED COPPER|3|8 +Brand#43|PROMO POLISHED COPPER|14|8 +Brand#43|PROMO POLISHED COPPER|36|8 +Brand#43|PROMO POLISHED NICKEL|14|8 +Brand#43|PROMO POLISHED NICKEL|19|8 +Brand#43|PROMO POLISHED STEEL|14|8 +Brand#43|PROMO POLISHED STEEL|23|8 +Brand#43|PROMO POLISHED TIN|3|8 +Brand#43|PROMO POLISHED TIN|36|8 +Brand#43|SMALL ANODIZED BRASS|19|8 +Brand#43|SMALL ANODIZED COPPER|14|8 +Brand#43|SMALL ANODIZED COPPER|19|8 +Brand#43|SMALL ANODIZED COPPER|49|8 +Brand#43|SMALL ANODIZED NICKEL|14|8 +Brand#43|SMALL ANODIZED NICKEL|45|8 +Brand#43|SMALL ANODIZED STEEL|49|8 +Brand#43|SMALL ANODIZED TIN|49|8 +Brand#43|SMALL BRUSHED COPPER|19|8 +Brand#43|SMALL BRUSHED COPPER|49|8 +Brand#43|SMALL BRUSHED NICKEL|9|8 +Brand#43|SMALL BRUSHED NICKEL|49|8 +Brand#43|SMALL BRUSHED STEEL|45|8 +Brand#43|SMALL BRUSHED TIN|3|8 +Brand#43|SMALL BURNISHED COPPER|23|8 +Brand#43|SMALL BURNISHED STEEL|9|8 +Brand#43|SMALL BURNISHED STEEL|45|8 +Brand#43|SMALL BURNISHED TIN|9|8 +Brand#43|SMALL BURNISHED TIN|49|8 +Brand#43|SMALL PLATED BRASS|23|8 +Brand#43|SMALL PLATED BRASS|45|8 +Brand#43|SMALL PLATED COPPER|45|8 +Brand#43|SMALL PLATED NICKEL|3|8 +Brand#43|SMALL PLATED NICKEL|19|8 +Brand#43|SMALL PLATED NICKEL|23|8 +Brand#43|SMALL PLATED NICKEL|45|8 +Brand#43|SMALL PLATED NICKEL|49|8 +Brand#43|SMALL PLATED STEEL|14|8 +Brand#43|SMALL PLATED STEEL|36|8 +Brand#43|SMALL PLATED TIN|14|8 +Brand#43|SMALL POLISHED BRASS|9|8 +Brand#43|SMALL POLISHED BRASS|19|8 +Brand#43|SMALL POLISHED COPPER|9|8 +Brand#43|SMALL POLISHED COPPER|19|8 +Brand#43|SMALL POLISHED NICKEL|3|8 +Brand#43|SMALL POLISHED NICKEL|36|8 +Brand#43|SMALL POLISHED STEEL|45|8 +Brand#43|SMALL POLISHED STEEL|49|8 +Brand#43|SMALL POLISHED TIN|36|8 +Brand#43|STANDARD ANODIZED COPPER|3|8 +Brand#43|STANDARD ANODIZED COPPER|9|8 +Brand#43|STANDARD ANODIZED COPPER|14|8 +Brand#43|STANDARD ANODIZED COPPER|49|8 +Brand#43|STANDARD ANODIZED NICKEL|49|8 +Brand#43|STANDARD ANODIZED STEEL|3|8 +Brand#43|STANDARD ANODIZED STEEL|14|8 +Brand#43|STANDARD ANODIZED STEEL|45|8 +Brand#43|STANDARD ANODIZED STEEL|49|8 +Brand#43|STANDARD ANODIZED TIN|14|8 +Brand#43|STANDARD BRUSHED BRASS|14|8 +Brand#43|STANDARD BRUSHED BRASS|36|8 +Brand#43|STANDARD BRUSHED NICKEL|49|8 +Brand#43|STANDARD BRUSHED TIN|19|8 +Brand#43|STANDARD BRUSHED TIN|45|8 +Brand#43|STANDARD BRUSHED TIN|49|8 +Brand#43|STANDARD BURNISHED BRASS|23|8 +Brand#43|STANDARD BURNISHED BRASS|49|8 +Brand#43|STANDARD BURNISHED COPPER|9|8 +Brand#43|STANDARD BURNISHED COPPER|14|8 +Brand#43|STANDARD BURNISHED COPPER|45|8 +Brand#43|STANDARD BURNISHED NICKEL|19|8 +Brand#43|STANDARD BURNISHED NICKEL|49|8 +Brand#43|STANDARD BURNISHED STEEL|9|8 +Brand#43|STANDARD BURNISHED STEEL|19|8 +Brand#43|STANDARD BURNISHED STEEL|45|8 +Brand#43|STANDARD BURNISHED TIN|19|8 +Brand#43|STANDARD PLATED COPPER|36|8 +Brand#43|STANDARD PLATED NICKEL|19|8 +Brand#43|STANDARD PLATED NICKEL|49|8 +Brand#43|STANDARD PLATED TIN|14|8 +Brand#43|STANDARD PLATED TIN|23|8 +Brand#43|STANDARD POLISHED BRASS|19|8 +Brand#43|STANDARD POLISHED COPPER|45|8 +Brand#43|STANDARD POLISHED NICKEL|3|8 +Brand#43|STANDARD POLISHED NICKEL|14|8 +Brand#43|STANDARD POLISHED NICKEL|23|8 +Brand#43|STANDARD POLISHED NICKEL|36|8 +Brand#43|STANDARD POLISHED NICKEL|45|8 +Brand#43|STANDARD POLISHED STEEL|14|8 +Brand#43|STANDARD POLISHED STEEL|49|8 +Brand#44|ECONOMY ANODIZED BRASS|3|8 +Brand#44|ECONOMY ANODIZED COPPER|23|8 +Brand#44|ECONOMY ANODIZED COPPER|49|8 +Brand#44|ECONOMY ANODIZED NICKEL|23|8 +Brand#44|ECONOMY ANODIZED STEEL|19|8 +Brand#44|ECONOMY ANODIZED STEEL|45|8 +Brand#44|ECONOMY ANODIZED TIN|14|8 +Brand#44|ECONOMY ANODIZED TIN|36|8 +Brand#44|ECONOMY BRUSHED COPPER|23|8 +Brand#44|ECONOMY BRUSHED STEEL|9|8 +Brand#44|ECONOMY BRUSHED STEEL|19|8 +Brand#44|ECONOMY BRUSHED TIN|19|8 +Brand#44|ECONOMY BRUSHED TIN|49|8 +Brand#44|ECONOMY BURNISHED COPPER|3|8 +Brand#44|ECONOMY BURNISHED COPPER|9|8 +Brand#44|ECONOMY BURNISHED COPPER|14|8 +Brand#44|ECONOMY BURNISHED COPPER|23|8 +Brand#44|ECONOMY BURNISHED COPPER|49|8 +Brand#44|ECONOMY BURNISHED NICKEL|23|8 +Brand#44|ECONOMY BURNISHED NICKEL|49|8 +Brand#44|ECONOMY BURNISHED STEEL|9|8 +Brand#44|ECONOMY BURNISHED STEEL|19|8 +Brand#44|ECONOMY BURNISHED STEEL|49|8 +Brand#44|ECONOMY BURNISHED TIN|3|8 +Brand#44|ECONOMY BURNISHED TIN|19|8 +Brand#44|ECONOMY BURNISHED TIN|45|8 +Brand#44|ECONOMY PLATED COPPER|45|8 +Brand#44|ECONOMY PLATED NICKEL|23|8 +Brand#44|ECONOMY PLATED STEEL|14|8 +Brand#44|ECONOMY PLATED STEEL|23|8 +Brand#44|ECONOMY PLATED STEEL|36|8 +Brand#44|ECONOMY PLATED TIN|19|8 +Brand#44|ECONOMY POLISHED BRASS|23|8 +Brand#44|ECONOMY POLISHED BRASS|36|8 +Brand#44|ECONOMY POLISHED COPPER|9|8 +Brand#44|ECONOMY POLISHED COPPER|19|8 +Brand#44|ECONOMY POLISHED NICKEL|23|8 +Brand#44|ECONOMY POLISHED NICKEL|36|8 +Brand#44|ECONOMY POLISHED NICKEL|45|8 +Brand#44|ECONOMY POLISHED NICKEL|49|8 +Brand#44|ECONOMY POLISHED STEEL|9|8 +Brand#44|ECONOMY POLISHED STEEL|49|8 +Brand#44|ECONOMY POLISHED TIN|3|8 +Brand#44|ECONOMY POLISHED TIN|19|8 +Brand#44|LARGE ANODIZED BRASS|3|8 +Brand#44|LARGE ANODIZED BRASS|23|8 +Brand#44|LARGE ANODIZED BRASS|49|8 +Brand#44|LARGE ANODIZED COPPER|9|8 +Brand#44|LARGE ANODIZED COPPER|45|8 +Brand#44|LARGE ANODIZED NICKEL|49|8 +Brand#44|LARGE ANODIZED STEEL|19|8 +Brand#44|LARGE ANODIZED TIN|14|8 +Brand#44|LARGE BRUSHED BRASS|14|8 +Brand#44|LARGE BRUSHED COPPER|14|8 +Brand#44|LARGE BRUSHED NICKEL|19|8 +Brand#44|LARGE BRUSHED NICKEL|23|8 +Brand#44|LARGE BRUSHED NICKEL|45|8 +Brand#44|LARGE BRUSHED TIN|23|8 +Brand#44|LARGE BURNISHED COPPER|9|8 +Brand#44|LARGE BURNISHED COPPER|19|8 +Brand#44|LARGE BURNISHED COPPER|23|8 +Brand#44|LARGE BURNISHED NICKEL|36|8 +Brand#44|LARGE BURNISHED NICKEL|49|8 +Brand#44|LARGE BURNISHED STEEL|23|8 +Brand#44|LARGE BURNISHED STEEL|49|8 +Brand#44|LARGE BURNISHED TIN|14|8 +Brand#44|LARGE PLATED BRASS|19|8 +Brand#44|LARGE PLATED COPPER|14|8 +Brand#44|LARGE PLATED COPPER|19|8 +Brand#44|LARGE PLATED NICKEL|9|8 +Brand#44|LARGE PLATED NICKEL|23|8 +Brand#44|LARGE PLATED STEEL|23|8 +Brand#44|LARGE PLATED TIN|14|8 +Brand#44|LARGE PLATED TIN|19|8 +Brand#44|LARGE PLATED TIN|36|8 +Brand#44|LARGE PLATED TIN|49|8 +Brand#44|LARGE POLISHED BRASS|9|8 +Brand#44|LARGE POLISHED BRASS|19|8 +Brand#44|LARGE POLISHED BRASS|23|8 +Brand#44|LARGE POLISHED COPPER|9|8 +Brand#44|LARGE POLISHED COPPER|49|8 +Brand#44|LARGE POLISHED NICKEL|23|8 +Brand#44|LARGE POLISHED NICKEL|36|8 +Brand#44|LARGE POLISHED STEEL|45|8 +Brand#44|LARGE POLISHED TIN|9|8 +Brand#44|MEDIUM ANODIZED BRASS|36|8 +Brand#44|MEDIUM ANODIZED COPPER|14|8 +Brand#44|MEDIUM ANODIZED COPPER|49|8 +Brand#44|MEDIUM ANODIZED NICKEL|19|8 +Brand#44|MEDIUM ANODIZED NICKEL|45|8 +Brand#44|MEDIUM ANODIZED STEEL|9|8 +Brand#44|MEDIUM ANODIZED STEEL|23|8 +Brand#44|MEDIUM ANODIZED TIN|45|8 +Brand#44|MEDIUM BRUSHED COPPER|14|8 +Brand#44|MEDIUM BRUSHED NICKEL|14|8 +Brand#44|MEDIUM BRUSHED STEEL|14|8 +Brand#44|MEDIUM BRUSHED STEEL|19|8 +Brand#44|MEDIUM BURNISHED BRASS|3|8 +Brand#44|MEDIUM BURNISHED BRASS|45|8 +Brand#44|MEDIUM BURNISHED COPPER|45|8 +Brand#44|MEDIUM BURNISHED NICKEL|3|8 +Brand#44|MEDIUM BURNISHED NICKEL|14|8 +Brand#44|MEDIUM BURNISHED STEEL|23|8 +Brand#44|MEDIUM BURNISHED TIN|19|8 +Brand#44|MEDIUM BURNISHED TIN|23|8 +Brand#44|MEDIUM PLATED BRASS|3|8 +Brand#44|MEDIUM PLATED BRASS|23|8 +Brand#44|MEDIUM PLATED COPPER|3|8 +Brand#44|MEDIUM PLATED NICKEL|23|8 +Brand#44|MEDIUM PLATED NICKEL|49|8 +Brand#44|PROMO ANODIZED BRASS|3|8 +Brand#44|PROMO ANODIZED BRASS|14|8 +Brand#44|PROMO ANODIZED BRASS|49|8 +Brand#44|PROMO ANODIZED COPPER|23|8 +Brand#44|PROMO ANODIZED NICKEL|23|8 +Brand#44|PROMO ANODIZED NICKEL|36|8 +Brand#44|PROMO ANODIZED STEEL|9|8 +Brand#44|PROMO ANODIZED STEEL|49|8 +Brand#44|PROMO BRUSHED BRASS|9|8 +Brand#44|PROMO BRUSHED COPPER|9|8 +Brand#44|PROMO BRUSHED COPPER|23|8 +Brand#44|PROMO BRUSHED COPPER|36|8 +Brand#44|PROMO BRUSHED NICKEL|23|8 +Brand#44|PROMO BRUSHED NICKEL|45|8 +Brand#44|PROMO BRUSHED STEEL|3|8 +Brand#44|PROMO BRUSHED STEEL|9|8 +Brand#44|PROMO BRUSHED STEEL|45|8 +Brand#44|PROMO BRUSHED STEEL|49|8 +Brand#44|PROMO BRUSHED TIN|3|8 +Brand#44|PROMO BRUSHED TIN|19|8 +Brand#44|PROMO BRUSHED TIN|45|8 +Brand#44|PROMO BURNISHED BRASS|36|8 +Brand#44|PROMO BURNISHED NICKEL|3|8 +Brand#44|PROMO BURNISHED STEEL|9|8 +Brand#44|PROMO BURNISHED STEEL|19|8 +Brand#44|PROMO BURNISHED STEEL|49|8 +Brand#44|PROMO PLATED BRASS|23|8 +Brand#44|PROMO PLATED NICKEL|9|8 +Brand#44|PROMO PLATED NICKEL|23|8 +Brand#44|PROMO PLATED STEEL|23|8 +Brand#44|PROMO PLATED STEEL|49|8 +Brand#44|PROMO PLATED TIN|14|8 +Brand#44|PROMO PLATED TIN|36|8 +Brand#44|PROMO POLISHED BRASS|36|8 +Brand#44|PROMO POLISHED COPPER|9|8 +Brand#44|PROMO POLISHED NICKEL|45|8 +Brand#44|PROMO POLISHED STEEL|9|8 +Brand#44|PROMO POLISHED STEEL|45|8 +Brand#44|PROMO POLISHED TIN|14|8 +Brand#44|PROMO POLISHED TIN|23|8 +Brand#44|PROMO POLISHED TIN|36|8 +Brand#44|PROMO POLISHED TIN|45|8 +Brand#44|PROMO POLISHED TIN|49|8 +Brand#44|SMALL ANODIZED BRASS|3|8 +Brand#44|SMALL ANODIZED BRASS|9|8 +Brand#44|SMALL ANODIZED BRASS|36|8 +Brand#44|SMALL ANODIZED COPPER|14|8 +Brand#44|SMALL ANODIZED COPPER|19|8 +Brand#44|SMALL ANODIZED COPPER|23|8 +Brand#44|SMALL ANODIZED NICKEL|23|8 +Brand#44|SMALL ANODIZED TIN|14|8 +Brand#44|SMALL ANODIZED TIN|19|8 +Brand#44|SMALL ANODIZED TIN|23|8 +Brand#44|SMALL ANODIZED TIN|45|8 +Brand#44|SMALL BRUSHED BRASS|14|8 +Brand#44|SMALL BRUSHED COPPER|23|8 +Brand#44|SMALL BRUSHED TIN|36|8 +Brand#44|SMALL BURNISHED BRASS|3|8 +Brand#44|SMALL BURNISHED BRASS|36|8 +Brand#44|SMALL BURNISHED BRASS|49|8 +Brand#44|SMALL BURNISHED NICKEL|14|8 +Brand#44|SMALL BURNISHED NICKEL|45|8 +Brand#44|SMALL BURNISHED TIN|9|8 +Brand#44|SMALL BURNISHED TIN|23|8 +Brand#44|SMALL BURNISHED TIN|49|8 +Brand#44|SMALL PLATED BRASS|36|8 +Brand#44|SMALL PLATED COPPER|14|8 +Brand#44|SMALL PLATED NICKEL|45|8 +Brand#44|SMALL PLATED NICKEL|49|8 +Brand#44|SMALL PLATED TIN|19|8 +Brand#44|SMALL POLISHED COPPER|9|8 +Brand#44|SMALL POLISHED COPPER|49|8 +Brand#44|SMALL POLISHED NICKEL|9|8 +Brand#44|SMALL POLISHED NICKEL|14|8 +Brand#44|SMALL POLISHED NICKEL|19|8 +Brand#44|SMALL POLISHED NICKEL|23|8 +Brand#44|SMALL POLISHED NICKEL|45|8 +Brand#44|SMALL POLISHED STEEL|3|8 +Brand#44|SMALL POLISHED TIN|3|8 +Brand#44|SMALL POLISHED TIN|14|8 +Brand#44|SMALL POLISHED TIN|19|8 +Brand#44|SMALL POLISHED TIN|23|8 +Brand#44|SMALL POLISHED TIN|45|8 +Brand#44|STANDARD ANODIZED COPPER|3|8 +Brand#44|STANDARD ANODIZED COPPER|19|8 +Brand#44|STANDARD ANODIZED STEEL|14|8 +Brand#44|STANDARD ANODIZED STEEL|45|8 +Brand#44|STANDARD ANODIZED TIN|23|8 +Brand#44|STANDARD ANODIZED TIN|36|8 +Brand#44|STANDARD BRUSHED BRASS|14|8 +Brand#44|STANDARD BRUSHED BRASS|45|8 +Brand#44|STANDARD BRUSHED COPPER|19|8 +Brand#44|STANDARD BRUSHED NICKEL|23|8 +Brand#44|STANDARD BRUSHED STEEL|23|8 +Brand#44|STANDARD BRUSHED STEEL|49|8 +Brand#44|STANDARD BRUSHED TIN|9|8 +Brand#44|STANDARD BRUSHED TIN|19|8 +Brand#44|STANDARD BRUSHED TIN|23|8 +Brand#44|STANDARD BURNISHED BRASS|9|8 +Brand#44|STANDARD BURNISHED BRASS|49|8 +Brand#44|STANDARD BURNISHED COPPER|45|8 +Brand#44|STANDARD BURNISHED NICKEL|19|8 +Brand#44|STANDARD BURNISHED NICKEL|23|8 +Brand#44|STANDARD BURNISHED STEEL|3|8 +Brand#44|STANDARD BURNISHED STEEL|14|8 +Brand#44|STANDARD BURNISHED STEEL|45|8 +Brand#44|STANDARD BURNISHED TIN|19|8 +Brand#44|STANDARD PLATED BRASS|9|8 +Brand#44|STANDARD PLATED BRASS|45|8 +Brand#44|STANDARD PLATED COPPER|9|8 +Brand#44|STANDARD PLATED COPPER|23|8 +Brand#44|STANDARD PLATED COPPER|49|8 +Brand#44|STANDARD PLATED NICKEL|14|8 +Brand#44|STANDARD PLATED NICKEL|19|8 +Brand#44|STANDARD PLATED TIN|19|8 +Brand#44|STANDARD PLATED TIN|49|8 +Brand#44|STANDARD POLISHED COPPER|14|8 +Brand#44|STANDARD POLISHED COPPER|19|8 +Brand#44|STANDARD POLISHED COPPER|45|8 +Brand#44|STANDARD POLISHED COPPER|49|8 +Brand#44|STANDARD POLISHED NICKEL|36|8 +Brand#44|STANDARD POLISHED TIN|9|8 +Brand#44|STANDARD POLISHED TIN|19|8 +Brand#51|ECONOMY ANODIZED BRASS|49|8 +Brand#51|ECONOMY ANODIZED COPPER|3|8 +Brand#51|ECONOMY ANODIZED NICKEL|3|8 +Brand#51|ECONOMY ANODIZED NICKEL|23|8 +Brand#51|ECONOMY ANODIZED STEEL|36|8 +Brand#51|ECONOMY ANODIZED STEEL|45|8 +Brand#51|ECONOMY ANODIZED STEEL|49|8 +Brand#51|ECONOMY ANODIZED TIN|23|8 +Brand#51|ECONOMY BRUSHED BRASS|3|8 +Brand#51|ECONOMY BRUSHED COPPER|36|8 +Brand#51|ECONOMY BRUSHED COPPER|45|8 +Brand#51|ECONOMY BRUSHED NICKEL|14|8 +Brand#51|ECONOMY BRUSHED NICKEL|19|8 +Brand#51|ECONOMY BRUSHED STEEL|9|8 +Brand#51|ECONOMY BRUSHED STEEL|14|8 +Brand#51|ECONOMY BRUSHED STEEL|49|8 +Brand#51|ECONOMY BRUSHED TIN|19|8 +Brand#51|ECONOMY BURNISHED BRASS|14|8 +Brand#51|ECONOMY BURNISHED STEEL|14|8 +Brand#51|ECONOMY BURNISHED STEEL|19|8 +Brand#51|ECONOMY BURNISHED STEEL|36|8 +Brand#51|ECONOMY BURNISHED TIN|14|8 +Brand#51|ECONOMY BURNISHED TIN|45|8 +Brand#51|ECONOMY PLATED BRASS|3|8 +Brand#51|ECONOMY PLATED BRASS|23|8 +Brand#51|ECONOMY PLATED BRASS|36|8 +Brand#51|ECONOMY PLATED COPPER|49|8 +Brand#51|ECONOMY PLATED NICKEL|9|8 +Brand#51|ECONOMY PLATED NICKEL|14|8 +Brand#51|ECONOMY PLATED NICKEL|49|8 +Brand#51|ECONOMY PLATED TIN|36|8 +Brand#51|ECONOMY PLATED TIN|49|8 +Brand#51|ECONOMY POLISHED BRASS|14|8 +Brand#51|ECONOMY POLISHED BRASS|36|8 +Brand#51|ECONOMY POLISHED BRASS|49|8 +Brand#51|ECONOMY POLISHED COPPER|9|8 +Brand#51|ECONOMY POLISHED NICKEL|19|8 +Brand#51|ECONOMY POLISHED NICKEL|36|8 +Brand#51|ECONOMY POLISHED STEEL|3|8 +Brand#51|ECONOMY POLISHED STEEL|9|8 +Brand#51|ECONOMY POLISHED STEEL|14|8 +Brand#51|ECONOMY POLISHED STEEL|36|8 +Brand#51|ECONOMY POLISHED TIN|14|8 +Brand#51|ECONOMY POLISHED TIN|19|8 +Brand#51|LARGE ANODIZED BRASS|19|8 +Brand#51|LARGE ANODIZED BRASS|23|8 +Brand#51|LARGE ANODIZED COPPER|36|8 +Brand#51|LARGE ANODIZED COPPER|49|8 +Brand#51|LARGE ANODIZED NICKEL|14|8 +Brand#51|LARGE ANODIZED NICKEL|45|8 +Brand#51|LARGE ANODIZED STEEL|45|8 +Brand#51|LARGE ANODIZED TIN|19|8 +Brand#51|LARGE BRUSHED BRASS|9|8 +Brand#51|LARGE BRUSHED BRASS|23|8 +Brand#51|LARGE BRUSHED COPPER|23|8 +Brand#51|LARGE BRUSHED COPPER|49|8 +Brand#51|LARGE BRUSHED NICKEL|9|8 +Brand#51|LARGE BRUSHED NICKEL|19|8 +Brand#51|LARGE BRUSHED NICKEL|45|8 +Brand#51|LARGE BURNISHED BRASS|3|8 +Brand#51|LARGE BURNISHED BRASS|14|8 +Brand#51|LARGE BURNISHED BRASS|36|8 +Brand#51|LARGE BURNISHED NICKEL|23|8 +Brand#51|LARGE BURNISHED STEEL|9|8 +Brand#51|LARGE BURNISHED STEEL|36|8 +Brand#51|LARGE PLATED BRASS|23|8 +Brand#51|LARGE PLATED COPPER|49|8 +Brand#51|LARGE PLATED NICKEL|3|8 +Brand#51|LARGE PLATED NICKEL|36|8 +Brand#51|LARGE PLATED STEEL|3|8 +Brand#51|LARGE PLATED TIN|9|8 +Brand#51|LARGE PLATED TIN|36|8 +Brand#51|LARGE POLISHED BRASS|9|8 +Brand#51|LARGE POLISHED COPPER|14|8 +Brand#51|LARGE POLISHED COPPER|45|8 +Brand#51|LARGE POLISHED NICKEL|14|8 +Brand#51|LARGE POLISHED STEEL|3|8 +Brand#51|LARGE POLISHED TIN|14|8 +Brand#51|LARGE POLISHED TIN|23|8 +Brand#51|MEDIUM ANODIZED BRASS|23|8 +Brand#51|MEDIUM ANODIZED BRASS|49|8 +Brand#51|MEDIUM ANODIZED COPPER|9|8 +Brand#51|MEDIUM ANODIZED COPPER|45|8 +Brand#51|MEDIUM ANODIZED NICKEL|9|8 +Brand#51|MEDIUM ANODIZED NICKEL|14|8 +Brand#51|MEDIUM ANODIZED NICKEL|36|8 +Brand#51|MEDIUM ANODIZED STEEL|3|8 +Brand#51|MEDIUM ANODIZED STEEL|36|8 +Brand#51|MEDIUM ANODIZED TIN|3|8 +Brand#51|MEDIUM ANODIZED TIN|19|8 +Brand#51|MEDIUM BRUSHED COPPER|3|8 +Brand#51|MEDIUM BRUSHED COPPER|45|8 +Brand#51|MEDIUM BRUSHED NICKEL|14|8 +Brand#51|MEDIUM BURNISHED BRASS|9|8 +Brand#51|MEDIUM BURNISHED COPPER|3|8 +Brand#51|MEDIUM BURNISHED COPPER|9|8 +Brand#51|MEDIUM BURNISHED COPPER|19|8 +Brand#51|MEDIUM BURNISHED NICKEL|9|8 +Brand#51|MEDIUM BURNISHED NICKEL|23|8 +Brand#51|MEDIUM BURNISHED NICKEL|36|8 +Brand#51|MEDIUM BURNISHED STEEL|14|8 +Brand#51|MEDIUM BURNISHED STEEL|49|8 +Brand#51|MEDIUM BURNISHED TIN|9|8 +Brand#51|MEDIUM BURNISHED TIN|49|8 +Brand#51|MEDIUM PLATED BRASS|49|8 +Brand#51|MEDIUM PLATED COPPER|9|8 +Brand#51|MEDIUM PLATED COPPER|19|8 +Brand#51|MEDIUM PLATED NICKEL|3|8 +Brand#51|MEDIUM PLATED NICKEL|9|8 +Brand#51|MEDIUM PLATED STEEL|9|8 +Brand#51|MEDIUM PLATED STEEL|49|8 +Brand#51|PROMO ANODIZED COPPER|49|8 +Brand#51|PROMO ANODIZED NICKEL|19|8 +Brand#51|PROMO ANODIZED TIN|14|8 +Brand#51|PROMO ANODIZED TIN|19|8 +Brand#51|PROMO BRUSHED BRASS|19|8 +Brand#51|PROMO BRUSHED NICKEL|9|8 +Brand#51|PROMO BRUSHED NICKEL|14|8 +Brand#51|PROMO BRUSHED STEEL|49|8 +Brand#51|PROMO BRUSHED TIN|45|8 +Brand#51|PROMO BURNISHED BRASS|3|8 +Brand#51|PROMO BURNISHED BRASS|19|8 +Brand#51|PROMO BURNISHED BRASS|23|8 +Brand#51|PROMO BURNISHED NICKEL|3|8 +Brand#51|PROMO BURNISHED STEEL|14|8 +Brand#51|PROMO BURNISHED TIN|3|8 +Brand#51|PROMO BURNISHED TIN|36|8 +Brand#51|PROMO BURNISHED TIN|45|8 +Brand#51|PROMO PLATED BRASS|19|8 +Brand#51|PROMO PLATED BRASS|49|8 +Brand#51|PROMO PLATED COPPER|19|8 +Brand#51|PROMO PLATED NICKEL|23|8 +Brand#51|PROMO PLATED STEEL|3|8 +Brand#51|PROMO PLATED STEEL|23|8 +Brand#51|PROMO PLATED STEEL|49|8 +Brand#51|PROMO PLATED TIN|3|8 +Brand#51|PROMO PLATED TIN|19|8 +Brand#51|PROMO POLISHED BRASS|3|8 +Brand#51|PROMO POLISHED BRASS|9|8 +Brand#51|PROMO POLISHED BRASS|19|8 +Brand#51|PROMO POLISHED BRASS|23|8 +Brand#51|PROMO POLISHED COPPER|9|8 +Brand#51|PROMO POLISHED COPPER|14|8 +Brand#51|PROMO POLISHED STEEL|36|8 +Brand#51|PROMO POLISHED STEEL|45|8 +Brand#51|SMALL ANODIZED BRASS|9|8 +Brand#51|SMALL ANODIZED COPPER|49|8 +Brand#51|SMALL ANODIZED NICKEL|14|8 +Brand#51|SMALL ANODIZED STEEL|3|8 +Brand#51|SMALL ANODIZED STEEL|14|8 +Brand#51|SMALL ANODIZED STEEL|23|8 +Brand#51|SMALL ANODIZED STEEL|45|8 +Brand#51|SMALL ANODIZED TIN|19|8 +Brand#51|SMALL BRUSHED BRASS|9|8 +Brand#51|SMALL BRUSHED COPPER|3|8 +Brand#51|SMALL BRUSHED COPPER|19|8 +Brand#51|SMALL BRUSHED COPPER|45|8 +Brand#51|SMALL BRUSHED NICKEL|23|8 +Brand#51|SMALL BRUSHED STEEL|3|8 +Brand#51|SMALL BRUSHED STEEL|9|8 +Brand#51|SMALL BRUSHED STEEL|14|8 +Brand#51|SMALL BRUSHED TIN|9|8 +Brand#51|SMALL BRUSHED TIN|36|8 +Brand#51|SMALL BURNISHED BRASS|36|8 +Brand#51|SMALL BURNISHED BRASS|49|8 +Brand#51|SMALL BURNISHED COPPER|14|8 +Brand#51|SMALL BURNISHED COPPER|23|8 +Brand#51|SMALL BURNISHED NICKEL|19|8 +Brand#51|SMALL BURNISHED NICKEL|49|8 +Brand#51|SMALL BURNISHED STEEL|14|8 +Brand#51|SMALL BURNISHED STEEL|19|8 +Brand#51|SMALL BURNISHED TIN|49|8 +Brand#51|SMALL PLATED COPPER|45|8 +Brand#51|SMALL PLATED COPPER|49|8 +Brand#51|SMALL PLATED NICKEL|9|8 +Brand#51|SMALL PLATED STEEL|36|8 +Brand#51|SMALL PLATED STEEL|45|8 +Brand#51|SMALL PLATED TIN|19|8 +Brand#51|SMALL POLISHED BRASS|19|8 +Brand#51|SMALL POLISHED COPPER|36|8 +Brand#51|SMALL POLISHED STEEL|23|8 +Brand#51|SMALL POLISHED STEEL|45|8 +Brand#51|SMALL POLISHED TIN|49|8 +Brand#51|STANDARD ANODIZED BRASS|19|8 +Brand#51|STANDARD ANODIZED BRASS|36|8 +Brand#51|STANDARD ANODIZED NICKEL|3|8 +Brand#51|STANDARD ANODIZED NICKEL|9|8 +Brand#51|STANDARD ANODIZED NICKEL|19|8 +Brand#51|STANDARD ANODIZED STEEL|9|8 +Brand#51|STANDARD ANODIZED STEEL|36|8 +Brand#51|STANDARD ANODIZED TIN|9|8 +Brand#51|STANDARD ANODIZED TIN|23|8 +Brand#51|STANDARD BRUSHED COPPER|23|8 +Brand#51|STANDARD BRUSHED COPPER|45|8 +Brand#51|STANDARD BRUSHED NICKEL|19|8 +Brand#51|STANDARD BRUSHED NICKEL|23|8 +Brand#51|STANDARD BRUSHED STEEL|19|8 +Brand#51|STANDARD BURNISHED BRASS|3|8 +Brand#51|STANDARD BURNISHED BRASS|23|8 +Brand#51|STANDARD BURNISHED COPPER|23|8 +Brand#51|STANDARD BURNISHED NICKEL|14|8 +Brand#51|STANDARD BURNISHED NICKEL|23|8 +Brand#51|STANDARD BURNISHED NICKEL|36|8 +Brand#51|STANDARD BURNISHED NICKEL|49|8 +Brand#51|STANDARD BURNISHED TIN|14|8 +Brand#51|STANDARD PLATED BRASS|49|8 +Brand#51|STANDARD PLATED COPPER|19|8 +Brand#51|STANDARD PLATED COPPER|45|8 +Brand#51|STANDARD PLATED NICKEL|19|8 +Brand#51|STANDARD PLATED STEEL|19|8 +Brand#51|STANDARD PLATED TIN|9|8 +Brand#51|STANDARD POLISHED BRASS|3|8 +Brand#51|STANDARD POLISHED BRASS|45|8 +Brand#51|STANDARD POLISHED COPPER|9|8 +Brand#51|STANDARD POLISHED COPPER|49|8 +Brand#51|STANDARD POLISHED NICKEL|3|8 +Brand#51|STANDARD POLISHED NICKEL|49|8 +Brand#51|STANDARD POLISHED STEEL|9|8 +Brand#51|STANDARD POLISHED STEEL|14|8 +Brand#51|STANDARD POLISHED STEEL|49|8 +Brand#51|STANDARD POLISHED TIN|14|8 +Brand#51|STANDARD POLISHED TIN|23|8 +Brand#51|STANDARD POLISHED TIN|49|8 +Brand#52|ECONOMY ANODIZED BRASS|14|8 +Brand#52|ECONOMY ANODIZED BRASS|36|8 +Brand#52|ECONOMY ANODIZED NICKEL|23|8 +Brand#52|ECONOMY ANODIZED STEEL|3|8 +Brand#52|ECONOMY ANODIZED STEEL|19|8 +Brand#52|ECONOMY ANODIZED TIN|3|8 +Brand#52|ECONOMY ANODIZED TIN|14|8 +Brand#52|ECONOMY ANODIZED TIN|49|8 +Brand#52|ECONOMY BRUSHED BRASS|36|8 +Brand#52|ECONOMY BRUSHED STEEL|3|8 +Brand#52|ECONOMY BRUSHED STEEL|14|8 +Brand#52|ECONOMY BRUSHED TIN|9|8 +Brand#52|ECONOMY BRUSHED TIN|36|8 +Brand#52|ECONOMY BRUSHED TIN|49|8 +Brand#52|ECONOMY BURNISHED COPPER|45|8 +Brand#52|ECONOMY BURNISHED NICKEL|23|8 +Brand#52|ECONOMY BURNISHED STEEL|9|8 +Brand#52|ECONOMY BURNISHED STEEL|36|8 +Brand#52|ECONOMY BURNISHED TIN|3|8 +Brand#52|ECONOMY PLATED BRASS|3|8 +Brand#52|ECONOMY PLATED BRASS|14|8 +Brand#52|ECONOMY PLATED BRASS|23|8 +Brand#52|ECONOMY PLATED BRASS|45|8 +Brand#52|ECONOMY PLATED COPPER|49|8 +Brand#52|ECONOMY PLATED NICKEL|3|8 +Brand#52|ECONOMY PLATED NICKEL|49|8 +Brand#52|ECONOMY PLATED STEEL|3|8 +Brand#52|ECONOMY PLATED STEEL|14|8 +Brand#52|ECONOMY PLATED TIN|3|8 +Brand#52|ECONOMY PLATED TIN|19|8 +Brand#52|ECONOMY PLATED TIN|23|8 +Brand#52|ECONOMY POLISHED BRASS|23|8 +Brand#52|ECONOMY POLISHED BRASS|45|8 +Brand#52|ECONOMY POLISHED BRASS|49|8 +Brand#52|ECONOMY POLISHED NICKEL|19|8 +Brand#52|ECONOMY POLISHED NICKEL|23|8 +Brand#52|ECONOMY POLISHED NICKEL|45|8 +Brand#52|ECONOMY POLISHED STEEL|9|8 +Brand#52|ECONOMY POLISHED STEEL|45|8 +Brand#52|ECONOMY POLISHED STEEL|49|8 +Brand#52|ECONOMY POLISHED TIN|19|8 +Brand#52|ECONOMY POLISHED TIN|36|8 +Brand#52|ECONOMY POLISHED TIN|49|8 +Brand#52|LARGE ANODIZED BRASS|14|8 +Brand#52|LARGE ANODIZED STEEL|9|8 +Brand#52|LARGE ANODIZED STEEL|19|8 +Brand#52|LARGE ANODIZED STEEL|36|8 +Brand#52|LARGE ANODIZED STEEL|45|8 +Brand#52|LARGE ANODIZED TIN|9|8 +Brand#52|LARGE ANODIZED TIN|14|8 +Brand#52|LARGE ANODIZED TIN|36|8 +Brand#52|LARGE BRUSHED BRASS|19|8 +Brand#52|LARGE BRUSHED COPPER|14|8 +Brand#52|LARGE BRUSHED COPPER|49|8 +Brand#52|LARGE BRUSHED NICKEL|36|8 +Brand#52|LARGE BRUSHED TIN|19|8 +Brand#52|LARGE BRUSHED TIN|49|8 +Brand#52|LARGE BURNISHED BRASS|19|8 +Brand#52|LARGE BURNISHED BRASS|49|8 +Brand#52|LARGE BURNISHED COPPER|3|8 +Brand#52|LARGE BURNISHED COPPER|23|8 +Brand#52|LARGE BURNISHED NICKEL|3|8 +Brand#52|LARGE BURNISHED NICKEL|9|8 +Brand#52|LARGE BURNISHED STEEL|9|8 +Brand#52|LARGE BURNISHED STEEL|14|8 +Brand#52|LARGE BURNISHED TIN|14|8 +Brand#52|LARGE BURNISHED TIN|45|8 +Brand#52|LARGE PLATED BRASS|14|8 +Brand#52|LARGE PLATED COPPER|3|8 +Brand#52|LARGE PLATED COPPER|14|8 +Brand#52|LARGE PLATED COPPER|45|8 +Brand#52|LARGE PLATED NICKEL|14|8 +Brand#52|LARGE PLATED NICKEL|49|8 +Brand#52|LARGE PLATED TIN|45|8 +Brand#52|LARGE POLISHED COPPER|14|8 +Brand#52|LARGE POLISHED NICKEL|23|8 +Brand#52|LARGE POLISHED NICKEL|49|8 +Brand#52|LARGE POLISHED TIN|9|8 +Brand#52|MEDIUM ANODIZED BRASS|3|8 +Brand#52|MEDIUM ANODIZED COPPER|3|8 +Brand#52|MEDIUM ANODIZED COPPER|14|8 +Brand#52|MEDIUM ANODIZED COPPER|36|8 +Brand#52|MEDIUM ANODIZED COPPER|49|8 +Brand#52|MEDIUM ANODIZED NICKEL|23|8 +Brand#52|MEDIUM ANODIZED NICKEL|45|8 +Brand#52|MEDIUM ANODIZED STEEL|19|8 +Brand#52|MEDIUM ANODIZED STEEL|45|8 +Brand#52|MEDIUM ANODIZED TIN|19|8 +Brand#52|MEDIUM ANODIZED TIN|49|8 +Brand#52|MEDIUM BRUSHED BRASS|9|8 +Brand#52|MEDIUM BRUSHED COPPER|3|8 +Brand#52|MEDIUM BRUSHED COPPER|9|8 +Brand#52|MEDIUM BRUSHED NICKEL|49|8 +Brand#52|MEDIUM BRUSHED STEEL|23|8 +Brand#52|MEDIUM BRUSHED STEEL|36|8 +Brand#52|MEDIUM BRUSHED STEEL|45|8 +Brand#52|MEDIUM BRUSHED STEEL|49|8 +Brand#52|MEDIUM BRUSHED TIN|19|8 +Brand#52|MEDIUM BRUSHED TIN|23|8 +Brand#52|MEDIUM BRUSHED TIN|49|8 +Brand#52|MEDIUM BURNISHED COPPER|36|8 +Brand#52|MEDIUM BURNISHED NICKEL|14|8 +Brand#52|MEDIUM BURNISHED NICKEL|19|8 +Brand#52|MEDIUM BURNISHED TIN|9|8 +Brand#52|MEDIUM BURNISHED TIN|19|8 +Brand#52|MEDIUM BURNISHED TIN|49|8 +Brand#52|MEDIUM PLATED COPPER|14|8 +Brand#52|MEDIUM PLATED COPPER|19|8 +Brand#52|MEDIUM PLATED COPPER|36|8 +Brand#52|MEDIUM PLATED NICKEL|3|8 +Brand#52|MEDIUM PLATED STEEL|36|8 +Brand#52|MEDIUM PLATED TIN|3|8 +Brand#52|MEDIUM PLATED TIN|9|8 +Brand#52|MEDIUM PLATED TIN|14|8 +Brand#52|PROMO ANODIZED BRASS|36|8 +Brand#52|PROMO ANODIZED COPPER|19|8 +Brand#52|PROMO ANODIZED COPPER|23|8 +Brand#52|PROMO ANODIZED COPPER|36|8 +Brand#52|PROMO ANODIZED TIN|9|8 +Brand#52|PROMO ANODIZED TIN|23|8 +Brand#52|PROMO BRUSHED BRASS|3|8 +Brand#52|PROMO BRUSHED BRASS|14|8 +Brand#52|PROMO BRUSHED BRASS|45|8 +Brand#52|PROMO BRUSHED COPPER|45|8 +Brand#52|PROMO BRUSHED NICKEL|45|8 +Brand#52|PROMO BRUSHED NICKEL|49|8 +Brand#52|PROMO BRUSHED STEEL|9|8 +Brand#52|PROMO BRUSHED STEEL|14|8 +Brand#52|PROMO BRUSHED STEEL|23|8 +Brand#52|PROMO BURNISHED BRASS|14|8 +Brand#52|PROMO BURNISHED BRASS|23|8 +Brand#52|PROMO BURNISHED COPPER|45|8 +Brand#52|PROMO BURNISHED COPPER|49|8 +Brand#52|PROMO BURNISHED NICKEL|9|8 +Brand#52|PROMO BURNISHED NICKEL|14|8 +Brand#52|PROMO BURNISHED NICKEL|49|8 +Brand#52|PROMO PLATED BRASS|3|8 +Brand#52|PROMO PLATED BRASS|45|8 +Brand#52|PROMO PLATED BRASS|49|8 +Brand#52|PROMO PLATED COPPER|3|8 +Brand#52|PROMO PLATED COPPER|9|8 +Brand#52|PROMO PLATED COPPER|45|8 +Brand#52|PROMO PLATED NICKEL|19|8 +Brand#52|PROMO PLATED NICKEL|23|8 +Brand#52|PROMO PLATED NICKEL|36|8 +Brand#52|PROMO PLATED NICKEL|45|8 +Brand#52|PROMO PLATED STEEL|3|8 +Brand#52|PROMO PLATED STEEL|23|8 +Brand#52|PROMO PLATED STEEL|49|8 +Brand#52|PROMO POLISHED BRASS|36|8 +Brand#52|PROMO POLISHED COPPER|23|8 +Brand#52|PROMO POLISHED COPPER|49|8 +Brand#52|PROMO POLISHED NICKEL|14|8 +Brand#52|PROMO POLISHED STEEL|45|8 +Brand#52|PROMO POLISHED TIN|3|8 +Brand#52|PROMO POLISHED TIN|9|8 +Brand#52|PROMO POLISHED TIN|14|8 +Brand#52|PROMO POLISHED TIN|19|8 +Brand#52|PROMO POLISHED TIN|45|8 +Brand#52|SMALL ANODIZED BRASS|3|8 +Brand#52|SMALL ANODIZED BRASS|14|8 +Brand#52|SMALL ANODIZED BRASS|23|8 +Brand#52|SMALL ANODIZED COPPER|23|8 +Brand#52|SMALL ANODIZED NICKEL|45|8 +Brand#52|SMALL ANODIZED STEEL|23|8 +Brand#52|SMALL ANODIZED TIN|19|8 +Brand#52|SMALL ANODIZED TIN|23|8 +Brand#52|SMALL ANODIZED TIN|49|8 +Brand#52|SMALL BRUSHED BRASS|9|8 +Brand#52|SMALL BRUSHED BRASS|49|8 +Brand#52|SMALL BRUSHED COPPER|23|8 +Brand#52|SMALL BRUSHED NICKEL|19|8 +Brand#52|SMALL BRUSHED TIN|3|8 +Brand#52|SMALL BRUSHED TIN|19|8 +Brand#52|SMALL BRUSHED TIN|45|8 +Brand#52|SMALL BRUSHED TIN|49|8 +Brand#52|SMALL BURNISHED BRASS|9|8 +Brand#52|SMALL BURNISHED BRASS|45|8 +Brand#52|SMALL BURNISHED COPPER|9|8 +Brand#52|SMALL BURNISHED COPPER|45|8 +Brand#52|SMALL BURNISHED NICKEL|3|8 +Brand#52|SMALL BURNISHED NICKEL|14|8 +Brand#52|SMALL BURNISHED TIN|36|8 +Brand#52|SMALL PLATED BRASS|3|8 +Brand#52|SMALL PLATED BRASS|45|8 +Brand#52|SMALL PLATED BRASS|49|8 +Brand#52|SMALL PLATED COPPER|49|8 +Brand#52|SMALL PLATED NICKEL|14|8 +Brand#52|SMALL PLATED NICKEL|36|8 +Brand#52|SMALL POLISHED BRASS|23|8 +Brand#52|SMALL POLISHED COPPER|9|8 +Brand#52|SMALL POLISHED COPPER|36|8 +Brand#52|SMALL POLISHED COPPER|45|8 +Brand#52|SMALL POLISHED STEEL|3|8 +Brand#52|SMALL POLISHED STEEL|9|8 +Brand#52|SMALL POLISHED STEEL|49|8 +Brand#52|SMALL POLISHED TIN|9|8 +Brand#52|SMALL POLISHED TIN|14|8 +Brand#52|STANDARD ANODIZED BRASS|49|8 +Brand#52|STANDARD ANODIZED COPPER|3|8 +Brand#52|STANDARD ANODIZED COPPER|9|8 +Brand#52|STANDARD ANODIZED COPPER|19|8 +Brand#52|STANDARD ANODIZED COPPER|36|8 +Brand#52|STANDARD ANODIZED COPPER|45|8 +Brand#52|STANDARD ANODIZED STEEL|3|8 +Brand#52|STANDARD ANODIZED STEEL|23|8 +Brand#52|STANDARD ANODIZED STEEL|49|8 +Brand#52|STANDARD ANODIZED TIN|3|8 +Brand#52|STANDARD BRUSHED BRASS|3|8 +Brand#52|STANDARD BRUSHED COPPER|45|8 +Brand#52|STANDARD BRUSHED STEEL|14|8 +Brand#52|STANDARD BRUSHED TIN|9|8 +Brand#52|STANDARD BURNISHED BRASS|49|8 +Brand#52|STANDARD BURNISHED COPPER|19|8 +Brand#52|STANDARD BURNISHED COPPER|23|8 +Brand#52|STANDARD BURNISHED STEEL|3|8 +Brand#52|STANDARD BURNISHED TIN|19|8 +Brand#52|STANDARD PLATED BRASS|49|8 +Brand#52|STANDARD PLATED STEEL|14|8 +Brand#52|STANDARD PLATED STEEL|36|8 +Brand#52|STANDARD POLISHED BRASS|3|8 +Brand#52|STANDARD POLISHED BRASS|9|8 +Brand#52|STANDARD POLISHED BRASS|49|8 +Brand#52|STANDARD POLISHED COPPER|9|8 +Brand#52|STANDARD POLISHED COPPER|14|8 +Brand#52|STANDARD POLISHED NICKEL|45|8 +Brand#52|STANDARD POLISHED STEEL|45|8 +Brand#52|STANDARD POLISHED TIN|19|8 +Brand#53|ECONOMY ANODIZED BRASS|9|8 +Brand#53|ECONOMY ANODIZED BRASS|36|8 +Brand#53|ECONOMY ANODIZED BRASS|45|8 +Brand#53|ECONOMY ANODIZED COPPER|45|8 +Brand#53|ECONOMY ANODIZED NICKEL|19|8 +Brand#53|ECONOMY ANODIZED STEEL|45|8 +Brand#53|ECONOMY ANODIZED TIN|14|8 +Brand#53|ECONOMY ANODIZED TIN|36|8 +Brand#53|ECONOMY BRUSHED COPPER|3|8 +Brand#53|ECONOMY BRUSHED NICKEL|23|8 +Brand#53|ECONOMY BRUSHED STEEL|23|8 +Brand#53|ECONOMY BRUSHED STEEL|49|8 +Brand#53|ECONOMY BRUSHED TIN|3|8 +Brand#53|ECONOMY BURNISHED BRASS|9|8 +Brand#53|ECONOMY BURNISHED BRASS|45|8 +Brand#53|ECONOMY BURNISHED COPPER|9|8 +Brand#53|ECONOMY BURNISHED COPPER|14|8 +Brand#53|ECONOMY BURNISHED COPPER|19|8 +Brand#53|ECONOMY BURNISHED NICKEL|3|8 +Brand#53|ECONOMY BURNISHED NICKEL|14|8 +Brand#53|ECONOMY BURNISHED NICKEL|36|8 +Brand#53|ECONOMY BURNISHED NICKEL|45|8 +Brand#53|ECONOMY BURNISHED STEEL|19|8 +Brand#53|ECONOMY BURNISHED STEEL|23|8 +Brand#53|ECONOMY BURNISHED STEEL|36|8 +Brand#53|ECONOMY BURNISHED TIN|3|8 +Brand#53|ECONOMY BURNISHED TIN|49|8 +Brand#53|ECONOMY PLATED BRASS|14|8 +Brand#53|ECONOMY PLATED BRASS|19|8 +Brand#53|ECONOMY PLATED COPPER|3|8 +Brand#53|ECONOMY PLATED TIN|19|8 +Brand#53|ECONOMY POLISHED COPPER|14|8 +Brand#53|ECONOMY POLISHED COPPER|19|8 +Brand#53|ECONOMY POLISHED NICKEL|36|8 +Brand#53|ECONOMY POLISHED STEEL|3|8 +Brand#53|ECONOMY POLISHED STEEL|9|8 +Brand#53|LARGE ANODIZED BRASS|19|8 +Brand#53|LARGE ANODIZED BRASS|45|8 +Brand#53|LARGE ANODIZED STEEL|45|8 +Brand#53|LARGE ANODIZED TIN|23|8 +Brand#53|LARGE ANODIZED TIN|45|8 +Brand#53|LARGE ANODIZED TIN|49|8 +Brand#53|LARGE BRUSHED COPPER|19|8 +Brand#53|LARGE BRUSHED COPPER|45|8 +Brand#53|LARGE BRUSHED STEEL|9|8 +Brand#53|LARGE BRUSHED STEEL|45|8 +Brand#53|LARGE BRUSHED TIN|3|8 +Brand#53|LARGE BRUSHED TIN|9|8 +Brand#53|LARGE BRUSHED TIN|36|8 +Brand#53|LARGE BURNISHED BRASS|3|8 +Brand#53|LARGE BURNISHED NICKEL|14|8 +Brand#53|LARGE BURNISHED NICKEL|23|8 +Brand#53|LARGE BURNISHED STEEL|3|8 +Brand#53|LARGE BURNISHED STEEL|19|8 +Brand#53|LARGE BURNISHED STEEL|23|8 +Brand#53|LARGE BURNISHED STEEL|45|8 +Brand#53|LARGE BURNISHED TIN|9|8 +Brand#53|LARGE PLATED BRASS|9|8 +Brand#53|LARGE PLATED BRASS|49|8 +Brand#53|LARGE PLATED NICKEL|49|8 +Brand#53|LARGE PLATED STEEL|45|8 +Brand#53|LARGE PLATED TIN|23|8 +Brand#53|LARGE POLISHED BRASS|3|8 +Brand#53|LARGE POLISHED BRASS|23|8 +Brand#53|LARGE POLISHED COPPER|23|8 +Brand#53|LARGE POLISHED NICKEL|3|8 +Brand#53|LARGE POLISHED NICKEL|14|8 +Brand#53|LARGE POLISHED NICKEL|23|8 +Brand#53|LARGE POLISHED STEEL|3|8 +Brand#53|LARGE POLISHED STEEL|23|8 +Brand#53|LARGE POLISHED TIN|9|8 +Brand#53|LARGE POLISHED TIN|49|8 +Brand#53|MEDIUM ANODIZED BRASS|3|8 +Brand#53|MEDIUM ANODIZED COPPER|9|8 +Brand#53|MEDIUM ANODIZED COPPER|45|8 +Brand#53|MEDIUM ANODIZED STEEL|9|8 +Brand#53|MEDIUM ANODIZED STEEL|23|8 +Brand#53|MEDIUM ANODIZED STEEL|36|8 +Brand#53|MEDIUM ANODIZED TIN|3|8 +Brand#53|MEDIUM BRUSHED COPPER|9|8 +Brand#53|MEDIUM BRUSHED COPPER|36|8 +Brand#53|MEDIUM BRUSHED NICKEL|14|8 +Brand#53|MEDIUM BRUSHED NICKEL|23|8 +Brand#53|MEDIUM BRUSHED STEEL|45|8 +Brand#53|MEDIUM BRUSHED TIN|9|8 +Brand#53|MEDIUM BURNISHED COPPER|3|8 +Brand#53|MEDIUM BURNISHED COPPER|14|8 +Brand#53|MEDIUM BURNISHED COPPER|45|8 +Brand#53|MEDIUM BURNISHED NICKEL|19|8 +Brand#53|MEDIUM BURNISHED NICKEL|36|8 +Brand#53|MEDIUM BURNISHED STEEL|14|8 +Brand#53|MEDIUM BURNISHED STEEL|49|8 +Brand#53|MEDIUM BURNISHED TIN|9|8 +Brand#53|MEDIUM BURNISHED TIN|14|8 +Brand#53|MEDIUM PLATED BRASS|9|8 +Brand#53|MEDIUM PLATED BRASS|19|8 +Brand#53|MEDIUM PLATED NICKEL|23|8 +Brand#53|MEDIUM PLATED NICKEL|36|8 +Brand#53|MEDIUM PLATED NICKEL|45|8 +Brand#53|MEDIUM PLATED STEEL|19|8 +Brand#53|MEDIUM PLATED STEEL|45|8 +Brand#53|PROMO ANODIZED BRASS|19|8 +Brand#53|PROMO ANODIZED BRASS|23|8 +Brand#53|PROMO ANODIZED BRASS|36|8 +Brand#53|PROMO ANODIZED COPPER|3|8 +Brand#53|PROMO ANODIZED COPPER|9|8 +Brand#53|PROMO ANODIZED NICKEL|36|8 +Brand#53|PROMO ANODIZED STEEL|3|8 +Brand#53|PROMO ANODIZED STEEL|14|8 +Brand#53|PROMO ANODIZED TIN|19|8 +Brand#53|PROMO ANODIZED TIN|49|8 +Brand#53|PROMO BRUSHED BRASS|45|8 +Brand#53|PROMO BRUSHED COPPER|9|8 +Brand#53|PROMO BRUSHED COPPER|14|8 +Brand#53|PROMO BRUSHED NICKEL|14|8 +Brand#53|PROMO BRUSHED NICKEL|49|8 +Brand#53|PROMO BRUSHED STEEL|3|8 +Brand#53|PROMO BRUSHED TIN|23|8 +Brand#53|PROMO BURNISHED BRASS|14|8 +Brand#53|PROMO BURNISHED BRASS|23|8 +Brand#53|PROMO BURNISHED BRASS|36|8 +Brand#53|PROMO BURNISHED COPPER|14|8 +Brand#53|PROMO BURNISHED NICKEL|14|8 +Brand#53|PROMO BURNISHED STEEL|23|8 +Brand#53|PROMO BURNISHED TIN|3|8 +Brand#53|PROMO BURNISHED TIN|9|8 +Brand#53|PROMO BURNISHED TIN|19|8 +Brand#53|PROMO BURNISHED TIN|45|8 +Brand#53|PROMO PLATED BRASS|45|8 +Brand#53|PROMO PLATED BRASS|49|8 +Brand#53|PROMO PLATED COPPER|23|8 +Brand#53|PROMO PLATED COPPER|45|8 +Brand#53|PROMO PLATED COPPER|49|8 +Brand#53|PROMO PLATED NICKEL|49|8 +Brand#53|PROMO PLATED STEEL|19|8 +Brand#53|PROMO PLATED TIN|45|8 +Brand#53|PROMO PLATED TIN|49|8 +Brand#53|PROMO POLISHED BRASS|14|8 +Brand#53|PROMO POLISHED BRASS|19|8 +Brand#53|PROMO POLISHED BRASS|36|8 +Brand#53|PROMO POLISHED NICKEL|19|8 +Brand#53|PROMO POLISHED NICKEL|23|8 +Brand#53|PROMO POLISHED NICKEL|45|8 +Brand#53|PROMO POLISHED STEEL|3|8 +Brand#53|PROMO POLISHED STEEL|9|8 +Brand#53|PROMO POLISHED TIN|36|8 +Brand#53|PROMO POLISHED TIN|45|8 +Brand#53|SMALL ANODIZED BRASS|3|8 +Brand#53|SMALL ANODIZED BRASS|9|8 +Brand#53|SMALL ANODIZED BRASS|45|8 +Brand#53|SMALL ANODIZED COPPER|3|8 +Brand#53|SMALL ANODIZED COPPER|19|8 +Brand#53|SMALL ANODIZED COPPER|23|8 +Brand#53|SMALL ANODIZED NICKEL|9|8 +Brand#53|SMALL ANODIZED NICKEL|19|8 +Brand#53|SMALL ANODIZED STEEL|23|8 +Brand#53|SMALL ANODIZED STEEL|45|8 +Brand#53|SMALL ANODIZED TIN|36|8 +Brand#53|SMALL BRUSHED BRASS|14|8 +Brand#53|SMALL BRUSHED BRASS|36|8 +Brand#53|SMALL BRUSHED STEEL|45|8 +Brand#53|SMALL BRUSHED TIN|3|8 +Brand#53|SMALL BRUSHED TIN|14|8 +Brand#53|SMALL BRUSHED TIN|19|8 +Brand#53|SMALL BRUSHED TIN|45|8 +Brand#53|SMALL BRUSHED TIN|49|8 +Brand#53|SMALL BURNISHED BRASS|45|8 +Brand#53|SMALL BURNISHED BRASS|49|8 +Brand#53|SMALL BURNISHED COPPER|19|8 +Brand#53|SMALL BURNISHED COPPER|23|8 +Brand#53|SMALL BURNISHED COPPER|36|8 +Brand#53|SMALL BURNISHED COPPER|45|8 +Brand#53|SMALL BURNISHED COPPER|49|8 +Brand#53|SMALL BURNISHED NICKEL|14|8 +Brand#53|SMALL BURNISHED STEEL|9|8 +Brand#53|SMALL BURNISHED STEEL|36|8 +Brand#53|SMALL BURNISHED TIN|14|8 +Brand#53|SMALL BURNISHED TIN|23|8 +Brand#53|SMALL PLATED BRASS|9|8 +Brand#53|SMALL PLATED BRASS|36|8 +Brand#53|SMALL PLATED NICKEL|9|8 +Brand#53|SMALL PLATED NICKEL|14|8 +Brand#53|SMALL PLATED NICKEL|23|8 +Brand#53|SMALL PLATED STEEL|19|8 +Brand#53|SMALL PLATED STEEL|23|8 +Brand#53|SMALL PLATED TIN|9|8 +Brand#53|SMALL POLISHED BRASS|36|8 +Brand#53|SMALL POLISHED COPPER|23|8 +Brand#53|SMALL POLISHED NICKEL|3|8 +Brand#53|SMALL POLISHED NICKEL|19|8 +Brand#53|SMALL POLISHED STEEL|3|8 +Brand#53|SMALL POLISHED STEEL|23|8 +Brand#53|SMALL POLISHED TIN|23|8 +Brand#53|SMALL POLISHED TIN|36|8 +Brand#53|STANDARD ANODIZED BRASS|14|8 +Brand#53|STANDARD ANODIZED BRASS|23|8 +Brand#53|STANDARD ANODIZED BRASS|45|8 +Brand#53|STANDARD ANODIZED COPPER|36|8 +Brand#53|STANDARD ANODIZED NICKEL|9|8 +Brand#53|STANDARD ANODIZED NICKEL|19|8 +Brand#53|STANDARD ANODIZED STEEL|9|8 +Brand#53|STANDARD ANODIZED STEEL|19|8 +Brand#53|STANDARD ANODIZED STEEL|45|8 +Brand#53|STANDARD ANODIZED TIN|14|8 +Brand#53|STANDARD ANODIZED TIN|49|8 +Brand#53|STANDARD BRUSHED BRASS|14|8 +Brand#53|STANDARD BRUSHED BRASS|19|8 +Brand#53|STANDARD BRUSHED COPPER|49|8 +Brand#53|STANDARD BRUSHED NICKEL|36|8 +Brand#53|STANDARD BRUSHED NICKEL|45|8 +Brand#53|STANDARD BRUSHED NICKEL|49|8 +Brand#53|STANDARD BRUSHED STEEL|23|8 +Brand#53|STANDARD BURNISHED BRASS|19|8 +Brand#53|STANDARD BURNISHED BRASS|49|8 +Brand#53|STANDARD BURNISHED COPPER|3|8 +Brand#53|STANDARD BURNISHED COPPER|23|8 +Brand#53|STANDARD BURNISHED COPPER|45|8 +Brand#53|STANDARD BURNISHED NICKEL|49|8 +Brand#53|STANDARD BURNISHED STEEL|19|8 +Brand#53|STANDARD BURNISHED STEEL|23|8 +Brand#53|STANDARD BURNISHED TIN|3|8 +Brand#53|STANDARD BURNISHED TIN|14|8 +Brand#53|STANDARD BURNISHED TIN|19|8 +Brand#53|STANDARD BURNISHED TIN|36|8 +Brand#53|STANDARD PLATED BRASS|19|8 +Brand#53|STANDARD PLATED COPPER|3|8 +Brand#53|STANDARD PLATED NICKEL|14|8 +Brand#53|STANDARD PLATED NICKEL|36|8 +Brand#53|STANDARD PLATED STEEL|14|8 +Brand#53|STANDARD PLATED STEEL|23|8 +Brand#53|STANDARD PLATED STEEL|45|8 +Brand#53|STANDARD PLATED TIN|9|8 +Brand#53|STANDARD PLATED TIN|14|8 +Brand#53|STANDARD PLATED TIN|19|8 +Brand#53|STANDARD PLATED TIN|23|8 +Brand#53|STANDARD POLISHED BRASS|36|8 +Brand#53|STANDARD POLISHED NICKEL|3|8 +Brand#53|STANDARD POLISHED NICKEL|36|8 +Brand#53|STANDARD POLISHED NICKEL|49|8 +Brand#53|STANDARD POLISHED TIN|9|8 +Brand#54|ECONOMY ANODIZED NICKEL|9|8 +Brand#54|ECONOMY ANODIZED NICKEL|23|8 +Brand#54|ECONOMY ANODIZED STEEL|19|8 +Brand#54|ECONOMY ANODIZED STEEL|23|8 +Brand#54|ECONOMY ANODIZED TIN|3|8 +Brand#54|ECONOMY ANODIZED TIN|45|8 +Brand#54|ECONOMY BRUSHED BRASS|14|8 +Brand#54|ECONOMY BRUSHED BRASS|19|8 +Brand#54|ECONOMY BRUSHED BRASS|23|8 +Brand#54|ECONOMY BRUSHED COPPER|9|8 +Brand#54|ECONOMY BRUSHED COPPER|45|8 +Brand#54|ECONOMY BRUSHED NICKEL|9|8 +Brand#54|ECONOMY BRUSHED NICKEL|23|8 +Brand#54|ECONOMY BRUSHED NICKEL|36|8 +Brand#54|ECONOMY BRUSHED NICKEL|49|8 +Brand#54|ECONOMY BRUSHED STEEL|3|8 +Brand#54|ECONOMY BRUSHED STEEL|14|8 +Brand#54|ECONOMY BURNISHED COPPER|9|8 +Brand#54|ECONOMY BURNISHED COPPER|36|8 +Brand#54|ECONOMY BURNISHED NICKEL|36|8 +Brand#54|ECONOMY BURNISHED STEEL|14|8 +Brand#54|ECONOMY BURNISHED STEEL|36|8 +Brand#54|ECONOMY BURNISHED TIN|9|8 +Brand#54|ECONOMY BURNISHED TIN|14|8 +Brand#54|ECONOMY BURNISHED TIN|23|8 +Brand#54|ECONOMY PLATED COPPER|14|8 +Brand#54|ECONOMY PLATED COPPER|19|8 +Brand#54|ECONOMY PLATED NICKEL|23|8 +Brand#54|ECONOMY PLATED NICKEL|45|8 +Brand#54|ECONOMY PLATED STEEL|3|8 +Brand#54|ECONOMY PLATED STEEL|19|8 +Brand#54|ECONOMY PLATED TIN|23|8 +Brand#54|ECONOMY POLISHED BRASS|23|8 +Brand#54|ECONOMY POLISHED BRASS|36|8 +Brand#54|ECONOMY POLISHED BRASS|49|8 +Brand#54|ECONOMY POLISHED COPPER|9|8 +Brand#54|ECONOMY POLISHED COPPER|19|8 +Brand#54|ECONOMY POLISHED COPPER|23|8 +Brand#54|ECONOMY POLISHED COPPER|45|8 +Brand#54|ECONOMY POLISHED STEEL|14|8 +Brand#54|ECONOMY POLISHED STEEL|19|8 +Brand#54|ECONOMY POLISHED STEEL|23|8 +Brand#54|LARGE ANODIZED COPPER|3|8 +Brand#54|LARGE ANODIZED COPPER|45|8 +Brand#54|LARGE ANODIZED STEEL|9|8 +Brand#54|LARGE ANODIZED STEEL|14|8 +Brand#54|LARGE ANODIZED TIN|23|8 +Brand#54|LARGE BRUSHED BRASS|3|8 +Brand#54|LARGE BRUSHED BRASS|14|8 +Brand#54|LARGE BRUSHED BRASS|45|8 +Brand#54|LARGE BRUSHED COPPER|14|8 +Brand#54|LARGE BRUSHED COPPER|45|8 +Brand#54|LARGE BRUSHED NICKEL|3|8 +Brand#54|LARGE BRUSHED STEEL|36|8 +Brand#54|LARGE BRUSHED STEEL|49|8 +Brand#54|LARGE BRUSHED TIN|36|8 +Brand#54|LARGE BURNISHED BRASS|23|8 +Brand#54|LARGE BURNISHED COPPER|49|8 +Brand#54|LARGE BURNISHED NICKEL|23|8 +Brand#54|LARGE BURNISHED NICKEL|49|8 +Brand#54|LARGE BURNISHED STEEL|49|8 +Brand#54|LARGE BURNISHED TIN|14|8 +Brand#54|LARGE BURNISHED TIN|49|8 +Brand#54|LARGE PLATED BRASS|23|8 +Brand#54|LARGE PLATED BRASS|45|8 +Brand#54|LARGE PLATED COPPER|49|8 +Brand#54|LARGE PLATED STEEL|3|8 +Brand#54|LARGE PLATED STEEL|9|8 +Brand#54|LARGE PLATED STEEL|19|8 +Brand#54|LARGE PLATED STEEL|36|8 +Brand#54|LARGE PLATED TIN|9|8 +Brand#54|LARGE POLISHED BRASS|49|8 +Brand#54|LARGE POLISHED COPPER|45|8 +Brand#54|LARGE POLISHED NICKEL|14|8 +Brand#54|LARGE POLISHED STEEL|3|8 +Brand#54|LARGE POLISHED STEEL|14|8 +Brand#54|LARGE POLISHED STEEL|19|8 +Brand#54|LARGE POLISHED TIN|36|8 +Brand#54|MEDIUM ANODIZED BRASS|9|8 +Brand#54|MEDIUM ANODIZED BRASS|36|8 +Brand#54|MEDIUM ANODIZED BRASS|49|8 +Brand#54|MEDIUM ANODIZED COPPER|23|8 +Brand#54|MEDIUM ANODIZED NICKEL|3|8 +Brand#54|MEDIUM ANODIZED NICKEL|14|8 +Brand#54|MEDIUM ANODIZED NICKEL|19|8 +Brand#54|MEDIUM ANODIZED NICKEL|36|8 +Brand#54|MEDIUM ANODIZED STEEL|3|8 +Brand#54|MEDIUM BRUSHED BRASS|3|8 +Brand#54|MEDIUM BRUSHED BRASS|14|8 +Brand#54|MEDIUM BRUSHED BRASS|19|8 +Brand#54|MEDIUM BRUSHED BRASS|45|8 +Brand#54|MEDIUM BRUSHED COPPER|14|8 +Brand#54|MEDIUM BRUSHED COPPER|19|8 +Brand#54|MEDIUM BRUSHED COPPER|23|8 +Brand#54|MEDIUM BRUSHED NICKEL|9|8 +Brand#54|MEDIUM BRUSHED STEEL|9|8 +Brand#54|MEDIUM BRUSHED STEEL|45|8 +Brand#54|MEDIUM BRUSHED TIN|14|8 +Brand#54|MEDIUM BRUSHED TIN|49|8 +Brand#54|MEDIUM BURNISHED BRASS|23|8 +Brand#54|MEDIUM BURNISHED BRASS|49|8 +Brand#54|MEDIUM BURNISHED COPPER|3|8 +Brand#54|MEDIUM BURNISHED COPPER|36|8 +Brand#54|MEDIUM BURNISHED COPPER|45|8 +Brand#54|MEDIUM BURNISHED STEEL|14|8 +Brand#54|MEDIUM BURNISHED STEEL|19|8 +Brand#54|MEDIUM BURNISHED TIN|9|8 +Brand#54|MEDIUM BURNISHED TIN|19|8 +Brand#54|MEDIUM BURNISHED TIN|36|8 +Brand#54|MEDIUM PLATED BRASS|3|8 +Brand#54|MEDIUM PLATED BRASS|23|8 +Brand#54|MEDIUM PLATED COPPER|9|8 +Brand#54|MEDIUM PLATED COPPER|45|8 +Brand#54|MEDIUM PLATED COPPER|49|8 +Brand#54|MEDIUM PLATED NICKEL|45|8 +Brand#54|MEDIUM PLATED TIN|19|8 +Brand#54|MEDIUM PLATED TIN|23|8 +Brand#54|PROMO ANODIZED BRASS|3|8 +Brand#54|PROMO ANODIZED BRASS|9|8 +Brand#54|PROMO ANODIZED BRASS|14|8 +Brand#54|PROMO ANODIZED BRASS|23|8 +Brand#54|PROMO ANODIZED BRASS|36|8 +Brand#54|PROMO ANODIZED COPPER|23|8 +Brand#54|PROMO ANODIZED STEEL|36|8 +Brand#54|PROMO ANODIZED TIN|19|8 +Brand#54|PROMO BRUSHED BRASS|23|8 +Brand#54|PROMO BRUSHED BRASS|49|8 +Brand#54|PROMO BRUSHED COPPER|3|8 +Brand#54|PROMO BRUSHED COPPER|45|8 +Brand#54|PROMO BRUSHED NICKEL|3|8 +Brand#54|PROMO BRUSHED NICKEL|23|8 +Brand#54|PROMO BRUSHED STEEL|36|8 +Brand#54|PROMO BRUSHED STEEL|49|8 +Brand#54|PROMO BRUSHED TIN|45|8 +Brand#54|PROMO BURNISHED COPPER|14|8 +Brand#54|PROMO BURNISHED NICKEL|19|8 +Brand#54|PROMO BURNISHED NICKEL|49|8 +Brand#54|PROMO BURNISHED STEEL|3|8 +Brand#54|PROMO BURNISHED TIN|3|8 +Brand#54|PROMO BURNISHED TIN|14|8 +Brand#54|PROMO BURNISHED TIN|45|8 +Brand#54|PROMO PLATED COPPER|36|8 +Brand#54|PROMO PLATED COPPER|45|8 +Brand#54|PROMO PLATED COPPER|49|8 +Brand#54|PROMO PLATED NICKEL|3|8 +Brand#54|PROMO PLATED NICKEL|14|8 +Brand#54|PROMO PLATED NICKEL|19|8 +Brand#54|PROMO PLATED STEEL|45|8 +Brand#54|PROMO PLATED TIN|14|8 +Brand#54|PROMO PLATED TIN|23|8 +Brand#54|PROMO POLISHED BRASS|14|8 +Brand#54|PROMO POLISHED BRASS|36|8 +Brand#54|PROMO POLISHED COPPER|14|8 +Brand#54|PROMO POLISHED COPPER|23|8 +Brand#54|PROMO POLISHED NICKEL|14|8 +Brand#54|PROMO POLISHED NICKEL|19|8 +Brand#54|PROMO POLISHED NICKEL|23|8 +Brand#54|PROMO POLISHED STEEL|23|8 +Brand#54|PROMO POLISHED STEEL|36|8 +Brand#54|PROMO POLISHED TIN|49|8 +Brand#54|SMALL ANODIZED BRASS|19|8 +Brand#54|SMALL ANODIZED COPPER|23|8 +Brand#54|SMALL ANODIZED COPPER|45|8 +Brand#54|SMALL ANODIZED NICKEL|14|8 +Brand#54|SMALL ANODIZED STEEL|9|8 +Brand#54|SMALL ANODIZED TIN|14|8 +Brand#54|SMALL BRUSHED BRASS|9|8 +Brand#54|SMALL BRUSHED BRASS|49|8 +Brand#54|SMALL BRUSHED COPPER|45|8 +Brand#54|SMALL BRUSHED TIN|19|8 +Brand#54|SMALL BRUSHED TIN|36|8 +Brand#54|SMALL BURNISHED BRASS|9|8 +Brand#54|SMALL BURNISHED BRASS|14|8 +Brand#54|SMALL BURNISHED COPPER|3|8 +Brand#54|SMALL BURNISHED COPPER|14|8 +Brand#54|SMALL BURNISHED STEEL|9|8 +Brand#54|SMALL BURNISHED TIN|23|8 +Brand#54|SMALL BURNISHED TIN|49|8 +Brand#54|SMALL PLATED COPPER|14|8 +Brand#54|SMALL PLATED COPPER|23|8 +Brand#54|SMALL PLATED COPPER|45|8 +Brand#54|SMALL PLATED NICKEL|14|8 +Brand#54|SMALL PLATED STEEL|49|8 +Brand#54|SMALL PLATED TIN|14|8 +Brand#54|SMALL PLATED TIN|23|8 +Brand#54|SMALL PLATED TIN|36|8 +Brand#54|SMALL POLISHED BRASS|9|8 +Brand#54|SMALL POLISHED BRASS|36|8 +Brand#54|SMALL POLISHED COPPER|3|8 +Brand#54|SMALL POLISHED COPPER|49|8 +Brand#54|SMALL POLISHED NICKEL|3|8 +Brand#54|SMALL POLISHED NICKEL|14|8 +Brand#54|SMALL POLISHED NICKEL|23|8 +Brand#54|SMALL POLISHED STEEL|3|8 +Brand#54|SMALL POLISHED STEEL|23|8 +Brand#54|SMALL POLISHED TIN|45|8 +Brand#54|STANDARD ANODIZED BRASS|9|8 +Brand#54|STANDARD ANODIZED BRASS|45|8 +Brand#54|STANDARD ANODIZED COPPER|9|8 +Brand#54|STANDARD ANODIZED COPPER|23|8 +Brand#54|STANDARD ANODIZED STEEL|3|8 +Brand#54|STANDARD ANODIZED STEEL|14|8 +Brand#54|STANDARD ANODIZED STEEL|23|8 +Brand#54|STANDARD ANODIZED TIN|45|8 +Brand#54|STANDARD BRUSHED BRASS|36|8 +Brand#54|STANDARD BRUSHED COPPER|36|8 +Brand#54|STANDARD BRUSHED NICKEL|14|8 +Brand#54|STANDARD BRUSHED NICKEL|49|8 +Brand#54|STANDARD BRUSHED STEEL|9|8 +Brand#54|STANDARD BRUSHED STEEL|36|8 +Brand#54|STANDARD BRUSHED TIN|19|8 +Brand#54|STANDARD BRUSHED TIN|23|8 +Brand#54|STANDARD BRUSHED TIN|49|8 +Brand#54|STANDARD BURNISHED BRASS|45|8 +Brand#54|STANDARD BURNISHED COPPER|9|8 +Brand#54|STANDARD BURNISHED COPPER|19|8 +Brand#54|STANDARD BURNISHED NICKEL|23|8 +Brand#54|STANDARD BURNISHED STEEL|14|8 +Brand#54|STANDARD PLATED BRASS|3|8 +Brand#54|STANDARD PLATED BRASS|23|8 +Brand#54|STANDARD PLATED COPPER|36|8 +Brand#54|STANDARD PLATED NICKEL|36|8 +Brand#54|STANDARD PLATED STEEL|45|8 +Brand#54|STANDARD PLATED TIN|49|8 +Brand#54|STANDARD POLISHED BRASS|49|8 +Brand#54|STANDARD POLISHED COPPER|19|8 +Brand#54|STANDARD POLISHED COPPER|23|8 +Brand#54|STANDARD POLISHED NICKEL|36|8 +Brand#54|STANDARD POLISHED STEEL|19|8 +Brand#54|STANDARD POLISHED TIN|9|8 +Brand#54|STANDARD POLISHED TIN|14|8 +Brand#55|ECONOMY ANODIZED COPPER|23|8 +Brand#55|ECONOMY ANODIZED NICKEL|9|8 +Brand#55|ECONOMY ANODIZED NICKEL|14|8 +Brand#55|ECONOMY ANODIZED NICKEL|45|8 +Brand#55|ECONOMY ANODIZED STEEL|9|8 +Brand#55|ECONOMY ANODIZED STEEL|49|8 +Brand#55|ECONOMY ANODIZED TIN|9|8 +Brand#55|ECONOMY ANODIZED TIN|14|8 +Brand#55|ECONOMY ANODIZED TIN|19|8 +Brand#55|ECONOMY ANODIZED TIN|23|8 +Brand#55|ECONOMY ANODIZED TIN|36|8 +Brand#55|ECONOMY BRUSHED COPPER|23|8 +Brand#55|ECONOMY BRUSHED STEEL|49|8 +Brand#55|ECONOMY BRUSHED TIN|3|8 +Brand#55|ECONOMY BRUSHED TIN|23|8 +Brand#55|ECONOMY BURNISHED BRASS|3|8 +Brand#55|ECONOMY BURNISHED BRASS|14|8 +Brand#55|ECONOMY BURNISHED COPPER|23|8 +Brand#55|ECONOMY BURNISHED NICKEL|19|8 +Brand#55|ECONOMY BURNISHED NICKEL|49|8 +Brand#55|ECONOMY BURNISHED STEEL|9|8 +Brand#55|ECONOMY BURNISHED STEEL|19|8 +Brand#55|ECONOMY BURNISHED STEEL|49|8 +Brand#55|ECONOMY BURNISHED TIN|45|8 +Brand#55|ECONOMY PLATED BRASS|45|8 +Brand#55|ECONOMY PLATED COPPER|49|8 +Brand#55|ECONOMY PLATED NICKEL|19|8 +Brand#55|ECONOMY PLATED NICKEL|36|8 +Brand#55|ECONOMY PLATED TIN|23|8 +Brand#55|ECONOMY POLISHED BRASS|19|8 +Brand#55|ECONOMY POLISHED BRASS|23|8 +Brand#55|ECONOMY POLISHED COPPER|23|8 +Brand#55|ECONOMY POLISHED COPPER|45|8 +Brand#55|ECONOMY POLISHED NICKEL|9|8 +Brand#55|ECONOMY POLISHED NICKEL|14|8 +Brand#55|ECONOMY POLISHED NICKEL|19|8 +Brand#55|ECONOMY POLISHED NICKEL|45|8 +Brand#55|ECONOMY POLISHED TIN|9|8 +Brand#55|LARGE ANODIZED BRASS|36|8 +Brand#55|LARGE ANODIZED COPPER|9|8 +Brand#55|LARGE ANODIZED COPPER|36|8 +Brand#55|LARGE ANODIZED COPPER|45|8 +Brand#55|LARGE ANODIZED NICKEL|36|8 +Brand#55|LARGE ANODIZED STEEL|9|8 +Brand#55|LARGE ANODIZED TIN|14|8 +Brand#55|LARGE BRUSHED COPPER|9|8 +Brand#55|LARGE BRUSHED COPPER|19|8 +Brand#55|LARGE BRUSHED NICKEL|14|8 +Brand#55|LARGE BRUSHED TIN|9|8 +Brand#55|LARGE BURNISHED BRASS|3|8 +Brand#55|LARGE BURNISHED BRASS|49|8 +Brand#55|LARGE BURNISHED COPPER|36|8 +Brand#55|LARGE BURNISHED COPPER|49|8 +Brand#55|LARGE BURNISHED NICKEL|19|8 +Brand#55|LARGE BURNISHED NICKEL|45|8 +Brand#55|LARGE BURNISHED STEEL|3|8 +Brand#55|LARGE BURNISHED STEEL|23|8 +Brand#55|LARGE PLATED COPPER|14|8 +Brand#55|LARGE PLATED NICKEL|9|8 +Brand#55|LARGE PLATED STEEL|19|8 +Brand#55|LARGE PLATED STEEL|36|8 +Brand#55|LARGE PLATED STEEL|49|8 +Brand#55|LARGE PLATED TIN|9|8 +Brand#55|LARGE PLATED TIN|14|8 +Brand#55|LARGE PLATED TIN|36|8 +Brand#55|LARGE PLATED TIN|45|8 +Brand#55|LARGE POLISHED BRASS|3|8 +Brand#55|LARGE POLISHED COPPER|9|8 +Brand#55|LARGE POLISHED COPPER|36|8 +Brand#55|LARGE POLISHED TIN|9|8 +Brand#55|LARGE POLISHED TIN|45|8 +Brand#55|MEDIUM ANODIZED BRASS|23|8 +Brand#55|MEDIUM ANODIZED COPPER|14|8 +Brand#55|MEDIUM ANODIZED COPPER|49|8 +Brand#55|MEDIUM ANODIZED NICKEL|14|8 +Brand#55|MEDIUM ANODIZED NICKEL|19|8 +Brand#55|MEDIUM ANODIZED NICKEL|45|8 +Brand#55|MEDIUM ANODIZED STEEL|3|8 +Brand#55|MEDIUM ANODIZED STEEL|14|8 +Brand#55|MEDIUM ANODIZED TIN|45|8 +Brand#55|MEDIUM BRUSHED COPPER|23|8 +Brand#55|MEDIUM BRUSHED NICKEL|9|8 +Brand#55|MEDIUM BRUSHED NICKEL|36|8 +Brand#55|MEDIUM BRUSHED STEEL|14|8 +Brand#55|MEDIUM BRUSHED STEEL|36|8 +Brand#55|MEDIUM BRUSHED STEEL|49|8 +Brand#55|MEDIUM BRUSHED TIN|45|8 +Brand#55|MEDIUM BURNISHED COPPER|23|8 +Brand#55|MEDIUM BURNISHED NICKEL|23|8 +Brand#55|MEDIUM BURNISHED STEEL|14|8 +Brand#55|MEDIUM BURNISHED STEEL|36|8 +Brand#55|MEDIUM BURNISHED STEEL|49|8 +Brand#55|MEDIUM BURNISHED TIN|45|8 +Brand#55|MEDIUM PLATED BRASS|23|8 +Brand#55|MEDIUM PLATED COPPER|9|8 +Brand#55|MEDIUM PLATED COPPER|45|8 +Brand#55|MEDIUM PLATED NICKEL|49|8 +Brand#55|MEDIUM PLATED TIN|3|8 +Brand#55|MEDIUM PLATED TIN|14|8 +Brand#55|MEDIUM PLATED TIN|36|8 +Brand#55|PROMO ANODIZED BRASS|45|8 +Brand#55|PROMO ANODIZED BRASS|49|8 +Brand#55|PROMO ANODIZED COPPER|3|8 +Brand#55|PROMO ANODIZED COPPER|45|8 +Brand#55|PROMO ANODIZED COPPER|49|8 +Brand#55|PROMO ANODIZED NICKEL|3|8 +Brand#55|PROMO ANODIZED NICKEL|14|8 +Brand#55|PROMO ANODIZED NICKEL|36|8 +Brand#55|PROMO ANODIZED STEEL|3|8 +Brand#55|PROMO ANODIZED STEEL|36|8 +Brand#55|PROMO ANODIZED STEEL|49|8 +Brand#55|PROMO ANODIZED TIN|36|8 +Brand#55|PROMO ANODIZED TIN|49|8 +Brand#55|PROMO BRUSHED BRASS|9|8 +Brand#55|PROMO BRUSHED COPPER|9|8 +Brand#55|PROMO BRUSHED NICKEL|36|8 +Brand#55|PROMO BRUSHED NICKEL|49|8 +Brand#55|PROMO BRUSHED STEEL|3|8 +Brand#55|PROMO BRUSHED STEEL|9|8 +Brand#55|PROMO BRUSHED STEEL|36|8 +Brand#55|PROMO BRUSHED STEEL|45|8 +Brand#55|PROMO BRUSHED TIN|49|8 +Brand#55|PROMO BURNISHED BRASS|49|8 +Brand#55|PROMO BURNISHED COPPER|14|8 +Brand#55|PROMO BURNISHED STEEL|9|8 +Brand#55|PROMO BURNISHED TIN|45|8 +Brand#55|PROMO BURNISHED TIN|49|8 +Brand#55|PROMO PLATED BRASS|9|8 +Brand#55|PROMO PLATED BRASS|36|8 +Brand#55|PROMO PLATED BRASS|45|8 +Brand#55|PROMO PLATED COPPER|14|8 +Brand#55|PROMO PLATED COPPER|23|8 +Brand#55|PROMO PLATED NICKEL|14|8 +Brand#55|PROMO PLATED NICKEL|49|8 +Brand#55|PROMO PLATED TIN|36|8 +Brand#55|PROMO PLATED TIN|45|8 +Brand#55|PROMO POLISHED BRASS|3|8 +Brand#55|PROMO POLISHED COPPER|36|8 +Brand#55|PROMO POLISHED STEEL|3|8 +Brand#55|PROMO POLISHED STEEL|14|8 +Brand#55|PROMO POLISHED STEEL|36|8 +Brand#55|SMALL ANODIZED BRASS|19|8 +Brand#55|SMALL ANODIZED COPPER|14|8 +Brand#55|SMALL ANODIZED NICKEL|3|8 +Brand#55|SMALL ANODIZED STEEL|14|8 +Brand#55|SMALL ANODIZED STEEL|19|8 +Brand#55|SMALL ANODIZED STEEL|49|8 +Brand#55|SMALL ANODIZED TIN|3|8 +Brand#55|SMALL BRUSHED BRASS|19|8 +Brand#55|SMALL BRUSHED BRASS|49|8 +Brand#55|SMALL BRUSHED COPPER|14|8 +Brand#55|SMALL BRUSHED COPPER|36|8 +Brand#55|SMALL BRUSHED COPPER|45|8 +Brand#55|SMALL BRUSHED TIN|23|8 +Brand#55|SMALL BURNISHED BRASS|9|8 +Brand#55|SMALL BURNISHED COPPER|45|8 +Brand#55|SMALL BURNISHED NICKEL|3|8 +Brand#55|SMALL BURNISHED STEEL|19|8 +Brand#55|SMALL BURNISHED STEEL|23|8 +Brand#55|SMALL BURNISHED TIN|3|8 +Brand#55|SMALL BURNISHED TIN|14|8 +Brand#55|SMALL BURNISHED TIN|19|8 +Brand#55|SMALL BURNISHED TIN|36|8 +Brand#55|SMALL PLATED BRASS|45|8 +Brand#55|SMALL PLATED COPPER|9|8 +Brand#55|SMALL PLATED COPPER|19|8 +Brand#55|SMALL PLATED COPPER|23|8 +Brand#55|SMALL PLATED COPPER|45|8 +Brand#55|SMALL PLATED NICKEL|9|8 +Brand#55|SMALL PLATED NICKEL|23|8 +Brand#55|SMALL PLATED STEEL|49|8 +Brand#55|SMALL PLATED TIN|3|8 +Brand#55|SMALL PLATED TIN|9|8 +Brand#55|SMALL PLATED TIN|14|8 +Brand#55|SMALL PLATED TIN|49|8 +Brand#55|SMALL POLISHED BRASS|14|8 +Brand#55|SMALL POLISHED COPPER|3|8 +Brand#55|SMALL POLISHED TIN|19|8 +Brand#55|SMALL POLISHED TIN|49|8 +Brand#55|STANDARD ANODIZED BRASS|14|8 +Brand#55|STANDARD ANODIZED BRASS|36|8 +Brand#55|STANDARD ANODIZED COPPER|23|8 +Brand#55|STANDARD ANODIZED NICKEL|23|8 +Brand#55|STANDARD ANODIZED TIN|19|8 +Brand#55|STANDARD ANODIZED TIN|49|8 +Brand#55|STANDARD BRUSHED BRASS|3|8 +Brand#55|STANDARD BRUSHED BRASS|36|8 +Brand#55|STANDARD BRUSHED BRASS|45|8 +Brand#55|STANDARD BRUSHED COPPER|3|8 +Brand#55|STANDARD BRUSHED COPPER|23|8 +Brand#55|STANDARD BRUSHED NICKEL|19|8 +Brand#55|STANDARD BRUSHED TIN|23|8 +Brand#55|STANDARD BURNISHED BRASS|49|8 +Brand#55|STANDARD BURNISHED COPPER|23|8 +Brand#55|STANDARD BURNISHED COPPER|36|8 +Brand#55|STANDARD BURNISHED NICKEL|3|8 +Brand#55|STANDARD BURNISHED NICKEL|14|8 +Brand#55|STANDARD BURNISHED NICKEL|36|8 +Brand#55|STANDARD BURNISHED NICKEL|45|8 +Brand#55|STANDARD BURNISHED STEEL|14|8 +Brand#55|STANDARD BURNISHED STEEL|49|8 +Brand#55|STANDARD PLATED BRASS|19|8 +Brand#55|STANDARD PLATED BRASS|23|8 +Brand#55|STANDARD PLATED COPPER|23|8 +Brand#55|STANDARD PLATED NICKEL|49|8 +Brand#55|STANDARD PLATED TIN|23|8 +Brand#55|STANDARD POLISHED BRASS|19|8 +Brand#55|STANDARD POLISHED BRASS|49|8 +Brand#55|STANDARD POLISHED COPPER|9|8 +Brand#55|STANDARD POLISHED COPPER|36|8 +Brand#55|STANDARD POLISHED STEEL|9|8 +Brand#55|STANDARD POLISHED STEEL|36|8 +Brand#55|STANDARD POLISHED STEEL|45|8 +Brand#55|STANDARD POLISHED STEEL|49|8 +Brand#12|PROMO ANODIZED NICKEL|49|7 +Brand#13|LARGE PLATED TIN|23|7 +Brand#14|PROMO PLATED BRASS|19|7 +Brand#22|STANDARD POLISHED TIN|3|7 +Brand#23|ECONOMY PLATED NICKEL|19|7 +Brand#23|LARGE BURNISHED NICKEL|14|7 +Brand#24|PROMO BRUSHED NICKEL|14|7 +Brand#31|MEDIUM BURNISHED NICKEL|23|7 +Brand#32|LARGE BRUSHED COPPER|3|7 +Brand#32|LARGE POLISHED NICKEL|23|7 +Brand#32|STANDARD BURNISHED STEEL|19|7 +Brand#33|ECONOMY BRUSHED BRASS|3|7 +Brand#33|PROMO PLATED NICKEL|9|7 +Brand#33|SMALL ANODIZED COPPER|23|7 +Brand#41|ECONOMY BRUSHED COPPER|36|7 +Brand#41|PROMO POLISHED BRASS|45|7 +Brand#42|MEDIUM PLATED STEEL|45|7 +Brand#42|STANDARD PLATED COPPER|19|7 +Brand#43|LARGE POLISHED COPPER|19|7 +Brand#44|PROMO BURNISHED STEEL|45|7 +Brand#51|STANDARD PLATED TIN|45|7 +Brand#52|STANDARD ANODIZED STEEL|14|7 +Brand#53|STANDARD ANODIZED NICKEL|14|7 +Brand#55|ECONOMY POLISHED TIN|19|7 +Brand#55|SMALL BURNISHED STEEL|3|7 +Brand#32|MEDIUM BURNISHED STEEL|3|6 +Brand#11|ECONOMY ANODIZED BRASS|3|4 +Brand#11|ECONOMY ANODIZED BRASS|45|4 +Brand#11|ECONOMY ANODIZED COPPER|3|4 +Brand#11|ECONOMY ANODIZED COPPER|19|4 +Brand#11|ECONOMY ANODIZED COPPER|36|4 +Brand#11|ECONOMY ANODIZED COPPER|45|4 +Brand#11|ECONOMY ANODIZED STEEL|9|4 +Brand#11|ECONOMY ANODIZED STEEL|14|4 +Brand#11|ECONOMY ANODIZED STEEL|23|4 +Brand#11|ECONOMY ANODIZED STEEL|45|4 +Brand#11|ECONOMY ANODIZED STEEL|49|4 +Brand#11|ECONOMY ANODIZED TIN|3|4 +Brand#11|ECONOMY ANODIZED TIN|9|4 +Brand#11|ECONOMY ANODIZED TIN|49|4 +Brand#11|ECONOMY BRUSHED BRASS|3|4 +Brand#11|ECONOMY BRUSHED BRASS|19|4 +Brand#11|ECONOMY BRUSHED COPPER|3|4 +Brand#11|ECONOMY BRUSHED COPPER|19|4 +Brand#11|ECONOMY BRUSHED NICKEL|14|4 +Brand#11|ECONOMY BRUSHED STEEL|3|4 +Brand#11|ECONOMY BRUSHED STEEL|36|4 +Brand#11|ECONOMY BRUSHED TIN|23|4 +Brand#11|ECONOMY BRUSHED TIN|45|4 +Brand#11|ECONOMY BURNISHED BRASS|3|4 +Brand#11|ECONOMY BURNISHED BRASS|9|4 +Brand#11|ECONOMY BURNISHED BRASS|14|4 +Brand#11|ECONOMY BURNISHED BRASS|19|4 +Brand#11|ECONOMY BURNISHED BRASS|49|4 +Brand#11|ECONOMY BURNISHED COPPER|14|4 +Brand#11|ECONOMY BURNISHED COPPER|23|4 +Brand#11|ECONOMY BURNISHED COPPER|36|4 +Brand#11|ECONOMY BURNISHED NICKEL|9|4 +Brand#11|ECONOMY BURNISHED NICKEL|49|4 +Brand#11|ECONOMY BURNISHED STEEL|14|4 +Brand#11|ECONOMY BURNISHED TIN|19|4 +Brand#11|ECONOMY BURNISHED TIN|23|4 +Brand#11|ECONOMY BURNISHED TIN|45|4 +Brand#11|ECONOMY PLATED BRASS|3|4 +Brand#11|ECONOMY PLATED BRASS|9|4 +Brand#11|ECONOMY PLATED BRASS|36|4 +Brand#11|ECONOMY PLATED BRASS|49|4 +Brand#11|ECONOMY PLATED COPPER|36|4 +Brand#11|ECONOMY PLATED NICKEL|3|4 +Brand#11|ECONOMY PLATED NICKEL|49|4 +Brand#11|ECONOMY PLATED STEEL|3|4 +Brand#11|ECONOMY PLATED STEEL|14|4 +Brand#11|ECONOMY PLATED STEEL|19|4 +Brand#11|ECONOMY PLATED STEEL|49|4 +Brand#11|ECONOMY PLATED TIN|3|4 +Brand#11|ECONOMY PLATED TIN|9|4 +Brand#11|ECONOMY PLATED TIN|19|4 +Brand#11|ECONOMY PLATED TIN|36|4 +Brand#11|ECONOMY POLISHED BRASS|9|4 +Brand#11|ECONOMY POLISHED BRASS|19|4 +Brand#11|ECONOMY POLISHED BRASS|23|4 +Brand#11|ECONOMY POLISHED BRASS|36|4 +Brand#11|ECONOMY POLISHED BRASS|49|4 +Brand#11|ECONOMY POLISHED COPPER|3|4 +Brand#11|ECONOMY POLISHED COPPER|19|4 +Brand#11|ECONOMY POLISHED COPPER|23|4 +Brand#11|ECONOMY POLISHED NICKEL|36|4 +Brand#11|ECONOMY POLISHED NICKEL|49|4 +Brand#11|ECONOMY POLISHED STEEL|9|4 +Brand#11|ECONOMY POLISHED STEEL|14|4 +Brand#11|ECONOMY POLISHED STEEL|23|4 +Brand#11|ECONOMY POLISHED STEEL|36|4 +Brand#11|ECONOMY POLISHED STEEL|45|4 +Brand#11|ECONOMY POLISHED TIN|49|4 +Brand#11|LARGE ANODIZED BRASS|3|4 +Brand#11|LARGE ANODIZED BRASS|9|4 +Brand#11|LARGE ANODIZED BRASS|19|4 +Brand#11|LARGE ANODIZED BRASS|23|4 +Brand#11|LARGE ANODIZED COPPER|3|4 +Brand#11|LARGE ANODIZED COPPER|9|4 +Brand#11|LARGE ANODIZED COPPER|36|4 +Brand#11|LARGE ANODIZED COPPER|45|4 +Brand#11|LARGE ANODIZED NICKEL|23|4 +Brand#11|LARGE ANODIZED STEEL|14|4 +Brand#11|LARGE ANODIZED STEEL|49|4 +Brand#11|LARGE ANODIZED TIN|3|4 +Brand#11|LARGE ANODIZED TIN|9|4 +Brand#11|LARGE ANODIZED TIN|14|4 +Brand#11|LARGE ANODIZED TIN|19|4 +Brand#11|LARGE BRUSHED BRASS|36|4 +Brand#11|LARGE BRUSHED BRASS|45|4 +Brand#11|LARGE BRUSHED COPPER|3|4 +Brand#11|LARGE BRUSHED NICKEL|9|4 +Brand#11|LARGE BRUSHED NICKEL|36|4 +Brand#11|LARGE BRUSHED NICKEL|49|4 +Brand#11|LARGE BRUSHED STEEL|3|4 +Brand#11|LARGE BRUSHED STEEL|9|4 +Brand#11|LARGE BRUSHED STEEL|23|4 +Brand#11|LARGE BRUSHED STEEL|45|4 +Brand#11|LARGE BRUSHED TIN|3|4 +Brand#11|LARGE BURNISHED BRASS|9|4 +Brand#11|LARGE BURNISHED BRASS|19|4 +Brand#11|LARGE BURNISHED BRASS|36|4 +Brand#11|LARGE BURNISHED COPPER|19|4 +Brand#11|LARGE BURNISHED COPPER|45|4 +Brand#11|LARGE BURNISHED NICKEL|3|4 +Brand#11|LARGE BURNISHED NICKEL|49|4 +Brand#11|LARGE BURNISHED STEEL|14|4 +Brand#11|LARGE BURNISHED STEEL|23|4 +Brand#11|LARGE BURNISHED STEEL|45|4 +Brand#11|LARGE BURNISHED TIN|3|4 +Brand#11|LARGE BURNISHED TIN|9|4 +Brand#11|LARGE BURNISHED TIN|36|4 +Brand#11|LARGE BURNISHED TIN|45|4 +Brand#11|LARGE PLATED BRASS|9|4 +Brand#11|LARGE PLATED BRASS|36|4 +Brand#11|LARGE PLATED BRASS|45|4 +Brand#11|LARGE PLATED BRASS|49|4 +Brand#11|LARGE PLATED COPPER|3|4 +Brand#11|LARGE PLATED COPPER|9|4 +Brand#11|LARGE PLATED COPPER|14|4 +Brand#11|LARGE PLATED COPPER|19|4 +Brand#11|LARGE PLATED COPPER|23|4 +Brand#11|LARGE PLATED COPPER|36|4 +Brand#11|LARGE PLATED COPPER|45|4 +Brand#11|LARGE PLATED COPPER|49|4 +Brand#11|LARGE PLATED NICKEL|9|4 +Brand#11|LARGE PLATED NICKEL|14|4 +Brand#11|LARGE PLATED NICKEL|19|4 +Brand#11|LARGE PLATED NICKEL|49|4 +Brand#11|LARGE PLATED STEEL|9|4 +Brand#11|LARGE PLATED STEEL|49|4 +Brand#11|LARGE PLATED TIN|23|4 +Brand#11|LARGE PLATED TIN|45|4 +Brand#11|LARGE POLISHED BRASS|3|4 +Brand#11|LARGE POLISHED BRASS|14|4 +Brand#11|LARGE POLISHED BRASS|19|4 +Brand#11|LARGE POLISHED BRASS|23|4 +Brand#11|LARGE POLISHED BRASS|45|4 +Brand#11|LARGE POLISHED COPPER|3|4 +Brand#11|LARGE POLISHED COPPER|19|4 +Brand#11|LARGE POLISHED NICKEL|49|4 +Brand#11|LARGE POLISHED STEEL|14|4 +Brand#11|LARGE POLISHED STEEL|23|4 +Brand#11|LARGE POLISHED STEEL|45|4 +Brand#11|LARGE POLISHED TIN|9|4 +Brand#11|LARGE POLISHED TIN|14|4 +Brand#11|LARGE POLISHED TIN|45|4 +Brand#11|LARGE POLISHED TIN|49|4 +Brand#11|MEDIUM ANODIZED BRASS|19|4 +Brand#11|MEDIUM ANODIZED COPPER|3|4 +Brand#11|MEDIUM ANODIZED COPPER|45|4 +Brand#11|MEDIUM ANODIZED COPPER|49|4 +Brand#11|MEDIUM ANODIZED STEEL|14|4 +Brand#11|MEDIUM ANODIZED STEEL|23|4 +Brand#11|MEDIUM ANODIZED TIN|14|4 +Brand#11|MEDIUM ANODIZED TIN|19|4 +Brand#11|MEDIUM BRUSHED BRASS|14|4 +Brand#11|MEDIUM BRUSHED BRASS|36|4 +Brand#11|MEDIUM BRUSHED BRASS|49|4 +Brand#11|MEDIUM BRUSHED COPPER|36|4 +Brand#11|MEDIUM BRUSHED COPPER|49|4 +Brand#11|MEDIUM BRUSHED NICKEL|3|4 +Brand#11|MEDIUM BRUSHED NICKEL|19|4 +Brand#11|MEDIUM BRUSHED NICKEL|49|4 +Brand#11|MEDIUM BRUSHED STEEL|14|4 +Brand#11|MEDIUM BRUSHED TIN|3|4 +Brand#11|MEDIUM BRUSHED TIN|9|4 +Brand#11|MEDIUM BRUSHED TIN|49|4 +Brand#11|MEDIUM BURNISHED BRASS|9|4 +Brand#11|MEDIUM BURNISHED BRASS|14|4 +Brand#11|MEDIUM BURNISHED BRASS|36|4 +Brand#11|MEDIUM BURNISHED COPPER|3|4 +Brand#11|MEDIUM BURNISHED COPPER|36|4 +Brand#11|MEDIUM BURNISHED NICKEL|14|4 +Brand#11|MEDIUM BURNISHED NICKEL|19|4 +Brand#11|MEDIUM BURNISHED NICKEL|36|4 +Brand#11|MEDIUM BURNISHED NICKEL|45|4 +Brand#11|MEDIUM BURNISHED STEEL|23|4 +Brand#11|MEDIUM BURNISHED STEEL|45|4 +Brand#11|MEDIUM BURNISHED STEEL|49|4 +Brand#11|MEDIUM BURNISHED TIN|23|4 +Brand#11|MEDIUM BURNISHED TIN|45|4 +Brand#11|MEDIUM PLATED BRASS|19|4 +Brand#11|MEDIUM PLATED COPPER|23|4 +Brand#11|MEDIUM PLATED COPPER|45|4 +Brand#11|MEDIUM PLATED COPPER|49|4 +Brand#11|MEDIUM PLATED NICKEL|36|4 +Brand#11|MEDIUM PLATED NICKEL|49|4 +Brand#11|MEDIUM PLATED STEEL|49|4 +Brand#11|MEDIUM PLATED TIN|36|4 +Brand#11|MEDIUM PLATED TIN|49|4 +Brand#11|PROMO ANODIZED BRASS|3|4 +Brand#11|PROMO ANODIZED BRASS|9|4 +Brand#11|PROMO ANODIZED BRASS|14|4 +Brand#11|PROMO ANODIZED BRASS|23|4 +Brand#11|PROMO ANODIZED COPPER|3|4 +Brand#11|PROMO ANODIZED COPPER|23|4 +Brand#11|PROMO ANODIZED COPPER|45|4 +Brand#11|PROMO ANODIZED NICKEL|14|4 +Brand#11|PROMO ANODIZED NICKEL|19|4 +Brand#11|PROMO ANODIZED NICKEL|23|4 +Brand#11|PROMO ANODIZED NICKEL|49|4 +Brand#11|PROMO ANODIZED STEEL|9|4 +Brand#11|PROMO ANODIZED STEEL|14|4 +Brand#11|PROMO ANODIZED TIN|14|4 +Brand#11|PROMO ANODIZED TIN|45|4 +Brand#11|PROMO BRUSHED BRASS|9|4 +Brand#11|PROMO BRUSHED BRASS|14|4 +Brand#11|PROMO BRUSHED BRASS|19|4 +Brand#11|PROMO BRUSHED BRASS|23|4 +Brand#11|PROMO BRUSHED BRASS|45|4 +Brand#11|PROMO BRUSHED COPPER|3|4 +Brand#11|PROMO BRUSHED COPPER|23|4 +Brand#11|PROMO BRUSHED COPPER|45|4 +Brand#11|PROMO BRUSHED COPPER|49|4 +Brand#11|PROMO BRUSHED NICKEL|3|4 +Brand#11|PROMO BRUSHED NICKEL|14|4 +Brand#11|PROMO BRUSHED NICKEL|23|4 +Brand#11|PROMO BRUSHED NICKEL|45|4 +Brand#11|PROMO BRUSHED NICKEL|49|4 +Brand#11|PROMO BRUSHED STEEL|3|4 +Brand#11|PROMO BRUSHED STEEL|14|4 +Brand#11|PROMO BRUSHED STEEL|19|4 +Brand#11|PROMO BRUSHED TIN|3|4 +Brand#11|PROMO BRUSHED TIN|9|4 +Brand#11|PROMO BRUSHED TIN|23|4 +Brand#11|PROMO BRUSHED TIN|49|4 +Brand#11|PROMO BURNISHED BRASS|14|4 +Brand#11|PROMO BURNISHED BRASS|45|4 +Brand#11|PROMO BURNISHED COPPER|9|4 +Brand#11|PROMO BURNISHED COPPER|19|4 +Brand#11|PROMO BURNISHED COPPER|36|4 +Brand#11|PROMO BURNISHED NICKEL|9|4 +Brand#11|PROMO BURNISHED NICKEL|19|4 +Brand#11|PROMO BURNISHED NICKEL|49|4 +Brand#11|PROMO BURNISHED STEEL|3|4 +Brand#11|PROMO BURNISHED STEEL|9|4 +Brand#11|PROMO BURNISHED TIN|3|4 +Brand#11|PROMO BURNISHED TIN|9|4 +Brand#11|PROMO BURNISHED TIN|14|4 +Brand#11|PROMO BURNISHED TIN|19|4 +Brand#11|PROMO BURNISHED TIN|49|4 +Brand#11|PROMO PLATED BRASS|3|4 +Brand#11|PROMO PLATED BRASS|9|4 +Brand#11|PROMO PLATED BRASS|36|4 +Brand#11|PROMO PLATED COPPER|9|4 +Brand#11|PROMO PLATED COPPER|23|4 +Brand#11|PROMO PLATED NICKEL|19|4 +Brand#11|PROMO PLATED NICKEL|23|4 +Brand#11|PROMO PLATED NICKEL|36|4 +Brand#11|PROMO PLATED NICKEL|45|4 +Brand#11|PROMO PLATED STEEL|36|4 +Brand#11|PROMO PLATED STEEL|45|4 +Brand#11|PROMO PLATED TIN|45|4 +Brand#11|PROMO POLISHED BRASS|9|4 +Brand#11|PROMO POLISHED BRASS|45|4 +Brand#11|PROMO POLISHED BRASS|49|4 +Brand#11|PROMO POLISHED COPPER|3|4 +Brand#11|PROMO POLISHED COPPER|36|4 +Brand#11|PROMO POLISHED COPPER|49|4 +Brand#11|PROMO POLISHED NICKEL|14|4 +Brand#11|PROMO POLISHED NICKEL|19|4 +Brand#11|PROMO POLISHED STEEL|9|4 +Brand#11|PROMO POLISHED STEEL|14|4 +Brand#11|PROMO POLISHED STEEL|36|4 +Brand#11|PROMO POLISHED TIN|36|4 +Brand#11|PROMO POLISHED TIN|45|4 +Brand#11|SMALL ANODIZED BRASS|3|4 +Brand#11|SMALL ANODIZED BRASS|14|4 +Brand#11|SMALL ANODIZED BRASS|19|4 +Brand#11|SMALL ANODIZED BRASS|36|4 +Brand#11|SMALL ANODIZED COPPER|9|4 +Brand#11|SMALL ANODIZED COPPER|23|4 +Brand#11|SMALL ANODIZED COPPER|36|4 +Brand#11|SMALL ANODIZED NICKEL|3|4 +Brand#11|SMALL ANODIZED NICKEL|14|4 +Brand#11|SMALL ANODIZED NICKEL|19|4 +Brand#11|SMALL ANODIZED NICKEL|45|4 +Brand#11|SMALL ANODIZED STEEL|19|4 +Brand#11|SMALL ANODIZED STEEL|36|4 +Brand#11|SMALL ANODIZED TIN|3|4 +Brand#11|SMALL ANODIZED TIN|14|4 +Brand#11|SMALL ANODIZED TIN|49|4 +Brand#11|SMALL BRUSHED BRASS|3|4 +Brand#11|SMALL BRUSHED BRASS|9|4 +Brand#11|SMALL BRUSHED BRASS|14|4 +Brand#11|SMALL BRUSHED COPPER|3|4 +Brand#11|SMALL BRUSHED COPPER|23|4 +Brand#11|SMALL BRUSHED COPPER|36|4 +Brand#11|SMALL BRUSHED COPPER|45|4 +Brand#11|SMALL BRUSHED COPPER|49|4 +Brand#11|SMALL BRUSHED STEEL|9|4 +Brand#11|SMALL BRUSHED STEEL|19|4 +Brand#11|SMALL BRUSHED STEEL|36|4 +Brand#11|SMALL BRUSHED STEEL|45|4 +Brand#11|SMALL BRUSHED TIN|9|4 +Brand#11|SMALL BRUSHED TIN|23|4 +Brand#11|SMALL BRUSHED TIN|36|4 +Brand#11|SMALL BRUSHED TIN|45|4 +Brand#11|SMALL BURNISHED BRASS|3|4 +Brand#11|SMALL BURNISHED BRASS|23|4 +Brand#11|SMALL BURNISHED BRASS|36|4 +Brand#11|SMALL BURNISHED COPPER|3|4 +Brand#11|SMALL BURNISHED COPPER|14|4 +Brand#11|SMALL BURNISHED NICKEL|36|4 +Brand#11|SMALL BURNISHED NICKEL|45|4 +Brand#11|SMALL BURNISHED STEEL|14|4 +Brand#11|SMALL BURNISHED STEEL|23|4 +Brand#11|SMALL BURNISHED STEEL|49|4 +Brand#11|SMALL BURNISHED TIN|14|4 +Brand#11|SMALL BURNISHED TIN|23|4 +Brand#11|SMALL BURNISHED TIN|36|4 +Brand#11|SMALL BURNISHED TIN|49|4 +Brand#11|SMALL PLATED BRASS|9|4 +Brand#11|SMALL PLATED BRASS|23|4 +Brand#11|SMALL PLATED COPPER|3|4 +Brand#11|SMALL PLATED COPPER|14|4 +Brand#11|SMALL PLATED COPPER|36|4 +Brand#11|SMALL PLATED NICKEL|3|4 +Brand#11|SMALL PLATED NICKEL|14|4 +Brand#11|SMALL PLATED NICKEL|19|4 +Brand#11|SMALL PLATED STEEL|23|4 +Brand#11|SMALL PLATED STEEL|36|4 +Brand#11|SMALL PLATED TIN|49|4 +Brand#11|SMALL POLISHED BRASS|36|4 +Brand#11|SMALL POLISHED BRASS|45|4 +Brand#11|SMALL POLISHED BRASS|49|4 +Brand#11|SMALL POLISHED COPPER|3|4 +Brand#11|SMALL POLISHED COPPER|14|4 +Brand#11|SMALL POLISHED COPPER|19|4 +Brand#11|SMALL POLISHED COPPER|49|4 +Brand#11|SMALL POLISHED NICKEL|3|4 +Brand#11|SMALL POLISHED NICKEL|14|4 +Brand#11|SMALL POLISHED NICKEL|19|4 +Brand#11|SMALL POLISHED STEEL|9|4 +Brand#11|SMALL POLISHED STEEL|49|4 +Brand#11|SMALL POLISHED TIN|14|4 +Brand#11|SMALL POLISHED TIN|19|4 +Brand#11|SMALL POLISHED TIN|36|4 +Brand#11|SMALL POLISHED TIN|45|4 +Brand#11|SMALL POLISHED TIN|49|4 +Brand#11|STANDARD ANODIZED BRASS|3|4 +Brand#11|STANDARD ANODIZED BRASS|9|4 +Brand#11|STANDARD ANODIZED BRASS|36|4 +Brand#11|STANDARD ANODIZED BRASS|49|4 +Brand#11|STANDARD ANODIZED COPPER|23|4 +Brand#11|STANDARD ANODIZED COPPER|45|4 +Brand#11|STANDARD ANODIZED NICKEL|3|4 +Brand#11|STANDARD ANODIZED NICKEL|49|4 +Brand#11|STANDARD ANODIZED STEEL|3|4 +Brand#11|STANDARD ANODIZED STEEL|14|4 +Brand#11|STANDARD ANODIZED STEEL|23|4 +Brand#11|STANDARD ANODIZED STEEL|36|4 +Brand#11|STANDARD ANODIZED STEEL|45|4 +Brand#11|STANDARD ANODIZED STEEL|49|4 +Brand#11|STANDARD ANODIZED TIN|3|4 +Brand#11|STANDARD ANODIZED TIN|19|4 +Brand#11|STANDARD ANODIZED TIN|36|4 +Brand#11|STANDARD ANODIZED TIN|49|4 +Brand#11|STANDARD BRUSHED BRASS|9|4 +Brand#11|STANDARD BRUSHED BRASS|14|4 +Brand#11|STANDARD BRUSHED BRASS|36|4 +Brand#11|STANDARD BRUSHED BRASS|45|4 +Brand#11|STANDARD BRUSHED COPPER|9|4 +Brand#11|STANDARD BRUSHED COPPER|19|4 +Brand#11|STANDARD BRUSHED COPPER|49|4 +Brand#11|STANDARD BRUSHED NICKEL|19|4 +Brand#11|STANDARD BRUSHED NICKEL|23|4 +Brand#11|STANDARD BRUSHED NICKEL|36|4 +Brand#11|STANDARD BRUSHED NICKEL|49|4 +Brand#11|STANDARD BRUSHED STEEL|23|4 +Brand#11|STANDARD BRUSHED STEEL|36|4 +Brand#11|STANDARD BRUSHED TIN|14|4 +Brand#11|STANDARD BRUSHED TIN|45|4 +Brand#11|STANDARD BURNISHED BRASS|3|4 +Brand#11|STANDARD BURNISHED BRASS|14|4 +Brand#11|STANDARD BURNISHED BRASS|45|4 +Brand#11|STANDARD BURNISHED COPPER|3|4 +Brand#11|STANDARD BURNISHED COPPER|45|4 +Brand#11|STANDARD BURNISHED NICKEL|3|4 +Brand#11|STANDARD BURNISHED NICKEL|9|4 +Brand#11|STANDARD BURNISHED NICKEL|14|4 +Brand#11|STANDARD BURNISHED NICKEL|19|4 +Brand#11|STANDARD BURNISHED STEEL|9|4 +Brand#11|STANDARD BURNISHED STEEL|14|4 +Brand#11|STANDARD BURNISHED STEEL|19|4 +Brand#11|STANDARD BURNISHED STEEL|49|4 +Brand#11|STANDARD BURNISHED TIN|9|4 +Brand#11|STANDARD BURNISHED TIN|19|4 +Brand#11|STANDARD BURNISHED TIN|23|4 +Brand#11|STANDARD BURNISHED TIN|36|4 +Brand#11|STANDARD PLATED BRASS|3|4 +Brand#11|STANDARD PLATED BRASS|14|4 +Brand#11|STANDARD PLATED BRASS|36|4 +Brand#11|STANDARD PLATED COPPER|9|4 +Brand#11|STANDARD PLATED COPPER|14|4 +Brand#11|STANDARD PLATED COPPER|45|4 +Brand#11|STANDARD PLATED NICKEL|3|4 +Brand#11|STANDARD PLATED NICKEL|9|4 +Brand#11|STANDARD PLATED NICKEL|23|4 +Brand#11|STANDARD PLATED NICKEL|49|4 +Brand#11|STANDARD PLATED STEEL|9|4 +Brand#11|STANDARD PLATED STEEL|36|4 +Brand#11|STANDARD PLATED TIN|19|4 +Brand#11|STANDARD POLISHED BRASS|19|4 +Brand#11|STANDARD POLISHED BRASS|36|4 +Brand#11|STANDARD POLISHED BRASS|49|4 +Brand#11|STANDARD POLISHED COPPER|3|4 +Brand#11|STANDARD POLISHED COPPER|45|4 +Brand#11|STANDARD POLISHED COPPER|49|4 +Brand#11|STANDARD POLISHED NICKEL|14|4 +Brand#11|STANDARD POLISHED NICKEL|36|4 +Brand#11|STANDARD POLISHED NICKEL|45|4 +Brand#11|STANDARD POLISHED STEEL|14|4 +Brand#11|STANDARD POLISHED STEEL|23|4 +Brand#11|STANDARD POLISHED STEEL|36|4 +Brand#11|STANDARD POLISHED STEEL|45|4 +Brand#11|STANDARD POLISHED TIN|3|4 +Brand#11|STANDARD POLISHED TIN|19|4 +Brand#11|STANDARD POLISHED TIN|36|4 +Brand#11|STANDARD POLISHED TIN|45|4 +Brand#12|ECONOMY ANODIZED BRASS|9|4 +Brand#12|ECONOMY ANODIZED BRASS|19|4 +Brand#12|ECONOMY ANODIZED BRASS|23|4 +Brand#12|ECONOMY ANODIZED COPPER|9|4 +Brand#12|ECONOMY ANODIZED COPPER|19|4 +Brand#12|ECONOMY ANODIZED COPPER|23|4 +Brand#12|ECONOMY ANODIZED COPPER|36|4 +Brand#12|ECONOMY ANODIZED COPPER|45|4 +Brand#12|ECONOMY ANODIZED COPPER|49|4 +Brand#12|ECONOMY ANODIZED NICKEL|3|4 +Brand#12|ECONOMY ANODIZED NICKEL|9|4 +Brand#12|ECONOMY ANODIZED NICKEL|23|4 +Brand#12|ECONOMY ANODIZED NICKEL|49|4 +Brand#12|ECONOMY ANODIZED STEEL|9|4 +Brand#12|ECONOMY ANODIZED STEEL|49|4 +Brand#12|ECONOMY ANODIZED TIN|9|4 +Brand#12|ECONOMY ANODIZED TIN|36|4 +Brand#12|ECONOMY ANODIZED TIN|49|4 +Brand#12|ECONOMY BRUSHED BRASS|9|4 +Brand#12|ECONOMY BRUSHED BRASS|14|4 +Brand#12|ECONOMY BRUSHED BRASS|45|4 +Brand#12|ECONOMY BRUSHED COPPER|45|4 +Brand#12|ECONOMY BRUSHED NICKEL|9|4 +Brand#12|ECONOMY BRUSHED NICKEL|14|4 +Brand#12|ECONOMY BRUSHED NICKEL|19|4 +Brand#12|ECONOMY BRUSHED NICKEL|36|4 +Brand#12|ECONOMY BRUSHED NICKEL|45|4 +Brand#12|ECONOMY BRUSHED NICKEL|49|4 +Brand#12|ECONOMY BRUSHED STEEL|14|4 +Brand#12|ECONOMY BRUSHED STEEL|19|4 +Brand#12|ECONOMY BRUSHED TIN|45|4 +Brand#12|ECONOMY BURNISHED BRASS|3|4 +Brand#12|ECONOMY BURNISHED BRASS|14|4 +Brand#12|ECONOMY BURNISHED BRASS|36|4 +Brand#12|ECONOMY BURNISHED BRASS|45|4 +Brand#12|ECONOMY BURNISHED COPPER|9|4 +Brand#12|ECONOMY BURNISHED COPPER|23|4 +Brand#12|ECONOMY BURNISHED COPPER|36|4 +Brand#12|ECONOMY BURNISHED COPPER|45|4 +Brand#12|ECONOMY BURNISHED NICKEL|9|4 +Brand#12|ECONOMY BURNISHED NICKEL|49|4 +Brand#12|ECONOMY BURNISHED STEEL|14|4 +Brand#12|ECONOMY BURNISHED STEEL|19|4 +Brand#12|ECONOMY BURNISHED STEEL|23|4 +Brand#12|ECONOMY BURNISHED STEEL|45|4 +Brand#12|ECONOMY BURNISHED TIN|49|4 +Brand#12|ECONOMY PLATED BRASS|9|4 +Brand#12|ECONOMY PLATED BRASS|14|4 +Brand#12|ECONOMY PLATED BRASS|23|4 +Brand#12|ECONOMY PLATED BRASS|36|4 +Brand#12|ECONOMY PLATED COPPER|49|4 +Brand#12|ECONOMY PLATED NICKEL|14|4 +Brand#12|ECONOMY PLATED NICKEL|23|4 +Brand#12|ECONOMY PLATED NICKEL|36|4 +Brand#12|ECONOMY PLATED NICKEL|45|4 +Brand#12|ECONOMY PLATED NICKEL|49|4 +Brand#12|ECONOMY PLATED STEEL|3|4 +Brand#12|ECONOMY PLATED STEEL|9|4 +Brand#12|ECONOMY PLATED STEEL|14|4 +Brand#12|ECONOMY PLATED STEEL|19|4 +Brand#12|ECONOMY PLATED STEEL|36|4 +Brand#12|ECONOMY PLATED STEEL|49|4 +Brand#12|ECONOMY PLATED TIN|9|4 +Brand#12|ECONOMY PLATED TIN|14|4 +Brand#12|ECONOMY PLATED TIN|19|4 +Brand#12|ECONOMY PLATED TIN|23|4 +Brand#12|ECONOMY POLISHED BRASS|36|4 +Brand#12|ECONOMY POLISHED BRASS|49|4 +Brand#12|ECONOMY POLISHED COPPER|23|4 +Brand#12|ECONOMY POLISHED COPPER|45|4 +Brand#12|ECONOMY POLISHED NICKEL|9|4 +Brand#12|ECONOMY POLISHED NICKEL|23|4 +Brand#12|ECONOMY POLISHED STEEL|14|4 +Brand#12|ECONOMY POLISHED STEEL|36|4 +Brand#12|ECONOMY POLISHED STEEL|45|4 +Brand#12|ECONOMY POLISHED TIN|23|4 +Brand#12|ECONOMY POLISHED TIN|45|4 +Brand#12|LARGE ANODIZED BRASS|3|4 +Brand#12|LARGE ANODIZED BRASS|9|4 +Brand#12|LARGE ANODIZED BRASS|19|4 +Brand#12|LARGE ANODIZED BRASS|49|4 +Brand#12|LARGE ANODIZED COPPER|3|4 +Brand#12|LARGE ANODIZED COPPER|23|4 +Brand#12|LARGE ANODIZED NICKEL|3|4 +Brand#12|LARGE ANODIZED NICKEL|14|4 +Brand#12|LARGE ANODIZED NICKEL|19|4 +Brand#12|LARGE ANODIZED NICKEL|23|4 +Brand#12|LARGE ANODIZED NICKEL|45|4 +Brand#12|LARGE ANODIZED STEEL|14|4 +Brand#12|LARGE ANODIZED STEEL|19|4 +Brand#12|LARGE ANODIZED STEEL|45|4 +Brand#12|LARGE ANODIZED TIN|9|4 +Brand#12|LARGE ANODIZED TIN|36|4 +Brand#12|LARGE ANODIZED TIN|45|4 +Brand#12|LARGE BRUSHED BRASS|3|4 +Brand#12|LARGE BRUSHED COPPER|3|4 +Brand#12|LARGE BRUSHED COPPER|9|4 +Brand#12|LARGE BRUSHED COPPER|45|4 +Brand#12|LARGE BRUSHED NICKEL|3|4 +Brand#12|LARGE BRUSHED NICKEL|19|4 +Brand#12|LARGE BRUSHED NICKEL|45|4 +Brand#12|LARGE BRUSHED STEEL|14|4 +Brand#12|LARGE BRUSHED TIN|36|4 +Brand#12|LARGE BRUSHED TIN|49|4 +Brand#12|LARGE BURNISHED BRASS|3|4 +Brand#12|LARGE BURNISHED BRASS|19|4 +Brand#12|LARGE BURNISHED BRASS|23|4 +Brand#12|LARGE BURNISHED BRASS|36|4 +Brand#12|LARGE BURNISHED BRASS|49|4 +Brand#12|LARGE BURNISHED COPPER|9|4 +Brand#12|LARGE BURNISHED COPPER|14|4 +Brand#12|LARGE BURNISHED COPPER|23|4 +Brand#12|LARGE BURNISHED COPPER|45|4 +Brand#12|LARGE BURNISHED NICKEL|9|4 +Brand#12|LARGE BURNISHED NICKEL|23|4 +Brand#12|LARGE BURNISHED NICKEL|36|4 +Brand#12|LARGE BURNISHED NICKEL|49|4 +Brand#12|LARGE BURNISHED STEEL|14|4 +Brand#12|LARGE BURNISHED STEEL|19|4 +Brand#12|LARGE BURNISHED STEEL|23|4 +Brand#12|LARGE BURNISHED STEEL|36|4 +Brand#12|LARGE BURNISHED TIN|19|4 +Brand#12|LARGE PLATED BRASS|14|4 +Brand#12|LARGE PLATED BRASS|19|4 +Brand#12|LARGE PLATED BRASS|23|4 +Brand#12|LARGE PLATED BRASS|36|4 +Brand#12|LARGE PLATED BRASS|45|4 +Brand#12|LARGE PLATED COPPER|9|4 +Brand#12|LARGE PLATED COPPER|19|4 +Brand#12|LARGE PLATED NICKEL|14|4 +Brand#12|LARGE PLATED NICKEL|19|4 +Brand#12|LARGE PLATED NICKEL|23|4 +Brand#12|LARGE PLATED NICKEL|45|4 +Brand#12|LARGE PLATED STEEL|23|4 +Brand#12|LARGE PLATED STEEL|45|4 +Brand#12|LARGE PLATED STEEL|49|4 +Brand#12|LARGE PLATED TIN|3|4 +Brand#12|LARGE PLATED TIN|23|4 +Brand#12|LARGE POLISHED BRASS|14|4 +Brand#12|LARGE POLISHED BRASS|36|4 +Brand#12|LARGE POLISHED BRASS|45|4 +Brand#12|LARGE POLISHED COPPER|14|4 +Brand#12|LARGE POLISHED COPPER|45|4 +Brand#12|LARGE POLISHED NICKEL|3|4 +Brand#12|LARGE POLISHED NICKEL|9|4 +Brand#12|LARGE POLISHED STEEL|3|4 +Brand#12|LARGE POLISHED STEEL|19|4 +Brand#12|LARGE POLISHED STEEL|45|4 +Brand#12|LARGE POLISHED TIN|14|4 +Brand#12|LARGE POLISHED TIN|23|4 +Brand#12|LARGE POLISHED TIN|49|4 +Brand#12|MEDIUM ANODIZED BRASS|9|4 +Brand#12|MEDIUM ANODIZED BRASS|19|4 +Brand#12|MEDIUM ANODIZED BRASS|36|4 +Brand#12|MEDIUM ANODIZED COPPER|14|4 +Brand#12|MEDIUM ANODIZED COPPER|36|4 +Brand#12|MEDIUM ANODIZED COPPER|45|4 +Brand#12|MEDIUM ANODIZED NICKEL|14|4 +Brand#12|MEDIUM ANODIZED NICKEL|23|4 +Brand#12|MEDIUM ANODIZED NICKEL|45|4 +Brand#12|MEDIUM ANODIZED NICKEL|49|4 +Brand#12|MEDIUM ANODIZED STEEL|23|4 +Brand#12|MEDIUM ANODIZED STEEL|36|4 +Brand#12|MEDIUM ANODIZED TIN|14|4 +Brand#12|MEDIUM ANODIZED TIN|36|4 +Brand#12|MEDIUM ANODIZED TIN|45|4 +Brand#12|MEDIUM BRUSHED BRASS|19|4 +Brand#12|MEDIUM BRUSHED BRASS|36|4 +Brand#12|MEDIUM BRUSHED BRASS|49|4 +Brand#12|MEDIUM BRUSHED COPPER|14|4 +Brand#12|MEDIUM BRUSHED COPPER|45|4 +Brand#12|MEDIUM BRUSHED COPPER|49|4 +Brand#12|MEDIUM BRUSHED NICKEL|3|4 +Brand#12|MEDIUM BRUSHED NICKEL|9|4 +Brand#12|MEDIUM BRUSHED NICKEL|19|4 +Brand#12|MEDIUM BRUSHED NICKEL|23|4 +Brand#12|MEDIUM BRUSHED STEEL|14|4 +Brand#12|MEDIUM BRUSHED STEEL|45|4 +Brand#12|MEDIUM BRUSHED STEEL|49|4 +Brand#12|MEDIUM BRUSHED TIN|23|4 +Brand#12|MEDIUM BRUSHED TIN|45|4 +Brand#12|MEDIUM BURNISHED BRASS|3|4 +Brand#12|MEDIUM BURNISHED BRASS|9|4 +Brand#12|MEDIUM BURNISHED BRASS|14|4 +Brand#12|MEDIUM BURNISHED COPPER|9|4 +Brand#12|MEDIUM BURNISHED COPPER|14|4 +Brand#12|MEDIUM BURNISHED COPPER|23|4 +Brand#12|MEDIUM BURNISHED COPPER|36|4 +Brand#12|MEDIUM BURNISHED NICKEL|14|4 +Brand#12|MEDIUM BURNISHED NICKEL|19|4 +Brand#12|MEDIUM BURNISHED NICKEL|23|4 +Brand#12|MEDIUM BURNISHED NICKEL|36|4 +Brand#12|MEDIUM BURNISHED NICKEL|45|4 +Brand#12|MEDIUM BURNISHED STEEL|23|4 +Brand#12|MEDIUM BURNISHED STEEL|36|4 +Brand#12|MEDIUM BURNISHED STEEL|45|4 +Brand#12|MEDIUM BURNISHED TIN|23|4 +Brand#12|MEDIUM BURNISHED TIN|36|4 +Brand#12|MEDIUM BURNISHED TIN|49|4 +Brand#12|MEDIUM PLATED BRASS|19|4 +Brand#12|MEDIUM PLATED BRASS|45|4 +Brand#12|MEDIUM PLATED COPPER|3|4 +Brand#12|MEDIUM PLATED COPPER|9|4 +Brand#12|MEDIUM PLATED COPPER|14|4 +Brand#12|MEDIUM PLATED COPPER|23|4 +Brand#12|MEDIUM PLATED COPPER|36|4 +Brand#12|MEDIUM PLATED NICKEL|14|4 +Brand#12|MEDIUM PLATED NICKEL|19|4 +Brand#12|MEDIUM PLATED STEEL|36|4 +Brand#12|MEDIUM PLATED STEEL|49|4 +Brand#12|MEDIUM PLATED TIN|49|4 +Brand#12|PROMO ANODIZED BRASS|9|4 +Brand#12|PROMO ANODIZED BRASS|23|4 +Brand#12|PROMO ANODIZED BRASS|36|4 +Brand#12|PROMO ANODIZED COPPER|9|4 +Brand#12|PROMO ANODIZED COPPER|14|4 +Brand#12|PROMO ANODIZED COPPER|23|4 +Brand#12|PROMO ANODIZED STEEL|3|4 +Brand#12|PROMO ANODIZED STEEL|9|4 +Brand#12|PROMO ANODIZED STEEL|14|4 +Brand#12|PROMO ANODIZED STEEL|45|4 +Brand#12|PROMO ANODIZED TIN|3|4 +Brand#12|PROMO ANODIZED TIN|45|4 +Brand#12|PROMO BRUSHED BRASS|14|4 +Brand#12|PROMO BRUSHED COPPER|14|4 +Brand#12|PROMO BRUSHED COPPER|19|4 +Brand#12|PROMO BRUSHED COPPER|45|4 +Brand#12|PROMO BRUSHED COPPER|49|4 +Brand#12|PROMO BRUSHED NICKEL|3|4 +Brand#12|PROMO BRUSHED NICKEL|9|4 +Brand#12|PROMO BRUSHED NICKEL|14|4 +Brand#12|PROMO BRUSHED NICKEL|19|4 +Brand#12|PROMO BRUSHED NICKEL|36|4 +Brand#12|PROMO BRUSHED NICKEL|45|4 +Brand#12|PROMO BRUSHED NICKEL|49|4 +Brand#12|PROMO BRUSHED STEEL|36|4 +Brand#12|PROMO BRUSHED TIN|19|4 +Brand#12|PROMO BRUSHED TIN|23|4 +Brand#12|PROMO BRUSHED TIN|49|4 +Brand#12|PROMO BURNISHED BRASS|19|4 +Brand#12|PROMO BURNISHED BRASS|23|4 +Brand#12|PROMO BURNISHED BRASS|36|4 +Brand#12|PROMO BURNISHED BRASS|49|4 +Brand#12|PROMO BURNISHED COPPER|9|4 +Brand#12|PROMO BURNISHED COPPER|14|4 +Brand#12|PROMO BURNISHED COPPER|23|4 +Brand#12|PROMO BURNISHED COPPER|36|4 +Brand#12|PROMO BURNISHED COPPER|45|4 +Brand#12|PROMO BURNISHED COPPER|49|4 +Brand#12|PROMO BURNISHED NICKEL|3|4 +Brand#12|PROMO BURNISHED NICKEL|19|4 +Brand#12|PROMO BURNISHED NICKEL|23|4 +Brand#12|PROMO BURNISHED NICKEL|36|4 +Brand#12|PROMO BURNISHED NICKEL|45|4 +Brand#12|PROMO BURNISHED STEEL|14|4 +Brand#12|PROMO BURNISHED STEEL|19|4 +Brand#12|PROMO BURNISHED STEEL|23|4 +Brand#12|PROMO BURNISHED STEEL|45|4 +Brand#12|PROMO BURNISHED STEEL|49|4 +Brand#12|PROMO BURNISHED TIN|3|4 +Brand#12|PROMO BURNISHED TIN|19|4 +Brand#12|PROMO PLATED BRASS|14|4 +Brand#12|PROMO PLATED BRASS|23|4 +Brand#12|PROMO PLATED COPPER|3|4 +Brand#12|PROMO PLATED COPPER|19|4 +Brand#12|PROMO PLATED COPPER|49|4 +Brand#12|PROMO PLATED NICKEL|9|4 +Brand#12|PROMO PLATED NICKEL|19|4 +Brand#12|PROMO PLATED NICKEL|49|4 +Brand#12|PROMO PLATED STEEL|9|4 +Brand#12|PROMO PLATED STEEL|14|4 +Brand#12|PROMO PLATED STEEL|23|4 +Brand#12|PROMO PLATED STEEL|45|4 +Brand#12|PROMO PLATED TIN|14|4 +Brand#12|PROMO PLATED TIN|19|4 +Brand#12|PROMO PLATED TIN|49|4 +Brand#12|PROMO POLISHED BRASS|14|4 +Brand#12|PROMO POLISHED BRASS|45|4 +Brand#12|PROMO POLISHED COPPER|3|4 +Brand#12|PROMO POLISHED COPPER|9|4 +Brand#12|PROMO POLISHED COPPER|36|4 +Brand#12|PROMO POLISHED COPPER|49|4 +Brand#12|PROMO POLISHED NICKEL|9|4 +Brand#12|PROMO POLISHED NICKEL|23|4 +Brand#12|PROMO POLISHED NICKEL|45|4 +Brand#12|PROMO POLISHED STEEL|9|4 +Brand#12|PROMO POLISHED STEEL|14|4 +Brand#12|PROMO POLISHED TIN|9|4 +Brand#12|PROMO POLISHED TIN|45|4 +Brand#12|SMALL ANODIZED BRASS|3|4 +Brand#12|SMALL ANODIZED BRASS|14|4 +Brand#12|SMALL ANODIZED BRASS|19|4 +Brand#12|SMALL ANODIZED BRASS|23|4 +Brand#12|SMALL ANODIZED COPPER|19|4 +Brand#12|SMALL ANODIZED COPPER|23|4 +Brand#12|SMALL ANODIZED COPPER|45|4 +Brand#12|SMALL ANODIZED COPPER|49|4 +Brand#12|SMALL ANODIZED NICKEL|9|4 +Brand#12|SMALL ANODIZED NICKEL|14|4 +Brand#12|SMALL ANODIZED STEEL|19|4 +Brand#12|SMALL ANODIZED STEEL|36|4 +Brand#12|SMALL ANODIZED TIN|3|4 +Brand#12|SMALL ANODIZED TIN|36|4 +Brand#12|SMALL BRUSHED BRASS|9|4 +Brand#12|SMALL BRUSHED BRASS|19|4 +Brand#12|SMALL BRUSHED COPPER|9|4 +Brand#12|SMALL BRUSHED COPPER|14|4 +Brand#12|SMALL BRUSHED COPPER|19|4 +Brand#12|SMALL BRUSHED COPPER|23|4 +Brand#12|SMALL BRUSHED COPPER|45|4 +Brand#12|SMALL BRUSHED COPPER|49|4 +Brand#12|SMALL BRUSHED STEEL|3|4 +Brand#12|SMALL BRUSHED TIN|14|4 +Brand#12|SMALL BRUSHED TIN|19|4 +Brand#12|SMALL BRUSHED TIN|23|4 +Brand#12|SMALL BRUSHED TIN|36|4 +Brand#12|SMALL BURNISHED BRASS|3|4 +Brand#12|SMALL BURNISHED COPPER|3|4 +Brand#12|SMALL BURNISHED COPPER|9|4 +Brand#12|SMALL BURNISHED COPPER|19|4 +Brand#12|SMALL BURNISHED COPPER|45|4 +Brand#12|SMALL BURNISHED NICKEL|23|4 +Brand#12|SMALL BURNISHED NICKEL|49|4 +Brand#12|SMALL BURNISHED STEEL|14|4 +Brand#12|SMALL BURNISHED STEEL|19|4 +Brand#12|SMALL BURNISHED STEEL|36|4 +Brand#12|SMALL BURNISHED STEEL|45|4 +Brand#12|SMALL BURNISHED STEEL|49|4 +Brand#12|SMALL BURNISHED TIN|9|4 +Brand#12|SMALL BURNISHED TIN|36|4 +Brand#12|SMALL BURNISHED TIN|49|4 +Brand#12|SMALL PLATED BRASS|9|4 +Brand#12|SMALL PLATED BRASS|36|4 +Brand#12|SMALL PLATED COPPER|3|4 +Brand#12|SMALL PLATED COPPER|9|4 +Brand#12|SMALL PLATED COPPER|14|4 +Brand#12|SMALL PLATED COPPER|36|4 +Brand#12|SMALL PLATED COPPER|45|4 +Brand#12|SMALL PLATED COPPER|49|4 +Brand#12|SMALL PLATED NICKEL|9|4 +Brand#12|SMALL PLATED NICKEL|36|4 +Brand#12|SMALL PLATED STEEL|14|4 +Brand#12|SMALL PLATED TIN|3|4 +Brand#12|SMALL PLATED TIN|9|4 +Brand#12|SMALL PLATED TIN|14|4 +Brand#12|SMALL PLATED TIN|19|4 +Brand#12|SMALL PLATED TIN|36|4 +Brand#12|SMALL PLATED TIN|49|4 +Brand#12|SMALL POLISHED BRASS|3|4 +Brand#12|SMALL POLISHED BRASS|9|4 +Brand#12|SMALL POLISHED BRASS|49|4 +Brand#12|SMALL POLISHED COPPER|3|4 +Brand#12|SMALL POLISHED COPPER|9|4 +Brand#12|SMALL POLISHED COPPER|19|4 +Brand#12|SMALL POLISHED COPPER|23|4 +Brand#12|SMALL POLISHED COPPER|36|4 +Brand#12|SMALL POLISHED NICKEL|3|4 +Brand#12|SMALL POLISHED NICKEL|9|4 +Brand#12|SMALL POLISHED NICKEL|19|4 +Brand#12|SMALL POLISHED NICKEL|36|4 +Brand#12|SMALL POLISHED NICKEL|45|4 +Brand#12|SMALL POLISHED STEEL|3|4 +Brand#12|SMALL POLISHED STEEL|9|4 +Brand#12|SMALL POLISHED STEEL|14|4 +Brand#12|SMALL POLISHED STEEL|23|4 +Brand#12|SMALL POLISHED STEEL|36|4 +Brand#12|SMALL POLISHED STEEL|49|4 +Brand#12|SMALL POLISHED TIN|3|4 +Brand#12|SMALL POLISHED TIN|9|4 +Brand#12|SMALL POLISHED TIN|23|4 +Brand#12|SMALL POLISHED TIN|49|4 +Brand#12|STANDARD ANODIZED BRASS|9|4 +Brand#12|STANDARD ANODIZED BRASS|19|4 +Brand#12|STANDARD ANODIZED BRASS|45|4 +Brand#12|STANDARD ANODIZED COPPER|9|4 +Brand#12|STANDARD ANODIZED COPPER|19|4 +Brand#12|STANDARD ANODIZED COPPER|36|4 +Brand#12|STANDARD ANODIZED COPPER|49|4 +Brand#12|STANDARD ANODIZED STEEL|3|4 +Brand#12|STANDARD ANODIZED STEEL|45|4 +Brand#12|STANDARD ANODIZED TIN|19|4 +Brand#12|STANDARD BRUSHED BRASS|9|4 +Brand#12|STANDARD BRUSHED BRASS|14|4 +Brand#12|STANDARD BRUSHED BRASS|49|4 +Brand#12|STANDARD BRUSHED COPPER|19|4 +Brand#12|STANDARD BRUSHED COPPER|23|4 +Brand#12|STANDARD BRUSHED COPPER|45|4 +Brand#12|STANDARD BRUSHED NICKEL|49|4 +Brand#12|STANDARD BRUSHED STEEL|14|4 +Brand#12|STANDARD BRUSHED STEEL|19|4 +Brand#12|STANDARD BRUSHED STEEL|23|4 +Brand#12|STANDARD BRUSHED STEEL|49|4 +Brand#12|STANDARD BRUSHED TIN|3|4 +Brand#12|STANDARD BRUSHED TIN|49|4 +Brand#12|STANDARD BURNISHED BRASS|9|4 +Brand#12|STANDARD BURNISHED BRASS|45|4 +Brand#12|STANDARD BURNISHED COPPER|19|4 +Brand#12|STANDARD BURNISHED COPPER|23|4 +Brand#12|STANDARD BURNISHED COPPER|36|4 +Brand#12|STANDARD BURNISHED COPPER|49|4 +Brand#12|STANDARD BURNISHED NICKEL|19|4 +Brand#12|STANDARD BURNISHED NICKEL|36|4 +Brand#12|STANDARD BURNISHED NICKEL|45|4 +Brand#12|STANDARD BURNISHED NICKEL|49|4 +Brand#12|STANDARD BURNISHED STEEL|3|4 +Brand#12|STANDARD BURNISHED STEEL|19|4 +Brand#12|STANDARD BURNISHED STEEL|23|4 +Brand#12|STANDARD BURNISHED STEEL|36|4 +Brand#12|STANDARD BURNISHED STEEL|45|4 +Brand#12|STANDARD BURNISHED TIN|19|4 +Brand#12|STANDARD PLATED BRASS|14|4 +Brand#12|STANDARD PLATED BRASS|23|4 +Brand#12|STANDARD PLATED BRASS|36|4 +Brand#12|STANDARD PLATED BRASS|45|4 +Brand#12|STANDARD PLATED COPPER|3|4 +Brand#12|STANDARD PLATED COPPER|9|4 +Brand#12|STANDARD PLATED COPPER|19|4 +Brand#12|STANDARD PLATED COPPER|45|4 +Brand#12|STANDARD PLATED NICKEL|23|4 +Brand#12|STANDARD PLATED NICKEL|36|4 +Brand#12|STANDARD PLATED NICKEL|49|4 +Brand#12|STANDARD PLATED STEEL|9|4 +Brand#12|STANDARD PLATED TIN|14|4 +Brand#12|STANDARD PLATED TIN|23|4 +Brand#12|STANDARD PLATED TIN|49|4 +Brand#12|STANDARD POLISHED BRASS|9|4 +Brand#12|STANDARD POLISHED BRASS|19|4 +Brand#12|STANDARD POLISHED BRASS|49|4 +Brand#12|STANDARD POLISHED COPPER|14|4 +Brand#12|STANDARD POLISHED COPPER|45|4 +Brand#12|STANDARD POLISHED COPPER|49|4 +Brand#12|STANDARD POLISHED NICKEL|9|4 +Brand#12|STANDARD POLISHED NICKEL|14|4 +Brand#12|STANDARD POLISHED NICKEL|19|4 +Brand#12|STANDARD POLISHED NICKEL|23|4 +Brand#12|STANDARD POLISHED NICKEL|45|4 +Brand#12|STANDARD POLISHED STEEL|36|4 +Brand#12|STANDARD POLISHED TIN|14|4 +Brand#12|STANDARD POLISHED TIN|19|4 +Brand#12|STANDARD POLISHED TIN|49|4 +Brand#13|ECONOMY ANODIZED BRASS|3|4 +Brand#13|ECONOMY ANODIZED BRASS|9|4 +Brand#13|ECONOMY ANODIZED BRASS|14|4 +Brand#13|ECONOMY ANODIZED BRASS|23|4 +Brand#13|ECONOMY ANODIZED BRASS|49|4 +Brand#13|ECONOMY ANODIZED COPPER|3|4 +Brand#13|ECONOMY ANODIZED COPPER|36|4 +Brand#13|ECONOMY ANODIZED COPPER|49|4 +Brand#13|ECONOMY ANODIZED STEEL|14|4 +Brand#13|ECONOMY ANODIZED STEEL|19|4 +Brand#13|ECONOMY ANODIZED STEEL|36|4 +Brand#13|ECONOMY ANODIZED STEEL|49|4 +Brand#13|ECONOMY ANODIZED TIN|3|4 +Brand#13|ECONOMY ANODIZED TIN|14|4 +Brand#13|ECONOMY ANODIZED TIN|36|4 +Brand#13|ECONOMY BRUSHED BRASS|3|4 +Brand#13|ECONOMY BRUSHED BRASS|14|4 +Brand#13|ECONOMY BRUSHED BRASS|23|4 +Brand#13|ECONOMY BRUSHED BRASS|36|4 +Brand#13|ECONOMY BRUSHED BRASS|49|4 +Brand#13|ECONOMY BRUSHED COPPER|19|4 +Brand#13|ECONOMY BRUSHED COPPER|23|4 +Brand#13|ECONOMY BRUSHED COPPER|45|4 +Brand#13|ECONOMY BRUSHED NICKEL|3|4 +Brand#13|ECONOMY BRUSHED NICKEL|9|4 +Brand#13|ECONOMY BRUSHED NICKEL|14|4 +Brand#13|ECONOMY BRUSHED STEEL|19|4 +Brand#13|ECONOMY BRUSHED STEEL|23|4 +Brand#13|ECONOMY BRUSHED STEEL|36|4 +Brand#13|ECONOMY BRUSHED TIN|3|4 +Brand#13|ECONOMY BRUSHED TIN|36|4 +Brand#13|ECONOMY BRUSHED TIN|45|4 +Brand#13|ECONOMY BURNISHED BRASS|9|4 +Brand#13|ECONOMY BURNISHED BRASS|14|4 +Brand#13|ECONOMY BURNISHED BRASS|19|4 +Brand#13|ECONOMY BURNISHED BRASS|23|4 +Brand#13|ECONOMY BURNISHED BRASS|36|4 +Brand#13|ECONOMY BURNISHED COPPER|3|4 +Brand#13|ECONOMY BURNISHED COPPER|9|4 +Brand#13|ECONOMY BURNISHED COPPER|49|4 +Brand#13|ECONOMY BURNISHED NICKEL|14|4 +Brand#13|ECONOMY BURNISHED NICKEL|23|4 +Brand#13|ECONOMY BURNISHED NICKEL|45|4 +Brand#13|ECONOMY BURNISHED NICKEL|49|4 +Brand#13|ECONOMY BURNISHED STEEL|9|4 +Brand#13|ECONOMY BURNISHED STEEL|23|4 +Brand#13|ECONOMY BURNISHED STEEL|49|4 +Brand#13|ECONOMY BURNISHED TIN|3|4 +Brand#13|ECONOMY BURNISHED TIN|9|4 +Brand#13|ECONOMY BURNISHED TIN|19|4 +Brand#13|ECONOMY BURNISHED TIN|45|4 +Brand#13|ECONOMY PLATED BRASS|3|4 +Brand#13|ECONOMY PLATED BRASS|19|4 +Brand#13|ECONOMY PLATED BRASS|45|4 +Brand#13|ECONOMY PLATED COPPER|23|4 +Brand#13|ECONOMY PLATED COPPER|45|4 +Brand#13|ECONOMY PLATED NICKEL|45|4 +Brand#13|ECONOMY PLATED STEEL|9|4 +Brand#13|ECONOMY PLATED STEEL|14|4 +Brand#13|ECONOMY PLATED STEEL|49|4 +Brand#13|ECONOMY PLATED TIN|19|4 +Brand#13|ECONOMY PLATED TIN|36|4 +Brand#13|ECONOMY PLATED TIN|49|4 +Brand#13|ECONOMY POLISHED BRASS|19|4 +Brand#13|ECONOMY POLISHED COPPER|3|4 +Brand#13|ECONOMY POLISHED COPPER|14|4 +Brand#13|ECONOMY POLISHED COPPER|23|4 +Brand#13|ECONOMY POLISHED NICKEL|9|4 +Brand#13|ECONOMY POLISHED NICKEL|14|4 +Brand#13|ECONOMY POLISHED NICKEL|19|4 +Brand#13|ECONOMY POLISHED NICKEL|36|4 +Brand#13|ECONOMY POLISHED NICKEL|45|4 +Brand#13|ECONOMY POLISHED NICKEL|49|4 +Brand#13|ECONOMY POLISHED STEEL|14|4 +Brand#13|ECONOMY POLISHED TIN|9|4 +Brand#13|ECONOMY POLISHED TIN|14|4 +Brand#13|ECONOMY POLISHED TIN|49|4 +Brand#13|LARGE ANODIZED BRASS|3|4 +Brand#13|LARGE ANODIZED BRASS|9|4 +Brand#13|LARGE ANODIZED BRASS|14|4 +Brand#13|LARGE ANODIZED BRASS|19|4 +Brand#13|LARGE ANODIZED BRASS|23|4 +Brand#13|LARGE ANODIZED COPPER|9|4 +Brand#13|LARGE ANODIZED COPPER|14|4 +Brand#13|LARGE ANODIZED COPPER|36|4 +Brand#13|LARGE ANODIZED COPPER|45|4 +Brand#13|LARGE ANODIZED COPPER|49|4 +Brand#13|LARGE ANODIZED NICKEL|3|4 +Brand#13|LARGE ANODIZED NICKEL|9|4 +Brand#13|LARGE ANODIZED NICKEL|36|4 +Brand#13|LARGE ANODIZED STEEL|23|4 +Brand#13|LARGE ANODIZED TIN|3|4 +Brand#13|LARGE ANODIZED TIN|23|4 +Brand#13|LARGE BRUSHED BRASS|14|4 +Brand#13|LARGE BRUSHED BRASS|23|4 +Brand#13|LARGE BRUSHED BRASS|36|4 +Brand#13|LARGE BRUSHED COPPER|3|4 +Brand#13|LARGE BRUSHED COPPER|14|4 +Brand#13|LARGE BRUSHED COPPER|23|4 +Brand#13|LARGE BRUSHED COPPER|36|4 +Brand#13|LARGE BRUSHED NICKEL|14|4 +Brand#13|LARGE BRUSHED NICKEL|19|4 +Brand#13|LARGE BRUSHED STEEL|9|4 +Brand#13|LARGE BRUSHED STEEL|14|4 +Brand#13|LARGE BRUSHED STEEL|45|4 +Brand#13|LARGE BRUSHED STEEL|49|4 +Brand#13|LARGE BRUSHED TIN|14|4 +Brand#13|LARGE BRUSHED TIN|19|4 +Brand#13|LARGE BRUSHED TIN|45|4 +Brand#13|LARGE BRUSHED TIN|49|4 +Brand#13|LARGE BURNISHED BRASS|9|4 +Brand#13|LARGE BURNISHED BRASS|19|4 +Brand#13|LARGE BURNISHED BRASS|36|4 +Brand#13|LARGE BURNISHED BRASS|49|4 +Brand#13|LARGE BURNISHED COPPER|9|4 +Brand#13|LARGE BURNISHED COPPER|49|4 +Brand#13|LARGE BURNISHED NICKEL|3|4 +Brand#13|LARGE BURNISHED NICKEL|23|4 +Brand#13|LARGE BURNISHED NICKEL|36|4 +Brand#13|LARGE BURNISHED STEEL|36|4 +Brand#13|LARGE BURNISHED TIN|14|4 +Brand#13|LARGE BURNISHED TIN|19|4 +Brand#13|LARGE BURNISHED TIN|36|4 +Brand#13|LARGE BURNISHED TIN|49|4 +Brand#13|LARGE PLATED BRASS|3|4 +Brand#13|LARGE PLATED BRASS|14|4 +Brand#13|LARGE PLATED BRASS|23|4 +Brand#13|LARGE PLATED BRASS|36|4 +Brand#13|LARGE PLATED BRASS|49|4 +Brand#13|LARGE PLATED COPPER|45|4 +Brand#13|LARGE PLATED NICKEL|3|4 +Brand#13|LARGE PLATED NICKEL|14|4 +Brand#13|LARGE PLATED STEEL|19|4 +Brand#13|LARGE PLATED STEEL|23|4 +Brand#13|LARGE PLATED TIN|3|4 +Brand#13|LARGE PLATED TIN|19|4 +Brand#13|LARGE PLATED TIN|49|4 +Brand#13|LARGE POLISHED BRASS|3|4 +Brand#13|LARGE POLISHED BRASS|45|4 +Brand#13|LARGE POLISHED COPPER|3|4 +Brand#13|LARGE POLISHED COPPER|9|4 +Brand#13|LARGE POLISHED COPPER|19|4 +Brand#13|LARGE POLISHED COPPER|23|4 +Brand#13|LARGE POLISHED COPPER|36|4 +Brand#13|LARGE POLISHED COPPER|49|4 +Brand#13|LARGE POLISHED NICKEL|3|4 +Brand#13|LARGE POLISHED NICKEL|19|4 +Brand#13|LARGE POLISHED NICKEL|36|4 +Brand#13|LARGE POLISHED STEEL|14|4 +Brand#13|LARGE POLISHED STEEL|45|4 +Brand#13|LARGE POLISHED STEEL|49|4 +Brand#13|LARGE POLISHED TIN|49|4 +Brand#13|MEDIUM ANODIZED BRASS|3|4 +Brand#13|MEDIUM ANODIZED BRASS|9|4 +Brand#13|MEDIUM ANODIZED BRASS|14|4 +Brand#13|MEDIUM ANODIZED BRASS|36|4 +Brand#13|MEDIUM ANODIZED COPPER|9|4 +Brand#13|MEDIUM ANODIZED COPPER|14|4 +Brand#13|MEDIUM ANODIZED COPPER|19|4 +Brand#13|MEDIUM ANODIZED NICKEL|19|4 +Brand#13|MEDIUM ANODIZED NICKEL|23|4 +Brand#13|MEDIUM ANODIZED NICKEL|49|4 +Brand#13|MEDIUM ANODIZED STEEL|19|4 +Brand#13|MEDIUM ANODIZED STEEL|36|4 +Brand#13|MEDIUM ANODIZED STEEL|45|4 +Brand#13|MEDIUM ANODIZED TIN|14|4 +Brand#13|MEDIUM ANODIZED TIN|19|4 +Brand#13|MEDIUM ANODIZED TIN|49|4 +Brand#13|MEDIUM BRUSHED BRASS|3|4 +Brand#13|MEDIUM BRUSHED BRASS|19|4 +Brand#13|MEDIUM BRUSHED BRASS|23|4 +Brand#13|MEDIUM BRUSHED COPPER|9|4 +Brand#13|MEDIUM BRUSHED COPPER|36|4 +Brand#13|MEDIUM BRUSHED COPPER|45|4 +Brand#13|MEDIUM BRUSHED NICKEL|23|4 +Brand#13|MEDIUM BRUSHED NICKEL|36|4 +Brand#13|MEDIUM BRUSHED NICKEL|45|4 +Brand#13|MEDIUM BRUSHED STEEL|3|4 +Brand#13|MEDIUM BRUSHED STEEL|23|4 +Brand#13|MEDIUM BRUSHED TIN|3|4 +Brand#13|MEDIUM BRUSHED TIN|14|4 +Brand#13|MEDIUM BRUSHED TIN|36|4 +Brand#13|MEDIUM BRUSHED TIN|49|4 +Brand#13|MEDIUM BURNISHED BRASS|9|4 +Brand#13|MEDIUM BURNISHED BRASS|23|4 +Brand#13|MEDIUM BURNISHED BRASS|49|4 +Brand#13|MEDIUM BURNISHED COPPER|14|4 +Brand#13|MEDIUM BURNISHED COPPER|49|4 +Brand#13|MEDIUM BURNISHED NICKEL|14|4 +Brand#13|MEDIUM BURNISHED NICKEL|19|4 +Brand#13|MEDIUM BURNISHED NICKEL|45|4 +Brand#13|MEDIUM BURNISHED STEEL|9|4 +Brand#13|MEDIUM BURNISHED STEEL|23|4 +Brand#13|MEDIUM BURNISHED STEEL|36|4 +Brand#13|MEDIUM BURNISHED TIN|9|4 +Brand#13|MEDIUM BURNISHED TIN|14|4 +Brand#13|MEDIUM BURNISHED TIN|23|4 +Brand#13|MEDIUM PLATED BRASS|3|4 +Brand#13|MEDIUM PLATED BRASS|14|4 +Brand#13|MEDIUM PLATED BRASS|36|4 +Brand#13|MEDIUM PLATED BRASS|45|4 +Brand#13|MEDIUM PLATED COPPER|3|4 +Brand#13|MEDIUM PLATED COPPER|9|4 +Brand#13|MEDIUM PLATED COPPER|23|4 +Brand#13|MEDIUM PLATED NICKEL|9|4 +Brand#13|MEDIUM PLATED NICKEL|49|4 +Brand#13|MEDIUM PLATED STEEL|14|4 +Brand#13|MEDIUM PLATED STEEL|49|4 +Brand#13|MEDIUM PLATED TIN|14|4 +Brand#13|MEDIUM PLATED TIN|23|4 +Brand#13|MEDIUM PLATED TIN|45|4 +Brand#13|MEDIUM PLATED TIN|49|4 +Brand#13|PROMO ANODIZED BRASS|9|4 +Brand#13|PROMO ANODIZED BRASS|36|4 +Brand#13|PROMO ANODIZED BRASS|49|4 +Brand#13|PROMO ANODIZED COPPER|19|4 +Brand#13|PROMO ANODIZED COPPER|36|4 +Brand#13|PROMO ANODIZED COPPER|49|4 +Brand#13|PROMO ANODIZED NICKEL|14|4 +Brand#13|PROMO ANODIZED NICKEL|19|4 +Brand#13|PROMO ANODIZED NICKEL|23|4 +Brand#13|PROMO ANODIZED NICKEL|36|4 +Brand#13|PROMO ANODIZED STEEL|3|4 +Brand#13|PROMO ANODIZED STEEL|9|4 +Brand#13|PROMO ANODIZED STEEL|14|4 +Brand#13|PROMO ANODIZED STEEL|23|4 +Brand#13|PROMO ANODIZED STEEL|45|4 +Brand#13|PROMO ANODIZED STEEL|49|4 +Brand#13|PROMO ANODIZED TIN|3|4 +Brand#13|PROMO ANODIZED TIN|9|4 +Brand#13|PROMO ANODIZED TIN|14|4 +Brand#13|PROMO ANODIZED TIN|19|4 +Brand#13|PROMO ANODIZED TIN|23|4 +Brand#13|PROMO ANODIZED TIN|45|4 +Brand#13|PROMO BRUSHED BRASS|9|4 +Brand#13|PROMO BRUSHED BRASS|14|4 +Brand#13|PROMO BRUSHED BRASS|19|4 +Brand#13|PROMO BRUSHED COPPER|9|4 +Brand#13|PROMO BRUSHED COPPER|23|4 +Brand#13|PROMO BRUSHED COPPER|45|4 +Brand#13|PROMO BRUSHED NICKEL|3|4 +Brand#13|PROMO BRUSHED NICKEL|45|4 +Brand#13|PROMO BRUSHED STEEL|14|4 +Brand#13|PROMO BRUSHED STEEL|19|4 +Brand#13|PROMO BRUSHED STEEL|36|4 +Brand#13|PROMO BRUSHED STEEL|49|4 +Brand#13|PROMO BRUSHED TIN|19|4 +Brand#13|PROMO BRUSHED TIN|49|4 +Brand#13|PROMO BURNISHED BRASS|3|4 +Brand#13|PROMO BURNISHED BRASS|14|4 +Brand#13|PROMO BURNISHED BRASS|49|4 +Brand#13|PROMO BURNISHED COPPER|14|4 +Brand#13|PROMO BURNISHED COPPER|36|4 +Brand#13|PROMO BURNISHED NICKEL|19|4 +Brand#13|PROMO BURNISHED NICKEL|23|4 +Brand#13|PROMO BURNISHED NICKEL|45|4 +Brand#13|PROMO BURNISHED STEEL|3|4 +Brand#13|PROMO BURNISHED STEEL|36|4 +Brand#13|PROMO BURNISHED TIN|36|4 +Brand#13|PROMO BURNISHED TIN|49|4 +Brand#13|PROMO PLATED BRASS|3|4 +Brand#13|PROMO PLATED BRASS|9|4 +Brand#13|PROMO PLATED BRASS|19|4 +Brand#13|PROMO PLATED BRASS|23|4 +Brand#13|PROMO PLATED BRASS|36|4 +Brand#13|PROMO PLATED BRASS|45|4 +Brand#13|PROMO PLATED COPPER|19|4 +Brand#13|PROMO PLATED COPPER|23|4 +Brand#13|PROMO PLATED COPPER|49|4 +Brand#13|PROMO PLATED NICKEL|45|4 +Brand#13|PROMO PLATED STEEL|3|4 +Brand#13|PROMO PLATED STEEL|14|4 +Brand#13|PROMO PLATED STEEL|23|4 +Brand#13|PROMO PLATED STEEL|36|4 +Brand#13|PROMO PLATED STEEL|49|4 +Brand#13|PROMO PLATED TIN|3|4 +Brand#13|PROMO PLATED TIN|9|4 +Brand#13|PROMO PLATED TIN|19|4 +Brand#13|PROMO PLATED TIN|36|4 +Brand#13|PROMO PLATED TIN|45|4 +Brand#13|PROMO PLATED TIN|49|4 +Brand#13|PROMO POLISHED BRASS|9|4 +Brand#13|PROMO POLISHED BRASS|14|4 +Brand#13|PROMO POLISHED BRASS|23|4 +Brand#13|PROMO POLISHED COPPER|3|4 +Brand#13|PROMO POLISHED COPPER|23|4 +Brand#13|PROMO POLISHED COPPER|49|4 +Brand#13|PROMO POLISHED NICKEL|9|4 +Brand#13|PROMO POLISHED NICKEL|19|4 +Brand#13|PROMO POLISHED STEEL|3|4 +Brand#13|PROMO POLISHED STEEL|9|4 +Brand#13|PROMO POLISHED STEEL|19|4 +Brand#13|PROMO POLISHED STEEL|49|4 +Brand#13|PROMO POLISHED TIN|3|4 +Brand#13|PROMO POLISHED TIN|14|4 +Brand#13|PROMO POLISHED TIN|49|4 +Brand#13|SMALL ANODIZED BRASS|3|4 +Brand#13|SMALL ANODIZED BRASS|9|4 +Brand#13|SMALL ANODIZED BRASS|23|4 +Brand#13|SMALL ANODIZED BRASS|45|4 +Brand#13|SMALL ANODIZED COPPER|3|4 +Brand#13|SMALL ANODIZED COPPER|14|4 +Brand#13|SMALL ANODIZED COPPER|45|4 +Brand#13|SMALL ANODIZED COPPER|49|4 +Brand#13|SMALL ANODIZED NICKEL|9|4 +Brand#13|SMALL ANODIZED NICKEL|23|4 +Brand#13|SMALL ANODIZED NICKEL|36|4 +Brand#13|SMALL ANODIZED STEEL|19|4 +Brand#13|SMALL ANODIZED STEEL|36|4 +Brand#13|SMALL ANODIZED STEEL|49|4 +Brand#13|SMALL ANODIZED TIN|3|4 +Brand#13|SMALL BRUSHED BRASS|23|4 +Brand#13|SMALL BRUSHED BRASS|45|4 +Brand#13|SMALL BRUSHED COPPER|3|4 +Brand#13|SMALL BRUSHED COPPER|49|4 +Brand#13|SMALL BRUSHED NICKEL|45|4 +Brand#13|SMALL BRUSHED NICKEL|49|4 +Brand#13|SMALL BRUSHED STEEL|9|4 +Brand#13|SMALL BRUSHED STEEL|14|4 +Brand#13|SMALL BRUSHED STEEL|19|4 +Brand#13|SMALL BRUSHED TIN|14|4 +Brand#13|SMALL BRUSHED TIN|19|4 +Brand#13|SMALL BRUSHED TIN|36|4 +Brand#13|SMALL BURNISHED BRASS|9|4 +Brand#13|SMALL BURNISHED BRASS|23|4 +Brand#13|SMALL BURNISHED BRASS|36|4 +Brand#13|SMALL BURNISHED COPPER|3|4 +Brand#13|SMALL BURNISHED COPPER|14|4 +Brand#13|SMALL BURNISHED COPPER|19|4 +Brand#13|SMALL BURNISHED COPPER|36|4 +Brand#13|SMALL BURNISHED NICKEL|14|4 +Brand#13|SMALL BURNISHED NICKEL|36|4 +Brand#13|SMALL BURNISHED STEEL|14|4 +Brand#13|SMALL BURNISHED TIN|3|4 +Brand#13|SMALL BURNISHED TIN|23|4 +Brand#13|SMALL BURNISHED TIN|45|4 +Brand#13|SMALL PLATED BRASS|3|4 +Brand#13|SMALL PLATED BRASS|14|4 +Brand#13|SMALL PLATED COPPER|9|4 +Brand#13|SMALL PLATED COPPER|45|4 +Brand#13|SMALL PLATED NICKEL|3|4 +Brand#13|SMALL PLATED NICKEL|9|4 +Brand#13|SMALL PLATED NICKEL|19|4 +Brand#13|SMALL PLATED STEEL|3|4 +Brand#13|SMALL PLATED STEEL|45|4 +Brand#13|SMALL PLATED STEEL|49|4 +Brand#13|SMALL PLATED TIN|9|4 +Brand#13|SMALL PLATED TIN|23|4 +Brand#13|SMALL PLATED TIN|45|4 +Brand#13|SMALL POLISHED BRASS|3|4 +Brand#13|SMALL POLISHED BRASS|19|4 +Brand#13|SMALL POLISHED BRASS|36|4 +Brand#13|SMALL POLISHED COPPER|14|4 +Brand#13|SMALL POLISHED COPPER|23|4 +Brand#13|SMALL POLISHED COPPER|36|4 +Brand#13|SMALL POLISHED NICKEL|9|4 +Brand#13|SMALL POLISHED NICKEL|23|4 +Brand#13|SMALL POLISHED NICKEL|49|4 +Brand#13|SMALL POLISHED STEEL|9|4 +Brand#13|SMALL POLISHED STEEL|19|4 +Brand#13|SMALL POLISHED TIN|3|4 +Brand#13|SMALL POLISHED TIN|9|4 +Brand#13|SMALL POLISHED TIN|19|4 +Brand#13|SMALL POLISHED TIN|23|4 +Brand#13|SMALL POLISHED TIN|36|4 +Brand#13|SMALL POLISHED TIN|45|4 +Brand#13|SMALL POLISHED TIN|49|4 +Brand#13|STANDARD ANODIZED BRASS|3|4 +Brand#13|STANDARD ANODIZED BRASS|19|4 +Brand#13|STANDARD ANODIZED BRASS|36|4 +Brand#13|STANDARD ANODIZED BRASS|45|4 +Brand#13|STANDARD ANODIZED COPPER|9|4 +Brand#13|STANDARD ANODIZED COPPER|45|4 +Brand#13|STANDARD ANODIZED NICKEL|9|4 +Brand#13|STANDARD ANODIZED NICKEL|36|4 +Brand#13|STANDARD ANODIZED STEEL|49|4 +Brand#13|STANDARD ANODIZED TIN|3|4 +Brand#13|STANDARD ANODIZED TIN|14|4 +Brand#13|STANDARD ANODIZED TIN|19|4 +Brand#13|STANDARD ANODIZED TIN|45|4 +Brand#13|STANDARD ANODIZED TIN|49|4 +Brand#13|STANDARD BRUSHED BRASS|3|4 +Brand#13|STANDARD BRUSHED BRASS|9|4 +Brand#13|STANDARD BRUSHED BRASS|19|4 +Brand#13|STANDARD BRUSHED BRASS|23|4 +Brand#13|STANDARD BRUSHED BRASS|45|4 +Brand#13|STANDARD BRUSHED BRASS|49|4 +Brand#13|STANDARD BRUSHED COPPER|14|4 +Brand#13|STANDARD BRUSHED COPPER|36|4 +Brand#13|STANDARD BRUSHED COPPER|45|4 +Brand#13|STANDARD BRUSHED NICKEL|3|4 +Brand#13|STANDARD BRUSHED NICKEL|9|4 +Brand#13|STANDARD BRUSHED NICKEL|19|4 +Brand#13|STANDARD BRUSHED NICKEL|23|4 +Brand#13|STANDARD BRUSHED NICKEL|45|4 +Brand#13|STANDARD BRUSHED STEEL|3|4 +Brand#13|STANDARD BRUSHED STEEL|14|4 +Brand#13|STANDARD BRUSHED STEEL|19|4 +Brand#13|STANDARD BRUSHED STEEL|23|4 +Brand#13|STANDARD BRUSHED TIN|14|4 +Brand#13|STANDARD BRUSHED TIN|36|4 +Brand#13|STANDARD BRUSHED TIN|45|4 +Brand#13|STANDARD BURNISHED BRASS|14|4 +Brand#13|STANDARD BURNISHED BRASS|45|4 +Brand#13|STANDARD BURNISHED COPPER|19|4 +Brand#13|STANDARD BURNISHED NICKEL|36|4 +Brand#13|STANDARD BURNISHED NICKEL|45|4 +Brand#13|STANDARD BURNISHED STEEL|9|4 +Brand#13|STANDARD BURNISHED STEEL|14|4 +Brand#13|STANDARD BURNISHED STEEL|23|4 +Brand#13|STANDARD BURNISHED STEEL|36|4 +Brand#13|STANDARD BURNISHED STEEL|49|4 +Brand#13|STANDARD BURNISHED TIN|14|4 +Brand#13|STANDARD BURNISHED TIN|45|4 +Brand#13|STANDARD PLATED COPPER|3|4 +Brand#13|STANDARD PLATED COPPER|9|4 +Brand#13|STANDARD PLATED COPPER|19|4 +Brand#13|STANDARD PLATED COPPER|49|4 +Brand#13|STANDARD PLATED NICKEL|19|4 +Brand#13|STANDARD PLATED STEEL|3|4 +Brand#13|STANDARD PLATED STEEL|23|4 +Brand#13|STANDARD PLATED STEEL|45|4 +Brand#13|STANDARD PLATED TIN|3|4 +Brand#13|STANDARD PLATED TIN|9|4 +Brand#13|STANDARD POLISHED BRASS|3|4 +Brand#13|STANDARD POLISHED BRASS|9|4 +Brand#13|STANDARD POLISHED BRASS|14|4 +Brand#13|STANDARD POLISHED BRASS|23|4 +Brand#13|STANDARD POLISHED BRASS|49|4 +Brand#13|STANDARD POLISHED COPPER|9|4 +Brand#13|STANDARD POLISHED COPPER|19|4 +Brand#13|STANDARD POLISHED COPPER|49|4 +Brand#13|STANDARD POLISHED NICKEL|14|4 +Brand#13|STANDARD POLISHED STEEL|3|4 +Brand#13|STANDARD POLISHED TIN|3|4 +Brand#13|STANDARD POLISHED TIN|9|4 +Brand#13|STANDARD POLISHED TIN|49|4 +Brand#14|ECONOMY ANODIZED BRASS|9|4 +Brand#14|ECONOMY ANODIZED BRASS|19|4 +Brand#14|ECONOMY ANODIZED COPPER|19|4 +Brand#14|ECONOMY ANODIZED COPPER|23|4 +Brand#14|ECONOMY ANODIZED COPPER|49|4 +Brand#14|ECONOMY ANODIZED NICKEL|3|4 +Brand#14|ECONOMY ANODIZED NICKEL|19|4 +Brand#14|ECONOMY ANODIZED NICKEL|36|4 +Brand#14|ECONOMY ANODIZED STEEL|23|4 +Brand#14|ECONOMY ANODIZED STEEL|36|4 +Brand#14|ECONOMY ANODIZED TIN|14|4 +Brand#14|ECONOMY ANODIZED TIN|36|4 +Brand#14|ECONOMY ANODIZED TIN|49|4 +Brand#14|ECONOMY BRUSHED BRASS|19|4 +Brand#14|ECONOMY BRUSHED BRASS|36|4 +Brand#14|ECONOMY BRUSHED BRASS|45|4 +Brand#14|ECONOMY BRUSHED COPPER|9|4 +Brand#14|ECONOMY BRUSHED COPPER|14|4 +Brand#14|ECONOMY BRUSHED COPPER|23|4 +Brand#14|ECONOMY BRUSHED COPPER|36|4 +Brand#14|ECONOMY BRUSHED NICKEL|19|4 +Brand#14|ECONOMY BRUSHED NICKEL|23|4 +Brand#14|ECONOMY BRUSHED NICKEL|45|4 +Brand#14|ECONOMY BRUSHED NICKEL|49|4 +Brand#14|ECONOMY BRUSHED STEEL|9|4 +Brand#14|ECONOMY BRUSHED STEEL|14|4 +Brand#14|ECONOMY BRUSHED STEEL|19|4 +Brand#14|ECONOMY BRUSHED STEEL|23|4 +Brand#14|ECONOMY BRUSHED TIN|9|4 +Brand#14|ECONOMY BRUSHED TIN|19|4 +Brand#14|ECONOMY BRUSHED TIN|23|4 +Brand#14|ECONOMY BRUSHED TIN|36|4 +Brand#14|ECONOMY BRUSHED TIN|45|4 +Brand#14|ECONOMY BURNISHED BRASS|3|4 +Brand#14|ECONOMY BURNISHED BRASS|9|4 +Brand#14|ECONOMY BURNISHED BRASS|19|4 +Brand#14|ECONOMY BURNISHED BRASS|36|4 +Brand#14|ECONOMY BURNISHED COPPER|3|4 +Brand#14|ECONOMY BURNISHED COPPER|14|4 +Brand#14|ECONOMY BURNISHED COPPER|19|4 +Brand#14|ECONOMY BURNISHED NICKEL|14|4 +Brand#14|ECONOMY BURNISHED NICKEL|19|4 +Brand#14|ECONOMY BURNISHED NICKEL|49|4 +Brand#14|ECONOMY BURNISHED TIN|3|4 +Brand#14|ECONOMY BURNISHED TIN|45|4 +Brand#14|ECONOMY BURNISHED TIN|49|4 +Brand#14|ECONOMY PLATED BRASS|3|4 +Brand#14|ECONOMY PLATED BRASS|19|4 +Brand#14|ECONOMY PLATED BRASS|23|4 +Brand#14|ECONOMY PLATED BRASS|49|4 +Brand#14|ECONOMY PLATED COPPER|36|4 +Brand#14|ECONOMY PLATED COPPER|45|4 +Brand#14|ECONOMY PLATED COPPER|49|4 +Brand#14|ECONOMY PLATED NICKEL|14|4 +Brand#14|ECONOMY PLATED NICKEL|45|4 +Brand#14|ECONOMY PLATED STEEL|14|4 +Brand#14|ECONOMY PLATED STEEL|19|4 +Brand#14|ECONOMY PLATED STEEL|23|4 +Brand#14|ECONOMY PLATED STEEL|45|4 +Brand#14|ECONOMY PLATED STEEL|49|4 +Brand#14|ECONOMY PLATED TIN|3|4 +Brand#14|ECONOMY PLATED TIN|14|4 +Brand#14|ECONOMY PLATED TIN|23|4 +Brand#14|ECONOMY PLATED TIN|49|4 +Brand#14|ECONOMY POLISHED BRASS|9|4 +Brand#14|ECONOMY POLISHED BRASS|14|4 +Brand#14|ECONOMY POLISHED BRASS|45|4 +Brand#14|ECONOMY POLISHED COPPER|3|4 +Brand#14|ECONOMY POLISHED COPPER|9|4 +Brand#14|ECONOMY POLISHED COPPER|19|4 +Brand#14|ECONOMY POLISHED COPPER|36|4 +Brand#14|ECONOMY POLISHED COPPER|45|4 +Brand#14|ECONOMY POLISHED NICKEL|23|4 +Brand#14|ECONOMY POLISHED STEEL|14|4 +Brand#14|ECONOMY POLISHED STEEL|19|4 +Brand#14|ECONOMY POLISHED STEEL|23|4 +Brand#14|ECONOMY POLISHED STEEL|36|4 +Brand#14|ECONOMY POLISHED TIN|9|4 +Brand#14|ECONOMY POLISHED TIN|14|4 +Brand#14|ECONOMY POLISHED TIN|36|4 +Brand#14|ECONOMY POLISHED TIN|45|4 +Brand#14|LARGE ANODIZED BRASS|23|4 +Brand#14|LARGE ANODIZED BRASS|36|4 +Brand#14|LARGE ANODIZED BRASS|45|4 +Brand#14|LARGE ANODIZED BRASS|49|4 +Brand#14|LARGE ANODIZED COPPER|9|4 +Brand#14|LARGE ANODIZED COPPER|36|4 +Brand#14|LARGE ANODIZED NICKEL|3|4 +Brand#14|LARGE ANODIZED NICKEL|19|4 +Brand#14|LARGE ANODIZED STEEL|14|4 +Brand#14|LARGE ANODIZED STEEL|23|4 +Brand#14|LARGE ANODIZED STEEL|36|4 +Brand#14|LARGE ANODIZED STEEL|49|4 +Brand#14|LARGE ANODIZED TIN|3|4 +Brand#14|LARGE ANODIZED TIN|36|4 +Brand#14|LARGE ANODIZED TIN|45|4 +Brand#14|LARGE ANODIZED TIN|49|4 +Brand#14|LARGE BRUSHED BRASS|3|4 +Brand#14|LARGE BRUSHED BRASS|19|4 +Brand#14|LARGE BRUSHED BRASS|36|4 +Brand#14|LARGE BRUSHED COPPER|3|4 +Brand#14|LARGE BRUSHED COPPER|45|4 +Brand#14|LARGE BRUSHED NICKEL|9|4 +Brand#14|LARGE BRUSHED NICKEL|36|4 +Brand#14|LARGE BRUSHED NICKEL|49|4 +Brand#14|LARGE BRUSHED STEEL|14|4 +Brand#14|LARGE BRUSHED STEEL|23|4 +Brand#14|LARGE BRUSHED STEEL|49|4 +Brand#14|LARGE BRUSHED TIN|19|4 +Brand#14|LARGE BRUSHED TIN|23|4 +Brand#14|LARGE BURNISHED BRASS|3|4 +Brand#14|LARGE BURNISHED BRASS|19|4 +Brand#14|LARGE BURNISHED BRASS|36|4 +Brand#14|LARGE BURNISHED COPPER|3|4 +Brand#14|LARGE BURNISHED COPPER|23|4 +Brand#14|LARGE BURNISHED COPPER|36|4 +Brand#14|LARGE BURNISHED COPPER|45|4 +Brand#14|LARGE BURNISHED NICKEL|14|4 +Brand#14|LARGE BURNISHED NICKEL|19|4 +Brand#14|LARGE BURNISHED NICKEL|45|4 +Brand#14|LARGE BURNISHED STEEL|49|4 +Brand#14|LARGE BURNISHED TIN|3|4 +Brand#14|LARGE BURNISHED TIN|14|4 +Brand#14|LARGE BURNISHED TIN|36|4 +Brand#14|LARGE BURNISHED TIN|49|4 +Brand#14|LARGE PLATED BRASS|3|4 +Brand#14|LARGE PLATED BRASS|9|4 +Brand#14|LARGE PLATED COPPER|9|4 +Brand#14|LARGE PLATED COPPER|14|4 +Brand#14|LARGE PLATED COPPER|19|4 +Brand#14|LARGE PLATED COPPER|45|4 +Brand#14|LARGE PLATED NICKEL|3|4 +Brand#14|LARGE PLATED NICKEL|9|4 +Brand#14|LARGE PLATED NICKEL|14|4 +Brand#14|LARGE PLATED STEEL|14|4 +Brand#14|LARGE PLATED STEEL|19|4 +Brand#14|LARGE PLATED TIN|3|4 +Brand#14|LARGE PLATED TIN|9|4 +Brand#14|LARGE PLATED TIN|19|4 +Brand#14|LARGE PLATED TIN|23|4 +Brand#14|LARGE PLATED TIN|45|4 +Brand#14|LARGE PLATED TIN|49|4 +Brand#14|LARGE POLISHED BRASS|49|4 +Brand#14|LARGE POLISHED COPPER|3|4 +Brand#14|LARGE POLISHED COPPER|14|4 +Brand#14|LARGE POLISHED COPPER|19|4 +Brand#14|LARGE POLISHED COPPER|36|4 +Brand#14|LARGE POLISHED COPPER|49|4 +Brand#14|LARGE POLISHED NICKEL|3|4 +Brand#14|LARGE POLISHED NICKEL|19|4 +Brand#14|LARGE POLISHED NICKEL|45|4 +Brand#14|LARGE POLISHED NICKEL|49|4 +Brand#14|LARGE POLISHED STEEL|9|4 +Brand#14|LARGE POLISHED STEEL|14|4 +Brand#14|LARGE POLISHED STEEL|36|4 +Brand#14|LARGE POLISHED STEEL|49|4 +Brand#14|LARGE POLISHED TIN|3|4 +Brand#14|LARGE POLISHED TIN|19|4 +Brand#14|MEDIUM ANODIZED BRASS|9|4 +Brand#14|MEDIUM ANODIZED BRASS|23|4 +Brand#14|MEDIUM ANODIZED BRASS|36|4 +Brand#14|MEDIUM ANODIZED BRASS|45|4 +Brand#14|MEDIUM ANODIZED BRASS|49|4 +Brand#14|MEDIUM ANODIZED COPPER|3|4 +Brand#14|MEDIUM ANODIZED COPPER|14|4 +Brand#14|MEDIUM ANODIZED COPPER|23|4 +Brand#14|MEDIUM ANODIZED NICKEL|23|4 +Brand#14|MEDIUM ANODIZED NICKEL|49|4 +Brand#14|MEDIUM ANODIZED STEEL|3|4 +Brand#14|MEDIUM ANODIZED STEEL|14|4 +Brand#14|MEDIUM ANODIZED STEEL|23|4 +Brand#14|MEDIUM ANODIZED STEEL|45|4 +Brand#14|MEDIUM ANODIZED STEEL|49|4 +Brand#14|MEDIUM ANODIZED TIN|3|4 +Brand#14|MEDIUM ANODIZED TIN|19|4 +Brand#14|MEDIUM ANODIZED TIN|23|4 +Brand#14|MEDIUM ANODIZED TIN|45|4 +Brand#14|MEDIUM BRUSHED BRASS|3|4 +Brand#14|MEDIUM BRUSHED BRASS|14|4 +Brand#14|MEDIUM BRUSHED BRASS|36|4 +Brand#14|MEDIUM BRUSHED BRASS|45|4 +Brand#14|MEDIUM BRUSHED COPPER|3|4 +Brand#14|MEDIUM BRUSHED COPPER|14|4 +Brand#14|MEDIUM BRUSHED COPPER|19|4 +Brand#14|MEDIUM BRUSHED COPPER|49|4 +Brand#14|MEDIUM BRUSHED NICKEL|3|4 +Brand#14|MEDIUM BRUSHED NICKEL|19|4 +Brand#14|MEDIUM BRUSHED NICKEL|23|4 +Brand#14|MEDIUM BRUSHED STEEL|3|4 +Brand#14|MEDIUM BRUSHED STEEL|14|4 +Brand#14|MEDIUM BRUSHED STEEL|45|4 +Brand#14|MEDIUM BRUSHED TIN|36|4 +Brand#14|MEDIUM BRUSHED TIN|49|4 +Brand#14|MEDIUM BURNISHED BRASS|9|4 +Brand#14|MEDIUM BURNISHED BRASS|14|4 +Brand#14|MEDIUM BURNISHED BRASS|45|4 +Brand#14|MEDIUM BURNISHED COPPER|19|4 +Brand#14|MEDIUM BURNISHED COPPER|23|4 +Brand#14|MEDIUM BURNISHED COPPER|36|4 +Brand#14|MEDIUM BURNISHED COPPER|49|4 +Brand#14|MEDIUM BURNISHED NICKEL|45|4 +Brand#14|MEDIUM BURNISHED STEEL|9|4 +Brand#14|MEDIUM BURNISHED TIN|9|4 +Brand#14|MEDIUM BURNISHED TIN|23|4 +Brand#14|MEDIUM PLATED BRASS|14|4 +Brand#14|MEDIUM PLATED COPPER|49|4 +Brand#14|MEDIUM PLATED NICKEL|3|4 +Brand#14|MEDIUM PLATED NICKEL|14|4 +Brand#14|MEDIUM PLATED NICKEL|19|4 +Brand#14|MEDIUM PLATED NICKEL|36|4 +Brand#14|MEDIUM PLATED NICKEL|45|4 +Brand#14|MEDIUM PLATED STEEL|3|4 +Brand#14|MEDIUM PLATED STEEL|14|4 +Brand#14|MEDIUM PLATED STEEL|23|4 +Brand#14|PROMO ANODIZED BRASS|3|4 +Brand#14|PROMO ANODIZED BRASS|9|4 +Brand#14|PROMO ANODIZED BRASS|14|4 +Brand#14|PROMO ANODIZED BRASS|49|4 +Brand#14|PROMO ANODIZED COPPER|23|4 +Brand#14|PROMO ANODIZED COPPER|49|4 +Brand#14|PROMO ANODIZED NICKEL|3|4 +Brand#14|PROMO ANODIZED NICKEL|23|4 +Brand#14|PROMO ANODIZED STEEL|9|4 +Brand#14|PROMO ANODIZED STEEL|49|4 +Brand#14|PROMO ANODIZED TIN|3|4 +Brand#14|PROMO ANODIZED TIN|23|4 +Brand#14|PROMO ANODIZED TIN|36|4 +Brand#14|PROMO ANODIZED TIN|45|4 +Brand#14|PROMO ANODIZED TIN|49|4 +Brand#14|PROMO BRUSHED BRASS|3|4 +Brand#14|PROMO BRUSHED BRASS|9|4 +Brand#14|PROMO BRUSHED COPPER|3|4 +Brand#14|PROMO BRUSHED COPPER|19|4 +Brand#14|PROMO BRUSHED NICKEL|3|4 +Brand#14|PROMO BRUSHED NICKEL|9|4 +Brand#14|PROMO BRUSHED NICKEL|14|4 +Brand#14|PROMO BRUSHED STEEL|14|4 +Brand#14|PROMO BRUSHED STEEL|19|4 +Brand#14|PROMO BRUSHED STEEL|23|4 +Brand#14|PROMO BRUSHED STEEL|45|4 +Brand#14|PROMO BRUSHED TIN|14|4 +Brand#14|PROMO BRUSHED TIN|19|4 +Brand#14|PROMO BRUSHED TIN|23|4 +Brand#14|PROMO BRUSHED TIN|45|4 +Brand#14|PROMO BRUSHED TIN|49|4 +Brand#14|PROMO BURNISHED BRASS|3|4 +Brand#14|PROMO BURNISHED BRASS|14|4 +Brand#14|PROMO BURNISHED COPPER|3|4 +Brand#14|PROMO BURNISHED COPPER|9|4 +Brand#14|PROMO BURNISHED COPPER|14|4 +Brand#14|PROMO BURNISHED COPPER|19|4 +Brand#14|PROMO BURNISHED COPPER|36|4 +Brand#14|PROMO BURNISHED NICKEL|23|4 +Brand#14|PROMO BURNISHED NICKEL|45|4 +Brand#14|PROMO BURNISHED NICKEL|49|4 +Brand#14|PROMO BURNISHED STEEL|3|4 +Brand#14|PROMO BURNISHED STEEL|19|4 +Brand#14|PROMO BURNISHED STEEL|49|4 +Brand#14|PROMO BURNISHED TIN|3|4 +Brand#14|PROMO BURNISHED TIN|9|4 +Brand#14|PROMO BURNISHED TIN|23|4 +Brand#14|PROMO PLATED BRASS|3|4 +Brand#14|PROMO PLATED BRASS|23|4 +Brand#14|PROMO PLATED BRASS|49|4 +Brand#14|PROMO PLATED COPPER|3|4 +Brand#14|PROMO PLATED COPPER|9|4 +Brand#14|PROMO PLATED COPPER|36|4 +Brand#14|PROMO PLATED COPPER|49|4 +Brand#14|PROMO PLATED NICKEL|14|4 +Brand#14|PROMO PLATED NICKEL|19|4 +Brand#14|PROMO PLATED STEEL|36|4 +Brand#14|PROMO PLATED STEEL|45|4 +Brand#14|PROMO PLATED TIN|23|4 +Brand#14|PROMO POLISHED BRASS|3|4 +Brand#14|PROMO POLISHED BRASS|45|4 +Brand#14|PROMO POLISHED COPPER|9|4 +Brand#14|PROMO POLISHED COPPER|23|4 +Brand#14|PROMO POLISHED COPPER|36|4 +Brand#14|PROMO POLISHED COPPER|45|4 +Brand#14|PROMO POLISHED COPPER|49|4 +Brand#14|PROMO POLISHED NICKEL|19|4 +Brand#14|PROMO POLISHED NICKEL|23|4 +Brand#14|PROMO POLISHED NICKEL|36|4 +Brand#14|PROMO POLISHED NICKEL|49|4 +Brand#14|PROMO POLISHED STEEL|9|4 +Brand#14|PROMO POLISHED STEEL|45|4 +Brand#14|PROMO POLISHED TIN|23|4 +Brand#14|PROMO POLISHED TIN|36|4 +Brand#14|SMALL ANODIZED BRASS|3|4 +Brand#14|SMALL ANODIZED BRASS|19|4 +Brand#14|SMALL ANODIZED BRASS|23|4 +Brand#14|SMALL ANODIZED BRASS|36|4 +Brand#14|SMALL ANODIZED BRASS|45|4 +Brand#14|SMALL ANODIZED BRASS|49|4 +Brand#14|SMALL ANODIZED COPPER|9|4 +Brand#14|SMALL ANODIZED COPPER|19|4 +Brand#14|SMALL ANODIZED COPPER|23|4 +Brand#14|SMALL ANODIZED COPPER|36|4 +Brand#14|SMALL ANODIZED COPPER|45|4 +Brand#14|SMALL ANODIZED NICKEL|14|4 +Brand#14|SMALL ANODIZED NICKEL|23|4 +Brand#14|SMALL ANODIZED STEEL|45|4 +Brand#14|SMALL ANODIZED TIN|9|4 +Brand#14|SMALL ANODIZED TIN|14|4 +Brand#14|SMALL ANODIZED TIN|23|4 +Brand#14|SMALL ANODIZED TIN|36|4 +Brand#14|SMALL ANODIZED TIN|49|4 +Brand#14|SMALL BRUSHED BRASS|3|4 +Brand#14|SMALL BRUSHED BRASS|36|4 +Brand#14|SMALL BRUSHED COPPER|9|4 +Brand#14|SMALL BRUSHED COPPER|14|4 +Brand#14|SMALL BRUSHED COPPER|19|4 +Brand#14|SMALL BRUSHED COPPER|23|4 +Brand#14|SMALL BRUSHED COPPER|45|4 +Brand#14|SMALL BRUSHED NICKEL|3|4 +Brand#14|SMALL BRUSHED NICKEL|14|4 +Brand#14|SMALL BRUSHED NICKEL|23|4 +Brand#14|SMALL BRUSHED NICKEL|45|4 +Brand#14|SMALL BRUSHED STEEL|9|4 +Brand#14|SMALL BRUSHED STEEL|19|4 +Brand#14|SMALL BRUSHED STEEL|49|4 +Brand#14|SMALL BRUSHED TIN|3|4 +Brand#14|SMALL BRUSHED TIN|23|4 +Brand#14|SMALL BRUSHED TIN|45|4 +Brand#14|SMALL BURNISHED BRASS|9|4 +Brand#14|SMALL BURNISHED COPPER|3|4 +Brand#14|SMALL BURNISHED COPPER|9|4 +Brand#14|SMALL BURNISHED COPPER|19|4 +Brand#14|SMALL BURNISHED COPPER|23|4 +Brand#14|SMALL BURNISHED COPPER|49|4 +Brand#14|SMALL BURNISHED NICKEL|3|4 +Brand#14|SMALL BURNISHED NICKEL|23|4 +Brand#14|SMALL BURNISHED STEEL|3|4 +Brand#14|SMALL BURNISHED TIN|3|4 +Brand#14|SMALL BURNISHED TIN|9|4 +Brand#14|SMALL BURNISHED TIN|14|4 +Brand#14|SMALL BURNISHED TIN|36|4 +Brand#14|SMALL BURNISHED TIN|45|4 +Brand#14|SMALL PLATED BRASS|3|4 +Brand#14|SMALL PLATED BRASS|19|4 +Brand#14|SMALL PLATED COPPER|14|4 +Brand#14|SMALL PLATED COPPER|36|4 +Brand#14|SMALL PLATED COPPER|45|4 +Brand#14|SMALL PLATED NICKEL|3|4 +Brand#14|SMALL PLATED NICKEL|9|4 +Brand#14|SMALL PLATED NICKEL|45|4 +Brand#14|SMALL PLATED NICKEL|49|4 +Brand#14|SMALL PLATED STEEL|3|4 +Brand#14|SMALL PLATED STEEL|45|4 +Brand#14|SMALL PLATED TIN|3|4 +Brand#14|SMALL PLATED TIN|23|4 +Brand#14|SMALL PLATED TIN|36|4 +Brand#14|SMALL POLISHED COPPER|9|4 +Brand#14|SMALL POLISHED COPPER|19|4 +Brand#14|SMALL POLISHED COPPER|23|4 +Brand#14|SMALL POLISHED COPPER|45|4 +Brand#14|SMALL POLISHED NICKEL|14|4 +Brand#14|SMALL POLISHED NICKEL|23|4 +Brand#14|SMALL POLISHED TIN|23|4 +Brand#14|SMALL POLISHED TIN|45|4 +Brand#14|STANDARD ANODIZED BRASS|19|4 +Brand#14|STANDARD ANODIZED BRASS|23|4 +Brand#14|STANDARD ANODIZED BRASS|45|4 +Brand#14|STANDARD ANODIZED BRASS|49|4 +Brand#14|STANDARD ANODIZED COPPER|36|4 +Brand#14|STANDARD ANODIZED NICKEL|9|4 +Brand#14|STANDARD ANODIZED NICKEL|14|4 +Brand#14|STANDARD ANODIZED NICKEL|23|4 +Brand#14|STANDARD ANODIZED NICKEL|36|4 +Brand#14|STANDARD ANODIZED NICKEL|45|4 +Brand#14|STANDARD ANODIZED NICKEL|49|4 +Brand#14|STANDARD ANODIZED STEEL|3|4 +Brand#14|STANDARD ANODIZED STEEL|14|4 +Brand#14|STANDARD ANODIZED STEEL|19|4 +Brand#14|STANDARD ANODIZED TIN|9|4 +Brand#14|STANDARD ANODIZED TIN|14|4 +Brand#14|STANDARD ANODIZED TIN|19|4 +Brand#14|STANDARD ANODIZED TIN|23|4 +Brand#14|STANDARD BRUSHED BRASS|14|4 +Brand#14|STANDARD BRUSHED BRASS|36|4 +Brand#14|STANDARD BRUSHED COPPER|14|4 +Brand#14|STANDARD BRUSHED COPPER|19|4 +Brand#14|STANDARD BRUSHED COPPER|23|4 +Brand#14|STANDARD BRUSHED COPPER|45|4 +Brand#14|STANDARD BRUSHED COPPER|49|4 +Brand#14|STANDARD BRUSHED NICKEL|9|4 +Brand#14|STANDARD BRUSHED NICKEL|19|4 +Brand#14|STANDARD BRUSHED NICKEL|36|4 +Brand#14|STANDARD BRUSHED NICKEL|45|4 +Brand#14|STANDARD BRUSHED STEEL|3|4 +Brand#14|STANDARD BRUSHED STEEL|9|4 +Brand#14|STANDARD BRUSHED STEEL|19|4 +Brand#14|STANDARD BRUSHED STEEL|36|4 +Brand#14|STANDARD BRUSHED TIN|3|4 +Brand#14|STANDARD BRUSHED TIN|14|4 +Brand#14|STANDARD BRUSHED TIN|36|4 +Brand#14|STANDARD BURNISHED COPPER|36|4 +Brand#14|STANDARD BURNISHED COPPER|45|4 +Brand#14|STANDARD BURNISHED COPPER|49|4 +Brand#14|STANDARD BURNISHED NICKEL|9|4 +Brand#14|STANDARD BURNISHED NICKEL|14|4 +Brand#14|STANDARD BURNISHED NICKEL|36|4 +Brand#14|STANDARD BURNISHED STEEL|3|4 +Brand#14|STANDARD BURNISHED STEEL|9|4 +Brand#14|STANDARD BURNISHED STEEL|36|4 +Brand#14|STANDARD BURNISHED STEEL|49|4 +Brand#14|STANDARD BURNISHED TIN|23|4 +Brand#14|STANDARD BURNISHED TIN|36|4 +Brand#14|STANDARD BURNISHED TIN|45|4 +Brand#14|STANDARD PLATED BRASS|23|4 +Brand#14|STANDARD PLATED BRASS|36|4 +Brand#14|STANDARD PLATED COPPER|3|4 +Brand#14|STANDARD PLATED COPPER|9|4 +Brand#14|STANDARD PLATED COPPER|19|4 +Brand#14|STANDARD PLATED NICKEL|36|4 +Brand#14|STANDARD PLATED NICKEL|45|4 +Brand#14|STANDARD PLATED STEEL|14|4 +Brand#14|STANDARD PLATED STEEL|19|4 +Brand#14|STANDARD PLATED STEEL|45|4 +Brand#14|STANDARD PLATED STEEL|49|4 +Brand#14|STANDARD PLATED TIN|14|4 +Brand#14|STANDARD PLATED TIN|23|4 +Brand#14|STANDARD PLATED TIN|36|4 +Brand#14|STANDARD PLATED TIN|45|4 +Brand#14|STANDARD POLISHED BRASS|3|4 +Brand#14|STANDARD POLISHED BRASS|36|4 +Brand#14|STANDARD POLISHED COPPER|9|4 +Brand#14|STANDARD POLISHED COPPER|23|4 +Brand#14|STANDARD POLISHED NICKEL|14|4 +Brand#14|STANDARD POLISHED NICKEL|23|4 +Brand#14|STANDARD POLISHED NICKEL|45|4 +Brand#14|STANDARD POLISHED NICKEL|49|4 +Brand#14|STANDARD POLISHED STEEL|3|4 +Brand#14|STANDARD POLISHED STEEL|9|4 +Brand#14|STANDARD POLISHED STEEL|14|4 +Brand#14|STANDARD POLISHED STEEL|19|4 +Brand#14|STANDARD POLISHED TIN|19|4 +Brand#14|STANDARD POLISHED TIN|23|4 +Brand#14|STANDARD POLISHED TIN|36|4 +Brand#15|ECONOMY ANODIZED BRASS|14|4 +Brand#15|ECONOMY ANODIZED BRASS|19|4 +Brand#15|ECONOMY ANODIZED BRASS|45|4 +Brand#15|ECONOMY ANODIZED BRASS|49|4 +Brand#15|ECONOMY ANODIZED COPPER|3|4 +Brand#15|ECONOMY ANODIZED COPPER|14|4 +Brand#15|ECONOMY ANODIZED COPPER|23|4 +Brand#15|ECONOMY ANODIZED COPPER|36|4 +Brand#15|ECONOMY ANODIZED NICKEL|14|4 +Brand#15|ECONOMY ANODIZED NICKEL|45|4 +Brand#15|ECONOMY ANODIZED NICKEL|49|4 +Brand#15|ECONOMY ANODIZED STEEL|9|4 +Brand#15|ECONOMY ANODIZED STEEL|19|4 +Brand#15|ECONOMY ANODIZED STEEL|45|4 +Brand#15|ECONOMY ANODIZED STEEL|49|4 +Brand#15|ECONOMY ANODIZED TIN|3|4 +Brand#15|ECONOMY ANODIZED TIN|14|4 +Brand#15|ECONOMY ANODIZED TIN|23|4 +Brand#15|ECONOMY ANODIZED TIN|45|4 +Brand#15|ECONOMY ANODIZED TIN|49|4 +Brand#15|ECONOMY BRUSHED BRASS|9|4 +Brand#15|ECONOMY BRUSHED BRASS|14|4 +Brand#15|ECONOMY BRUSHED BRASS|36|4 +Brand#15|ECONOMY BRUSHED BRASS|45|4 +Brand#15|ECONOMY BRUSHED BRASS|49|4 +Brand#15|ECONOMY BRUSHED COPPER|14|4 +Brand#15|ECONOMY BRUSHED COPPER|19|4 +Brand#15|ECONOMY BRUSHED COPPER|45|4 +Brand#15|ECONOMY BRUSHED COPPER|49|4 +Brand#15|ECONOMY BRUSHED NICKEL|19|4 +Brand#15|ECONOMY BRUSHED STEEL|3|4 +Brand#15|ECONOMY BRUSHED STEEL|14|4 +Brand#15|ECONOMY BRUSHED TIN|3|4 +Brand#15|ECONOMY BRUSHED TIN|19|4 +Brand#15|ECONOMY BRUSHED TIN|23|4 +Brand#15|ECONOMY BRUSHED TIN|45|4 +Brand#15|ECONOMY BURNISHED BRASS|23|4 +Brand#15|ECONOMY BURNISHED COPPER|3|4 +Brand#15|ECONOMY BURNISHED NICKEL|3|4 +Brand#15|ECONOMY BURNISHED NICKEL|45|4 +Brand#15|ECONOMY BURNISHED STEEL|14|4 +Brand#15|ECONOMY BURNISHED STEEL|23|4 +Brand#15|ECONOMY BURNISHED STEEL|36|4 +Brand#15|ECONOMY BURNISHED TIN|3|4 +Brand#15|ECONOMY BURNISHED TIN|14|4 +Brand#15|ECONOMY BURNISHED TIN|19|4 +Brand#15|ECONOMY BURNISHED TIN|36|4 +Brand#15|ECONOMY PLATED BRASS|9|4 +Brand#15|ECONOMY PLATED BRASS|19|4 +Brand#15|ECONOMY PLATED BRASS|23|4 +Brand#15|ECONOMY PLATED BRASS|45|4 +Brand#15|ECONOMY PLATED BRASS|49|4 +Brand#15|ECONOMY PLATED COPPER|14|4 +Brand#15|ECONOMY PLATED COPPER|19|4 +Brand#15|ECONOMY PLATED NICKEL|3|4 +Brand#15|ECONOMY PLATED NICKEL|23|4 +Brand#15|ECONOMY PLATED NICKEL|49|4 +Brand#15|ECONOMY PLATED STEEL|9|4 +Brand#15|ECONOMY PLATED STEEL|23|4 +Brand#15|ECONOMY PLATED STEEL|36|4 +Brand#15|ECONOMY PLATED STEEL|45|4 +Brand#15|ECONOMY PLATED STEEL|49|4 +Brand#15|ECONOMY PLATED TIN|3|4 +Brand#15|ECONOMY PLATED TIN|19|4 +Brand#15|ECONOMY PLATED TIN|23|4 +Brand#15|ECONOMY PLATED TIN|36|4 +Brand#15|ECONOMY PLATED TIN|45|4 +Brand#15|ECONOMY PLATED TIN|49|4 +Brand#15|ECONOMY POLISHED BRASS|9|4 +Brand#15|ECONOMY POLISHED BRASS|23|4 +Brand#15|ECONOMY POLISHED BRASS|45|4 +Brand#15|ECONOMY POLISHED BRASS|49|4 +Brand#15|ECONOMY POLISHED COPPER|14|4 +Brand#15|ECONOMY POLISHED COPPER|19|4 +Brand#15|ECONOMY POLISHED COPPER|23|4 +Brand#15|ECONOMY POLISHED NICKEL|23|4 +Brand#15|ECONOMY POLISHED STEEL|14|4 +Brand#15|ECONOMY POLISHED STEEL|45|4 +Brand#15|ECONOMY POLISHED TIN|19|4 +Brand#15|ECONOMY POLISHED TIN|45|4 +Brand#15|ECONOMY POLISHED TIN|49|4 +Brand#15|LARGE ANODIZED BRASS|23|4 +Brand#15|LARGE ANODIZED BRASS|45|4 +Brand#15|LARGE ANODIZED BRASS|49|4 +Brand#15|LARGE ANODIZED COPPER|3|4 +Brand#15|LARGE ANODIZED COPPER|9|4 +Brand#15|LARGE ANODIZED NICKEL|9|4 +Brand#15|LARGE ANODIZED NICKEL|45|4 +Brand#15|LARGE ANODIZED STEEL|9|4 +Brand#15|LARGE ANODIZED STEEL|36|4 +Brand#15|LARGE ANODIZED STEEL|49|4 +Brand#15|LARGE ANODIZED TIN|3|4 +Brand#15|LARGE ANODIZED TIN|9|4 +Brand#15|LARGE ANODIZED TIN|19|4 +Brand#15|LARGE ANODIZED TIN|45|4 +Brand#15|LARGE ANODIZED TIN|49|4 +Brand#15|LARGE BRUSHED BRASS|3|4 +Brand#15|LARGE BRUSHED COPPER|23|4 +Brand#15|LARGE BRUSHED COPPER|49|4 +Brand#15|LARGE BRUSHED NICKEL|3|4 +Brand#15|LARGE BRUSHED NICKEL|14|4 +Brand#15|LARGE BRUSHED NICKEL|23|4 +Brand#15|LARGE BRUSHED NICKEL|36|4 +Brand#15|LARGE BRUSHED STEEL|3|4 +Brand#15|LARGE BRUSHED STEEL|9|4 +Brand#15|LARGE BRUSHED STEEL|36|4 +Brand#15|LARGE BRUSHED STEEL|49|4 +Brand#15|LARGE BRUSHED TIN|14|4 +Brand#15|LARGE BRUSHED TIN|45|4 +Brand#15|LARGE BURNISHED BRASS|49|4 +Brand#15|LARGE BURNISHED COPPER|3|4 +Brand#15|LARGE BURNISHED COPPER|14|4 +Brand#15|LARGE BURNISHED NICKEL|14|4 +Brand#15|LARGE BURNISHED NICKEL|23|4 +Brand#15|LARGE BURNISHED NICKEL|45|4 +Brand#15|LARGE BURNISHED STEEL|3|4 +Brand#15|LARGE BURNISHED TIN|3|4 +Brand#15|LARGE BURNISHED TIN|9|4 +Brand#15|LARGE BURNISHED TIN|19|4 +Brand#15|LARGE BURNISHED TIN|23|4 +Brand#15|LARGE BURNISHED TIN|36|4 +Brand#15|LARGE BURNISHED TIN|45|4 +Brand#15|LARGE PLATED BRASS|3|4 +Brand#15|LARGE PLATED BRASS|14|4 +Brand#15|LARGE PLATED BRASS|19|4 +Brand#15|LARGE PLATED BRASS|23|4 +Brand#15|LARGE PLATED BRASS|49|4 +Brand#15|LARGE PLATED COPPER|3|4 +Brand#15|LARGE PLATED COPPER|14|4 +Brand#15|LARGE PLATED COPPER|23|4 +Brand#15|LARGE PLATED NICKEL|36|4 +Brand#15|LARGE PLATED STEEL|3|4 +Brand#15|LARGE PLATED STEEL|45|4 +Brand#15|LARGE PLATED STEEL|49|4 +Brand#15|LARGE PLATED TIN|9|4 +Brand#15|LARGE PLATED TIN|19|4 +Brand#15|LARGE PLATED TIN|36|4 +Brand#15|LARGE PLATED TIN|45|4 +Brand#15|LARGE POLISHED BRASS|3|4 +Brand#15|LARGE POLISHED BRASS|9|4 +Brand#15|LARGE POLISHED BRASS|14|4 +Brand#15|LARGE POLISHED COPPER|9|4 +Brand#15|LARGE POLISHED COPPER|14|4 +Brand#15|LARGE POLISHED COPPER|19|4 +Brand#15|LARGE POLISHED COPPER|45|4 +Brand#15|LARGE POLISHED NICKEL|3|4 +Brand#15|LARGE POLISHED NICKEL|14|4 +Brand#15|LARGE POLISHED NICKEL|19|4 +Brand#15|LARGE POLISHED NICKEL|23|4 +Brand#15|LARGE POLISHED NICKEL|36|4 +Brand#15|LARGE POLISHED NICKEL|49|4 +Brand#15|LARGE POLISHED STEEL|3|4 +Brand#15|MEDIUM ANODIZED BRASS|14|4 +Brand#15|MEDIUM ANODIZED BRASS|45|4 +Brand#15|MEDIUM ANODIZED BRASS|49|4 +Brand#15|MEDIUM ANODIZED COPPER|3|4 +Brand#15|MEDIUM ANODIZED COPPER|14|4 +Brand#15|MEDIUM ANODIZED COPPER|23|4 +Brand#15|MEDIUM ANODIZED COPPER|45|4 +Brand#15|MEDIUM ANODIZED COPPER|49|4 +Brand#15|MEDIUM ANODIZED NICKEL|14|4 +Brand#15|MEDIUM ANODIZED NICKEL|19|4 +Brand#15|MEDIUM ANODIZED NICKEL|23|4 +Brand#15|MEDIUM ANODIZED NICKEL|49|4 +Brand#15|MEDIUM ANODIZED STEEL|3|4 +Brand#15|MEDIUM ANODIZED STEEL|14|4 +Brand#15|MEDIUM ANODIZED STEEL|36|4 +Brand#15|MEDIUM ANODIZED TIN|9|4 +Brand#15|MEDIUM ANODIZED TIN|36|4 +Brand#15|MEDIUM ANODIZED TIN|45|4 +Brand#15|MEDIUM BRUSHED BRASS|9|4 +Brand#15|MEDIUM BRUSHED BRASS|36|4 +Brand#15|MEDIUM BRUSHED COPPER|19|4 +Brand#15|MEDIUM BRUSHED NICKEL|36|4 +Brand#15|MEDIUM BRUSHED NICKEL|45|4 +Brand#15|MEDIUM BRUSHED STEEL|9|4 +Brand#15|MEDIUM BRUSHED STEEL|14|4 +Brand#15|MEDIUM BRUSHED STEEL|23|4 +Brand#15|MEDIUM BRUSHED TIN|3|4 +Brand#15|MEDIUM BRUSHED TIN|36|4 +Brand#15|MEDIUM BRUSHED TIN|45|4 +Brand#15|MEDIUM BRUSHED TIN|49|4 +Brand#15|MEDIUM BURNISHED BRASS|3|4 +Brand#15|MEDIUM BURNISHED BRASS|14|4 +Brand#15|MEDIUM BURNISHED BRASS|19|4 +Brand#15|MEDIUM BURNISHED BRASS|23|4 +Brand#15|MEDIUM BURNISHED BRASS|49|4 +Brand#15|MEDIUM BURNISHED COPPER|9|4 +Brand#15|MEDIUM BURNISHED COPPER|19|4 +Brand#15|MEDIUM BURNISHED COPPER|36|4 +Brand#15|MEDIUM BURNISHED NICKEL|3|4 +Brand#15|MEDIUM BURNISHED NICKEL|23|4 +Brand#15|MEDIUM BURNISHED STEEL|9|4 +Brand#15|MEDIUM BURNISHED STEEL|36|4 +Brand#15|MEDIUM BURNISHED STEEL|45|4 +Brand#15|MEDIUM BURNISHED TIN|3|4 +Brand#15|MEDIUM BURNISHED TIN|19|4 +Brand#15|MEDIUM BURNISHED TIN|23|4 +Brand#15|MEDIUM BURNISHED TIN|36|4 +Brand#15|MEDIUM PLATED BRASS|3|4 +Brand#15|MEDIUM PLATED BRASS|9|4 +Brand#15|MEDIUM PLATED BRASS|19|4 +Brand#15|MEDIUM PLATED BRASS|23|4 +Brand#15|MEDIUM PLATED BRASS|49|4 +Brand#15|MEDIUM PLATED COPPER|9|4 +Brand#15|MEDIUM PLATED COPPER|19|4 +Brand#15|MEDIUM PLATED COPPER|36|4 +Brand#15|MEDIUM PLATED COPPER|45|4 +Brand#15|MEDIUM PLATED COPPER|49|4 +Brand#15|MEDIUM PLATED NICKEL|3|4 +Brand#15|MEDIUM PLATED NICKEL|9|4 +Brand#15|MEDIUM PLATED NICKEL|14|4 +Brand#15|MEDIUM PLATED NICKEL|19|4 +Brand#15|MEDIUM PLATED NICKEL|36|4 +Brand#15|MEDIUM PLATED NICKEL|45|4 +Brand#15|MEDIUM PLATED STEEL|3|4 +Brand#15|MEDIUM PLATED STEEL|14|4 +Brand#15|MEDIUM PLATED STEEL|23|4 +Brand#15|MEDIUM PLATED STEEL|36|4 +Brand#15|MEDIUM PLATED TIN|14|4 +Brand#15|PROMO ANODIZED BRASS|3|4 +Brand#15|PROMO ANODIZED BRASS|9|4 +Brand#15|PROMO ANODIZED BRASS|19|4 +Brand#15|PROMO ANODIZED BRASS|49|4 +Brand#15|PROMO ANODIZED COPPER|3|4 +Brand#15|PROMO ANODIZED COPPER|19|4 +Brand#15|PROMO ANODIZED COPPER|23|4 +Brand#15|PROMO ANODIZED COPPER|49|4 +Brand#15|PROMO ANODIZED NICKEL|19|4 +Brand#15|PROMO ANODIZED STEEL|23|4 +Brand#15|PROMO ANODIZED STEEL|45|4 +Brand#15|PROMO ANODIZED TIN|23|4 +Brand#15|PROMO ANODIZED TIN|36|4 +Brand#15|PROMO ANODIZED TIN|45|4 +Brand#15|PROMO BRUSHED BRASS|3|4 +Brand#15|PROMO BRUSHED BRASS|23|4 +Brand#15|PROMO BRUSHED BRASS|45|4 +Brand#15|PROMO BRUSHED COPPER|14|4 +Brand#15|PROMO BRUSHED COPPER|49|4 +Brand#15|PROMO BRUSHED NICKEL|3|4 +Brand#15|PROMO BRUSHED NICKEL|14|4 +Brand#15|PROMO BRUSHED NICKEL|45|4 +Brand#15|PROMO BRUSHED STEEL|3|4 +Brand#15|PROMO BRUSHED STEEL|19|4 +Brand#15|PROMO BRUSHED TIN|9|4 +Brand#15|PROMO BRUSHED TIN|14|4 +Brand#15|PROMO BRUSHED TIN|45|4 +Brand#15|PROMO BURNISHED BRASS|3|4 +Brand#15|PROMO BURNISHED BRASS|19|4 +Brand#15|PROMO BURNISHED BRASS|45|4 +Brand#15|PROMO BURNISHED COPPER|23|4 +Brand#15|PROMO BURNISHED COPPER|49|4 +Brand#15|PROMO BURNISHED NICKEL|45|4 +Brand#15|PROMO BURNISHED STEEL|14|4 +Brand#15|PROMO BURNISHED STEEL|45|4 +Brand#15|PROMO BURNISHED STEEL|49|4 +Brand#15|PROMO BURNISHED TIN|3|4 +Brand#15|PROMO BURNISHED TIN|23|4 +Brand#15|PROMO PLATED BRASS|3|4 +Brand#15|PROMO PLATED BRASS|9|4 +Brand#15|PROMO PLATED BRASS|45|4 +Brand#15|PROMO PLATED COPPER|19|4 +Brand#15|PROMO PLATED COPPER|49|4 +Brand#15|PROMO PLATED NICKEL|3|4 +Brand#15|PROMO PLATED NICKEL|49|4 +Brand#15|PROMO PLATED STEEL|9|4 +Brand#15|PROMO PLATED STEEL|19|4 +Brand#15|PROMO PLATED STEEL|45|4 +Brand#15|PROMO PLATED STEEL|49|4 +Brand#15|PROMO PLATED TIN|14|4 +Brand#15|PROMO PLATED TIN|36|4 +Brand#15|PROMO PLATED TIN|45|4 +Brand#15|PROMO PLATED TIN|49|4 +Brand#15|PROMO POLISHED BRASS|19|4 +Brand#15|PROMO POLISHED BRASS|23|4 +Brand#15|PROMO POLISHED BRASS|36|4 +Brand#15|PROMO POLISHED BRASS|45|4 +Brand#15|PROMO POLISHED BRASS|49|4 +Brand#15|PROMO POLISHED COPPER|23|4 +Brand#15|PROMO POLISHED NICKEL|3|4 +Brand#15|PROMO POLISHED NICKEL|9|4 +Brand#15|PROMO POLISHED NICKEL|14|4 +Brand#15|PROMO POLISHED NICKEL|45|4 +Brand#15|PROMO POLISHED STEEL|23|4 +Brand#15|PROMO POLISHED STEEL|36|4 +Brand#15|PROMO POLISHED STEEL|45|4 +Brand#15|PROMO POLISHED TIN|14|4 +Brand#15|PROMO POLISHED TIN|19|4 +Brand#15|PROMO POLISHED TIN|36|4 +Brand#15|SMALL ANODIZED BRASS|3|4 +Brand#15|SMALL ANODIZED BRASS|36|4 +Brand#15|SMALL ANODIZED COPPER|3|4 +Brand#15|SMALL ANODIZED COPPER|9|4 +Brand#15|SMALL ANODIZED COPPER|14|4 +Brand#15|SMALL ANODIZED COPPER|19|4 +Brand#15|SMALL ANODIZED COPPER|36|4 +Brand#15|SMALL ANODIZED COPPER|49|4 +Brand#15|SMALL ANODIZED NICKEL|45|4 +Brand#15|SMALL ANODIZED NICKEL|49|4 +Brand#15|SMALL ANODIZED STEEL|19|4 +Brand#15|SMALL ANODIZED STEEL|36|4 +Brand#15|SMALL ANODIZED TIN|3|4 +Brand#15|SMALL ANODIZED TIN|9|4 +Brand#15|SMALL ANODIZED TIN|49|4 +Brand#15|SMALL BRUSHED COPPER|3|4 +Brand#15|SMALL BRUSHED COPPER|36|4 +Brand#15|SMALL BRUSHED COPPER|49|4 +Brand#15|SMALL BRUSHED NICKEL|3|4 +Brand#15|SMALL BRUSHED NICKEL|45|4 +Brand#15|SMALL BRUSHED STEEL|3|4 +Brand#15|SMALL BRUSHED STEEL|45|4 +Brand#15|SMALL BRUSHED STEEL|49|4 +Brand#15|SMALL BRUSHED TIN|3|4 +Brand#15|SMALL BRUSHED TIN|14|4 +Brand#15|SMALL BRUSHED TIN|49|4 +Brand#15|SMALL BURNISHED BRASS|36|4 +Brand#15|SMALL BURNISHED BRASS|45|4 +Brand#15|SMALL BURNISHED BRASS|49|4 +Brand#15|SMALL BURNISHED COPPER|23|4 +Brand#15|SMALL BURNISHED COPPER|36|4 +Brand#15|SMALL BURNISHED COPPER|45|4 +Brand#15|SMALL BURNISHED NICKEL|14|4 +Brand#15|SMALL BURNISHED NICKEL|23|4 +Brand#15|SMALL BURNISHED NICKEL|49|4 +Brand#15|SMALL BURNISHED STEEL|3|4 +Brand#15|SMALL BURNISHED STEEL|14|4 +Brand#15|SMALL BURNISHED STEEL|23|4 +Brand#15|SMALL BURNISHED STEEL|36|4 +Brand#15|SMALL BURNISHED STEEL|45|4 +Brand#15|SMALL BURNISHED STEEL|49|4 +Brand#15|SMALL BURNISHED TIN|36|4 +Brand#15|SMALL BURNISHED TIN|49|4 +Brand#15|SMALL PLATED BRASS|3|4 +Brand#15|SMALL PLATED BRASS|9|4 +Brand#15|SMALL PLATED BRASS|14|4 +Brand#15|SMALL PLATED NICKEL|14|4 +Brand#15|SMALL PLATED NICKEL|36|4 +Brand#15|SMALL PLATED NICKEL|49|4 +Brand#15|SMALL PLATED TIN|3|4 +Brand#15|SMALL PLATED TIN|23|4 +Brand#15|SMALL PLATED TIN|49|4 +Brand#15|SMALL POLISHED BRASS|14|4 +Brand#15|SMALL POLISHED BRASS|36|4 +Brand#15|SMALL POLISHED COPPER|14|4 +Brand#15|SMALL POLISHED COPPER|19|4 +Brand#15|SMALL POLISHED COPPER|23|4 +Brand#15|SMALL POLISHED NICKEL|3|4 +Brand#15|SMALL POLISHED NICKEL|9|4 +Brand#15|SMALL POLISHED NICKEL|36|4 +Brand#15|SMALL POLISHED NICKEL|49|4 +Brand#15|SMALL POLISHED STEEL|14|4 +Brand#15|SMALL POLISHED STEEL|19|4 +Brand#15|SMALL POLISHED TIN|14|4 +Brand#15|SMALL POLISHED TIN|23|4 +Brand#15|STANDARD ANODIZED BRASS|3|4 +Brand#15|STANDARD ANODIZED BRASS|36|4 +Brand#15|STANDARD ANODIZED BRASS|49|4 +Brand#15|STANDARD ANODIZED COPPER|9|4 +Brand#15|STANDARD ANODIZED COPPER|19|4 +Brand#15|STANDARD ANODIZED COPPER|49|4 +Brand#15|STANDARD ANODIZED NICKEL|14|4 +Brand#15|STANDARD ANODIZED NICKEL|19|4 +Brand#15|STANDARD ANODIZED NICKEL|49|4 +Brand#15|STANDARD ANODIZED STEEL|23|4 +Brand#15|STANDARD ANODIZED STEEL|49|4 +Brand#15|STANDARD ANODIZED TIN|9|4 +Brand#15|STANDARD ANODIZED TIN|14|4 +Brand#15|STANDARD ANODIZED TIN|23|4 +Brand#15|STANDARD ANODIZED TIN|49|4 +Brand#15|STANDARD BRUSHED BRASS|9|4 +Brand#15|STANDARD BRUSHED BRASS|14|4 +Brand#15|STANDARD BRUSHED BRASS|23|4 +Brand#15|STANDARD BRUSHED COPPER|3|4 +Brand#15|STANDARD BRUSHED COPPER|19|4 +Brand#15|STANDARD BRUSHED COPPER|36|4 +Brand#15|STANDARD BRUSHED NICKEL|36|4 +Brand#15|STANDARD BRUSHED NICKEL|45|4 +Brand#15|STANDARD BRUSHED NICKEL|49|4 +Brand#15|STANDARD BRUSHED STEEL|3|4 +Brand#15|STANDARD BRUSHED STEEL|23|4 +Brand#15|STANDARD BRUSHED STEEL|36|4 +Brand#15|STANDARD BRUSHED STEEL|45|4 +Brand#15|STANDARD BRUSHED TIN|3|4 +Brand#15|STANDARD BRUSHED TIN|9|4 +Brand#15|STANDARD BRUSHED TIN|14|4 +Brand#15|STANDARD BRUSHED TIN|19|4 +Brand#15|STANDARD BRUSHED TIN|36|4 +Brand#15|STANDARD BRUSHED TIN|49|4 +Brand#15|STANDARD BURNISHED BRASS|14|4 +Brand#15|STANDARD BURNISHED BRASS|36|4 +Brand#15|STANDARD BURNISHED COPPER|3|4 +Brand#15|STANDARD BURNISHED COPPER|9|4 +Brand#15|STANDARD BURNISHED COPPER|23|4 +Brand#15|STANDARD BURNISHED NICKEL|3|4 +Brand#15|STANDARD BURNISHED NICKEL|19|4 +Brand#15|STANDARD BURNISHED STEEL|3|4 +Brand#15|STANDARD BURNISHED STEEL|9|4 +Brand#15|STANDARD BURNISHED STEEL|14|4 +Brand#15|STANDARD BURNISHED STEEL|36|4 +Brand#15|STANDARD BURNISHED STEEL|49|4 +Brand#15|STANDARD BURNISHED TIN|19|4 +Brand#15|STANDARD BURNISHED TIN|23|4 +Brand#15|STANDARD BURNISHED TIN|36|4 +Brand#15|STANDARD PLATED BRASS|19|4 +Brand#15|STANDARD PLATED BRASS|49|4 +Brand#15|STANDARD PLATED COPPER|3|4 +Brand#15|STANDARD PLATED COPPER|19|4 +Brand#15|STANDARD PLATED COPPER|23|4 +Brand#15|STANDARD PLATED NICKEL|19|4 +Brand#15|STANDARD PLATED NICKEL|36|4 +Brand#15|STANDARD PLATED NICKEL|45|4 +Brand#15|STANDARD PLATED STEEL|9|4 +Brand#15|STANDARD PLATED STEEL|45|4 +Brand#15|STANDARD PLATED TIN|19|4 +Brand#15|STANDARD PLATED TIN|49|4 +Brand#15|STANDARD POLISHED BRASS|36|4 +Brand#15|STANDARD POLISHED BRASS|49|4 +Brand#15|STANDARD POLISHED COPPER|14|4 +Brand#15|STANDARD POLISHED COPPER|19|4 +Brand#15|STANDARD POLISHED COPPER|45|4 +Brand#15|STANDARD POLISHED COPPER|49|4 +Brand#15|STANDARD POLISHED NICKEL|9|4 +Brand#15|STANDARD POLISHED NICKEL|19|4 +Brand#15|STANDARD POLISHED NICKEL|49|4 +Brand#15|STANDARD POLISHED STEEL|9|4 +Brand#15|STANDARD POLISHED STEEL|14|4 +Brand#15|STANDARD POLISHED STEEL|19|4 +Brand#15|STANDARD POLISHED STEEL|36|4 +Brand#15|STANDARD POLISHED STEEL|45|4 +Brand#15|STANDARD POLISHED TIN|9|4 +Brand#15|STANDARD POLISHED TIN|19|4 +Brand#15|STANDARD POLISHED TIN|45|4 +Brand#21|ECONOMY ANODIZED BRASS|3|4 +Brand#21|ECONOMY ANODIZED BRASS|9|4 +Brand#21|ECONOMY ANODIZED BRASS|19|4 +Brand#21|ECONOMY ANODIZED COPPER|9|4 +Brand#21|ECONOMY ANODIZED COPPER|23|4 +Brand#21|ECONOMY ANODIZED NICKEL|3|4 +Brand#21|ECONOMY ANODIZED NICKEL|19|4 +Brand#21|ECONOMY ANODIZED NICKEL|49|4 +Brand#21|ECONOMY ANODIZED STEEL|9|4 +Brand#21|ECONOMY ANODIZED STEEL|14|4 +Brand#21|ECONOMY ANODIZED STEEL|23|4 +Brand#21|ECONOMY ANODIZED TIN|14|4 +Brand#21|ECONOMY ANODIZED TIN|19|4 +Brand#21|ECONOMY ANODIZED TIN|23|4 +Brand#21|ECONOMY ANODIZED TIN|45|4 +Brand#21|ECONOMY ANODIZED TIN|49|4 +Brand#21|ECONOMY BRUSHED BRASS|9|4 +Brand#21|ECONOMY BRUSHED BRASS|23|4 +Brand#21|ECONOMY BRUSHED BRASS|45|4 +Brand#21|ECONOMY BRUSHED BRASS|49|4 +Brand#21|ECONOMY BRUSHED COPPER|3|4 +Brand#21|ECONOMY BRUSHED COPPER|23|4 +Brand#21|ECONOMY BRUSHED COPPER|36|4 +Brand#21|ECONOMY BRUSHED COPPER|49|4 +Brand#21|ECONOMY BRUSHED NICKEL|3|4 +Brand#21|ECONOMY BRUSHED NICKEL|45|4 +Brand#21|ECONOMY BRUSHED NICKEL|49|4 +Brand#21|ECONOMY BRUSHED STEEL|9|4 +Brand#21|ECONOMY BRUSHED STEEL|14|4 +Brand#21|ECONOMY BRUSHED STEEL|19|4 +Brand#21|ECONOMY BRUSHED STEEL|23|4 +Brand#21|ECONOMY BRUSHED STEEL|36|4 +Brand#21|ECONOMY BRUSHED TIN|3|4 +Brand#21|ECONOMY BRUSHED TIN|45|4 +Brand#21|ECONOMY BRUSHED TIN|49|4 +Brand#21|ECONOMY BURNISHED BRASS|23|4 +Brand#21|ECONOMY BURNISHED COPPER|9|4 +Brand#21|ECONOMY BURNISHED COPPER|14|4 +Brand#21|ECONOMY BURNISHED COPPER|36|4 +Brand#21|ECONOMY BURNISHED NICKEL|14|4 +Brand#21|ECONOMY BURNISHED NICKEL|19|4 +Brand#21|ECONOMY BURNISHED NICKEL|23|4 +Brand#21|ECONOMY BURNISHED NICKEL|36|4 +Brand#21|ECONOMY BURNISHED STEEL|3|4 +Brand#21|ECONOMY BURNISHED STEEL|19|4 +Brand#21|ECONOMY BURNISHED STEEL|49|4 +Brand#21|ECONOMY BURNISHED TIN|23|4 +Brand#21|ECONOMY BURNISHED TIN|36|4 +Brand#21|ECONOMY PLATED BRASS|14|4 +Brand#21|ECONOMY PLATED BRASS|19|4 +Brand#21|ECONOMY PLATED BRASS|36|4 +Brand#21|ECONOMY PLATED BRASS|45|4 +Brand#21|ECONOMY PLATED COPPER|9|4 +Brand#21|ECONOMY PLATED COPPER|19|4 +Brand#21|ECONOMY PLATED COPPER|23|4 +Brand#21|ECONOMY PLATED NICKEL|9|4 +Brand#21|ECONOMY PLATED NICKEL|14|4 +Brand#21|ECONOMY PLATED NICKEL|19|4 +Brand#21|ECONOMY PLATED STEEL|36|4 +Brand#21|ECONOMY PLATED STEEL|49|4 +Brand#21|ECONOMY PLATED TIN|9|4 +Brand#21|ECONOMY PLATED TIN|14|4 +Brand#21|ECONOMY PLATED TIN|45|4 +Brand#21|ECONOMY PLATED TIN|49|4 +Brand#21|ECONOMY POLISHED BRASS|3|4 +Brand#21|ECONOMY POLISHED BRASS|19|4 +Brand#21|ECONOMY POLISHED BRASS|36|4 +Brand#21|ECONOMY POLISHED BRASS|45|4 +Brand#21|ECONOMY POLISHED COPPER|45|4 +Brand#21|ECONOMY POLISHED COPPER|49|4 +Brand#21|ECONOMY POLISHED NICKEL|9|4 +Brand#21|ECONOMY POLISHED NICKEL|23|4 +Brand#21|ECONOMY POLISHED STEEL|3|4 +Brand#21|ECONOMY POLISHED STEEL|9|4 +Brand#21|ECONOMY POLISHED TIN|14|4 +Brand#21|ECONOMY POLISHED TIN|36|4 +Brand#21|LARGE ANODIZED BRASS|36|4 +Brand#21|LARGE ANODIZED BRASS|49|4 +Brand#21|LARGE ANODIZED COPPER|9|4 +Brand#21|LARGE ANODIZED COPPER|14|4 +Brand#21|LARGE ANODIZED COPPER|23|4 +Brand#21|LARGE ANODIZED COPPER|45|4 +Brand#21|LARGE ANODIZED COPPER|49|4 +Brand#21|LARGE ANODIZED NICKEL|23|4 +Brand#21|LARGE ANODIZED STEEL|19|4 +Brand#21|LARGE ANODIZED TIN|9|4 +Brand#21|LARGE ANODIZED TIN|23|4 +Brand#21|LARGE ANODIZED TIN|36|4 +Brand#21|LARGE BRUSHED BRASS|3|4 +Brand#21|LARGE BRUSHED BRASS|14|4 +Brand#21|LARGE BRUSHED BRASS|36|4 +Brand#21|LARGE BRUSHED COPPER|14|4 +Brand#21|LARGE BRUSHED COPPER|45|4 +Brand#21|LARGE BRUSHED NICKEL|9|4 +Brand#21|LARGE BRUSHED NICKEL|19|4 +Brand#21|LARGE BRUSHED NICKEL|49|4 +Brand#21|LARGE BRUSHED STEEL|3|4 +Brand#21|LARGE BRUSHED STEEL|19|4 +Brand#21|LARGE BRUSHED STEEL|36|4 +Brand#21|LARGE BURNISHED BRASS|3|4 +Brand#21|LARGE BURNISHED BRASS|9|4 +Brand#21|LARGE BURNISHED BRASS|23|4 +Brand#21|LARGE BURNISHED BRASS|49|4 +Brand#21|LARGE BURNISHED COPPER|36|4 +Brand#21|LARGE BURNISHED COPPER|45|4 +Brand#21|LARGE BURNISHED COPPER|49|4 +Brand#21|LARGE BURNISHED NICKEL|19|4 +Brand#21|LARGE BURNISHED NICKEL|23|4 +Brand#21|LARGE BURNISHED NICKEL|45|4 +Brand#21|LARGE BURNISHED NICKEL|49|4 +Brand#21|LARGE BURNISHED STEEL|9|4 +Brand#21|LARGE BURNISHED STEEL|23|4 +Brand#21|LARGE BURNISHED TIN|19|4 +Brand#21|LARGE BURNISHED TIN|36|4 +Brand#21|LARGE PLATED BRASS|3|4 +Brand#21|LARGE PLATED BRASS|49|4 +Brand#21|LARGE PLATED NICKEL|3|4 +Brand#21|LARGE PLATED NICKEL|14|4 +Brand#21|LARGE PLATED NICKEL|19|4 +Brand#21|LARGE PLATED NICKEL|36|4 +Brand#21|LARGE PLATED NICKEL|49|4 +Brand#21|LARGE PLATED STEEL|9|4 +Brand#21|LARGE PLATED STEEL|23|4 +Brand#21|LARGE PLATED TIN|19|4 +Brand#21|LARGE POLISHED BRASS|3|4 +Brand#21|LARGE POLISHED BRASS|9|4 +Brand#21|LARGE POLISHED BRASS|45|4 +Brand#21|LARGE POLISHED COPPER|3|4 +Brand#21|LARGE POLISHED COPPER|36|4 +Brand#21|LARGE POLISHED COPPER|45|4 +Brand#21|LARGE POLISHED NICKEL|9|4 +Brand#21|LARGE POLISHED NICKEL|14|4 +Brand#21|LARGE POLISHED NICKEL|19|4 +Brand#21|LARGE POLISHED STEEL|14|4 +Brand#21|LARGE POLISHED STEEL|19|4 +Brand#21|LARGE POLISHED STEEL|36|4 +Brand#21|LARGE POLISHED STEEL|49|4 +Brand#21|LARGE POLISHED TIN|9|4 +Brand#21|LARGE POLISHED TIN|14|4 +Brand#21|LARGE POLISHED TIN|23|4 +Brand#21|LARGE POLISHED TIN|49|4 +Brand#21|MEDIUM ANODIZED BRASS|19|4 +Brand#21|MEDIUM ANODIZED BRASS|45|4 +Brand#21|MEDIUM ANODIZED COPPER|36|4 +Brand#21|MEDIUM ANODIZED COPPER|49|4 +Brand#21|MEDIUM ANODIZED NICKEL|9|4 +Brand#21|MEDIUM ANODIZED NICKEL|19|4 +Brand#21|MEDIUM ANODIZED NICKEL|49|4 +Brand#21|MEDIUM ANODIZED STEEL|3|4 +Brand#21|MEDIUM ANODIZED STEEL|9|4 +Brand#21|MEDIUM ANODIZED STEEL|19|4 +Brand#21|MEDIUM ANODIZED STEEL|23|4 +Brand#21|MEDIUM ANODIZED STEEL|49|4 +Brand#21|MEDIUM ANODIZED TIN|3|4 +Brand#21|MEDIUM ANODIZED TIN|19|4 +Brand#21|MEDIUM ANODIZED TIN|36|4 +Brand#21|MEDIUM BRUSHED BRASS|36|4 +Brand#21|MEDIUM BRUSHED COPPER|9|4 +Brand#21|MEDIUM BRUSHED COPPER|36|4 +Brand#21|MEDIUM BRUSHED COPPER|49|4 +Brand#21|MEDIUM BRUSHED NICKEL|3|4 +Brand#21|MEDIUM BRUSHED NICKEL|9|4 +Brand#21|MEDIUM BRUSHED NICKEL|23|4 +Brand#21|MEDIUM BRUSHED NICKEL|36|4 +Brand#21|MEDIUM BRUSHED NICKEL|45|4 +Brand#21|MEDIUM BRUSHED STEEL|3|4 +Brand#21|MEDIUM BRUSHED STEEL|9|4 +Brand#21|MEDIUM BRUSHED STEEL|14|4 +Brand#21|MEDIUM BRUSHED STEEL|36|4 +Brand#21|MEDIUM BRUSHED STEEL|49|4 +Brand#21|MEDIUM BRUSHED TIN|3|4 +Brand#21|MEDIUM BRUSHED TIN|14|4 +Brand#21|MEDIUM BRUSHED TIN|49|4 +Brand#21|MEDIUM BURNISHED BRASS|23|4 +Brand#21|MEDIUM BURNISHED BRASS|45|4 +Brand#21|MEDIUM BURNISHED COPPER|3|4 +Brand#21|MEDIUM BURNISHED COPPER|9|4 +Brand#21|MEDIUM BURNISHED COPPER|14|4 +Brand#21|MEDIUM BURNISHED COPPER|45|4 +Brand#21|MEDIUM BURNISHED NICKEL|3|4 +Brand#21|MEDIUM BURNISHED NICKEL|19|4 +Brand#21|MEDIUM BURNISHED NICKEL|45|4 +Brand#21|MEDIUM BURNISHED NICKEL|49|4 +Brand#21|MEDIUM BURNISHED STEEL|49|4 +Brand#21|MEDIUM BURNISHED TIN|3|4 +Brand#21|MEDIUM BURNISHED TIN|19|4 +Brand#21|MEDIUM BURNISHED TIN|23|4 +Brand#21|MEDIUM BURNISHED TIN|36|4 +Brand#21|MEDIUM PLATED BRASS|3|4 +Brand#21|MEDIUM PLATED BRASS|19|4 +Brand#21|MEDIUM PLATED BRASS|23|4 +Brand#21|MEDIUM PLATED BRASS|49|4 +Brand#21|MEDIUM PLATED COPPER|3|4 +Brand#21|MEDIUM PLATED COPPER|19|4 +Brand#21|MEDIUM PLATED COPPER|36|4 +Brand#21|MEDIUM PLATED COPPER|45|4 +Brand#21|MEDIUM PLATED NICKEL|3|4 +Brand#21|MEDIUM PLATED NICKEL|9|4 +Brand#21|MEDIUM PLATED NICKEL|14|4 +Brand#21|MEDIUM PLATED NICKEL|45|4 +Brand#21|MEDIUM PLATED NICKEL|49|4 +Brand#21|MEDIUM PLATED TIN|19|4 +Brand#21|MEDIUM PLATED TIN|45|4 +Brand#21|MEDIUM PLATED TIN|49|4 +Brand#21|PROMO ANODIZED BRASS|3|4 +Brand#21|PROMO ANODIZED BRASS|23|4 +Brand#21|PROMO ANODIZED BRASS|45|4 +Brand#21|PROMO ANODIZED BRASS|49|4 +Brand#21|PROMO ANODIZED COPPER|3|4 +Brand#21|PROMO ANODIZED COPPER|19|4 +Brand#21|PROMO ANODIZED COPPER|49|4 +Brand#21|PROMO ANODIZED NICKEL|36|4 +Brand#21|PROMO ANODIZED NICKEL|45|4 +Brand#21|PROMO ANODIZED STEEL|14|4 +Brand#21|PROMO ANODIZED STEEL|23|4 +Brand#21|PROMO ANODIZED TIN|3|4 +Brand#21|PROMO ANODIZED TIN|9|4 +Brand#21|PROMO ANODIZED TIN|36|4 +Brand#21|PROMO ANODIZED TIN|49|4 +Brand#21|PROMO BRUSHED BRASS|9|4 +Brand#21|PROMO BRUSHED BRASS|14|4 +Brand#21|PROMO BRUSHED BRASS|36|4 +Brand#21|PROMO BRUSHED COPPER|9|4 +Brand#21|PROMO BRUSHED COPPER|14|4 +Brand#21|PROMO BRUSHED COPPER|19|4 +Brand#21|PROMO BRUSHED COPPER|23|4 +Brand#21|PROMO BRUSHED NICKEL|3|4 +Brand#21|PROMO BRUSHED NICKEL|14|4 +Brand#21|PROMO BRUSHED NICKEL|23|4 +Brand#21|PROMO BRUSHED STEEL|9|4 +Brand#21|PROMO BRUSHED STEEL|49|4 +Brand#21|PROMO BRUSHED TIN|49|4 +Brand#21|PROMO BURNISHED BRASS|3|4 +Brand#21|PROMO BURNISHED BRASS|14|4 +Brand#21|PROMO BURNISHED BRASS|36|4 +Brand#21|PROMO BURNISHED COPPER|14|4 +Brand#21|PROMO BURNISHED COPPER|19|4 +Brand#21|PROMO BURNISHED COPPER|23|4 +Brand#21|PROMO BURNISHED COPPER|36|4 +Brand#21|PROMO BURNISHED COPPER|45|4 +Brand#21|PROMO BURNISHED NICKEL|9|4 +Brand#21|PROMO BURNISHED NICKEL|14|4 +Brand#21|PROMO BURNISHED NICKEL|45|4 +Brand#21|PROMO BURNISHED NICKEL|49|4 +Brand#21|PROMO BURNISHED STEEL|3|4 +Brand#21|PROMO BURNISHED STEEL|19|4 +Brand#21|PROMO BURNISHED TIN|3|4 +Brand#21|PROMO BURNISHED TIN|9|4 +Brand#21|PROMO BURNISHED TIN|14|4 +Brand#21|PROMO BURNISHED TIN|19|4 +Brand#21|PROMO BURNISHED TIN|23|4 +Brand#21|PROMO PLATED BRASS|9|4 +Brand#21|PROMO PLATED BRASS|45|4 +Brand#21|PROMO PLATED COPPER|36|4 +Brand#21|PROMO PLATED COPPER|45|4 +Brand#21|PROMO PLATED NICKEL|9|4 +Brand#21|PROMO PLATED NICKEL|36|4 +Brand#21|PROMO PLATED STEEL|19|4 +Brand#21|PROMO PLATED STEEL|45|4 +Brand#21|PROMO PLATED TIN|9|4 +Brand#21|PROMO PLATED TIN|19|4 +Brand#21|PROMO PLATED TIN|49|4 +Brand#21|PROMO POLISHED BRASS|36|4 +Brand#21|PROMO POLISHED BRASS|49|4 +Brand#21|PROMO POLISHED COPPER|23|4 +Brand#21|PROMO POLISHED COPPER|49|4 +Brand#21|PROMO POLISHED NICKEL|3|4 +Brand#21|PROMO POLISHED NICKEL|9|4 +Brand#21|PROMO POLISHED NICKEL|19|4 +Brand#21|PROMO POLISHED NICKEL|49|4 +Brand#21|PROMO POLISHED TIN|3|4 +Brand#21|PROMO POLISHED TIN|23|4 +Brand#21|PROMO POLISHED TIN|36|4 +Brand#21|SMALL ANODIZED BRASS|9|4 +Brand#21|SMALL ANODIZED BRASS|14|4 +Brand#21|SMALL ANODIZED BRASS|36|4 +Brand#21|SMALL ANODIZED BRASS|49|4 +Brand#21|SMALL ANODIZED COPPER|3|4 +Brand#21|SMALL ANODIZED COPPER|14|4 +Brand#21|SMALL ANODIZED COPPER|23|4 +Brand#21|SMALL ANODIZED COPPER|36|4 +Brand#21|SMALL ANODIZED STEEL|9|4 +Brand#21|SMALL ANODIZED STEEL|19|4 +Brand#21|SMALL ANODIZED TIN|3|4 +Brand#21|SMALL ANODIZED TIN|45|4 +Brand#21|SMALL BRUSHED BRASS|3|4 +Brand#21|SMALL BRUSHED BRASS|9|4 +Brand#21|SMALL BRUSHED BRASS|23|4 +Brand#21|SMALL BRUSHED BRASS|49|4 +Brand#21|SMALL BRUSHED COPPER|19|4 +Brand#21|SMALL BRUSHED COPPER|23|4 +Brand#21|SMALL BRUSHED COPPER|49|4 +Brand#21|SMALL BRUSHED NICKEL|3|4 +Brand#21|SMALL BRUSHED NICKEL|49|4 +Brand#21|SMALL BRUSHED STEEL|19|4 +Brand#21|SMALL BRUSHED STEEL|23|4 +Brand#21|SMALL BRUSHED STEEL|45|4 +Brand#21|SMALL BRUSHED STEEL|49|4 +Brand#21|SMALL BRUSHED TIN|36|4 +Brand#21|SMALL BRUSHED TIN|49|4 +Brand#21|SMALL BURNISHED BRASS|3|4 +Brand#21|SMALL BURNISHED BRASS|9|4 +Brand#21|SMALL BURNISHED BRASS|19|4 +Brand#21|SMALL BURNISHED BRASS|23|4 +Brand#21|SMALL BURNISHED BRASS|45|4 +Brand#21|SMALL BURNISHED COPPER|9|4 +Brand#21|SMALL BURNISHED COPPER|23|4 +Brand#21|SMALL BURNISHED NICKEL|3|4 +Brand#21|SMALL BURNISHED NICKEL|19|4 +Brand#21|SMALL BURNISHED NICKEL|23|4 +Brand#21|SMALL BURNISHED STEEL|3|4 +Brand#21|SMALL BURNISHED STEEL|14|4 +Brand#21|SMALL BURNISHED STEEL|19|4 +Brand#21|SMALL BURNISHED STEEL|36|4 +Brand#21|SMALL BURNISHED STEEL|45|4 +Brand#21|SMALL BURNISHED TIN|14|4 +Brand#21|SMALL BURNISHED TIN|19|4 +Brand#21|SMALL BURNISHED TIN|36|4 +Brand#21|SMALL BURNISHED TIN|45|4 +Brand#21|SMALL BURNISHED TIN|49|4 +Brand#21|SMALL PLATED BRASS|19|4 +Brand#21|SMALL PLATED BRASS|45|4 +Brand#21|SMALL PLATED BRASS|49|4 +Brand#21|SMALL PLATED COPPER|19|4 +Brand#21|SMALL PLATED COPPER|49|4 +Brand#21|SMALL PLATED NICKEL|19|4 +Brand#21|SMALL PLATED NICKEL|49|4 +Brand#21|SMALL PLATED STEEL|14|4 +Brand#21|SMALL PLATED STEEL|36|4 +Brand#21|SMALL PLATED TIN|3|4 +Brand#21|SMALL PLATED TIN|9|4 +Brand#21|SMALL PLATED TIN|14|4 +Brand#21|SMALL PLATED TIN|23|4 +Brand#21|SMALL POLISHED BRASS|3|4 +Brand#21|SMALL POLISHED BRASS|9|4 +Brand#21|SMALL POLISHED BRASS|23|4 +Brand#21|SMALL POLISHED BRASS|45|4 +Brand#21|SMALL POLISHED COPPER|3|4 +Brand#21|SMALL POLISHED COPPER|9|4 +Brand#21|SMALL POLISHED COPPER|19|4 +Brand#21|SMALL POLISHED COPPER|45|4 +Brand#21|SMALL POLISHED NICKEL|3|4 +Brand#21|SMALL POLISHED NICKEL|14|4 +Brand#21|SMALL POLISHED NICKEL|45|4 +Brand#21|SMALL POLISHED STEEL|14|4 +Brand#21|SMALL POLISHED STEEL|19|4 +Brand#21|SMALL POLISHED STEEL|49|4 +Brand#21|SMALL POLISHED TIN|3|4 +Brand#21|SMALL POLISHED TIN|9|4 +Brand#21|SMALL POLISHED TIN|23|4 +Brand#21|SMALL POLISHED TIN|36|4 +Brand#21|SMALL POLISHED TIN|45|4 +Brand#21|SMALL POLISHED TIN|49|4 +Brand#21|STANDARD ANODIZED BRASS|9|4 +Brand#21|STANDARD ANODIZED BRASS|14|4 +Brand#21|STANDARD ANODIZED BRASS|49|4 +Brand#21|STANDARD ANODIZED COPPER|9|4 +Brand#21|STANDARD ANODIZED COPPER|19|4 +Brand#21|STANDARD ANODIZED COPPER|49|4 +Brand#21|STANDARD ANODIZED NICKEL|14|4 +Brand#21|STANDARD ANODIZED NICKEL|23|4 +Brand#21|STANDARD ANODIZED STEEL|9|4 +Brand#21|STANDARD ANODIZED STEEL|14|4 +Brand#21|STANDARD ANODIZED STEEL|45|4 +Brand#21|STANDARD ANODIZED TIN|14|4 +Brand#21|STANDARD ANODIZED TIN|19|4 +Brand#21|STANDARD ANODIZED TIN|23|4 +Brand#21|STANDARD ANODIZED TIN|45|4 +Brand#21|STANDARD BRUSHED BRASS|3|4 +Brand#21|STANDARD BRUSHED BRASS|23|4 +Brand#21|STANDARD BRUSHED COPPER|9|4 +Brand#21|STANDARD BRUSHED COPPER|14|4 +Brand#21|STANDARD BRUSHED COPPER|19|4 +Brand#21|STANDARD BRUSHED COPPER|45|4 +Brand#21|STANDARD BRUSHED COPPER|49|4 +Brand#21|STANDARD BRUSHED NICKEL|3|4 +Brand#21|STANDARD BRUSHED NICKEL|9|4 +Brand#21|STANDARD BRUSHED NICKEL|36|4 +Brand#21|STANDARD BRUSHED NICKEL|49|4 +Brand#21|STANDARD BRUSHED TIN|3|4 +Brand#21|STANDARD BRUSHED TIN|9|4 +Brand#21|STANDARD BRUSHED TIN|14|4 +Brand#21|STANDARD BRUSHED TIN|19|4 +Brand#21|STANDARD BRUSHED TIN|49|4 +Brand#21|STANDARD BURNISHED BRASS|9|4 +Brand#21|STANDARD BURNISHED BRASS|23|4 +Brand#21|STANDARD BURNISHED COPPER|23|4 +Brand#21|STANDARD BURNISHED COPPER|36|4 +Brand#21|STANDARD BURNISHED COPPER|45|4 +Brand#21|STANDARD BURNISHED COPPER|49|4 +Brand#21|STANDARD BURNISHED NICKEL|14|4 +Brand#21|STANDARD BURNISHED NICKEL|19|4 +Brand#21|STANDARD BURNISHED NICKEL|49|4 +Brand#21|STANDARD BURNISHED STEEL|9|4 +Brand#21|STANDARD BURNISHED STEEL|23|4 +Brand#21|STANDARD BURNISHED TIN|3|4 +Brand#21|STANDARD BURNISHED TIN|9|4 +Brand#21|STANDARD PLATED BRASS|3|4 +Brand#21|STANDARD PLATED BRASS|9|4 +Brand#21|STANDARD PLATED BRASS|45|4 +Brand#21|STANDARD PLATED COPPER|9|4 +Brand#21|STANDARD PLATED NICKEL|9|4 +Brand#21|STANDARD PLATED NICKEL|14|4 +Brand#21|STANDARD PLATED NICKEL|23|4 +Brand#21|STANDARD PLATED STEEL|3|4 +Brand#21|STANDARD PLATED STEEL|9|4 +Brand#21|STANDARD PLATED STEEL|19|4 +Brand#21|STANDARD PLATED STEEL|23|4 +Brand#21|STANDARD PLATED STEEL|45|4 +Brand#21|STANDARD PLATED TIN|19|4 +Brand#21|STANDARD PLATED TIN|23|4 +Brand#21|STANDARD PLATED TIN|36|4 +Brand#21|STANDARD POLISHED BRASS|3|4 +Brand#21|STANDARD POLISHED BRASS|23|4 +Brand#21|STANDARD POLISHED BRASS|36|4 +Brand#21|STANDARD POLISHED COPPER|3|4 +Brand#21|STANDARD POLISHED COPPER|36|4 +Brand#21|STANDARD POLISHED NICKEL|3|4 +Brand#21|STANDARD POLISHED NICKEL|36|4 +Brand#21|STANDARD POLISHED NICKEL|45|4 +Brand#21|STANDARD POLISHED NICKEL|49|4 +Brand#21|STANDARD POLISHED STEEL|9|4 +Brand#21|STANDARD POLISHED STEEL|23|4 +Brand#21|STANDARD POLISHED STEEL|45|4 +Brand#21|STANDARD POLISHED STEEL|49|4 +Brand#21|STANDARD POLISHED TIN|3|4 +Brand#21|STANDARD POLISHED TIN|19|4 +Brand#21|STANDARD POLISHED TIN|23|4 +Brand#21|STANDARD POLISHED TIN|45|4 +Brand#21|STANDARD POLISHED TIN|49|4 +Brand#22|ECONOMY ANODIZED BRASS|14|4 +Brand#22|ECONOMY ANODIZED BRASS|23|4 +Brand#22|ECONOMY ANODIZED BRASS|45|4 +Brand#22|ECONOMY ANODIZED BRASS|49|4 +Brand#22|ECONOMY ANODIZED COPPER|3|4 +Brand#22|ECONOMY ANODIZED COPPER|9|4 +Brand#22|ECONOMY ANODIZED COPPER|19|4 +Brand#22|ECONOMY ANODIZED NICKEL|9|4 +Brand#22|ECONOMY ANODIZED NICKEL|14|4 +Brand#22|ECONOMY ANODIZED NICKEL|49|4 +Brand#22|ECONOMY ANODIZED STEEL|3|4 +Brand#22|ECONOMY ANODIZED STEEL|9|4 +Brand#22|ECONOMY ANODIZED STEEL|14|4 +Brand#22|ECONOMY ANODIZED STEEL|19|4 +Brand#22|ECONOMY ANODIZED STEEL|36|4 +Brand#22|ECONOMY ANODIZED STEEL|49|4 +Brand#22|ECONOMY ANODIZED TIN|3|4 +Brand#22|ECONOMY ANODIZED TIN|9|4 +Brand#22|ECONOMY ANODIZED TIN|19|4 +Brand#22|ECONOMY BRUSHED BRASS|3|4 +Brand#22|ECONOMY BRUSHED BRASS|36|4 +Brand#22|ECONOMY BRUSHED COPPER|14|4 +Brand#22|ECONOMY BRUSHED COPPER|36|4 +Brand#22|ECONOMY BRUSHED COPPER|45|4 +Brand#22|ECONOMY BRUSHED COPPER|49|4 +Brand#22|ECONOMY BRUSHED NICKEL|19|4 +Brand#22|ECONOMY BRUSHED NICKEL|23|4 +Brand#22|ECONOMY BRUSHED NICKEL|49|4 +Brand#22|ECONOMY BRUSHED STEEL|9|4 +Brand#22|ECONOMY BRUSHED STEEL|14|4 +Brand#22|ECONOMY BRUSHED STEEL|23|4 +Brand#22|ECONOMY BRUSHED STEEL|36|4 +Brand#22|ECONOMY BRUSHED TIN|9|4 +Brand#22|ECONOMY BRUSHED TIN|14|4 +Brand#22|ECONOMY BRUSHED TIN|19|4 +Brand#22|ECONOMY BURNISHED BRASS|3|4 +Brand#22|ECONOMY BURNISHED BRASS|9|4 +Brand#22|ECONOMY BURNISHED BRASS|49|4 +Brand#22|ECONOMY BURNISHED COPPER|19|4 +Brand#22|ECONOMY BURNISHED COPPER|23|4 +Brand#22|ECONOMY BURNISHED COPPER|36|4 +Brand#22|ECONOMY BURNISHED NICKEL|19|4 +Brand#22|ECONOMY BURNISHED NICKEL|45|4 +Brand#22|ECONOMY BURNISHED STEEL|3|4 +Brand#22|ECONOMY BURNISHED STEEL|14|4 +Brand#22|ECONOMY BURNISHED TIN|3|4 +Brand#22|ECONOMY BURNISHED TIN|14|4 +Brand#22|ECONOMY BURNISHED TIN|36|4 +Brand#22|ECONOMY BURNISHED TIN|45|4 +Brand#22|ECONOMY BURNISHED TIN|49|4 +Brand#22|ECONOMY PLATED BRASS|9|4 +Brand#22|ECONOMY PLATED BRASS|14|4 +Brand#22|ECONOMY PLATED BRASS|23|4 +Brand#22|ECONOMY PLATED COPPER|14|4 +Brand#22|ECONOMY PLATED COPPER|23|4 +Brand#22|ECONOMY PLATED COPPER|36|4 +Brand#22|ECONOMY PLATED COPPER|45|4 +Brand#22|ECONOMY PLATED COPPER|49|4 +Brand#22|ECONOMY PLATED NICKEL|19|4 +Brand#22|ECONOMY PLATED NICKEL|23|4 +Brand#22|ECONOMY PLATED STEEL|9|4 +Brand#22|ECONOMY PLATED STEEL|36|4 +Brand#22|ECONOMY PLATED STEEL|49|4 +Brand#22|ECONOMY PLATED TIN|3|4 +Brand#22|ECONOMY PLATED TIN|14|4 +Brand#22|ECONOMY PLATED TIN|23|4 +Brand#22|ECONOMY PLATED TIN|36|4 +Brand#22|ECONOMY PLATED TIN|45|4 +Brand#22|ECONOMY POLISHED BRASS|3|4 +Brand#22|ECONOMY POLISHED BRASS|9|4 +Brand#22|ECONOMY POLISHED BRASS|14|4 +Brand#22|ECONOMY POLISHED BRASS|19|4 +Brand#22|ECONOMY POLISHED BRASS|49|4 +Brand#22|ECONOMY POLISHED COPPER|3|4 +Brand#22|ECONOMY POLISHED COPPER|36|4 +Brand#22|ECONOMY POLISHED NICKEL|3|4 +Brand#22|ECONOMY POLISHED NICKEL|14|4 +Brand#22|ECONOMY POLISHED NICKEL|19|4 +Brand#22|ECONOMY POLISHED NICKEL|23|4 +Brand#22|ECONOMY POLISHED NICKEL|36|4 +Brand#22|ECONOMY POLISHED NICKEL|49|4 +Brand#22|ECONOMY POLISHED STEEL|3|4 +Brand#22|ECONOMY POLISHED TIN|3|4 +Brand#22|ECONOMY POLISHED TIN|23|4 +Brand#22|LARGE ANODIZED BRASS|3|4 +Brand#22|LARGE ANODIZED BRASS|9|4 +Brand#22|LARGE ANODIZED BRASS|19|4 +Brand#22|LARGE ANODIZED BRASS|23|4 +Brand#22|LARGE ANODIZED BRASS|36|4 +Brand#22|LARGE ANODIZED BRASS|45|4 +Brand#22|LARGE ANODIZED COPPER|14|4 +Brand#22|LARGE ANODIZED COPPER|45|4 +Brand#22|LARGE ANODIZED COPPER|49|4 +Brand#22|LARGE ANODIZED NICKEL|3|4 +Brand#22|LARGE ANODIZED NICKEL|9|4 +Brand#22|LARGE ANODIZED NICKEL|36|4 +Brand#22|LARGE ANODIZED NICKEL|49|4 +Brand#22|LARGE ANODIZED STEEL|3|4 +Brand#22|LARGE ANODIZED STEEL|14|4 +Brand#22|LARGE ANODIZED STEEL|23|4 +Brand#22|LARGE ANODIZED STEEL|49|4 +Brand#22|LARGE ANODIZED TIN|36|4 +Brand#22|LARGE BRUSHED BRASS|3|4 +Brand#22|LARGE BRUSHED COPPER|3|4 +Brand#22|LARGE BRUSHED NICKEL|3|4 +Brand#22|LARGE BRUSHED NICKEL|19|4 +Brand#22|LARGE BRUSHED NICKEL|36|4 +Brand#22|LARGE BRUSHED STEEL|9|4 +Brand#22|LARGE BRUSHED STEEL|45|4 +Brand#22|LARGE BRUSHED STEEL|49|4 +Brand#22|LARGE BRUSHED TIN|3|4 +Brand#22|LARGE BRUSHED TIN|9|4 +Brand#22|LARGE BRUSHED TIN|19|4 +Brand#22|LARGE BRUSHED TIN|45|4 +Brand#22|LARGE BRUSHED TIN|49|4 +Brand#22|LARGE BURNISHED BRASS|19|4 +Brand#22|LARGE BURNISHED BRASS|45|4 +Brand#22|LARGE BURNISHED BRASS|49|4 +Brand#22|LARGE BURNISHED COPPER|3|4 +Brand#22|LARGE BURNISHED COPPER|14|4 +Brand#22|LARGE BURNISHED COPPER|36|4 +Brand#22|LARGE BURNISHED COPPER|45|4 +Brand#22|LARGE BURNISHED COPPER|49|4 +Brand#22|LARGE BURNISHED NICKEL|14|4 +Brand#22|LARGE BURNISHED STEEL|3|4 +Brand#22|LARGE BURNISHED STEEL|19|4 +Brand#22|LARGE BURNISHED STEEL|23|4 +Brand#22|LARGE BURNISHED STEEL|45|4 +Brand#22|LARGE BURNISHED TIN|9|4 +Brand#22|LARGE BURNISHED TIN|14|4 +Brand#22|LARGE BURNISHED TIN|49|4 +Brand#22|LARGE PLATED BRASS|9|4 +Brand#22|LARGE PLATED BRASS|14|4 +Brand#22|LARGE PLATED BRASS|36|4 +Brand#22|LARGE PLATED BRASS|49|4 +Brand#22|LARGE PLATED COPPER|9|4 +Brand#22|LARGE PLATED COPPER|14|4 +Brand#22|LARGE PLATED COPPER|49|4 +Brand#22|LARGE PLATED NICKEL|14|4 +Brand#22|LARGE PLATED NICKEL|49|4 +Brand#22|LARGE PLATED STEEL|3|4 +Brand#22|LARGE PLATED STEEL|36|4 +Brand#22|LARGE PLATED STEEL|45|4 +Brand#22|LARGE PLATED STEEL|49|4 +Brand#22|LARGE PLATED TIN|9|4 +Brand#22|LARGE PLATED TIN|19|4 +Brand#22|LARGE POLISHED BRASS|9|4 +Brand#22|LARGE POLISHED BRASS|19|4 +Brand#22|LARGE POLISHED COPPER|14|4 +Brand#22|LARGE POLISHED COPPER|45|4 +Brand#22|LARGE POLISHED NICKEL|9|4 +Brand#22|LARGE POLISHED NICKEL|36|4 +Brand#22|LARGE POLISHED STEEL|14|4 +Brand#22|LARGE POLISHED STEEL|19|4 +Brand#22|LARGE POLISHED STEEL|23|4 +Brand#22|LARGE POLISHED STEEL|36|4 +Brand#22|LARGE POLISHED TIN|3|4 +Brand#22|LARGE POLISHED TIN|19|4 +Brand#22|LARGE POLISHED TIN|23|4 +Brand#22|MEDIUM ANODIZED BRASS|3|4 +Brand#22|MEDIUM ANODIZED BRASS|19|4 +Brand#22|MEDIUM ANODIZED BRASS|36|4 +Brand#22|MEDIUM ANODIZED BRASS|45|4 +Brand#22|MEDIUM ANODIZED COPPER|49|4 +Brand#22|MEDIUM ANODIZED NICKEL|14|4 +Brand#22|MEDIUM ANODIZED STEEL|3|4 +Brand#22|MEDIUM ANODIZED STEEL|14|4 +Brand#22|MEDIUM ANODIZED STEEL|45|4 +Brand#22|MEDIUM ANODIZED STEEL|49|4 +Brand#22|MEDIUM ANODIZED TIN|3|4 +Brand#22|MEDIUM ANODIZED TIN|9|4 +Brand#22|MEDIUM ANODIZED TIN|14|4 +Brand#22|MEDIUM ANODIZED TIN|36|4 +Brand#22|MEDIUM ANODIZED TIN|49|4 +Brand#22|MEDIUM BRUSHED BRASS|3|4 +Brand#22|MEDIUM BRUSHED BRASS|9|4 +Brand#22|MEDIUM BRUSHED BRASS|14|4 +Brand#22|MEDIUM BRUSHED BRASS|19|4 +Brand#22|MEDIUM BRUSHED BRASS|23|4 +Brand#22|MEDIUM BRUSHED COPPER|23|4 +Brand#22|MEDIUM BRUSHED NICKEL|3|4 +Brand#22|MEDIUM BRUSHED NICKEL|19|4 +Brand#22|MEDIUM BRUSHED NICKEL|23|4 +Brand#22|MEDIUM BRUSHED NICKEL|36|4 +Brand#22|MEDIUM BRUSHED NICKEL|45|4 +Brand#22|MEDIUM BRUSHED STEEL|9|4 +Brand#22|MEDIUM BRUSHED TIN|9|4 +Brand#22|MEDIUM BRUSHED TIN|14|4 +Brand#22|MEDIUM BRUSHED TIN|19|4 +Brand#22|MEDIUM BRUSHED TIN|23|4 +Brand#22|MEDIUM BRUSHED TIN|45|4 +Brand#22|MEDIUM BURNISHED BRASS|3|4 +Brand#22|MEDIUM BURNISHED BRASS|19|4 +Brand#22|MEDIUM BURNISHED BRASS|23|4 +Brand#22|MEDIUM BURNISHED COPPER|3|4 +Brand#22|MEDIUM BURNISHED COPPER|19|4 +Brand#22|MEDIUM BURNISHED NICKEL|19|4 +Brand#22|MEDIUM BURNISHED NICKEL|45|4 +Brand#22|MEDIUM BURNISHED NICKEL|49|4 +Brand#22|MEDIUM BURNISHED STEEL|23|4 +Brand#22|MEDIUM BURNISHED STEEL|49|4 +Brand#22|MEDIUM BURNISHED TIN|23|4 +Brand#22|MEDIUM BURNISHED TIN|45|4 +Brand#22|MEDIUM PLATED BRASS|3|4 +Brand#22|MEDIUM PLATED BRASS|19|4 +Brand#22|MEDIUM PLATED BRASS|45|4 +Brand#22|MEDIUM PLATED BRASS|49|4 +Brand#22|MEDIUM PLATED COPPER|9|4 +Brand#22|MEDIUM PLATED COPPER|14|4 +Brand#22|MEDIUM PLATED COPPER|23|4 +Brand#22|MEDIUM PLATED COPPER|49|4 +Brand#22|MEDIUM PLATED NICKEL|19|4 +Brand#22|MEDIUM PLATED STEEL|14|4 +Brand#22|MEDIUM PLATED STEEL|36|4 +Brand#22|MEDIUM PLATED STEEL|49|4 +Brand#22|MEDIUM PLATED TIN|3|4 +Brand#22|MEDIUM PLATED TIN|9|4 +Brand#22|MEDIUM PLATED TIN|14|4 +Brand#22|PROMO ANODIZED BRASS|14|4 +Brand#22|PROMO ANODIZED COPPER|14|4 +Brand#22|PROMO ANODIZED COPPER|36|4 +Brand#22|PROMO ANODIZED COPPER|49|4 +Brand#22|PROMO ANODIZED NICKEL|3|4 +Brand#22|PROMO ANODIZED NICKEL|14|4 +Brand#22|PROMO ANODIZED NICKEL|19|4 +Brand#22|PROMO ANODIZED NICKEL|49|4 +Brand#22|PROMO ANODIZED STEEL|3|4 +Brand#22|PROMO ANODIZED STEEL|23|4 +Brand#22|PROMO ANODIZED STEEL|45|4 +Brand#22|PROMO ANODIZED TIN|3|4 +Brand#22|PROMO ANODIZED TIN|9|4 +Brand#22|PROMO BRUSHED BRASS|9|4 +Brand#22|PROMO BRUSHED COPPER|3|4 +Brand#22|PROMO BRUSHED COPPER|9|4 +Brand#22|PROMO BRUSHED COPPER|14|4 +Brand#22|PROMO BRUSHED COPPER|19|4 +Brand#22|PROMO BRUSHED NICKEL|3|4 +Brand#22|PROMO BRUSHED NICKEL|23|4 +Brand#22|PROMO BRUSHED STEEL|9|4 +Brand#22|PROMO BRUSHED STEEL|14|4 +Brand#22|PROMO BRUSHED STEEL|19|4 +Brand#22|PROMO BRUSHED STEEL|23|4 +Brand#22|PROMO BRUSHED STEEL|49|4 +Brand#22|PROMO BRUSHED TIN|14|4 +Brand#22|PROMO BRUSHED TIN|23|4 +Brand#22|PROMO BRUSHED TIN|45|4 +Brand#22|PROMO BRUSHED TIN|49|4 +Brand#22|PROMO BURNISHED BRASS|9|4 +Brand#22|PROMO BURNISHED BRASS|19|4 +Brand#22|PROMO BURNISHED BRASS|45|4 +Brand#22|PROMO BURNISHED COPPER|3|4 +Brand#22|PROMO BURNISHED COPPER|9|4 +Brand#22|PROMO BURNISHED COPPER|19|4 +Brand#22|PROMO BURNISHED COPPER|45|4 +Brand#22|PROMO BURNISHED NICKEL|9|4 +Brand#22|PROMO BURNISHED NICKEL|23|4 +Brand#22|PROMO BURNISHED NICKEL|36|4 +Brand#22|PROMO BURNISHED NICKEL|49|4 +Brand#22|PROMO BURNISHED STEEL|9|4 +Brand#22|PROMO BURNISHED TIN|9|4 +Brand#22|PROMO BURNISHED TIN|19|4 +Brand#22|PROMO BURNISHED TIN|23|4 +Brand#22|PROMO BURNISHED TIN|36|4 +Brand#22|PROMO BURNISHED TIN|45|4 +Brand#22|PROMO BURNISHED TIN|49|4 +Brand#22|PROMO PLATED BRASS|49|4 +Brand#22|PROMO PLATED COPPER|9|4 +Brand#22|PROMO PLATED COPPER|23|4 +Brand#22|PROMO PLATED COPPER|49|4 +Brand#22|PROMO PLATED NICKEL|3|4 +Brand#22|PROMO PLATED NICKEL|14|4 +Brand#22|PROMO PLATED NICKEL|36|4 +Brand#22|PROMO PLATED STEEL|14|4 +Brand#22|PROMO PLATED STEEL|19|4 +Brand#22|PROMO PLATED STEEL|49|4 +Brand#22|PROMO PLATED TIN|9|4 +Brand#22|PROMO PLATED TIN|14|4 +Brand#22|PROMO PLATED TIN|45|4 +Brand#22|PROMO PLATED TIN|49|4 +Brand#22|PROMO POLISHED BRASS|19|4 +Brand#22|PROMO POLISHED BRASS|23|4 +Brand#22|PROMO POLISHED COPPER|9|4 +Brand#22|PROMO POLISHED COPPER|14|4 +Brand#22|PROMO POLISHED COPPER|36|4 +Brand#22|PROMO POLISHED COPPER|49|4 +Brand#22|PROMO POLISHED NICKEL|3|4 +Brand#22|PROMO POLISHED NICKEL|14|4 +Brand#22|PROMO POLISHED STEEL|3|4 +Brand#22|PROMO POLISHED STEEL|9|4 +Brand#22|PROMO POLISHED STEEL|23|4 +Brand#22|PROMO POLISHED STEEL|45|4 +Brand#22|PROMO POLISHED TIN|9|4 +Brand#22|PROMO POLISHED TIN|36|4 +Brand#22|PROMO POLISHED TIN|45|4 +Brand#22|SMALL ANODIZED BRASS|3|4 +Brand#22|SMALL ANODIZED BRASS|9|4 +Brand#22|SMALL ANODIZED BRASS|23|4 +Brand#22|SMALL ANODIZED BRASS|45|4 +Brand#22|SMALL ANODIZED COPPER|14|4 +Brand#22|SMALL ANODIZED COPPER|36|4 +Brand#22|SMALL ANODIZED NICKEL|9|4 +Brand#22|SMALL ANODIZED NICKEL|14|4 +Brand#22|SMALL ANODIZED NICKEL|19|4 +Brand#22|SMALL ANODIZED NICKEL|49|4 +Brand#22|SMALL ANODIZED STEEL|3|4 +Brand#22|SMALL ANODIZED STEEL|9|4 +Brand#22|SMALL ANODIZED STEEL|36|4 +Brand#22|SMALL ANODIZED STEEL|49|4 +Brand#22|SMALL ANODIZED TIN|3|4 +Brand#22|SMALL ANODIZED TIN|14|4 +Brand#22|SMALL ANODIZED TIN|36|4 +Brand#22|SMALL BRUSHED BRASS|23|4 +Brand#22|SMALL BRUSHED BRASS|49|4 +Brand#22|SMALL BRUSHED COPPER|3|4 +Brand#22|SMALL BRUSHED COPPER|14|4 +Brand#22|SMALL BRUSHED COPPER|19|4 +Brand#22|SMALL BRUSHED COPPER|23|4 +Brand#22|SMALL BRUSHED COPPER|49|4 +Brand#22|SMALL BRUSHED NICKEL|14|4 +Brand#22|SMALL BRUSHED NICKEL|19|4 +Brand#22|SMALL BRUSHED NICKEL|36|4 +Brand#22|SMALL BRUSHED STEEL|3|4 +Brand#22|SMALL BRUSHED STEEL|9|4 +Brand#22|SMALL BRUSHED STEEL|14|4 +Brand#22|SMALL BRUSHED STEEL|19|4 +Brand#22|SMALL BRUSHED STEEL|36|4 +Brand#22|SMALL BRUSHED STEEL|49|4 +Brand#22|SMALL BRUSHED TIN|3|4 +Brand#22|SMALL BRUSHED TIN|9|4 +Brand#22|SMALL BRUSHED TIN|36|4 +Brand#22|SMALL BURNISHED BRASS|45|4 +Brand#22|SMALL BURNISHED BRASS|49|4 +Brand#22|SMALL BURNISHED COPPER|9|4 +Brand#22|SMALL BURNISHED COPPER|23|4 +Brand#22|SMALL BURNISHED COPPER|36|4 +Brand#22|SMALL BURNISHED NICKEL|14|4 +Brand#22|SMALL BURNISHED NICKEL|19|4 +Brand#22|SMALL BURNISHED NICKEL|23|4 +Brand#22|SMALL BURNISHED NICKEL|36|4 +Brand#22|SMALL BURNISHED NICKEL|45|4 +Brand#22|SMALL BURNISHED STEEL|3|4 +Brand#22|SMALL BURNISHED STEEL|19|4 +Brand#22|SMALL BURNISHED TIN|9|4 +Brand#22|SMALL BURNISHED TIN|14|4 +Brand#22|SMALL PLATED BRASS|3|4 +Brand#22|SMALL PLATED BRASS|19|4 +Brand#22|SMALL PLATED BRASS|36|4 +Brand#22|SMALL PLATED BRASS|45|4 +Brand#22|SMALL PLATED COPPER|9|4 +Brand#22|SMALL PLATED COPPER|19|4 +Brand#22|SMALL PLATED COPPER|23|4 +Brand#22|SMALL PLATED COPPER|45|4 +Brand#22|SMALL PLATED NICKEL|14|4 +Brand#22|SMALL PLATED NICKEL|23|4 +Brand#22|SMALL PLATED NICKEL|36|4 +Brand#22|SMALL PLATED NICKEL|49|4 +Brand#22|SMALL PLATED STEEL|9|4 +Brand#22|SMALL PLATED TIN|3|4 +Brand#22|SMALL PLATED TIN|9|4 +Brand#22|SMALL PLATED TIN|14|4 +Brand#22|SMALL PLATED TIN|19|4 +Brand#22|SMALL PLATED TIN|36|4 +Brand#22|SMALL PLATED TIN|49|4 +Brand#22|SMALL POLISHED BRASS|9|4 +Brand#22|SMALL POLISHED BRASS|23|4 +Brand#22|SMALL POLISHED BRASS|49|4 +Brand#22|SMALL POLISHED COPPER|14|4 +Brand#22|SMALL POLISHED COPPER|36|4 +Brand#22|SMALL POLISHED NICKEL|36|4 +Brand#22|SMALL POLISHED STEEL|3|4 +Brand#22|SMALL POLISHED STEEL|19|4 +Brand#22|SMALL POLISHED STEEL|23|4 +Brand#22|SMALL POLISHED STEEL|36|4 +Brand#22|SMALL POLISHED TIN|3|4 +Brand#22|SMALL POLISHED TIN|9|4 +Brand#22|SMALL POLISHED TIN|36|4 +Brand#22|STANDARD ANODIZED BRASS|9|4 +Brand#22|STANDARD ANODIZED BRASS|45|4 +Brand#22|STANDARD ANODIZED BRASS|49|4 +Brand#22|STANDARD ANODIZED COPPER|3|4 +Brand#22|STANDARD ANODIZED COPPER|19|4 +Brand#22|STANDARD ANODIZED NICKEL|19|4 +Brand#22|STANDARD ANODIZED NICKEL|45|4 +Brand#22|STANDARD ANODIZED STEEL|3|4 +Brand#22|STANDARD ANODIZED STEEL|9|4 +Brand#22|STANDARD ANODIZED STEEL|36|4 +Brand#22|STANDARD ANODIZED STEEL|45|4 +Brand#22|STANDARD ANODIZED TIN|19|4 +Brand#22|STANDARD ANODIZED TIN|23|4 +Brand#22|STANDARD ANODIZED TIN|36|4 +Brand#22|STANDARD BRUSHED BRASS|23|4 +Brand#22|STANDARD BRUSHED BRASS|45|4 +Brand#22|STANDARD BRUSHED BRASS|49|4 +Brand#22|STANDARD BRUSHED COPPER|3|4 +Brand#22|STANDARD BRUSHED COPPER|9|4 +Brand#22|STANDARD BRUSHED COPPER|14|4 +Brand#22|STANDARD BRUSHED COPPER|23|4 +Brand#22|STANDARD BRUSHED COPPER|45|4 +Brand#22|STANDARD BRUSHED COPPER|49|4 +Brand#22|STANDARD BRUSHED NICKEL|3|4 +Brand#22|STANDARD BRUSHED NICKEL|36|4 +Brand#22|STANDARD BRUSHED STEEL|3|4 +Brand#22|STANDARD BRUSHED STEEL|23|4 +Brand#22|STANDARD BURNISHED BRASS|3|4 +Brand#22|STANDARD BURNISHED BRASS|9|4 +Brand#22|STANDARD BURNISHED BRASS|19|4 +Brand#22|STANDARD BURNISHED COPPER|3|4 +Brand#22|STANDARD BURNISHED COPPER|14|4 +Brand#22|STANDARD BURNISHED COPPER|19|4 +Brand#22|STANDARD BURNISHED COPPER|23|4 +Brand#22|STANDARD BURNISHED COPPER|45|4 +Brand#22|STANDARD BURNISHED NICKEL|9|4 +Brand#22|STANDARD BURNISHED NICKEL|49|4 +Brand#22|STANDARD BURNISHED STEEL|3|4 +Brand#22|STANDARD BURNISHED STEEL|14|4 +Brand#22|STANDARD BURNISHED STEEL|19|4 +Brand#22|STANDARD BURNISHED STEEL|23|4 +Brand#22|STANDARD BURNISHED STEEL|49|4 +Brand#22|STANDARD BURNISHED TIN|36|4 +Brand#22|STANDARD BURNISHED TIN|49|4 +Brand#22|STANDARD PLATED COPPER|9|4 +Brand#22|STANDARD PLATED COPPER|45|4 +Brand#22|STANDARD PLATED COPPER|49|4 +Brand#22|STANDARD PLATED NICKEL|3|4 +Brand#22|STANDARD PLATED NICKEL|14|4 +Brand#22|STANDARD PLATED NICKEL|45|4 +Brand#22|STANDARD PLATED NICKEL|49|4 +Brand#22|STANDARD PLATED STEEL|3|4 +Brand#22|STANDARD PLATED TIN|9|4 +Brand#22|STANDARD PLATED TIN|14|4 +Brand#22|STANDARD PLATED TIN|19|4 +Brand#22|STANDARD PLATED TIN|45|4 +Brand#22|STANDARD POLISHED BRASS|23|4 +Brand#22|STANDARD POLISHED COPPER|3|4 +Brand#22|STANDARD POLISHED COPPER|14|4 +Brand#22|STANDARD POLISHED COPPER|23|4 +Brand#22|STANDARD POLISHED COPPER|36|4 +Brand#22|STANDARD POLISHED COPPER|45|4 +Brand#22|STANDARD POLISHED COPPER|49|4 +Brand#22|STANDARD POLISHED NICKEL|9|4 +Brand#22|STANDARD POLISHED NICKEL|36|4 +Brand#22|STANDARD POLISHED NICKEL|49|4 +Brand#22|STANDARD POLISHED STEEL|3|4 +Brand#22|STANDARD POLISHED STEEL|23|4 +Brand#22|STANDARD POLISHED TIN|14|4 +Brand#22|STANDARD POLISHED TIN|23|4 +Brand#22|STANDARD POLISHED TIN|36|4 +Brand#22|STANDARD POLISHED TIN|49|4 +Brand#23|ECONOMY ANODIZED BRASS|14|4 +Brand#23|ECONOMY ANODIZED BRASS|19|4 +Brand#23|ECONOMY ANODIZED BRASS|23|4 +Brand#23|ECONOMY ANODIZED BRASS|45|4 +Brand#23|ECONOMY ANODIZED COPPER|9|4 +Brand#23|ECONOMY ANODIZED COPPER|14|4 +Brand#23|ECONOMY ANODIZED COPPER|19|4 +Brand#23|ECONOMY ANODIZED COPPER|36|4 +Brand#23|ECONOMY ANODIZED COPPER|45|4 +Brand#23|ECONOMY ANODIZED NICKEL|14|4 +Brand#23|ECONOMY ANODIZED NICKEL|45|4 +Brand#23|ECONOMY ANODIZED STEEL|3|4 +Brand#23|ECONOMY ANODIZED STEEL|19|4 +Brand#23|ECONOMY ANODIZED TIN|3|4 +Brand#23|ECONOMY ANODIZED TIN|9|4 +Brand#23|ECONOMY BRUSHED BRASS|23|4 +Brand#23|ECONOMY BRUSHED BRASS|45|4 +Brand#23|ECONOMY BRUSHED BRASS|49|4 +Brand#23|ECONOMY BRUSHED COPPER|45|4 +Brand#23|ECONOMY BRUSHED NICKEL|3|4 +Brand#23|ECONOMY BRUSHED NICKEL|9|4 +Brand#23|ECONOMY BRUSHED STEEL|14|4 +Brand#23|ECONOMY BRUSHED STEEL|36|4 +Brand#23|ECONOMY BRUSHED STEEL|45|4 +Brand#23|ECONOMY BRUSHED TIN|3|4 +Brand#23|ECONOMY BRUSHED TIN|36|4 +Brand#23|ECONOMY BURNISHED BRASS|3|4 +Brand#23|ECONOMY BURNISHED BRASS|19|4 +Brand#23|ECONOMY BURNISHED BRASS|36|4 +Brand#23|ECONOMY BURNISHED COPPER|19|4 +Brand#23|ECONOMY BURNISHED COPPER|36|4 +Brand#23|ECONOMY BURNISHED NICKEL|14|4 +Brand#23|ECONOMY BURNISHED NICKEL|49|4 +Brand#23|ECONOMY BURNISHED STEEL|19|4 +Brand#23|ECONOMY BURNISHED STEEL|36|4 +Brand#23|ECONOMY BURNISHED TIN|14|4 +Brand#23|ECONOMY BURNISHED TIN|23|4 +Brand#23|ECONOMY PLATED BRASS|3|4 +Brand#23|ECONOMY PLATED BRASS|36|4 +Brand#23|ECONOMY PLATED COPPER|3|4 +Brand#23|ECONOMY PLATED COPPER|45|4 +Brand#23|ECONOMY PLATED NICKEL|14|4 +Brand#23|ECONOMY PLATED NICKEL|36|4 +Brand#23|ECONOMY PLATED STEEL|9|4 +Brand#23|ECONOMY PLATED STEEL|23|4 +Brand#23|ECONOMY PLATED STEEL|45|4 +Brand#23|ECONOMY POLISHED BRASS|3|4 +Brand#23|ECONOMY POLISHED BRASS|14|4 +Brand#23|ECONOMY POLISHED BRASS|23|4 +Brand#23|ECONOMY POLISHED BRASS|45|4 +Brand#23|ECONOMY POLISHED COPPER|36|4 +Brand#23|ECONOMY POLISHED NICKEL|9|4 +Brand#23|ECONOMY POLISHED NICKEL|14|4 +Brand#23|ECONOMY POLISHED NICKEL|49|4 +Brand#23|ECONOMY POLISHED STEEL|9|4 +Brand#23|ECONOMY POLISHED STEEL|19|4 +Brand#23|ECONOMY POLISHED TIN|9|4 +Brand#23|ECONOMY POLISHED TIN|14|4 +Brand#23|ECONOMY POLISHED TIN|19|4 +Brand#23|ECONOMY POLISHED TIN|23|4 +Brand#23|ECONOMY POLISHED TIN|36|4 +Brand#23|LARGE ANODIZED BRASS|3|4 +Brand#23|LARGE ANODIZED BRASS|23|4 +Brand#23|LARGE ANODIZED COPPER|14|4 +Brand#23|LARGE ANODIZED COPPER|23|4 +Brand#23|LARGE ANODIZED NICKEL|3|4 +Brand#23|LARGE ANODIZED NICKEL|45|4 +Brand#23|LARGE ANODIZED NICKEL|49|4 +Brand#23|LARGE ANODIZED STEEL|3|4 +Brand#23|LARGE ANODIZED TIN|3|4 +Brand#23|LARGE ANODIZED TIN|9|4 +Brand#23|LARGE ANODIZED TIN|23|4 +Brand#23|LARGE BRUSHED BRASS|3|4 +Brand#23|LARGE BRUSHED BRASS|19|4 +Brand#23|LARGE BRUSHED BRASS|23|4 +Brand#23|LARGE BRUSHED BRASS|49|4 +Brand#23|LARGE BRUSHED COPPER|36|4 +Brand#23|LARGE BRUSHED COPPER|45|4 +Brand#23|LARGE BRUSHED COPPER|49|4 +Brand#23|LARGE BRUSHED NICKEL|9|4 +Brand#23|LARGE BRUSHED NICKEL|19|4 +Brand#23|LARGE BRUSHED NICKEL|49|4 +Brand#23|LARGE BRUSHED STEEL|45|4 +Brand#23|LARGE BRUSHED TIN|14|4 +Brand#23|LARGE BRUSHED TIN|23|4 +Brand#23|LARGE BRUSHED TIN|36|4 +Brand#23|LARGE BRUSHED TIN|45|4 +Brand#23|LARGE BURNISHED BRASS|3|4 +Brand#23|LARGE BURNISHED BRASS|9|4 +Brand#23|LARGE BURNISHED BRASS|14|4 +Brand#23|LARGE BURNISHED BRASS|19|4 +Brand#23|LARGE BURNISHED BRASS|36|4 +Brand#23|LARGE BURNISHED BRASS|45|4 +Brand#23|LARGE BURNISHED NICKEL|23|4 +Brand#23|LARGE BURNISHED STEEL|36|4 +Brand#23|LARGE BURNISHED TIN|3|4 +Brand#23|LARGE BURNISHED TIN|9|4 +Brand#23|LARGE BURNISHED TIN|36|4 +Brand#23|LARGE BURNISHED TIN|45|4 +Brand#23|LARGE PLATED BRASS|19|4 +Brand#23|LARGE PLATED BRASS|23|4 +Brand#23|LARGE PLATED BRASS|49|4 +Brand#23|LARGE PLATED COPPER|3|4 +Brand#23|LARGE PLATED COPPER|36|4 +Brand#23|LARGE PLATED COPPER|49|4 +Brand#23|LARGE PLATED NICKEL|3|4 +Brand#23|LARGE PLATED NICKEL|14|4 +Brand#23|LARGE PLATED NICKEL|19|4 +Brand#23|LARGE PLATED STEEL|19|4 +Brand#23|LARGE PLATED STEEL|36|4 +Brand#23|LARGE PLATED TIN|9|4 +Brand#23|LARGE PLATED TIN|14|4 +Brand#23|LARGE PLATED TIN|19|4 +Brand#23|LARGE PLATED TIN|23|4 +Brand#23|LARGE PLATED TIN|36|4 +Brand#23|LARGE PLATED TIN|45|4 +Brand#23|LARGE POLISHED BRASS|3|4 +Brand#23|LARGE POLISHED BRASS|14|4 +Brand#23|LARGE POLISHED BRASS|23|4 +Brand#23|LARGE POLISHED BRASS|36|4 +Brand#23|LARGE POLISHED BRASS|45|4 +Brand#23|LARGE POLISHED BRASS|49|4 +Brand#23|LARGE POLISHED COPPER|19|4 +Brand#23|LARGE POLISHED NICKEL|14|4 +Brand#23|LARGE POLISHED NICKEL|19|4 +Brand#23|LARGE POLISHED NICKEL|23|4 +Brand#23|LARGE POLISHED NICKEL|45|4 +Brand#23|LARGE POLISHED STEEL|9|4 +Brand#23|LARGE POLISHED STEEL|14|4 +Brand#23|LARGE POLISHED STEEL|19|4 +Brand#23|LARGE POLISHED STEEL|36|4 +Brand#23|LARGE POLISHED TIN|19|4 +Brand#23|LARGE POLISHED TIN|23|4 +Brand#23|MEDIUM ANODIZED BRASS|14|4 +Brand#23|MEDIUM ANODIZED BRASS|19|4 +Brand#23|MEDIUM ANODIZED BRASS|36|4 +Brand#23|MEDIUM ANODIZED BRASS|49|4 +Brand#23|MEDIUM ANODIZED COPPER|3|4 +Brand#23|MEDIUM ANODIZED COPPER|9|4 +Brand#23|MEDIUM ANODIZED NICKEL|36|4 +Brand#23|MEDIUM ANODIZED NICKEL|49|4 +Brand#23|MEDIUM ANODIZED STEEL|9|4 +Brand#23|MEDIUM ANODIZED STEEL|14|4 +Brand#23|MEDIUM ANODIZED TIN|3|4 +Brand#23|MEDIUM ANODIZED TIN|9|4 +Brand#23|MEDIUM ANODIZED TIN|19|4 +Brand#23|MEDIUM ANODIZED TIN|36|4 +Brand#23|MEDIUM ANODIZED TIN|49|4 +Brand#23|MEDIUM BRUSHED BRASS|23|4 +Brand#23|MEDIUM BRUSHED BRASS|36|4 +Brand#23|MEDIUM BRUSHED COPPER|9|4 +Brand#23|MEDIUM BRUSHED COPPER|36|4 +Brand#23|MEDIUM BRUSHED NICKEL|9|4 +Brand#23|MEDIUM BRUSHED STEEL|9|4 +Brand#23|MEDIUM BRUSHED STEEL|14|4 +Brand#23|MEDIUM BRUSHED STEEL|19|4 +Brand#23|MEDIUM BRUSHED STEEL|23|4 +Brand#23|MEDIUM BRUSHED STEEL|49|4 +Brand#23|MEDIUM BRUSHED TIN|3|4 +Brand#23|MEDIUM BRUSHED TIN|9|4 +Brand#23|MEDIUM BRUSHED TIN|19|4 +Brand#23|MEDIUM BRUSHED TIN|36|4 +Brand#23|MEDIUM BURNISHED BRASS|19|4 +Brand#23|MEDIUM BURNISHED BRASS|23|4 +Brand#23|MEDIUM BURNISHED BRASS|45|4 +Brand#23|MEDIUM BURNISHED BRASS|49|4 +Brand#23|MEDIUM BURNISHED COPPER|49|4 +Brand#23|MEDIUM BURNISHED NICKEL|14|4 +Brand#23|MEDIUM BURNISHED NICKEL|23|4 +Brand#23|MEDIUM BURNISHED NICKEL|36|4 +Brand#23|MEDIUM BURNISHED STEEL|19|4 +Brand#23|MEDIUM BURNISHED STEEL|36|4 +Brand#23|MEDIUM BURNISHED STEEL|49|4 +Brand#23|MEDIUM BURNISHED TIN|3|4 +Brand#23|MEDIUM BURNISHED TIN|19|4 +Brand#23|MEDIUM BURNISHED TIN|23|4 +Brand#23|MEDIUM BURNISHED TIN|49|4 +Brand#23|MEDIUM PLATED BRASS|3|4 +Brand#23|MEDIUM PLATED BRASS|23|4 +Brand#23|MEDIUM PLATED BRASS|36|4 +Brand#23|MEDIUM PLATED BRASS|49|4 +Brand#23|MEDIUM PLATED COPPER|3|4 +Brand#23|MEDIUM PLATED COPPER|14|4 +Brand#23|MEDIUM PLATED COPPER|36|4 +Brand#23|MEDIUM PLATED COPPER|45|4 +Brand#23|MEDIUM PLATED COPPER|49|4 +Brand#23|MEDIUM PLATED NICKEL|14|4 +Brand#23|MEDIUM PLATED NICKEL|45|4 +Brand#23|MEDIUM PLATED STEEL|3|4 +Brand#23|MEDIUM PLATED STEEL|9|4 +Brand#23|MEDIUM PLATED STEEL|45|4 +Brand#23|MEDIUM PLATED STEEL|49|4 +Brand#23|MEDIUM PLATED TIN|9|4 +Brand#23|MEDIUM PLATED TIN|14|4 +Brand#23|MEDIUM PLATED TIN|36|4 +Brand#23|PROMO ANODIZED BRASS|14|4 +Brand#23|PROMO ANODIZED BRASS|36|4 +Brand#23|PROMO ANODIZED BRASS|45|4 +Brand#23|PROMO ANODIZED BRASS|49|4 +Brand#23|PROMO ANODIZED COPPER|9|4 +Brand#23|PROMO ANODIZED COPPER|14|4 +Brand#23|PROMO ANODIZED NICKEL|9|4 +Brand#23|PROMO ANODIZED NICKEL|19|4 +Brand#23|PROMO ANODIZED NICKEL|49|4 +Brand#23|PROMO ANODIZED STEEL|14|4 +Brand#23|PROMO ANODIZED STEEL|45|4 +Brand#23|PROMO ANODIZED STEEL|49|4 +Brand#23|PROMO ANODIZED TIN|36|4 +Brand#23|PROMO ANODIZED TIN|45|4 +Brand#23|PROMO BRUSHED BRASS|3|4 +Brand#23|PROMO BRUSHED BRASS|9|4 +Brand#23|PROMO BRUSHED BRASS|14|4 +Brand#23|PROMO BRUSHED BRASS|45|4 +Brand#23|PROMO BRUSHED BRASS|49|4 +Brand#23|PROMO BRUSHED COPPER|3|4 +Brand#23|PROMO BRUSHED COPPER|9|4 +Brand#23|PROMO BRUSHED COPPER|49|4 +Brand#23|PROMO BRUSHED NICKEL|9|4 +Brand#23|PROMO BRUSHED NICKEL|36|4 +Brand#23|PROMO BRUSHED STEEL|14|4 +Brand#23|PROMO BRUSHED STEEL|19|4 +Brand#23|PROMO BRUSHED STEEL|23|4 +Brand#23|PROMO BRUSHED STEEL|36|4 +Brand#23|PROMO BRUSHED STEEL|45|4 +Brand#23|PROMO BRUSHED STEEL|49|4 +Brand#23|PROMO BRUSHED TIN|14|4 +Brand#23|PROMO BRUSHED TIN|36|4 +Brand#23|PROMO BURNISHED BRASS|3|4 +Brand#23|PROMO BURNISHED BRASS|19|4 +Brand#23|PROMO BURNISHED BRASS|23|4 +Brand#23|PROMO BURNISHED BRASS|36|4 +Brand#23|PROMO BURNISHED COPPER|45|4 +Brand#23|PROMO BURNISHED NICKEL|3|4 +Brand#23|PROMO BURNISHED NICKEL|14|4 +Brand#23|PROMO BURNISHED NICKEL|36|4 +Brand#23|PROMO BURNISHED NICKEL|45|4 +Brand#23|PROMO BURNISHED STEEL|19|4 +Brand#23|PROMO BURNISHED STEEL|36|4 +Brand#23|PROMO BURNISHED STEEL|49|4 +Brand#23|PROMO BURNISHED TIN|19|4 +Brand#23|PROMO BURNISHED TIN|23|4 +Brand#23|PROMO PLATED BRASS|9|4 +Brand#23|PROMO PLATED BRASS|36|4 +Brand#23|PROMO PLATED BRASS|45|4 +Brand#23|PROMO PLATED COPPER|3|4 +Brand#23|PROMO PLATED COPPER|9|4 +Brand#23|PROMO PLATED COPPER|19|4 +Brand#23|PROMO PLATED COPPER|49|4 +Brand#23|PROMO PLATED NICKEL|14|4 +Brand#23|PROMO PLATED NICKEL|19|4 +Brand#23|PROMO PLATED NICKEL|49|4 +Brand#23|PROMO PLATED STEEL|36|4 +Brand#23|PROMO PLATED TIN|49|4 +Brand#23|PROMO POLISHED BRASS|3|4 +Brand#23|PROMO POLISHED BRASS|23|4 +Brand#23|PROMO POLISHED BRASS|36|4 +Brand#23|PROMO POLISHED BRASS|49|4 +Brand#23|PROMO POLISHED COPPER|3|4 +Brand#23|PROMO POLISHED COPPER|14|4 +Brand#23|PROMO POLISHED COPPER|19|4 +Brand#23|PROMO POLISHED COPPER|49|4 +Brand#23|PROMO POLISHED NICKEL|14|4 +Brand#23|PROMO POLISHED NICKEL|49|4 +Brand#23|PROMO POLISHED STEEL|9|4 +Brand#23|PROMO POLISHED STEEL|36|4 +Brand#23|PROMO POLISHED STEEL|45|4 +Brand#23|PROMO POLISHED TIN|3|4 +Brand#23|PROMO POLISHED TIN|9|4 +Brand#23|PROMO POLISHED TIN|19|4 +Brand#23|SMALL ANODIZED BRASS|3|4 +Brand#23|SMALL ANODIZED BRASS|9|4 +Brand#23|SMALL ANODIZED COPPER|3|4 +Brand#23|SMALL ANODIZED COPPER|9|4 +Brand#23|SMALL ANODIZED COPPER|23|4 +Brand#23|SMALL ANODIZED COPPER|49|4 +Brand#23|SMALL ANODIZED NICKEL|3|4 +Brand#23|SMALL ANODIZED NICKEL|9|4 +Brand#23|SMALL ANODIZED NICKEL|19|4 +Brand#23|SMALL ANODIZED STEEL|9|4 +Brand#23|SMALL ANODIZED STEEL|19|4 +Brand#23|SMALL ANODIZED STEEL|36|4 +Brand#23|SMALL ANODIZED TIN|14|4 +Brand#23|SMALL ANODIZED TIN|19|4 +Brand#23|SMALL ANODIZED TIN|23|4 +Brand#23|SMALL ANODIZED TIN|49|4 +Brand#23|SMALL BRUSHED BRASS|3|4 +Brand#23|SMALL BRUSHED BRASS|14|4 +Brand#23|SMALL BRUSHED BRASS|36|4 +Brand#23|SMALL BRUSHED COPPER|3|4 +Brand#23|SMALL BRUSHED COPPER|14|4 +Brand#23|SMALL BRUSHED COPPER|36|4 +Brand#23|SMALL BRUSHED COPPER|49|4 +Brand#23|SMALL BRUSHED NICKEL|19|4 +Brand#23|SMALL BRUSHED NICKEL|36|4 +Brand#23|SMALL BRUSHED NICKEL|45|4 +Brand#23|SMALL BRUSHED STEEL|9|4 +Brand#23|SMALL BRUSHED STEEL|14|4 +Brand#23|SMALL BRUSHED STEEL|19|4 +Brand#23|SMALL BRUSHED TIN|9|4 +Brand#23|SMALL BRUSHED TIN|19|4 +Brand#23|SMALL BRUSHED TIN|23|4 +Brand#23|SMALL BRUSHED TIN|36|4 +Brand#23|SMALL BRUSHED TIN|49|4 +Brand#23|SMALL BURNISHED BRASS|36|4 +Brand#23|SMALL BURNISHED COPPER|3|4 +Brand#23|SMALL BURNISHED COPPER|9|4 +Brand#23|SMALL BURNISHED COPPER|19|4 +Brand#23|SMALL BURNISHED COPPER|49|4 +Brand#23|SMALL BURNISHED NICKEL|19|4 +Brand#23|SMALL BURNISHED NICKEL|23|4 +Brand#23|SMALL BURNISHED STEEL|3|4 +Brand#23|SMALL BURNISHED STEEL|36|4 +Brand#23|SMALL BURNISHED TIN|9|4 +Brand#23|SMALL BURNISHED TIN|19|4 +Brand#23|SMALL BURNISHED TIN|23|4 +Brand#23|SMALL BURNISHED TIN|49|4 +Brand#23|SMALL PLATED BRASS|14|4 +Brand#23|SMALL PLATED BRASS|19|4 +Brand#23|SMALL PLATED BRASS|23|4 +Brand#23|SMALL PLATED BRASS|36|4 +Brand#23|SMALL PLATED COPPER|9|4 +Brand#23|SMALL PLATED COPPER|19|4 +Brand#23|SMALL PLATED COPPER|23|4 +Brand#23|SMALL PLATED NICKEL|14|4 +Brand#23|SMALL PLATED NICKEL|19|4 +Brand#23|SMALL PLATED NICKEL|49|4 +Brand#23|SMALL PLATED STEEL|3|4 +Brand#23|SMALL PLATED STEEL|45|4 +Brand#23|SMALL PLATED TIN|36|4 +Brand#23|SMALL POLISHED BRASS|9|4 +Brand#23|SMALL POLISHED BRASS|14|4 +Brand#23|SMALL POLISHED BRASS|23|4 +Brand#23|SMALL POLISHED COPPER|14|4 +Brand#23|SMALL POLISHED COPPER|23|4 +Brand#23|SMALL POLISHED COPPER|36|4 +Brand#23|SMALL POLISHED COPPER|45|4 +Brand#23|SMALL POLISHED STEEL|3|4 +Brand#23|SMALL POLISHED STEEL|9|4 +Brand#23|SMALL POLISHED STEEL|14|4 +Brand#23|SMALL POLISHED STEEL|45|4 +Brand#23|SMALL POLISHED STEEL|49|4 +Brand#23|SMALL POLISHED TIN|9|4 +Brand#23|SMALL POLISHED TIN|14|4 +Brand#23|SMALL POLISHED TIN|36|4 +Brand#23|SMALL POLISHED TIN|45|4 +Brand#23|STANDARD ANODIZED BRASS|3|4 +Brand#23|STANDARD ANODIZED BRASS|9|4 +Brand#23|STANDARD ANODIZED BRASS|14|4 +Brand#23|STANDARD ANODIZED BRASS|45|4 +Brand#23|STANDARD ANODIZED COPPER|9|4 +Brand#23|STANDARD ANODIZED COPPER|49|4 +Brand#23|STANDARD ANODIZED NICKEL|3|4 +Brand#23|STANDARD ANODIZED NICKEL|36|4 +Brand#23|STANDARD ANODIZED NICKEL|45|4 +Brand#23|STANDARD ANODIZED NICKEL|49|4 +Brand#23|STANDARD ANODIZED STEEL|3|4 +Brand#23|STANDARD ANODIZED STEEL|36|4 +Brand#23|STANDARD ANODIZED TIN|36|4 +Brand#23|STANDARD BRUSHED BRASS|14|4 +Brand#23|STANDARD BRUSHED BRASS|23|4 +Brand#23|STANDARD BRUSHED BRASS|45|4 +Brand#23|STANDARD BRUSHED BRASS|49|4 +Brand#23|STANDARD BRUSHED COPPER|3|4 +Brand#23|STANDARD BRUSHED COPPER|19|4 +Brand#23|STANDARD BRUSHED COPPER|23|4 +Brand#23|STANDARD BRUSHED COPPER|45|4 +Brand#23|STANDARD BRUSHED STEEL|3|4 +Brand#23|STANDARD BRUSHED STEEL|23|4 +Brand#23|STANDARD BRUSHED TIN|9|4 +Brand#23|STANDARD BRUSHED TIN|23|4 +Brand#23|STANDARD BURNISHED BRASS|14|4 +Brand#23|STANDARD BURNISHED BRASS|19|4 +Brand#23|STANDARD BURNISHED BRASS|23|4 +Brand#23|STANDARD BURNISHED BRASS|49|4 +Brand#23|STANDARD BURNISHED COPPER|9|4 +Brand#23|STANDARD BURNISHED COPPER|14|4 +Brand#23|STANDARD BURNISHED COPPER|23|4 +Brand#23|STANDARD BURNISHED NICKEL|3|4 +Brand#23|STANDARD BURNISHED NICKEL|14|4 +Brand#23|STANDARD BURNISHED NICKEL|19|4 +Brand#23|STANDARD BURNISHED STEEL|3|4 +Brand#23|STANDARD BURNISHED STEEL|14|4 +Brand#23|STANDARD BURNISHED STEEL|19|4 +Brand#23|STANDARD BURNISHED TIN|3|4 +Brand#23|STANDARD BURNISHED TIN|23|4 +Brand#23|STANDARD PLATED BRASS|14|4 +Brand#23|STANDARD PLATED BRASS|45|4 +Brand#23|STANDARD PLATED COPPER|9|4 +Brand#23|STANDARD PLATED COPPER|19|4 +Brand#23|STANDARD PLATED NICKEL|9|4 +Brand#23|STANDARD PLATED NICKEL|45|4 +Brand#23|STANDARD PLATED STEEL|23|4 +Brand#23|STANDARD PLATED TIN|49|4 +Brand#23|STANDARD POLISHED BRASS|3|4 +Brand#23|STANDARD POLISHED BRASS|14|4 +Brand#23|STANDARD POLISHED BRASS|23|4 +Brand#23|STANDARD POLISHED COPPER|3|4 +Brand#23|STANDARD POLISHED COPPER|9|4 +Brand#23|STANDARD POLISHED COPPER|49|4 +Brand#23|STANDARD POLISHED NICKEL|19|4 +Brand#23|STANDARD POLISHED NICKEL|23|4 +Brand#23|STANDARD POLISHED NICKEL|45|4 +Brand#23|STANDARD POLISHED NICKEL|49|4 +Brand#23|STANDARD POLISHED STEEL|3|4 +Brand#23|STANDARD POLISHED STEEL|9|4 +Brand#23|STANDARD POLISHED STEEL|19|4 +Brand#23|STANDARD POLISHED STEEL|36|4 +Brand#23|STANDARD POLISHED STEEL|45|4 +Brand#23|STANDARD POLISHED STEEL|49|4 +Brand#23|STANDARD POLISHED TIN|9|4 +Brand#23|STANDARD POLISHED TIN|14|4 +Brand#23|STANDARD POLISHED TIN|49|4 +Brand#24|ECONOMY ANODIZED BRASS|9|4 +Brand#24|ECONOMY ANODIZED BRASS|14|4 +Brand#24|ECONOMY ANODIZED BRASS|36|4 +Brand#24|ECONOMY ANODIZED BRASS|45|4 +Brand#24|ECONOMY ANODIZED BRASS|49|4 +Brand#24|ECONOMY ANODIZED COPPER|19|4 +Brand#24|ECONOMY ANODIZED COPPER|45|4 +Brand#24|ECONOMY ANODIZED NICKEL|23|4 +Brand#24|ECONOMY ANODIZED NICKEL|45|4 +Brand#24|ECONOMY ANODIZED NICKEL|49|4 +Brand#24|ECONOMY ANODIZED STEEL|9|4 +Brand#24|ECONOMY ANODIZED TIN|9|4 +Brand#24|ECONOMY ANODIZED TIN|49|4 +Brand#24|ECONOMY BRUSHED BRASS|36|4 +Brand#24|ECONOMY BRUSHED BRASS|45|4 +Brand#24|ECONOMY BRUSHED BRASS|49|4 +Brand#24|ECONOMY BRUSHED COPPER|9|4 +Brand#24|ECONOMY BRUSHED COPPER|19|4 +Brand#24|ECONOMY BRUSHED COPPER|45|4 +Brand#24|ECONOMY BRUSHED COPPER|49|4 +Brand#24|ECONOMY BRUSHED NICKEL|14|4 +Brand#24|ECONOMY BRUSHED NICKEL|19|4 +Brand#24|ECONOMY BRUSHED STEEL|3|4 +Brand#24|ECONOMY BRUSHED STEEL|19|4 +Brand#24|ECONOMY BRUSHED STEEL|45|4 +Brand#24|ECONOMY BRUSHED TIN|3|4 +Brand#24|ECONOMY BRUSHED TIN|19|4 +Brand#24|ECONOMY BRUSHED TIN|23|4 +Brand#24|ECONOMY BRUSHED TIN|45|4 +Brand#24|ECONOMY BURNISHED BRASS|3|4 +Brand#24|ECONOMY BURNISHED BRASS|9|4 +Brand#24|ECONOMY BURNISHED BRASS|36|4 +Brand#24|ECONOMY BURNISHED BRASS|45|4 +Brand#24|ECONOMY BURNISHED COPPER|9|4 +Brand#24|ECONOMY BURNISHED COPPER|36|4 +Brand#24|ECONOMY BURNISHED NICKEL|23|4 +Brand#24|ECONOMY BURNISHED NICKEL|36|4 +Brand#24|ECONOMY BURNISHED NICKEL|45|4 +Brand#24|ECONOMY BURNISHED NICKEL|49|4 +Brand#24|ECONOMY BURNISHED STEEL|14|4 +Brand#24|ECONOMY BURNISHED STEEL|23|4 +Brand#24|ECONOMY BURNISHED TIN|3|4 +Brand#24|ECONOMY BURNISHED TIN|9|4 +Brand#24|ECONOMY BURNISHED TIN|19|4 +Brand#24|ECONOMY BURNISHED TIN|45|4 +Brand#24|ECONOMY PLATED BRASS|3|4 +Brand#24|ECONOMY PLATED BRASS|9|4 +Brand#24|ECONOMY PLATED BRASS|23|4 +Brand#24|ECONOMY PLATED BRASS|45|4 +Brand#24|ECONOMY PLATED COPPER|3|4 +Brand#24|ECONOMY PLATED COPPER|14|4 +Brand#24|ECONOMY PLATED COPPER|23|4 +Brand#24|ECONOMY PLATED NICKEL|45|4 +Brand#24|ECONOMY PLATED NICKEL|49|4 +Brand#24|ECONOMY PLATED STEEL|3|4 +Brand#24|ECONOMY PLATED STEEL|23|4 +Brand#24|ECONOMY PLATED TIN|14|4 +Brand#24|ECONOMY PLATED TIN|19|4 +Brand#24|ECONOMY PLATED TIN|23|4 +Brand#24|ECONOMY PLATED TIN|45|4 +Brand#24|ECONOMY POLISHED BRASS|19|4 +Brand#24|ECONOMY POLISHED BRASS|49|4 +Brand#24|ECONOMY POLISHED COPPER|9|4 +Brand#24|ECONOMY POLISHED COPPER|14|4 +Brand#24|ECONOMY POLISHED COPPER|45|4 +Brand#24|ECONOMY POLISHED NICKEL|9|4 +Brand#24|ECONOMY POLISHED NICKEL|19|4 +Brand#24|ECONOMY POLISHED NICKEL|45|4 +Brand#24|ECONOMY POLISHED NICKEL|49|4 +Brand#24|ECONOMY POLISHED STEEL|19|4 +Brand#24|ECONOMY POLISHED STEEL|45|4 +Brand#24|ECONOMY POLISHED STEEL|49|4 +Brand#24|ECONOMY POLISHED TIN|3|4 +Brand#24|LARGE ANODIZED BRASS|14|4 +Brand#24|LARGE ANODIZED BRASS|19|4 +Brand#24|LARGE ANODIZED BRASS|49|4 +Brand#24|LARGE ANODIZED COPPER|3|4 +Brand#24|LARGE ANODIZED COPPER|9|4 +Brand#24|LARGE ANODIZED COPPER|36|4 +Brand#24|LARGE ANODIZED COPPER|49|4 +Brand#24|LARGE ANODIZED NICKEL|9|4 +Brand#24|LARGE ANODIZED NICKEL|19|4 +Brand#24|LARGE ANODIZED NICKEL|36|4 +Brand#24|LARGE ANODIZED NICKEL|45|4 +Brand#24|LARGE ANODIZED STEEL|9|4 +Brand#24|LARGE ANODIZED STEEL|36|4 +Brand#24|LARGE ANODIZED TIN|14|4 +Brand#24|LARGE ANODIZED TIN|36|4 +Brand#24|LARGE ANODIZED TIN|45|4 +Brand#24|LARGE BRUSHED BRASS|3|4 +Brand#24|LARGE BRUSHED BRASS|23|4 +Brand#24|LARGE BRUSHED COPPER|23|4 +Brand#24|LARGE BRUSHED COPPER|36|4 +Brand#24|LARGE BRUSHED COPPER|45|4 +Brand#24|LARGE BRUSHED NICKEL|9|4 +Brand#24|LARGE BRUSHED NICKEL|19|4 +Brand#24|LARGE BRUSHED NICKEL|23|4 +Brand#24|LARGE BRUSHED STEEL|14|4 +Brand#24|LARGE BRUSHED STEEL|36|4 +Brand#24|LARGE BRUSHED TIN|3|4 +Brand#24|LARGE BRUSHED TIN|14|4 +Brand#24|LARGE BRUSHED TIN|19|4 +Brand#24|LARGE BURNISHED BRASS|19|4 +Brand#24|LARGE BURNISHED BRASS|49|4 +Brand#24|LARGE BURNISHED COPPER|9|4 +Brand#24|LARGE BURNISHED COPPER|14|4 +Brand#24|LARGE BURNISHED COPPER|19|4 +Brand#24|LARGE BURNISHED COPPER|23|4 +Brand#24|LARGE BURNISHED COPPER|45|4 +Brand#24|LARGE BURNISHED NICKEL|3|4 +Brand#24|LARGE BURNISHED NICKEL|9|4 +Brand#24|LARGE BURNISHED NICKEL|23|4 +Brand#24|LARGE BURNISHED NICKEL|45|4 +Brand#24|LARGE BURNISHED STEEL|9|4 +Brand#24|LARGE BURNISHED STEEL|49|4 +Brand#24|LARGE BURNISHED TIN|3|4 +Brand#24|LARGE BURNISHED TIN|19|4 +Brand#24|LARGE BURNISHED TIN|36|4 +Brand#24|LARGE PLATED BRASS|3|4 +Brand#24|LARGE PLATED BRASS|14|4 +Brand#24|LARGE PLATED BRASS|36|4 +Brand#24|LARGE PLATED BRASS|45|4 +Brand#24|LARGE PLATED COPPER|36|4 +Brand#24|LARGE PLATED NICKEL|3|4 +Brand#24|LARGE PLATED NICKEL|9|4 +Brand#24|LARGE PLATED NICKEL|23|4 +Brand#24|LARGE PLATED NICKEL|36|4 +Brand#24|LARGE PLATED NICKEL|45|4 +Brand#24|LARGE PLATED STEEL|9|4 +Brand#24|LARGE PLATED STEEL|14|4 +Brand#24|LARGE PLATED STEEL|23|4 +Brand#24|LARGE PLATED STEEL|49|4 +Brand#24|LARGE PLATED TIN|36|4 +Brand#24|LARGE PLATED TIN|49|4 +Brand#24|LARGE POLISHED BRASS|9|4 +Brand#24|LARGE POLISHED BRASS|19|4 +Brand#24|LARGE POLISHED BRASS|23|4 +Brand#24|LARGE POLISHED BRASS|49|4 +Brand#24|LARGE POLISHED COPPER|3|4 +Brand#24|LARGE POLISHED COPPER|19|4 +Brand#24|LARGE POLISHED COPPER|36|4 +Brand#24|LARGE POLISHED COPPER|49|4 +Brand#24|LARGE POLISHED NICKEL|3|4 +Brand#24|LARGE POLISHED NICKEL|14|4 +Brand#24|LARGE POLISHED STEEL|14|4 +Brand#24|LARGE POLISHED TIN|3|4 +Brand#24|LARGE POLISHED TIN|9|4 +Brand#24|LARGE POLISHED TIN|19|4 +Brand#24|LARGE POLISHED TIN|36|4 +Brand#24|LARGE POLISHED TIN|45|4 +Brand#24|MEDIUM ANODIZED BRASS|3|4 +Brand#24|MEDIUM ANODIZED BRASS|9|4 +Brand#24|MEDIUM ANODIZED BRASS|19|4 +Brand#24|MEDIUM ANODIZED BRASS|23|4 +Brand#24|MEDIUM ANODIZED BRASS|36|4 +Brand#24|MEDIUM ANODIZED COPPER|36|4 +Brand#24|MEDIUM ANODIZED NICKEL|19|4 +Brand#24|MEDIUM ANODIZED NICKEL|45|4 +Brand#24|MEDIUM ANODIZED NICKEL|49|4 +Brand#24|MEDIUM ANODIZED STEEL|3|4 +Brand#24|MEDIUM ANODIZED STEEL|14|4 +Brand#24|MEDIUM ANODIZED STEEL|36|4 +Brand#24|MEDIUM ANODIZED STEEL|45|4 +Brand#24|MEDIUM ANODIZED TIN|9|4 +Brand#24|MEDIUM ANODIZED TIN|19|4 +Brand#24|MEDIUM ANODIZED TIN|23|4 +Brand#24|MEDIUM ANODIZED TIN|36|4 +Brand#24|MEDIUM ANODIZED TIN|45|4 +Brand#24|MEDIUM ANODIZED TIN|49|4 +Brand#24|MEDIUM BRUSHED BRASS|9|4 +Brand#24|MEDIUM BRUSHED BRASS|14|4 +Brand#24|MEDIUM BRUSHED BRASS|23|4 +Brand#24|MEDIUM BRUSHED BRASS|36|4 +Brand#24|MEDIUM BRUSHED COPPER|9|4 +Brand#24|MEDIUM BRUSHED COPPER|45|4 +Brand#24|MEDIUM BRUSHED NICKEL|3|4 +Brand#24|MEDIUM BRUSHED NICKEL|23|4 +Brand#24|MEDIUM BRUSHED STEEL|3|4 +Brand#24|MEDIUM BRUSHED STEEL|9|4 +Brand#24|MEDIUM BRUSHED STEEL|14|4 +Brand#24|MEDIUM BRUSHED STEEL|45|4 +Brand#24|MEDIUM BRUSHED TIN|19|4 +Brand#24|MEDIUM BRUSHED TIN|36|4 +Brand#24|MEDIUM BRUSHED TIN|45|4 +Brand#24|MEDIUM BURNISHED BRASS|3|4 +Brand#24|MEDIUM BURNISHED BRASS|14|4 +Brand#24|MEDIUM BURNISHED BRASS|19|4 +Brand#24|MEDIUM BURNISHED BRASS|45|4 +Brand#24|MEDIUM BURNISHED COPPER|36|4 +Brand#24|MEDIUM BURNISHED COPPER|45|4 +Brand#24|MEDIUM BURNISHED NICKEL|3|4 +Brand#24|MEDIUM BURNISHED NICKEL|9|4 +Brand#24|MEDIUM BURNISHED NICKEL|14|4 +Brand#24|MEDIUM BURNISHED NICKEL|19|4 +Brand#24|MEDIUM BURNISHED STEEL|9|4 +Brand#24|MEDIUM BURNISHED STEEL|14|4 +Brand#24|MEDIUM BURNISHED STEEL|45|4 +Brand#24|MEDIUM BURNISHED TIN|3|4 +Brand#24|MEDIUM BURNISHED TIN|19|4 +Brand#24|MEDIUM BURNISHED TIN|45|4 +Brand#24|MEDIUM BURNISHED TIN|49|4 +Brand#24|MEDIUM PLATED BRASS|9|4 +Brand#24|MEDIUM PLATED BRASS|14|4 +Brand#24|MEDIUM PLATED COPPER|14|4 +Brand#24|MEDIUM PLATED COPPER|36|4 +Brand#24|MEDIUM PLATED NICKEL|14|4 +Brand#24|MEDIUM PLATED NICKEL|23|4 +Brand#24|MEDIUM PLATED NICKEL|49|4 +Brand#24|MEDIUM PLATED STEEL|3|4 +Brand#24|MEDIUM PLATED STEEL|23|4 +Brand#24|MEDIUM PLATED TIN|3|4 +Brand#24|MEDIUM PLATED TIN|9|4 +Brand#24|MEDIUM PLATED TIN|14|4 +Brand#24|MEDIUM PLATED TIN|19|4 +Brand#24|MEDIUM PLATED TIN|23|4 +Brand#24|MEDIUM PLATED TIN|36|4 +Brand#24|MEDIUM PLATED TIN|45|4 +Brand#24|MEDIUM PLATED TIN|49|4 +Brand#24|PROMO ANODIZED BRASS|9|4 +Brand#24|PROMO ANODIZED BRASS|14|4 +Brand#24|PROMO ANODIZED BRASS|19|4 +Brand#24|PROMO ANODIZED BRASS|23|4 +Brand#24|PROMO ANODIZED BRASS|36|4 +Brand#24|PROMO ANODIZED BRASS|45|4 +Brand#24|PROMO ANODIZED BRASS|49|4 +Brand#24|PROMO ANODIZED COPPER|14|4 +Brand#24|PROMO ANODIZED COPPER|23|4 +Brand#24|PROMO ANODIZED COPPER|49|4 +Brand#24|PROMO ANODIZED NICKEL|9|4 +Brand#24|PROMO ANODIZED NICKEL|23|4 +Brand#24|PROMO ANODIZED NICKEL|49|4 +Brand#24|PROMO ANODIZED STEEL|3|4 +Brand#24|PROMO ANODIZED STEEL|14|4 +Brand#24|PROMO ANODIZED STEEL|49|4 +Brand#24|PROMO ANODIZED TIN|36|4 +Brand#24|PROMO ANODIZED TIN|45|4 +Brand#24|PROMO BRUSHED BRASS|3|4 +Brand#24|PROMO BRUSHED BRASS|9|4 +Brand#24|PROMO BRUSHED BRASS|36|4 +Brand#24|PROMO BRUSHED BRASS|45|4 +Brand#24|PROMO BRUSHED BRASS|49|4 +Brand#24|PROMO BRUSHED COPPER|9|4 +Brand#24|PROMO BRUSHED COPPER|36|4 +Brand#24|PROMO BRUSHED NICKEL|23|4 +Brand#24|PROMO BRUSHED STEEL|9|4 +Brand#24|PROMO BRUSHED STEEL|14|4 +Brand#24|PROMO BRUSHED STEEL|36|4 +Brand#24|PROMO BRUSHED STEEL|45|4 +Brand#24|PROMO BRUSHED STEEL|49|4 +Brand#24|PROMO BRUSHED TIN|19|4 +Brand#24|PROMO BRUSHED TIN|23|4 +Brand#24|PROMO BRUSHED TIN|45|4 +Brand#24|PROMO BRUSHED TIN|49|4 +Brand#24|PROMO BURNISHED BRASS|3|4 +Brand#24|PROMO BURNISHED BRASS|9|4 +Brand#24|PROMO BURNISHED BRASS|19|4 +Brand#24|PROMO BURNISHED BRASS|45|4 +Brand#24|PROMO BURNISHED COPPER|3|4 +Brand#24|PROMO BURNISHED COPPER|9|4 +Brand#24|PROMO BURNISHED COPPER|14|4 +Brand#24|PROMO BURNISHED COPPER|19|4 +Brand#24|PROMO BURNISHED COPPER|23|4 +Brand#24|PROMO BURNISHED COPPER|36|4 +Brand#24|PROMO BURNISHED NICKEL|9|4 +Brand#24|PROMO BURNISHED NICKEL|49|4 +Brand#24|PROMO BURNISHED TIN|3|4 +Brand#24|PROMO BURNISHED TIN|9|4 +Brand#24|PROMO BURNISHED TIN|36|4 +Brand#24|PROMO PLATED BRASS|14|4 +Brand#24|PROMO PLATED COPPER|19|4 +Brand#24|PROMO PLATED COPPER|23|4 +Brand#24|PROMO PLATED NICKEL|3|4 +Brand#24|PROMO PLATED NICKEL|19|4 +Brand#24|PROMO PLATED NICKEL|45|4 +Brand#24|PROMO PLATED NICKEL|49|4 +Brand#24|PROMO PLATED STEEL|19|4 +Brand#24|PROMO PLATED STEEL|45|4 +Brand#24|PROMO PLATED TIN|3|4 +Brand#24|PROMO PLATED TIN|9|4 +Brand#24|PROMO PLATED TIN|45|4 +Brand#24|PROMO POLISHED BRASS|23|4 +Brand#24|PROMO POLISHED BRASS|49|4 +Brand#24|PROMO POLISHED COPPER|36|4 +Brand#24|PROMO POLISHED NICKEL|3|4 +Brand#24|PROMO POLISHED NICKEL|14|4 +Brand#24|PROMO POLISHED NICKEL|19|4 +Brand#24|PROMO POLISHED NICKEL|23|4 +Brand#24|PROMO POLISHED STEEL|3|4 +Brand#24|PROMO POLISHED STEEL|19|4 +Brand#24|PROMO POLISHED STEEL|45|4 +Brand#24|PROMO POLISHED STEEL|49|4 +Brand#24|PROMO POLISHED TIN|19|4 +Brand#24|PROMO POLISHED TIN|23|4 +Brand#24|PROMO POLISHED TIN|36|4 +Brand#24|PROMO POLISHED TIN|49|4 +Brand#24|SMALL ANODIZED BRASS|3|4 +Brand#24|SMALL ANODIZED BRASS|9|4 +Brand#24|SMALL ANODIZED BRASS|36|4 +Brand#24|SMALL ANODIZED BRASS|45|4 +Brand#24|SMALL ANODIZED BRASS|49|4 +Brand#24|SMALL ANODIZED COPPER|14|4 +Brand#24|SMALL ANODIZED COPPER|23|4 +Brand#24|SMALL ANODIZED COPPER|49|4 +Brand#24|SMALL ANODIZED NICKEL|3|4 +Brand#24|SMALL ANODIZED NICKEL|14|4 +Brand#24|SMALL ANODIZED NICKEL|36|4 +Brand#24|SMALL ANODIZED STEEL|14|4 +Brand#24|SMALL ANODIZED STEEL|36|4 +Brand#24|SMALL ANODIZED TIN|3|4 +Brand#24|SMALL ANODIZED TIN|19|4 +Brand#24|SMALL ANODIZED TIN|49|4 +Brand#24|SMALL BRUSHED BRASS|14|4 +Brand#24|SMALL BRUSHED BRASS|49|4 +Brand#24|SMALL BRUSHED COPPER|36|4 +Brand#24|SMALL BRUSHED COPPER|45|4 +Brand#24|SMALL BRUSHED COPPER|49|4 +Brand#24|SMALL BRUSHED NICKEL|3|4 +Brand#24|SMALL BRUSHED NICKEL|9|4 +Brand#24|SMALL BRUSHED NICKEL|14|4 +Brand#24|SMALL BRUSHED NICKEL|23|4 +Brand#24|SMALL BRUSHED NICKEL|45|4 +Brand#24|SMALL BRUSHED STEEL|3|4 +Brand#24|SMALL BRUSHED STEEL|49|4 +Brand#24|SMALL BRUSHED TIN|23|4 +Brand#24|SMALL BRUSHED TIN|45|4 +Brand#24|SMALL BURNISHED BRASS|9|4 +Brand#24|SMALL BURNISHED BRASS|23|4 +Brand#24|SMALL BURNISHED BRASS|45|4 +Brand#24|SMALL BURNISHED COPPER|3|4 +Brand#24|SMALL BURNISHED COPPER|9|4 +Brand#24|SMALL BURNISHED COPPER|14|4 +Brand#24|SMALL BURNISHED NICKEL|49|4 +Brand#24|SMALL BURNISHED STEEL|3|4 +Brand#24|SMALL BURNISHED STEEL|9|4 +Brand#24|SMALL BURNISHED STEEL|14|4 +Brand#24|SMALL BURNISHED STEEL|19|4 +Brand#24|SMALL BURNISHED STEEL|45|4 +Brand#24|SMALL BURNISHED TIN|3|4 +Brand#24|SMALL BURNISHED TIN|19|4 +Brand#24|SMALL BURNISHED TIN|36|4 +Brand#24|SMALL BURNISHED TIN|49|4 +Brand#24|SMALL PLATED BRASS|49|4 +Brand#24|SMALL PLATED COPPER|9|4 +Brand#24|SMALL PLATED COPPER|14|4 +Brand#24|SMALL PLATED COPPER|36|4 +Brand#24|SMALL PLATED COPPER|45|4 +Brand#24|SMALL PLATED COPPER|49|4 +Brand#24|SMALL PLATED NICKEL|9|4 +Brand#24|SMALL PLATED NICKEL|19|4 +Brand#24|SMALL PLATED NICKEL|23|4 +Brand#24|SMALL PLATED NICKEL|36|4 +Brand#24|SMALL PLATED NICKEL|45|4 +Brand#24|SMALL PLATED STEEL|9|4 +Brand#24|SMALL PLATED STEEL|45|4 +Brand#24|SMALL PLATED TIN|19|4 +Brand#24|SMALL PLATED TIN|36|4 +Brand#24|SMALL PLATED TIN|49|4 +Brand#24|SMALL POLISHED BRASS|19|4 +Brand#24|SMALL POLISHED BRASS|36|4 +Brand#24|SMALL POLISHED BRASS|45|4 +Brand#24|SMALL POLISHED BRASS|49|4 +Brand#24|SMALL POLISHED COPPER|9|4 +Brand#24|SMALL POLISHED COPPER|14|4 +Brand#24|SMALL POLISHED COPPER|19|4 +Brand#24|SMALL POLISHED COPPER|49|4 +Brand#24|SMALL POLISHED NICKEL|14|4 +Brand#24|SMALL POLISHED NICKEL|23|4 +Brand#24|SMALL POLISHED STEEL|23|4 +Brand#24|SMALL POLISHED STEEL|36|4 +Brand#24|SMALL POLISHED TIN|14|4 +Brand#24|SMALL POLISHED TIN|23|4 +Brand#24|STANDARD ANODIZED BRASS|9|4 +Brand#24|STANDARD ANODIZED BRASS|19|4 +Brand#24|STANDARD ANODIZED BRASS|45|4 +Brand#24|STANDARD ANODIZED COPPER|3|4 +Brand#24|STANDARD ANODIZED COPPER|9|4 +Brand#24|STANDARD ANODIZED COPPER|23|4 +Brand#24|STANDARD ANODIZED COPPER|36|4 +Brand#24|STANDARD ANODIZED COPPER|45|4 +Brand#24|STANDARD ANODIZED COPPER|49|4 +Brand#24|STANDARD ANODIZED NICKEL|19|4 +Brand#24|STANDARD ANODIZED NICKEL|23|4 +Brand#24|STANDARD ANODIZED NICKEL|45|4 +Brand#24|STANDARD ANODIZED STEEL|9|4 +Brand#24|STANDARD ANODIZED STEEL|19|4 +Brand#24|STANDARD ANODIZED STEEL|45|4 +Brand#24|STANDARD ANODIZED STEEL|49|4 +Brand#24|STANDARD ANODIZED TIN|9|4 +Brand#24|STANDARD ANODIZED TIN|23|4 +Brand#24|STANDARD BRUSHED BRASS|45|4 +Brand#24|STANDARD BRUSHED COPPER|3|4 +Brand#24|STANDARD BRUSHED NICKEL|9|4 +Brand#24|STANDARD BRUSHED NICKEL|36|4 +Brand#24|STANDARD BRUSHED STEEL|3|4 +Brand#24|STANDARD BRUSHED STEEL|9|4 +Brand#24|STANDARD BRUSHED STEEL|14|4 +Brand#24|STANDARD BRUSHED STEEL|36|4 +Brand#24|STANDARD BRUSHED STEEL|49|4 +Brand#24|STANDARD BRUSHED TIN|9|4 +Brand#24|STANDARD BRUSHED TIN|19|4 +Brand#24|STANDARD BRUSHED TIN|45|4 +Brand#24|STANDARD BURNISHED BRASS|3|4 +Brand#24|STANDARD BURNISHED BRASS|9|4 +Brand#24|STANDARD BURNISHED BRASS|19|4 +Brand#24|STANDARD BURNISHED BRASS|23|4 +Brand#24|STANDARD BURNISHED BRASS|49|4 +Brand#24|STANDARD BURNISHED COPPER|9|4 +Brand#24|STANDARD BURNISHED COPPER|14|4 +Brand#24|STANDARD BURNISHED COPPER|36|4 +Brand#24|STANDARD BURNISHED NICKEL|14|4 +Brand#24|STANDARD BURNISHED NICKEL|45|4 +Brand#24|STANDARD BURNISHED NICKEL|49|4 +Brand#24|STANDARD BURNISHED STEEL|3|4 +Brand#24|STANDARD BURNISHED STEEL|14|4 +Brand#24|STANDARD BURNISHED STEEL|19|4 +Brand#24|STANDARD BURNISHED STEEL|23|4 +Brand#24|STANDARD BURNISHED STEEL|49|4 +Brand#24|STANDARD BURNISHED TIN|9|4 +Brand#24|STANDARD BURNISHED TIN|19|4 +Brand#24|STANDARD BURNISHED TIN|36|4 +Brand#24|STANDARD BURNISHED TIN|49|4 +Brand#24|STANDARD PLATED BRASS|3|4 +Brand#24|STANDARD PLATED BRASS|19|4 +Brand#24|STANDARD PLATED BRASS|45|4 +Brand#24|STANDARD PLATED BRASS|49|4 +Brand#24|STANDARD PLATED COPPER|19|4 +Brand#24|STANDARD PLATED COPPER|45|4 +Brand#24|STANDARD PLATED NICKEL|49|4 +Brand#24|STANDARD PLATED STEEL|23|4 +Brand#24|STANDARD PLATED STEEL|36|4 +Brand#24|STANDARD PLATED TIN|3|4 +Brand#24|STANDARD PLATED TIN|14|4 +Brand#24|STANDARD PLATED TIN|19|4 +Brand#24|STANDARD PLATED TIN|23|4 +Brand#24|STANDARD POLISHED BRASS|3|4 +Brand#24|STANDARD POLISHED BRASS|19|4 +Brand#24|STANDARD POLISHED BRASS|36|4 +Brand#24|STANDARD POLISHED COPPER|19|4 +Brand#24|STANDARD POLISHED NICKEL|19|4 +Brand#24|STANDARD POLISHED NICKEL|36|4 +Brand#24|STANDARD POLISHED STEEL|36|4 +Brand#24|STANDARD POLISHED STEEL|45|4 +Brand#24|STANDARD POLISHED STEEL|49|4 +Brand#24|STANDARD POLISHED TIN|3|4 +Brand#24|STANDARD POLISHED TIN|9|4 +Brand#24|STANDARD POLISHED TIN|14|4 +Brand#24|STANDARD POLISHED TIN|19|4 +Brand#24|STANDARD POLISHED TIN|36|4 +Brand#24|STANDARD POLISHED TIN|49|4 +Brand#25|ECONOMY ANODIZED BRASS|9|4 +Brand#25|ECONOMY ANODIZED BRASS|14|4 +Brand#25|ECONOMY ANODIZED BRASS|23|4 +Brand#25|ECONOMY ANODIZED BRASS|45|4 +Brand#25|ECONOMY ANODIZED COPPER|3|4 +Brand#25|ECONOMY ANODIZED COPPER|36|4 +Brand#25|ECONOMY ANODIZED COPPER|45|4 +Brand#25|ECONOMY ANODIZED COPPER|49|4 +Brand#25|ECONOMY ANODIZED NICKEL|23|4 +Brand#25|ECONOMY ANODIZED NICKEL|36|4 +Brand#25|ECONOMY ANODIZED NICKEL|49|4 +Brand#25|ECONOMY ANODIZED STEEL|9|4 +Brand#25|ECONOMY ANODIZED STEEL|23|4 +Brand#25|ECONOMY ANODIZED TIN|3|4 +Brand#25|ECONOMY ANODIZED TIN|9|4 +Brand#25|ECONOMY ANODIZED TIN|14|4 +Brand#25|ECONOMY ANODIZED TIN|19|4 +Brand#25|ECONOMY ANODIZED TIN|23|4 +Brand#25|ECONOMY ANODIZED TIN|45|4 +Brand#25|ECONOMY BRUSHED BRASS|9|4 +Brand#25|ECONOMY BRUSHED BRASS|23|4 +Brand#25|ECONOMY BRUSHED BRASS|49|4 +Brand#25|ECONOMY BRUSHED COPPER|19|4 +Brand#25|ECONOMY BRUSHED COPPER|23|4 +Brand#25|ECONOMY BRUSHED COPPER|36|4 +Brand#25|ECONOMY BRUSHED COPPER|49|4 +Brand#25|ECONOMY BRUSHED NICKEL|19|4 +Brand#25|ECONOMY BRUSHED STEEL|14|4 +Brand#25|ECONOMY BRUSHED STEEL|23|4 +Brand#25|ECONOMY BRUSHED TIN|19|4 +Brand#25|ECONOMY BRUSHED TIN|36|4 +Brand#25|ECONOMY BURNISHED BRASS|3|4 +Brand#25|ECONOMY BURNISHED BRASS|23|4 +Brand#25|ECONOMY BURNISHED BRASS|36|4 +Brand#25|ECONOMY BURNISHED BRASS|45|4 +Brand#25|ECONOMY BURNISHED BRASS|49|4 +Brand#25|ECONOMY BURNISHED COPPER|3|4 +Brand#25|ECONOMY BURNISHED COPPER|36|4 +Brand#25|ECONOMY BURNISHED NICKEL|19|4 +Brand#25|ECONOMY BURNISHED NICKEL|49|4 +Brand#25|ECONOMY BURNISHED STEEL|14|4 +Brand#25|ECONOMY BURNISHED STEEL|19|4 +Brand#25|ECONOMY BURNISHED STEEL|23|4 +Brand#25|ECONOMY BURNISHED STEEL|45|4 +Brand#25|ECONOMY BURNISHED TIN|3|4 +Brand#25|ECONOMY BURNISHED TIN|9|4 +Brand#25|ECONOMY BURNISHED TIN|19|4 +Brand#25|ECONOMY BURNISHED TIN|49|4 +Brand#25|ECONOMY PLATED BRASS|9|4 +Brand#25|ECONOMY PLATED BRASS|19|4 +Brand#25|ECONOMY PLATED BRASS|36|4 +Brand#25|ECONOMY PLATED BRASS|45|4 +Brand#25|ECONOMY PLATED BRASS|49|4 +Brand#25|ECONOMY PLATED COPPER|14|4 +Brand#25|ECONOMY PLATED COPPER|23|4 +Brand#25|ECONOMY PLATED COPPER|36|4 +Brand#25|ECONOMY PLATED COPPER|49|4 +Brand#25|ECONOMY PLATED NICKEL|3|4 +Brand#25|ECONOMY PLATED NICKEL|9|4 +Brand#25|ECONOMY PLATED NICKEL|23|4 +Brand#25|ECONOMY PLATED NICKEL|49|4 +Brand#25|ECONOMY PLATED STEEL|3|4 +Brand#25|ECONOMY PLATED STEEL|14|4 +Brand#25|ECONOMY PLATED STEEL|36|4 +Brand#25|ECONOMY PLATED STEEL|45|4 +Brand#25|ECONOMY PLATED TIN|9|4 +Brand#25|ECONOMY PLATED TIN|23|4 +Brand#25|ECONOMY PLATED TIN|45|4 +Brand#25|ECONOMY PLATED TIN|49|4 +Brand#25|ECONOMY POLISHED BRASS|14|4 +Brand#25|ECONOMY POLISHED BRASS|23|4 +Brand#25|ECONOMY POLISHED BRASS|49|4 +Brand#25|ECONOMY POLISHED COPPER|19|4 +Brand#25|ECONOMY POLISHED COPPER|45|4 +Brand#25|ECONOMY POLISHED COPPER|49|4 +Brand#25|ECONOMY POLISHED NICKEL|19|4 +Brand#25|ECONOMY POLISHED NICKEL|36|4 +Brand#25|ECONOMY POLISHED NICKEL|49|4 +Brand#25|ECONOMY POLISHED STEEL|3|4 +Brand#25|ECONOMY POLISHED STEEL|19|4 +Brand#25|ECONOMY POLISHED STEEL|23|4 +Brand#25|ECONOMY POLISHED STEEL|45|4 +Brand#25|ECONOMY POLISHED STEEL|49|4 +Brand#25|ECONOMY POLISHED TIN|3|4 +Brand#25|ECONOMY POLISHED TIN|9|4 +Brand#25|ECONOMY POLISHED TIN|14|4 +Brand#25|LARGE ANODIZED BRASS|9|4 +Brand#25|LARGE ANODIZED BRASS|19|4 +Brand#25|LARGE ANODIZED BRASS|36|4 +Brand#25|LARGE ANODIZED BRASS|49|4 +Brand#25|LARGE ANODIZED COPPER|49|4 +Brand#25|LARGE ANODIZED NICKEL|9|4 +Brand#25|LARGE ANODIZED NICKEL|19|4 +Brand#25|LARGE ANODIZED NICKEL|23|4 +Brand#25|LARGE ANODIZED NICKEL|49|4 +Brand#25|LARGE ANODIZED STEEL|19|4 +Brand#25|LARGE ANODIZED STEEL|23|4 +Brand#25|LARGE ANODIZED STEEL|36|4 +Brand#25|LARGE ANODIZED STEEL|49|4 +Brand#25|LARGE ANODIZED TIN|14|4 +Brand#25|LARGE ANODIZED TIN|49|4 +Brand#25|LARGE BRUSHED BRASS|14|4 +Brand#25|LARGE BRUSHED BRASS|36|4 +Brand#25|LARGE BRUSHED BRASS|45|4 +Brand#25|LARGE BRUSHED COPPER|3|4 +Brand#25|LARGE BRUSHED COPPER|9|4 +Brand#25|LARGE BRUSHED COPPER|19|4 +Brand#25|LARGE BRUSHED COPPER|23|4 +Brand#25|LARGE BRUSHED COPPER|45|4 +Brand#25|LARGE BRUSHED COPPER|49|4 +Brand#25|LARGE BRUSHED NICKEL|3|4 +Brand#25|LARGE BRUSHED NICKEL|23|4 +Brand#25|LARGE BRUSHED NICKEL|45|4 +Brand#25|LARGE BRUSHED STEEL|3|4 +Brand#25|LARGE BRUSHED STEEL|9|4 +Brand#25|LARGE BRUSHED STEEL|14|4 +Brand#25|LARGE BRUSHED TIN|14|4 +Brand#25|LARGE BRUSHED TIN|19|4 +Brand#25|LARGE BRUSHED TIN|23|4 +Brand#25|LARGE BRUSHED TIN|36|4 +Brand#25|LARGE BRUSHED TIN|45|4 +Brand#25|LARGE BURNISHED BRASS|19|4 +Brand#25|LARGE BURNISHED COPPER|9|4 +Brand#25|LARGE BURNISHED COPPER|49|4 +Brand#25|LARGE BURNISHED NICKEL|3|4 +Brand#25|LARGE BURNISHED STEEL|3|4 +Brand#25|LARGE BURNISHED STEEL|9|4 +Brand#25|LARGE BURNISHED STEEL|19|4 +Brand#25|LARGE BURNISHED STEEL|49|4 +Brand#25|LARGE BURNISHED TIN|19|4 +Brand#25|LARGE BURNISHED TIN|45|4 +Brand#25|LARGE BURNISHED TIN|49|4 +Brand#25|LARGE PLATED BRASS|14|4 +Brand#25|LARGE PLATED BRASS|45|4 +Brand#25|LARGE PLATED COPPER|19|4 +Brand#25|LARGE PLATED COPPER|23|4 +Brand#25|LARGE PLATED NICKEL|3|4 +Brand#25|LARGE PLATED NICKEL|9|4 +Brand#25|LARGE PLATED NICKEL|14|4 +Brand#25|LARGE PLATED NICKEL|19|4 +Brand#25|LARGE PLATED NICKEL|23|4 +Brand#25|LARGE PLATED STEEL|14|4 +Brand#25|LARGE PLATED STEEL|36|4 +Brand#25|LARGE PLATED TIN|14|4 +Brand#25|LARGE POLISHED BRASS|3|4 +Brand#25|LARGE POLISHED BRASS|45|4 +Brand#25|LARGE POLISHED BRASS|49|4 +Brand#25|LARGE POLISHED COPPER|3|4 +Brand#25|LARGE POLISHED COPPER|9|4 +Brand#25|LARGE POLISHED COPPER|19|4 +Brand#25|LARGE POLISHED COPPER|49|4 +Brand#25|LARGE POLISHED NICKEL|3|4 +Brand#25|LARGE POLISHED NICKEL|9|4 +Brand#25|LARGE POLISHED NICKEL|23|4 +Brand#25|LARGE POLISHED STEEL|3|4 +Brand#25|LARGE POLISHED STEEL|14|4 +Brand#25|LARGE POLISHED TIN|9|4 +Brand#25|LARGE POLISHED TIN|19|4 +Brand#25|LARGE POLISHED TIN|36|4 +Brand#25|MEDIUM ANODIZED BRASS|23|4 +Brand#25|MEDIUM ANODIZED BRASS|45|4 +Brand#25|MEDIUM ANODIZED COPPER|3|4 +Brand#25|MEDIUM ANODIZED COPPER|14|4 +Brand#25|MEDIUM ANODIZED COPPER|19|4 +Brand#25|MEDIUM ANODIZED COPPER|23|4 +Brand#25|MEDIUM ANODIZED NICKEL|14|4 +Brand#25|MEDIUM ANODIZED NICKEL|19|4 +Brand#25|MEDIUM ANODIZED STEEL|9|4 +Brand#25|MEDIUM ANODIZED STEEL|45|4 +Brand#25|MEDIUM ANODIZED STEEL|49|4 +Brand#25|MEDIUM ANODIZED TIN|14|4 +Brand#25|MEDIUM ANODIZED TIN|19|4 +Brand#25|MEDIUM ANODIZED TIN|23|4 +Brand#25|MEDIUM ANODIZED TIN|36|4 +Brand#25|MEDIUM BRUSHED BRASS|19|4 +Brand#25|MEDIUM BRUSHED BRASS|23|4 +Brand#25|MEDIUM BRUSHED BRASS|45|4 +Brand#25|MEDIUM BRUSHED COPPER|19|4 +Brand#25|MEDIUM BRUSHED COPPER|36|4 +Brand#25|MEDIUM BRUSHED NICKEL|9|4 +Brand#25|MEDIUM BRUSHED NICKEL|19|4 +Brand#25|MEDIUM BRUSHED NICKEL|45|4 +Brand#25|MEDIUM BRUSHED STEEL|14|4 +Brand#25|MEDIUM BRUSHED STEEL|19|4 +Brand#25|MEDIUM BRUSHED STEEL|23|4 +Brand#25|MEDIUM BRUSHED STEEL|36|4 +Brand#25|MEDIUM BRUSHED STEEL|49|4 +Brand#25|MEDIUM BRUSHED TIN|3|4 +Brand#25|MEDIUM BRUSHED TIN|36|4 +Brand#25|MEDIUM BRUSHED TIN|45|4 +Brand#25|MEDIUM BURNISHED BRASS|9|4 +Brand#25|MEDIUM BURNISHED BRASS|14|4 +Brand#25|MEDIUM BURNISHED BRASS|23|4 +Brand#25|MEDIUM BURNISHED COPPER|9|4 +Brand#25|MEDIUM BURNISHED COPPER|23|4 +Brand#25|MEDIUM BURNISHED NICKEL|23|4 +Brand#25|MEDIUM BURNISHED NICKEL|36|4 +Brand#25|MEDIUM BURNISHED NICKEL|45|4 +Brand#25|MEDIUM BURNISHED NICKEL|49|4 +Brand#25|MEDIUM BURNISHED STEEL|9|4 +Brand#25|MEDIUM BURNISHED STEEL|14|4 +Brand#25|MEDIUM BURNISHED STEEL|23|4 +Brand#25|MEDIUM BURNISHED TIN|23|4 +Brand#25|MEDIUM BURNISHED TIN|36|4 +Brand#25|MEDIUM BURNISHED TIN|45|4 +Brand#25|MEDIUM PLATED BRASS|3|4 +Brand#25|MEDIUM PLATED BRASS|9|4 +Brand#25|MEDIUM PLATED BRASS|19|4 +Brand#25|MEDIUM PLATED BRASS|23|4 +Brand#25|MEDIUM PLATED BRASS|36|4 +Brand#25|MEDIUM PLATED COPPER|3|4 +Brand#25|MEDIUM PLATED COPPER|19|4 +Brand#25|MEDIUM PLATED COPPER|36|4 +Brand#25|MEDIUM PLATED NICKEL|3|4 +Brand#25|MEDIUM PLATED NICKEL|14|4 +Brand#25|MEDIUM PLATED STEEL|14|4 +Brand#25|MEDIUM PLATED STEEL|19|4 +Brand#25|MEDIUM PLATED STEEL|49|4 +Brand#25|MEDIUM PLATED TIN|9|4 +Brand#25|MEDIUM PLATED TIN|19|4 +Brand#25|MEDIUM PLATED TIN|23|4 +Brand#25|PROMO ANODIZED BRASS|3|4 +Brand#25|PROMO ANODIZED BRASS|19|4 +Brand#25|PROMO ANODIZED COPPER|9|4 +Brand#25|PROMO ANODIZED COPPER|14|4 +Brand#25|PROMO ANODIZED NICKEL|9|4 +Brand#25|PROMO ANODIZED NICKEL|19|4 +Brand#25|PROMO ANODIZED STEEL|3|4 +Brand#25|PROMO ANODIZED STEEL|14|4 +Brand#25|PROMO ANODIZED STEEL|36|4 +Brand#25|PROMO ANODIZED TIN|45|4 +Brand#25|PROMO BRUSHED BRASS|3|4 +Brand#25|PROMO BRUSHED BRASS|9|4 +Brand#25|PROMO BRUSHED COPPER|3|4 +Brand#25|PROMO BRUSHED COPPER|36|4 +Brand#25|PROMO BRUSHED NICKEL|23|4 +Brand#25|PROMO BRUSHED NICKEL|49|4 +Brand#25|PROMO BRUSHED STEEL|19|4 +Brand#25|PROMO BRUSHED STEEL|36|4 +Brand#25|PROMO BRUSHED TIN|3|4 +Brand#25|PROMO BRUSHED TIN|23|4 +Brand#25|PROMO BRUSHED TIN|36|4 +Brand#25|PROMO BURNISHED BRASS|9|4 +Brand#25|PROMO BURNISHED COPPER|3|4 +Brand#25|PROMO BURNISHED COPPER|9|4 +Brand#25|PROMO BURNISHED NICKEL|14|4 +Brand#25|PROMO BURNISHED NICKEL|19|4 +Brand#25|PROMO BURNISHED NICKEL|23|4 +Brand#25|PROMO BURNISHED STEEL|3|4 +Brand#25|PROMO BURNISHED STEEL|49|4 +Brand#25|PROMO BURNISHED TIN|9|4 +Brand#25|PROMO BURNISHED TIN|23|4 +Brand#25|PROMO BURNISHED TIN|45|4 +Brand#25|PROMO BURNISHED TIN|49|4 +Brand#25|PROMO PLATED BRASS|36|4 +Brand#25|PROMO PLATED BRASS|45|4 +Brand#25|PROMO PLATED COPPER|3|4 +Brand#25|PROMO PLATED COPPER|14|4 +Brand#25|PROMO PLATED COPPER|19|4 +Brand#25|PROMO PLATED COPPER|45|4 +Brand#25|PROMO PLATED NICKEL|14|4 +Brand#25|PROMO PLATED NICKEL|19|4 +Brand#25|PROMO PLATED NICKEL|23|4 +Brand#25|PROMO PLATED NICKEL|45|4 +Brand#25|PROMO PLATED STEEL|14|4 +Brand#25|PROMO PLATED STEEL|19|4 +Brand#25|PROMO PLATED TIN|3|4 +Brand#25|PROMO PLATED TIN|19|4 +Brand#25|PROMO PLATED TIN|23|4 +Brand#25|PROMO PLATED TIN|36|4 +Brand#25|PROMO PLATED TIN|49|4 +Brand#25|PROMO POLISHED BRASS|9|4 +Brand#25|PROMO POLISHED BRASS|23|4 +Brand#25|PROMO POLISHED BRASS|45|4 +Brand#25|PROMO POLISHED COPPER|3|4 +Brand#25|PROMO POLISHED COPPER|45|4 +Brand#25|PROMO POLISHED NICKEL|3|4 +Brand#25|PROMO POLISHED NICKEL|9|4 +Brand#25|PROMO POLISHED STEEL|19|4 +Brand#25|PROMO POLISHED TIN|3|4 +Brand#25|PROMO POLISHED TIN|23|4 +Brand#25|PROMO POLISHED TIN|45|4 +Brand#25|PROMO POLISHED TIN|49|4 +Brand#25|SMALL ANODIZED BRASS|45|4 +Brand#25|SMALL ANODIZED COPPER|3|4 +Brand#25|SMALL ANODIZED COPPER|9|4 +Brand#25|SMALL ANODIZED COPPER|14|4 +Brand#25|SMALL ANODIZED COPPER|19|4 +Brand#25|SMALL ANODIZED COPPER|49|4 +Brand#25|SMALL ANODIZED NICKEL|3|4 +Brand#25|SMALL ANODIZED NICKEL|9|4 +Brand#25|SMALL ANODIZED NICKEL|23|4 +Brand#25|SMALL ANODIZED NICKEL|45|4 +Brand#25|SMALL ANODIZED STEEL|3|4 +Brand#25|SMALL ANODIZED STEEL|9|4 +Brand#25|SMALL ANODIZED STEEL|14|4 +Brand#25|SMALL ANODIZED STEEL|19|4 +Brand#25|SMALL ANODIZED STEEL|45|4 +Brand#25|SMALL ANODIZED STEEL|49|4 +Brand#25|SMALL ANODIZED TIN|9|4 +Brand#25|SMALL ANODIZED TIN|19|4 +Brand#25|SMALL BRUSHED BRASS|9|4 +Brand#25|SMALL BRUSHED BRASS|14|4 +Brand#25|SMALL BRUSHED BRASS|19|4 +Brand#25|SMALL BRUSHED BRASS|45|4 +Brand#25|SMALL BRUSHED COPPER|3|4 +Brand#25|SMALL BRUSHED COPPER|9|4 +Brand#25|SMALL BRUSHED COPPER|45|4 +Brand#25|SMALL BRUSHED COPPER|49|4 +Brand#25|SMALL BRUSHED NICKEL|19|4 +Brand#25|SMALL BRUSHED NICKEL|23|4 +Brand#25|SMALL BRUSHED NICKEL|36|4 +Brand#25|SMALL BRUSHED NICKEL|45|4 +Brand#25|SMALL BRUSHED STEEL|19|4 +Brand#25|SMALL BRUSHED STEEL|36|4 +Brand#25|SMALL BRUSHED STEEL|45|4 +Brand#25|SMALL BRUSHED STEEL|49|4 +Brand#25|SMALL BRUSHED TIN|9|4 +Brand#25|SMALL BRUSHED TIN|14|4 +Brand#25|SMALL BRUSHED TIN|19|4 +Brand#25|SMALL BURNISHED BRASS|14|4 +Brand#25|SMALL BURNISHED BRASS|19|4 +Brand#25|SMALL BURNISHED BRASS|45|4 +Brand#25|SMALL BURNISHED BRASS|49|4 +Brand#25|SMALL BURNISHED COPPER|3|4 +Brand#25|SMALL BURNISHED COPPER|14|4 +Brand#25|SMALL BURNISHED COPPER|19|4 +Brand#25|SMALL BURNISHED COPPER|23|4 +Brand#25|SMALL BURNISHED NICKEL|14|4 +Brand#25|SMALL BURNISHED NICKEL|19|4 +Brand#25|SMALL BURNISHED STEEL|9|4 +Brand#25|SMALL BURNISHED STEEL|19|4 +Brand#25|SMALL BURNISHED STEEL|23|4 +Brand#25|SMALL BURNISHED STEEL|36|4 +Brand#25|SMALL BURNISHED TIN|9|4 +Brand#25|SMALL BURNISHED TIN|14|4 +Brand#25|SMALL BURNISHED TIN|23|4 +Brand#25|SMALL BURNISHED TIN|36|4 +Brand#25|SMALL BURNISHED TIN|49|4 +Brand#25|SMALL PLATED BRASS|3|4 +Brand#25|SMALL PLATED BRASS|23|4 +Brand#25|SMALL PLATED BRASS|45|4 +Brand#25|SMALL PLATED COPPER|3|4 +Brand#25|SMALL PLATED COPPER|14|4 +Brand#25|SMALL PLATED NICKEL|3|4 +Brand#25|SMALL PLATED NICKEL|19|4 +Brand#25|SMALL PLATED NICKEL|23|4 +Brand#25|SMALL PLATED NICKEL|49|4 +Brand#25|SMALL PLATED STEEL|3|4 +Brand#25|SMALL PLATED STEEL|14|4 +Brand#25|SMALL PLATED TIN|9|4 +Brand#25|SMALL PLATED TIN|14|4 +Brand#25|SMALL PLATED TIN|19|4 +Brand#25|SMALL PLATED TIN|36|4 +Brand#25|SMALL PLATED TIN|45|4 +Brand#25|SMALL POLISHED BRASS|14|4 +Brand#25|SMALL POLISHED BRASS|36|4 +Brand#25|SMALL POLISHED NICKEL|36|4 +Brand#25|SMALL POLISHED NICKEL|49|4 +Brand#25|SMALL POLISHED STEEL|9|4 +Brand#25|SMALL POLISHED STEEL|49|4 +Brand#25|SMALL POLISHED TIN|14|4 +Brand#25|STANDARD ANODIZED BRASS|14|4 +Brand#25|STANDARD ANODIZED BRASS|23|4 +Brand#25|STANDARD ANODIZED BRASS|36|4 +Brand#25|STANDARD ANODIZED COPPER|9|4 +Brand#25|STANDARD ANODIZED COPPER|14|4 +Brand#25|STANDARD ANODIZED COPPER|19|4 +Brand#25|STANDARD ANODIZED COPPER|36|4 +Brand#25|STANDARD ANODIZED COPPER|49|4 +Brand#25|STANDARD ANODIZED NICKEL|9|4 +Brand#25|STANDARD ANODIZED NICKEL|19|4 +Brand#25|STANDARD ANODIZED NICKEL|36|4 +Brand#25|STANDARD ANODIZED STEEL|19|4 +Brand#25|STANDARD ANODIZED STEEL|36|4 +Brand#25|STANDARD ANODIZED STEEL|45|4 +Brand#25|STANDARD ANODIZED STEEL|49|4 +Brand#25|STANDARD ANODIZED TIN|36|4 +Brand#25|STANDARD ANODIZED TIN|45|4 +Brand#25|STANDARD BRUSHED BRASS|14|4 +Brand#25|STANDARD BRUSHED BRASS|19|4 +Brand#25|STANDARD BRUSHED BRASS|23|4 +Brand#25|STANDARD BRUSHED COPPER|45|4 +Brand#25|STANDARD BRUSHED NICKEL|3|4 +Brand#25|STANDARD BRUSHED NICKEL|9|4 +Brand#25|STANDARD BRUSHED NICKEL|45|4 +Brand#25|STANDARD BRUSHED STEEL|14|4 +Brand#25|STANDARD BRUSHED STEEL|23|4 +Brand#25|STANDARD BRUSHED STEEL|45|4 +Brand#25|STANDARD BRUSHED TIN|3|4 +Brand#25|STANDARD BRUSHED TIN|9|4 +Brand#25|STANDARD BRUSHED TIN|14|4 +Brand#25|STANDARD BURNISHED BRASS|19|4 +Brand#25|STANDARD BURNISHED BRASS|36|4 +Brand#25|STANDARD BURNISHED BRASS|45|4 +Brand#25|STANDARD BURNISHED BRASS|49|4 +Brand#25|STANDARD BURNISHED COPPER|3|4 +Brand#25|STANDARD BURNISHED COPPER|14|4 +Brand#25|STANDARD BURNISHED COPPER|36|4 +Brand#25|STANDARD BURNISHED COPPER|45|4 +Brand#25|STANDARD BURNISHED COPPER|49|4 +Brand#25|STANDARD BURNISHED NICKEL|14|4 +Brand#25|STANDARD BURNISHED NICKEL|45|4 +Brand#25|STANDARD BURNISHED NICKEL|49|4 +Brand#25|STANDARD BURNISHED STEEL|3|4 +Brand#25|STANDARD BURNISHED STEEL|9|4 +Brand#25|STANDARD BURNISHED STEEL|19|4 +Brand#25|STANDARD BURNISHED STEEL|49|4 +Brand#25|STANDARD BURNISHED TIN|9|4 +Brand#25|STANDARD BURNISHED TIN|45|4 +Brand#25|STANDARD PLATED BRASS|3|4 +Brand#25|STANDARD PLATED BRASS|36|4 +Brand#25|STANDARD PLATED COPPER|3|4 +Brand#25|STANDARD PLATED COPPER|19|4 +Brand#25|STANDARD PLATED NICKEL|9|4 +Brand#25|STANDARD PLATED NICKEL|19|4 +Brand#25|STANDARD PLATED STEEL|23|4 +Brand#25|STANDARD PLATED TIN|3|4 +Brand#25|STANDARD PLATED TIN|9|4 +Brand#25|STANDARD PLATED TIN|14|4 +Brand#25|STANDARD PLATED TIN|19|4 +Brand#25|STANDARD PLATED TIN|45|4 +Brand#25|STANDARD POLISHED BRASS|3|4 +Brand#25|STANDARD POLISHED BRASS|14|4 +Brand#25|STANDARD POLISHED BRASS|23|4 +Brand#25|STANDARD POLISHED BRASS|45|4 +Brand#25|STANDARD POLISHED COPPER|9|4 +Brand#25|STANDARD POLISHED COPPER|19|4 +Brand#25|STANDARD POLISHED COPPER|45|4 +Brand#25|STANDARD POLISHED COPPER|49|4 +Brand#25|STANDARD POLISHED NICKEL|14|4 +Brand#25|STANDARD POLISHED NICKEL|23|4 +Brand#25|STANDARD POLISHED NICKEL|49|4 +Brand#25|STANDARD POLISHED STEEL|49|4 +Brand#25|STANDARD POLISHED TIN|9|4 +Brand#25|STANDARD POLISHED TIN|23|4 +Brand#31|ECONOMY ANODIZED BRASS|3|4 +Brand#31|ECONOMY ANODIZED BRASS|9|4 +Brand#31|ECONOMY ANODIZED BRASS|23|4 +Brand#31|ECONOMY ANODIZED COPPER|3|4 +Brand#31|ECONOMY ANODIZED COPPER|9|4 +Brand#31|ECONOMY ANODIZED COPPER|14|4 +Brand#31|ECONOMY ANODIZED COPPER|36|4 +Brand#31|ECONOMY ANODIZED COPPER|45|4 +Brand#31|ECONOMY ANODIZED NICKEL|19|4 +Brand#31|ECONOMY ANODIZED NICKEL|23|4 +Brand#31|ECONOMY ANODIZED NICKEL|36|4 +Brand#31|ECONOMY ANODIZED STEEL|9|4 +Brand#31|ECONOMY ANODIZED STEEL|19|4 +Brand#31|ECONOMY ANODIZED STEEL|23|4 +Brand#31|ECONOMY ANODIZED STEEL|49|4 +Brand#31|ECONOMY ANODIZED TIN|14|4 +Brand#31|ECONOMY ANODIZED TIN|45|4 +Brand#31|ECONOMY BRUSHED BRASS|14|4 +Brand#31|ECONOMY BRUSHED BRASS|23|4 +Brand#31|ECONOMY BRUSHED BRASS|45|4 +Brand#31|ECONOMY BRUSHED BRASS|49|4 +Brand#31|ECONOMY BRUSHED COPPER|19|4 +Brand#31|ECONOMY BRUSHED COPPER|23|4 +Brand#31|ECONOMY BRUSHED COPPER|45|4 +Brand#31|ECONOMY BRUSHED COPPER|49|4 +Brand#31|ECONOMY BRUSHED NICKEL|23|4 +Brand#31|ECONOMY BRUSHED NICKEL|36|4 +Brand#31|ECONOMY BRUSHED NICKEL|45|4 +Brand#31|ECONOMY BRUSHED NICKEL|49|4 +Brand#31|ECONOMY BRUSHED STEEL|45|4 +Brand#31|ECONOMY BRUSHED TIN|3|4 +Brand#31|ECONOMY BRUSHED TIN|9|4 +Brand#31|ECONOMY BRUSHED TIN|45|4 +Brand#31|ECONOMY BURNISHED BRASS|9|4 +Brand#31|ECONOMY BURNISHED BRASS|19|4 +Brand#31|ECONOMY BURNISHED BRASS|36|4 +Brand#31|ECONOMY BURNISHED BRASS|49|4 +Brand#31|ECONOMY BURNISHED COPPER|3|4 +Brand#31|ECONOMY BURNISHED COPPER|23|4 +Brand#31|ECONOMY BURNISHED COPPER|36|4 +Brand#31|ECONOMY BURNISHED NICKEL|3|4 +Brand#31|ECONOMY BURNISHED NICKEL|9|4 +Brand#31|ECONOMY BURNISHED NICKEL|14|4 +Brand#31|ECONOMY BURNISHED NICKEL|23|4 +Brand#31|ECONOMY BURNISHED NICKEL|49|4 +Brand#31|ECONOMY BURNISHED STEEL|9|4 +Brand#31|ECONOMY BURNISHED STEEL|23|4 +Brand#31|ECONOMY BURNISHED STEEL|36|4 +Brand#31|ECONOMY BURNISHED STEEL|45|4 +Brand#31|ECONOMY BURNISHED TIN|36|4 +Brand#31|ECONOMY PLATED BRASS|3|4 +Brand#31|ECONOMY PLATED BRASS|9|4 +Brand#31|ECONOMY PLATED BRASS|14|4 +Brand#31|ECONOMY PLATED BRASS|19|4 +Brand#31|ECONOMY PLATED BRASS|49|4 +Brand#31|ECONOMY PLATED COPPER|9|4 +Brand#31|ECONOMY PLATED COPPER|14|4 +Brand#31|ECONOMY PLATED COPPER|23|4 +Brand#31|ECONOMY PLATED COPPER|36|4 +Brand#31|ECONOMY PLATED COPPER|45|4 +Brand#31|ECONOMY PLATED NICKEL|3|4 +Brand#31|ECONOMY PLATED NICKEL|14|4 +Brand#31|ECONOMY PLATED NICKEL|19|4 +Brand#31|ECONOMY PLATED NICKEL|23|4 +Brand#31|ECONOMY PLATED NICKEL|45|4 +Brand#31|ECONOMY PLATED NICKEL|49|4 +Brand#31|ECONOMY PLATED STEEL|9|4 +Brand#31|ECONOMY PLATED STEEL|14|4 +Brand#31|ECONOMY PLATED STEEL|19|4 +Brand#31|ECONOMY PLATED STEEL|36|4 +Brand#31|ECONOMY PLATED TIN|9|4 +Brand#31|ECONOMY PLATED TIN|14|4 +Brand#31|ECONOMY PLATED TIN|49|4 +Brand#31|ECONOMY POLISHED BRASS|19|4 +Brand#31|ECONOMY POLISHED BRASS|49|4 +Brand#31|ECONOMY POLISHED COPPER|9|4 +Brand#31|ECONOMY POLISHED COPPER|23|4 +Brand#31|ECONOMY POLISHED COPPER|36|4 +Brand#31|ECONOMY POLISHED COPPER|45|4 +Brand#31|ECONOMY POLISHED NICKEL|19|4 +Brand#31|ECONOMY POLISHED NICKEL|23|4 +Brand#31|ECONOMY POLISHED NICKEL|49|4 +Brand#31|ECONOMY POLISHED STEEL|14|4 +Brand#31|ECONOMY POLISHED STEEL|19|4 +Brand#31|ECONOMY POLISHED STEEL|23|4 +Brand#31|ECONOMY POLISHED STEEL|36|4 +Brand#31|ECONOMY POLISHED TIN|9|4 +Brand#31|ECONOMY POLISHED TIN|14|4 +Brand#31|ECONOMY POLISHED TIN|19|4 +Brand#31|ECONOMY POLISHED TIN|23|4 +Brand#31|ECONOMY POLISHED TIN|49|4 +Brand#31|LARGE ANODIZED BRASS|9|4 +Brand#31|LARGE ANODIZED BRASS|19|4 +Brand#31|LARGE ANODIZED BRASS|23|4 +Brand#31|LARGE ANODIZED BRASS|49|4 +Brand#31|LARGE ANODIZED COPPER|9|4 +Brand#31|LARGE ANODIZED NICKEL|3|4 +Brand#31|LARGE ANODIZED NICKEL|9|4 +Brand#31|LARGE ANODIZED NICKEL|23|4 +Brand#31|LARGE ANODIZED STEEL|14|4 +Brand#31|LARGE ANODIZED STEEL|19|4 +Brand#31|LARGE ANODIZED STEEL|23|4 +Brand#31|LARGE ANODIZED TIN|23|4 +Brand#31|LARGE BRUSHED BRASS|3|4 +Brand#31|LARGE BRUSHED BRASS|14|4 +Brand#31|LARGE BRUSHED BRASS|19|4 +Brand#31|LARGE BRUSHED COPPER|14|4 +Brand#31|LARGE BRUSHED COPPER|23|4 +Brand#31|LARGE BRUSHED COPPER|36|4 +Brand#31|LARGE BRUSHED COPPER|49|4 +Brand#31|LARGE BRUSHED NICKEL|9|4 +Brand#31|LARGE BRUSHED NICKEL|49|4 +Brand#31|LARGE BRUSHED STEEL|3|4 +Brand#31|LARGE BRUSHED STEEL|9|4 +Brand#31|LARGE BRUSHED STEEL|19|4 +Brand#31|LARGE BRUSHED STEEL|49|4 +Brand#31|LARGE BRUSHED TIN|3|4 +Brand#31|LARGE BRUSHED TIN|9|4 +Brand#31|LARGE BRUSHED TIN|19|4 +Brand#31|LARGE BRUSHED TIN|23|4 +Brand#31|LARGE BURNISHED BRASS|9|4 +Brand#31|LARGE BURNISHED BRASS|14|4 +Brand#31|LARGE BURNISHED COPPER|3|4 +Brand#31|LARGE BURNISHED COPPER|14|4 +Brand#31|LARGE BURNISHED COPPER|19|4 +Brand#31|LARGE BURNISHED COPPER|49|4 +Brand#31|LARGE BURNISHED NICKEL|3|4 +Brand#31|LARGE BURNISHED NICKEL|23|4 +Brand#31|LARGE BURNISHED STEEL|14|4 +Brand#31|LARGE BURNISHED STEEL|19|4 +Brand#31|LARGE BURNISHED STEEL|45|4 +Brand#31|LARGE BURNISHED TIN|3|4 +Brand#31|LARGE BURNISHED TIN|9|4 +Brand#31|LARGE BURNISHED TIN|36|4 +Brand#31|LARGE BURNISHED TIN|45|4 +Brand#31|LARGE PLATED BRASS|19|4 +Brand#31|LARGE PLATED BRASS|36|4 +Brand#31|LARGE PLATED COPPER|9|4 +Brand#31|LARGE PLATED COPPER|14|4 +Brand#31|LARGE PLATED COPPER|36|4 +Brand#31|LARGE PLATED COPPER|49|4 +Brand#31|LARGE PLATED NICKEL|3|4 +Brand#31|LARGE PLATED NICKEL|9|4 +Brand#31|LARGE PLATED NICKEL|14|4 +Brand#31|LARGE PLATED STEEL|3|4 +Brand#31|LARGE PLATED STEEL|19|4 +Brand#31|LARGE PLATED STEEL|36|4 +Brand#31|LARGE PLATED STEEL|49|4 +Brand#31|LARGE PLATED TIN|3|4 +Brand#31|LARGE PLATED TIN|19|4 +Brand#31|LARGE PLATED TIN|45|4 +Brand#31|LARGE PLATED TIN|49|4 +Brand#31|LARGE POLISHED BRASS|3|4 +Brand#31|LARGE POLISHED BRASS|14|4 +Brand#31|LARGE POLISHED BRASS|19|4 +Brand#31|LARGE POLISHED BRASS|36|4 +Brand#31|LARGE POLISHED BRASS|49|4 +Brand#31|LARGE POLISHED COPPER|36|4 +Brand#31|LARGE POLISHED COPPER|45|4 +Brand#31|LARGE POLISHED NICKEL|9|4 +Brand#31|LARGE POLISHED NICKEL|23|4 +Brand#31|LARGE POLISHED STEEL|3|4 +Brand#31|LARGE POLISHED STEEL|9|4 +Brand#31|LARGE POLISHED STEEL|14|4 +Brand#31|LARGE POLISHED STEEL|19|4 +Brand#31|LARGE POLISHED TIN|36|4 +Brand#31|LARGE POLISHED TIN|45|4 +Brand#31|MEDIUM ANODIZED BRASS|3|4 +Brand#31|MEDIUM ANODIZED BRASS|9|4 +Brand#31|MEDIUM ANODIZED BRASS|36|4 +Brand#31|MEDIUM ANODIZED BRASS|49|4 +Brand#31|MEDIUM ANODIZED COPPER|36|4 +Brand#31|MEDIUM ANODIZED NICKEL|23|4 +Brand#31|MEDIUM ANODIZED NICKEL|36|4 +Brand#31|MEDIUM ANODIZED NICKEL|45|4 +Brand#31|MEDIUM ANODIZED STEEL|36|4 +Brand#31|MEDIUM ANODIZED TIN|36|4 +Brand#31|MEDIUM ANODIZED TIN|45|4 +Brand#31|MEDIUM ANODIZED TIN|49|4 +Brand#31|MEDIUM BRUSHED BRASS|49|4 +Brand#31|MEDIUM BRUSHED COPPER|3|4 +Brand#31|MEDIUM BRUSHED COPPER|45|4 +Brand#31|MEDIUM BRUSHED NICKEL|3|4 +Brand#31|MEDIUM BRUSHED NICKEL|23|4 +Brand#31|MEDIUM BRUSHED NICKEL|45|4 +Brand#31|MEDIUM BRUSHED STEEL|9|4 +Brand#31|MEDIUM BRUSHED STEEL|14|4 +Brand#31|MEDIUM BRUSHED STEEL|36|4 +Brand#31|MEDIUM BRUSHED STEEL|45|4 +Brand#31|MEDIUM BRUSHED TIN|19|4 +Brand#31|MEDIUM BRUSHED TIN|36|4 +Brand#31|MEDIUM BRUSHED TIN|45|4 +Brand#31|MEDIUM BURNISHED BRASS|9|4 +Brand#31|MEDIUM BURNISHED BRASS|36|4 +Brand#31|MEDIUM BURNISHED COPPER|3|4 +Brand#31|MEDIUM BURNISHED COPPER|9|4 +Brand#31|MEDIUM BURNISHED COPPER|14|4 +Brand#31|MEDIUM BURNISHED COPPER|23|4 +Brand#31|MEDIUM BURNISHED NICKEL|36|4 +Brand#31|MEDIUM BURNISHED NICKEL|49|4 +Brand#31|MEDIUM BURNISHED STEEL|14|4 +Brand#31|MEDIUM BURNISHED STEEL|49|4 +Brand#31|MEDIUM BURNISHED TIN|9|4 +Brand#31|MEDIUM BURNISHED TIN|45|4 +Brand#31|MEDIUM BURNISHED TIN|49|4 +Brand#31|MEDIUM PLATED BRASS|14|4 +Brand#31|MEDIUM PLATED BRASS|36|4 +Brand#31|MEDIUM PLATED BRASS|45|4 +Brand#31|MEDIUM PLATED COPPER|45|4 +Brand#31|MEDIUM PLATED NICKEL|14|4 +Brand#31|MEDIUM PLATED NICKEL|19|4 +Brand#31|MEDIUM PLATED NICKEL|45|4 +Brand#31|MEDIUM PLATED STEEL|14|4 +Brand#31|MEDIUM PLATED STEEL|49|4 +Brand#31|MEDIUM PLATED TIN|3|4 +Brand#31|MEDIUM PLATED TIN|9|4 +Brand#31|MEDIUM PLATED TIN|14|4 +Brand#31|MEDIUM PLATED TIN|36|4 +Brand#31|MEDIUM PLATED TIN|49|4 +Brand#31|PROMO ANODIZED BRASS|19|4 +Brand#31|PROMO ANODIZED BRASS|45|4 +Brand#31|PROMO ANODIZED COPPER|19|4 +Brand#31|PROMO ANODIZED COPPER|36|4 +Brand#31|PROMO ANODIZED COPPER|45|4 +Brand#31|PROMO ANODIZED NICKEL|9|4 +Brand#31|PROMO ANODIZED NICKEL|49|4 +Brand#31|PROMO ANODIZED STEEL|3|4 +Brand#31|PROMO ANODIZED STEEL|23|4 +Brand#31|PROMO ANODIZED STEEL|45|4 +Brand#31|PROMO ANODIZED TIN|9|4 +Brand#31|PROMO ANODIZED TIN|45|4 +Brand#31|PROMO ANODIZED TIN|49|4 +Brand#31|PROMO BRUSHED BRASS|9|4 +Brand#31|PROMO BRUSHED BRASS|14|4 +Brand#31|PROMO BRUSHED BRASS|45|4 +Brand#31|PROMO BRUSHED COPPER|9|4 +Brand#31|PROMO BRUSHED COPPER|36|4 +Brand#31|PROMO BRUSHED COPPER|49|4 +Brand#31|PROMO BRUSHED NICKEL|19|4 +Brand#31|PROMO BRUSHED NICKEL|36|4 +Brand#31|PROMO BRUSHED NICKEL|45|4 +Brand#31|PROMO BRUSHED STEEL|14|4 +Brand#31|PROMO BRUSHED STEEL|19|4 +Brand#31|PROMO BRUSHED STEEL|36|4 +Brand#31|PROMO BRUSHED TIN|14|4 +Brand#31|PROMO BRUSHED TIN|19|4 +Brand#31|PROMO BRUSHED TIN|23|4 +Brand#31|PROMO BRUSHED TIN|49|4 +Brand#31|PROMO BURNISHED BRASS|23|4 +Brand#31|PROMO BURNISHED BRASS|45|4 +Brand#31|PROMO BURNISHED COPPER|23|4 +Brand#31|PROMO BURNISHED COPPER|49|4 +Brand#31|PROMO BURNISHED NICKEL|23|4 +Brand#31|PROMO BURNISHED NICKEL|36|4 +Brand#31|PROMO BURNISHED STEEL|9|4 +Brand#31|PROMO BURNISHED TIN|3|4 +Brand#31|PROMO BURNISHED TIN|9|4 +Brand#31|PROMO BURNISHED TIN|14|4 +Brand#31|PROMO BURNISHED TIN|19|4 +Brand#31|PROMO BURNISHED TIN|36|4 +Brand#31|PROMO BURNISHED TIN|45|4 +Brand#31|PROMO PLATED BRASS|9|4 +Brand#31|PROMO PLATED BRASS|14|4 +Brand#31|PROMO PLATED BRASS|19|4 +Brand#31|PROMO PLATED BRASS|49|4 +Brand#31|PROMO PLATED COPPER|3|4 +Brand#31|PROMO PLATED COPPER|9|4 +Brand#31|PROMO PLATED COPPER|23|4 +Brand#31|PROMO PLATED COPPER|45|4 +Brand#31|PROMO PLATED NICKEL|3|4 +Brand#31|PROMO PLATED NICKEL|9|4 +Brand#31|PROMO PLATED NICKEL|14|4 +Brand#31|PROMO PLATED NICKEL|19|4 +Brand#31|PROMO PLATED NICKEL|23|4 +Brand#31|PROMO PLATED NICKEL|49|4 +Brand#31|PROMO PLATED STEEL|3|4 +Brand#31|PROMO PLATED STEEL|9|4 +Brand#31|PROMO PLATED STEEL|14|4 +Brand#31|PROMO PLATED TIN|9|4 +Brand#31|PROMO PLATED TIN|36|4 +Brand#31|PROMO POLISHED BRASS|14|4 +Brand#31|PROMO POLISHED BRASS|36|4 +Brand#31|PROMO POLISHED COPPER|14|4 +Brand#31|PROMO POLISHED NICKEL|9|4 +Brand#31|PROMO POLISHED NICKEL|36|4 +Brand#31|PROMO POLISHED STEEL|19|4 +Brand#31|PROMO POLISHED STEEL|45|4 +Brand#31|PROMO POLISHED STEEL|49|4 +Brand#31|PROMO POLISHED TIN|3|4 +Brand#31|PROMO POLISHED TIN|14|4 +Brand#31|PROMO POLISHED TIN|19|4 +Brand#31|PROMO POLISHED TIN|23|4 +Brand#31|PROMO POLISHED TIN|36|4 +Brand#31|SMALL ANODIZED BRASS|3|4 +Brand#31|SMALL ANODIZED BRASS|14|4 +Brand#31|SMALL ANODIZED BRASS|23|4 +Brand#31|SMALL ANODIZED BRASS|45|4 +Brand#31|SMALL ANODIZED BRASS|49|4 +Brand#31|SMALL ANODIZED COPPER|9|4 +Brand#31|SMALL ANODIZED COPPER|19|4 +Brand#31|SMALL ANODIZED COPPER|23|4 +Brand#31|SMALL ANODIZED NICKEL|19|4 +Brand#31|SMALL ANODIZED NICKEL|36|4 +Brand#31|SMALL ANODIZED NICKEL|45|4 +Brand#31|SMALL ANODIZED STEEL|19|4 +Brand#31|SMALL ANODIZED STEEL|23|4 +Brand#31|SMALL ANODIZED STEEL|36|4 +Brand#31|SMALL ANODIZED STEEL|49|4 +Brand#31|SMALL ANODIZED TIN|9|4 +Brand#31|SMALL ANODIZED TIN|19|4 +Brand#31|SMALL ANODIZED TIN|45|4 +Brand#31|SMALL ANODIZED TIN|49|4 +Brand#31|SMALL BRUSHED BRASS|9|4 +Brand#31|SMALL BRUSHED BRASS|14|4 +Brand#31|SMALL BRUSHED BRASS|19|4 +Brand#31|SMALL BRUSHED BRASS|36|4 +Brand#31|SMALL BRUSHED COPPER|36|4 +Brand#31|SMALL BRUSHED COPPER|45|4 +Brand#31|SMALL BRUSHED COPPER|49|4 +Brand#31|SMALL BRUSHED NICKEL|9|4 +Brand#31|SMALL BRUSHED NICKEL|45|4 +Brand#31|SMALL BRUSHED STEEL|19|4 +Brand#31|SMALL BRUSHED STEEL|45|4 +Brand#31|SMALL BRUSHED TIN|23|4 +Brand#31|SMALL BRUSHED TIN|36|4 +Brand#31|SMALL BURNISHED BRASS|19|4 +Brand#31|SMALL BURNISHED BRASS|23|4 +Brand#31|SMALL BURNISHED BRASS|45|4 +Brand#31|SMALL BURNISHED COPPER|9|4 +Brand#31|SMALL BURNISHED COPPER|14|4 +Brand#31|SMALL BURNISHED COPPER|23|4 +Brand#31|SMALL BURNISHED COPPER|36|4 +Brand#31|SMALL BURNISHED COPPER|45|4 +Brand#31|SMALL BURNISHED COPPER|49|4 +Brand#31|SMALL BURNISHED NICKEL|19|4 +Brand#31|SMALL BURNISHED NICKEL|36|4 +Brand#31|SMALL BURNISHED NICKEL|45|4 +Brand#31|SMALL BURNISHED TIN|3|4 +Brand#31|SMALL BURNISHED TIN|9|4 +Brand#31|SMALL BURNISHED TIN|19|4 +Brand#31|SMALL PLATED BRASS|9|4 +Brand#31|SMALL PLATED BRASS|19|4 +Brand#31|SMALL PLATED BRASS|36|4 +Brand#31|SMALL PLATED BRASS|45|4 +Brand#31|SMALL PLATED COPPER|3|4 +Brand#31|SMALL PLATED COPPER|36|4 +Brand#31|SMALL PLATED COPPER|45|4 +Brand#31|SMALL PLATED NICKEL|3|4 +Brand#31|SMALL PLATED NICKEL|9|4 +Brand#31|SMALL PLATED NICKEL|14|4 +Brand#31|SMALL PLATED NICKEL|45|4 +Brand#31|SMALL PLATED NICKEL|49|4 +Brand#31|SMALL PLATED STEEL|3|4 +Brand#31|SMALL PLATED STEEL|49|4 +Brand#31|SMALL PLATED TIN|14|4 +Brand#31|SMALL PLATED TIN|19|4 +Brand#31|SMALL PLATED TIN|23|4 +Brand#31|SMALL PLATED TIN|49|4 +Brand#31|SMALL POLISHED BRASS|9|4 +Brand#31|SMALL POLISHED BRASS|36|4 +Brand#31|SMALL POLISHED BRASS|45|4 +Brand#31|SMALL POLISHED COPPER|14|4 +Brand#31|SMALL POLISHED COPPER|23|4 +Brand#31|SMALL POLISHED COPPER|45|4 +Brand#31|SMALL POLISHED COPPER|49|4 +Brand#31|SMALL POLISHED NICKEL|9|4 +Brand#31|SMALL POLISHED NICKEL|23|4 +Brand#31|SMALL POLISHED NICKEL|45|4 +Brand#31|SMALL POLISHED NICKEL|49|4 +Brand#31|SMALL POLISHED STEEL|36|4 +Brand#31|SMALL POLISHED STEEL|45|4 +Brand#31|SMALL POLISHED TIN|3|4 +Brand#31|SMALL POLISHED TIN|19|4 +Brand#31|STANDARD ANODIZED BRASS|3|4 +Brand#31|STANDARD ANODIZED BRASS|14|4 +Brand#31|STANDARD ANODIZED BRASS|23|4 +Brand#31|STANDARD ANODIZED BRASS|49|4 +Brand#31|STANDARD ANODIZED COPPER|3|4 +Brand#31|STANDARD ANODIZED COPPER|9|4 +Brand#31|STANDARD ANODIZED COPPER|19|4 +Brand#31|STANDARD ANODIZED COPPER|36|4 +Brand#31|STANDARD ANODIZED COPPER|49|4 +Brand#31|STANDARD ANODIZED NICKEL|36|4 +Brand#31|STANDARD ANODIZED NICKEL|49|4 +Brand#31|STANDARD ANODIZED STEEL|3|4 +Brand#31|STANDARD ANODIZED STEEL|14|4 +Brand#31|STANDARD ANODIZED STEEL|23|4 +Brand#31|STANDARD ANODIZED TIN|14|4 +Brand#31|STANDARD ANODIZED TIN|23|4 +Brand#31|STANDARD BRUSHED BRASS|3|4 +Brand#31|STANDARD BRUSHED BRASS|14|4 +Brand#31|STANDARD BRUSHED BRASS|19|4 +Brand#31|STANDARD BRUSHED BRASS|23|4 +Brand#31|STANDARD BRUSHED BRASS|49|4 +Brand#31|STANDARD BRUSHED COPPER|9|4 +Brand#31|STANDARD BRUSHED COPPER|14|4 +Brand#31|STANDARD BRUSHED COPPER|19|4 +Brand#31|STANDARD BRUSHED COPPER|23|4 +Brand#31|STANDARD BRUSHED COPPER|49|4 +Brand#31|STANDARD BRUSHED NICKEL|14|4 +Brand#31|STANDARD BRUSHED NICKEL|19|4 +Brand#31|STANDARD BRUSHED NICKEL|23|4 +Brand#31|STANDARD BRUSHED NICKEL|49|4 +Brand#31|STANDARD BRUSHED STEEL|3|4 +Brand#31|STANDARD BRUSHED STEEL|23|4 +Brand#31|STANDARD BRUSHED STEEL|49|4 +Brand#31|STANDARD BRUSHED TIN|49|4 +Brand#31|STANDARD BURNISHED BRASS|3|4 +Brand#31|STANDARD BURNISHED BRASS|14|4 +Brand#31|STANDARD BURNISHED BRASS|19|4 +Brand#31|STANDARD BURNISHED COPPER|19|4 +Brand#31|STANDARD BURNISHED COPPER|36|4 +Brand#31|STANDARD BURNISHED COPPER|45|4 +Brand#31|STANDARD BURNISHED NICKEL|3|4 +Brand#31|STANDARD BURNISHED NICKEL|36|4 +Brand#31|STANDARD BURNISHED TIN|14|4 +Brand#31|STANDARD BURNISHED TIN|23|4 +Brand#31|STANDARD BURNISHED TIN|45|4 +Brand#31|STANDARD BURNISHED TIN|49|4 +Brand#31|STANDARD PLATED BRASS|14|4 +Brand#31|STANDARD PLATED BRASS|23|4 +Brand#31|STANDARD PLATED BRASS|45|4 +Brand#31|STANDARD PLATED BRASS|49|4 +Brand#31|STANDARD PLATED COPPER|9|4 +Brand#31|STANDARD PLATED COPPER|19|4 +Brand#31|STANDARD PLATED COPPER|45|4 +Brand#31|STANDARD PLATED NICKEL|14|4 +Brand#31|STANDARD PLATED NICKEL|19|4 +Brand#31|STANDARD PLATED NICKEL|45|4 +Brand#31|STANDARD PLATED NICKEL|49|4 +Brand#31|STANDARD PLATED STEEL|3|4 +Brand#31|STANDARD PLATED STEEL|14|4 +Brand#31|STANDARD PLATED STEEL|36|4 +Brand#31|STANDARD PLATED STEEL|45|4 +Brand#31|STANDARD PLATED STEEL|49|4 +Brand#31|STANDARD PLATED TIN|3|4 +Brand#31|STANDARD PLATED TIN|45|4 +Brand#31|STANDARD PLATED TIN|49|4 +Brand#31|STANDARD POLISHED BRASS|3|4 +Brand#31|STANDARD POLISHED BRASS|9|4 +Brand#31|STANDARD POLISHED BRASS|45|4 +Brand#31|STANDARD POLISHED COPPER|9|4 +Brand#31|STANDARD POLISHED COPPER|36|4 +Brand#31|STANDARD POLISHED COPPER|49|4 +Brand#31|STANDARD POLISHED NICKEL|3|4 +Brand#31|STANDARD POLISHED NICKEL|14|4 +Brand#31|STANDARD POLISHED NICKEL|36|4 +Brand#31|STANDARD POLISHED NICKEL|49|4 +Brand#31|STANDARD POLISHED STEEL|9|4 +Brand#31|STANDARD POLISHED STEEL|49|4 +Brand#31|STANDARD POLISHED TIN|3|4 +Brand#31|STANDARD POLISHED TIN|9|4 +Brand#31|STANDARD POLISHED TIN|14|4 +Brand#32|ECONOMY ANODIZED BRASS|36|4 +Brand#32|ECONOMY ANODIZED NICKEL|9|4 +Brand#32|ECONOMY ANODIZED NICKEL|23|4 +Brand#32|ECONOMY ANODIZED NICKEL|36|4 +Brand#32|ECONOMY ANODIZED STEEL|9|4 +Brand#32|ECONOMY ANODIZED STEEL|14|4 +Brand#32|ECONOMY ANODIZED STEEL|36|4 +Brand#32|ECONOMY ANODIZED TIN|3|4 +Brand#32|ECONOMY ANODIZED TIN|9|4 +Brand#32|ECONOMY ANODIZED TIN|14|4 +Brand#32|ECONOMY ANODIZED TIN|19|4 +Brand#32|ECONOMY ANODIZED TIN|36|4 +Brand#32|ECONOMY ANODIZED TIN|45|4 +Brand#32|ECONOMY BRUSHED BRASS|14|4 +Brand#32|ECONOMY BRUSHED BRASS|19|4 +Brand#32|ECONOMY BRUSHED BRASS|23|4 +Brand#32|ECONOMY BRUSHED BRASS|36|4 +Brand#32|ECONOMY BRUSHED COPPER|9|4 +Brand#32|ECONOMY BRUSHED COPPER|19|4 +Brand#32|ECONOMY BRUSHED COPPER|45|4 +Brand#32|ECONOMY BRUSHED NICKEL|9|4 +Brand#32|ECONOMY BRUSHED NICKEL|14|4 +Brand#32|ECONOMY BRUSHED NICKEL|23|4 +Brand#32|ECONOMY BRUSHED NICKEL|45|4 +Brand#32|ECONOMY BRUSHED STEEL|19|4 +Brand#32|ECONOMY BRUSHED STEEL|23|4 +Brand#32|ECONOMY BRUSHED STEEL|45|4 +Brand#32|ECONOMY BRUSHED STEEL|49|4 +Brand#32|ECONOMY BRUSHED TIN|9|4 +Brand#32|ECONOMY BRUSHED TIN|36|4 +Brand#32|ECONOMY BURNISHED BRASS|3|4 +Brand#32|ECONOMY BURNISHED BRASS|9|4 +Brand#32|ECONOMY BURNISHED BRASS|14|4 +Brand#32|ECONOMY BURNISHED BRASS|19|4 +Brand#32|ECONOMY BURNISHED BRASS|23|4 +Brand#32|ECONOMY BURNISHED BRASS|36|4 +Brand#32|ECONOMY BURNISHED BRASS|49|4 +Brand#32|ECONOMY BURNISHED COPPER|19|4 +Brand#32|ECONOMY BURNISHED COPPER|23|4 +Brand#32|ECONOMY BURNISHED COPPER|36|4 +Brand#32|ECONOMY BURNISHED COPPER|45|4 +Brand#32|ECONOMY BURNISHED COPPER|49|4 +Brand#32|ECONOMY BURNISHED NICKEL|45|4 +Brand#32|ECONOMY BURNISHED NICKEL|49|4 +Brand#32|ECONOMY BURNISHED STEEL|23|4 +Brand#32|ECONOMY BURNISHED STEEL|45|4 +Brand#32|ECONOMY BURNISHED STEEL|49|4 +Brand#32|ECONOMY BURNISHED TIN|14|4 +Brand#32|ECONOMY PLATED BRASS|23|4 +Brand#32|ECONOMY PLATED BRASS|36|4 +Brand#32|ECONOMY PLATED COPPER|3|4 +Brand#32|ECONOMY PLATED COPPER|9|4 +Brand#32|ECONOMY PLATED COPPER|14|4 +Brand#32|ECONOMY PLATED COPPER|23|4 +Brand#32|ECONOMY PLATED COPPER|36|4 +Brand#32|ECONOMY PLATED COPPER|45|4 +Brand#32|ECONOMY PLATED COPPER|49|4 +Brand#32|ECONOMY PLATED NICKEL|9|4 +Brand#32|ECONOMY PLATED NICKEL|45|4 +Brand#32|ECONOMY PLATED STEEL|9|4 +Brand#32|ECONOMY PLATED STEEL|45|4 +Brand#32|ECONOMY PLATED TIN|3|4 +Brand#32|ECONOMY PLATED TIN|14|4 +Brand#32|ECONOMY PLATED TIN|36|4 +Brand#32|ECONOMY POLISHED BRASS|9|4 +Brand#32|ECONOMY POLISHED COPPER|14|4 +Brand#32|ECONOMY POLISHED COPPER|19|4 +Brand#32|ECONOMY POLISHED NICKEL|36|4 +Brand#32|ECONOMY POLISHED NICKEL|45|4 +Brand#32|ECONOMY POLISHED STEEL|3|4 +Brand#32|ECONOMY POLISHED STEEL|14|4 +Brand#32|ECONOMY POLISHED STEEL|45|4 +Brand#32|ECONOMY POLISHED STEEL|49|4 +Brand#32|ECONOMY POLISHED TIN|14|4 +Brand#32|ECONOMY POLISHED TIN|36|4 +Brand#32|ECONOMY POLISHED TIN|45|4 +Brand#32|ECONOMY POLISHED TIN|49|4 +Brand#32|LARGE ANODIZED BRASS|14|4 +Brand#32|LARGE ANODIZED BRASS|23|4 +Brand#32|LARGE ANODIZED COPPER|9|4 +Brand#32|LARGE ANODIZED COPPER|14|4 +Brand#32|LARGE ANODIZED COPPER|36|4 +Brand#32|LARGE ANODIZED COPPER|45|4 +Brand#32|LARGE ANODIZED NICKEL|14|4 +Brand#32|LARGE ANODIZED NICKEL|23|4 +Brand#32|LARGE ANODIZED STEEL|19|4 +Brand#32|LARGE ANODIZED STEEL|23|4 +Brand#32|LARGE ANODIZED STEEL|45|4 +Brand#32|LARGE ANODIZED TIN|3|4 +Brand#32|LARGE ANODIZED TIN|45|4 +Brand#32|LARGE BRUSHED BRASS|9|4 +Brand#32|LARGE BRUSHED BRASS|36|4 +Brand#32|LARGE BRUSHED BRASS|49|4 +Brand#32|LARGE BRUSHED COPPER|19|4 +Brand#32|LARGE BRUSHED COPPER|23|4 +Brand#32|LARGE BRUSHED COPPER|36|4 +Brand#32|LARGE BRUSHED NICKEL|23|4 +Brand#32|LARGE BRUSHED NICKEL|36|4 +Brand#32|LARGE BRUSHED STEEL|3|4 +Brand#32|LARGE BRUSHED STEEL|14|4 +Brand#32|LARGE BRUSHED STEEL|19|4 +Brand#32|LARGE BRUSHED STEEL|36|4 +Brand#32|LARGE BRUSHED STEEL|49|4 +Brand#32|LARGE BRUSHED TIN|3|4 +Brand#32|LARGE BRUSHED TIN|45|4 +Brand#32|LARGE BRUSHED TIN|49|4 +Brand#32|LARGE BURNISHED BRASS|19|4 +Brand#32|LARGE BURNISHED COPPER|3|4 +Brand#32|LARGE BURNISHED COPPER|9|4 +Brand#32|LARGE BURNISHED COPPER|19|4 +Brand#32|LARGE BURNISHED COPPER|45|4 +Brand#32|LARGE BURNISHED NICKEL|14|4 +Brand#32|LARGE BURNISHED NICKEL|23|4 +Brand#32|LARGE BURNISHED NICKEL|49|4 +Brand#32|LARGE BURNISHED STEEL|3|4 +Brand#32|LARGE BURNISHED STEEL|36|4 +Brand#32|LARGE BURNISHED STEEL|45|4 +Brand#32|LARGE BURNISHED TIN|19|4 +Brand#32|LARGE PLATED COPPER|3|4 +Brand#32|LARGE PLATED COPPER|9|4 +Brand#32|LARGE PLATED COPPER|23|4 +Brand#32|LARGE PLATED COPPER|45|4 +Brand#32|LARGE PLATED NICKEL|9|4 +Brand#32|LARGE PLATED NICKEL|49|4 +Brand#32|LARGE PLATED STEEL|3|4 +Brand#32|LARGE PLATED STEEL|9|4 +Brand#32|LARGE PLATED STEEL|14|4 +Brand#32|LARGE PLATED STEEL|36|4 +Brand#32|LARGE PLATED STEEL|49|4 +Brand#32|LARGE PLATED TIN|19|4 +Brand#32|LARGE PLATED TIN|23|4 +Brand#32|LARGE PLATED TIN|45|4 +Brand#32|LARGE PLATED TIN|49|4 +Brand#32|LARGE POLISHED BRASS|3|4 +Brand#32|LARGE POLISHED BRASS|14|4 +Brand#32|LARGE POLISHED BRASS|49|4 +Brand#32|LARGE POLISHED COPPER|14|4 +Brand#32|LARGE POLISHED COPPER|36|4 +Brand#32|LARGE POLISHED COPPER|45|4 +Brand#32|LARGE POLISHED COPPER|49|4 +Brand#32|LARGE POLISHED NICKEL|14|4 +Brand#32|LARGE POLISHED NICKEL|19|4 +Brand#32|LARGE POLISHED NICKEL|36|4 +Brand#32|LARGE POLISHED NICKEL|45|4 +Brand#32|LARGE POLISHED NICKEL|49|4 +Brand#32|LARGE POLISHED STEEL|3|4 +Brand#32|LARGE POLISHED STEEL|9|4 +Brand#32|LARGE POLISHED TIN|23|4 +Brand#32|LARGE POLISHED TIN|36|4 +Brand#32|MEDIUM ANODIZED BRASS|9|4 +Brand#32|MEDIUM ANODIZED BRASS|14|4 +Brand#32|MEDIUM ANODIZED BRASS|19|4 +Brand#32|MEDIUM ANODIZED BRASS|49|4 +Brand#32|MEDIUM ANODIZED COPPER|9|4 +Brand#32|MEDIUM ANODIZED COPPER|19|4 +Brand#32|MEDIUM ANODIZED COPPER|23|4 +Brand#32|MEDIUM ANODIZED COPPER|36|4 +Brand#32|MEDIUM ANODIZED NICKEL|3|4 +Brand#32|MEDIUM ANODIZED NICKEL|9|4 +Brand#32|MEDIUM ANODIZED NICKEL|14|4 +Brand#32|MEDIUM ANODIZED NICKEL|19|4 +Brand#32|MEDIUM ANODIZED NICKEL|23|4 +Brand#32|MEDIUM ANODIZED STEEL|14|4 +Brand#32|MEDIUM ANODIZED STEEL|36|4 +Brand#32|MEDIUM ANODIZED STEEL|45|4 +Brand#32|MEDIUM ANODIZED TIN|14|4 +Brand#32|MEDIUM ANODIZED TIN|23|4 +Brand#32|MEDIUM BRUSHED BRASS|23|4 +Brand#32|MEDIUM BRUSHED BRASS|45|4 +Brand#32|MEDIUM BRUSHED COPPER|3|4 +Brand#32|MEDIUM BRUSHED COPPER|9|4 +Brand#32|MEDIUM BRUSHED COPPER|19|4 +Brand#32|MEDIUM BRUSHED COPPER|45|4 +Brand#32|MEDIUM BRUSHED NICKEL|14|4 +Brand#32|MEDIUM BRUSHED NICKEL|23|4 +Brand#32|MEDIUM BRUSHED NICKEL|49|4 +Brand#32|MEDIUM BRUSHED STEEL|9|4 +Brand#32|MEDIUM BRUSHED STEEL|14|4 +Brand#32|MEDIUM BRUSHED STEEL|19|4 +Brand#32|MEDIUM BRUSHED STEEL|36|4 +Brand#32|MEDIUM BRUSHED STEEL|45|4 +Brand#32|MEDIUM BRUSHED STEEL|49|4 +Brand#32|MEDIUM BRUSHED TIN|14|4 +Brand#32|MEDIUM BRUSHED TIN|49|4 +Brand#32|MEDIUM BURNISHED BRASS|9|4 +Brand#32|MEDIUM BURNISHED BRASS|36|4 +Brand#32|MEDIUM BURNISHED COPPER|3|4 +Brand#32|MEDIUM BURNISHED COPPER|14|4 +Brand#32|MEDIUM BURNISHED COPPER|45|4 +Brand#32|MEDIUM BURNISHED NICKEL|3|4 +Brand#32|MEDIUM BURNISHED NICKEL|9|4 +Brand#32|MEDIUM BURNISHED NICKEL|36|4 +Brand#32|MEDIUM BURNISHED STEEL|19|4 +Brand#32|MEDIUM BURNISHED STEEL|36|4 +Brand#32|MEDIUM BURNISHED TIN|19|4 +Brand#32|MEDIUM BURNISHED TIN|36|4 +Brand#32|MEDIUM BURNISHED TIN|45|4 +Brand#32|MEDIUM BURNISHED TIN|49|4 +Brand#32|MEDIUM PLATED BRASS|19|4 +Brand#32|MEDIUM PLATED BRASS|36|4 +Brand#32|MEDIUM PLATED COPPER|14|4 +Brand#32|MEDIUM PLATED COPPER|45|4 +Brand#32|MEDIUM PLATED COPPER|49|4 +Brand#32|MEDIUM PLATED NICKEL|3|4 +Brand#32|MEDIUM PLATED NICKEL|14|4 +Brand#32|MEDIUM PLATED NICKEL|19|4 +Brand#32|MEDIUM PLATED NICKEL|36|4 +Brand#32|MEDIUM PLATED NICKEL|45|4 +Brand#32|MEDIUM PLATED NICKEL|49|4 +Brand#32|MEDIUM PLATED STEEL|19|4 +Brand#32|MEDIUM PLATED STEEL|36|4 +Brand#32|MEDIUM PLATED TIN|9|4 +Brand#32|MEDIUM PLATED TIN|45|4 +Brand#32|MEDIUM PLATED TIN|49|4 +Brand#32|PROMO ANODIZED BRASS|19|4 +Brand#32|PROMO ANODIZED BRASS|23|4 +Brand#32|PROMO ANODIZED BRASS|49|4 +Brand#32|PROMO ANODIZED COPPER|14|4 +Brand#32|PROMO ANODIZED COPPER|36|4 +Brand#32|PROMO ANODIZED NICKEL|23|4 +Brand#32|PROMO ANODIZED NICKEL|45|4 +Brand#32|PROMO ANODIZED STEEL|14|4 +Brand#32|PROMO ANODIZED STEEL|45|4 +Brand#32|PROMO ANODIZED TIN|9|4 +Brand#32|PROMO ANODIZED TIN|19|4 +Brand#32|PROMO ANODIZED TIN|23|4 +Brand#32|PROMO BRUSHED BRASS|23|4 +Brand#32|PROMO BRUSHED BRASS|45|4 +Brand#32|PROMO BRUSHED COPPER|9|4 +Brand#32|PROMO BRUSHED COPPER|19|4 +Brand#32|PROMO BRUSHED COPPER|36|4 +Brand#32|PROMO BRUSHED NICKEL|14|4 +Brand#32|PROMO BRUSHED NICKEL|19|4 +Brand#32|PROMO BRUSHED NICKEL|49|4 +Brand#32|PROMO BRUSHED STEEL|14|4 +Brand#32|PROMO BRUSHED STEEL|19|4 +Brand#32|PROMO BRUSHED STEEL|36|4 +Brand#32|PROMO BRUSHED TIN|3|4 +Brand#32|PROMO BRUSHED TIN|19|4 +Brand#32|PROMO BURNISHED BRASS|9|4 +Brand#32|PROMO BURNISHED BRASS|23|4 +Brand#32|PROMO BURNISHED BRASS|36|4 +Brand#32|PROMO BURNISHED BRASS|49|4 +Brand#32|PROMO BURNISHED COPPER|14|4 +Brand#32|PROMO BURNISHED COPPER|23|4 +Brand#32|PROMO BURNISHED COPPER|45|4 +Brand#32|PROMO BURNISHED STEEL|3|4 +Brand#32|PROMO BURNISHED STEEL|19|4 +Brand#32|PROMO BURNISHED STEEL|49|4 +Brand#32|PROMO BURNISHED TIN|19|4 +Brand#32|PROMO PLATED BRASS|14|4 +Brand#32|PROMO PLATED BRASS|19|4 +Brand#32|PROMO PLATED BRASS|45|4 +Brand#32|PROMO PLATED BRASS|49|4 +Brand#32|PROMO PLATED COPPER|9|4 +Brand#32|PROMO PLATED COPPER|14|4 +Brand#32|PROMO PLATED COPPER|36|4 +Brand#32|PROMO PLATED NICKEL|3|4 +Brand#32|PROMO PLATED NICKEL|14|4 +Brand#32|PROMO PLATED NICKEL|19|4 +Brand#32|PROMO PLATED NICKEL|23|4 +Brand#32|PROMO PLATED NICKEL|45|4 +Brand#32|PROMO PLATED STEEL|9|4 +Brand#32|PROMO PLATED STEEL|19|4 +Brand#32|PROMO PLATED TIN|14|4 +Brand#32|PROMO PLATED TIN|23|4 +Brand#32|PROMO POLISHED BRASS|9|4 +Brand#32|PROMO POLISHED BRASS|19|4 +Brand#32|PROMO POLISHED BRASS|36|4 +Brand#32|PROMO POLISHED COPPER|3|4 +Brand#32|PROMO POLISHED COPPER|9|4 +Brand#32|PROMO POLISHED COPPER|14|4 +Brand#32|PROMO POLISHED COPPER|19|4 +Brand#32|PROMO POLISHED COPPER|23|4 +Brand#32|PROMO POLISHED COPPER|36|4 +Brand#32|PROMO POLISHED NICKEL|14|4 +Brand#32|PROMO POLISHED NICKEL|19|4 +Brand#32|PROMO POLISHED NICKEL|45|4 +Brand#32|PROMO POLISHED STEEL|9|4 +Brand#32|PROMO POLISHED STEEL|23|4 +Brand#32|PROMO POLISHED STEEL|45|4 +Brand#32|SMALL ANODIZED BRASS|9|4 +Brand#32|SMALL ANODIZED COPPER|3|4 +Brand#32|SMALL ANODIZED COPPER|19|4 +Brand#32|SMALL ANODIZED COPPER|45|4 +Brand#32|SMALL ANODIZED NICKEL|3|4 +Brand#32|SMALL ANODIZED NICKEL|14|4 +Brand#32|SMALL ANODIZED NICKEL|19|4 +Brand#32|SMALL ANODIZED NICKEL|36|4 +Brand#32|SMALL ANODIZED NICKEL|45|4 +Brand#32|SMALL ANODIZED STEEL|9|4 +Brand#32|SMALL ANODIZED STEEL|14|4 +Brand#32|SMALL ANODIZED STEEL|19|4 +Brand#32|SMALL ANODIZED TIN|9|4 +Brand#32|SMALL ANODIZED TIN|19|4 +Brand#32|SMALL ANODIZED TIN|23|4 +Brand#32|SMALL ANODIZED TIN|45|4 +Brand#32|SMALL BRUSHED BRASS|3|4 +Brand#32|SMALL BRUSHED BRASS|9|4 +Brand#32|SMALL BRUSHED BRASS|19|4 +Brand#32|SMALL BRUSHED BRASS|23|4 +Brand#32|SMALL BRUSHED BRASS|45|4 +Brand#32|SMALL BRUSHED COPPER|3|4 +Brand#32|SMALL BRUSHED COPPER|9|4 +Brand#32|SMALL BRUSHED COPPER|45|4 +Brand#32|SMALL BRUSHED NICKEL|9|4 +Brand#32|SMALL BRUSHED NICKEL|14|4 +Brand#32|SMALL BRUSHED NICKEL|23|4 +Brand#32|SMALL BRUSHED NICKEL|45|4 +Brand#32|SMALL BRUSHED STEEL|3|4 +Brand#32|SMALL BRUSHED STEEL|19|4 +Brand#32|SMALL BRUSHED STEEL|23|4 +Brand#32|SMALL BRUSHED STEEL|45|4 +Brand#32|SMALL BRUSHED STEEL|49|4 +Brand#32|SMALL BRUSHED TIN|19|4 +Brand#32|SMALL BRUSHED TIN|23|4 +Brand#32|SMALL BRUSHED TIN|36|4 +Brand#32|SMALL BRUSHED TIN|45|4 +Brand#32|SMALL BRUSHED TIN|49|4 +Brand#32|SMALL BURNISHED BRASS|3|4 +Brand#32|SMALL BURNISHED BRASS|14|4 +Brand#32|SMALL BURNISHED BRASS|19|4 +Brand#32|SMALL BURNISHED BRASS|23|4 +Brand#32|SMALL BURNISHED COPPER|9|4 +Brand#32|SMALL BURNISHED COPPER|14|4 +Brand#32|SMALL BURNISHED COPPER|23|4 +Brand#32|SMALL BURNISHED COPPER|36|4 +Brand#32|SMALL BURNISHED COPPER|49|4 +Brand#32|SMALL BURNISHED NICKEL|14|4 +Brand#32|SMALL BURNISHED NICKEL|19|4 +Brand#32|SMALL BURNISHED NICKEL|36|4 +Brand#32|SMALL BURNISHED NICKEL|45|4 +Brand#32|SMALL BURNISHED NICKEL|49|4 +Brand#32|SMALL BURNISHED STEEL|36|4 +Brand#32|SMALL BURNISHED STEEL|45|4 +Brand#32|SMALL BURNISHED STEEL|49|4 +Brand#32|SMALL BURNISHED TIN|9|4 +Brand#32|SMALL PLATED BRASS|14|4 +Brand#32|SMALL PLATED BRASS|23|4 +Brand#32|SMALL PLATED BRASS|49|4 +Brand#32|SMALL PLATED NICKEL|19|4 +Brand#32|SMALL PLATED NICKEL|23|4 +Brand#32|SMALL PLATED STEEL|45|4 +Brand#32|SMALL PLATED STEEL|49|4 +Brand#32|SMALL PLATED TIN|9|4 +Brand#32|SMALL PLATED TIN|19|4 +Brand#32|SMALL POLISHED BRASS|9|4 +Brand#32|SMALL POLISHED BRASS|19|4 +Brand#32|SMALL POLISHED BRASS|36|4 +Brand#32|SMALL POLISHED BRASS|49|4 +Brand#32|SMALL POLISHED NICKEL|3|4 +Brand#32|SMALL POLISHED NICKEL|14|4 +Brand#32|SMALL POLISHED NICKEL|19|4 +Brand#32|SMALL POLISHED STEEL|49|4 +Brand#32|SMALL POLISHED TIN|3|4 +Brand#32|SMALL POLISHED TIN|14|4 +Brand#32|SMALL POLISHED TIN|23|4 +Brand#32|SMALL POLISHED TIN|49|4 +Brand#32|STANDARD ANODIZED BRASS|9|4 +Brand#32|STANDARD ANODIZED BRASS|23|4 +Brand#32|STANDARD ANODIZED BRASS|36|4 +Brand#32|STANDARD ANODIZED BRASS|45|4 +Brand#32|STANDARD ANODIZED COPPER|3|4 +Brand#32|STANDARD ANODIZED COPPER|9|4 +Brand#32|STANDARD ANODIZED NICKEL|3|4 +Brand#32|STANDARD ANODIZED NICKEL|14|4 +Brand#32|STANDARD ANODIZED NICKEL|23|4 +Brand#32|STANDARD ANODIZED STEEL|23|4 +Brand#32|STANDARD ANODIZED TIN|3|4 +Brand#32|STANDARD ANODIZED TIN|23|4 +Brand#32|STANDARD ANODIZED TIN|49|4 +Brand#32|STANDARD BRUSHED BRASS|3|4 +Brand#32|STANDARD BRUSHED BRASS|9|4 +Brand#32|STANDARD BRUSHED BRASS|19|4 +Brand#32|STANDARD BRUSHED BRASS|23|4 +Brand#32|STANDARD BRUSHED COPPER|14|4 +Brand#32|STANDARD BRUSHED COPPER|19|4 +Brand#32|STANDARD BRUSHED COPPER|23|4 +Brand#32|STANDARD BRUSHED COPPER|49|4 +Brand#32|STANDARD BRUSHED NICKEL|9|4 +Brand#32|STANDARD BRUSHED NICKEL|23|4 +Brand#32|STANDARD BRUSHED NICKEL|45|4 +Brand#32|STANDARD BRUSHED NICKEL|49|4 +Brand#32|STANDARD BRUSHED TIN|14|4 +Brand#32|STANDARD BRUSHED TIN|19|4 +Brand#32|STANDARD BRUSHED TIN|23|4 +Brand#32|STANDARD BURNISHED BRASS|3|4 +Brand#32|STANDARD BURNISHED BRASS|19|4 +Brand#32|STANDARD BURNISHED BRASS|23|4 +Brand#32|STANDARD BURNISHED BRASS|36|4 +Brand#32|STANDARD BURNISHED COPPER|14|4 +Brand#32|STANDARD BURNISHED COPPER|23|4 +Brand#32|STANDARD BURNISHED COPPER|36|4 +Brand#32|STANDARD BURNISHED NICKEL|36|4 +Brand#32|STANDARD BURNISHED NICKEL|45|4 +Brand#32|STANDARD BURNISHED STEEL|9|4 +Brand#32|STANDARD BURNISHED STEEL|23|4 +Brand#32|STANDARD BURNISHED TIN|3|4 +Brand#32|STANDARD BURNISHED TIN|9|4 +Brand#32|STANDARD BURNISHED TIN|19|4 +Brand#32|STANDARD BURNISHED TIN|36|4 +Brand#32|STANDARD PLATED BRASS|23|4 +Brand#32|STANDARD PLATED BRASS|45|4 +Brand#32|STANDARD PLATED COPPER|3|4 +Brand#32|STANDARD PLATED COPPER|36|4 +Brand#32|STANDARD PLATED NICKEL|49|4 +Brand#32|STANDARD PLATED STEEL|3|4 +Brand#32|STANDARD PLATED STEEL|19|4 +Brand#32|STANDARD PLATED STEEL|36|4 +Brand#32|STANDARD PLATED TIN|14|4 +Brand#32|STANDARD PLATED TIN|23|4 +Brand#32|STANDARD PLATED TIN|36|4 +Brand#32|STANDARD PLATED TIN|49|4 +Brand#32|STANDARD POLISHED BRASS|19|4 +Brand#32|STANDARD POLISHED BRASS|23|4 +Brand#32|STANDARD POLISHED BRASS|45|4 +Brand#32|STANDARD POLISHED BRASS|49|4 +Brand#32|STANDARD POLISHED COPPER|19|4 +Brand#32|STANDARD POLISHED COPPER|45|4 +Brand#32|STANDARD POLISHED COPPER|49|4 +Brand#32|STANDARD POLISHED NICKEL|19|4 +Brand#32|STANDARD POLISHED NICKEL|36|4 +Brand#32|STANDARD POLISHED NICKEL|45|4 +Brand#32|STANDARD POLISHED STEEL|3|4 +Brand#32|STANDARD POLISHED STEEL|19|4 +Brand#32|STANDARD POLISHED STEEL|45|4 +Brand#32|STANDARD POLISHED STEEL|49|4 +Brand#32|STANDARD POLISHED TIN|9|4 +Brand#32|STANDARD POLISHED TIN|36|4 +Brand#33|ECONOMY ANODIZED BRASS|9|4 +Brand#33|ECONOMY ANODIZED BRASS|14|4 +Brand#33|ECONOMY ANODIZED BRASS|23|4 +Brand#33|ECONOMY ANODIZED BRASS|36|4 +Brand#33|ECONOMY ANODIZED COPPER|14|4 +Brand#33|ECONOMY ANODIZED NICKEL|9|4 +Brand#33|ECONOMY ANODIZED NICKEL|49|4 +Brand#33|ECONOMY ANODIZED STEEL|3|4 +Brand#33|ECONOMY ANODIZED STEEL|14|4 +Brand#33|ECONOMY ANODIZED TIN|9|4 +Brand#33|ECONOMY ANODIZED TIN|14|4 +Brand#33|ECONOMY ANODIZED TIN|36|4 +Brand#33|ECONOMY BRUSHED BRASS|23|4 +Brand#33|ECONOMY BRUSHED COPPER|49|4 +Brand#33|ECONOMY BRUSHED NICKEL|3|4 +Brand#33|ECONOMY BRUSHED NICKEL|36|4 +Brand#33|ECONOMY BRUSHED STEEL|3|4 +Brand#33|ECONOMY BRUSHED STEEL|9|4 +Brand#33|ECONOMY BRUSHED STEEL|45|4 +Brand#33|ECONOMY BRUSHED TIN|9|4 +Brand#33|ECONOMY BRUSHED TIN|14|4 +Brand#33|ECONOMY BRUSHED TIN|19|4 +Brand#33|ECONOMY BRUSHED TIN|23|4 +Brand#33|ECONOMY BRUSHED TIN|49|4 +Brand#33|ECONOMY BURNISHED BRASS|23|4 +Brand#33|ECONOMY BURNISHED BRASS|36|4 +Brand#33|ECONOMY BURNISHED COPPER|9|4 +Brand#33|ECONOMY BURNISHED COPPER|23|4 +Brand#33|ECONOMY BURNISHED COPPER|36|4 +Brand#33|ECONOMY BURNISHED COPPER|45|4 +Brand#33|ECONOMY BURNISHED COPPER|49|4 +Brand#33|ECONOMY BURNISHED NICKEL|3|4 +Brand#33|ECONOMY BURNISHED NICKEL|23|4 +Brand#33|ECONOMY BURNISHED NICKEL|36|4 +Brand#33|ECONOMY BURNISHED NICKEL|49|4 +Brand#33|ECONOMY BURNISHED STEEL|3|4 +Brand#33|ECONOMY BURNISHED STEEL|14|4 +Brand#33|ECONOMY BURNISHED STEEL|23|4 +Brand#33|ECONOMY BURNISHED STEEL|45|4 +Brand#33|ECONOMY BURNISHED TIN|14|4 +Brand#33|ECONOMY BURNISHED TIN|19|4 +Brand#33|ECONOMY BURNISHED TIN|36|4 +Brand#33|ECONOMY PLATED BRASS|23|4 +Brand#33|ECONOMY PLATED COPPER|3|4 +Brand#33|ECONOMY PLATED COPPER|36|4 +Brand#33|ECONOMY PLATED COPPER|45|4 +Brand#33|ECONOMY PLATED NICKEL|9|4 +Brand#33|ECONOMY PLATED NICKEL|14|4 +Brand#33|ECONOMY PLATED NICKEL|23|4 +Brand#33|ECONOMY PLATED NICKEL|45|4 +Brand#33|ECONOMY PLATED NICKEL|49|4 +Brand#33|ECONOMY PLATED STEEL|36|4 +Brand#33|ECONOMY PLATED STEEL|49|4 +Brand#33|ECONOMY PLATED TIN|14|4 +Brand#33|ECONOMY PLATED TIN|19|4 +Brand#33|ECONOMY PLATED TIN|23|4 +Brand#33|ECONOMY PLATED TIN|36|4 +Brand#33|ECONOMY POLISHED BRASS|9|4 +Brand#33|ECONOMY POLISHED COPPER|9|4 +Brand#33|ECONOMY POLISHED COPPER|23|4 +Brand#33|ECONOMY POLISHED NICKEL|3|4 +Brand#33|ECONOMY POLISHED NICKEL|23|4 +Brand#33|ECONOMY POLISHED NICKEL|36|4 +Brand#33|ECONOMY POLISHED NICKEL|49|4 +Brand#33|ECONOMY POLISHED STEEL|14|4 +Brand#33|ECONOMY POLISHED STEEL|19|4 +Brand#33|ECONOMY POLISHED STEEL|23|4 +Brand#33|ECONOMY POLISHED STEEL|45|4 +Brand#33|ECONOMY POLISHED TIN|3|4 +Brand#33|ECONOMY POLISHED TIN|9|4 +Brand#33|ECONOMY POLISHED TIN|14|4 +Brand#33|ECONOMY POLISHED TIN|23|4 +Brand#33|ECONOMY POLISHED TIN|45|4 +Brand#33|LARGE ANODIZED BRASS|14|4 +Brand#33|LARGE ANODIZED COPPER|14|4 +Brand#33|LARGE ANODIZED COPPER|45|4 +Brand#33|LARGE ANODIZED COPPER|49|4 +Brand#33|LARGE ANODIZED NICKEL|3|4 +Brand#33|LARGE ANODIZED NICKEL|9|4 +Brand#33|LARGE ANODIZED NICKEL|14|4 +Brand#33|LARGE ANODIZED NICKEL|36|4 +Brand#33|LARGE ANODIZED STEEL|14|4 +Brand#33|LARGE ANODIZED STEEL|19|4 +Brand#33|LARGE ANODIZED STEEL|36|4 +Brand#33|LARGE ANODIZED TIN|14|4 +Brand#33|LARGE ANODIZED TIN|19|4 +Brand#33|LARGE BRUSHED BRASS|45|4 +Brand#33|LARGE BRUSHED COPPER|3|4 +Brand#33|LARGE BRUSHED COPPER|14|4 +Brand#33|LARGE BRUSHED COPPER|23|4 +Brand#33|LARGE BRUSHED COPPER|36|4 +Brand#33|LARGE BRUSHED COPPER|45|4 +Brand#33|LARGE BRUSHED NICKEL|3|4 +Brand#33|LARGE BRUSHED STEEL|9|4 +Brand#33|LARGE BRUSHED STEEL|14|4 +Brand#33|LARGE BRUSHED STEEL|19|4 +Brand#33|LARGE BRUSHED TIN|9|4 +Brand#33|LARGE BURNISHED COPPER|3|4 +Brand#33|LARGE BURNISHED COPPER|23|4 +Brand#33|LARGE BURNISHED COPPER|36|4 +Brand#33|LARGE BURNISHED COPPER|49|4 +Brand#33|LARGE BURNISHED NICKEL|23|4 +Brand#33|LARGE BURNISHED NICKEL|45|4 +Brand#33|LARGE BURNISHED STEEL|19|4 +Brand#33|LARGE BURNISHED STEEL|36|4 +Brand#33|LARGE BURNISHED TIN|19|4 +Brand#33|LARGE BURNISHED TIN|36|4 +Brand#33|LARGE PLATED BRASS|9|4 +Brand#33|LARGE PLATED BRASS|23|4 +Brand#33|LARGE PLATED BRASS|36|4 +Brand#33|LARGE PLATED BRASS|49|4 +Brand#33|LARGE PLATED COPPER|9|4 +Brand#33|LARGE PLATED COPPER|14|4 +Brand#33|LARGE PLATED COPPER|19|4 +Brand#33|LARGE PLATED COPPER|23|4 +Brand#33|LARGE PLATED COPPER|45|4 +Brand#33|LARGE PLATED COPPER|49|4 +Brand#33|LARGE PLATED NICKEL|14|4 +Brand#33|LARGE PLATED NICKEL|45|4 +Brand#33|LARGE PLATED STEEL|9|4 +Brand#33|LARGE PLATED STEEL|19|4 +Brand#33|LARGE PLATED STEEL|23|4 +Brand#33|LARGE PLATED STEEL|36|4 +Brand#33|LARGE PLATED STEEL|45|4 +Brand#33|LARGE PLATED STEEL|49|4 +Brand#33|LARGE PLATED TIN|9|4 +Brand#33|LARGE PLATED TIN|14|4 +Brand#33|LARGE PLATED TIN|23|4 +Brand#33|LARGE PLATED TIN|45|4 +Brand#33|LARGE PLATED TIN|49|4 +Brand#33|LARGE POLISHED BRASS|19|4 +Brand#33|LARGE POLISHED BRASS|45|4 +Brand#33|LARGE POLISHED BRASS|49|4 +Brand#33|LARGE POLISHED COPPER|14|4 +Brand#33|LARGE POLISHED COPPER|19|4 +Brand#33|LARGE POLISHED COPPER|23|4 +Brand#33|LARGE POLISHED COPPER|49|4 +Brand#33|LARGE POLISHED NICKEL|23|4 +Brand#33|LARGE POLISHED NICKEL|36|4 +Brand#33|LARGE POLISHED STEEL|9|4 +Brand#33|LARGE POLISHED STEEL|14|4 +Brand#33|LARGE POLISHED STEEL|19|4 +Brand#33|LARGE POLISHED STEEL|23|4 +Brand#33|LARGE POLISHED STEEL|49|4 +Brand#33|LARGE POLISHED TIN|45|4 +Brand#33|MEDIUM ANODIZED BRASS|9|4 +Brand#33|MEDIUM ANODIZED BRASS|36|4 +Brand#33|MEDIUM ANODIZED BRASS|45|4 +Brand#33|MEDIUM ANODIZED BRASS|49|4 +Brand#33|MEDIUM ANODIZED COPPER|14|4 +Brand#33|MEDIUM ANODIZED COPPER|36|4 +Brand#33|MEDIUM ANODIZED COPPER|49|4 +Brand#33|MEDIUM ANODIZED NICKEL|9|4 +Brand#33|MEDIUM ANODIZED NICKEL|14|4 +Brand#33|MEDIUM ANODIZED NICKEL|23|4 +Brand#33|MEDIUM ANODIZED STEEL|19|4 +Brand#33|MEDIUM ANODIZED STEEL|23|4 +Brand#33|MEDIUM ANODIZED STEEL|36|4 +Brand#33|MEDIUM ANODIZED STEEL|49|4 +Brand#33|MEDIUM ANODIZED TIN|3|4 +Brand#33|MEDIUM ANODIZED TIN|19|4 +Brand#33|MEDIUM BRUSHED BRASS|3|4 +Brand#33|MEDIUM BRUSHED BRASS|14|4 +Brand#33|MEDIUM BRUSHED BRASS|36|4 +Brand#33|MEDIUM BRUSHED COPPER|23|4 +Brand#33|MEDIUM BRUSHED NICKEL|19|4 +Brand#33|MEDIUM BRUSHED NICKEL|36|4 +Brand#33|MEDIUM BRUSHED NICKEL|49|4 +Brand#33|MEDIUM BRUSHED STEEL|3|4 +Brand#33|MEDIUM BRUSHED STEEL|9|4 +Brand#33|MEDIUM BRUSHED STEEL|19|4 +Brand#33|MEDIUM BRUSHED STEEL|49|4 +Brand#33|MEDIUM BRUSHED TIN|3|4 +Brand#33|MEDIUM BRUSHED TIN|14|4 +Brand#33|MEDIUM BURNISHED BRASS|14|4 +Brand#33|MEDIUM BURNISHED BRASS|19|4 +Brand#33|MEDIUM BURNISHED BRASS|45|4 +Brand#33|MEDIUM BURNISHED COPPER|19|4 +Brand#33|MEDIUM BURNISHED COPPER|36|4 +Brand#33|MEDIUM BURNISHED NICKEL|14|4 +Brand#33|MEDIUM BURNISHED NICKEL|23|4 +Brand#33|MEDIUM BURNISHED NICKEL|36|4 +Brand#33|MEDIUM BURNISHED STEEL|23|4 +Brand#33|MEDIUM BURNISHED STEEL|36|4 +Brand#33|MEDIUM BURNISHED STEEL|45|4 +Brand#33|MEDIUM BURNISHED TIN|19|4 +Brand#33|MEDIUM PLATED BRASS|9|4 +Brand#33|MEDIUM PLATED BRASS|36|4 +Brand#33|MEDIUM PLATED BRASS|45|4 +Brand#33|MEDIUM PLATED BRASS|49|4 +Brand#33|MEDIUM PLATED COPPER|45|4 +Brand#33|MEDIUM PLATED COPPER|49|4 +Brand#33|MEDIUM PLATED NICKEL|14|4 +Brand#33|MEDIUM PLATED STEEL|9|4 +Brand#33|MEDIUM PLATED STEEL|23|4 +Brand#33|MEDIUM PLATED STEEL|36|4 +Brand#33|MEDIUM PLATED STEEL|49|4 +Brand#33|MEDIUM PLATED TIN|3|4 +Brand#33|PROMO ANODIZED BRASS|36|4 +Brand#33|PROMO ANODIZED COPPER|3|4 +Brand#33|PROMO ANODIZED COPPER|9|4 +Brand#33|PROMO ANODIZED NICKEL|3|4 +Brand#33|PROMO ANODIZED NICKEL|19|4 +Brand#33|PROMO ANODIZED NICKEL|49|4 +Brand#33|PROMO ANODIZED STEEL|3|4 +Brand#33|PROMO ANODIZED STEEL|49|4 +Brand#33|PROMO BRUSHED BRASS|9|4 +Brand#33|PROMO BRUSHED BRASS|14|4 +Brand#33|PROMO BRUSHED BRASS|36|4 +Brand#33|PROMO BRUSHED BRASS|45|4 +Brand#33|PROMO BRUSHED BRASS|49|4 +Brand#33|PROMO BRUSHED COPPER|19|4 +Brand#33|PROMO BRUSHED COPPER|23|4 +Brand#33|PROMO BRUSHED COPPER|36|4 +Brand#33|PROMO BRUSHED NICKEL|3|4 +Brand#33|PROMO BRUSHED NICKEL|23|4 +Brand#33|PROMO BRUSHED STEEL|19|4 +Brand#33|PROMO BRUSHED STEEL|23|4 +Brand#33|PROMO BRUSHED TIN|14|4 +Brand#33|PROMO BRUSHED TIN|19|4 +Brand#33|PROMO BRUSHED TIN|36|4 +Brand#33|PROMO BRUSHED TIN|45|4 +Brand#33|PROMO BURNISHED BRASS|14|4 +Brand#33|PROMO BURNISHED BRASS|23|4 +Brand#33|PROMO BURNISHED COPPER|9|4 +Brand#33|PROMO BURNISHED COPPER|14|4 +Brand#33|PROMO BURNISHED COPPER|36|4 +Brand#33|PROMO BURNISHED NICKEL|9|4 +Brand#33|PROMO BURNISHED NICKEL|36|4 +Brand#33|PROMO BURNISHED NICKEL|45|4 +Brand#33|PROMO BURNISHED STEEL|23|4 +Brand#33|PROMO BURNISHED STEEL|49|4 +Brand#33|PROMO BURNISHED TIN|14|4 +Brand#33|PROMO PLATED BRASS|19|4 +Brand#33|PROMO PLATED COPPER|9|4 +Brand#33|PROMO PLATED COPPER|23|4 +Brand#33|PROMO PLATED COPPER|36|4 +Brand#33|PROMO PLATED COPPER|49|4 +Brand#33|PROMO PLATED NICKEL|3|4 +Brand#33|PROMO PLATED NICKEL|36|4 +Brand#33|PROMO PLATED STEEL|3|4 +Brand#33|PROMO PLATED STEEL|14|4 +Brand#33|PROMO PLATED STEEL|45|4 +Brand#33|PROMO PLATED TIN|3|4 +Brand#33|PROMO PLATED TIN|9|4 +Brand#33|PROMO PLATED TIN|19|4 +Brand#33|PROMO PLATED TIN|49|4 +Brand#33|PROMO POLISHED COPPER|9|4 +Brand#33|PROMO POLISHED COPPER|19|4 +Brand#33|PROMO POLISHED COPPER|23|4 +Brand#33|PROMO POLISHED NICKEL|19|4 +Brand#33|PROMO POLISHED NICKEL|36|4 +Brand#33|PROMO POLISHED STEEL|36|4 +Brand#33|PROMO POLISHED TIN|9|4 +Brand#33|PROMO POLISHED TIN|23|4 +Brand#33|SMALL ANODIZED BRASS|14|4 +Brand#33|SMALL ANODIZED BRASS|19|4 +Brand#33|SMALL ANODIZED BRASS|45|4 +Brand#33|SMALL ANODIZED BRASS|49|4 +Brand#33|SMALL ANODIZED COPPER|36|4 +Brand#33|SMALL ANODIZED NICKEL|19|4 +Brand#33|SMALL ANODIZED STEEL|23|4 +Brand#33|SMALL ANODIZED STEEL|45|4 +Brand#33|SMALL ANODIZED TIN|3|4 +Brand#33|SMALL ANODIZED TIN|14|4 +Brand#33|SMALL ANODIZED TIN|36|4 +Brand#33|SMALL BRUSHED BRASS|19|4 +Brand#33|SMALL BRUSHED BRASS|45|4 +Brand#33|SMALL BRUSHED COPPER|3|4 +Brand#33|SMALL BRUSHED COPPER|19|4 +Brand#33|SMALL BRUSHED NICKEL|14|4 +Brand#33|SMALL BRUSHED NICKEL|23|4 +Brand#33|SMALL BRUSHED NICKEL|36|4 +Brand#33|SMALL BRUSHED NICKEL|49|4 +Brand#33|SMALL BRUSHED STEEL|14|4 +Brand#33|SMALL BRUSHED STEEL|19|4 +Brand#33|SMALL BRUSHED STEEL|36|4 +Brand#33|SMALL BRUSHED STEEL|45|4 +Brand#33|SMALL BRUSHED STEEL|49|4 +Brand#33|SMALL BRUSHED TIN|3|4 +Brand#33|SMALL BRUSHED TIN|14|4 +Brand#33|SMALL BRUSHED TIN|23|4 +Brand#33|SMALL BRUSHED TIN|49|4 +Brand#33|SMALL BURNISHED BRASS|3|4 +Brand#33|SMALL BURNISHED BRASS|19|4 +Brand#33|SMALL BURNISHED BRASS|49|4 +Brand#33|SMALL BURNISHED COPPER|3|4 +Brand#33|SMALL BURNISHED COPPER|9|4 +Brand#33|SMALL BURNISHED COPPER|14|4 +Brand#33|SMALL BURNISHED COPPER|23|4 +Brand#33|SMALL BURNISHED NICKEL|3|4 +Brand#33|SMALL BURNISHED NICKEL|9|4 +Brand#33|SMALL BURNISHED NICKEL|14|4 +Brand#33|SMALL BURNISHED NICKEL|23|4 +Brand#33|SMALL BURNISHED NICKEL|45|4 +Brand#33|SMALL BURNISHED NICKEL|49|4 +Brand#33|SMALL BURNISHED STEEL|3|4 +Brand#33|SMALL BURNISHED STEEL|49|4 +Brand#33|SMALL BURNISHED TIN|3|4 +Brand#33|SMALL BURNISHED TIN|14|4 +Brand#33|SMALL PLATED BRASS|3|4 +Brand#33|SMALL PLATED BRASS|36|4 +Brand#33|SMALL PLATED BRASS|45|4 +Brand#33|SMALL PLATED COPPER|14|4 +Brand#33|SMALL PLATED COPPER|19|4 +Brand#33|SMALL PLATED COPPER|23|4 +Brand#33|SMALL PLATED COPPER|36|4 +Brand#33|SMALL PLATED COPPER|49|4 +Brand#33|SMALL PLATED NICKEL|3|4 +Brand#33|SMALL PLATED NICKEL|9|4 +Brand#33|SMALL PLATED STEEL|9|4 +Brand#33|SMALL PLATED STEEL|36|4 +Brand#33|SMALL PLATED STEEL|45|4 +Brand#33|SMALL PLATED TIN|3|4 +Brand#33|SMALL POLISHED BRASS|49|4 +Brand#33|SMALL POLISHED COPPER|14|4 +Brand#33|SMALL POLISHED COPPER|23|4 +Brand#33|SMALL POLISHED COPPER|45|4 +Brand#33|SMALL POLISHED NICKEL|14|4 +Brand#33|SMALL POLISHED NICKEL|23|4 +Brand#33|SMALL POLISHED NICKEL|36|4 +Brand#33|SMALL POLISHED NICKEL|45|4 +Brand#33|SMALL POLISHED STEEL|19|4 +Brand#33|SMALL POLISHED STEEL|36|4 +Brand#33|SMALL POLISHED STEEL|45|4 +Brand#33|SMALL POLISHED TIN|36|4 +Brand#33|STANDARD ANODIZED BRASS|3|4 +Brand#33|STANDARD ANODIZED BRASS|14|4 +Brand#33|STANDARD ANODIZED BRASS|19|4 +Brand#33|STANDARD ANODIZED BRASS|45|4 +Brand#33|STANDARD ANODIZED COPPER|9|4 +Brand#33|STANDARD ANODIZED COPPER|45|4 +Brand#33|STANDARD ANODIZED NICKEL|3|4 +Brand#33|STANDARD ANODIZED NICKEL|14|4 +Brand#33|STANDARD ANODIZED NICKEL|19|4 +Brand#33|STANDARD ANODIZED NICKEL|23|4 +Brand#33|STANDARD ANODIZED STEEL|23|4 +Brand#33|STANDARD ANODIZED TIN|14|4 +Brand#33|STANDARD ANODIZED TIN|45|4 +Brand#33|STANDARD BRUSHED BRASS|3|4 +Brand#33|STANDARD BRUSHED BRASS|14|4 +Brand#33|STANDARD BRUSHED COPPER|14|4 +Brand#33|STANDARD BRUSHED COPPER|23|4 +Brand#33|STANDARD BRUSHED COPPER|49|4 +Brand#33|STANDARD BRUSHED NICKEL|3|4 +Brand#33|STANDARD BRUSHED NICKEL|9|4 +Brand#33|STANDARD BRUSHED NICKEL|19|4 +Brand#33|STANDARD BRUSHED NICKEL|45|4 +Brand#33|STANDARD BRUSHED STEEL|23|4 +Brand#33|STANDARD BRUSHED STEEL|36|4 +Brand#33|STANDARD BRUSHED STEEL|49|4 +Brand#33|STANDARD BRUSHED TIN|3|4 +Brand#33|STANDARD BRUSHED TIN|9|4 +Brand#33|STANDARD BRUSHED TIN|36|4 +Brand#33|STANDARD BRUSHED TIN|45|4 +Brand#33|STANDARD BRUSHED TIN|49|4 +Brand#33|STANDARD BURNISHED BRASS|3|4 +Brand#33|STANDARD BURNISHED BRASS|36|4 +Brand#33|STANDARD BURNISHED COPPER|3|4 +Brand#33|STANDARD BURNISHED COPPER|9|4 +Brand#33|STANDARD BURNISHED COPPER|36|4 +Brand#33|STANDARD BURNISHED COPPER|45|4 +Brand#33|STANDARD BURNISHED COPPER|49|4 +Brand#33|STANDARD BURNISHED NICKEL|3|4 +Brand#33|STANDARD BURNISHED NICKEL|9|4 +Brand#33|STANDARD BURNISHED NICKEL|14|4 +Brand#33|STANDARD BURNISHED NICKEL|45|4 +Brand#33|STANDARD BURNISHED STEEL|9|4 +Brand#33|STANDARD BURNISHED STEEL|19|4 +Brand#33|STANDARD BURNISHED STEEL|23|4 +Brand#33|STANDARD BURNISHED TIN|9|4 +Brand#33|STANDARD BURNISHED TIN|45|4 +Brand#33|STANDARD BURNISHED TIN|49|4 +Brand#33|STANDARD PLATED BRASS|9|4 +Brand#33|STANDARD PLATED COPPER|3|4 +Brand#33|STANDARD PLATED COPPER|9|4 +Brand#33|STANDARD PLATED COPPER|36|4 +Brand#33|STANDARD PLATED NICKEL|14|4 +Brand#33|STANDARD PLATED NICKEL|19|4 +Brand#33|STANDARD PLATED NICKEL|23|4 +Brand#33|STANDARD PLATED NICKEL|45|4 +Brand#33|STANDARD PLATED STEEL|36|4 +Brand#33|STANDARD PLATED STEEL|45|4 +Brand#33|STANDARD PLATED TIN|19|4 +Brand#33|STANDARD PLATED TIN|23|4 +Brand#33|STANDARD PLATED TIN|36|4 +Brand#33|STANDARD PLATED TIN|45|4 +Brand#33|STANDARD POLISHED BRASS|45|4 +Brand#33|STANDARD POLISHED BRASS|49|4 +Brand#33|STANDARD POLISHED COPPER|19|4 +Brand#33|STANDARD POLISHED COPPER|36|4 +Brand#33|STANDARD POLISHED NICKEL|19|4 +Brand#33|STANDARD POLISHED NICKEL|23|4 +Brand#33|STANDARD POLISHED STEEL|9|4 +Brand#33|STANDARD POLISHED STEEL|36|4 +Brand#33|STANDARD POLISHED TIN|3|4 +Brand#33|STANDARD POLISHED TIN|9|4 +Brand#33|STANDARD POLISHED TIN|19|4 +Brand#33|STANDARD POLISHED TIN|23|4 +Brand#33|STANDARD POLISHED TIN|36|4 +Brand#34|ECONOMY ANODIZED BRASS|9|4 +Brand#34|ECONOMY ANODIZED BRASS|23|4 +Brand#34|ECONOMY ANODIZED BRASS|36|4 +Brand#34|ECONOMY ANODIZED BRASS|45|4 +Brand#34|ECONOMY ANODIZED COPPER|3|4 +Brand#34|ECONOMY ANODIZED COPPER|19|4 +Brand#34|ECONOMY ANODIZED COPPER|36|4 +Brand#34|ECONOMY ANODIZED COPPER|49|4 +Brand#34|ECONOMY ANODIZED NICKEL|19|4 +Brand#34|ECONOMY ANODIZED NICKEL|36|4 +Brand#34|ECONOMY ANODIZED STEEL|9|4 +Brand#34|ECONOMY ANODIZED STEEL|14|4 +Brand#34|ECONOMY ANODIZED STEEL|19|4 +Brand#34|ECONOMY ANODIZED STEEL|36|4 +Brand#34|ECONOMY BRUSHED BRASS|23|4 +Brand#34|ECONOMY BRUSHED BRASS|45|4 +Brand#34|ECONOMY BRUSHED BRASS|49|4 +Brand#34|ECONOMY BRUSHED COPPER|36|4 +Brand#34|ECONOMY BRUSHED NICKEL|3|4 +Brand#34|ECONOMY BRUSHED NICKEL|14|4 +Brand#34|ECONOMY BRUSHED NICKEL|19|4 +Brand#34|ECONOMY BRUSHED NICKEL|45|4 +Brand#34|ECONOMY BRUSHED STEEL|45|4 +Brand#34|ECONOMY BRUSHED STEEL|49|4 +Brand#34|ECONOMY BRUSHED TIN|9|4 +Brand#34|ECONOMY BRUSHED TIN|23|4 +Brand#34|ECONOMY BRUSHED TIN|36|4 +Brand#34|ECONOMY BRUSHED TIN|45|4 +Brand#34|ECONOMY BURNISHED BRASS|3|4 +Brand#34|ECONOMY BURNISHED BRASS|49|4 +Brand#34|ECONOMY BURNISHED COPPER|3|4 +Brand#34|ECONOMY BURNISHED COPPER|49|4 +Brand#34|ECONOMY BURNISHED NICKEL|3|4 +Brand#34|ECONOMY BURNISHED NICKEL|9|4 +Brand#34|ECONOMY BURNISHED NICKEL|23|4 +Brand#34|ECONOMY BURNISHED STEEL|19|4 +Brand#34|ECONOMY BURNISHED STEEL|23|4 +Brand#34|ECONOMY BURNISHED STEEL|36|4 +Brand#34|ECONOMY BURNISHED STEEL|45|4 +Brand#34|ECONOMY BURNISHED TIN|23|4 +Brand#34|ECONOMY PLATED BRASS|36|4 +Brand#34|ECONOMY PLATED BRASS|49|4 +Brand#34|ECONOMY PLATED COPPER|14|4 +Brand#34|ECONOMY PLATED COPPER|19|4 +Brand#34|ECONOMY PLATED NICKEL|14|4 +Brand#34|ECONOMY PLATED NICKEL|19|4 +Brand#34|ECONOMY PLATED STEEL|19|4 +Brand#34|ECONOMY PLATED STEEL|23|4 +Brand#34|ECONOMY PLATED STEEL|36|4 +Brand#34|ECONOMY PLATED STEEL|45|4 +Brand#34|ECONOMY PLATED STEEL|49|4 +Brand#34|ECONOMY PLATED TIN|19|4 +Brand#34|ECONOMY PLATED TIN|23|4 +Brand#34|ECONOMY PLATED TIN|36|4 +Brand#34|ECONOMY PLATED TIN|49|4 +Brand#34|ECONOMY POLISHED BRASS|3|4 +Brand#34|ECONOMY POLISHED BRASS|23|4 +Brand#34|ECONOMY POLISHED BRASS|45|4 +Brand#34|ECONOMY POLISHED COPPER|3|4 +Brand#34|ECONOMY POLISHED COPPER|9|4 +Brand#34|ECONOMY POLISHED COPPER|23|4 +Brand#34|ECONOMY POLISHED COPPER|49|4 +Brand#34|ECONOMY POLISHED NICKEL|3|4 +Brand#34|ECONOMY POLISHED NICKEL|23|4 +Brand#34|ECONOMY POLISHED NICKEL|36|4 +Brand#34|ECONOMY POLISHED NICKEL|49|4 +Brand#34|ECONOMY POLISHED STEEL|19|4 +Brand#34|ECONOMY POLISHED TIN|3|4 +Brand#34|ECONOMY POLISHED TIN|19|4 +Brand#34|ECONOMY POLISHED TIN|45|4 +Brand#34|LARGE ANODIZED BRASS|3|4 +Brand#34|LARGE ANODIZED BRASS|14|4 +Brand#34|LARGE ANODIZED BRASS|23|4 +Brand#34|LARGE ANODIZED BRASS|36|4 +Brand#34|LARGE ANODIZED BRASS|45|4 +Brand#34|LARGE ANODIZED BRASS|49|4 +Brand#34|LARGE ANODIZED COPPER|14|4 +Brand#34|LARGE ANODIZED COPPER|19|4 +Brand#34|LARGE ANODIZED COPPER|36|4 +Brand#34|LARGE ANODIZED COPPER|45|4 +Brand#34|LARGE ANODIZED NICKEL|14|4 +Brand#34|LARGE ANODIZED NICKEL|36|4 +Brand#34|LARGE ANODIZED STEEL|9|4 +Brand#34|LARGE ANODIZED STEEL|23|4 +Brand#34|LARGE ANODIZED TIN|3|4 +Brand#34|LARGE ANODIZED TIN|9|4 +Brand#34|LARGE ANODIZED TIN|19|4 +Brand#34|LARGE ANODIZED TIN|49|4 +Brand#34|LARGE BRUSHED BRASS|3|4 +Brand#34|LARGE BRUSHED COPPER|14|4 +Brand#34|LARGE BRUSHED COPPER|23|4 +Brand#34|LARGE BRUSHED COPPER|45|4 +Brand#34|LARGE BRUSHED COPPER|49|4 +Brand#34|LARGE BRUSHED NICKEL|3|4 +Brand#34|LARGE BRUSHED NICKEL|9|4 +Brand#34|LARGE BRUSHED NICKEL|23|4 +Brand#34|LARGE BRUSHED NICKEL|45|4 +Brand#34|LARGE BRUSHED STEEL|3|4 +Brand#34|LARGE BRUSHED STEEL|14|4 +Brand#34|LARGE BRUSHED STEEL|23|4 +Brand#34|LARGE BRUSHED TIN|19|4 +Brand#34|LARGE BRUSHED TIN|45|4 +Brand#34|LARGE BURNISHED BRASS|3|4 +Brand#34|LARGE BURNISHED BRASS|9|4 +Brand#34|LARGE BURNISHED BRASS|19|4 +Brand#34|LARGE BURNISHED BRASS|49|4 +Brand#34|LARGE BURNISHED COPPER|9|4 +Brand#34|LARGE BURNISHED COPPER|45|4 +Brand#34|LARGE BURNISHED NICKEL|9|4 +Brand#34|LARGE BURNISHED NICKEL|19|4 +Brand#34|LARGE BURNISHED NICKEL|36|4 +Brand#34|LARGE BURNISHED NICKEL|45|4 +Brand#34|LARGE BURNISHED STEEL|3|4 +Brand#34|LARGE BURNISHED STEEL|23|4 +Brand#34|LARGE BURNISHED STEEL|49|4 +Brand#34|LARGE BURNISHED TIN|19|4 +Brand#34|LARGE BURNISHED TIN|36|4 +Brand#34|LARGE PLATED BRASS|3|4 +Brand#34|LARGE PLATED BRASS|14|4 +Brand#34|LARGE PLATED BRASS|23|4 +Brand#34|LARGE PLATED BRASS|45|4 +Brand#34|LARGE PLATED BRASS|49|4 +Brand#34|LARGE PLATED COPPER|23|4 +Brand#34|LARGE PLATED COPPER|45|4 +Brand#34|LARGE PLATED NICKEL|19|4 +Brand#34|LARGE PLATED NICKEL|23|4 +Brand#34|LARGE PLATED NICKEL|36|4 +Brand#34|LARGE PLATED NICKEL|49|4 +Brand#34|LARGE PLATED STEEL|19|4 +Brand#34|LARGE PLATED STEEL|36|4 +Brand#34|LARGE PLATED STEEL|45|4 +Brand#34|LARGE PLATED STEEL|49|4 +Brand#34|LARGE PLATED TIN|9|4 +Brand#34|LARGE PLATED TIN|49|4 +Brand#34|LARGE POLISHED BRASS|9|4 +Brand#34|LARGE POLISHED COPPER|49|4 +Brand#34|LARGE POLISHED NICKEL|23|4 +Brand#34|LARGE POLISHED NICKEL|36|4 +Brand#34|LARGE POLISHED STEEL|9|4 +Brand#34|LARGE POLISHED STEEL|45|4 +Brand#34|LARGE POLISHED TIN|9|4 +Brand#34|LARGE POLISHED TIN|49|4 +Brand#34|MEDIUM ANODIZED BRASS|3|4 +Brand#34|MEDIUM ANODIZED BRASS|14|4 +Brand#34|MEDIUM ANODIZED BRASS|19|4 +Brand#34|MEDIUM ANODIZED COPPER|9|4 +Brand#34|MEDIUM ANODIZED COPPER|14|4 +Brand#34|MEDIUM ANODIZED COPPER|49|4 +Brand#34|MEDIUM ANODIZED NICKEL|3|4 +Brand#34|MEDIUM ANODIZED NICKEL|9|4 +Brand#34|MEDIUM ANODIZED NICKEL|19|4 +Brand#34|MEDIUM ANODIZED NICKEL|23|4 +Brand#34|MEDIUM ANODIZED NICKEL|36|4 +Brand#34|MEDIUM ANODIZED NICKEL|45|4 +Brand#34|MEDIUM ANODIZED STEEL|14|4 +Brand#34|MEDIUM ANODIZED STEEL|23|4 +Brand#34|MEDIUM ANODIZED STEEL|36|4 +Brand#34|MEDIUM ANODIZED STEEL|45|4 +Brand#34|MEDIUM ANODIZED TIN|3|4 +Brand#34|MEDIUM ANODIZED TIN|19|4 +Brand#34|MEDIUM ANODIZED TIN|36|4 +Brand#34|MEDIUM BRUSHED BRASS|14|4 +Brand#34|MEDIUM BRUSHED BRASS|36|4 +Brand#34|MEDIUM BRUSHED BRASS|45|4 +Brand#34|MEDIUM BRUSHED COPPER|3|4 +Brand#34|MEDIUM BRUSHED NICKEL|3|4 +Brand#34|MEDIUM BRUSHED NICKEL|19|4 +Brand#34|MEDIUM BRUSHED NICKEL|36|4 +Brand#34|MEDIUM BRUSHED NICKEL|45|4 +Brand#34|MEDIUM BRUSHED STEEL|3|4 +Brand#34|MEDIUM BRUSHED STEEL|14|4 +Brand#34|MEDIUM BRUSHED STEEL|49|4 +Brand#34|MEDIUM BRUSHED TIN|3|4 +Brand#34|MEDIUM BRUSHED TIN|14|4 +Brand#34|MEDIUM BRUSHED TIN|19|4 +Brand#34|MEDIUM BRUSHED TIN|23|4 +Brand#34|MEDIUM BRUSHED TIN|45|4 +Brand#34|MEDIUM BURNISHED BRASS|3|4 +Brand#34|MEDIUM BURNISHED BRASS|19|4 +Brand#34|MEDIUM BURNISHED BRASS|36|4 +Brand#34|MEDIUM BURNISHED BRASS|45|4 +Brand#34|MEDIUM BURNISHED COPPER|9|4 +Brand#34|MEDIUM BURNISHED COPPER|19|4 +Brand#34|MEDIUM BURNISHED COPPER|36|4 +Brand#34|MEDIUM BURNISHED COPPER|45|4 +Brand#34|MEDIUM BURNISHED NICKEL|14|4 +Brand#34|MEDIUM BURNISHED NICKEL|23|4 +Brand#34|MEDIUM BURNISHED NICKEL|45|4 +Brand#34|MEDIUM BURNISHED STEEL|3|4 +Brand#34|MEDIUM BURNISHED STEEL|9|4 +Brand#34|MEDIUM BURNISHED STEEL|14|4 +Brand#34|MEDIUM BURNISHED STEEL|19|4 +Brand#34|MEDIUM BURNISHED STEEL|45|4 +Brand#34|MEDIUM BURNISHED STEEL|49|4 +Brand#34|MEDIUM BURNISHED TIN|9|4 +Brand#34|MEDIUM BURNISHED TIN|14|4 +Brand#34|MEDIUM BURNISHED TIN|19|4 +Brand#34|MEDIUM BURNISHED TIN|49|4 +Brand#34|MEDIUM PLATED BRASS|3|4 +Brand#34|MEDIUM PLATED BRASS|14|4 +Brand#34|MEDIUM PLATED BRASS|45|4 +Brand#34|MEDIUM PLATED COPPER|3|4 +Brand#34|MEDIUM PLATED COPPER|23|4 +Brand#34|MEDIUM PLATED COPPER|36|4 +Brand#34|MEDIUM PLATED COPPER|45|4 +Brand#34|MEDIUM PLATED NICKEL|3|4 +Brand#34|MEDIUM PLATED NICKEL|14|4 +Brand#34|MEDIUM PLATED STEEL|3|4 +Brand#34|MEDIUM PLATED STEEL|9|4 +Brand#34|MEDIUM PLATED TIN|3|4 +Brand#34|MEDIUM PLATED TIN|45|4 +Brand#34|PROMO ANODIZED BRASS|19|4 +Brand#34|PROMO ANODIZED BRASS|45|4 +Brand#34|PROMO ANODIZED COPPER|19|4 +Brand#34|PROMO ANODIZED COPPER|23|4 +Brand#34|PROMO ANODIZED COPPER|49|4 +Brand#34|PROMO ANODIZED NICKEL|23|4 +Brand#34|PROMO ANODIZED NICKEL|49|4 +Brand#34|PROMO ANODIZED STEEL|3|4 +Brand#34|PROMO ANODIZED STEEL|36|4 +Brand#34|PROMO ANODIZED STEEL|49|4 +Brand#34|PROMO ANODIZED TIN|19|4 +Brand#34|PROMO ANODIZED TIN|45|4 +Brand#34|PROMO BRUSHED BRASS|3|4 +Brand#34|PROMO BRUSHED BRASS|14|4 +Brand#34|PROMO BRUSHED BRASS|36|4 +Brand#34|PROMO BRUSHED COPPER|19|4 +Brand#34|PROMO BRUSHED COPPER|23|4 +Brand#34|PROMO BRUSHED COPPER|36|4 +Brand#34|PROMO BRUSHED NICKEL|3|4 +Brand#34|PROMO BRUSHED NICKEL|9|4 +Brand#34|PROMO BRUSHED NICKEL|14|4 +Brand#34|PROMO BRUSHED NICKEL|23|4 +Brand#34|PROMO BRUSHED NICKEL|36|4 +Brand#34|PROMO BRUSHED STEEL|14|4 +Brand#34|PROMO BRUSHED STEEL|23|4 +Brand#34|PROMO BRUSHED STEEL|49|4 +Brand#34|PROMO BRUSHED TIN|9|4 +Brand#34|PROMO BRUSHED TIN|19|4 +Brand#34|PROMO BRUSHED TIN|23|4 +Brand#34|PROMO BRUSHED TIN|49|4 +Brand#34|PROMO BURNISHED BRASS|3|4 +Brand#34|PROMO BURNISHED BRASS|19|4 +Brand#34|PROMO BURNISHED BRASS|23|4 +Brand#34|PROMO BURNISHED BRASS|49|4 +Brand#34|PROMO BURNISHED COPPER|9|4 +Brand#34|PROMO BURNISHED COPPER|19|4 +Brand#34|PROMO BURNISHED COPPER|23|4 +Brand#34|PROMO BURNISHED COPPER|36|4 +Brand#34|PROMO BURNISHED COPPER|45|4 +Brand#34|PROMO BURNISHED NICKEL|3|4 +Brand#34|PROMO BURNISHED NICKEL|9|4 +Brand#34|PROMO BURNISHED NICKEL|36|4 +Brand#34|PROMO BURNISHED STEEL|3|4 +Brand#34|PROMO BURNISHED STEEL|19|4 +Brand#34|PROMO BURNISHED STEEL|36|4 +Brand#34|PROMO BURNISHED TIN|3|4 +Brand#34|PROMO BURNISHED TIN|9|4 +Brand#34|PROMO BURNISHED TIN|19|4 +Brand#34|PROMO BURNISHED TIN|23|4 +Brand#34|PROMO BURNISHED TIN|49|4 +Brand#34|PROMO PLATED BRASS|14|4 +Brand#34|PROMO PLATED COPPER|3|4 +Brand#34|PROMO PLATED COPPER|9|4 +Brand#34|PROMO PLATED COPPER|19|4 +Brand#34|PROMO PLATED NICKEL|45|4 +Brand#34|PROMO PLATED STEEL|3|4 +Brand#34|PROMO PLATED STEEL|19|4 +Brand#34|PROMO PLATED STEEL|49|4 +Brand#34|PROMO PLATED TIN|3|4 +Brand#34|PROMO PLATED TIN|23|4 +Brand#34|PROMO POLISHED BRASS|3|4 +Brand#34|PROMO POLISHED BRASS|36|4 +Brand#34|PROMO POLISHED BRASS|45|4 +Brand#34|PROMO POLISHED BRASS|49|4 +Brand#34|PROMO POLISHED COPPER|3|4 +Brand#34|PROMO POLISHED COPPER|45|4 +Brand#34|PROMO POLISHED COPPER|49|4 +Brand#34|PROMO POLISHED NICKEL|3|4 +Brand#34|PROMO POLISHED NICKEL|9|4 +Brand#34|PROMO POLISHED NICKEL|14|4 +Brand#34|PROMO POLISHED NICKEL|23|4 +Brand#34|PROMO POLISHED NICKEL|36|4 +Brand#34|PROMO POLISHED NICKEL|45|4 +Brand#34|PROMO POLISHED STEEL|36|4 +Brand#34|PROMO POLISHED STEEL|45|4 +Brand#34|PROMO POLISHED TIN|36|4 +Brand#34|SMALL ANODIZED BRASS|3|4 +Brand#34|SMALL ANODIZED BRASS|36|4 +Brand#34|SMALL ANODIZED BRASS|45|4 +Brand#34|SMALL ANODIZED COPPER|3|4 +Brand#34|SMALL ANODIZED COPPER|36|4 +Brand#34|SMALL ANODIZED COPPER|45|4 +Brand#34|SMALL ANODIZED NICKEL|19|4 +Brand#34|SMALL ANODIZED STEEL|3|4 +Brand#34|SMALL ANODIZED STEEL|14|4 +Brand#34|SMALL ANODIZED STEEL|23|4 +Brand#34|SMALL ANODIZED STEEL|36|4 +Brand#34|SMALL ANODIZED TIN|3|4 +Brand#34|SMALL ANODIZED TIN|19|4 +Brand#34|SMALL ANODIZED TIN|23|4 +Brand#34|SMALL ANODIZED TIN|36|4 +Brand#34|SMALL BRUSHED BRASS|3|4 +Brand#34|SMALL BRUSHED BRASS|23|4 +Brand#34|SMALL BRUSHED BRASS|36|4 +Brand#34|SMALL BRUSHED BRASS|45|4 +Brand#34|SMALL BRUSHED BRASS|49|4 +Brand#34|SMALL BRUSHED COPPER|3|4 +Brand#34|SMALL BRUSHED COPPER|9|4 +Brand#34|SMALL BRUSHED NICKEL|3|4 +Brand#34|SMALL BRUSHED NICKEL|23|4 +Brand#34|SMALL BRUSHED NICKEL|36|4 +Brand#34|SMALL BRUSHED NICKEL|49|4 +Brand#34|SMALL BRUSHED STEEL|19|4 +Brand#34|SMALL BRUSHED STEEL|23|4 +Brand#34|SMALL BRUSHED STEEL|36|4 +Brand#34|SMALL BRUSHED STEEL|49|4 +Brand#34|SMALL BRUSHED TIN|9|4 +Brand#34|SMALL BRUSHED TIN|14|4 +Brand#34|SMALL BRUSHED TIN|19|4 +Brand#34|SMALL BRUSHED TIN|23|4 +Brand#34|SMALL BRUSHED TIN|36|4 +Brand#34|SMALL BRUSHED TIN|45|4 +Brand#34|SMALL BURNISHED BRASS|3|4 +Brand#34|SMALL BURNISHED BRASS|36|4 +Brand#34|SMALL BURNISHED BRASS|49|4 +Brand#34|SMALL BURNISHED COPPER|3|4 +Brand#34|SMALL BURNISHED COPPER|49|4 +Brand#34|SMALL BURNISHED NICKEL|19|4 +Brand#34|SMALL BURNISHED NICKEL|23|4 +Brand#34|SMALL BURNISHED STEEL|3|4 +Brand#34|SMALL BURNISHED STEEL|9|4 +Brand#34|SMALL BURNISHED STEEL|19|4 +Brand#34|SMALL BURNISHED STEEL|36|4 +Brand#34|SMALL BURNISHED STEEL|49|4 +Brand#34|SMALL BURNISHED TIN|14|4 +Brand#34|SMALL BURNISHED TIN|23|4 +Brand#34|SMALL BURNISHED TIN|45|4 +Brand#34|SMALL PLATED BRASS|9|4 +Brand#34|SMALL PLATED BRASS|45|4 +Brand#34|SMALL PLATED COPPER|3|4 +Brand#34|SMALL PLATED COPPER|9|4 +Brand#34|SMALL PLATED COPPER|14|4 +Brand#34|SMALL PLATED COPPER|36|4 +Brand#34|SMALL PLATED COPPER|45|4 +Brand#34|SMALL PLATED NICKEL|14|4 +Brand#34|SMALL PLATED NICKEL|19|4 +Brand#34|SMALL PLATED NICKEL|49|4 +Brand#34|SMALL PLATED STEEL|3|4 +Brand#34|SMALL PLATED STEEL|14|4 +Brand#34|SMALL PLATED STEEL|23|4 +Brand#34|SMALL PLATED STEEL|36|4 +Brand#34|SMALL PLATED STEEL|49|4 +Brand#34|SMALL PLATED TIN|3|4 +Brand#34|SMALL PLATED TIN|23|4 +Brand#34|SMALL PLATED TIN|36|4 +Brand#34|SMALL PLATED TIN|49|4 +Brand#34|SMALL POLISHED BRASS|3|4 +Brand#34|SMALL POLISHED BRASS|9|4 +Brand#34|SMALL POLISHED BRASS|19|4 +Brand#34|SMALL POLISHED BRASS|36|4 +Brand#34|SMALL POLISHED BRASS|49|4 +Brand#34|SMALL POLISHED COPPER|3|4 +Brand#34|SMALL POLISHED COPPER|14|4 +Brand#34|SMALL POLISHED NICKEL|9|4 +Brand#34|SMALL POLISHED NICKEL|14|4 +Brand#34|SMALL POLISHED NICKEL|45|4 +Brand#34|SMALL POLISHED NICKEL|49|4 +Brand#34|SMALL POLISHED STEEL|3|4 +Brand#34|SMALL POLISHED STEEL|14|4 +Brand#34|SMALL POLISHED STEEL|23|4 +Brand#34|SMALL POLISHED STEEL|45|4 +Brand#34|SMALL POLISHED TIN|3|4 +Brand#34|SMALL POLISHED TIN|9|4 +Brand#34|SMALL POLISHED TIN|14|4 +Brand#34|SMALL POLISHED TIN|19|4 +Brand#34|SMALL POLISHED TIN|23|4 +Brand#34|SMALL POLISHED TIN|45|4 +Brand#34|STANDARD ANODIZED BRASS|3|4 +Brand#34|STANDARD ANODIZED COPPER|49|4 +Brand#34|STANDARD ANODIZED STEEL|14|4 +Brand#34|STANDARD ANODIZED STEEL|19|4 +Brand#34|STANDARD ANODIZED STEEL|23|4 +Brand#34|STANDARD ANODIZED STEEL|36|4 +Brand#34|STANDARD ANODIZED STEEL|49|4 +Brand#34|STANDARD ANODIZED TIN|9|4 +Brand#34|STANDARD ANODIZED TIN|19|4 +Brand#34|STANDARD ANODIZED TIN|23|4 +Brand#34|STANDARD BRUSHED BRASS|9|4 +Brand#34|STANDARD BRUSHED BRASS|19|4 +Brand#34|STANDARD BRUSHED BRASS|23|4 +Brand#34|STANDARD BRUSHED COPPER|9|4 +Brand#34|STANDARD BRUSHED COPPER|36|4 +Brand#34|STANDARD BRUSHED COPPER|45|4 +Brand#34|STANDARD BRUSHED NICKEL|3|4 +Brand#34|STANDARD BRUSHED NICKEL|9|4 +Brand#34|STANDARD BRUSHED NICKEL|14|4 +Brand#34|STANDARD BRUSHED NICKEL|23|4 +Brand#34|STANDARD BRUSHED NICKEL|49|4 +Brand#34|STANDARD BRUSHED STEEL|3|4 +Brand#34|STANDARD BRUSHED STEEL|9|4 +Brand#34|STANDARD BRUSHED STEEL|36|4 +Brand#34|STANDARD BRUSHED TIN|19|4 +Brand#34|STANDARD BRUSHED TIN|23|4 +Brand#34|STANDARD BRUSHED TIN|36|4 +Brand#34|STANDARD BURNISHED BRASS|3|4 +Brand#34|STANDARD BURNISHED BRASS|23|4 +Brand#34|STANDARD BURNISHED BRASS|36|4 +Brand#34|STANDARD BURNISHED BRASS|45|4 +Brand#34|STANDARD BURNISHED COPPER|14|4 +Brand#34|STANDARD BURNISHED COPPER|19|4 +Brand#34|STANDARD BURNISHED COPPER|36|4 +Brand#34|STANDARD BURNISHED NICKEL|3|4 +Brand#34|STANDARD BURNISHED NICKEL|9|4 +Brand#34|STANDARD BURNISHED NICKEL|45|4 +Brand#34|STANDARD BURNISHED STEEL|3|4 +Brand#34|STANDARD BURNISHED STEEL|36|4 +Brand#34|STANDARD BURNISHED STEEL|45|4 +Brand#34|STANDARD BURNISHED TIN|3|4 +Brand#34|STANDARD BURNISHED TIN|14|4 +Brand#34|STANDARD BURNISHED TIN|19|4 +Brand#34|STANDARD BURNISHED TIN|36|4 +Brand#34|STANDARD PLATED BRASS|9|4 +Brand#34|STANDARD PLATED BRASS|23|4 +Brand#34|STANDARD PLATED BRASS|36|4 +Brand#34|STANDARD PLATED COPPER|3|4 +Brand#34|STANDARD PLATED COPPER|19|4 +Brand#34|STANDARD PLATED COPPER|49|4 +Brand#34|STANDARD PLATED NICKEL|9|4 +Brand#34|STANDARD PLATED NICKEL|23|4 +Brand#34|STANDARD PLATED STEEL|3|4 +Brand#34|STANDARD PLATED STEEL|14|4 +Brand#34|STANDARD PLATED STEEL|19|4 +Brand#34|STANDARD PLATED TIN|23|4 +Brand#34|STANDARD PLATED TIN|49|4 +Brand#34|STANDARD POLISHED BRASS|3|4 +Brand#34|STANDARD POLISHED BRASS|14|4 +Brand#34|STANDARD POLISHED COPPER|3|4 +Brand#34|STANDARD POLISHED COPPER|9|4 +Brand#34|STANDARD POLISHED NICKEL|3|4 +Brand#34|STANDARD POLISHED NICKEL|9|4 +Brand#34|STANDARD POLISHED NICKEL|14|4 +Brand#34|STANDARD POLISHED NICKEL|19|4 +Brand#34|STANDARD POLISHED NICKEL|23|4 +Brand#34|STANDARD POLISHED NICKEL|45|4 +Brand#34|STANDARD POLISHED STEEL|45|4 +Brand#34|STANDARD POLISHED TIN|14|4 +Brand#34|STANDARD POLISHED TIN|49|4 +Brand#35|ECONOMY ANODIZED COPPER|14|4 +Brand#35|ECONOMY ANODIZED NICKEL|45|4 +Brand#35|ECONOMY ANODIZED STEEL|3|4 +Brand#35|ECONOMY ANODIZED STEEL|9|4 +Brand#35|ECONOMY ANODIZED TIN|3|4 +Brand#35|ECONOMY ANODIZED TIN|9|4 +Brand#35|ECONOMY ANODIZED TIN|49|4 +Brand#35|ECONOMY BRUSHED BRASS|23|4 +Brand#35|ECONOMY BRUSHED BRASS|45|4 +Brand#35|ECONOMY BRUSHED COPPER|9|4 +Brand#35|ECONOMY BRUSHED COPPER|14|4 +Brand#35|ECONOMY BRUSHED COPPER|36|4 +Brand#35|ECONOMY BRUSHED COPPER|49|4 +Brand#35|ECONOMY BRUSHED NICKEL|3|4 +Brand#35|ECONOMY BRUSHED NICKEL|9|4 +Brand#35|ECONOMY BRUSHED NICKEL|19|4 +Brand#35|ECONOMY BRUSHED NICKEL|23|4 +Brand#35|ECONOMY BRUSHED NICKEL|36|4 +Brand#35|ECONOMY BRUSHED STEEL|3|4 +Brand#35|ECONOMY BRUSHED STEEL|9|4 +Brand#35|ECONOMY BRUSHED TIN|14|4 +Brand#35|ECONOMY BRUSHED TIN|45|4 +Brand#35|ECONOMY BURNISHED BRASS|23|4 +Brand#35|ECONOMY BURNISHED BRASS|45|4 +Brand#35|ECONOMY BURNISHED BRASS|49|4 +Brand#35|ECONOMY BURNISHED COPPER|3|4 +Brand#35|ECONOMY BURNISHED COPPER|49|4 +Brand#35|ECONOMY BURNISHED NICKEL|9|4 +Brand#35|ECONOMY BURNISHED NICKEL|14|4 +Brand#35|ECONOMY BURNISHED NICKEL|36|4 +Brand#35|ECONOMY BURNISHED NICKEL|45|4 +Brand#35|ECONOMY BURNISHED STEEL|3|4 +Brand#35|ECONOMY BURNISHED STEEL|9|4 +Brand#35|ECONOMY BURNISHED STEEL|14|4 +Brand#35|ECONOMY BURNISHED STEEL|23|4 +Brand#35|ECONOMY BURNISHED STEEL|49|4 +Brand#35|ECONOMY BURNISHED TIN|19|4 +Brand#35|ECONOMY BURNISHED TIN|36|4 +Brand#35|ECONOMY BURNISHED TIN|49|4 +Brand#35|ECONOMY PLATED BRASS|19|4 +Brand#35|ECONOMY PLATED COPPER|36|4 +Brand#35|ECONOMY PLATED COPPER|49|4 +Brand#35|ECONOMY PLATED NICKEL|9|4 +Brand#35|ECONOMY PLATED STEEL|3|4 +Brand#35|ECONOMY PLATED STEEL|9|4 +Brand#35|ECONOMY PLATED STEEL|45|4 +Brand#35|ECONOMY PLATED TIN|3|4 +Brand#35|ECONOMY PLATED TIN|9|4 +Brand#35|ECONOMY PLATED TIN|19|4 +Brand#35|ECONOMY PLATED TIN|23|4 +Brand#35|ECONOMY POLISHED BRASS|19|4 +Brand#35|ECONOMY POLISHED BRASS|23|4 +Brand#35|ECONOMY POLISHED BRASS|49|4 +Brand#35|ECONOMY POLISHED COPPER|19|4 +Brand#35|ECONOMY POLISHED COPPER|23|4 +Brand#35|ECONOMY POLISHED COPPER|45|4 +Brand#35|ECONOMY POLISHED COPPER|49|4 +Brand#35|ECONOMY POLISHED NICKEL|3|4 +Brand#35|ECONOMY POLISHED NICKEL|14|4 +Brand#35|ECONOMY POLISHED NICKEL|36|4 +Brand#35|ECONOMY POLISHED NICKEL|45|4 +Brand#35|ECONOMY POLISHED STEEL|23|4 +Brand#35|ECONOMY POLISHED TIN|9|4 +Brand#35|ECONOMY POLISHED TIN|36|4 +Brand#35|ECONOMY POLISHED TIN|45|4 +Brand#35|LARGE ANODIZED BRASS|14|4 +Brand#35|LARGE ANODIZED BRASS|19|4 +Brand#35|LARGE ANODIZED NICKEL|19|4 +Brand#35|LARGE ANODIZED NICKEL|36|4 +Brand#35|LARGE ANODIZED STEEL|14|4 +Brand#35|LARGE ANODIZED STEEL|36|4 +Brand#35|LARGE BRUSHED BRASS|9|4 +Brand#35|LARGE BRUSHED BRASS|19|4 +Brand#35|LARGE BRUSHED BRASS|36|4 +Brand#35|LARGE BRUSHED BRASS|45|4 +Brand#35|LARGE BRUSHED BRASS|49|4 +Brand#35|LARGE BRUSHED COPPER|36|4 +Brand#35|LARGE BRUSHED COPPER|45|4 +Brand#35|LARGE BRUSHED COPPER|49|4 +Brand#35|LARGE BRUSHED NICKEL|14|4 +Brand#35|LARGE BRUSHED NICKEL|45|4 +Brand#35|LARGE BRUSHED STEEL|9|4 +Brand#35|LARGE BRUSHED STEEL|45|4 +Brand#35|LARGE BRUSHED STEEL|49|4 +Brand#35|LARGE BRUSHED TIN|3|4 +Brand#35|LARGE BRUSHED TIN|9|4 +Brand#35|LARGE BRUSHED TIN|19|4 +Brand#35|LARGE BURNISHED BRASS|9|4 +Brand#35|LARGE BURNISHED BRASS|23|4 +Brand#35|LARGE BURNISHED COPPER|45|4 +Brand#35|LARGE BURNISHED COPPER|49|4 +Brand#35|LARGE BURNISHED NICKEL|36|4 +Brand#35|LARGE BURNISHED STEEL|23|4 +Brand#35|LARGE BURNISHED TIN|45|4 +Brand#35|LARGE PLATED COPPER|3|4 +Brand#35|LARGE PLATED COPPER|9|4 +Brand#35|LARGE PLATED COPPER|14|4 +Brand#35|LARGE PLATED COPPER|36|4 +Brand#35|LARGE PLATED COPPER|49|4 +Brand#35|LARGE PLATED NICKEL|9|4 +Brand#35|LARGE PLATED NICKEL|14|4 +Brand#35|LARGE PLATED NICKEL|23|4 +Brand#35|LARGE PLATED NICKEL|49|4 +Brand#35|LARGE PLATED STEEL|36|4 +Brand#35|LARGE PLATED STEEL|45|4 +Brand#35|LARGE PLATED TIN|3|4 +Brand#35|LARGE PLATED TIN|49|4 +Brand#35|LARGE POLISHED BRASS|3|4 +Brand#35|LARGE POLISHED BRASS|9|4 +Brand#35|LARGE POLISHED BRASS|14|4 +Brand#35|LARGE POLISHED BRASS|23|4 +Brand#35|LARGE POLISHED BRASS|36|4 +Brand#35|LARGE POLISHED BRASS|45|4 +Brand#35|LARGE POLISHED COPPER|9|4 +Brand#35|LARGE POLISHED COPPER|45|4 +Brand#35|LARGE POLISHED NICKEL|3|4 +Brand#35|LARGE POLISHED NICKEL|9|4 +Brand#35|LARGE POLISHED NICKEL|14|4 +Brand#35|LARGE POLISHED NICKEL|19|4 +Brand#35|LARGE POLISHED NICKEL|49|4 +Brand#35|LARGE POLISHED STEEL|3|4 +Brand#35|LARGE POLISHED STEEL|9|4 +Brand#35|LARGE POLISHED STEEL|45|4 +Brand#35|LARGE POLISHED STEEL|49|4 +Brand#35|LARGE POLISHED TIN|19|4 +Brand#35|LARGE POLISHED TIN|36|4 +Brand#35|LARGE POLISHED TIN|45|4 +Brand#35|LARGE POLISHED TIN|49|4 +Brand#35|MEDIUM ANODIZED BRASS|3|4 +Brand#35|MEDIUM ANODIZED BRASS|9|4 +Brand#35|MEDIUM ANODIZED BRASS|14|4 +Brand#35|MEDIUM ANODIZED BRASS|19|4 +Brand#35|MEDIUM ANODIZED BRASS|36|4 +Brand#35|MEDIUM ANODIZED COPPER|3|4 +Brand#35|MEDIUM ANODIZED COPPER|23|4 +Brand#35|MEDIUM ANODIZED COPPER|45|4 +Brand#35|MEDIUM ANODIZED COPPER|49|4 +Brand#35|MEDIUM ANODIZED NICKEL|36|4 +Brand#35|MEDIUM ANODIZED NICKEL|45|4 +Brand#35|MEDIUM ANODIZED NICKEL|49|4 +Brand#35|MEDIUM ANODIZED STEEL|9|4 +Brand#35|MEDIUM ANODIZED STEEL|14|4 +Brand#35|MEDIUM ANODIZED STEEL|36|4 +Brand#35|MEDIUM ANODIZED STEEL|49|4 +Brand#35|MEDIUM ANODIZED TIN|9|4 +Brand#35|MEDIUM BRUSHED BRASS|14|4 +Brand#35|MEDIUM BRUSHED COPPER|9|4 +Brand#35|MEDIUM BRUSHED COPPER|49|4 +Brand#35|MEDIUM BRUSHED NICKEL|14|4 +Brand#35|MEDIUM BRUSHED NICKEL|36|4 +Brand#35|MEDIUM BRUSHED STEEL|9|4 +Brand#35|MEDIUM BRUSHED STEEL|19|4 +Brand#35|MEDIUM BRUSHED STEEL|36|4 +Brand#35|MEDIUM BRUSHED TIN|3|4 +Brand#35|MEDIUM BRUSHED TIN|36|4 +Brand#35|MEDIUM BRUSHED TIN|45|4 +Brand#35|MEDIUM BURNISHED BRASS|14|4 +Brand#35|MEDIUM BURNISHED BRASS|19|4 +Brand#35|MEDIUM BURNISHED BRASS|23|4 +Brand#35|MEDIUM BURNISHED COPPER|3|4 +Brand#35|MEDIUM BURNISHED COPPER|9|4 +Brand#35|MEDIUM BURNISHED COPPER|14|4 +Brand#35|MEDIUM BURNISHED COPPER|19|4 +Brand#35|MEDIUM BURNISHED COPPER|45|4 +Brand#35|MEDIUM BURNISHED NICKEL|36|4 +Brand#35|MEDIUM BURNISHED NICKEL|45|4 +Brand#35|MEDIUM BURNISHED NICKEL|49|4 +Brand#35|MEDIUM BURNISHED STEEL|14|4 +Brand#35|MEDIUM PLATED BRASS|9|4 +Brand#35|MEDIUM PLATED BRASS|19|4 +Brand#35|MEDIUM PLATED BRASS|49|4 +Brand#35|MEDIUM PLATED COPPER|14|4 +Brand#35|MEDIUM PLATED NICKEL|3|4 +Brand#35|MEDIUM PLATED NICKEL|19|4 +Brand#35|MEDIUM PLATED STEEL|9|4 +Brand#35|MEDIUM PLATED STEEL|19|4 +Brand#35|MEDIUM PLATED STEEL|45|4 +Brand#35|MEDIUM PLATED STEEL|49|4 +Brand#35|MEDIUM PLATED TIN|3|4 +Brand#35|MEDIUM PLATED TIN|9|4 +Brand#35|MEDIUM PLATED TIN|45|4 +Brand#35|PROMO ANODIZED BRASS|19|4 +Brand#35|PROMO ANODIZED BRASS|23|4 +Brand#35|PROMO ANODIZED BRASS|36|4 +Brand#35|PROMO ANODIZED BRASS|49|4 +Brand#35|PROMO ANODIZED COPPER|19|4 +Brand#35|PROMO ANODIZED NICKEL|9|4 +Brand#35|PROMO ANODIZED NICKEL|19|4 +Brand#35|PROMO ANODIZED NICKEL|23|4 +Brand#35|PROMO ANODIZED STEEL|9|4 +Brand#35|PROMO ANODIZED STEEL|19|4 +Brand#35|PROMO ANODIZED TIN|3|4 +Brand#35|PROMO ANODIZED TIN|19|4 +Brand#35|PROMO ANODIZED TIN|23|4 +Brand#35|PROMO ANODIZED TIN|36|4 +Brand#35|PROMO ANODIZED TIN|45|4 +Brand#35|PROMO BRUSHED BRASS|9|4 +Brand#35|PROMO BRUSHED BRASS|19|4 +Brand#35|PROMO BRUSHED BRASS|36|4 +Brand#35|PROMO BRUSHED BRASS|49|4 +Brand#35|PROMO BRUSHED COPPER|19|4 +Brand#35|PROMO BRUSHED COPPER|45|4 +Brand#35|PROMO BRUSHED NICKEL|23|4 +Brand#35|PROMO BRUSHED STEEL|3|4 +Brand#35|PROMO BRUSHED STEEL|45|4 +Brand#35|PROMO BRUSHED STEEL|49|4 +Brand#35|PROMO BRUSHED TIN|9|4 +Brand#35|PROMO BRUSHED TIN|14|4 +Brand#35|PROMO BRUSHED TIN|23|4 +Brand#35|PROMO BRUSHED TIN|36|4 +Brand#35|PROMO BURNISHED BRASS|9|4 +Brand#35|PROMO BURNISHED BRASS|36|4 +Brand#35|PROMO BURNISHED BRASS|45|4 +Brand#35|PROMO BURNISHED NICKEL|9|4 +Brand#35|PROMO BURNISHED STEEL|19|4 +Brand#35|PROMO BURNISHED STEEL|23|4 +Brand#35|PROMO BURNISHED STEEL|36|4 +Brand#35|PROMO BURNISHED TIN|49|4 +Brand#35|PROMO PLATED BRASS|3|4 +Brand#35|PROMO PLATED BRASS|9|4 +Brand#35|PROMO PLATED BRASS|36|4 +Brand#35|PROMO PLATED COPPER|9|4 +Brand#35|PROMO PLATED COPPER|14|4 +Brand#35|PROMO PLATED COPPER|19|4 +Brand#35|PROMO PLATED COPPER|45|4 +Brand#35|PROMO PLATED COPPER|49|4 +Brand#35|PROMO PLATED NICKEL|3|4 +Brand#35|PROMO PLATED NICKEL|36|4 +Brand#35|PROMO PLATED NICKEL|49|4 +Brand#35|PROMO PLATED STEEL|19|4 +Brand#35|PROMO PLATED TIN|49|4 +Brand#35|PROMO POLISHED BRASS|14|4 +Brand#35|PROMO POLISHED BRASS|36|4 +Brand#35|PROMO POLISHED BRASS|45|4 +Brand#35|PROMO POLISHED BRASS|49|4 +Brand#35|PROMO POLISHED COPPER|9|4 +Brand#35|PROMO POLISHED COPPER|45|4 +Brand#35|PROMO POLISHED NICKEL|3|4 +Brand#35|PROMO POLISHED NICKEL|14|4 +Brand#35|PROMO POLISHED NICKEL|36|4 +Brand#35|PROMO POLISHED STEEL|3|4 +Brand#35|PROMO POLISHED STEEL|23|4 +Brand#35|PROMO POLISHED STEEL|36|4 +Brand#35|PROMO POLISHED STEEL|49|4 +Brand#35|PROMO POLISHED TIN|9|4 +Brand#35|PROMO POLISHED TIN|19|4 +Brand#35|SMALL ANODIZED BRASS|3|4 +Brand#35|SMALL ANODIZED COPPER|3|4 +Brand#35|SMALL ANODIZED COPPER|9|4 +Brand#35|SMALL ANODIZED COPPER|23|4 +Brand#35|SMALL ANODIZED COPPER|36|4 +Brand#35|SMALL ANODIZED COPPER|45|4 +Brand#35|SMALL ANODIZED COPPER|49|4 +Brand#35|SMALL ANODIZED NICKEL|3|4 +Brand#35|SMALL ANODIZED NICKEL|14|4 +Brand#35|SMALL ANODIZED NICKEL|45|4 +Brand#35|SMALL ANODIZED STEEL|3|4 +Brand#35|SMALL ANODIZED STEEL|9|4 +Brand#35|SMALL ANODIZED STEEL|23|4 +Brand#35|SMALL ANODIZED STEEL|36|4 +Brand#35|SMALL ANODIZED TIN|9|4 +Brand#35|SMALL ANODIZED TIN|19|4 +Brand#35|SMALL ANODIZED TIN|23|4 +Brand#35|SMALL ANODIZED TIN|45|4 +Brand#35|SMALL BRUSHED BRASS|3|4 +Brand#35|SMALL BRUSHED BRASS|9|4 +Brand#35|SMALL BRUSHED BRASS|19|4 +Brand#35|SMALL BRUSHED BRASS|36|4 +Brand#35|SMALL BRUSHED COPPER|14|4 +Brand#35|SMALL BRUSHED COPPER|23|4 +Brand#35|SMALL BRUSHED COPPER|36|4 +Brand#35|SMALL BRUSHED NICKEL|36|4 +Brand#35|SMALL BRUSHED NICKEL|45|4 +Brand#35|SMALL BRUSHED STEEL|3|4 +Brand#35|SMALL BRUSHED STEEL|14|4 +Brand#35|SMALL BRUSHED TIN|3|4 +Brand#35|SMALL BRUSHED TIN|36|4 +Brand#35|SMALL BURNISHED BRASS|3|4 +Brand#35|SMALL BURNISHED BRASS|19|4 +Brand#35|SMALL BURNISHED COPPER|9|4 +Brand#35|SMALL BURNISHED COPPER|19|4 +Brand#35|SMALL BURNISHED COPPER|23|4 +Brand#35|SMALL BURNISHED NICKEL|14|4 +Brand#35|SMALL BURNISHED NICKEL|45|4 +Brand#35|SMALL BURNISHED NICKEL|49|4 +Brand#35|SMALL BURNISHED STEEL|9|4 +Brand#35|SMALL BURNISHED TIN|3|4 +Brand#35|SMALL BURNISHED TIN|9|4 +Brand#35|SMALL BURNISHED TIN|14|4 +Brand#35|SMALL BURNISHED TIN|23|4 +Brand#35|SMALL BURNISHED TIN|36|4 +Brand#35|SMALL BURNISHED TIN|49|4 +Brand#35|SMALL PLATED BRASS|3|4 +Brand#35|SMALL PLATED BRASS|14|4 +Brand#35|SMALL PLATED BRASS|23|4 +Brand#35|SMALL PLATED BRASS|45|4 +Brand#35|SMALL PLATED BRASS|49|4 +Brand#35|SMALL PLATED COPPER|9|4 +Brand#35|SMALL PLATED COPPER|19|4 +Brand#35|SMALL PLATED COPPER|23|4 +Brand#35|SMALL PLATED COPPER|36|4 +Brand#35|SMALL PLATED NICKEL|3|4 +Brand#35|SMALL PLATED NICKEL|14|4 +Brand#35|SMALL PLATED NICKEL|19|4 +Brand#35|SMALL PLATED STEEL|9|4 +Brand#35|SMALL PLATED STEEL|19|4 +Brand#35|SMALL PLATED STEEL|45|4 +Brand#35|SMALL PLATED STEEL|49|4 +Brand#35|SMALL PLATED TIN|19|4 +Brand#35|SMALL PLATED TIN|23|4 +Brand#35|SMALL POLISHED BRASS|19|4 +Brand#35|SMALL POLISHED BRASS|49|4 +Brand#35|SMALL POLISHED COPPER|9|4 +Brand#35|SMALL POLISHED NICKEL|3|4 +Brand#35|SMALL POLISHED NICKEL|9|4 +Brand#35|SMALL POLISHED NICKEL|23|4 +Brand#35|SMALL POLISHED NICKEL|45|4 +Brand#35|SMALL POLISHED STEEL|3|4 +Brand#35|SMALL POLISHED STEEL|9|4 +Brand#35|SMALL POLISHED STEEL|14|4 +Brand#35|SMALL POLISHED TIN|36|4 +Brand#35|STANDARD ANODIZED BRASS|3|4 +Brand#35|STANDARD ANODIZED BRASS|23|4 +Brand#35|STANDARD ANODIZED BRASS|36|4 +Brand#35|STANDARD ANODIZED BRASS|49|4 +Brand#35|STANDARD ANODIZED COPPER|9|4 +Brand#35|STANDARD ANODIZED COPPER|19|4 +Brand#35|STANDARD ANODIZED COPPER|49|4 +Brand#35|STANDARD ANODIZED NICKEL|3|4 +Brand#35|STANDARD ANODIZED NICKEL|9|4 +Brand#35|STANDARD ANODIZED NICKEL|19|4 +Brand#35|STANDARD ANODIZED NICKEL|23|4 +Brand#35|STANDARD ANODIZED NICKEL|45|4 +Brand#35|STANDARD ANODIZED STEEL|19|4 +Brand#35|STANDARD ANODIZED STEEL|23|4 +Brand#35|STANDARD ANODIZED STEEL|36|4 +Brand#35|STANDARD ANODIZED STEEL|45|4 +Brand#35|STANDARD ANODIZED TIN|14|4 +Brand#35|STANDARD ANODIZED TIN|19|4 +Brand#35|STANDARD BRUSHED BRASS|3|4 +Brand#35|STANDARD BRUSHED BRASS|9|4 +Brand#35|STANDARD BRUSHED BRASS|49|4 +Brand#35|STANDARD BRUSHED COPPER|9|4 +Brand#35|STANDARD BRUSHED COPPER|49|4 +Brand#35|STANDARD BRUSHED NICKEL|3|4 +Brand#35|STANDARD BRUSHED NICKEL|19|4 +Brand#35|STANDARD BRUSHED NICKEL|23|4 +Brand#35|STANDARD BRUSHED NICKEL|36|4 +Brand#35|STANDARD BRUSHED STEEL|19|4 +Brand#35|STANDARD BRUSHED STEEL|23|4 +Brand#35|STANDARD BRUSHED STEEL|45|4 +Brand#35|STANDARD BRUSHED TIN|9|4 +Brand#35|STANDARD BRUSHED TIN|14|4 +Brand#35|STANDARD BRUSHED TIN|19|4 +Brand#35|STANDARD BRUSHED TIN|36|4 +Brand#35|STANDARD BRUSHED TIN|49|4 +Brand#35|STANDARD BURNISHED BRASS|3|4 +Brand#35|STANDARD BURNISHED BRASS|9|4 +Brand#35|STANDARD BURNISHED BRASS|14|4 +Brand#35|STANDARD BURNISHED BRASS|19|4 +Brand#35|STANDARD BURNISHED BRASS|23|4 +Brand#35|STANDARD BURNISHED BRASS|49|4 +Brand#35|STANDARD BURNISHED COPPER|14|4 +Brand#35|STANDARD BURNISHED COPPER|19|4 +Brand#35|STANDARD BURNISHED COPPER|23|4 +Brand#35|STANDARD BURNISHED COPPER|45|4 +Brand#35|STANDARD BURNISHED NICKEL|3|4 +Brand#35|STANDARD BURNISHED NICKEL|19|4 +Brand#35|STANDARD BURNISHED NICKEL|23|4 +Brand#35|STANDARD BURNISHED STEEL|9|4 +Brand#35|STANDARD BURNISHED STEEL|19|4 +Brand#35|STANDARD BURNISHED TIN|9|4 +Brand#35|STANDARD BURNISHED TIN|14|4 +Brand#35|STANDARD BURNISHED TIN|23|4 +Brand#35|STANDARD PLATED BRASS|3|4 +Brand#35|STANDARD PLATED BRASS|9|4 +Brand#35|STANDARD PLATED COPPER|23|4 +Brand#35|STANDARD PLATED COPPER|49|4 +Brand#35|STANDARD PLATED NICKEL|36|4 +Brand#35|STANDARD PLATED NICKEL|45|4 +Brand#35|STANDARD PLATED STEEL|3|4 +Brand#35|STANDARD PLATED STEEL|14|4 +Brand#35|STANDARD PLATED STEEL|49|4 +Brand#35|STANDARD PLATED TIN|3|4 +Brand#35|STANDARD POLISHED BRASS|3|4 +Brand#35|STANDARD POLISHED BRASS|14|4 +Brand#35|STANDARD POLISHED BRASS|45|4 +Brand#35|STANDARD POLISHED COPPER|3|4 +Brand#35|STANDARD POLISHED COPPER|9|4 +Brand#35|STANDARD POLISHED COPPER|14|4 +Brand#35|STANDARD POLISHED COPPER|19|4 +Brand#35|STANDARD POLISHED COPPER|36|4 +Brand#35|STANDARD POLISHED COPPER|45|4 +Brand#35|STANDARD POLISHED NICKEL|19|4 +Brand#35|STANDARD POLISHED NICKEL|45|4 +Brand#35|STANDARD POLISHED STEEL|9|4 +Brand#35|STANDARD POLISHED STEEL|19|4 +Brand#35|STANDARD POLISHED TIN|19|4 +Brand#35|STANDARD POLISHED TIN|23|4 +Brand#35|STANDARD POLISHED TIN|49|4 +Brand#41|ECONOMY ANODIZED BRASS|3|4 +Brand#41|ECONOMY ANODIZED BRASS|9|4 +Brand#41|ECONOMY ANODIZED COPPER|3|4 +Brand#41|ECONOMY ANODIZED COPPER|9|4 +Brand#41|ECONOMY ANODIZED NICKEL|3|4 +Brand#41|ECONOMY ANODIZED NICKEL|9|4 +Brand#41|ECONOMY ANODIZED NICKEL|14|4 +Brand#41|ECONOMY ANODIZED NICKEL|23|4 +Brand#41|ECONOMY ANODIZED NICKEL|36|4 +Brand#41|ECONOMY ANODIZED NICKEL|49|4 +Brand#41|ECONOMY ANODIZED STEEL|9|4 +Brand#41|ECONOMY ANODIZED STEEL|14|4 +Brand#41|ECONOMY ANODIZED STEEL|23|4 +Brand#41|ECONOMY ANODIZED TIN|9|4 +Brand#41|ECONOMY ANODIZED TIN|19|4 +Brand#41|ECONOMY ANODIZED TIN|49|4 +Brand#41|ECONOMY BRUSHED BRASS|9|4 +Brand#41|ECONOMY BRUSHED BRASS|19|4 +Brand#41|ECONOMY BRUSHED BRASS|45|4 +Brand#41|ECONOMY BRUSHED BRASS|49|4 +Brand#41|ECONOMY BRUSHED COPPER|9|4 +Brand#41|ECONOMY BRUSHED COPPER|45|4 +Brand#41|ECONOMY BRUSHED NICKEL|3|4 +Brand#41|ECONOMY BRUSHED NICKEL|9|4 +Brand#41|ECONOMY BRUSHED NICKEL|14|4 +Brand#41|ECONOMY BRUSHED NICKEL|23|4 +Brand#41|ECONOMY BRUSHED STEEL|14|4 +Brand#41|ECONOMY BRUSHED STEEL|23|4 +Brand#41|ECONOMY BRUSHED STEEL|49|4 +Brand#41|ECONOMY BRUSHED TIN|19|4 +Brand#41|ECONOMY BURNISHED BRASS|9|4 +Brand#41|ECONOMY BURNISHED COPPER|19|4 +Brand#41|ECONOMY BURNISHED COPPER|23|4 +Brand#41|ECONOMY BURNISHED COPPER|36|4 +Brand#41|ECONOMY BURNISHED NICKEL|9|4 +Brand#41|ECONOMY BURNISHED NICKEL|19|4 +Brand#41|ECONOMY BURNISHED NICKEL|23|4 +Brand#41|ECONOMY BURNISHED STEEL|9|4 +Brand#41|ECONOMY BURNISHED STEEL|45|4 +Brand#41|ECONOMY BURNISHED TIN|19|4 +Brand#41|ECONOMY BURNISHED TIN|45|4 +Brand#41|ECONOMY BURNISHED TIN|49|4 +Brand#41|ECONOMY PLATED COPPER|3|4 +Brand#41|ECONOMY PLATED COPPER|9|4 +Brand#41|ECONOMY PLATED COPPER|19|4 +Brand#41|ECONOMY PLATED COPPER|23|4 +Brand#41|ECONOMY PLATED COPPER|36|4 +Brand#41|ECONOMY PLATED NICKEL|19|4 +Brand#41|ECONOMY PLATED NICKEL|49|4 +Brand#41|ECONOMY PLATED TIN|14|4 +Brand#41|ECONOMY PLATED TIN|36|4 +Brand#41|ECONOMY POLISHED BRASS|3|4 +Brand#41|ECONOMY POLISHED BRASS|9|4 +Brand#41|ECONOMY POLISHED COPPER|3|4 +Brand#41|ECONOMY POLISHED COPPER|9|4 +Brand#41|ECONOMY POLISHED COPPER|19|4 +Brand#41|ECONOMY POLISHED COPPER|23|4 +Brand#41|ECONOMY POLISHED NICKEL|3|4 +Brand#41|ECONOMY POLISHED NICKEL|14|4 +Brand#41|ECONOMY POLISHED NICKEL|36|4 +Brand#41|ECONOMY POLISHED STEEL|9|4 +Brand#41|ECONOMY POLISHED STEEL|14|4 +Brand#41|ECONOMY POLISHED STEEL|36|4 +Brand#41|ECONOMY POLISHED TIN|9|4 +Brand#41|LARGE ANODIZED BRASS|19|4 +Brand#41|LARGE ANODIZED BRASS|49|4 +Brand#41|LARGE ANODIZED COPPER|19|4 +Brand#41|LARGE ANODIZED COPPER|23|4 +Brand#41|LARGE ANODIZED COPPER|49|4 +Brand#41|LARGE ANODIZED NICKEL|14|4 +Brand#41|LARGE ANODIZED NICKEL|23|4 +Brand#41|LARGE ANODIZED NICKEL|36|4 +Brand#41|LARGE ANODIZED NICKEL|45|4 +Brand#41|LARGE ANODIZED NICKEL|49|4 +Brand#41|LARGE ANODIZED STEEL|9|4 +Brand#41|LARGE ANODIZED STEEL|45|4 +Brand#41|LARGE ANODIZED STEEL|49|4 +Brand#41|LARGE ANODIZED TIN|9|4 +Brand#41|LARGE ANODIZED TIN|14|4 +Brand#41|LARGE ANODIZED TIN|36|4 +Brand#41|LARGE ANODIZED TIN|49|4 +Brand#41|LARGE BRUSHED BRASS|19|4 +Brand#41|LARGE BRUSHED BRASS|36|4 +Brand#41|LARGE BRUSHED BRASS|45|4 +Brand#41|LARGE BRUSHED BRASS|49|4 +Brand#41|LARGE BRUSHED COPPER|3|4 +Brand#41|LARGE BRUSHED COPPER|14|4 +Brand#41|LARGE BRUSHED COPPER|45|4 +Brand#41|LARGE BRUSHED NICKEL|3|4 +Brand#41|LARGE BRUSHED NICKEL|9|4 +Brand#41|LARGE BRUSHED NICKEL|49|4 +Brand#41|LARGE BRUSHED STEEL|3|4 +Brand#41|LARGE BRUSHED STEEL|19|4 +Brand#41|LARGE BRUSHED TIN|9|4 +Brand#41|LARGE BRUSHED TIN|23|4 +Brand#41|LARGE BURNISHED BRASS|9|4 +Brand#41|LARGE BURNISHED BRASS|14|4 +Brand#41|LARGE BURNISHED BRASS|45|4 +Brand#41|LARGE BURNISHED BRASS|49|4 +Brand#41|LARGE BURNISHED COPPER|9|4 +Brand#41|LARGE BURNISHED COPPER|36|4 +Brand#41|LARGE BURNISHED NICKEL|3|4 +Brand#41|LARGE BURNISHED NICKEL|9|4 +Brand#41|LARGE BURNISHED NICKEL|23|4 +Brand#41|LARGE BURNISHED STEEL|36|4 +Brand#41|LARGE BURNISHED TIN|23|4 +Brand#41|LARGE BURNISHED TIN|49|4 +Brand#41|LARGE PLATED BRASS|49|4 +Brand#41|LARGE PLATED NICKEL|23|4 +Brand#41|LARGE PLATED NICKEL|45|4 +Brand#41|LARGE PLATED STEEL|9|4 +Brand#41|LARGE PLATED STEEL|45|4 +Brand#41|LARGE PLATED TIN|9|4 +Brand#41|LARGE PLATED TIN|49|4 +Brand#41|LARGE POLISHED BRASS|9|4 +Brand#41|LARGE POLISHED BRASS|23|4 +Brand#41|LARGE POLISHED COPPER|9|4 +Brand#41|LARGE POLISHED COPPER|45|4 +Brand#41|LARGE POLISHED NICKEL|9|4 +Brand#41|LARGE POLISHED NICKEL|19|4 +Brand#41|LARGE POLISHED NICKEL|36|4 +Brand#41|LARGE POLISHED STEEL|19|4 +Brand#41|LARGE POLISHED STEEL|36|4 +Brand#41|LARGE POLISHED STEEL|45|4 +Brand#41|LARGE POLISHED STEEL|49|4 +Brand#41|LARGE POLISHED TIN|23|4 +Brand#41|LARGE POLISHED TIN|36|4 +Brand#41|LARGE POLISHED TIN|45|4 +Brand#41|LARGE POLISHED TIN|49|4 +Brand#41|MEDIUM ANODIZED BRASS|14|4 +Brand#41|MEDIUM ANODIZED BRASS|19|4 +Brand#41|MEDIUM ANODIZED BRASS|23|4 +Brand#41|MEDIUM ANODIZED COPPER|9|4 +Brand#41|MEDIUM ANODIZED COPPER|14|4 +Brand#41|MEDIUM ANODIZED COPPER|19|4 +Brand#41|MEDIUM ANODIZED COPPER|36|4 +Brand#41|MEDIUM ANODIZED COPPER|45|4 +Brand#41|MEDIUM ANODIZED COPPER|49|4 +Brand#41|MEDIUM ANODIZED NICKEL|9|4 +Brand#41|MEDIUM ANODIZED NICKEL|14|4 +Brand#41|MEDIUM ANODIZED NICKEL|23|4 +Brand#41|MEDIUM ANODIZED NICKEL|45|4 +Brand#41|MEDIUM ANODIZED STEEL|9|4 +Brand#41|MEDIUM ANODIZED STEEL|14|4 +Brand#41|MEDIUM ANODIZED STEEL|19|4 +Brand#41|MEDIUM BRUSHED BRASS|23|4 +Brand#41|MEDIUM BRUSHED COPPER|9|4 +Brand#41|MEDIUM BRUSHED COPPER|19|4 +Brand#41|MEDIUM BRUSHED COPPER|23|4 +Brand#41|MEDIUM BRUSHED COPPER|36|4 +Brand#41|MEDIUM BRUSHED COPPER|45|4 +Brand#41|MEDIUM BRUSHED NICKEL|9|4 +Brand#41|MEDIUM BRUSHED NICKEL|19|4 +Brand#41|MEDIUM BRUSHED NICKEL|36|4 +Brand#41|MEDIUM BRUSHED NICKEL|45|4 +Brand#41|MEDIUM BRUSHED STEEL|3|4 +Brand#41|MEDIUM BRUSHED STEEL|14|4 +Brand#41|MEDIUM BRUSHED STEEL|23|4 +Brand#41|MEDIUM BRUSHED TIN|14|4 +Brand#41|MEDIUM BRUSHED TIN|36|4 +Brand#41|MEDIUM BRUSHED TIN|45|4 +Brand#41|MEDIUM BURNISHED BRASS|9|4 +Brand#41|MEDIUM BURNISHED BRASS|19|4 +Brand#41|MEDIUM BURNISHED BRASS|45|4 +Brand#41|MEDIUM BURNISHED COPPER|45|4 +Brand#41|MEDIUM BURNISHED COPPER|49|4 +Brand#41|MEDIUM BURNISHED NICKEL|14|4 +Brand#41|MEDIUM BURNISHED NICKEL|36|4 +Brand#41|MEDIUM BURNISHED STEEL|9|4 +Brand#41|MEDIUM BURNISHED STEEL|14|4 +Brand#41|MEDIUM BURNISHED STEEL|19|4 +Brand#41|MEDIUM BURNISHED STEEL|49|4 +Brand#41|MEDIUM BURNISHED TIN|9|4 +Brand#41|MEDIUM BURNISHED TIN|23|4 +Brand#41|MEDIUM BURNISHED TIN|36|4 +Brand#41|MEDIUM PLATED BRASS|3|4 +Brand#41|MEDIUM PLATED BRASS|9|4 +Brand#41|MEDIUM PLATED BRASS|14|4 +Brand#41|MEDIUM PLATED BRASS|36|4 +Brand#41|MEDIUM PLATED COPPER|3|4 +Brand#41|MEDIUM PLATED COPPER|14|4 +Brand#41|MEDIUM PLATED COPPER|36|4 +Brand#41|MEDIUM PLATED NICKEL|3|4 +Brand#41|MEDIUM PLATED NICKEL|14|4 +Brand#41|MEDIUM PLATED STEEL|14|4 +Brand#41|MEDIUM PLATED TIN|14|4 +Brand#41|MEDIUM PLATED TIN|19|4 +Brand#41|MEDIUM PLATED TIN|23|4 +Brand#41|MEDIUM PLATED TIN|49|4 +Brand#41|PROMO ANODIZED BRASS|19|4 +Brand#41|PROMO ANODIZED BRASS|23|4 +Brand#41|PROMO ANODIZED BRASS|45|4 +Brand#41|PROMO ANODIZED COPPER|9|4 +Brand#41|PROMO ANODIZED COPPER|19|4 +Brand#41|PROMO ANODIZED COPPER|23|4 +Brand#41|PROMO ANODIZED COPPER|49|4 +Brand#41|PROMO ANODIZED NICKEL|9|4 +Brand#41|PROMO ANODIZED NICKEL|14|4 +Brand#41|PROMO ANODIZED NICKEL|23|4 +Brand#41|PROMO ANODIZED NICKEL|36|4 +Brand#41|PROMO ANODIZED STEEL|3|4 +Brand#41|PROMO ANODIZED STEEL|36|4 +Brand#41|PROMO ANODIZED STEEL|45|4 +Brand#41|PROMO ANODIZED TIN|3|4 +Brand#41|PROMO ANODIZED TIN|14|4 +Brand#41|PROMO ANODIZED TIN|19|4 +Brand#41|PROMO ANODIZED TIN|23|4 +Brand#41|PROMO ANODIZED TIN|45|4 +Brand#41|PROMO ANODIZED TIN|49|4 +Brand#41|PROMO BRUSHED BRASS|45|4 +Brand#41|PROMO BRUSHED BRASS|49|4 +Brand#41|PROMO BRUSHED COPPER|3|4 +Brand#41|PROMO BRUSHED COPPER|9|4 +Brand#41|PROMO BRUSHED COPPER|23|4 +Brand#41|PROMO BRUSHED NICKEL|14|4 +Brand#41|PROMO BRUSHED NICKEL|19|4 +Brand#41|PROMO BRUSHED NICKEL|45|4 +Brand#41|PROMO BRUSHED STEEL|14|4 +Brand#41|PROMO BRUSHED TIN|3|4 +Brand#41|PROMO BRUSHED TIN|19|4 +Brand#41|PROMO BRUSHED TIN|23|4 +Brand#41|PROMO BRUSHED TIN|36|4 +Brand#41|PROMO BURNISHED BRASS|3|4 +Brand#41|PROMO BURNISHED BRASS|19|4 +Brand#41|PROMO BURNISHED BRASS|36|4 +Brand#41|PROMO BURNISHED BRASS|45|4 +Brand#41|PROMO BURNISHED BRASS|49|4 +Brand#41|PROMO BURNISHED COPPER|3|4 +Brand#41|PROMO BURNISHED COPPER|14|4 +Brand#41|PROMO BURNISHED NICKEL|3|4 +Brand#41|PROMO BURNISHED NICKEL|9|4 +Brand#41|PROMO BURNISHED NICKEL|45|4 +Brand#41|PROMO BURNISHED NICKEL|49|4 +Brand#41|PROMO BURNISHED STEEL|3|4 +Brand#41|PROMO BURNISHED STEEL|9|4 +Brand#41|PROMO BURNISHED STEEL|19|4 +Brand#41|PROMO BURNISHED STEEL|23|4 +Brand#41|PROMO BURNISHED STEEL|45|4 +Brand#41|PROMO BURNISHED STEEL|49|4 +Brand#41|PROMO BURNISHED TIN|9|4 +Brand#41|PROMO BURNISHED TIN|36|4 +Brand#41|PROMO BURNISHED TIN|45|4 +Brand#41|PROMO BURNISHED TIN|49|4 +Brand#41|PROMO PLATED BRASS|19|4 +Brand#41|PROMO PLATED BRASS|23|4 +Brand#41|PROMO PLATED BRASS|45|4 +Brand#41|PROMO PLATED COPPER|3|4 +Brand#41|PROMO PLATED COPPER|19|4 +Brand#41|PROMO PLATED NICKEL|23|4 +Brand#41|PROMO PLATED NICKEL|45|4 +Brand#41|PROMO PLATED STEEL|9|4 +Brand#41|PROMO PLATED STEEL|23|4 +Brand#41|PROMO PLATED TIN|9|4 +Brand#41|PROMO PLATED TIN|23|4 +Brand#41|PROMO POLISHED BRASS|3|4 +Brand#41|PROMO POLISHED BRASS|49|4 +Brand#41|PROMO POLISHED NICKEL|9|4 +Brand#41|PROMO POLISHED NICKEL|23|4 +Brand#41|PROMO POLISHED NICKEL|36|4 +Brand#41|PROMO POLISHED NICKEL|45|4 +Brand#41|PROMO POLISHED NICKEL|49|4 +Brand#41|PROMO POLISHED STEEL|14|4 +Brand#41|PROMO POLISHED STEEL|23|4 +Brand#41|PROMO POLISHED TIN|3|4 +Brand#41|PROMO POLISHED TIN|36|4 +Brand#41|PROMO POLISHED TIN|49|4 +Brand#41|SMALL ANODIZED BRASS|19|4 +Brand#41|SMALL ANODIZED BRASS|49|4 +Brand#41|SMALL ANODIZED COPPER|36|4 +Brand#41|SMALL ANODIZED COPPER|45|4 +Brand#41|SMALL ANODIZED NICKEL|3|4 +Brand#41|SMALL ANODIZED NICKEL|23|4 +Brand#41|SMALL ANODIZED NICKEL|49|4 +Brand#41|SMALL ANODIZED STEEL|19|4 +Brand#41|SMALL ANODIZED TIN|14|4 +Brand#41|SMALL ANODIZED TIN|36|4 +Brand#41|SMALL ANODIZED TIN|49|4 +Brand#41|SMALL BRUSHED BRASS|14|4 +Brand#41|SMALL BRUSHED BRASS|19|4 +Brand#41|SMALL BRUSHED BRASS|36|4 +Brand#41|SMALL BRUSHED COPPER|23|4 +Brand#41|SMALL BRUSHED COPPER|36|4 +Brand#41|SMALL BRUSHED NICKEL|3|4 +Brand#41|SMALL BRUSHED NICKEL|19|4 +Brand#41|SMALL BRUSHED NICKEL|49|4 +Brand#41|SMALL BRUSHED STEEL|9|4 +Brand#41|SMALL BRUSHED STEEL|14|4 +Brand#41|SMALL BRUSHED TIN|23|4 +Brand#41|SMALL BRUSHED TIN|45|4 +Brand#41|SMALL BRUSHED TIN|49|4 +Brand#41|SMALL BURNISHED BRASS|23|4 +Brand#41|SMALL BURNISHED BRASS|36|4 +Brand#41|SMALL BURNISHED COPPER|14|4 +Brand#41|SMALL BURNISHED COPPER|36|4 +Brand#41|SMALL BURNISHED COPPER|49|4 +Brand#41|SMALL BURNISHED NICKEL|14|4 +Brand#41|SMALL BURNISHED NICKEL|49|4 +Brand#41|SMALL BURNISHED STEEL|14|4 +Brand#41|SMALL BURNISHED STEEL|19|4 +Brand#41|SMALL BURNISHED STEEL|36|4 +Brand#41|SMALL BURNISHED TIN|9|4 +Brand#41|SMALL BURNISHED TIN|19|4 +Brand#41|SMALL BURNISHED TIN|36|4 +Brand#41|SMALL BURNISHED TIN|45|4 +Brand#41|SMALL BURNISHED TIN|49|4 +Brand#41|SMALL PLATED BRASS|19|4 +Brand#41|SMALL PLATED BRASS|45|4 +Brand#41|SMALL PLATED COPPER|3|4 +Brand#41|SMALL PLATED COPPER|36|4 +Brand#41|SMALL PLATED COPPER|45|4 +Brand#41|SMALL PLATED COPPER|49|4 +Brand#41|SMALL PLATED NICKEL|14|4 +Brand#41|SMALL PLATED NICKEL|45|4 +Brand#41|SMALL PLATED NICKEL|49|4 +Brand#41|SMALL PLATED STEEL|3|4 +Brand#41|SMALL PLATED STEEL|19|4 +Brand#41|SMALL PLATED STEEL|23|4 +Brand#41|SMALL PLATED TIN|14|4 +Brand#41|SMALL PLATED TIN|36|4 +Brand#41|SMALL PLATED TIN|45|4 +Brand#41|SMALL POLISHED BRASS|3|4 +Brand#41|SMALL POLISHED BRASS|9|4 +Brand#41|SMALL POLISHED BRASS|14|4 +Brand#41|SMALL POLISHED BRASS|23|4 +Brand#41|SMALL POLISHED COPPER|9|4 +Brand#41|SMALL POLISHED COPPER|19|4 +Brand#41|SMALL POLISHED COPPER|49|4 +Brand#41|SMALL POLISHED NICKEL|36|4 +Brand#41|SMALL POLISHED NICKEL|45|4 +Brand#41|SMALL POLISHED STEEL|3|4 +Brand#41|SMALL POLISHED STEEL|9|4 +Brand#41|SMALL POLISHED STEEL|14|4 +Brand#41|SMALL POLISHED STEEL|19|4 +Brand#41|SMALL POLISHED STEEL|23|4 +Brand#41|SMALL POLISHED TIN|3|4 +Brand#41|STANDARD ANODIZED BRASS|9|4 +Brand#41|STANDARD ANODIZED BRASS|19|4 +Brand#41|STANDARD ANODIZED BRASS|23|4 +Brand#41|STANDARD ANODIZED BRASS|45|4 +Brand#41|STANDARD ANODIZED BRASS|49|4 +Brand#41|STANDARD ANODIZED COPPER|19|4 +Brand#41|STANDARD ANODIZED COPPER|45|4 +Brand#41|STANDARD ANODIZED NICKEL|14|4 +Brand#41|STANDARD ANODIZED NICKEL|19|4 +Brand#41|STANDARD ANODIZED STEEL|3|4 +Brand#41|STANDARD ANODIZED STEEL|9|4 +Brand#41|STANDARD ANODIZED STEEL|14|4 +Brand#41|STANDARD ANODIZED STEEL|19|4 +Brand#41|STANDARD ANODIZED STEEL|36|4 +Brand#41|STANDARD ANODIZED TIN|9|4 +Brand#41|STANDARD ANODIZED TIN|14|4 +Brand#41|STANDARD ANODIZED TIN|36|4 +Brand#41|STANDARD ANODIZED TIN|45|4 +Brand#41|STANDARD ANODIZED TIN|49|4 +Brand#41|STANDARD BRUSHED BRASS|3|4 +Brand#41|STANDARD BRUSHED BRASS|14|4 +Brand#41|STANDARD BRUSHED BRASS|19|4 +Brand#41|STANDARD BRUSHED BRASS|23|4 +Brand#41|STANDARD BRUSHED BRASS|45|4 +Brand#41|STANDARD BRUSHED BRASS|49|4 +Brand#41|STANDARD BRUSHED COPPER|14|4 +Brand#41|STANDARD BRUSHED COPPER|23|4 +Brand#41|STANDARD BRUSHED COPPER|36|4 +Brand#41|STANDARD BRUSHED COPPER|49|4 +Brand#41|STANDARD BRUSHED NICKEL|23|4 +Brand#41|STANDARD BRUSHED NICKEL|36|4 +Brand#41|STANDARD BRUSHED STEEL|9|4 +Brand#41|STANDARD BRUSHED STEEL|23|4 +Brand#41|STANDARD BRUSHED STEEL|36|4 +Brand#41|STANDARD BRUSHED TIN|14|4 +Brand#41|STANDARD BURNISHED BRASS|19|4 +Brand#41|STANDARD BURNISHED BRASS|23|4 +Brand#41|STANDARD BURNISHED BRASS|45|4 +Brand#41|STANDARD BURNISHED BRASS|49|4 +Brand#41|STANDARD BURNISHED COPPER|3|4 +Brand#41|STANDARD BURNISHED COPPER|23|4 +Brand#41|STANDARD BURNISHED COPPER|45|4 +Brand#41|STANDARD BURNISHED COPPER|49|4 +Brand#41|STANDARD BURNISHED NICKEL|3|4 +Brand#41|STANDARD BURNISHED NICKEL|9|4 +Brand#41|STANDARD BURNISHED NICKEL|45|4 +Brand#41|STANDARD BURNISHED STEEL|19|4 +Brand#41|STANDARD BURNISHED STEEL|36|4 +Brand#41|STANDARD BURNISHED STEEL|45|4 +Brand#41|STANDARD BURNISHED TIN|9|4 +Brand#41|STANDARD BURNISHED TIN|49|4 +Brand#41|STANDARD PLATED BRASS|3|4 +Brand#41|STANDARD PLATED BRASS|23|4 +Brand#41|STANDARD PLATED COPPER|14|4 +Brand#41|STANDARD PLATED COPPER|19|4 +Brand#41|STANDARD PLATED COPPER|23|4 +Brand#41|STANDARD PLATED NICKEL|3|4 +Brand#41|STANDARD PLATED NICKEL|36|4 +Brand#41|STANDARD PLATED STEEL|23|4 +Brand#41|STANDARD PLATED STEEL|45|4 +Brand#41|STANDARD PLATED TIN|19|4 +Brand#41|STANDARD PLATED TIN|23|4 +Brand#41|STANDARD PLATED TIN|36|4 +Brand#41|STANDARD POLISHED BRASS|9|4 +Brand#41|STANDARD POLISHED BRASS|23|4 +Brand#41|STANDARD POLISHED BRASS|45|4 +Brand#41|STANDARD POLISHED BRASS|49|4 +Brand#41|STANDARD POLISHED COPPER|19|4 +Brand#41|STANDARD POLISHED COPPER|45|4 +Brand#41|STANDARD POLISHED COPPER|49|4 +Brand#41|STANDARD POLISHED NICKEL|9|4 +Brand#41|STANDARD POLISHED NICKEL|19|4 +Brand#41|STANDARD POLISHED NICKEL|23|4 +Brand#41|STANDARD POLISHED NICKEL|49|4 +Brand#41|STANDARD POLISHED STEEL|9|4 +Brand#41|STANDARD POLISHED STEEL|14|4 +Brand#41|STANDARD POLISHED STEEL|19|4 +Brand#41|STANDARD POLISHED STEEL|23|4 +Brand#41|STANDARD POLISHED STEEL|49|4 +Brand#41|STANDARD POLISHED TIN|3|4 +Brand#41|STANDARD POLISHED TIN|9|4 +Brand#41|STANDARD POLISHED TIN|49|4 +Brand#42|ECONOMY ANODIZED BRASS|3|4 +Brand#42|ECONOMY ANODIZED BRASS|45|4 +Brand#42|ECONOMY ANODIZED COPPER|3|4 +Brand#42|ECONOMY ANODIZED COPPER|9|4 +Brand#42|ECONOMY ANODIZED COPPER|19|4 +Brand#42|ECONOMY ANODIZED NICKEL|9|4 +Brand#42|ECONOMY ANODIZED NICKEL|23|4 +Brand#42|ECONOMY ANODIZED STEEL|14|4 +Brand#42|ECONOMY ANODIZED STEEL|36|4 +Brand#42|ECONOMY ANODIZED TIN|3|4 +Brand#42|ECONOMY ANODIZED TIN|9|4 +Brand#42|ECONOMY BRUSHED BRASS|14|4 +Brand#42|ECONOMY BRUSHED BRASS|19|4 +Brand#42|ECONOMY BRUSHED BRASS|36|4 +Brand#42|ECONOMY BRUSHED BRASS|45|4 +Brand#42|ECONOMY BRUSHED COPPER|14|4 +Brand#42|ECONOMY BRUSHED COPPER|19|4 +Brand#42|ECONOMY BRUSHED COPPER|23|4 +Brand#42|ECONOMY BRUSHED COPPER|45|4 +Brand#42|ECONOMY BRUSHED NICKEL|23|4 +Brand#42|ECONOMY BRUSHED NICKEL|36|4 +Brand#42|ECONOMY BRUSHED STEEL|36|4 +Brand#42|ECONOMY BRUSHED TIN|23|4 +Brand#42|ECONOMY BURNISHED BRASS|9|4 +Brand#42|ECONOMY BURNISHED BRASS|19|4 +Brand#42|ECONOMY BURNISHED BRASS|36|4 +Brand#42|ECONOMY BURNISHED BRASS|45|4 +Brand#42|ECONOMY BURNISHED BRASS|49|4 +Brand#42|ECONOMY BURNISHED COPPER|9|4 +Brand#42|ECONOMY BURNISHED COPPER|14|4 +Brand#42|ECONOMY BURNISHED COPPER|23|4 +Brand#42|ECONOMY BURNISHED COPPER|36|4 +Brand#42|ECONOMY BURNISHED COPPER|45|4 +Brand#42|ECONOMY BURNISHED NICKEL|9|4 +Brand#42|ECONOMY BURNISHED NICKEL|14|4 +Brand#42|ECONOMY BURNISHED NICKEL|19|4 +Brand#42|ECONOMY BURNISHED NICKEL|36|4 +Brand#42|ECONOMY BURNISHED NICKEL|45|4 +Brand#42|ECONOMY BURNISHED STEEL|3|4 +Brand#42|ECONOMY BURNISHED STEEL|36|4 +Brand#42|ECONOMY BURNISHED TIN|3|4 +Brand#42|ECONOMY PLATED BRASS|19|4 +Brand#42|ECONOMY PLATED BRASS|36|4 +Brand#42|ECONOMY PLATED BRASS|45|4 +Brand#42|ECONOMY PLATED COPPER|19|4 +Brand#42|ECONOMY PLATED COPPER|45|4 +Brand#42|ECONOMY PLATED COPPER|49|4 +Brand#42|ECONOMY PLATED NICKEL|3|4 +Brand#42|ECONOMY PLATED NICKEL|14|4 +Brand#42|ECONOMY PLATED NICKEL|23|4 +Brand#42|ECONOMY PLATED NICKEL|45|4 +Brand#42|ECONOMY PLATED STEEL|3|4 +Brand#42|ECONOMY PLATED STEEL|23|4 +Brand#42|ECONOMY PLATED TIN|36|4 +Brand#42|ECONOMY POLISHED BRASS|3|4 +Brand#42|ECONOMY POLISHED BRASS|14|4 +Brand#42|ECONOMY POLISHED BRASS|19|4 +Brand#42|ECONOMY POLISHED BRASS|23|4 +Brand#42|ECONOMY POLISHED BRASS|36|4 +Brand#42|ECONOMY POLISHED BRASS|45|4 +Brand#42|ECONOMY POLISHED BRASS|49|4 +Brand#42|ECONOMY POLISHED COPPER|14|4 +Brand#42|ECONOMY POLISHED COPPER|19|4 +Brand#42|ECONOMY POLISHED COPPER|49|4 +Brand#42|ECONOMY POLISHED NICKEL|3|4 +Brand#42|ECONOMY POLISHED NICKEL|9|4 +Brand#42|ECONOMY POLISHED NICKEL|19|4 +Brand#42|ECONOMY POLISHED STEEL|3|4 +Brand#42|ECONOMY POLISHED STEEL|19|4 +Brand#42|ECONOMY POLISHED STEEL|45|4 +Brand#42|ECONOMY POLISHED STEEL|49|4 +Brand#42|ECONOMY POLISHED TIN|9|4 +Brand#42|ECONOMY POLISHED TIN|14|4 +Brand#42|ECONOMY POLISHED TIN|19|4 +Brand#42|ECONOMY POLISHED TIN|45|4 +Brand#42|ECONOMY POLISHED TIN|49|4 +Brand#42|LARGE ANODIZED BRASS|14|4 +Brand#42|LARGE ANODIZED BRASS|36|4 +Brand#42|LARGE ANODIZED COPPER|9|4 +Brand#42|LARGE ANODIZED COPPER|19|4 +Brand#42|LARGE ANODIZED COPPER|45|4 +Brand#42|LARGE ANODIZED NICKEL|14|4 +Brand#42|LARGE ANODIZED NICKEL|19|4 +Brand#42|LARGE ANODIZED NICKEL|23|4 +Brand#42|LARGE ANODIZED NICKEL|36|4 +Brand#42|LARGE ANODIZED STEEL|19|4 +Brand#42|LARGE ANODIZED STEEL|23|4 +Brand#42|LARGE ANODIZED STEEL|45|4 +Brand#42|LARGE ANODIZED STEEL|49|4 +Brand#42|LARGE ANODIZED TIN|19|4 +Brand#42|LARGE ANODIZED TIN|36|4 +Brand#42|LARGE ANODIZED TIN|49|4 +Brand#42|LARGE BRUSHED BRASS|9|4 +Brand#42|LARGE BRUSHED BRASS|36|4 +Brand#42|LARGE BRUSHED COPPER|14|4 +Brand#42|LARGE BRUSHED COPPER|23|4 +Brand#42|LARGE BRUSHED COPPER|36|4 +Brand#42|LARGE BRUSHED COPPER|45|4 +Brand#42|LARGE BRUSHED NICKEL|3|4 +Brand#42|LARGE BRUSHED NICKEL|9|4 +Brand#42|LARGE BRUSHED NICKEL|14|4 +Brand#42|LARGE BRUSHED NICKEL|45|4 +Brand#42|LARGE BRUSHED STEEL|3|4 +Brand#42|LARGE BRUSHED STEEL|36|4 +Brand#42|LARGE BRUSHED STEEL|49|4 +Brand#42|LARGE BRUSHED TIN|9|4 +Brand#42|LARGE BRUSHED TIN|14|4 +Brand#42|LARGE BRUSHED TIN|36|4 +Brand#42|LARGE BURNISHED BRASS|19|4 +Brand#42|LARGE BURNISHED BRASS|23|4 +Brand#42|LARGE BURNISHED BRASS|36|4 +Brand#42|LARGE BURNISHED BRASS|45|4 +Brand#42|LARGE BURNISHED COPPER|3|4 +Brand#42|LARGE BURNISHED COPPER|23|4 +Brand#42|LARGE BURNISHED COPPER|45|4 +Brand#42|LARGE BURNISHED COPPER|49|4 +Brand#42|LARGE BURNISHED NICKEL|36|4 +Brand#42|LARGE BURNISHED NICKEL|45|4 +Brand#42|LARGE BURNISHED STEEL|14|4 +Brand#42|LARGE BURNISHED STEEL|19|4 +Brand#42|LARGE BURNISHED STEEL|45|4 +Brand#42|LARGE BURNISHED TIN|3|4 +Brand#42|LARGE BURNISHED TIN|14|4 +Brand#42|LARGE BURNISHED TIN|36|4 +Brand#42|LARGE PLATED BRASS|45|4 +Brand#42|LARGE PLATED BRASS|49|4 +Brand#42|LARGE PLATED COPPER|3|4 +Brand#42|LARGE PLATED COPPER|23|4 +Brand#42|LARGE PLATED NICKEL|14|4 +Brand#42|LARGE PLATED NICKEL|19|4 +Brand#42|LARGE PLATED NICKEL|36|4 +Brand#42|LARGE PLATED NICKEL|49|4 +Brand#42|LARGE PLATED STEEL|3|4 +Brand#42|LARGE PLATED STEEL|14|4 +Brand#42|LARGE PLATED STEEL|19|4 +Brand#42|LARGE PLATED STEEL|23|4 +Brand#42|LARGE PLATED STEEL|36|4 +Brand#42|LARGE PLATED STEEL|49|4 +Brand#42|LARGE PLATED TIN|23|4 +Brand#42|LARGE PLATED TIN|36|4 +Brand#42|LARGE POLISHED BRASS|3|4 +Brand#42|LARGE POLISHED BRASS|9|4 +Brand#42|LARGE POLISHED BRASS|23|4 +Brand#42|LARGE POLISHED BRASS|45|4 +Brand#42|LARGE POLISHED BRASS|49|4 +Brand#42|LARGE POLISHED COPPER|9|4 +Brand#42|LARGE POLISHED COPPER|19|4 +Brand#42|LARGE POLISHED COPPER|45|4 +Brand#42|LARGE POLISHED NICKEL|3|4 +Brand#42|LARGE POLISHED NICKEL|9|4 +Brand#42|LARGE POLISHED NICKEL|14|4 +Brand#42|LARGE POLISHED NICKEL|19|4 +Brand#42|LARGE POLISHED NICKEL|45|4 +Brand#42|LARGE POLISHED STEEL|19|4 +Brand#42|LARGE POLISHED STEEL|23|4 +Brand#42|LARGE POLISHED STEEL|49|4 +Brand#42|LARGE POLISHED TIN|36|4 +Brand#42|MEDIUM ANODIZED BRASS|14|4 +Brand#42|MEDIUM ANODIZED BRASS|23|4 +Brand#42|MEDIUM ANODIZED BRASS|36|4 +Brand#42|MEDIUM ANODIZED BRASS|45|4 +Brand#42|MEDIUM ANODIZED COPPER|9|4 +Brand#42|MEDIUM ANODIZED COPPER|14|4 +Brand#42|MEDIUM ANODIZED NICKEL|3|4 +Brand#42|MEDIUM ANODIZED NICKEL|9|4 +Brand#42|MEDIUM ANODIZED NICKEL|14|4 +Brand#42|MEDIUM ANODIZED NICKEL|23|4 +Brand#42|MEDIUM ANODIZED NICKEL|45|4 +Brand#42|MEDIUM ANODIZED STEEL|9|4 +Brand#42|MEDIUM ANODIZED TIN|3|4 +Brand#42|MEDIUM ANODIZED TIN|9|4 +Brand#42|MEDIUM ANODIZED TIN|23|4 +Brand#42|MEDIUM BRUSHED BRASS|14|4 +Brand#42|MEDIUM BRUSHED BRASS|23|4 +Brand#42|MEDIUM BRUSHED BRASS|36|4 +Brand#42|MEDIUM BRUSHED BRASS|45|4 +Brand#42|MEDIUM BRUSHED COPPER|23|4 +Brand#42|MEDIUM BRUSHED COPPER|36|4 +Brand#42|MEDIUM BRUSHED COPPER|45|4 +Brand#42|MEDIUM BRUSHED NICKEL|23|4 +Brand#42|MEDIUM BRUSHED NICKEL|45|4 +Brand#42|MEDIUM BRUSHED STEEL|9|4 +Brand#42|MEDIUM BRUSHED STEEL|23|4 +Brand#42|MEDIUM BRUSHED TIN|3|4 +Brand#42|MEDIUM BRUSHED TIN|9|4 +Brand#42|MEDIUM BRUSHED TIN|36|4 +Brand#42|MEDIUM BRUSHED TIN|45|4 +Brand#42|MEDIUM BURNISHED BRASS|3|4 +Brand#42|MEDIUM BURNISHED BRASS|9|4 +Brand#42|MEDIUM BURNISHED BRASS|19|4 +Brand#42|MEDIUM BURNISHED BRASS|23|4 +Brand#42|MEDIUM BURNISHED BRASS|49|4 +Brand#42|MEDIUM BURNISHED COPPER|19|4 +Brand#42|MEDIUM BURNISHED COPPER|36|4 +Brand#42|MEDIUM BURNISHED NICKEL|45|4 +Brand#42|MEDIUM BURNISHED NICKEL|49|4 +Brand#42|MEDIUM BURNISHED STEEL|45|4 +Brand#42|MEDIUM BURNISHED TIN|9|4 +Brand#42|MEDIUM BURNISHED TIN|23|4 +Brand#42|MEDIUM BURNISHED TIN|45|4 +Brand#42|MEDIUM PLATED BRASS|3|4 +Brand#42|MEDIUM PLATED BRASS|14|4 +Brand#42|MEDIUM PLATED BRASS|23|4 +Brand#42|MEDIUM PLATED COPPER|9|4 +Brand#42|MEDIUM PLATED COPPER|14|4 +Brand#42|MEDIUM PLATED COPPER|19|4 +Brand#42|MEDIUM PLATED NICKEL|3|4 +Brand#42|MEDIUM PLATED NICKEL|45|4 +Brand#42|MEDIUM PLATED NICKEL|49|4 +Brand#42|MEDIUM PLATED STEEL|23|4 +Brand#42|MEDIUM PLATED STEEL|49|4 +Brand#42|MEDIUM PLATED TIN|3|4 +Brand#42|MEDIUM PLATED TIN|19|4 +Brand#42|MEDIUM PLATED TIN|23|4 +Brand#42|PROMO ANODIZED BRASS|3|4 +Brand#42|PROMO ANODIZED BRASS|23|4 +Brand#42|PROMO ANODIZED BRASS|49|4 +Brand#42|PROMO ANODIZED COPPER|19|4 +Brand#42|PROMO ANODIZED COPPER|36|4 +Brand#42|PROMO ANODIZED COPPER|49|4 +Brand#42|PROMO ANODIZED NICKEL|3|4 +Brand#42|PROMO ANODIZED NICKEL|19|4 +Brand#42|PROMO ANODIZED NICKEL|36|4 +Brand#42|PROMO ANODIZED STEEL|9|4 +Brand#42|PROMO ANODIZED STEEL|14|4 +Brand#42|PROMO ANODIZED STEEL|45|4 +Brand#42|PROMO ANODIZED TIN|9|4 +Brand#42|PROMO ANODIZED TIN|19|4 +Brand#42|PROMO ANODIZED TIN|45|4 +Brand#42|PROMO BRUSHED BRASS|3|4 +Brand#42|PROMO BRUSHED BRASS|14|4 +Brand#42|PROMO BRUSHED BRASS|23|4 +Brand#42|PROMO BRUSHED COPPER|3|4 +Brand#42|PROMO BRUSHED COPPER|19|4 +Brand#42|PROMO BRUSHED COPPER|23|4 +Brand#42|PROMO BRUSHED COPPER|36|4 +Brand#42|PROMO BRUSHED COPPER|45|4 +Brand#42|PROMO BRUSHED COPPER|49|4 +Brand#42|PROMO BRUSHED NICKEL|9|4 +Brand#42|PROMO BRUSHED NICKEL|14|4 +Brand#42|PROMO BRUSHED STEEL|3|4 +Brand#42|PROMO BRUSHED STEEL|14|4 +Brand#42|PROMO BRUSHED STEEL|49|4 +Brand#42|PROMO BRUSHED TIN|9|4 +Brand#42|PROMO BRUSHED TIN|23|4 +Brand#42|PROMO BRUSHED TIN|49|4 +Brand#42|PROMO BURNISHED BRASS|9|4 +Brand#42|PROMO BURNISHED BRASS|36|4 +Brand#42|PROMO BURNISHED COPPER|3|4 +Brand#42|PROMO BURNISHED COPPER|14|4 +Brand#42|PROMO BURNISHED COPPER|19|4 +Brand#42|PROMO BURNISHED NICKEL|9|4 +Brand#42|PROMO BURNISHED NICKEL|19|4 +Brand#42|PROMO BURNISHED NICKEL|49|4 +Brand#42|PROMO BURNISHED STEEL|3|4 +Brand#42|PROMO BURNISHED STEEL|9|4 +Brand#42|PROMO BURNISHED STEEL|14|4 +Brand#42|PROMO BURNISHED STEEL|36|4 +Brand#42|PROMO BURNISHED STEEL|45|4 +Brand#42|PROMO BURNISHED TIN|3|4 +Brand#42|PROMO BURNISHED TIN|19|4 +Brand#42|PROMO BURNISHED TIN|36|4 +Brand#42|PROMO PLATED BRASS|45|4 +Brand#42|PROMO PLATED BRASS|49|4 +Brand#42|PROMO PLATED COPPER|3|4 +Brand#42|PROMO PLATED COPPER|14|4 +Brand#42|PROMO PLATED COPPER|23|4 +Brand#42|PROMO PLATED COPPER|49|4 +Brand#42|PROMO PLATED NICKEL|3|4 +Brand#42|PROMO PLATED NICKEL|9|4 +Brand#42|PROMO PLATED NICKEL|14|4 +Brand#42|PROMO PLATED NICKEL|19|4 +Brand#42|PROMO PLATED NICKEL|49|4 +Brand#42|PROMO PLATED STEEL|3|4 +Brand#42|PROMO PLATED STEEL|9|4 +Brand#42|PROMO PLATED STEEL|36|4 +Brand#42|PROMO PLATED TIN|3|4 +Brand#42|PROMO POLISHED BRASS|3|4 +Brand#42|PROMO POLISHED COPPER|9|4 +Brand#42|PROMO POLISHED COPPER|23|4 +Brand#42|PROMO POLISHED COPPER|45|4 +Brand#42|PROMO POLISHED NICKEL|14|4 +Brand#42|PROMO POLISHED NICKEL|23|4 +Brand#42|PROMO POLISHED NICKEL|36|4 +Brand#42|PROMO POLISHED NICKEL|45|4 +Brand#42|PROMO POLISHED NICKEL|49|4 +Brand#42|PROMO POLISHED TIN|14|4 +Brand#42|PROMO POLISHED TIN|19|4 +Brand#42|PROMO POLISHED TIN|23|4 +Brand#42|PROMO POLISHED TIN|36|4 +Brand#42|PROMO POLISHED TIN|45|4 +Brand#42|SMALL ANODIZED BRASS|9|4 +Brand#42|SMALL ANODIZED BRASS|14|4 +Brand#42|SMALL ANODIZED BRASS|49|4 +Brand#42|SMALL ANODIZED COPPER|3|4 +Brand#42|SMALL ANODIZED COPPER|9|4 +Brand#42|SMALL ANODIZED COPPER|45|4 +Brand#42|SMALL ANODIZED NICKEL|3|4 +Brand#42|SMALL ANODIZED STEEL|14|4 +Brand#42|SMALL ANODIZED STEEL|45|4 +Brand#42|SMALL ANODIZED TIN|9|4 +Brand#42|SMALL ANODIZED TIN|14|4 +Brand#42|SMALL BRUSHED BRASS|3|4 +Brand#42|SMALL BRUSHED BRASS|9|4 +Brand#42|SMALL BRUSHED BRASS|19|4 +Brand#42|SMALL BRUSHED BRASS|23|4 +Brand#42|SMALL BRUSHED BRASS|49|4 +Brand#42|SMALL BRUSHED COPPER|23|4 +Brand#42|SMALL BRUSHED COPPER|45|4 +Brand#42|SMALL BRUSHED NICKEL|19|4 +Brand#42|SMALL BRUSHED NICKEL|36|4 +Brand#42|SMALL BRUSHED NICKEL|45|4 +Brand#42|SMALL BRUSHED TIN|3|4 +Brand#42|SMALL BRUSHED TIN|19|4 +Brand#42|SMALL BRUSHED TIN|36|4 +Brand#42|SMALL BURNISHED BRASS|14|4 +Brand#42|SMALL BURNISHED BRASS|19|4 +Brand#42|SMALL BURNISHED BRASS|45|4 +Brand#42|SMALL BURNISHED COPPER|9|4 +Brand#42|SMALL BURNISHED COPPER|14|4 +Brand#42|SMALL BURNISHED COPPER|19|4 +Brand#42|SMALL BURNISHED COPPER|23|4 +Brand#42|SMALL BURNISHED COPPER|49|4 +Brand#42|SMALL BURNISHED NICKEL|9|4 +Brand#42|SMALL BURNISHED NICKEL|14|4 +Brand#42|SMALL BURNISHED STEEL|9|4 +Brand#42|SMALL BURNISHED STEEL|36|4 +Brand#42|SMALL BURNISHED STEEL|45|4 +Brand#42|SMALL BURNISHED STEEL|49|4 +Brand#42|SMALL BURNISHED TIN|3|4 +Brand#42|SMALL BURNISHED TIN|45|4 +Brand#42|SMALL PLATED BRASS|3|4 +Brand#42|SMALL PLATED BRASS|9|4 +Brand#42|SMALL PLATED BRASS|23|4 +Brand#42|SMALL PLATED BRASS|45|4 +Brand#42|SMALL PLATED BRASS|49|4 +Brand#42|SMALL PLATED COPPER|3|4 +Brand#42|SMALL PLATED COPPER|45|4 +Brand#42|SMALL PLATED NICKEL|9|4 +Brand#42|SMALL PLATED NICKEL|14|4 +Brand#42|SMALL PLATED NICKEL|36|4 +Brand#42|SMALL PLATED NICKEL|45|4 +Brand#42|SMALL PLATED STEEL|9|4 +Brand#42|SMALL PLATED STEEL|14|4 +Brand#42|SMALL PLATED STEEL|45|4 +Brand#42|SMALL PLATED TIN|49|4 +Brand#42|SMALL POLISHED BRASS|14|4 +Brand#42|SMALL POLISHED BRASS|19|4 +Brand#42|SMALL POLISHED BRASS|49|4 +Brand#42|SMALL POLISHED COPPER|9|4 +Brand#42|SMALL POLISHED COPPER|19|4 +Brand#42|SMALL POLISHED COPPER|49|4 +Brand#42|SMALL POLISHED NICKEL|3|4 +Brand#42|SMALL POLISHED NICKEL|36|4 +Brand#42|SMALL POLISHED NICKEL|49|4 +Brand#42|SMALL POLISHED STEEL|3|4 +Brand#42|SMALL POLISHED STEEL|19|4 +Brand#42|SMALL POLISHED TIN|3|4 +Brand#42|SMALL POLISHED TIN|19|4 +Brand#42|STANDARD ANODIZED BRASS|3|4 +Brand#42|STANDARD ANODIZED BRASS|14|4 +Brand#42|STANDARD ANODIZED BRASS|19|4 +Brand#42|STANDARD ANODIZED BRASS|49|4 +Brand#42|STANDARD ANODIZED COPPER|3|4 +Brand#42|STANDARD ANODIZED COPPER|9|4 +Brand#42|STANDARD ANODIZED COPPER|23|4 +Brand#42|STANDARD ANODIZED COPPER|49|4 +Brand#42|STANDARD ANODIZED NICKEL|3|4 +Brand#42|STANDARD ANODIZED NICKEL|23|4 +Brand#42|STANDARD ANODIZED NICKEL|36|4 +Brand#42|STANDARD ANODIZED NICKEL|45|4 +Brand#42|STANDARD ANODIZED NICKEL|49|4 +Brand#42|STANDARD ANODIZED TIN|14|4 +Brand#42|STANDARD ANODIZED TIN|19|4 +Brand#42|STANDARD ANODIZED TIN|49|4 +Brand#42|STANDARD BRUSHED BRASS|14|4 +Brand#42|STANDARD BRUSHED BRASS|45|4 +Brand#42|STANDARD BRUSHED COPPER|9|4 +Brand#42|STANDARD BRUSHED COPPER|14|4 +Brand#42|STANDARD BRUSHED COPPER|19|4 +Brand#42|STANDARD BRUSHED COPPER|45|4 +Brand#42|STANDARD BRUSHED NICKEL|19|4 +Brand#42|STANDARD BRUSHED STEEL|3|4 +Brand#42|STANDARD BRUSHED STEEL|36|4 +Brand#42|STANDARD BRUSHED STEEL|45|4 +Brand#42|STANDARD BRUSHED TIN|14|4 +Brand#42|STANDARD BRUSHED TIN|19|4 +Brand#42|STANDARD BURNISHED BRASS|19|4 +Brand#42|STANDARD BURNISHED BRASS|23|4 +Brand#42|STANDARD BURNISHED COPPER|3|4 +Brand#42|STANDARD BURNISHED COPPER|9|4 +Brand#42|STANDARD BURNISHED COPPER|14|4 +Brand#42|STANDARD BURNISHED COPPER|19|4 +Brand#42|STANDARD BURNISHED COPPER|23|4 +Brand#42|STANDARD BURNISHED NICKEL|9|4 +Brand#42|STANDARD BURNISHED NICKEL|19|4 +Brand#42|STANDARD BURNISHED NICKEL|36|4 +Brand#42|STANDARD BURNISHED NICKEL|45|4 +Brand#42|STANDARD BURNISHED STEEL|3|4 +Brand#42|STANDARD BURNISHED STEEL|14|4 +Brand#42|STANDARD BURNISHED STEEL|23|4 +Brand#42|STANDARD BURNISHED STEEL|45|4 +Brand#42|STANDARD BURNISHED TIN|3|4 +Brand#42|STANDARD BURNISHED TIN|14|4 +Brand#42|STANDARD BURNISHED TIN|19|4 +Brand#42|STANDARD BURNISHED TIN|36|4 +Brand#42|STANDARD PLATED BRASS|3|4 +Brand#42|STANDARD PLATED BRASS|9|4 +Brand#42|STANDARD PLATED BRASS|19|4 +Brand#42|STANDARD PLATED BRASS|23|4 +Brand#42|STANDARD PLATED BRASS|36|4 +Brand#42|STANDARD PLATED BRASS|49|4 +Brand#42|STANDARD PLATED COPPER|36|4 +Brand#42|STANDARD PLATED NICKEL|9|4 +Brand#42|STANDARD PLATED NICKEL|36|4 +Brand#42|STANDARD PLATED NICKEL|49|4 +Brand#42|STANDARD PLATED STEEL|3|4 +Brand#42|STANDARD PLATED STEEL|9|4 +Brand#42|STANDARD PLATED STEEL|23|4 +Brand#42|STANDARD PLATED TIN|19|4 +Brand#42|STANDARD POLISHED BRASS|3|4 +Brand#42|STANDARD POLISHED BRASS|14|4 +Brand#42|STANDARD POLISHED BRASS|45|4 +Brand#42|STANDARD POLISHED BRASS|49|4 +Brand#42|STANDARD POLISHED COPPER|3|4 +Brand#42|STANDARD POLISHED COPPER|9|4 +Brand#42|STANDARD POLISHED COPPER|36|4 +Brand#42|STANDARD POLISHED COPPER|45|4 +Brand#42|STANDARD POLISHED NICKEL|36|4 +Brand#42|STANDARD POLISHED NICKEL|45|4 +Brand#42|STANDARD POLISHED NICKEL|49|4 +Brand#42|STANDARD POLISHED STEEL|45|4 +Brand#42|STANDARD POLISHED TIN|3|4 +Brand#42|STANDARD POLISHED TIN|9|4 +Brand#42|STANDARD POLISHED TIN|19|4 +Brand#43|ECONOMY ANODIZED BRASS|19|4 +Brand#43|ECONOMY ANODIZED COPPER|23|4 +Brand#43|ECONOMY ANODIZED COPPER|36|4 +Brand#43|ECONOMY ANODIZED COPPER|49|4 +Brand#43|ECONOMY ANODIZED NICKEL|9|4 +Brand#43|ECONOMY ANODIZED NICKEL|14|4 +Brand#43|ECONOMY ANODIZED NICKEL|19|4 +Brand#43|ECONOMY ANODIZED NICKEL|23|4 +Brand#43|ECONOMY ANODIZED NICKEL|45|4 +Brand#43|ECONOMY ANODIZED STEEL|3|4 +Brand#43|ECONOMY ANODIZED STEEL|9|4 +Brand#43|ECONOMY ANODIZED STEEL|14|4 +Brand#43|ECONOMY ANODIZED STEEL|19|4 +Brand#43|ECONOMY ANODIZED TIN|19|4 +Brand#43|ECONOMY ANODIZED TIN|23|4 +Brand#43|ECONOMY ANODIZED TIN|36|4 +Brand#43|ECONOMY BRUSHED BRASS|3|4 +Brand#43|ECONOMY BRUSHED BRASS|23|4 +Brand#43|ECONOMY BRUSHED BRASS|36|4 +Brand#43|ECONOMY BRUSHED BRASS|49|4 +Brand#43|ECONOMY BRUSHED COPPER|14|4 +Brand#43|ECONOMY BRUSHED COPPER|19|4 +Brand#43|ECONOMY BRUSHED COPPER|36|4 +Brand#43|ECONOMY BRUSHED NICKEL|23|4 +Brand#43|ECONOMY BRUSHED NICKEL|36|4 +Brand#43|ECONOMY BRUSHED NICKEL|45|4 +Brand#43|ECONOMY BRUSHED STEEL|19|4 +Brand#43|ECONOMY BRUSHED TIN|3|4 +Brand#43|ECONOMY BRUSHED TIN|14|4 +Brand#43|ECONOMY BRUSHED TIN|19|4 +Brand#43|ECONOMY BRUSHED TIN|23|4 +Brand#43|ECONOMY BURNISHED BRASS|9|4 +Brand#43|ECONOMY BURNISHED BRASS|14|4 +Brand#43|ECONOMY BURNISHED BRASS|23|4 +Brand#43|ECONOMY BURNISHED BRASS|36|4 +Brand#43|ECONOMY BURNISHED BRASS|45|4 +Brand#43|ECONOMY BURNISHED COPPER|3|4 +Brand#43|ECONOMY BURNISHED COPPER|19|4 +Brand#43|ECONOMY BURNISHED COPPER|23|4 +Brand#43|ECONOMY BURNISHED COPPER|45|4 +Brand#43|ECONOMY BURNISHED NICKEL|49|4 +Brand#43|ECONOMY BURNISHED STEEL|14|4 +Brand#43|ECONOMY BURNISHED STEEL|45|4 +Brand#43|ECONOMY BURNISHED TIN|14|4 +Brand#43|ECONOMY BURNISHED TIN|36|4 +Brand#43|ECONOMY PLATED BRASS|3|4 +Brand#43|ECONOMY PLATED BRASS|14|4 +Brand#43|ECONOMY PLATED BRASS|19|4 +Brand#43|ECONOMY PLATED BRASS|23|4 +Brand#43|ECONOMY PLATED BRASS|36|4 +Brand#43|ECONOMY PLATED BRASS|49|4 +Brand#43|ECONOMY PLATED COPPER|14|4 +Brand#43|ECONOMY PLATED COPPER|36|4 +Brand#43|ECONOMY PLATED NICKEL|36|4 +Brand#43|ECONOMY PLATED NICKEL|45|4 +Brand#43|ECONOMY PLATED STEEL|9|4 +Brand#43|ECONOMY PLATED STEEL|45|4 +Brand#43|ECONOMY PLATED STEEL|49|4 +Brand#43|ECONOMY PLATED TIN|3|4 +Brand#43|ECONOMY PLATED TIN|14|4 +Brand#43|ECONOMY PLATED TIN|36|4 +Brand#43|ECONOMY PLATED TIN|45|4 +Brand#43|ECONOMY POLISHED BRASS|3|4 +Brand#43|ECONOMY POLISHED BRASS|9|4 +Brand#43|ECONOMY POLISHED BRASS|14|4 +Brand#43|ECONOMY POLISHED BRASS|36|4 +Brand#43|ECONOMY POLISHED BRASS|49|4 +Brand#43|ECONOMY POLISHED COPPER|3|4 +Brand#43|ECONOMY POLISHED COPPER|14|4 +Brand#43|ECONOMY POLISHED COPPER|23|4 +Brand#43|ECONOMY POLISHED COPPER|45|4 +Brand#43|ECONOMY POLISHED NICKEL|3|4 +Brand#43|ECONOMY POLISHED NICKEL|9|4 +Brand#43|ECONOMY POLISHED NICKEL|14|4 +Brand#43|ECONOMY POLISHED NICKEL|23|4 +Brand#43|ECONOMY POLISHED NICKEL|49|4 +Brand#43|ECONOMY POLISHED STEEL|19|4 +Brand#43|ECONOMY POLISHED STEEL|45|4 +Brand#43|LARGE ANODIZED BRASS|19|4 +Brand#43|LARGE ANODIZED BRASS|23|4 +Brand#43|LARGE ANODIZED COPPER|3|4 +Brand#43|LARGE ANODIZED COPPER|36|4 +Brand#43|LARGE ANODIZED COPPER|45|4 +Brand#43|LARGE ANODIZED NICKEL|14|4 +Brand#43|LARGE ANODIZED STEEL|3|4 +Brand#43|LARGE ANODIZED STEEL|9|4 +Brand#43|LARGE ANODIZED STEEL|14|4 +Brand#43|LARGE ANODIZED TIN|3|4 +Brand#43|LARGE ANODIZED TIN|49|4 +Brand#43|LARGE BRUSHED BRASS|14|4 +Brand#43|LARGE BRUSHED BRASS|19|4 +Brand#43|LARGE BRUSHED BRASS|36|4 +Brand#43|LARGE BRUSHED COPPER|3|4 +Brand#43|LARGE BRUSHED COPPER|23|4 +Brand#43|LARGE BRUSHED COPPER|45|4 +Brand#43|LARGE BRUSHED NICKEL|3|4 +Brand#43|LARGE BRUSHED NICKEL|45|4 +Brand#43|LARGE BRUSHED STEEL|19|4 +Brand#43|LARGE BRUSHED STEEL|49|4 +Brand#43|LARGE BRUSHED TIN|3|4 +Brand#43|LARGE BRUSHED TIN|14|4 +Brand#43|LARGE BRUSHED TIN|45|4 +Brand#43|LARGE BRUSHED TIN|49|4 +Brand#43|LARGE BURNISHED BRASS|3|4 +Brand#43|LARGE BURNISHED BRASS|19|4 +Brand#43|LARGE BURNISHED COPPER|9|4 +Brand#43|LARGE BURNISHED COPPER|19|4 +Brand#43|LARGE BURNISHED COPPER|23|4 +Brand#43|LARGE BURNISHED COPPER|49|4 +Brand#43|LARGE BURNISHED NICKEL|9|4 +Brand#43|LARGE BURNISHED NICKEL|19|4 +Brand#43|LARGE BURNISHED NICKEL|45|4 +Brand#43|LARGE BURNISHED STEEL|19|4 +Brand#43|LARGE BURNISHED STEEL|23|4 +Brand#43|LARGE BURNISHED STEEL|45|4 +Brand#43|LARGE BURNISHED STEEL|49|4 +Brand#43|LARGE BURNISHED TIN|9|4 +Brand#43|LARGE BURNISHED TIN|49|4 +Brand#43|LARGE PLATED BRASS|3|4 +Brand#43|LARGE PLATED BRASS|36|4 +Brand#43|LARGE PLATED COPPER|3|4 +Brand#43|LARGE PLATED COPPER|14|4 +Brand#43|LARGE PLATED COPPER|19|4 +Brand#43|LARGE PLATED COPPER|23|4 +Brand#43|LARGE PLATED COPPER|49|4 +Brand#43|LARGE PLATED NICKEL|19|4 +Brand#43|LARGE PLATED NICKEL|23|4 +Brand#43|LARGE PLATED NICKEL|36|4 +Brand#43|LARGE PLATED STEEL|9|4 +Brand#43|LARGE PLATED STEEL|19|4 +Brand#43|LARGE PLATED STEEL|45|4 +Brand#43|LARGE PLATED TIN|3|4 +Brand#43|LARGE PLATED TIN|49|4 +Brand#43|LARGE POLISHED BRASS|19|4 +Brand#43|LARGE POLISHED BRASS|23|4 +Brand#43|LARGE POLISHED BRASS|45|4 +Brand#43|LARGE POLISHED BRASS|49|4 +Brand#43|LARGE POLISHED COPPER|9|4 +Brand#43|LARGE POLISHED COPPER|45|4 +Brand#43|LARGE POLISHED NICKEL|14|4 +Brand#43|LARGE POLISHED NICKEL|19|4 +Brand#43|LARGE POLISHED NICKEL|36|4 +Brand#43|LARGE POLISHED NICKEL|45|4 +Brand#43|LARGE POLISHED NICKEL|49|4 +Brand#43|LARGE POLISHED STEEL|3|4 +Brand#43|LARGE POLISHED STEEL|23|4 +Brand#43|LARGE POLISHED STEEL|45|4 +Brand#43|LARGE POLISHED STEEL|49|4 +Brand#43|LARGE POLISHED TIN|3|4 +Brand#43|LARGE POLISHED TIN|19|4 +Brand#43|LARGE POLISHED TIN|23|4 +Brand#43|LARGE POLISHED TIN|36|4 +Brand#43|MEDIUM ANODIZED BRASS|9|4 +Brand#43|MEDIUM ANODIZED BRASS|23|4 +Brand#43|MEDIUM ANODIZED BRASS|45|4 +Brand#43|MEDIUM ANODIZED COPPER|36|4 +Brand#43|MEDIUM ANODIZED NICKEL|19|4 +Brand#43|MEDIUM ANODIZED NICKEL|23|4 +Brand#43|MEDIUM ANODIZED NICKEL|45|4 +Brand#43|MEDIUM ANODIZED STEEL|14|4 +Brand#43|MEDIUM ANODIZED STEEL|23|4 +Brand#43|MEDIUM ANODIZED STEEL|49|4 +Brand#43|MEDIUM ANODIZED TIN|3|4 +Brand#43|MEDIUM ANODIZED TIN|9|4 +Brand#43|MEDIUM ANODIZED TIN|14|4 +Brand#43|MEDIUM ANODIZED TIN|19|4 +Brand#43|MEDIUM BRUSHED BRASS|19|4 +Brand#43|MEDIUM BRUSHED BRASS|49|4 +Brand#43|MEDIUM BRUSHED COPPER|3|4 +Brand#43|MEDIUM BRUSHED COPPER|9|4 +Brand#43|MEDIUM BRUSHED COPPER|19|4 +Brand#43|MEDIUM BRUSHED COPPER|36|4 +Brand#43|MEDIUM BRUSHED COPPER|49|4 +Brand#43|MEDIUM BRUSHED NICKEL|9|4 +Brand#43|MEDIUM BRUSHED NICKEL|14|4 +Brand#43|MEDIUM BRUSHED NICKEL|19|4 +Brand#43|MEDIUM BRUSHED NICKEL|36|4 +Brand#43|MEDIUM BRUSHED NICKEL|45|4 +Brand#43|MEDIUM BRUSHED NICKEL|49|4 +Brand#43|MEDIUM BRUSHED STEEL|3|4 +Brand#43|MEDIUM BRUSHED STEEL|9|4 +Brand#43|MEDIUM BRUSHED STEEL|23|4 +Brand#43|MEDIUM BRUSHED STEEL|45|4 +Brand#43|MEDIUM BRUSHED TIN|9|4 +Brand#43|MEDIUM BRUSHED TIN|14|4 +Brand#43|MEDIUM BRUSHED TIN|36|4 +Brand#43|MEDIUM BRUSHED TIN|45|4 +Brand#43|MEDIUM BURNISHED BRASS|9|4 +Brand#43|MEDIUM BURNISHED BRASS|14|4 +Brand#43|MEDIUM BURNISHED COPPER|9|4 +Brand#43|MEDIUM BURNISHED COPPER|36|4 +Brand#43|MEDIUM BURNISHED COPPER|45|4 +Brand#43|MEDIUM BURNISHED NICKEL|3|4 +Brand#43|MEDIUM BURNISHED STEEL|9|4 +Brand#43|MEDIUM BURNISHED STEEL|36|4 +Brand#43|MEDIUM BURNISHED TIN|23|4 +Brand#43|MEDIUM PLATED BRASS|9|4 +Brand#43|MEDIUM PLATED BRASS|14|4 +Brand#43|MEDIUM PLATED COPPER|14|4 +Brand#43|MEDIUM PLATED COPPER|45|4 +Brand#43|MEDIUM PLATED NICKEL|23|4 +Brand#43|MEDIUM PLATED NICKEL|49|4 +Brand#43|MEDIUM PLATED STEEL|9|4 +Brand#43|MEDIUM PLATED STEEL|14|4 +Brand#43|MEDIUM PLATED STEEL|19|4 +Brand#43|MEDIUM PLATED STEEL|23|4 +Brand#43|MEDIUM PLATED TIN|9|4 +Brand#43|MEDIUM PLATED TIN|14|4 +Brand#43|MEDIUM PLATED TIN|49|4 +Brand#43|PROMO ANODIZED BRASS|19|4 +Brand#43|PROMO ANODIZED BRASS|23|4 +Brand#43|PROMO ANODIZED BRASS|49|4 +Brand#43|PROMO ANODIZED COPPER|3|4 +Brand#43|PROMO ANODIZED COPPER|9|4 +Brand#43|PROMO ANODIZED COPPER|14|4 +Brand#43|PROMO ANODIZED COPPER|19|4 +Brand#43|PROMO ANODIZED COPPER|49|4 +Brand#43|PROMO ANODIZED NICKEL|9|4 +Brand#43|PROMO ANODIZED NICKEL|36|4 +Brand#43|PROMO ANODIZED STEEL|3|4 +Brand#43|PROMO ANODIZED STEEL|19|4 +Brand#43|PROMO ANODIZED STEEL|36|4 +Brand#43|PROMO ANODIZED STEEL|45|4 +Brand#43|PROMO ANODIZED TIN|36|4 +Brand#43|PROMO ANODIZED TIN|45|4 +Brand#43|PROMO BRUSHED BRASS|9|4 +Brand#43|PROMO BRUSHED BRASS|23|4 +Brand#43|PROMO BRUSHED BRASS|49|4 +Brand#43|PROMO BRUSHED COPPER|14|4 +Brand#43|PROMO BRUSHED COPPER|45|4 +Brand#43|PROMO BRUSHED COPPER|49|4 +Brand#43|PROMO BRUSHED NICKEL|3|4 +Brand#43|PROMO BRUSHED STEEL|3|4 +Brand#43|PROMO BRUSHED STEEL|23|4 +Brand#43|PROMO BRUSHED TIN|9|4 +Brand#43|PROMO BRUSHED TIN|14|4 +Brand#43|PROMO BRUSHED TIN|19|4 +Brand#43|PROMO BRUSHED TIN|23|4 +Brand#43|PROMO BRUSHED TIN|36|4 +Brand#43|PROMO BRUSHED TIN|45|4 +Brand#43|PROMO BURNISHED BRASS|9|4 +Brand#43|PROMO BURNISHED BRASS|36|4 +Brand#43|PROMO BURNISHED BRASS|45|4 +Brand#43|PROMO BURNISHED COPPER|3|4 +Brand#43|PROMO BURNISHED COPPER|9|4 +Brand#43|PROMO BURNISHED COPPER|19|4 +Brand#43|PROMO BURNISHED COPPER|23|4 +Brand#43|PROMO BURNISHED COPPER|45|4 +Brand#43|PROMO BURNISHED NICKEL|9|4 +Brand#43|PROMO BURNISHED NICKEL|19|4 +Brand#43|PROMO BURNISHED NICKEL|23|4 +Brand#43|PROMO BURNISHED NICKEL|36|4 +Brand#43|PROMO BURNISHED NICKEL|45|4 +Brand#43|PROMO BURNISHED STEEL|3|4 +Brand#43|PROMO BURNISHED STEEL|9|4 +Brand#43|PROMO BURNISHED STEEL|19|4 +Brand#43|PROMO BURNISHED STEEL|23|4 +Brand#43|PROMO BURNISHED TIN|19|4 +Brand#43|PROMO BURNISHED TIN|45|4 +Brand#43|PROMO PLATED BRASS|3|4 +Brand#43|PROMO PLATED BRASS|9|4 +Brand#43|PROMO PLATED BRASS|19|4 +Brand#43|PROMO PLATED BRASS|23|4 +Brand#43|PROMO PLATED COPPER|3|4 +Brand#43|PROMO PLATED COPPER|23|4 +Brand#43|PROMO PLATED COPPER|36|4 +Brand#43|PROMO PLATED COPPER|49|4 +Brand#43|PROMO PLATED NICKEL|3|4 +Brand#43|PROMO PLATED NICKEL|19|4 +Brand#43|PROMO PLATED NICKEL|36|4 +Brand#43|PROMO PLATED NICKEL|49|4 +Brand#43|PROMO PLATED STEEL|3|4 +Brand#43|PROMO PLATED STEEL|19|4 +Brand#43|PROMO PLATED STEEL|23|4 +Brand#43|PROMO PLATED STEEL|36|4 +Brand#43|PROMO PLATED STEEL|49|4 +Brand#43|PROMO PLATED TIN|3|4 +Brand#43|PROMO PLATED TIN|14|4 +Brand#43|PROMO PLATED TIN|49|4 +Brand#43|PROMO POLISHED BRASS|3|4 +Brand#43|PROMO POLISHED BRASS|14|4 +Brand#43|PROMO POLISHED BRASS|19|4 +Brand#43|PROMO POLISHED BRASS|49|4 +Brand#43|PROMO POLISHED COPPER|9|4 +Brand#43|PROMO POLISHED COPPER|45|4 +Brand#43|PROMO POLISHED COPPER|49|4 +Brand#43|PROMO POLISHED NICKEL|9|4 +Brand#43|PROMO POLISHED NICKEL|23|4 +Brand#43|PROMO POLISHED NICKEL|36|4 +Brand#43|PROMO POLISHED NICKEL|45|4 +Brand#43|PROMO POLISHED NICKEL|49|4 +Brand#43|PROMO POLISHED STEEL|19|4 +Brand#43|PROMO POLISHED STEEL|45|4 +Brand#43|PROMO POLISHED STEEL|49|4 +Brand#43|PROMO POLISHED TIN|9|4 +Brand#43|PROMO POLISHED TIN|19|4 +Brand#43|PROMO POLISHED TIN|23|4 +Brand#43|PROMO POLISHED TIN|49|4 +Brand#43|SMALL ANODIZED BRASS|3|4 +Brand#43|SMALL ANODIZED BRASS|9|4 +Brand#43|SMALL ANODIZED BRASS|14|4 +Brand#43|SMALL ANODIZED COPPER|23|4 +Brand#43|SMALL ANODIZED COPPER|45|4 +Brand#43|SMALL ANODIZED NICKEL|9|4 +Brand#43|SMALL ANODIZED NICKEL|49|4 +Brand#43|SMALL ANODIZED STEEL|9|4 +Brand#43|SMALL ANODIZED STEEL|14|4 +Brand#43|SMALL ANODIZED STEEL|19|4 +Brand#43|SMALL ANODIZED TIN|19|4 +Brand#43|SMALL ANODIZED TIN|45|4 +Brand#43|SMALL BRUSHED BRASS|3|4 +Brand#43|SMALL BRUSHED BRASS|14|4 +Brand#43|SMALL BRUSHED BRASS|23|4 +Brand#43|SMALL BRUSHED BRASS|36|4 +Brand#43|SMALL BRUSHED COPPER|14|4 +Brand#43|SMALL BRUSHED NICKEL|3|4 +Brand#43|SMALL BRUSHED NICKEL|14|4 +Brand#43|SMALL BRUSHED NICKEL|19|4 +Brand#43|SMALL BRUSHED NICKEL|45|4 +Brand#43|SMALL BRUSHED STEEL|19|4 +Brand#43|SMALL BRUSHED STEEL|23|4 +Brand#43|SMALL BRUSHED STEEL|49|4 +Brand#43|SMALL BRUSHED TIN|23|4 +Brand#43|SMALL BRUSHED TIN|36|4 +Brand#43|SMALL BURNISHED BRASS|19|4 +Brand#43|SMALL BURNISHED BRASS|23|4 +Brand#43|SMALL BURNISHED BRASS|49|4 +Brand#43|SMALL BURNISHED COPPER|3|4 +Brand#43|SMALL BURNISHED COPPER|9|4 +Brand#43|SMALL BURNISHED COPPER|36|4 +Brand#43|SMALL BURNISHED NICKEL|3|4 +Brand#43|SMALL BURNISHED NICKEL|36|4 +Brand#43|SMALL BURNISHED STEEL|14|4 +Brand#43|SMALL BURNISHED STEEL|19|4 +Brand#43|SMALL BURNISHED STEEL|23|4 +Brand#43|SMALL BURNISHED STEEL|49|4 +Brand#43|SMALL BURNISHED TIN|14|4 +Brand#43|SMALL BURNISHED TIN|19|4 +Brand#43|SMALL BURNISHED TIN|36|4 +Brand#43|SMALL PLATED BRASS|3|4 +Brand#43|SMALL PLATED BRASS|9|4 +Brand#43|SMALL PLATED BRASS|14|4 +Brand#43|SMALL PLATED COPPER|3|4 +Brand#43|SMALL PLATED COPPER|36|4 +Brand#43|SMALL PLATED NICKEL|14|4 +Brand#43|SMALL PLATED NICKEL|36|4 +Brand#43|SMALL PLATED STEEL|9|4 +Brand#43|SMALL PLATED STEEL|23|4 +Brand#43|SMALL PLATED STEEL|45|4 +Brand#43|SMALL PLATED STEEL|49|4 +Brand#43|SMALL PLATED TIN|3|4 +Brand#43|SMALL PLATED TIN|36|4 +Brand#43|SMALL PLATED TIN|49|4 +Brand#43|SMALL POLISHED BRASS|36|4 +Brand#43|SMALL POLISHED BRASS|49|4 +Brand#43|SMALL POLISHED COPPER|23|4 +Brand#43|SMALL POLISHED COPPER|36|4 +Brand#43|SMALL POLISHED NICKEL|9|4 +Brand#43|SMALL POLISHED NICKEL|49|4 +Brand#43|SMALL POLISHED STEEL|3|4 +Brand#43|SMALL POLISHED STEEL|14|4 +Brand#43|SMALL POLISHED STEEL|23|4 +Brand#43|SMALL POLISHED STEEL|36|4 +Brand#43|SMALL POLISHED TIN|3|4 +Brand#43|SMALL POLISHED TIN|9|4 +Brand#43|SMALL POLISHED TIN|23|4 +Brand#43|STANDARD ANODIZED BRASS|3|4 +Brand#43|STANDARD ANODIZED BRASS|9|4 +Brand#43|STANDARD ANODIZED BRASS|14|4 +Brand#43|STANDARD ANODIZED BRASS|19|4 +Brand#43|STANDARD ANODIZED BRASS|45|4 +Brand#43|STANDARD ANODIZED COPPER|19|4 +Brand#43|STANDARD ANODIZED COPPER|23|4 +Brand#43|STANDARD ANODIZED COPPER|45|4 +Brand#43|STANDARD ANODIZED NICKEL|19|4 +Brand#43|STANDARD ANODIZED NICKEL|36|4 +Brand#43|STANDARD ANODIZED NICKEL|45|4 +Brand#43|STANDARD ANODIZED STEEL|19|4 +Brand#43|STANDARD ANODIZED TIN|3|4 +Brand#43|STANDARD BRUSHED BRASS|9|4 +Brand#43|STANDARD BRUSHED BRASS|19|4 +Brand#43|STANDARD BRUSHED BRASS|23|4 +Brand#43|STANDARD BRUSHED COPPER|3|4 +Brand#43|STANDARD BRUSHED COPPER|14|4 +Brand#43|STANDARD BRUSHED COPPER|23|4 +Brand#43|STANDARD BRUSHED COPPER|36|4 +Brand#43|STANDARD BRUSHED COPPER|45|4 +Brand#43|STANDARD BRUSHED COPPER|49|4 +Brand#43|STANDARD BRUSHED NICKEL|14|4 +Brand#43|STANDARD BRUSHED STEEL|3|4 +Brand#43|STANDARD BRUSHED STEEL|9|4 +Brand#43|STANDARD BRUSHED STEEL|23|4 +Brand#43|STANDARD BRUSHED STEEL|45|4 +Brand#43|STANDARD BRUSHED STEEL|49|4 +Brand#43|STANDARD BRUSHED TIN|3|4 +Brand#43|STANDARD BRUSHED TIN|14|4 +Brand#43|STANDARD BRUSHED TIN|23|4 +Brand#43|STANDARD BRUSHED TIN|36|4 +Brand#43|STANDARD BURNISHED BRASS|19|4 +Brand#43|STANDARD BURNISHED COPPER|19|4 +Brand#43|STANDARD BURNISHED COPPER|23|4 +Brand#43|STANDARD BURNISHED NICKEL|3|4 +Brand#43|STANDARD BURNISHED NICKEL|14|4 +Brand#43|STANDARD BURNISHED STEEL|3|4 +Brand#43|STANDARD BURNISHED STEEL|14|4 +Brand#43|STANDARD BURNISHED TIN|9|4 +Brand#43|STANDARD BURNISHED TIN|45|4 +Brand#43|STANDARD PLATED BRASS|9|4 +Brand#43|STANDARD PLATED BRASS|36|4 +Brand#43|STANDARD PLATED BRASS|49|4 +Brand#43|STANDARD PLATED COPPER|9|4 +Brand#43|STANDARD PLATED COPPER|14|4 +Brand#43|STANDARD PLATED COPPER|49|4 +Brand#43|STANDARD PLATED NICKEL|3|4 +Brand#43|STANDARD PLATED NICKEL|9|4 +Brand#43|STANDARD PLATED NICKEL|45|4 +Brand#43|STANDARD PLATED STEEL|9|4 +Brand#43|STANDARD PLATED STEEL|14|4 +Brand#43|STANDARD PLATED STEEL|19|4 +Brand#43|STANDARD PLATED STEEL|45|4 +Brand#43|STANDARD PLATED STEEL|49|4 +Brand#43|STANDARD PLATED TIN|36|4 +Brand#43|STANDARD POLISHED BRASS|23|4 +Brand#43|STANDARD POLISHED BRASS|45|4 +Brand#43|STANDARD POLISHED BRASS|49|4 +Brand#43|STANDARD POLISHED COPPER|9|4 +Brand#43|STANDARD POLISHED COPPER|14|4 +Brand#43|STANDARD POLISHED COPPER|23|4 +Brand#43|STANDARD POLISHED COPPER|36|4 +Brand#43|STANDARD POLISHED NICKEL|9|4 +Brand#43|STANDARD POLISHED NICKEL|19|4 +Brand#43|STANDARD POLISHED NICKEL|49|4 +Brand#43|STANDARD POLISHED STEEL|19|4 +Brand#43|STANDARD POLISHED STEEL|23|4 +Brand#43|STANDARD POLISHED STEEL|45|4 +Brand#43|STANDARD POLISHED TIN|3|4 +Brand#43|STANDARD POLISHED TIN|19|4 +Brand#43|STANDARD POLISHED TIN|45|4 +Brand#43|STANDARD POLISHED TIN|49|4 +Brand#44|ECONOMY ANODIZED BRASS|23|4 +Brand#44|ECONOMY ANODIZED BRASS|36|4 +Brand#44|ECONOMY ANODIZED BRASS|49|4 +Brand#44|ECONOMY ANODIZED COPPER|14|4 +Brand#44|ECONOMY ANODIZED COPPER|19|4 +Brand#44|ECONOMY ANODIZED COPPER|36|4 +Brand#44|ECONOMY ANODIZED NICKEL|9|4 +Brand#44|ECONOMY ANODIZED NICKEL|14|4 +Brand#44|ECONOMY ANODIZED NICKEL|19|4 +Brand#44|ECONOMY ANODIZED NICKEL|49|4 +Brand#44|ECONOMY ANODIZED STEEL|36|4 +Brand#44|ECONOMY ANODIZED STEEL|49|4 +Brand#44|ECONOMY ANODIZED TIN|23|4 +Brand#44|ECONOMY ANODIZED TIN|45|4 +Brand#44|ECONOMY ANODIZED TIN|49|4 +Brand#44|ECONOMY BRUSHED BRASS|3|4 +Brand#44|ECONOMY BRUSHED BRASS|9|4 +Brand#44|ECONOMY BRUSHED BRASS|19|4 +Brand#44|ECONOMY BRUSHED BRASS|45|4 +Brand#44|ECONOMY BRUSHED BRASS|49|4 +Brand#44|ECONOMY BRUSHED COPPER|3|4 +Brand#44|ECONOMY BRUSHED COPPER|9|4 +Brand#44|ECONOMY BRUSHED COPPER|14|4 +Brand#44|ECONOMY BRUSHED COPPER|36|4 +Brand#44|ECONOMY BRUSHED COPPER|45|4 +Brand#44|ECONOMY BRUSHED NICKEL|3|4 +Brand#44|ECONOMY BRUSHED NICKEL|14|4 +Brand#44|ECONOMY BRUSHED STEEL|3|4 +Brand#44|ECONOMY BRUSHED STEEL|23|4 +Brand#44|ECONOMY BRUSHED STEEL|45|4 +Brand#44|ECONOMY BRUSHED STEEL|49|4 +Brand#44|ECONOMY BRUSHED TIN|9|4 +Brand#44|ECONOMY BRUSHED TIN|23|4 +Brand#44|ECONOMY BRUSHED TIN|36|4 +Brand#44|ECONOMY BURNISHED BRASS|3|4 +Brand#44|ECONOMY BURNISHED BRASS|14|4 +Brand#44|ECONOMY BURNISHED BRASS|19|4 +Brand#44|ECONOMY BURNISHED BRASS|49|4 +Brand#44|ECONOMY BURNISHED COPPER|19|4 +Brand#44|ECONOMY BURNISHED COPPER|45|4 +Brand#44|ECONOMY BURNISHED NICKEL|19|4 +Brand#44|ECONOMY BURNISHED NICKEL|36|4 +Brand#44|ECONOMY BURNISHED NICKEL|45|4 +Brand#44|ECONOMY BURNISHED STEEL|3|4 +Brand#44|ECONOMY BURNISHED STEEL|23|4 +Brand#44|ECONOMY BURNISHED TIN|23|4 +Brand#44|ECONOMY BURNISHED TIN|36|4 +Brand#44|ECONOMY PLATED BRASS|14|4 +Brand#44|ECONOMY PLATED BRASS|19|4 +Brand#44|ECONOMY PLATED BRASS|36|4 +Brand#44|ECONOMY PLATED BRASS|45|4 +Brand#44|ECONOMY PLATED COPPER|19|4 +Brand#44|ECONOMY PLATED COPPER|49|4 +Brand#44|ECONOMY PLATED NICKEL|3|4 +Brand#44|ECONOMY PLATED NICKEL|9|4 +Brand#44|ECONOMY PLATED NICKEL|19|4 +Brand#44|ECONOMY PLATED NICKEL|45|4 +Brand#44|ECONOMY PLATED NICKEL|49|4 +Brand#44|ECONOMY PLATED STEEL|9|4 +Brand#44|ECONOMY PLATED STEEL|19|4 +Brand#44|ECONOMY PLATED STEEL|49|4 +Brand#44|ECONOMY PLATED TIN|3|4 +Brand#44|ECONOMY PLATED TIN|9|4 +Brand#44|ECONOMY PLATED TIN|14|4 +Brand#44|ECONOMY PLATED TIN|23|4 +Brand#44|ECONOMY PLATED TIN|36|4 +Brand#44|ECONOMY PLATED TIN|49|4 +Brand#44|ECONOMY POLISHED BRASS|9|4 +Brand#44|ECONOMY POLISHED BRASS|14|4 +Brand#44|ECONOMY POLISHED COPPER|3|4 +Brand#44|ECONOMY POLISHED COPPER|45|4 +Brand#44|ECONOMY POLISHED COPPER|49|4 +Brand#44|ECONOMY POLISHED NICKEL|3|4 +Brand#44|ECONOMY POLISHED NICKEL|14|4 +Brand#44|ECONOMY POLISHED NICKEL|19|4 +Brand#44|ECONOMY POLISHED STEEL|3|4 +Brand#44|ECONOMY POLISHED STEEL|14|4 +Brand#44|ECONOMY POLISHED STEEL|19|4 +Brand#44|ECONOMY POLISHED STEEL|36|4 +Brand#44|ECONOMY POLISHED STEEL|45|4 +Brand#44|ECONOMY POLISHED TIN|9|4 +Brand#44|ECONOMY POLISHED TIN|14|4 +Brand#44|ECONOMY POLISHED TIN|23|4 +Brand#44|LARGE ANODIZED BRASS|14|4 +Brand#44|LARGE ANODIZED BRASS|19|4 +Brand#44|LARGE ANODIZED BRASS|36|4 +Brand#44|LARGE ANODIZED COPPER|23|4 +Brand#44|LARGE ANODIZED COPPER|49|4 +Brand#44|LARGE ANODIZED NICKEL|9|4 +Brand#44|LARGE ANODIZED NICKEL|45|4 +Brand#44|LARGE ANODIZED STEEL|3|4 +Brand#44|LARGE ANODIZED STEEL|9|4 +Brand#44|LARGE ANODIZED STEEL|14|4 +Brand#44|LARGE ANODIZED STEEL|36|4 +Brand#44|LARGE ANODIZED STEEL|45|4 +Brand#44|LARGE ANODIZED STEEL|49|4 +Brand#44|LARGE ANODIZED TIN|9|4 +Brand#44|LARGE ANODIZED TIN|19|4 +Brand#44|LARGE ANODIZED TIN|36|4 +Brand#44|LARGE ANODIZED TIN|45|4 +Brand#44|LARGE ANODIZED TIN|49|4 +Brand#44|LARGE BRUSHED BRASS|3|4 +Brand#44|LARGE BRUSHED BRASS|23|4 +Brand#44|LARGE BRUSHED BRASS|36|4 +Brand#44|LARGE BRUSHED BRASS|45|4 +Brand#44|LARGE BRUSHED BRASS|49|4 +Brand#44|LARGE BRUSHED COPPER|3|4 +Brand#44|LARGE BRUSHED COPPER|19|4 +Brand#44|LARGE BRUSHED COPPER|45|4 +Brand#44|LARGE BRUSHED COPPER|49|4 +Brand#44|LARGE BRUSHED NICKEL|36|4 +Brand#44|LARGE BRUSHED NICKEL|49|4 +Brand#44|LARGE BRUSHED STEEL|19|4 +Brand#44|LARGE BRUSHED STEEL|45|4 +Brand#44|LARGE BRUSHED TIN|36|4 +Brand#44|LARGE BRUSHED TIN|45|4 +Brand#44|LARGE BRUSHED TIN|49|4 +Brand#44|LARGE BURNISHED BRASS|9|4 +Brand#44|LARGE BURNISHED BRASS|23|4 +Brand#44|LARGE BURNISHED BRASS|45|4 +Brand#44|LARGE BURNISHED COPPER|3|4 +Brand#44|LARGE BURNISHED COPPER|36|4 +Brand#44|LARGE BURNISHED COPPER|45|4 +Brand#44|LARGE BURNISHED COPPER|49|4 +Brand#44|LARGE BURNISHED NICKEL|19|4 +Brand#44|LARGE BURNISHED NICKEL|45|4 +Brand#44|LARGE BURNISHED STEEL|9|4 +Brand#44|LARGE BURNISHED TIN|9|4 +Brand#44|LARGE BURNISHED TIN|45|4 +Brand#44|LARGE BURNISHED TIN|49|4 +Brand#44|LARGE PLATED BRASS|36|4 +Brand#44|LARGE PLATED COPPER|3|4 +Brand#44|LARGE PLATED NICKEL|19|4 +Brand#44|LARGE PLATED NICKEL|45|4 +Brand#44|LARGE PLATED NICKEL|49|4 +Brand#44|LARGE PLATED STEEL|19|4 +Brand#44|LARGE PLATED STEEL|49|4 +Brand#44|LARGE PLATED TIN|23|4 +Brand#44|LARGE PLATED TIN|45|4 +Brand#44|LARGE POLISHED BRASS|3|4 +Brand#44|LARGE POLISHED COPPER|3|4 +Brand#44|LARGE POLISHED COPPER|14|4 +Brand#44|LARGE POLISHED COPPER|19|4 +Brand#44|LARGE POLISHED NICKEL|14|4 +Brand#44|LARGE POLISHED NICKEL|45|4 +Brand#44|LARGE POLISHED STEEL|3|4 +Brand#44|LARGE POLISHED STEEL|14|4 +Brand#44|LARGE POLISHED STEEL|23|4 +Brand#44|LARGE POLISHED STEEL|49|4 +Brand#44|LARGE POLISHED TIN|23|4 +Brand#44|LARGE POLISHED TIN|45|4 +Brand#44|MEDIUM ANODIZED BRASS|14|4 +Brand#44|MEDIUM ANODIZED BRASS|19|4 +Brand#44|MEDIUM ANODIZED COPPER|3|4 +Brand#44|MEDIUM ANODIZED COPPER|19|4 +Brand#44|MEDIUM ANODIZED COPPER|23|4 +Brand#44|MEDIUM ANODIZED NICKEL|3|4 +Brand#44|MEDIUM ANODIZED STEEL|19|4 +Brand#44|MEDIUM ANODIZED TIN|3|4 +Brand#44|MEDIUM ANODIZED TIN|14|4 +Brand#44|MEDIUM ANODIZED TIN|19|4 +Brand#44|MEDIUM ANODIZED TIN|23|4 +Brand#44|MEDIUM ANODIZED TIN|36|4 +Brand#44|MEDIUM BRUSHED BRASS|14|4 +Brand#44|MEDIUM BRUSHED BRASS|19|4 +Brand#44|MEDIUM BRUSHED BRASS|23|4 +Brand#44|MEDIUM BRUSHED COPPER|45|4 +Brand#44|MEDIUM BRUSHED NICKEL|9|4 +Brand#44|MEDIUM BRUSHED NICKEL|19|4 +Brand#44|MEDIUM BRUSHED STEEL|9|4 +Brand#44|MEDIUM BRUSHED STEEL|23|4 +Brand#44|MEDIUM BRUSHED STEEL|49|4 +Brand#44|MEDIUM BRUSHED TIN|3|4 +Brand#44|MEDIUM BRUSHED TIN|23|4 +Brand#44|MEDIUM BRUSHED TIN|49|4 +Brand#44|MEDIUM BURNISHED BRASS|14|4 +Brand#44|MEDIUM BURNISHED BRASS|19|4 +Brand#44|MEDIUM BURNISHED BRASS|23|4 +Brand#44|MEDIUM BURNISHED BRASS|49|4 +Brand#44|MEDIUM BURNISHED COPPER|14|4 +Brand#44|MEDIUM BURNISHED COPPER|23|4 +Brand#44|MEDIUM BURNISHED NICKEL|9|4 +Brand#44|MEDIUM BURNISHED NICKEL|19|4 +Brand#44|MEDIUM BURNISHED STEEL|9|4 +Brand#44|MEDIUM BURNISHED STEEL|36|4 +Brand#44|MEDIUM BURNISHED STEEL|45|4 +Brand#44|MEDIUM BURNISHED TIN|3|4 +Brand#44|MEDIUM BURNISHED TIN|9|4 +Brand#44|MEDIUM BURNISHED TIN|14|4 +Brand#44|MEDIUM BURNISHED TIN|36|4 +Brand#44|MEDIUM PLATED COPPER|14|4 +Brand#44|MEDIUM PLATED COPPER|23|4 +Brand#44|MEDIUM PLATED COPPER|36|4 +Brand#44|MEDIUM PLATED NICKEL|9|4 +Brand#44|MEDIUM PLATED NICKEL|14|4 +Brand#44|MEDIUM PLATED NICKEL|19|4 +Brand#44|MEDIUM PLATED NICKEL|36|4 +Brand#44|MEDIUM PLATED STEEL|3|4 +Brand#44|MEDIUM PLATED STEEL|36|4 +Brand#44|MEDIUM PLATED TIN|19|4 +Brand#44|MEDIUM PLATED TIN|45|4 +Brand#44|PROMO ANODIZED BRASS|23|4 +Brand#44|PROMO ANODIZED BRASS|45|4 +Brand#44|PROMO ANODIZED COPPER|3|4 +Brand#44|PROMO ANODIZED COPPER|9|4 +Brand#44|PROMO ANODIZED COPPER|14|4 +Brand#44|PROMO ANODIZED COPPER|49|4 +Brand#44|PROMO ANODIZED NICKEL|3|4 +Brand#44|PROMO ANODIZED NICKEL|49|4 +Brand#44|PROMO ANODIZED STEEL|14|4 +Brand#44|PROMO ANODIZED STEEL|19|4 +Brand#44|PROMO ANODIZED STEEL|45|4 +Brand#44|PROMO ANODIZED TIN|9|4 +Brand#44|PROMO ANODIZED TIN|14|4 +Brand#44|PROMO ANODIZED TIN|36|4 +Brand#44|PROMO ANODIZED TIN|49|4 +Brand#44|PROMO BRUSHED BRASS|14|4 +Brand#44|PROMO BRUSHED BRASS|23|4 +Brand#44|PROMO BRUSHED BRASS|36|4 +Brand#44|PROMO BRUSHED BRASS|45|4 +Brand#44|PROMO BRUSHED BRASS|49|4 +Brand#44|PROMO BRUSHED COPPER|14|4 +Brand#44|PROMO BRUSHED COPPER|19|4 +Brand#44|PROMO BRUSHED COPPER|45|4 +Brand#44|PROMO BRUSHED COPPER|49|4 +Brand#44|PROMO BRUSHED NICKEL|9|4 +Brand#44|PROMO BRUSHED NICKEL|36|4 +Brand#44|PROMO BRUSHED NICKEL|49|4 +Brand#44|PROMO BRUSHED TIN|14|4 +Brand#44|PROMO BRUSHED TIN|23|4 +Brand#44|PROMO BRUSHED TIN|36|4 +Brand#44|PROMO BURNISHED BRASS|9|4 +Brand#44|PROMO BURNISHED BRASS|14|4 +Brand#44|PROMO BURNISHED BRASS|23|4 +Brand#44|PROMO BURNISHED BRASS|45|4 +Brand#44|PROMO BURNISHED COPPER|14|4 +Brand#44|PROMO BURNISHED COPPER|19|4 +Brand#44|PROMO BURNISHED COPPER|36|4 +Brand#44|PROMO BURNISHED NICKEL|9|4 +Brand#44|PROMO BURNISHED NICKEL|19|4 +Brand#44|PROMO BURNISHED NICKEL|23|4 +Brand#44|PROMO BURNISHED STEEL|3|4 +Brand#44|PROMO BURNISHED STEEL|36|4 +Brand#44|PROMO BURNISHED TIN|9|4 +Brand#44|PROMO BURNISHED TIN|23|4 +Brand#44|PROMO BURNISHED TIN|36|4 +Brand#44|PROMO BURNISHED TIN|49|4 +Brand#44|PROMO PLATED BRASS|3|4 +Brand#44|PROMO PLATED BRASS|49|4 +Brand#44|PROMO PLATED COPPER|3|4 +Brand#44|PROMO PLATED COPPER|9|4 +Brand#44|PROMO PLATED COPPER|14|4 +Brand#44|PROMO PLATED COPPER|36|4 +Brand#44|PROMO PLATED COPPER|49|4 +Brand#44|PROMO PLATED NICKEL|14|4 +Brand#44|PROMO PLATED NICKEL|49|4 +Brand#44|PROMO PLATED STEEL|3|4 +Brand#44|PROMO PLATED STEEL|9|4 +Brand#44|PROMO PLATED STEEL|19|4 +Brand#44|PROMO PLATED STEEL|45|4 +Brand#44|PROMO PLATED TIN|23|4 +Brand#44|PROMO POLISHED BRASS|3|4 +Brand#44|PROMO POLISHED BRASS|14|4 +Brand#44|PROMO POLISHED BRASS|19|4 +Brand#44|PROMO POLISHED BRASS|49|4 +Brand#44|PROMO POLISHED COPPER|19|4 +Brand#44|PROMO POLISHED COPPER|49|4 +Brand#44|PROMO POLISHED NICKEL|3|4 +Brand#44|PROMO POLISHED NICKEL|23|4 +Brand#44|PROMO POLISHED NICKEL|36|4 +Brand#44|PROMO POLISHED NICKEL|49|4 +Brand#44|PROMO POLISHED STEEL|14|4 +Brand#44|PROMO POLISHED STEEL|23|4 +Brand#44|PROMO POLISHED TIN|9|4 +Brand#44|SMALL ANODIZED BRASS|14|4 +Brand#44|SMALL ANODIZED BRASS|23|4 +Brand#44|SMALL ANODIZED COPPER|36|4 +Brand#44|SMALL ANODIZED COPPER|45|4 +Brand#44|SMALL ANODIZED NICKEL|3|4 +Brand#44|SMALL ANODIZED NICKEL|9|4 +Brand#44|SMALL ANODIZED NICKEL|14|4 +Brand#44|SMALL ANODIZED NICKEL|19|4 +Brand#44|SMALL ANODIZED NICKEL|36|4 +Brand#44|SMALL ANODIZED NICKEL|45|4 +Brand#44|SMALL ANODIZED NICKEL|49|4 +Brand#44|SMALL ANODIZED STEEL|3|4 +Brand#44|SMALL ANODIZED STEEL|23|4 +Brand#44|SMALL ANODIZED STEEL|49|4 +Brand#44|SMALL ANODIZED TIN|3|4 +Brand#44|SMALL ANODIZED TIN|9|4 +Brand#44|SMALL ANODIZED TIN|36|4 +Brand#44|SMALL ANODIZED TIN|49|4 +Brand#44|SMALL BRUSHED BRASS|3|4 +Brand#44|SMALL BRUSHED BRASS|9|4 +Brand#44|SMALL BRUSHED BRASS|36|4 +Brand#44|SMALL BRUSHED COPPER|9|4 +Brand#44|SMALL BRUSHED COPPER|14|4 +Brand#44|SMALL BRUSHED NICKEL|14|4 +Brand#44|SMALL BRUSHED NICKEL|36|4 +Brand#44|SMALL BRUSHED NICKEL|49|4 +Brand#44|SMALL BRUSHED STEEL|3|4 +Brand#44|SMALL BRUSHED STEEL|9|4 +Brand#44|SMALL BRUSHED STEEL|45|4 +Brand#44|SMALL BRUSHED STEEL|49|4 +Brand#44|SMALL BRUSHED TIN|9|4 +Brand#44|SMALL BRUSHED TIN|23|4 +Brand#44|SMALL BURNISHED BRASS|9|4 +Brand#44|SMALL BURNISHED BRASS|14|4 +Brand#44|SMALL BURNISHED BRASS|19|4 +Brand#44|SMALL BURNISHED BRASS|23|4 +Brand#44|SMALL BURNISHED BRASS|45|4 +Brand#44|SMALL BURNISHED COPPER|9|4 +Brand#44|SMALL BURNISHED COPPER|19|4 +Brand#44|SMALL BURNISHED COPPER|36|4 +Brand#44|SMALL BURNISHED COPPER|45|4 +Brand#44|SMALL BURNISHED COPPER|49|4 +Brand#44|SMALL BURNISHED NICKEL|9|4 +Brand#44|SMALL BURNISHED NICKEL|19|4 +Brand#44|SMALL BURNISHED NICKEL|23|4 +Brand#44|SMALL BURNISHED NICKEL|36|4 +Brand#44|SMALL BURNISHED STEEL|45|4 +Brand#44|SMALL BURNISHED STEEL|49|4 +Brand#44|SMALL BURNISHED TIN|19|4 +Brand#44|SMALL PLATED BRASS|9|4 +Brand#44|SMALL PLATED BRASS|14|4 +Brand#44|SMALL PLATED BRASS|45|4 +Brand#44|SMALL PLATED COPPER|9|4 +Brand#44|SMALL PLATED COPPER|19|4 +Brand#44|SMALL PLATED COPPER|23|4 +Brand#44|SMALL PLATED COPPER|36|4 +Brand#44|SMALL PLATED COPPER|49|4 +Brand#44|SMALL PLATED NICKEL|3|4 +Brand#44|SMALL PLATED NICKEL|19|4 +Brand#44|SMALL PLATED NICKEL|23|4 +Brand#44|SMALL PLATED STEEL|23|4 +Brand#44|SMALL PLATED STEEL|36|4 +Brand#44|SMALL PLATED TIN|3|4 +Brand#44|SMALL PLATED TIN|23|4 +Brand#44|SMALL PLATED TIN|45|4 +Brand#44|SMALL PLATED TIN|49|4 +Brand#44|SMALL POLISHED BRASS|14|4 +Brand#44|SMALL POLISHED BRASS|19|4 +Brand#44|SMALL POLISHED BRASS|23|4 +Brand#44|SMALL POLISHED BRASS|36|4 +Brand#44|SMALL POLISHED COPPER|3|4 +Brand#44|SMALL POLISHED COPPER|19|4 +Brand#44|SMALL POLISHED COPPER|45|4 +Brand#44|SMALL POLISHED NICKEL|36|4 +Brand#44|SMALL POLISHED STEEL|23|4 +Brand#44|SMALL POLISHED STEEL|36|4 +Brand#44|SMALL POLISHED STEEL|45|4 +Brand#44|SMALL POLISHED STEEL|49|4 +Brand#44|STANDARD ANODIZED BRASS|23|4 +Brand#44|STANDARD ANODIZED BRASS|36|4 +Brand#44|STANDARD ANODIZED COPPER|14|4 +Brand#44|STANDARD ANODIZED COPPER|23|4 +Brand#44|STANDARD ANODIZED COPPER|36|4 +Brand#44|STANDARD ANODIZED NICKEL|3|4 +Brand#44|STANDARD ANODIZED NICKEL|14|4 +Brand#44|STANDARD ANODIZED NICKEL|23|4 +Brand#44|STANDARD ANODIZED NICKEL|49|4 +Brand#44|STANDARD ANODIZED STEEL|49|4 +Brand#44|STANDARD ANODIZED TIN|3|4 +Brand#44|STANDARD ANODIZED TIN|14|4 +Brand#44|STANDARD ANODIZED TIN|19|4 +Brand#44|STANDARD BRUSHED BRASS|19|4 +Brand#44|STANDARD BRUSHED BRASS|49|4 +Brand#44|STANDARD BRUSHED COPPER|3|4 +Brand#44|STANDARD BRUSHED COPPER|45|4 +Brand#44|STANDARD BRUSHED NICKEL|3|4 +Brand#44|STANDARD BRUSHED NICKEL|19|4 +Brand#44|STANDARD BRUSHED NICKEL|36|4 +Brand#44|STANDARD BRUSHED NICKEL|45|4 +Brand#44|STANDARD BRUSHED NICKEL|49|4 +Brand#44|STANDARD BRUSHED STEEL|9|4 +Brand#44|STANDARD BRUSHED STEEL|14|4 +Brand#44|STANDARD BRUSHED STEEL|36|4 +Brand#44|STANDARD BRUSHED TIN|14|4 +Brand#44|STANDARD BRUSHED TIN|36|4 +Brand#44|STANDARD BURNISHED BRASS|3|4 +Brand#44|STANDARD BURNISHED BRASS|14|4 +Brand#44|STANDARD BURNISHED BRASS|23|4 +Brand#44|STANDARD BURNISHED BRASS|36|4 +Brand#44|STANDARD BURNISHED BRASS|45|4 +Brand#44|STANDARD BURNISHED COPPER|9|4 +Brand#44|STANDARD BURNISHED COPPER|14|4 +Brand#44|STANDARD BURNISHED COPPER|23|4 +Brand#44|STANDARD BURNISHED NICKEL|3|4 +Brand#44|STANDARD BURNISHED NICKEL|36|4 +Brand#44|STANDARD BURNISHED NICKEL|49|4 +Brand#44|STANDARD BURNISHED STEEL|9|4 +Brand#44|STANDARD BURNISHED TIN|3|4 +Brand#44|STANDARD BURNISHED TIN|23|4 +Brand#44|STANDARD BURNISHED TIN|45|4 +Brand#44|STANDARD BURNISHED TIN|49|4 +Brand#44|STANDARD PLATED BRASS|14|4 +Brand#44|STANDARD PLATED BRASS|19|4 +Brand#44|STANDARD PLATED BRASS|23|4 +Brand#44|STANDARD PLATED COPPER|3|4 +Brand#44|STANDARD PLATED COPPER|36|4 +Brand#44|STANDARD PLATED NICKEL|3|4 +Brand#44|STANDARD PLATED NICKEL|9|4 +Brand#44|STANDARD PLATED NICKEL|23|4 +Brand#44|STANDARD PLATED NICKEL|36|4 +Brand#44|STANDARD PLATED NICKEL|49|4 +Brand#44|STANDARD PLATED STEEL|3|4 +Brand#44|STANDARD PLATED STEEL|9|4 +Brand#44|STANDARD PLATED STEEL|14|4 +Brand#44|STANDARD PLATED STEEL|23|4 +Brand#44|STANDARD PLATED STEEL|49|4 +Brand#44|STANDARD PLATED TIN|14|4 +Brand#44|STANDARD PLATED TIN|36|4 +Brand#44|STANDARD PLATED TIN|45|4 +Brand#44|STANDARD POLISHED BRASS|3|4 +Brand#44|STANDARD POLISHED BRASS|9|4 +Brand#44|STANDARD POLISHED BRASS|19|4 +Brand#44|STANDARD POLISHED COPPER|9|4 +Brand#44|STANDARD POLISHED NICKEL|9|4 +Brand#44|STANDARD POLISHED NICKEL|14|4 +Brand#44|STANDARD POLISHED NICKEL|23|4 +Brand#44|STANDARD POLISHED NICKEL|49|4 +Brand#44|STANDARD POLISHED STEEL|3|4 +Brand#44|STANDARD POLISHED STEEL|36|4 +Brand#44|STANDARD POLISHED STEEL|45|4 +Brand#44|STANDARD POLISHED TIN|3|4 +Brand#44|STANDARD POLISHED TIN|49|4 +Brand#51|ECONOMY ANODIZED BRASS|3|4 +Brand#51|ECONOMY ANODIZED BRASS|14|4 +Brand#51|ECONOMY ANODIZED BRASS|23|4 +Brand#51|ECONOMY ANODIZED COPPER|9|4 +Brand#51|ECONOMY ANODIZED COPPER|14|4 +Brand#51|ECONOMY ANODIZED COPPER|36|4 +Brand#51|ECONOMY ANODIZED NICKEL|9|4 +Brand#51|ECONOMY ANODIZED NICKEL|49|4 +Brand#51|ECONOMY ANODIZED STEEL|19|4 +Brand#51|ECONOMY ANODIZED STEEL|23|4 +Brand#51|ECONOMY ANODIZED TIN|3|4 +Brand#51|ECONOMY ANODIZED TIN|45|4 +Brand#51|ECONOMY ANODIZED TIN|49|4 +Brand#51|ECONOMY BRUSHED BRASS|9|4 +Brand#51|ECONOMY BRUSHED BRASS|14|4 +Brand#51|ECONOMY BRUSHED BRASS|19|4 +Brand#51|ECONOMY BRUSHED BRASS|23|4 +Brand#51|ECONOMY BRUSHED BRASS|36|4 +Brand#51|ECONOMY BRUSHED BRASS|45|4 +Brand#51|ECONOMY BRUSHED COPPER|9|4 +Brand#51|ECONOMY BRUSHED COPPER|19|4 +Brand#51|ECONOMY BRUSHED NICKEL|3|4 +Brand#51|ECONOMY BRUSHED NICKEL|23|4 +Brand#51|ECONOMY BRUSHED NICKEL|45|4 +Brand#51|ECONOMY BRUSHED STEEL|3|4 +Brand#51|ECONOMY BRUSHED STEEL|19|4 +Brand#51|ECONOMY BRUSHED TIN|3|4 +Brand#51|ECONOMY BRUSHED TIN|9|4 +Brand#51|ECONOMY BRUSHED TIN|14|4 +Brand#51|ECONOMY BRUSHED TIN|49|4 +Brand#51|ECONOMY BURNISHED BRASS|9|4 +Brand#51|ECONOMY BURNISHED BRASS|19|4 +Brand#51|ECONOMY BURNISHED BRASS|45|4 +Brand#51|ECONOMY BURNISHED BRASS|49|4 +Brand#51|ECONOMY BURNISHED COPPER|3|4 +Brand#51|ECONOMY BURNISHED COPPER|9|4 +Brand#51|ECONOMY BURNISHED COPPER|14|4 +Brand#51|ECONOMY BURNISHED COPPER|19|4 +Brand#51|ECONOMY BURNISHED COPPER|36|4 +Brand#51|ECONOMY BURNISHED COPPER|49|4 +Brand#51|ECONOMY BURNISHED NICKEL|3|4 +Brand#51|ECONOMY BURNISHED NICKEL|14|4 +Brand#51|ECONOMY BURNISHED NICKEL|23|4 +Brand#51|ECONOMY BURNISHED NICKEL|45|4 +Brand#51|ECONOMY BURNISHED STEEL|3|4 +Brand#51|ECONOMY BURNISHED STEEL|45|4 +Brand#51|ECONOMY BURNISHED STEEL|49|4 +Brand#51|ECONOMY BURNISHED TIN|9|4 +Brand#51|ECONOMY BURNISHED TIN|19|4 +Brand#51|ECONOMY BURNISHED TIN|49|4 +Brand#51|ECONOMY PLATED BRASS|14|4 +Brand#51|ECONOMY PLATED BRASS|19|4 +Brand#51|ECONOMY PLATED BRASS|49|4 +Brand#51|ECONOMY PLATED COPPER|3|4 +Brand#51|ECONOMY PLATED COPPER|9|4 +Brand#51|ECONOMY PLATED COPPER|14|4 +Brand#51|ECONOMY PLATED COPPER|19|4 +Brand#51|ECONOMY PLATED STEEL|3|4 +Brand#51|ECONOMY PLATED STEEL|14|4 +Brand#51|ECONOMY PLATED STEEL|36|4 +Brand#51|ECONOMY PLATED STEEL|45|4 +Brand#51|ECONOMY PLATED STEEL|49|4 +Brand#51|ECONOMY POLISHED BRASS|3|4 +Brand#51|ECONOMY POLISHED BRASS|9|4 +Brand#51|ECONOMY POLISHED BRASS|19|4 +Brand#51|ECONOMY POLISHED COPPER|3|4 +Brand#51|ECONOMY POLISHED COPPER|14|4 +Brand#51|ECONOMY POLISHED COPPER|19|4 +Brand#51|ECONOMY POLISHED COPPER|45|4 +Brand#51|ECONOMY POLISHED COPPER|49|4 +Brand#51|ECONOMY POLISHED NICKEL|45|4 +Brand#51|ECONOMY POLISHED NICKEL|49|4 +Brand#51|ECONOMY POLISHED STEEL|23|4 +Brand#51|ECONOMY POLISHED STEEL|49|4 +Brand#51|ECONOMY POLISHED TIN|3|4 +Brand#51|ECONOMY POLISHED TIN|9|4 +Brand#51|ECONOMY POLISHED TIN|23|4 +Brand#51|ECONOMY POLISHED TIN|45|4 +Brand#51|ECONOMY POLISHED TIN|49|4 +Brand#51|LARGE ANODIZED BRASS|9|4 +Brand#51|LARGE ANODIZED BRASS|14|4 +Brand#51|LARGE ANODIZED BRASS|36|4 +Brand#51|LARGE ANODIZED BRASS|45|4 +Brand#51|LARGE ANODIZED BRASS|49|4 +Brand#51|LARGE ANODIZED COPPER|3|4 +Brand#51|LARGE ANODIZED COPPER|9|4 +Brand#51|LARGE ANODIZED NICKEL|3|4 +Brand#51|LARGE ANODIZED NICKEL|9|4 +Brand#51|LARGE ANODIZED NICKEL|23|4 +Brand#51|LARGE ANODIZED STEEL|14|4 +Brand#51|LARGE ANODIZED STEEL|19|4 +Brand#51|LARGE ANODIZED STEEL|23|4 +Brand#51|LARGE ANODIZED STEEL|36|4 +Brand#51|LARGE ANODIZED STEEL|49|4 +Brand#51|LARGE ANODIZED TIN|36|4 +Brand#51|LARGE BRUSHED BRASS|3|4 +Brand#51|LARGE BRUSHED BRASS|14|4 +Brand#51|LARGE BRUSHED BRASS|19|4 +Brand#51|LARGE BRUSHED BRASS|36|4 +Brand#51|LARGE BRUSHED COPPER|3|4 +Brand#51|LARGE BRUSHED COPPER|45|4 +Brand#51|LARGE BRUSHED NICKEL|3|4 +Brand#51|LARGE BRUSHED NICKEL|23|4 +Brand#51|LARGE BRUSHED STEEL|3|4 +Brand#51|LARGE BRUSHED STEEL|9|4 +Brand#51|LARGE BRUSHED STEEL|14|4 +Brand#51|LARGE BRUSHED STEEL|23|4 +Brand#51|LARGE BRUSHED TIN|3|4 +Brand#51|LARGE BRUSHED TIN|9|4 +Brand#51|LARGE BRUSHED TIN|19|4 +Brand#51|LARGE BRUSHED TIN|23|4 +Brand#51|LARGE BRUSHED TIN|36|4 +Brand#51|LARGE BRUSHED TIN|45|4 +Brand#51|LARGE BURNISHED BRASS|9|4 +Brand#51|LARGE BURNISHED BRASS|19|4 +Brand#51|LARGE BURNISHED BRASS|23|4 +Brand#51|LARGE BURNISHED COPPER|19|4 +Brand#51|LARGE BURNISHED COPPER|45|4 +Brand#51|LARGE BURNISHED NICKEL|9|4 +Brand#51|LARGE BURNISHED NICKEL|14|4 +Brand#51|LARGE BURNISHED NICKEL|19|4 +Brand#51|LARGE BURNISHED NICKEL|36|4 +Brand#51|LARGE BURNISHED NICKEL|45|4 +Brand#51|LARGE BURNISHED NICKEL|49|4 +Brand#51|LARGE BURNISHED STEEL|19|4 +Brand#51|LARGE BURNISHED STEEL|45|4 +Brand#51|LARGE BURNISHED TIN|3|4 +Brand#51|LARGE BURNISHED TIN|9|4 +Brand#51|LARGE BURNISHED TIN|14|4 +Brand#51|LARGE BURNISHED TIN|36|4 +Brand#51|LARGE BURNISHED TIN|49|4 +Brand#51|LARGE PLATED BRASS|9|4 +Brand#51|LARGE PLATED BRASS|14|4 +Brand#51|LARGE PLATED BRASS|19|4 +Brand#51|LARGE PLATED COPPER|3|4 +Brand#51|LARGE PLATED COPPER|14|4 +Brand#51|LARGE PLATED COPPER|19|4 +Brand#51|LARGE PLATED NICKEL|14|4 +Brand#51|LARGE PLATED STEEL|9|4 +Brand#51|LARGE PLATED STEEL|14|4 +Brand#51|LARGE PLATED STEEL|19|4 +Brand#51|LARGE PLATED STEEL|23|4 +Brand#51|LARGE PLATED TIN|45|4 +Brand#51|LARGE PLATED TIN|49|4 +Brand#51|LARGE POLISHED BRASS|14|4 +Brand#51|LARGE POLISHED BRASS|19|4 +Brand#51|LARGE POLISHED BRASS|49|4 +Brand#51|LARGE POLISHED COPPER|9|4 +Brand#51|LARGE POLISHED COPPER|19|4 +Brand#51|LARGE POLISHED COPPER|49|4 +Brand#51|LARGE POLISHED NICKEL|3|4 +Brand#51|LARGE POLISHED NICKEL|23|4 +Brand#51|LARGE POLISHED NICKEL|36|4 +Brand#51|LARGE POLISHED NICKEL|45|4 +Brand#51|LARGE POLISHED STEEL|19|4 +Brand#51|LARGE POLISHED STEEL|23|4 +Brand#51|LARGE POLISHED STEEL|36|4 +Brand#51|LARGE POLISHED TIN|19|4 +Brand#51|MEDIUM ANODIZED BRASS|14|4 +Brand#51|MEDIUM ANODIZED BRASS|19|4 +Brand#51|MEDIUM ANODIZED BRASS|36|4 +Brand#51|MEDIUM ANODIZED COPPER|19|4 +Brand#51|MEDIUM ANODIZED COPPER|36|4 +Brand#51|MEDIUM ANODIZED STEEL|19|4 +Brand#51|MEDIUM ANODIZED STEEL|45|4 +Brand#51|MEDIUM ANODIZED TIN|49|4 +Brand#51|MEDIUM BRUSHED BRASS|3|4 +Brand#51|MEDIUM BRUSHED BRASS|23|4 +Brand#51|MEDIUM BRUSHED BRASS|36|4 +Brand#51|MEDIUM BRUSHED BRASS|45|4 +Brand#51|MEDIUM BRUSHED COPPER|9|4 +Brand#51|MEDIUM BRUSHED COPPER|14|4 +Brand#51|MEDIUM BRUSHED COPPER|23|4 +Brand#51|MEDIUM BRUSHED COPPER|36|4 +Brand#51|MEDIUM BRUSHED NICKEL|3|4 +Brand#51|MEDIUM BRUSHED NICKEL|9|4 +Brand#51|MEDIUM BRUSHED NICKEL|19|4 +Brand#51|MEDIUM BRUSHED NICKEL|23|4 +Brand#51|MEDIUM BRUSHED NICKEL|36|4 +Brand#51|MEDIUM BRUSHED STEEL|3|4 +Brand#51|MEDIUM BRUSHED STEEL|9|4 +Brand#51|MEDIUM BRUSHED STEEL|19|4 +Brand#51|MEDIUM BRUSHED STEEL|45|4 +Brand#51|MEDIUM BRUSHED TIN|3|4 +Brand#51|MEDIUM BRUSHED TIN|19|4 +Brand#51|MEDIUM BRUSHED TIN|36|4 +Brand#51|MEDIUM BURNISHED BRASS|3|4 +Brand#51|MEDIUM BURNISHED BRASS|19|4 +Brand#51|MEDIUM BURNISHED BRASS|23|4 +Brand#51|MEDIUM BURNISHED COPPER|14|4 +Brand#51|MEDIUM BURNISHED COPPER|23|4 +Brand#51|MEDIUM BURNISHED COPPER|36|4 +Brand#51|MEDIUM BURNISHED COPPER|45|4 +Brand#51|MEDIUM BURNISHED COPPER|49|4 +Brand#51|MEDIUM BURNISHED NICKEL|3|4 +Brand#51|MEDIUM BURNISHED NICKEL|19|4 +Brand#51|MEDIUM BURNISHED NICKEL|45|4 +Brand#51|MEDIUM BURNISHED STEEL|19|4 +Brand#51|MEDIUM BURNISHED STEEL|36|4 +Brand#51|MEDIUM BURNISHED TIN|3|4 +Brand#51|MEDIUM BURNISHED TIN|14|4 +Brand#51|MEDIUM BURNISHED TIN|19|4 +Brand#51|MEDIUM BURNISHED TIN|23|4 +Brand#51|MEDIUM BURNISHED TIN|36|4 +Brand#51|MEDIUM BURNISHED TIN|45|4 +Brand#51|MEDIUM PLATED BRASS|3|4 +Brand#51|MEDIUM PLATED BRASS|23|4 +Brand#51|MEDIUM PLATED BRASS|36|4 +Brand#51|MEDIUM PLATED COPPER|3|4 +Brand#51|MEDIUM PLATED COPPER|14|4 +Brand#51|MEDIUM PLATED COPPER|23|4 +Brand#51|MEDIUM PLATED COPPER|36|4 +Brand#51|MEDIUM PLATED COPPER|45|4 +Brand#51|MEDIUM PLATED COPPER|49|4 +Brand#51|MEDIUM PLATED NICKEL|19|4 +Brand#51|MEDIUM PLATED STEEL|14|4 +Brand#51|MEDIUM PLATED STEEL|19|4 +Brand#51|MEDIUM PLATED STEEL|23|4 +Brand#51|MEDIUM PLATED STEEL|36|4 +Brand#51|MEDIUM PLATED STEEL|45|4 +Brand#51|MEDIUM PLATED TIN|3|4 +Brand#51|MEDIUM PLATED TIN|9|4 +Brand#51|MEDIUM PLATED TIN|19|4 +Brand#51|MEDIUM PLATED TIN|49|4 +Brand#51|PROMO ANODIZED BRASS|45|4 +Brand#51|PROMO ANODIZED BRASS|49|4 +Brand#51|PROMO ANODIZED COPPER|3|4 +Brand#51|PROMO ANODIZED COPPER|9|4 +Brand#51|PROMO ANODIZED COPPER|14|4 +Brand#51|PROMO ANODIZED NICKEL|3|4 +Brand#51|PROMO ANODIZED NICKEL|36|4 +Brand#51|PROMO ANODIZED STEEL|3|4 +Brand#51|PROMO ANODIZED STEEL|23|4 +Brand#51|PROMO ANODIZED TIN|3|4 +Brand#51|PROMO ANODIZED TIN|45|4 +Brand#51|PROMO BRUSHED BRASS|3|4 +Brand#51|PROMO BRUSHED BRASS|14|4 +Brand#51|PROMO BRUSHED BRASS|36|4 +Brand#51|PROMO BRUSHED BRASS|49|4 +Brand#51|PROMO BRUSHED COPPER|3|4 +Brand#51|PROMO BRUSHED COPPER|9|4 +Brand#51|PROMO BRUSHED COPPER|14|4 +Brand#51|PROMO BRUSHED COPPER|45|4 +Brand#51|PROMO BRUSHED NICKEL|45|4 +Brand#51|PROMO BRUSHED STEEL|3|4 +Brand#51|PROMO BRUSHED STEEL|14|4 +Brand#51|PROMO BRUSHED STEEL|23|4 +Brand#51|PROMO BRUSHED STEEL|45|4 +Brand#51|PROMO BRUSHED TIN|9|4 +Brand#51|PROMO BRUSHED TIN|19|4 +Brand#51|PROMO BRUSHED TIN|49|4 +Brand#51|PROMO BURNISHED BRASS|36|4 +Brand#51|PROMO BURNISHED BRASS|49|4 +Brand#51|PROMO BURNISHED COPPER|14|4 +Brand#51|PROMO BURNISHED COPPER|36|4 +Brand#51|PROMO BURNISHED COPPER|45|4 +Brand#51|PROMO BURNISHED COPPER|49|4 +Brand#51|PROMO BURNISHED NICKEL|9|4 +Brand#51|PROMO BURNISHED NICKEL|19|4 +Brand#51|PROMO BURNISHED NICKEL|23|4 +Brand#51|PROMO BURNISHED NICKEL|36|4 +Brand#51|PROMO BURNISHED NICKEL|45|4 +Brand#51|PROMO BURNISHED NICKEL|49|4 +Brand#51|PROMO BURNISHED STEEL|3|4 +Brand#51|PROMO BURNISHED STEEL|19|4 +Brand#51|PROMO BURNISHED STEEL|45|4 +Brand#51|PROMO BURNISHED TIN|49|4 +Brand#51|PROMO PLATED BRASS|3|4 +Brand#51|PROMO PLATED BRASS|23|4 +Brand#51|PROMO PLATED BRASS|45|4 +Brand#51|PROMO PLATED COPPER|3|4 +Brand#51|PROMO PLATED COPPER|45|4 +Brand#51|PROMO PLATED COPPER|49|4 +Brand#51|PROMO PLATED NICKEL|3|4 +Brand#51|PROMO PLATED STEEL|19|4 +Brand#51|PROMO PLATED TIN|23|4 +Brand#51|PROMO PLATED TIN|36|4 +Brand#51|PROMO PLATED TIN|45|4 +Brand#51|PROMO POLISHED BRASS|14|4 +Brand#51|PROMO POLISHED BRASS|36|4 +Brand#51|PROMO POLISHED BRASS|45|4 +Brand#51|PROMO POLISHED COPPER|23|4 +Brand#51|PROMO POLISHED COPPER|45|4 +Brand#51|PROMO POLISHED COPPER|49|4 +Brand#51|PROMO POLISHED NICKEL|9|4 +Brand#51|PROMO POLISHED NICKEL|14|4 +Brand#51|PROMO POLISHED NICKEL|36|4 +Brand#51|PROMO POLISHED NICKEL|45|4 +Brand#51|PROMO POLISHED NICKEL|49|4 +Brand#51|PROMO POLISHED STEEL|3|4 +Brand#51|PROMO POLISHED STEEL|9|4 +Brand#51|PROMO POLISHED STEEL|14|4 +Brand#51|PROMO POLISHED STEEL|23|4 +Brand#51|PROMO POLISHED STEEL|49|4 +Brand#51|PROMO POLISHED TIN|3|4 +Brand#51|PROMO POLISHED TIN|19|4 +Brand#51|PROMO POLISHED TIN|23|4 +Brand#51|PROMO POLISHED TIN|45|4 +Brand#51|PROMO POLISHED TIN|49|4 +Brand#51|SMALL ANODIZED BRASS|3|4 +Brand#51|SMALL ANODIZED BRASS|14|4 +Brand#51|SMALL ANODIZED BRASS|19|4 +Brand#51|SMALL ANODIZED BRASS|36|4 +Brand#51|SMALL ANODIZED BRASS|49|4 +Brand#51|SMALL ANODIZED COPPER|3|4 +Brand#51|SMALL ANODIZED COPPER|14|4 +Brand#51|SMALL ANODIZED COPPER|19|4 +Brand#51|SMALL ANODIZED NICKEL|3|4 +Brand#51|SMALL ANODIZED NICKEL|23|4 +Brand#51|SMALL ANODIZED STEEL|9|4 +Brand#51|SMALL ANODIZED STEEL|19|4 +Brand#51|SMALL ANODIZED TIN|23|4 +Brand#51|SMALL ANODIZED TIN|36|4 +Brand#51|SMALL ANODIZED TIN|45|4 +Brand#51|SMALL ANODIZED TIN|49|4 +Brand#51|SMALL BRUSHED BRASS|14|4 +Brand#51|SMALL BRUSHED BRASS|23|4 +Brand#51|SMALL BRUSHED BRASS|36|4 +Brand#51|SMALL BRUSHED COPPER|14|4 +Brand#51|SMALL BRUSHED COPPER|23|4 +Brand#51|SMALL BRUSHED NICKEL|19|4 +Brand#51|SMALL BRUSHED NICKEL|49|4 +Brand#51|SMALL BRUSHED STEEL|19|4 +Brand#51|SMALL BRUSHED STEEL|23|4 +Brand#51|SMALL BRUSHED STEEL|45|4 +Brand#51|SMALL BRUSHED STEEL|49|4 +Brand#51|SMALL BRUSHED TIN|3|4 +Brand#51|SMALL BRUSHED TIN|14|4 +Brand#51|SMALL BRUSHED TIN|49|4 +Brand#51|SMALL BURNISHED BRASS|3|4 +Brand#51|SMALL BURNISHED BRASS|45|4 +Brand#51|SMALL BURNISHED COPPER|9|4 +Brand#51|SMALL BURNISHED COPPER|49|4 +Brand#51|SMALL BURNISHED NICKEL|9|4 +Brand#51|SMALL BURNISHED NICKEL|36|4 +Brand#51|SMALL BURNISHED STEEL|3|4 +Brand#51|SMALL BURNISHED STEEL|9|4 +Brand#51|SMALL BURNISHED STEEL|23|4 +Brand#51|SMALL BURNISHED TIN|14|4 +Brand#51|SMALL BURNISHED TIN|19|4 +Brand#51|SMALL BURNISHED TIN|36|4 +Brand#51|SMALL BURNISHED TIN|45|4 +Brand#51|SMALL PLATED BRASS|9|4 +Brand#51|SMALL PLATED BRASS|14|4 +Brand#51|SMALL PLATED BRASS|45|4 +Brand#51|SMALL PLATED BRASS|49|4 +Brand#51|SMALL PLATED COPPER|3|4 +Brand#51|SMALL PLATED NICKEL|3|4 +Brand#51|SMALL PLATED NICKEL|19|4 +Brand#51|SMALL PLATED NICKEL|23|4 +Brand#51|SMALL PLATED NICKEL|45|4 +Brand#51|SMALL PLATED STEEL|3|4 +Brand#51|SMALL PLATED STEEL|14|4 +Brand#51|SMALL PLATED STEEL|23|4 +Brand#51|SMALL PLATED TIN|3|4 +Brand#51|SMALL PLATED TIN|45|4 +Brand#51|SMALL PLATED TIN|49|4 +Brand#51|SMALL POLISHED BRASS|3|4 +Brand#51|SMALL POLISHED BRASS|9|4 +Brand#51|SMALL POLISHED BRASS|14|4 +Brand#51|SMALL POLISHED BRASS|23|4 +Brand#51|SMALL POLISHED BRASS|49|4 +Brand#51|SMALL POLISHED COPPER|9|4 +Brand#51|SMALL POLISHED COPPER|14|4 +Brand#51|SMALL POLISHED COPPER|19|4 +Brand#51|SMALL POLISHED COPPER|49|4 +Brand#51|SMALL POLISHED NICKEL|9|4 +Brand#51|SMALL POLISHED NICKEL|14|4 +Brand#51|SMALL POLISHED NICKEL|36|4 +Brand#51|SMALL POLISHED NICKEL|45|4 +Brand#51|SMALL POLISHED NICKEL|49|4 +Brand#51|SMALL POLISHED STEEL|9|4 +Brand#51|SMALL POLISHED STEEL|19|4 +Brand#51|SMALL POLISHED STEEL|36|4 +Brand#51|SMALL POLISHED STEEL|49|4 +Brand#51|SMALL POLISHED TIN|3|4 +Brand#51|SMALL POLISHED TIN|9|4 +Brand#51|SMALL POLISHED TIN|14|4 +Brand#51|SMALL POLISHED TIN|45|4 +Brand#51|STANDARD ANODIZED BRASS|3|4 +Brand#51|STANDARD ANODIZED BRASS|14|4 +Brand#51|STANDARD ANODIZED BRASS|45|4 +Brand#51|STANDARD ANODIZED COPPER|3|4 +Brand#51|STANDARD ANODIZED COPPER|9|4 +Brand#51|STANDARD ANODIZED COPPER|23|4 +Brand#51|STANDARD ANODIZED COPPER|45|4 +Brand#51|STANDARD ANODIZED NICKEL|14|4 +Brand#51|STANDARD ANODIZED STEEL|3|4 +Brand#51|STANDARD ANODIZED STEEL|14|4 +Brand#51|STANDARD ANODIZED STEEL|23|4 +Brand#51|STANDARD ANODIZED STEEL|45|4 +Brand#51|STANDARD ANODIZED TIN|3|4 +Brand#51|STANDARD ANODIZED TIN|36|4 +Brand#51|STANDARD ANODIZED TIN|49|4 +Brand#51|STANDARD BRUSHED BRASS|3|4 +Brand#51|STANDARD BRUSHED BRASS|14|4 +Brand#51|STANDARD BRUSHED BRASS|23|4 +Brand#51|STANDARD BRUSHED BRASS|49|4 +Brand#51|STANDARD BRUSHED COPPER|9|4 +Brand#51|STANDARD BRUSHED COPPER|14|4 +Brand#51|STANDARD BRUSHED COPPER|49|4 +Brand#51|STANDARD BRUSHED NICKEL|3|4 +Brand#51|STANDARD BRUSHED NICKEL|36|4 +Brand#51|STANDARD BRUSHED STEEL|3|4 +Brand#51|STANDARD BRUSHED STEEL|9|4 +Brand#51|STANDARD BRUSHED TIN|3|4 +Brand#51|STANDARD BRUSHED TIN|14|4 +Brand#51|STANDARD BRUSHED TIN|49|4 +Brand#51|STANDARD BURNISHED BRASS|9|4 +Brand#51|STANDARD BURNISHED BRASS|36|4 +Brand#51|STANDARD BURNISHED BRASS|45|4 +Brand#51|STANDARD BURNISHED BRASS|49|4 +Brand#51|STANDARD BURNISHED COPPER|9|4 +Brand#51|STANDARD BURNISHED COPPER|19|4 +Brand#51|STANDARD BURNISHED COPPER|45|4 +Brand#51|STANDARD BURNISHED COPPER|49|4 +Brand#51|STANDARD BURNISHED NICKEL|45|4 +Brand#51|STANDARD BURNISHED STEEL|3|4 +Brand#51|STANDARD BURNISHED STEEL|49|4 +Brand#51|STANDARD BURNISHED TIN|3|4 +Brand#51|STANDARD BURNISHED TIN|23|4 +Brand#51|STANDARD BURNISHED TIN|45|4 +Brand#51|STANDARD BURNISHED TIN|49|4 +Brand#51|STANDARD PLATED BRASS|9|4 +Brand#51|STANDARD PLATED BRASS|14|4 +Brand#51|STANDARD PLATED COPPER|3|4 +Brand#51|STANDARD PLATED COPPER|14|4 +Brand#51|STANDARD PLATED COPPER|23|4 +Brand#51|STANDARD PLATED COPPER|49|4 +Brand#51|STANDARD PLATED NICKEL|3|4 +Brand#51|STANDARD PLATED NICKEL|23|4 +Brand#51|STANDARD PLATED NICKEL|36|4 +Brand#51|STANDARD PLATED NICKEL|45|4 +Brand#51|STANDARD PLATED NICKEL|49|4 +Brand#51|STANDARD PLATED STEEL|3|4 +Brand#51|STANDARD PLATED STEEL|9|4 +Brand#51|STANDARD PLATED STEEL|14|4 +Brand#51|STANDARD PLATED STEEL|23|4 +Brand#51|STANDARD PLATED STEEL|36|4 +Brand#51|STANDARD PLATED STEEL|49|4 +Brand#51|STANDARD PLATED TIN|3|4 +Brand#51|STANDARD PLATED TIN|49|4 +Brand#51|STANDARD POLISHED BRASS|9|4 +Brand#51|STANDARD POLISHED BRASS|14|4 +Brand#51|STANDARD POLISHED BRASS|19|4 +Brand#51|STANDARD POLISHED BRASS|36|4 +Brand#51|STANDARD POLISHED BRASS|49|4 +Brand#51|STANDARD POLISHED COPPER|14|4 +Brand#51|STANDARD POLISHED COPPER|19|4 +Brand#51|STANDARD POLISHED COPPER|23|4 +Brand#51|STANDARD POLISHED COPPER|36|4 +Brand#51|STANDARD POLISHED NICKEL|14|4 +Brand#51|STANDARD POLISHED NICKEL|23|4 +Brand#51|STANDARD POLISHED STEEL|3|4 +Brand#51|STANDARD POLISHED STEEL|23|4 +Brand#51|STANDARD POLISHED TIN|9|4 +Brand#51|STANDARD POLISHED TIN|36|4 +Brand#51|STANDARD POLISHED TIN|45|4 +Brand#52|ECONOMY ANODIZED BRASS|23|4 +Brand#52|ECONOMY ANODIZED COPPER|3|4 +Brand#52|ECONOMY ANODIZED COPPER|9|4 +Brand#52|ECONOMY ANODIZED COPPER|14|4 +Brand#52|ECONOMY ANODIZED COPPER|45|4 +Brand#52|ECONOMY ANODIZED COPPER|49|4 +Brand#52|ECONOMY ANODIZED NICKEL|19|4 +Brand#52|ECONOMY ANODIZED NICKEL|36|4 +Brand#52|ECONOMY ANODIZED NICKEL|49|4 +Brand#52|ECONOMY ANODIZED STEEL|9|4 +Brand#52|ECONOMY ANODIZED STEEL|45|4 +Brand#52|ECONOMY ANODIZED TIN|23|4 +Brand#52|ECONOMY BRUSHED BRASS|3|4 +Brand#52|ECONOMY BRUSHED BRASS|23|4 +Brand#52|ECONOMY BRUSHED COPPER|3|4 +Brand#52|ECONOMY BRUSHED COPPER|9|4 +Brand#52|ECONOMY BRUSHED COPPER|14|4 +Brand#52|ECONOMY BRUSHED COPPER|36|4 +Brand#52|ECONOMY BRUSHED NICKEL|14|4 +Brand#52|ECONOMY BRUSHED NICKEL|19|4 +Brand#52|ECONOMY BRUSHED NICKEL|36|4 +Brand#52|ECONOMY BRUSHED NICKEL|49|4 +Brand#52|ECONOMY BRUSHED STEEL|45|4 +Brand#52|ECONOMY BRUSHED STEEL|49|4 +Brand#52|ECONOMY BRUSHED TIN|3|4 +Brand#52|ECONOMY BRUSHED TIN|19|4 +Brand#52|ECONOMY BRUSHED TIN|23|4 +Brand#52|ECONOMY BURNISHED BRASS|3|4 +Brand#52|ECONOMY BURNISHED BRASS|9|4 +Brand#52|ECONOMY BURNISHED BRASS|14|4 +Brand#52|ECONOMY BURNISHED BRASS|19|4 +Brand#52|ECONOMY BURNISHED BRASS|23|4 +Brand#52|ECONOMY BURNISHED BRASS|36|4 +Brand#52|ECONOMY BURNISHED BRASS|45|4 +Brand#52|ECONOMY BURNISHED BRASS|49|4 +Brand#52|ECONOMY BURNISHED COPPER|23|4 +Brand#52|ECONOMY BURNISHED COPPER|36|4 +Brand#52|ECONOMY BURNISHED COPPER|49|4 +Brand#52|ECONOMY BURNISHED NICKEL|3|4 +Brand#52|ECONOMY BURNISHED NICKEL|9|4 +Brand#52|ECONOMY BURNISHED STEEL|3|4 +Brand#52|ECONOMY BURNISHED STEEL|23|4 +Brand#52|ECONOMY BURNISHED STEEL|49|4 +Brand#52|ECONOMY BURNISHED TIN|9|4 +Brand#52|ECONOMY BURNISHED TIN|23|4 +Brand#52|ECONOMY BURNISHED TIN|36|4 +Brand#52|ECONOMY BURNISHED TIN|45|4 +Brand#52|ECONOMY PLATED BRASS|9|4 +Brand#52|ECONOMY PLATED COPPER|14|4 +Brand#52|ECONOMY PLATED COPPER|23|4 +Brand#52|ECONOMY PLATED COPPER|45|4 +Brand#52|ECONOMY PLATED NICKEL|9|4 +Brand#52|ECONOMY PLATED NICKEL|19|4 +Brand#52|ECONOMY PLATED STEEL|9|4 +Brand#52|ECONOMY PLATED STEEL|19|4 +Brand#52|ECONOMY PLATED STEEL|23|4 +Brand#52|ECONOMY PLATED STEEL|36|4 +Brand#52|ECONOMY PLATED TIN|45|4 +Brand#52|ECONOMY PLATED TIN|49|4 +Brand#52|ECONOMY POLISHED BRASS|9|4 +Brand#52|ECONOMY POLISHED COPPER|9|4 +Brand#52|ECONOMY POLISHED COPPER|36|4 +Brand#52|ECONOMY POLISHED NICKEL|3|4 +Brand#52|ECONOMY POLISHED NICKEL|9|4 +Brand#52|ECONOMY POLISHED NICKEL|36|4 +Brand#52|ECONOMY POLISHED NICKEL|49|4 +Brand#52|ECONOMY POLISHED STEEL|14|4 +Brand#52|ECONOMY POLISHED STEEL|19|4 +Brand#52|ECONOMY POLISHED STEEL|23|4 +Brand#52|ECONOMY POLISHED STEEL|36|4 +Brand#52|ECONOMY POLISHED TIN|3|4 +Brand#52|ECONOMY POLISHED TIN|9|4 +Brand#52|LARGE ANODIZED BRASS|19|4 +Brand#52|LARGE ANODIZED BRASS|36|4 +Brand#52|LARGE ANODIZED BRASS|49|4 +Brand#52|LARGE ANODIZED COPPER|3|4 +Brand#52|LARGE ANODIZED COPPER|9|4 +Brand#52|LARGE ANODIZED COPPER|19|4 +Brand#52|LARGE ANODIZED COPPER|23|4 +Brand#52|LARGE ANODIZED COPPER|36|4 +Brand#52|LARGE ANODIZED NICKEL|9|4 +Brand#52|LARGE ANODIZED NICKEL|14|4 +Brand#52|LARGE ANODIZED NICKEL|19|4 +Brand#52|LARGE ANODIZED NICKEL|49|4 +Brand#52|LARGE ANODIZED STEEL|3|4 +Brand#52|LARGE ANODIZED STEEL|14|4 +Brand#52|LARGE ANODIZED TIN|19|4 +Brand#52|LARGE ANODIZED TIN|23|4 +Brand#52|LARGE ANODIZED TIN|45|4 +Brand#52|LARGE ANODIZED TIN|49|4 +Brand#52|LARGE BRUSHED BRASS|9|4 +Brand#52|LARGE BRUSHED BRASS|36|4 +Brand#52|LARGE BRUSHED COPPER|9|4 +Brand#52|LARGE BRUSHED COPPER|19|4 +Brand#52|LARGE BRUSHED COPPER|45|4 +Brand#52|LARGE BRUSHED NICKEL|3|4 +Brand#52|LARGE BRUSHED NICKEL|9|4 +Brand#52|LARGE BRUSHED NICKEL|19|4 +Brand#52|LARGE BRUSHED NICKEL|23|4 +Brand#52|LARGE BRUSHED NICKEL|45|4 +Brand#52|LARGE BRUSHED NICKEL|49|4 +Brand#52|LARGE BRUSHED STEEL|9|4 +Brand#52|LARGE BRUSHED STEEL|45|4 +Brand#52|LARGE BRUSHED STEEL|49|4 +Brand#52|LARGE BRUSHED TIN|3|4 +Brand#52|LARGE BRUSHED TIN|14|4 +Brand#52|LARGE BRUSHED TIN|36|4 +Brand#52|LARGE BURNISHED BRASS|3|4 +Brand#52|LARGE BURNISHED BRASS|9|4 +Brand#52|LARGE BURNISHED BRASS|23|4 +Brand#52|LARGE BURNISHED BRASS|45|4 +Brand#52|LARGE BURNISHED COPPER|36|4 +Brand#52|LARGE BURNISHED COPPER|49|4 +Brand#52|LARGE BURNISHED NICKEL|14|4 +Brand#52|LARGE BURNISHED NICKEL|19|4 +Brand#52|LARGE BURNISHED NICKEL|36|4 +Brand#52|LARGE BURNISHED NICKEL|45|4 +Brand#52|LARGE BURNISHED STEEL|36|4 +Brand#52|LARGE BURNISHED TIN|9|4 +Brand#52|LARGE BURNISHED TIN|19|4 +Brand#52|LARGE BURNISHED TIN|36|4 +Brand#52|LARGE BURNISHED TIN|49|4 +Brand#52|LARGE PLATED BRASS|3|4 +Brand#52|LARGE PLATED COPPER|9|4 +Brand#52|LARGE PLATED COPPER|49|4 +Brand#52|LARGE PLATED NICKEL|9|4 +Brand#52|LARGE PLATED NICKEL|36|4 +Brand#52|LARGE PLATED STEEL|9|4 +Brand#52|LARGE PLATED STEEL|19|4 +Brand#52|LARGE PLATED STEEL|45|4 +Brand#52|LARGE PLATED TIN|9|4 +Brand#52|LARGE POLISHED BRASS|36|4 +Brand#52|LARGE POLISHED COPPER|23|4 +Brand#52|LARGE POLISHED COPPER|45|4 +Brand#52|LARGE POLISHED NICKEL|3|4 +Brand#52|LARGE POLISHED NICKEL|14|4 +Brand#52|LARGE POLISHED NICKEL|19|4 +Brand#52|LARGE POLISHED NICKEL|36|4 +Brand#52|LARGE POLISHED NICKEL|45|4 +Brand#52|LARGE POLISHED STEEL|3|4 +Brand#52|LARGE POLISHED STEEL|9|4 +Brand#52|LARGE POLISHED TIN|3|4 +Brand#52|MEDIUM ANODIZED BRASS|14|4 +Brand#52|MEDIUM ANODIZED BRASS|23|4 +Brand#52|MEDIUM ANODIZED BRASS|45|4 +Brand#52|MEDIUM ANODIZED BRASS|49|4 +Brand#52|MEDIUM ANODIZED COPPER|9|4 +Brand#52|MEDIUM ANODIZED NICKEL|3|4 +Brand#52|MEDIUM ANODIZED NICKEL|19|4 +Brand#52|MEDIUM ANODIZED NICKEL|36|4 +Brand#52|MEDIUM ANODIZED STEEL|3|4 +Brand#52|MEDIUM ANODIZED STEEL|14|4 +Brand#52|MEDIUM ANODIZED TIN|14|4 +Brand#52|MEDIUM ANODIZED TIN|36|4 +Brand#52|MEDIUM BRUSHED BRASS|19|4 +Brand#52|MEDIUM BRUSHED COPPER|19|4 +Brand#52|MEDIUM BRUSHED COPPER|23|4 +Brand#52|MEDIUM BRUSHED COPPER|45|4 +Brand#52|MEDIUM BRUSHED NICKEL|3|4 +Brand#52|MEDIUM BRUSHED NICKEL|9|4 +Brand#52|MEDIUM BRUSHED STEEL|3|4 +Brand#52|MEDIUM BRUSHED STEEL|9|4 +Brand#52|MEDIUM BRUSHED STEEL|19|4 +Brand#52|MEDIUM BRUSHED TIN|3|4 +Brand#52|MEDIUM BRUSHED TIN|45|4 +Brand#52|MEDIUM BURNISHED BRASS|19|4 +Brand#52|MEDIUM BURNISHED BRASS|23|4 +Brand#52|MEDIUM BURNISHED BRASS|36|4 +Brand#52|MEDIUM BURNISHED COPPER|9|4 +Brand#52|MEDIUM BURNISHED COPPER|19|4 +Brand#52|MEDIUM BURNISHED COPPER|45|4 +Brand#52|MEDIUM BURNISHED COPPER|49|4 +Brand#52|MEDIUM BURNISHED NICKEL|3|4 +Brand#52|MEDIUM BURNISHED NICKEL|9|4 +Brand#52|MEDIUM BURNISHED STEEL|9|4 +Brand#52|MEDIUM BURNISHED STEEL|14|4 +Brand#52|MEDIUM BURNISHED STEEL|23|4 +Brand#52|MEDIUM BURNISHED STEEL|36|4 +Brand#52|MEDIUM BURNISHED STEEL|45|4 +Brand#52|MEDIUM BURNISHED STEEL|49|4 +Brand#52|MEDIUM BURNISHED TIN|36|4 +Brand#52|MEDIUM PLATED BRASS|3|4 +Brand#52|MEDIUM PLATED BRASS|9|4 +Brand#52|MEDIUM PLATED BRASS|19|4 +Brand#52|MEDIUM PLATED BRASS|36|4 +Brand#52|MEDIUM PLATED BRASS|45|4 +Brand#52|MEDIUM PLATED COPPER|3|4 +Brand#52|MEDIUM PLATED COPPER|45|4 +Brand#52|MEDIUM PLATED COPPER|49|4 +Brand#52|MEDIUM PLATED NICKEL|9|4 +Brand#52|MEDIUM PLATED NICKEL|14|4 +Brand#52|MEDIUM PLATED NICKEL|45|4 +Brand#52|MEDIUM PLATED STEEL|3|4 +Brand#52|MEDIUM PLATED STEEL|9|4 +Brand#52|MEDIUM PLATED STEEL|14|4 +Brand#52|MEDIUM PLATED STEEL|19|4 +Brand#52|MEDIUM PLATED STEEL|23|4 +Brand#52|MEDIUM PLATED STEEL|45|4 +Brand#52|MEDIUM PLATED STEEL|49|4 +Brand#52|MEDIUM PLATED TIN|19|4 +Brand#52|PROMO ANODIZED BRASS|14|4 +Brand#52|PROMO ANODIZED BRASS|19|4 +Brand#52|PROMO ANODIZED COPPER|3|4 +Brand#52|PROMO ANODIZED COPPER|9|4 +Brand#52|PROMO ANODIZED COPPER|45|4 +Brand#52|PROMO ANODIZED NICKEL|14|4 +Brand#52|PROMO ANODIZED NICKEL|19|4 +Brand#52|PROMO ANODIZED NICKEL|23|4 +Brand#52|PROMO ANODIZED NICKEL|36|4 +Brand#52|PROMO ANODIZED NICKEL|45|4 +Brand#52|PROMO ANODIZED STEEL|3|4 +Brand#52|PROMO ANODIZED STEEL|14|4 +Brand#52|PROMO ANODIZED STEEL|45|4 +Brand#52|PROMO ANODIZED TIN|45|4 +Brand#52|PROMO BRUSHED BRASS|19|4 +Brand#52|PROMO BRUSHED BRASS|23|4 +Brand#52|PROMO BRUSHED BRASS|49|4 +Brand#52|PROMO BRUSHED COPPER|3|4 +Brand#52|PROMO BRUSHED COPPER|9|4 +Brand#52|PROMO BRUSHED COPPER|19|4 +Brand#52|PROMO BRUSHED COPPER|23|4 +Brand#52|PROMO BRUSHED COPPER|36|4 +Brand#52|PROMO BRUSHED NICKEL|14|4 +Brand#52|PROMO BRUSHED NICKEL|36|4 +Brand#52|PROMO BRUSHED STEEL|3|4 +Brand#52|PROMO BRUSHED STEEL|19|4 +Brand#52|PROMO BRUSHED STEEL|45|4 +Brand#52|PROMO BRUSHED STEEL|49|4 +Brand#52|PROMO BRUSHED TIN|3|4 +Brand#52|PROMO BRUSHED TIN|19|4 +Brand#52|PROMO BRUSHED TIN|23|4 +Brand#52|PROMO BRUSHED TIN|45|4 +Brand#52|PROMO BRUSHED TIN|49|4 +Brand#52|PROMO BURNISHED BRASS|45|4 +Brand#52|PROMO BURNISHED BRASS|49|4 +Brand#52|PROMO BURNISHED COPPER|9|4 +Brand#52|PROMO BURNISHED COPPER|36|4 +Brand#52|PROMO BURNISHED NICKEL|45|4 +Brand#52|PROMO BURNISHED STEEL|9|4 +Brand#52|PROMO BURNISHED STEEL|14|4 +Brand#52|PROMO BURNISHED STEEL|23|4 +Brand#52|PROMO BURNISHED STEEL|36|4 +Brand#52|PROMO BURNISHED STEEL|49|4 +Brand#52|PROMO BURNISHED TIN|9|4 +Brand#52|PROMO BURNISHED TIN|14|4 +Brand#52|PROMO BURNISHED TIN|36|4 +Brand#52|PROMO BURNISHED TIN|49|4 +Brand#52|PROMO PLATED BRASS|19|4 +Brand#52|PROMO PLATED BRASS|23|4 +Brand#52|PROMO PLATED BRASS|36|4 +Brand#52|PROMO PLATED COPPER|19|4 +Brand#52|PROMO PLATED COPPER|23|4 +Brand#52|PROMO PLATED NICKEL|3|4 +Brand#52|PROMO PLATED STEEL|36|4 +Brand#52|PROMO PLATED STEEL|45|4 +Brand#52|PROMO PLATED TIN|14|4 +Brand#52|PROMO PLATED TIN|19|4 +Brand#52|PROMO PLATED TIN|49|4 +Brand#52|PROMO POLISHED BRASS|9|4 +Brand#52|PROMO POLISHED BRASS|49|4 +Brand#52|PROMO POLISHED COPPER|3|4 +Brand#52|PROMO POLISHED COPPER|9|4 +Brand#52|PROMO POLISHED NICKEL|3|4 +Brand#52|PROMO POLISHED NICKEL|9|4 +Brand#52|PROMO POLISHED NICKEL|19|4 +Brand#52|PROMO POLISHED NICKEL|36|4 +Brand#52|PROMO POLISHED NICKEL|45|4 +Brand#52|PROMO POLISHED STEEL|3|4 +Brand#52|PROMO POLISHED STEEL|9|4 +Brand#52|PROMO POLISHED STEEL|14|4 +Brand#52|PROMO POLISHED STEEL|36|4 +Brand#52|PROMO POLISHED TIN|36|4 +Brand#52|SMALL ANODIZED BRASS|49|4 +Brand#52|SMALL ANODIZED COPPER|49|4 +Brand#52|SMALL ANODIZED NICKEL|9|4 +Brand#52|SMALL ANODIZED NICKEL|23|4 +Brand#52|SMALL ANODIZED NICKEL|49|4 +Brand#52|SMALL ANODIZED STEEL|9|4 +Brand#52|SMALL ANODIZED STEEL|19|4 +Brand#52|SMALL ANODIZED STEEL|49|4 +Brand#52|SMALL ANODIZED TIN|3|4 +Brand#52|SMALL BRUSHED BRASS|3|4 +Brand#52|SMALL BRUSHED BRASS|23|4 +Brand#52|SMALL BRUSHED BRASS|45|4 +Brand#52|SMALL BRUSHED COPPER|3|4 +Brand#52|SMALL BRUSHED COPPER|19|4 +Brand#52|SMALL BRUSHED COPPER|36|4 +Brand#52|SMALL BRUSHED COPPER|45|4 +Brand#52|SMALL BRUSHED COPPER|49|4 +Brand#52|SMALL BRUSHED NICKEL|3|4 +Brand#52|SMALL BRUSHED NICKEL|23|4 +Brand#52|SMALL BRUSHED NICKEL|36|4 +Brand#52|SMALL BRUSHED NICKEL|45|4 +Brand#52|SMALL BRUSHED STEEL|3|4 +Brand#52|SMALL BRUSHED STEEL|14|4 +Brand#52|SMALL BRUSHED STEEL|23|4 +Brand#52|SMALL BRUSHED TIN|9|4 +Brand#52|SMALL BRUSHED TIN|14|4 +Brand#52|SMALL BURNISHED BRASS|3|4 +Brand#52|SMALL BURNISHED BRASS|23|4 +Brand#52|SMALL BURNISHED BRASS|36|4 +Brand#52|SMALL BURNISHED BRASS|49|4 +Brand#52|SMALL BURNISHED COPPER|3|4 +Brand#52|SMALL BURNISHED COPPER|36|4 +Brand#52|SMALL BURNISHED COPPER|49|4 +Brand#52|SMALL BURNISHED NICKEL|23|4 +Brand#52|SMALL BURNISHED STEEL|36|4 +Brand#52|SMALL BURNISHED STEEL|45|4 +Brand#52|SMALL BURNISHED STEEL|49|4 +Brand#52|SMALL BURNISHED TIN|9|4 +Brand#52|SMALL BURNISHED TIN|19|4 +Brand#52|SMALL BURNISHED TIN|23|4 +Brand#52|SMALL BURNISHED TIN|45|4 +Brand#52|SMALL BURNISHED TIN|49|4 +Brand#52|SMALL PLATED BRASS|14|4 +Brand#52|SMALL PLATED BRASS|19|4 +Brand#52|SMALL PLATED COPPER|9|4 +Brand#52|SMALL PLATED COPPER|45|4 +Brand#52|SMALL PLATED NICKEL|9|4 +Brand#52|SMALL PLATED NICKEL|49|4 +Brand#52|SMALL PLATED STEEL|9|4 +Brand#52|SMALL PLATED STEEL|49|4 +Brand#52|SMALL PLATED TIN|9|4 +Brand#52|SMALL PLATED TIN|45|4 +Brand#52|SMALL PLATED TIN|49|4 +Brand#52|SMALL POLISHED BRASS|9|4 +Brand#52|SMALL POLISHED BRASS|36|4 +Brand#52|SMALL POLISHED BRASS|45|4 +Brand#52|SMALL POLISHED COPPER|3|4 +Brand#52|SMALL POLISHED COPPER|14|4 +Brand#52|SMALL POLISHED COPPER|23|4 +Brand#52|SMALL POLISHED NICKEL|14|4 +Brand#52|SMALL POLISHED NICKEL|23|4 +Brand#52|SMALL POLISHED NICKEL|36|4 +Brand#52|SMALL POLISHED NICKEL|45|4 +Brand#52|SMALL POLISHED NICKEL|49|4 +Brand#52|SMALL POLISHED STEEL|45|4 +Brand#52|SMALL POLISHED TIN|3|4 +Brand#52|SMALL POLISHED TIN|23|4 +Brand#52|SMALL POLISHED TIN|36|4 +Brand#52|SMALL POLISHED TIN|45|4 +Brand#52|SMALL POLISHED TIN|49|4 +Brand#52|STANDARD ANODIZED BRASS|3|4 +Brand#52|STANDARD ANODIZED BRASS|19|4 +Brand#52|STANDARD ANODIZED BRASS|36|4 +Brand#52|STANDARD ANODIZED COPPER|14|4 +Brand#52|STANDARD ANODIZED COPPER|23|4 +Brand#52|STANDARD ANODIZED NICKEL|9|4 +Brand#52|STANDARD ANODIZED NICKEL|19|4 +Brand#52|STANDARD ANODIZED NICKEL|36|4 +Brand#52|STANDARD ANODIZED NICKEL|45|4 +Brand#52|STANDARD ANODIZED NICKEL|49|4 +Brand#52|STANDARD ANODIZED STEEL|9|4 +Brand#52|STANDARD ANODIZED STEEL|36|4 +Brand#52|STANDARD ANODIZED STEEL|45|4 +Brand#52|STANDARD ANODIZED TIN|9|4 +Brand#52|STANDARD ANODIZED TIN|23|4 +Brand#52|STANDARD ANODIZED TIN|36|4 +Brand#52|STANDARD ANODIZED TIN|49|4 +Brand#52|STANDARD BRUSHED BRASS|9|4 +Brand#52|STANDARD BRUSHED BRASS|23|4 +Brand#52|STANDARD BRUSHED BRASS|45|4 +Brand#52|STANDARD BRUSHED BRASS|49|4 +Brand#52|STANDARD BRUSHED COPPER|23|4 +Brand#52|STANDARD BRUSHED COPPER|49|4 +Brand#52|STANDARD BRUSHED NICKEL|45|4 +Brand#52|STANDARD BRUSHED STEEL|3|4 +Brand#52|STANDARD BRUSHED STEEL|19|4 +Brand#52|STANDARD BRUSHED STEEL|36|4 +Brand#52|STANDARD BRUSHED STEEL|45|4 +Brand#52|STANDARD BRUSHED TIN|14|4 +Brand#52|STANDARD BRUSHED TIN|19|4 +Brand#52|STANDARD BRUSHED TIN|23|4 +Brand#52|STANDARD BRUSHED TIN|45|4 +Brand#52|STANDARD BURNISHED BRASS|9|4 +Brand#52|STANDARD BURNISHED BRASS|45|4 +Brand#52|STANDARD BURNISHED COPPER|9|4 +Brand#52|STANDARD BURNISHED COPPER|36|4 +Brand#52|STANDARD BURNISHED COPPER|45|4 +Brand#52|STANDARD BURNISHED NICKEL|9|4 +Brand#52|STANDARD BURNISHED NICKEL|14|4 +Brand#52|STANDARD BURNISHED NICKEL|19|4 +Brand#52|STANDARD BURNISHED NICKEL|23|4 +Brand#52|STANDARD BURNISHED NICKEL|45|4 +Brand#52|STANDARD BURNISHED STEEL|19|4 +Brand#52|STANDARD BURNISHED STEEL|45|4 +Brand#52|STANDARD BURNISHED TIN|3|4 +Brand#52|STANDARD BURNISHED TIN|36|4 +Brand#52|STANDARD PLATED BRASS|3|4 +Brand#52|STANDARD PLATED BRASS|9|4 +Brand#52|STANDARD PLATED BRASS|14|4 +Brand#52|STANDARD PLATED COPPER|14|4 +Brand#52|STANDARD PLATED COPPER|19|4 +Brand#52|STANDARD PLATED COPPER|36|4 +Brand#52|STANDARD PLATED NICKEL|19|4 +Brand#52|STANDARD PLATED NICKEL|23|4 +Brand#52|STANDARD PLATED NICKEL|36|4 +Brand#52|STANDARD PLATED NICKEL|49|4 +Brand#52|STANDARD PLATED STEEL|23|4 +Brand#52|STANDARD PLATED STEEL|49|4 +Brand#52|STANDARD PLATED TIN|19|4 +Brand#52|STANDARD POLISHED BRASS|19|4 +Brand#52|STANDARD POLISHED BRASS|23|4 +Brand#52|STANDARD POLISHED COPPER|3|4 +Brand#52|STANDARD POLISHED COPPER|19|4 +Brand#52|STANDARD POLISHED COPPER|23|4 +Brand#52|STANDARD POLISHED COPPER|45|4 +Brand#52|STANDARD POLISHED COPPER|49|4 +Brand#52|STANDARD POLISHED NICKEL|9|4 +Brand#52|STANDARD POLISHED STEEL|3|4 +Brand#52|STANDARD POLISHED STEEL|14|4 +Brand#52|STANDARD POLISHED STEEL|19|4 +Brand#52|STANDARD POLISHED TIN|9|4 +Brand#52|STANDARD POLISHED TIN|45|4 +Brand#53|ECONOMY ANODIZED BRASS|3|4 +Brand#53|ECONOMY ANODIZED BRASS|14|4 +Brand#53|ECONOMY ANODIZED BRASS|23|4 +Brand#53|ECONOMY ANODIZED COPPER|3|4 +Brand#53|ECONOMY ANODIZED COPPER|9|4 +Brand#53|ECONOMY ANODIZED COPPER|14|4 +Brand#53|ECONOMY ANODIZED COPPER|49|4 +Brand#53|ECONOMY ANODIZED NICKEL|3|4 +Brand#53|ECONOMY ANODIZED NICKEL|23|4 +Brand#53|ECONOMY ANODIZED NICKEL|45|4 +Brand#53|ECONOMY ANODIZED NICKEL|49|4 +Brand#53|ECONOMY ANODIZED STEEL|3|4 +Brand#53|ECONOMY ANODIZED STEEL|19|4 +Brand#53|ECONOMY ANODIZED STEEL|36|4 +Brand#53|ECONOMY ANODIZED STEEL|49|4 +Brand#53|ECONOMY ANODIZED TIN|19|4 +Brand#53|ECONOMY ANODIZED TIN|49|4 +Brand#53|ECONOMY BRUSHED BRASS|9|4 +Brand#53|ECONOMY BRUSHED BRASS|14|4 +Brand#53|ECONOMY BRUSHED COPPER|9|4 +Brand#53|ECONOMY BRUSHED COPPER|14|4 +Brand#53|ECONOMY BRUSHED COPPER|19|4 +Brand#53|ECONOMY BRUSHED COPPER|23|4 +Brand#53|ECONOMY BRUSHED COPPER|36|4 +Brand#53|ECONOMY BRUSHED NICKEL|3|4 +Brand#53|ECONOMY BRUSHED NICKEL|45|4 +Brand#53|ECONOMY BRUSHED STEEL|9|4 +Brand#53|ECONOMY BRUSHED STEEL|14|4 +Brand#53|ECONOMY BRUSHED STEEL|36|4 +Brand#53|ECONOMY BRUSHED TIN|14|4 +Brand#53|ECONOMY BRUSHED TIN|23|4 +Brand#53|ECONOMY BRUSHED TIN|45|4 +Brand#53|ECONOMY BRUSHED TIN|49|4 +Brand#53|ECONOMY BURNISHED BRASS|3|4 +Brand#53|ECONOMY BURNISHED BRASS|14|4 +Brand#53|ECONOMY BURNISHED BRASS|19|4 +Brand#53|ECONOMY BURNISHED BRASS|23|4 +Brand#53|ECONOMY BURNISHED BRASS|36|4 +Brand#53|ECONOMY BURNISHED COPPER|3|4 +Brand#53|ECONOMY BURNISHED COPPER|36|4 +Brand#53|ECONOMY BURNISHED COPPER|49|4 +Brand#53|ECONOMY BURNISHED NICKEL|9|4 +Brand#53|ECONOMY BURNISHED NICKEL|49|4 +Brand#53|ECONOMY BURNISHED STEEL|3|4 +Brand#53|ECONOMY BURNISHED STEEL|9|4 +Brand#53|ECONOMY BURNISHED STEEL|14|4 +Brand#53|ECONOMY BURNISHED STEEL|49|4 +Brand#53|ECONOMY BURNISHED TIN|9|4 +Brand#53|ECONOMY BURNISHED TIN|19|4 +Brand#53|ECONOMY BURNISHED TIN|36|4 +Brand#53|ECONOMY BURNISHED TIN|45|4 +Brand#53|ECONOMY PLATED BRASS|3|4 +Brand#53|ECONOMY PLATED BRASS|49|4 +Brand#53|ECONOMY PLATED COPPER|14|4 +Brand#53|ECONOMY PLATED NICKEL|14|4 +Brand#53|ECONOMY PLATED NICKEL|19|4 +Brand#53|ECONOMY PLATED NICKEL|36|4 +Brand#53|ECONOMY PLATED NICKEL|45|4 +Brand#53|ECONOMY PLATED NICKEL|49|4 +Brand#53|ECONOMY PLATED STEEL|14|4 +Brand#53|ECONOMY PLATED STEEL|19|4 +Brand#53|ECONOMY PLATED STEEL|23|4 +Brand#53|ECONOMY PLATED TIN|36|4 +Brand#53|ECONOMY PLATED TIN|49|4 +Brand#53|ECONOMY POLISHED BRASS|3|4 +Brand#53|ECONOMY POLISHED BRASS|9|4 +Brand#53|ECONOMY POLISHED BRASS|23|4 +Brand#53|ECONOMY POLISHED BRASS|36|4 +Brand#53|ECONOMY POLISHED BRASS|45|4 +Brand#53|ECONOMY POLISHED BRASS|49|4 +Brand#53|ECONOMY POLISHED COPPER|9|4 +Brand#53|ECONOMY POLISHED COPPER|36|4 +Brand#53|ECONOMY POLISHED COPPER|45|4 +Brand#53|ECONOMY POLISHED COPPER|49|4 +Brand#53|ECONOMY POLISHED NICKEL|14|4 +Brand#53|ECONOMY POLISHED NICKEL|19|4 +Brand#53|ECONOMY POLISHED NICKEL|45|4 +Brand#53|ECONOMY POLISHED NICKEL|49|4 +Brand#53|ECONOMY POLISHED STEEL|19|4 +Brand#53|ECONOMY POLISHED TIN|23|4 +Brand#53|LARGE ANODIZED BRASS|3|4 +Brand#53|LARGE ANODIZED BRASS|9|4 +Brand#53|LARGE ANODIZED BRASS|49|4 +Brand#53|LARGE ANODIZED COPPER|3|4 +Brand#53|LARGE ANODIZED COPPER|23|4 +Brand#53|LARGE ANODIZED COPPER|36|4 +Brand#53|LARGE ANODIZED NICKEL|3|4 +Brand#53|LARGE ANODIZED NICKEL|14|4 +Brand#53|LARGE ANODIZED NICKEL|19|4 +Brand#53|LARGE ANODIZED NICKEL|23|4 +Brand#53|LARGE ANODIZED NICKEL|36|4 +Brand#53|LARGE ANODIZED NICKEL|45|4 +Brand#53|LARGE ANODIZED NICKEL|49|4 +Brand#53|LARGE ANODIZED STEEL|9|4 +Brand#53|LARGE ANODIZED STEEL|14|4 +Brand#53|LARGE ANODIZED STEEL|36|4 +Brand#53|LARGE ANODIZED TIN|3|4 +Brand#53|LARGE ANODIZED TIN|14|4 +Brand#53|LARGE ANODIZED TIN|19|4 +Brand#53|LARGE BRUSHED BRASS|3|4 +Brand#53|LARGE BRUSHED BRASS|23|4 +Brand#53|LARGE BRUSHED BRASS|45|4 +Brand#53|LARGE BRUSHED COPPER|3|4 +Brand#53|LARGE BRUSHED COPPER|9|4 +Brand#53|LARGE BRUSHED COPPER|23|4 +Brand#53|LARGE BRUSHED NICKEL|3|4 +Brand#53|LARGE BRUSHED NICKEL|14|4 +Brand#53|LARGE BRUSHED NICKEL|19|4 +Brand#53|LARGE BRUSHED NICKEL|36|4 +Brand#53|LARGE BRUSHED NICKEL|49|4 +Brand#53|LARGE BRUSHED STEEL|3|4 +Brand#53|LARGE BRUSHED STEEL|14|4 +Brand#53|LARGE BRUSHED STEEL|23|4 +Brand#53|LARGE BRUSHED STEEL|49|4 +Brand#53|LARGE BRUSHED TIN|14|4 +Brand#53|LARGE BRUSHED TIN|45|4 +Brand#53|LARGE BRUSHED TIN|49|4 +Brand#53|LARGE BURNISHED BRASS|19|4 +Brand#53|LARGE BURNISHED BRASS|23|4 +Brand#53|LARGE BURNISHED BRASS|36|4 +Brand#53|LARGE BURNISHED BRASS|45|4 +Brand#53|LARGE BURNISHED COPPER|19|4 +Brand#53|LARGE BURNISHED COPPER|45|4 +Brand#53|LARGE BURNISHED COPPER|49|4 +Brand#53|LARGE BURNISHED NICKEL|36|4 +Brand#53|LARGE BURNISHED STEEL|9|4 +Brand#53|LARGE BURNISHED STEEL|49|4 +Brand#53|LARGE BURNISHED TIN|3|4 +Brand#53|LARGE BURNISHED TIN|23|4 +Brand#53|LARGE BURNISHED TIN|49|4 +Brand#53|LARGE PLATED BRASS|14|4 +Brand#53|LARGE PLATED BRASS|19|4 +Brand#53|LARGE PLATED BRASS|45|4 +Brand#53|LARGE PLATED COPPER|14|4 +Brand#53|LARGE PLATED COPPER|23|4 +Brand#53|LARGE PLATED COPPER|45|4 +Brand#53|LARGE PLATED NICKEL|19|4 +Brand#53|LARGE PLATED NICKEL|23|4 +Brand#53|LARGE PLATED NICKEL|36|4 +Brand#53|LARGE PLATED STEEL|19|4 +Brand#53|LARGE PLATED STEEL|49|4 +Brand#53|LARGE PLATED TIN|3|4 +Brand#53|LARGE PLATED TIN|19|4 +Brand#53|LARGE POLISHED BRASS|9|4 +Brand#53|LARGE POLISHED BRASS|19|4 +Brand#53|LARGE POLISHED COPPER|14|4 +Brand#53|LARGE POLISHED COPPER|19|4 +Brand#53|LARGE POLISHED COPPER|36|4 +Brand#53|LARGE POLISHED NICKEL|45|4 +Brand#53|LARGE POLISHED STEEL|9|4 +Brand#53|LARGE POLISHED TIN|14|4 +Brand#53|LARGE POLISHED TIN|19|4 +Brand#53|LARGE POLISHED TIN|36|4 +Brand#53|LARGE POLISHED TIN|45|4 +Brand#53|MEDIUM ANODIZED BRASS|9|4 +Brand#53|MEDIUM ANODIZED BRASS|19|4 +Brand#53|MEDIUM ANODIZED BRASS|23|4 +Brand#53|MEDIUM ANODIZED BRASS|45|4 +Brand#53|MEDIUM ANODIZED COPPER|36|4 +Brand#53|MEDIUM ANODIZED COPPER|49|4 +Brand#53|MEDIUM ANODIZED NICKEL|3|4 +Brand#53|MEDIUM ANODIZED NICKEL|9|4 +Brand#53|MEDIUM ANODIZED STEEL|3|4 +Brand#53|MEDIUM ANODIZED STEEL|19|4 +Brand#53|MEDIUM ANODIZED STEEL|45|4 +Brand#53|MEDIUM ANODIZED TIN|9|4 +Brand#53|MEDIUM ANODIZED TIN|19|4 +Brand#53|MEDIUM ANODIZED TIN|45|4 +Brand#53|MEDIUM BRUSHED BRASS|14|4 +Brand#53|MEDIUM BRUSHED BRASS|19|4 +Brand#53|MEDIUM BRUSHED BRASS|36|4 +Brand#53|MEDIUM BRUSHED BRASS|45|4 +Brand#53|MEDIUM BRUSHED COPPER|3|4 +Brand#53|MEDIUM BRUSHED COPPER|14|4 +Brand#53|MEDIUM BRUSHED COPPER|19|4 +Brand#53|MEDIUM BRUSHED COPPER|23|4 +Brand#53|MEDIUM BRUSHED NICKEL|36|4 +Brand#53|MEDIUM BRUSHED STEEL|9|4 +Brand#53|MEDIUM BRUSHED STEEL|19|4 +Brand#53|MEDIUM BRUSHED TIN|14|4 +Brand#53|MEDIUM BRUSHED TIN|49|4 +Brand#53|MEDIUM BURNISHED BRASS|9|4 +Brand#53|MEDIUM BURNISHED BRASS|19|4 +Brand#53|MEDIUM BURNISHED BRASS|23|4 +Brand#53|MEDIUM BURNISHED BRASS|36|4 +Brand#53|MEDIUM BURNISHED BRASS|45|4 +Brand#53|MEDIUM BURNISHED COPPER|23|4 +Brand#53|MEDIUM BURNISHED COPPER|36|4 +Brand#53|MEDIUM BURNISHED STEEL|3|4 +Brand#53|MEDIUM BURNISHED STEEL|45|4 +Brand#53|MEDIUM BURNISHED TIN|3|4 +Brand#53|MEDIUM BURNISHED TIN|19|4 +Brand#53|MEDIUM BURNISHED TIN|23|4 +Brand#53|MEDIUM BURNISHED TIN|36|4 +Brand#53|MEDIUM BURNISHED TIN|49|4 +Brand#53|MEDIUM PLATED BRASS|3|4 +Brand#53|MEDIUM PLATED BRASS|23|4 +Brand#53|MEDIUM PLATED COPPER|36|4 +Brand#53|MEDIUM PLATED COPPER|45|4 +Brand#53|MEDIUM PLATED COPPER|49|4 +Brand#53|MEDIUM PLATED NICKEL|9|4 +Brand#53|MEDIUM PLATED NICKEL|14|4 +Brand#53|MEDIUM PLATED NICKEL|19|4 +Brand#53|MEDIUM PLATED NICKEL|49|4 +Brand#53|MEDIUM PLATED STEEL|3|4 +Brand#53|MEDIUM PLATED STEEL|9|4 +Brand#53|MEDIUM PLATED STEEL|36|4 +Brand#53|MEDIUM PLATED STEEL|49|4 +Brand#53|MEDIUM PLATED TIN|3|4 +Brand#53|MEDIUM PLATED TIN|9|4 +Brand#53|MEDIUM PLATED TIN|19|4 +Brand#53|MEDIUM PLATED TIN|23|4 +Brand#53|MEDIUM PLATED TIN|36|4 +Brand#53|MEDIUM PLATED TIN|49|4 +Brand#53|PROMO ANODIZED BRASS|14|4 +Brand#53|PROMO ANODIZED COPPER|19|4 +Brand#53|PROMO ANODIZED COPPER|45|4 +Brand#53|PROMO ANODIZED NICKEL|9|4 +Brand#53|PROMO ANODIZED NICKEL|14|4 +Brand#53|PROMO ANODIZED NICKEL|19|4 +Brand#53|PROMO ANODIZED NICKEL|23|4 +Brand#53|PROMO ANODIZED NICKEL|45|4 +Brand#53|PROMO ANODIZED STEEL|23|4 +Brand#53|PROMO ANODIZED STEEL|36|4 +Brand#53|PROMO ANODIZED STEEL|49|4 +Brand#53|PROMO ANODIZED TIN|3|4 +Brand#53|PROMO ANODIZED TIN|9|4 +Brand#53|PROMO ANODIZED TIN|14|4 +Brand#53|PROMO ANODIZED TIN|23|4 +Brand#53|PROMO BRUSHED BRASS|3|4 +Brand#53|PROMO BRUSHED BRASS|9|4 +Brand#53|PROMO BRUSHED BRASS|14|4 +Brand#53|PROMO BRUSHED BRASS|19|4 +Brand#53|PROMO BRUSHED BRASS|23|4 +Brand#53|PROMO BRUSHED COPPER|19|4 +Brand#53|PROMO BRUSHED COPPER|45|4 +Brand#53|PROMO BRUSHED NICKEL|36|4 +Brand#53|PROMO BRUSHED NICKEL|45|4 +Brand#53|PROMO BRUSHED STEEL|9|4 +Brand#53|PROMO BRUSHED STEEL|36|4 +Brand#53|PROMO BRUSHED STEEL|45|4 +Brand#53|PROMO BRUSHED TIN|3|4 +Brand#53|PROMO BRUSHED TIN|45|4 +Brand#53|PROMO BURNISHED BRASS|3|4 +Brand#53|PROMO BURNISHED BRASS|9|4 +Brand#53|PROMO BURNISHED BRASS|45|4 +Brand#53|PROMO BURNISHED COPPER|3|4 +Brand#53|PROMO BURNISHED COPPER|19|4 +Brand#53|PROMO BURNISHED COPPER|23|4 +Brand#53|PROMO BURNISHED NICKEL|3|4 +Brand#53|PROMO BURNISHED NICKEL|23|4 +Brand#53|PROMO BURNISHED STEEL|19|4 +Brand#53|PROMO BURNISHED TIN|14|4 +Brand#53|PROMO BURNISHED TIN|36|4 +Brand#53|PROMO PLATED BRASS|3|4 +Brand#53|PROMO PLATED BRASS|9|4 +Brand#53|PROMO PLATED BRASS|14|4 +Brand#53|PROMO PLATED COPPER|19|4 +Brand#53|PROMO PLATED NICKEL|3|4 +Brand#53|PROMO PLATED NICKEL|9|4 +Brand#53|PROMO PLATED NICKEL|14|4 +Brand#53|PROMO PLATED NICKEL|19|4 +Brand#53|PROMO PLATED NICKEL|23|4 +Brand#53|PROMO PLATED NICKEL|45|4 +Brand#53|PROMO PLATED STEEL|3|4 +Brand#53|PROMO PLATED STEEL|14|4 +Brand#53|PROMO PLATED STEEL|23|4 +Brand#53|PROMO PLATED STEEL|36|4 +Brand#53|PROMO PLATED STEEL|45|4 +Brand#53|PROMO PLATED TIN|36|4 +Brand#53|PROMO POLISHED BRASS|23|4 +Brand#53|PROMO POLISHED BRASS|49|4 +Brand#53|PROMO POLISHED COPPER|9|4 +Brand#53|PROMO POLISHED COPPER|14|4 +Brand#53|PROMO POLISHED COPPER|36|4 +Brand#53|PROMO POLISHED COPPER|45|4 +Brand#53|PROMO POLISHED NICKEL|14|4 +Brand#53|PROMO POLISHED NICKEL|36|4 +Brand#53|PROMO POLISHED STEEL|14|4 +Brand#53|PROMO POLISHED STEEL|19|4 +Brand#53|PROMO POLISHED STEEL|23|4 +Brand#53|PROMO POLISHED TIN|3|4 +Brand#53|PROMO POLISHED TIN|9|4 +Brand#53|PROMO POLISHED TIN|19|4 +Brand#53|PROMO POLISHED TIN|23|4 +Brand#53|SMALL ANODIZED BRASS|14|4 +Brand#53|SMALL ANODIZED BRASS|36|4 +Brand#53|SMALL ANODIZED COPPER|14|4 +Brand#53|SMALL ANODIZED COPPER|45|4 +Brand#53|SMALL ANODIZED COPPER|49|4 +Brand#53|SMALL ANODIZED NICKEL|14|4 +Brand#53|SMALL ANODIZED STEEL|14|4 +Brand#53|SMALL ANODIZED STEEL|36|4 +Brand#53|SMALL ANODIZED TIN|14|4 +Brand#53|SMALL ANODIZED TIN|19|4 +Brand#53|SMALL ANODIZED TIN|23|4 +Brand#53|SMALL BRUSHED BRASS|3|4 +Brand#53|SMALL BRUSHED BRASS|19|4 +Brand#53|SMALL BRUSHED BRASS|23|4 +Brand#53|SMALL BRUSHED BRASS|45|4 +Brand#53|SMALL BRUSHED BRASS|49|4 +Brand#53|SMALL BRUSHED COPPER|9|4 +Brand#53|SMALL BRUSHED COPPER|19|4 +Brand#53|SMALL BRUSHED COPPER|23|4 +Brand#53|SMALL BRUSHED COPPER|45|4 +Brand#53|SMALL BRUSHED NICKEL|9|4 +Brand#53|SMALL BRUSHED NICKEL|14|4 +Brand#53|SMALL BRUSHED NICKEL|19|4 +Brand#53|SMALL BRUSHED NICKEL|23|4 +Brand#53|SMALL BRUSHED NICKEL|36|4 +Brand#53|SMALL BRUSHED STEEL|14|4 +Brand#53|SMALL BRUSHED STEEL|19|4 +Brand#53|SMALL BRUSHED TIN|9|4 +Brand#53|SMALL BRUSHED TIN|36|4 +Brand#53|SMALL BURNISHED BRASS|19|4 +Brand#53|SMALL BURNISHED NICKEL|3|4 +Brand#53|SMALL BURNISHED NICKEL|19|4 +Brand#53|SMALL BURNISHED STEEL|3|4 +Brand#53|SMALL BURNISHED STEEL|14|4 +Brand#53|SMALL BURNISHED STEEL|23|4 +Brand#53|SMALL BURNISHED STEEL|45|4 +Brand#53|SMALL BURNISHED TIN|9|4 +Brand#53|SMALL BURNISHED TIN|19|4 +Brand#53|SMALL BURNISHED TIN|36|4 +Brand#53|SMALL BURNISHED TIN|45|4 +Brand#53|SMALL BURNISHED TIN|49|4 +Brand#53|SMALL PLATED BRASS|14|4 +Brand#53|SMALL PLATED BRASS|19|4 +Brand#53|SMALL PLATED BRASS|23|4 +Brand#53|SMALL PLATED COPPER|45|4 +Brand#53|SMALL PLATED NICKEL|36|4 +Brand#53|SMALL PLATED NICKEL|49|4 +Brand#53|SMALL PLATED STEEL|9|4 +Brand#53|SMALL PLATED STEEL|45|4 +Brand#53|SMALL PLATED STEEL|49|4 +Brand#53|SMALL PLATED TIN|3|4 +Brand#53|SMALL PLATED TIN|23|4 +Brand#53|SMALL PLATED TIN|49|4 +Brand#53|SMALL POLISHED BRASS|23|4 +Brand#53|SMALL POLISHED COPPER|3|4 +Brand#53|SMALL POLISHED COPPER|14|4 +Brand#53|SMALL POLISHED COPPER|36|4 +Brand#53|SMALL POLISHED COPPER|45|4 +Brand#53|SMALL POLISHED COPPER|49|4 +Brand#53|SMALL POLISHED NICKEL|9|4 +Brand#53|SMALL POLISHED STEEL|9|4 +Brand#53|SMALL POLISHED STEEL|19|4 +Brand#53|SMALL POLISHED TIN|9|4 +Brand#53|SMALL POLISHED TIN|14|4 +Brand#53|STANDARD ANODIZED BRASS|3|4 +Brand#53|STANDARD ANODIZED BRASS|9|4 +Brand#53|STANDARD ANODIZED BRASS|49|4 +Brand#53|STANDARD ANODIZED COPPER|3|4 +Brand#53|STANDARD ANODIZED COPPER|23|4 +Brand#53|STANDARD ANODIZED COPPER|45|4 +Brand#53|STANDARD ANODIZED COPPER|49|4 +Brand#53|STANDARD ANODIZED NICKEL|23|4 +Brand#53|STANDARD ANODIZED NICKEL|45|4 +Brand#53|STANDARD ANODIZED NICKEL|49|4 +Brand#53|STANDARD ANODIZED STEEL|3|4 +Brand#53|STANDARD ANODIZED STEEL|14|4 +Brand#53|STANDARD ANODIZED STEEL|36|4 +Brand#53|STANDARD ANODIZED TIN|9|4 +Brand#53|STANDARD ANODIZED TIN|36|4 +Brand#53|STANDARD BRUSHED BRASS|23|4 +Brand#53|STANDARD BRUSHED BRASS|45|4 +Brand#53|STANDARD BRUSHED COPPER|14|4 +Brand#53|STANDARD BRUSHED COPPER|19|4 +Brand#53|STANDARD BRUSHED COPPER|23|4 +Brand#53|STANDARD BRUSHED COPPER|36|4 +Brand#53|STANDARD BRUSHED COPPER|45|4 +Brand#53|STANDARD BRUSHED NICKEL|19|4 +Brand#53|STANDARD BRUSHED NICKEL|23|4 +Brand#53|STANDARD BRUSHED STEEL|3|4 +Brand#53|STANDARD BRUSHED STEEL|9|4 +Brand#53|STANDARD BRUSHED STEEL|14|4 +Brand#53|STANDARD BRUSHED STEEL|19|4 +Brand#53|STANDARD BRUSHED STEEL|36|4 +Brand#53|STANDARD BRUSHED TIN|3|4 +Brand#53|STANDARD BRUSHED TIN|9|4 +Brand#53|STANDARD BRUSHED TIN|23|4 +Brand#53|STANDARD BURNISHED BRASS|3|4 +Brand#53|STANDARD BURNISHED BRASS|14|4 +Brand#53|STANDARD BURNISHED BRASS|23|4 +Brand#53|STANDARD BURNISHED BRASS|45|4 +Brand#53|STANDARD BURNISHED COPPER|9|4 +Brand#53|STANDARD BURNISHED COPPER|14|4 +Brand#53|STANDARD BURNISHED COPPER|49|4 +Brand#53|STANDARD BURNISHED NICKEL|3|4 +Brand#53|STANDARD BURNISHED NICKEL|9|4 +Brand#53|STANDARD BURNISHED NICKEL|14|4 +Brand#53|STANDARD BURNISHED NICKEL|19|4 +Brand#53|STANDARD BURNISHED STEEL|9|4 +Brand#53|STANDARD BURNISHED STEEL|14|4 +Brand#53|STANDARD BURNISHED STEEL|45|4 +Brand#53|STANDARD BURNISHED TIN|9|4 +Brand#53|STANDARD BURNISHED TIN|23|4 +Brand#53|STANDARD BURNISHED TIN|45|4 +Brand#53|STANDARD BURNISHED TIN|49|4 +Brand#53|STANDARD PLATED BRASS|14|4 +Brand#53|STANDARD PLATED BRASS|45|4 +Brand#53|STANDARD PLATED BRASS|49|4 +Brand#53|STANDARD PLATED COPPER|9|4 +Brand#53|STANDARD PLATED COPPER|14|4 +Brand#53|STANDARD PLATED COPPER|19|4 +Brand#53|STANDARD PLATED COPPER|23|4 +Brand#53|STANDARD PLATED COPPER|49|4 +Brand#53|STANDARD PLATED NICKEL|3|4 +Brand#53|STANDARD PLATED NICKEL|9|4 +Brand#53|STANDARD PLATED NICKEL|23|4 +Brand#53|STANDARD PLATED NICKEL|49|4 +Brand#53|STANDARD PLATED STEEL|3|4 +Brand#53|STANDARD PLATED STEEL|9|4 +Brand#53|STANDARD PLATED STEEL|36|4 +Brand#53|STANDARD PLATED STEEL|49|4 +Brand#53|STANDARD PLATED TIN|3|4 +Brand#53|STANDARD PLATED TIN|49|4 +Brand#53|STANDARD POLISHED BRASS|9|4 +Brand#53|STANDARD POLISHED BRASS|14|4 +Brand#53|STANDARD POLISHED BRASS|23|4 +Brand#53|STANDARD POLISHED COPPER|9|4 +Brand#53|STANDARD POLISHED COPPER|23|4 +Brand#53|STANDARD POLISHED NICKEL|19|4 +Brand#53|STANDARD POLISHED NICKEL|45|4 +Brand#53|STANDARD POLISHED STEEL|3|4 +Brand#53|STANDARD POLISHED STEEL|36|4 +Brand#53|STANDARD POLISHED TIN|3|4 +Brand#53|STANDARD POLISHED TIN|36|4 +Brand#54|ECONOMY ANODIZED BRASS|9|4 +Brand#54|ECONOMY ANODIZED BRASS|19|4 +Brand#54|ECONOMY ANODIZED BRASS|23|4 +Brand#54|ECONOMY ANODIZED BRASS|45|4 +Brand#54|ECONOMY ANODIZED BRASS|49|4 +Brand#54|ECONOMY ANODIZED COPPER|3|4 +Brand#54|ECONOMY ANODIZED COPPER|9|4 +Brand#54|ECONOMY ANODIZED COPPER|23|4 +Brand#54|ECONOMY ANODIZED COPPER|36|4 +Brand#54|ECONOMY ANODIZED COPPER|45|4 +Brand#54|ECONOMY ANODIZED COPPER|49|4 +Brand#54|ECONOMY ANODIZED NICKEL|3|4 +Brand#54|ECONOMY ANODIZED NICKEL|14|4 +Brand#54|ECONOMY ANODIZED NICKEL|19|4 +Brand#54|ECONOMY ANODIZED NICKEL|45|4 +Brand#54|ECONOMY ANODIZED STEEL|3|4 +Brand#54|ECONOMY ANODIZED STEEL|14|4 +Brand#54|ECONOMY ANODIZED STEEL|36|4 +Brand#54|ECONOMY ANODIZED STEEL|45|4 +Brand#54|ECONOMY ANODIZED TIN|9|4 +Brand#54|ECONOMY ANODIZED TIN|23|4 +Brand#54|ECONOMY ANODIZED TIN|49|4 +Brand#54|ECONOMY BRUSHED COPPER|19|4 +Brand#54|ECONOMY BRUSHED COPPER|23|4 +Brand#54|ECONOMY BRUSHED COPPER|36|4 +Brand#54|ECONOMY BRUSHED COPPER|49|4 +Brand#54|ECONOMY BRUSHED NICKEL|3|4 +Brand#54|ECONOMY BRUSHED NICKEL|19|4 +Brand#54|ECONOMY BRUSHED NICKEL|45|4 +Brand#54|ECONOMY BRUSHED STEEL|9|4 +Brand#54|ECONOMY BRUSHED TIN|19|4 +Brand#54|ECONOMY BRUSHED TIN|49|4 +Brand#54|ECONOMY BURNISHED BRASS|3|4 +Brand#54|ECONOMY BURNISHED BRASS|23|4 +Brand#54|ECONOMY BURNISHED BRASS|49|4 +Brand#54|ECONOMY BURNISHED COPPER|23|4 +Brand#54|ECONOMY BURNISHED NICKEL|3|4 +Brand#54|ECONOMY BURNISHED NICKEL|14|4 +Brand#54|ECONOMY BURNISHED NICKEL|45|4 +Brand#54|ECONOMY BURNISHED NICKEL|49|4 +Brand#54|ECONOMY BURNISHED STEEL|19|4 +Brand#54|ECONOMY BURNISHED TIN|3|4 +Brand#54|ECONOMY BURNISHED TIN|19|4 +Brand#54|ECONOMY BURNISHED TIN|49|4 +Brand#54|ECONOMY PLATED BRASS|9|4 +Brand#54|ECONOMY PLATED BRASS|14|4 +Brand#54|ECONOMY PLATED BRASS|19|4 +Brand#54|ECONOMY PLATED BRASS|23|4 +Brand#54|ECONOMY PLATED BRASS|36|4 +Brand#54|ECONOMY PLATED BRASS|45|4 +Brand#54|ECONOMY PLATED COPPER|3|4 +Brand#54|ECONOMY PLATED COPPER|23|4 +Brand#54|ECONOMY PLATED NICKEL|3|4 +Brand#54|ECONOMY PLATED NICKEL|14|4 +Brand#54|ECONOMY PLATED NICKEL|19|4 +Brand#54|ECONOMY PLATED STEEL|14|4 +Brand#54|ECONOMY PLATED STEEL|23|4 +Brand#54|ECONOMY PLATED STEEL|36|4 +Brand#54|ECONOMY PLATED STEEL|45|4 +Brand#54|ECONOMY PLATED STEEL|49|4 +Brand#54|ECONOMY PLATED TIN|3|4 +Brand#54|ECONOMY PLATED TIN|9|4 +Brand#54|ECONOMY PLATED TIN|14|4 +Brand#54|ECONOMY PLATED TIN|19|4 +Brand#54|ECONOMY PLATED TIN|45|4 +Brand#54|ECONOMY POLISHED BRASS|3|4 +Brand#54|ECONOMY POLISHED BRASS|19|4 +Brand#54|ECONOMY POLISHED COPPER|14|4 +Brand#54|ECONOMY POLISHED NICKEL|19|4 +Brand#54|ECONOMY POLISHED STEEL|9|4 +Brand#54|ECONOMY POLISHED TIN|14|4 +Brand#54|ECONOMY POLISHED TIN|49|4 +Brand#54|LARGE ANODIZED BRASS|14|4 +Brand#54|LARGE ANODIZED BRASS|23|4 +Brand#54|LARGE ANODIZED BRASS|36|4 +Brand#54|LARGE ANODIZED BRASS|49|4 +Brand#54|LARGE ANODIZED COPPER|19|4 +Brand#54|LARGE ANODIZED COPPER|23|4 +Brand#54|LARGE ANODIZED COPPER|36|4 +Brand#54|LARGE ANODIZED NICKEL|3|4 +Brand#54|LARGE ANODIZED NICKEL|14|4 +Brand#54|LARGE ANODIZED NICKEL|19|4 +Brand#54|LARGE ANODIZED NICKEL|36|4 +Brand#54|LARGE ANODIZED NICKEL|45|4 +Brand#54|LARGE ANODIZED STEEL|3|4 +Brand#54|LARGE ANODIZED STEEL|19|4 +Brand#54|LARGE ANODIZED STEEL|36|4 +Brand#54|LARGE ANODIZED TIN|3|4 +Brand#54|LARGE ANODIZED TIN|9|4 +Brand#54|LARGE ANODIZED TIN|19|4 +Brand#54|LARGE ANODIZED TIN|45|4 +Brand#54|LARGE BRUSHED BRASS|36|4 +Brand#54|LARGE BRUSHED COPPER|3|4 +Brand#54|LARGE BRUSHED COPPER|36|4 +Brand#54|LARGE BRUSHED COPPER|49|4 +Brand#54|LARGE BRUSHED NICKEL|14|4 +Brand#54|LARGE BRUSHED NICKEL|19|4 +Brand#54|LARGE BRUSHED NICKEL|45|4 +Brand#54|LARGE BRUSHED NICKEL|49|4 +Brand#54|LARGE BRUSHED STEEL|3|4 +Brand#54|LARGE BRUSHED STEEL|9|4 +Brand#54|LARGE BRUSHED STEEL|19|4 +Brand#54|LARGE BRUSHED STEEL|23|4 +Brand#54|LARGE BRUSHED STEEL|45|4 +Brand#54|LARGE BRUSHED TIN|14|4 +Brand#54|LARGE BRUSHED TIN|19|4 +Brand#54|LARGE BRUSHED TIN|45|4 +Brand#54|LARGE BURNISHED BRASS|14|4 +Brand#54|LARGE BURNISHED BRASS|19|4 +Brand#54|LARGE BURNISHED BRASS|36|4 +Brand#54|LARGE BURNISHED NICKEL|3|4 +Brand#54|LARGE BURNISHED NICKEL|19|4 +Brand#54|LARGE BURNISHED NICKEL|45|4 +Brand#54|LARGE BURNISHED STEEL|9|4 +Brand#54|LARGE BURNISHED STEEL|36|4 +Brand#54|LARGE BURNISHED STEEL|45|4 +Brand#54|LARGE BURNISHED TIN|9|4 +Brand#54|LARGE BURNISHED TIN|23|4 +Brand#54|LARGE BURNISHED TIN|36|4 +Brand#54|LARGE PLATED BRASS|3|4 +Brand#54|LARGE PLATED BRASS|14|4 +Brand#54|LARGE PLATED COPPER|14|4 +Brand#54|LARGE PLATED COPPER|36|4 +Brand#54|LARGE PLATED NICKEL|9|4 +Brand#54|LARGE PLATED NICKEL|14|4 +Brand#54|LARGE PLATED NICKEL|19|4 +Brand#54|LARGE PLATED NICKEL|45|4 +Brand#54|LARGE PLATED NICKEL|49|4 +Brand#54|LARGE PLATED STEEL|45|4 +Brand#54|LARGE PLATED TIN|3|4 +Brand#54|LARGE PLATED TIN|14|4 +Brand#54|LARGE PLATED TIN|49|4 +Brand#54|LARGE POLISHED BRASS|3|4 +Brand#54|LARGE POLISHED BRASS|14|4 +Brand#54|LARGE POLISHED BRASS|19|4 +Brand#54|LARGE POLISHED BRASS|36|4 +Brand#54|LARGE POLISHED COPPER|14|4 +Brand#54|LARGE POLISHED COPPER|23|4 +Brand#54|LARGE POLISHED COPPER|36|4 +Brand#54|LARGE POLISHED COPPER|49|4 +Brand#54|LARGE POLISHED NICKEL|45|4 +Brand#54|LARGE POLISHED NICKEL|49|4 +Brand#54|LARGE POLISHED STEEL|9|4 +Brand#54|LARGE POLISHED STEEL|23|4 +Brand#54|LARGE POLISHED STEEL|36|4 +Brand#54|LARGE POLISHED TIN|3|4 +Brand#54|LARGE POLISHED TIN|9|4 +Brand#54|LARGE POLISHED TIN|23|4 +Brand#54|MEDIUM ANODIZED BRASS|19|4 +Brand#54|MEDIUM ANODIZED BRASS|23|4 +Brand#54|MEDIUM ANODIZED BRASS|45|4 +Brand#54|MEDIUM ANODIZED COPPER|3|4 +Brand#54|MEDIUM ANODIZED COPPER|14|4 +Brand#54|MEDIUM ANODIZED COPPER|36|4 +Brand#54|MEDIUM ANODIZED COPPER|45|4 +Brand#54|MEDIUM ANODIZED NICKEL|9|4 +Brand#54|MEDIUM ANODIZED STEEL|14|4 +Brand#54|MEDIUM ANODIZED STEEL|45|4 +Brand#54|MEDIUM ANODIZED TIN|14|4 +Brand#54|MEDIUM ANODIZED TIN|49|4 +Brand#54|MEDIUM BRUSHED BRASS|36|4 +Brand#54|MEDIUM BRUSHED COPPER|9|4 +Brand#54|MEDIUM BRUSHED COPPER|45|4 +Brand#54|MEDIUM BRUSHED COPPER|49|4 +Brand#54|MEDIUM BRUSHED NICKEL|3|4 +Brand#54|MEDIUM BRUSHED NICKEL|19|4 +Brand#54|MEDIUM BRUSHED NICKEL|45|4 +Brand#54|MEDIUM BRUSHED NICKEL|49|4 +Brand#54|MEDIUM BRUSHED STEEL|3|4 +Brand#54|MEDIUM BRUSHED STEEL|14|4 +Brand#54|MEDIUM BRUSHED STEEL|19|4 +Brand#54|MEDIUM BRUSHED STEEL|23|4 +Brand#54|MEDIUM BRUSHED TIN|3|4 +Brand#54|MEDIUM BRUSHED TIN|19|4 +Brand#54|MEDIUM BRUSHED TIN|45|4 +Brand#54|MEDIUM BURNISHED BRASS|3|4 +Brand#54|MEDIUM BURNISHED BRASS|9|4 +Brand#54|MEDIUM BURNISHED BRASS|14|4 +Brand#54|MEDIUM BURNISHED BRASS|19|4 +Brand#54|MEDIUM BURNISHED BRASS|45|4 +Brand#54|MEDIUM BURNISHED COPPER|9|4 +Brand#54|MEDIUM BURNISHED COPPER|49|4 +Brand#54|MEDIUM BURNISHED NICKEL|3|4 +Brand#54|MEDIUM BURNISHED NICKEL|14|4 +Brand#54|MEDIUM BURNISHED NICKEL|23|4 +Brand#54|MEDIUM BURNISHED NICKEL|36|4 +Brand#54|MEDIUM BURNISHED STEEL|9|4 +Brand#54|MEDIUM BURNISHED STEEL|23|4 +Brand#54|MEDIUM BURNISHED STEEL|36|4 +Brand#54|MEDIUM BURNISHED TIN|14|4 +Brand#54|MEDIUM BURNISHED TIN|49|4 +Brand#54|MEDIUM PLATED BRASS|9|4 +Brand#54|MEDIUM PLATED BRASS|14|4 +Brand#54|MEDIUM PLATED BRASS|19|4 +Brand#54|MEDIUM PLATED BRASS|45|4 +Brand#54|MEDIUM PLATED COPPER|3|4 +Brand#54|MEDIUM PLATED COPPER|19|4 +Brand#54|MEDIUM PLATED NICKEL|3|4 +Brand#54|MEDIUM PLATED NICKEL|36|4 +Brand#54|MEDIUM PLATED STEEL|3|4 +Brand#54|MEDIUM PLATED STEEL|9|4 +Brand#54|MEDIUM PLATED STEEL|19|4 +Brand#54|MEDIUM PLATED STEEL|23|4 +Brand#54|MEDIUM PLATED STEEL|36|4 +Brand#54|MEDIUM PLATED STEEL|45|4 +Brand#54|MEDIUM PLATED STEEL|49|4 +Brand#54|MEDIUM PLATED TIN|3|4 +Brand#54|MEDIUM PLATED TIN|9|4 +Brand#54|MEDIUM PLATED TIN|14|4 +Brand#54|MEDIUM PLATED TIN|36|4 +Brand#54|PROMO ANODIZED COPPER|19|4 +Brand#54|PROMO ANODIZED NICKEL|3|4 +Brand#54|PROMO ANODIZED NICKEL|9|4 +Brand#54|PROMO ANODIZED NICKEL|19|4 +Brand#54|PROMO ANODIZED NICKEL|45|4 +Brand#54|PROMO ANODIZED NICKEL|49|4 +Brand#54|PROMO ANODIZED STEEL|45|4 +Brand#54|PROMO ANODIZED STEEL|49|4 +Brand#54|PROMO ANODIZED TIN|3|4 +Brand#54|PROMO ANODIZED TIN|23|4 +Brand#54|PROMO ANODIZED TIN|36|4 +Brand#54|PROMO BRUSHED BRASS|3|4 +Brand#54|PROMO BRUSHED BRASS|36|4 +Brand#54|PROMO BRUSHED BRASS|45|4 +Brand#54|PROMO BRUSHED COPPER|9|4 +Brand#54|PROMO BRUSHED COPPER|19|4 +Brand#54|PROMO BRUSHED COPPER|36|4 +Brand#54|PROMO BRUSHED NICKEL|14|4 +Brand#54|PROMO BRUSHED NICKEL|36|4 +Brand#54|PROMO BRUSHED NICKEL|45|4 +Brand#54|PROMO BRUSHED NICKEL|49|4 +Brand#54|PROMO BRUSHED STEEL|9|4 +Brand#54|PROMO BRUSHED STEEL|23|4 +Brand#54|PROMO BRUSHED TIN|19|4 +Brand#54|PROMO BRUSHED TIN|23|4 +Brand#54|PROMO BRUSHED TIN|36|4 +Brand#54|PROMO BURNISHED BRASS|3|4 +Brand#54|PROMO BURNISHED BRASS|23|4 +Brand#54|PROMO BURNISHED BRASS|45|4 +Brand#54|PROMO BURNISHED COPPER|3|4 +Brand#54|PROMO BURNISHED COPPER|19|4 +Brand#54|PROMO BURNISHED COPPER|23|4 +Brand#54|PROMO BURNISHED COPPER|36|4 +Brand#54|PROMO BURNISHED COPPER|45|4 +Brand#54|PROMO BURNISHED NICKEL|3|4 +Brand#54|PROMO BURNISHED NICKEL|14|4 +Brand#54|PROMO BURNISHED STEEL|19|4 +Brand#54|PROMO BURNISHED STEEL|45|4 +Brand#54|PROMO BURNISHED STEEL|49|4 +Brand#54|PROMO BURNISHED TIN|49|4 +Brand#54|PROMO PLATED BRASS|3|4 +Brand#54|PROMO PLATED BRASS|9|4 +Brand#54|PROMO PLATED BRASS|14|4 +Brand#54|PROMO PLATED BRASS|36|4 +Brand#54|PROMO PLATED COPPER|3|4 +Brand#54|PROMO PLATED COPPER|14|4 +Brand#54|PROMO PLATED COPPER|19|4 +Brand#54|PROMO PLATED NICKEL|23|4 +Brand#54|PROMO PLATED NICKEL|36|4 +Brand#54|PROMO PLATED NICKEL|45|4 +Brand#54|PROMO PLATED STEEL|3|4 +Brand#54|PROMO PLATED STEEL|14|4 +Brand#54|PROMO PLATED STEEL|19|4 +Brand#54|PROMO PLATED STEEL|23|4 +Brand#54|PROMO PLATED TIN|9|4 +Brand#54|PROMO PLATED TIN|19|4 +Brand#54|PROMO POLISHED BRASS|3|4 +Brand#54|PROMO POLISHED BRASS|19|4 +Brand#54|PROMO POLISHED BRASS|23|4 +Brand#54|PROMO POLISHED BRASS|45|4 +Brand#54|PROMO POLISHED BRASS|49|4 +Brand#54|PROMO POLISHED COPPER|9|4 +Brand#54|PROMO POLISHED COPPER|49|4 +Brand#54|PROMO POLISHED NICKEL|3|4 +Brand#54|PROMO POLISHED NICKEL|9|4 +Brand#54|PROMO POLISHED NICKEL|45|4 +Brand#54|PROMO POLISHED NICKEL|49|4 +Brand#54|PROMO POLISHED TIN|9|4 +Brand#54|PROMO POLISHED TIN|36|4 +Brand#54|SMALL ANODIZED BRASS|3|4 +Brand#54|SMALL ANODIZED BRASS|36|4 +Brand#54|SMALL ANODIZED BRASS|49|4 +Brand#54|SMALL ANODIZED COPPER|9|4 +Brand#54|SMALL ANODIZED COPPER|36|4 +Brand#54|SMALL ANODIZED NICKEL|3|4 +Brand#54|SMALL ANODIZED NICKEL|9|4 +Brand#54|SMALL ANODIZED NICKEL|36|4 +Brand#54|SMALL ANODIZED STEEL|14|4 +Brand#54|SMALL ANODIZED STEEL|19|4 +Brand#54|SMALL ANODIZED TIN|3|4 +Brand#54|SMALL ANODIZED TIN|9|4 +Brand#54|SMALL ANODIZED TIN|19|4 +Brand#54|SMALL ANODIZED TIN|23|4 +Brand#54|SMALL ANODIZED TIN|45|4 +Brand#54|SMALL ANODIZED TIN|49|4 +Brand#54|SMALL BRUSHED BRASS|3|4 +Brand#54|SMALL BRUSHED BRASS|14|4 +Brand#54|SMALL BRUSHED BRASS|45|4 +Brand#54|SMALL BRUSHED COPPER|3|4 +Brand#54|SMALL BRUSHED COPPER|14|4 +Brand#54|SMALL BRUSHED COPPER|36|4 +Brand#54|SMALL BRUSHED COPPER|49|4 +Brand#54|SMALL BRUSHED NICKEL|3|4 +Brand#54|SMALL BRUSHED NICKEL|9|4 +Brand#54|SMALL BRUSHED NICKEL|19|4 +Brand#54|SMALL BRUSHED NICKEL|23|4 +Brand#54|SMALL BRUSHED NICKEL|49|4 +Brand#54|SMALL BRUSHED STEEL|3|4 +Brand#54|SMALL BRUSHED STEEL|9|4 +Brand#54|SMALL BRUSHED STEEL|45|4 +Brand#54|SMALL BRUSHED STEEL|49|4 +Brand#54|SMALL BRUSHED TIN|9|4 +Brand#54|SMALL BURNISHED BRASS|19|4 +Brand#54|SMALL BURNISHED BRASS|36|4 +Brand#54|SMALL BURNISHED BRASS|49|4 +Brand#54|SMALL BURNISHED COPPER|9|4 +Brand#54|SMALL BURNISHED COPPER|36|4 +Brand#54|SMALL BURNISHED NICKEL|9|4 +Brand#54|SMALL BURNISHED NICKEL|19|4 +Brand#54|SMALL BURNISHED NICKEL|23|4 +Brand#54|SMALL BURNISHED NICKEL|45|4 +Brand#54|SMALL BURNISHED STEEL|14|4 +Brand#54|SMALL BURNISHED STEEL|23|4 +Brand#54|SMALL BURNISHED STEEL|36|4 +Brand#54|SMALL BURNISHED STEEL|49|4 +Brand#54|SMALL BURNISHED TIN|3|4 +Brand#54|SMALL BURNISHED TIN|14|4 +Brand#54|SMALL BURNISHED TIN|36|4 +Brand#54|SMALL BURNISHED TIN|45|4 +Brand#54|SMALL PLATED BRASS|36|4 +Brand#54|SMALL PLATED BRASS|45|4 +Brand#54|SMALL PLATED BRASS|49|4 +Brand#54|SMALL PLATED COPPER|9|4 +Brand#54|SMALL PLATED COPPER|49|4 +Brand#54|SMALL PLATED NICKEL|3|4 +Brand#54|SMALL PLATED NICKEL|19|4 +Brand#54|SMALL PLATED NICKEL|49|4 +Brand#54|SMALL PLATED STEEL|3|4 +Brand#54|SMALL PLATED STEEL|9|4 +Brand#54|SMALL PLATED STEEL|19|4 +Brand#54|SMALL PLATED STEEL|36|4 +Brand#54|SMALL PLATED STEEL|45|4 +Brand#54|SMALL PLATED TIN|9|4 +Brand#54|SMALL PLATED TIN|49|4 +Brand#54|SMALL POLISHED BRASS|14|4 +Brand#54|SMALL POLISHED BRASS|23|4 +Brand#54|SMALL POLISHED BRASS|49|4 +Brand#54|SMALL POLISHED COPPER|9|4 +Brand#54|SMALL POLISHED COPPER|23|4 +Brand#54|SMALL POLISHED NICKEL|9|4 +Brand#54|SMALL POLISHED NICKEL|19|4 +Brand#54|SMALL POLISHED NICKEL|45|4 +Brand#54|SMALL POLISHED STEEL|14|4 +Brand#54|SMALL POLISHED STEEL|19|4 +Brand#54|SMALL POLISHED STEEL|36|4 +Brand#54|SMALL POLISHED STEEL|45|4 +Brand#54|SMALL POLISHED TIN|3|4 +Brand#54|SMALL POLISHED TIN|9|4 +Brand#54|SMALL POLISHED TIN|14|4 +Brand#54|SMALL POLISHED TIN|19|4 +Brand#54|STANDARD ANODIZED BRASS|14|4 +Brand#54|STANDARD ANODIZED BRASS|19|4 +Brand#54|STANDARD ANODIZED BRASS|36|4 +Brand#54|STANDARD ANODIZED BRASS|49|4 +Brand#54|STANDARD ANODIZED COPPER|3|4 +Brand#54|STANDARD ANODIZED COPPER|19|4 +Brand#54|STANDARD ANODIZED COPPER|45|4 +Brand#54|STANDARD ANODIZED NICKEL|14|4 +Brand#54|STANDARD ANODIZED NICKEL|45|4 +Brand#54|STANDARD ANODIZED STEEL|9|4 +Brand#54|STANDARD ANODIZED STEEL|19|4 +Brand#54|STANDARD ANODIZED STEEL|36|4 +Brand#54|STANDARD ANODIZED STEEL|45|4 +Brand#54|STANDARD ANODIZED TIN|3|4 +Brand#54|STANDARD ANODIZED TIN|23|4 +Brand#54|STANDARD ANODIZED TIN|36|4 +Brand#54|STANDARD ANODIZED TIN|49|4 +Brand#54|STANDARD BRUSHED BRASS|3|4 +Brand#54|STANDARD BRUSHED BRASS|23|4 +Brand#54|STANDARD BRUSHED COPPER|3|4 +Brand#54|STANDARD BRUSHED COPPER|9|4 +Brand#54|STANDARD BRUSHED NICKEL|19|4 +Brand#54|STANDARD BRUSHED NICKEL|45|4 +Brand#54|STANDARD BRUSHED STEEL|3|4 +Brand#54|STANDARD BRUSHED STEEL|14|4 +Brand#54|STANDARD BRUSHED STEEL|19|4 +Brand#54|STANDARD BRUSHED TIN|3|4 +Brand#54|STANDARD BRUSHED TIN|36|4 +Brand#54|STANDARD BURNISHED BRASS|19|4 +Brand#54|STANDARD BURNISHED BRASS|23|4 +Brand#54|STANDARD BURNISHED BRASS|49|4 +Brand#54|STANDARD BURNISHED COPPER|14|4 +Brand#54|STANDARD BURNISHED COPPER|23|4 +Brand#54|STANDARD BURNISHED NICKEL|9|4 +Brand#54|STANDARD BURNISHED NICKEL|19|4 +Brand#54|STANDARD BURNISHED NICKEL|36|4 +Brand#54|STANDARD BURNISHED STEEL|3|4 +Brand#54|STANDARD BURNISHED STEEL|9|4 +Brand#54|STANDARD BURNISHED STEEL|36|4 +Brand#54|STANDARD BURNISHED STEEL|45|4 +Brand#54|STANDARD BURNISHED TIN|3|4 +Brand#54|STANDARD BURNISHED TIN|9|4 +Brand#54|STANDARD BURNISHED TIN|36|4 +Brand#54|STANDARD BURNISHED TIN|45|4 +Brand#54|STANDARD PLATED BRASS|9|4 +Brand#54|STANDARD PLATED BRASS|14|4 +Brand#54|STANDARD PLATED BRASS|36|4 +Brand#54|STANDARD PLATED BRASS|49|4 +Brand#54|STANDARD PLATED COPPER|14|4 +Brand#54|STANDARD PLATED NICKEL|3|4 +Brand#54|STANDARD PLATED NICKEL|23|4 +Brand#54|STANDARD PLATED STEEL|3|4 +Brand#54|STANDARD PLATED STEEL|9|4 +Brand#54|STANDARD PLATED STEEL|14|4 +Brand#54|STANDARD PLATED STEEL|19|4 +Brand#54|STANDARD PLATED STEEL|23|4 +Brand#54|STANDARD PLATED STEEL|49|4 +Brand#54|STANDARD PLATED TIN|9|4 +Brand#54|STANDARD POLISHED BRASS|36|4 +Brand#54|STANDARD POLISHED COPPER|36|4 +Brand#54|STANDARD POLISHED COPPER|49|4 +Brand#54|STANDARD POLISHED NICKEL|3|4 +Brand#54|STANDARD POLISHED NICKEL|9|4 +Brand#54|STANDARD POLISHED NICKEL|19|4 +Brand#54|STANDARD POLISHED NICKEL|45|4 +Brand#54|STANDARD POLISHED NICKEL|49|4 +Brand#54|STANDARD POLISHED STEEL|3|4 +Brand#54|STANDARD POLISHED STEEL|23|4 +Brand#54|STANDARD POLISHED STEEL|45|4 +Brand#54|STANDARD POLISHED TIN|3|4 +Brand#54|STANDARD POLISHED TIN|23|4 +Brand#55|ECONOMY ANODIZED BRASS|3|4 +Brand#55|ECONOMY ANODIZED BRASS|14|4 +Brand#55|ECONOMY ANODIZED BRASS|19|4 +Brand#55|ECONOMY ANODIZED BRASS|23|4 +Brand#55|ECONOMY ANODIZED BRASS|49|4 +Brand#55|ECONOMY ANODIZED COPPER|3|4 +Brand#55|ECONOMY ANODIZED COPPER|19|4 +Brand#55|ECONOMY ANODIZED COPPER|36|4 +Brand#55|ECONOMY ANODIZED NICKEL|3|4 +Brand#55|ECONOMY ANODIZED NICKEL|19|4 +Brand#55|ECONOMY ANODIZED NICKEL|23|4 +Brand#55|ECONOMY ANODIZED NICKEL|36|4 +Brand#55|ECONOMY ANODIZED STEEL|3|4 +Brand#55|ECONOMY ANODIZED STEEL|23|4 +Brand#55|ECONOMY ANODIZED STEEL|45|4 +Brand#55|ECONOMY ANODIZED TIN|3|4 +Brand#55|ECONOMY BRUSHED BRASS|9|4 +Brand#55|ECONOMY BRUSHED BRASS|14|4 +Brand#55|ECONOMY BRUSHED BRASS|19|4 +Brand#55|ECONOMY BRUSHED BRASS|36|4 +Brand#55|ECONOMY BRUSHED BRASS|45|4 +Brand#55|ECONOMY BRUSHED BRASS|49|4 +Brand#55|ECONOMY BRUSHED COPPER|3|4 +Brand#55|ECONOMY BRUSHED COPPER|19|4 +Brand#55|ECONOMY BRUSHED COPPER|45|4 +Brand#55|ECONOMY BRUSHED COPPER|49|4 +Brand#55|ECONOMY BRUSHED NICKEL|3|4 +Brand#55|ECONOMY BRUSHED NICKEL|9|4 +Brand#55|ECONOMY BRUSHED NICKEL|14|4 +Brand#55|ECONOMY BRUSHED NICKEL|36|4 +Brand#55|ECONOMY BRUSHED NICKEL|49|4 +Brand#55|ECONOMY BRUSHED STEEL|14|4 +Brand#55|ECONOMY BRUSHED STEEL|19|4 +Brand#55|ECONOMY BRUSHED STEEL|23|4 +Brand#55|ECONOMY BRUSHED STEEL|45|4 +Brand#55|ECONOMY BRUSHED TIN|9|4 +Brand#55|ECONOMY BRUSHED TIN|14|4 +Brand#55|ECONOMY BRUSHED TIN|19|4 +Brand#55|ECONOMY BRUSHED TIN|49|4 +Brand#55|ECONOMY BURNISHED BRASS|36|4 +Brand#55|ECONOMY BURNISHED BRASS|45|4 +Brand#55|ECONOMY BURNISHED BRASS|49|4 +Brand#55|ECONOMY BURNISHED COPPER|3|4 +Brand#55|ECONOMY BURNISHED COPPER|14|4 +Brand#55|ECONOMY BURNISHED COPPER|36|4 +Brand#55|ECONOMY BURNISHED NICKEL|9|4 +Brand#55|ECONOMY BURNISHED NICKEL|36|4 +Brand#55|ECONOMY BURNISHED NICKEL|45|4 +Brand#55|ECONOMY BURNISHED STEEL|3|4 +Brand#55|ECONOMY BURNISHED STEEL|14|4 +Brand#55|ECONOMY BURNISHED STEEL|36|4 +Brand#55|ECONOMY BURNISHED STEEL|45|4 +Brand#55|ECONOMY BURNISHED TIN|14|4 +Brand#55|ECONOMY PLATED BRASS|3|4 +Brand#55|ECONOMY PLATED BRASS|9|4 +Brand#55|ECONOMY PLATED BRASS|14|4 +Brand#55|ECONOMY PLATED BRASS|23|4 +Brand#55|ECONOMY PLATED BRASS|36|4 +Brand#55|ECONOMY PLATED COPPER|3|4 +Brand#55|ECONOMY PLATED COPPER|9|4 +Brand#55|ECONOMY PLATED COPPER|14|4 +Brand#55|ECONOMY PLATED NICKEL|45|4 +Brand#55|ECONOMY PLATED STEEL|3|4 +Brand#55|ECONOMY PLATED STEEL|19|4 +Brand#55|ECONOMY PLATED STEEL|36|4 +Brand#55|ECONOMY PLATED TIN|3|4 +Brand#55|ECONOMY PLATED TIN|14|4 +Brand#55|ECONOMY PLATED TIN|36|4 +Brand#55|ECONOMY PLATED TIN|45|4 +Brand#55|ECONOMY PLATED TIN|49|4 +Brand#55|ECONOMY POLISHED BRASS|3|4 +Brand#55|ECONOMY POLISHED BRASS|9|4 +Brand#55|ECONOMY POLISHED BRASS|14|4 +Brand#55|ECONOMY POLISHED BRASS|36|4 +Brand#55|ECONOMY POLISHED BRASS|45|4 +Brand#55|ECONOMY POLISHED COPPER|14|4 +Brand#55|ECONOMY POLISHED NICKEL|3|4 +Brand#55|ECONOMY POLISHED NICKEL|23|4 +Brand#55|ECONOMY POLISHED NICKEL|36|4 +Brand#55|ECONOMY POLISHED STEEL|9|4 +Brand#55|ECONOMY POLISHED STEEL|36|4 +Brand#55|ECONOMY POLISHED STEEL|45|4 +Brand#55|ECONOMY POLISHED TIN|3|4 +Brand#55|ECONOMY POLISHED TIN|23|4 +Brand#55|ECONOMY POLISHED TIN|45|4 +Brand#55|ECONOMY POLISHED TIN|49|4 +Brand#55|LARGE ANODIZED BRASS|3|4 +Brand#55|LARGE ANODIZED BRASS|14|4 +Brand#55|LARGE ANODIZED BRASS|19|4 +Brand#55|LARGE ANODIZED BRASS|45|4 +Brand#55|LARGE ANODIZED BRASS|49|4 +Brand#55|LARGE ANODIZED COPPER|19|4 +Brand#55|LARGE ANODIZED COPPER|49|4 +Brand#55|LARGE ANODIZED NICKEL|3|4 +Brand#55|LARGE ANODIZED NICKEL|49|4 +Brand#55|LARGE ANODIZED STEEL|3|4 +Brand#55|LARGE ANODIZED STEEL|19|4 +Brand#55|LARGE ANODIZED STEEL|36|4 +Brand#55|LARGE ANODIZED STEEL|45|4 +Brand#55|LARGE ANODIZED STEEL|49|4 +Brand#55|LARGE ANODIZED TIN|9|4 +Brand#55|LARGE ANODIZED TIN|23|4 +Brand#55|LARGE BRUSHED BRASS|19|4 +Brand#55|LARGE BRUSHED BRASS|23|4 +Brand#55|LARGE BRUSHED BRASS|36|4 +Brand#55|LARGE BRUSHED BRASS|45|4 +Brand#55|LARGE BRUSHED COPPER|36|4 +Brand#55|LARGE BRUSHED COPPER|45|4 +Brand#55|LARGE BRUSHED NICKEL|9|4 +Brand#55|LARGE BRUSHED NICKEL|45|4 +Brand#55|LARGE BRUSHED NICKEL|49|4 +Brand#55|LARGE BRUSHED STEEL|3|4 +Brand#55|LARGE BRUSHED STEEL|19|4 +Brand#55|LARGE BRUSHED STEEL|36|4 +Brand#55|LARGE BRUSHED STEEL|45|4 +Brand#55|LARGE BRUSHED TIN|3|4 +Brand#55|LARGE BRUSHED TIN|14|4 +Brand#55|LARGE BRUSHED TIN|23|4 +Brand#55|LARGE BRUSHED TIN|36|4 +Brand#55|LARGE BURNISHED BRASS|9|4 +Brand#55|LARGE BURNISHED BRASS|23|4 +Brand#55|LARGE BURNISHED BRASS|36|4 +Brand#55|LARGE BURNISHED COPPER|23|4 +Brand#55|LARGE BURNISHED COPPER|45|4 +Brand#55|LARGE BURNISHED NICKEL|3|4 +Brand#55|LARGE BURNISHED NICKEL|9|4 +Brand#55|LARGE BURNISHED STEEL|14|4 +Brand#55|LARGE BURNISHED TIN|23|4 +Brand#55|LARGE BURNISHED TIN|45|4 +Brand#55|LARGE PLATED BRASS|19|4 +Brand#55|LARGE PLATED BRASS|36|4 +Brand#55|LARGE PLATED BRASS|49|4 +Brand#55|LARGE PLATED COPPER|3|4 +Brand#55|LARGE PLATED COPPER|19|4 +Brand#55|LARGE PLATED COPPER|36|4 +Brand#55|LARGE PLATED NICKEL|3|4 +Brand#55|LARGE PLATED NICKEL|23|4 +Brand#55|LARGE PLATED NICKEL|45|4 +Brand#55|LARGE PLATED NICKEL|49|4 +Brand#55|LARGE PLATED STEEL|9|4 +Brand#55|LARGE PLATED STEEL|45|4 +Brand#55|LARGE PLATED TIN|3|4 +Brand#55|LARGE PLATED TIN|23|4 +Brand#55|LARGE PLATED TIN|49|4 +Brand#55|LARGE POLISHED BRASS|9|4 +Brand#55|LARGE POLISHED BRASS|14|4 +Brand#55|LARGE POLISHED BRASS|19|4 +Brand#55|LARGE POLISHED COPPER|3|4 +Brand#55|LARGE POLISHED COPPER|14|4 +Brand#55|LARGE POLISHED COPPER|19|4 +Brand#55|LARGE POLISHED COPPER|23|4 +Brand#55|LARGE POLISHED COPPER|45|4 +Brand#55|LARGE POLISHED COPPER|49|4 +Brand#55|LARGE POLISHED NICKEL|23|4 +Brand#55|LARGE POLISHED NICKEL|45|4 +Brand#55|LARGE POLISHED STEEL|23|4 +Brand#55|LARGE POLISHED STEEL|36|4 +Brand#55|LARGE POLISHED STEEL|49|4 +Brand#55|LARGE POLISHED TIN|3|4 +Brand#55|LARGE POLISHED TIN|19|4 +Brand#55|LARGE POLISHED TIN|23|4 +Brand#55|LARGE POLISHED TIN|49|4 +Brand#55|MEDIUM ANODIZED BRASS|3|4 +Brand#55|MEDIUM ANODIZED BRASS|14|4 +Brand#55|MEDIUM ANODIZED BRASS|19|4 +Brand#55|MEDIUM ANODIZED BRASS|45|4 +Brand#55|MEDIUM ANODIZED COPPER|9|4 +Brand#55|MEDIUM ANODIZED COPPER|23|4 +Brand#55|MEDIUM ANODIZED COPPER|45|4 +Brand#55|MEDIUM ANODIZED NICKEL|3|4 +Brand#55|MEDIUM ANODIZED NICKEL|9|4 +Brand#55|MEDIUM ANODIZED NICKEL|23|4 +Brand#55|MEDIUM ANODIZED STEEL|9|4 +Brand#55|MEDIUM ANODIZED STEEL|23|4 +Brand#55|MEDIUM ANODIZED STEEL|36|4 +Brand#55|MEDIUM ANODIZED STEEL|45|4 +Brand#55|MEDIUM ANODIZED STEEL|49|4 +Brand#55|MEDIUM ANODIZED TIN|3|4 +Brand#55|MEDIUM ANODIZED TIN|9|4 +Brand#55|MEDIUM ANODIZED TIN|14|4 +Brand#55|MEDIUM ANODIZED TIN|19|4 +Brand#55|MEDIUM ANODIZED TIN|36|4 +Brand#55|MEDIUM BRUSHED BRASS|3|4 +Brand#55|MEDIUM BRUSHED BRASS|9|4 +Brand#55|MEDIUM BRUSHED BRASS|36|4 +Brand#55|MEDIUM BRUSHED BRASS|45|4 +Brand#55|MEDIUM BRUSHED COPPER|9|4 +Brand#55|MEDIUM BRUSHED COPPER|14|4 +Brand#55|MEDIUM BRUSHED COPPER|19|4 +Brand#55|MEDIUM BRUSHED COPPER|36|4 +Brand#55|MEDIUM BRUSHED NICKEL|14|4 +Brand#55|MEDIUM BRUSHED NICKEL|49|4 +Brand#55|MEDIUM BRUSHED STEEL|3|4 +Brand#55|MEDIUM BRUSHED TIN|23|4 +Brand#55|MEDIUM BRUSHED TIN|49|4 +Brand#55|MEDIUM BURNISHED BRASS|9|4 +Brand#55|MEDIUM BURNISHED BRASS|23|4 +Brand#55|MEDIUM BURNISHED BRASS|45|4 +Brand#55|MEDIUM BURNISHED BRASS|49|4 +Brand#55|MEDIUM BURNISHED COPPER|9|4 +Brand#55|MEDIUM BURNISHED COPPER|14|4 +Brand#55|MEDIUM BURNISHED COPPER|19|4 +Brand#55|MEDIUM BURNISHED NICKEL|3|4 +Brand#55|MEDIUM BURNISHED NICKEL|9|4 +Brand#55|MEDIUM BURNISHED NICKEL|14|4 +Brand#55|MEDIUM BURNISHED NICKEL|36|4 +Brand#55|MEDIUM BURNISHED STEEL|9|4 +Brand#55|MEDIUM BURNISHED TIN|3|4 +Brand#55|MEDIUM BURNISHED TIN|23|4 +Brand#55|MEDIUM BURNISHED TIN|49|4 +Brand#55|MEDIUM PLATED BRASS|9|4 +Brand#55|MEDIUM PLATED BRASS|14|4 +Brand#55|MEDIUM PLATED BRASS|36|4 +Brand#55|MEDIUM PLATED BRASS|49|4 +Brand#55|MEDIUM PLATED COPPER|49|4 +Brand#55|MEDIUM PLATED NICKEL|9|4 +Brand#55|MEDIUM PLATED NICKEL|14|4 +Brand#55|MEDIUM PLATED NICKEL|45|4 +Brand#55|MEDIUM PLATED STEEL|9|4 +Brand#55|MEDIUM PLATED STEEL|23|4 +Brand#55|MEDIUM PLATED STEEL|49|4 +Brand#55|MEDIUM PLATED TIN|45|4 +Brand#55|MEDIUM PLATED TIN|49|4 +Brand#55|PROMO ANODIZED BRASS|9|4 +Brand#55|PROMO ANODIZED COPPER|19|4 +Brand#55|PROMO ANODIZED NICKEL|23|4 +Brand#55|PROMO ANODIZED NICKEL|45|4 +Brand#55|PROMO ANODIZED STEEL|9|4 +Brand#55|PROMO ANODIZED STEEL|14|4 +Brand#55|PROMO ANODIZED STEEL|23|4 +Brand#55|PROMO ANODIZED TIN|9|4 +Brand#55|PROMO ANODIZED TIN|23|4 +Brand#55|PROMO BRUSHED BRASS|14|4 +Brand#55|PROMO BRUSHED BRASS|19|4 +Brand#55|PROMO BRUSHED BRASS|49|4 +Brand#55|PROMO BRUSHED COPPER|14|4 +Brand#55|PROMO BRUSHED COPPER|23|4 +Brand#55|PROMO BRUSHED COPPER|36|4 +Brand#55|PROMO BRUSHED COPPER|45|4 +Brand#55|PROMO BRUSHED COPPER|49|4 +Brand#55|PROMO BRUSHED NICKEL|3|4 +Brand#55|PROMO BRUSHED NICKEL|19|4 +Brand#55|PROMO BRUSHED STEEL|14|4 +Brand#55|PROMO BRUSHED STEEL|19|4 +Brand#55|PROMO BRUSHED TIN|19|4 +Brand#55|PROMO BURNISHED BRASS|9|4 +Brand#55|PROMO BURNISHED BRASS|14|4 +Brand#55|PROMO BURNISHED BRASS|19|4 +Brand#55|PROMO BURNISHED COPPER|3|4 +Brand#55|PROMO BURNISHED COPPER|49|4 +Brand#55|PROMO BURNISHED NICKEL|14|4 +Brand#55|PROMO BURNISHED NICKEL|19|4 +Brand#55|PROMO BURNISHED NICKEL|23|4 +Brand#55|PROMO BURNISHED NICKEL|45|4 +Brand#55|PROMO BURNISHED NICKEL|49|4 +Brand#55|PROMO BURNISHED STEEL|19|4 +Brand#55|PROMO BURNISHED STEEL|36|4 +Brand#55|PROMO BURNISHED TIN|3|4 +Brand#55|PROMO BURNISHED TIN|14|4 +Brand#55|PROMO BURNISHED TIN|23|4 +Brand#55|PROMO PLATED BRASS|3|4 +Brand#55|PROMO PLATED BRASS|49|4 +Brand#55|PROMO PLATED COPPER|3|4 +Brand#55|PROMO PLATED COPPER|45|4 +Brand#55|PROMO PLATED NICKEL|3|4 +Brand#55|PROMO PLATED NICKEL|23|4 +Brand#55|PROMO PLATED STEEL|3|4 +Brand#55|PROMO PLATED STEEL|23|4 +Brand#55|PROMO PLATED STEEL|36|4 +Brand#55|PROMO PLATED STEEL|45|4 +Brand#55|PROMO PLATED STEEL|49|4 +Brand#55|PROMO PLATED TIN|3|4 +Brand#55|PROMO PLATED TIN|19|4 +Brand#55|PROMO PLATED TIN|23|4 +Brand#55|PROMO POLISHED BRASS|14|4 +Brand#55|PROMO POLISHED COPPER|3|4 +Brand#55|PROMO POLISHED COPPER|19|4 +Brand#55|PROMO POLISHED COPPER|45|4 +Brand#55|PROMO POLISHED COPPER|49|4 +Brand#55|PROMO POLISHED NICKEL|3|4 +Brand#55|PROMO POLISHED NICKEL|14|4 +Brand#55|PROMO POLISHED NICKEL|19|4 +Brand#55|PROMO POLISHED NICKEL|23|4 +Brand#55|PROMO POLISHED NICKEL|36|4 +Brand#55|PROMO POLISHED STEEL|19|4 +Brand#55|PROMO POLISHED STEEL|45|4 +Brand#55|PROMO POLISHED STEEL|49|4 +Brand#55|PROMO POLISHED TIN|3|4 +Brand#55|PROMO POLISHED TIN|9|4 +Brand#55|PROMO POLISHED TIN|14|4 +Brand#55|PROMO POLISHED TIN|19|4 +Brand#55|PROMO POLISHED TIN|23|4 +Brand#55|PROMO POLISHED TIN|36|4 +Brand#55|PROMO POLISHED TIN|45|4 +Brand#55|PROMO POLISHED TIN|49|4 +Brand#55|SMALL ANODIZED BRASS|23|4 +Brand#55|SMALL ANODIZED BRASS|36|4 +Brand#55|SMALL ANODIZED BRASS|45|4 +Brand#55|SMALL ANODIZED COPPER|9|4 +Brand#55|SMALL ANODIZED COPPER|19|4 +Brand#55|SMALL ANODIZED COPPER|23|4 +Brand#55|SMALL ANODIZED NICKEL|9|4 +Brand#55|SMALL ANODIZED NICKEL|14|4 +Brand#55|SMALL ANODIZED NICKEL|23|4 +Brand#55|SMALL ANODIZED NICKEL|36|4 +Brand#55|SMALL ANODIZED NICKEL|45|4 +Brand#55|SMALL ANODIZED STEEL|36|4 +Brand#55|SMALL ANODIZED TIN|9|4 +Brand#55|SMALL ANODIZED TIN|36|4 +Brand#55|SMALL ANODIZED TIN|45|4 +Brand#55|SMALL ANODIZED TIN|49|4 +Brand#55|SMALL BRUSHED BRASS|9|4 +Brand#55|SMALL BRUSHED BRASS|36|4 +Brand#55|SMALL BRUSHED COPPER|3|4 +Brand#55|SMALL BRUSHED COPPER|9|4 +Brand#55|SMALL BRUSHED COPPER|19|4 +Brand#55|SMALL BRUSHED COPPER|23|4 +Brand#55|SMALL BRUSHED NICKEL|3|4 +Brand#55|SMALL BRUSHED NICKEL|9|4 +Brand#55|SMALL BRUSHED NICKEL|19|4 +Brand#55|SMALL BRUSHED NICKEL|23|4 +Brand#55|SMALL BRUSHED NICKEL|45|4 +Brand#55|SMALL BRUSHED NICKEL|49|4 +Brand#55|SMALL BRUSHED STEEL|3|4 +Brand#55|SMALL BRUSHED STEEL|14|4 +Brand#55|SMALL BRUSHED STEEL|19|4 +Brand#55|SMALL BRUSHED STEEL|23|4 +Brand#55|SMALL BRUSHED STEEL|45|4 +Brand#55|SMALL BRUSHED STEEL|49|4 +Brand#55|SMALL BRUSHED TIN|9|4 +Brand#55|SMALL BRUSHED TIN|49|4 +Brand#55|SMALL BURNISHED BRASS|14|4 +Brand#55|SMALL BURNISHED BRASS|23|4 +Brand#55|SMALL BURNISHED COPPER|3|4 +Brand#55|SMALL BURNISHED COPPER|9|4 +Brand#55|SMALL BURNISHED COPPER|36|4 +Brand#55|SMALL BURNISHED NICKEL|9|4 +Brand#55|SMALL BURNISHED NICKEL|19|4 +Brand#55|SMALL BURNISHED NICKEL|36|4 +Brand#55|SMALL BURNISHED NICKEL|45|4 +Brand#55|SMALL BURNISHED STEEL|14|4 +Brand#55|SMALL BURNISHED TIN|9|4 +Brand#55|SMALL BURNISHED TIN|23|4 +Brand#55|SMALL PLATED COPPER|3|4 +Brand#55|SMALL PLATED COPPER|14|4 +Brand#55|SMALL PLATED COPPER|36|4 +Brand#55|SMALL PLATED COPPER|49|4 +Brand#55|SMALL PLATED NICKEL|14|4 +Brand#55|SMALL PLATED NICKEL|49|4 +Brand#55|SMALL PLATED STEEL|3|4 +Brand#55|SMALL PLATED STEEL|23|4 +Brand#55|SMALL PLATED STEEL|36|4 +Brand#55|SMALL PLATED TIN|36|4 +Brand#55|SMALL PLATED TIN|45|4 +Brand#55|SMALL POLISHED BRASS|9|4 +Brand#55|SMALL POLISHED BRASS|19|4 +Brand#55|SMALL POLISHED BRASS|49|4 +Brand#55|SMALL POLISHED COPPER|19|4 +Brand#55|SMALL POLISHED COPPER|23|4 +Brand#55|SMALL POLISHED COPPER|36|4 +Brand#55|SMALL POLISHED COPPER|45|4 +Brand#55|SMALL POLISHED COPPER|49|4 +Brand#55|SMALL POLISHED NICKEL|9|4 +Brand#55|SMALL POLISHED NICKEL|14|4 +Brand#55|SMALL POLISHED NICKEL|19|4 +Brand#55|SMALL POLISHED NICKEL|23|4 +Brand#55|SMALL POLISHED NICKEL|45|4 +Brand#55|SMALL POLISHED NICKEL|49|4 +Brand#55|SMALL POLISHED STEEL|19|4 +Brand#55|SMALL POLISHED STEEL|45|4 +Brand#55|SMALL POLISHED TIN|14|4 +Brand#55|SMALL POLISHED TIN|23|4 +Brand#55|SMALL POLISHED TIN|45|4 +Brand#55|STANDARD ANODIZED BRASS|9|4 +Brand#55|STANDARD ANODIZED BRASS|23|4 +Brand#55|STANDARD ANODIZED BRASS|49|4 +Brand#55|STANDARD ANODIZED COPPER|9|4 +Brand#55|STANDARD ANODIZED COPPER|14|4 +Brand#55|STANDARD ANODIZED COPPER|45|4 +Brand#55|STANDARD ANODIZED NICKEL|3|4 +Brand#55|STANDARD ANODIZED NICKEL|14|4 +Brand#55|STANDARD ANODIZED NICKEL|45|4 +Brand#55|STANDARD ANODIZED NICKEL|49|4 +Brand#55|STANDARD ANODIZED STEEL|3|4 +Brand#55|STANDARD ANODIZED STEEL|14|4 +Brand#55|STANDARD ANODIZED TIN|14|4 +Brand#55|STANDARD ANODIZED TIN|36|4 +Brand#55|STANDARD ANODIZED TIN|45|4 +Brand#55|STANDARD BRUSHED BRASS|9|4 +Brand#55|STANDARD BRUSHED BRASS|19|4 +Brand#55|STANDARD BRUSHED COPPER|14|4 +Brand#55|STANDARD BRUSHED COPPER|19|4 +Brand#55|STANDARD BRUSHED NICKEL|3|4 +Brand#55|STANDARD BRUSHED NICKEL|36|4 +Brand#55|STANDARD BRUSHED STEEL|9|4 +Brand#55|STANDARD BRUSHED STEEL|14|4 +Brand#55|STANDARD BRUSHED STEEL|19|4 +Brand#55|STANDARD BRUSHED STEEL|49|4 +Brand#55|STANDARD BRUSHED TIN|19|4 +Brand#55|STANDARD BRUSHED TIN|49|4 +Brand#55|STANDARD BURNISHED BRASS|9|4 +Brand#55|STANDARD BURNISHED BRASS|19|4 +Brand#55|STANDARD BURNISHED BRASS|23|4 +Brand#55|STANDARD BURNISHED BRASS|36|4 +Brand#55|STANDARD BURNISHED COPPER|3|4 +Brand#55|STANDARD BURNISHED NICKEL|9|4 +Brand#55|STANDARD BURNISHED NICKEL|49|4 +Brand#55|STANDARD BURNISHED STEEL|19|4 +Brand#55|STANDARD BURNISHED STEEL|23|4 +Brand#55|STANDARD BURNISHED STEEL|36|4 +Brand#55|STANDARD BURNISHED STEEL|45|4 +Brand#55|STANDARD BURNISHED TIN|9|4 +Brand#55|STANDARD BURNISHED TIN|19|4 +Brand#55|STANDARD BURNISHED TIN|36|4 +Brand#55|STANDARD BURNISHED TIN|49|4 +Brand#55|STANDARD PLATED BRASS|9|4 +Brand#55|STANDARD PLATED BRASS|45|4 +Brand#55|STANDARD PLATED BRASS|49|4 +Brand#55|STANDARD PLATED COPPER|9|4 +Brand#55|STANDARD PLATED COPPER|45|4 +Brand#55|STANDARD PLATED NICKEL|3|4 +Brand#55|STANDARD PLATED NICKEL|19|4 +Brand#55|STANDARD PLATED NICKEL|45|4 +Brand#55|STANDARD PLATED STEEL|14|4 +Brand#55|STANDARD PLATED STEEL|23|4 +Brand#55|STANDARD PLATED STEEL|49|4 +Brand#55|STANDARD PLATED TIN|9|4 +Brand#55|STANDARD PLATED TIN|14|4 +Brand#55|STANDARD PLATED TIN|36|4 +Brand#55|STANDARD POLISHED BRASS|3|4 +Brand#55|STANDARD POLISHED BRASS|9|4 +Brand#55|STANDARD POLISHED BRASS|23|4 +Brand#55|STANDARD POLISHED COPPER|3|4 +Brand#55|STANDARD POLISHED COPPER|23|4 +Brand#55|STANDARD POLISHED COPPER|45|4 +Brand#55|STANDARD POLISHED NICKEL|3|4 +Brand#55|STANDARD POLISHED NICKEL|23|4 +Brand#55|STANDARD POLISHED NICKEL|36|4 +Brand#55|STANDARD POLISHED NICKEL|45|4 +Brand#55|STANDARD POLISHED NICKEL|49|4 +Brand#55|STANDARD POLISHED STEEL|14|4 +Brand#55|STANDARD POLISHED STEEL|23|4 +Brand#55|STANDARD POLISHED TIN|9|4 +Brand#55|STANDARD POLISHED TIN|19|4 +Brand#55|STANDARD POLISHED TIN|36|4 +Brand#11|SMALL BRUSHED TIN|19|3 +Brand#15|LARGE PLATED NICKEL|45|3 +Brand#15|LARGE POLISHED NICKEL|9|3 +Brand#21|PROMO BURNISHED STEEL|45|3 +Brand#22|STANDARD PLATED STEEL|23|3 +Brand#25|LARGE PLATED STEEL|19|3 +Brand#32|STANDARD ANODIZED COPPER|23|3 +Brand#33|SMALL ANODIZED BRASS|9|3 +Brand#35|MEDIUM ANODIZED TIN|19|3 +Brand#51|SMALL PLATED BRASS|23|3 +Brand#52|MEDIUM BRUSHED BRASS|45|3 +Brand#53|MEDIUM BRUSHED TIN|45|3 +Brand#54|ECONOMY POLISHED BRASS|9|3 +Brand#55|PROMO PLATED BRASS|19|3 +Brand#55|STANDARD PLATED TIN|49|3 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q17.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q17.csv new file mode 100644 index 000000000..4987e51df --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q17.csv @@ -0,0 +1,2 @@ +avg_yearly +348406.054285714 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q18.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q18.csv new file mode 100644 index 000000000..a485ce09d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q18.csv @@ -0,0 +1,58 @@ +c_name|c_custkey|o_orderkey|o_orderdate|o_totalprice|sum +Customer#000128120|128120|4722021|1994-04-07|544089.09|323 +Customer#000144617|144617|3043270|1997-02-12|530604.44|317 +Customer#000013940|13940|2232932|1997-04-13|522720.61|304 +Customer#000066790|66790|2199712|1996-09-30|515531.82|327 +Customer#000046435|46435|4745607|1997-07-03|508047.99|309 +Customer#000015272|15272|3883783|1993-07-28|500241.33|302 +Customer#000146608|146608|3342468|1994-06-12|499794.58|303 +Customer#000096103|96103|5984582|1992-03-16|494398.79|312 +Customer#000024341|24341|1474818|1992-11-15|491348.26|302 +Customer#000137446|137446|5489475|1997-05-23|487763.25|311 +Customer#000107590|107590|4267751|1994-11-04|485141.38|301 +Customer#000050008|50008|2366755|1996-12-09|483891.26|302 +Customer#000015619|15619|3767271|1996-08-07|480083.96|318 +Customer#000077260|77260|1436544|1992-09-12|479499.43|307 +Customer#000109379|109379|5746311|1996-10-10|478064.11|302 +Customer#000054602|54602|5832321|1997-02-09|471220.08|307 +Customer#000105995|105995|2096705|1994-07-03|469692.58|307 +Customer#000148885|148885|2942469|1992-05-31|469630.44|313 +Customer#000114586|114586|551136|1993-05-19|469605.59|308 +Customer#000105260|105260|5296167|1996-09-06|469360.57|303 +Customer#000147197|147197|1263015|1997-02-02|467149.67|320 +Customer#000064483|64483|2745894|1996-07-04|466991.35|304 +Customer#000136573|136573|2761378|1996-05-31|461282.73|301 +Customer#000016384|16384|502886|1994-04-12|458378.92|312 +Customer#000117919|117919|2869152|1996-06-20|456815.92|317 +Customer#000012251|12251|735366|1993-11-24|455107.26|309 +Customer#000120098|120098|1971680|1995-06-14|453451.23|308 +Customer#000066098|66098|5007490|1992-08-07|453436.16|304 +Customer#000117076|117076|4290656|1997-02-05|449545.85|301 +Customer#000129379|129379|4720454|1997-06-07|448665.79|303 +Customer#000126865|126865|4702759|1994-11-07|447606.65|320 +Customer#000088876|88876|983201|1993-12-30|446717.46|304 +Customer#000036619|36619|4806726|1995-01-17|446704.09|328 +Customer#000141823|141823|2806245|1996-12-29|446269.12|310 +Customer#000053029|53029|2662214|1993-08-13|446144.49|302 +Customer#000018188|18188|3037414|1995-01-25|443807.22|308 +Customer#000066533|66533|29158|1995-10-21|443576.50|305 +Customer#000037729|37729|4134341|1995-06-29|441082.97|309 +Customer#000003566|3566|2329187|1998-01-04|439803.36|304 +Customer#000045538|45538|4527553|1994-05-22|436275.31|305 +Customer#000081581|81581|4739650|1995-11-04|435405.90|305 +Customer#000119989|119989|1544643|1997-09-20|434568.25|320 +Customer#000003680|3680|3861123|1998-07-03|433525.97|301 +Customer#000113131|113131|967334|1995-12-15|432957.75|301 +Customer#000141098|141098|565574|1995-09-24|430986.69|301 +Customer#000093392|93392|5200102|1997-01-22|425487.51|304 +Customer#000015631|15631|1845057|1994-05-12|419879.59|302 +Customer#000112987|112987|4439686|1996-09-17|418161.49|305 +Customer#000012599|12599|4259524|1998-02-12|415200.61|304 +Customer#000105410|105410|4478371|1996-03-05|412754.51|302 +Customer#000149842|149842|5156581|1994-05-30|411329.35|302 +Customer#000010129|10129|5849444|1994-03-21|409129.85|309 +Customer#000069904|69904|1742403|1996-10-19|408513.00|305 +Customer#000017746|17746|6882|1997-04-09|408446.93|303 +Customer#000013072|13072|1481925|1998-03-15|399195.47|301 +Customer#000082441|82441|857959|1994-02-07|382579.74|305 +Customer#000088703|88703|2995076|1994-01-30|363812.12|302 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q19.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q19.csv new file mode 100644 index 000000000..8d74250d4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q19.csv @@ -0,0 +1,2 @@ +revenue +3083843.0578 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q20.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q20.csv new file mode 100644 index 000000000..1ded996c3 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q20.csv @@ -0,0 +1,187 @@ +s_name|s_address +Supplier#000000020|JtPqm19E7tF 152Rl1wQZ8j0H +Supplier#000000091|35WVnU7GLNbQDcc2TARavGtk6RB6ZCd46UAY +Supplier#000000205|Alrx5TN,hdnG +Supplier#000000285|q TMZEDyZtv vUiFKBhT3NJlnIxpL +Supplier#000000287|UQR8bUA4V2HxVbw9K +Supplier#000000354|wSLcCW40Q8 +Supplier#000000378|mLPJtpu4wOc cSFzBR +Supplier#000000402|JR8vWoCteJtJg3okRpt0r28KEo +Supplier#000000530|0BvoewCPg2scOEfuL93FRKqSxHmdhw1 +Supplier#000000555|8Lp0QWPLFXrJrX1sTWkAEdzUsh5ke +Supplier#000000640|FwwyFbgz7c hHViKGT0OB +Supplier#000000729|CAOGYCBtTVT7aB1p6qHbxF6VVhXaHLgTpI +Supplier#000000736|GUIYDfv5xCxLgDx6KQ8khY ntVVnFqmfMKIgT +Supplier#000000761|tF8fMGa6HY4 w77mDwT4rO21kxwe7uTSYNW +Supplier#000000887|y mQ7NHjVbdqnbYr9 L +Supplier#000000935|JHRSOterYgt4MTNo7cupTzA,6MoNw 4 +Supplier#000000975|1qorM1ypBdwgPVuf6sMCKuF9D1rJN1iCTXKmalSt +Supplier#000001263|Aa4 UELS1JqY7qIjniwCWiC +Supplier#000001367|XY28GmOq3efOxOzBMmMLdmWjMpo +Supplier#000001426|QbcoO0TUClFSzfy90Lk k +Supplier#000001446|GOJRjfd6Z9UQ,fiuPz6CO5GDGV +Supplier#000001500|wnElVvfuyaPJROy6x0 +Supplier#000001602|ygd4iNQLQeVWW +Supplier#000001626|7Jud9t6xZNzlEB, +Supplier#000001682|C37Gkv 7a5ujZ9 +Supplier#000001700|UJeFoDLZ2VtflK +Supplier#000001726|8M92T8y7jYXzmvCANTtqR8GHuT +Supplier#000001730|aOSM0,btPDs UsC06himJn,6nswJG +Supplier#000001746|B4zMDOFcMGbkZQ4XxA,UyaQoEWZzMSGI wTsJp9N +Supplier#000001806|fRXWLy18Df5 +Supplier#000001855|f4GUmrMCs3Q +Supplier#000001931|mBhPe7YJU1aYMpwTiRdivl +Supplier#000002022|sNvMPl3TiNZBOYV3w2XX +Supplier#000002036|Z,ty7z5cPHh66iY5op,q +Supplier#000002096|HxuzMufyArbbf3uFR16 EpUgGKRYhyhVOast3d6r +Supplier#000002117|f9yGKhCFjhS4Jr3Sh0x rdjgntwH +Supplier#000002204|y2EF XUo,UyNoAQEH,gIazC7aRG1zmuzzf +Supplier#000002218|Ecn1vWAWNusGB,gW +Supplier#000002243|E8cm5YhMc6UR +Supplier#000002245|KsYA4445HcugJAb3eCmvtUslGA7Qne +Supplier#000002282|n8YZgSNuC4,iZ7s5oHTMHNFdv94DwZ2rrUEb0pgD +Supplier#000002303|EoC4LCpU2cuEPKcKyTFyMGFBGkF +Supplier#000002331|59JYvW4lw9LT1,8,qw8wkof +Supplier#000002373|asj8ud7aEmGoHuiqI5qVZ1rhpWS hJc9tF9 +Supplier#000002419|BtNpaOZWiVGVE53RWL22 +Supplier#000002571|i16xKt,WOrJhlf GkzsRdrd04sZ 5jei9MtB +Supplier#000002585|pzbCgCvYax82Wq5,dG4xzyDMiRW8d +Supplier#000002629|0qv0AW1BSzyR3 sDoDK1YOqm32v1 +Supplier#000002721|k5NlqeYhjeb8BgE +Supplier#000002730|Gilu9XLsEX,oU0EyshvFTWs +Supplier#000002775|unOFQoQpnWJJj +Supplier#000002799|Gv 0rVfXKNMOTRbbxy0W4cUDa 7h2UYOqt2d +Supplier#000002934|F0y pndtv8r vKXoJp +Supplier#000002941|eQNNPRrS27ngMG1ub, +Supplier#000003028|ICxsK4,41HKde4fRjWLt +Supplier#000003095|kucdXIhJ6IYsHy0ArE7n +Supplier#000003143|KNCFKKU,Sx8GjJTzUAL8BGILgRIlHDpj +Supplier#000003185|KeQVXWoPLYFHZdvXc4cg55 zg06NnGi69 NI8ihM +Supplier#000003189|o4NbuzTr9DH1wU5dpt9,NE +Supplier#000003201|nSTtv Ui0y0BdzWb4T6snugIhEhn14yM +Supplier#000003213|Cu 9bXI aZ6CtLa1N7LX +Supplier#000003275|SucWDuhYahP3UwkM +Supplier#000003288|nnNLdzTmV6P0uf7yBCiK3fWt2UxHJ0 +Supplier#000003314|IDIz6TesAcXI6pXtzbO uzevXon6CH9WATfo +Supplier#000003373|JtS5eXStDBPXdYc pvqwK2QGcZxzR9,PI +Supplier#000003421|9JXNyS4VCMDLl4CxDlJ L0 +Supplier#000003422|ohComRB6Mp4C5ZI3IoObmawsz8wY1iSU, +Supplier#000003441|twlhit80C6y8JjHCO3 +Supplier#000003590| tTRoffuAP1oPC +Supplier#000003607|GdAljb2Hv8rGL +Supplier#000003625|B2VSS5,2GVJQ6tZa37KdAmg erHp2jPgz +Supplier#000003723|I1mB,dmtyJpfO8rdEh8 myuhUVPeFs9B +Supplier#000003849|hKP1mR9G0UbXE4wwJUxBz zZga6jKr0 +Supplier#000003894|xwkorAQEMfdG +Supplier#000003941|cEP,VFaLpe9UZScU4gA1irRwtx +Supplier#000004059|yEm219AR9hZ4msN wREM7S EAws5f9LI1vo +Supplier#000004207|zlUWBwIceWjG4HdYE80M0V4xqIfu +Supplier#000004236|OF0jbzhEkICu2z8BDRvEBGx4H y0EoNhScU8 +Supplier#000004278|QpNNPCpui5CrnIR +Supplier#000004281|,cvbjKrbKIuMxz0J18VMrYXhndIBvG +Supplier#000004304|KaoqsWRG0jgFBYoh6 Opjf +Supplier#000004346|9X,TUgmcvC +Supplier#000004406|rJ,1QgxU5vyLb46t0TGP4K21JRxmQjBkiK +Supplier#000004430|uw8zge87fVkf3pm8WFxc bdfuhsqeG +Supplier#000004527|CrB53pDuKOHF4lUrZCXck2XA4V +Supplier#000004655|VTdApPWryXckXlSRcrizB4dAWHkQbCw2f +Supplier#000004851|aH0uUjKf7THpfRREAB +Supplier#000004871|1BJByVq4mD78SyWrwPV,AlLNKUcROUE6SuuxSMoV +Supplier#000004884|XY1ZxgtMRD DUQ7lpdNrOiSru +Supplier#000004975|lbAVbfg2U4u +Supplier#000005076|4FTJRJLKwikEAulgK4LjhWkHQxrpGb +Supplier#000005195|5CIXPZ1QIOcJr18MxZmL4Jm +Supplier#000005256|dDOXyWT5qePJE0BFC5fQ7SPcdEIRdzyQerbteels +Supplier#000005257|MRKS8njpTCQeHXa4qH38ZwL22ZY +Supplier#000005300|L3kYS3ABu6 +Supplier#000005323|zeoGNBPnCteCSwejGsHpB2MuCaqxw08UrAsPx +Supplier#000005386|7PUqqMjB4f5b +Supplier#000005426|SnsYc67ZAr PWpgUZFHa,3Hbp39bwc +Supplier#000005465|VXO21ND1p92UZXa,lQCk01zEDpVsFR +Supplier#000005484| QlNcarA4Trl20XRzbAIJqQZZfCYYBpCd,pzb gC +Supplier#000005505|OYzPHlWXgvfb3 +Supplier#000005506|dDrLVuBsC4Lp +Supplier#000005631|,W75 IFsCY9hmp2pnKBew7Fwv9Ao +Supplier#000005642|2vguqxX5W,z4rmScTavx1sFx0Hbzf +Supplier#000005686|MY9pgNOY7ZeiITt9re +Supplier#000005730|W GP,c8MvzsjwuGgShQnXZ6BD9lYOKTjZ +Supplier#000005736|ZNAr382Jy258LB +Supplier#000005737|OEm4O9XYoXHu0N0qRVrcF2DWS +Supplier#000005797|1C0cMQv9P8oE5FRLdRLB5boIAqr6CKxF89 +Supplier#000005875|Gg0z2JkspRXJ8tjuRuw82lP5aeo1MYg31xkQ8 +Supplier#000005974|amJ9VIm0Ffyza3wMVW8v3t8Kz985lJy +Supplier#000006059|XE,OwTevhRu3YwFwir1 +Supplier#000006065|7IiYouX4W7yVzfGsfxw3g9tUgJJFkw +Supplier#000006093|hhd6K,CN67Zvyo8ZzUb kXnULO +Supplier#000006099|R1IFvbg2n76bCg +Supplier#000006109|A2VKPMJXNgkDtFOb67bkpvDPM +Supplier#000006217|a5dYX927RHND6MQ5k36N +Supplier#000006297|UX59ndcVUk7otwOX,C2h +Supplier#000006435|viKmUS3zs2QDcWmDDTOjkcSt +Supplier#000006463|UrvGNIYmcWSICyFNtYGEjerqnCf8zsl5X9d5H +Supplier#000006478|Qa2bDIRFBE5X6 +Supplier#000006521|QRrYsIjsu9 +Supplier#000006642|e0o6OG8A9fiO8Ssb +Supplier#000006659|J7fzDxnSnYls4d6xrG9iEv9HGW nMmFLxBZ +Supplier#000006669|eaW2 uHUYWCTgVX +Supplier#000006748|Ao0Y nDSKgoi8Va0OhmCJl +Supplier#000006761|EWHJuleApVC nZjKBfwvA48ycgFFQ +Supplier#000006808|kkNY3DrRDmPjhJ1x3H3u5giBqC7 +Supplier#000006858|MDFid8SSVwqpJz4w7kI10DYYyKvk2ZVJrkjiHYZ +Supplier#000006946|8CU8FD,kh7BOiwn +Supplier#000006949|Ffu26iJzkOgygMr1klI exZSXrw7 +Supplier#000007072|Zy9t3SeZQrX9OEVUzTTRmZqdkSHFBg +Supplier#000007098|lXHSK0hoWcPPqxYd5CbjA3a4ep6NHATvKojdmux +Supplier#000007132|vCDwD0hqaigXBSG3Grjo,l88n9687o +Supplier#000007135|GzrnCh5T5VyFLatS5 +Supplier#000007147|4sPZUHoUXvFf52Mv6mKp0K3lwkGH7VJbBA +Supplier#000007160|8Ankp7fpXO8Ai7UmgnwESp8WMXw0sv3IabP +Supplier#000007169|zmORVoYECdS8SWDOVVc0OFD4 +Supplier#000007278|jYQMXA8Tg5mSk5jzp +Supplier#000007365|WZuJ9dfwaei,VnDOy14y +Supplier#000007398|6SMmUD1,,cmd60 +Supplier#000007402|X65wVTM tZAHEA8aV +Supplier#000007448|uJJB4JhITmiUaV5pQa +Supplier#000007458|q2xxORB2GBz1FULENYaQot1EiSK9ZiKZUM +Supplier#000007477|SERH,wLJ4spw5juH60bBruv8j0K +Supplier#000007509|BS05Ugh9CjiHjOcy8kTQg7eK +Supplier#000007561|AeOlKZVX,5p +Supplier#000007616|ariCwjAsnIY0ajDRA1GZv8WJkrKMFJcWtH +Supplier#000007760|izbmZTbwONm7g +Supplier#000007801|VRLI07Z UME6Pr +Supplier#000007865|WOnko907Ud +Supplier#000007885|yXzIOPJJV1Ct76BeZOhgeOqCQQi4K2 +Supplier#000007926|n solT,gR6u +Supplier#000007998|gDq8lqL29ldCRNUO0Qzpx5ARfDYb +Supplier#000008090|NCDPh2wCnl3pd6IE2LBUum9iK +Supplier#000008224|84uyRM0f5ma +Supplier#000008231|jgTMkwr2HR0 7NB b0wOB4ufp +Supplier#000008243|ZqtMbfGnAEt5sHk8Is3yKlfCSKrmIxOoeucFiik +Supplier#000008323|UViZS 1Eq8wErbcNJM9eOHRyECtMa0qLo3dWpiqP +Supplier#000008366|KTTSOnHZWpy4RcmhFwb75AWIvr89Umqp3dTtM8S +Supplier#000008532|7OYRAX0Vu5OnclSU61 uK Wu49,IJm73xJ +Supplier#000008595|fj,IpUXkaXtr64XdrnPoQAEO +Supplier#000008610|9K5KbS,wbWWYz6d8KsfRtgv3j4qs5Uz5 +Supplier#000008683|LfxkOyKLBh9MkaqaDQjdpQ4DkRAzdDItFY, +Supplier#000008705|Rm0y adNbu1WtID8nRcXoMPniC +Supplier#000008742|kEbFansgobnO76f,W0GgB +Supplier#000008841|jrSVfyZzMGQKYu 9isE, +Supplier#000008872|TnrWVlKuhcZcvv2cR8WqR3hgwovbLff +Supplier#000008879|An8q0oRCbe1UVde5ml Elgqx +Supplier#000008967|ZGvmjuekrTmvCsdjEq6mVEj,J3yA2OyFhe +Supplier#000008972|wYwlUsnV21dXwIzc3zA5Mfqn7h +Supplier#000009032|Bg0y qU8NtXnsZpa6ldt +Supplier#000009043|WTcbwhjWAt4i1Tit2MoQwZZQVeZgIRMZ44E6a0 +Supplier#000009278|aA27sLuHRXpf3r,FO2LondcMLo +Supplier#000009326|4EIo0ytXUpS0LP,tJOHQRKI3ayyK +Supplier#000009430|JK9AEEMlyr +Supplier#000009549|KX95OK6LSet2WUitP1,DL,R +Supplier#000009601|WZEUXUPc09wVnDj5l6wfRO9uR +Supplier#000009709|A9DoPk2KnKGRb12Et4g53864,xgK +Supplier#000009753|wfJ5mP9ENTcGhlWmpDkgU1 +Supplier#000009799|sWvdH4kQWch4F +Supplier#000009811|nXIxtBT6D1v6TCb2iMYkyU +Supplier#000009812|rbl9euXFoPLlKQVYDVyRouslbbbKDHAkyXY +Supplier#000009846|WTzdvihXbyqnx0JfbbVaxBOcO9d93DYbtBhi J +Supplier#000009899|U3NBqk s Zz06al2m +Supplier#000009974|Uvh0hWngOu96WgB,OafBQOqwpWqzwg8 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q21.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q21.csv new file mode 100644 index 000000000..7ed007bc1 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q21.csv @@ -0,0 +1,101 @@ +s_name|numwait +Supplier#000002829|20 +Supplier#000005808|18 +Supplier#000000262|17 +Supplier#000000496|17 +Supplier#000002160|17 +Supplier#000002301|17 +Supplier#000002540|17 +Supplier#000003063|17 +Supplier#000005178|17 +Supplier#000008331|17 +Supplier#000002005|16 +Supplier#000002095|16 +Supplier#000005799|16 +Supplier#000005842|16 +Supplier#000006450|16 +Supplier#000006939|16 +Supplier#000009200|16 +Supplier#000009727|16 +Supplier#000000486|15 +Supplier#000000565|15 +Supplier#000001046|15 +Supplier#000001047|15 +Supplier#000001161|15 +Supplier#000001336|15 +Supplier#000001435|15 +Supplier#000003075|15 +Supplier#000003335|15 +Supplier#000005649|15 +Supplier#000006027|15 +Supplier#000006795|15 +Supplier#000006800|15 +Supplier#000006824|15 +Supplier#000007131|15 +Supplier#000007382|15 +Supplier#000008913|15 +Supplier#000009787|15 +Supplier#000000633|14 +Supplier#000001960|14 +Supplier#000002323|14 +Supplier#000002490|14 +Supplier#000002993|14 +Supplier#000003101|14 +Supplier#000004489|14 +Supplier#000005435|14 +Supplier#000005583|14 +Supplier#000005774|14 +Supplier#000007579|14 +Supplier#000008180|14 +Supplier#000008695|14 +Supplier#000009224|14 +Supplier#000000357|13 +Supplier#000000436|13 +Supplier#000000610|13 +Supplier#000000788|13 +Supplier#000000889|13 +Supplier#000001062|13 +Supplier#000001498|13 +Supplier#000002056|13 +Supplier#000002312|13 +Supplier#000002344|13 +Supplier#000002596|13 +Supplier#000002615|13 +Supplier#000002978|13 +Supplier#000003048|13 +Supplier#000003234|13 +Supplier#000003727|13 +Supplier#000003806|13 +Supplier#000004472|13 +Supplier#000005236|13 +Supplier#000005906|13 +Supplier#000006241|13 +Supplier#000006326|13 +Supplier#000006384|13 +Supplier#000006394|13 +Supplier#000006624|13 +Supplier#000006629|13 +Supplier#000006682|13 +Supplier#000006737|13 +Supplier#000006825|13 +Supplier#000007021|13 +Supplier#000007417|13 +Supplier#000007497|13 +Supplier#000007602|13 +Supplier#000008134|13 +Supplier#000008234|13 +Supplier#000009435|13 +Supplier#000009436|13 +Supplier#000009564|13 +Supplier#000009896|13 +Supplier#000000379|12 +Supplier#000000673|12 +Supplier#000000762|12 +Supplier#000000811|12 +Supplier#000000821|12 +Supplier#000001337|12 +Supplier#000001916|12 +Supplier#000001925|12 +Supplier#000002039|12 +Supplier#000002357|12 +Supplier#000002483|12 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q22.csv b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q22.csv new file mode 100644 index 000000000..0cf5ad9da --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/tpch-sf1/q22.csv @@ -0,0 +1,8 @@ +cntrycode|numcust|totacctbal +13|888|6737713.99 +17|861|6460573.72 +18|964|7236687.40 +23|892|6701457.95 +29|948|7158866.63 +30|909|6808436.13 +31|922|6806670.18 diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/utf-8.txt b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/utf-8.txt new file mode 100644 index 000000000..a07eb0291 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/tests/testthat/utf-8.txt @@ -0,0 +1,19 @@ +# UTF-8 tests that can't be run on Windows CRAN +# R CMD check will try to parse the file anyway, +# we use a different file extension to avoid this. +df <- data.frame(中文1 = 1:10, 中文2 = 1:10, eng = 1:10) +df2 <- df %>% mutate(中文1 = 中文1 + 1) +gdf2 <- df %>% group_by(eng) %>% mutate(中文1 = 中文1 + 1) + +expect_equal(lobstr::obj_addrs(names(df)), lobstr::obj_addrs(names(df2))) +expect_equal(lobstr::obj_addrs(names(df)), lobstr::obj_addrs(names(gdf2))) + +df3 <- filter(df2, eng > 5) +gdf3 <- filter(gdf2, eng > 5) +expect_equal(lobstr::obj_addrs(names(df)), lobstr::obj_addrs(names(df3))) +expect_equal(lobstr::obj_addrs(names(df)), lobstr::obj_addrs(names(gdf3))) + +df4 <- filter(df2, 中文1 > 5) +gdf4 <- filter(gdf2, 中文1 > 5) +expect_equal(lobstr::obj_addrs(names(df)), lobstr::obj_addrs(names(df4))) +expect_equal(lobstr::obj_addrs(names(df)), lobstr::obj_addrs(names(gdf4))) diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/dd.png b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/dd.png new file mode 100644 index 0000000000000000000000000000000000000000..22178419de2afedf6732b7e7937b98f2b7b56c47 GIT binary patch literal 178618 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 GPd4|nNujElEmothnO7#R}`1qHJ^P}_w95m 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 + %\VignetteIndexEntry{30 Selective use of duckplyr} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This vignette demonstrates how to use duckplyr selectively, for individual data frames or for other packages. + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + +## Introduction + +The default behavior of duckplyr is to enable itself for all data frames in the session. +This happens when the package is attached with `library(duckplyr)`, or by calling `methods_overwrite()`. +To enable duckplyr for individual data frames instead of session-wide, it is sufficient to prefix all calls to duckplyr functions with `duckplyr::` and not attach the package. +Alternatively, `methods_restore()` can be called to undo the session-wide overwrite after `library(duckplyr)`. + +## External data with explicit qualification + +The following example uses `duckplyr::as_duckdb_tibble()` to convert a data frame to a duckplyr frame and to enable duckplyr operation. + +```{r} +lazy <- + duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + mutate(inflight_delay = arr_delay - dep_delay) |> + summarize( + .by = c(year, month), + mean_inflight_delay = mean(inflight_delay, na.rm = TRUE), + median_inflight_delay = median(inflight_delay, na.rm = TRUE), + ) |> + filter(month <= 6) +``` + +The result is a tibble, with its own class. + +```{r} +class(lazy) + +names(lazy) +``` + +DuckDB is responsible for eventually carrying out the operations. +Despite the filter coming very late in the pipeline, it is applied to the raw data. + +```{r} +lazy |> + explain() +``` + +All data frame operations are supported. +Computation happens upon the first request. + +```{r} +lazy$mean_inflight_delay +``` + +After the computation has been carried out, the results are preserved and available immediately: + +```{r} +lazy +``` + +## Restoring dplyr methods + +The same can be achieved by calling `methods_restore()` after `library(duckplyr)`. + +```{r} +library(duckplyr) + +methods_restore() +``` + +If the input is a plain data frame, duckplyr is not involved. + +```{r error = TRUE} +flights_df() |> + mutate(inflight_delay = arr_delay - dep_delay) |> + explain() +``` + + +## Own data + +Construct duckplyr frames directly with `duckdb_tibble()`: + +```{r} +data <- duckdb_tibble( + x = 1:3, + y = 5, + z = letters[1:3] +) +data +``` + + +## In other packages + +Like other dependencies, duckplyr must be declared in the `DESCRIPTION` file and optionally imported in the `NAMESPACE` file. +Because duckplyr does not import dplyr, it is necessary to import both packages. +The recipe below shows how to achieve this with the usethis package. + +- Add dplyr as a dependency with `usethis::use_package("dplyr")` +- Add duckplyr as a dependency with `usethis::use_package("duckplyr")` +- In your code, use a pattern like `data |> duckplyr::as_duckdb_tibble() |> dplyr::filter(...)` +- To avoid the package prefix and simply write `as_duckdb_tibble()` or `filter()`: + - Import the duckplyr function with `usethis::use_import_from("duckplyr", "as_duckdb_tibble")` + - Import the dplyr function with `usethis::use_import_from("dplyr", "filter")` + +Learn more about prudence in `vignette("prudence")`, about fallbacks to dplyr in `vignette("fallback")`, about the translation employed by duckplyr in `vignette("limits")`, about direct use of DuckDB functions in `vignette("duckdb")`, and about the usethis package at . diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/duckdb.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/duckdb.Rmd new file mode 100644 index 000000000..35622c6c4 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/duckdb.Rmd @@ -0,0 +1,139 @@ +--- +title: "Interoperability with DuckDB and dbplyr" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{25 Interoperability with DuckDB and dbplyr} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13", "callr")) && duckplyr:::can_load_extension("httpfs")), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This article describes how to use the full power of DuckDB with duckplyr. +Two options are discussed: interoperability with dbplyr and the use of DuckDB's functions in duckplyr. + + +```{r attach} +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") +``` + + +## Introduction + +The duckplyr package is a drop-in replacement for dplyr, designed to work with DuckDB as the backend. +There is a translation layer that converts R function calls to DuckDB functions or macros, aiming at full compatibility with R. +Many functions are translated already, and many more are not. +For functions that cannot be translated, duckplyr falls back to the original R implementation, disrupting the DuckDB pipeline and materializing intermediate results. + +Furthermore, DuckDB has functions with no R equivalent. +These might be used already by code that interacts with DuckDB through dbplyr, either making use of its passthrough feature (unknown functions are translated to SQL verbatim), or by using the `mutate(x = sql(...))` pattern. +When working with duckplyr, this functionality is still accessible, albeit through experimental interfaces: + +- `as_tbl()` converts a duckplyr table to a duckdb `tbl` object +- for a duckplyr table, the escape hatch `dd$fun(...)` can be used to call arbitrary DuckDB functions + +## From duckplyr to dbplyr + +The experimental `as_tbl()` function, introduced in duckplyr 1.1.0, transparently converts a duckplyr frame to a dbplyr `tbl` object: + +```{r} +df <- duckdb_tibble(a = 2L) +df + +tbl <- as_tbl(df) +tbl +``` + +It achieves this by creating a temporary view that points to the relational object created internally by duckplyr, in the same DBI connection as the duckplyr object. +No data is copied in this operation. +The view is discarded when the `tbl` object goes out of scope. + +This allows using arbitrary SQL code, either through `sql()` or by relying on dbplyr's passthrough feature. + +```{r} +tbl %>% + mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>% + show_query() +``` + +There is no R function called `least_common_multiple()`, it is interpreted as a SQL function. + +```{r, error = TRUE} +least_common_multiple(2, 3) +``` + +```{r} +tbl %>% + mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) +``` + +To continue processing with duckplyr, use `as_duckdb_tibble()`: + +```{r} +tbl %>% + mutate(b = sql("a + 1"), c = least_common_multiple(a, b)) %>% + as_duckdb_tibble() +``` + +## Call arbitrary functions in duckplyr + +The escape hatch, also introduced in duckplyr 1.1.0, allows calling arbitrary DuckDB functions directly from duckplyr, without going through SQL: + +```{r} +duckdb_tibble(a = 2L, b = 3L) %>% + mutate(c = dd$least_common_multiple(a, b)) +``` + +The `dd` prefix has been picked for the following reasons: + +- it is an abbreviation of "DuckDB" +- it is short and easy to type +- there is no package of this name +- objects are not commonly named `dd` in R + +A prefix is necessary to avoid name clashes with existing R functions. +If this is used widely, large-scale code analysis may help prioritize the translation of functions that are not yet supported by duckplyr. + +The [dd package](https://github.com/cynkra/dd), when attached, will provide a `dd` object containing many known DuckDB functions. +This adds support for autocomplete: + +![Screenshot for autocomplete with the dd package](dd.png) + +This package is not necessary to use duckplyr, and the list of functions is incomplete and growing. +In case you're wondering: + +```{r} +duckdb_tibble(a = "dbplyr", b = "duckplyr") %>% + mutate(c = dd$damerau_levenshtein(a, b)) +``` + +## Conclusion + +While duckplyr is designed to be a drop-in replacement for dplyr, it still allows to harness most if not all of the power of DuckDB. + +See `vignette("limits")` for limitations in the translation employed by duckplyr, `vignette("fallback")` for more information on fallback, and `vignette("telemetry")` for existing attempts to prioritize work on the translation layer. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/extend.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/extend.Rmd new file mode 100644 index 000000000..456c7cbde --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/extend.Rmd @@ -0,0 +1,156 @@ +--- +title: "Implementer's interface" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{99 Implementer's interface} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + +duckplyr also defines a set of generics that provide a low-level implementer's interface for dplyr's high-level user interface. +Other packages may then implement methods for those generics. + +```{r extensibility} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +library(duckplyr) +``` + + +```{r overwrite, echo = FALSE} +methods_overwrite() +``` + +```{r extensibility2} +# Create a relational to be used by examples below +new_dfrel <- function(x) { + stopifnot(is.data.frame(x)) + new_relational(list(x), class = "dfrel") +} +mtcars_rel <- new_dfrel(mtcars[1:5, 1:4]) + +# Example 1: return a data.frame +rel_to_df.dfrel <- function(rel, ...) { + unclass(rel)[[1]] +} +rel_to_df(mtcars_rel) + +# Example 2: A (random) filter +rel_filter.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the predicates defined + # by the exprs argument + new_dfrel(df[sample.int(nrow(df), 3, replace = TRUE), ]) +} + +rel_filter( + mtcars_rel, + list( + relexpr_function( + "gt", + list(relexpr_reference("cyl"), relexpr_constant("6")) + ) + ) +) + +# Example 3: A custom projection +rel_project.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, base::ncol(df)))]) +} + +rel_project( + mtcars_rel, + list(relexpr_reference("cyl"), relexpr_reference("disp")) +) + +# Example 4: A custom ordering (eg, ascending by mpg) +rel_order.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[order(df[[1]]), ]) +} + +rel_order( + mtcars_rel, + list(relexpr_reference("mpg")) +) + +# Example 5: A custom join +rel_join.dfrel <- function(left, right, conds, join, ...) { + left_df <- unclass(left)[[1]] + right_df <- unclass(right)[[1]] + + # A real implementation would evaluate the expressions + # defined by the conds argument, + # use different join types based on the join argument, + # and implement the join itself instead of relaying to left_join(). + new_dfrel(dplyr::left_join(left_df, right_df)) +} + +rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel) + +# Example 6: Limit the maximum rows returned +rel_limit.dfrel <- function(rel, n, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[seq_len(n), ]) +} + +rel_limit(mtcars_rel, 3) + +# Example 7: Suppress duplicate rows +# (ignoring row names) +rel_distinct.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[!duplicated(df), ]) +} + +rel_distinct(new_dfrel(mtcars[1:3, 1:4])) + +# Example 8: Return column names +rel_names.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + names(df) +} + +rel_names(mtcars_rel) +``` diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/fallback.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/fallback.Rmd new file mode 100644 index 000000000..b4c4bcaee --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/fallback.Rmd @@ -0,0 +1,178 @@ +--- +title: "Fallback to dplyr" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{15 Fallback} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13"))), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This article details the fallback mechanism in duckplyr, which allows support for all dplyr verbs and R functions. + + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + + +## Introduction + +The duckplyr package aims at providing a fully compatible drop-in replacement for dplyr. +All operations, R functions, and data types that are supported by dplyr should work in an identical way with duckplyr. +This is achieved in two ways: + +- A carefully selected subset of dplyr operations, R functions, and R data types are implemented in DuckDB, focusing on faithful translation. +- When DuckDB does not support an operation, duckplyr falls back to dplyr, guaranteeing identical behavior. + +## DuckDB mode + +The following operation is supported by duckplyr: + +```{r} +duckdb <- + duckplyr::duckdb_tibble(a = 1:3) |> + arrange(desc(a)) |> + mutate(b = a + 1) |> + select(-a) +``` + +The `explain()` function shows what happens under the hood: + +```{r} +duckdb |> + explain() +``` + +The plan shows three operations: + +- a data frame scan (the input), +- a sort operation, +- a projection (adding the `b` column and removing the `a` column). + +Each operation is supported by DuckDB. +The resulting object contains a plan for the entire pipeline that is executed lazily, only when the data is needed. + +## Relation objects + +DuckDB accepts a tree of interconnected _relation objects_ as input. +Each relation object represents a logical step of the execution plan. +The duckplyr package translates dplyr verbs into relation objects. + +The `last_rel()` function shows the last relation that has been materialized: + +```{r} +duckplyr::last_rel() +``` + +It is `NULL` because nothing has been computed yet. +Converting the object to a data frame triggers the computation: + +```{r} +duckdb |> collect() +duckplyr::last_rel() +``` + +The `last_rel()` function now shows a relation that describes logical plan for executing the whole pipeline. + +## Help from dplyr + +Using a custom function with a side effect is not supported by DuckDB and triggers a dplyr fallback: + +```{r} +verbose_plus_one <- function(x) { + message("Adding one to ", paste(x, collapse = ", ")) + x + 1 +} + +fallback <- + duckplyr::duckdb_tibble(a = 1:3) |> + arrange(desc(a)) |> + mutate(b = verbose_plus_one(a)) |> + select(-a) +``` + +The `verbose_plus_one()` function is not supported by DuckDB, so the `mutate()` step is forwarded to dplyr and already executed (eagerly) when the pipeline is defined. +This is confirmed by the `last_rel()` function: + +```{r} +duckplyr::last_rel() +``` + +Only the `arrange()` step is executed by DuckDB. +Because the dplyr implementation of `mutate()` needs the data before it can proceed, the data is first converted to a data frame, and this triggers the materialization of the first step. + +The `explain()` function also confirms indirectly that at least a part of the operation is handled by dplyr: + +```{r} +fallback |> + explain() +``` + +The final plan now only consists of a data frame scan. +This is the result of the `mutate()` step, which at this stage already has been executed by dplyr. + +Converting the final object to a data frame triggers the rest of the computation: + +```{r} +fallback |> collect() + +duckplyr::last_rel() +``` + +The `last_rel()` function confirms that only the final `select()` is handled by DuckDB again. + +## Enforce DuckDB operation + +For any duck frame, one can control the automatic materialization. +For fallbacks to dplyr, automatic materialization must be allowed for the duck frame at hand, as dplyr necessitates eager evaluation. + +Therefore, by making a data frame stingy, one can ensure a pipeline will error when a fallback to dplyr would have normally happened. +See `vignette("prudence")` for details. + +By using operations supported by duckplyr and avoiding fallbacks as much as possible, your pipelines will be executed by DuckDB in an optimized way. +As of duckplyr 1.1.0, most DuckDB functions can be used directly, see `vignette("duckdb")` for details. + +## Configure fallbacks + +Using the `fallback_sitrep()` and `fallback_config()` functions you can examine and change settings related to fallbacks. + +- You can choose to make fallbacks verbose with `fallback_config(info = TRUE)`. + +- You can change settings related to logging and reporting fallback to duckplyr development team to inform their work. + +See `vignette("telemetry")` for details. + +## Conclusion + +The fallback mechanism in duckplyr allows for a seamless integration of dplyr verbs and R functions that are not supported by DuckDB. +It is transparent to the user and only triggers when necessary. +With small or medium-sized data sets, it will not even be noticeable in most settings. + +See `vignette("large")` for techniques for working with large data, `vignette("limits")` for the currently implementated translations, `vignette("duckdb")` for direct access to DuckDB functions, `vignette("prudence")` for details on controlling fallback behavior, and `vignette("telemetry")` for the automatic reporting of fallback situations. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/large.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/large.Rmd new file mode 100644 index 000000000..d2c3bfe43 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/large.Rmd @@ -0,0 +1,327 @@ +--- +title: "Large data" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{01 Large data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "dbplyr", "nycflights13", "callr")) && duckplyr:::can_load_extension("httpfs")), + comment = "#>" +) + +options(conflicts.policy = list(warn = FALSE)) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +Working with large datasets in R can be challenging, especially when performance and memory constraints are a concern. +The duckplyr package, built on top of DuckDB, offers a powerful solution by enabling efficient data manipulation using familiar dplyr syntax. +This article explores strategies for handling large datasets with duckplyr, covering ingestion, materialization of intermediate and final results, and good practice. + +```{r attach} +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") +``` + + +## Introduction + +Data frames and other objects in R are stored in RAM. +This can become problematic: + +- Data must be loaded into RAM first, even if only part of it is needed. +- Data must be stored in RAM, even if it is not used. +- RAM is limited, and data sets can be larger than the available RAM. + +A variety of tools have been developed to work with large data sets, also in R. +One example is the dbplyr package, a dplyr backend that connects to SQL databases and is designed to work with various databases that support SQL. +This is a viable approach if the data is already stored in a database, or if the data is stored in Parquet or CSV files and loaded as a lazy table via `duckdb::tbl_file()`. + +The dbplyr package translates dplyr code to SQL. +The syntax and semantics are very similar, but not identical to plain dplyr. +In contrast, the duckplyr package aims to be a fully compatible drop-in replacement for dplyr, with *exactly* the same syntax and semantics: + +- Input and output are data frames or tibbles. +- All dplyr verbs are supported, with fallback. +- All R data types and functions are supported, with fallback. +- No SQL is generated, instead, DuckDB's "relational" interface is used. + +Full compatibility means fewer surprises and less cognitive load for the user. +With DuckDB as the backend, duckplyr can also handle large data sets that do not fit into RAM, keeping full dplyr compatibility. +The tools for bringing data into and out of R memory are modeled after the dplyr and dbplyr packages, and are described in the following sections. + +See `vignette("prudence")` on eager and lazy data, `vignette("limits")` for limitations in the translation employed by duckplyr, `vignette("duckdb")` for a way to overcome these limitations, and `vignette("fallback")` for more information on fallback. + + +## To duckplyr + +The `duckdb_tibble()` function creates a duckplyr data frame from vectors: + +```{r} +df <- duckdb_tibble(x = 1:3, y = letters[1:3]) +df +``` + +The `duckdb_tibble()` function is a drop-in replacement for `tibble()`, and can be used in the same way. + +Similarly, `as_duckdb_tibble()` can be used to convert a data frame or another object to a duckplyr data frame: + +```{r} +flights_df() |> + as_duckdb_tibble() +``` + +Existing code that uses DuckDB via dbplyr can also take advantage. +The following code creates a DuckDB connection and writes a data frame to a table: + +```{r} +path_duckdb <- tempfile(fileext = ".duckdb") +con <- DBI::dbConnect(duckdb::duckdb(path_duckdb)) +DBI::dbWriteTable(con, "data", data.frame(x = 1:3, y = letters[1:3])) + +dbplyr_data <- tbl(con, "data") +dbplyr_data + +dbplyr_data |> + explain() +``` + +The `explain()` output shows that the data is actually coming from a DuckDB table. +The `as_duckdb_tibble()` function can then be used to seamlessly convert the data to a duckplyr frame: + +```{r} +dbplyr_data |> + as_duckdb_tibble() + +dbplyr_data |> + as_duckdb_tibble() |> + explain() +``` + +This only works for DuckDB connections. +For other databases, turn the data into an R data frame or export it to a file before using `as_duckdb_tibble()`. + +```{r} +DBI::dbDisconnect(con) +``` + +For other common cases, the `duckdb_tibble()` function fails with a helpful error message: + +- duckplyr does not support `group_by()`: + +```{r error = TRUE} +duckdb_tibble(a = 1) |> + group_by(a) |> + as_duckdb_tibble() +``` + +- duckplyr does not support `rowwise()`: + +```{r error = TRUE} +duckdb_tibble(a = 1) |> + rowwise() |> + as_duckdb_tibble() +``` + +- Use `read_csv_duckdb()` to read with the built-in reader: + +```{r error = TRUE} +readr::read_csv("a\n1", show_col_types = FALSE) |> + as_duckdb_tibble() +``` + +In all cases, `as_tibble()` can be used to proceed with the existing code. + +## From files + +DuckDB supports data ingestion from CSV, Parquet, and JSON files. +The `read_csv_duckdb()` function accepts a file path and returns a duckplyr frame. + +```{r} +path_csv_1 <- tempfile(fileext = ".csv") +writeLines("x,y\n1,a\n2,b\n3,c", path_csv_1) +read_csv_duckdb(path_csv_1) +``` + +Reading multiple files is also supported: + +```{r} +path_csv_2 <- tempfile(fileext = ".csv") +writeLines("x,y\n4,d\n5,e\n6,f", path_csv_2) +read_csv_duckdb(c(path_csv_1, path_csv_2)) +``` + +The `options` argument can be used to control the reading. + +Similarly, the `read_parquet_duckdb()` and `read_json_duckdb()` functions can be used to read Parquet and JSON files, respectively. + +For reading from HTTPS or S3 URLs, the [httpfs extension](https://duckdb.org/docs/extensions/httpfs/overview.html) must be installed and loaded in each session. + +```{r} +db_exec("INSTALL httpfs") +db_exec("LOAD httpfs") +``` + +Installation is fast if the extension is already installed. +Once loaded, the `read_csv_duckdb()`, `read_parquet_duckdb()`, and `read_json_duckdb()` functions can be used with URLs: + +```{r} +url <- "https://blobs.duckdb.org/flight-data-partitioned/Year=2024/data_0.parquet" +flights_parquet <- read_parquet_duckdb(url) +flights_parquet +``` + +In all cases, the data is read lazily: only the metadata is read initially, and the data is read as required. +This means that data can be read from files that are larger than the available RAM. +The Parquet format is particularly efficient for this purpose, as it stores data in a columnar format and allows reading only the columns that are required. +See `vignette("prudence")` for more details on the concept of lazy data. + +## From DuckDB + +In addition to `as_duckdb_tibble()`, arbitrary DuckDB queries can be executed and the result can be converted to a duckplyr frame. +For this, [attach](https://duckdb.org/docs/sql/statements/attach.html) an existing DuckDB database first: + +```{r} +sql_attach <- paste0( + "ATTACH DATABASE '", + path_duckdb, + "' AS external (READ_ONLY)" +) +db_exec(sql_attach) +``` + +Then, use `read_sql_duckdb()` to execute a query and return a duckplyr frame: + +```{r} +read_sql_duckdb("SELECT * FROM external.data") +``` + +## Materialization + +In dbplyr, `compute()` is used to materialize a lazy table in a temporary table on the database, and `collect()` is used to bring the data into R memory. +This interface works exactly the same in duckplyr: + +```{r} +simple_data <- + duckdb_tibble(a = 1) |> + mutate(b = 2) + +simple_data |> + explain() + +simple_data_computed <- + simple_data |> + compute() +``` + +The `compute.duckplyr_df()` function returns a duckplyr frame that is materialized in a temporary table. +The return value of the function is a duckplyr frame that can be used in further computations. +The materialization is done in a temporary table, so the data is not persisted after the session ends: + +```{r} +simple_data_computed |> + explain() +``` + +The `collect()` function brings the data into R memory and returns a plain tibble: + +```{r} +duckdb_tibble(a = 1) |> + mutate(b = 2) |> + collect() +``` + +## To files + +To materialize data in a persistent file, the `compute_csv()` and `compute_parquet()` functions can be used. +The `compute_csv()` function writes the data to a CSV file: + +```{r} +path_csv_out <- tempfile(fileext = ".csv") +duckdb_tibble(a = 1) |> + mutate(b = 2) |> + compute_csv(path_csv_out) + +writeLines(readLines(path_csv_out)) +``` + +The `compute_parquet()` function writes the data to a Parquet file: + +```{r} +path_parquet_out <- tempfile(fileext = ".parquet") +duckdb_tibble(a = 1) |> + mutate(b = 2) |> + compute_parquet(path_parquet_out) |> + explain() +``` + +Just like with `compute.duckplyr_df()`, the return value of `compute_csv()` and `compute_parquet()` is a duckplyr frame that uses the created CSV or Parquet file and can be used in further computations. +At the time of writing, direct JSON export is not supported. + +## Memory usage + +Computations carried out by DuckDB allocate RAM in the context of the R process. +This memory separate from the memory used by R objects, and is managed by DuckDB. +Limit the memory used by DuckDB by setting a pragma with `db_exec()`: + +```{r} +read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit") + +db_exec("PRAGMA memory_limit = '1GB'") + +read_sql_duckdb("SELECT current_setting('memory_limit') AS memlimit") +``` + +See [the DuckDB documentation](https://duckdb.org/docs/configuration/overview.html) for other configuration options. + +## The big picture + +The functions shown in this vignette allow the construction of data transformation pipelines spanning multiple data sources and data that is too large to fit into memory. +Full compatibility with dplyr is provided, so existing code can be used with duckplyr with minimal changes. +The lazy computation of duckplyr frames allows for efficient data processing, as only the required data is read from disk. +The materialization functions allow the data to be persisted in temporary tables or files, depending on the use case. +A typical workflow might look like this: + +- Prepare all data sources as duckplyr frames: local data frames and files +- Combine the data sources using dplyr verbs +- Preview intermediate results as usual: the computation will be faster because only the first few rows are requested +- To avoid rerunning the whole pipeline all over, use `compute.duckplyr_df()` or `compute_parquet()` to materialize any intermediate result that is too large to fit into memory +- Collect the final result using `collect.duckplyr_df()` or write it to a file using `compute_csv()` or `compute_parquet()` + +There is a caveat: due to the design of duckplyr, if a dplyr verb is not supported or uses a function that is not supported, the data will be read into memory before being processed further. +By default, if the data pipeline starts with an ingestion function, the data will only be read into memory if it has less than 1 million cells or values in the table: + +```{r error = TRUE} +flights_parquet |> + group_by(Month) +``` + +Because `group_by()` is not supported, the data will be attempted to read into memory before the `group_by()` operation is executed. +Once the data is small enough to fit into memory, this works transparently. + +```{r} +flights_parquet |> + count(Month, DayofMonth) |> + group_by(Month) +``` + +See `vignette("prudence")` for the concepts and mechanisms at play, and `vignette("fallback")` for a detailed explanation of the fallback mechanism. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/limits.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/limits.Rmd new file mode 100644 index 000000000..f3108aa9d --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/limits.Rmd @@ -0,0 +1,411 @@ +--- +title: "Translations" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{20 Translations} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13", "lubridate"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +This article describes the translations provided by duckplyr for different data types, verbs, and functions within verbs. +If a translation is not provided, duckplyr falls back to dplyr, see `vignette("fallback")` for details. +The translation layer can be bypassed, see `vignette("duckdb")` for details. + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +conflict_prefer("lag", "dplyr") +``` + +## Data types + +duckplyr supports the following data types: + +- `is.logical()` +- `is.integer()` +- `is.numeric()` +- `is.character()` +- `is.Date()` +- `is.POSIXct()` (with UTC time zone) +- `is.difftime()` + +```{r} +duckplyr::duckdb_tibble( + logical = TRUE, + integer = 1L, + numeric = 1.1, + character = "a", + Date = as.Date("2025-01-11"), + POSIXct = as.POSIXct("2025-01-11 19:23:00", tz = "UTC"), + difftime = as.difftime(1, units = "secs"), +) |> + compute() +``` + +Generally, zero-column tibbles are not supported by duckplyr, neither as input nor as a result. + +```{r error = TRUE} +duckplyr::duckdb_tibble() +duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |> + select(-a) +``` + +Support for more data types, and passthrough of unknown data types, is planned. +Let's [discuss](https://github.com/tidyverse/duckplyr/discussions/) any additional data types you would like to see supported. + + +## Verbs + +Not all dplyr verbs are implemented within duckplyr. +For unsupported verbs, duckplyr automatically falls back to dplyr. +See `?unsupported` for a list of verbs for which duckplyr does not provide a method. + +See the [reference index](https://duckplyr.tidyverse.org/reference/index.html) for a list of verbs with corresponding duckplyr methods. + +Let's [discuss](https://github.com/tidyverse/duckplyr/discussions/) any additional verbs you would like to see supported. + + +## Functions within verbs + +For all functions used in dplyr verbs, translations must be provided. +If an expression contains a function for which no translation is provided, duckplyr falls back to dplyr. +With some exceptions, only positional matching is implemented. + +As of now, here are the translations provided: + +### Parentheses + +Implemented: `(`. + +Reference: `?Paren`. + +```{r} +duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |> + mutate((a + b) * c) +``` + +### Comparison operators + +Implemented: `>`, `>=`, `<`, `<=`, `==`, `!=`. + +Reference: `?Comparison`. + +```{r} +duckplyr::duckdb_tibble( + a = c(1, 2, NA), + b = c(2, NA, 3), + c = c(NA, 3, 4), + .prudence = "stingy" +) |> + mutate(a > b, b != c, c < a, a >= b, b <= c) +``` + +### Basic arithmetics + +Implemented: `+`, `-`, `*`, `/`. + +Reference: `?Arithmetic`. + +```{r} +duckplyr::duckdb_tibble(a = 1, b = 2, c = 3, .prudence = "stingy") |> + mutate(a + b, a / b, a - b, a * b) +``` + +### Math functions + +Implemented: `log()`, `log10()`, `abs()`. + +Reference: `?Math`. + +```{r} +duckplyr::duckdb_tibble(a = 1, b = 2, c = -3, .prudence = "stingy") |> + mutate(log10(a), log(b), abs(c)) +``` + +### Logical operators + +Implemented: `!`, `&`, `|`. + +Reference: `?Logic`. + +```{r} +duckplyr::duckdb_tibble(a = FALSE, b = TRUE, c = NA, .prudence = "stingy") |> + mutate(!a, a & b, b | c) +``` + +### Branching and conversion + +Implemented: + +- `is.na()`, `as.integer()` +- `dplyr::if_else()`, `dplyr::coalesce()` +- `strftime(x, format)` + +```{r} +duckplyr::duckdb_tibble(a = 1, b = NA, .prudence = "stingy") |> + mutate(is.na(b), if_else(is.na(b), 0, 1), as.integer(b)) + +duckplyr::duckdb_tibble( + a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"), + .prudence = "stingy") |> + mutate(strftime(a, "%H:%M:%S")) +``` + +### String manipulation + +Implemented: `grepl()`, `substr()`, `sub()`, `gsub()`. + +```{r} +duckplyr::duckdb_tibble(a = "abbc", .prudence = "stingy") |> + mutate(grepl("b", a), substr(a, 2L, 3L), sub("b", "B", a), gsub("b", "B", a)) +``` + +### Date manipulation + +Implemented: `lubridate::hour()`, `lubridate::minute()`, `lubridate::second()`, `lubridate::wday()`. + +```{r} +duckplyr::duckdb_tibble( + a = as.POSIXct("2025-01-11 19:23:46", tz = "UTC"), + .prudence = "stingy" +) |> + mutate( + hour = lubridate::hour(a), + minute = lubridate::minute(a), + second = lubridate::second(a), + wday = lubridate::wday(a) + ) +``` + +### Aggregation + +Implemented: + +- `sum(x, na.rm)`, `dplyr::n()`, `dplyr::n_distinct()` +- `mean(x, na.rm)`, `median(x, na.rm)`, `sd(x, na.rm)` +- `min()`, `max()`, `any()`, `all()` + +```{r} +duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, 2), .prudence = "stingy") |> + summarize( + sum(a), + n(), + n_distinct(b), + ) + +duckplyr::duckdb_tibble(a = 1:3, b = c(1, 2, NA), .prudence = "stingy") |> + summarize( + mean(b, na.rm = TRUE), + median(a), + sd(b), + ) + +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + summarize( + min(a), + max(a), + any(a > 1), + all(a > 1), + ) +``` + +### Shifting + +All optional arguments to `dplyr::lag()` and `dplyr::lead()` are supported. + +```{r} +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a), lead(a)) +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a, 2), lead(a, n = 2)) +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(lag(a, default = 0), lead(a, default = 4)) +duckplyr::duckdb_tibble(a = 1:3, b = c(2, 3, 1), .prudence = "stingy") |> + mutate(lag(a, order_by = b), lead(a, order_by = b)) +``` + +### Ranking + +[Ranking in DuckDB](https://duckdb.org/docs/sql/functions/window_functions.html) is very different from dplyr. +Most functions in DuckDB rank only by the current row number, whereas in dplyr, ranking is done by a column. +It will be difficult to provide translations for the following ranking functions. + +- `rank()`, `dplyr::min_rank()`, `dplyr::dense_rank()` +- `dplyr::percent_rank()`, `dplyr::cume_dist()` + +Implementing `dplyr::ntile()` is feasible for the `n` argument. +The only ranking function currently implemented is `dplyr::row_number()`. + +```{r} +duckplyr::duckdb_tibble(a = c(1, 2, 2, 3), .prudence = "stingy") |> + mutate(row_number()) +``` + +### Special cases + +`$` (`?Extract`) is implemented if the LHS is `.data` or `.env`: + +```{r} +b <- 4 +duckplyr::duckdb_tibble(a = 1, b = 2, .prudence = "stingy") |> + mutate(.data$a + .data$b, .env$b) +``` + +`%in%` (`?match`) is implemented if the RHS is a constant with up to 100 values: + +```{r} +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + mutate(a %in% c(1, 3)) |> + collect() +duckplyr::last_rel() +``` + +`dplyr::desc()` is only implemented in the context of `dplyr::arrange()`: + +```{r} +duckplyr::duckdb_tibble(a = 1:3, .prudence = "stingy") |> + arrange(desc(a)) |> + explain() +``` + +`suppressWarnings()` is a no-op: + +```{r} +duckplyr::duckdb_tibble(a = 1, .prudence = "stingy") |> + mutate(suppressWarnings(a + 1)) +``` + +### Contributing + +Refer to [our contributing guide](https://duckplyr.tidyverse.org/CONTRIBUTING.html#new-translations-for-functions) to learn how to contribute new translations to the package. +Ideally, duckplyr will also support adding custom translations for functions for the duration of the current R session. + +## Known incompatibilities + +This section tracks known incompatibilities between dplyr and duckplyr. +Changing these is likely to require substantial effort, and might be best addressed by providing new functions with consistent behavior in both dplyr and DuckDB. + +### Output order stability + +DuckDB does not guarantee order stability for the output. +For performance reasons, duckplyr does not enable output order stability by default. + +```{r} +duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + summarize(paste(day, collapse = " ")) # fallback + +duckplyr::flights_df() |> + distinct(day) |> + summarize(paste(day, collapse = " ")) +``` + +This can be changed globally with the `DUCKPLYR_OUTPUT_ORDER` environment variable, see `?config` for details. +With this setting, the output order is stable, but the plans are more complicated, and DuckDB needs to do more work. + +```{r} +duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + explain() + +withr::with_envvar( + c(DUCKPLYR_OUTPUT_ORDER = "TRUE"), + duckplyr::flights_df() |> + duckplyr::as_duckdb_tibble() |> + distinct(day) |> + explain() +) +``` + +### `sum()` + +In duckplyr, this function returns a numeric value also for integers, due to DuckDB's type stability requirement. + +```{r} +duckplyr::duckdb_tibble(a = 1:100) |> + summarize(sum(a)) + +duckplyr::duckdb_tibble(a = 1:1000000) |> + summarize(sum(a)) + +tibble(a = 1:100) |> + summarize(sum(a)) + +tibble(a = 1:1000000) |> + summarize(sum(a)) +``` + +### Empty vectors in aggregate functions + +At the time of writing, empty vectors only occur when summarizing an empty table without grouping. +In all cases, duckplyr returns `NA`, and the behavior of dplyr is different: + +- `sum()` for an empty vector returns `0` +- `any()` and `all()` return `FALSE` +- `min()` and `max()` return infinity values (with a warning) + +```{r} +duckplyr::duckdb_tibble(a = integer(), b = logical()) |> + summarize(sum(a), any(b), all(b), min(a), max(a)) +tibble(a = integer(), b = logical()) |> + summarize(sum(a), any(b), all(b), min(a), max(a)) +``` + +### `min()` and `max()` for logical input + +For completeness, duckplyr returns a logical for `min()` and `max()` when the input is logical, while dplyr returns an integer. + +```{r} +duckplyr::duckdb_tibble(a = c(TRUE, FALSE)) |> + summarize(min(a), max(a)) + +tibble(a = c(TRUE, FALSE)) |> + summarize(min(a), max(a)) +``` + +### `n_distinct()` and multiple arguments + +This function needs exactly one argument besides the optional `na.rm`. Multiple arguments is not supported. + +### `is.na()` and `NaN` values + +This function returns `FALSE` for `NaN` values in duckplyr, while it returns `TRUE` in dplyr. + +```{r} +duckplyr::duckdb_tibble(a = c(NA, NaN)) |> + mutate(is.na(a)) + +tibble(a = c(NA, NaN)) |> + mutate(is.na(a)) +``` + +### Other differences + +Does the same pipeline give different results with `tibble()` and `duckdb_tibble()`? +We would love to hear about it, please file an [issue](https://github.com/tidyverse/duckplyr/issues/new). diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/prudence.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/prudence.Rmd new file mode 100644 index 000000000..05117acb7 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/prudence.Rmd @@ -0,0 +1,324 @@ +--- +title: "Memory protection: controlling automatic materialization" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{10 Memory protection: controlling automatic materialization} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +clean_output <- function(x, options) { + x <- gsub("0x[0-9a-f]+", "0xdeadbeef", x) + x <- gsub("dataframe_[0-9]*_[0-9]*", " dataframe_42_42 ", x) + x <- gsub("[0-9]*\\.___row_number ASC", "42.___row_number ASC", x) + x <- gsub("─", "-", x) + x +} + +local({ + hook_source <- knitr::knit_hooks$get("document") + knitr::knit_hooks$set(document = clean_output) +}) + +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) +``` + +Unlike traditional data frames, duckplyr defers computation until absolutely necessary, allowing DuckDB to optimize execution. +This article explains how to control the materialization of data to maintain a seamless dplyr-like experience while remaining cautious of memory usage. + + + +```{r attach} +library(conflicted) +library(dplyr) +conflict_prefer("filter", "dplyr") +``` + +## Introduction + +From a user's perspective, data frames backed by duckplyr, with class `"duckplyr_df"`, behave as regular data frames in almost all respects. +In particular, direct column access like `df$x`, or retrieving the number of rows with `nrow()`, works identically. +Conceptually, duckplyr frames are "eager": + +```{r} +df <- + duckplyr::duckdb_tibble(x = 1:3) |> + mutate(y = x + 1) +df + +class(df) + +df$y + +nrow(df) +``` + +Under the hood, two key differences provide improved performance and usability: + +- **lazy materialization**: Unlike traditional data frames, duckplyr defers computation until absolutely necessary, i.e. lazily, allowing DuckDB to optimize execution. +- **prudence**: Automatic materialization is controllable, as automatic materialization of large data might otherwise inadvertently lead to memory problems. + +The term "prudence" is introduced here to set a clear distinction from the concept of "laziness", and because "control of automatic materialization" is a mouthful. + +## Eager and lazy computation + +For a duckplyr frame that is the result of a dplyr operation, accessing column data or retrieving the number of rows will trigger a computation that is carried out by DuckDB, not dplyr. +In this sense, duckplyr frames are also "lazy": the computation is deferred until the last possible moment, allowing DuckDB to optimize the whole pipeline. + +### Example + +This is explained in the following example that computes the mean arrival delay for flights departing from Newark airport (EWR) by day and month: + +```{r} +flights <- duckplyr::flights_df() + +flights_duckdb <- + flights |> + duckplyr::as_duckdb_tibble() + +system.time( + mean_arr_delay_ewr <- + flights_duckdb |> + filter(origin == "EWR", !is.na(arr_delay)) |> + summarize( + .by = month, + mean_arr_delay = mean(arr_delay), + min_arr_delay = min(arr_delay), + max_arr_delay = max(arr_delay), + median_arr_delay = median(arr_delay), + ) +) +``` + +Setting up the pipeline is fast, the size of the data does not affect the setup costs. +Because the computation is deferred, DuckDB can optimize the whole pipeline, which can be seen in the output below: + +```{r} +mean_arr_delay_ewr |> + explain() +``` + +The first step in the pipeline is to prune the unneeded columns, only `origin`, `month`, and `arr_delay` are kept. +The result becomes available when accessed: + +```{r} +system.time(mean_arr_delay_ewr$mean_arr_delay[[1]]) +``` + +### Comparison + +The functionality is similar to lazy tables in [dbplyr](https://dbplyr.tidyverse.org/) and lazy frames in [dtplyr](https://dtplyr.tidyverse.org/). +However, the behavior is different: at the time of writing, the internal structure of a lazy table or frame is different from a data frame, and columns cannot be accessed directly. + +| | **Eager** 😃 | **Lazy** 😴 | +|-------------|:------------:|:-----------:| +| **dplyr** | ✅ | | +| **dbplyr** | | ✅ | +| **dtplyr** | | ✅ | +| **duckplyr**| ✅ | ✅ | + +In contrast, with [dplyr](https://dplyr.tidyverse.org/), each intermediate step and also the final result is a proper data frame, and computed right away, forfeiting the opportunity for optimization: + +```{r} +system.time( + flights |> + filter(origin == "EWR", !is.na(arr_delay)) |> + summarize( + .by = c(month, day), + mean_arr_delay = mean(arr_delay), + min_arr_delay = min(arr_delay), + max_arr_delay = max(arr_delay), + median_arr_delay = median(arr_delay), + ) +) +``` + +See also the [duckplyr: dplyr Powered by DuckDB](https://duckdb.org/2024/04/02/duckplyr.html) blog post for more information. + +## Prudence + +Being both "eager" and "lazy" at the same time introduces a challenge: +it is too easy to accidentally trigger computation, +which is prohibitive if an intermediate result is too large to fit into memory. +Prudence is a setting for duckplyr frames that limits the size of the data that is materialized automatically. + +### Concept + +Three levels of prudence are available: + +- _lavish_ (careless about resources): always automatically materialize, as in the first example. +- _stingy_ (avoid spending at all cost): never automatically materialize, throw an error when attempting to access the data. +- _thrifty_ (use resources wisely): only automaticaly materialize the data if it is small, otherwise throw an error. + +For lavish duckplyr frames, as in the two previous examples, the underlying DuckDB computation is carried out upon the first request. +Once the results are computed, they are cached and subsequent requests are fast. +This is a good choice for small to medium-sized data, where DuckDB can provide a nice speedup but materializing the data is affordable at any stage. +This is the default for `duckdb_tibble()` and `as_duckdb_tibble()`. + +For stingy duckplyr frames, accessing a column or requesting the number of rows triggers an error. +This is a good choice for large data sets where the cost of materializing the data may be prohibitive due to size or computation time, and the user wants to control when the computation is carried out and where the results are stored. +Results can be materialized explicitly with `collect()` and other functions. + +Thrifty duckplyr frames are a compromise between lavish and stingy, discussed further below. + + +### Example + +Passing `prudence = "stingy"` to `as_duckdb_tibble()` creates a stingy duckplyr frame. + +```{r} +flights_stingy <- + flights |> + duckplyr::as_duckdb_tibble(prudence = "stingy") +``` + +The data can be displayed, and column names and types can be accessed. + +```{r} +flights_stingy + +names(flights_stingy)[1:10] + +class(flights_stingy) + +class(flights_stingy[[1]]) +``` + +On the other hand, accessing a column or requesting the number of rows triggers an error: + +```{r error = TRUE} +nrow(flights_stingy) + +flights_stingy[[1]] +``` + +This means that stingy duckplyr frames can also be used to enforce DuckDB operation for a pipeline. + +### Enforcing DuckDB operation + +For operations not supported by duckplyr, the original dplyr implementation is used as a fallback. +As the original dplyr implementation accesses columns directly, the data must be materialized before a fallback can be executed. +Therefore, stingy frames allow you to check that all operations are supported by DuckDB: for a stingy frame, fallbacks to dplyr are not possible. + +```{r error = TRUE} +flights_stingy |> + group_by(origin) |> + summarize(n = n()) |> + ungroup() +``` + +The same pipeline with a lavish frame works, but the computation is carried out by dplyr: + +```{r} +flights_stingy |> + duckplyr::as_duckdb_tibble(prudence = "lavish") |> + group_by(origin) |> + summarize(n = n()) |> + ungroup() +``` + +By using operations supported by duckplyr and avoiding fallbacks as much as possible, your pipelines will be executed by DuckDB in an optimized way. + + +### From stingy to lavish + +A stingy duckplyr frame can be converted to a lavish one with `as_duckdb_tibble(prudence = "lavish")`. +The `collect.duckplyr_df()` method triggers computation and converts to a plain tibble. +The difference between the two is the class of the returned object: + +```{r} +flights_stingy |> + duckplyr::as_duckdb_tibble(prudence = "lavish") |> + class() + +flights_stingy |> + collect() |> + class() +``` + +The same behavior is achieved with `as_tibble()` and `as.data.frame()`: + +```{r} +flights_stingy |> + as_tibble() |> + class() + +flights_stingy |> + as.data.frame() |> + class() +``` + +### Comparison + +Stingy duckplyr frames behave like lazy tables in dbplyr and lazy frames in dtplyr: the computation only starts when you _explicitly_ request it with `collect.duckplyr_df()` or through other means. +However, stingy duckplyr frames can be converted to lavish ones at any time, and vice versa. +In dtplyr and dbplyr, there are no lavish frames: collection always needs to be explicit. + + +## Thrift + +Thrifty is a compromise between stingy and lavish. +Materialization is allowed for data up to a certain size, measured in cells (values) and rows in the resulting data frame. + +```{r} +nrow(flights) +flights_partial <- + flights |> + duckplyr::as_duckdb_tibble(prudence = "thrifty") +``` + +With this setting, the data is materialized only if the result has fewer than 1,000,000 cells (rows multiplied by columns). + +```{r error = TRUE} +flights_partial |> + select(origin, dest, dep_delay, arr_delay) |> + nrow() +``` + +The original input is too large to be materialized, so the operation fails. +On the other hand, the result after aggregation is small enough to be materialized: + +```{r} +flights_partial |> + count(origin) |> + nrow() +``` + +Thrifty is a good choice for data sets where the cost of materializing the data is prohibitive only for large results. + +### File ingestion and custom limits + +Thrifty is the default for the ingestion functions like `read_parquet_duckdb()`. +Here, the limit is adapted depending on the source of the data: + +- For local files, the limit is 1,000,000 cells. +- For remote files (if the file name starts with a URL protocol specifier), the limit is 1,000 cells. + +A custom limit can be set by passing a named vector to `prudence`, with elements `cells` and/or `rows`: + +```r +read_parquet_duckdb( + "personas.parquet", + prudence = c(cells = 10000, rows = 1000) +) +``` + + +## Conclusion + +The duckplyr package provides + +- a drop-in replacement for duckplyr, which necessitates "eager" data frames that automatically materialize like in dplyr, +- optimization by DuckDB, which means "lazy" evaluation where the data is materialized at the latest possible stage. + +Automatic materialization can be dangerous for memory with large data, so duckplyr provides a setting called `prudence` that controls automatic materialization: +is the data automatically materialized _always_ ("lavish" frames), _never_ ("stingy" frames) or _up to a certain size_ ("thrifty" frames). + +See `vignette("large")` for more details on working with large data sets, `vignette("fallback")` for fallbacks to dplyr, `vignette("limits")` for the operations supported by duckplyr, and `vignette("duckdb")` for using DuckDB functions directly. diff --git a/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/telemetry.Rmd b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/telemetry.Rmd new file mode 100644 index 000000000..e104f3c20 --- /dev/null +++ b/duckplyr.Rcheck/00_pkg_src/duckplyr/vignettes/telemetry.Rmd @@ -0,0 +1,67 @@ +--- +title: "Telemetry" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{80 Telemetry} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + eval = identical(Sys.getenv("IN_PKGDOWN"), "true") || (getRversion() >= "4.1" && rlang::is_installed(c("conflicted", "nycflights13"))), + comment = "#>" +) + +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = 0) + +options(conflicts.policy = list(warn = FALSE)) +``` + +```{r attach} +library(conflicted) +library(duckplyr) +conflict_prefer("filter", "dplyr") +``` + +As a drop-in replacement for dplyr, duckplyr will use DuckDB for the operations only if it can, and fall back to dplyr otherwise. +A fallback will not change the correctness of the results, but it may be slower or consume more memory. +We would like to guide our efforts towards improving duckplyr, focusing on the features with the most impact. +To this end, duckplyr collects and uploads telemetry data about fallback situations, but only if permitted by the user: + +- Collection is on by default, but can be turned off. +- Uploads are done upon request only. +- There is an option to automatically upload when the package is loaded, this is also opt-in. + +The data collected contains: + +- The package version +- The error message +- The operation being performed, and the arguments + - For the input data frames, only the structure is included (column types only), no column names or data + +```{r include = FALSE} +Sys.setenv(DUCKPLYR_FALLBACK_COLLECT = "") +Sys.setenv(DUCKPLYR_FALLBACK_AUTOUPLOAD = "") +fallback_purge() +``` + +Fallback is silent by default, but can be made verbose. + +```{r} +Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE) +out <- + flights_df() |> + summarize(.by = origin, paste(dest, collapse = " ")) +``` + +After logs have been collected, the upload options are displayed the next time the duckplyr package is loaded in an R session. + +```{r, echo = FALSE} +duckplyr:::fallback_autoupload() +``` + +The `fallback_sitrep()` function describes the current configuration and the available options. + +See `vignette("fallback")` for details on the fallback mechanism. diff --git a/duckplyr.Rcheck/00check.log b/duckplyr.Rcheck/00check.log new file mode 100644 index 000000000..da73caf16 --- /dev/null +++ b/duckplyr.Rcheck/00check.log @@ -0,0 +1,284 @@ +* using log directory ‘/home/runner/work/duckplyr/duckplyr/duckplyr.Rcheck’ +* using R version 4.5.2 (2025-10-31) +* using platform: x86_64-pc-linux-gnu +* R was compiled by + gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 + GNU Fortran (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 +* running under: Ubuntu 24.04.3 LTS +* using session charset: UTF-8 +* using options ‘--no-manual --as-cran’ +* checking for file ‘duckplyr/DESCRIPTION’ ... OK +* checking extension type ... Package +* this is package ‘duckplyr’ version ‘1.1.3.9004’ +* package encoding: UTF-8 +* checking CRAN incoming feasibility ... NOTE +Maintainer: ‘Kirill Müller ’ + +Found the following (possibly) invalid URLs: + URL: https://app.codecov.io/gh/tidyverse/duckplyr + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: app.codecov.io + URL: https://dbplyr.tidyverse.org/ + From: inst/doc/prudence.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: dbplyr.tidyverse.org + URL: https://dplyr.tidyverse.org/ + From: inst/doc/prudence.html + README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: dplyr.tidyverse.org + URL: https://dtplyr.tidyverse.org/ + From: inst/doc/prudence.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: dtplyr.tidyverse.org + URL: https://duckdb.org/ + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/2024/04/02/duckplyr.html + From: inst/doc/prudence.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/configuration/overview.html + From: man/db_exec.Rd + man/duckplyr_execute.Rd + inst/doc/large.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/data/overview + From: man/df_from_file.Rd + man/read_file_duckdb.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/extensions/httpfs/overview.html + From: inst/doc/large.html + README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/sql/functions/window_functions.html + From: inst/doc/limits.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/sql/statements/attach.html + From: man/db_exec.Rd + inst/doc/large.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/sql/statements/copy.html#csv-options + From: man/compute_csv.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckdb.org/docs/sql/statements/copy.html#parquet-options + From: man/compute_parquet.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckdb.org + URL: https://duckplyr.tidyverse.org + From: DESCRIPTION + man/duckplyr-package.Rd + README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/CODE_OF_CONDUCT + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/CONTRIBUTING.html#new-translations-for-functions + From: inst/doc/limits.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/CONTRIBUTING.html#support-new-verbs + From: man/unsupported.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/developers.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/duckdb.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/fallback.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/large.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/limits.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/prudence.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/articles/telemetry.html + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://duckplyr.tidyverse.org/reference/index.html + From: inst/doc/limits.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: duckplyr.tidyverse.org + URL: https://forum.posit.co/ + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: forum.posit.co + URL: https://lifecycle.r-lib.org/articles/stages.html#deprecated + From: man/as_duckplyr_df.Rd + man/count.duckplyr_df.Rd + man/df_from_file.Rd + man/duckplyr_execute.Rd + man/is_duckplyr_df.Rd + man/summarise.duckplyr_df.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: lifecycle.r-lib.org + URL: https://lifecycle.r-lib.org/articles/stages.html#experimental + From: man/as_tbl.Rd + man/duckdb_tibble.Rd + man/filter.duckplyr_df.Rd + man/mutate.duckplyr_df.Rd + man/read_file_duckdb.Rd + man/read_sql_duckdb.Rd + man/slice_head.duckplyr_df.Rd + man/summarise.duckplyr_df.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: lifecycle.r-lib.org + URL: https://lifecycle.r-lib.org/articles/stages.html#stable + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: lifecycle.r-lib.org + URL: https://orcid.org/0000-0001-8552-0029 + From: man/duckplyr-package.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: orcid.org + URL: https://orcid.org/0000-0002-1416-3412 + From: man/duckplyr-package.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: orcid.org + URL: https://r4ds.hadley.nz/data-transform + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: r4ds.hadley.nz + URL: https://ror.org/03wc8by49 + From: man/duckplyr-package.Rd + Status: Error + Message: libcurl error code 6: + Could not resolve host: ror.org + URL: https://tidyverse.r-universe.dev/builds + From: README.md + Status: Error + Message: libcurl error code 6: + Could not resolve host: tidyverse.r-universe.dev + URL: https://usethis.r-lib.org/ + From: inst/doc/developers.html + Status: Error + Message: libcurl error code 6: + Could not resolve host: usethis.r-lib.org + +Found the following (possibly) invalid ORCID iDs: + iD: 0000-0001-8552-0029 (from: DESCRIPTION) + iD: 0000-0002-1416-3412 (from: DESCRIPTION) + +Found the following (possibly) invalid ROR IDs: + ID: 03wc8by49 (from: DESCRIPTION) +* checking package namespace information ... OK +* checking package dependencies ... OK +* checking if this is a source package ... OK +* checking if there is a namespace ... OK +* checking for executable files ... OK +* checking for hidden files and directories ... OK +* checking for portable file names ... OK +* checking for sufficient/correct file permissions ... OK +* checking whether package ‘duckplyr’ can be installed ... OK +* checking package directory ... OK +* checking for future file timestamps ... OK +* checking ‘build’ directory ... OK +* checking DESCRIPTION meta-information ... OK +* checking top-level files ... OK +* checking for left-over files ... OK +* checking index information ... OK +* checking package subdirectories ... OK +* checking code files for non-ASCII characters ... OK +* checking R files for syntax errors ... OK +* checking whether the package can be loaded ... OK +* checking whether the package can be loaded with stated dependencies ... OK +* checking whether the package can be unloaded cleanly ... OK +* checking whether the namespace can be loaded with stated dependencies ... OK +* checking whether the namespace can be unloaded cleanly ... OK +* checking loading without being on the library search path ... OK +* checking whether startup messages can be suppressed ... OK +* checking use of S3 registration ... OK +* checking dependencies in R code ... OK +* checking S3 generic/method consistency ... OK +* checking replacement functions ... OK +* checking foreign function calls ... OK +* checking R code for possible problems ... [16s/12s] OK +* checking Rd files ... OK +* checking Rd metadata ... OK +* checking Rd line widths ... OK +* checking Rd cross-references ... OK +* checking for missing documentation entries ... OK +* checking for code/documentation mismatches ... OK +* checking Rd \usage sections ... OK +* checking Rd contents ... OK +* checking for unstated dependencies in examples ... OK +* checking installed files from ‘inst/doc’ ... OK +* checking files in ‘vignettes’ ... OK +* checking examples ... OK +* checking for unstated dependencies in ‘tests’ ... WARNING +Warning: unable to access index for repository https://CRAN.R-project.org/src/contrib: + cannot open URL 'https://CRAN.R-project.org/src/contrib/PACKAGES' +Warning: unable to access index for repository https://bioconductor.org/packages/3.22/bioc/src/contrib: + cannot open URL 'https://bioconductor.org/packages/3.22/bioc/src/contrib/PACKAGES' +Warning: unable to access index for repository https://bioconductor.org/packages/3.22/data/annotation/src/contrib: + cannot open URL 'https://bioconductor.org/packages/3.22/data/annotation/src/contrib/PACKAGES' +Warning: unable to access index for repository https://bioconductor.org/packages/3.22/data/experiment/src/contrib: + cannot open URL 'https://bioconductor.org/packages/3.22/data/experiment/src/contrib/PACKAGES' +'::' or ':::' imports not declared from: + ‘dplyrr’ ‘pkg’ ‘somepkg’ +* checking tests ... [90s/89s] OK + Running ‘testthat.R’ [89s/89s] +* checking for unstated dependencies in vignettes ... OK +* checking package vignettes ... OK +* checking re-building of vignette outputs ... [25s/22s] OK +* checking for non-standard things in the check directory ... OK +* checking for detritus in the temp directory ... OK +* checking for new files in some other directories ... OK +* DONE +Status: 1 WARNING, 1 NOTE diff --git a/duckplyr.Rcheck/00install.out b/duckplyr.Rcheck/00install.out new file mode 100644 index 000000000..a0de8951b --- /dev/null +++ b/duckplyr.Rcheck/00install.out @@ -0,0 +1,15 @@ +* installing *source* package ‘duckplyr’ ... +** this is package ‘duckplyr’ version ‘1.1.3.9004’ +** using staged installation +** R +** inst +** byte-compile and prepare package for lazy loading +** help +*** installing help indices +*** copying figures +** building package indices +** installing vignettes +** testing if installed package can be loaded from temporary location +** testing if installed package can be loaded from final location +** testing if installed package keeps a record of temporary installation path +* DONE (duckplyr) diff --git a/duckplyr.Rcheck/R_check_bin/R b/duckplyr.Rcheck/R_check_bin/R new file mode 100755 index 000000000..3e289bc21 --- /dev/null +++ b/duckplyr.Rcheck/R_check_bin/R @@ -0,0 +1,2 @@ +echo "'R' should not be used without a path -- see par. 1.6 of the manual" +exit 1 diff --git a/duckplyr.Rcheck/R_check_bin/Rscript b/duckplyr.Rcheck/R_check_bin/Rscript new file mode 100755 index 000000000..6fead7417 --- /dev/null +++ b/duckplyr.Rcheck/R_check_bin/Rscript @@ -0,0 +1,2 @@ +echo "'Rscript' should not be used without a path -- see par. 1.6 of the manual" +exit 1 diff --git a/duckplyr.Rcheck/duckplyr-Ex.R b/duckplyr.Rcheck/duckplyr-Ex.R new file mode 100644 index 000000000..f5d728972 --- /dev/null +++ b/duckplyr.Rcheck/duckplyr-Ex.R @@ -0,0 +1,1251 @@ +pkgname <- "duckplyr" +source(file.path(R.home("share"), "R", "examples-header.R")) +options(warn = 1) +base::assign(".ExTimings", "duckplyr-Ex.timings", pos = 'CheckExEnv') +base::cat("name\tuser\tsystem\telapsed\n", file=base::get(".ExTimings", pos = 'CheckExEnv')) +base::assign(".format_ptime", +function(x) { + if(!is.na(x[4L])) x[1L] <- x[1L] + x[4L] + if(!is.na(x[5L])) x[2L] <- x[2L] + x[5L] + options(OutDec = '.') + format(x[1L:3L], digits = 7L) +}, +pos = 'CheckExEnv') + +### * +library('duckplyr') + +base::assign(".oldSearch", base::search(), pos = 'CheckExEnv') +base::assign(".old_wd", base::getwd(), pos = 'CheckExEnv') +cleanEx() +nameEx("anti_join.duckplyr_df") +### * anti_join.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: anti_join.duckplyr_df +### Title: Anti join +### Aliases: anti_join.duckplyr_df + +### ** Examples + +library(duckplyr) +band_members %>% anti_join(band_instruments) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("anti_join.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("arrange.duckplyr_df") +### * arrange.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: arrange.duckplyr_df +### Title: Order rows using column values +### Aliases: arrange.duckplyr_df + +### ** Examples + +library(duckplyr) +arrange(mtcars, cyl, disp) +arrange(mtcars, desc(disp)) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("arrange.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("as_duckplyr_df") +### * as_duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: as_duckplyr_df +### Title: Convert to a duckplyr data frame +### Aliases: as_duckplyr_df as_duckplyr_tibble +### Keywords: internal + +### ** Examples + +tibble(a = 1:3) %>% + mutate(b = a + 1) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("as_duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("as_tbl") +### * as_tbl + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: as_tbl +### Title: Convert a duckplyr frame to a dbplyr table +### Aliases: as_tbl + +### ** Examples + +## Don't show: +if (requireNamespace("dbplyr", quietly = TRUE)) withAutoprint({ # examplesIf +## End(Don't show) +df <- duckdb_tibble(a = 1L) +df + +tbl <- as_tbl(df) +tbl + +tbl %>% + mutate(b = sql("a + 1")) %>% + as_duckdb_tibble() +## Don't show: +}) # examplesIf +## End(Don't show) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("as_tbl", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("collect.duckplyr_df") +### * collect.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: collect.duckplyr_df +### Title: Force conversion to a data frame +### Aliases: collect.duckplyr_df + +### ** Examples + +library(duckplyr) +df <- duckdb_tibble(x = c(1, 2), .lazy = TRUE) +df +try(print(df$x)) +df <- collect(df) +df + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("collect.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("compute.duckplyr_df") +### * compute.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: compute.duckplyr_df +### Title: Compute results +### Aliases: compute.duckplyr_df + +### ** Examples + +library(duckplyr) +df <- duckdb_tibble(x = c(1, 2)) +df <- mutate(df, y = 2) +explain(df) +df <- compute(df) +explain(df) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("compute.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("compute_csv") +### * compute_csv + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: compute_csv +### Title: Compute results to a CSV file +### Aliases: compute_csv + +### ** Examples + +library(duckplyr) +df <- data.frame(x = c(1, 2)) +df <- mutate(df, y = 2) +path <- tempfile(fileext = ".csv") +df <- compute_csv(df, path) +readLines(path) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("compute_csv", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("compute_parquet") +### * compute_parquet + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: compute_parquet +### Title: Compute results to a Parquet file +### Aliases: compute_parquet + +### ** Examples + +library(duckplyr) +df <- data.frame(x = c(1, 2)) +df <- mutate(df, y = 2) +path <- tempfile(fileext = ".parquet") +df <- compute_parquet(df, path) +explain(df) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("compute_parquet", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("config") +### * config + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: config +### Title: Configuration options +### Aliases: config + +### ** Examples + +# Sys.setenv(DUCKPLYR_OUTPUT_ORDER = TRUE) +data.frame(a = 3:1) %>% + as_duckdb_tibble() %>% + inner_join(data.frame(a = 1:4), by = "a") + +withr::with_envvar(c(DUCKPLYR_OUTPUT_ORDER = "TRUE"), { + data.frame(a = 3:1) %>% + as_duckdb_tibble() %>% + inner_join(data.frame(a = 1:4), by = "a") +}) + +# Sys.setenv(DUCKPLYR_FORCE = TRUE) +add_one <- function(x) { + x + 1 +} + +data.frame(a = 3:1) %>% + as_duckdb_tibble() %>% + mutate(b = add_one(a)) + +try(withr::with_envvar(c(DUCKPLYR_FORCE = "TRUE"), { + data.frame(a = 3:1) %>% + as_duckdb_tibble() %>% + mutate(b = add_one(a)) +})) + +# Sys.setenv(DUCKPLYR_FALLBACK_INFO = TRUE) +withr::with_envvar(c(DUCKPLYR_FALLBACK_INFO = "TRUE"), { + data.frame(a = 3:1) %>% + as_duckdb_tibble() %>% + mutate(b = add_one(a)) +}) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("config", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("count.duckplyr_df") +### * count.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: count.duckplyr_df +### Title: Count the observations in each group +### Aliases: count.duckplyr_df + +### ** Examples + +library(duckplyr) +count(mtcars, am) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("count.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("db_exec") +### * db_exec + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: db_exec +### Title: Execute a statement for the default connection +### Aliases: db_exec + +### ** Examples + +db_exec("SET threads TO 2") + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("db_exec", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("df_from_file") +### * df_from_file + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: df_from_file +### Title: Read Parquet, CSV, and other files using DuckDB +### Aliases: df_from_file duckplyr_df_from_file df_from_csv +### duckplyr_df_from_csv df_from_parquet duckplyr_df_from_parquet +### df_to_parquet +### Keywords: internal + +### ** Examples + +# Create simple CSV file +path <- tempfile("duckplyr_test_", fileext = ".csv") +write.csv(data.frame(a = 1:3, b = letters[4:6]), path, row.names = FALSE) + +# Reading is immediate +df <- df_from_csv(path) + +# Materialization only upon access +names(df) +df$a + +# Return as tibble, specify column types: +df_from_file( + path, + "read_csv", + options = list(delim = ",", types = list(c("DOUBLE", "VARCHAR"))), + class = class(tibble()) +) + +# Read multiple file at once +path2 <- tempfile("duckplyr_test_", fileext = ".csv") +write.csv(data.frame(a = 4:6, b = letters[7:9]), path2, row.names = FALSE) + +duckplyr_df_from_csv(file.path(tempdir(), "duckplyr_test_*.csv")) + +unlink(c(path, path2)) + +# Write a Parquet file: +path_parquet <- tempfile(fileext = ".parquet") +df_to_parquet(df, path_parquet) + +# With a duckplyr_df, the materialization occurs outside of R: +df %>% + as_duckplyr_df() %>% + mutate(b = a + 1) %>% + df_to_parquet(path_parquet) + +duckplyr_df_from_parquet(path_parquet) + +unlink(path_parquet) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("df_from_file", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("distinct.duckplyr_df") +### * distinct.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: distinct.duckplyr_df +### Title: Keep distinct/unique rows +### Aliases: distinct.duckplyr_df + +### ** Examples + +df <- duckdb_tibble( + x = sample(10, 100, rep = TRUE), + y = sample(10, 100, rep = TRUE) +) +nrow(df) +nrow(distinct(df)) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("distinct.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("duckdb_tibble") +### * duckdb_tibble + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: duckdb_tibble +### Title: duckplyr data frames +### Aliases: duckdb_tibble as_duckdb_tibble is_duckdb_tibble + +### ** Examples + +x <- duckdb_tibble(a = 1) +x + +library(dplyr) +x %>% + mutate(b = 2) + +x$a + +y <- duckdb_tibble(a = 1, .prudence = "stingy") +y +try(length(y$a)) +length(collect(y)$a) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("duckdb_tibble", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("duckplyr_execute") +### * duckplyr_execute + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: duckplyr_execute +### Title: Execute a statement for the default connection +### Aliases: duckplyr_execute +### Keywords: internal + +### ** Examples + +duckplyr_execute("SET threads TO 2") + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("duckplyr_execute", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("explain.duckplyr_df") +### * explain.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: explain.duckplyr_df +### Title: Explain details of a tbl +### Aliases: explain.duckplyr_df + +### ** Examples + +library(duckplyr) +df <- duckdb_tibble(x = c(1, 2)) +df <- mutate(df, y = 2) +explain(df) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("explain.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("fallback") +### * fallback + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: fallback +### Title: Fallback to dplyr +### Aliases: fallback fallback_sitrep fallback_config fallback_review +### fallback_upload fallback_purge + +### ** Examples + +fallback_sitrep() + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("fallback", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("filter.duckplyr_df") +### * filter.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: filter.duckplyr_df +### Title: Keep rows that match a condition +### Aliases: filter.duckplyr_df + +### ** Examples + +df <- duckdb_tibble(x = 1:3, y = 3:1) +filter(df, x >= 2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("filter.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("flights_df") +### * flights_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: flights_df +### Title: Flight data +### Aliases: flights_df + +### ** Examples + +## Don't show: +if (requireNamespace("nycflights13", quietly = TRUE)) withAutoprint({ # examplesIf +## End(Don't show) +flights_df() +## Don't show: +}) # examplesIf +## End(Don't show) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("flights_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("full_join.duckplyr_df") +### * full_join.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: full_join.duckplyr_df +### Title: Full join +### Aliases: full_join.duckplyr_df + +### ** Examples + +library(duckplyr) +full_join(band_members, band_instruments) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("full_join.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("head.duckplyr_df") +### * head.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: head.duckplyr_df +### Title: Return the First Parts of an Object +### Aliases: head.duckplyr_df + +### ** Examples + +head(mtcars, 2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("head.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("inner_join.duckplyr_df") +### * inner_join.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: inner_join.duckplyr_df +### Title: Inner join +### Aliases: inner_join.duckplyr_df + +### ** Examples + +library(duckplyr) +inner_join(band_members, band_instruments) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("inner_join.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("intersect.duckplyr_df") +### * intersect.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: intersect.duckplyr_df +### Title: Intersect +### Aliases: intersect.duckplyr_df + +### ** Examples + +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +intersect(df1, df2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("intersect.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("is_duckplyr_df") +### * is_duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: is_duckplyr_df +### Title: Class predicate for duckplyr data frames +### Aliases: is_duckplyr_df +### Keywords: internal + +### ** Examples + +tibble(a = 1:3) %>% + is_duckplyr_df() + +tibble(a = 1:3) %>% + as_duckplyr_df() %>% + is_duckplyr_df() + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("is_duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("left_join.duckplyr_df") +### * left_join.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: left_join.duckplyr_df +### Title: Left join +### Aliases: left_join.duckplyr_df + +### ** Examples + +library(duckplyr) +left_join(band_members, band_instruments) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("left_join.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("methods_overwrite") +### * methods_overwrite + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: methods_overwrite +### Title: Forward all dplyr methods to duckplyr +### Aliases: methods_overwrite methods_restore + +### ** Examples + +tibble(a = 1:3) %>% + mutate(b = a + 1) + +methods_overwrite() + +tibble(a = 1:3) %>% + mutate(b = a + 1) + +methods_restore() + +tibble(a = 1:3) %>% + mutate(b = a + 1) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("methods_overwrite", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("mutate.duckplyr_df") +### * mutate.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: mutate.duckplyr_df +### Title: Create, modify, and delete columns +### Aliases: mutate.duckplyr_df + +### ** Examples + +library(duckplyr) +df <- data.frame(x = c(1, 2)) +df <- mutate(df, y = 2) +df + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("mutate.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("new_relational") +### * new_relational + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: new_relational +### Title: Relational implementer's interface +### Aliases: new_relational rel_to_df rel_filter rel_project rel_aggregate +### rel_order rel_join rel_limit rel_distinct rel_set_intersect +### rel_set_diff rel_set_symdiff rel_union_all rel_explain rel_alias +### rel_set_alias rel_names + +### ** Examples + +new_dfrel <- function(x) { + stopifnot(is.data.frame(x)) + new_relational(list(x), class = "dfrel") +} +mtcars_rel <- new_dfrel(mtcars[1:5, 1:4]) + +rel_to_df.dfrel <- function(rel, ...) { + unclass(rel)[[1]] +} +rel_to_df(mtcars_rel) + +rel_filter.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the predicates defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, nrow(df))), ]) +} + +rel_filter( + mtcars_rel, + list( + relexpr_function( + "gt", + list(relexpr_reference("cyl"), relexpr_constant("6")) + ) + ) +) + +rel_project.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[seq_len(min(3, ncol(df)))]) +} + +rel_project( + mtcars_rel, + list(relexpr_reference("cyl"), relexpr_reference("disp")) +) + +rel_order.dfrel <- function(rel, exprs, ...) { + df <- unclass(rel)[[1]] + + # A real implementation would evaluate the expressions defined + # by the exprs argument + new_dfrel(df[order(df[[1]]), ]) +} + +rel_order( + mtcars_rel, + list(relexpr_reference("mpg")) +) +## Don't show: +if (requireNamespace("dplyr", quietly = TRUE)) withAutoprint({ # examplesIf +## End(Don't show) +rel_join.dfrel <- function(left, right, conds, join, ...) { + left_df <- unclass(left)[[1]] + right_df <- unclass(right)[[1]] + + # A real implementation would evaluate the expressions + # defined by the conds argument, + # use different join types based on the join argument, + # and implement the join itself instead of relaying to left_join(). + new_dfrel(dplyr::left_join(left_df, right_df)) +} + +rel_join(new_dfrel(data.frame(mpg = 21)), mtcars_rel) +## Don't show: +}) # examplesIf +## End(Don't show) + +rel_limit.dfrel <- function(rel, n, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[seq_len(n), ]) +} + +rel_limit(mtcars_rel, 3) + +rel_distinct.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + new_dfrel(df[!duplicated(df), ]) +} + +rel_distinct(new_dfrel(mtcars[1:3, 1:4])) + +rel_names.dfrel <- function(rel, ...) { + df <- unclass(rel)[[1]] + + names(df) +} + +rel_names(mtcars_rel) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("new_relational", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("new_relexpr") +### * new_relexpr + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: new_relexpr +### Title: Relational expressions +### Aliases: new_relexpr relexpr_reference relexpr_constant +### relexpr_function relexpr_comparison relexpr_window relexpr_set_alias + +### ** Examples + +relexpr_set_alias( + alias = "my_predicate", + relexpr_function( + "<", + list( + relexpr_reference("my_number"), + relexpr_constant(42) + ) + ) +) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("new_relexpr", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("pull.duckplyr_df") +### * pull.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: pull.duckplyr_df +### Title: Extract a single column +### Aliases: pull.duckplyr_df + +### ** Examples + +library(duckplyr) +pull(mtcars, cyl) +pull(mtcars, 1) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("pull.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("read_csv_duckdb") +### * read_csv_duckdb + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: read_csv_duckdb +### Title: Read CSV files using DuckDB +### Aliases: read_csv_duckdb + +### ** Examples + +# Create simple CSV file +path <- tempfile("duckplyr_test_", fileext = ".csv") +write.csv(data.frame(a = 1:3, b = letters[4:6]), path, row.names = FALSE) + +# Reading is immediate +df <- read_csv_duckdb(path) + +# Names are always available +names(df) + +# Materialization upon access is turned off by default +try(print(df$a)) + +# Materialize explicitly +collect(df)$a + +# Automatic materialization with prudence = "lavish" +df <- read_csv_duckdb(path, prudence = "lavish") +df$a + +# Specify column types +read_csv_duckdb( + path, + options = list(delim = ",", types = list(c("DOUBLE", "VARCHAR"))) +) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("read_csv_duckdb", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("read_json_duckdb") +### * read_json_duckdb + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: read_json_duckdb +### Title: Read JSON files using DuckDB +### Aliases: read_json_duckdb + +### ** Examples + +## Don't show: +if (identical(Sys.getenv("IN_PKGDOWN"), "TRUE")) withAutoprint({ # examplesIf +## End(Don't show) + +# Create and read a simple JSON file +path <- tempfile("duckplyr_test_", fileext = ".json") +writeLines('[{"a": 1, "b": "x"}, {"a": 2, "b": "y"}]', path) + +# Reading needs the json extension +db_exec("INSTALL json") +db_exec("LOAD json") +read_json_duckdb(path) +## Don't show: +}) # examplesIf +## End(Don't show) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("read_json_duckdb", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("read_sql_duckdb") +### * read_sql_duckdb + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: read_sql_duckdb +### Title: Return SQL query as duckdb_tibble +### Aliases: read_sql_duckdb + +### ** Examples + +## Don't show: +if (getRversion() >= "4.3") withAutoprint({ # examplesIf +## End(Don't show) +read_sql_duckdb("FROM duckdb_settings()") +## Don't show: +}) # examplesIf +## End(Don't show) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("read_sql_duckdb", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("relocate.duckplyr_df") +### * relocate.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: relocate.duckplyr_df +### Title: Change column order +### Aliases: relocate.duckplyr_df + +### ** Examples + +df <- duckdb_tibble(a = 1, b = 1, c = 1, d = "a", e = "a", f = "a") +relocate(df, f) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("relocate.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("rename.duckplyr_df") +### * rename.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: rename.duckplyr_df +### Title: Rename columns +### Aliases: rename.duckplyr_df + +### ** Examples + +library(duckplyr) +rename(mtcars, thing = mpg) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("rename.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("right_join.duckplyr_df") +### * right_join.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: right_join.duckplyr_df +### Title: Right join +### Aliases: right_join.duckplyr_df + +### ** Examples + +library(duckplyr) +right_join(band_members, band_instruments) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("right_join.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("select.duckplyr_df") +### * select.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: select.duckplyr_df +### Title: Keep or drop columns using their names and types +### Aliases: select.duckplyr_df + +### ** Examples + +library(duckplyr) +select(mtcars, mpg) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("select.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("semi_join.duckplyr_df") +### * semi_join.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: semi_join.duckplyr_df +### Title: Semi join +### Aliases: semi_join.duckplyr_df + +### ** Examples + +library(duckplyr) +band_members %>% semi_join(band_instruments) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("semi_join.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("setdiff.duckplyr_df") +### * setdiff.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: setdiff.duckplyr_df +### Title: Set difference +### Aliases: setdiff.duckplyr_df + +### ** Examples + +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +setdiff(df1, df2) +setdiff(df2, df1) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("setdiff.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("slice_head.duckplyr_df") +### * slice_head.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: slice_head.duckplyr_df +### Title: Subset rows using their positions +### Aliases: slice_head.duckplyr_df + +### ** Examples + +library(duckplyr) +df <- data.frame(x = 1:3) +df <- slice_head(df, n = 2) +df + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("slice_head.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("stats_show") +### * stats_show + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: stats_show +### Title: Show stats +### Aliases: stats_show + +### ** Examples + +stats_show() + +tibble(a = 1:3) %>% + as_duckplyr_tibble() %>% + mutate(b = a + 1) + +stats_show() + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("stats_show", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("summarise.duckplyr_df") +### * summarise.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: summarise.duckplyr_df +### Title: Summarise each group down to one row +### Aliases: summarise.duckplyr_df + +### ** Examples + +library(duckplyr) +summarise(mtcars, mean = mean(disp), n = n()) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("summarise.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("symdiff.duckplyr_df") +### * symdiff.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: symdiff.duckplyr_df +### Title: Symmetric difference +### Aliases: symdiff.duckplyr_df + +### ** Examples + +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +symdiff(df1, df2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("symdiff.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("transmute.duckplyr_df") +### * transmute.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: transmute.duckplyr_df +### Title: Create, modify, and delete columns +### Aliases: transmute.duckplyr_df + +### ** Examples + +library(duckplyr) +transmute(mtcars, mpg2 = mpg*2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("transmute.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("union.duckplyr_df") +### * union.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: union.duckplyr_df +### Title: Union +### Aliases: union.duckplyr_df + +### ** Examples + +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +union(df1, df2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("union.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +cleanEx() +nameEx("union_all.duckplyr_df") +### * union_all.duckplyr_df + +flush(stderr()); flush(stdout()) + +base::assign(".ptime", proc.time(), pos = "CheckExEnv") +### Name: union_all.duckplyr_df +### Title: Union of all +### Aliases: union_all.duckplyr_df + +### ** Examples + +df1 <- duckdb_tibble(x = 1:3) +df2 <- duckdb_tibble(x = 3:5) +union_all(df1, df2) + + + +base::assign(".dptime", (proc.time() - get(".ptime", pos = "CheckExEnv")), pos = "CheckExEnv") +base::cat("union_all.duckplyr_df", base::get(".format_ptime", pos = 'CheckExEnv')(get(".dptime", pos = "CheckExEnv")), "\n", file=base::get(".ExTimings", pos = 'CheckExEnv'), append=TRUE, sep="\t") +### *