Formatting queries

FORMAT rewrites a FeatureQL query into canonical layout and syntax without executing it. Use it to normalize style, migrate away from legacy aliases, or preview what validate() will produce. Comments are preserved when they attach to a recognized anchor; everything else is collected at the end of the output.

Basic usage

Prefix any query with FORMAT:

FeatureQL
FORMAT (ESCAPED) SELECT F1 := 1 + 2 * 3;
Result
OUTPUT VARCHAR
SELECT\n f1 := 1 + 2 * 3\n;

The formatted text is the query result (one VARCHAR column). Add ESCAPED when you need markers instead of real newlines — useful in tests and programmatic diffs:

FORMAT (ESCAPED) SELECT F1 := 1;
null

Other options combine with FORMAT:

OptionPurpose
FIXRewrite non-strict constructs into canonical strict syntax (STRUCTROW, LISTARRAY, move BIND_VALUES from WITH to FOR, normalize function aliases like LENLENGTH).
REORDERReorder features within each clause by dependency layer (comments move with their anchor; see below).

FORMAT is also what powers client.validate() in the Python client (FORMAT (FIX) plus schema extraction). See Query tools for the broader inspection toolkit.

Layout and precedence

The formatter applies consistent indentation, breaks long SELECT lists across lines, and inserts parentheses only where operator precedence requires them. It does not change semantics — only surface syntax and spacing.

FIX — migrating to strict syntax

By default FeatureQL runs in strict mode. Legacy forms such as STRUCT[…], LIST(…), and type aliases like INT64 are rejected unless you add /* NON-STRICT */. Combine FORMAT (FIX) with exploratory syntax to rewrite it into strict, reviewable FeatureQL:

FeatureQL
FORMAT (ESCAPED, FIX) SELECT F1 := STRUCT[1, 2 AS name], F2 := LIST(3, 4);
Result
OUTPUT VARCHAR
SELECT\n f1 := ROW(1, 2 AS name),\n f2 := ARRAY(3, 4)\n;

FIX also normalizes function aliases when the alias is the only non-strict issue (LENLENGTH, MODMODULO, and similar).

Type aliases without FIX

Even without FIX, FORMAT rewrites type aliases to canonical names:

FeatureQL
FORMAT (ESCAPED) SELECT F1 := CAST(1 AS INT64), F2 := CAST('hi' AS STRING);
Result
OUTPUT VARCHAR
SELECT\n f1 := CAST(1 AS BIGINT),\n f2 := CAST('hi' AS VARCHAR)\n;

What FIX does not add

The formatter does not synthesize every annotation strict mode expects. In particular:

  • Floating-point comparison precision — strict DOUBLE comparisons need an explicit suffix on the operator (e.g. >.5, =.4). FORMAT (FIX) leaves plain > / = unchanged; re-running without /* NON-STRICT */ can then fail with COMPARISON-BINARY-DIGITS-EMPTY.
  • Typed null predicates — strict IS NULL normally requires ::type matching the expression (e.g. x IS NULL::BIGINT). FORMAT (FIX) does not insert ::type for you.

FeatureQL
FORMAT (FIX) SELECT F1 := (1)::BIGINT IS NULL;
Result
OUTPUT VARCHAR
SELECT f1 := (1)::BIGINT IS NULL ;

FeatureQL
FORMAT (FIX) SELECT F1 := 1e0::DOUBLE > 2e0::DOUBLE;
Result
OUTPUT VARCHAR
SELECT f1 := 1e0::DOUBLE > 2e0::DOUBLE ;

Add those annotations by hand (or keep /* NON-STRICT */ on the query you execute) when moving from exploratory syntax to strict FeatureQL.

Comments

FORMAT round-trips comments that attach to a syntactic anchor. Attachment is decided only by position — not by guessing intent from text.

Trailing comments (same line as code)

A comment to the right of code attaches to whatever syntactic unit closes immediately to its left:

  • Before the terminating comma → the parameter or leaf on the left (often forcing a multiline break for compound expressions).
  • After the terminating comma → the whole feature definition.

The comma is a granularity toggle you control directly: keep the comment before the comma to document a sub-expression; move it past the comma to document the feature.

A basic trailing comment:

FeatureQL
FORMAT (ESCAPED) SELECT
    f1 := 1, -- end-of-line note
    f2 := 2
;
Result
OUTPUT VARCHAR
SELECT\n f1 := 1, -- end-of-line note\n f2 := 2\n;

Before the comma, on a compound expression, the comment attaches to the leaf on its left, and the expression breaks across lines so the comment can sit on that leaf's line:

FeatureQL
FORMAT (ESCAPED) SELECT
    f1 := a + 1 -- before comma: leaf note
    ,
    f2 := 2
;
Result
OUTPUT VARCHAR
SELECT\n f1 := a\n + 1 -- before comma: leaf note\n ,\n f2 := 2\n;

After the comma, the same comment attaches to the whole feature and stays on the collapsed line:

FeatureQL
FORMAT (ESCAPED) SELECT
    f1 := a + 1
    , -- after comma: feature note
    f2 := 2
;
Result
OUTPUT VARCHAR
SELECT\n f1 := a + 1, -- after comma: feature note\n f2 := 2\n;

Long comments are never wrapped — they may extend past the usual line width.

Leading comments (own line)

A comment on its own line attaches to the nearest following item, but only in a feature-defining clause — a clause that admits := (WITH, FOR, SELECT).

  • In WITH / FOR / SELECT, a leading comment before the first feature in that clause stays with that feature.
  • In SELECT, a leading comment before a bare reference (no :=) attaches to that reference — even when a WITH feature shares the same name.

Leading on the first feature in SELECT:

FeatureQL
FORMAT (ESCAPED) SELECT
    -- documents the first output
    f1 := 1,
    f2 := 2
;
Result
OUTPUT VARCHAR
SELECT\n -- documents the first output\n f1 := 1,\n f2 := 2\n;

Leading on a bare SELECT reference (not the homonymous WITH definition):

FeatureQL
FORMAT (ESCAPED)
WITH
    F1 := INPUT(BIGINT)
SELECT
    -- attaches to the bare reference
    F1
FOR
    F1 := BIND_VALUE(1)
;
Result
OUTPUT VARCHAR
WITH\n f1 := INPUT(BIGINT)\nSELECT\n -- attaches to the bare reference\n f1\nFOR\n f1 := BIND_VALUE(1)\n;

Leading on a FOR binding:

FeatureQL
FORMAT (ESCAPED)
WITH
    INP := INPUT(BIGINT)
SELECT
    INP
FOR
    -- attaches to the binding
    INP := BIND_VALUES(ARRAY(1, 2, 3))
;
Result
OUTPUT VARCHAR
WITH\n inp := INPUT(BIGINT)\nSELECT\n inp\nFOR\n -- attaches to the binding\n inp := BIND_VALUES(ARRAY(1, 2, 3))\n;

Non-feature clauses (WHERE, ORDER BY, LIMIT, …)

These clauses do not define features. A leading comment there has no anchor and becomes unrecognized. Trailing comments on expressions inside those clauses still attach to the leaf on their left.

Unrecognized leading comment before WHERE:

FeatureQL
FORMAT (ESCAPED)
SELECT F1 := 1
-- leading in WHERE has no anchor
WHERE F1 > 0
;
Result
OUTPUT VARCHAR
SELECT\n f1 := 1\nWHERE f1 > 0\n -- unrecognized comments\n -- leading in WHERE has no anchor\n;

Unrecognized comments are emitted in a single block at the end of the formatted query, under -- unrecognized comments. A warning is also returned.

Section comments (-- *)

Lines starting with -- * are meant to label groups of features (one or more consecutive -- * lines form a single section comment). FORMAT does not yet treat them as sections — they are formatted like ordinary leading comments, which also means they only attach in a feature-defining clause and become unrecognized before WHERE, ORDER BY, LIMIT, or OFFSET. Section-aware layout and FORMAT (REORDER) comment behavior are still being implemented.

Pre-FORMAT text

Comments and text outside the query passed to FORMAT (for example a -- note above the FORMAT keyword in the original buffer) are discarded — they are not part of the query boundary.

REORDER and comments

With FORMAT (REORDER), a leading feature comment is intended to move with its feature, a trailing comment with whatever it attached to, section comments stay at the top of their group, and unrecognized comments stay at the end. Reorder support is not fully wired for comments yet; use FORMAT (ESCAPED) without REORDER for stable comment output today.

See also

Last update at: 2026/06/20 10:08:10