GRDB.swift/GRDB/QueryInterface/SQLSelectQuery.swift
Gwendal Roué a8b067885f Merge branch 'master' of github.com:groue/GRDB.swift into Swift3
Conflicts:
	DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.pbxproj
	GRDB/Core/Database.swift
	README.md
	TODO.md
2016-09-08 08:37:58 +02:00

750 lines
26 KiB
Swift

#if !USING_BUILTIN_SQLITE
#if os(OSX)
import SQLiteMacOSX
#elseif os(iOS)
#if (arch(i386) || arch(x86_64))
import SQLiteiPhoneSimulator
#else
import SQLiteiPhoneOS
#endif
#elseif os(watchOS)
#if (arch(i386) || arch(x86_64))
import SQLiteWatchSimulator
#else
import SQLiteWatchOS
#endif
#endif
#endif
// MARK: - _SQLSelectQuery
/// This type is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public struct _SQLSelectQuery {
var selection: [_SQLSelectable]
var isDistinct: Bool
var source: _SQLSource?
var whereExpression: _SQLExpression?
var groupByExpressions: [_SQLExpression]
var orderings: [_SQLOrderable]
var isReversed: Bool
var havingExpression: _SQLExpression?
var limit: _SQLLimit?
init(
select selection: [_SQLSelectable],
isDistinct: Bool = false,
from source: _SQLSource? = nil,
filter whereExpression: _SQLExpression? = nil,
groupBy groupByExpressions: [_SQLExpression] = [],
orderBy orderings: [_SQLOrderable] = [],
isReversed: Bool = false,
having havingExpression: _SQLExpression? = nil,
limit: _SQLLimit? = nil)
{
self.selection = selection
self.isDistinct = isDistinct
self.source = source
self.whereExpression = whereExpression
self.groupByExpressions = groupByExpressions
self.orderings = orderings
self.isReversed = isReversed
self.havingExpression = havingExpression
self.limit = limit
}
func sql(_ arguments: inout StatementArguments?) -> String {
var sql = "SELECT"
if isDistinct {
sql += " DISTINCT"
}
assert(!selection.isEmpty)
sql += " " + selection.map { $0.resultColumnSQL(&arguments) }.joined(separator: ", ")
if let source = source {
sql += " FROM " + source.sql(&arguments)
}
if let whereExpression = whereExpression {
sql += " WHERE " + whereExpression.sql(&arguments)
}
if !groupByExpressions.isEmpty {
sql += " GROUP BY " + groupByExpressions.map { $0.sql(&arguments) }.joined(separator: ", ")
}
if let havingExpression = havingExpression {
sql += " HAVING " + havingExpression.sql(&arguments)
}
var orderings = self.orderings
if isReversed {
if orderings.isEmpty {
// https://www.sqlite.org/lang_createtable.html#rowid
//
// > The rowid value can be accessed using one of the special
// > case-independent names "rowid", "oid", or "_rowid_" in
// > place of a column name. If a table contains a user defined
// > column named "rowid", "oid" or "_rowid_", then that name
// > always refers the explicitly declared column and cannot be
// > used to retrieve the integer rowid value.
//
// Here we assume that _rowid_ is not a custom column.
// TODO: support for user-defined _rowid_ column.
// TODO: support for WITHOUT ROWID tables.
orderings = [Column("_rowid_").desc]
} else {
orderings = orderings.map { $0.reversedOrdering }
}
}
if !orderings.isEmpty {
sql += " ORDER BY " + orderings.map { $0.orderingSQL(&arguments) }.joined(separator: ", ")
}
if let limit = limit {
sql += " LIMIT " + limit.sql
}
return sql
}
/// Returns a query that counts the number of rows matched by self.
var countQuery: _SQLSelectQuery {
guard groupByExpressions.isEmpty && limit == nil else {
// SELECT ... GROUP BY ...
// SELECT ... LIMIT ...
return trivialCountQuery
}
guard let source = source, case .table(name: let tableName, alias: let alias) = source else {
// SELECT ... FROM (something which is not a table)
return trivialCountQuery
}
assert(!selection.isEmpty)
if selection.count == 1 {
let selectable = self.selection[0]
switch selectable.sqlSelectableKind {
case .star(sourceName: let sourceName):
guard !isDistinct else {
return trivialCountQuery
}
if let sourceName = sourceName {
guard sourceName == tableName || sourceName == alias else {
return trivialCountQuery
}
}
// SELECT * FROM tableName ...
// ->
// SELECT COUNT(*) FROM tableName ...
var countQuery = unorderedQuery
countQuery.selection = [_SQLExpression.count(selectable)]
return countQuery
case .expression(let expression):
// SELECT [DISTINCT] expr FROM tableName ...
if isDistinct {
// SELECT DISTINCT expr FROM tableName ...
// ->
// SELECT COUNT(DISTINCT expr) FROM tableName ...
var countQuery = unorderedQuery
countQuery.isDistinct = false
countQuery.selection = [_SQLExpression.countDistinct(expression)]
return countQuery
} else {
// SELECT expr FROM tableName ...
// ->
// SELECT COUNT(*) FROM tableName ...
var countQuery = unorderedQuery
countQuery.selection = [_SQLExpression.count(_SQLResultColumn.star(nil))]
return countQuery
}
}
} else {
// SELECT [DISTINCT] expr1, expr2, ... FROM tableName ...
guard !isDistinct else {
return trivialCountQuery
}
// SELECT expr1, expr2, ... FROM tableName ...
// ->
// SELECT COUNT(*) FROM tableName ...
var countQuery = unorderedQuery
countQuery.selection = [_SQLExpression.count(_SQLResultColumn.star(nil))]
return countQuery
}
}
func makeDeleteStatement(_ db: Database) -> UpdateStatement {
guard groupByExpressions.isEmpty else {
fatalError("Can't delete query with GROUP BY expression")
}
guard havingExpression == nil else {
fatalError("Can't delete query with GROUP BY expression")
}
guard limit == nil else {
fatalError("Can't delete query with limit")
}
var sql = "DELETE"
var arguments: StatementArguments? = StatementArguments()
if let source = source {
sql += " FROM " + source.sql(&arguments)
}
if let whereExpression = whereExpression {
sql += " WHERE " + whereExpression.sql(&arguments)
}
let statement = try! db.makeUpdateStatement(sql)
statement.arguments = arguments!
return statement
}
// SELECT COUNT(*) FROM (self)
private var trivialCountQuery: _SQLSelectQuery {
return _SQLSelectQuery(
select: [_SQLExpression.count(_SQLResultColumn.star(nil))],
from: .query(query: unorderedQuery, alias: nil))
}
/// Remove ordering
private var unorderedQuery: _SQLSelectQuery {
var query = self
query.isReversed = false
query.orderings = []
return query
}
}
// MARK: - _SQLSource
indirect enum _SQLSource {
case table(name: String, alias: String?)
case query(query: _SQLSelectQuery, alias: String?)
func sql(_ arguments: inout StatementArguments?) -> String {
switch self {
case .table(let table, let alias):
if let alias = alias {
return table.quotedDatabaseIdentifier + " AS " + alias.quotedDatabaseIdentifier
} else {
return table.quotedDatabaseIdentifier
}
case .query(let query, let alias):
if let alias = alias {
return "(" + query.sql(&arguments) + ") AS " + alias.quotedDatabaseIdentifier
} else {
return "(" + query.sql(&arguments) + ")"
}
}
}
}
// MARK: - _SQLOrderable
/// This protocol is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public protocol _SQLOrderable {
var reversedOrdering: _SQLOrderingExpression { get }
func orderingSQL(_ arguments: inout StatementArguments?) -> String
}
/// This type is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public enum _SQLOrderingExpression {
case asc(_SQLExpression)
case desc(_SQLExpression)
}
extension _SQLOrderingExpression : _SQLOrderable {
/// This property is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var reversedOrdering: _SQLOrderingExpression {
switch self {
case .asc(let expression):
return .desc(expression)
case .desc(let expression):
return .asc(expression)
}
}
/// This method is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public func orderingSQL(_ arguments: inout StatementArguments?) -> String {
switch self {
case .asc(let expression):
return expression.sql(&arguments) + " ASC"
case .desc(let expression):
return expression.sql(&arguments) + " DESC"
}
}
}
// MARK: - _SQLLimit
struct _SQLLimit {
let limit: Int
let offset: Int?
var sql: String {
if let offset = offset {
return "\(limit) OFFSET \(offset)"
} else {
return "\(limit)"
}
}
}
// MARK: - _SpecificSQLExpressible
/// This protocol is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public protocol _SpecificSQLExpressible : SQLExpressible {
// SQLExpressible can be adopted by Swift standard types, and user
// types, through the DatabaseValueConvertible protocol which inherits
// from SQLExpressible.
//
// For example, Int adopts SQLExpressible through
// DatabaseValueConvertible.
//
// _SpecificSQLExpressible, on the other side, is not adopted by any
// Swift standard type or any user type. It is only adopted by GRDB types,
// such as Column and _SQLExpression.
//
// This separation lets us define functions and operators that do not
// spill out. The three declarations below have no chance overloading a
// Swift-defined operator, or a user-defined operator:
//
// - ==(SQLExpressible, _SpecificSQLExpressible)
// - ==(_SpecificSQLExpressible, SQLExpressible)
// - ==(_SpecificSQLExpressible, _SpecificSQLExpressible)
}
extension _SpecificSQLExpressible where Self: _SQLOrderable {
/// This property is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var reversedOrdering: _SQLOrderingExpression {
return .desc(sqlExpression)
}
/// This method is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public func orderingSQL(_ arguments: inout StatementArguments?) -> String {
return sqlExpression.sql(&arguments)
}
}
extension _SpecificSQLExpressible where Self: _SQLSelectable {
/// This method is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public func resultColumnSQL(_ arguments: inout StatementArguments?) -> String {
return sqlExpression.sql(&arguments)
}
/// This method is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public func countedSQL(_ arguments: inout StatementArguments?) -> String {
return sqlExpression.sql(&arguments)
}
/// This property is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var sqlSelectableKind: _SQLSelectableKind {
return .expression(sqlExpression)
}
}
extension _SpecificSQLExpressible {
/// Returns a value that can be used as an argument to FetchRequest.order()
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var asc: _SQLOrderingExpression {
return .asc(sqlExpression)
}
/// Returns a value that can be used as an argument to FetchRequest.order()
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var desc: _SQLOrderingExpression {
return .desc(sqlExpression)
}
/// Returns a value that can be used as an argument to FetchRequest.select()
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public func aliased(_ alias: String) -> _SQLSelectable {
return _SQLResultColumn.expression(expression: sqlExpression, alias: alias)
}
}
/// This type is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public indirect enum _SQLExpression {
/// For example: `name || 'rrr' AS pirateName`
case sqlLiteral(String, StatementArguments?)
/// For example: `1` or `'foo'`
case value(DatabaseValueConvertible?) // TODO: switch to DatabaseValue?
/// For example: `name`, `table.name`
case identifier(identifier: String, sourceName: String?)
/// For example: `name = 'foo' COLLATE NOCASE`
case collate(_SQLExpression, String)
/// For example: `NOT condition`
case notOperator(_SQLExpression)
/// For example: `name = 'foo'`
case equalOperator(_SQLExpression, _SQLExpression)
/// For example: `name <> 'foo'`
case notEqualOperator(_SQLExpression, _SQLExpression)
/// For example: `name IS NULL`
case isOperator(_SQLExpression, _SQLExpression)
/// For example: `name IS NOT NULL`
case isNotOperator(_SQLExpression, _SQLExpression)
/// For example: `-value`
case prefixOperator(String, _SQLExpression)
/// For example: `age + 1`
case infixOperator(String, _SQLExpression, _SQLExpression)
/// For example: `id IN (1,2,3)`
case inOperator([_SQLExpression], _SQLExpression)
/// For example `id IN (SELECT ...)`
case inSubQuery(_SQLSelectQuery, _SQLExpression)
/// For example `EXISTS (SELECT ...)`
case exists(_SQLSelectQuery)
/// For example: `age BETWEEN 1 AND 2`
case between(value: _SQLExpression, min: _SQLExpression, max: _SQLExpression)
/// For example: `LOWER(name)`
case function(String, [_SQLExpression])
/// For example: `COUNT(*)`
case count(_SQLSelectable)
/// For example: `COUNT(DISTINCT name)`
case countDistinct(_SQLExpression)
///
func sql(_ arguments: inout StatementArguments?) -> String {
switch self {
case .sqlLiteral(let sql, let literalArguments):
if let literalArguments = literalArguments {
guard arguments != nil else {
fatalError("Not implemented")
}
arguments!.values.append(contentsOf: literalArguments.values)
for (name, value) in literalArguments.namedValues {
guard arguments!.namedValues[name] == nil else {
fatalError("argument \(String(reflecting: name)) can't be reused")
}
arguments!.namedValues[name] = value
}
}
return sql
case .value(let value):
guard let value = value else {
return "NULL"
}
if arguments == nil {
return value.sqlLiteral
} else {
arguments!.values.append(value)
return "?"
}
case .identifier(let identifier, let sourceName):
if let sourceName = sourceName {
return sourceName.quotedDatabaseIdentifier + "." + identifier.quotedDatabaseIdentifier
} else {
return identifier.quotedDatabaseIdentifier
}
case .collate(let expression, let collation):
let sql = expression.sql(&arguments)
let chars = sql.characters
if chars.last! == ")" {
return String(chars.prefix(upTo: chars.index(chars.endIndex, offsetBy: -1))) + " COLLATE " + collation + ")"
} else {
return sql + " COLLATE " + collation
}
case .notOperator(let condition):
switch condition {
case .notOperator(let expression):
return expression.sql(&arguments)
case .inOperator(let expressions, let expression):
if expressions.isEmpty {
return "1"
} else {
return "(" + expression.sql(&arguments) + " NOT IN (" + (expressions.map { $0.sql(&arguments) } as [String]).joined(separator: ", ") + "))"
}
case .inSubQuery(let subQuery, let expression):
return "(" + expression.sql(&arguments) + " NOT IN (" + subQuery.sql(&arguments) + "))"
case .exists(let subQuery):
return "(NOT EXISTS (" + subQuery.sql(&arguments) + "))"
case .equalOperator(let lhs, let rhs):
return _SQLExpression.notEqualOperator(lhs, rhs).sql(&arguments)
case .notEqualOperator(let lhs, let rhs):
return _SQLExpression.equalOperator(lhs, rhs).sql(&arguments)
case .isOperator(let lhs, let rhs):
return _SQLExpression.isNotOperator(lhs, rhs).sql(&arguments)
case .isNotOperator(let lhs, let rhs):
return _SQLExpression.isOperator(lhs, rhs).sql(&arguments)
default:
return "(NOT " + condition.sql(&arguments) + ")"
}
case .equalOperator(let lhs, let rhs):
switch (lhs, rhs) {
case (let lhs, .value(let rhs)) where rhs == nil:
// Swiftism!
// Turn `filter(a == nil)` into `a IS NULL` since the intention is obviously to check for NULL. `a = NULL` would evaluate to NULL.
return "(" + lhs.sql(&arguments) + " IS NULL)"
case (.value(let lhs), let rhs) where lhs == nil:
// Swiftism!
return "(" + rhs.sql(&arguments) + " IS NULL)"
default:
return "(" + lhs.sql(&arguments) + " = " + rhs.sql(&arguments) + ")"
}
case .notEqualOperator(let lhs, let rhs):
switch (lhs, rhs) {
case (let lhs, .value(let rhs)) where rhs == nil:
// Swiftism!
// Turn `filter(a != nil)` into `a IS NOT NULL` since the intention is obviously to check for NULL. `a <> NULL` would evaluate to NULL.
return "(" + lhs.sql(&arguments) + " IS NOT NULL)"
case (.value(let lhs), let rhs) where lhs == nil:
// Swiftism!
return "(" + rhs.sql(&arguments) + " IS NOT NULL)"
default:
return "(" + lhs.sql(&arguments) + " <> " + rhs.sql(&arguments) + ")"
}
case .isOperator(let lhs, let rhs):
switch (lhs, rhs) {
case (let lhs, .value(let rhs)) where rhs == nil:
return "(" + lhs.sql(&arguments) + " IS NULL)"
case (.value(let lhs), let rhs) where lhs == nil:
return "(" + rhs.sql(&arguments) + " IS NULL)"
default:
return "(" + lhs.sql(&arguments) + " IS " + rhs.sql(&arguments) + ")"
}
case .isNotOperator(let lhs, let rhs):
switch (lhs, rhs) {
case (let lhs, .value(let rhs)) where rhs == nil:
return "(" + lhs.sql(&arguments) + " IS NOT NULL)"
case (.value(let lhs), let rhs) where lhs == nil:
return "(" + rhs.sql(&arguments) + " IS NOT NULL)"
default:
return "(" + lhs.sql(&arguments) + " IS NOT " + rhs.sql(&arguments) + ")"
}
case .prefixOperator(let SQLOperator, let value):
return SQLOperator + value.sql(&arguments)
case .infixOperator(let SQLOperator, let lhs, let rhs):
return "(" + lhs.sql(&arguments) + " \(SQLOperator) " + rhs.sql(&arguments) + ")"
case .inOperator(let expressions, let expression):
guard !expressions.isEmpty else {
return "0"
}
return "(" + expression.sql(&arguments) + " IN (" + (expressions.map { $0.sql(&arguments) } as [String]).joined(separator: ", ") + "))"
case .inSubQuery(let subQuery, let expression):
return "(" + expression.sql(&arguments) + " IN (" + subQuery.sql(&arguments) + "))"
case .exists(let subQuery):
return "(EXISTS (" + subQuery.sql(&arguments) + "))"
case .between(value: let value, min: let min, max: let max):
return "(" + value.sql(&arguments) + " BETWEEN " + min.sql(&arguments) + " AND " + max.sql(&arguments) + ")"
case .function(let functionName, let functionArguments):
return functionName + "(" + (functionArguments.map { $0.sql(&arguments) } as [String]).joined(separator: ", ") + ")"
case .count(let counted):
return "COUNT(" + counted.countedSQL(&arguments) + ")"
case .countDistinct(let expression):
return "COUNT(DISTINCT " + expression.sql(&arguments) + ")"
}
}
}
extension _SQLExpression : _SpecificSQLExpressible {
/// This property is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var sqlExpression: _SQLExpression {
return self
}
}
extension _SQLExpression : _SQLSelectable {}
extension _SQLExpression : _SQLOrderable {}
// MARK: - _SQLSelectable
/// This protocol is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public protocol _SQLSelectable {
func resultColumnSQL(_ arguments: inout StatementArguments?) -> String
func countedSQL(_ arguments: inout StatementArguments?) -> String
var sqlSelectableKind: _SQLSelectableKind { get }
}
/// This type is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public enum _SQLSelectableKind {
case expression(_SQLExpression)
case star(sourceName: String?)
}
enum _SQLResultColumn {
case star(String?)
case expression(expression: _SQLExpression, alias: String)
}
extension _SQLResultColumn : _SQLSelectable {
func resultColumnSQL(_ arguments: inout StatementArguments?) -> String {
switch self {
case .star(let sourceName):
if let sourceName = sourceName {
return sourceName.quotedDatabaseIdentifier + ".*"
} else {
return "*"
}
case .expression(expression: let expression, alias: let alias):
return expression.sql(&arguments) + " AS " + alias.quotedDatabaseIdentifier
}
}
func countedSQL(_ arguments: inout StatementArguments?) -> String {
switch self {
case .star:
return "*"
case .expression(expression: let expression, alias: _):
return expression.sql(&arguments)
}
}
var sqlSelectableKind: _SQLSelectableKind {
switch self {
case .star(let sourceName):
return .star(sourceName: sourceName)
case .expression(expression: let expression, alias: _):
return .expression(expression)
}
}
}
// MARK: - Column
/// A column in the database
///
/// See https://github.com/groue/GRDB.swift#the-query-interface
public struct Column {
let sourceName: String?
/// The name of the column
public let name: String
/// Initializes a column given its name.
public init(_ name: String) {
self.name = name
self.sourceName = nil
}
init(_ name: String, sourceName: String?) {
self.name = name
self.sourceName = sourceName
}
}
extension Column : _SpecificSQLExpressible {
/// This property is an implementation detail of the query interface.
/// Do not use it directly.
///
/// See https://github.com/groue/GRDB.swift/#the-query-interface
public var sqlExpression: _SQLExpression {
return .identifier(identifier: name, sourceName: sourceName)
}
}
extension Column : _SQLSelectable {}
extension Column : _SQLOrderable {}