/// 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: 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 /// /// 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 /// /// 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 /// /// - 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 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 /// /// ## 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 } }