Parameters
FlyQL supports parameter placeholders — $name (named) and $1 (positional) — that can stand in for values anywhere a literal would normally appear. Parameters are resolved at runtime by calling bindParams() before generating SQL or evaluating the query in memory.
This is the safe way to template queries: you keep the query text static and bind concrete values later. There is no string concatenation, no escaping, no risk of injection.
Why Parameters
Section titled “Why Parameters”A typical use case is a tool or dashboard that runs the same query shape with different inputs:
status = $code and env = $env
from flyql import parse, bind_params
ast = parse("status = $code and env = $env")bind_params(ast.root, {"code": 200, "env": "prod"})The parameter values can be supplied from form fields, API requests, or any other runtime source — without ever rebuilding the query string.
Syntax
Section titled “Syntax”Named Parameters
Section titled “Named Parameters”A named parameter is $ followed by a letter or underscore, then any combination of letters, digits, and underscores:
status = $code
env = $my_env
user = $_internal
The parameter name does not include the $ prefix — $code references a parameter named code.
Positional Parameters
Section titled “Positional Parameters”A positional parameter is $ followed by digits only. Positional parameters are 1-indexed (like SQL $1):
a = $1 and b = $2
$0 is not allowed. $1abc (digits followed by letters) is not allowed.
Where Parameters Can Appear
Section titled “Where Parameters Can Appear”Parameters work in every position where a literal value is valid:
Comparison Values
Section titled “Comparison Values”status = $code
age >= $min_age
name != $banned
score > $1
has, like, ilike
Section titled “has, like, ilike”message has $needle
path like $pattern
title ilike $search
tags not has $excluded
IN-Lists
Section titled “IN-Lists”Parameters can be mixed with literal values inside in and not in lists:
status in [$x, $y]
env in [$current, 'staging', 'dev']
code not in [$banned, 401, 403]
Temporal Function Arguments
Section titled “Temporal Function Arguments”Parameters can replace duration values inside ago(), timezone arguments inside today() and startOf():
created > ago($duration)
date = today($tz)
created > startOf('day', $tz)
The bound value for an ago() parameter must be a duration string like "5m", "1h", or "7d".
Transformer Arguments
Section titled “Transformer Arguments”Parameters can appear inside transformer argument positions:
tags|split($sep) has $needle
bindParams()
Section titled “bindParams()”After parsing, call bindParams() to substitute parameter placeholders with concrete values. The function walks the AST in place and replaces every Parameter node it finds.
Python
Section titled “Python”from flyql import parse, bind_params
ast = parse("status = $code and env = $env")bind_params(ast.root, {"code": 200, "env": "prod"})JavaScript
Section titled “JavaScript”import { parse, bindParams } from 'flyql'
const ast = parse('status = $code and env = $env')bindParams(ast.root, { code: 200, env: 'prod' })import flyql "github.com/iamtelescope/flyql/golang"
ast, _ := flyql.Parse(`status = $code and env = $env`)err := flyql.BindParams(ast.Root, map[string]any{ "code": 200, "env": "prod",})| Language | Signature |
|---|---|
| Python | bind_params(node, params: dict) -> Node |
| JavaScript | bindParams(node, params: object): Node |
| Go | BindParams(node *Node, params map[string]any) error |
node— the rootNodereturned byparse().params— a dict/object/map mapping parameter names to concrete values. Named parameters use the name as the key (e.g.,"code"). Positional parameters use string keys of digits (e.g.,"1","2"). Mixed dicts are allowed.- Return value — Python and JS return the same
Node(mutated in place) for chaining. Go returns anerror.
Type Mapping
Section titled “Type Mapping”The bound value’s native type determines the resulting value_type in the AST:
| Native type | Resulting value_type |
|---|---|
string | "string" |
int (within int64 range) | "int" |
int (outside int64 range) | "bigint" |
float | "float" |
bool | "bool" |
null / None / nil | "null" |
The type is determined by the bound value’s runtime type, not by parsing the value as a string. Binding "200" produces a string, not an integer:
bind_params(parse("a = $x").root, {"x": "200"})# expression.value → "200"# expression.value_type → "string"
bind_params(parse("a = $x").root, {"x": 200})# expression.value → 200# expression.value_type → "int"Unsupported types (e.g., Decimal, BigInt, custom objects, undefined) raise an error:
unsupported parameter value type: DecimalStrict Validation
Section titled “Strict Validation”bindParams() is strict about both missing and extra parameters.
Missing Parameters
Section titled “Missing Parameters”If the query references a parameter that is not in the provided dict, an error is raised:
bind_params(parse("a = $x").root, {})# FlyqlError: unbound parameter: $xExtra Parameters
Section titled “Extra Parameters”If the dict contains a parameter that is not referenced in the query, an error is raised:
bind_params(parse("a = $x").root, {"x": 1, "y": 2})# FlyqlError: unused parameter: yThis is stricter than typical SQL drivers, which often defer parameter validation to query execution time. Catching these errors at bind time keeps mistakes visible.
Positional Parameters
Section titled “Positional Parameters”For positional parameters, every index from 1 to the maximum referenced index must be provided. Gaps are not allowed:
bind_params(parse("a = $1 and b = $3").root, {"1": 1, "3": 3})# FlyqlError: unbound parameter: $2In-Memory Matching
Section titled “In-Memory Matching”Parameters work the same way with the matcher. Bind first, then evaluate:
from flyql import parse, bind_paramsfrom flyql.matcher.evaluator import Evaluator
ast = parse("status = $code")bind_params(ast.root, {"code": 200})
result = Evaluator().evaluate(ast.root, {"status": 200})If you forget to call bind_params() before evaluating, the matcher raises a clear error:
unbound parameter '$code' — call bind_params() before evaluatingThe same guard exists in every SQL generator, so you cannot accidentally produce SQL with unresolved parameters.
AST Representation
Section titled “AST Representation”A parameter is represented in the AST as an Expression with value_type: "parameter" and a Parameter value object:
Query: status = $code
Node (leaf) expression: key: { segments: ["status"], raw: "status" } operator: "=" value: { name: "code", positional: false } value_type: "parameter"For parameters in IN-lists, the Parameter object appears inside expression.values:
Query: status in [$x, 'ok']
Node (leaf) expression: key: { segments: ["status"], raw: "status" } operator: "in" values: [{ name: "x", positional: false }, "ok"] values_types: ["parameter", "string"]For parameters in temporal functions, they appear in FunctionCall.parameter_args:
Query: created > ago($d)
Node (leaf) expression: key: { segments: ["created"], raw: "created" } operator: ">" value: name: "ago" duration_args: [] parameter_args: [{ name: "d", positional: false }] value_type: "function"After bindParams() resolves them, these Parameter placeholders are replaced with concrete values and the AST looks like a normal parsed query.
Errors
Section titled “Errors”| Error | Errno | When |
|---|---|---|
empty parameter name | 74 | Query contains $ with no name (e.g., status = $) |
invalid parameter name | 75 | Name starts with a digit but contains non-digits (e.g., $1abc) |
positional parameters are 1-indexed | 76 | Positional $0 (e.g., status = $0) |
bindParams() raises FlyqlError (or returns an error in Go) for:
unbound parameter: $name— referenced in query, missing from dictunused parameter: name— provided in dict, not referenced in queryunsupported parameter value type: <type>— bound value is not one of the supported types
See Also
Section titled “See Also”- Values & Types — the value types parameters resolve to
- Lists — IN-list syntax (parameters can appear in list values)
- AST & Custom Generators — how parameters appear in the AST