--- title: "Extending qryflow Functionality" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Extending qryflow Functionality} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r} library(qryflow) ``` # Overview `qryflow` is designed to be easily extended, allowing users to define custom chunk types. This vignette provides relevant background knowledge on how `qryflow` works under the hood, then walks through how to create and register custom chunk types. This vignette assumes the knowledge found in the "Getting Started" (`vignette("getting-started", package = "qryflow")`) vignette. # Big Picture: How `qryflow` Works When you run a SQL script using `qryflow`, the process follows these steps: 1. Split the SQL script into chunks using tagged comments (e.g., `-- @query: name`) 2. Parse each chunk, capturing type, name, and other tags 4. Execute each chunk using a **type-specific handler** To support a new chunk type, you'll need to: - **Create a handler** — which defines how to execute the chunk and return results. - **Register** your new type with `qryflow` so the package knows how to process it. # Creating Handlers Each chunk type needs to have an associated handler. Handlers accept both a `qryflow_chunk` object and a database connection object (e.g., `DBI::dbConnect`). They should execute the SQL as appropriate and then return the result: This is the handler for the "exec" type: ```r qryflow_exec_handler <- function(con, chunk, ...) { # Pass the SQL of the chunk to desired execution strategy result <- DBI::dbExecute(con, chunk$sql, ...) # Return the result result } ``` After a custom handler has been created, it needs to be validated and registered. ## Validate the Handler `qryflow` provides `validate_qryflow_handler()` to test whether the handler function meets specifications. An error will occur if: - The object is not a function - The formal arguments are not included - The formal arguments are not in the right order ```r validate_qryflow_handler(qryflow_exec_handler) ``` Note: This does not test that the code within your function is correct nor does it verify the correct output type. # How the Registry Works `qryflow` maintains an internal environment called `.qryflow_handlers` to store registered chunk handlers. When the package is loaded, default types like "`query`" and "`exec`" are automatically registered. You can register additional types using: ```r register_qryflow_type("custom", my_custom_handler_func, overwrite = TRUE) ``` This will validate the handler before registering in the internal environment. We can access what types are registered: ```r ls_qryflow_types() ``` Custom types must be re-registered each session. To make them persistent, add registration calls to your `.Rprofile`, or create a small package with an `.onLoad()` hook. # Toy Example: Create `query-send` Chunk Type This example shows how to implement a new chunk type that's similar to `exec` and `query`. We will create a new type, called `query-send` that works like `query` except calls `DBI::dbSendQuery` instead of `DBI::dbGetQuery`. First, create the handler: ```{r} query_send_handler <- function(con, chunk, ...) { res <- DBI::dbSendQuery(con, chunk$sql, ...) results <- DBI::dbFetch(res) DBI::dbClearResult(res) results } ``` Validate it by hand, if you'd like: ```{r} validate_qryflow_handler(query_send_handler) ``` Then, register it: ```{r} register_qryflow_type( "query-send", handler = query_send_handler, overwrite = TRUE ) ``` Check that it registered properly: ```{r} ls_qryflow_types() ``` And test it out on some SQL: ```{r} # Creates an in-memory sqlite database and populates it with an mtcars table, named "mtcars" con <- example_db_connect(mtcars) # Create sql <- " -- @query-send: df_mtcars SELECT * FROM mtcars; " results <- qryflow(con, sql) head(results) ```