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 1 AS FEATURE1, FEATURE1 + 1 AS FEATURE2;| 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 1 AS FEATURE1, FEATURE1 + 1 AS FEATURE2;
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:
/* NON-STRICT */
FORMAT (FIX) SELECT F1 := STRUCT[1, 2 AS name], F2 := LIST(3, 4);| OUTPUT VARCHAR |
|---|
| SELECT F1 := ROW(1, 2 AS NAME), F2 := ARRAY[3, 4]; |
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.
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.
/* NON-STRICT */
FORMAT SELECT F1 := LEN('hello'), F2 := MOD(10, 3);| OUTPUT VARCHAR |
|---|
| SELECT F1 := LENGTH('hello'), F2 := MODULO(10, 3); |
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 1 AS FEATURE1, FEATURE1 + 1 AS FEATURE2;
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
1 AS FM.TUTORIALS.QUERYTOOLS.FEATURE1,
FM.TUTORIALS.QUERYTOOLS.FEATURE1 + 1 AS FM.TUTORIALS.QUERYTOOLS.FEATURE2
;| 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 | LNAME BIGINT |
|---|---|---|---|
| 1-concepts/1-why_featuremesh.md | DOC_PAGE | [1, 1] | 31 |
| 1-concepts/2-design_philosophy.md | DOC_PAGE | [1, 2] | 33 |
| 1-concepts/3-batch_analytics.md | DOC_PAGE | [1, 3] | 31 |
| 1-concepts/4-real_time_serving.md | DOC_PAGE | [1, 4] | 33 |
| 1-concepts/5-business_use_cases.md | DOC_PAGE | [1, 5] | 34 |
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 FUNCTION SIGNATURES (covered in Operators & Functions ), these tools make FeatureQL largely self-documenting.
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 |