Source code for sqlspec.builder._temporal

"""Temporal query expressions.

Overrides sqlglot's built-in version_sql generators to produce dialect-specific
time-travel query syntax. Uses sqlglot's exp.Version and Table.version slot.

Supported syntax by dialect:
    - BigQuery: table FOR SYSTEM_TIME AS OF timestamp (built-in)
    - Oracle: table AS OF TIMESTAMP timestamp / AS OF SCN scn
    - Snowflake: table AT (TIMESTAMP => timestamp) / BEFORE (...)
    - DuckDB: table AT (TIMESTAMP => timestamp)
    - CockroachDB (Postgres): table AS OF SYSTEM TIME timestamp
"""

from typing import TYPE_CHECKING

from sqlglot import exp

from sqlspec.builder._generation import invalidate_generator_dispatch

if TYPE_CHECKING:
    from sqlglot.generator import Generator
    from sqlglot.generators.bigquery import BigQueryGenerator
    from sqlglot.generators.duckdb import DuckDBGenerator
    from sqlglot.generators.oracle import OracleGenerator
    from sqlglot.generators.postgres import PostgresGenerator
    from sqlglot.generators.snowflake import SnowflakeGenerator

__all__ = ("create_temporal_table", "register_version_generators")


def _oracle_version_sql(self: "OracleGenerator", expression: exp.Version) -> str:
    """Oracle: AS OF TIMESTAMP timestamp or AS OF SCN scn."""
    expr = self.sql(expression, "expression")
    this = expression.name or "TIMESTAMP"
    return f"AS OF {this} {expr}"


def _bigquery_version_sql(self: "BigQueryGenerator", expression: exp.Version) -> str:
    """BigQuery: FOR SYSTEM_TIME AS OF timestamp."""
    expr = self.sql(expression, "expression")
    return f"FOR SYSTEM_TIME AS OF {expr}"


def _snowflake_version_sql(self: "SnowflakeGenerator", expression: exp.Version) -> str:
    """Snowflake: AT (TIMESTAMP => timestamp) or BEFORE (TIMESTAMP => ...).

    AS OF is mapped to AT, and BEFORE is supported for point-before queries.
    """
    kind = expression.text("kind")
    expr = self.sql(expression, "expression")
    this = expression.name or "TIMESTAMP"
    if kind and "BEFORE" in kind.upper():
        return f"BEFORE ({this} => {expr})"
    return f"AT ({this} => {expr})"


def _duckdb_version_sql(self: "DuckDBGenerator", expression: exp.Version) -> str:
    """DuckDB: AT (TIMESTAMP => timestamp)."""
    expr = self.sql(expression, "expression")
    return f"AT (TIMESTAMP => {expr})"


def _cockroachdb_version_sql(self: "PostgresGenerator", expression: exp.Version) -> str:
    """CockroachDB (via Postgres dialect): AS OF SYSTEM TIME timestamp."""
    expr = self.sql(expression, "expression")
    return f"AS OF SYSTEM TIME {expr}"


def _default_version_sql(self: "Generator", expression: exp.Version) -> str:
    """Default: AS OF SYSTEM TIME timestamp (CockroachDB style).

    When no dialect is specified, we default to CockroachDB/Postgres style
    which is commonly expected for time-travel queries.
    """
    expr = self.sql(expression, "expression")
    return f"AS OF SYSTEM TIME {expr}"


[docs] def create_temporal_table( table: "str | exp.Table | exp.Expr", as_of: "exp.Expr | str", kind: "str | None" = None ) -> exp.Table: """Create a table expression with temporal (time-travel) version clause. Args: table: Table name or expression. as_of: Timestamp or SCN expression for the point-in-time. kind: Optional version kind. Defaults to dialect-appropriate value. Returns: Table expression with version clause that generates dialect-specific SQL. """ register_version_generators() if isinstance(table, str): table_expr = exp.to_table(table) elif isinstance(table, exp.Table): table_expr = table.copy() else: table_expr = exp.to_table(str(table)) as_of_expr = exp.Literal.string(as_of) if isinstance(as_of, str) else as_of version = exp.Version(this=kind or "TIMESTAMP", kind="AS OF", expression=as_of_expr) table_expr.set("version", version) return table_expr
_VERSION_GENERATORS_REGISTERED = False
[docs] def register_version_generators() -> None: """Register dialect-specific version_sql generators. This function is idempotent - calling it multiple times has no effect after the first call. It is called automatically when the builder module is imported. Registers custom SQL generators for temporal (time-travel) queries: - Default (no dialect): AS OF SYSTEM TIME (CockroachDB style) - BigQuery: FOR SYSTEM_TIME AS OF - Oracle: AS OF TIMESTAMP / AS OF SCN - Snowflake: AT (TIMESTAMP => ...) / BEFORE (...) - DuckDB: AT (TIMESTAMP => ...) - Postgres/CockroachDB: AS OF SYSTEM TIME """ global _VERSION_GENERATORS_REGISTERED if _VERSION_GENERATORS_REGISTERED: return from sqlglot.dialects.bigquery import BigQuery from sqlglot.dialects.duckdb import DuckDB from sqlglot.dialects.oracle import Oracle from sqlglot.dialects.postgres import Postgres from sqlglot.dialects.snowflake import Snowflake from sqlglot.generator import Generator Generator.TRANSFORMS[exp.Version] = _default_version_sql BigQuery.Generator.TRANSFORMS[exp.Version] = _bigquery_version_sql Oracle.Generator.TRANSFORMS[exp.Version] = _oracle_version_sql Snowflake.Generator.TRANSFORMS[exp.Version] = _snowflake_version_sql DuckDB.Generator.TRANSFORMS[exp.Version] = _duckdb_version_sql Postgres.Generator.TRANSFORMS[exp.Version] = _cockroachdb_version_sql invalidate_generator_dispatch( Generator, BigQuery.Generator, Oracle.Generator, Snowflake.Generator, DuckDB.Generator, Postgres.Generator ) _VERSION_GENERATORS_REGISTERED = True