Skip to content

The SQL surface

SQL runs on DuckDB and the result says so. This emulator does not claim Snowflake SQL compatibility; it claims that statements real Snowflake accepts are either answered or refused by name, and parity is that list, generated by running each one against the built image.

A construct belongs in this emulator if and only if real Snowflake accepts it. Not “if a consumer wants it” — a convenience Snowflake does not offer would make a consumer that works here fail on the real thing, which is worse than failing in both places because it fails later and somewhere else.

The other direction is the same rule read backwards: SQL that Snowflake would reject is the product’s defect, not this emulator’s, and adding a Spark spelling here to make a model run would hide it.

Until v0.1.4 it was not one. The duckdb CLI the image pins exits 0 after refusing a statement — it writes the diagnosis to stderr and stops — so an unparseable statement produced empty stdout, which rendered as the acknowledgement shape:

{"success": true, "data": {"rowtype": [{"name": "status"}], "rowset": [["ok"]]}}

THIS IS NOT SQL AT ALL answered ok. So did CREATE TASK, Time Travel, TO_DATE, MERGEeighteen constructs, none of them implemented, none of them saying so. parity.md at the time listed four of them as not implemented and said nothing about the rest.

It survived a day of measurement because a developer’s duckdb exits 1. A probe run against a host build reported honest failures the shipped image did not give, and the difference was credited to the code. stderr is the signal now, and the parity probe runs against the image for the same reason.

Ordinary SELECT with CTEs and window functions; QUALIFY; LISTAGG, ARRAY_AGG, TRY_CAST; IFF, NVL, NVL2, ZEROIFNULL, NULLIFZERO; TO_DATE, TO_VARCHAR, TO_CHAR, TO_TIMESTAMP, PARSE_JSON, ARRAY_SIZE, ARRAY_GENERATE_RANGE, CHARINDEX; DATEDIFF; TABLE(GENERATOR (ROWCOUNT => n)) with SEQ4(); SHOW TABLES, DESCRIBE TABLE, information_schema.columns, SHOW FUNCTIONS, CREATE SCHEMA.

Plus stages, semi-structured, and tasks and streams.

Three-part names, and where a schema really is

Section titled “Three-part names, and where a schema really is”

TEST_DB is a fiction — the engine has no such catalog — so the database qualifier is removed before a statement reaches it. Two things about how:

Only the session’s own database is removed, and only from a three-part name. TEST_DB.GOLD.orders becomes GOLD.orders; OTHER_DB.PUBLIC.t is left whole, so the engine can say plainly that OTHER_DB does not exist rather than answering about a table that was never asked for. A struct access is a.b.c too, and the database test is what keeps v.customer.email intact.

The schema survives. Every schema used to be flattened into the default one, which meant TEST_DB.SILVER.orders and TEST_DB.GOLD.orders were a single table — selecting from the silver one returned the gold row, with no error anywhere. They are two tables now, and GOLD.orders names the same one as TEST_DB.GOLD.orders, as it would on an account.

PUBLIC is the exception, and deliberately: Snowflake’s default schema is PUBLIC and the engine’s is main, so the two are treated as one idea. SHOW OBJECTS maps it back, reporting PUBLIC for anything in the default schema and the real name for anything else.

Any schema works. It was once a list of four — PUBLIC, GOLD, SILVER, MAIN, three of them this family’s own names — and a project whose models lived in bronze or staging could not run here at all, for a reason its author had no way to see.

DATEADD(day | week | hour | minute | second, n, d) works and keeps the type Snowflake returns — DATE + INTEGER is a DATE in DuckDB, so a day offset stays a date.

MONTH, QUARTER and YEAR are refused. Every DuckDB spelling of them widens a DATE to a TIMESTAMP, and SQL unifies a CASE to one type, so there is no expression that returns a DATE for a DATE and a TIMESTAMP for a TIMESTAMP. Answering with the wrong type is worse than not answering: a value that is right and a type that is a lie is invisible to every row check.

A TIMESTAMP given to the day form is an error here where Snowflake would answer. That is a divergence, and it is recorded rather than smoothed over.

MERGE, OBJECT_CONSTRUCT, PUT, REMOVE, INFER_SCHEMA, Time Travel, CLONE, GRANT and roles, stored procedures. Each names itself when asked.