@askdb/core API reference: bindPreparedQuery()
Reference · @askdb/core
bindPreparedQuery() reruns a query ask() returned, with new values and no model call. It checks the stored template again under the schema and scope you pass now. Rerun a query with new values walks through it.
bindPreparedQuery(prepared, values, guardrails)
Section titled “bindPreparedQuery(prepared, values, guardrails)”Local rebind of a stored preparedQuery with new business values, without a model call. Synchronous. It re-runs the SQL checks on the template under the schema, tenant scope and modes you pass now, then renders the template. It returns sql with the business values inlined (with a tenant scope in the default tenantSqlMode: "sql-params", run it with tenantParams; in "sql-only" the tenant IDs are inlined too), unboundSql + params for driver binding, bindings for the business parameters, and the verdict. sql and unboundSql keep the trailing ; of namedSql, if it has one; remove it before you wrap the statement (see Row limits).
import { bindPreparedQuery } from "@askdb/core";
const rebound = bindPreparedQuery( result.preparedQuery!, { state_name: "Utah" }, { schema, tenantScope }, // the same schema and the caller's scope, as for ask());await pool.query(rebound.sql, rebound.tenantParams ?? []);// or: await pool.query(rebound.unboundSql, rebound.params);The third argument, BindGuardrails, is required:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
schema | AnyNormalizedSchema | yes | — | The schema to check the template under: its tenant policy and its sensitive markers. |
tenantScope | TenantScope | with a tenant policy | — | The caller’s scope, as for ask(). Tenant IDs are rendered from it. A subtree scope must be expanded first with expandTenantScope(). |
sensitiveGuardrailMode | "warn" | "strict" | "off" | no | "warn" | As for ask(): "warn" binds and reports sensitive references in verdict; "strict" refuses them. |
acceptWarnings | ReadonlyArray<"tenant"> | no | — | With a policy whose enforcement is warn, a template that fails the tenant check is refused unless this includes "tenant". |
tenantSqlMode | TenantSqlOutputMode | no | "sql-params" | As for ask(): "sql-params" puts tenant markers in sql and the IDs in tenantParams, with the markers of the template’s built-in dialect (a rebind always has one, so ask()’s custom-dialect rule never applies); "sql-only" inlines them as escaped literals and refuses a tenant ID that isn’t printable ASCII or contains a backslash. Any other value throws TenantScopeError (TENANT_SQL_MODE_REQUIRED). |
dialect | DialectSpec | BuiltInDialectId | for a "host" template, or one stored without dialectRules | the built-in spec | The dialect rules to check and render under: the DialectSpec you passed to ask(), or a built-in id. Its id must be the template’s dialect. |
Which dialect rules a rebind uses. ask() records them in preparedQuery.dialectRules: "builtin" when it ran with a built-in id or the exported constant (POSTGRES_DIALECT and the others), "host" for any other DialectSpec object, such as { ...POSTGRES_DIALECT, backslashEscapes: true }. A "builtin" template rebinds under the built-in spec unless you pass dialect. A "host" template needs the same spec as dialect, so your backslashEscapes, blockedFunctions, extraForbiddenKeywords and extraValidate apply at rebind too. A template stored without dialectRules, before 1.0.0-beta.46, needs dialect as well: pass the spec ask() ran with, or the built-in id, such as "postgres", if it ran with the built-in rules. If your app loads two copies of @askdb/core, such as ESM beside CJS, a constant from the other copy is a different object, so its templates are "host": pass dialect (ADR 0032).
Tenant IDs come from tenantScope, never from values. Each :tenant_<root>_ids placeholder is filled in both forms, as ask() fills it. In sql, it becomes driver markers numbered from the first slot, with the IDs in tenantParams (the default, "sql-params"), or escaped literals in "sql-only" (col = :tenant_agency_ids becomes col IN ('7', '8') with several IDs). In unboundSql, it becomes driver markers after the business markers, with the IDs in params (appended for $N / @pN, interleaved in source order for ?, and bindings[].indices remapped). Run each form with its own array, never the two concatenated.
Every check uses the bind-time inputs, so a template refused today may have bound yesterday: a tightened policy, or a scope other than the one it was produced under, applies at the next rebind. It throws:
QueryParameterError(MISSING_GUARDRAIL_CONTEXT) without the third argument or itsschema; (DIALECT_SPEC_REQUIRED) withoutdialectfor a"host"template or one stored withoutdialectRules; (DIALECT_UNSUPPORTED) for adialectwhoseidisn’t the template’s; (TENANT_VALUE_SUPPLIED) for a:tenant_*key invalues; (TENANT_PARAM_ALIGNMENT) when the tenant markers can’t be lined up with the business params; (INVALID_DECLARATION) for a parameter declaring atypeorcardinalityAskDB doesn’t support; and the existing reasons for a wrong, missing or misplaced business value.TenantScopeError: as inask()for a missing or invalid scope;SUBTREE_NOT_RESOLVABLEfor an unexpandedsubtree;UNRESOLVED_TENANT_PLACEHOLDERfor a placeholder the scope has no IDs for. That includes a template scoped through another root, any placeholder under aglobalscope, and any placeholder when the schema has no tenant policy.UNESCAPABLE_TENANT_IDin"sql-only"mode for a tenant ID that isn’t printable ASCII or contains a backslash.TENANT_SQL_MODE_REQUIREDfor atenantSqlModethat is neither mode.SqlValidationError,TenantGuardrailErrororSensitiveReferenceErrorfor adenyverdict (see Guardrail verdict). A refused tenant warning throwsTenantGuardrailErrorwith a message namingacceptWarnings.
@askdb/client users call askdb.bind(), which supplies the client’s schema and expands a subtree scope.
List parameters are arity-stable in unboundSql only on PostgreSQL/CockroachDB (= ANY($n)). Elsewhere a changed list length changes the marker count — rebind rather than swapping the array. Map SQL Server values via parameters[].markers (@p0-based).
| Type | What it is |
|---|---|
PreparedQuery | The template ask() returns as result.preparedQuery: definitions and the named SQL, no runtime values. Store it as is. |
BindGuardrails | The third argument, as in the table above. |
BoundQuery | The return value: { sql, tenantParams?, unboundSql, params, bindings, verdict }. Run sql with tenantParams, or unboundSql with params. |
QueryParameterValue | One value in values: string, number or boolean. A list parameter takes an array of them. |
QueryParameterType | A parameter’s declared type: "string", "number", "boolean", "date" or "datetime". |
QueryParameterBinding | One entry of bindings: a business parameter with its value, markers and indices. |
QueryParamSlot | One entry of params: a QueryParameterValue, or an array of them for a list bound to one marker (listBinding: "array"). |
A refused value or template throws QueryParameterError; QueryParameterError reasons lists each reason.
Read next
Section titled “Read next”© 2026 Yahya Gilany

