Skip to content

Views & references

Two kinds of asset besides the one that writes rows. A view materializes its definition: its destination creates a warehouse view from what data() returns. A reference materializes nothing: it points at a table that already exists and reads it. Both are assets in every other respect, with lineage, relations, a schema and a place in the catalog.

Asset View Reference
data() returns rows a view definition nothing to return
A run conforms and writes creates or replaces the view does nothing
upstream.data the data read the destination's reference to the upstream

Views

@il.source
class Reporting(il.Source):
    @il.view(relations={"orders": il.Relation("asset", "shop.orders", many=True)})
    def order_totals(self, orders: list[il.Upstream]) -> str:
        return " UNION ALL ".join(f"SELECT date, total FROM {upstream.data.name}" for upstream in orders)

A view has exactly one destination, declared like any asset's. Each upstream parameter receives the destination's reference to that upstream in data: on a SQL warehouse a TableReference, whose name is the qualified table, quoted for the dialect. The function composes the definition from those references; a many-valued relation is a list, and the author writes the UNION ALL.

A raw-SQL view is written in one dialect. dialect="bigquery" pins it to BigQuery destinations; the default, None, is the author's assertion that the SQL is portable, and it is checked against no dialect.

A view is unpartitioned and reads no upstream data, so it may sit on partitioned upstreams: it refers to the whole table, and the warehouse resolves the query. It is recreated on every run, cheaply and idempotently, so it follows the accounts bound to it. Put it in a job after its connectors.

What the graph checks, when it is built: the view has one destination; its dialect, when declared, is the destination's; every bound upstream is reachable from that destination (reaches, decided from configuration: same BigQuery location, same DuckDB database). Anything the query gets wrong is the warehouse's verdict when the view is created, reported as the view's failure.

Portable views

interloper-sql adds ibis_view: each upstream's data is an ibis.Table typed from the upstream's declared schema, the function returns an Ibis expression, and the destination compiles it for its own dialect. The expression's columns must match the view's declared schema. The base package depends on Ibis only; the SQLAlchemy destination lives behind interloper-sql[postgres], so a package that defines ibis_views pulls in no database driver.

from interloper_sql import ibis_view, union

@ibis_view(schema=Totals, relations={"orders": il.Relation("asset", "shop.orders", many=True)})
def totals(self, orders: list[il.Upstream]) -> ibis.Table:
    rows = union(orders, account_id=lambda upstream: upstream.asset.source.discriminator)
    return rows.group_by([rows.date, rows.account_id]).aggregate(total=rows.total.sum())

Every upstream of a ibis_view must declare a schema: that is what types its table.

References

@il.source
class GoogleAnalytics4Export(il.Source):
    property_id: str = il.InputField(discriminator=True)

    @il.reference(table="events_*", dataset="analytics_{property_id}", schema=Events)
    def events(self) -> None:
        """The property's raw export, read and never written."""

A reference names its table as written, wildcard and all; the dataset is the asset's, the source's by default. Either may name a field of the source in braces (dataset="analytics_{property_id}"), filled per instance, which is how the GA4 export points events at the property's own dataset. Nothing is created or written. Downstream assets read it like any asset, and views build on it like any table.

A source whose tables exist in one warehouse only says so with a narrowed destinations relation, checked when a destination is bound: the GA4 export declares relations={"destinations": il.Relation("destination", "bigquery_destination", many=True, optional=True)} on its @il.source, so the app offers it BigQuery destinations only.

Backends

Destination Views
BigQuery yes; reaches the same location
DuckDB yes; reaches the same database
Snowflake, ClickHouse, Databricks, Fabric, SQL not yet: table_parts, replace_view and reaches to implement
GCS, S3, CSV, files, Sheets, Memory no: a view there is a configuration error