Skip to content

tdfs4ds.feature_store.valid_time_column

tdfs4ds.feature_store.valid_time_column

Valid time read from a column of the source (0.3.5.0).

A process registered with upload_features(..., valid_time_column='EVENT_DATE') does not stamp its values with one valid time per run. Each source row carries its own: the row's value is valid from that column's value until the entity's next row (or 9999-01-01 for the last one). A whole history is written by one run.

Rules (the MERGE's, applied per entity):

  • A row is a change point. A later absence means "unchanged", never "gone".
  • Equal consecutive values of an entity and feature are merged (NULL-safe), so no period is opened for an unchanged value, as the ingestion MERGE does.
  • "From t0 onward": t0 = the entity's earliest valid time in the load. The load replaces the entity's stored history from t0 onward and leaves what is before untouched. A re-run is idempotent, an incremental load rewrites only from its own t0, a backfill needs no order.

The write is three statements per storage table, sent as ONE request (one transaction: a failure leaves the table as it was), verified on Vantage 20:

a. NONSEQUENCED VALIDTIME DELETE the rows of the entity starting at or after t0; b. NONSEQUENCED VALIDTIME UPDATE the row spanning t0 so that it ends at t0; c. one NONSEQUENCED VALIDTIME INSERT per feature of the computed periods (one per feature, not a UNION ALL: a UNION takes its column type from its first branch, and two features of one table may have VARCHAR lengths that differ).

The option is the process setting VALID_TIME_COLUMN (FS_PROCESS_SETTINGS), read fail-closed by run(): a history load stamped with the run time would be silently wrong.

STAGE_COLUMN module-attribute

STAGE_COLUMN = 'TDFS_VALID_TIME'

KEYS_TABLE module-attribute

KEYS_TABLE = 'TDFS_VT_KEYS'

END_OF_TIME module-attribute

END_OF_TIME = '9999-01-01 00:00:00'

_TS_RE module-attribute

_TS_RE = compile('^\\s*TIMESTAMP\\s*(?:\\(\\s*(\\d+)\\s*\\))?(\\s+WITH\\s+TIME\\s+ZONE)?', IGNORECASE)

qualified

qualified(database, object_name, force=False)

Build db.obj with each part quoted only as needed.

qualified('TDB', 'FS_X') == 'TDB.FS_X' (unchanged for ordinary names); qualified('SVC@CORP', 'FS_X') == '"SVC@CORP".FS_X'. An empty/None database yields the bare object name, so unqualified callers keep working.

force=True quotes both parts unconditionally — only for the sites that have always emitted quotes and whose exact text is already persisted (dataset view DDL, returned volatile-table names).

sql_literal

sql_literal(value)

Escape value for use inside a single-quoted SQL string literal.

For the DBC probes and catalog predicates that compare a name as data (WHERE DatabaseName = '…'). The caller supplies the surrounding quotes.

column_kind

column_kind(sql_type: str) -> Optional[str]

'date', 'timestamp0' (no fractional seconds), 'timestamp' or None (not a usable valid-time type). Pure.

staging_expression

staging_expression(column: str, sql_type: str) -> str

The staged TDFS_VALID_TIME value, a TIMESTAMP(0) WITH TIME ZONE. Pure.

A DATE is midnight of that day. A timestamp with fractional seconds goes through its text (CAST of a TIMESTAMP(6) to TIMESTAMP(0) is Error 7454), which keeps the seconds and drops the fraction. A value without a time zone is read in the session time zone, as FEATURE_STORE_TIME is.

format_time

format_time(value) -> Optional[str]

A database timestamp as the text a VALIDTIME PERIOD literal takes. Pure.

keys_sql

keys_sql(keys_ref: str, volatile_ref: str, entity_columns) -> str

The volatile table of each staged entity with its t0 (earliest valid time). Pure.

_ids

_ids(feature_ids) -> str

_versions

_versions(feature_versions) -> str

delete_sql

delete_sql(target: str, keys_ref: str, entity_columns, feature_ids, feature_versions) -> str

a. The entity's stored rows starting at or after its t0. Pure.

trim_sql

trim_sql(target: str, keys_ref: str, entity_columns, feature_ids, feature_versions) -> str

b. The stored row in force at t0 ends at t0. Pure.

insert_sql

insert_sql(target: str, volatile_ref: str, entity_columns, feature_id, feature_name, feature_version, period: bool = False) -> str

c. The periods of one feature: unchanged values dropped, each period ending at the next kept row of the entity, the last at 9999-01-01. Pure.

PREV_V is NULL both for "previous value NULL" and "no previous row": RN = 1 tells them apart.

period: the feature holds a PERIOD value. Teradata refuses PERIOD data in a query with a window function (Error 6976), so the value travels as its bounds and is rebuilt outside the window queries (:func:_period_insert_sql).

_period_insert_sql

_period_insert_sql(target: str, volatile_ref: str, entity_columns, feature_id, feature_name, feature_version) -> str

:func:insert_sql for a PERIOD feature. Pure.

Same periods and the same change rule, computed on BEGIN / END of the value (a PERIOD never has a NULL bound, so a NULL BEGIN means a NULL value). No query block that holds a window function carries a PERIOD column; the value is rebuilt last.

is_period_type

is_period_type(sql_type) -> bool

A PERIOD type as TYPE() prints it (PERIOD(TIMESTAMP(0))). Pure.

table_request

table_request(target: str, volatile_ref: str, keys_ref: str, entity_columns, features) -> str

The one request writing a storage table: a, b, then c per feature. Pure.

features: [(feature_id, feature_name, feature_version)] of that table, with an optional fourth item True for a PERIOD feature.

store_with_valid_time_column

store_with_valid_time_column(entity_id, volatile_table_name, features_infos, executor=None) -> dict

Write the staged batch, each row at its own valid time. Raises on a database error (the caller records the batch FAILED; each table's request is all or nothing).

Returns {"t_min", "t_max", "rows", "tables"}: the earliest and latest valid time of the batch (text, None for an empty batch), its staged rows, the tables written.

_period_features

_period_features(execute, volatile_ref: str, feature_names) -> set

The upper-cased names of the staged features holding PERIOD values, read with TYPE() on one staged row (the batch is not empty here). A failed read raises: a PERIOD feature written with the plain SQL fails anyway (Error 6976).

read_valid_time_column

read_valid_time_column(process_id: str) -> Optional[str]

The VALID_TIME_COLUMN setting of process_id, or None.

Fail-closed, unlike the other settings: a table that exists but cannot be read raises, because running such a process with the run's valid time would write a wrong history. No table means no process of this store could have recorded the option.