Skip to content

Parity — v0.2.1

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.

FeatureDetailStatus
Seeded PAT🟢
Session context functions🟢
FeatureDetailStatus
SELECT, CTE, window🟢
QUALIFY🟢
TRY_CAST🟢
IFF / NVL / NVL2 / ZEROIFNULL🟢
TO_DATE / TO_VARCHAR / TO_TIMESTAMP🟢
DATEDIFF🟢
DATEADDDAY, 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🟢
MERGEduckdb: 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🟢
FeatureDetailStatus
VARIANT columns🟢
Colon path🟢
Colon path with a cast🟢
LATERAL FLATTEN over VARIANTvalue 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_CONSTRUCTduckdb: Catalog Error: Scalar Function with name object_construct does not exist!🔴
FeatureDetailStatus
CREATE STAGE🟢
LISTWalks 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 loads every file under itA stage reference matches every file whose path STARTS WITH it, which is Snowflake’s rule and covers a directory, a partial name and an exact file with one behaviour. It was REFUSED by name until a consumer needed it: a task body is a single statement, so one COPY INTO per part file turns an eight-table bronze into thirty-odd chained tasks — the emulator deciding the shape of a pipeline. The .gz that AUTO_COMPRESS leaves needs no special case, since the uncompressed name is a prefix of the compressed one.🟢
The prefix really loaded both parts🟢
PUTThe 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.🟢
REMOVEduckdb: Parser Error: syntax error at or near “REMOVE”🔴
INFER_SCHEMATYPE 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🟢
FeatureDetailStatus
TASK_HISTORY🟢
FeatureDetailStatus
SHOW TABLES🟢
DESCRIBE TABLE🟢
information_schema.columns🟢
SHOW FUNCTIONS🟢
CREATE SCHEMA🟢
Time Travelduckdb: Parser Error: syntax error at or near “OFFSET”🔴
CLONEduckdb: Parser Error: syntax error at or near “CLONE”🔴
GRANT / rolesduckdb: Parser Error: syntax error at or near “GRANT”🔴
FeatureDetailStatus
CREATE TASKSCHEDULE takes ’ SECOND | MINUTE | HOUR’. USING CRON is refused by name — a cron expression means specific wall-clock times, and firing on an interval instead would be a schedule that is not the one asked for. WHEN is refused for the same reason in the other direction: a predicate that is never evaluated makes a conditional task unconditional.🟢
Task graphs (AFTER)🟢
ALTER TASK RESUME / SUSPEND🟢
SHOW TASKS🟢
EXECUTE TASKRuns 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 ranSelected 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 loadedCounted 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 PROJECTdbt 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 modelsRead 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 QUERYbuild 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 insertedCounted 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 STREAMAPPEND-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 proceduresduckdb: Catalog Error: Table Function with name system$wait does not exist!🔴
FeatureDetailStatus
CREATE / SHOW / SUSPEND🟢

77 of 84 answered.