Skip to content

🚀 SQL 2.0.0

Choose a tag to compare

@MihaelIsaev MihaelIsaev released this 06 Oct 00:29
· 16 commits to master since this release

SwifQL is now SQL 🚀

The project moved to the SwiftStream organization and the package, product, module, and query root now share one name: SQL.

Install 2.0.0 with:

.package(
    url: "https://github.com/SwiftStream/SQL",
    from: "2.0.0"
)

and use product SQL:

.product(name: "SQL", package: "SQL")

Breaking change: SwifQL → SQL

was

import SwifQL

let query = SwifQL
    .select(\User.$id)  // or Path.Column("id")
    .from(User.table)   // or Path.Table("users")

became

import SQL

let query = SQL         // <-- SwifQL replaced with SQL
    .select(\User.$id)
    .from(User.table)

There is no compatibility module named SwifQL. After import SQL, retained old SwifQL* symbols may still be available as deprecated/renamed bridges where provided.

Declarative SQL

You can keep the direct fluent style:

let query = SQL
    .select(\User.$id, \User.$email)
    .from(User.table)
    .where(\User.$email == "john@example.com")
    .limit(10)

Result Builder DSL

Brand new gorgeous way to write your queries

let query = SQL {
    Select {
        \User.$id
        \User.$email
    }
    // or Select(\User.$id, \User.$email)

    From {
        User.table
    }
    // or From(User.table)

    Where {
        \User.$email == "john@example.com"
    }
    // you still can use
    // Where(\User.$email == "john@example.com")
    // for simple predicates

    Limit(10)
}

The classic declarative and result-builder approaches use the same composable parts, preparation, and binding pipeline.

Select { ... } can be easily replaced with declarative Select(...) form, same for From, Where and other parts.

The biggest advantage of a result-builder DSL, aside from its elegance, is that you can use if/else/for and other conditional constructs.

Reusable queries

SQLQuery lets a reusable value hide conditional SQL while the call site stays small:

struct UserQuery: SQLQuery {
    let active: Bool
    let email: String?
    let roles: [String]?

    var query: Query {
        Select {
            \User.$id
            \User.$email
        }

        From {
            User.table
        }

        Where {
            \User.$active == active

            if let email {
                \User.$email == email
            }

            if let roles {
                Or {
                    for role in roles {
                        \User.$role == role
                    }
                }
            }
        }
    }
}

Outside the type, it is just a normal composable value:

let users = UserQuery(
    active: true,
    email: email,
    roles: roles
)

let prepared = users.prepare(.psql)

Because SQLQuery is SQLable, UserQuery(...) can also be nested directly in FROM/JOIN/subquery positions without manually unwrapping .query.

Declarative table DDL

let createUsers = CreateTable("users") {
    NewColumn("id", .uuid).primaryKey()
    NewColumn("email", .text).unique().notNull()
}

createUsers.prepare(.psql).plain

will give:

CREATE TABLE "users" ("id" uuid PRIMARY KEY, "email" text UNIQUE NOT NULL)

SQL only builds the statement. Migration history, transaction policy, and execution stay in your application or database layer.

More SQL

The 2.0 line significantly expands the SQL surface, including analytics, JSON and nested values, joins and set operations, PIVOT/UNPIVOT, MERGE, COPY, DML/RETURNING, DDL, sequences, macros, and table/file functions.

PostgreSQL, MySQL, and DuckDB use the same preparation model:

query.prepare(.psql)
query.prepare(.mysql)
query.prepare(.duck)

Shared semantic values

SQL includes database-facing civil and interval values:

let date = PureDate(year: 2026, month: 9, day: 4)!
let time = PureTime(hour: 12, minute: 34, second: 56, nanosecond: 123_456_789)!
let dateTime = DateTime(
    year: 2026,
    month: 9,
    day: 4,
    hour: 12,
    minute: 34,
    second: 56,
    nanosecond: 123_456_789
)!
let interval = Interval(months: 2, days: -3, microseconds: 4)

SQL.select(date, time, dateTime, interval).prepare(.psql).plain

will give:

SELECT DATE '2026-09-04', TIME '12:34:56.123456789', TIMESTAMP '2026-09-04 12:34:56.123456789', INTERVAL '2 months -3 days 4 microseconds'

Swift 6

SQL 2.0.0 requires Swift 6.3+ and uses Swift 6 language mode.

The query/bind graph is intentionally not hidden behind unchecked Sendable conformances. Build/prepare on the originating isolation and move an application-owned Sendable snapshot across actor boundaries when needed.

Advanced migration notes

If you have extensions that manually concatenate SQLable.parts, use the structural composition helpers so statement/subquery ownership is preserved.

Predefined Fn.Name values are immutable.

was

Fn.Name.coalesce = .custom("my_coalesce")

became

let name = Fn.Name.custom("my_coalesce")
let fn = Fn.build(name)

Normal calls such as Fn.coalesce(...) stay the same.

Validation

The final candidate passed 737 tests / 63 suites on Swiftly 6.3.3, Xcode 26.6 / Swift 6.3.3, and Xcode 27 / Swift 6.4. Fresh normal-import clients also passed on all three toolchains with the Query shorthand, explicit SQLContent, all 37 direct-root representatives, PostgreSQL/MySQL/DuckDB bind checks, and deprecated SwifQL fluent/builder/unary parity.

For the short v1 → v2 path and full compatibility details see: