Skip to main content

❄️🦆 SnowDuck

CI/CD Pipeline Python 3.11+ License: MIT Code style: ruff

Run Snowflake SQL locally, powered by DuckDB

SnowDuck is a lightweight, in-memory SQL engine that emulates Snowflake's behavior for development and testing. Write and test Snowflake SQL locally without cloud access or costs.

Why SnowDuck?

  • 🚀 Fast Development - Test SQL queries instantly without waiting for cloud connections
  • 💰 Zero Cloud Costs - Develop and test locally without Snowflake compute charges
  • 🧪 Easy Testing - Mock Snowflake databases for unit tests and CI/CD pipelines
  • ⚡ Lightning Fast - Powered by DuckDB's in-memory execution engine
  • 🔌 Drop-in Compatible - Uses Snowflake's connector interface - just patch and go

Features

Core SQL Support

Category Functions
DDL Operations CREATE/DROP DATABASE, SCHEMA, TABLE, VIEW, SEQUENCE, STAGE; ALTER TABLE ADD/DROP/RENAME/ALTER COLUMN; TRUNCATE TABLE [IF EXISTS]
DML Operations INSERT, UPDATE, DELETE, MERGE, INSERT OVERWRITE
Comments Inline COMMENT = '...' / COMMENT '...' on tables, views and columns, and COMMENT ON - read back through SHOW and INFORMATION_SCHEMA (dbt persist_docs)
Advanced SQL CTEs, JOINs, subqueries, CASE, QUALIFY, LIKE ANY/ALL
SQL UDFs CREATE FUNCTION ... AS $$ ... $$ (scalar and table)
Table Functions TABLE(...), FLATTEN, SPLIT_TO_TABLE, LATERAL FLATTEN
Session Variables SET/SELECT $variable syntax
Session Parameters ALTER SESSION SET/UNSET, SHOW PARAMETERS
Catalog (SHOW) OBJECTS, TABLES, VIEWS, SCHEMAS, DATABASES, COLUMNS, [USER] FUNCTIONS, SEQUENCES, STAGES, WAREHOUSES, PARAMETERS, VARIABLES - with TERSE, LIKE, STARTS WITH, LIMIT ... FROM
Information Schema Per-database INFORMATION_SCHEMA: DATABASES, SCHEMATA, TABLES, VIEWS, COLUMNS, FUNCTIONS, SEQUENCES, TABLE_CONSTRAINTS, KEY_COLUMN_USAGE, REFERENTIAL_CONSTRAINTS, ENABLED_ROLES, and more

Function Support

Category Functions
String CONCAT, CONCAT_WS, SPLIT, SPLIT_PART, CONTAINS, REPLACE, TRIM, LTRIM, RTRIM, LPAD, RPAD, SPACE, STRTOK, TRANSLATE, REVERSE, STARTSWITH, ENDSWITH, ASCII, CHR, INITCAP, SOUNDEX, UPPER, LOWER, LENGTH, LEN, SUBSTR, SUBSTRING, INSTR, POSITION, INSERT, RTRIMMED_LENGTH, EDITDISTANCE, JAROWINKLER_SIMILARITY, PARSE_URL
Date/Time DATEADD, DATEDIFF, TIMEDIFF, DATE_TRUNC, DATE_PART, EXTRACT, LAST_DAY, NEXT_DAY, PREVIOUS_DAY, DAYNAME, MONTHNAME, TIME_SLICE, YEAROFWEEK, ADD_MONTHS, DATE_FROM_PARTS, TIME_FROM_PARTS, TIMESTAMP_FROM_PARTS, CONVERT_TIMEZONE, TO_DATE, TO_TIMESTAMP
Numeric ABS, CEIL, FLOOR, ROUND, MOD, SQRT, POWER, EXP, LN, LOG, SIGN, DIV0, DIV0NULL, WIDTH_BUCKET, TRUNCATE, CBRT, FACTORIAL, DEGREES, RADIANS, PI, RANDOM, GREATEST, LEAST
Aggregate COUNT, SUM, AVG, MIN, MAX, MEDIAN, LISTAGG, ANY_VALUE, KURTOSIS, SKEW, COVAR_POP, COVAR_SAMP
Window ROW_NUMBER, RANK, DENSE_RANK, LEAD, LAG, FIRST_VALUE, LAST_VALUE
JSON PARSE_JSON, OBJECT_CONSTRUCT, OBJECT_CONSTRUCT_KEEP_NULL, OBJECT_INSERT, OBJECT_DELETE, OBJECT_PICK, OBJECT_AGG, GET_PATH, TRY_PARSE_JSON, OBJECT_KEYS, CHECK_JSON, TO_JSON, TO_OBJECT
VARIANT IS_ARRAY, IS_OBJECT, IS_NULL_VALUE, IS_INTEGER, IS_DOUBLE, IS_DECIMAL, IS_REAL, IS_BOOLEAN, IS_VARCHAR, IS_CHAR, IS_DATE, IS_TIME, IS_TIMESTAMP_NTZ/LTZ/TZ, IS_BINARY, AS_VARCHAR, AS_CHAR, AS_INTEGER, AS_DOUBLE, AS_REAL, AS_DECIMAL, AS_NUMBER, AS_BOOLEAN, AS_ARRAY, AS_OBJECT, AS_DATE, AS_TIME, AS_TIMESTAMP_NTZ/LTZ/TZ, AS_BINARY, GET, GET_PATH, MAP_CAT, TYPEOF
Array ARRAY_CONSTRUCT, ARRAY_SIZE, ARRAY_CONTAINS, FLATTEN, ARRAY_SLICE, ARRAY_CAT, ARRAY_APPEND, ARRAY_PREPEND, ARRAY_SORT, ARRAY_REVERSE, ARRAY_MIN, ARRAY_MAX, ARRAY_SUM, ARRAYS_OVERLAP, ARRAY_DISTINCT, ARRAY_INTERSECTION, ARRAY_EXCEPT
Conditional NVL, NVL2, DECODE, IFF, COALESCE, NULLIF, EQUAL_NULL, ZEROIFNULL, NULLIFZERO, BOOLAND, BOOLOR, BOOLXOR, BOOLNOT
Conversion TO_CHAR, TO_VARCHAR (with Snowflake's numeric format models), TO_NUMBER, TO_DECIMAL, TO_NUMERIC, TO_BOOLEAN, TO_DATE, TO_TIME, TIME, TO_BINARY, TRY_CAST, TRY_TO_NUMBER, TRY_TO_DECIMAL, TRY_TO_DATE, TRY_TO_TIMESTAMP, TRY_TO_BOOLEAN, TRY_TO_BINARY
Regex REGEXP_LIKE, RLIKE, REGEXP_SUBSTR, REGEXP_SUBSTR_ALL, REGEXP_REPLACE, REGEXP_COUNT, REGEXP_INSTR
Hash MD5, SHA1, SHA2, SHA256, HASH
Encoding BASE64_ENCODE, BASE64_DECODE_STRING, HEX_ENCODE, HEX_DECODE_STRING
Bitwise BITAND, BITOR, BITXOR, BITNOT, BITAND_AGG, BITOR_AGG, BITXOR_AGG
Boolean Agg BOOLAND_AGG, BOOLOR_AGG
Utility UUID_STRING, TYPEOF, HLL, HLL_ACCUMULATE, HLL_COMBINE, HLL_ESTIMATE, GET_DDL, COLLATION, OCTET_LENGTH
Context CURRENT_VERSION, CURRENT_ACCOUNT, CURRENT_CLIENT, CURRENT_SESSION, CURRENT_REGION, CURRENT_ROLE, CURRENT_DATABASE, CURRENT_SCHEMA, CURRENT_WAREHOUSE

Snowflake Semantics, Not Just Snowflake Syntax

SnowDuck matches Snowflake's behaviour, not only its function names. The cases below all return a different answer under a naive DuckDB translation, so they are pinned by an executable conformance suite (tests/conformance/):

Snowflake behaviour Result
REGEXP_LIKE anchors the pattern at both ends REGEXP_LIKE('xabcx','a.c') → FALSE
REGEXP_SUBSTR returns NULL when nothing matches REGEXP_SUBSTR('abc','[0-9]+') → NULL
REGEXP_REPLACE replaces every occurrence by default REGEXP_REPLACE('a1b2','[0-9]','X') → aXbX
TO_NUMBER defaults to NUMBER(38,0) and rounds TO_NUMBER('123.45') → 123
TO_NUMBER(x, p, s) yields exact DECIMAL, not DOUBLE TO_NUMBER('123.45',10,2) → 123.45
OBJECT_CONSTRUCT drops NULL-valued keys OBJECT_CONSTRUCT('a',1,'b',NULL) → {"a":1}
OBJECT_CONSTRUCT_KEEP_NULL keeps them → {"a":1,"b":null}
DAYNAME/MONTHNAME return 3-letter abbreviations DAYNAME(...) → Mon
GET indexes arrays from 0 GET(ARRAY_CONSTRUCT('a','b'),1) → b
CONCAT_WS does not skip NULLs CONCAT_WS('-','a',NULL) → NULL
DIV0NULL returns 0, not NULL DIV0NULL(10,0) → 0
FLATTEN exposes all six columns SEQ, KEY, PATH, INDEX, VALUE, THIS
OBJECT_CONSTRUCT drops NULLs top-level only nested nulls survive
Arrays are heterogeneous ARRAY_CONSTRUCT(1,'two') → [1,"two"]
A format model sets decoration, not scale TO_NUMBER('$1,234.56','$9,999.99') → 1235
TO_CHAR's format model is the layout TO_CHAR(1234.56,'$9,999.99') → ' $1,234.56'
A value too wide for the model renders as # TO_CHAR(12345.67,'9999.99') → '########'
AS_<type> checks the stored type, not casts AS_INTEGER(PARSE_JSON('"5"')) → NULL
GET reads object members as well as elements GET(PARSE_JSON('{"a":7}'),'a') → 7
OBJECT_CONSTRUCT(*) builds an object per row {"A":1,"B":"x"}
Inline comments reach the catalog CREATE TABLE t (id INT COMMENT 'c') → readable via INFORMATION_SCHEMA

Snowflake Identifier Semantics

Unquoted identifiers fold to upper case, quoted ones keep their case - the same rule Snowflake applies:

CREATE TABLE my_model (id INT);   -- creates MY_MODEL with column ID
CREATE TABLE "MixedCase" (x INT); -- creates MixedCase

This is what makes a name read back out of the catalog match the name a client builds for it. dbt-snowflake, for instance, lists a schema with SHOW OBJECTS and then looks each relation up under the upper-cased name its own Relation renders to.

SQL User-Defined Functions

cur.execute("""
    CREATE OR REPLACE FUNCTION is_valid_id(id VARCHAR)
    RETURNS BOOLEAN
    AS $$ LENGTH(id) = 11 AND REGEXP_LIKE(id, '[0-9]+') $$
""")

cur.execute("SELECT is_valid_id('12345678901')")  # -> True

Scalar and table UDFs are compiled to DuckDB macros, and the body is translated through the same pipeline as any other statement - so Snowflake functions used inside a UDF work too. Non-SQL UDFs (JavaScript, Python) raise a clear error rather than silently returning the wrong thing.

Cursor Methods

SnowDuck supports all standard Snowflake cursor methods:

  • execute() - Execute SQL statements
  • fetchone() - Fetch a single row
  • fetchmany(size) - Fetch multiple rows
  • fetchall() - Fetch all rows
  • fetch_pandas_all() - Fetch all rows as pandas DataFrame
  • fetch_pandas_batches() - Fetch rows as iterator of DataFrames
  • get_result_batches() - Get Arrow record batches
  • describe() - Get result schema without execution

Note: SnowDuck is designed for development and testing. Use production Snowflake for production workloads.

Quick Start

Installation

# Using uv (recommended)
uv pip install snowduck

# Or using pip
pip install snowduck

Basic Usage

import snowflake.connector
from snowduck import start_patch_snowflake

# Patch the Snowflake connector to use DuckDB
start_patch_snowflake()

# Use Snowflake connector as normal - it's now backed by DuckDB!
with snowflake.connector.connect() as conn:
    cursor = conn.cursor()
    
    cursor.execute("CREATE DATABASE my_database")
    cursor.execute("USE DATABASE my_database")
    
    cursor.execute("""
        CREATE TABLE employees (id INTEGER, name VARCHAR, salary INTEGER)
    """)
    
    cursor.execute("""
        INSERT INTO employees VALUES
        (1, 'Alice', 95000),
        (2, 'Bob', 75000),
        (3, 'Carol', 105000)
    """)
    
    cursor.execute("""
        SELECT name, salary, RANK() OVER (ORDER BY salary DESC) as rank
        FROM employees
    """)
    
    for row in cursor.fetchall():
        print(f"{row[0]}: \${row[1]:,} (Rank: {row[2]})")

Data Persistence

# In-memory (default) - fast, isolated
start_patch_snowflake()

# File-based - persistent across restarts, including CREATE DATABASE
# (each database is stored beside it as my_data.<NAME>.duckdb)
start_patch_snowflake(db_file='my_data.duckdb')

# Fresh start - reset existing data
start_patch_snowflake(db_file='my_data.duckdb', reset=True)

Test Data Seeding

from snowduck import seed_table

with snowflake.connector.connect() as conn:
    # From dict
    seed_table(conn, 'customers', {
        'id': [1, 2, 3],
        'name': ['Acme', 'TechStart', 'DataCo']
    })
    
    # From pandas DataFrame
    seed_table(conn, 'orders', df)

Testing

Using the Decorator

from snowduck import mock_snowflake

@mock_snowflake
def test_query():
    conn = snowflake.connector.connect()
    cursor = conn.cursor()
    cursor.execute("SELECT 1")
    assert cursor.fetchone()[0] == 1

Using the Context Manager

from snowduck import patch_snowflake

def test_with_fixture():
    with patch_snowflake():
        conn = snowflake.connector.connect()
        # Test code here

pytest Fixture

import pytest
from snowduck import patch_snowflake

@pytest.fixture
def conn():
    with patch_snowflake():
        yield snowflake.connector.connect()

def test_feature(conn):
    cursor = conn.cursor()
    cursor.execute("SELECT 1")

REST API Server

# Install with server extras
uv pip install snowduck[server]

# Start the server
uvicorn snowduck.server:app --reload

The server provides:

  • Execute SQL queries via REST API
  • Arrow IPC format responses
  • Multi-session support

Architecture

┌─────────────────────┐
│  Your Application   │
│  (Snowflake code)   │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  SnowDuck Patch     │  ← Intercepts connector calls
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  SQL Translator     │  ← AST rewrites, then sqlglot's
└──────────┬──────────┘    DuckDB generator
           │
           │
           ▼
┌─────────────────────┐
│   DuckDB Engine     │  ← Fast in-memory execution
└─────────────────────┘

Examples

See the examples/ directory for Jupyter notebooks demonstrating:

  • Basic operations and queries
  • String, date, and numeric functions
  • JSON and array operations
  • Window functions
  • Advanced SQL patterns

Development

git clone https://github.com/hupe1980/snowduck.git
cd snowduck
uv sync
just test
just check

Contributing

Contributions welcome! See issues for areas where help is needed.

License

MIT License - see LICENSE for details.

Metadata

Release files for snowduck 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for snowduck 0.3.0
File Size Uploaded
snowduck-0.3.0.tar.gz 403.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for snowduck 0.3.0
File Interpreter ABI Platform
snowduck-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 552.9 kB

Release files / snowduck-0.3.0.tar.gz

Download URL snowduck-0.3.0.tar.gz
Size 403.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d71d81a7fe2f8a0efa66a6cea2faf8abfc739a4aaa626c9cda37d16babad4e93
BLAKE2b-256 checksum
How to use checksums
b80743a53d7b1065adfff584edbb7ecb73724e3946c464119f9904a6e49f211c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.

Transparency log

Release files / snowduck-0.3.0-py3-none-any.whl

Download URL snowduck-0.3.0-py3-none-any.whl
Size 149.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e234d6a1450a4d45b00402946e74930b5428a09a3c44899cf6ad9cbfc6e51fa8
BLAKE2b-256 checksum
How to use checksums
b9bb5e838809a9a05f7f50b020f5b9ac900c9e08bf25bc96455e30a2d5776813
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page