## ----include = FALSE---------------------------------------------------------- # Evaluate chunks only where the ducklake DuckDB extension is already # installed. The probe never downloads anything, so building this vignette # needs no network access. ducklake_available <- ducklake::ducklake_extension_available() knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = ducklake_available ) # Use a unique temp directory for this vignette to avoid conflicts during R CMD check vignette_temp_dir <- file.path(tempdir(), "ducklake_vignette") dir.create(vignette_temp_dir, showWarnings = FALSE, recursive = TRUE) knitr::opts_knit$set(root.dir = vignette_temp_dir) ## ----setup, message=FALSE----------------------------------------------------- library(ducklake) library(dplyr) ## ----create, message=FALSE---------------------------------------------------- # Create a data lake in a specific directory attach_ducklake("my_lake", lake_path = vignette_temp_dir) ## ----attach, eval=FALSE------------------------------------------------------- # # Attach to an existing lake (creates it if it doesn't exist) # attach_ducklake("existing_lake", lake_path = "/path/to/data_lake") ## ----attach-postgres, eval=FALSE---------------------------------------------- # # PostgreSQL catalog for multi-client access # attach_ducklake( # "shared_lake", # backend = "postgres", # catalog_connection_string = "dbname=ducklake_catalog host=localhost", # lake_path = "/shared/lake/data/" # ) # # # SQLite catalog for lightweight local multi-client setups # attach_ducklake( # "team_lake", # backend = "sqlite", # catalog_connection_string = "metadata.sqlite", # lake_path = "data_files/" # ) ## ----detach, eval=FALSE------------------------------------------------------- # # Detach when done (doesn't delete the lake) # detach_ducklake("my_lake") ## ----load_df------------------------------------------------------------------ with_transaction( create_table(mtcars, "cars"), author = "Data Engineer", commit_message = "Initial car data load" ) ## ----update_cars-------------------------------------------------------------- # Create a second version of the cars table with_transaction( get_ducklake_table("cars") |> mutate(kpl = mpg * 0.425144) |> # Add km/L conversion replace_table("cars"), author = "Data Engineer", commit_message = "Add km/L metric to cars table" ) ## ----load_csv----------------------------------------------------------------- # First write a sample CSV (in practice, you'd have an existing file) csv_path <- file.path(vignette_temp_dir, "sample_data.csv") write.csv(head(iris, 20), csv_path, row.names = FALSE) # Load the CSV into the data lake with_transaction( create_table(csv_path, "iris_sample"), author = "Data Engineer", commit_message = "Load iris sample from CSV" ) ## ----load_url, eval=FALSE----------------------------------------------------- # # ducklake can load data directly from URLs # with_transaction( # create_table("https://example.com/data.csv", "remote_data"), # author = "Data Engineer", # commit_message = "Load remote dataset" # ) ## ----add_data_files, eval=FALSE----------------------------------------------- # add_data_files( # "readings", # c("extracts/jan.parquet", "extracts/feb.parquet"), # create = TRUE # ) # # # See which files back a table # list_ducklake_files("readings") ## ----load_pipeline------------------------------------------------------------ with_transaction( mtcars |> filter(mpg > 20) |> create_table("efficient_cars"), author = "Data Analyst", commit_message = "Load filtered car data" ) ## ----list_all_tables---------------------------------------------------------- # Every table and view, with its schema and type list_ducklake_tables() ## ----create_view-------------------------------------------------------------- get_ducklake_table("cars") |> filter(mpg > 25) |> create_view("v_efficient_cars") get_ducklake_table("v_efficient_cars") |> collect() ## ----drop_view---------------------------------------------------------------- drop_view("v_efficient_cars") ## ----comments----------------------------------------------------------------- set_table_comment("cars", "Motor Trend road tests of 1973-74 models") set_column_comments( "cars", mpg = "Miles per US gallon", wt = "Weight (1000 lbs)" ) get_table_comments("cars") ## ----labels------------------------------------------------------------------- df_visits <- data.frame(subject = c("S1", "S2"), sbp = c(128, 141)) attr(df_visits$subject, "label") <- "Subject identifier" attr(df_visits$sbp, "label") <- "Systolic blood pressure (mmHg)" create_table(df_visits, "visits") collected <- get_ducklake_table("visits") |> collect() attr(collected$sbp, "label") ## ----read_table--------------------------------------------------------------- # Returns a lazy dplyr tbl cars_data <- get_ducklake_table("cars") # Use dplyr verbs cars_data |> filter(cyl == 6) |> select(mpg, cyl, hp) |> head(3) ## ----collect------------------------------------------------------------------ # Fetch all data into a data.frame cars_df <- get_ducklake_table("cars") |> collect() head(cars_df, 3) ## ----view_versions------------------------------------------------------------ # See all snapshots for the cars table list_table_snapshots("cars") ## ----read_version------------------------------------------------------------- # Query data as it existed at snapshot 1 -- before the kpl column was added get_ducklake_table_version("cars", version = 1) |> select(mpg, cyl, hp) |> head(3) ## ----read_timestamp, eval=FALSE----------------------------------------------- # # Query data as of a specific time (see list_table_snapshots() for times) # get_ducklake_table_asof("cars", timestamp = "2024-01-15 10:30:00") |> # collect() ## ----replace_table------------------------------------------------------------ with_transaction( get_ducklake_table("cars") |> mutate(hp_per_cyl = hp / as.numeric(cyl)) |> # Add derived metric replace_table("cars"), author = "Data Engineer", commit_message = "Add horsepower per cylinder metric" ) ## ----list_snapshots----------------------------------------------------------- list_table_snapshots() ## ----list_table_snapshots, eval=FALSE----------------------------------------- # list_table_snapshots("cars") ## ----restore------------------------------------------------------------------ # Roll cars back to snapshot 1. The restore is recorded as a new snapshot, # so nothing is lost -- you can still time-travel to any version. restore_table_version( "cars", version = 1, author = "Data Engineer" ) list_table_snapshots("cars") ## ----simple_transaction, eval=FALSE------------------------------------------- # with_transaction( # create_table(my_data, "my_table"), # author = "Your Name", # commit_message = "What changed and why" # ) ## ----multi_step, eval=FALSE--------------------------------------------------- # with_transaction({ # # All these operations happen atomically # create_table(raw_data, "raw_table") # # cleaned <- get_ducklake_table("raw_table") |> # filter(!is.na(key_field)) |> # create_table("clean_table") # # get_ducklake_table("clean_table") |> # mutate(derived_field = calculate_something(x)) |> # create_table("analysis_table") # }, # author = "Data Engineer", # commit_message = "Full ETL pipeline run" # ) ## ----manual_transaction, eval=FALSE------------------------------------------- # # For fine-grained control # begin_transaction() # # create_table(data1, "table1") # create_table(data2, "table2") # # # Commit or rollback # commit_transaction( # author = "Your Name", # commit_message = "Manual transaction commit" # ) # # # Or if something went wrong: # # rollback_transaction() ## ----show_query--------------------------------------------------------------- get_ducklake_table("cars") |> filter(mpg > 25) |> select(mpg, cyl, hp) |> show_query() ## ----show_ducklake_query------------------------------------------------------ get_ducklake_table("cars") |> mutate(mpg = round(mpg)) |> show_ducklake_query() ## ----filter_early------------------------------------------------------------- # Good: Filter before other operations get_ducklake_table("cars") |> filter(cyl == 6) |> mutate(kpl = mpg * 0.425144) |> head(3) ## ----select_columns----------------------------------------------------------- # Good: Select only needed columns get_ducklake_table("cars") |> select(mpg, cyl, hp) |> filter(mpg > 25) ## ----sorting_partitioning, eval=FALSE----------------------------------------- # # Sorting suits high-cardinality columns like timestamps or ids # set_table_sorting("events", "event_time") # # # Partitioning suits low-cardinality columns like year or region # set_table_partitioning("sales", c("year(order_date)", "region")) ## ----options, eval=FALSE------------------------------------------------------ # # Trade write speed for smaller files # set_ducklake_option("parquet_compression", "zstd") # # # Require a commit message on every snapshot -- useful for audit discipline # set_ducklake_option("require_commit_message", TRUE) # # get_ducklake_options() ## ----cleanup------------------------------------------------------------------ # Detach from the lake detach_ducklake("my_lake")