Skip to content

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.

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.

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.

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.

Parameters work in every position where a literal value is valid:

status = $code
age >= $min_age
name != $banned
score > $1
message has $needle
path like $pattern
title ilike $search
tags not has $excluded

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]

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".

Parameters can appear inside transformer argument positions:

tags|split($sep) has $needle

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.

from flyql import parse, bind_params
ast = parse("status = $code and env = $env")
bind_params(ast.root, {"code": 200, "env": "prod"})
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",
})
LanguageSignature
Pythonbind_params(node, params: dict) -> Node
JavaScriptbindParams(node, params: object): Node
GoBindParams(node *Node, params map[string]any) error
  • node — the root Node returned by parse().
  • 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 an error.

The bound value’s native type determines the resulting value_type in the AST:

Native typeResulting 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: Decimal

bindParams() is strict about both missing and extra 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: $x

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: y

This is stricter than typical SQL drivers, which often defer parameter validation to query execution time. Catching these errors at bind time keeps mistakes visible.

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: $2

Parameters work the same way with the matcher. Bind first, then evaluate:

from flyql import parse, bind_params
from 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 evaluating

The same guard exists in every SQL generator, so you cannot accidentally produce SQL with unresolved parameters.

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.

ErrorErrnoWhen
empty parameter name74Query contains $ with no name (e.g., status = $)
invalid parameter name75Name starts with a digit but contains non-digits (e.g., $1abc)
positional parameters are 1-indexed76Positional $0 (e.g., status = $0)

bindParams() raises FlyqlError (or returns an error in Go) for:

  • unbound parameter: $name — referenced in query, missing from dict
  • unused parameter: name — provided in dict, not referenced in query
  • unsupported parameter value type: <type> — bound value is not one of the supported types