add swiftUI code

This commit is contained in:
zeus
2025-01-22 14:09:10 +08:00
parent 68e7b7347c
commit 8a99853829
2531 changed files with 486215 additions and 0 deletions
@@ -0,0 +1,126 @@
/// A type that represents a column in a database table.
///
/// ## Topics
///
/// ### Standard Columns
///
/// - ``rowID``
///
/// ### Deriving SQL Expressions
///
/// - ``detached``
/// - ``match(_:)-727nk``
/// - ``match(_:)-1vvo8``
///
/// ### Creating Column Assignments
///
/// - ``noOverwrite``
/// - ``set(to:)``
public protocol ColumnExpression: SQLSpecificExpressible {
/// The column name.
///
/// The column name is never qualified with a table name.
///
/// For example, the name of a column can be `"score"`, but
/// not `"player.score"`.
var name: String { get }
}
extension ColumnExpression {
/// Returns an SQL column.
public var sqlExpression: SQLExpression {
.column(name)
}
/// An SQL expression that refers to an aliased column
/// (`expression AS alias`).
///
/// Once detached, a column is never qualified with any table name in the
/// SQL generated by the query interface.
///
/// For example, see how `Column("total").detached` makes it possible to
/// sort this query, when a raw `Column("total")` could not:
///
/// ```swift
/// // SELECT player.*,
/// // (player.score + player.bonus) AS total,
/// // team.*
/// // FROM player
/// // JOIN team ON team.id = player.teamID
/// // ORDER BY total, player.name
/// // ~~~~~
/// let request = Player
/// .annotated(with: (Column("score") + Column("bonus")).forKey("total"))
/// .including(required: Player.team)
/// .order(Column("total").detached, Column("name"))
/// ```
public var detached: SQLExpression {
SQL(sql: name.quotedDatabaseIdentifier).sqlExpression
}
}
extension ColumnExpression where Self == Column {
/// The hidden rowID column.
public static var rowID: Self { Column.rowID }
}
/// A column in a database table.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord {
/// var score: Int
/// }
///
/// let maximumScore = try dbQueue.read { db in
/// // SELECT MAX(score) FROM player
/// try Player
/// .select(max(Column("score")), as: Int.self)
/// .fetchOne(db)
/// }
/// ```
///
/// ## Topics
///
/// ### Standard Columns
///
/// - ``rowID-3bn70``
/// - ``rank``
///
/// ### Creating A Column
///
/// - ``init(_:)-5grmu``
/// - ``init(_:)-7xc4z``
public struct Column: Sendable {
/// The hidden rowID column.
public static let rowID = Column("rowid")
public var name: String
/// Creates a `Column` given its name.
///
/// The name should be unqualified, such as `"score"`. Qualified name such
/// as `"player.score"` are unsupported.
public init(_ name: String) {
self.name = name
}
/// Creates a `Column` given a `CodingKey`.
public init(_ codingKey: some CodingKey) {
self.name = codingKey.stringValue
}
}
extension Column: ColumnExpression { }
/// Support for column enums:
///
/// struct Player {
/// enum Columns: String, ColumnExpression {
/// case id, name, score
/// }
/// }
extension ColumnExpression where Self: RawRepresentable, Self.RawValue == String {
public var name: String { rawValue }
}
@@ -0,0 +1,50 @@
/// DatabasePromise represents a value that can only be resolved when a
/// database connection is available.
///
/// This type is important for the query interface, which lets the user define
/// requests without any database context.
///
/// For example, consider those two requests:
///
/// let playerRequest = Player.filter(key: 1)
/// let countryRequest = Country.filter(key: "FR")
///
/// Both need a database connection in order to introspect the database schema,
/// find the primary key of both table, and generate the correct SQL:
///
/// try dbQueue.read { db in
/// // SELECT * FROM player WHERE id = 1
/// let player = try playerRequest.fetchOne(db)
/// // SELECT * FROM country WHERE code = 'FR'
/// let country = try countryRequest.fetchOne(db)
/// }
///
/// Such late computations are backed by DatabasePromise. In our example,
/// see SQLRelation.filterPromise.
struct DatabasePromise<T> {
/// Returns the resolved value.
let resolve: (Database) throws -> T
/// Creates a promise that resolves to a value.
init(value: T) {
self.resolve = { _ in value }
}
/// Creates a promise from a closure.
init(_ resolve: @escaping (Database) throws -> T) {
self.resolve = resolve
}
/// Returns a promise whose value is transformed by the given closure.
func map<U>(_ transform: @escaping (T) throws -> U) -> DatabasePromise<U> {
DatabasePromise<U> { db in
try transform(resolve(db))
}
}
}
extension DatabasePromise: CustomStringConvertible {
var description: String {
"DatabasePromise<\(T.self)>"
}
}
@@ -0,0 +1,311 @@
// MARK: - _SQLAssociation
/// An SQL association is a non-empty chain of steps which starts at the
/// "pivot" and ends on the "destination":
///
/// // SELECT origin.*, destination.*
/// // FROM origin
/// // JOIN pivot ON ...
/// // JOIN ...
/// // JOIN ...
/// // JOIN destination ON ...
/// Origin.including(required: association)
///
/// For direct associations such as BelongTo or HasMany, the chain contains a
/// single element, the "destination", without intermediate step:
///
/// // "Origin" belongsTo "destination":
/// // SELECT origin.*, destination.*
/// // FROM origin
/// // JOIN destination ON destination.originId = origin.id
/// let association = Origin.belongsTo(Destination.self)
/// Origin.including(required: association)
///
/// Indirect associations such as HasManyThrough have one or several
/// intermediate steps:
///
/// // "Origin" has many "destination" through "pivot":
/// // SELECT origin.*, destination.*
/// // FROM origin
/// // JOIN pivot ON pivot.originId = origin.id
/// // JOIN destination ON destination.id = pivot.destinationId
/// let association = Origin.hasMany(
/// Destination.self,
/// through: Origin.hasMany(Pivot.self),
/// via: Pivot.belongsTo(Destination.self))
/// Origin.including(required: association)
///
/// // "Origin" has many "destination" through "pivot1" and "pivot2":
/// // SELECT origin.*, destination.*
/// // FROM origin
/// // JOIN pivot1 ON pivot1.originId = origin.id
/// // JOIN pivot2 ON pivot2.pivot1Id = pivot1.id
/// // JOIN destination ON destination.id = pivot.destinationId
/// let association = Origin.hasMany(
/// Destination.self,
/// through: Origin.hasMany(Pivot1.self),
/// via: Pivot1.hasMany(
/// Destination.self,
/// through: Pivot1.hasMany(Pivot2.self),
/// via: Pivot2.belongsTo(Destination.self)))
/// Origin.including(required: association)
public struct _SQLAssociation {
// All steps, from pivot to destination. Never empty.
private(set) var steps: [SQLAssociationStep]
var keyPath: [String] { steps.map(\.keyName) }
var destination: SQLAssociationStep {
get { steps[steps.count - 1] }
set { steps[steps.count - 1] = newValue }
}
var pivot: SQLAssociationStep {
get { steps[0] }
set { steps[0] = newValue }
}
init(steps: [SQLAssociationStep]) {
assert(!steps.isEmpty)
self.steps = steps
}
init(
key: SQLAssociationKey,
condition: SQLAssociationCondition,
relation: SQLRelation,
cardinality: SQLAssociationCardinality)
{
let step = SQLAssociationStep(
key: key,
condition: condition,
relation: relation,
cardinality: cardinality)
self.init(steps: [step])
}
/// Changes the destination key
func forDestinationKey(_ key: SQLAssociationKey) -> Self {
with {
$0.destination.key = key
}
}
/// Returns a new association
func through(_ other: _SQLAssociation) -> Self {
_SQLAssociation(steps: other.steps + steps)
}
/// Returns the destination of the association, reversing the association
/// up to the pivot.
///
/// This method feeds `TableRecord.request(for:)`, and allows
/// `including(all:)` to prefetch associated records.
func destinationRelation() -> SQLRelation {
if steps.count == 1 {
return destination.relation
}
// This is an indirect join from origin to destination, through
// some intermediate steps:
//
// SELECT destination.*
// FROM destination
// JOIN pivot ON (pivot.destinationId = destination.id) AND (pivot.originId = 1)
//
// let association = Origin.hasMany(
// Destination.self,
// through: Origin.hasMany(Pivot.self),
// via: Pivot.belongsTo(Destination.self))
// Origin(id: 1).request(for: association)
let reversedSteps = zip(steps, steps.dropFirst())
.map { (step, nextStep) in
// Intermediate steps are not selected, and including(all:)
// children can't impact the destination relation:
let relation = step.relation
.selectOnly([])
.removingPrefetchedAssociations()
// Don't interfere with user-defined keys that could be added later
let key = step.key.with {
$0.baseName = "grdb_\($0.baseName)"
}
return SQLAssociationStep(
key: key,
condition: nextStep.condition.reversed(to: step.relation.source.tableName),
relation: relation,
cardinality: .toOne)
}
.reversed()
let reversedAssociation = _SQLAssociation(steps: Array(reversedSteps))
return destination.relation.appendingChild(for: reversedAssociation, kind: .oneRequired)
}
}
extension _SQLAssociation: Refinable { }
struct SQLAssociationStep: Refinable {
var key: SQLAssociationKey
var condition: SQLAssociationCondition
var relation: SQLRelation
var cardinality: SQLAssociationCardinality
var keyName: String { key.name(singular: cardinality.isSingular) }
}
enum SQLAssociationCardinality {
case toOne
case toMany
var isSingular: Bool {
switch self {
case .toOne:
return true
case .toMany:
return false
}
}
}
// MARK: - SQLAssociationKey
/// Associations are meant to be consumed, most often into Decodable records.
///
/// Those records have singular or plural property names, and we want
/// associations to be able to fill those singular or plural names
/// automatically, so that the user does not have to perform explicit
/// decoding configuration.
///
/// Those plural or singular names are not decided when the association is
/// defined. For example, the Author.books association, which looks plural, may
/// actually generate "book" or "books" depending on the context:
///
/// struct Author: TableRecord {
/// static let books = hasMany(Book.self)
/// }
/// struct Book: TableRecord {
/// }
///
/// // "books"
/// struct AuthorInfo: FetchableRecord, Decodable {
/// var author: Author
/// var books: [Book]
/// }
/// let request = Author.including(all: Author.books)
/// let authorInfos = try AuthorInfo.fetchAll(db, request)
///
/// "book"
/// struct AuthorInfo: FetchableRecord, Decodable {
/// var author: Author
/// var book: Book
/// }
/// let request = Author.including(required: Author.books)
/// let authorInfos = try AuthorInfo.fetchAll(db, request)
///
/// "bookCount"
/// struct AuthorInfo: FetchableRecord, Decodable {
/// var author: Author
/// var bookCount: Int
/// }
/// let request = Author.annotated(with: Author.books.count)
/// let authorInfos = try AuthorInfo.fetchAll(db, request)
///
/// The SQLAssociationKey type aims at providing the necessary support for
/// those various inflections.
enum SQLAssociationKey: Refinable {
/// A key that is inflected in singular and plural contexts.
///
/// For example:
///
/// struct Author: TableRecord {
/// static let databaseTableName = "authors"
/// }
/// struct Book: TableRecord {
/// let author = belongsTo(Author.self)
/// }
///
/// let request = Book.including(required: Book.author)
/// let row = try Row.fetchOne(db, request)!
/// row.scopes["author"] // singularized "authors" table name
case inflected(String)
/// A key that is inflected in plural contexts, but stricly honors
/// user-provided name in singular contexts.
///
/// For example:
///
/// struct Country: TableRecord {
/// let demographics = hasOne(Demographics.self, key: "demographics")
/// }
///
/// let request = Country.including(required: Country.demographics)
/// let row = try Row.fetchOne(db, request)!
/// row.scopes["demographics"] // not singularized
case fixedSingular(String)
/// A key that is inflected in singular contexts, but stricly honors
/// user-provided name in plural contexts.
/// See .inflected and .fixedSingular for some context.
case fixedPlural(String)
/// A key that is never inflected.
case fixed(String)
var baseName: String {
get {
switch self {
case let .inflected(name),
let .fixedSingular(name),
let .fixedPlural(name),
let .fixed(name):
return name
}
}
set {
switch self {
case .inflected:
self = .inflected(newValue)
case .fixedSingular:
self = .fixedSingular(newValue)
case .fixedPlural:
self = .fixedPlural(newValue)
case .fixed:
self = .fixed(newValue)
}
}
}
func name(singular: Bool) -> String {
if singular {
return singularizedName
} else {
return pluralizedName
}
}
var pluralizedName: String {
switch self {
case .inflected(let name):
return name.pluralized
case .fixedSingular(let name):
return name.pluralized
case .fixedPlural(let name):
return name
case .fixed(let name):
return name
}
}
var singularizedName: String {
switch self {
case .inflected(let name):
return name.singularized
case .fixedSingular(let name):
return name
case .fixedPlural(let name):
return name.singularized
case .fixed(let name):
return name
}
}
}
@@ -0,0 +1,119 @@
/// The right-hand side of the `IN` or `NOT IN` SQL operators
///
/// See <https://sqlite.org/lang_expr.html#the_in_and_not_in_operators>
struct SQLCollection {
private var impl: Impl
private enum Impl {
/// An array collection
///
/// id IN (1, 2, 3)
/// ~~~~~~~~~
case array([SQLExpression])
/// A subquery
///
/// score IN (SELECT ...)
/// ~~~~~~~~~~~~
case subquery(SQLSubquery)
/// A table
///
/// score IN table
/// ~~~~~
case table(String)
}
static func array(_ expressions: [SQLExpression]) -> Self {
self.init(impl: .array(expressions))
}
static func subquery(_ subquery: SQLSubquery) -> Self {
self.init(impl: .subquery(subquery))
}
static func table(_ tableName: String) -> Self {
self.init(impl: .table(tableName))
}
}
extension SQLCollection {
/// Returns a qualified collection.
func qualified(with alias: TableAlias) -> SQLCollection {
switch impl {
case .subquery,
.table:
return self
case let .array(expressions):
return .array(expressions.map { $0.qualified(with: alias) })
}
}
/// The expressions in the collection, if known.
///
/// This property makes it possible to track individual rows identified by
/// their row ids, and ignore modifications to other rows:
///
/// // Track rows 1, 2, 3 only
/// let request = Player.filter(keys: [1, 2, 3])
/// let regionObservation = DatabaseRegionObservation(tracking: request)
/// let valueObservation = ValueObservation.tracking(request.fetchAll)
var collectionExpressions: [SQLExpression]? {
switch impl {
case .subquery,
.table:
return nil
case let .array(expressions):
return expressions
}
}
/// Returns an SQL string that represents the collection.
///
/// - parameter context: An SQL generation context which accepts
/// statement arguments.
func sql(_ context: SQLGenerationContext) throws -> String {
switch impl {
case let .array(expressions):
return try "("
+ expressions.map { try $0.sql(context) }.joined(separator: ", ")
+ ")"
case let .subquery(subquery):
return try "("
+ subquery.sql(context)
+ ")"
case let .table(tableName):
return tableName.quotedDatabaseIdentifier
}
}
/// Returns an expression that check whether the collection contains
/// the expression.
func contains(_ value: SQLExpression) -> SQLExpression {
switch impl {
case .subquery,
.table:
return .in(value, self)
case let .array(expressions):
guard let expression = expressions.first else {
// [].contains(...) -> false
return false.sqlExpression
}
if expressions.count == 1 {
// Output `value = expression` instead of `value IN (expression)`,
// because it looks nicer. Force the equal `=` operator, so that
// the result evaluates just as `value IN (expression)`, even
// if expression is NULL.
return .compare(.equal, value, expression)
}
return .in(value, self)
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,177 @@
/// SQLForeignKeyRequest looks for the foreign keys associations need to
/// join tables.
///
/// Columns mapping come from foreign keys, when they exist in the
/// database schema.
///
/// When the schema does not define any foreign key, we can still infer complete
/// mapping from partial information and primary keys.
struct SQLForeignKeyRequest {
let originTable: String
let destinationTable: String
let originColumns: [String]?
let destinationColumns: [String]?
init(originTable: String, destinationTable: String, foreignKey: ForeignKey?) {
self.originTable = originTable
self.destinationTable = destinationTable
self.originColumns = foreignKey?.originColumns
self.destinationColumns = foreignKey?.destinationColumns
}
/// The (origin, destination) column pairs that join a left table to a right table.
func fetchForeignKeyMapping(_ db: Database) throws -> ForeignKeyMapping {
if let originColumns, let destinationColumns {
// Total information: no need to query the database schema.
GRDBPrecondition(originColumns.count == destinationColumns.count, "Number of columns don't match")
let mapping = zip(originColumns, destinationColumns).map {
(origin: $0, destination: $1)
}
return mapping
}
// Incomplete information: let's look for schema foreign keys
//
// But maybe the tables are views. In this case, don't throw
// "no such table" error, because this is confusing for the user,
// as discovered in <https://github.com/groue/GRDB.swift/discussions/1481>.
// Instead, we'll crash with a clear message.
guard let originType = try tableType(db, for: originTable) else {
throw DatabaseError.noSuchTable(originTable)
}
if originType.isView {
if originColumns == nil {
fatalError("""
Could not infer foreign key from '\(originTable)' \
to '\(destinationTable)'. To fix this error, provide an \
explicit `ForeignKey` in the association definition.
""")
}
} else {
let foreignKeys = try db.foreignKeys(on: originTable).filter { foreignKey in
if destinationTable.lowercased() != foreignKey.destinationTable.lowercased() {
return false
}
if let originColumns {
let originColumns = Set(originColumns.lazy.map { $0.lowercased() })
let foreignKeyColumns = Set(foreignKey.mapping.lazy.map { $0.origin.lowercased() })
if originColumns != foreignKeyColumns {
return false
}
}
if let destinationColumns {
// TODO: test
let destinationColumns = Set(destinationColumns.lazy.map { $0.lowercased() })
let foreignKeyColumns = Set(foreignKey.mapping.lazy.map { $0.destination.lowercased() })
if destinationColumns != foreignKeyColumns {
return false
}
}
return true
}
// Matching foreign key(s) found
if let foreignKey = foreignKeys.first {
if foreignKeys.count == 1 {
// Non-ambiguous
return foreignKey.mapping
} else {
// Ambiguous: can't choose
fatalError("""
Ambiguous foreign key from '\(originTable)' to \
'\(destinationTable)'. To fix this error, provide an \
explicit `ForeignKey` in the association definition.
""")
}
}
}
// No matching foreign key found: use the destination primary key
if let originColumns {
guard let destinationType = try tableType(db, for: destinationTable) else {
throw DatabaseError.noSuchTable(destinationTable)
}
if destinationType.isView {
fatalError("""
Could not infer foreign key from '\(originTable)' \
to '\(destinationTable)'. To fix this error, provide an \
explicit `ForeignKey` in the association definition, \
with both origin and destination columns.
""")
}
let destinationColumns = try db.primaryKey(destinationTable).columns
if originColumns.count == destinationColumns.count {
let mapping = zip(originColumns, destinationColumns).map {
(origin: $0, destination: $1)
}
return mapping
}
}
fatalError("""
Could not infer foreign key from '\(originTable)' to \
'\(destinationTable)'. To fix this error, provide an \
explicit `ForeignKey` in the association definition.
""")
}
private struct TableType {
var isView: Bool
}
private func tableType(_ db: Database, for name: String) throws -> TableType? {
for schemaID in try db.schemaIdentifiers() {
if try db.schema(schemaID).containsObjectNamed(name, ofType: .table) {
return TableType(isView: false)
}
if try db.schema(schemaID).containsObjectNamed(name, ofType: .view) {
return TableType(isView: true)
}
}
return nil
}
}
// Foreign key columns mapping
typealias ForeignKeyMapping = [(origin: String, destination: String)]
// Join columns mapping
typealias JoinMapping = [(left: String, right: String)]
extension ForeignKeyMapping {
/// Orient the foreign key mapping for a SQL join.
///
/// - parameter originIsLeft: Whether the table at the origin of a
/// foreign key is on the left of a JOIN clause.
///
/// For example, the two requests below use the same
/// `ForeignKeyMapping` from `book.authorID` (origin of the foreign key)
/// to `author.id` (destination).
///
/// In the first request, the book origin is on the left of the
/// join clause:
///
/// // SELECT book.*, author.*
/// // FROM book
/// // JOIN author ON author.id = book.authorID
/// Book.including(required: Book.author)
///
/// In the second request, the book origin is on the right of the
/// join clause:
///
/// // SELECT author.*, book.*
/// // FROM author
/// // JOIN book ON book.authorID = author.id
/// Author.including(required: Author.books)
func joinMapping(originIsLeft: Bool) -> JoinMapping {
if originIsLeft {
return map { (left: $0.origin, right: $0.destination) }
} else {
return map { (left: $0.destination, right: $0.origin) }
}
}
}
@@ -0,0 +1,535 @@
/// The `ABS` SQL function.
///
/// For example:
///
/// ```swift
/// // ABS(amount)
/// abs(Column("amount"))
/// ```
public func abs(_ value: some SQLSpecificExpressible) -> SQLExpression {
.function("ABS", [value.sqlExpression])
}
#if GRDBCUSTOMSQLITE || GRDBCIPHER
/// The `AVG` SQL function.
///
/// For example:
///
/// ```swift
/// // AVG(length)
/// average(Column("length"))
/// ```
public func average(
_ value: some SQLSpecificExpressible,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression {
.aggregateFunction("AVG", [value.sqlExpression], filter: filter?.sqlExpression)
}
#else
/// The `AVG` SQL function.
///
/// For example:
///
/// ```swift
/// // AVG(length) FILTER (WHERE length > 0)
/// average(Column("length"), filter: Column("length") > 0)
/// ```
@available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) // SQLite 3.30+
public func average(
_ value: some SQLSpecificExpressible,
filter: some SQLSpecificExpressible)
-> SQLExpression {
.aggregateFunction(
"AVG", [value.sqlExpression],
filter: filter.sqlExpression)
}
/// The `AVG` SQL function.
///
/// For example:
///
/// ```swift
/// // AVG(length)
/// average(Column("length"))
/// ```
public func average(_ value: some SQLSpecificExpressible) -> SQLExpression {
.aggregateFunction("AVG", [value.sqlExpression])
}
#endif
/// The `CAST` SQL function.
///
/// For example:
///
/// ```swift
/// // CAST(value AS REAL)
/// cast(Column("value"), as: .real)
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_expr.html#castexpr>
public func cast(_ expression: some SQLSpecificExpressible, as storageClass: Database.StorageClass) -> SQLExpression {
.cast(expression.sqlExpression, as: storageClass)
}
/// The `COUNT` SQL function.
///
/// For example:
///
/// ```swift
/// // COUNT(email)
/// count(Column("email"))
/// ```
public func count(_ counted: some SQLSpecificExpressible) -> SQLExpression {
.count(counted.sqlExpression)
}
/// The `COUNT(DISTINCT)` SQL function.
///
/// For example:
///
/// ```swift
/// // COUNT(DISTINCT email)
/// count(distinct: Column("email"))
/// ```
public func count(distinct value: some SQLSpecificExpressible) -> SQLExpression {
.countDistinct(value.sqlExpression)
}
extension SQLSpecificExpressible {
/// The `IFNULL` SQL function.
///
/// For example:
///
/// ```swift
/// // IFNULL(name, 'Anonymous')
/// Column("name") ?? "Anonymous"
/// ```
public static func ?? (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.function("IFNULL", [lhs.sqlExpression, rhs.sqlExpression])
}
}
/// The `LENGTH` SQL function.
///
/// For example:
///
/// ```swift
/// // LENGTH(name)
/// length(Column("name"))
/// ```
public func length(_ value: some SQLSpecificExpressible) -> SQLExpression {
.function("LENGTH", [value.sqlExpression])
}
#if GRDBCUSTOMSQLITE || GRDBCIPHER
/// The `MAX` SQL function.
///
/// For example:
///
/// ```swift
/// // MAX(score)
/// max(Column("score"))
/// ```
public func max(
_ value: some SQLSpecificExpressible,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression {
.aggregateFunction("MAX", [value.sqlExpression], filter: filter?.sqlExpression)
}
#else
/// The `MAX` SQL function.
///
/// For example:
///
/// ```swift
/// // MAX(score) FILTER (WHERE score < 0)
/// max(Column("score"), filter: Column("score") < 0)
/// ```
@available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) // SQLite 3.30+
public func max(
_ value: some SQLSpecificExpressible,
filter: some SQLSpecificExpressible)
-> SQLExpression {
.aggregateFunction("MAX", [value.sqlExpression], filter: filter.sqlExpression)
}
/// The `MAX` SQL function.
///
/// For example:
///
/// ```swift
/// // MAX(score)
/// max(Column("score"))
/// ```
public func max(_ value: some SQLSpecificExpressible) -> SQLExpression {
.aggregateFunction("MAX", [value.sqlExpression])
}
#endif
#if GRDBCUSTOMSQLITE || GRDBCIPHER
/// The `MIN` SQL function.
///
/// For example:
///
/// ```swift
/// // MIN(score)
/// min(Column("score"))
/// ```
public func min(
_ value: some SQLSpecificExpressible,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression {
.aggregateFunction("MIN", [value.sqlExpression], filter: filter?.sqlExpression)
}
#else
/// The `MIN` SQL function.
///
/// For example:
///
/// ```swift
/// // MIN(score) FILTER (WHERE score > 0)
/// min(Column("score"), filter: Column("score") > 0)
/// ```
@available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) // SQLite 3.30+
public func min(
_ value: some SQLSpecificExpressible,
filter: some SQLSpecificExpressible)
-> SQLExpression {
.aggregateFunction("MIN", [value.sqlExpression], filter: filter.sqlExpression)
}
/// The `MIN` SQL function.
///
/// For example:
///
/// ```swift
/// // MIN(score)
/// min(Column("score"))
/// ```
public func min(_ value: some SQLSpecificExpressible) -> SQLExpression {
.aggregateFunction("MIN", [value.sqlExpression])
}
#endif
#if GRDBCUSTOMSQLITE || GRDBCIPHER
/// The `SUM` SQL function.
///
/// For example:
///
/// ```swift
/// // SUM(amount)
/// sum(Column("amount"))
/// ```
///
/// See also ``total(_:)``.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
public func sum(
_ value: some SQLSpecificExpressible,
orderBy ordering: (any SQLOrderingTerm)? = nil,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression
{
.aggregateFunction(
"SUM", [value.sqlExpression],
ordering: ordering?.sqlOrdering,
filter: filter?.sqlExpression)
}
#else
/// The `SUM` SQL function.
///
/// For example:
///
/// ```swift
/// // SUM(amount) FILTER (WHERE amount > 0)
/// sum(Column("amount"), filter: Column("amount") > 0)
/// ```
///
/// See also ``total(_:)``.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
@available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) // SQLite 3.30+
public func sum(
_ value: some SQLSpecificExpressible,
filter: some SQLSpecificExpressible)
-> SQLExpression {
.aggregateFunction(
"SUM", [value.sqlExpression],
filter: filter.sqlExpression)
}
/// The `SUM` SQL function.
///
/// For example:
///
/// ```swift
/// // SUM(amount)
/// sum(Column("amount"))
/// ```
///
/// See also ``total(_:)``.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
public func sum(_ value: some SQLSpecificExpressible) -> SQLExpression {
.aggregateFunction("SUM", [value.sqlExpression])
}
#endif
#if GRDBCUSTOMSQLITE || GRDBCIPHER
/// The `TOTAL` SQL function.
///
/// For example:
///
/// ```swift
/// // TOTAL(amount)
/// total(Column("amount"))
/// ```
///
/// See also ``sum(_:)``.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
public func total(
_ value: some SQLSpecificExpressible,
orderBy ordering: (any SQLOrderingTerm)? = nil,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression
{
.aggregateFunction(
"TOTAL", [value.sqlExpression],
ordering: ordering?.sqlOrdering,
filter: filter?.sqlExpression)
}
#else
/// The `TOTAL` SQL function.
///
/// For example:
///
/// ```swift
/// // TOTAL(amount) FILTER (WHERE amount > 0)
/// total(Column("amount"), filter: Column("amount") > 0)
/// ```
///
/// See also ``total(_:)``.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
@available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) // SQLite 3.30+
public func total(
_ value: some SQLSpecificExpressible,
filter: some SQLSpecificExpressible)
-> SQLExpression {
.aggregateFunction(
"TOTAL", [value.sqlExpression],
filter: filter.sqlExpression)
}
/// The `TOTAL` SQL function.
///
/// For example:
///
/// ```swift
/// // TOTAL(amount)
/// total(Column("amount"))
/// ```
///
/// See also ``sum(_:)``.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
public func total(_ value: some SQLSpecificExpressible) -> SQLExpression {
.aggregateFunction("TOTAL", [value.sqlExpression])
}
#endif
// MARK: - String functions
extension SQLSpecificExpressible {
/// An SQL expression that calls the Foundation
/// `String.capitalized` property.
///
/// For example:
///
/// ```swift
/// Column("name").capitalized
/// ```
public var capitalized: SQLExpression {
DatabaseFunction.capitalize(sqlExpression)
}
/// An SQL expression that calls the Swift
/// `String.lowercased()` method.
///
/// For example:
///
/// ```swift
/// Column("name").lowercased
/// ```
public var lowercased: SQLExpression {
DatabaseFunction.lowercase(sqlExpression)
}
/// An SQL expression that calls the Swift
/// `String.uppercased()` method.
///
/// For example:
///
/// ```swift
/// Column("name").uppercased
/// ```
public var uppercased: SQLExpression {
DatabaseFunction.uppercase(sqlExpression)
}
}
extension SQLSpecificExpressible {
/// An SQL expression that calls the Foundation
/// `String.localizedCapitalized` property.
///
/// For example:
///
/// ```swift
/// Column("name").localizedCapitalized
/// ```
public var localizedCapitalized: SQLExpression {
DatabaseFunction.localizedCapitalize(sqlExpression)
}
/// An SQL expression that calls the Foundation
/// `String.localizedLowercase` property.
///
/// For example:
///
/// ```swift
/// Column("name").localizedLowercased
/// ```
public var localizedLowercased: SQLExpression {
DatabaseFunction.localizedLowercase(sqlExpression)
}
/// An SQL expression that calls the Foundation
/// `String.localizedUppercase` property.
///
/// For example:
///
/// ```swift
/// Column("name").localizedUppercased
/// ```
public var localizedUppercased: SQLExpression {
DatabaseFunction.localizedUppercase(sqlExpression)
}
}
// MARK: - Date functions
/// A date modifier for SQLite date functions.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_datefunc.html>
public enum SQLDateModifier: SQLSpecificExpressible, Sendable {
/// Adds the specified amount of seconds
case second(Double)
/// Adds the specified amount of minutes
case minute(Int)
/// Adds the specified amount of hours
case hour(Int)
/// Adds the specified amount of days
case day(Int)
/// Adds the specified amount of months
case month(Int)
/// Adds the specified amount of years
case year(Int)
/// Shifts the date backwards to the beginning of the current day
case startOfDay
/// Shifts the date backwards to the beginning of the current month
case startOfMonth
/// Shifts the date backwards to the beginning of the current year
case startOfYear
/// See <https://www.sqlite.org/lang_datefunc.html>
case weekday(Int)
/// See <https://www.sqlite.org/lang_datefunc.html>
case unixEpoch
/// See <https://www.sqlite.org/lang_datefunc.html>
case localTime
/// See <https://www.sqlite.org/lang_datefunc.html>
case utc
public var sqlExpression: SQLExpression {
rawValue.sqlExpression
}
var rawValue: String {
switch self {
case let .day(value):
return "\(value) days"
case let .hour(value):
return "\(value) hours"
case let .minute(value):
return "\(value) minutes"
case let .second(value):
return "\(value) seconds"
case let .month(value):
return "\(value) months"
case let .year(value):
return "\(value) years"
case .startOfMonth:
return "start of month"
case .startOfYear:
return "start of year"
case .startOfDay:
return "start of day"
case let .weekday(value):
return "weekday \(value)"
case .unixEpoch:
return "unixepoch"
case .localTime:
return "localtime"
case .utc:
return "utc"
}
}
}
/// The `JULIANDAY` SQL function.
///
/// For example:
///
/// ```swift
/// // JULIANDAY(date)
/// julianDay(Column("date"))
///
/// // JULIANDAY(date, '1 days')
/// julianDay(Column("date"), .day(1))
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_datefunc.html>
public func julianDay(_ value: some SQLSpecificExpressible, _ modifiers: SQLDateModifier...) -> SQLExpression {
.function("JULIANDAY", [value.sqlExpression] + modifiers.map(\.sqlExpression))
}
// MARK: DATETIME(...)
/// The `DATETIME` SQL function.
///
/// For example:
///
/// ```swift
/// // DATETIME(date)
/// dateTime(Column("date"))
///
/// // DATETIME(date, '1 days')
/// dateTime(Column("date"), .day(1))
/// ```
///
/// Related SQLite documentation:<https://www.sqlite.org/lang_datefunc.html>
public func dateTime(_ value: some SQLSpecificExpressible, _ modifiers: SQLDateModifier...) -> SQLExpression {
.function("DATETIME", [value.sqlExpression] + modifiers.map(\.sqlExpression))
}
@@ -0,0 +1,525 @@
extension SQLSpecificExpressible {
// MARK: - Egality and Identity Operators (=, <>, IS, IS NOT)
/// Compares two SQL expressions.
///
/// For example:
///
/// ```swift
/// // name = 'Arthur'
/// Column("name") == "Arthur"
/// ```
///
/// When the right operand is nil, `IS NULL` is used instead of the
/// `=` operator:
///
/// ```swift
/// // name IS NULL
/// Column("name") == nil
/// ```
public static func == (lhs: Self, rhs: (any SQLExpressible)?) -> SQLExpression {
.equal(lhs.sqlExpression, rhs?.sqlExpression ?? .null)
}
/// The `=` SQL operator.
public static func == (lhs: Self, rhs: Bool) -> SQLExpression {
if rhs {
return lhs.sqlExpression.is(.true)
} else {
return lhs.sqlExpression.is(.false)
}
}
/// Compares two SQL expressions.
///
/// For example:
///
/// ```swift
/// // 'Arthur' = name
/// "Arthur" == Column("name")
/// ```
///
/// When the left operand is nil, `IS NULL` is used instead of the
/// `=` operator:
///
/// ```swift
/// // name IS NULL
/// nil == Column("name")
/// ```
public static func == (lhs: (any SQLExpressible)?, rhs: Self) -> SQLExpression {
.equal(lhs?.sqlExpression ?? .null, rhs.sqlExpression)
}
/// The `=` SQL operator.
public static func == (lhs: Bool, rhs: Self) -> SQLExpression {
if lhs {
return rhs.sqlExpression.is(.true)
} else {
return rhs.sqlExpression.is(.false)
}
}
/// The `=` SQL operator.
public static func == (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.equal(lhs.sqlExpression, rhs.sqlExpression)
}
/// Compares two SQL expressions.
///
/// For example:
///
/// ```swift
/// // name <> 'Arthur'
/// Column("name") != "Arthur"
/// ```
///
/// When the right operand is nil, `IS NOT NULL` is used instead of the
/// `<>` operator:
///
/// ```swift
/// // name IS NOT NULL
/// Column("name") != nil
/// ```
public static func != (lhs: Self, rhs: (any SQLExpressible)?) -> SQLExpression {
!(lhs == rhs)
}
/// The `<>` SQL operator.
public static func != (lhs: Self, rhs: Bool) -> SQLExpression {
!(lhs == rhs)
}
/// Compares two SQL expressions.
///
/// For example:
///
/// ```swift
/// // 'Arthur' <> name
/// "Arthur" != Column("name")
/// ```
///
/// When the left operand is nil, `IS NOT NULL` is used instead of the
/// `<>` operator:
///
/// ```swift
/// // name IS NOT NULL
/// nil != Column("name")
/// ```
public static func != (lhs: (any SQLExpressible)?, rhs: Self) -> SQLExpression {
!(lhs == rhs)
}
/// The `<>` SQL operator.
public static func != (lhs: Bool, rhs: Self) -> SQLExpression {
!(lhs == rhs)
}
/// The `<>` SQL operator.
public static func != (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
!(lhs == rhs)
}
/// The `IS` SQL operator.
public static func === (lhs: Self, rhs: (any SQLExpressible)?) -> SQLExpression {
.compare(.is, lhs.sqlExpression, rhs?.sqlExpression ?? .null)
}
/// The `IS` SQL operator.
public static func === (lhs: (any SQLExpressible)?, rhs: Self) -> SQLExpression {
if let lhs {
return .compare(.is, lhs.sqlExpression, rhs.sqlExpression)
} else {
return .compare(.is, rhs.sqlExpression, .null)
}
}
/// The `IS` SQL operator.
public static func === (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.compare(.is, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `IS NOT` SQL operator.
public static func !== (lhs: Self, rhs: (any SQLExpressible)?) -> SQLExpression {
!(lhs === rhs)
}
/// The `IS NOT` SQL operator.
public static func !== (lhs: (any SQLExpressible)?, rhs: Self) -> SQLExpression {
!(lhs === rhs)
}
/// The `IS NOT` SQL operator.
public static func !== (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
!(lhs === rhs)
}
// MARK: - Comparison Operators (<, >, <=, >=)
/// The `<` SQL operator.
public static func < (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.lessThan, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<` SQL operator.
public static func < (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.lessThan, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<` SQL operator.
public static func < (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.lessThan, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<=` SQL operator.
public static func <= (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.lessThanOrEqual, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<=` SQL operator.
public static func <= (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.lessThanOrEqual, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<=` SQL operator.
public static func <= (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.lessThanOrEqual, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>` SQL operator.
public static func > (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.greaterThan, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>` SQL operator.
public static func > (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.greaterThan, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>` SQL operator.
public static func > (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.greaterThan, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>=` SQL operator.
public static func >= (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.greaterThanOrEqual, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>=` SQL operator.
public static func >= (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.greaterThanOrEqual, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>=` SQL operator.
public static func >= (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.greaterThanOrEqual, lhs.sqlExpression, rhs.sqlExpression)
}
}
// MARK: - Inclusion Operators (BETWEEN, IN)
extension Range where Bound: SQLExpressible {
/// Returns an SQL expression that checks the inclusion of an expression in
/// a range.
///
/// For example:
///
/// ```swift
/// // email >= 'A' AND email < 'B'
/// ("A"..<"B").contains(Column("email"))
/// ```
public func contains(_ element: some SQLSpecificExpressible) -> SQLExpression {
(element >= lowerBound) && (element < upperBound)
}
}
extension ClosedRange where Bound: SQLExpressible {
/// Returns an SQL expression that checks the inclusion of an expression in
/// a range.
///
/// For example:
///
/// ```swift
/// // initial BETWEEN 'A' AND 'B'
/// ("A"..."B").contains(Column("initial"))
/// ```
public func contains(_ element: some SQLSpecificExpressible) -> SQLExpression {
.between(
expression: element.sqlExpression,
lowerBound: lowerBound.sqlExpression,
upperBound: upperBound.sqlExpression)
}
}
extension CountableRange where Bound: SQLExpressible {
/// Returns an SQL expression that checks the inclusion of an expression in
/// a range.
///
/// For example:
///
/// ```swift
/// // id >= 1 AND id < 10
/// (1..<10).contains(Column("id"))
/// ```
public func contains(_ element: some SQLSpecificExpressible) -> SQLExpression {
(element >= lowerBound) && (element < upperBound)
}
}
extension CountableClosedRange where Bound: SQLExpressible {
/// Returns an SQL expression that checks the inclusion of an expression in
/// a range.
///
/// For example:
///
/// ```swift
/// // id BETWEEN 1 AND 10
/// (1...10).contains(Column("id"))
/// ```
public func contains(_ element: some SQLSpecificExpressible) -> SQLExpression {
.between(
expression: element.sqlExpression,
lowerBound: lowerBound.sqlExpression,
upperBound: upperBound.sqlExpression)
}
}
extension Sequence where Element: SQLExpressible {
/// Returns an SQL expression that checks the inclusion of an expression in
/// a sequence.
///
/// For example:
///
/// ```swift
/// // id IN (1,2,3)
/// [1, 2, 3].contains(Column("id"))
/// ```
public func contains(_ element: some SQLSpecificExpressible) -> SQLExpression {
SQLCollection.array(map(\.sqlExpression)).contains(element.sqlExpression)
}
}
extension Sequence where Element == any SQLExpressible {
/// Returns an SQL expression that checks the inclusion of an expression in
/// a sequence.
///
/// For example:
///
/// ```swift
/// // id IN (1,2,3)
/// [1, 2, 3].contains(Column("id"))
/// ```
public func contains(_ element: some SQLSpecificExpressible) -> SQLExpression {
SQLCollection.array(map(\.sqlExpression)).contains(element.sqlExpression)
}
}
// MARK: - Arithmetic Operators (+, -, *, /)
extension SQLSpecificExpressible {
/// The `*` SQL operator.
public static func * (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.associativeBinary(.multiply, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `*` SQL operator.
public static func * (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.associativeBinary(.multiply, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `*` SQL operator.
public static func * (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.associativeBinary(.multiply, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `/` SQL operator.
public static func / (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.divide, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `/` SQL operator.
public static func / (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.divide, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `/` SQL operator.
public static func / (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.divide, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `+` SQL operator.
public static func + (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.associativeBinary(.add, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `+` SQL operator.
public static func + (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.associativeBinary(.add, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `+` SQL operator.
public static func + (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.associativeBinary(.add, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `-` SQL operator.
public static prefix func - (value: Self) -> SQLExpression {
.unary(.minus, value.sqlExpression)
}
/// The `-` SQL operator.
public static func - (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.subtract, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `-` SQL operator.
public static func - (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.subtract, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `-` SQL operator.
public static func - (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.subtract, lhs.sqlExpression, rhs.sqlExpression)
}
// MARK: - Logical Operators (AND, OR, NOT)
/// The `AND` SQL operator.
public static func && (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.associativeBinary(.and, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `AND` SQL operator.
public static func && (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.associativeBinary(.and, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `AND` SQL operator.
public static func && (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.associativeBinary(.and, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `OR` SQL operator.
public static func || (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.associativeBinary(.or, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `OR` SQL operator.
public static func || (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.associativeBinary(.or, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `OR` SQL operator.
public static func || (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.associativeBinary(.or, [lhs.sqlExpression, rhs.sqlExpression])
}
/// A negated logical SQL expression.
///
/// For example:
///
/// ```swift
/// // NOT isBlue
/// !Column("isBlue")
/// ```
///
/// Some expressions are negated with specific SQL operators:
///
/// ```swift
/// // id NOT BETWEEN 1 AND 10
/// !((1...10).contains(Column("id")))
/// ```
public static prefix func ! (value: Self) -> SQLExpression {
value.sqlExpression.is(.falsey)
}
}
// MARK: - Bitwise Operators (&, |, ~, <<, >>)
extension SQLSpecificExpressible {
/// The `&` SQL operator.
public static func & (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.associativeBinary(.bitwiseAnd, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `&` SQL operator.
public static func & (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.associativeBinary(.bitwiseAnd, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `&` SQL operator.
public static func & (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.associativeBinary(.bitwiseAnd, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `|` SQL operator.
public static func | (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.associativeBinary(.bitwiseOr, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `|` SQL operator.
public static func | (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.associativeBinary(.bitwiseOr, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `|` SQL operator.
public static func | (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.associativeBinary(.bitwiseOr, [lhs.sqlExpression, rhs.sqlExpression])
}
/// The `<<` SQL operator.
public static func << (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.leftShift, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<<` SQL operator.
public static func << (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.leftShift, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `<<` SQL operator.
public static func << (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.leftShift, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>>` SQL operator.
public static func >> (lhs: Self, rhs: some SQLExpressible) -> SQLExpression {
.binary(.rightShift, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>>` SQL operator.
public static func >> (lhs: some SQLExpressible, rhs: Self) -> SQLExpression {
.binary(.rightShift, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `>>` SQL operator.
public static func >> (lhs: Self, rhs: some SQLSpecificExpressible) -> SQLExpression {
.binary(.rightShift, lhs.sqlExpression, rhs.sqlExpression)
}
/// The `~` SQL operator.
public static prefix func ~ (value: Self) -> SQLExpression {
.unary(.bitwiseNot, value.sqlExpression)
}
}
// MARK: - Like Operator
extension SQLSpecificExpressible {
/// The `LIKE` SQL operator.
///
/// For example:
///
/// ```swift
/// // email LIKE '%@example.com"
/// Column("email").like("%@example.com")
///
/// // title LIKE '%10\%%' ESCAPE '\'
/// Column("title").like("%10\\%%", escape: "\\")
/// ```
public func like(_ pattern: some SQLExpressible, escape: (any SQLExpressible)? = nil) -> SQLExpression {
.escapableBinary(.like, sqlExpression, pattern.sqlExpression, escape: escape?.sqlExpression)
}
}
@@ -0,0 +1,150 @@
/// An SQL ordering term.
///
/// `SQLOrdering` is an opaque representation of an SQL ordering term.
/// You generally build `SQLOrdering` from other expressions. For example:
///
/// ```swift
/// Column("score").desc
/// SQL("score DESC").sqlOrdering
/// ```
///
/// `SQLOrdering` is better used as the return type of a function. For
/// function arguments, prefer the ``SQLOrderingTerm`` protocol.
///
/// Related SQLite documentation: <https://www.sqlite.org/syntax/ordering-term.html>
public struct SQLOrdering {
private var impl: Impl
private enum Impl {
/// An expression
///
/// ORDER BY score
case expression(SQLExpression)
/// An ascending expression
///
/// ORDER BY score ASC
case asc(SQLExpression)
/// An descending expression
///
/// ORDER BY score DESC
case desc(SQLExpression)
/// Only available from SQLite 3.30.0
case ascNullsLast(SQLExpression)
/// Only available from SQLite 3.30.0
case descNullsFirst(SQLExpression)
/// A literal SQL ordering
case literal(SQL)
}
static func expression(_ expression: SQLExpression) -> SQLOrdering {
self.init(impl: .expression(expression))
}
static func asc(_ expression: SQLExpression) -> SQLOrdering {
self.init(impl: .asc(expression))
}
static func desc(_ expression: SQLExpression) -> SQLOrdering {
self.init(impl: .desc(expression))
}
static func ascNullsLast(_ expression: SQLExpression) -> SQLOrdering {
self.init(impl: .ascNullsLast(expression))
}
static func descNullsFirst(_ expression: SQLExpression) -> SQLOrdering {
self.init(impl: .descNullsFirst(expression))
}
static func literal(_ sqlLiteral: SQL) -> SQLOrdering {
self.init(impl: .literal(sqlLiteral))
}
}
extension SQLOrdering {
func sql(_ context: SQLGenerationContext) throws -> String {
switch impl {
case .expression(let expression):
return try expression.sql(context)
case .asc(let expression):
return try expression.sql(context) + " ASC"
case .desc(let expression):
return try expression.sql(context) + " DESC"
case .ascNullsLast(let expression):
return try expression.sql(context) + " ASC NULLS LAST"
case .descNullsFirst(let expression):
return try expression.sql(context) + " DESC NULLS FIRST"
case .literal(let literal):
return try literal.sql(context)
}
}
}
extension SQLOrdering {
func qualified(with alias: TableAlias) -> SQLOrdering {
switch impl {
case .expression(let expression):
return .expression(expression.qualified(with: alias))
case .asc(let expression):
return .asc(expression.qualified(with: alias))
case .desc(let expression):
return .desc(expression.qualified(with: alias))
case .ascNullsLast(let expression):
return .ascNullsLast(expression.qualified(with: alias))
case .descNullsFirst(let expression):
return .descNullsFirst(expression.qualified(with: alias))
case .literal(let literal):
return .literal(literal.qualified(with: alias))
}
}
}
extension SQLOrdering {
var reversed: SQLOrdering {
switch impl {
case .expression(let expression):
return .desc(expression)
case .asc(let expression):
return .desc(expression)
case .desc(let expression):
return .asc(expression)
case .ascNullsLast(let expression):
return .descNullsFirst(expression)
case .descNullsFirst(let expression):
return .ascNullsLast(expression)
case .literal:
fatalError("""
Ordering literals can't be reversed. \
To resolve this error, order by expression literals instead. \
For example: order(SQL("(score + bonus)").sqlExpression)
""")
}
}
}
// MARK: - SQLOrderingTerm
/// A type that can be used as an SQL ordering term.
///
/// Related SQLite documentation <https://www.sqlite.org/syntax/ordering-term.html>
///
/// ## Topics
///
/// ### Supporting Type
///
/// - ``SQLOrdering``
public protocol SQLOrderingTerm {
/// Returns an SQL ordering.
var sqlOrdering: SQLOrdering { get }
}
extension SQLOrdering: SQLOrderingTerm {
// Not a real deprecation, just a usage warning
@available(*, deprecated, message: "Already QLOrdering:")
public var sqlOrdering: SQLOrdering { self }
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,336 @@
/// An SQL result column.
///
/// `SQLSelection` is an opaque representation of an SQL result column.
/// You generally build `SQLSelection` from other expressions. For example:
///
/// ```swift
/// // Aliased expressions
/// (Column("score") + Column("bonus")).forKey("total")
///
/// // Literal selection
/// SQL("IFNULL(name, \(defaultName)) AS name").sqlSelection
/// ```
///
/// `SQLSelection` is better used as the return type of a function. For
/// function arguments, prefer the ``SQLSelectable`` protocol.
///
/// Related SQLite documentation: <https://www.sqlite.org/syntax/result-column.html>
public struct SQLSelection {
private var impl: Impl
/// The private implementation of the public `SQLSelection`.
private enum Impl {
/// All columns: `*`
case allColumns
/// All columns, qualified: `player.*`
case qualifiedAllColumns(TableAlias)
/// An expression
case expression(SQLExpression)
/// An aliased expression
///
/// <expression> AS name
case aliasedExpression(SQLExpression, String)
/// A literal SQL selection
case literal(SQL)
}
/// All columns: `*`
static let allColumns = SQLSelection(impl: .allColumns)
/// All columns, qualified: `player.*`
static func qualifiedAllColumns(_ alias: TableAlias) -> Self {
self.init(impl: .qualifiedAllColumns(alias))
}
/// An expression
static func expression(_ expression: SQLExpression) -> Self {
self.init(impl: .expression(expression))
}
/// An aliased expression
///
/// <expression> AS name
static func aliasedExpression(_ expression: SQLExpression, _ name: String) -> Self {
self.init(impl: .aliasedExpression(expression, name))
}
/// A literal SQL selection
static func literal(_ sqlLiteral: SQL) -> Self {
self.init(impl: .literal(sqlLiteral))
}
}
extension SQLSelection {
/// Returns the number of columns in the selection.
///
/// Returns nil when the number of columns is unknown.
func columnCount(_ context: SQLGenerationContext) throws -> Int? {
switch impl {
case .allColumns:
// Likely a GRDB bug: we can't count the number of columns in an
// unqualified table.
return nil
case let .qualifiedAllColumns(alias):
return try context.columnCount(in: alias.tableName)
case .expression,
.aliasedExpression:
return 1
case .literal:
// We do not embed any SQL parser: we can't count the number of
// columns in a literal selection.
return nil
}
}
/// If the selection can be counted, return how to count it.
func count(distinct: Bool) -> SQLCount? {
switch impl {
case .allColumns:
// SELECT DISTINCT * FROM tableName ...
if distinct {
// Can't count
return nil
}
// SELECT * FROM tableName ...
// ->
// SELECT COUNT(*) FROM tableName ...
return .all
case .qualifiedAllColumns:
return nil
case let .expression(expression),
let .aliasedExpression(expression, _):
if distinct {
// SELECT DISTINCT expr FROM tableName ...
// ->
// SELECT COUNT(DISTINCT expr) FROM tableName ...
return .distinct(expression)
} else {
// SELECT expr FROM tableName ...
// ->
// SELECT COUNT(*) FROM tableName ...
return .all
}
case .literal:
return nil
}
}
/// Returns the SQL that feeds the argument of the `COUNT` function.
///
/// For example:
///
/// COUNT(*)
/// COUNT(id)
/// ^---- countedSQL
///
/// - parameter context: An SQL generation context which accepts
/// statement arguments.
func countedSQL(_ context: SQLGenerationContext) throws -> String {
switch impl {
case .allColumns:
return "*"
case let .qualifiedAllColumns(alias):
if context.qualifier(for: alias) != nil {
// SELECT COUNT(t.*) is invalid SQL
fatalError("Not implemented, or invalid query")
}
return "*"
case let .expression(expression),
let .aliasedExpression(expression, _):
return try expression.sql(context)
case .literal:
fatalError("""
Selection literals can't be counted. \
To resolve this error, select one or several literal expressions instead. \
See SQL.sqlExpression.
""")
}
}
/// Returns the SQL that feeds the selection of a `SELECT` statement.
///
/// For example:
///
/// 1
/// name
/// COUNT(*)
/// (score + bonus) AS total
///
/// See <https://sqlite.org/syntax/result-column.html>
///
/// - parameter context: An SQL generation context which accepts
/// statement arguments.
func sql(_ context: SQLGenerationContext) throws -> String {
switch impl {
case .allColumns:
return "*"
case let .qualifiedAllColumns(alias):
if let qualifier = context.qualifier(for: alias) {
return qualifier.quotedDatabaseIdentifier + ".*"
}
return "*"
case let .expression(expression):
return try expression.sql(context)
case let .aliasedExpression(expression, name):
return try expression.sql(context) + " AS " + name.quotedDatabaseIdentifier
case let .literal(sqlLiteral):
return try sqlLiteral.sql(context)
}
}
/// Returns true if the selection is an aggregate.
///
/// When in doubt, returns false.
///
/// SELECT * -- false
/// SELECT score -- false
/// SELECT COUNT(*) -- true
/// SELECT MAX(score) -- true
/// SELECT MAX(score) + 1 -- true
///
/// This method makes it possible to avoid inserting `LIMIT 1` to the SQL
/// of some requests:
///
/// // SELECT MAX("score") FROM "player"
/// try Player.select(max(Column("score")), as: Int.self).fetchOne(db)
///
/// // SELECT "score" FROM "player" LIMIT 1
/// try Player.select(Column("score"), as: Int.self).fetchOne(db)
var isAggregate: Bool {
switch impl {
case let .expression(expression),
let .aliasedExpression(expression, _):
return expression.isAggregate
default:
return false
}
}
/// Returns a qualified selection
func qualified(with alias: TableAlias) -> SQLSelection {
switch impl {
case .qualifiedAllColumns:
return self
case .allColumns:
return .qualifiedAllColumns(alias)
case let .expression(expression):
return .expression(expression.qualified(with: alias))
case let .aliasedExpression(expression, name):
return .aliasedExpression(expression.qualified(with: alias), name)
case let .literal(sqlLiteral):
return .literal(sqlLiteral.qualified(with: alias))
}
}
/// Supports SQLRelation.fetchCount.
///
/// See <https://github.com/groue/GRDB.swift/issues/1357>
var isTriviallyCountable: Bool {
switch impl {
case .aliasedExpression, .literal:
return false
case .allColumns, .qualifiedAllColumns, .expression:
return true
}
}
}
extension [SQLSelection] {
/// Returns the number of columns in the selection.
///
/// This method raises a fatal error if the selection contains a literal,
///
/// See `SQLSelection.columnCount(_:)` for testability.
func columnCount(_ context: SQLGenerationContext) throws -> Int {
try reduce(0) { acc, selection in
guard let count = try selection.columnCount(context) else {
// Found an SQL literal:
// - Player.select(sql: "id, name, score")
// - Player.select(literal: "id, name, score")
fatalError("""
Selection literals don't known how many columns they contain. \
To resolve this error, select one or several expressions instead.
""")
}
return acc + count
}
}
}
enum SQLCount {
/// Represents `COUNT(*)`
case all
/// Represents `COUNT(DISTINCT expression)`
case distinct(SQLExpression)
}
// MARK: - SQLSelectable
/// A type that can be used as SQL result columns.
///
/// Related SQLite documentation <https://www.sqlite.org/syntax/result-column.html>
///
/// ## Topics
///
/// ### Supporting Types
///
/// - ``AllColumns``
/// - ``SQLSelection``
public protocol SQLSelectable {
/// Returns an SQL selection.
var sqlSelection: SQLSelection { get }
}
extension SQLSelection: SQLSelectable {
// Not a real deprecation, just a usage warning
@available(*, deprecated, message: "Already SQLSelection")
public var sqlSelection: SQLSelection { self }
}
// MARK: - AllColumns
/// `AllColumns` is the `*` in `SELECT *`.
///
/// For example:
///
/// ```swift
/// try dbQueue.read { db in
/// // SELECT * FROM player
/// let players = try Player.select(AllColumns()).fetchAll(db)
/// }
/// ```
public struct AllColumns: Sendable {
/// The `*` selection.
public init() { }
}
extension AllColumns: SQLSelectable {
public var sqlSelection: SQLSelection {
.allColumns
}
}
@@ -0,0 +1,136 @@
/// An SQL subquery.
///
/// `SQLSubquery` is an opaque representation of an SQL subquery.
public struct SQLSubquery {
private var impl: Impl
private enum Impl {
/// A literal SQL query
case literal(SQL)
/// A query interface relation
case relation(SQLRelation)
}
static func literal(_ sqlLiteral: SQL) -> Self {
self.init(impl: .literal(sqlLiteral))
}
static func relation(_ relation: SQLRelation) -> Self {
self.init(impl: .relation(relation))
}
}
extension SQLSubquery {
/// The number of columns selected by the subquery.
///
/// This method makes it possible to find the columns of a CTE in a request
/// that includes a CTE association:
///
/// // WITH cte AS (SELECT 1 AS a, 2 AS b)
/// // SELECT player.*, cte.*
/// // FROM player
/// // JOIN cte
/// let cte = CommonTableExpression(named: "cte", sql: "SELECT 1 AS a, 2 AS b")
/// let request = Player
/// .with(cte)
/// .including(required: Player.association(to: cte))
/// let row = try Row.fetchOne(db, request)!
///
/// // We know that "SELECT 1 AS a, 2 AS b" selects two columns,
/// // so we can find cte columns in the row:
/// row.scopes["cte"] // [a:1, b:2]
func columnCount(_ db: Database) throws -> Int {
switch impl {
case let .literal(sqlLiteral):
// Compile request. We can freely use the statement cache because we
// do not execute the statement or modify its arguments.
let context = SQLGenerationContext(db)
let sql = try sqlLiteral.sql(context)
let statement = try db.cachedStatement(sql: sql)
return statement.columnCount
case let .relation(relation):
return try SQLQueryGenerator(relation: relation).columnCount(db)
}
}
}
extension SQLSubquery {
/// Returns the subquery SQL.
///
/// For example:
///
/// // SELECT *
/// // FROM "player"
/// // WHERE "score" = (SELECT MAX("score") FROM "player")
/// let maxScore = Player.select(max(Column("score")))
/// let players = try Player
/// .filter(Column("score") == maxScore)
/// .fetchAll(db)
///
/// - parameter context: An SQL generation context.
/// - parameter singleResult: A hint that a single result row will be
/// consumed. Implementations can optionally use it to optimize the
/// generated SQL, for example by adding a `LIMIT 1` SQL clause.
/// - returns: An SQL string.
func sql(_ context: SQLGenerationContext) throws -> String {
switch impl {
case let .literal(sqlLiteral):
return try sqlLiteral.sql(context)
case let .relation(relation):
return try SQLQueryGenerator(relation: relation).requestSQL(context)
}
}
}
// MARK: - SQLSubqueryable
/// A type that can be used as SQL subquery.
///
/// Related SQLite documentation <https://www.sqlite.org/syntax/select-stmt.html>
///
/// ## Topics
///
/// ### Supporting Types
///
/// - ``SQLSubquery``
public protocol SQLSubqueryable: SQLSpecificExpressible {
var sqlSubquery: SQLSubquery { get }
}
extension SQLSubquery: SQLSubqueryable {
// Not a real deprecation, just a usage warning
@available(*, deprecated, message: "Already SQLSubquery")
public var sqlSubquery: SQLSubquery { self }
}
extension SQLSubqueryable {
/// Returns a subquery expression.
public var sqlExpression: SQLExpression {
.subquery(sqlSubquery)
}
}
extension SQLSubqueryable {
/// Returns an expression that checks the inclusion of the expression in
/// the subquery.
///
/// // 1000 IN (SELECT score FROM player)
/// let request = Player.select(Column("score"), as: Int.self)
/// let condition = request.contains(1000)
public func contains(_ element: some SQLExpressible) -> SQLExpression {
SQLCollection.subquery(sqlSubquery).contains(element.sqlExpression)
}
/// Returns an expression that is true if and only if the subquery would
/// return one or more rows.
///
/// // EXISTS (SELECT * FROM player WHERE name = 'Arthur')
/// let request = Player.filter(Column("name") == "Arthur")
/// let condition = request.exists()
public func exists() -> SQLExpression {
.exists(sqlSubquery)
}
}
File diff suppressed because it is too large Load Diff