Parity — v0.2.0
Every row below is a statement real Snowflake accepts, run against the image this repository builds. 🟢 means the emulator answered it; 🔴 means it refused, by name, with the reason recorded.
A refusal is not a bug in itself — this family would rather fail honestly than answer wrongly. A 🔴 with a reason is a gap someone can plan around; a silent success is one nobody can see.
Every 🟢 names a witness in docs/witnesses.json, which is generated from
the same run.
Identity
Section titled “Identity”| Feature | Detail | Status |
|---|---|---|
| Seeded PAT | 🟢 | |
| Session context functions | 🟢 |
| Feature | Detail | Status |
|---|---|---|
| SELECT, CTE, window | 🟢 | |
| QUALIFY | 🟢 | |
| TRY_CAST | 🟢 | |
| IFF / NVL / NVL2 / ZEROIFNULL | 🟢 | |
| TO_DATE / TO_VARCHAR / TO_TIMESTAMP | 🟢 | |
| DATEDIFF | 🟢 | |
| DATEADD | DAY, WEEK, HOUR, MINUTE and SECOND. MONTH, QUARTER and YEAR are refused: every DuckDB spelling of them widens a DATE to a TIMESTAMP, and a CASE cannot return two types, so the answer would carry the wrong type for a DATE argument. A TIMESTAMP given to the day form is an error here where Snowflake would answer. | 🟢 |
| GENERATOR / SEQ4 | 🟢 | |
| A date series, the way core’s silver builds one | 🟢 | |
| DATEADD(month, …) is refused | 🟢 | |
| LISTAGG / ARRAY_AGG | 🟢 | |
| MERGE | duckdb: Parser Error: syntax error at or near “MERGE” | 🔴 |
| Decimal keeps its scale | 🟢 | |
| NULL is null, not a word | 🟢 | |
| Unparseable SQL is refused | 🟢 | |
| Snowflake scalar type names in DDL | 🟢 |
Semi-structured
Section titled “Semi-structured”| Feature | Detail | Status |
|---|---|---|
| VARIANT columns | 🟢 | |
| Colon path | 🟢 | |
| Colon path with a cast | 🟢 | |
| LATERAL FLATTEN over VARIANT | value and index only. SEQ, KEY, PATH and THIS are not produced, and OUTER, RECURSIVE, PATH and MODE are refused by name because each changes which rows come back. | 🟢 |
| LATERAL FLATTEN over an array | 🟢 | |
| ARRAY_GENERATE_RANGE | 🟢 | |
| PARSE_JSON | 🟢 | |
| OBJECT_CONSTRUCT | duckdb: Catalog Error: Scalar Function with name object_construct does not exist! | 🔴 |
Stages
Section titled “Stages”| Feature | Detail | Status |
|---|---|---|
| CREATE STAGE | 🟢 | |
| LIST | Walks the whole stage directory, so a named stage created inside it appears in the user stage’s listing. | 🟢 |
| CREATE FILE FORMAT | 🟢 | |
| COPY INTO from an internal stage | 🟢 | |
| COPY INTO with a named format | 🟢 | |
| COPY INTO JSON | 🟢 | |
| External stages are refused | 🟢 | |
| An unsupported format option is refused | 🟢 | |
| A prefix is refused | Real Snowflake resolves a stage reference by prefix and loads EVERY file under it, which is the ordinary way to load a paged feed. This resolves one name and the .gz AUTO_COMPRESS leaves, so a prefix is refused BY NAME. It used to come back as duckdb’s own words about a path inside the container, which left the reader to work out that the feature was missing rather than the file. | 🟢 |
| PUT | The driver uploads the bytes itself, as it does against a real account: the answer names LOCAL_FS and the stage directory, and the connector’s file transfer agent does the copying. AUTO_COMPRESS defaults to TRUE, so the stage holds <name>.gz. Set SNOWFLAKE_STAGE_CLIENT_DIR when the client sees the stage at a different path than the server does. | 🟢 |
| REMOVE | duckdb: Parser Error: syntax error at or near “REMOVE” | 🔴 |
| INFER_SCHEMA | TYPE reports the names an ACCOUNT reports — NUMBER(38,0), TIMESTAMP_NTZ — so a CREATE TABLE built from it is portable. DESCRIBE TABLE still reports the ENGINE’s names (DECIMAL(38,0)), and deliberately: the family’s money_is_never_stored_as_float contract accepts only decimal and numeric prefixes, so renaming what DESCRIBE reports would fail 52 gold contracts. The two statements answer in different vocabularies and that is recorded rather than reconciled. | 🟢 |
| INFER_SCHEMA composes (WHERE over the result) | 🟢 | |
| INFER_SCHEMA without FILE_FORMAT is refused | 🟢 |
Tasks and streams
Section titled “Tasks and streams”| Feature | Detail | Status |
|---|---|---|
| TASK_HISTORY | 🟢 |
Catalog
Section titled “Catalog”| Feature | Detail | Status |
|---|---|---|
| SHOW TABLES | 🟢 | |
| DESCRIBE TABLE | 🟢 | |
| information_schema.columns | 🟢 | |
| SHOW FUNCTIONS | 🟢 | |
| CREATE SCHEMA | 🟢 | |
| Time Travel | duckdb: Parser Error: syntax error at or near “OFFSET” | 🔴 |
| CLONE | duckdb: Parser Error: syntax error at or near “CLONE” | 🔴 |
| GRANT / roles | duckdb: Parser Error: syntax error at or near “GRANT” | 🔴 |
Orchestration
Section titled “Orchestration”| Feature | Detail | Status |
|---|---|---|
| CREATE TASK | SCHEDULE takes ’ | 🟢 |
| Task graphs (AFTER) | 🟢 | |
| ALTER TASK RESUME / SUSPEND | 🟢 | |
| SHOW TASKS | 🟢 | |
| EXECUTE TASK | Runs the named task and everything downstream of it, as Snowflake does. A resumed root task also fires on its own interval. Its runs, and the runs of everything downstream, are readable through TASK_HISTORY(). | 🟢 |
| DROP TASK | 🟢 | |
| USING CRON is refused | 🟢 | |
| A manual task (no SCHEDULE, no AFTER) | The body is the whole statement after the task’s own AS, including a body carrying its own AS — a CREATE TABLE … AS SELECT, which is what a dbt model compiles to. It was not: the optional properties group was greedy, so a task with no WAREHOUSE swallowed the body’s first AS, stored a truncated statement, ran that, and reported SUCCEEDED. | 🟢 |
| EXECUTE TASK on a manual task | 🟢 | |
| A manual task with a CTAS body | 🟢 | |
| EXECUTE TASK runs the CTAS | 🟢 | |
| The CTAS body actually ran | Selected back from the table the body creates, so this cannot pass on a task that succeeded while running a different statement. Every other probe here is judged on whether the STATEMENT succeeded, which is one level away from whether anything happened. | 🟢 |
| A task body loads from a stage | 🟢 | |
| EXECUTE TASK runs the COPY INTO | 🟢 | |
| The task’s COPY INTO actually loaded | Counted from the table the task loaded into. The body used to go straight to duckdb, which answers COPY INTO with a syntax error at INTO — while the identical statement outside a task loaded fine. | 🟢 |
| A task body reads a stream | 🟢 | |
| EXECUTE TASK runs the stream read | 🟢 | |
| CREATE DBT PROJECT | 🟢 | |
| SHOW DBT PROJECTS | 🟢 | |
| EXECUTE DBT PROJECT | dbt runs in THIS image, on argv, the same way duckdb does — no second service and no network hop between the statement and what executes it. The profile is generated under the name the PROJECT declares, so a project runs here without being edited to say an emulator-specific one. | 🟢 |
| dbt really built the models | Read back from the model dbt was asked to build, so this cannot pass on a run that reported success and built nothing. | 🟢 |
| A dbt failure fails the QUERY | build is not one of run, test or deps, so it is refused by name. Snowflake made dbt errors query failures in October 2025 precisely so tasks could handle them: a failed run that returned Success = FALSE from a SUCCESSFUL statement let a task graph run its downstream nodes anyway. | 🟢 |
| A dbt task with no warehouse is refused | 🟢 | |
| A task body runs EXECUTE DBT PROJECT | 🟢 | |
| EXECUTE TASK runs the dbt project | 🟢 | |
| DROP DBT PROJECT | 🟢 | |
| The task’s stream read actually inserted | Counted from the table the task inserted into. The body used to go straight to duckdb, which has no table for a stream name at all. | 🟢 |
| CREATE STREAM | APPEND-ONLY, and it proves it rather than assuming it. DuckDB keeps no change log, so the stream remembers the first rowid it has not shown and a checksum of the rows it has. If a row before that point is updated or deleted the stream REFUSES TO BE READ, naming what happened — Snowflake would report those as DELETE and INSERT rows, and answering without them would silently drop the change. METADATA$ACTION is always INSERT for the same reason. | 🟢 |
| Reading a stream | 🟢 | |
| SYSTEM$STREAM_HAS_DATA | 🟢 | |
| SHOW STREAMS | 🟢 | |
| DROP STREAM | 🟢 | |
| Stored procedures | duckdb: Catalog Error: Table Function with name system$wait does not exist! | 🔴 |
Warehouses
Section titled “Warehouses”| Feature | Detail | Status |
|---|---|---|
| CREATE / SHOW / SUSPEND | 🟢 |
76 of 83 answered.