Yaal

Yaal compiles a SQL string into dialect SQL and ordered bind values. You write the full statement, mark the parts that depend on arguments, and pass a JSON object. Yaal removes the unused parts and returns the SQL plus the binds. It does not open a database, shape rows, or track entities.

--(id, active)--
select *
from users u
where optional(u.id = {{id}})
  and optional(u.active = {{active}})

{"id": 1} compiles to select * from users u where (u.id = ?) with one bind. An omitted or null argument drops its optional(...) block. A list expands only where the placeholder is written with ...:

--(id)--
where optional(u.user_id in ({{id}}...))

{"id": [1, 2]} becomes in (?, ?) and binds [1, 2].

License: MIT.

Library

let params = serde_json::json!({"id": [1, 2]}).as_object().unwrap().clone();
let compiled = yaal::compile(sql, &params, &yaal::CompileOptions::default())?;

CompileOptions.placeholder is ? or $1, $2, …. --sql-- splits the input into separate statements that share the same parameters. Each statement numbers its own $1.

The result is JSON:

{
  "statements": [
    {
      "sql": "select * from users u where (u.user_id in (?, ?))",
      "parameters": [
        {"name": "id", "type": "integer", "value": 1},
        {"name": "id", "type": "integer", "value": 2}
      ]
    }
  ]
}

CLI

cargo run -p yaal-cli -- compile --sql query.sql --params params.json
cargo run -p yaal-cli -- compile --sql query.sql --param id=1 --placeholder numbered

SQL is read from stdin when --sql is omitted and stdin is not a terminal. yaal compile with no SQL exits instead of waiting.

Bindings

cargo build -p yaal-ffi produces the shared library. Set YAAL_LIB to that file (libyaal_ffi.so, libyaal_ffi.dylib, or yaal_ffi.dll). The C functions are yaal_compile and yaal_free (bindings/yaal.h). Each binding returns the same JSON: statements of sql plus parameters (name, type, value). A compiler failure raises the language's compile error.

import yaal
compiled = yaal.compile(sql, {"id": 1}, placeholder="question")

pip install yaal-sql installs that module with the shared library inside the wheel. The PyPI project is yaal-sql because the name yaal is already taken.

var compiled = Yaal.Compiler.Compile(sql, new { id = 1 }, "question");

The NuGet package targets .NET Standard 2.0, so .NET Framework 4.6.1, .NET Core 2.0, and later can reference it. The package includes the native compiler for Linux x86_64, macOS Apple silicon, and Windows x64.

import "github.com/kirubasankars/yaal/bindings/go"

compiled, err := yaal.Compile(sql, map[string]any{"id": 1}, "question")

go get github.com/kirubasankars/yaal/bindings/go@v1.1.0 installs that module. The module tag is bindings/go/v1.1.0, and the module contains the native compiler for Linux x86_64, macOS Apple silicon, and Windows x64. Compile loads YAAL_LIB when that variable is set, then the embedded library, then the shared library built under target/debug or target/release.

Yaal.Compiled compiled = Yaal.compile(sql, Map.of("id", 1), "question");

Java calls the library with the foreign function API, so the program needs --enable-native-access=ALL-UNNAMED. The Maven coordinate is io.github.kirubasankars:yaal. That jar contains the native compiler for Linux x86_64, macOS Apple silicon, and Windows x64.

<dependency>
  <groupId>io.github.kirubasankars</groupId>
  <artifactId>yaal</artifactId>
  <version>1.1.0</version>
</dependency>

Syntax

Build

make test
make ci
make docs

make ci is what GitHub Actions runs: the Rust tests, the language bindings, and cargo fmt --check.

make docs serves this README and the syntax pages at http://127.0.0.1:8080. Set DOCS_PORT to use another port. A push to main publishes the same pages to https://kirubasankars.github.io/yaal/.

make test runs the Rust tests, then the Python, Go, C#, and Java bindings against libyaal_ffi. The Java step needs a JDK.