Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust

ButtercupQL is written in Rust, and the buttercupql crate is the compiler. It has no dependencies.

[dependencies]
buttercupql = { git = "https://github.com/mkusiappiah/ButtercupQL" }

Compiling

compile() takes the text and the options: the database, and optionally a schema, a loader for modules, a style of placeholder and a query to compile in place of the main one.

use buttercupql::{Dialect, Options, Value};

let text = "from cases\nwhere status == \"open\"\nsort -risk\ntake 20\n";
let compiled = buttercupql::compile(text, &Options::new(Dialect::Postgres)).unwrap();
assert_eq!(
    compiled.sql,
    "SELECT *\nFROM cases\nWHERE status = $1\nORDER BY risk DESC NULLS LAST\nLIMIT 20"
);
assert_eq!(compiled.values().unwrap(), [Value::Text("open".to_string())]);

On failure, compile() returns every problem found. Diagnostic::render() shows one as bcql does, under its line:

use buttercupql::{Dialect, Options, SourceFile};

let text = "from cases\nwhere status = \"open\"\n";
let problems = buttercupql::compile(text, &Options::new(Dialect::Sqlite)).unwrap_err();
let shown = problems[0].render(&SourceFile::new("open.bcql", text), false);
assert!(shown.starts_with("error: use `==` to compare\n  ┌─ open.bcql:2:14\n"));

Options

use buttercupql::{Dialect, FileLoader, Options, ParamStyle, Schema};

let schema = Schema::from_sql("CREATE TABLE cases (key text NOT NULL, risk integer);").unwrap();
let loader = FileLoader::new("queries");
let options = Options::new(Dialect::DuckDb)
    .with_schema(&schema)                  // check names, and use what the tables say
    .with_name("queries/report.bcql")      // the name messages show, and where imports are found from
    .with_loader(&loader)                  // where modules come from
    .with_params(ParamStyle::Colon)        // `:p1` placeholders
    .with_query("for_operator");           // a named query or a query `def`

Queries with arguments

A query def compiled with with_query() keeps its parameters as placeholders, and bind() gives the values, in order, with defaults for those not given:

use buttercupql::{Dialect, Options, Value};

let text = "def for_account(account: str, rows: int = 10):\n    from cases\n    where account_id == account\n    take rows\n";
let compiled = buttercupql::compile(text, &Options::new(Dialect::Postgres).with_query("for_account")).unwrap();
assert_eq!(compiled.sql, "SELECT *\nFROM cases\nWHERE account_id = $1\nLIMIT $2");
let values = compiled.bind(&[("account", Value::Text("A1".to_string()))]).unwrap();
assert_eq!(values, [Value::Text("A1".to_string()), Value::Int(10)]);

Reading without compiling

parse() reads a text into its syntax tree, buttercupql::ast, with every problem found, as editors do while you type. buttercupql::builtins describes every step, function and method.

The crate’s own documentation, with cargo doc -p buttercupql --open, describes each of these.