Repository navigation
🚀 Move to Swift 6 and expand SQL support
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.0and:
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 2MERGE
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.