Skip to content

🚀 Move to Swift 6 and expand SQL support

Choose a tag to compare

@MihaelIsaev MihaelIsaev released this 29 Aug 00:15
· 64 commits to master since this release

SwifQL 2 now runs in Swift 6 language mode, with a much broader SQL surface and safer query composition.

Install it with:

.package(
    url: "https://github.com/SwifQL/SwifQL",
    exact: "2.0.0-beta.5.0.0"
)

Swift 6

SwifQL now uses:

// swift-tools-version:6.0

and:

swiftLanguageModes: [.v6]

The macOS deployment target is still 10.15.

Normal query source stays familiar:

let query = SwifQL
    .select(\User.email, \User.name)
    .from(User.table)
    .where(\User.email == "john@gmail.com")
    .orderBy(.asc(\User.name))
    .limit(10)

More SQL

A lot of new SQL can now be expressed directly through the same SwifQL DSL.

PIVOT

let cities = Path.Table("cities")

let query = SwifQL
    .pivot(cities)
    .on(cities.column("year"), in: 2000, 2010)
    .using(Fn.sum(cities.column("population")) => "total")
    .groupBy(cities.column("country"))
    .orderBy(.desc(cities.column("country")))
    .limit(2)

will give:

PIVOT "cities" ON "year" IN (2000, 2010) USING sum("population") as "total" GROUP BY "country" ORDER BY "country" DESC LIMIT 2

MERGE

let target = Path.Table("merge_target")
let source = Path.Table("merge_source")

SwifQL.merge(
    into: target,
    using: source,
    on: target.column("id") == source.column("id")
)

will give:

MERGE INTO "merge_target" USING "merge_source" ON "merge_target"."id" = "merge_source"."id"

The incremental form works too:

SwifQL
    .merge(into: target)
    .using(source)
    .on(target.column("id") == source.column("id"))

COPY

let events = Path.Table("events")

SwifQL.copy(
    events,
    to: "events.csv",
    options: .format("csv"), .header
)

will give:

COPY "events" TO 'events.csv' (FORMAT 'csv', HEADER)

There is much more in this beta: SELECT analytics, JSON, nested types/values, LIST/lambda helpers, joins, set operations, star/COLUMNS, UNPIVOT, DML/RETURNING, common DDL, sequences, macros, ATTACH/DETACH/USE, table/file functions, and more.

Dialects

SwifQL 2 keeps the same preparation model across PostgreSQL, MySQL, and DuckDB:

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

and:

SQLDialect.all // [.psql, .mysql, .duck]

Breaking change: structural parts

This affects advanced extensions that manually concatenate SwifQLable.parts.

was

extension SwifQLable {
    func appendingMyFragment(_ fragment: SwifQLable) -> SwifQLable {
        SwifQLableParts(parts: self.parts + fragment.parts)
    }
}

became

extension SwifQLable {
    func appendingMyFragment(_ fragment: SwifQLable) -> SwifQLable {
        structurallyAppending(fragment)
    }
}

Real statement/subquery/set-result regions are now preserved structurally, so query ownership survives type erasure, copied parts, nested SQL, builders, set operations, PIVOT/UNPIVOT, and other composition paths.

Normal SQL-shaped query source does not need to manipulate those frames.

Breaking change: 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 function calls stay the same:

SwifQL.select(Fn.coalesce("hello", "world"))

Strict concurrency and actors

SwifQL is clean under Swift 6 strict concurrency, but query/bind graphs are intentionally not marked Sendable when that would be untrue.

For actor-based wrappers use this shape:

build/prepare SwifQL on caller isolation
        ↓
convert to your own Sendable snapshot
        ↓
send the snapshot to the actor

Do not send SwifQLable, SwifQLSplittedQuery, or arbitrary [Encodable] binds across actors by hiding them behind unchecked conformances.

A complete example is in MIGRATION.md.

raw(_:) fix

Static raw now uses the text you pass:

SwifQLableParts.raw("TAIL").prepare(.psql).plain
// " TAIL"

"TAIL".raw.prepare(.psql).plain
// "TAIL"

Validation

Apple Swift 6.3.3
448 tests / 42 suites
concurrency errors: 0
concurrency warnings: 0

The current DuckDB SQL support was also validated against DuckDB v1.5.5.

For migration details see MIGRATION.md, and for the full current overview see RELEASE_NOTES.md.