Prepared statements
Prepared statements are the final step for taking features to production. They pre-compile a feature and its entire dependency graph — including all data source calls and cross-source joins — into a single optimized query. This eliminates parsing and resolution overhead at request time, giving you predictable latency for real-time serving.
Defining the base features
A prepared statement wraps an existing feature. Start by creating the features you want to serve:
CREATE OR REPLACE FEATURES IN FM.TUTORIALS.PREPARED_STATEMENTS AS
SELECT
FEATURE_A := INPUT(BIGINT),
FEATURE_B := INPUT(BIGINT),
FEATURE_C := INPUT(BIGINT),
FEATURE_Q := FEATURE_A + 1,
FEATURE_R := FEATURE_A + FEATURE_B + FEATURE_C
;| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R | CREATED | Feature created as not exists |
Here, FEATURE_Q has one input, while FEATURE_R combines three inputs. A prepared statement resolves the selected feature's entire dependency chain at compile time.
In a real deployment, these features would typically include EXTERNAL_REDIS(), EXTERNAL_COLUMNS(), or EXTERNAL_HTTP() calls — the prepared statement compiles all of that into a single execution plan.
Creating the prepared statement
Use PREPARED_STATEMENT() to compile a feature for real-time serving:
CREATE OR REPLACE FEATURE FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS AS
PREPARED_STATEMENT(
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q
USING FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A
);| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS | CREATED | Feature created as not exists |
The USING clause declares the input tables. The single-input statement above produces one input table. To create multiple tables, group inputs that must be paired row-by-row and list independent inputs separately:
CREATE OR REPLACE FEATURE FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS AS
PREPARED_STATEMENT(
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R
USING
(FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B),
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C
);| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS | CREATED | Feature created as not exists |
(FEATURE_A, FEATURE_B) becomes input_table_1, while FEATURE_C becomes input_table_2. The tables are combined with a cross product during execution.
You can inspect which persisted features are prepared statements:
SHOW FEATURES (COLUMNS (NAME, FUNCTION))
WHERE NAME IN (
'FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS',
'FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS'
);| NAME VARCHAR | FUNCTION VARCHAR |
|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS | PREPARED_STATEMENT |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS | PREPARED_STATEMENT |
Refresh before serving
Persisting a prepared statement updates the registry. REFRESH FEATURES synchronously installs the latest definition in a local, development serving process:
REFRESH FEATURES
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS,
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS
;| FEATURE VARCHAR | KIND VARCHAR | STATUS VARCHAR | MESSAGE VARCHAR |
|---|---|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS | PREPARED_STATEMENT | REFRESHED | (empty) |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS | PREPARED_STATEMENT | REFRESHED | (empty) |
The command accepts an explicit list only. It is disabled by default and should not be enabled in hosted or multi-pod deployments, where refresh requires cluster-wide coordination.
Call a prepared statement
From Python
from featuremesh import ServingClient
client = ServingClient(access_token="YOUR_ACCESS_TOKEN")
result = client.execute_prepared_statement(
"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS",
{"input_table_1": [[1], [5]]},
) The same call is executable in the documentation SLT:
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS{"input_table_1": [[1], [5]]} | FEATURE_A BIGINT | FEATURE_Q_PS BIGINT |
|---|---|
| 1 | 2 |
| 5 | 6 |
One JSON object represents one serving request. Multiple objects make independent calls, and numbered expected groups keep each response separate:
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_Q_PS{"input_table_1": [[2], [1]]} | FEATURE_A BIGINT | FEATURE_Q_PS BIGINT |
|---|---|
| 1 | 2 |
| 2 | 3 |
{"input_table_1": [[10]]} | FEATURE_A BIGINT | FEATURE_Q_PS BIGINT |
|---|---|
| 10 | 11 |
For a statement with two input tables, supply both tables in the same object:
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS{"input_table_1": [[1, 2], [4, 5]], "input_table_2": [[10]]} | FEATURE_A BIGINT | FEATURE_B BIGINT | FEATURE_C BIGINT | FEATURE_R_PS BIGINT |
|---|---|---|---|
| 1 | 2 | 10 | 13 |
| 4 | 5 | 10 | 19 |
From curl
curl -X POST "http://localhost:10090/v1/featureql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"statement": {
"id": "FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS",
"inputs": {
"input_table_1": [[1, 2], [4, 5]],
"input_table_2": [[10]]
}
}
}' The inputs object maps generated input-table names to rows. The order of values in each row follows the order declared in its USING group.