SQL, as a first-class
language in Swift

Real SQLite, written in typed, composable, refactorable Swift. No query strings. No stringly typed columns. No ORM hiding the SQL.

.package(url: "https://github.com/lukevanin/swiftql.git", from: "1.5.4")

The source is the statement

Every clause appears once, under its SQL name, in SQL's grammatical order. Read a SwiftQL query and you are reading the statement it produces.

SQLite
SELECT * FROM Person
WHERE name = 'Fred'
ORDER BY age ASC
LIMIT 5
SwiftQL
let query = sql { schema in
    let person = schema.table(Person.self)
    Select(person)
    From(person)
    Where(person.name == "Fred")
    OrderBy(person.age.ascending())
    Limit(5)
}

That correspondence is the whole design. Other typed query libraries reach SQL through a method chain: you start from a table type and attach clauses to it, in an order the library accepts rather than the order SQL defines. SwiftQL keeps SQL's own order, which is why porting a query is a mechanical exercise rather than an interpretive one - clause by clause, in both directions. The porting guide maps every clause and works through ports up to recursive common table expressions.

Rename a column, and the compiler finds every query

A string-based query survives the rename, ships, and fails on a device you cannot reach. SwiftQL moves that failure to the build.

Tables are Swift types
@SQLTable
struct Person {
    var id: String
    var occupationId: String?
    var name: String
    var age: Int
}
Results decode into them
let request = database.makeRequest(with: query)

let people: [Person] = try request.fetchAll()
let first: Person? = try request.fetchOne()

Select(person) fixes the row type when the query is constructed, so both execution methods expose their result type directly. There are no untyped row dictionaries and no manual casts. Xcode completes and navigates the table model, and the compiler catches missing fields, incompatible expression types, and invalid clause ordering.

Coverage is the point

A first-class construct that only handles simple statements is a demo. SwiftQL's aim is to express what SQLite expresses, with the remaining surface tracked as conformance work rather than left undefined.

Queries

Inner, left, and cross joins; grouping and HAVING; ordering and pagination; scalar and table subqueries; compound queries.

Common table expressions

Ordinary and recursive CTEs, with the self-reference passed to the closure as a typed table like any other.

Expressions

Boolean, numeric, text, optional, conditional, and aggregate expressions composed with Swift operators and generic constraints.

Writes

Typed inserts, updates, and deletes with the same SQL-shaped API, plus basic table creation.

Live data

Observe typed results through GRDB-backed Combine publishers, or adopt XLQueryObserver directly from SwiftUI.

Your domain

Extend SQLite with Swift enums, custom value types, and type-safe custom SQL functions.

What is supported is not a claim, it is an inventory. Every feature is recorded with evidence from a real SQLite engine, versioned with the library, and published as a conformance report. The gaps are recorded there too.

How SwiftQL compares

Swift has good persistence libraries. They differ mainly in how much of the relational model they ask you to give up.

Shape Where the SQL lives Type safety comes from
SwiftQL Result builder, one clause per statement in SQL order Visible, in SQL order, in Swift Macros + generics, checked at compile time
StructuredQueries Chained methods with typed closures and key paths Emitted in SQL order; #sql escape hatch for typed SQL strings @Table macro + key paths
GRDB Records + query interface, with raw SQL always available Visible when you write it; strings when you drop down Codable records and column definitions
SQLite.swift Expression DSL Behind the DSL Generic Expression types
SwiftData Object graph Hidden - no SQL surface @Model macro over an opaque store
Fluent Server-side ORM Hidden behind the model layer Model definitions and property wrappers

SwiftQL is not a GRDB alternative, it is a typed query layer above it. Execution, connection management, transactions, and observation are GRDB's, and deliberately so: that part is mature, well understood, and not worth reimplementing. SwiftQL replaces the part where queries become strings.

Choose something else when