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.

This page is the long form. The short version sits with the other statement tools: Statement tools .

Basic usage

Prefix any query with FORMAT:

FeatureQL
FORMAT (ESCAPED) RETURN F1 := 1 + 2 * 3;
Result
OUTPUT VARCHAR
RETURN\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) RETURN F1 := 1;
null

Other options combine with FORMAT:

OptionPurpose
FIXRewrite non-strict constructs into canonical strict syntax (STRUCT → ROW, LIST → ARRAY, move BIND_VALUES from DEFINE to FOR, normalize function aliases like LEN → LENGTH).
WIDTH nSet the pretty printer's preferred output width.

FORMAT is also what powers client.validate() in the Python package (FORMAT (FIX) plus schema extraction). See Statement tools and Python library tools . The FORMAT syntax reference lists the stable statement forms.

Layout and precedence

The formatter applies consistent indentation, breaks long RETURN 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 is non-strict: legacy forms such as STRUCT[…], LIST(…), and type aliases like INT64 compile, and compiler warnings stay warnings. Add /* STRICT */ to reject those aliases and to turn any remaining warning into UE/STRICT-SYNTAX. Combine FORMAT (FIX) with exploratory syntax to rewrite it into canonical, reviewable FeatureQL:

FeatureQL
FORMAT (ESCAPED, FIX) RETURN F1 := STRUCT[1, 2 AS name], F2 := LIST(3, 4);
Result
OUTPUT VARCHAR
RETURN\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 (LEN → LENGTH, MOD → MODULO, and similar).

Type aliases without FIX

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

FeatureQL
FORMAT (ESCAPED) RETURN F1 := CAST(1 AS INT64), F2 := CAST('hi' AS STRING);
Result
OUTPUT VARCHAR
RETURN\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 */ expects. In particular:

  • Floating-point comparison precision — under /* STRICT */, a bare DOUBLE comparison is a warning (COMPARISON-DIGITS-DEFAULT-SQL) and therefore a UE/STRICT-SYNTAX. Write an explicit suffix (e.g. >.5, =.4). FORMAT (FIX) leaves plain > / = unchanged.
  • Typed null predicates — IS NULL may carry ::type matching the expression (e.g. x IS NULL::BIGINT). FORMAT (FIX) does not insert ::type for you.

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

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

Add those annotations by hand before adding /* STRICT */, or keep the query non-strict until they are in place.

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) RETURN
    f1 := 1, -- end-of-line note
    f2 := 2
;
Result
OUTPUT VARCHAR
RETURN\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) RETURN
    f1 := a + 1 -- before comma: leaf note
    ,
    f2 := 2
;
Result
OUTPUT VARCHAR
RETURN\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) RETURN
    f1 := a + 1
    , -- after comma: feature note
    f2 := 2
;
Result
OUTPUT VARCHAR
RETURN\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 := (DEFINE, FOR, RETURN).

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

Leading on the first feature in RETURN:

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

Leading on a bare RETURN reference (not the homonymous DEFINE definition):

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

Leading on a FOR binding:

FeatureQL
FORMAT (ESCAPED)
DEFINE
    INP := INPUT(BIGINT)
RETURN
    INP
FOR
    -- attaches to the binding
    INP := BIND_VALUES(ARRAY(1, 2, 3))
;
Result
OUTPUT VARCHAR
DEFINE\n inp := INPUT(BIGINT)\nRETURN\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)
RETURN F1 := 1
-- leading in WHERE has no anchor
WHERE F1 > 0
;
Result
OUTPUT VARCHAR
RETURN\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.

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.

See also