JDBC data sources

SOURCE_JDBC() connects FeatureQL to any JDBC-compatible database — PostgreSQL, MySQL, Oracle, SQL Server, and others. This gives your real-time features direct access to relational data without building custom data-fetching services.

Creating a JDBC source

Establish a connection using a standard JDBC connection string:

FeatureQL
CREATE OR REPLACE FEATURE FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONN AS
    SOURCE_JDBC(
        'postgresql://featuremesh:featuremesh@host.docker.internal:5433/featuremesh?sslmode=disable'
        WITH (
            tables=ARRAY['serving_slt_customers'],
            timeout='500ms'
        )
    );
Result
feature_name VARCHARstatus VARCHARmessage VARCHAR
FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONNCREATEDFeature created as not exists

The tables array acts as an allowlist — only the listed tables and views are accessible through this connection. This is both a security measure and a documentation aid: anyone reading the feature definition can see exactly which tables it touches.

In a local development process, synchronously install the persisted connection before querying it:

FeatureQL
REFRESH FEATURES FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONN;
Result
FEATURE VARCHARKIND VARCHARSTATUS VARCHARMESSAGE VARCHAR
FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONNSOURCE_JDBCREFRESHED(empty)

The refresh returns only after the DataFusion catalog can resolve the configured table.

Querying with EXTERNAL_COLUMNS()

The most straightforward way to access JDBC data is EXTERNAL_COLUMNS() with a VIEW() reference. This maps database columns directly to feature fields, just like batch data source mappings:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    POSTGRES_SRC := EXTERNAL_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        last_order_id BIGINT,
        status VARCHAR
        FROM VIEW(FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONN[serving_slt_customers])
    ),
SELECT
    CUSTOMER_ID := BIND_VALUES(ARRAY[1, 2]),
    LAST_ORDER_ID := POSTGRES_SRC[last_order_id],
    STATUS := POSTGRES_SRC[status]
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINTSTATUS VARCHAR
1101active
2202inactive

The BIND TO CUSTOMER_ID clause links the customer_id column to the input feature, so FeatureMesh knows how to parameterize the query.

Querying with EXTERNAL_VIEW()

For more control over the SQL sent to the database, use EXTERNAL_VIEW() with an explicit query:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    POSTGRES_SRC := EXTERNAL_VIEW(
        `SELECT customer_id, last_order_id, status
         FROM %FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONN[serving_slt_customers]`
        ON `SELF.customer_id=%CUSTOMER_ID`
        AS ROW(customer_id BIGINT, last_order_id BIGINT, status VARCHAR)
    ),
SELECT
    CUSTOMER_ID := BIND_VALUES(ARRAY[1, 2]),
    LAST_ORDER_ID := POSTGRES_SRC[last_order_id],
    STATUS := POSTGRES_SRC[status]
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINTSTATUS VARCHAR
1101active
2202inactive

The %FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONN[serving_slt_customers] placeholder resolves to the actual table reference, and %CUSTOMER_ID binds the input. This approach is useful when you need to select specific columns or apply database-side transformations.

The connection reads current database state. Updating customer 2 in PostgreSQL changes the next serving result without recreating the source:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    POSTGRES_SRC := EXTERNAL_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        last_order_id BIGINT,
        status VARCHAR
        FROM VIEW(FM.TUTORIALS.EXTERNAL_JDBC.POSTGRES_CONN[serving_slt_customers])
    ),
SELECT
    CUSTOMER_ID := BIND_VALUE(2),
    LAST_ORDER_ID := POSTGRES_SRC[last_order_id],
    STATUS := POSTGRES_SRC[status]
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINTSTATUS VARCHAR
2303active

Best practices

Reuse connections: Create one SOURCE_JDBC() per database and share it across features. Connection pooling happens automatically.

Be precise with types: Specify exact types in ROW() specifications. Type mismatches between the database and FeatureQL cause runtime errors, not compile-time warnings.

Watch for load: FeatureMesh can generate bursts of concurrent queries when serving many requests. Monitor your database load and consider connection limits, read replicas, or caching layers if needed.

Index your join columns: The columns used in BIND TO or ON clauses become WHERE conditions in the generated SQL. Make sure they're indexed in the source database.