Query tools
FeatureQL includes built-in tools for inspecting, debugging, and exploring your features — all accessible from within the query language itself.
Inspecting queries with EXPLAIN
Prefix any query with EXPLAIN to see the generated SQL, output schema, and entity relationships without executing it:
EXPLAIN SELECT feature1 := 1, feature2 := feature1 + 1;| OUTPUT VARCHAR |
|---|
| {query_sql: WITH\nCTE_GCY637ZF_0 AS (\nSELECT\n\t1 AS FEATURE1\n), \nCTE_GCY637ZF_1 AS (\nSELECT\n\t(FEATURE1 + 1) AS FEATURE2, \n\tFEATURE1\nFROM CTE_GCY637ZF_0\n)\nSELECT FEATURE1, FEATURE2\nFROM CTE_GCY637ZF_1\n, query_outputs: [[FEATURE1, BIGINT], [FEATURE2, BIGINT]], entities: NULL, user: {user: {id: WEBGUEST_USER_NIC, role: GUEST}, project_id: WEBGUEST_PROJECT_SLTTESTS}} |
This is the fastest way to check what SQL FeatureQL will produce for a given query. The query_sql field shows the CTE structure, query_outputs lists the result columns with their types, and entities shows any entity relationships discovered.
Inspecting features with DESCRIBE
DESCRIBE shows metadata about a specific feature — its function, parameters, dependencies, and type information:
CREATE TEMPORARY FEATURES AS SELECT feature1 := 1, feature2 := feature1 + 1;
DESCRIBE FEATURE2;| OUTPUT VARCHAR |
|---|
| [{name: FEATURE2, function: ADD, params: {expr1: {kind: IDENTIFIER}, expr2: {kind: LITERAL}}, status: DEV, position: 0.0, meta: {}, properties: {mode_overwrite: NORMAL, mode_temporary: TEMPORARY}, unnamed_dependencies: {}, id: 2E6S99CCGW1E, feature_type: {}, input: [], signature: NULL, dependencies: {}, restrictions: [], inputs: [], formula: NULL, computed_properties: {}}] |
Use this when you need to understand how a feature is defined, what it depends on, or what type it produces. It works with both temporary and persisted features.
Formatting queries with FORMAT
FORMAT rewrites a query into its canonical form without executing it. This is useful for normalizing syntax and auto fixing non strict constructs.
Use FORMAT (ESCAPED) to get output with
markers instead of actual newlines, which is useful for programmatic comparisons.
Strict mode and FIX
By default, FeatureQL operates in strict mode. Constructs like STRUCT, LIST, and type aliases like INT64 are rejected. Add /* NON-STRICT */ to bypass strict checks, and combine with FORMAT (FIX) to rewrite them into canonical forms:
@fql-playground(fix_combined_struct_list_escaped)
FIX rewrites STRUCT to ROW(...), LIST to ARRAY[...], normalizes bracket/parenthesis mismatches on ROW and ARRAY, and moves BIND_VALUES from the WITH clause to a FOR clause.
The formatter does not synthesize every piece of syntax strict mode expects. In particular, it does not add:
- Floating-point comparison precision — for
FLOAT/DOUBLE, strict comparisons use an explicit suffix on the operator (e.g.=.5,>.4,BETWEEN.3). If you relied on/* NON-STRICT */(warning + default SQL casts), the formatted text may still omit.N; re-running withoutNON-STRICTcan then fail withCOMPARISON-BINARY-DIGITS-EMPTY. - Typed null predicates —
IS NULL/IS NOT NULLnormally require::typematching the expression (e.g.x IS NULL::BIGINT). With/* NON-STRICT */, omitting::typeis allowed (with a warning);FORMAT (FIX)does not insert::typefor you, because formatting runs before full type inference.
Add those annotations by hand (or keep /* NON-STRICT */ on the query you execute) when moving from exploratory syntax to strict, reviewable FeatureQL.
FORMAT (FIX) output still uses plain IS NULL (no ::BIGINT inserted):
@fql-playground(fix_format_preserves_is_null_without_predicate_type)
The same applies to DOUBLE comparisons: plain >, not >.9:
@fql-playground(fix_format_preserves_float_compare_without_digits)
Alias normalization
FORMAT always normalizes function aliases (LEN → LENGTH, MOD → MODULO, POWER → POW, NOW → CURRENT_TIMESTAMP) and type aliases (INT64 → BIGINT, FLOAT64 → DOUBLE, STRING → VARCHAR, BOOL → BOOLEAN, INTEGER → INT) to their canonical names.
@fql-playground(func_alias_multiple)
Recreating features with SHOW CREATE
SHOW CREATE FEATURES returns the FeatureQL code needed to recreate one or more features. This is useful for understanding how features are stored internally and for exporting definitions.
For temporary features:
START TRANSACTION;
CREATE TEMPORARY FEATURES AS SELECT feature1 := 1, feature2 := feature1 + 1;
SHOW CREATE FEATURES FEATURE1, FEATURE2;
COMMIT;| OUTPUT VARCHAR |
|---|
| CREATE FEATURES AS SELECT FEATURE1 := 1, FEATURE2 := FEATURE1 + 1 ; |
Notice that FeatureQL reorders features by dependency layer and may rewrite syntax — 1 becomes VALUE(1), for example. The semantics are preserved even when the surface syntax changes.
For persisted features, the output includes full namespace paths:
CREATE OR REPLACE FEATURES AS
SELECT
FM.TUTORIALS.QUERYTOOLS.FEATURE1 := 1,
FM.TUTORIALS.QUERYTOOLS.FEATURE2 := FM.TUTORIALS.QUERYTOOLS.FEATURE1 + 1
;| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.QUERYTOOLS.FEATURE2 | CREATED | Feature created as not exists |
| FM.TUTORIALS.QUERYTOOLS.FEATURE1 | CREATED | Feature created as not exists |
SHOW CREATE FEATURES FM.TUTORIALS.QUERYTOOLS.FEATURE2;| OUTPUT VARCHAR |
|---|
| CREATE FEATURES AS SELECT FM.TUTORIALS.QUERYTOOLS.FEATURE1 := 1, FM.TUTORIALS.QUERYTOOLS.FEATURE2 := FM.TUTORIALS.QUERYTOOLS.FEATURE1 + 1 ; |
Features are topologically ordered by their dependency graph, making the output self-contained — you can run it directly to recreate the features.
Cross-dialect SQL output
Specify a target dialect to see the actual SQL that would run in a specific backend:
SHOW CREATE FEATURES (LANGUAGE DUCKDB)
FM.TUTORIALS.QUERYTOOLS.FEATURE1,
FM.TUTORIALS.QUERYTOOLS.FEATURE2
;| OUTPUT VARCHAR |
|---|
| WITH/* stage.layer=0 stage.fields_or_features_to_compute=['FM.TUTORIALS.QUERYTOOLS.FEATURE1'] stage.layer_functions=['VALUE'] stage.fields_or_features_to_report=[] features_non_affected=[] where_for_this_layer=EmptyExpr(kind=<ExpressionTypeEnum.EMPTY: 'EMPTY'>, type=EmptyType(), value=None) => stage.cte_type=<CteTypeEnum.STANDARD: 'STANDARD'> */CTE_2Y7H9CHY_0 AS (SELECT/* */ 1 AS "FM.TUTORIALS.QUERYTOOLS.FEATURE1"), /* stage.layer=1 stage.fields_or_features_to_compute=['FM.TUTORIALS.QUERYTOOLS.FEATURE2'] stage.layer_functions=['ADD'] stage.fields_or_features_to_report=['FM.TUTORIALS.QUERYTOOLS.FEATURE1'] features_non_affected=[] where_for_this_layer=EmptyExpr(kind=<ExpressionTypeEnum.EMPTY: 'EMPTY'>, type=EmptyType(), value=None) => stage.cte_type=<CteTypeEnum.STANDARD: 'STANDARD'> */CTE_2Y7H9CHY_1 AS (SELECT/* */ ("FM.TUTORIALS.QUERYTOOLS.FEATURE1" + 1) AS "FM.TUTORIALS.QUERYTOOLS.FEATURE2", "FM.TUTORIALS.QUERYTOOLS.FEATURE1"FROM CTE_2Y7H9CHY_0)SELECT /* */ "FM.TUTORIALS.QUERYTOOLS.FEATURE1", "FM.TUTORIALS.QUERYTOOLS.FEATURE2"FROM CTE_2Y7H9CHY_1 |
Supported dialects: DUCKDB, TRINO, BIGQUERY, DATAFUSION. The output includes CTEs, stage comments, and dialect-specific syntax — useful for understanding how FeatureQL maps to your backend.
Exploring documentation
SHOW DOCS lets you search the built-in documentation without leaving the query environment:
SHOW DOCS (
EXCLUDE (content),
INCLUDE (LENGTH(name) as lname)
)
WHERE lname>30 and category = 'DOC_PAGE'
ORDER BY name ASC
LIMIT 5| NAME VARCHAR | CATEGORY VARCHAR | DISPLAY_ORDER ARRAY | TITLE VARCHAR | TAGS VARCHAR | RELATED_TAGS VARCHAR | LNAME BIGINT |
|---|---|---|---|---|---|---|
| 1-concepts/1-why_featuremesh.md | DOC_PAGE | [1, 1] | Why FeatureMesh? | [(empty)] | [(empty)] | 31 |
| 1-concepts/2-design_philosophy.md | DOC_PAGE | [1, 2] | Design philosophy | [(empty)] | [(empty)] | 33 |
| 1-concepts/3-in_your_organisation.md | DOC_PAGE | [1, 3] | FeatureQL in your organization | [(empty)] | [(empty)] | 36 |
| 1-concepts/4-batch_analytics.md | DOC_PAGE | [1, 4] | Batch analytics | [(empty)] | [(empty)] | 31 |
| 1-concepts/5-real_time_serving.md | DOC_PAGE | [1, 5] | Real-time serving | [(empty)] | [(empty)] | 33 |
Filter by category, search content with LIKE, and sort results to find relevant pages, code samples, and function references. Combined with SHOW FUNCTIONS and SHOW SIGNATURES (covered in Operators & Functions ), these tools make FeatureQL largely self-documenting.
Warnings
FeatureQL returns warnings alongside successful translation or execution (for example UNUSED-FEATURE when a WITH feature is not reachable from the SELECT list). They do not block the query; they surface in query(), translate(), validate(), and the warnings field on API responses.
Each warning includes a stable acknowledgement token derived from its code and message, appended as (acknowledge with ACK-XXXX). To silence a warning you accept, put that token in any line or block comment in the same query (-- ACK-XXXX or /* … ACK-XXXX … */); placement does not matter. A comment that contains an ACK-XXXX token with no matching warning produces STALE-WARNING-ACK — remove the leftover token from the query.
Client-side productivity tools
The Python client (BatchClient) wraps the query tools above into higher-level methods designed for iterative development and LLM-assisted workflows. Each method orchestrates one or more FeatureQL queries and returns structured results.
| Method | What it does | Key FeatureQL under the hood |
|---|---|---|
client.validate(query) | Format to strict mode, extract output schema, list features in topological order, surface warnings — without executing SQL | FORMAT (FIX), EXPLAIN (FORMAT JSON), translate() |
client.diagnose(query) | Execute features one at a time in topological order, stop at the first failure, return intermediate dataframes | EXPLAIN (FORMAT JSON) → incremental_queries → sequential query() |
client.help("tag1", "tag2") | Search documentation pages, code samples, function signatures, and tests by tag or keyword | SHOW DOCS, SHOW SIGNATURES, SHOW TESTS |
client.describe("fm.ns") | List persisted features under a prefix with their types, formulas, and dependency lineage | SHOW FEATURES, SHOW CREATE FEATURES, EXPLAIN (FORMAT JSON) |
All four return a result object with .text (markdown) and structured fields. Call .display() in a notebook to render the markdown directly.
These methods are also exposed as HTTP endpoints (POST /validate, /diagnose, /help, /describe on port 8101) and as MCP tools for LLM agents.
Code samples
They represent end to end tests for capabilities. They use the SLT formalism to describe the test.
# name_of_the_test
query FEATURE1:I,FEATURE2:I,?:I [rowsort]
SELECT
FEATURE1 := 1,
FEATURE2 := 2,
FEATURE1 + FEATURE2
;
----
1 2 3 The line query represent the expected output of the query: column names and types.
Types in SLT queries are represented as:
| Type | Code |
|---|---|
| Integer | I |
| Decimal | D |
| String | V |
| Boolean | B |
| Temporal | T |
| JSON | J |
| Row | R |
| Array of scalars | A |
| Array of rows | Q |