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:
FORMAT (ESCAPED) SELECT F1 := 1 + 2 * 3;| 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; Other options combine with FORMAT:
| Option | Purpose |
|---|---|
FIX | Rewrite non-strict constructs into canonical strict syntax (STRUCT → ROW, LIST → ARRAY, move BIND_VALUES from WITH to FOR, normalize function aliases like LEN → LENGTH). |
REORDER | Reorder 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:
FORMAT (ESCAPED, FIX) SELECT F1 := STRUCT[1, 2 AS name], F2 := LIST(3, 4);| 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 (LEN → LENGTH, MOD → MODULO, and similar).
Type aliases without FIX
Even without FIX, FORMAT rewrites type aliases to canonical names:
FORMAT (ESCAPED) SELECT F1 := CAST(1 AS INT64), F2 := CAST('hi' AS STRING);| 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
DOUBLEcomparisons need an explicit suffix on the operator (e.g.>.5,=.4).FORMAT (FIX)leaves plain>/=unchanged; re-running without/* NON-STRICT */can then fail withCOMPARISON-BINARY-DIGITS-EMPTY. - Typed null predicates — strict
IS NULLnormally requires::typematching the expression (e.g.x IS NULL::BIGINT).FORMAT (FIX)does not insert::typefor you.
FORMAT (FIX) SELECT F1 := (1)::BIGINT IS NULL;| OUTPUT VARCHAR |
|---|
| SELECT f1 := (1)::BIGINT IS NULL ; |
FORMAT (FIX) SELECT F1 := 1e0::DOUBLE > 2e0::DOUBLE;| 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:
FORMAT (ESCAPED) SELECT
f1 := 1, -- end-of-line note
f2 := 2
;| 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:
FORMAT (ESCAPED) SELECT
f1 := a + 1 -- before comma: leaf note
,
f2 := 2
;| 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:
FORMAT (ESCAPED) SELECT
f1 := a + 1
, -- after comma: feature note
f2 := 2
;| 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 aWITHfeature shares the same name.
Leading on the first feature in SELECT:
FORMAT (ESCAPED) SELECT
-- documents the first output
f1 := 1,
f2 := 2
;| 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):
FORMAT (ESCAPED)
WITH
F1 := INPUT(BIGINT)
SELECT
-- attaches to the bare reference
F1
FOR
F1 := BIND_VALUE(1)
;| 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:
FORMAT (ESCAPED)
WITH
INP := INPUT(BIGINT)
SELECT
INP
FOR
-- attaches to the binding
INP := BIND_VALUES(ARRAY(1, 2, 3))
;| 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:
FORMAT (ESCAPED)
SELECT F1 := 1
-- leading in WHERE has no anchor
WHERE F1 > 0
;| 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
- Query tools —
EXPLAIN,DESCRIBE,SHOW CREATE,SHOW DOCS, and client wrappers - Friendly syntax — stylistic forms the formatter normalizes