Skip to content

Query Builder

Snowman provides a query builder that maintains the syntax of SQL. You can generate INSERT / UPDATE / DELETE / TRUNCATE queries, but it does not currently official support complex query syntax like SELECT.

Tip

The query builder is based on simple rules.

  • SQL reserved words can be written in a way that can be accessed like properties with a dot . in Python.
  • If a SQL reserved word matches a Python reserved word, an underscore _ is added to the end of the sql reserved word.

If you know SQL, you can build Snowman queries with a fluent python coding 🚀

Note

The paramstyle used by Snowman is format. The reason for not supporting pyformat is to avoid key duplication when complex query patterns occur.

In other words, it uses %s to embed parameters, not %(name)s.

Builder Examples

Select Query

🚧 This is a draft. This interface is subject to disruptive changes. 🚧

With the current Python features, it is not possible to write complex queries that include only specific fields or table joins in a type-safe manner. SELECT is an experimental feature, and may be implemented in the future, but it is believed that Python Type Hint evolution is necessary for that.

Example

from snowman.query import select
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    user: User | None = (
        select()
        .from_(User)
        .where(
            lambda c: (c.self.id == 1)
            .and_(c.self.name == "John Doe")
            .and_(c.self.age >= 18),
        )
        .order.by(
            lambda c: [
                c.self.id.desc,
                c.self.name.asc.nulls.last,
            ],
        )
        .limit(1)
        .offset(0)
        .execute(cursor)
        .fetchone()
    )

Example

from snowman.query import select
from snowman.query.minify import minify
from your.database.schema import User

query, params = (
    select()
    .from_(User)
    .where(
        lambda c: (c.self.id == 1)
        .and_(c.self.name == "John Doe")
        .and_(c.self.age >= 18),
    )
    .order.by(
        lambda c: [
            c.self.id.desc,
            c.self.name.asc.nulls.last,
        ],
    )
    .limit(1)
    .offset(0)
).build()

expected = minify(
    """
    SELECT
        *
    FROM
        DATABASE.SCHEMA.USER
    WHERE
        ID = %s
        AND NAME = %s
        AND AGE >= %s
    ORDER BY
        ID DESC,
        NAME ASC NULLS LAST
    LIMIT %s
    OFFSET %s
    """,
)

assert query == expected
assert params == (1, "John Doe", 18, 1, 0)

Insert Query

Example

from snowman.query import insert
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    insert.into(User).values(
        User(
            id=1,
            name="John Doe",
        ),
    ).execute(cursor)

Example

import datetime

from snowman.query import insert
from snowman.query.minify import minify
from your.database.schema import User

now = datetime.datetime.now()

query, params = (
    insert.into(
        User,
    ).values(
        User(
            id=1,
            name="John Doe",
            created_at=now,
        ),
    )
).build()

expected = minify(
    """
    INSERT INTO
        DATABASE.SCHEMA.USER
    (
        ID,
        NAME,
        AGE,
        CREATED_AT
    )
    VALUES (
        %s,
        %s,
        %s,
        %s
    )
    """,
)

assert query == expected
assert params == (1, "John Doe", None, now)

Example

from snowman.query import insert
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    insert.into(User).values(
        [
            User(id=1, name="John Doe"),
            User(id=2, name="Alice Smith"),
        ],
    ).execute(cursor)

Update Query

Example

import datetime

from snowman.query import update
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    update(
        User,
    ).set(
        {"name": "Jane Doe"},
    ).where(
        lambda c: (c.self.name.in_(["Jane", "Doe"]))
        .and_(c.self.age > 18)
        .and_(c.self.created_at >= datetime.datetime(2001, 1, 1)),
    ).execute(cursor)

Example

import datetime

from snowman.query import update
from snowman.query.minify import minify
from your.database.schema import User

query, params = (
    update(
        User,
    )
    .set(
        {"name": "Jane Doe"},
    )
    .where(
        lambda c: (c.self.name.in_(["Jane", "Doe"]))
        .and_(c.self.age > 18)
        .and_(c.self.created_at >= datetime.datetime(2001, 1, 1)),
    )
).build()

expected = minify(
    """
    UPDATE
        DATABASE.SCHEMA.USER
    SET
        NAME = %s
    WHERE
        NAME IN (%s)
        AND AGE > %s
        AND CREATED_AT >= %s
    """,
)

assert query == expected
assert params == ("Jane Doe", ["Jane", "Doe"], 18, datetime.datetime(2001, 1, 1))

Delete Query

Example

from snowman.query import delete
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    delete.from_(
        User,
    ).where(
        lambda c: c.self.id == 1,
    ).execute(cursor)

Example

from snowman.query import delete
from snowman.query.minify import minify
from your.database.schema import User

query, params = (
    delete.from_(
        User,
    ).where(
        lambda c: c.self.id == 1,
    )
).build()

expected = minify(
    """
    DELETE FROM
        DATABASE.SCHEMA.USER
    WHERE
        ID = %s
    """,
)

assert query == expected
assert params == (1,)

Truncate Query

Example

from snowman.query import truncate
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    truncate.table.if_.exists(User).execute(cursor)

Example

from snowman.query import truncate
from your.database.schema import User

query, params = truncate.table.if_.exists(User).build()

expected = "TRUNCATE TABLE IF EXISTS DATABASE.SCHEMA.USER"

assert query == expected
assert params == ()

Where Clause

There are several ways to generate the WHERE clause. For simple conditions, you can complete everything in Python, and benefit from static analysis.

The method of generating the WHERE clause using function like lambda is the easiest way to write type safe queries without importing other modules. The argument name of the lambda function is recommended to use c (the first letter of Context).

Example

import datetime

from snowman.query import update
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    update(
        User,
    ).set(
        {"name": "Jane Doe"},
    ).where(
        lambda c: (c.self.name.in_(["Jane", "Doe"]))
        .and_(
            c.group(
                (c.self.age.is_.not_.null)
                .and_(c.self.age >= 18)
                .and_(c.self.age <= 100),
            ),
        )
        .and_(c.self.created_at >= datetime.datetime(2001, 1, 1)),
    ).execute(cursor)

Tip

c.self is an abbreviation for c(User).

This is useful when writing repeatedly, and can be easily written with dot chain.

If you explicitly state that it is a column of User, it will be as follows.

import datetime

from snowman.query import update
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    update(
        User,
    ).set(
        {"name": "Jane Doe"},
    ).where(
        lambda c: (c(User).name.in_(["Jane", "Doe"]))
        .and_(
            c.group(
                (c(User).age.is_.not_.null)
                .and_(c(User).age >= 18)
                .and_(c(User).age <= 100),
            ),
        )
        .and_(c(User).created_at >= datetime.datetime(2001, 1, 1)),
    ).execute(cursor)

The method using lambda expressions is easy to write expressions, but if you write complex conditions, errors may be difficult to read.
By using Condition, you can write conditions more strictly and make it easier to track errors.

Example

import datetime

from snowman.query import update
from snowman.query.expression import column as c
from snowman.query.expression import group
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    update(
        User,
    ).set(
        {"name": "Jane Doe"},
    ).where(
        (c(User).name.in_(["Jane", "Doe"]))
        .and_(
            group(
                (c(User).age.is_.not_.null)
                .and_(c(User).age >= 18)
                .and_(c(User).age <= 100),
            ),
        )
        .and_(c(User).created_at >= datetime.datetime(2001, 1, 1)),
    ).execute(cursor)

If you want to write a more complex condition, you can use string and params.
This method is useful when you want to write a condition that is difficult to write with Condition.

Example

import datetime

from snowman.query import update
from your.database.schema import User
from your.snowflake import snowflake_conn

with snowflake_conn.cursor() as cursor:
    update(
        User,
    ).set(
        {"name": "Jane Doe"},
    ).where(
        """
        NAME IN (%s)
        AND (
            AGE IS NOT NULL
            AND AGE >= %s
            AND AGE <= %s
        )
        AND CREATED_AT >= %s
        """,
        (["Jane", "Doe"], 18, 100, datetime.datetime(2001, 1, 1)),
    ).execute(cursor)