A DuckDB database file can hold no rows at all. That is a distribution format for a semantic layer, and it addresses a common last-mile problem on data platforms.
Views over raw Parquet paths read the files directly, while views over delta_scan read the Delta table through the Delta Kernel. Views over delta_scan keep the table semantics.
What the file actually is
A DuckDB table stores rows inside the file. A view stores the text of a query, so a database made only of views contains no data of its own, and the file stays small no matter how much the referenced dataset grows.
You build it by attaching a file, creating views with fully qualified locations, and uploading the result somewhere readable. Consumers attach it read only:
ATTACH 'https://example.com/catalogs/rail_catalog.duckdb' AS rail (READ_ONLY);
SELECT station_name, count(*) AS calls
FROM rail.services
GROUP BY station_name
ORDER BY calls DESC;
The part that matters on a client project is the indirection. Because a consumer queries the view instead of the files, you can repartition, move buckets, or rename a column and fix it in one place with CREATE OR REPLACE VIEW, mapping a renamed column back to the name consumers already use. The queries downstream don't change. That is a contract, and contracts are what break first when a platform grows.
The limits are plain in DuckDB's own write-up, and two of them shape every design decision below. Consumers need access to the catalog file and to every source its views reference. Connections over HTTPS and the S3 API are read only, so changing a view means editing a local copy and uploading it again.
What Parquet globs cost you on a governed lake
Here is where our view diverges from the straight reading of the announcement.
A view over a Delta table instead of a file glob looks like this, and it is an illustration of the shape rather than a snippet from a running job:
CREATE OR REPLACE VIEW shipments AS
SELECT ship_date, plant_id, sku, qty_shipped
FROM delta_scan('s3://client-lake/gold/shipments');
The two arguments are mutually exclusive, and a timestamp in the future is refused, which is the right default for anyone tempted to resolve "latest" at read time.
The catalog file still carries no credentials. Access to the underlying storage is a separate matter, which is the whole of the next section.
The access question decides the design
A shared catalog is only as good as the answer to "who can read what it points at".
The view-only file is the opposite. It uses documented DuckDB features such as views and read-only ATTACH, and the publisher decides exactly what is visible.
The scenario we would build it for
Take a manufacturer whose lakehouse is the system of record for shipments, and who owes a weekly dataset to a dozen distributors. The usual implementations are both bad. Either every distributor gets a login into the client's analytics estate, which means identity plumbing, row filters and a support burden for a dozen outside parties. Or somebody generates a dozen CSVs on a schedule, and within a quarter nobody can say which file was authoritative, three distributors are reading a column that was renamed, and the "pipeline" is one person's notebook.
Here is the design we would reach for. The catalog names the relations the distributor is supposed to use, with the joins and the column names already decided, and nothing else. The distributor gets one URL, attaches it read only, and queries shipments, invoices and returns.
When the client splits the weekly file into one per day, or moves the prefix, we replace the view and upload the catalog again. The distributors notice nothing. When a schema change renames a field, the view aliases it back and the distributors still notice nothing, which is the only reason the pattern is worth the setup over handing out paths.
The same shape works inside a client too. Semantic layers that live in people's notebooks drift. One that lives in a reviewed file holds still.
The cases that call for something else
Anything with a write path is out. The catalog cannot be modified in place over HTTPS or S3, which makes publishing a deliberate act, and that is fine for publishing and useless for anything interactive.
Row-level or column-level policy that has to follow the user needs another approach. A published catalog is one view of the world for whoever holds it, so per-user policy has to be expressed as separate publications with separate credentials, which stops being practical as the number of recipients grows.
Latency-sensitive work is the last case. A distributor running a wide scan from another continent over many small files will have a bad time, and the fix is layout work on our side instead of a different catalog format.
Start with one recipient and one view
If you have a dataset you currently hand out as paths or CSVs, publish one catalog file with one view over it, give it to one recipient, and change the underlying layout on purpose a week later to prove the indirection holds. That single exercise tells you whether the pattern fits your governance, because it forces you to answer the credential question before you have a dozen distributors depending on the answer.
If the data is sitting in Delta inside Unity Catalog, use delta_scan in place of a Parquet glob, and read Databricks' external access requirements first. We write more about how we build these platforms on /databricks.