From fd8d39b3c176165fe2b32ba3e0d35ee8b2998a5a Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 16:29:44 -0400 Subject: [PATCH 01/21] add additional doc blocks --- .../SQLite/Codable/SQLiteDataDecoder.swift | 19 +++- .../SQLite/Codable/SQLiteQueryEncoder.swift | 28 ++++++ .../SQLiteQueryExpressionEncoder.swift | 28 ++++-- .../SQLiteQueryExpressionRepresentable.swift | 9 ++ Sources/SQLite/Codable/SQLiteRowDecoder.swift | 25 +++++ .../SQLite/Database/SQLiteConnection.swift | 99 +++++++++++-------- Sources/SQLite/Database/SQLiteDatabase.swift | 10 +- .../Query/SQLiteQuery+SelectBuilder.swift | 23 +++-- Sources/SQLite/Query/SQLiteQuery.swift | 10 ++ Tests/SQLiteTests/SQLiteTests.swift | 16 +++ 10 files changed, 209 insertions(+), 58 deletions(-) create mode 100644 Sources/SQLite/Codable/SQLiteQueryExpressionRepresentable.swift diff --git a/Sources/SQLite/Codable/SQLiteDataDecoder.swift b/Sources/SQLite/Codable/SQLiteDataDecoder.swift index 10c541c..66fba58 100644 --- a/Sources/SQLite/Codable/SQLiteDataDecoder.swift +++ b/Sources/SQLite/Codable/SQLiteDataDecoder.swift @@ -1,6 +1,21 @@ -struct SQLiteDataDecoder { - init() { } +/// Decodes `Decodable` types from `SQLiteData`. +/// +/// let string = try SQLiteDecoder().decode(String.self, from: .text("Hello")) +/// print(string) // "Hello" +/// +public struct SQLiteDataDecoder { + /// Creates a new `SQLiteDataDecoder`. + public init() { } + /// Decodes `Decodable` types from `SQLiteData`. + /// + /// let string = try SQLiteDecoder().decode(String.self, from: .text("Hello")) + /// print(string) // "Hello" + /// + /// - parameters: + /// - type: `Decodable` type to decode. + /// - data: `SQLiteData` to decode. + /// - returns: Instance of decoded type. public func decode(_ type: D.Type, from data: SQLiteData) throws -> D where D: Decodable { if let convertible = type as? SQLiteDataConvertible.Type { return try convertible.convertFromSQLiteData(data) as! D diff --git a/Sources/SQLite/Codable/SQLiteQueryEncoder.swift b/Sources/SQLite/Codable/SQLiteQueryEncoder.swift index a08ac2a..3576112 100644 --- a/Sources/SQLite/Codable/SQLiteQueryEncoder.swift +++ b/Sources/SQLite/Codable/SQLiteQueryEncoder.swift @@ -1,6 +1,34 @@ +/// Encodes keyed `Encodable` values to a `SQLiteQuery` expression dictionary. +/// +/// struct User: Codable { +/// var name: String +/// } +/// +/// let user = User(name: "Vapor") +/// let data = try SQLiteQueryEncoder().encode(user) +/// print(data) // ["name": .data(.text("Vapor"))] +/// +/// This encoder uses `SQLiteQueryExpressionEncoder` internally. Any types conforming to `SQLiteQueryExpressionRepresentable` +/// or `SQLiteDataConvertible` will be specially encoded. +/// +/// This encoder does _not_ support unkeyed or single value codable objects. public struct SQLiteQueryEncoder { + /// Creates a new `SQLiteQueryEncoder`. public init() { } + /// Encodes keyed `Encodable` values to a `SQLiteQuery` expression dictionary. + /// + /// struct User: Codable { + /// var name: String + /// } + /// + /// let user = User(name: "Vapor") + /// let data = try SQLiteQueryEncoder().encode(user) + /// print(data) // ["name": .data(.text("Vapor"))] + /// + /// - parameters: + /// - value: `Encodable` value to encode. + /// - returns: `SQLiteQuery` compatible data. public func encode(_ value: E) throws -> [String: SQLiteQuery.Expression] where E: Encodable { diff --git a/Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift b/Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift index d90599a..4c0deb2 100644 --- a/Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift +++ b/Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift @@ -1,11 +1,25 @@ -public protocol SQLiteQueryExpressionRepresentable { - var sqliteQueryExpression: SQLiteQuery.Expression { get } -} - -struct SQLiteQueryExpressionEncoder { - init() { } +/// Encodes `Encodable` values to `SQLiteQuery.Expression`. +/// +/// let expr = try SQLiteQueryExpressionEncoder().encode("Hello") +/// print(expr) // .data(.text("Hello")) +/// +/// Conform your types to `SQLiteQueryExpressionRepresentable` or `SQLiteDataConvertible` to +/// customize how they are encoded. +/// +/// Use `SQLiteQueryEncoder` to encode top-level nested structures, such as models. +public struct SQLiteQueryExpressionEncoder { + /// Creates a new `SQLiteQueryExpressionEncoder`. + public init() { } - func encode(_ value: E) throws -> SQLiteQuery.Expression + /// Encodes `Encodable` values to `SQLiteQuery.Expression`. + /// + /// let expr = try SQLiteQueryExpressionEncoder().encode("Hello") + /// print(expr) // .data(.text("Hello")) + /// + /// - parameters: + /// - value: `Encodable` value to encode. + /// - returns: `SQLiteQuery.Expression` representing the encoded data. + public func encode(_ value: E) throws -> SQLiteQuery.Expression where E: Encodable { if let sqlite = value as? SQLiteQueryExpressionRepresentable { diff --git a/Sources/SQLite/Codable/SQLiteQueryExpressionRepresentable.swift b/Sources/SQLite/Codable/SQLiteQueryExpressionRepresentable.swift new file mode 100644 index 0000000..214ae45 --- /dev/null +++ b/Sources/SQLite/Codable/SQLiteQueryExpressionRepresentable.swift @@ -0,0 +1,9 @@ +/// Types conforming to this protocol can implement custom logic for converting to +/// a `SQLiteQuery.Expression`. Conformance to this protocol will be checked when using +/// `SQLiteQueryExpressionEncoder` and `SQLiteQueryEncoder`. +/// +/// By default, types will encode to `SQLiteQuery.Expression.data(...)`. +public protocol SQLiteQueryExpressionRepresentable { + /// Custom `SQLiteQuery.Expression` to encode to. + var sqliteQueryExpression: SQLiteQuery.Expression { get } +} diff --git a/Sources/SQLite/Codable/SQLiteRowDecoder.swift b/Sources/SQLite/Codable/SQLiteRowDecoder.swift index 87bdb15..9e3a584 100644 --- a/Sources/SQLite/Codable/SQLiteRowDecoder.swift +++ b/Sources/SQLite/Codable/SQLiteRowDecoder.swift @@ -1,6 +1,31 @@ +/// Decodes `Decodable` types from SQLite rows (`[SQLiteColumn: SQLiteData]`). +/// +/// struct User: Codable { +/// var name: String +/// } +/// +/// let row: [SQLiteColumn: SQLiteData] ... +/// let user = try SQLiteRowDecoder().decode(User.self, from: row, table: "users") +/// +/// Uses `SQLiteDataDecoder` internally to decode each column. Use `SQLiteDataConvertible` to +/// customize how your types are decoded. public struct SQLiteRowDecoder { + /// Creates a new `SQLiteRowDecoder`. public init() { } + /// Decodes `Decodable` types from SQLite rows (`[SQLiteColumn: SQLiteData]`). + /// + /// struct User: Codable { + /// var name: String + /// } + /// + /// let row: [SQLiteColumn: SQLiteData] ... + /// let user = try SQLiteRowDecoder().decode(User.self, from: row, table: "users") + /// + /// - parameters: + /// - type: `Decodable` type to decode. + /// - data: SQLite row (`[SQLiteColumn: SQLiteData]`) to decode. + /// - returns: Instance of decoded type. public func decode(_ type: D.Type, from row: [SQLiteColumn: SQLiteData], table: String? = nil) throws -> D where D: Decodable { diff --git a/Sources/SQLite/Database/SQLiteConnection.swift b/Sources/SQLite/Database/SQLiteConnection.swift index 4fbb9bf..b899f07 100644 --- a/Sources/SQLite/Database/SQLiteConnection.swift +++ b/Sources/SQLite/Database/SQLiteConnection.swift @@ -4,28 +4,41 @@ import CSQLite import SQLite3 #endif +/// A connection to a SQLite database, created by `SQLiteDatabase`. +/// +/// let conn = try sqliteDB.newConnection(on: ...).wait() +/// +/// Use this connection to execute queries on the database. +/// +/// try conn.query("SELECT sqlite_version();").wait() +/// +/// You can also build queries, using the available query builders. +/// +/// let res = try conn.select() +/// .column(function: "sqlite_version", as: "version") +/// .run().wait() +/// public final class SQLiteConnection: BasicWorker, DatabaseConnection { + /// See `DatabaseConnection`. public typealias Database = SQLiteDatabase + /// See `DatabaseConnection`. public var isClosed: Bool + /// See `BasicWorker`. public let eventLoop: EventLoop + /// See `DatabaseConnection`. public var extend: Extend /// Optional logger, if set queries should be logged to it. public var logger: DatabaseLogger? + /// Reference to parent `SQLiteDatabase` that created this connection. + /// This reference will ensure the DB stays alive since this connection uses + /// it's C pointer handle. internal let database: SQLiteDatabase - /// Returns the last error message, if one exists. - internal var errorMessage: String? { - guard let raw = sqlite3_errmsg(database.handle) else { - return nil - } - return String(cString: raw) - } - /// Create a new SQLite conncetion. internal init(database: SQLiteDatabase, on worker: Worker) { self.database = database @@ -35,53 +48,50 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection { } /// Returns an identifier for the last inserted row. - public var lastAutoincrementID: Int? { - let id = sqlite3_last_insert_rowid(database.handle) - return Int(id) + public var lastAutoincrementID: Int64? { + return sqlite3_last_insert_rowid(database.handle) } - - /// Closes the database connection. - public func close() { - isClosed = true + + /// Returns the last error message, if one exists. + internal var errorMessage: String? { + guard let raw = sqlite3_errmsg(database.handle) else { + return nil + } + return String(cString: raw) } + /// Executes the supplied `SQLiteQuery` on the connection, aggregating the results into an array. + /// + /// let rows = try conn.query("SELECT * FROM users").wait() + /// + /// - parameters: + /// - query: `SQLiteQuery` to execute. + /// - returns: A `Future` containing array of rows. public func query(_ query: SQLiteQuery) -> Future<[[SQLiteColumn: SQLiteData]]> { - var binds: [SQLiteData] = [] - let sql = query.serialize(&binds) - return self.query(sql, binds) + var rows: [[SQLiteColumn: SQLiteData]] = [] + return self.query(query) { rows.append($0) }.map { rows } } + /// Executes the supplied `SQLiteQuery` on the connection, calling the supplied closure for each row returned. + /// + /// try conn.query("SELECT * FROM users") { row in + /// print(row) + /// }.wait() + /// + /// - parameters: + /// - query: `SQLiteQuery` to execute. + /// - onRow: Callback for handling each row. + /// - returns: A `Future` that signals completion of the query. public func query(_ query: SQLiteQuery, onRow: @escaping ([SQLiteColumn: SQLiteData]) throws -> ()) -> Future { var binds: [SQLiteData] = [] let sql = query.serialize(&binds) - return self.query(sql, binds, onRow: onRow) - } - - public func query(_ string: String, _ parameters: [SQLiteData] = [], decoding: D.Type) -> Future<[D]> - where D: Decodable - { - return query(string, parameters).map { try $0.map { try SQLiteRowDecoder().decode(D.self, from: $0) } } - } - - public func query(_ string: String, _ parameters: [SQLiteData] = []) -> Future<[[SQLiteColumn: SQLiteData]]> { - var rows: [[SQLiteColumn: SQLiteData]] = [] - return query(string, parameters) { rows.append($0) }.map { rows } - } - - public func query(_ string: String, _ parameters: [SQLiteData] = [], decoding: D.Type, onRow: @escaping (D) throws -> ()) -> Future - where D: Decodable - { - return query(string, parameters) { try onRow(SQLiteRowDecoder().decode(D.self, from: $0)) } - } - - public func query(_ string: String, _ parameters: [SQLiteData] = [], onRow: @escaping ([SQLiteColumn: SQLiteData]) throws -> ()) -> Future { let promise = eventLoop.newPromise(Void.self) // log before anything happens, in case there's an error - logger?.record(query: string, values: parameters.map { $0.description }) + logger?.record(query: sql, values: binds.map { $0.description }) database.blockingIO.submit { state in do { - let statement = try SQLiteStatement(query: string, on: self) - try statement.bind(parameters) + let statement = try SQLiteStatement(query: sql, on: self) + try statement.bind(binds) if let columns = try statement.getColumns() { while let row = try statement.nextRow(for: columns) { self.eventLoop.execute { @@ -100,4 +110,9 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection { } return promise.futureResult } + + /// See `DatabaseConnection`. + public func close() { + isClosed = true + } } diff --git a/Sources/SQLite/Database/SQLiteDatabase.swift b/Sources/SQLite/Database/SQLiteDatabase.swift index 2a19963..a841753 100644 --- a/Sources/SQLite/Database/SQLiteDatabase.swift +++ b/Sources/SQLite/Database/SQLiteDatabase.swift @@ -4,7 +4,15 @@ import CSQLite import SQLite3 #endif -/// SQlite database. Used to make connections. +/// An open SQLite database using in-memory or file-based storage. +/// +/// let sqliteDB = SQLiteDatabase(storage: .memory) +/// +/// Use this database to create new connections for executing queries. +/// +/// let conn = try sqliteDB.newConnection(on: ...).wait() +/// try conn.query("SELECT sqlite_version();").wait() +/// public final class SQLiteDatabase: Database, LogSupporting { /// The path to the SQLite file. public let storage: SQLiteStorage diff --git a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift index 2801814..b8e4b01 100644 --- a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift @@ -1,6 +1,7 @@ extension SQLiteQuery { public final class SelectBuilder: SQLitePredicateBuilder { public var select: Select + public var predicate: SQLiteQuery.Expression? { get { return select.predicate } set { select.predicate = newValue } @@ -13,14 +14,24 @@ extension SQLiteQuery { self.connection = connection } - @discardableResult - public func all() -> Self { - return columns(.all(nil)) + public func column(function: String, as alias: String? = nil) -> Self { + return column(function: function, .expressions(distinct: false, []), as: alias) + } + + public func column(function: String, _ parameters: Expression.Function.Parameters?, as alias: String? = nil) -> Self { + return column(expression: .function(.init(name: function, parameters: parameters)), as: alias) + } + + public func column(expression: Expression, as alias: String? = nil) -> Self { + return column(.expression(expression, alias: alias)) + } + + public func all(table: String? = nil) -> Self { + return column(.all(table)) } - @discardableResult - public func columns(_ columns: Select.ResultColumn...) -> Self { - select.columns += columns + public func column(_ column: Select.ResultColumn) -> Self { + select.columns.append(column) return self } diff --git a/Sources/SQLite/Query/SQLiteQuery.swift b/Sources/SQLite/Query/SQLiteQuery.swift index 52e7093..aefa52d 100644 --- a/Sources/SQLite/Query/SQLiteQuery.swift +++ b/Sources/SQLite/Query/SQLiteQuery.swift @@ -6,6 +6,13 @@ public enum SQLiteQuery { case insert(Insert) case select(Select) case update(Update) + case raw(String, [SQLiteData]) +} + +extension SQLiteQuery: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .raw(value, []) + } } extension SQLiteSerializer { @@ -18,6 +25,9 @@ extension SQLiteSerializer { case .select(let select): return serialize(select, &binds) case .insert(let insert): return serialize(insert, &binds) case .update(let update): return serialize(update, &binds) + case .raw(let string, let values): + binds = values + return string } } } diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index e6f38fd..2e19ed6 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -21,6 +21,22 @@ struct Galaxy: SQLiteTable { } class SQLiteTests: XCTestCase { + func testVersion() throws { + let conn = try SQLiteConnection.makeTest() + + let res = try conn.query("SELECT sqlite_version();").wait() + print(res) + } + + func testVersionBuild() throws { + let conn = try SQLiteConnection.makeTest() + + let res = try conn.select() + .column(function: "sqlite_version", as: "version") + .run().wait() + print(res) + } + func testSQLQuery() throws { let conn = try SQLiteConnection.makeTest() From 159e72ea1a17cba22b608c7b9661f746e2109d6a Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 16:35:42 -0400 Subject: [PATCH 02/21] SQLiteDatabase doc blocks --- .../Database/DatabaseIdentifier+SQLite.swift | 6 ++++++ Sources/SQLite/Database/SQLiteDatabase.swift | 19 +++++++++++-------- Sources/SQLite/Database/SQLiteStorage.swift | 6 +++++- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 Sources/SQLite/Database/DatabaseIdentifier+SQLite.swift diff --git a/Sources/SQLite/Database/DatabaseIdentifier+SQLite.swift b/Sources/SQLite/Database/DatabaseIdentifier+SQLite.swift new file mode 100644 index 0000000..7773c72 --- /dev/null +++ b/Sources/SQLite/Database/DatabaseIdentifier+SQLite.swift @@ -0,0 +1,6 @@ +extension DatabaseIdentifier { + /// Default `DatabaseIdentifier` for SQLite databases. + public static var sqlite: DatabaseIdentifier { + return "sqlite" + } +} diff --git a/Sources/SQLite/Database/SQLiteDatabase.swift b/Sources/SQLite/Database/SQLiteDatabase.swift index a841753..6fdf612 100644 --- a/Sources/SQLite/Database/SQLiteDatabase.swift +++ b/Sources/SQLite/Database/SQLiteDatabase.swift @@ -14,14 +14,23 @@ import SQLite3 /// try conn.query("SELECT sqlite_version();").wait() /// public final class SQLiteDatabase: Database, LogSupporting { - /// The path to the SQLite file. + /// SQLite storage method. See `SQLiteStorage`. public let storage: SQLiteStorage + /// Thread pool for performing blocking IO work. See `BlockingIOThreadPool`. internal let blockingIO: BlockingIOThreadPool + /// Internal SQLite database handle. internal let handle: OpaquePointer /// Create a new SQLite database. + /// + /// let sqliteDB = SQLiteDatabase(storage: .memory) + /// + /// - parameters: + /// - storage: SQLite storage method. See `SQLiteStorage`. + /// - threadPool: Thread pool for performing blocking IO work. See `BlockingIOThreadPool`. + /// - throws: Errors creating the SQLite database. public init(storage: SQLiteStorage = .memory, threadPool: BlockingIOThreadPool? = nil) throws { self.storage = storage self.blockingIO = threadPool ?? BlockingIOThreadPool(numberOfThreads: 2) @@ -46,6 +55,7 @@ public final class SQLiteDatabase: Database, LogSupporting { conn.logger = logger } + /// Closes the open SQLite handle on deinit. deinit { sqlite3_close(handle) self.blockingIO.shutdownGracefully { error in @@ -55,10 +65,3 @@ public final class SQLiteDatabase: Database, LogSupporting { } } } - -extension DatabaseIdentifier { - /// Default `DatabaseIdentifier` for SQLite databases. - public static var sqlite: DatabaseIdentifier { - return "sqlite" - } -} diff --git a/Sources/SQLite/Database/SQLiteStorage.swift b/Sources/SQLite/Database/SQLiteStorage.swift index b917894..ff26161 100644 --- a/Sources/SQLite/Database/SQLiteStorage.swift +++ b/Sources/SQLite/Database/SQLiteStorage.swift @@ -1,9 +1,13 @@ /// Available SQLite storage methods. public enum SQLiteStorage { + /// In-memory storage. Not persisted between application launches. + /// Good for unit testing or caching. case memory + + /// File-based storage, persisted between application launches. case file(path: String) - var path: String { + internal var path: String { switch self { case .memory: return ":memory:" case .file(let path): return path From dda6a954593be89f7aae8b509ab06ccc401c1912 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 19:05:16 -0400 Subject: [PATCH 03/21] add AlterTableBuilder docs --- .../SQLite/Query/SQLiteQuery+AlterTable.swift | 21 ++++- .../Query/SQLiteQuery+AlterTableBuilder.swift | 76 ++++++++++++++----- Tests/SQLiteTests/SQLiteTests.swift | 12 +-- 3 files changed, 82 insertions(+), 27 deletions(-) diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift index 1162f80..bda5cd0 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift @@ -1,13 +1,28 @@ extension SQLiteQuery { + /// Represents an `ALTER TABLE ...` query. + /// + /// See `SQLiteQuery.AlterTableBuilder` to build this query. public struct AlterTable { + /// Supported `ALTER TABLE` methods. public enum Value { + /// Renames the table. case rename(String) + + /// Adds a new column to the table. case addColumn(ColumnDefinition) } + /// Name of table to alter. public var table: TableName + + /// Type of `ALTER` to perform. public var value: Value + /// Creates a new `AlterTable`. + /// + /// - parameters: + /// - table: Name of table to alter. + /// - value: Type of `ALTER` to perform. public init(table: TableName, value: Value) { self.table = table self.value = value @@ -15,8 +30,10 @@ extension SQLiteQuery { } } +// MARK: Serialize + extension SQLiteSerializer { - func serialize(_ alter: SQLiteQuery.AlterTable, _ binds: inout [SQLiteData]) -> String { + internal func serialize(_ alter: SQLiteQuery.AlterTable, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] sql.append("ALTER TABLE") sql.append(serialize(alter.table)) @@ -24,7 +41,7 @@ extension SQLiteSerializer { return sql.joined(separator: " ") } - func serialize(_ value: SQLiteQuery.AlterTable.Value, _ binds: inout [SQLiteData]) -> String { + internal func serialize(_ value: SQLiteQuery.AlterTable.Value, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] switch value { case .rename(let name): diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift index f78e86c..f993a0b 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift @@ -1,35 +1,66 @@ extension SQLiteQuery { - public final class AlterTableBuilder { + /// Builds `AlterTable` queries. + public final class AlterTableBuilder where Table: SQLiteTable { + /// Query being built. public var alter: AlterTable + + /// Database connection to execute the query on. public let connection: SQLiteConnection - init(table: TableName, on connection: SQLiteConnection) { - self.alter = .init(table: table, value: .rename(table.name)) + /// Creates a new `AlterTableBuilder`. + /// + /// - parameters: + /// - table: Name of existing table to alter. + /// - connection: `SQLiteConnection` to perform the query on. + init(table: Table.Type, on connection: SQLiteConnection) { + self.alter = .init(table: .init(stringLiteral: Table.sqliteTableName), value: .rename(Table.sqliteTableName)) self.connection = connection } - @discardableResult - public func rename(to name: String) -> Self { - alter.value = .rename(name) + /// Renames the table. + /// + /// conn.alter(table: Bar.self).rename(to: "foo").run() + /// + /// - parameters: + /// - to: New table name. + /// - returns: Self for chaining. + public func rename(to tableName: TableName) -> Self { + alter.value = .rename(tableName.name) return self } - @discardableResult - public func addColumn( + /// Adds a new column to the table. Only one column can be added per `ALTER` statement. + /// + /// conn.alter(table: Planet.self).addColumn(for: \.name, type: .text, .notNull).run() + /// + /// - parameters: + /// - keyPath: Swift `KeyPath` to property that should be added. + /// - type: Name of type to use for this column. + /// - constraints: Zero or more column constraints to add. + /// - returns: Self for chaining. + public func addColumn( for keyPath: KeyPath, - _ typeName: TypeName? = nil, + type typeName: TypeName? = nil, _ constraints: SQLiteQuery.ColumnConstraint... - ) -> Self - where Table: SQLiteTable - { - alter.value = .addColumn(.init( - name: keyPath.qualifiedColumnName.name, - typeName: typeName, - constraints: constraints - )) + ) -> Self { + return addColumn(.init(name: keyPath.qualifiedColumnName.name, typeName: typeName, constraints: constraints)) + } + + /// Adds a new column to the table. Only one column can be added per `ALTER` statement. + /// + /// conn.alter(table: Planet.self).addColumn(...).run() + /// + /// - parameters: + /// - columnDefinition: Column definition to add. + /// - returns: Self for chaining. + public func addColumn(_ columnDefinition: ColumnDefinition) -> Self { + alter.value = .addColumn(columnDefinition) return self } + /// Runs the `ALTER` query. + /// + /// - returns: A `Future` that signals completion. public func run() -> Future { return connection.query(.alterTable(alter)).transform(to: ()) } @@ -37,9 +68,16 @@ extension SQLiteQuery { } extension SQLiteConnection { - public func alter
(table: Table.Type) -> SQLiteQuery.AlterTableBuilder + /// Creates a new `AlterTableBuilder`. + /// + /// conn.alter(table: Planet.self)... + /// + /// - parameters: + /// - table: Table to alter. + /// - returns: `AlterTableBuilder`. + public func alter
(table: Table.Type) -> SQLiteQuery.AlterTableBuilder
where Table: SQLiteTable { - return .init(table: .init(stringLiteral: Table.sqliteTableName), on: self) + return .init(table: Table.self, on: self) } } diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 2e19ed6..2e8b914 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -60,14 +60,14 @@ class SQLiteTests: XCTestCase { .run().wait() try conn.alter(table: Planet.self) - .addColumn(for: \Planet.name, .text, .notNull, .default(.literal("Unamed Planet"))) + .addColumn(for: \Planet.name, type: .text, .notNull, .default(.literal("Unamed Planet"))) .run().wait() try conn.insert(into: Galaxy.self) .value(Galaxy(name: "Milky Way")) .run().wait() - let galaxyID = conn.lastAutoincrementID! + let galaxyID = conn.lastAutoincrementID.flatMap(Int.init)! try conn.insert(into: Planet.self) .value(Planet(name: "Earth", galaxyID: galaxyID)) @@ -149,12 +149,12 @@ class SQLiteTests: XCTestCase { _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() _ = try database.query("CREATE TABLE `foo` (bar TEXT)").wait() - _ = try database.query("INSERT INTO `foo` VALUES(?)", [unicode.convertToSQLiteData()]).wait() + _ = try database.query(.raw("INSERT INTO `foo` VALUES(?)", [unicode.convertToSQLiteData()])).wait() let selectAllResults = try database.query("SELECT * FROM `foo`").wait().first XCTAssertNotNil(selectAllResults) XCTAssertEqual(selectAllResults!.firstValue(forColumn: "bar"), .text(unicode)) - let selectWhereResults = try database.query("SELECT * FROM `foo` WHERE bar = '\(unicode)'").wait().first + let selectWhereResults = try database.query(.raw("SELECT * FROM `foo` WHERE bar = '\(unicode)'", [])).wait().first XCTAssertNotNil(selectWhereResults) XCTAssertEqual(selectWhereResults!.firstValue(forColumn: "bar"), .text(unicode)) } @@ -165,7 +165,7 @@ class SQLiteTests: XCTestCase { _ = try database.query("DROP TABLE IF EXISTS foo").wait() _ = try database.query("CREATE TABLE foo (max INT)").wait() - _ = try database.query("INSERT INTO foo VALUES (?)", [max.convertToSQLiteData()]).wait() + _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [max.convertToSQLiteData()])).wait() if let result = try! database.query("SELECT * FROM foo").wait().first { XCTAssertEqual(result.firstValue(forColumn: "max"), .integer(max)) @@ -178,7 +178,7 @@ class SQLiteTests: XCTestCase { _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() _ = try database.query("CREATE TABLE foo (bar BLOB(4))").wait() - _ = try database.query("INSERT INTO foo VALUES (?)", [data.convertToSQLiteData()]).wait() + _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [data.convertToSQLiteData()])).wait() if let result = try database.query("SELECT * FROM foo").wait().first { XCTAssertEqual(result.firstValue(forColumn: "bar"), .blob(data)) From 7c07e730b36a87676dca96eb34ed1d00d3f56a08 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 19:41:16 -0400 Subject: [PATCH 04/21] column definition + column / table name cleanup --- .../SQLite/Query/SQLiteQuery+AlterTable.swift | 4 +- .../Query/SQLiteQuery+AlterTableBuilder.swift | 8 +- Sources/SQLite/Query/SQLiteQuery+Column.swift | 52 +++---- .../SQLiteQuery+ColumnConstraint+0.swift | 112 +++++++++++++++ ...teQuery+ColumnConstraint+Nullability.swift | 29 ++++ ...iteQuery+ColumnConstraint+PrimaryKey.swift | 38 +++++ .../SQLiteQuery+ColumnConstraint+Unique.swift | 23 +++ .../Query/SQLiteQuery+ColumnConstraint.swift | 135 ------------------ .../Query/SQLiteQuery+ColumnDefinition.swift | 4 +- .../Query/SQLiteQuery+CreateBuilder.swift | 4 +- .../Query/SQLiteQuery+DeleteBuilder.swift | 2 +- .../Query/SQLiteQuery+DropTableBuilder.swift | 2 +- .../Query/SQLiteQuery+Expression+0.swift | 2 +- .../SQLite/Query/SQLiteQuery+ForeignKey.swift | 8 +- Sources/SQLite/Query/SQLiteQuery+Insert.swift | 4 +- .../Query/SQLiteQuery+InsertBuilder.swift | 4 +- Sources/SQLite/Query/SQLiteQuery+Join.swift | 2 +- Sources/SQLite/Query/SQLiteQuery+Name.swift | 36 +++++ Sources/SQLite/Query/SQLiteQuery+Select.swift | 2 +- .../Query/SQLiteQuery+SelectBuilder.swift | 31 ++-- .../SQLite/Query/SQLiteQuery+SetValues.swift | 4 +- .../SQLite/Query/SQLiteQuery+TableName.swift | 14 +- .../Query/SQLiteQuery+UpdateBuilder.swift | 2 +- Tests/SQLiteTests/SQLiteTests.swift | 2 +- 24 files changed, 305 insertions(+), 219 deletions(-) create mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift create mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift create mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift create mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint.swift create mode 100644 Sources/SQLite/Query/SQLiteQuery+Name.swift diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift index bda5cd0..89ecc38 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift @@ -6,7 +6,7 @@ extension SQLiteQuery { /// Supported `ALTER TABLE` methods. public enum Value { /// Renames the table. - case rename(String) + case rename(Name) /// Adds a new column to the table. case addColumn(ColumnDefinition) @@ -46,7 +46,7 @@ extension SQLiteSerializer { switch value { case .rename(let name): sql.append("RENAME TO") - sql.append(escapeString(name)) + sql.append(serialize(name)) case .addColumn(let columnDefinition): sql.append("ADD") sql.append(serialize(columnDefinition, &binds)) diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift index f993a0b..3c342e4 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift @@ -13,7 +13,7 @@ extension SQLiteQuery { /// - table: Name of existing table to alter. /// - connection: `SQLiteConnection` to perform the query on. init(table: Table.Type, on connection: SQLiteConnection) { - self.alter = .init(table: .init(stringLiteral: Table.sqliteTableName), value: .rename(Table.sqliteTableName)) + self.alter = .init(table: Table.sqliteTableName, value: .rename(Table.sqliteTableName.name)) self.connection = connection } @@ -24,8 +24,8 @@ extension SQLiteQuery { /// - parameters: /// - to: New table name. /// - returns: Self for chaining. - public func rename(to tableName: TableName) -> Self { - alter.value = .rename(tableName.name) + public func rename(to tableName: Name) -> Self { + alter.value = .rename(tableName) return self } @@ -43,7 +43,7 @@ extension SQLiteQuery { type typeName: TypeName? = nil, _ constraints: SQLiteQuery.ColumnConstraint... ) -> Self { - return addColumn(.init(name: keyPath.qualifiedColumnName.name, typeName: typeName, constraints: constraints)) + return addColumn(.init(name: keyPath.sqliteColumnName.name, typeName: typeName, constraints: constraints)) } /// Adds a new column to the table. Only one column can be added per `ALTER` statement. diff --git a/Sources/SQLite/Query/SQLiteQuery+Column.swift b/Sources/SQLite/Query/SQLiteQuery+Column.swift index 54a4f6f..dd4be38 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Column.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Column.swift @@ -1,53 +1,37 @@ extension SQLiteQuery { - public struct QualifiedColumnName { - public var schema: String? - public var table: String? - public var name: ColumnName + /// Represents a SQLite column name with optional table name. + public struct ColumnName { + /// Optional table name. + public var table: TableName? + + /// Column name. + public var name: Name - public init(schema: String? = nil, table: String? = nil, name: ColumnName) { - self.schema = schema + /// Creates a new `ColumnName`. + public init(table: TableName? = nil, name: Name) { self.table = table self.name = name } } - - public struct ColumnName { - public var string: String - public init(_ string: String) { - self.string = string - } - } } -extension SQLiteQuery.QualifiedColumnName: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self.init(name: .init(stringLiteral: value)) - } -} +// MARK: String Literal extension SQLiteQuery.ColumnName: ExpressibleByStringLiteral { + /// See `ExpressibleByStringLiteral`. public init(stringLiteral value: String) { - self.init(value) + self.init(name: .init(stringLiteral: value)) } } +// MARK: Serialize + extension SQLiteSerializer { - func serialize(_ columns: [SQLiteQuery.ColumnName]) -> String { - return "(" + columns.map(serialize).joined(separator: ", ") + ")" - } - - func serialize(_ column: SQLiteQuery.QualifiedColumnName) -> String { - switch (column.schema, column.table) { - case (.some(let schema), .some(let table)): - return escapeString(schema) + "." + escapeString(table) + "." + serialize(column.name) - case (.none, .some(let table)): - return escapeString(table) + "." + serialize(column.name) - default: + func serialize(_ column: SQLiteQuery.ColumnName) -> String { + if let table = column.table { + return serialize(table) + "." + serialize(column.name) + } else { return serialize(column.name) } } - - func serialize(_ column: SQLiteQuery.ColumnName) -> String { - return escapeString(column.string) - } } diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift new file mode 100644 index 0000000..b956b32 --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift @@ -0,0 +1,112 @@ +extension SQLiteQuery { + /// SQLite column definition constraint. + public struct ColumnConstraint { + /// Creates a new `PRIMARY KEY` column constraint. + /// + /// - parameters: + /// - autoIncrement: If `true`, the primary key will auto increment. + /// `true` by default. + /// - returns: New column constraint. + public static func primaryKey(autoIncrement: Bool = true) -> ColumnConstraint { + return .init(.primaryKey(.init(autoIncrement: autoIncrement))) + } + + /// Creates a new `NOT NULL` column constraint. + public static var notNull: ColumnConstraint { + return .init(.nullability(.init(allowNull: false))) + } + + /// Creates a new `DEFAULT ` column constraint. + /// + /// - parameters + /// - expression: Expression to evaluate when setting the default value. + /// - returns: New column constraint. + public static func `default`(_ expression: Expression) -> ColumnConstraint { + return .init(.default(expression)) + } + + /// Creates a new `REFERENCES` column constraint. + /// + /// - parameters + /// - keyPath: Swift `KeyPath` to referenced column. + /// - returns: New column constraint. + public static func references(_ keyPath: KeyPath) -> ColumnConstraint + where Table: SQLiteTable + { + return .init(.references(.init( + foreignTable: Table.sqliteTableName, + foreignColumns: [keyPath.sqliteColumnName.name], + onDelete: nil, + onUpdate: nil, + match: nil, + deferrence: nil + ))) + } + + /// Supported column constraint values. + public enum Value { + /// `PRIMARY KEY` + case primaryKey(PrimaryKey) + + /// `NULL` or `NOT NULL` + case nullability(Nullability) + + /// `UNIQUE` + case unique(Unique) + + /// `CHECK` + case check(Expression) + + /// `DEFAULT` + case `default`(Expression) + + /// `COLLATE` + case collate(String) + + /// `REFERENCES` + case references(ForeignKeyReference) + } + + /// Optional constraint name. + public var name: String? + + /// Contraint value. + public var value: Value + + /// Creates a new `ColumnConstraint`. + /// + /// - parameters: + /// - name: Optional constraint name. + /// - value: Constraint value. + public init(name: String? = nil, _ value: Value) { + self.name = name + self.value = value + } + } +} + +// MARK: Serialize + +extension SQLiteSerializer { + func serialize(_ constraint: SQLiteQuery.ColumnConstraint, _ binds: inout [SQLiteData]) -> String { + var sql: [String] = [] + if let name = constraint.name { + sql.append("CONSTRAINT") + sql.append(escapeString(name)) + } + sql.append(serialize(constraint.value, &binds)) + return sql.joined(separator: " ") + } + + func serialize(_ value: SQLiteQuery.ColumnConstraint.Value, _ binds: inout [SQLiteData]) -> String { + switch value { + case .primaryKey(let primaryKey): return serialize(primaryKey) + case .nullability(let nullability): return serialize(nullability) + case .unique(let unique): return serialize(unique) + case .check(let expr): return "CHECK (" + serialize(expr, &binds) + ")" + case .default(let expr): return "DEFAULT (" + serialize(expr, &binds) + ")" + case .collate(let name): return "COLLATE " + name + case .references(let reference): return serialize(reference) + } + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift new file mode 100644 index 0000000..97134d0 --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift @@ -0,0 +1,29 @@ +extension SQLiteQuery.ColumnConstraint { + public struct Nullability { + public var allowNull: Bool + public var conflictResolution: SQLiteQuery.ConflictResolution? + + public init(allowNull: Bool = false, conflictResolution: SQLiteQuery.ConflictResolution? = nil) { + self.allowNull = allowNull + self.conflictResolution = conflictResolution + } + } +} + + +// MARK: Serialize + +extension SQLiteSerializer { + public func serialize(_ nullability: SQLiteQuery.ColumnConstraint.Nullability) -> String { + var sql: [String] = [] + if !nullability.allowNull { + sql.append("NOT") + } + sql.append("NULL") + if let conflictResolution = nullability.conflictResolution { + sql.append("ON CONFLICT") + sql.append(serialize(conflictResolution)) + } + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift new file mode 100644 index 0000000..acbcc45 --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift @@ -0,0 +1,38 @@ +extension SQLiteQuery.ColumnConstraint { + public struct PrimaryKey { + public var direction: SQLiteQuery.Direction? + public var conflictResolution: SQLiteQuery.ConflictResolution? + public var autoIncrement: Bool + + public init( + direction: SQLiteQuery.Direction? = nil, + conflictResolution: SQLiteQuery.ConflictResolution? = nil, + autoIncrement: Bool = false + ) { + self.direction = direction + self.conflictResolution = conflictResolution + self.autoIncrement = autoIncrement + } + } +} + + +// MARK: Serialize + +extension SQLiteSerializer { + public func serialize(_ primaryKey: SQLiteQuery.ColumnConstraint.PrimaryKey) -> String { + var sql: [String] = [] + sql.append("PRIMARY KEY") + if let direction = primaryKey.direction { + sql.append(serialize(direction)) + } + if let conflictResolution = primaryKey.conflictResolution { + sql.append("ON CONFLICT") + sql.append(serialize(conflictResolution)) + } + if primaryKey.autoIncrement { + sql.append("AUTOINCREMENT") + } + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift new file mode 100644 index 0000000..ad5f7ad --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift @@ -0,0 +1,23 @@ +extension SQLiteQuery.ColumnConstraint { + public struct Unique { + public var conflictResolution: SQLiteQuery.ConflictResolution? + + public init(conflictResolution: SQLiteQuery.ConflictResolution? = nil) { + self.conflictResolution = conflictResolution + } + } +} + +// MARK: Serializer + +extension SQLiteSerializer { + public func serialize(_ unique: SQLiteQuery.ColumnConstraint.Unique) -> String { + var sql: [String] = [] + sql.append("UNIQUE") + if let conflictResolution = unique.conflictResolution { + sql.append("ON CONFLICT") + sql.append(serialize(conflictResolution)) + } + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint.swift deleted file mode 100644 index 72125ae..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint.swift +++ /dev/null @@ -1,135 +0,0 @@ -extension SQLiteQuery { - public struct ColumnConstraint { - public static func primaryKey(autoIncrement: Bool = true) -> ColumnConstraint { - return .init(.primaryKey(.init(autoIncrement: autoIncrement))) - } - - public static var notNull: ColumnConstraint { - return .init(.nullability(.init(allowNull: false))) - } - - public static func `default`(_ expression: Expression) -> ColumnConstraint { - return .init(.default(expression)) - } - - public static func foreignKey( - to keyPath: KeyPath - ) -> ColumnConstraint - where Table: SQLiteTable - { - let fk = ForeignKeyReference.init( - foreignTable: .init(name: Table.sqliteTableName), - foreignColumns: [keyPath.qualifiedColumnName.name], - onDelete: nil, - onUpdate: nil, - match: nil, - deferrence: nil - ) - return .init(.foreignKey(fk)) - } - - public struct PrimaryKey { - public var direction: Direction? - public var conflictResolution: ConflictResolution? - public var autoIncrement: Bool - - public init( - direction: Direction? = nil, - conflictResolution: ConflictResolution? = nil, - autoIncrement: Bool = false - ) { - self.direction = direction - self.conflictResolution = conflictResolution - self.autoIncrement = autoIncrement - } - } - - public struct Nullability { - public var allowNull: Bool - public var conflictResolution: ConflictResolution? - - public init(allowNull: Bool = false, conflictResolution: ConflictResolution? = nil) { - self.allowNull = allowNull - self.conflictResolution = conflictResolution - } - } - - public struct Unique { - public var conflictResolution: ConflictResolution? - } - - public enum Value { - case primaryKey(PrimaryKey) - case nullability(Nullability) - case unique(Unique) - case check(Expression) - case `default`(Expression) - case collate(String) - case foreignKey(ForeignKeyReference) - } - public var name: String? - public var value: Value - - public init(name: String? = nil, _ value: Value) { - self.name = name - self.value = value - } - } -} - -extension SQLiteSerializer { - func serialize(_ constraint: SQLiteQuery.ColumnConstraint, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - if let name = constraint.name { - sql.append("CONSTRAINT") - sql.append(escapeString(name)) - } - sql.append(serialize(constraint.value, &binds)) - return sql.joined(separator: " ") - } - - func serialize(_ value: SQLiteQuery.ColumnConstraint.Value, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch value { - case .primaryKey(let primaryKey): - sql.append("PRIMARY KEY") - if let direction = primaryKey.direction { - sql.append(serialize(direction)) - } - if let conflictResolution = primaryKey.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - if primaryKey.autoIncrement { - sql.append("AUTOINCREMENT") - } - case .nullability(let nullability): - if !nullability.allowNull { - sql.append("NOT") - } - sql.append("NULL") - if let conflictResolution = nullability.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - case .unique(let unique): - sql.append("UNIQUE") - if let conflictResolution = unique.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - case .check(let expr): - sql.append("CHECK") - sql.append("(" + serialize(expr, &binds) + ")") - case .default(let expr): - sql.append("DEFAULT") - sql.append("(" + serialize(expr, &binds) + ")") - case .collate(let name): - sql.append("COLLATE") - sql.append(name) - case .foreignKey(let reference): - sql.append(serialize(reference)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift index efc9fa4..c7ea5bf 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift @@ -1,10 +1,10 @@ extension SQLiteQuery { public struct ColumnDefinition { - public var name: ColumnName + public var name: Name public var typeName: TypeName? public var constraints: [ColumnConstraint] - public init(name: ColumnName, typeName: TypeName? = nil, constraints: [ColumnConstraint] = []) { + public init(name: Name, typeName: TypeName? = nil, constraints: [ColumnConstraint] = []) { self.name = name self.typeName = typeName self.constraints = constraints diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift index 2516f9a..61f4b95 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift @@ -22,7 +22,7 @@ extension SQLiteQuery { case .select: schema = .init(columns: []) } schema.columns.append(.init( - name: keyPath.qualifiedColumnName.name, + name: keyPath.sqliteColumnName.name, typeName: typeName, constraints: constraints )) @@ -40,6 +40,6 @@ extension SQLiteConnection { public func create
(table: Table.Type) -> SQLiteQuery.CreateTableBuilder where Table: SQLiteTable { - return .init(table: .init(stringLiteral: Table.sqliteTableName), on: self) + return .init(table: Table.sqliteTableName, on: self) } } diff --git a/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift index c6f1653..98c5833 100644 --- a/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift @@ -22,6 +22,6 @@ extension SQLiteConnection { public func delete
(from table: Table.Type) -> SQLiteQuery.DeleteBuilder where Table: SQLiteTable { - return .init(table: .init(table: .init(stringLiteral: Table.sqliteTableName)), on: self) + return .init(table: .init(table: .init(table: Table.sqliteTableName)), on: self) } } diff --git a/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift index 04ef265..220b8dd 100644 --- a/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift @@ -24,6 +24,6 @@ extension SQLiteConnection { public func drop
(table: Table.Type) -> SQLiteQuery.DropTableBuilder where Table: SQLiteTable { - return .init(table: .init(stringLiteral: Table.sqliteTableName), on: self) + return .init(table: Table.sqliteTableName, on: self) } } diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift index 35025fb..9c8250a 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift @@ -70,7 +70,7 @@ extension SQLiteQuery { case literal(Literal) case data(SQLiteData) - case column(QualifiedColumnName) + case column(ColumnName) case unary(UnaryOperator, Expression) case binary(Expression, BinaryOperator, Expression) case function(Function) diff --git a/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift b/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift index 27feb61..60b87c0 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift @@ -18,7 +18,7 @@ extension SQLiteQuery { } public var foreignTable: TableName - public var foreignColumns: [ColumnName] + public var foreignColumns: [Name] public var onDelete: Action? public var onUpdate: Action? public var match: String? @@ -26,7 +26,7 @@ extension SQLiteQuery { public init( foreignTable: TableName, - foreignColumns: [ColumnName], + foreignColumns: [Name], onDelete: Action? = nil, onUpdate: Action? = nil, match: String? = nil, @@ -42,10 +42,10 @@ extension SQLiteQuery { } public struct ForeignKey { - public var columns: [ColumnName] + public var columns: [Name] public var reference: ForeignKeyReference - public init(columns: [ColumnName], reference: ForeignKeyReference) { + public init(columns: [Name], reference: ForeignKeyReference) { self.columns = columns self.reference = reference } diff --git a/Sources/SQLite/Query/SQLiteQuery+Insert.swift b/Sources/SQLite/Query/SQLiteQuery+Insert.swift index 7ccf333..0b0d828 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Insert.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Insert.swift @@ -19,7 +19,7 @@ extension SQLiteQuery { public var with: WithClause? public var conflictResolution: ConflictResolution? public var table: AliasableTableName - public var columns: [ColumnName] + public var columns: [Name] public var values: Values public var upsert: UpsertClause? @@ -27,7 +27,7 @@ extension SQLiteQuery { with: WithClause? = nil, conflictResolution: ConflictResolution? = nil, table: AliasableTableName, - columns: [ColumnName] = [], + columns: [Name] = [], values: Values = .defaults, upsert: UpsertClause? = nil ) { diff --git a/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift index d50acd0..b8dec47 100644 --- a/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift @@ -41,7 +41,7 @@ extension SQLiteQuery { where E: Encodable { let values = try values.map { try SQLiteQueryEncoder().encode($0) } - insert.columns = values[0].keys.map { ColumnName.init($0) } + insert.columns = values[0].keys.map { .init($0) } insert.values = .values(values.map { .init($0.values) }) return self } @@ -56,6 +56,6 @@ extension SQLiteConnection { public func insert
(into table: Table.Type) -> SQLiteQuery.InsertBuilder where Table: SQLiteTable { - return .init(table: .init(stringLiteral: Table.sqliteTableName), on: self) + return .init(table: .init(table: Table.sqliteTableName), on: self) } } diff --git a/Sources/SQLite/Query/SQLiteQuery+Join.swift b/Sources/SQLite/Query/SQLiteQuery+Join.swift index bde5308..7dc34e9 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Join.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Join.swift @@ -10,7 +10,7 @@ extension SQLiteQuery { public enum Constraint { case condition(Expression) - case using([ColumnName]) + case using([Name]) } public var natural: Bool diff --git a/Sources/SQLite/Query/SQLiteQuery+Name.swift b/Sources/SQLite/Query/SQLiteQuery+Name.swift new file mode 100644 index 0000000..989c67a --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+Name.swift @@ -0,0 +1,36 @@ +extension SQLiteQuery { + /// Represents a SQLite column, schema, table, or constraint name. + public struct Name { + /// String value. + public var string: String + + /// Creates a new `Name`. + /// + /// - parameters: + /// - string: String value. + public init(_ string: String) { + self.string = string + } + } +} + +// MARK: String Literal + +extension SQLiteQuery.Name: ExpressibleByStringLiteral { + /// See `ExpressibleByStringLiteral`. + public init(stringLiteral value: String) { + self.init(value) + } +} + +// MARK: Serialize + +extension SQLiteSerializer { + func serialize(_ columns: [SQLiteQuery.Name]) -> String { + return "(" + columns.map(serialize).joined(separator: ", ") + ")" + } + + func serialize(_ name: SQLiteQuery.Name) -> String { + return escapeString(name.string) + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+Select.swift b/Sources/SQLite/Query/SQLiteQuery+Select.swift index 745349d..9db0aa6 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Select.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Select.swift @@ -15,7 +15,7 @@ extension SQLiteQuery { public struct WithClause { public struct CommonTableExpression { public var table: String - public var columns: [ColumnName] + public var columns: [Name] public var select: Select } diff --git a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift index b8e4b01..30a3ab7 100644 --- a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift @@ -43,7 +43,7 @@ extension SQLiteQuery { public func from
(_ table: Table.Type) -> Self where Table: SQLiteTable { - select.tables.append(.table(.init(stringLiteral: Table.sqliteTableName))) + select.tables.append(.table(.init(table:.init(table: Table.sqliteTableName)))) return self } @@ -56,10 +56,10 @@ extension SQLiteQuery { let join = SQLiteQuery.JoinClause.init( table: select.tables[0], joins: [ - SQLiteQuery.JoinClause.Join.init( + SQLiteQuery.JoinClause.Join( natural: false, .inner, - table: .init(stringLiteral: Table.sqliteTableName), + table: .table(.init(table:.init(table: Table.sqliteTableName))), constraint: .condition(expr) ) ] @@ -88,7 +88,7 @@ extension SQLiteQuery { extension Dictionary where Key == SQLiteColumn, Value == SQLiteData { public func decode
(_ type: Table.Type) throws -> Table where Table: SQLiteTable { - return try decode(Table.self, from: Table.sqliteTableName) + return try decode(Table.self, from: Table.sqliteTableName.name.string) } public func decode(_ type: D.Type, from table: String) throws -> D where D: Decodable { @@ -99,7 +99,7 @@ extension Dictionary where Key == SQLiteColumn, Value == SQLiteData { public func ==(_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression where Table: SQLiteTable, Value: Encodable { - return try .binary(.column(lhs.qualifiedColumnName), .equal, .bind(rhs)) + return try .binary(.column(lhs.sqliteColumnName), .equal, .bind(rhs)) } public func ==( @@ -107,36 +107,37 @@ public func ==( ) -> SQLiteQuery.Expression where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable { - return .binary(.column(lhs.qualifiedColumnName), .equal, .column(rhs.qualifiedColumnName)) + return .binary(.column(lhs.sqliteColumnName), .equal, .column(rhs.sqliteColumnName)) } public func !=(_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression where Table: SQLiteTable, Value: Encodable { - return try .binary(.column(lhs.qualifiedColumnName), .notEqual, .bind(rhs)) + return try .binary(.column(lhs.sqliteColumnName), .notEqual, .bind(rhs)) } public protocol SQLiteTable: Codable, Reflectable { - static var sqliteTableName: String { get } + static var sqliteTableName: SQLiteQuery.TableName { get } +} + +extension SQLiteTable { + public static var sqliteTableName: SQLiteQuery.TableName { + return .init(stringLiteral: "\(Self.self)") + } } extension KeyPath where Root: SQLiteTable { - public var qualifiedColumnName: SQLiteQuery.QualifiedColumnName { + public var sqliteColumnName: SQLiteQuery.ColumnName { guard let property = try! Root.reflectProperty(forKey: self) else { fatalError("Could not reflect property of type \(Value.self) on \(Root.self): \(self)") } return .init( - table: Root.sqliteTableName, + table: .init(Root.sqliteTableName), name: .init(property.path[0]) ) } } -extension SQLiteTable { - public static var sqliteTableName: String { - return "\(Self.self)" - } -} extension SQLiteConnection { public func select() -> SQLiteQuery.SelectBuilder { diff --git a/Sources/SQLite/Query/SQLiteQuery+SetValues.swift b/Sources/SQLite/Query/SQLiteQuery+SetValues.swift index 9b3ee35..930d0ec 100644 --- a/Sources/SQLite/Query/SQLiteQuery+SetValues.swift +++ b/Sources/SQLite/Query/SQLiteQuery+SetValues.swift @@ -1,10 +1,10 @@ extension SQLiteQuery { public struct SetValues { public struct ColumnGroup { - public var columns: [ColumnName] + public var columns: [Name] public var value: Expression - public init(columns: [ColumnName], value: Expression) { + public init(columns: [Name], value: Expression) { self.columns = columns self.value = value } diff --git a/Sources/SQLite/Query/SQLiteQuery+TableName.swift b/Sources/SQLite/Query/SQLiteQuery+TableName.swift index 4096f54..7438cb0 100644 --- a/Sources/SQLite/Query/SQLiteQuery+TableName.swift +++ b/Sources/SQLite/Query/SQLiteQuery+TableName.swift @@ -1,9 +1,9 @@ extension SQLiteQuery { public struct TableName { - public var schema: String? - public var name: String + public var schema: Name? + public var name: Name - public init(schema: String? = nil, name: String) { + public init(schema: Name? = nil, name: Name) { self.schema = schema self.name = name } @@ -38,7 +38,7 @@ extension SQLiteQuery { extension SQLiteQuery.TableName: ExpressibleByStringLiteral { public init(stringLiteral value: String) { - self.init(name: value) + self.init(name: .init(value)) } } @@ -56,13 +56,11 @@ extension SQLiteQuery.QualifiedTableName: ExpressibleByStringLiteral { extension SQLiteSerializer { func serialize(_ table: SQLiteQuery.TableName) -> String { - var sql: [String] = [] if let schema = table.schema { - sql.append(escapeString(schema) + "." + escapeString(table.name)) + return serialize(schema) + "." + serialize(table.name) } else { - sql.append(escapeString(table.name)) + return serialize(table.name) } - return sql.joined(separator: " ") } func serialize(_ table: SQLiteQuery.AliasableTableName) -> String { diff --git a/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift index 3f40d2f..1b29a40 100644 --- a/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift @@ -38,6 +38,6 @@ extension SQLiteConnection { public func update
(_ table: Table.Type) -> SQLiteQuery.UpdateBuilder where Table: SQLiteTable { - return .init(table: .init(table: .init(stringLiteral: Table.sqliteTableName)), on: self) + return .init(table: .init(table: .init(table: Table.sqliteTableName)), on: self) } } diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 2e8b914..13b8264 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -56,7 +56,7 @@ class SQLiteTests: XCTestCase { .run().wait() try conn.create(table: Planet.self) .column(for: \Planet.id, .integer, .primaryKey(), .notNull) - .column(for: \Planet.galaxyID, .integer, .notNull, .foreignKey(to: \Galaxy.id)) + .column(for: \Planet.galaxyID, .integer, .notNull, .references(\Galaxy.id)) .run().wait() try conn.alter(table: Planet.self) From 4e99ea169fc0cece2544866e901ecebb07711a60 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 20:49:03 -0400 Subject: [PATCH 05/21] foreign key + reference updates --- .../Query/SQLiteQuery+AlterTableBuilder.swift | 2 +- .../SQLiteQuery+ColumnConstraint+0.swift | 83 +++++++++++++---- ...teQuery+ColumnConstraint+Nullability.swift | 10 +++ ...iteQuery+ColumnConstraint+PrimaryKey.swift | 15 +++- .../SQLiteQuery+ColumnConstraint+Unique.swift | 6 ++ .../Query/SQLiteQuery+ColumnDefinition.swift | 22 +++-- .../SQLiteQuery+ConflictResolution.swift | 37 +++++++- ...t => SQLiteQuery+CreateTableBuilder.swift} | 2 +- .../Query/SQLiteQuery+Expression+0.swift | 64 +++---------- ...QLiteQuery+Expression+BinaryOperator.swift | 16 ++++ .../SQLite/Query/SQLiteQuery+ForeignKey.swift | 89 +++++++------------ Tests/SQLiteTests/SQLiteTests.swift | 10 +-- 12 files changed, 219 insertions(+), 137 deletions(-) rename Sources/SQLite/Query/{SQLiteQuery+CreateBuilder.swift => SQLiteQuery+CreateTableBuilder.swift} (97%) diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift index 3c342e4..828d36b 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift @@ -40,7 +40,7 @@ extension SQLiteQuery { /// - returns: Self for chaining. public func addColumn( for keyPath: KeyPath, - type typeName: TypeName? = nil, + type typeName: TypeName, _ constraints: SQLiteQuery.ColumnConstraint... ) -> Self { return addColumn(.init(name: keyPath.sqliteColumnName.name, typeName: typeName, constraints: constraints)) diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift index b956b32..07d0632 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift @@ -4,43 +4,92 @@ extension SQLiteQuery { /// Creates a new `PRIMARY KEY` column constraint. /// /// - parameters: + /// - direction: Primary key direction. + /// - onConflict: `ON CONFLICT` resolution. /// - autoIncrement: If `true`, the primary key will auto increment. /// `true` by default. + /// - named: Optional constraint name. /// - returns: New column constraint. - public static func primaryKey(autoIncrement: Bool = true) -> ColumnConstraint { - return .init(.primaryKey(.init(autoIncrement: autoIncrement))) + public static func primaryKey( + direction: Direction? = nil, + onConflict: ConflictResolution? = nil, + autoIncrement: Bool = true, + named name: String? = nil + ) -> ColumnConstraint { + return .init( + name: name, + value: .primaryKey(.init( + direction: direction, + conflictResolution: onConflict, + autoIncrement: autoIncrement + )) + ) } /// Creates a new `NOT NULL` column constraint. - public static var notNull: ColumnConstraint { - return .init(.nullability(.init(allowNull: false))) + /// + /// - parameters: + /// - onConflict: `ON CONFLICT` resolution. + /// - named: Optional constraint name. + /// - returns: New column constraint. + public static func notNull(onConflict: ConflictResolution? = nil, named name: String? = nil) -> ColumnConstraint { + return .init( + name: name, + value: .nullability(.init(allowNull: false, conflictResolution: onConflict)) + ) + } + + /// Creates a new `UNIQUE` column constraint. + /// + /// - parameters: + /// - onConflict: `ON CONFLICT` resolution. + /// - named: Optional constraint name. + /// - returns: New column constraint. + public static func unique(onConflict: ConflictResolution? = nil, named name: String? = nil) -> ColumnConstraint { + return .init( + name: name, + value: .unique(.init(conflictResolution: onConflict)) + ) } /// Creates a new `DEFAULT ` column constraint. /// /// - parameters /// - expression: Expression to evaluate when setting the default value. + /// - named: Optional constraint name. /// - returns: New column constraint. - public static func `default`(_ expression: Expression) -> ColumnConstraint { - return .init(.default(expression)) + public static func `default`(_ expression: Expression, named name: String? = nil) -> ColumnConstraint { + return .init(name: name, value: .default(expression)) } /// Creates a new `REFERENCES` column constraint. /// /// - parameters /// - keyPath: Swift `KeyPath` to referenced column. + /// - onDelete: `ON DELETE` foreign key action. + /// - onUpdate: `ON UPDATE` foreign key action. + /// - deferrable: Foreign key check deferrence. + /// - named: Optional constraint name. /// - returns: New column constraint. - public static func references(_ keyPath: KeyPath) -> ColumnConstraint + public static func references( + _ keyPath: KeyPath, + onDelete: ForeignKey.Action? = nil, + onUpdate: ForeignKey.Action? = nil, + deferrable: ForeignKey.Deferrence? = nil, + named name: String? = nil + ) -> ColumnConstraint where Table: SQLiteTable { - return .init(.references(.init( - foreignTable: Table.sqliteTableName, - foreignColumns: [keyPath.sqliteColumnName.name], - onDelete: nil, - onUpdate: nil, - match: nil, - deferrence: nil - ))) + return .init( + name: name, + value: .references(.init( + foreignTable: Table.sqliteTableName, + foreignColumns: [keyPath.sqliteColumnName.name], + onDelete: onDelete, + onUpdate: onUpdate, + deferrence: deferrable + ) + )) } /// Supported column constraint values. @@ -64,7 +113,7 @@ extension SQLiteQuery { case collate(String) /// `REFERENCES` - case references(ForeignKeyReference) + case references(ForeignKey.Reference) } /// Optional constraint name. @@ -78,7 +127,7 @@ extension SQLiteQuery { /// - parameters: /// - name: Optional constraint name. /// - value: Constraint value. - public init(name: String? = nil, _ value: Value) { + public init(name: String? = nil, value: Value) { self.name = name self.value = value } diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift index 97134d0..3218fdb 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift @@ -1,8 +1,18 @@ extension SQLiteQuery.ColumnConstraint { + /// `NOT NULL` or `NULL` constraint. public struct Nullability { + /// If `true`, this constraint will allow null. public var allowNull: Bool + + /// `ON CONFLICT` resolution. public var conflictResolution: SQLiteQuery.ConflictResolution? + /// Creates a new `Nullability` constraint. + /// + /// - parameters: + /// - allowNull: If `true`, this constraint will allow null. + /// Defaults to `false`. + /// - conflictResolution: `ON CONFLICT` resolution. public init(allowNull: Bool = false, conflictResolution: SQLiteQuery.ConflictResolution? = nil) { self.allowNull = allowNull self.conflictResolution = conflictResolution diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift index acbcc45..00d5597 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift @@ -1,13 +1,26 @@ extension SQLiteQuery.ColumnConstraint { + /// `PRIMARY KEY` constraint. public struct PrimaryKey { + /// Primary key direction. public var direction: SQLiteQuery.Direction? + + /// `ON CONFLICT` resolution. public var conflictResolution: SQLiteQuery.ConflictResolution? + + //// If `true`, this primary key will autoincrement. public var autoIncrement: Bool + /// Creates a new `PrimaryKey` constraint. + /// + /// - parameters: + /// - direction: Primary key direction. + /// - conflictResolution: `ON CONFLICT` resolution. + /// - autoIncrement: If `true`, this primary key will autoincrement. + /// Defaults to `true`. public init( direction: SQLiteQuery.Direction? = nil, conflictResolution: SQLiteQuery.ConflictResolution? = nil, - autoIncrement: Bool = false + autoIncrement: Bool = true ) { self.direction = direction self.conflictResolution = conflictResolution diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift index ad5f7ad..9d04fdb 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift @@ -1,7 +1,13 @@ extension SQLiteQuery.ColumnConstraint { + /// `UNIQUE` constraint. public struct Unique { + /// `ON CONFLICT` resolution. public var conflictResolution: SQLiteQuery.ConflictResolution? + /// Creates a new `Unique` constraint. + /// + /// - parameters: + /// - conflictResolution: `ON CONFLICT` resolution. public init(conflictResolution: SQLiteQuery.ConflictResolution? = nil) { self.conflictResolution = conflictResolution } diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift index c7ea5bf..91847d1 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift @@ -1,10 +1,22 @@ extension SQLiteQuery { + /// Column definition. public struct ColumnDefinition { + /// Column name. public var name: Name - public var typeName: TypeName? + + /// Column type name. + public var typeName: TypeName + + /// Zero or more column constraints. public var constraints: [ColumnConstraint] - public init(name: Name, typeName: TypeName? = nil, constraints: [ColumnConstraint] = []) { + /// Creates a new `ColumnDefinition`. + /// + /// - parameters: + /// - name: Column name. + /// - typeName: Column type name. + /// - constraints: Zero or more column constraints. + public init(name: Name, typeName: TypeName, constraints: [ColumnConstraint] = []) { self.name = name self.typeName = typeName self.constraints = constraints @@ -12,13 +24,13 @@ extension SQLiteQuery { } } +// MARK: Serialize + extension SQLiteSerializer { func serialize(_ columnDefinition: SQLiteQuery.ColumnDefinition, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] sql.append(serialize(columnDefinition.name)) - if let typeName = columnDefinition.typeName { - sql.append(serialize(typeName)) - } + sql.append(serialize(columnDefinition.typeName)) sql += columnDefinition.constraints.map { serialize($0, &binds) } return sql.joined(separator: " ") } diff --git a/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift b/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift index 3168c82..9705961 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift @@ -1,16 +1,49 @@ extension SQLiteQuery { + /// `ON CONFLICT` clause. Supported constraint conflict resolution algorithms. + /// + /// https://www.sqlite.org/lang_conflict.html public enum ConflictResolution { + /// When a UNIQUE or PRIMARY KEY constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing + /// the constraint violation prior to inserting or updating the current row and the command continues executing normally. If a + /// NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with the default value for + /// that column, or if the column has no default value, then the ABORT algorithm is used. If a CHECK constraint violation + /// occurs, the REPLACE conflict resolution algorithm always works like ABORT. + /// + /// When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and + /// only if recursive triggers are enabled. + /// + /// The update hook is not invoked for rows that are deleted by the REPLACE conflict resolution strategy. Nor does REPLACE + /// increment the change counter. The exceptional behaviors defined in this paragraph might change in a future release. case replace + + /// When an applicable constraint violation occurs, the ROLLBACK resolution algorithm aborts the current SQL statement with + /// an SQLITE_CONSTRAINT error and rolls back the current transaction. If no transaction is active (other than the implied + /// transaction that is created on every command) then the ROLLBACK resolution algorithm works the same as the ABORT algorithm. case rollback + + /// When an applicable constraint violation occurs, the ABORT resolution algorithm aborts the current SQL statement with an + /// SQLITE_CONSTRAINT error and backs out any changes made by the current SQL statement; but changes caused by prior SQL + /// statements within the same transaction are preserved and the transaction remains active. This is the default behavior and + /// the behavior specified by the SQL standard. case abort + /// When an applicable constraint violation occurs, the FAIL resolution algorithm aborts the current SQL statement with an + /// SQLITE_CONSTRAINT error. But the FAIL resolution does not back out prior changes of the SQL statement that failed nor does + /// it end the transaction. For example, if an UPDATE statement encountered a constraint violation on the 100th row that it + /// attempts to update, then the first 99 row changes are preserved but changes to rows 100 and beyond never occur. case fail + /// When an applicable constraint violation occurs, the IGNORE resolution algorithm skips the one row that contains the + /// constraint violation and continues processing subsequent rows of the SQL statement as if nothing went wrong. Other rows + /// before and after the row that contained the constraint violation are inserted or updated normally. No error is returned + /// when the IGNORE conflict resolution algorithm is used. case ignore } } +// MARK: Serialize + extension SQLiteSerializer { - func serialize(_ conflictResolution: SQLiteQuery.ConflictResolution) -> String { - switch conflictResolution { + func serialize(_ algorithm: SQLiteQuery.ConflictResolution) -> String { + switch algorithm { case .abort: return "ABORT" case .fail: return "FAIL" case .ignore: return "IGNORE" diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift similarity index 97% rename from Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift rename to Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift index 61f4b95..cb140ff 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift @@ -11,7 +11,7 @@ extension SQLiteQuery { @discardableResult public func column( for keyPath: KeyPath, - _ typeName: TypeName? = nil, + type typeName: TypeName, _ constraints: SQLiteQuery.ColumnConstraint... ) -> Self where Table: SQLiteTable diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift index 9c8250a..b369f0c 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift @@ -15,32 +15,11 @@ extension SQLiteQuery { } } - public enum NullOperator { - /// `ISNULL` - case isNull - /// `NOT NULL` or `NOTNULL` - case notNull - } - - - public enum IdentityOperator { - /// `IS` - case `is` - - /// `IS NOT` - case isNot - } - public enum BetweenOperator { case between case notBetween } - public enum SubsetOperator { - case `in` - case notIn - } - public enum SubsetExpression { case subSelect(Select) case expressions([Expression]) @@ -81,14 +60,8 @@ extension SQLiteQuery { case collate(Expression, String) /// NOT LIKE ESCAPE case compare(Compare) - /// IS NULL - case nullOperator(Expression, NullOperator) - /// IS NOT - case identityOperator(Expression, IdentityOperator, Expression) // BETWEEN AND case betweenOperator(Expression, BetweenOperator, Expression, Expression) - // ` IN ()` - case subset(Expression, SubsetOperator, SubsetExpression) case subSelect(ExistsOperator?, Select) /// CASE (WHEN THEN ) ELSE END case caseExpression(Expression?, [CaseCondition], Expression?) @@ -135,33 +108,24 @@ extension SQLiteSerializer { return "(" + exprs.map { serialize($0, &binds) }.joined(separator: ", ") + ")" case .function(let function): return serialize(function, &binds) - case .subset(let expr, let op, let subset): - return serialize(expr, &binds) + " " + serialize(op) + " " + serialize(subset, &binds) default: return "\(expr)" } } - func serialize(_ op: SQLiteQuery.Expression.SubsetOperator) -> String { - switch op { - case .in: return "IN" - case .notIn: return "NOT IN" - } - } - - func serialize(_ subset: SQLiteQuery.Expression.SubsetExpression, _ binds: inout [SQLiteData]) -> String { - switch subset { - case .expressions(let exprs): return exprs.map { serialize($0, &binds) }.joined(separator: ", ") - case .subSelect(let select): return serialize(select, &binds) - case .table(let table): return serialize(table) - case .tableFunction(let schema, let function): - if let schema = schema { - return escapeString(schema) + "." + serialize(function, &binds) - } else { - return serialize(function, &binds) - } - } - } - +// func serialize(_ subset: SQLiteQuery.Expression.SubsetExpression, _ binds: inout [SQLiteData]) -> String { +// switch subset { +// case .expressions(let exprs): return exprs.map { serialize($0, &binds) }.joined(separator: ", ") +// case .subSelect(let select): return serialize(select, &binds) +// case .table(let table): return serialize(table) +// case .tableFunction(let schema, let function): +// if let schema = schema { +// return escapeString(schema) + "." + serialize(function, &binds) +// } else { +// return serialize(function, &binds) +// } +// } +// } +// func serialize(_ function: SQLiteQuery.Expression.Function, _ binds: inout [SQLiteData]) -> String { if let parameters = function.parameters { return function.name + "(" + serialize(parameters, &binds) + ")" diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift index ebb6900..6cd06b5 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift @@ -53,6 +53,18 @@ extension SQLiteQuery.Expression { /// `OR` case or + + /// `IS` + case `is` + + /// `IS NOT` + case isNot + + /// `IN` + case `in` + + /// `NOT IN` + case notIn } } @@ -109,6 +121,10 @@ extension SQLiteSerializer { case .subtract: return "-" case .and: return "AND" case .or: return "OR" + case .in: return "IN" + case .notIn: return "NOT IN" + case .is: return "IS" + case .isNot: return "IS NOT" } } } diff --git a/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift b/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift index 60b87c0..cd8d76e 100644 --- a/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift +++ b/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift @@ -1,14 +1,5 @@ extension SQLiteQuery { - public struct ForeignKeyReference { - public struct Deferrence { - public enum Value { - case deferred - case immediate - } - public var not: Bool - public var value: Value? - } - + public struct ForeignKey { public enum Action { case setNull case setDefault @@ -17,35 +8,37 @@ extension SQLiteQuery { case noAction } - public var foreignTable: TableName - public var foreignColumns: [Name] - public var onDelete: Action? - public var onUpdate: Action? - public var match: String? - public var deferrence: Deferrence? + public enum Deferrence { + case not + case immediate + } - public init( - foreignTable: TableName, - foreignColumns: [Name], - onDelete: Action? = nil, - onUpdate: Action? = nil, - match: String? = nil, - deferrence: Deferrence? = nil - ) { - self.foreignTable = foreignTable - self.foreignColumns = foreignColumns - self.onDelete = onDelete - self.onUpdate = onUpdate - self.match = match - self.deferrence = deferrence + public struct Reference { + public var foreignTable: TableName + public var foreignColumns: [Name] + public var onDelete: Action? + public var onUpdate: Action? + public var deferrence: Deferrence? + + public init( + foreignTable: TableName, + foreignColumns: [Name], + onDelete: Action? = nil, + onUpdate: Action? = nil, + deferrence: Deferrence? = nil + ) { + self.foreignTable = foreignTable + self.foreignColumns = foreignColumns + self.onDelete = onDelete + self.onUpdate = onUpdate + self.deferrence = deferrence + } } - } - - public struct ForeignKey { + public var columns: [Name] - public var reference: ForeignKeyReference + public var reference: Reference - public init(columns: [Name], reference: ForeignKeyReference) { + public init(columns: [Name], reference: Reference) { self.columns = columns self.reference = reference } @@ -61,7 +54,7 @@ extension SQLiteSerializer { return sql.joined(separator: " ") } - func serialize(_ foreignKey: SQLiteQuery.ForeignKeyReference) -> String { + func serialize(_ foreignKey: SQLiteQuery.ForeignKey.Reference) -> String { var sql: [String] = [] sql.append("REFERENCES") sql.append(serialize(foreignKey.foreignTable)) @@ -76,17 +69,13 @@ extension SQLiteSerializer { sql.append("ON UPDATE") sql.append(serialize(onUpdate)) } - if let match = foreignKey.match { - sql.append("MATCH") - sql.append(match) - } if let deferrence = foreignKey.deferrence { sql.append(serialize(deferrence)) } return sql.joined(separator: " ") } - func serialize(_ action: SQLiteQuery.ForeignKeyReference.Action) -> String { + func serialize(_ action: SQLiteQuery.ForeignKey.Action) -> String { switch action { case .cascade: return "CASCADE" case .noAction: return "NO ACTION" @@ -96,20 +85,10 @@ extension SQLiteSerializer { } } - func serialize(_ deferrence: SQLiteQuery.ForeignKeyReference.Deferrence) -> String { - var sql: [String] = [] - if deferrence.not { - sql.append("NOT") + func serialize(_ deferrence: SQLiteQuery.ForeignKey.Deferrence) -> String { + switch deferrence { + case .not: return "NOT DEFERRABLE" + case .immediate: return "DEFERRABLE INITIALLY IMMEDIATE" } - sql.append("DEFERRABLE") - switch deferrence.value { - case .none: break - case .some(let value): - switch value { - case .deferred: sql.append("INITIALLY DEFERRED") - case .immediate: sql.append("INITIALLY IMMEDIATE") - } - } - return sql.joined(separator: " ") } } diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 13b8264..15bfd26 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -51,16 +51,16 @@ class SQLiteTests: XCTestCase { .run().wait() try conn.create(table: Galaxy.self) - .column(for: \Galaxy.id, .integer, .primaryKey(), .notNull) - .column(for: \Galaxy.name) + .column(for: \Galaxy.id, type: .integer, .primaryKey(), .notNull()) + .column(for: \Galaxy.name, type: .text) .run().wait() try conn.create(table: Planet.self) - .column(for: \Planet.id, .integer, .primaryKey(), .notNull) - .column(for: \Planet.galaxyID, .integer, .notNull, .references(\Galaxy.id)) + .column(for: \Planet.id, type: .integer, .primaryKey(), .notNull()) + .column(for: \Planet.galaxyID, type: .integer, .notNull(), .references(\Galaxy.id)) .run().wait() try conn.alter(table: Planet.self) - .addColumn(for: \Planet.name, type: .text, .notNull, .default(.literal("Unamed Planet"))) + .addColumn(for: \Planet.name, type: .text, .notNull(), .default(.literal("Unamed Planet"))) .run().wait() try conn.insert(into: Galaxy.self) From 0c7f7441fa684657813ffc0cb2c5500d66eaa5a8 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 21:09:58 -0400 Subject: [PATCH 06/21] add additional methods to create builder --- .../SQLiteQuery+CreateTableBuilder.swift | 120 +++++++++++++++--- Tests/SQLiteTests/SQLiteTests.swift | 18 ++- 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift index cb140ff..3143cac 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift @@ -1,45 +1,131 @@ extension SQLiteQuery { - public final class CreateTableBuilder { + /// Builds `CreateTable` queries. + public final class CreateTableBuilder
where Table: SQLiteTable { + /// Query being built. public var create: CreateTable + + /// Database connection to execute the query on. public let connection: SQLiteConnection - init(table: TableName, on connection: SQLiteConnection) { - self.create = .init(table: table, source: .schema(.init(columns: []))) + /// Creates a new `CreateTableBuilder`. + /// + /// - parameters: + /// - table: Name of existing table to create. + /// - connection: `SQLiteConnection` to perform the query on. + init(table: Table.Type, on connection: SQLiteConnection) { + self.create = .init(table: Table.sqliteTableName, source: .schema(.init(columns: []))) self.connection = connection } - @discardableResult - public func column( + /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. + public func temporary() -> Self { + create.temporary = true + return self + } + + /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the + /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view + /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An + /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is + /// specified. + public func ifNotExists() -> Self { + create.ifNotExists = true + return self + } + + /// Adds a column to the table. + /// + /// conn.create(table: Planet.self).column(for: \.name, type: .text, .notNull).run() + /// + /// - parameters: + /// - keyPath: Swift `KeyPath` to property that should be added. + /// - type: Name of type to use for this column. + /// - constraints: Zero or more column constraints to add. + /// - returns: Self for chaining. + public func column( for keyPath: KeyPath, type typeName: TypeName, _ constraints: SQLiteQuery.ColumnConstraint... - ) -> Self - where Table: SQLiteTable - { - var schema: CreateTable.Schema - switch create.source { - case .schema(let existing): schema = existing - case .select: schema = .init(columns: []) - } - schema.columns.append(.init( + ) -> Self { + return column(.init( name: keyPath.sqliteColumnName.name, typeName: typeName, constraints: constraints )) - create.source = .schema(schema) + } + /// Adds a column to the table. + /// + /// conn.create(table: Planet.self).column(...).run() + /// + /// - parameters: + /// - columnDefinition: Column definition to add. + /// - returns: Self for chaining. + public func column(_ columnDefinition: ColumnDefinition) -> Self { + schema.columns.append(columnDefinition) return self } + /// By default, every row in SQLite has a special column, usually called the "rowid", that uniquely identifies that row within + /// the table. However if the phrase "WITHOUT ROWID" is added to the end of a CREATE TABLE statement, then the special "rowid" + /// column is omitted. There are sometimes space and performance advantages to omitting the rowid. + /// + /// https://www.sqlite.org/withoutrowid.html + public func withoutRowID() -> Self { + schema.withoutRowID = true + return self + } + + /// A `CREATE TABLE ... AS SELECT` statement creates and populates a database table based on the results of a SELECT statement. + /// The table has the same number of columns as the rows returned by the SELECT statement. The name of each column is the same + /// as the name of the corresponding column in the result set of the SELECT statement. + /// + /// conn.create(table: GalaxyCopy.self).as { $0.select().all().from(Galaxy.self) }.run() + /// + /// - parameters: + /// - closure: Closure accepting a `SQLiteConnection` and returning a `SelectBuilder`. + /// - returns: Self for chaining. + public func `as`(_ closure: (SQLiteConnection) -> SelectBuilder) -> Self { + create.source = .select(closure(connection).select) + return self + } + + // TODO: Support adding table constraints. + + /// Runs the `CREATE` query. + /// + /// - returns: A `Future` that signals completion. public func run() -> Future { return connection.query(.createTable(create)).transform(to: ()) } + + // MARK: Private + + /// Convenience accessor for setting schema. + private var schema: CreateTable.Schema { + get { + switch create.source { + case .schema(let schema): return schema + case .select: return .init(columns: []) + } + } + set { + create.source = .schema(newValue) + } + } } } extension SQLiteConnection { - public func create
(table: Table.Type) -> SQLiteQuery.CreateTableBuilder + /// Creates a new `CreateTableBuilder`. + /// + /// conn.create(table: Planet.self)... + /// + /// - parameters: + /// - table: Table to create. + /// - returns: `CreateTableBuilder`. + public func create
(table: Table.Type) -> SQLiteQuery.CreateTableBuilder
where Table: SQLiteTable { - return .init(table: Table.sqliteTableName, on: self) + return .init(table: Table.self, on: self) } } diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 15bfd26..4d6c808 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -20,6 +20,10 @@ struct Galaxy: SQLiteTable { } } +struct Galaxy2: SQLiteTable { + +} + class SQLiteTests: XCTestCase { func testVersion() throws { let conn = try SQLiteConnection.makeTest() @@ -51,16 +55,16 @@ class SQLiteTests: XCTestCase { .run().wait() try conn.create(table: Galaxy.self) - .column(for: \Galaxy.id, type: .integer, .primaryKey(), .notNull()) - .column(for: \Galaxy.name, type: .text) + .column(for: \.id, type: .integer, .primaryKey(), .notNull()) + .column(for: \.name, type: .text) .run().wait() try conn.create(table: Planet.self) - .column(for: \Planet.id, type: .integer, .primaryKey(), .notNull()) - .column(for: \Planet.galaxyID, type: .integer, .notNull(), .references(\Galaxy.id)) + .column(for: \.id, type: .integer, .primaryKey(), .notNull()) + .column(for: \.galaxyID, type: .integer, .notNull(), .references(\Galaxy.id)) .run().wait() try conn.alter(table: Planet.self) - .addColumn(for: \Planet.name, type: .text, .notNull(), .default(.literal("Unamed Planet"))) + .addColumn(for: \.name, type: .text, .notNull(), .default(.literal("Unamed Planet"))) .run().wait() try conn.insert(into: Galaxy.self) @@ -107,6 +111,10 @@ class SQLiteTests: XCTestCase { .run { try ($0.decode(Planet.self), $0.decode(Galaxy.self)) } .wait() print(selectC) + + try conn.create(table: Galaxy2.self).temporary().ifNotExists() + .as { $0.select().all().from(Galaxy.self) } + .run().wait() } func testTables() throws { From a58e7894ed6b4301f0d928401c106f5f0ed22681 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 21:43:42 -0400 Subject: [PATCH 07/21] add comments for CreateTable + Builder --- .../Query/SQLiteQuery+CreateTable.swift | 54 +++++++++++++++---- .../SQLiteQuery+CreateTableBuilder.swift | 16 +++--- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift b/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift index 8568ea7..4462e11 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift +++ b/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift @@ -1,10 +1,29 @@ extension SQLiteQuery { + /// The `CREATE TABLE` command is used to create a new table in an SQLite database. + /// + /// https://www.sqlite.org/lang_createtable.html public struct CreateTable { - public struct Schema { + /// A collection of columns and constraints defining a table. + public struct SchemaDefinition { + /// Columns to create. public var columns: [ColumnDefinition] + + /// Table constraints (different from column constraints) to create. public var tableConstraints: [TableConstraint] + + /// By default, every row in SQLite has a special column, usually called the "rowid", that uniquely identifies that row within + /// the table. However if the phrase "WITHOUT ROWID" is added to the end of a CREATE TABLE statement, then the special "rowid" + /// column is omitted. There are sometimes space and performance advantages to omitting the rowid. + /// + /// https://www.sqlite.org/withoutrowid.html public var withoutRowID: Bool + /// Creates a new `CreateTable`. + /// + /// - parameters: + /// - columns: Columns to create. + /// - tableConstraints: Table constraints (different from column constraints) to create. + /// - withoutRowID: See `withoutRowID`. public init( columns: [ColumnDefinition], tableConstraints: [TableConstraint] = [], @@ -16,26 +35,41 @@ extension SQLiteQuery { } } - public enum Source { - case schema(Schema) + /// Source for table schema. Either a definition or the results of a `SELECT` statement. + public enum SchemaSource { + /// A collection of columns and constraints defining a table. + case definition(SchemaDefinition) + /// The results of a `SELECT` statement. case select(Select) } + /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. public var temporary: Bool + + /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the + /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view + /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An + /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is + /// specified. public var ifNotExists: Bool + + /// Name of the table to create. public var table: TableName - public var source: Source + /// Source of the schema information. + public var schemaSource: SchemaSource + + /// Creates a new `CreateTable` query. public init( temporary: Bool = false, ifNotExists: Bool = false, table: TableName, - source: Source + schemaSource: SchemaSource ) { self.temporary = temporary self.ifNotExists = ifNotExists self.table = table - self.source = source + self.schemaSource = schemaSource } } } @@ -52,17 +86,17 @@ extension SQLiteSerializer { sql.append("IF NOT EXISTS") } sql.append(serialize(create.table)) - sql.append(serialize(create.source, &binds)) + sql.append(serialize(create.schemaSource, &binds)) return sql.joined(separator: " ") } - func serialize(_ source: SQLiteQuery.CreateTable.Source, _ binds: inout [SQLiteData]) -> String { + func serialize(_ source: SQLiteQuery.CreateTable.SchemaSource, _ binds: inout [SQLiteData]) -> String { switch source { - case .schema(let schema): return serialize(schema, &binds) + case .definition(let schema): return serialize(schema, &binds) case .select(let select): return "AS " + serialize(select, &binds) } } - func serialize(_ schema: SQLiteQuery.CreateTable.Schema, _ binds: inout [SQLiteData]) -> String { + func serialize(_ schema: SQLiteQuery.CreateTable.SchemaDefinition, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] sql.append("(" + ( schema.columns.map { serialize($0, &binds) } + schema.tableConstraints.map { serialize($0, &binds) } diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift index 3143cac..67030d9 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift @@ -13,7 +13,7 @@ extension SQLiteQuery { /// - table: Name of existing table to create. /// - connection: `SQLiteConnection` to perform the query on. init(table: Table.Type, on connection: SQLiteConnection) { - self.create = .init(table: Table.sqliteTableName, source: .schema(.init(columns: []))) + self.create = .init(table: Table.sqliteTableName, schemaSource: .definition(.init(columns: []))) self.connection = connection } @@ -61,7 +61,7 @@ extension SQLiteQuery { /// - columnDefinition: Column definition to add. /// - returns: Self for chaining. public func column(_ columnDefinition: ColumnDefinition) -> Self { - schema.columns.append(columnDefinition) + schemaDefinition.columns.append(columnDefinition) return self } @@ -71,7 +71,7 @@ extension SQLiteQuery { /// /// https://www.sqlite.org/withoutrowid.html public func withoutRowID() -> Self { - schema.withoutRowID = true + schemaDefinition.withoutRowID = true return self } @@ -85,7 +85,7 @@ extension SQLiteQuery { /// - closure: Closure accepting a `SQLiteConnection` and returning a `SelectBuilder`. /// - returns: Self for chaining. public func `as`(_ closure: (SQLiteConnection) -> SelectBuilder) -> Self { - create.source = .select(closure(connection).select) + create.schemaSource = .select(closure(connection).select) return self } @@ -101,15 +101,15 @@ extension SQLiteQuery { // MARK: Private /// Convenience accessor for setting schema. - private var schema: CreateTable.Schema { + private var schemaDefinition: CreateTable.SchemaDefinition { get { - switch create.source { - case .schema(let schema): return schema + switch create.schemaSource { + case .definition(let definition): return definition case .select: return .init(columns: []) } } set { - create.source = .schema(newValue) + create.schemaSource = .definition(newValue) } } } From 93dcc2ce3d181ee1d4a331b70a0c8efbc51014a5 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Fri, 15 Jun 2018 21:56:16 -0400 Subject: [PATCH 08/21] remove WITH/CTEs --- Sources/SQLite/Query/SQLiteQuery+Delete.swift | 10 +++--- Sources/SQLite/Query/SQLiteQuery+Insert.swift | 6 ---- Sources/SQLite/Query/SQLiteQuery+Update.swift | 7 +---- Sources/SQLite/Query/SQLiteQuery+With.swift | 31 ------------------- 4 files changed, 5 insertions(+), 49 deletions(-) delete mode 100644 Sources/SQLite/Query/SQLiteQuery+With.swift diff --git a/Sources/SQLite/Query/SQLiteQuery+Delete.swift b/Sources/SQLite/Query/SQLiteQuery+Delete.swift index d2d0750..d81ae52 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Delete.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Delete.swift @@ -1,26 +1,24 @@ extension SQLiteQuery { + /// A `DELETE ...` query. public struct Delete { - public var with: WithClause? = nil + /// public var table: QualifiedTableName public var predicate: Expression? public init( - with: WithClause? = nil, table: QualifiedTableName, predicate: Expression? = nil ) { - self.with = with self.table = table self.predicate = predicate } } } +// MARK: Serialize + extension SQLiteSerializer { func serialize(_ delete: SQLiteQuery.Delete, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] - if let with = delete.with { - sql.append(serialize(with, &binds)) - } sql.append("DELETE FROM") sql.append(serialize(delete.table)) if let predicate = delete.predicate { diff --git a/Sources/SQLite/Query/SQLiteQuery+Insert.swift b/Sources/SQLite/Query/SQLiteQuery+Insert.swift index 0b0d828..6e6f1f9 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Insert.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Insert.swift @@ -16,7 +16,6 @@ extension SQLiteQuery { public var action: Action } - public var with: WithClause? public var conflictResolution: ConflictResolution? public var table: AliasableTableName public var columns: [Name] @@ -24,14 +23,12 @@ extension SQLiteQuery { public var upsert: UpsertClause? public init( - with: WithClause? = nil, conflictResolution: ConflictResolution? = nil, table: AliasableTableName, columns: [Name] = [], values: Values = .defaults, upsert: UpsertClause? = nil ) { - self.with = with self.conflictResolution = conflictResolution self.table = table self.columns = columns @@ -45,9 +42,6 @@ extension SQLiteQuery { extension SQLiteSerializer { func serialize(_ insert: SQLiteQuery.Insert, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] - if let with = insert.with { - sql.append(serialize(with, &binds)) - } sql.append("INSERT") if let conflictResolution = insert.conflictResolution { sql.append("OR") diff --git a/Sources/SQLite/Query/SQLiteQuery+Update.swift b/Sources/SQLite/Query/SQLiteQuery+Update.swift index 16a2974..92e496f 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Update.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Update.swift @@ -1,18 +1,16 @@ extension SQLiteQuery { public struct Update { - public var with: WithClause? = nil public var conflictResolution: ConflictResolution? = nil public var table: QualifiedTableName public var values: SetValues public var predicate: Expression? + public init( - with: WithClause? = nil, conflictResolution: ConflictResolution? = nil, table: QualifiedTableName, values: SetValues, predicate: Expression? = nil ) { - self.with = with self.conflictResolution = conflictResolution self.table = table self.values = values @@ -23,9 +21,6 @@ extension SQLiteQuery { extension SQLiteSerializer { func serialize(_ update: SQLiteQuery.Update, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] - if let with = update.with { - sql.append(serialize(with, &binds)) - } sql.append("UPDATE") if let conflictResolution = update.conflictResolution { sql.append("OR") diff --git a/Sources/SQLite/Query/SQLiteQuery+With.swift b/Sources/SQLite/Query/SQLiteQuery+With.swift deleted file mode 100644 index fd28ac4..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+With.swift +++ /dev/null @@ -1,31 +0,0 @@ -extension SQLiteQuery { - public struct WithClause { - public struct CommonTableExpression { - public var table: String - public var select: Select - } - - public var recursive: Bool - public var expressions: [CommonTableExpression] - } -} - -extension SQLiteSerializer { - func serialize(_ with: SQLiteQuery.WithClause, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("WITH") - if with.recursive { - sql.append("RECURSIVE") - } - sql.append(with.expressions.map { serialize($0, &binds) }.joined(separator: ", ")) - return sql.joined(separator: " ") - } - - func serialize(_ cte: SQLiteQuery.WithClause.CommonTableExpression, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append(escapeString(cte.table)) - sql.append("AS") - sql.append("(" + serialize(cte.select, &binds) + ")") - return sql.joined(separator: " ") - } -} From 4db36f08c35c5bd81b78d0743544b55a3adae8e7 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Sat, 16 Jun 2018 01:30:37 -0400 Subject: [PATCH 09/21] cleanup expression operators --- Sources/SQLite/Query/SQLiteQuery+Delete.swift | 10 +- .../Query/SQLiteQuery+DeleteBuilder.swift | 27 ++- .../SQLite/Query/SQLiteQuery+Direction.swift | 7 +- .../SQLite/Query/SQLiteQuery+DropTable.swift | 9 +- .../Query/SQLiteQuery+DropTableBuilder.swift | 27 ++- .../Query/SQLiteQuery+Expression+0.swift | 112 ++++-------- ...QLiteQuery+Expression+BinaryOperator.swift | 64 +++---- .../SQLiteQuery+Expression+Compare.swift | 67 -------- .../SQLiteQuery+Expression+Function.swift | 16 ++ .../SQLiteQuery+Expression+Literal.swift | 2 +- .../SQLiteQuery+Expression+Operators.swift | 162 ++++++++++++++++++ ...SQLiteQuery+Expression+UnaryOperator.swift | 24 --- .../Query/SQLiteQuery+InsertBuilder.swift | 5 - .../Query/SQLiteQuery+SelectBuilder.swift | 20 --- .../SQLite/Query/SQLiteQuerySerializer.swift | 13 +- Tests/SQLiteTests/SQLiteTests.swift | 8 +- 16 files changed, 329 insertions(+), 244 deletions(-) delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+Compare.swift create mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift create mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+UnaryOperator.swift diff --git a/Sources/SQLite/Query/SQLiteQuery+Delete.swift b/Sources/SQLite/Query/SQLiteQuery+Delete.swift index d81ae52..e08264b 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Delete.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Delete.swift @@ -1,9 +1,15 @@ extension SQLiteQuery { /// A `DELETE ...` query. public struct Delete { - /// + /// Name of table to delete from. public var table: QualifiedTableName + + /// If the WHERE clause is not present, all records in the table are deleted. If a WHERE clause is supplied, + /// then only those rows for which the WHERE clause boolean expression is true are deleted. Rows for which + /// the expression is false or NULL are retained. public var predicate: Expression? + + /// Creates a new `Delete`. public init( table: QualifiedTableName, predicate: Expression? = nil @@ -17,7 +23,7 @@ extension SQLiteQuery { // MARK: Serialize extension SQLiteSerializer { - func serialize(_ delete: SQLiteQuery.Delete, _ binds: inout [SQLiteData]) -> String { + internal func serialize(_ delete: SQLiteQuery.Delete, _ binds: inout [SQLiteData]) -> String { var sql: [String] = [] sql.append("DELETE FROM") sql.append(serialize(delete.table)) diff --git a/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift index 98c5833..ee50171 100644 --- a/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift @@ -1,17 +1,27 @@ extension SQLiteQuery { - public final class DeleteBuilder: SQLitePredicateBuilder { + /// Builds `Delete` queries. + public final class DeleteBuilder
: SQLitePredicateBuilder where Table: SQLiteTable { + /// Query being build. public var delete: Delete + + /// Database connection to execute the query on. public let connection: SQLiteConnection + + /// See `SQLitePredicateBuilder`. public var predicate: SQLiteQuery.Expression? { get { return delete.predicate } set { delete.predicate = newValue } } - init(table: QualifiedTableName, on connection: SQLiteConnection) { - self.delete = .init(table: table) + /// Creates a new `DeleteBuilder`. + internal init(table: Table.Type, on connection: SQLiteConnection) { + self.delete = .init(table: .init(table: .init(table: Table.sqliteTableName))) self.connection = connection } + /// Runs the `DELETE` query. + /// + /// - returns: A `Future` that signals completion. public func run() -> Future { return connection.query(.delete(delete)).transform(to: ()) } @@ -19,9 +29,16 @@ extension SQLiteQuery { } extension SQLiteConnection { - public func delete
(from table: Table.Type) -> SQLiteQuery.DeleteBuilder + /// Creates a new `CreateTableBuilder`. + /// + /// conn.delete(from: Planet.self)... + /// + /// - parameters: + /// - table: Table to delete from. + /// - returns: `DeleteBuilder`. + public func delete
(from table: Table.Type) -> SQLiteQuery.DeleteBuilder
where Table: SQLiteTable { - return .init(table: .init(table: .init(table: Table.sqliteTableName)), on: self) + return .init(table: Table.self, on: self) } } diff --git a/Sources/SQLite/Query/SQLiteQuery+Direction.swift b/Sources/SQLite/Query/SQLiteQuery+Direction.swift index 99f9a66..c7caa6f 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Direction.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Direction.swift @@ -1,12 +1,17 @@ extension SQLiteQuery { + /// Sort direction. public enum Direction { + /// `ASC`. case ascending + /// `DESC`. case descending } } +// MARK: Serialize + extension SQLiteSerializer { - func serialize(_ direction: SQLiteQuery.Direction) -> String { + internal func serialize(_ direction: SQLiteQuery.Direction) -> String { switch direction { case .ascending: return "ASC" case .descending: return "DESC" diff --git a/Sources/SQLite/Query/SQLiteQuery+DropTable.swift b/Sources/SQLite/Query/SQLiteQuery+DropTable.swift index 3b77b12..3778030 100644 --- a/Sources/SQLite/Query/SQLiteQuery+DropTable.swift +++ b/Sources/SQLite/Query/SQLiteQuery+DropTable.swift @@ -1,8 +1,13 @@ extension SQLiteQuery { + /// `DROP TABLE` query. public struct DropTable { + /// Name of table to drop. public var table: TableName + + /// The optional IF EXISTS clause suppresses the error that would normally result if the table does not exist. public var ifExists: Bool + /// Creates a new `DropTable` query. public init(table: TableName, ifExists: Bool = false) { self.table = table self.ifExists = ifExists @@ -10,8 +15,10 @@ extension SQLiteQuery { } } +// MARK: Serialize + extension SQLiteSerializer { - func serialize(_ drop: SQLiteQuery.DropTable) -> String { + internal func serialize(_ drop: SQLiteQuery.DropTable) -> String { var sql: [String] = [] sql.append("DROP TABLE") if drop.ifExists { diff --git a/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift index 220b8dd..28b9dc0 100644 --- a/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift @@ -1,19 +1,27 @@ extension SQLiteQuery { - public final class DropTableBuilder { + /// Builds a `DropTable` query. + public final class DropTableBuilder
where Table: SQLiteTable { + /// Query being built. public var drop: DropTable + + /// Database connection to execute the query on. public let connection: SQLiteConnection - init(table: TableName, on connection: SQLiteConnection) { - self.drop = .init(table: table) + /// Creates a new `DropTableBuilder`. + init(table: Table.Type, on connection: SQLiteConnection) { + self.drop = .init(table: Table.sqliteTableName) self.connection = connection } - @discardableResult + /// The optional IF EXISTS clause suppresses the error that would normally result if the table does not exist. public func ifExists() -> Self { drop.ifExists = true return self } + /// Runs the `DROP TABLE` query. + /// + /// - returns: A `Future` that signals completion. public func run() -> Future { return connection.query(.dropTable(drop)).transform(to: ()) } @@ -21,9 +29,16 @@ extension SQLiteQuery { } extension SQLiteConnection { - public func drop
(table: Table.Type) -> SQLiteQuery.DropTableBuilder + /// Creates a new `DropTableBuilder`. + /// + /// conn.drop(table: Planet.self)... + /// + /// - parameters: + /// - table: Table to drop. + /// - returns: `DropTableBuilder`. + public func drop
(table: Table.Type) -> SQLiteQuery.DropTableBuilder
where Table: SQLiteTable { - return .init(table: Table.sqliteTableName, on: self) + return .init(table: Table.self, on: self) } } diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift index b369f0c..517f185 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift @@ -1,101 +1,68 @@ extension SQLiteQuery { public indirect enum Expression { - public struct Function { - public enum Parameters { - case all - case expressions(distinct: Bool, [Expression]) - } - - public var name: String - public var parameters: Parameters? - - public init(name: String, parameters: Parameters? = nil) { - self.name = name - self.parameters = parameters - } - } - - public enum BetweenOperator { - case between - case notBetween - } - - public enum SubsetExpression { - case subSelect(Select) - case expressions([Expression]) - case table(QualifiedTableName) - case tableFunction(schemaName: String?, Function) - } - - public enum ExistsOperator { - case exists - case notExists - } - - public struct CaseCondition { - public var when: Expression - public var then: Expression - } - - public enum RaiseFunction { - public enum Fail { - case rollback - case abort - case fail - } - case ignore - case fail(Fail, message: String) + /// Binds an `Encodable` value as an `Expression`. + /// + /// - parameters: + /// - value: `Encodable` value to bind. + /// - returns: `Expression`. + public static func bind(_ value: E) throws -> SQLiteQuery.Expression where E: Encodable { + return try SQLiteQueryExpressionEncoder().encode(value) } + /// Literal strings, integers, and constants. case literal(Literal) + + /// Bound data. case data(SQLiteData) + + /// Column name. case column(ColumnName) - case unary(UnaryOperator, Expression) + + /// Binary expression. case binary(Expression, BinaryOperator, Expression) + + /// Function. case function(Function) + + /// Group of expressions. case expressions([Expression]) + /// `CAST ( AS )` case cast(Expression, typeName: TypeName) + /// ` COLLATE ` case collate(Expression, String) - /// NOT LIKE ESCAPE - case compare(Compare) - // BETWEEN AND - case betweenOperator(Expression, BetweenOperator, Expression, Expression) - case subSelect(ExistsOperator?, Select) - /// CASE (WHEN THEN ) ELSE END - case caseExpression(Expression?, [CaseCondition], Expression?) - case raiseFunction(RaiseFunction) + + /// `(SELECT ...)` + case subSelect(Select) } } -extension SQLiteQuery.Expression { - public static func bind(_ value: E) throws -> SQLiteQuery.Expression where E: Encodable { - return try SQLiteQueryExpressionEncoder().encode(value) - } -} +// MARK: Swift Literals extension SQLiteQuery.Expression: ExpressibleByStringLiteral { + /// See `ExpressibleByStringLiteral`. public init(stringLiteral value: String) { self = .column(.init(stringLiteral: value)) } } extension SQLiteQuery.Expression: ExpressibleByArrayLiteral { + /// See `ExpressibleByArrayLiteral`. public init(arrayLiteral elements: SQLiteQuery.Expression...) { self = .expressions(elements) } } +// MARK: Serialize + extension SQLiteSerializer { - func serialize(_ expr: SQLiteQuery.Expression, _ binds: inout [SQLiteData]) -> String { + internal func serialize(_ expr: SQLiteQuery.Expression, _ binds: inout [SQLiteData]) -> String { switch expr { case .data(let value): binds.append(value) return "?" case .literal(let literal): return serialize(literal) - case .unary(let op, let expr): - return serialize(op) + " " + serialize(expr, &binds) case .binary(let lhs, let op, let rhs): switch (op, rhs) { case (.equal, .literal(let l)) where l == .null: return serialize(lhs, &binds) + " IS NULL" @@ -103,29 +70,18 @@ extension SQLiteSerializer { default: return serialize(lhs, &binds) + " " + serialize(op) + " " + serialize(rhs, &binds) } case .column(let col): return serialize(col) - case .compare(let compare): return serialize(compare, &binds) case .expressions(let exprs): return "(" + exprs.map { serialize($0, &binds) }.joined(separator: ", ") + ")" case .function(let function): return serialize(function, &binds) - default: return "\(expr)" + case .subSelect(let select): return "(" + serialize(select, &binds) + ")" + case .collate(let expr, let collate): + return serialize(expr, &binds) + " COLLATE " + collate + case .cast(let expr, let typeName): + return "CAST (" + serialize(expr, &binds) + " AS " + serialize(typeName) + ")" } } -// func serialize(_ subset: SQLiteQuery.Expression.SubsetExpression, _ binds: inout [SQLiteData]) -> String { -// switch subset { -// case .expressions(let exprs): return exprs.map { serialize($0, &binds) }.joined(separator: ", ") -// case .subSelect(let select): return serialize(select, &binds) -// case .table(let table): return serialize(table) -// case .tableFunction(let schema, let function): -// if let schema = schema { -// return escapeString(schema) + "." + serialize(function, &binds) -// } else { -// return serialize(function, &binds) -// } -// } -// } -// func serialize(_ function: SQLiteQuery.Expression.Function, _ binds: inout [SQLiteData]) -> String { if let parameters = function.parameters { return function.name + "(" + serialize(parameters, &binds) + ")" diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift index 6cd06b5..29bb60f 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift @@ -65,40 +65,34 @@ extension SQLiteQuery.Expression { /// `NOT IN` case notIn + + /// `LIKE` + case like + + /// `NOT LIKE` + case notLike + + /// `GLOB` + case glob + + /// `NOT GLOB` + case notGlob + + /// `MATCH` + case match + + /// `NOT MATCH` + case notMatch + + /// `REGEXP` + case regexp + + /// `NOT REGEXP` + case notRegexp } } -public func ==(_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .equal, rhs) -} - -public func !=(_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .notEqual, rhs) -} - -public func ||(_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .or, rhs) -} - -public func &&(_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .and, rhs) -} - -public func &=(_ lhs: inout SQLiteQuery.Expression?, _ rhs: SQLiteQuery.Expression) { - if let l = lhs { - lhs = l && rhs - } else { - lhs = rhs - } -} - -public func |=(_ lhs: inout SQLiteQuery.Expression?, _ rhs: SQLiteQuery.Expression) { - if let l = lhs { - lhs = l || rhs - } else { - lhs = rhs - } -} +// MARK: Serialize extension SQLiteSerializer { func serialize(_ expr: SQLiteQuery.Expression.BinaryOperator) -> String { @@ -125,6 +119,14 @@ extension SQLiteSerializer { case .notIn: return "NOT IN" case .is: return "IS" case .isNot: return "IS NOT" + case .like: return "LIKE" + case .glob: return "GLOB" + case .match: return "MATCH" + case .regexp: return "REGEXP" + case .notLike: return "NOT LIKE" + case .notGlob: return "NOT GLOB" + case .notMatch: return "NOT MATCH" + case .notRegexp: return "NOT REGEXP" } } } diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Compare.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Compare.swift deleted file mode 100644 index 9702044..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Compare.swift +++ /dev/null @@ -1,67 +0,0 @@ -extension SQLiteQuery.Expression { - public struct Compare { - public enum Operator { - /// `LIKE` - case like - - /// `GLOB` - case glob - - /// `MATCH` - case match - - /// `REGEXP` - case regexp - } - - public var left: SQLiteQuery.Expression - public var not: Bool - public var op: Operator - public var right: SQLiteQuery.Expression - public var escape: SQLiteQuery.Expression? - - public init( - _ left: SQLiteQuery.Expression, - not: Bool = false, - _ op: Operator, - _ right: SQLiteQuery.Expression, - escape: SQLiteQuery.Expression? = nil - ) { - self.left = left - self.not = not - self.op = op - self.right = right - self.escape = escape - } - } -} - -infix operator ~~ -public func ~~(_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .compare(.init(lhs, .like, rhs)) -} - -extension SQLiteSerializer { - func serialize(_ compare: SQLiteQuery.Expression.Compare, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append(serialize(compare.left, &binds)) - if compare.not { - sql.append("NOT") - } - sql.append(serialize(compare.op)) - sql.append(serialize(compare.right, &binds)) - if let escape = compare.escape { - sql.append("ESCAPE") - sql.append(serialize(escape, &binds)) - } - return sql.joined(separator: " ") - } - func serialize(_ expr: SQLiteQuery.Expression.Compare.Operator) -> String { - switch expr { - case .like: return "LIKE" - case .glob: return "GLOB" - case .match: return "MATCH" - case .regexp: return "REGEXP" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift new file mode 100644 index 0000000..ed32f59 --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift @@ -0,0 +1,16 @@ +extension SQLiteQuery.Expression { + public struct Function { + public enum Parameters { + case all + case expressions(distinct: Bool, [SQLiteQuery.Expression]) + } + + public var name: String + public var parameters: Parameters? + + public init(name: String, parameters: Parameters? = nil) { + self.name = name + self.parameters = parameters + } + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift index 5f0760c..0dafb89 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift @@ -3,8 +3,8 @@ extension SQLiteQuery.Expression { case numeric(String) case string(String) case blob(Data) - case null case bool(Bool) + case null case currentTime case currentDate case currentTimestamp diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift new file mode 100644 index 0000000..ee5743e --- /dev/null +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift @@ -0,0 +1,162 @@ +infix operator ~~ +infix operator !~ + +// MARK: Expression to Expression operators + +public func < (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .lessThan, rhs) +} + +public func <= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .lessThanOrEqual, rhs) +} + +public func > (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .greaterThan, rhs) +} + +public func >= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .greaterThanOrEqual, rhs) +} + +public func == (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .equal, rhs) +} + +public func != (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .notEqual, rhs) +} + +public func ~~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { + return .binary(lhs, .in, .expressions(rhs)) +} + +public func !~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { + return .binary(lhs, .notIn, .expressions(rhs)) +} + +// MARK: KeyPath to Value operators + +public func < (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .lessThan, .bind(rhs)) +} + +public func <= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .bind(rhs)) +} + +public func > (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .greaterThan, .bind(rhs)) +} + +public func >= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .bind(rhs)) +} + +public func == (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .equal, .bind(rhs)) +} + +public func != (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .notEqual, .bind(rhs)) +} + +public func ~~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .in, .bind(rhs)) +} + +public func !~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression + where Table: SQLiteTable, Value: Encodable +{ + return try .binary(.column(lhs.sqliteColumnName), .notIn, .bind(rhs)) +} + +// MARK: KeyPath to KeyPath operators + +public func < (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .lessThan, .column(rhs.sqliteColumnName)) +} + +public func <= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .column(rhs.sqliteColumnName)) +} + +public func > (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .greaterThan, .column(rhs.sqliteColumnName)) +} + +public func >= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .column(rhs.sqliteColumnName)) +} + +public func == (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .equal, .column(rhs.sqliteColumnName)) +} + +public func != (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .notEqual, .column(rhs.sqliteColumnName)) +} + +public func ~~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .in, .column(rhs.sqliteColumnName)) +} + +public func !~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression + where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +{ + return .binary(.column(lhs.sqliteColumnName), .notIn, .column(rhs.sqliteColumnName)) +} + +// MARK: AND / OR operators + +public func && (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .and, rhs) +} + +public func || (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { + return .binary(lhs, .or, rhs) +} + +public func &= (_ lhs: inout SQLiteQuery.Expression?, _ rhs: SQLiteQuery.Expression) { + if let l = lhs { + lhs = l && rhs + } else { + lhs = rhs + } +} + +public func |= (_ lhs: inout SQLiteQuery.Expression?, _ rhs: SQLiteQuery.Expression) { + if let l = lhs { + lhs = l || rhs + } else { + lhs = rhs + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+UnaryOperator.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+UnaryOperator.swift deleted file mode 100644 index 9f994bf..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+UnaryOperator.swift +++ /dev/null @@ -1,24 +0,0 @@ -extension SQLiteQuery.Expression { - public enum UnaryOperator { - /// `-` - case negative - /// `+` - case noop - /// `~` - case collate - /// `NOT` - case not - } -} - - -extension SQLiteSerializer { - func serialize(_ expr: SQLiteQuery.Expression.UnaryOperator) -> String { - switch expr { - case .negative: return "-" - case .noop: return "+" - case .collate: return "~" - case .not: return "!" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift index b8dec47..ff88300 100644 --- a/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift @@ -8,19 +8,16 @@ extension SQLiteQuery { self.connection = connection } - @discardableResult public func or(_ conflictResolution: SQLiteQuery.ConflictResolution) -> Self { insert.conflictResolution = conflictResolution return self } - @discardableResult public func defaults() throws -> Self { insert.values = .defaults return self } - @discardableResult public func from(_ select: (SelectBuilder) -> ()) throws -> Self { let builder = connection.select() select(builder) @@ -28,7 +25,6 @@ extension SQLiteQuery { return self } - @discardableResult public func value(_ value: E) throws -> Self where E: Encodable { @@ -36,7 +32,6 @@ extension SQLiteQuery { return self } - @discardableResult public func values(_ values: [E]) throws -> Self where E: Encodable { diff --git a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift index 30a3ab7..a886f1f 100644 --- a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift @@ -96,26 +96,6 @@ extension Dictionary where Key == SQLiteColumn, Value == SQLiteData { } } -public func ==(_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .equal, .bind(rhs)) -} - -public func ==( - _ lhs: KeyPath, _ rhs: KeyPath -) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .equal, .column(rhs.sqliteColumnName)) -} - -public func !=(_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .notEqual, .bind(rhs)) -} - public protocol SQLiteTable: Codable, Reflectable { static var sqliteTableName: SQLiteQuery.TableName { get } } diff --git a/Sources/SQLite/Query/SQLiteQuerySerializer.swift b/Sources/SQLite/Query/SQLiteQuerySerializer.swift index b66325f..89759d5 100644 --- a/Sources/SQLite/Query/SQLiteQuerySerializer.swift +++ b/Sources/SQLite/Query/SQLiteQuerySerializer.swift @@ -25,14 +25,23 @@ extension SQLitePredicateBuilder { return self } - @discardableResult - public func `where`(or expressions: SQLiteQuery.Expression...) -> Self { + public func orWhere(_ expressions: SQLiteQuery.Expression...) -> Self { for expression in expressions { self.predicate |= expression } return self } + public func `where`(_ lhs: SQLiteQuery.Expression, _ op: SQLiteQuery.Expression.BinaryOperator, _ rhs: SQLiteQuery.Expression) -> Self { + predicate &= .binary(lhs, op, rhs) + return self + } + + public func orWhere(_ lhs: SQLiteQuery.Expression, _ op: SQLiteQuery.Expression.BinaryOperator, _ rhs: SQLiteQuery.Expression) -> Self { + predicate |= .binary(lhs, op, rhs) + return self + } + public func `where`(group: (SQLitePredicateBuilder) throws -> ()) rethrows -> Self { let builder = SQLiteQuery.SelectBuilder(on: connection) try group(builder) diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 4d6c808..a9f27d8 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -94,7 +94,7 @@ class SQLiteTests: XCTestCase { let selectA = try conn.select().all() .from(Planet.self) - .where(or: \Planet.name == "Mars", \Planet.name == "Venus", \Planet.name == "Earth") + .orWhere(\Planet.name == "Mars", \Planet.name == "Venus", \Planet.name == "Earth") .run(decoding: Planet.self).wait() print(selectA) @@ -112,6 +112,12 @@ class SQLiteTests: XCTestCase { .wait() print(selectC) + let res = try conn.select().all().from(Planet.self) + .where("name", .like, .bind("%rth")) + .orWhere(.literal(1), .equal, .literal(2)) + .run().wait() + print(res) + try conn.create(table: Galaxy2.self).temporary().ifNotExists() .as { $0.select().all().from(Galaxy.self) } .run().wait() From 3d2ef119edebd4f628bf589a1ad31e3486d19a0f Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Sun, 17 Jun 2018 02:23:21 -0400 Subject: [PATCH 10/21] The Great SQL Protocolization --- Package.swift | 5 +- Sources/SQL/SQLBinaryOperator.swift | 162 +++++++ Sources/SQL/SQLBind.swift | 23 + Sources/SQL/SQLCollation.swift | 2 + Sources/SQL/SQLColumnConstraint.swift | 105 ++++ .../SQL/SQLColumnConstraintAlgorithm.swift | 86 ++++ Sources/SQL/SQLColumnDefinition.swift | 32 ++ Sources/SQL/SQLColumnIdentifier.swift | 60 +++ Sources/SQL/SQLConflictResolution.swift | 51 ++ Sources/SQL/SQLConnection.swift | 8 + Sources/SQL/SQLCreateTable.swift | 64 +++ Sources/SQL/SQLCreateTableBuilder.swift | 83 ++++ Sources/SQL/SQLDataType.swift | 3 + Sources/SQL/SQLDelete.swift | 42 ++ Sources/SQL/SQLDeleteBuilder.swift | 43 ++ Sources/SQL/SQLDirection.swift | 29 ++ Sources/SQL/SQLDistinct.swift | 42 ++ Sources/SQL/SQLDrop.swift | 38 ++ Sources/SQL/SQLDropTableBuilder.swift | 35 ++ Sources/SQL/SQLExpression.swift | 183 +++++++ Sources/SQL/SQLForeignKey.swift | 54 +++ Sources/SQL/SQLFunction.swift | 21 + Sources/SQL/SQLFunctionArgument.swift | 30 ++ Sources/SQL/SQLGroupBy.swift | 18 + Sources/SQL/SQLIdentifier.swift | 17 + Sources/SQL/SQLInsert.swift | 37 ++ Sources/SQL/SQLInsertBuilder.swift | 67 +++ Sources/SQL/SQLLiteral.swift | 63 +++ Sources/SQL/SQLOrderBy.swift | 20 + Sources/SQL/SQLPrimaryKey.swift | 17 + Sources/SQL/SQLQuery.swift | 33 ++ Sources/SQL/SQLQueryBuilder.swift | 60 +++ .../SQLQueryEncoder.swift} | 19 +- Sources/SQL/SQLRowDecoder.swift | 8 + Sources/SQL/SQLSelect.swift | 65 +++ Sources/SQL/SQLSelectBuilder.swift | 115 +++++ Sources/SQL/SQLSelectExpression.swift | 89 ++++ Sources/SQL/SQLSerializable.swift | 9 + Sources/SQL/SQLTable.swift | 11 + Sources/SQL/SQLTableConstraint.swift | 59 +++ Sources/SQL/SQLTableConstraintAlgorithm.swift | 78 +++ Sources/SQL/SQLTableIdentifier.swift | 35 ++ Sources/SQL/SQLUpdate.swift | 41 ++ Sources/SQL/SQLUpdateBuilder.swift | 60 +++ Sources/SQL/SQLWhereBuilder.swift | 45 ++ Sources/SQL/SQLWith.swift | 10 + ...nEncoder.swift => SQLiteDataEncoder.swift} | 56 +-- Sources/SQLite/Codable/SQLiteRowDecoder.swift | 6 +- .../SQLite/Database/SQLiteConnection.swift | 11 +- Sources/SQLite/Query/SQLiteBind.swift | 29 ++ Sources/SQLite/Query/SQLiteCollation.swift | 6 + Sources/SQLite/Query/SQLiteFunction.swift | 24 + Sources/SQLite/Query/SQLitePrimaryKey.swift | 35 ++ .../SQLite/Query/SQLiteQuery+AlterTable.swift | 112 ++--- .../Query/SQLiteQuery+AlterTableBuilder.swift | 167 +++---- Sources/SQLite/Query/SQLiteQuery+Column.swift | 37 -- .../SQLiteQuery+ColumnConstraint+0.swift | 161 ------ ...teQuery+ColumnConstraint+Nullability.swift | 39 -- ...iteQuery+ColumnConstraint+PrimaryKey.swift | 51 -- .../SQLiteQuery+ColumnConstraint+Unique.swift | 29 -- .../Query/SQLiteQuery+ColumnDefinition.swift | 37 -- .../SQLiteQuery+ConflictResolution.swift | 54 --- .../Query/SQLiteQuery+CreateTable.swift | 155 +++--- .../SQLiteQuery+CreateTableBuilder.swift | 131 ----- Sources/SQLite/Query/SQLiteQuery+Delete.swift | 36 -- .../Query/SQLiteQuery+DeleteBuilder.swift | 44 -- .../SQLite/Query/SQLiteQuery+Direction.swift | 20 - .../SQLite/Query/SQLiteQuery+DropTable.swift | 30 -- .../Query/SQLiteQuery+DropTableBuilder.swift | 44 -- .../Query/SQLiteQuery+Expression+0.swift | 105 ---- ...QLiteQuery+Expression+BinaryOperator.swift | 132 ----- .../SQLiteQuery+Expression+Function.swift | 16 - .../SQLiteQuery+Expression+Literal.swift | 102 ++-- .../SQLiteQuery+Expression+Operators.swift | 297 ++++++------ .../SQLite/Query/SQLiteQuery+ForeignKey.swift | 94 ---- .../Query/SQLiteQuery+IndexedColumn.swift | 51 -- Sources/SQLite/Query/SQLiteQuery+Insert.swift | 94 ---- .../Query/SQLiteQuery+InsertBuilder.swift | 56 --- Sources/SQLite/Query/SQLiteQuery+Join.swift | 166 +++---- Sources/SQLite/Query/SQLiteQuery+Name.swift | 36 -- Sources/SQLite/Query/SQLiteQuery+Select.swift | 138 ------ .../Query/SQLiteQuery+SelectBuilder.swift | 126 ----- .../SQLite/Query/SQLiteQuery+SetValues.swift | 45 -- .../Query/SQLiteQuery+TableConstraint.swift | 69 --- .../SQLite/Query/SQLiteQuery+TableName.swift | 90 ---- .../Query/SQLiteQuery+TableOrSubquery.swift | 30 -- .../SQLite/Query/SQLiteQuery+TypeName.swift | 21 - Sources/SQLite/Query/SQLiteQuery+Update.swift | 37 -- .../Query/SQLiteQuery+UpdateBuilder.swift | 43 -- Sources/SQLite/Query/SQLiteQuery.swift | 181 +++++-- .../SQLiteQueryExpressionRepresentable.swift | 0 .../SQLite/Query/SQLiteQuerySerializer.swift | 57 --- Sources/SQLite/Query/SQLiteTable.swift | 1 + Sources/SQLite/Row/SQLiteData.swift | 6 +- Sources/SQLite/Row/SQLiteDataType.swift | 13 +- Sources/SQLite/Utilities/Exports.swift | 1 + Tests/SQLiteTests/SQLiteTests.swift | 459 ++++++++++-------- 97 files changed, 3193 insertions(+), 2764 deletions(-) create mode 100644 Sources/SQL/SQLBinaryOperator.swift create mode 100644 Sources/SQL/SQLBind.swift create mode 100644 Sources/SQL/SQLCollation.swift create mode 100644 Sources/SQL/SQLColumnConstraint.swift create mode 100644 Sources/SQL/SQLColumnConstraintAlgorithm.swift create mode 100644 Sources/SQL/SQLColumnDefinition.swift create mode 100644 Sources/SQL/SQLColumnIdentifier.swift create mode 100644 Sources/SQL/SQLConflictResolution.swift create mode 100644 Sources/SQL/SQLConnection.swift create mode 100644 Sources/SQL/SQLCreateTable.swift create mode 100644 Sources/SQL/SQLCreateTableBuilder.swift create mode 100644 Sources/SQL/SQLDataType.swift create mode 100644 Sources/SQL/SQLDelete.swift create mode 100644 Sources/SQL/SQLDeleteBuilder.swift create mode 100644 Sources/SQL/SQLDirection.swift create mode 100644 Sources/SQL/SQLDistinct.swift create mode 100644 Sources/SQL/SQLDrop.swift create mode 100644 Sources/SQL/SQLDropTableBuilder.swift create mode 100644 Sources/SQL/SQLExpression.swift create mode 100644 Sources/SQL/SQLForeignKey.swift create mode 100644 Sources/SQL/SQLFunction.swift create mode 100644 Sources/SQL/SQLFunctionArgument.swift create mode 100644 Sources/SQL/SQLGroupBy.swift create mode 100644 Sources/SQL/SQLIdentifier.swift create mode 100644 Sources/SQL/SQLInsert.swift create mode 100644 Sources/SQL/SQLInsertBuilder.swift create mode 100644 Sources/SQL/SQLLiteral.swift create mode 100644 Sources/SQL/SQLOrderBy.swift create mode 100644 Sources/SQL/SQLPrimaryKey.swift create mode 100644 Sources/SQL/SQLQuery.swift create mode 100644 Sources/SQL/SQLQueryBuilder.swift rename Sources/{SQLite/Codable/SQLiteQueryEncoder.swift => SQL/SQLQueryEncoder.swift} (90%) create mode 100644 Sources/SQL/SQLRowDecoder.swift create mode 100644 Sources/SQL/SQLSelect.swift create mode 100644 Sources/SQL/SQLSelectBuilder.swift create mode 100644 Sources/SQL/SQLSelectExpression.swift create mode 100644 Sources/SQL/SQLSerializable.swift create mode 100644 Sources/SQL/SQLTable.swift create mode 100644 Sources/SQL/SQLTableConstraint.swift create mode 100644 Sources/SQL/SQLTableConstraintAlgorithm.swift create mode 100644 Sources/SQL/SQLTableIdentifier.swift create mode 100644 Sources/SQL/SQLUpdate.swift create mode 100644 Sources/SQL/SQLUpdateBuilder.swift create mode 100644 Sources/SQL/SQLWhereBuilder.swift create mode 100644 Sources/SQL/SQLWith.swift rename Sources/SQLite/Codable/{SQLiteQueryExpressionEncoder.swift => SQLiteDataEncoder.swift} (71%) create mode 100644 Sources/SQLite/Query/SQLiteBind.swift create mode 100644 Sources/SQLite/Query/SQLiteCollation.swift create mode 100644 Sources/SQLite/Query/SQLiteFunction.swift create mode 100644 Sources/SQLite/Query/SQLitePrimaryKey.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Column.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Delete.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Direction.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+DropTable.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+0.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+IndexedColumn.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Insert.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Name.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Select.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+SetValues.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+TableConstraint.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+TableName.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+TableOrSubquery.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+TypeName.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Update.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift rename Sources/SQLite/{Codable => Query}/SQLiteQueryExpressionRepresentable.swift (100%) delete mode 100644 Sources/SQLite/Query/SQLiteQuerySerializer.swift create mode 100644 Sources/SQLite/Query/SQLiteTable.swift diff --git a/Package.swift b/Package.swift index b56c1b7..f5c60df 100644 --- a/Package.swift +++ b/Package.swift @@ -14,13 +14,14 @@ let package = Package( .package(url: "https://github.com/vapor/database-kit.git", from: "1.0.0"), ], targets: [ + .target(name: "SQL", dependencies: ["Core"]), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"]), ] ) #if os(Linux) package.targets.append(.target(name: "CSQLite")) -package.targets.append(.target(name: "SQLite", dependencies: ["Async", "Bits", "Core", "CSQLite", "DatabaseKit", "Debugging"])) +package.targets.append(.target(name: "SQLite", dependencies: ["Async", "Bits", "Core", "CSQLite", "DatabaseKit", "Debugging", "SQL"])) #else -package.targets.append(.target(name: "SQLite", dependencies: ["Async", "Bits", "Core", "DatabaseKit", "Debugging"])) +package.targets.append(.target(name: "SQLite", dependencies: ["Async", "Bits", "Core", "DatabaseKit", "Debugging", "SQL"])) #endif diff --git a/Sources/SQL/SQLBinaryOperator.swift b/Sources/SQL/SQLBinaryOperator.swift new file mode 100644 index 0000000..27ebe9e --- /dev/null +++ b/Sources/SQL/SQLBinaryOperator.swift @@ -0,0 +1,162 @@ +public protocol SQLBinaryOperator: SQLSerializable { + static var equal: Self { get } + static var notEqual: Self { get } + static var and: Self { get } + static var or: Self { get } +} + +// MARK: Generic + +public enum GenericSQLBinaryOperator: SQLBinaryOperator { + /// See `SQLBinaryOperator`. + public static var equal: GenericSQLBinaryOperator { return ._equal } + + /// See `SQLBinaryOperator`. + public static var notEqual: GenericSQLBinaryOperator { return ._notEqual } + + /// See `SQLBinaryOperator`. + public static var and: GenericSQLBinaryOperator { return ._and } + + /// See `SQLBinaryOperator`. + public static var or: GenericSQLBinaryOperator { return ._or } + + /// `||` + case _concatenate + + /// `*` + case _multiply + + /// `/` + case _divide + + /// `%` + case _modulo + + /// `+` + case _add + + /// `-` + case _subtract + + /// `<<` + case _bitwiseShiftLeft + + /// `>>` + case _bitwiseShiftRight + + /// `&` + case _bitwiseAnd + + /// `|` + case _bitwiseOr + + /// `<` + case _lessThan + + /// `<=` + case _lessThanOrEqual + + /// `>` + case _greaterThan + + /// `>=` + case _greaterThanOrEqual + + /// `=` or `==` + case _equal + + /// `!=` or `<>` + case _notEqual + + /// `AND` + case _and + + /// `OR` + case _or + + /// `IS` + case _is + + /// `IS NOT` + case _isNot + + /// `IN` + case _in + + /// `NOT IN` + case _notIn + + /// `LIKE` + case _like + + /// `NOT LIKE` + case _notLike + + /// `GLOB` + case _glob + + /// `NOT GLOB` + case _notGlob + + /// `MATCH` + case _match + + /// `NOT MATCH` + case _notMatch + + /// `REGEXP` + case _regexp + + /// `NOT REGEXP` + case _notRegexp + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._add: return "+" + case ._bitwiseAnd: return "&" + case ._bitwiseOr: return "|" + case ._bitwiseShiftLeft: return "<<" + case ._bitwiseShiftRight: return ">>" + case ._concatenate: return "||" + case ._divide: return "/" + case ._equal: return "=" + case ._greaterThan: return ">" + case ._greaterThanOrEqual: return ">=" + case ._lessThan: return "<" + case ._lessThanOrEqual: return "<=" + case ._modulo: return "%" + case ._multiply: return "*" + case ._notEqual: return "!=" + case ._subtract: return "-" + case ._and: return "AND" + case ._or: return "OR" + case ._in: return "IN" + case ._notIn: return "NOT IN" + case ._is: return "IS" + case ._isNot: return "IS NOT" + case ._like: return "LIKE" + case ._glob: return "GLOB" + case ._match: return "MATCH" + case ._regexp: return "REGEXP" + case ._notLike: return "NOT LIKE" + case ._notGlob: return "NOT GLOB" + case ._notMatch: return "NOT MATCH" + case ._notRegexp: return "NOT REGEXP" + } + } +} + +// MARK: Operator + +public func == (_ lhs: KeyPath, _ rhs: V) -> E + where T: SQLTable, V: Encodable, E: SQLExpression +{ + return E.binary(.column(.keyPath(lhs)), .equal, .bind(.encodable(rhs))) +} + +public func != (_ lhs: KeyPath, _ rhs: V) -> E + where T: SQLTable, V: Encodable, E: SQLExpression +{ + return E.binary(.column(.keyPath(lhs)), .notEqual, .bind(.encodable(rhs))) +} diff --git a/Sources/SQL/SQLBind.swift b/Sources/SQL/SQLBind.swift new file mode 100644 index 0000000..042f477 --- /dev/null +++ b/Sources/SQL/SQLBind.swift @@ -0,0 +1,23 @@ +public protocol SQLBind: SQLSerializable { + static func encodable(_ value: E) -> Self + where E: Encodable +} + +// MARK: Generic + +public struct GenericSQLBind: SQLBind { + /// See `SQLBind`. + public static func encodable(_ value: E) -> GenericSQLBind + where E: Encodable + { + return self.init(value: value) + } + + public var value: Encodable + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + binds.append(value) + return "?" + } +} diff --git a/Sources/SQL/SQLCollation.swift b/Sources/SQL/SQLCollation.swift new file mode 100644 index 0000000..2a59c26 --- /dev/null +++ b/Sources/SQL/SQLCollation.swift @@ -0,0 +1,2 @@ + +public protocol SQLCollation: SQLSerializable { } diff --git a/Sources/SQL/SQLColumnConstraint.swift b/Sources/SQL/SQLColumnConstraint.swift new file mode 100644 index 0000000..9517f1e --- /dev/null +++ b/Sources/SQL/SQLColumnConstraint.swift @@ -0,0 +1,105 @@ +public protocol SQLColumnConstraint: SQLSerializable { + associatedtype Identifier: SQLIdentifier + associatedtype ColumnConstraintAlgorithm: SQLColumnConstraintAlgorithm + static func constraint(_ algorithm: ColumnConstraintAlgorithm, _ identifier: Identifier?) -> Self +} + +// MARK: Convenience + +extension SQLColumnConstraint { + public static var primaryKey: Self { + return .primaryKey(identifier: nil) + } + + /// Creates a new `PRIMARY KEY` column constraint. + /// + /// - parameters: + /// - identifier: Optional constraint name. + /// - returns: New column constraint. + public static func primaryKey(identifier: Identifier?) -> Self { + return .constraint(.primaryKey(.primaryKey()), identifier) + } + + public static var notNull: Self { + return .notNull(identifier: nil) + } + + /// Creates a new `NOT NULL` column constraint. + /// + /// - parameters: + /// - identifier: Optional constraint name. + /// - returns: New column constraint. + public static func notNull(identifier: Identifier?) -> Self { + return .constraint(.notNull, identifier) + } + + /// Creates a new `UNIQUE` column constraint. + /// + /// - parameters: + /// - identifier: Optional constraint name. + /// - returns: New column constraint. + public static func unique(identifier: Identifier? = nil) -> Self { + return .constraint(.unique, identifier) + } + + /// Creates a new `DEFAULT ` column constraint. + /// + /// - parameters + /// - expression: Expression to evaluate when setting the default value. + /// - identifier: Optional constraint name. + /// - returns: New column constraint. + public static func `default`( + _ expression: ColumnConstraintAlgorithm.Expression, + identifier: Identifier? = nil + ) -> Self { + return .constraint(.default(expression), identifier) + } + + public static func references( + _ keyPath: KeyPath, + onDelete: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + onUpdate: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + identifier: Identifier? = nil + ) -> Self + where T: SQLTable + { + return references(.keyPath(keyPath), [.keyPath(keyPath)], onDelete: onDelete, onUpdate: onUpdate, identifier: identifier) + } + + public static func references( + _ foreignTable: ColumnConstraintAlgorithm.ForeignKey.TableIdentifier, + _ foreignColumns: [ColumnConstraintAlgorithm.ForeignKey.Identifier], + onDelete: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + onUpdate: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + identifier: Identifier? = nil + ) -> Self { + return .constraint(.foreignKey(.foreignKey(foreignTable, foreignColumns, onDelete: onDelete, onUpdate: onUpdate)), identifier) + } +} + + +// MARK: Generic + +public struct GenericSQLColumnConstraint: SQLColumnConstraint + where Identifier: SQLIdentifier, ColumnConstraintAlgorithm: SQLColumnConstraintAlgorithm +{ + public typealias `Self` = GenericSQLColumnConstraint + + /// See `SQLColumnConstraint`. + public static func constraint(_ algorithm: ColumnConstraintAlgorithm, _ identifier: Identifier?) -> Self { + return .init(identifier: identifier, algorithm: algorithm) + } + + public var identifier: Identifier? + + public var algorithm: ColumnConstraintAlgorithm + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + if let identifier = self.identifier { + return "CONSTRAINT " + identifier.serialize(&binds) + " " + algorithm.serialize(&binds) + } else { + return algorithm.serialize(&binds) + } + } +} diff --git a/Sources/SQL/SQLColumnConstraintAlgorithm.swift b/Sources/SQL/SQLColumnConstraintAlgorithm.swift new file mode 100644 index 0000000..dce9d89 --- /dev/null +++ b/Sources/SQL/SQLColumnConstraintAlgorithm.swift @@ -0,0 +1,86 @@ +public protocol SQLColumnConstraintAlgorithm: SQLSerializable { + associatedtype Expression: SQLExpression + associatedtype Collation: SQLCollation + associatedtype PrimaryKey: SQLPrimaryKey + associatedtype ForeignKey: SQLForeignKey + static func primaryKey(_ primaryKey: PrimaryKey) -> Self + static var notNull: Self { get } + static var unique: Self { get } + static func check(_ expression: Expression) -> Self + static func collate(_ collation: Collation) -> Self + static func `default`(_ expression: Expression) -> Self + static func foreignKey(_ foreignKey: ForeignKey) -> Self +} + +// MARK: Generic + +public enum GenericSQLColumnConstraintAlgorithm: SQLColumnConstraintAlgorithm + where Expression: SQLExpression, Collation: SQLCollation, PrimaryKey: SQLPrimaryKey, ForeignKey: SQLForeignKey +{ + public typealias `Self` = GenericSQLColumnConstraintAlgorithm + + /// See `SQLColumnConstraintAlgorithm`. + public static func primaryKey(_ primaryKey: PrimaryKey) -> Self { + return ._primaryKey(primaryKey) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static var notNull: Self { + return ._notNull + } + + /// See `SQLColumnConstraintAlgorithm`. + public static var unique: Self { + return ._unique + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func check(_ expression: Expression) -> Self { + return ._check(expression) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func collate(_ collation: Collation) -> Self { + return .collate(collation) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func `default`(_ expression: Expression) -> Self { + return ._default(expression) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func foreignKey(_ foreignKey: ForeignKey) -> Self { + return ._foreignKey(foreignKey) + } + + case _primaryKey(PrimaryKey) + case _notNull + case _unique + case _check(Expression) + case _collate(Collation) + case _default(Expression) + case _foreignKey(ForeignKey) + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._primaryKey(let primaryKey): + let pk = primaryKey.serialize(&binds) + if pk.isEmpty { + return "PRIMARY KEY" + } else { + return "PRIMARY KEY " + pk + } + case ._notNull: return "NOT NULL" + case ._unique: return "UNIQUE" + case ._check(let expression): + return "CHECK (" + expression.serialize(&binds) + ")" + case ._collate(let collation): + return "COLLATE " + collation.serialize(&binds) + case ._default(let expression): + return "DEFAULT (" + expression.serialize(&binds) + ")" + case ._foreignKey(let foreignKey): return "REFERENCES " + foreignKey.serialize(&binds) + } + } +} diff --git a/Sources/SQL/SQLColumnDefinition.swift b/Sources/SQL/SQLColumnDefinition.swift new file mode 100644 index 0000000..75b9bf3 --- /dev/null +++ b/Sources/SQL/SQLColumnDefinition.swift @@ -0,0 +1,32 @@ +public protocol SQLColumnDefinition: SQLSerializable { + associatedtype ColumnIdentifier: SQLColumnIdentifier + associatedtype DataType: SQLDataType + associatedtype ColumnConstraint: SQLColumnConstraint + static func columnDefinition(_ column: ColumnIdentifier, _ dataType: DataType, _ constraints: [ColumnConstraint]) -> Self +} + +// MARK: Generic + +public struct GenericSQLColumnDefinition: SQLColumnDefinition + where ColumnIdentifier: SQLColumnIdentifier, DataType: SQLDataType, ColumnConstraint: SQLColumnConstraint +{ + public typealias `Self` = GenericSQLColumnDefinition + + /// See `SQLColumnDefinition`. + public static func columnDefinition(_ column: ColumnIdentifier, _ dataType: DataType, _ constraints: [ColumnConstraint]) -> Self { + return .init(column: column, dataType: dataType, constraints: constraints) + } + + public var column: ColumnIdentifier + public var dataType: DataType + public var constraints: [ColumnConstraint] + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append(column.identifier.serialize(&binds)) + sql.append(dataType.serialize(&binds)) + sql.append(constraints.serialize(&binds, joinedBy: " ")) + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLColumnIdentifier.swift b/Sources/SQL/SQLColumnIdentifier.swift new file mode 100644 index 0000000..3c3eed0 --- /dev/null +++ b/Sources/SQL/SQLColumnIdentifier.swift @@ -0,0 +1,60 @@ +public protocol SQLColumnIdentifier: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Identifier: SQLIdentifier + + static func column(_ table: TableIdentifier?, _ identifier: Identifier) -> Self + + var table: TableIdentifier? { get set } + var identifier: Identifier { get set } +} + +// MARK: Convenience + + +extension SQLColumnIdentifier { + static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { + guard let property = try! T.reflectProperty(forKey: keyPath) else { + fatalError("Could not reflect property of type \(V.self) on \(T.self): \(keyPath)") + } + return .column(.table(.identifier(T.sqlTableIdentifierString)), .identifier(property.path[0])) + } +} +extension SQLTableIdentifier { + static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { + return .table(.identifier(T.sqlTableIdentifierString)) + } +} + +extension SQLIdentifier { + static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { + guard let property = try! T.reflectProperty(forKey: keyPath) else { + fatalError("Could not reflect property of type \(V.self) on \(T.self): \(keyPath)") + } + return .identifier(property.path[0]) + } +} + +// MARK: Generic + +public struct GenericSQLColumnIdentifier: SQLColumnIdentifier + where TableIdentifier: SQLTableIdentifier, Identifier: SQLIdentifier +{ + /// See `SQLColumnIdentifier`. + public static func column(_ table: TableIdentifier?, _ identifier: Identifier) -> GenericSQLColumnIdentifier { + return self.init(table: table, identifier: identifier) + } + + /// See `SQLColumnIdentifier`. + public var table: TableIdentifier? + + /// See `SQLColumnIdentifier`. + public var identifier: Identifier + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch table { + case .some(let table): return table.serialize(&binds) + "." + identifier.serialize(&binds) + case .none: return identifier.serialize(&binds) + } + } +} diff --git a/Sources/SQL/SQLConflictResolution.swift b/Sources/SQL/SQLConflictResolution.swift new file mode 100644 index 0000000..68cd043 --- /dev/null +++ b/Sources/SQL/SQLConflictResolution.swift @@ -0,0 +1,51 @@ +public protocol SQLConflictResolution: SQLSerializable { } + +/// `ON CONFLICT` clause. Supported constraint conflict resolution algorithms. +public enum GenericSQLConflictResolution: SQLConflictResolution { + /// When a UNIQUE or PRIMARY KEY constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing + /// the constraint violation prior to inserting or updating the current row and the command continues executing normally. If a + /// NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with the default value for + /// that column, or if the column has no default value, then the ABORT algorithm is used. If a CHECK constraint violation + /// occurs, the REPLACE conflict resolution algorithm always works like ABORT. + /// + /// When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and + /// only if recursive triggers are enabled. + /// + /// The update hook is not invoked for rows that are deleted by the REPLACE conflict resolution strategy. Nor does REPLACE + /// increment the change counter. The exceptional behaviors defined in this paragraph might change in a future release. + case replace + + /// When an applicable constraint violation occurs, the ROLLBACK resolution algorithm aborts the current SQL statement with + /// an SQLITE_CONSTRAINT error and rolls back the current transaction. If no transaction is active (other than the implied + /// transaction that is created on every command) then the ROLLBACK resolution algorithm works the same as the ABORT algorithm. + case rollback + + /// When an applicable constraint violation occurs, the ABORT resolution algorithm aborts the current SQL statement with an + /// SQLITE_CONSTRAINT error and backs out any changes made by the current SQL statement; but changes caused by prior SQL + /// statements within the same transaction are preserved and the transaction remains active. This is the default behavior and + /// the behavior specified by the SQL standard. + case abort + + /// When an applicable constraint violation occurs, the FAIL resolution algorithm aborts the current SQL statement with an + /// SQLITE_CONSTRAINT error. But the FAIL resolution does not back out prior changes of the SQL statement that failed nor does + /// it end the transaction. For example, if an UPDATE statement encountered a constraint violation on the 100th row that it + /// attempts to update, then the first 99 row changes are preserved but changes to rows 100 and beyond never occur. + case fail + + /// When an applicable constraint violation occurs, the IGNORE resolution algorithm skips the one row that contains the + /// constraint violation and continues processing subsequent rows of the SQL statement as if nothing went wrong. Other rows + /// before and after the row that contained the constraint violation are inserted or updated normally. No error is returned + /// when the IGNORE conflict resolution algorithm is used. + case ignore + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case .abort: return "ABORT" + case .fail: return "FAIL" + case .ignore: return "IGNORE" + case .replace: return "REPLACE" + case .rollback: return "ROLLBACK" + } + } +} diff --git a/Sources/SQL/SQLConnection.swift b/Sources/SQL/SQLConnection.swift new file mode 100644 index 0000000..e294a78 --- /dev/null +++ b/Sources/SQL/SQLConnection.swift @@ -0,0 +1,8 @@ +import Async + +public protocol SQLConnection { + associatedtype Output + associatedtype Query: SQLQuery + where Query.RowDecoder.Row == Output + func query(_ query: Query, _ handler: @escaping (Output) throws -> ()) -> Future +} diff --git a/Sources/SQL/SQLCreateTable.swift b/Sources/SQL/SQLCreateTable.swift new file mode 100644 index 0000000..808ceea --- /dev/null +++ b/Sources/SQL/SQLCreateTable.swift @@ -0,0 +1,64 @@ +public protocol SQLCreateTable: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype ColumnDefinition: SQLColumnDefinition + associatedtype TableConstraint: SQLTableConstraint + + static func createTable(_ table: TableIdentifier) -> Self + + /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. + var temporary: Bool { get set } + + /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the + /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view + /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An + /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is + /// specified. + var ifNotExists: Bool { get set } + + var table: TableIdentifier { get set } + var columns: [ColumnDefinition] { get set } + var tableConstraints: [TableConstraint] { get set } +} + +/// The `CREATE TABLE` command is used to create a new table in a database. +public struct GenericSQLCreateTable: SQLCreateTable + where TableIdentifier: SQLTableIdentifier, ColumnDefinition: SQLColumnDefinition, TableConstraint: SQLTableConstraint +{ + public typealias `Self` = GenericSQLCreateTable + + /// See `SQLCreateTable`. + public static func createTable(_ table: TableIdentifier) -> Self { + return .init(temporary: false, ifNotExists: false, table: table, columns: [], tableConstraints: []) + } + + /// See `SQLCreateTable`. + public var temporary: Bool + + /// See `SQLCreateTable`. + public var ifNotExists: Bool + + /// See `SQLCreateTable`. + public var table: TableIdentifier + + /// See `SQLCreateTable`. + public var columns: [ColumnDefinition] + + /// See `SQLCreateTable`. + public var tableConstraints: [TableConstraint] + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("CREATE") + if temporary { + sql.append("TEMPORARY") + } + sql.append("TABLE") + if ifNotExists { + sql.append("IF NOT EXISTS") + } + sql.append(table.serialize(&binds)) + sql.append("(" + columns.serialize(&binds) + tableConstraints.serialize(&binds) + ")") + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLCreateTableBuilder.swift b/Sources/SQL/SQLCreateTableBuilder.swift new file mode 100644 index 0000000..28202df --- /dev/null +++ b/Sources/SQL/SQLCreateTableBuilder.swift @@ -0,0 +1,83 @@ +public final class SQLCreateTableBuilder: SQLQueryBuilder + where Connection: SQLConnection +{ + /// `CreateTable` query being built. + public var createTable: Connection.Query.CreateTable + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .createTable(createTable) + } + + /// Creates a new `SQLCreateTableBuilder`. + public init(_ createTable: Connection.Query.CreateTable, on connection: Connection) { + self.createTable = createTable + self.connection = connection + } + + + /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. + public func temporary() -> Self { + createTable.temporary = true + return self + } + + /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the + /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view + /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An + /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is + /// specified. + public func ifNotExists() -> Self { + createTable.ifNotExists = true + return self + } + + /// Adds a column to the table. + /// + /// conn.create(table: Planet.self).column(for: \.name, type: .text, .notNull).run() + /// + /// - parameters: + /// - keyPath: Swift `KeyPath` to property that should be added. + /// - type: Name of type to use for this column. + /// - constraints: Zero or more column constraints to add. + /// - returns: Self for chaining. + public func column( + for keyPath: KeyPath, + type typeName: Connection.Query.CreateTable.ColumnDefinition.DataType, + _ constraints: Connection.Query.CreateTable.ColumnDefinition.ColumnConstraint... + ) -> Self where T: SQLTable { + return column(.columnDefinition(.keyPath(keyPath), typeName, constraints)) + } + + /// Adds a column to the table. + /// + /// conn.create(table: Planet.self).column(...).run() + /// + /// - parameters: + /// - columnDefinition: Column definition to add. + /// - returns: Self for chaining. + public func column(_ columnDefinition: Connection.Query.CreateTable.ColumnDefinition) -> Self { + createTable.columns.append(columnDefinition) + return self + } +} + +// MARK: Connection + +extension SQLConnection { + /// Creates a new `SQLCreateTableBuilder`. + /// + /// conn.create(table: Planet.self)... + /// + /// - parameters: + /// - table: Table to create. + /// - returns: `CreateTableBuilder`. + public func create
(table: Table.Type) -> SQLCreateTableBuilder + where Table: SQLTable + { + return .init(.createTable(.table(Table.self)), on: self) + } +} diff --git a/Sources/SQL/SQLDataType.swift b/Sources/SQL/SQLDataType.swift new file mode 100644 index 0000000..00862de --- /dev/null +++ b/Sources/SQL/SQLDataType.swift @@ -0,0 +1,3 @@ +public protocol SQLDataType: SQLSerializable { + +} diff --git a/Sources/SQL/SQLDelete.swift b/Sources/SQL/SQLDelete.swift new file mode 100644 index 0000000..b87f411 --- /dev/null +++ b/Sources/SQL/SQLDelete.swift @@ -0,0 +1,42 @@ +public protocol SQLDelete: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Expression: SQLExpression + + static func delete(_ table: TableIdentifier) -> Self + + var from: TableIdentifier { get set } + + /// If the WHERE clause is not present, all records in the table are deleted. If a WHERE clause is supplied, + /// then only those rows for which the WHERE clause boolean expression is true are deleted. Rows for which + /// the expression is false or NULL are retained. + var `where`: Expression? { get set } +} + +// MARK: Generic + +public struct GenericSQLDelete: SQLDelete + where TableIdentifier: SQLTableIdentifier, Expression: SQLExpression +{ + /// See `SQLDelete`. + public static func delete(_ table: TableIdentifier) -> GenericSQLDelete { + return .init(from: table, where: nil) + } + + /// See `SQLDelete`. + public var from: TableIdentifier + + /// See `SQLDelete`. + public var `where`: Expression? + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("DELETE FROM") + sql.append(from.serialize(&binds)) + if let `where` = self.where { + sql.append("WHERE") + sql.append(`where`.serialize(&binds)) + } + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLDeleteBuilder.swift b/Sources/SQL/SQLDeleteBuilder.swift new file mode 100644 index 0000000..7fe1ea8 --- /dev/null +++ b/Sources/SQL/SQLDeleteBuilder.swift @@ -0,0 +1,43 @@ +public final class SQLDeleteBuilder: SQLQueryBuilder, SQLWhereBuilder + where Connection: SQLConnection +{ + /// `Delete` query being built. + public var delete: Connection.Query.Delete + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .delete(delete) + } + + /// See `SQLWhereBuilder`. + public var `where`: Connection.Query.Delete.Expression? { + get { return delete.where } + set { delete.where = newValue } + } + + /// Creates a new `SQLDeleteBuilder`. + public init(_ delete: Connection.Query.Delete, on connection: Connection) { + self.delete = delete + self.connection = connection + } +} + +// MARK: Connection + +extension SQLConnection { + /// Creates a new `SQLDeleteBuilder`. + /// + /// conn.delete(from: Planet.self)... + /// + /// - parameters: + /// - table: Table to delete from. + /// - returns: Newly created `SQLDeleteBuilder`. + public func delete
(from table: Table.Type) -> SQLDeleteBuilder + where Table: SQLTable + { + return .init(.delete(.table(Table.self)), on: self) + } +} diff --git a/Sources/SQL/SQLDirection.swift b/Sources/SQL/SQLDirection.swift new file mode 100644 index 0000000..b15496b --- /dev/null +++ b/Sources/SQL/SQLDirection.swift @@ -0,0 +1,29 @@ +public protocol SQLDirection: SQLSerializable { + static var ascending: Self { get } + static var descending: Self { get } +} + +// MARK: Generic + +public enum GenericSQLDirection: SQLDirection { + /// See `SQLDirection`. + public static var ascending: GenericSQLDirection { + return ._ascending + } + + /// See `SQLDirection`. + public static var descending: GenericSQLDirection { + return ._descending + } + + case _ascending + case _descending + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._ascending: return "ASC" + case ._descending: return "DESC" + } + } +} diff --git a/Sources/SQL/SQLDistinct.swift b/Sources/SQL/SQLDistinct.swift new file mode 100644 index 0000000..f351536 --- /dev/null +++ b/Sources/SQL/SQLDistinct.swift @@ -0,0 +1,42 @@ +public protocol SQLDistinct: SQLSerializable { + static var all: Self { get } + static var distinct: Self { get } + var isDistinct: Bool { get } +} + +// MARK: Default + +extension SQLDistinct { + public func serialize(_ binds: inout [Encodable]) -> String { + if isDistinct { + return "DISTINCT" + } else { + return "ALL" + } + } +} + +// MARK: Generic + +public enum GenericSQLDistinct: SQLDistinct { + /// See `SQLDistinct`. + public static var all: GenericSQLDistinct { + return ._all + } + + /// See `SQLDistinct`. + public static var distinct: GenericSQLDistinct { + return ._distinct + } + + /// See `SQLDistinct`. + public var isDistinct: Bool { + switch self { + case ._all: return false + case ._distinct: return true + } + } + + case _distinct + case _all +} diff --git a/Sources/SQL/SQLDrop.swift b/Sources/SQL/SQLDrop.swift new file mode 100644 index 0000000..33e881f --- /dev/null +++ b/Sources/SQL/SQLDrop.swift @@ -0,0 +1,38 @@ +public protocol SQLDropTable: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + + /// Creates a new `SQLDropTable`. + static func dropTable(_ table: TableIdentifier, ifExists: Bool) -> Self + + /// Table to drop. + var table: TableIdentifier { get set } + + /// The optional IF EXISTS clause suppresses the error that would normally result if the table does not exist. + var ifExists: Bool { get set } +} + +public struct GenericSQLDropTable: SQLDropTable + where TableIdentifier: SQLTableIdentifier +{ + /// See `SQLDropTable`. + public static func dropTable(_ table: TableIdentifier, ifExists: Bool) -> GenericSQLDropTable { + return .init(table: table, ifExists: ifExists) + } + + /// See `SQLDropTable`. + public var table: TableIdentifier + + /// See `SQLDropTable`. + public var ifExists: Bool + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("DROP TABLE") + if ifExists { + sql.append("IF EXISTS") + } + sql.append(table.serialize(&binds)) + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLDropTableBuilder.swift b/Sources/SQL/SQLDropTableBuilder.swift new file mode 100644 index 0000000..350ed2d --- /dev/null +++ b/Sources/SQL/SQLDropTableBuilder.swift @@ -0,0 +1,35 @@ +public final class SQLDropTableBuilder: SQLQueryBuilder + where Connection: SQLConnection +{ + /// `DropTable` query being built. + public var dropTable: Connection.Query.DropTable + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .dropTable(dropTable) + } + + /// Creates a new `SQLDropTableBuilder`. + public init(_ dropTable: Connection.Query.DropTable, on connection: Connection) { + self.dropTable = dropTable + self.connection = connection + } + + public func ifExists() -> Self { + dropTable.ifExists = true + return self + } +} + +// MARK: Connection + +extension SQLConnection { + public func drop
(table: Table.Type) -> SQLDropTableBuilder + where Table: SQLTable + { + return .init(.dropTable(.table(Table.self), ifExists: false), on: self) + } +} diff --git a/Sources/SQL/SQLExpression.swift b/Sources/SQL/SQLExpression.swift new file mode 100644 index 0000000..99c3e5f --- /dev/null +++ b/Sources/SQL/SQLExpression.swift @@ -0,0 +1,183 @@ +public protocol SQLExpression: SQLSerializable { + associatedtype Literal: SQLLiteral + associatedtype Bind: SQLBind + associatedtype ColumnIdentifier: SQLColumnIdentifier + associatedtype BinaryOperator: SQLBinaryOperator + associatedtype Function: SQLFunction + associatedtype Subquery: SQLSerializable + + /// Literal strings, integers, and constants. + static func literal(_ literal: Literal) -> Self + + /// Bound value. + static func bind(_ bind: Bind) -> Self + + /// Column name. + static func column(_ column: ColumnIdentifier) -> Self + + /// Binary expression. + static func binary(_ lhs: Self, _ op: BinaryOperator, _ rhs: Self) -> Self + + /// Function. + static func function(_ function: Function) -> Self + + /// Group of expressions. + static func group(_ expressions: [Self]) -> Self + + /// `(SELECT ...)` + static func subquery(_ subquery: Subquery) -> Self + + // FIXME: collate + // FIXME: cast + + var isNull: Bool { get } +// var literal: Literal? { get } +// var bind: Bind? { get } +// var column: ColumnIdentifier? { get } +// var binary: (Self, BinaryOperator, Self)? { get } +// var function: Function? { get } +// var group: [Self]? { get } +// var select: Select? { get } +} + +// MARK: Convenience + +public func && (_ lhs: E, _ rhs: E) -> E where E: SQLExpression { + return E.binary(lhs, .and, rhs) +} + +public func || (_ lhs: E, _ rhs: E) -> E where E: SQLExpression { + return E.binary(lhs, .or, rhs) +} + +public func &= (_ lhs: inout E?, _ rhs: E) where E: SQLExpression { + if let l = lhs { + lhs = l && rhs + } else { + lhs = rhs + } +} + +public func |= (_ lhs: inout E?, _ rhs: E) where E: SQLExpression { + if let l = lhs { + lhs = l || rhs + } else { + lhs = rhs + } +} + + +// MARK: Generic + +public indirect enum GenericSQLExpression: SQLExpression + where Literal: SQLLiteral, Bind: SQLBind, ColumnIdentifier: SQLColumnIdentifier, BinaryOperator: SQLBinaryOperator, Function: SQLFunction, Subquery: SQLSerializable +{ + public typealias `Self` = GenericSQLExpression + + public static func literal(_ literal: Literal) -> Self { + return ._literal(literal) + } + + public static func bind(_ bind: Bind) -> Self { + return ._bind(bind) + } + + public static func column(_ column: ColumnIdentifier) -> Self { + return ._column(column) + } + + public static func binary(_ lhs: Self, _ op: BinaryOperator, _ rhs: Self) -> Self { + return ._binary(lhs, op, rhs) + } + + public static func function(_ function: Function) -> Self { + return ._function(function) + } + + public static func group(_ expressions: [Self]) -> Self { + return ._group(expressions) + } + + public static func subquery(_ subquery: Subquery) -> Self { + return ._subquery(subquery) + } + +// public var literal: Literal? { +// switch self { +// case ._literal(let literal): return literal +// default: return nil +// } +// } +// +// public var bind: Bind? { +// switch self { +// case ._bind(let bind): return bind +// default: return nil +// } +// } +// +// public var column: ColumnIdentifier? { +// switch self { +// case ._column(let column): return column +// default: return nil +// } +// } +// +// public var binary: (Self, BinaryOperator, Self)? { +// switch self { +// case ._binary(let lhs, let op, let rhs): return (lhs, op, rhs) +// default: return nil +// } +// } +// +// public var function: Function? { +// switch self { +// case ._function(let function): return function +// default: return nil +// } +// } +// +// public var group: [Self]? { +// switch self { +// case ._group(let group): return group +// default: return nil +// } +// } +// +// public var select: Select? { +// switch self { +// case ._select(let select): return select +// default: return nil +// } +// } + + case _literal(Literal) + case _bind(Bind) + case _column(ColumnIdentifier) + case _binary(`Self`, BinaryOperator, `Self`) + case _function(Function) + case _group([`Self`]) + case _subquery(Subquery) + + public var isNull: Bool { + switch self { + case ._literal(let literal): return literal.isNull + default: return false + } + } + + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._literal(let literal): return literal.serialize(&binds) + case ._bind(let bind): return bind.serialize(&binds) + case ._column(let column): return column.serialize(&binds) + case ._binary(let lhs, let op, let rhs): + return lhs.serialize(&binds) + " " + op.serialize(&binds) + " " + rhs.serialize(&binds) + case ._function(let function): return function.serialize(&binds) + case ._group(let group): + return "(" + group.map { $0.serialize(&binds) }.joined(separator: ", ") + ")" + case ._subquery(let subquery): + return "(" + subquery.serialize(&binds) + ")" + } + } +} diff --git a/Sources/SQL/SQLForeignKey.swift b/Sources/SQL/SQLForeignKey.swift new file mode 100644 index 0000000..0af6efe --- /dev/null +++ b/Sources/SQL/SQLForeignKey.swift @@ -0,0 +1,54 @@ +public protocol SQLForeignKey: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Identifier: SQLIdentifier + associatedtype ConflictResolution: SQLConflictResolution + + static func foreignKey( + _ foreignTable: TableIdentifier, + _ foreignColumns: [Identifier], + onDelete: ConflictResolution?, + onUpdate: ConflictResolution? + ) -> Self +} + +// MARK: Generic + +public struct GenericSQLForeignKey: SQLForeignKey + where TableIdentifier: SQLTableIdentifier, Identifier: SQLIdentifier, ConflictResolution: SQLConflictResolution +{ + public typealias `Self` = GenericSQLForeignKey + + /// See `SQLForeignKey`. + public static func foreignKey( + _ foreignTable: TableIdentifier, + _ foreignColumns: [Identifier], + onDelete: ConflictResolution?, + onUpdate: ConflictResolution? + ) -> Self { + return .init(foreignTable: foreignTable, foreignColumns: foreignColumns, onDelete: onDelete, onUpdate: onUpdate) + } + + public var foreignTable: TableIdentifier + + public var foreignColumns: [Identifier] + + public var onDelete: ConflictResolution? + + public var onUpdate: ConflictResolution? + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append(foreignTable.serialize(&binds)) + sql.append("(" + foreignColumns.serialize(&binds) + ")") + if let onDelete = onDelete { + sql.append("ON DELETE") + sql.append(onDelete.serialize(&binds)) + } + if let onUpdate = onUpdate { + sql.append("ON UPDATE") + sql.append(onUpdate.serialize(&binds)) + } + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLFunction.swift b/Sources/SQL/SQLFunction.swift new file mode 100644 index 0000000..926d0e0 --- /dev/null +++ b/Sources/SQL/SQLFunction.swift @@ -0,0 +1,21 @@ +public protocol SQLFunction: SQLSerializable { + associatedtype Argument: SQLFunctionArgument + static func function(_ name: String, _ args: [Argument]) -> Self +} + +// MARK: Generic + +public struct GenericSQLFunction: SQLFunction where Argument: SQLFunctionArgument { + /// See `SQLFunction`. + public static func function(_ name: String, _ args: [Argument]) -> GenericSQLFunction { + return .init(name: name, arguments: args) + } + + public var name: String + public var arguments: [Argument] + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + return name + "(" + arguments.map { $0.serialize(&binds) }.joined(separator: ", ") + ")" + } +} diff --git a/Sources/SQL/SQLFunctionArgument.swift b/Sources/SQL/SQLFunctionArgument.swift new file mode 100644 index 0000000..e018ed0 --- /dev/null +++ b/Sources/SQL/SQLFunctionArgument.swift @@ -0,0 +1,30 @@ +public protocol SQLFunctionArgument: SQLSerializable { + associatedtype Expression: SQLExpression + static var all: Self { get } + static func expression(_ expression: Expression) -> Self +} + +// MARK: Generic + +public enum GenericSQLFunctionArgument: SQLFunctionArgument where Expression: SQLExpression { + /// See `SQLFunctionArgument`. + public static var all: GenericSQLFunctionArgument { + return ._all + } + + /// See `SQLFunctionArgument`. + public static func expression(_ expression: Expression) -> GenericSQLFunctionArgument { + return ._expression(expression) + } + + case _all + case _expression(Expression) + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._all: return "*" + case ._expression(let expr): return expr.serialize(&binds) + } + } +} diff --git a/Sources/SQL/SQLGroupBy.swift b/Sources/SQL/SQLGroupBy.swift new file mode 100644 index 0000000..b41969f --- /dev/null +++ b/Sources/SQL/SQLGroupBy.swift @@ -0,0 +1,18 @@ +public protocol SQLGroupBy: SQLSerializable { + associatedtype Expression: SQLExpression + static func groupBy(_ expression: Expression) -> Self +} + +// MARK: Generic + +public struct GenericSQLGroupBy: SQLGroupBy where Expression: SQLExpression { + public static func groupBy(_ expression: Expression) -> GenericSQLGroupBy { + return .init(expression: expression) + } + + public var expression: Expression + + public func serialize(_ binds: inout [Encodable]) -> String { + return expression.serialize(&binds) + } +} diff --git a/Sources/SQL/SQLIdentifier.swift b/Sources/SQL/SQLIdentifier.swift new file mode 100644 index 0000000..1ac47c2 --- /dev/null +++ b/Sources/SQL/SQLIdentifier.swift @@ -0,0 +1,17 @@ +public protocol SQLIdentifier: SQLSerializable { + static func identifier(_ string: String) -> Self +} + +// MARK: Generic + +public struct GenericSQLIdentifier: SQLIdentifier { + public static func identifier(_ string: String) -> GenericSQLIdentifier { + return self.init(string: string) + } + + public let string: String + + public func serialize(_ binds: inout [Encodable]) -> String { + return "\"" + string + "\"" + } +} diff --git a/Sources/SQL/SQLInsert.swift b/Sources/SQL/SQLInsert.swift new file mode 100644 index 0000000..75ef01e --- /dev/null +++ b/Sources/SQL/SQLInsert.swift @@ -0,0 +1,37 @@ +public protocol SQLInsert: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype ColumnIdentifier: SQLColumnIdentifier + associatedtype Expression: SQLExpression + + static func insert(_ table: TableIdentifier) -> Self + var columns: [ColumnIdentifier] { get set } + var values: [[Expression]] { get set } +} + +// MARK: Generic + +public struct GenericSQLInsert: SQLInsert + where TableIdentifier: SQLTableIdentifier, ColumnIdentifier: SQLColumnIdentifier, Expression: SQLExpression +{ + public typealias `Self` = GenericSQLInsert + + /// See `SQLInsert`. + public static func insert(_ table: TableIdentifier) -> Self { + return .init(table: table, columns: [], values: []) + } + + public var table: TableIdentifier + public var columns: [ColumnIdentifier] + public var values: [[Expression]] + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("INSERT INTO") + sql.append(table.serialize(&binds)) + sql.append("(" + columns.serialize(&binds) + ")") + sql.append("VALUES") + sql.append(values.map { "(" + $0.serialize(&binds) + ")"}.joined(separator: ", ")) + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLInsertBuilder.swift b/Sources/SQL/SQLInsertBuilder.swift new file mode 100644 index 0000000..a96cf1a --- /dev/null +++ b/Sources/SQL/SQLInsertBuilder.swift @@ -0,0 +1,67 @@ +public final class SQLInsertBuilder: SQLQueryBuilder + where Connection: SQLConnection +{ + /// `Insert` query being built. + public var insert: Connection.Query.Insert + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .insert(insert) + } + + /// Creates a new `SQLInsertBuilder`. + public init(_ insert: Connection.Query.Insert, on connection: Connection) { + self.insert = insert + self.connection = connection + } + + public func value(_ value: E) throws -> Self + where E: Encodable + { + return values([value]) + } + + public func values(_ values: [E]) -> Self + where E: Encodable + { + values.forEach { model in + let row = SQLQueryEncoder(Connection.Query.Insert.Expression.self).encode(model) + if insert.columns.isEmpty { + insert.columns += row.map { .column(nil, .identifier($0.key)) } + } else { + assert( + insert.columns.count == row.count, + "Column count (\(insert.columns.count)) did not equal value count (\(row.count)): \(model)." + ) + } + insert.values.append(row.map { row in + if row.value.isNull { + return .literal(.null) // FIXME: use `DEFAULT` value + } else { + return row.value + } + }) + } + return self + } +} + +// MARK: Connection + +extension SQLConnection { + /// Creates a new `SQLInsertBuilder`. + /// + /// conn.insert(into: Planet.self)... + /// + /// - parameters: + /// - table: Table to insert into. + /// - returns: Newly created `SQLInsertBuilder`. + public func insert
(into table: Table.Type) -> SQLInsertBuilder + where Table: SQLTable + { + return .init(.insert(.table(Table.self)), on: self) + } +} diff --git a/Sources/SQL/SQLLiteral.swift b/Sources/SQL/SQLLiteral.swift new file mode 100644 index 0000000..7dda80a --- /dev/null +++ b/Sources/SQL/SQLLiteral.swift @@ -0,0 +1,63 @@ +public protocol SQLLiteral: SQLSerializable { + static func string(_ string: String) -> Self + static func numeric(_ string: String) -> Self + static var null: Self { get } + static var `default`: Self { get } + static func boolean(_ bool: Bool) -> Self + + var isNull: Bool { get } +} + +// MARK: Generic + +public enum GenericSQLLiteral: SQLLiteral { + /// See `SQLLiteral`. + public static func string(_ string: String) -> GenericSQLLiteral { + return ._string(string) + } + + /// See `SQLLiteral`. + public static func numeric(_ string: String) -> GenericSQLLiteral { + return ._numeric(string) + } + + /// See `SQLLiteral`. + public static var null: GenericSQLLiteral { + return ._null + } + + /// See `SQLLiteral`. + public static var `default`: GenericSQLLiteral { + return ._default + } + + /// See `SQLLiteral`. + public static func boolean(_ bool: Bool) -> GenericSQLLiteral { + return ._boolean(bool) + } + + case _string(String) + case _numeric(String) + case _null + case _default + case _boolean(Bool) + + /// See `SQLLiteral`. + public var isNull: Bool { + switch self { + case ._null: return true + default: return false + } + } + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._boolean(let bool): return bool.description.uppercased() + case ._null: return "NULL" + case ._default: return "DEFAULT" + case ._numeric(let string): return string + case ._string(let string): return "'" + string + "'" + } + } +} diff --git a/Sources/SQL/SQLOrderBy.swift b/Sources/SQL/SQLOrderBy.swift new file mode 100644 index 0000000..55ce93c --- /dev/null +++ b/Sources/SQL/SQLOrderBy.swift @@ -0,0 +1,20 @@ +public protocol SQLOrderBy: SQLSerializable { + associatedtype Expression: SQLExpression + associatedtype Direction: SQLDirection + static func orderBy(_ expression: Expression, _ direction: Direction) -> Self +} + +// MARK: Generic + +public struct GenericSQLOrderBy: SQLOrderBy where Expression: SQLExpression, Direction: SQLDirection { + public static func orderBy(_ expression: Expression, _ direction: Direction) -> GenericSQLOrderBy { + return .init(expression: expression, direction: direction) + } + + public var expression: Expression + public var direction: Direction + + public func serialize(_ binds: inout [Encodable]) -> String { + return expression.serialize(&binds) + " " + direction.serialize(&binds) + } +} diff --git a/Sources/SQL/SQLPrimaryKey.swift b/Sources/SQL/SQLPrimaryKey.swift new file mode 100644 index 0000000..d879cc4 --- /dev/null +++ b/Sources/SQL/SQLPrimaryKey.swift @@ -0,0 +1,17 @@ +public protocol SQLPrimaryKey: SQLSerializable { + static func primaryKey() -> Self +} + +// MARK: Generic + +public struct GenericSQLPrimaryKey: SQLPrimaryKey { + /// See `SQLPrimaryKey`. + public static func primaryKey() -> GenericSQLPrimaryKey { + return .init() + } + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + return "" + } +} diff --git a/Sources/SQL/SQLQuery.swift b/Sources/SQL/SQLQuery.swift new file mode 100644 index 0000000..caf6539 --- /dev/null +++ b/Sources/SQL/SQLQuery.swift @@ -0,0 +1,33 @@ +public protocol SQLQuery: SQLSerializable { + associatedtype CreateTable: SQLCreateTable + associatedtype Delete: SQLDelete + associatedtype DropTable: SQLDropTable + associatedtype Insert: SQLInsert + associatedtype Select: SQLSelect + associatedtype Update: SQLUpdate + + associatedtype RowDecoder: SQLRowDecoder + + static func createTable(_ createTable: CreateTable) -> Self + static func delete(_ delete: Delete) -> Self + static func dropTable(_ dropTable: DropTable) -> Self + static func insert(_ insert: Insert) -> Self + static func select(_ select: Select) -> Self + static func update(_ update: Update) -> Self +} + + +/// A `CREATE TABLE ... AS SELECT` statement creates and populates a database table based on the results of a SELECT statement. +/// The table has the same number of columns as the rows returned by the SELECT statement. The name of each column is the same +/// as the name of the corresponding column in the result set of the SELECT statement. +/// +/// conn.create(table: GalaxyCopy.self).as { $0.select().all().from(Galaxy.self) }.run() +/// +/// - parameters: +/// - closure: Closure accepting a `SQLiteConnection` and returning a `SelectBuilder`. +/// - returns: Self for chaining. +//public func `as`(_ closure: (SQLiteConnection) -> SelectBuilder) -> Self { +// create.schemaSource = .select(closure(connection).select) +// return self +//} +// diff --git a/Sources/SQL/SQLQueryBuilder.swift b/Sources/SQL/SQLQueryBuilder.swift new file mode 100644 index 0000000..3af1c34 --- /dev/null +++ b/Sources/SQL/SQLQueryBuilder.swift @@ -0,0 +1,60 @@ +import Async + +public protocol SQLQueryBuilder: class { + associatedtype Connection: SQLConnection + var query: Connection.Query { get } + var connection: Connection { get } +} + +extension SQLQueryBuilder { + public func run() -> Future { + return connection.query(query) { _ in } + } +} + +public protocol SQLQueryFetcher: SQLQueryBuilder { } + +extension SQLQueryFetcher { + // MARK: Decode + + public func all(decoding type: D.Type) -> Future<[D]> + where D: Decodable + { + var all: [D] = [] + return run(decoding: D.self) { all.append($0) }.map { all } + } + + public func run( + decoding type: D.Type, + into handler: @escaping (D) throws -> () + ) -> Future + where D: Decodable + { + return run { row in + let d = try Connection.Query.RowDecoder.init().decode(D.self, from: row, table: nil) + try handler(d) + } + } + + // MARK: All + + public func all() -> Future<[Connection.Output]> { + var all: [Connection.Output] = [] + return connection.query(query) { all.append($0) }.map { all } + } + + public func run(_ handler: @escaping (Connection.Output) throws -> ()) -> Future { + return connection.query(query, handler) + } +} + + +//extension Dictionary where Key == SQLiteColumn, Value == SQLiteData { +// public func decode
(_ type: Table.Type) throws -> Table where Table: SQLiteTable { +// return try decode(Table.self, from: Table.sqlTableIdentifier.name.string) +// } +// +// public func decode(_ type: D.Type, from table: SQLiteQuery.Expression.ColumnIdentifier.TableIdentifier) throws -> D where D: Decodable { +// return try SQLiteRowDecoder().decode(D.self, from: self, table: table) +// } +//} diff --git a/Sources/SQLite/Codable/SQLiteQueryEncoder.swift b/Sources/SQL/SQLQueryEncoder.swift similarity index 90% rename from Sources/SQLite/Codable/SQLiteQueryEncoder.swift rename to Sources/SQL/SQLQueryEncoder.swift index 3576112..068e94d 100644 --- a/Sources/SQLite/Codable/SQLiteQueryEncoder.swift +++ b/Sources/SQL/SQLQueryEncoder.swift @@ -12,9 +12,9 @@ /// or `SQLiteDataConvertible` will be specially encoded. /// /// This encoder does _not_ support unkeyed or single value codable objects. -public struct SQLiteQueryEncoder { - /// Creates a new `SQLiteQueryEncoder`. - public init() { } +internal struct SQLQueryEncoder where Expression: SQLExpression { + /// Creates a new `SQLQueryEncoder`. + internal init(_ type: Expression.Type) { } /// Encodes keyed `Encodable` values to a `SQLiteQuery` expression dictionary. /// @@ -29,11 +29,16 @@ public struct SQLiteQueryEncoder { /// - parameters: /// - value: `Encodable` value to encode. /// - returns: `SQLiteQuery` compatible data. - public func encode(_ value: E) throws -> [String: SQLiteQuery.Expression] + internal func encode(_ value: E) -> [String: Expression] where E: Encodable { let encoder = _Encoder() - try value.encode(to: encoder) + do { + try value.encode(to: encoder) + } catch { + assertionFailure("Failed to encode \(value) to SQL query expression.") + return [:] + } return encoder.row } @@ -42,7 +47,7 @@ public struct SQLiteQueryEncoder { private final class _Encoder: Encoder { let codingPath: [CodingKey] = [] var userInfo: [CodingUserInfoKey: Any] = [:] - var row: [String: SQLiteQuery.Expression] + var row: [String: Expression] init() { self.row = [:] @@ -73,7 +78,7 @@ public struct SQLiteQueryEncoder { } mutating func encode(_ encodable: T, forKey key: Key) throws where T : Encodable { - encoder.row[key.stringValue] = try SQLiteQueryExpressionEncoder().encode(encodable) + encoder.row[key.stringValue] = .bind(.encodable(encodable)) } mutating func _encodeIfPresent(_ value: T?, forKey key: Key) throws where T : Encodable { diff --git a/Sources/SQL/SQLRowDecoder.swift b/Sources/SQL/SQLRowDecoder.swift new file mode 100644 index 0000000..f1ae60e --- /dev/null +++ b/Sources/SQL/SQLRowDecoder.swift @@ -0,0 +1,8 @@ +public protocol SQLRowDecoder { + associatedtype Row + associatedtype TableIdentifier: SQLTableIdentifier + + init() + func decode(_ type: D.Type, from row: Row, table: TableIdentifier?) throws -> D + where D: Decodable +} diff --git a/Sources/SQL/SQLSelect.swift b/Sources/SQL/SQLSelect.swift new file mode 100644 index 0000000..ac2161b --- /dev/null +++ b/Sources/SQL/SQLSelect.swift @@ -0,0 +1,65 @@ +public protocol SQLSelect: SQLSerializable { + associatedtype Distinct: SQLDistinct + associatedtype SelectExpression: SQLSelectExpression + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Expression: SQLExpression + associatedtype GroupBy: SQLGroupBy + associatedtype OrderBy: SQLOrderBy + + static func select() -> Self + + var distinct: Distinct? { get set } + var columns: [SelectExpression] { get set } + var from: [TableIdentifier] { get set } + var `where`: Expression? { get set } + var groupBy: [GroupBy] { get set } + var orderBy: [OrderBy] { get set } +} + +// MARK: Generic + +public struct GenericSQLSelect: SQLSelect + where Distinct: SQLDistinct, SelectExpression: SQLSelectExpression, TableIdentifier: SQLTableIdentifier, Expression: SQLExpression, GroupBy: SQLGroupBy, OrderBy: SQLOrderBy +{ + public typealias `Self` = GenericSQLSelect + + public var distinct: Distinct? + public var columns: [SelectExpression] + public var from: [TableIdentifier] + public var `where`: Expression? + public var groupBy: [GroupBy] + public var orderBy: [OrderBy] + + /// See `SQLSelect`. + public static func select() -> Self { + return .init(distinct: nil, columns: [], from: [], where: nil, groupBy: [], orderBy: []) + } + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("SELECT") + if let distinct = self.distinct { + sql.append(distinct.serialize(&binds)) + } + sql.append(columns.serialize(&binds)) + if !from.isEmpty { + sql.append("FROM") + sql.append(from.serialize(&binds)) + } + if let `where` = self.where { + sql.append("WHERE") + sql.append(`where`.serialize(&binds)) + } + if !groupBy.isEmpty { + sql.append("GROUP BY") + sql.append(groupBy.serialize(&binds)) + } + if !orderBy.isEmpty { + sql.append("ORDER BY") + sql.append(orderBy.serialize(&binds)) + } + return sql.joined(separator: " ") + + } +} diff --git a/Sources/SQL/SQLSelectBuilder.swift b/Sources/SQL/SQLSelectBuilder.swift new file mode 100644 index 0000000..a63bba1 --- /dev/null +++ b/Sources/SQL/SQLSelectBuilder.swift @@ -0,0 +1,115 @@ +public final class SQLSelectBuilder: SQLQueryFetcher, SQLWhereBuilder + where Connection: SQLConnection +{ + /// `Select` query being built. + public var select: Connection.Query.Select + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .select(select) + } + + /// See `SQLWhereBuilder`. + public var `where`: Connection.Query.Select.Expression? { + get { return select.where } + set { select.where = newValue } + } + + /// Creates a new `SQLCreateTableBuilder`. + public init(_ select: Connection.Query.Select, on connection: Connection) { + self.select = select + self.connection = connection + } + + public func column(function: String, as alias: String? = nil) -> Self { + return column(function: function, as: alias) + } + + public func column(function: String, _ arguments: Connection.Query.Select.SelectExpression.Expression.Function.Argument..., as alias: String? = nil) -> Self { + return column(expression: .function(.function(function, arguments)), as: alias) + } + + public func column(expression: Connection.Query.Select.SelectExpression.Expression, as alias: String? = nil) -> Self { + return column(.expression(expression, alias: alias)) + } + + public func all() -> Self { + return column(.all) + } + + public func all(table: String) -> Self { + return column(.allTable(table)) + } + + public func column(_ column: Connection.Query.Select.SelectExpression) -> Self { + select.columns.append(column) + return self + } + + public func from(_ tables: Connection.Query.Select.TableIdentifier...) -> Self { + select.from += tables + return self + } + + public func from
(_ table: Table.Type) -> Self + where Table: SQLTable + { + select.from.append(.table(.identifier(Table.sqlTableIdentifierString))) + return self + } + + public func groupBy(_ keyPath: KeyPath) -> Self + where T: SQLTable + { + return groupBy(.column(.keyPath(keyPath))) + } + + public func groupBy(_ expression: Connection.Query.Select.GroupBy.Expression) -> Self { + select.groupBy.append(.groupBy(expression)) + return self + } + + public func orderBy(_ keyPath: KeyPath, _ direction: Connection.Query.Select.OrderBy.Direction = .ascending) -> Self + where T: SQLTable + { + return orderBy(.column(.keyPath(keyPath)), direction) + } + + public func orderBy(_ expression: Connection.Query.Select.OrderBy.Expression, _ direction: Connection.Query.Select.OrderBy.Direction = .ascending) -> Self { + select.orderBy.append(.orderBy(expression, direction)) + return self + } + + // public func join
(_ table: Table.Type, on expr: Expression) -> Self + // where Table: SQLiteTable + // { + // switch select.tables.count { + // case 0: fatalError("Must select from a atable before joining.") + // default: + // let join = SQLiteQuery.JoinClause.init( + // table: select.tables[0], + // joins: [ + // SQLiteQuery.JoinClause.Join( + // natural: false, + // .inner, + // table: .table(.init(table:.init(table: Table.sqliteTableName))), + // constraint: .condition(expr) + // ) + // ] + // ) + // select.tables[0] = .joinClause(join) + // } + // return self + // } +} + +// MARK: Connection + +extension SQLConnection { + public func select() -> SQLSelectBuilder { + return .init(.select(), on: self) + } +} diff --git a/Sources/SQL/SQLSelectExpression.swift b/Sources/SQL/SQLSelectExpression.swift new file mode 100644 index 0000000..de6712e --- /dev/null +++ b/Sources/SQL/SQLSelectExpression.swift @@ -0,0 +1,89 @@ +public protocol SQLSelectExpression: SQLSerializable { + associatedtype Expression: SQLExpression + + static var all: Self { get } + static func allTable(_ table: String) -> Self + static func expression(_ expression: Expression, alias: String?) -> Self + + var isAll: Bool { get } + var allTable: String? { get } + var expression: (expression: Expression, alias: String?)? { get } +} + +// MARK: Default + +extension SQLSelectExpression { + public func serialize(_ binds: inout [Encodable]) -> String { + switch (isAll, allTable, expression) { + case (true, .none, .none): return "*" + case (false, .some(let table), .none): return table + ".*" + case (false, .none, .some(let e)): + switch e.alias { + case .none: return e.expression.serialize(&binds) + case .some(let alias): return e.expression.serialize(&binds) + " AS " + alias + } + default: fatalError("Unsupported SQLSelectExpression.") + } + } +} + + +// MARK: Convenience + +extension SQLSelectExpression { + public static func function(_ function: Expression, as alias: String? = nil) -> Self { + return .expression(function, alias: alias) + } +} + +// MARK: Generic + +public enum GenericSQLSelectExpression: SQLSelectExpression where Expression: SQLExpression { + /// See `SQLSelectExpression`. + public static var all: GenericSQLSelectExpression { + return ._all + } + + /// See `SQLSelectExpression`. + public static func allTable(_ table: String) -> GenericSQLSelectExpression { + return ._allTable(table) + } + + /// See `SQLSelectExpression`. + public static func expression(_ expression: Expression, alias: String?) -> GenericSQLSelectExpression { + return ._expression(expression, alias: alias) + } + + /// See `SQLSelectExpression`. + public var isAll: Bool { + switch self { + case ._all: return true + default: return false + } + } + + /// See `SQLSelectExpression`. + public var allTable: String? { + switch self { + case ._allTable(let table): return table + default: return nil + } + } + + /// See `SQLSelectExpression`. + public var expression: (expression: Expression, alias: String?)? { + switch self { + case ._expression(let expr, let alias): return (expr, alias) + default: return nil + } + } + + /// `*` + case _all + + /// `table.*` + case _allTable(String) + + /// `md5(a) AS hash` + case _expression(Expression, alias: String?) +} diff --git a/Sources/SQL/SQLSerializable.swift b/Sources/SQL/SQLSerializable.swift new file mode 100644 index 0000000..9bedd78 --- /dev/null +++ b/Sources/SQL/SQLSerializable.swift @@ -0,0 +1,9 @@ +public protocol SQLSerializable { + func serialize(_ binds: inout [Encodable]) -> String +} + +extension Array where Element: SQLSerializable { + public func serialize(_ binds: inout [Encodable], joinedBy separator: String = ", ") -> String { + return map { $0.serialize(&binds) }.joined(separator: separator) + } +} diff --git a/Sources/SQL/SQLTable.swift b/Sources/SQL/SQLTable.swift new file mode 100644 index 0000000..a7e57f5 --- /dev/null +++ b/Sources/SQL/SQLTable.swift @@ -0,0 +1,11 @@ +import Core + +public protocol SQLTable: Codable, Reflectable { + static var sqlTableIdentifierString: String { get } +} + +extension SQLTable { + public static var sqlTableIdentifierString: String { + return "\(Self.self)" + } +} diff --git a/Sources/SQL/SQLTableConstraint.swift b/Sources/SQL/SQLTableConstraint.swift new file mode 100644 index 0000000..75715e0 --- /dev/null +++ b/Sources/SQL/SQLTableConstraint.swift @@ -0,0 +1,59 @@ +public protocol SQLTableConstraint: SQLSerializable { + associatedtype Identifier: SQLIdentifier + associatedtype TableConstraintAlgorithm: SQLTableConstraintAlgorithm + static func constraint(_ algorithm: TableConstraintAlgorithm, _ identifier: Identifier?) -> Self +} + +// MARK: Convenience + +extension SQLTableConstraint { + public static func primaryKey( + _ columns: TableConstraintAlgorithm.ColumnIdentifier..., + identifier: Identifier? = nil + ) -> Self { + return .constraint(.primaryKey(columns, .primaryKey()), identifier) + } + public static func unique( + _ columns: TableConstraintAlgorithm.ColumnIdentifier..., + identifier: Identifier? = nil + ) -> Self { + return .constraint(.unique(columns), identifier) + } + + public static func foreignKey( + _ columns: [TableConstraintAlgorithm.ColumnIdentifier], + references foreignTable: TableConstraintAlgorithm.ForeignKey.TableIdentifier, + _ foreignColumns: [TableConstraintAlgorithm.ForeignKey.Identifier], + onDelete: TableConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + onUpdate: TableConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + identifier: Identifier? = nil + ) -> Self { + return .constraint(.foreignKey(columns, .foreignKey(foreignTable, foreignColumns, onDelete: onDelete, onUpdate: onUpdate)), identifier) + } +} + +// MARK: Generic + +public struct GenericSQLTableConstraint: SQLTableConstraint + where Identifier: SQLIdentifier, TableConstraintAlgorithm: SQLTableConstraintAlgorithm +{ + public typealias `Self` = GenericSQLTableConstraint + + /// See `SQLColumnConstraint`. + public static func constraint(_ algorithm: TableConstraintAlgorithm, _ identifier: Identifier?) -> Self { + return .init(identifier: identifier, algorithm: algorithm) + } + + public var identifier: Identifier? + + public var algorithm: TableConstraintAlgorithm + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + if let identifier = self.identifier { + return "CONSTRAINT " + identifier.serialize(&binds) + " " + algorithm.serialize(&binds) + } else { + return algorithm.serialize(&binds) + } + } +} diff --git a/Sources/SQL/SQLTableConstraintAlgorithm.swift b/Sources/SQL/SQLTableConstraintAlgorithm.swift new file mode 100644 index 0000000..67395df --- /dev/null +++ b/Sources/SQL/SQLTableConstraintAlgorithm.swift @@ -0,0 +1,78 @@ +public protocol SQLTableConstraintAlgorithm: SQLSerializable { + associatedtype ColumnIdentifier: SQLColumnIdentifier + associatedtype Expression: SQLExpression + associatedtype Collation: SQLCollation + associatedtype PrimaryKey: SQLPrimaryKey + associatedtype ForeignKey: SQLForeignKey + static func primaryKey(_ columns: [ColumnIdentifier],_ primaryKey: PrimaryKey) -> Self + static var notNull: Self { get } + static func unique(_ columns: [ColumnIdentifier]) -> Self + static func check(_ expression: Expression) -> Self + static func foreignKey(_ columns: [ColumnIdentifier], _ foreignKey: ForeignKey) -> Self +} + +// MARK: Generic + +public enum GenericSQLTableConstraintAlgorithm: SQLTableConstraintAlgorithm + where ColumnIdentifier: SQLColumnIdentifier, Expression: SQLExpression, Collation: SQLCollation, PrimaryKey: SQLPrimaryKey, ForeignKey: SQLForeignKey +{ + public typealias `Self` = GenericSQLTableConstraintAlgorithm + + /// See `SQLColumnConstraintAlgorithm`. + public static func primaryKey(_ columns: [ColumnIdentifier], _ primaryKey: PrimaryKey) -> Self { + return ._primaryKey(columns, primaryKey) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static var notNull: Self { + return ._notNull + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func unique(_ columns: [ColumnIdentifier]) -> Self { + return ._unique(columns) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func check(_ expression: Expression) -> Self { + return ._check(expression) + } + + /// See `SQLColumnConstraintAlgorithm`. + public static func foreignKey(_ columns: [ColumnIdentifier], _ foreignKey: ForeignKey) -> Self { + return ._foreignKey(columns, foreignKey) + } + + case _primaryKey([ColumnIdentifier], PrimaryKey) + case _notNull + case _unique([ColumnIdentifier]) + case _check(Expression) + case _foreignKey([ColumnIdentifier], ForeignKey) + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._primaryKey(let columns, let primaryKey): + var sql: [String] = [] + sql.append("PRIMARY KEY") + sql.append("(" + columns.serialize(&binds) + ")") + sql.append(primaryKey.serialize(&binds)) + return sql.joined(separator: " ") + case ._notNull: return "NOT NULL" + case ._unique(let columns): + var sql: [String] = [] + sql.append("UNIQUE") + sql.append("(" + columns.serialize(&binds) + ")") + return sql.joined(separator: " ") + case ._check(let expression): + return "CHECK (" + expression.serialize(&binds) + ")" + case ._foreignKey(let columns, let foreignKey): + var sql: [String] = [] + sql.append("FOREIGN KEY") + sql.append("(" + columns.serialize(&binds) + ")") + sql.append("REFERENCES") + sql.append(foreignKey.serialize(&binds)) + return sql.joined(separator: " ") + } + } +} diff --git a/Sources/SQL/SQLTableIdentifier.swift b/Sources/SQL/SQLTableIdentifier.swift new file mode 100644 index 0000000..251e459 --- /dev/null +++ b/Sources/SQL/SQLTableIdentifier.swift @@ -0,0 +1,35 @@ +public protocol SQLTableIdentifier: SQLSerializable { + associatedtype Identifier: SQLIdentifier + + static func table(_ identifier: Identifier) -> Self + var identifier: Identifier { get set } +} + +// MARK: Convenience + +extension SQLTableIdentifier { + static func table
(_ table: Table.Type) -> Self + where Table: SQLTable + { + return .table(.identifier(Table.sqlTableIdentifierString)) + } +} + +// MARK: Generic + +public struct GenericSQLTableIdentifier: SQLTableIdentifier + where Identifier: SQLIdentifier +{ + /// See `SQLTableIdentifier`. + public static func table(_ identifier: Identifier) -> GenericSQLTableIdentifier { + return .init(identifier: identifier) + } + + /// See `SQLTableIdentifier`. + public var identifier: Identifier + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + return identifier.serialize(&binds) + } +} diff --git a/Sources/SQL/SQLUpdate.swift b/Sources/SQL/SQLUpdate.swift new file mode 100644 index 0000000..3436d9c --- /dev/null +++ b/Sources/SQL/SQLUpdate.swift @@ -0,0 +1,41 @@ +public protocol SQLUpdate: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Identifier: SQLIdentifier + associatedtype Expression: SQLExpression + + static func update(_ table: TableIdentifier) -> Self + + var table: TableIdentifier { get set } + var values: [(Identifier, Expression)] { get set } + var `where`: Expression? { get set } +} + +// MARK: Generic + +public struct GenericSQLUpdate: SQLUpdate + where TableIdentifier: SQLTableIdentifier, Identifier: SQLIdentifier, Expression: SQLExpression +{ + public typealias `Self` = GenericSQLUpdate + + public static func update(_ table: TableIdentifier) -> Self { + return .init(table: table, values: [], where: nil) + } + + public var table: TableIdentifier + public var values: [(Identifier, Expression)] + public var `where`: Expression? + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("UPDATE") + sql.append(table.serialize(&binds)) + sql.append("SET") + sql.append(values.map { $0.0.serialize(&binds) + " = " + $0.1.serialize(&binds) }.joined(separator: ", ")) + if let `where` = self.where { + sql.append("WHERE") + sql.append(`where`.serialize(&binds)) + } + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQL/SQLUpdateBuilder.swift b/Sources/SQL/SQLUpdateBuilder.swift new file mode 100644 index 0000000..996e346 --- /dev/null +++ b/Sources/SQL/SQLUpdateBuilder.swift @@ -0,0 +1,60 @@ +public final class SQLUpdateBuilder: SQLQueryBuilder, SQLWhereBuilder + where Connection: SQLConnection +{ + /// `Update` query being built. + public var update: Connection.Query.Update + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .update(update) + } + + /// See `SQLWhereBuilder`. + public var `where`: Connection.Query.Update.Expression? { + get { return update.where } + set { update.where = newValue } + } + + /// Creates a new `SQLDeleteBuilder`. + public init(_ update: Connection.Query.Update, on connection: Connection) { + self.update = update + self.connection = connection + } + + public func set(_ model: E)-> Self + where E: Encodable + { + let row = SQLQueryEncoder(Connection.Query.Update.Expression.self).encode(model) + update.values += row.map { row -> (Connection.Query.Update.Identifier, Connection.Query.Update.Expression) in + return (.identifier(row.key), row.value) + } + return self + } + + public func set(_ keyPath: KeyPath, to value: V) -> Self + where V: Encodable, T: SQLTable + { + update.values.append((.keyPath(keyPath), .bind(.encodable(value)))) + return self + } +} + +// MARK: Connection + +extension SQLConnection { + /// Creates a new `SQLUpdateBuilder`. + /// + /// conn.update(Planet.self)... + /// + /// - parameters: + /// - table: Table to update. + /// - returns: Newly created `SQLUpdateBuilder`. + public func update
(_ table: Table.Type) -> SQLUpdateBuilder + where Table: SQLTable + { + return .init(.update(.table(Table.self)), on: self) + } +} diff --git a/Sources/SQL/SQLWhereBuilder.swift b/Sources/SQL/SQLWhereBuilder.swift new file mode 100644 index 0000000..fef32a3 --- /dev/null +++ b/Sources/SQL/SQLWhereBuilder.swift @@ -0,0 +1,45 @@ +public protocol SQLWhereBuilder: class { + associatedtype Expression: SQLExpression + var `where`: Expression? { get set } +} + +extension SQLWhereBuilder { + public func `where`(_ expressions: Expression...) -> Self { + for expression in expressions { + self.where &= expression + } + return self + } + + public func orWhere(_ expressions: Expression...) -> Self { + for expression in expressions { + self.where |= expression + } + return self + } + + public func `where`(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { + self.where &= .binary(lhs, op, rhs) + return self + } + + public func orWhere(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { + self.where |= .binary(lhs, op, rhs) + return self + } + + public func `where`(group: (NestedSQLWhereBuilder) throws -> ()) rethrows -> Self { + let builder = NestedSQLWhereBuilder(Self.self) + try group(builder) + if let sub = builder.where { + self.where &= sub + } + return self + } +} + +public final class NestedSQLWhereBuilder: SQLWhereBuilder where WhereBuilder: SQLWhereBuilder { + public typealias Expression = WhereBuilder.Expression + public var `where`: WhereBuilder.Expression? + internal init(_ type: WhereBuilder.Type) { } +} diff --git a/Sources/SQL/SQLWith.swift b/Sources/SQL/SQLWith.swift new file mode 100644 index 0000000..72ee358 --- /dev/null +++ b/Sources/SQL/SQLWith.swift @@ -0,0 +1,10 @@ +//public struct WithClause { +// public struct CommonTableExpression { +// public var table: String +// public var columns: [Name] +// public var select: Select +// } +// +// public var recursive: Bool +// public var expressions: [CommonTableExpression] +//} diff --git a/Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift b/Sources/SQLite/Codable/SQLiteDataEncoder.swift similarity index 71% rename from Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift rename to Sources/SQLite/Codable/SQLiteDataEncoder.swift index 4c0deb2..b1769fe 100644 --- a/Sources/SQLite/Codable/SQLiteQueryExpressionEncoder.swift +++ b/Sources/SQLite/Codable/SQLiteDataEncoder.swift @@ -1,39 +1,42 @@ -/// Encodes `Encodable` values to `SQLiteQuery.Expression`. +/// Encodes `Encodable` values to `SQLiteData`. /// -/// let expr = try SQLiteQueryExpressionEncoder().encode("Hello") -/// print(expr) // .data(.text("Hello")) +/// let expr = try SQLiteDataEncoder().encode("Hello") +/// print(expr) // .text("Hello") /// -/// Conform your types to `SQLiteQueryExpressionRepresentable` or `SQLiteDataConvertible` to +/// Conform your types to `SQLiteDataConvertible` to /// customize how they are encoded. -/// -/// Use `SQLiteQueryEncoder` to encode top-level nested structures, such as models. -public struct SQLiteQueryExpressionEncoder { - /// Creates a new `SQLiteQueryExpressionEncoder`. +public struct SQLiteDataEncoder { + /// Creates a new `SQLiteDataEncoder`. public init() { } - /// Encodes `Encodable` values to `SQLiteQuery.Expression`. + /// Encodes `Encodable` values to `SQLiteData`. /// - /// let expr = try SQLiteQueryExpressionEncoder().encode("Hello") - /// print(expr) // .data(.text("Hello")) + /// let expr = try SQLiteDataEncoder().encode("Hello") + /// print(expr) // .text("Hello") /// /// - parameters: /// - value: `Encodable` value to encode. - /// - returns: `SQLiteQuery.Expression` representing the encoded data. - public func encode(_ value: E) throws -> SQLiteQuery.Expression - where E: Encodable - { - if let sqlite = value as? SQLiteQueryExpressionRepresentable { - return sqlite.sqliteQueryExpression - } else if let value = value as? SQLiteDataConvertible { - return try .data(value.convertToSQLiteData()) + /// - returns: `SQLiteData` representing the encoded data. + public func encode(_ value: Encodable) throws -> SQLiteData { + if let value = value as? SQLiteDataConvertible { + return try value.convertToSQLiteData() } else { let encoder = _Encoder() do { try value.encode(to: encoder) return encoder.data! } catch is _DoJSONError { - let json = try JSONEncoder().encode(value) - return .data(.blob(json)) + struct AnyEncodable: Encodable { + var encodable: Encodable + init(_ encodable: Encodable) { + self.encodable = encodable + } + func encode(to encoder: Encoder) throws { + try encodable.encode(to: encoder) + } + } + let json = try JSONEncoder().encode(AnyEncodable(value)) + return .blob(json) } } } @@ -43,7 +46,7 @@ public struct SQLiteQueryExpressionEncoder { private final class _Encoder: Encoder { let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] = [:] - var data: SQLiteQuery.Expression? + var data: SQLiteData? init() { self.data = nil @@ -74,15 +77,12 @@ public struct SQLiteQueryExpressionEncoder { } mutating func encodeNil() throws { - encoder.data = .literal(.null) + encoder.data = .null } mutating func encode(_ value: T) throws where T : Encodable { - if let sqlite = value as? SQLiteQueryExpressionRepresentable { - encoder.data = sqlite.sqliteQueryExpression - return - } else if let convertible = value as? SQLiteDataConvertible { - encoder.data = try .data(convertible.convertToSQLiteData()) + if let convertible = value as? SQLiteDataConvertible { + encoder.data = try convertible.convertToSQLiteData() return } try value.encode(to: encoder) diff --git a/Sources/SQLite/Codable/SQLiteRowDecoder.swift b/Sources/SQLite/Codable/SQLiteRowDecoder.swift index 9e3a584..66b5432 100644 --- a/Sources/SQLite/Codable/SQLiteRowDecoder.swift +++ b/Sources/SQLite/Codable/SQLiteRowDecoder.swift @@ -9,7 +9,7 @@ /// /// Uses `SQLiteDataDecoder` internally to decode each column. Use `SQLiteDataConvertible` to /// customize how your types are decoded. -public struct SQLiteRowDecoder { +public struct SQLiteRowDecoder: SQLRowDecoder { /// Creates a new `SQLiteRowDecoder`. public init() { } @@ -26,10 +26,10 @@ public struct SQLiteRowDecoder { /// - type: `Decodable` type to decode. /// - data: SQLite row (`[SQLiteColumn: SQLiteData]`) to decode. /// - returns: Instance of decoded type. - public func decode(_ type: D.Type, from row: [SQLiteColumn: SQLiteData], table: String? = nil) throws -> D + public func decode(_ type: D.Type, from row: [SQLiteColumn: SQLiteData], table: SQLiteQuery.TableIdentifier? = nil) throws -> D where D: Decodable { - return try D(from: _Decoder(row: row, table: table)) + return try D(from: _Decoder(row: row, table: table?.identifier.string)) } // MARK: Private diff --git a/Sources/SQLite/Database/SQLiteConnection.swift b/Sources/SQLite/Database/SQLiteConnection.swift index b899f07..a8bec4c 100644 --- a/Sources/SQLite/Database/SQLiteConnection.swift +++ b/Sources/SQLite/Database/SQLiteConnection.swift @@ -18,7 +18,7 @@ import SQLite3 /// .column(function: "sqlite_version", as: "version") /// .run().wait() /// -public final class SQLiteConnection: BasicWorker, DatabaseConnection { +public final class SQLiteConnection: BasicWorker, DatabaseConnection, SQLConnection { /// See `DatabaseConnection`. public typealias Database = SQLiteDatabase @@ -82,16 +82,17 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection { /// - query: `SQLiteQuery` to execute. /// - onRow: Callback for handling each row. /// - returns: A `Future` that signals completion of the query. - public func query(_ query: SQLiteQuery, onRow: @escaping ([SQLiteColumn: SQLiteData]) throws -> ()) -> Future { - var binds: [SQLiteData] = [] + public func query(_ query: SQLiteQuery, _ onRow: @escaping ([SQLiteColumn: SQLiteData]) throws -> ()) -> Future { + var binds: [Encodable] = [] let sql = query.serialize(&binds) let promise = eventLoop.newPromise(Void.self) + let data = try! binds.map { try SQLiteDataEncoder().encode($0) } // log before anything happens, in case there's an error - logger?.record(query: sql, values: binds.map { $0.description }) + logger?.record(query: sql, values: data.map { $0.description }) database.blockingIO.submit { state in do { let statement = try SQLiteStatement(query: sql, on: self) - try statement.bind(binds) + try statement.bind(data) if let columns = try statement.getColumns() { while let row = try statement.nextRow(for: columns) { self.eventLoop.execute { diff --git a/Sources/SQLite/Query/SQLiteBind.swift b/Sources/SQLite/Query/SQLiteBind.swift new file mode 100644 index 0000000..90ae9af --- /dev/null +++ b/Sources/SQLite/Query/SQLiteBind.swift @@ -0,0 +1,29 @@ +public struct SQLiteBind: SQLBind { + /// See `SQLBind`. + public static func encodable(_ value: E) -> SQLiteBind + where E: Encodable + { + if let expr = value as? SQLiteQueryExpressionRepresentable { + return self.init(value: .expression(expr.sqliteQueryExpression)) + } else { + return self.init(value: .encodable(value)) + } + } + + public enum Value { + case expression(SQLiteQuery.Expression) + case encodable(Encodable) + } + + public var value: Value + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch value { + case .expression(let expr): return expr.serialize(&binds) + case .encodable(let value): + binds.append(value) + return "?" + } + } +} diff --git a/Sources/SQLite/Query/SQLiteCollation.swift b/Sources/SQLite/Query/SQLiteCollation.swift new file mode 100644 index 0000000..62345ed --- /dev/null +++ b/Sources/SQLite/Query/SQLiteCollation.swift @@ -0,0 +1,6 @@ +public enum SQLiteCollation: SQLCollation { + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + return "X" + } +} diff --git a/Sources/SQLite/Query/SQLiteFunction.swift b/Sources/SQLite/Query/SQLiteFunction.swift new file mode 100644 index 0000000..269806f --- /dev/null +++ b/Sources/SQLite/Query/SQLiteFunction.swift @@ -0,0 +1,24 @@ +public struct SQLiteFunction: SQLFunction { + public typealias Argument = GenericSQLFunctionArgument + + public static var count: SQLiteFunction { + return .init(name: "COUNT", arguments: [.all]) + } + + public static func function(_ name: String, _ args: [Argument]) -> SQLiteFunction { + return .init(name: name, arguments: args) + } + + public let name: String + public let arguments: [Argument] + + public func serialize(_ binds: inout [Encodable]) -> String { + return name + "(" + arguments.map { $0.serialize(&binds) }.joined(separator: ", ") + ")" + } +} + +extension SQLSelectExpression where Expression.Function == SQLiteFunction { + public static func count(as alias: String? = nil) -> Self { + return .expression(.function(.count), alias: alias) + } +} diff --git a/Sources/SQLite/Query/SQLitePrimaryKey.swift b/Sources/SQLite/Query/SQLitePrimaryKey.swift new file mode 100644 index 0000000..00cdf48 --- /dev/null +++ b/Sources/SQLite/Query/SQLitePrimaryKey.swift @@ -0,0 +1,35 @@ +public struct SQLitePrimaryKey: SQLPrimaryKey { + /// See `SQLPrimaryKey`. + public static func primaryKey() -> SQLitePrimaryKey { + return .init(autoIncrement: false) + } + + /// See `SQLPrimaryKey`. + public static func primaryKey(autoIncrement: Bool) -> SQLitePrimaryKey { + return .init(autoIncrement: autoIncrement) + } + + /// The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. + /// It is usually not needed. + /// + /// In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT ROWID tables) which is always a 64-bit + /// signed integer. + /// + /// https://www.sqlite.org/autoinc.html + public var autoIncrement: Bool + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + if autoIncrement { + return "AUTOINCREMENT" + } else { + return "" + } + } +} + +extension SQLColumnConstraint where ColumnConstraintAlgorithm.PrimaryKey == SQLitePrimaryKey { + public static func primaryKey(autoIncrement: Bool) -> Self { + return .constraint(.primaryKey(.primaryKey(autoIncrement: autoIncrement)), nil) + } +} diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift index 89ecc38..b51acb5 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift @@ -1,56 +1,56 @@ -extension SQLiteQuery { - /// Represents an `ALTER TABLE ...` query. - /// - /// See `SQLiteQuery.AlterTableBuilder` to build this query. - public struct AlterTable { - /// Supported `ALTER TABLE` methods. - public enum Value { - /// Renames the table. - case rename(Name) - - /// Adds a new column to the table. - case addColumn(ColumnDefinition) - } - - /// Name of table to alter. - public var table: TableName - - /// Type of `ALTER` to perform. - public var value: Value - - /// Creates a new `AlterTable`. - /// - /// - parameters: - /// - table: Name of table to alter. - /// - value: Type of `ALTER` to perform. - public init(table: TableName, value: Value) { - self.table = table - self.value = value - } - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - internal func serialize(_ alter: SQLiteQuery.AlterTable, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("ALTER TABLE") - sql.append(serialize(alter.table)) - sql.append(serialize(alter.value, &binds)) - return sql.joined(separator: " ") - } - - internal func serialize(_ value: SQLiteQuery.AlterTable.Value, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch value { - case .rename(let name): - sql.append("RENAME TO") - sql.append(serialize(name)) - case .addColumn(let columnDefinition): - sql.append("ADD") - sql.append(serialize(columnDefinition, &binds)) - } - return sql.joined(separator: " ") - } -} +//extension SQLiteQuery { +// /// Represents an `ALTER TABLE ...` query. +// /// +// /// See `SQLiteQuery.AlterTableBuilder` to build this query. +// public struct AlterTable { +// /// Supported `ALTER TABLE` methods. +// public enum Value { +// /// Renames the table. +// case rename(Name) +// +// /// Adds a new column to the table. +// case addColumn(ColumnDefinition) +// } +// +// /// Name of table to alter. +// public var table: TableName +// +// /// Type of `ALTER` to perform. +// public var value: Value +// +// /// Creates a new `AlterTable`. +// /// +// /// - parameters: +// /// - table: Name of table to alter. +// /// - value: Type of `ALTER` to perform. +// public init(table: TableName, value: Value) { +// self.table = table +// self.value = value +// } +// } +//} +// +//// MARK: Serialize +// +////extension SQLiteSerializer { +//// internal func serialize(_ alter: SQLiteQuery.AlterTable, _ binds: inout [SQLiteData]) -> String { +//// var sql: [String] = [] +//// sql.append("ALTER TABLE") +//// sql.append(serialize(alter.table)) +//// sql.append(serialize(alter.value, &binds)) +//// return sql.joined(separator: " ") +//// } +//// +//// internal func serialize(_ value: SQLiteQuery.AlterTable.Value, _ binds: inout [SQLiteData]) -> String { +//// var sql: [String] = [] +//// switch value { +//// case .rename(let name): +//// sql.append("RENAME TO") +//// sql.append(serialize(name)) +//// case .addColumn(let columnDefinition): +//// sql.append("ADD") +//// sql.append(serialize(columnDefinition, &binds)) +//// } +//// return sql.joined(separator: " ") +//// } +////} diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift index 828d36b..6d18de5 100644 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift +++ b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift @@ -1,83 +1,84 @@ -extension SQLiteQuery { - /// Builds `AlterTable` queries. - public final class AlterTableBuilder
where Table: SQLiteTable { - /// Query being built. - public var alter: AlterTable - - /// Database connection to execute the query on. - public let connection: SQLiteConnection - - /// Creates a new `AlterTableBuilder`. - /// - /// - parameters: - /// - table: Name of existing table to alter. - /// - connection: `SQLiteConnection` to perform the query on. - init(table: Table.Type, on connection: SQLiteConnection) { - self.alter = .init(table: Table.sqliteTableName, value: .rename(Table.sqliteTableName.name)) - self.connection = connection - } - - /// Renames the table. - /// - /// conn.alter(table: Bar.self).rename(to: "foo").run() - /// - /// - parameters: - /// - to: New table name. - /// - returns: Self for chaining. - public func rename(to tableName: Name) -> Self { - alter.value = .rename(tableName) - return self - } - - /// Adds a new column to the table. Only one column can be added per `ALTER` statement. - /// - /// conn.alter(table: Planet.self).addColumn(for: \.name, type: .text, .notNull).run() - /// - /// - parameters: - /// - keyPath: Swift `KeyPath` to property that should be added. - /// - type: Name of type to use for this column. - /// - constraints: Zero or more column constraints to add. - /// - returns: Self for chaining. - public func addColumn( - for keyPath: KeyPath, - type typeName: TypeName, - _ constraints: SQLiteQuery.ColumnConstraint... - ) -> Self { - return addColumn(.init(name: keyPath.sqliteColumnName.name, typeName: typeName, constraints: constraints)) - } - - /// Adds a new column to the table. Only one column can be added per `ALTER` statement. - /// - /// conn.alter(table: Planet.self).addColumn(...).run() - /// - /// - parameters: - /// - columnDefinition: Column definition to add. - /// - returns: Self for chaining. - public func addColumn(_ columnDefinition: ColumnDefinition) -> Self { - alter.value = .addColumn(columnDefinition) - return self - } - - /// Runs the `ALTER` query. - /// - /// - returns: A `Future` that signals completion. - public func run() -> Future { - return connection.query(.alterTable(alter)).transform(to: ()) - } - } -} - -extension SQLiteConnection { - /// Creates a new `AlterTableBuilder`. - /// - /// conn.alter(table: Planet.self)... - /// - /// - parameters: - /// - table: Table to alter. - /// - returns: `AlterTableBuilder`. - public func alter
(table: Table.Type) -> SQLiteQuery.AlterTableBuilder
- where Table: SQLiteTable - { - return .init(table: Table.self, on: self) - } -} +//extension SQLiteQuery { +// /// Builds `AlterTable` queries. +// public final class AlterTableBuilder
where Table: SQLiteTable { +// /// Query being built. +// public var alter: AlterTable +// +// /// Database connection to execute the query on. +// public let connection: SQLiteConnection +// +// /// Creates a new `AlterTableBuilder`. +// /// +// /// - parameters: +// /// - table: Name of existing table to alter. +// /// - connection: `SQLiteConnection` to perform the query on. +// init(table: Table.Type, on connection: SQLiteConnection) { +// fatalError() +//// self.alter = .init(table: Table.sqliTableName, value: .rename(Table.sqliteTableName.name)) +// self.connection = connection +// } +// +// /// Renames the table. +// /// +// /// conn.alter(table: Bar.self).rename(to: "foo").run() +// /// +// /// - parameters: +// /// - to: New table name. +// /// - returns: Self for chaining. +// public func rename(to tableName: Name) -> Self { +// alter.value = .rename(tableName) +// return self +// } +// +// /// Adds a new column to the table. Only one column can be added per `ALTER` statement. +// /// +// /// conn.alter(table: Planet.self).addColumn(for: \.name, type: .text, .notNull).run() +// /// +// /// - parameters: +// /// - keyPath: Swift `KeyPath` to property that should be added. +// /// - type: Name of type to use for this column. +// /// - constraints: Zero or more column constraints to add. +// /// - returns: Self for chaining. +// public func addColumn( +// for keyPath: KeyPath, +// type typeName: TypeName, +// _ constraints: SQLiteQuery.ColumnConstraint... +// ) -> Self { +// return addColumn(.init(name: keyPath.sqlColumnName.name, typeName: typeName, constraints: constraints)) +// } +// +// /// Adds a new column to the table. Only one column can be added per `ALTER` statement. +// /// +// /// conn.alter(table: Planet.self).addColumn(...).run() +// /// +// /// - parameters: +// /// - columnDefinition: Column definition to add. +// /// - returns: Self for chaining. +// public func addColumn(_ columnDefinition: ColumnDefinition) -> Self { +// alter.value = .addColumn(columnDefinition) +// return self +// } +// +// /// Runs the `ALTER` query. +// /// +// /// - returns: A `Future` that signals completion. +// public func run() -> Future { +// return connection.query(.alterTable(alter)).transform(to: ()) +// } +// } +//} +// +//extension SQLiteConnection { +// /// Creates a new `AlterTableBuilder`. +// /// +// /// conn.alter(table: Planet.self)... +// /// +// /// - parameters: +// /// - table: Table to alter. +// /// - returns: `AlterTableBuilder`. +// public func alter
(table: Table.Type) -> SQLiteQuery.AlterTableBuilder
+// where Table: SQLiteTable +// { +// return .init(table: Table.self, on: self) +// } +//} diff --git a/Sources/SQLite/Query/SQLiteQuery+Column.swift b/Sources/SQLite/Query/SQLiteQuery+Column.swift deleted file mode 100644 index dd4be38..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Column.swift +++ /dev/null @@ -1,37 +0,0 @@ -extension SQLiteQuery { - /// Represents a SQLite column name with optional table name. - public struct ColumnName { - /// Optional table name. - public var table: TableName? - - /// Column name. - public var name: Name - - /// Creates a new `ColumnName`. - public init(table: TableName? = nil, name: Name) { - self.table = table - self.name = name - } - } -} - -// MARK: String Literal - -extension SQLiteQuery.ColumnName: ExpressibleByStringLiteral { - /// See `ExpressibleByStringLiteral`. - public init(stringLiteral value: String) { - self.init(name: .init(stringLiteral: value)) - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - func serialize(_ column: SQLiteQuery.ColumnName) -> String { - if let table = column.table { - return serialize(table) + "." + serialize(column.name) - } else { - return serialize(column.name) - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift deleted file mode 100644 index 07d0632..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+0.swift +++ /dev/null @@ -1,161 +0,0 @@ -extension SQLiteQuery { - /// SQLite column definition constraint. - public struct ColumnConstraint { - /// Creates a new `PRIMARY KEY` column constraint. - /// - /// - parameters: - /// - direction: Primary key direction. - /// - onConflict: `ON CONFLICT` resolution. - /// - autoIncrement: If `true`, the primary key will auto increment. - /// `true` by default. - /// - named: Optional constraint name. - /// - returns: New column constraint. - public static func primaryKey( - direction: Direction? = nil, - onConflict: ConflictResolution? = nil, - autoIncrement: Bool = true, - named name: String? = nil - ) -> ColumnConstraint { - return .init( - name: name, - value: .primaryKey(.init( - direction: direction, - conflictResolution: onConflict, - autoIncrement: autoIncrement - )) - ) - } - - /// Creates a new `NOT NULL` column constraint. - /// - /// - parameters: - /// - onConflict: `ON CONFLICT` resolution. - /// - named: Optional constraint name. - /// - returns: New column constraint. - public static func notNull(onConflict: ConflictResolution? = nil, named name: String? = nil) -> ColumnConstraint { - return .init( - name: name, - value: .nullability(.init(allowNull: false, conflictResolution: onConflict)) - ) - } - - /// Creates a new `UNIQUE` column constraint. - /// - /// - parameters: - /// - onConflict: `ON CONFLICT` resolution. - /// - named: Optional constraint name. - /// - returns: New column constraint. - public static func unique(onConflict: ConflictResolution? = nil, named name: String? = nil) -> ColumnConstraint { - return .init( - name: name, - value: .unique(.init(conflictResolution: onConflict)) - ) - } - - /// Creates a new `DEFAULT ` column constraint. - /// - /// - parameters - /// - expression: Expression to evaluate when setting the default value. - /// - named: Optional constraint name. - /// - returns: New column constraint. - public static func `default`(_ expression: Expression, named name: String? = nil) -> ColumnConstraint { - return .init(name: name, value: .default(expression)) - } - - /// Creates a new `REFERENCES` column constraint. - /// - /// - parameters - /// - keyPath: Swift `KeyPath` to referenced column. - /// - onDelete: `ON DELETE` foreign key action. - /// - onUpdate: `ON UPDATE` foreign key action. - /// - deferrable: Foreign key check deferrence. - /// - named: Optional constraint name. - /// - returns: New column constraint. - public static func references( - _ keyPath: KeyPath, - onDelete: ForeignKey.Action? = nil, - onUpdate: ForeignKey.Action? = nil, - deferrable: ForeignKey.Deferrence? = nil, - named name: String? = nil - ) -> ColumnConstraint - where Table: SQLiteTable - { - return .init( - name: name, - value: .references(.init( - foreignTable: Table.sqliteTableName, - foreignColumns: [keyPath.sqliteColumnName.name], - onDelete: onDelete, - onUpdate: onUpdate, - deferrence: deferrable - ) - )) - } - - /// Supported column constraint values. - public enum Value { - /// `PRIMARY KEY` - case primaryKey(PrimaryKey) - - /// `NULL` or `NOT NULL` - case nullability(Nullability) - - /// `UNIQUE` - case unique(Unique) - - /// `CHECK` - case check(Expression) - - /// `DEFAULT` - case `default`(Expression) - - /// `COLLATE` - case collate(String) - - /// `REFERENCES` - case references(ForeignKey.Reference) - } - - /// Optional constraint name. - public var name: String? - - /// Contraint value. - public var value: Value - - /// Creates a new `ColumnConstraint`. - /// - /// - parameters: - /// - name: Optional constraint name. - /// - value: Constraint value. - public init(name: String? = nil, value: Value) { - self.name = name - self.value = value - } - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - func serialize(_ constraint: SQLiteQuery.ColumnConstraint, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - if let name = constraint.name { - sql.append("CONSTRAINT") - sql.append(escapeString(name)) - } - sql.append(serialize(constraint.value, &binds)) - return sql.joined(separator: " ") - } - - func serialize(_ value: SQLiteQuery.ColumnConstraint.Value, _ binds: inout [SQLiteData]) -> String { - switch value { - case .primaryKey(let primaryKey): return serialize(primaryKey) - case .nullability(let nullability): return serialize(nullability) - case .unique(let unique): return serialize(unique) - case .check(let expr): return "CHECK (" + serialize(expr, &binds) + ")" - case .default(let expr): return "DEFAULT (" + serialize(expr, &binds) + ")" - case .collate(let name): return "COLLATE " + name - case .references(let reference): return serialize(reference) - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift deleted file mode 100644 index 3218fdb..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Nullability.swift +++ /dev/null @@ -1,39 +0,0 @@ -extension SQLiteQuery.ColumnConstraint { - /// `NOT NULL` or `NULL` constraint. - public struct Nullability { - /// If `true`, this constraint will allow null. - public var allowNull: Bool - - /// `ON CONFLICT` resolution. - public var conflictResolution: SQLiteQuery.ConflictResolution? - - /// Creates a new `Nullability` constraint. - /// - /// - parameters: - /// - allowNull: If `true`, this constraint will allow null. - /// Defaults to `false`. - /// - conflictResolution: `ON CONFLICT` resolution. - public init(allowNull: Bool = false, conflictResolution: SQLiteQuery.ConflictResolution? = nil) { - self.allowNull = allowNull - self.conflictResolution = conflictResolution - } - } -} - - -// MARK: Serialize - -extension SQLiteSerializer { - public func serialize(_ nullability: SQLiteQuery.ColumnConstraint.Nullability) -> String { - var sql: [String] = [] - if !nullability.allowNull { - sql.append("NOT") - } - sql.append("NULL") - if let conflictResolution = nullability.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift deleted file mode 100644 index 00d5597..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+PrimaryKey.swift +++ /dev/null @@ -1,51 +0,0 @@ -extension SQLiteQuery.ColumnConstraint { - /// `PRIMARY KEY` constraint. - public struct PrimaryKey { - /// Primary key direction. - public var direction: SQLiteQuery.Direction? - - /// `ON CONFLICT` resolution. - public var conflictResolution: SQLiteQuery.ConflictResolution? - - //// If `true`, this primary key will autoincrement. - public var autoIncrement: Bool - - /// Creates a new `PrimaryKey` constraint. - /// - /// - parameters: - /// - direction: Primary key direction. - /// - conflictResolution: `ON CONFLICT` resolution. - /// - autoIncrement: If `true`, this primary key will autoincrement. - /// Defaults to `true`. - public init( - direction: SQLiteQuery.Direction? = nil, - conflictResolution: SQLiteQuery.ConflictResolution? = nil, - autoIncrement: Bool = true - ) { - self.direction = direction - self.conflictResolution = conflictResolution - self.autoIncrement = autoIncrement - } - } -} - - -// MARK: Serialize - -extension SQLiteSerializer { - public func serialize(_ primaryKey: SQLiteQuery.ColumnConstraint.PrimaryKey) -> String { - var sql: [String] = [] - sql.append("PRIMARY KEY") - if let direction = primaryKey.direction { - sql.append(serialize(direction)) - } - if let conflictResolution = primaryKey.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - if primaryKey.autoIncrement { - sql.append("AUTOINCREMENT") - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift deleted file mode 100644 index 9d04fdb..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnConstraint+Unique.swift +++ /dev/null @@ -1,29 +0,0 @@ -extension SQLiteQuery.ColumnConstraint { - /// `UNIQUE` constraint. - public struct Unique { - /// `ON CONFLICT` resolution. - public var conflictResolution: SQLiteQuery.ConflictResolution? - - /// Creates a new `Unique` constraint. - /// - /// - parameters: - /// - conflictResolution: `ON CONFLICT` resolution. - public init(conflictResolution: SQLiteQuery.ConflictResolution? = nil) { - self.conflictResolution = conflictResolution - } - } -} - -// MARK: Serializer - -extension SQLiteSerializer { - public func serialize(_ unique: SQLiteQuery.ColumnConstraint.Unique) -> String { - var sql: [String] = [] - sql.append("UNIQUE") - if let conflictResolution = unique.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift b/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift deleted file mode 100644 index 91847d1..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ColumnDefinition.swift +++ /dev/null @@ -1,37 +0,0 @@ -extension SQLiteQuery { - /// Column definition. - public struct ColumnDefinition { - /// Column name. - public var name: Name - - /// Column type name. - public var typeName: TypeName - - /// Zero or more column constraints. - public var constraints: [ColumnConstraint] - - /// Creates a new `ColumnDefinition`. - /// - /// - parameters: - /// - name: Column name. - /// - typeName: Column type name. - /// - constraints: Zero or more column constraints. - public init(name: Name, typeName: TypeName, constraints: [ColumnConstraint] = []) { - self.name = name - self.typeName = typeName - self.constraints = constraints - } - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - func serialize(_ columnDefinition: SQLiteQuery.ColumnDefinition, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append(serialize(columnDefinition.name)) - sql.append(serialize(columnDefinition.typeName)) - sql += columnDefinition.constraints.map { serialize($0, &binds) } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift b/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift deleted file mode 100644 index 9705961..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ConflictResolution.swift +++ /dev/null @@ -1,54 +0,0 @@ -extension SQLiteQuery { - /// `ON CONFLICT` clause. Supported constraint conflict resolution algorithms. - /// - /// https://www.sqlite.org/lang_conflict.html - public enum ConflictResolution { - /// When a UNIQUE or PRIMARY KEY constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing - /// the constraint violation prior to inserting or updating the current row and the command continues executing normally. If a - /// NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with the default value for - /// that column, or if the column has no default value, then the ABORT algorithm is used. If a CHECK constraint violation - /// occurs, the REPLACE conflict resolution algorithm always works like ABORT. - /// - /// When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and - /// only if recursive triggers are enabled. - /// - /// The update hook is not invoked for rows that are deleted by the REPLACE conflict resolution strategy. Nor does REPLACE - /// increment the change counter. The exceptional behaviors defined in this paragraph might change in a future release. - case replace - - /// When an applicable constraint violation occurs, the ROLLBACK resolution algorithm aborts the current SQL statement with - /// an SQLITE_CONSTRAINT error and rolls back the current transaction. If no transaction is active (other than the implied - /// transaction that is created on every command) then the ROLLBACK resolution algorithm works the same as the ABORT algorithm. - case rollback - - /// When an applicable constraint violation occurs, the ABORT resolution algorithm aborts the current SQL statement with an - /// SQLITE_CONSTRAINT error and backs out any changes made by the current SQL statement; but changes caused by prior SQL - /// statements within the same transaction are preserved and the transaction remains active. This is the default behavior and - /// the behavior specified by the SQL standard. - case abort - /// When an applicable constraint violation occurs, the FAIL resolution algorithm aborts the current SQL statement with an - /// SQLITE_CONSTRAINT error. But the FAIL resolution does not back out prior changes of the SQL statement that failed nor does - /// it end the transaction. For example, if an UPDATE statement encountered a constraint violation on the 100th row that it - /// attempts to update, then the first 99 row changes are preserved but changes to rows 100 and beyond never occur. - case fail - /// When an applicable constraint violation occurs, the IGNORE resolution algorithm skips the one row that contains the - /// constraint violation and continues processing subsequent rows of the SQL statement as if nothing went wrong. Other rows - /// before and after the row that contained the constraint violation are inserted or updated normally. No error is returned - /// when the IGNORE conflict resolution algorithm is used. - case ignore - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - func serialize(_ algorithm: SQLiteQuery.ConflictResolution) -> String { - switch algorithm { - case .abort: return "ABORT" - case .fail: return "FAIL" - case .ignore: return "IGNORE" - case .replace: return "REPLACE" - case .rollback: return "ROLLBACK" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift b/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift index 4462e11..404ec95 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift +++ b/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift @@ -1,107 +1,70 @@ -extension SQLiteQuery { - /// The `CREATE TABLE` command is used to create a new table in an SQLite database. +extension SQLCreateTableBuilder where Connection.Query.CreateTable == SQLiteCreateTable { + /// By default, every row in SQLite has a special column, usually called the "rowid", that uniquely identifies that row within + /// the table. However if the phrase "WITHOUT ROWID" is added to the end of a CREATE TABLE statement, then the special "rowid" + /// column is omitted. There are sometimes space and performance advantages to omitting the rowid. /// - /// https://www.sqlite.org/lang_createtable.html - public struct CreateTable { - /// A collection of columns and constraints defining a table. - public struct SchemaDefinition { - /// Columns to create. - public var columns: [ColumnDefinition] - - /// Table constraints (different from column constraints) to create. - public var tableConstraints: [TableConstraint] - - /// By default, every row in SQLite has a special column, usually called the "rowid", that uniquely identifies that row within - /// the table. However if the phrase "WITHOUT ROWID" is added to the end of a CREATE TABLE statement, then the special "rowid" - /// column is omitted. There are sometimes space and performance advantages to omitting the rowid. - /// - /// https://www.sqlite.org/withoutrowid.html - public var withoutRowID: Bool - - /// Creates a new `CreateTable`. - /// - /// - parameters: - /// - columns: Columns to create. - /// - tableConstraints: Table constraints (different from column constraints) to create. - /// - withoutRowID: See `withoutRowID`. - public init( - columns: [ColumnDefinition], - tableConstraints: [TableConstraint] = [], - withoutRowID: Bool = false - ) { - self.columns = columns - self.tableConstraints = tableConstraints - self.withoutRowID = withoutRowID - } - } - - /// Source for table schema. Either a definition or the results of a `SELECT` statement. - public enum SchemaSource { - /// A collection of columns and constraints defining a table. - case definition(SchemaDefinition) - /// The results of a `SELECT` statement. - case select(Select) - } - - /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. - public var temporary: Bool - - /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the - /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view - /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An - /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is - /// specified. - public var ifNotExists: Bool - - /// Name of the table to create. - public var table: TableName - - /// Source of the schema information. - public var schemaSource: SchemaSource - - /// Creates a new `CreateTable` query. - public init( - temporary: Bool = false, - ifNotExists: Bool = false, - table: TableName, - schemaSource: SchemaSource - ) { - self.temporary = temporary - self.ifNotExists = ifNotExists - self.table = table - self.schemaSource = schemaSource - } + /// https://www.sqlite.org/withoutrowid.html + public func withoutRowID() -> Self { + createTable.withoutRowID = true + return self } } -extension SQLiteSerializer { - func serialize(_ create: SQLiteQuery.CreateTable, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("CREATE") - if create.temporary { - sql.append("TEMP") - } - sql.append("TABLE") - if create.ifNotExists { - sql.append("IF NOT EXISTS") - } - sql.append(serialize(create.table)) - sql.append(serialize(create.schemaSource, &binds)) - return sql.joined(separator: " ") +/// The `CREATE TABLE` command is used to create a new table in an SQLite database. +/// +/// https://www.sqlite.org/lang_createtable.html +public struct SQLiteCreateTable: SQLCreateTable { + /// See `SQLCreateTable`. + public static func createTable(_ table: SQLiteQuery.TableIdentifier) -> SQLiteCreateTable { + return .init(createTable: .createTable(table), withoutRowID: false) } - func serialize(_ source: SQLiteQuery.CreateTable.SchemaSource, _ binds: inout [SQLiteData]) -> String { - switch source { - case .definition(let schema): return serialize(schema, &binds) - case .select(let select): return "AS " + serialize(select, &binds) - } + /// See `SQLCreateTable`. + public var createTable: GenericSQLCreateTable + + + /// See `SQLCreateTable`. + public var temporary: Bool { + get { return createTable.temporary } + set { return createTable.temporary = newValue } } - func serialize(_ schema: SQLiteQuery.CreateTable.SchemaDefinition, _ binds: inout [SQLiteData]) -> String { + + /// See `SQLCreateTable`. + public var ifNotExists: Bool { + get { return createTable.ifNotExists } + set { return createTable.ifNotExists = newValue } + } + + /// See `SQLCreateTable`. + public var table: SQLiteQuery.TableIdentifier { + get { return createTable.table } + set { return createTable.table = newValue } + } + + /// See `SQLCreateTable`. + public var columns: [SQLiteQuery.ColumnDefinition] { + get { return createTable.columns } + set { return createTable.columns = newValue } + } + + /// See `SQLCreateTable`. + public var tableConstraints: [SQLiteQuery.TableConstraint] { + get { return createTable.tableConstraints } + set { return createTable.tableConstraints = newValue } + } + + /// By default, every row in SQLite has a special column, usually called the "rowid", that uniquely identifies that row within + /// the table. However if the phrase "WITHOUT ROWID" is added to the end of a CREATE TABLE statement, then the special "rowid" + /// column is omitted. There are sometimes space and performance advantages to omitting the rowid. + /// + /// https://www.sqlite.org/withoutrowid.html + public var withoutRowID: Bool + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { var sql: [String] = [] - sql.append("(" + ( - schema.columns.map { serialize($0, &binds) } + schema.tableConstraints.map { serialize($0, &binds) } - ).joined(separator: ", ") + ")") - if schema.withoutRowID { + sql.append(createTable.serialize(&binds)) + if withoutRowID { sql.append("WITHOUT ROWID") } return sql.joined(separator: " ") diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift deleted file mode 100644 index 67030d9..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+CreateTableBuilder.swift +++ /dev/null @@ -1,131 +0,0 @@ -extension SQLiteQuery { - /// Builds `CreateTable` queries. - public final class CreateTableBuilder
where Table: SQLiteTable { - /// Query being built. - public var create: CreateTable - - /// Database connection to execute the query on. - public let connection: SQLiteConnection - - /// Creates a new `CreateTableBuilder`. - /// - /// - parameters: - /// - table: Name of existing table to create. - /// - connection: `SQLiteConnection` to perform the query on. - init(table: Table.Type, on connection: SQLiteConnection) { - self.create = .init(table: Table.sqliteTableName, schemaSource: .definition(.init(columns: []))) - self.connection = connection - } - - /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. - public func temporary() -> Self { - create.temporary = true - return self - } - - /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the - /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view - /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An - /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is - /// specified. - public func ifNotExists() -> Self { - create.ifNotExists = true - return self - } - - /// Adds a column to the table. - /// - /// conn.create(table: Planet.self).column(for: \.name, type: .text, .notNull).run() - /// - /// - parameters: - /// - keyPath: Swift `KeyPath` to property that should be added. - /// - type: Name of type to use for this column. - /// - constraints: Zero or more column constraints to add. - /// - returns: Self for chaining. - public func column( - for keyPath: KeyPath, - type typeName: TypeName, - _ constraints: SQLiteQuery.ColumnConstraint... - ) -> Self { - return column(.init( - name: keyPath.sqliteColumnName.name, - typeName: typeName, - constraints: constraints - )) - } - /// Adds a column to the table. - /// - /// conn.create(table: Planet.self).column(...).run() - /// - /// - parameters: - /// - columnDefinition: Column definition to add. - /// - returns: Self for chaining. - public func column(_ columnDefinition: ColumnDefinition) -> Self { - schemaDefinition.columns.append(columnDefinition) - return self - } - - /// By default, every row in SQLite has a special column, usually called the "rowid", that uniquely identifies that row within - /// the table. However if the phrase "WITHOUT ROWID" is added to the end of a CREATE TABLE statement, then the special "rowid" - /// column is omitted. There are sometimes space and performance advantages to omitting the rowid. - /// - /// https://www.sqlite.org/withoutrowid.html - public func withoutRowID() -> Self { - schemaDefinition.withoutRowID = true - return self - } - - /// A `CREATE TABLE ... AS SELECT` statement creates and populates a database table based on the results of a SELECT statement. - /// The table has the same number of columns as the rows returned by the SELECT statement. The name of each column is the same - /// as the name of the corresponding column in the result set of the SELECT statement. - /// - /// conn.create(table: GalaxyCopy.self).as { $0.select().all().from(Galaxy.self) }.run() - /// - /// - parameters: - /// - closure: Closure accepting a `SQLiteConnection` and returning a `SelectBuilder`. - /// - returns: Self for chaining. - public func `as`(_ closure: (SQLiteConnection) -> SelectBuilder) -> Self { - create.schemaSource = .select(closure(connection).select) - return self - } - - // TODO: Support adding table constraints. - - /// Runs the `CREATE` query. - /// - /// - returns: A `Future` that signals completion. - public func run() -> Future { - return connection.query(.createTable(create)).transform(to: ()) - } - - // MARK: Private - - /// Convenience accessor for setting schema. - private var schemaDefinition: CreateTable.SchemaDefinition { - get { - switch create.schemaSource { - case .definition(let definition): return definition - case .select: return .init(columns: []) - } - } - set { - create.schemaSource = .definition(newValue) - } - } - } -} - -extension SQLiteConnection { - /// Creates a new `CreateTableBuilder`. - /// - /// conn.create(table: Planet.self)... - /// - /// - parameters: - /// - table: Table to create. - /// - returns: `CreateTableBuilder`. - public func create
(table: Table.Type) -> SQLiteQuery.CreateTableBuilder
- where Table: SQLiteTable - { - return .init(table: Table.self, on: self) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Delete.swift b/Sources/SQLite/Query/SQLiteQuery+Delete.swift deleted file mode 100644 index e08264b..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Delete.swift +++ /dev/null @@ -1,36 +0,0 @@ -extension SQLiteQuery { - /// A `DELETE ...` query. - public struct Delete { - /// Name of table to delete from. - public var table: QualifiedTableName - - /// If the WHERE clause is not present, all records in the table are deleted. If a WHERE clause is supplied, - /// then only those rows for which the WHERE clause boolean expression is true are deleted. Rows for which - /// the expression is false or NULL are retained. - public var predicate: Expression? - - /// Creates a new `Delete`. - public init( - table: QualifiedTableName, - predicate: Expression? = nil - ) { - self.table = table - self.predicate = predicate - } - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - internal func serialize(_ delete: SQLiteQuery.Delete, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("DELETE FROM") - sql.append(serialize(delete.table)) - if let predicate = delete.predicate { - sql.append("WHERE") - sql.append(serialize(predicate, &binds)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift deleted file mode 100644 index ee50171..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+DeleteBuilder.swift +++ /dev/null @@ -1,44 +0,0 @@ -extension SQLiteQuery { - /// Builds `Delete` queries. - public final class DeleteBuilder
: SQLitePredicateBuilder where Table: SQLiteTable { - /// Query being build. - public var delete: Delete - - /// Database connection to execute the query on. - public let connection: SQLiteConnection - - /// See `SQLitePredicateBuilder`. - public var predicate: SQLiteQuery.Expression? { - get { return delete.predicate } - set { delete.predicate = newValue } - } - - /// Creates a new `DeleteBuilder`. - internal init(table: Table.Type, on connection: SQLiteConnection) { - self.delete = .init(table: .init(table: .init(table: Table.sqliteTableName))) - self.connection = connection - } - - /// Runs the `DELETE` query. - /// - /// - returns: A `Future` that signals completion. - public func run() -> Future { - return connection.query(.delete(delete)).transform(to: ()) - } - } -} - -extension SQLiteConnection { - /// Creates a new `CreateTableBuilder`. - /// - /// conn.delete(from: Planet.self)... - /// - /// - parameters: - /// - table: Table to delete from. - /// - returns: `DeleteBuilder`. - public func delete
(from table: Table.Type) -> SQLiteQuery.DeleteBuilder
- where Table: SQLiteTable - { - return .init(table: Table.self, on: self) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Direction.swift b/Sources/SQLite/Query/SQLiteQuery+Direction.swift deleted file mode 100644 index c7caa6f..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Direction.swift +++ /dev/null @@ -1,20 +0,0 @@ -extension SQLiteQuery { - /// Sort direction. - public enum Direction { - /// `ASC`. - case ascending - /// `DESC`. - case descending - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - internal func serialize(_ direction: SQLiteQuery.Direction) -> String { - switch direction { - case .ascending: return "ASC" - case .descending: return "DESC" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+DropTable.swift b/Sources/SQLite/Query/SQLiteQuery+DropTable.swift deleted file mode 100644 index 3778030..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+DropTable.swift +++ /dev/null @@ -1,30 +0,0 @@ -extension SQLiteQuery { - /// `DROP TABLE` query. - public struct DropTable { - /// Name of table to drop. - public var table: TableName - - /// The optional IF EXISTS clause suppresses the error that would normally result if the table does not exist. - public var ifExists: Bool - - /// Creates a new `DropTable` query. - public init(table: TableName, ifExists: Bool = false) { - self.table = table - self.ifExists = ifExists - } - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - internal func serialize(_ drop: SQLiteQuery.DropTable) -> String { - var sql: [String] = [] - sql.append("DROP TABLE") - if drop.ifExists { - sql.append("IF EXISTS") - } - sql.append(serialize(drop.table)) - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift deleted file mode 100644 index 28b9dc0..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+DropTableBuilder.swift +++ /dev/null @@ -1,44 +0,0 @@ -extension SQLiteQuery { - /// Builds a `DropTable` query. - public final class DropTableBuilder
where Table: SQLiteTable { - /// Query being built. - public var drop: DropTable - - /// Database connection to execute the query on. - public let connection: SQLiteConnection - - /// Creates a new `DropTableBuilder`. - init(table: Table.Type, on connection: SQLiteConnection) { - self.drop = .init(table: Table.sqliteTableName) - self.connection = connection - } - - /// The optional IF EXISTS clause suppresses the error that would normally result if the table does not exist. - public func ifExists() -> Self { - drop.ifExists = true - return self - } - - /// Runs the `DROP TABLE` query. - /// - /// - returns: A `Future` that signals completion. - public func run() -> Future { - return connection.query(.dropTable(drop)).transform(to: ()) - } - } -} - -extension SQLiteConnection { - /// Creates a new `DropTableBuilder`. - /// - /// conn.drop(table: Planet.self)... - /// - /// - parameters: - /// - table: Table to drop. - /// - returns: `DropTableBuilder`. - public func drop
(table: Table.Type) -> SQLiteQuery.DropTableBuilder
- where Table: SQLiteTable - { - return .init(table: Table.self, on: self) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift deleted file mode 100644 index 517f185..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+0.swift +++ /dev/null @@ -1,105 +0,0 @@ -extension SQLiteQuery { - public indirect enum Expression { - /// Binds an `Encodable` value as an `Expression`. - /// - /// - parameters: - /// - value: `Encodable` value to bind. - /// - returns: `Expression`. - public static func bind(_ value: E) throws -> SQLiteQuery.Expression where E: Encodable { - return try SQLiteQueryExpressionEncoder().encode(value) - } - - /// Literal strings, integers, and constants. - case literal(Literal) - - /// Bound data. - case data(SQLiteData) - - /// Column name. - case column(ColumnName) - - /// Binary expression. - case binary(Expression, BinaryOperator, Expression) - - /// Function. - case function(Function) - - /// Group of expressions. - case expressions([Expression]) - - /// `CAST ( AS )` - case cast(Expression, typeName: TypeName) - - /// ` COLLATE ` - case collate(Expression, String) - - /// `(SELECT ...)` - case subSelect(Select) - } -} - -// MARK: Swift Literals - -extension SQLiteQuery.Expression: ExpressibleByStringLiteral { - /// See `ExpressibleByStringLiteral`. - public init(stringLiteral value: String) { - self = .column(.init(stringLiteral: value)) - } -} - -extension SQLiteQuery.Expression: ExpressibleByArrayLiteral { - /// See `ExpressibleByArrayLiteral`. - public init(arrayLiteral elements: SQLiteQuery.Expression...) { - self = .expressions(elements) - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - internal func serialize(_ expr: SQLiteQuery.Expression, _ binds: inout [SQLiteData]) -> String { - switch expr { - case .data(let value): - binds.append(value) - return "?" - case .literal(let literal): return serialize(literal) - case .binary(let lhs, let op, let rhs): - switch (op, rhs) { - case (.equal, .literal(let l)) where l == .null: return serialize(lhs, &binds) + " IS NULL" - case (.notEqual, .literal(let l)) where l == .null: return serialize(lhs, &binds) + " IS NOT NULL" - default: return serialize(lhs, &binds) + " " + serialize(op) + " " + serialize(rhs, &binds) - } - case .column(let col): return serialize(col) - case .expressions(let exprs): - return "(" + exprs.map { serialize($0, &binds) }.joined(separator: ", ") + ")" - case .function(let function): - return serialize(function, &binds) - case .subSelect(let select): return "(" + serialize(select, &binds) + ")" - case .collate(let expr, let collate): - return serialize(expr, &binds) + " COLLATE " + collate - case .cast(let expr, let typeName): - return "CAST (" + serialize(expr, &binds) + " AS " + serialize(typeName) + ")" - } - } - - func serialize(_ function: SQLiteQuery.Expression.Function, _ binds: inout [SQLiteData]) -> String { - if let parameters = function.parameters { - return function.name + "(" + serialize(parameters, &binds) + ")" - } else { - return function.name - } - } - - func serialize(_ parameters: SQLiteQuery.Expression.Function.Parameters, _ binds: inout [SQLiteData]) -> String { - switch parameters { - case .all: return "*" - case .expressions(let distinct, let exprs): - var sql: [String] = [] - if distinct { - sql.append("DISTINCT") - } - sql.append(exprs.map { serialize($0, &binds) }.joined(separator: ", ")) - return sql.joined(separator: " ") - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift deleted file mode 100644 index 29bb60f..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+BinaryOperator.swift +++ /dev/null @@ -1,132 +0,0 @@ -extension SQLiteQuery.Expression { - public enum BinaryOperator { - /// `||` - case concatenate - - /// `*` - case multiply - - /// `/` - case divide - - /// `%` - case modulo - - /// `+` - case add - - /// `-` - case subtract - - /// `<<` - case bitwiseShiftLeft - - /// `>>` - case bitwiseShiftRight - - /// `&` - case bitwiseAnd - - /// `|` - case bitwiseOr - - /// `<` - case lessThan - - /// `<=` - case lessThanOrEqual - - /// `>` - case greaterThan - - /// `>=` - case greaterThanOrEqual - - /// `=` or `==` - case equal - - /// `!=` or `<>` - case notEqual - - /// `AND` - case and - - /// `OR` - case or - - /// `IS` - case `is` - - /// `IS NOT` - case isNot - - /// `IN` - case `in` - - /// `NOT IN` - case notIn - - /// `LIKE` - case like - - /// `NOT LIKE` - case notLike - - /// `GLOB` - case glob - - /// `NOT GLOB` - case notGlob - - /// `MATCH` - case match - - /// `NOT MATCH` - case notMatch - - /// `REGEXP` - case regexp - - /// `NOT REGEXP` - case notRegexp - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - func serialize(_ expr: SQLiteQuery.Expression.BinaryOperator) -> String { - switch expr { - case .add: return "+" - case .bitwiseAnd: return "&" - case .bitwiseOr: return "|" - case .bitwiseShiftLeft: return "<<" - case .bitwiseShiftRight: return ">>" - case .concatenate: return "||" - case .divide: return "/" - case .equal: return "=" - case .greaterThan: return ">" - case .greaterThanOrEqual: return ">=" - case .lessThan: return "<" - case .lessThanOrEqual: return "<=" - case .modulo: return "%" - case .multiply: return "*" - case .notEqual: return "!=" - case .subtract: return "-" - case .and: return "AND" - case .or: return "OR" - case .in: return "IN" - case .notIn: return "NOT IN" - case .is: return "IS" - case .isNot: return "IS NOT" - case .like: return "LIKE" - case .glob: return "GLOB" - case .match: return "MATCH" - case .regexp: return "REGEXP" - case .notLike: return "NOT LIKE" - case .notGlob: return "NOT GLOB" - case .notMatch: return "NOT MATCH" - case .notRegexp: return "NOT REGEXP" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift deleted file mode 100644 index ed32f59..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Function.swift +++ /dev/null @@ -1,16 +0,0 @@ -extension SQLiteQuery.Expression { - public struct Function { - public enum Parameters { - case all - case expressions(distinct: Bool, [SQLiteQuery.Expression]) - } - - public var name: String - public var parameters: Parameters? - - public init(name: String, parameters: Parameters? = nil) { - self.name = name - self.parameters = parameters - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift index 0dafb89..ebd2950 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift @@ -1,51 +1,51 @@ -extension SQLiteQuery.Expression { - public enum Literal: Equatable { - case numeric(String) - case string(String) - case blob(Data) - case bool(Bool) - case null - case currentTime - case currentDate - case currentTimestamp - } -} - -extension SQLiteQuery.Expression.Literal: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self = .string(value) - } -} - -extension SQLiteQuery.Expression.Literal: ExpressibleByIntegerLiteral { - public init(integerLiteral value: Int) { - self = .numeric(value.description) - } -} - -extension SQLiteQuery.Expression.Literal: ExpressibleByFloatLiteral { - public init(floatLiteral value: Double) { - self = .numeric(value.description) - } -} - -extension SQLiteQuery.Expression.Literal: ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = .bool(value) - } -} - -extension SQLiteSerializer { - func serialize(_ literal: SQLiteQuery.Expression.Literal) -> String { - switch literal { - case .numeric(let string): return string - case .string(let string): return "'" + string + "'" - case .blob(let blob): return "0x" + blob.hexEncodedString() - case .null: return "NULL" - case .bool(let bool): return bool.description.uppercased() - case .currentTime: return "CURRENT_TIME" - case .currentDate: return "CURRENT_DATE" - case .currentTimestamp: return "CURRENT_TIMESTAMP" - } - } -} +//extension SQLiteQuery.Expression { +// public enum Literal: Equatable { +// case numeric(String) +// case string(String) +// case blob(Data) +// case bool(Bool) +// case null +// case currentTime +// case currentDate +// case currentTimestamp +// } +//} +// +//extension SQLiteQuery.Expression.Literal: ExpressibleByStringLiteral { +// public init(stringLiteral value: String) { +// self = .string(value) +// } +//} +// +//extension SQLiteQuery.Expression.Literal: ExpressibleByIntegerLiteral { +// public init(integerLiteral value: Int) { +// self = .numeric(value.description) +// } +//} +// +//extension SQLiteQuery.Expression.Literal: ExpressibleByFloatLiteral { +// public init(floatLiteral value: Double) { +// self = .numeric(value.description) +// } +//} +// +//extension SQLiteQuery.Expression.Literal: ExpressibleByBooleanLiteral { +// public init(booleanLiteral value: Bool) { +// self = .bool(value) +// } +//} +// +//extension SQLiteSerializer { +// func serialize(_ literal: SQLiteQuery.Expression.Literal) -> String { +// switch literal { +// case .numeric(let string): return string +// case .string(let string): return "'" + string + "'" +// case .blob(let blob): return "0x" + blob.hexEncodedString() +// case .null: return "NULL" +// case .bool(let bool): return bool.description.uppercased() +// case .currentTime: return "CURRENT_TIME" +// case .currentDate: return "CURRENT_DATE" +// case .currentTimestamp: return "CURRENT_TIMESTAMP" +// } +// } +//} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift index ee5743e..b603762 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift @@ -1,162 +1,139 @@ infix operator ~~ infix operator !~ - -// MARK: Expression to Expression operators - -public func < (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .lessThan, rhs) -} - -public func <= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .lessThanOrEqual, rhs) -} - -public func > (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .greaterThan, rhs) -} - -public func >= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .greaterThanOrEqual, rhs) -} - -public func == (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .equal, rhs) -} - -public func != (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .notEqual, rhs) -} - -public func ~~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { - return .binary(lhs, .in, .expressions(rhs)) -} - -public func !~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { - return .binary(lhs, .notIn, .expressions(rhs)) -} - -// MARK: KeyPath to Value operators - -public func < (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .lessThan, .bind(rhs)) -} - -public func <= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .bind(rhs)) -} - -public func > (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .greaterThan, .bind(rhs)) -} - -public func >= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .bind(rhs)) -} - -public func == (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .equal, .bind(rhs)) -} - -public func != (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .notEqual, .bind(rhs)) -} - -public func ~~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .in, .bind(rhs)) -} - -public func !~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression - where Table: SQLiteTable, Value: Encodable -{ - return try .binary(.column(lhs.sqliteColumnName), .notIn, .bind(rhs)) -} - -// MARK: KeyPath to KeyPath operators - -public func < (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .lessThan, .column(rhs.sqliteColumnName)) -} - -public func <= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .column(rhs.sqliteColumnName)) -} - -public func > (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .greaterThan, .column(rhs.sqliteColumnName)) -} - -public func >= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .column(rhs.sqliteColumnName)) -} - -public func == (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .equal, .column(rhs.sqliteColumnName)) -} - -public func != (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .notEqual, .column(rhs.sqliteColumnName)) -} - -public func ~~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .in, .column(rhs.sqliteColumnName)) -} - -public func !~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression - where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -{ - return .binary(.column(lhs.sqliteColumnName), .notIn, .column(rhs.sqliteColumnName)) -} - -// MARK: AND / OR operators - -public func && (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .and, rhs) -} - -public func || (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - return .binary(lhs, .or, rhs) -} - -public func &= (_ lhs: inout SQLiteQuery.Expression?, _ rhs: SQLiteQuery.Expression) { - if let l = lhs { - lhs = l && rhs - } else { - lhs = rhs - } -} - -public func |= (_ lhs: inout SQLiteQuery.Expression?, _ rhs: SQLiteQuery.Expression) { - if let l = lhs { - lhs = l || rhs - } else { - lhs = rhs - } -} +// +//// MARK: Expression to Expression operators +// +//public func < (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { +// return .binary(lhs, .lessThan, rhs) +//} +// +//public func <= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { +// return .binary(lhs, .lessThanOrEqual, rhs) +//} +// +//public func > (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { +// return .binary(lhs, .greaterThan, rhs) +//} +// +//public func >= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { +// return .binary(lhs, .greaterThanOrEqual, rhs) +//} +// +//public func == (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { +// return .binary(lhs, .equal, rhs) +//} +// +//public func != (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { +// return .binary(lhs, .notEqual, rhs) +//} +// +//public func ~~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { +// return .binary(lhs, .in, .expressions(rhs)) +//} +// +//public func !~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { +// return .binary(lhs, .notIn, .expressions(rhs)) +//} +// +//// MARK: KeyPath to Value operators +// +//public func < (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .lessThan, .bind(rhs)) +//} +// +//public func <= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .bind(rhs)) +//} +// +//public func > (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .greaterThan, .bind(rhs)) +//} +// +//public func >= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .bind(rhs)) +//} +// +//public func == (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .equal, .bind(rhs)) +//} +// +//public func != (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .notEqual, .bind(rhs)) +//} +// +//public func ~~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .in, .bind(rhs)) +//} +// +//public func !~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression +// where Table: SQLiteTable, Value: Encodable +//{ +// return try .binary(.column(lhs.sqliteColumnName), .notIn, .bind(rhs)) +//} +// +//// MARK: KeyPath to KeyPath operators +// +//public func < (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .lessThan, .column(rhs.sqliteColumnName)) +//} +// +//public func <= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .column(rhs.sqliteColumnName)) +//} +// +//public func > (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .greaterThan, .column(rhs.sqliteColumnName)) +//} +// +//public func >= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .column(rhs.sqliteColumnName)) +//} +// +//public func == (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .equal, .column(rhs.sqliteColumnName)) +//} +// +//public func != (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .notEqual, .column(rhs.sqliteColumnName)) +//} +// +//public func ~~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .in, .column(rhs.sqliteColumnName)) +//} +// +//public func !~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression +// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable +//{ +// return .binary(.column(lhs.sqliteColumnName), .notIn, .column(rhs.sqliteColumnName)) +//} +// +//// MARK: AND / OR operators +// diff --git a/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift b/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift deleted file mode 100644 index cd8d76e..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+ForeignKey.swift +++ /dev/null @@ -1,94 +0,0 @@ -extension SQLiteQuery { - public struct ForeignKey { - public enum Action { - case setNull - case setDefault - case cascade - case restrict - case noAction - } - - public enum Deferrence { - case not - case immediate - } - - public struct Reference { - public var foreignTable: TableName - public var foreignColumns: [Name] - public var onDelete: Action? - public var onUpdate: Action? - public var deferrence: Deferrence? - - public init( - foreignTable: TableName, - foreignColumns: [Name], - onDelete: Action? = nil, - onUpdate: Action? = nil, - deferrence: Deferrence? = nil - ) { - self.foreignTable = foreignTable - self.foreignColumns = foreignColumns - self.onDelete = onDelete - self.onUpdate = onUpdate - self.deferrence = deferrence - } - } - - public var columns: [Name] - public var reference: Reference - - public init(columns: [Name], reference: Reference) { - self.columns = columns - self.reference = reference - } - } -} - -extension SQLiteSerializer { - func serialize(_ foreignKey: SQLiteQuery.ForeignKey) -> String { - var sql: [String] = [] - sql.append("FOREIGN KEY") - sql.append(serialize(foreignKey.columns)) - sql.append(serialize(foreignKey.reference)) - return sql.joined(separator: " ") - } - - func serialize(_ foreignKey: SQLiteQuery.ForeignKey.Reference) -> String { - var sql: [String] = [] - sql.append("REFERENCES") - sql.append(serialize(foreignKey.foreignTable)) - if !foreignKey.foreignColumns.isEmpty { - sql.append(serialize(foreignKey.foreignColumns)) - } - if let onDelete = foreignKey.onDelete { - sql.append("ON DELETE") - sql.append(serialize(onDelete)) - } - if let onUpdate = foreignKey.onUpdate { - sql.append("ON UPDATE") - sql.append(serialize(onUpdate)) - } - if let deferrence = foreignKey.deferrence { - sql.append(serialize(deferrence)) - } - return sql.joined(separator: " ") - } - - func serialize(_ action: SQLiteQuery.ForeignKey.Action) -> String { - switch action { - case .cascade: return "CASCADE" - case .noAction: return "NO ACTION" - case .restrict: return "RESTRICT" - case .setDefault: return "SET DEFAULT" - case .setNull: return "SET NULL" - } - } - - func serialize(_ deferrence: SQLiteQuery.ForeignKey.Deferrence) -> String { - switch deferrence { - case .not: return "NOT DEFERRABLE" - case .immediate: return "DEFERRABLE INITIALLY IMMEDIATE" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+IndexedColumn.swift b/Sources/SQLite/Query/SQLiteQuery+IndexedColumn.swift deleted file mode 100644 index ee85874..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+IndexedColumn.swift +++ /dev/null @@ -1,51 +0,0 @@ -extension SQLiteQuery { - public struct IndexedColumns { - public var columns: [IndexedColumn] - public var predicate: Expression? - } - - - public struct IndexedColumn { - public enum Value { - case column(ColumnName) - case expression(Expression) - } - public var value: Value - public var collate: String? - public var direction: Direction? - - public init(value: Value, collate: String? = nil, direction: Direction? = nil) { - self.value = value - self.collate = collate - self.direction = direction - } - } -} - -extension SQLiteSerializer { - func serialize(_ indexed: SQLiteQuery.IndexedColumns, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("(" + indexed.columns.map { serialize($0, &binds) }.joined(separator: ", ") + ")") - if let predicate = indexed.predicate { - sql.append("WHERE") - sql.append(serialize(predicate, &binds)) - } - return sql.joined(separator: " ") - } - - func serialize(_ column: SQLiteQuery.IndexedColumn, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch column.value { - case .column(let string): sql.append(serialize(string)) - case .expression(let expr): sql.append(serialize(expr, &binds)) - } - if let collate = column.collate { - sql.append("COLLATE") - sql.append(collate) - } - if let direction = column.direction { - sql.append(serialize(direction)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Insert.swift b/Sources/SQLite/Query/SQLiteQuery+Insert.swift deleted file mode 100644 index 6e6f1f9..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Insert.swift +++ /dev/null @@ -1,94 +0,0 @@ -extension SQLiteQuery { - public struct Insert { - public enum Values { - case values([[Expression]]) - case select(Select) - case defaults - } - - public struct UpsertClause { - public enum Action { - case nothing - case update(SetValues) - } - - public var indexedColumns: IndexedColumns? - public var action: Action - } - - public var conflictResolution: ConflictResolution? - public var table: AliasableTableName - public var columns: [Name] - public var values: Values - public var upsert: UpsertClause? - - public init( - conflictResolution: ConflictResolution? = nil, - table: AliasableTableName, - columns: [Name] = [], - values: Values = .defaults, - upsert: UpsertClause? = nil - ) { - self.conflictResolution = conflictResolution - self.table = table - self.columns = columns - self.values = values - self.upsert = upsert - } - } -} - - -extension SQLiteSerializer { - func serialize(_ insert: SQLiteQuery.Insert, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("INSERT") - if let conflictResolution = insert.conflictResolution { - sql.append("OR") - sql.append(serialize(conflictResolution)) - } - sql.append("INTO") - sql.append(serialize(insert.table)) - if !insert.columns.isEmpty { - sql.append(serialize(insert.columns)) - } - sql.append(serialize(insert.values, &binds)) - if let upsert = insert.upsert { - sql.append(serialize(upsert, &binds)) - } - return sql.joined(separator: " ") - } - - func serialize(_ values: SQLiteQuery.Insert.Values, _ binds: inout [SQLiteData]) -> String { - switch values { - case .defaults: return "DEFAULT VALUES" - case .select(let select): return serialize(select, &binds) - case .values(let values): - return "VALUES " + values.map { - return "(" + $0.map { serialize($0, &binds) }.joined(separator: ", ") + ")" - }.joined(separator: ", ") - } - } - - func serialize(_ upsert: SQLiteQuery.Insert.UpsertClause, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("ON CONFLICT") - if let indexed = upsert.indexedColumns { - sql.append(serialize(indexed, &binds)) - } - sql.append("DO") - sql.append(serialize(upsert.action, &binds)) - return sql.joined(separator: " ") - } - - func serialize(_ action: SQLiteQuery.Insert.UpsertClause.Action, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch action { - case .nothing: sql.append("NOTHING") - case .update(let setValues): - sql.append("UPDATE") - sql.append(serialize(setValues, &binds)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift deleted file mode 100644 index ff88300..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+InsertBuilder.swift +++ /dev/null @@ -1,56 +0,0 @@ -extension SQLiteQuery { - public final class InsertBuilder { - public var insert: Insert - public let connection: SQLiteConnection - - init(table: AliasableTableName, on connection: SQLiteConnection) { - self.insert = .init(table: table) - self.connection = connection - } - - public func or(_ conflictResolution: SQLiteQuery.ConflictResolution) -> Self { - insert.conflictResolution = conflictResolution - return self - } - - public func defaults() throws -> Self { - insert.values = .defaults - return self - } - - public func from(_ select: (SelectBuilder) -> ()) throws -> Self { - let builder = connection.select() - select(builder) - insert.values = .select(builder.select) - return self - } - - public func value(_ value: E) throws -> Self - where E: Encodable - { - try values([value]) - return self - } - - public func values(_ values: [E]) throws -> Self - where E: Encodable - { - let values = try values.map { try SQLiteQueryEncoder().encode($0) } - insert.columns = values[0].keys.map { .init($0) } - insert.values = .values(values.map { .init($0.values) }) - return self - } - - public func run() -> Future { - return connection.query(.insert(insert)).transform(to: ()) - } - } -} - -extension SQLiteConnection { - public func insert
(into table: Table.Type) -> SQLiteQuery.InsertBuilder - where Table: SQLiteTable - { - return .init(table: .init(table: Table.sqliteTableName), on: self) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Join.swift b/Sources/SQLite/Query/SQLiteQuery+Join.swift index 7dc34e9..b292e0a 100644 --- a/Sources/SQLite/Query/SQLiteQuery+Join.swift +++ b/Sources/SQLite/Query/SQLiteQuery+Join.swift @@ -1,83 +1,83 @@ -extension SQLiteQuery { - public struct JoinClause { - public struct Join { - public enum Operator { - case inner - case outer - case cross - - } - - public enum Constraint { - case condition(Expression) - case using([Name]) - } - - public var natural: Bool - public var op: Operator? - public var table: TableOrSubquery - public var constraint: Constraint? - - public init(natural: Bool = false, _ op: Operator? = nil, table: TableOrSubquery, constraint: Constraint? = nil) { - self.natural = natural - self.op = op - self.table = table - self.constraint = constraint - } - } - public var table: TableOrSubquery - public var joins: [Join] - - public init(table: TableOrSubquery, joins: [Join] = []) { - self.table = table - self.joins = joins - } - } -} - -extension SQLiteSerializer { - func serialize(_ join: SQLiteQuery.JoinClause, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append(serialize(join.table, &binds)) - sql += join.joins.map { serialize($0, &binds) } - return sql.joined(separator: " ") - } - - func serialize(_ join: SQLiteQuery.JoinClause.Join, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - if join.natural { - sql.append("NATURAL") - } - if let op = join.op { - sql.append(serialize(op)) - sql.append("JOIN") - } - sql.append(serialize(join.table, &binds)) - if let constraint = join.constraint { - sql.append(serialize(constraint, &binds)) - } - return sql.joined(separator: " ") - - } - - func serialize(_ constraint: SQLiteQuery.JoinClause.Join.Constraint, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch constraint { - case .condition(let expr): - sql.append("ON") - sql.append(serialize(expr, &binds)) - case .using(let columns): - sql.append("USING") - sql.append(serialize(columns)) - } - return sql.joined(separator: " ") - } - - func serialize(_ op: SQLiteQuery.JoinClause.Join.Operator) -> String { - switch op { - case .outer: return "LEFT OUTER" - case .inner: return "INNER" - case .cross: return "CROSS" - } - } -} +//extension SQLiteQuery { +// public struct JoinClause { +// public struct Join { +// public enum Operator { +// case inner +// case outer +// case cross +// +// } +// +// public enum Constraint { +// case condition(Expression) +// case using([Name]) +// } +// +// public var natural: Bool +// public var op: Operator? +// public var table: TableOrSubquery +// public var constraint: Constraint? +// +// public init(natural: Bool = false, _ op: Operator? = nil, table: TableOrSubquery, constraint: Constraint? = nil) { +// self.natural = natural +// self.op = op +// self.table = table +// self.constraint = constraint +// } +// } +// public var table: TableOrSubquery +// public var joins: [Join] +// +// public init(table: TableOrSubquery, joins: [Join] = []) { +// self.table = table +// self.joins = joins +// } +// } +//} +// +//extension SQLiteSerializer { +// func serialize(_ join: SQLiteQuery.JoinClause, _ binds: inout [SQLiteData]) -> String { +// var sql: [String] = [] +// sql.append(serialize(join.table, &binds)) +// sql += join.joins.map { serialize($0, &binds) } +// return sql.joined(separator: " ") +// } +// +// func serialize(_ join: SQLiteQuery.JoinClause.Join, _ binds: inout [SQLiteData]) -> String { +// var sql: [String] = [] +// if join.natural { +// sql.append("NATURAL") +// } +// if let op = join.op { +// sql.append(serialize(op)) +// sql.append("JOIN") +// } +// sql.append(serialize(join.table, &binds)) +// if let constraint = join.constraint { +// sql.append(serialize(constraint, &binds)) +// } +// return sql.joined(separator: " ") +// +// } +// +// func serialize(_ constraint: SQLiteQuery.JoinClause.Join.Constraint, _ binds: inout [SQLiteData]) -> String { +// var sql: [String] = [] +// switch constraint { +// case .condition(let expr): +// sql.append("ON") +// sql.append(serialize(expr, &binds)) +// case .using(let columns): +// sql.append("USING") +// sql.append(serialize(columns)) +// } +// return sql.joined(separator: " ") +// } +// +// func serialize(_ op: SQLiteQuery.JoinClause.Join.Operator) -> String { +// switch op { +// case .outer: return "LEFT OUTER" +// case .inner: return "INNER" +// case .cross: return "CROSS" +// } +// } +//} diff --git a/Sources/SQLite/Query/SQLiteQuery+Name.swift b/Sources/SQLite/Query/SQLiteQuery+Name.swift deleted file mode 100644 index 989c67a..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Name.swift +++ /dev/null @@ -1,36 +0,0 @@ -extension SQLiteQuery { - /// Represents a SQLite column, schema, table, or constraint name. - public struct Name { - /// String value. - public var string: String - - /// Creates a new `Name`. - /// - /// - parameters: - /// - string: String value. - public init(_ string: String) { - self.string = string - } - } -} - -// MARK: String Literal - -extension SQLiteQuery.Name: ExpressibleByStringLiteral { - /// See `ExpressibleByStringLiteral`. - public init(stringLiteral value: String) { - self.init(value) - } -} - -// MARK: Serialize - -extension SQLiteSerializer { - func serialize(_ columns: [SQLiteQuery.Name]) -> String { - return "(" + columns.map(serialize).joined(separator: ", ") + ")" - } - - func serialize(_ name: SQLiteQuery.Name) -> String { - return escapeString(name.string) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Select.swift b/Sources/SQLite/Query/SQLiteQuery+Select.swift deleted file mode 100644 index 9db0aa6..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Select.swift +++ /dev/null @@ -1,138 +0,0 @@ -extension SQLiteQuery { - public struct Select { - public enum Distinct { - case distinct - case all - } - - public enum ResultColumn { - /// `*` and `table.*` - case all(String?) - /// `md5(a) AS hash` - case expression(Expression, alias: String?) - } - - public struct WithClause { - public struct CommonTableExpression { - public var table: String - public var columns: [Name] - public var select: Select - } - - public var recursive: Bool - public var expressions: [CommonTableExpression] - } - - - public var with: WithClause? - public var distinct: Distinct? - public var columns: [ResultColumn] - public var tables: [TableOrSubquery] - public var predicate: Expression? - public var orderBy: [OrderBy] - - public init( - with: WithClause? = nil, - distinct: Distinct? = nil, - columns: [ResultColumn] = [], - tables: [TableOrSubquery] = [], - predicate: Expression? = nil, - orderBy: [OrderBy] = [] - ) { - self.with = with - self.distinct = distinct - self.columns = columns - self.tables = tables - self.predicate = predicate - self.orderBy = orderBy - } - } -} - -extension SQLiteQuery { - public struct OrderBy { - public var expression: Expression - public var direction: Direction - - public init(expression: Expression, direction: Direction) { - self.expression = expression - self.direction = direction - } - } -} - -extension SQLiteSerializer { - func serialize(_ orderBy: SQLiteQuery.OrderBy, _ binds: inout [SQLiteData]) -> String { - return serialize(orderBy.expression, &binds) + " " + serialize(orderBy.direction) - } -} - -extension SQLiteSerializer { - func serialize(_ select: SQLiteQuery.Select, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("SELECT") - if let with = select.with { - sql.append(serialize(with, &binds)) - } - if let distinct = select.distinct { - sql.append(serialize(distinct)) - } - sql.append(select.columns.map { serialize($0, &binds) }.joined(separator: ", ")) - if !select.tables.isEmpty { - sql.append("FROM") - sql.append(select.tables.map { serialize($0, &binds) }.joined(separator: ", ")) - } - if let predicate = select.predicate { - sql.append("WHERE") - sql.append(serialize(predicate, &binds)) - } - if !select.orderBy.isEmpty { - sql.append("ORDER BY") - sql.append(select.orderBy.map { serialize($0, &binds) }.joined(separator: ", ")) - } - return sql.joined(separator: " ") - } - - func serialize(_ distinct: SQLiteQuery.Select.Distinct) -> String { - switch distinct { - case .all: return "ALL" - case .distinct: return "DISTINCT" - } - } - - func serialize(_ distinct: SQLiteQuery.Select.ResultColumn, _ binds: inout [SQLiteData]) -> String { - switch distinct { - case .all(let table): - if let table = table { - return escapeString(table) + ".*" - } else { - return "*" - } - case .expression(let expr, let alias): - if let alias = alias { - return serialize(expr, &binds) + " AS " + escapeString(alias) - } else { - return serialize(expr, &binds) - } - } - } - - func serialize(_ with: SQLiteQuery.Select.WithClause, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("WITH") - if with.recursive { - sql.append("RECURSIVE") - } - sql.append(with.expressions.map { serialize($0, &binds) }.joined(separator: ", ")) - return sql.joined(separator: " ") - } - - func serialize(_ cte: SQLiteQuery.Select.WithClause.CommonTableExpression, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append(escapeString(cte.table)) - sql.append(serialize(cte.columns)) - sql.append("AS") - sql.append("(" + serialize(cte.select, &binds) + ")") - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift deleted file mode 100644 index a886f1f..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+SelectBuilder.swift +++ /dev/null @@ -1,126 +0,0 @@ -extension SQLiteQuery { - public final class SelectBuilder: SQLitePredicateBuilder { - public var select: Select - - public var predicate: SQLiteQuery.Expression? { - get { return select.predicate } - set { select.predicate = newValue } - } - - public let connection: SQLiteConnection - - init(on connection: SQLiteConnection) { - self.select = .init() - self.connection = connection - } - - public func column(function: String, as alias: String? = nil) -> Self { - return column(function: function, .expressions(distinct: false, []), as: alias) - } - - public func column(function: String, _ parameters: Expression.Function.Parameters?, as alias: String? = nil) -> Self { - return column(expression: .function(.init(name: function, parameters: parameters)), as: alias) - } - - public func column(expression: Expression, as alias: String? = nil) -> Self { - return column(.expression(expression, alias: alias)) - } - - public func all(table: String? = nil) -> Self { - return column(.all(table)) - } - - public func column(_ column: Select.ResultColumn) -> Self { - select.columns.append(column) - return self - } - - public func from(_ tables: TableOrSubquery...) -> Self { - select.tables += tables - return self - } - - public func from
(_ table: Table.Type) -> Self - where Table: SQLiteTable - { - select.tables.append(.table(.init(table:.init(table: Table.sqliteTableName)))) - return self - } - - public func join
(_ table: Table.Type, on expr: Expression) -> Self - where Table: SQLiteTable - { - switch select.tables.count { - case 0: fatalError("Must select from a atable before joining.") - default: - let join = SQLiteQuery.JoinClause.init( - table: select.tables[0], - joins: [ - SQLiteQuery.JoinClause.Join( - natural: false, - .inner, - table: .table(.init(table:.init(table: Table.sqliteTableName))), - constraint: .condition(expr) - ) - ] - ) - select.tables[0] = .joinClause(join) - } - return self - } - - public func run(decoding type: D.Type) -> Future<[D]> - where D: Decodable - { - return run { try SQLiteRowDecoder().decode(D.self, from: $0) } - } - - public func run(_ convert: @escaping ([SQLiteColumn: SQLiteData]) throws -> (T)) -> Future<[T]> { - return run().map { try $0.map { try convert($0) } } - } - - - public func run() -> Future<[[SQLiteColumn: SQLiteData]]> { - return connection.query(.select(select)) - } - } -} - -extension Dictionary where Key == SQLiteColumn, Value == SQLiteData { - public func decode
(_ type: Table.Type) throws -> Table where Table: SQLiteTable { - return try decode(Table.self, from: Table.sqliteTableName.name.string) - } - - public func decode(_ type: D.Type, from table: String) throws -> D where D: Decodable { - return try SQLiteRowDecoder().decode(D.self, from: self, table: table) - } -} - -public protocol SQLiteTable: Codable, Reflectable { - static var sqliteTableName: SQLiteQuery.TableName { get } -} - -extension SQLiteTable { - public static var sqliteTableName: SQLiteQuery.TableName { - return .init(stringLiteral: "\(Self.self)") - } -} - -extension KeyPath where Root: SQLiteTable { - public var sqliteColumnName: SQLiteQuery.ColumnName { - guard let property = try! Root.reflectProperty(forKey: self) else { - fatalError("Could not reflect property of type \(Value.self) on \(Root.self): \(self)") - } - return .init( - table: .init(Root.sqliteTableName), - name: .init(property.path[0]) - ) - } -} - - -extension SQLiteConnection { - public func select() -> SQLiteQuery.SelectBuilder { - return .init(on: self) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+SetValues.swift b/Sources/SQLite/Query/SQLiteQuery+SetValues.swift deleted file mode 100644 index 930d0ec..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+SetValues.swift +++ /dev/null @@ -1,45 +0,0 @@ -extension SQLiteQuery { - public struct SetValues { - public struct ColumnGroup { - public var columns: [Name] - public var value: Expression - - public init(columns: [Name], value: Expression) { - self.columns = columns - self.value = value - } - } - - public var columns: [ColumnGroup] - public var predicate: Expression? - - public init(columns: [ColumnGroup], predicate: Expression? = nil) { - self.columns = columns - self.predicate = predicate - } - } -} - -extension SQLiteSerializer { - func serialize(_ update: SQLiteQuery.SetValues, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("SET") - sql.append(update.columns.map { serialize($0, &binds) }.joined(separator: ", ")) - if let predicate = update.predicate { - sql.append("WHERE") - sql.append(serialize(predicate, &binds)) - } - return sql.joined(separator: " ") - } - - func serialize(_ update: SQLiteQuery.SetValues.ColumnGroup, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch update.columns.count { - case 1: sql.append(serialize(update.columns[0])) - default: sql.append(serialize(update.columns)) - } - sql.append("=") - sql.append(serialize(update.value, &binds)) - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+TableConstraint.swift b/Sources/SQLite/Query/SQLiteQuery+TableConstraint.swift deleted file mode 100644 index 7ca719f..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+TableConstraint.swift +++ /dev/null @@ -1,69 +0,0 @@ -extension SQLiteQuery { - public struct TableConstraint { - public struct UniqueOrPrimaryKey { - public var columns: [IndexedColumn] - public var conflictResolution: ConflictResolution? - - public init(columns: [IndexedColumn], conflictResolution: ConflictResolution? = nil) { - self.columns = columns - self.conflictResolution = conflictResolution - } - } - - public enum Value { - case primaryKey(UniqueOrPrimaryKey) - case unique(UniqueOrPrimaryKey) - case check(Expression) - case foreignKey(ForeignKey) - } - - public var name: String? - public var value: Value - public init (name: String? = nil, value: Value) { - self.name = name - self.value = value - } - } -} - -extension SQLiteSerializer { - func serialize(_ constraint: SQLiteQuery.TableConstraint, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - if let name = constraint.name { - sql.append("CONSTRAINT") - sql.append(escapeString(name)) - } - sql.append(serialize(constraint.value, &binds)) - return sql.joined(separator: " ") - } - - func serialize(_ value: SQLiteQuery.TableConstraint.Value, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - switch value { - case .primaryKey(let primaryKey): - sql.append("PRIMARY KEY") - sql.append( - "(" + primaryKey.columns.map { serialize($0, &binds) }.joined(separator: ", ") + ")" - ) - if let conflictResolution = primaryKey.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - case .unique(let unique): - sql.append("UNIQUE") - sql.append( - "(" + unique.columns.map { serialize($0, &binds) }.joined(separator: ", ") + ")" - ) - if let conflictResolution = unique.conflictResolution { - sql.append("ON CONFLICT") - sql.append(serialize(conflictResolution)) - } - case .check(let expr): - sql.append("CHECK") - sql.append("(" + serialize(expr, &binds) + ")") - case .foreignKey(let foreignKey): - sql.append(serialize(foreignKey)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+TableName.swift b/Sources/SQLite/Query/SQLiteQuery+TableName.swift deleted file mode 100644 index 7438cb0..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+TableName.swift +++ /dev/null @@ -1,90 +0,0 @@ -extension SQLiteQuery { - public struct TableName { - public var schema: Name? - public var name: Name - - public init(schema: Name? = nil, name: Name) { - self.schema = schema - self.name = name - } - } - - public struct AliasableTableName { - public var table: TableName - public var alias: String? - - public init(table: TableName, alias: String? = nil) { - self.table = table - self.alias = alias - } - } - - - public struct QualifiedTableName { - public enum Indexing { - case index(name: String) - case noIndex - } - - public var table: AliasableTableName - public var indexing: Indexing? - - public init(table: AliasableTableName, indexing: Indexing? = nil) { - self.table = table - self.indexing = indexing - } - } -} - -extension SQLiteQuery.TableName: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self.init(name: .init(value)) - } -} - -extension SQLiteQuery.AliasableTableName: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self.init(table: .init(stringLiteral: value)) - } -} - -extension SQLiteQuery.QualifiedTableName: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self.init(table: .init(stringLiteral: value)) - } -} - -extension SQLiteSerializer { - func serialize(_ table: SQLiteQuery.TableName) -> String { - if let schema = table.schema { - return serialize(schema) + "." + serialize(table.name) - } else { - return serialize(table.name) - } - } - - func serialize(_ table: SQLiteQuery.AliasableTableName) -> String { - var sql: [String] = [] - sql.append(serialize(table.table)) - if let alias = table.alias { - sql.append("AS") - sql.append(escapeString(alias)) - } - return sql.joined(separator: " ") - } - func serialize(_ qualifiedTable: SQLiteQuery.QualifiedTableName) -> String { - var sql: [String] = [] - sql.append(serialize(qualifiedTable.table)) - if let indexing = qualifiedTable.indexing { - sql.append(serialize(indexing)) - } - return sql.joined(separator: " ") - } - - func serialize(_ qualifiedTable: SQLiteQuery.QualifiedTableName.Indexing) -> String { - switch qualifiedTable { - case .index(let name): return "INDEXED BY " + escapeString(name) - case .noIndex: return "NOT INDEXED" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+TableOrSubquery.swift b/Sources/SQLite/Query/SQLiteQuery+TableOrSubquery.swift deleted file mode 100644 index 5fd0064..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+TableOrSubquery.swift +++ /dev/null @@ -1,30 +0,0 @@ -extension SQLiteQuery { - public indirect enum TableOrSubquery { - public enum TableIndex { - case indexed(String) - case notIndexed - } - - case table(QualifiedTableName) - case tableFunction(schemaName: String?, name: String, parameters: [Expression], alias: String?) - case joinClause(JoinClause) - case tables([TableOrSubquery]) - case subQuery(Select, alias: String?) - } -} - -extension SQLiteQuery.TableOrSubquery: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self = .table(.init(stringLiteral: value)) - } -} - -extension SQLiteSerializer { - func serialize(_ table: SQLiteQuery.TableOrSubquery, _ binds: inout [SQLiteData]) -> String { - switch table { - case .table(let table): return serialize(table) - case .joinClause(let join): return serialize(join, &binds) - default: return "\(table)" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+TypeName.swift b/Sources/SQLite/Query/SQLiteQuery+TypeName.swift deleted file mode 100644 index b5779a8..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+TypeName.swift +++ /dev/null @@ -1,21 +0,0 @@ -extension SQLiteQuery { - public enum TypeName { - case text - case numeric - case integer - case real - case none - } -} - -extension SQLiteSerializer { - func serialize(_ type: SQLiteQuery.TypeName) -> String { - switch type { - case .integer: return "INTEGER" - case .none: return "NONE" - case .numeric: return "NUMERIC" - case .real: return "REAL" - case .text: return "TEXT" - } - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+Update.swift b/Sources/SQLite/Query/SQLiteQuery+Update.swift deleted file mode 100644 index 92e496f..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Update.swift +++ /dev/null @@ -1,37 +0,0 @@ -extension SQLiteQuery { - public struct Update { - public var conflictResolution: ConflictResolution? = nil - public var table: QualifiedTableName - public var values: SetValues - public var predicate: Expression? - - public init( - conflictResolution: ConflictResolution? = nil, - table: QualifiedTableName, - values: SetValues, - predicate: Expression? = nil - ) { - self.conflictResolution = conflictResolution - self.table = table - self.values = values - self.predicate = predicate - } - } -} -extension SQLiteSerializer { - func serialize(_ update: SQLiteQuery.Update, _ binds: inout [SQLiteData]) -> String { - var sql: [String] = [] - sql.append("UPDATE") - if let conflictResolution = update.conflictResolution { - sql.append("OR") - sql.append(serialize(conflictResolution)) - } - sql.append(serialize(update.table)) - sql.append(serialize(update.values, &binds)) - if let predicate = update.predicate { - sql.append("WHERE") - sql.append(serialize(predicate, &binds)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift deleted file mode 100644 index 1b29a40..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+UpdateBuilder.swift +++ /dev/null @@ -1,43 +0,0 @@ -extension SQLiteQuery { - public final class UpdateBuilder: SQLitePredicateBuilder { - public var update: Update - public let connection: SQLiteConnection - public var predicate: SQLiteQuery.Expression? { - get { return update.predicate } - set { update.predicate = newValue } - } - - init(table: QualifiedTableName, on connection: SQLiteConnection) { - self.update = .init(table: table, values: .init(columns: [])) - self.connection = connection - } - - @discardableResult - public func or(_ conflictResolution: SQLiteQuery.ConflictResolution) -> Self { - update.conflictResolution = conflictResolution - return self - } - - @discardableResult - public func set(_ value: E) throws -> Self - where E: Encodable - { - for (col, val) in try SQLiteQueryEncoder().encode(value) { - update.values.columns.append(.init(columns: [.init(col)], value: val)) - } - return self - } - - public func run() -> Future { - return connection.query(.update(update)).transform(to: ()) - } - } -} - -extension SQLiteConnection { - public func update
(_ table: Table.Type) -> SQLiteQuery.UpdateBuilder - where Table: SQLiteTable - { - return .init(table: .init(table: .init(table: Table.sqliteTableName)), on: self) - } -} diff --git a/Sources/SQLite/Query/SQLiteQuery.swift b/Sources/SQLite/Query/SQLiteQuery.swift index aefa52d..3ff08fc 100644 --- a/Sources/SQLite/Query/SQLiteQuery.swift +++ b/Sources/SQLite/Query/SQLiteQuery.swift @@ -1,33 +1,160 @@ -public enum SQLiteQuery { - case alterTable(AlterTable) - case createTable(CreateTable) - case delete(Delete) - case dropTable(DropTable) - case insert(Insert) - case select(Select) - case update(Update) - case raw(String, [SQLiteData]) +public enum SQLiteQuery: SQLQuery { + /// See `SQLQuery`. + public static func createTable(_ createTable: SQLiteCreateTable) -> SQLiteQuery { + return ._createTable(createTable) + } + + /// See `SQLQuery`. + public static func delete(_ delete: SQLiteQuery.Delete) -> SQLiteQuery { + return ._delete(delete) + } + + /// See `SQLQuery`. + public static func dropTable(_ dropTable: GenericSQLDropTable) -> SQLiteQuery { + return ._dropTable(dropTable) + } + + /// See `SQLQuery`. + public static func insert(_ insert: Insert) -> SQLiteQuery { + return ._insert(insert) + } + + /// See `SQLQuery`. + public static func select(_ select: Select) -> SQLiteQuery { + return ._select(select) + } + + /// See `SQLQuery`. + public static func update(_ update: SQLiteQuery.Update) -> SQLiteQuery { + return ._update(update) + } + + /// See `SQLQuery`. + public typealias BinaryOperator = GenericSQLBinaryOperator + + /// See `SQLQuery`. + public typealias Collation = SQLiteCollation + + /// See `SQLQuery`. + public typealias ColumnConstraintAlgorithm = GenericSQLColumnConstraintAlgorithm + + /// See `SQLQuery`. + public typealias ColumnConstraint = GenericSQLColumnConstraint + + /// See `SQLQuery`. + public typealias ColumnDefinition = GenericSQLColumnDefinition + + /// See `SQLQuery`. + public typealias ColumnIdentifier = GenericSQLColumnIdentifier + + /// See `SQLQuery`. + public typealias ConflictResolution = GenericSQLConflictResolution + + /// See `SQLQuery`. + public typealias CreateTable = SQLiteCreateTable + + /// See `SQLQuery`. + public typealias DataType = SQLiteDataType + + /// See `SQLQuery`. + public typealias Delete = GenericSQLDelete + + /// See `SQLQuery`. + public typealias Direction = GenericSQLDirection + + /// See `SQLQuery`. + public typealias Distinct = GenericSQLDistinct + + /// See `SQLQuery`. + public typealias DropTable = GenericSQLDropTable + + /// See `SQLQuery`. + public typealias Expression = GenericSQLExpression + + /// See `SQLQuery`. + public typealias ForeignKey = GenericSQLForeignKey + + /// See `SQLQuery`. + public typealias GroupBy = GenericSQLGroupBy + + /// See `SQLQuery`. + public typealias Identifier = GenericSQLIdentifier + + /// See `SQLQuery`. + public typealias Insert = GenericSQLInsert + + /// See `SQLQuery`. + public typealias Literal = GenericSQLLiteral + + /// See `SQLQuery`. + public typealias OrderBy = GenericSQLOrderBy + + /// See `SQLQuery`. + public typealias PrimaryKey = SQLitePrimaryKey + + /// See `SQLQuery`. + public typealias Select = GenericSQLSelect + + /// See `SQLQuery`. + public typealias SelectExpression = GenericSQLSelectExpression + + /// See `SQLQuery`. + public typealias TableConstraintAlgorithm = GenericSQLTableConstraintAlgorithm + + /// See `SQLQuery`. + public typealias TableConstraint = GenericSQLTableConstraint + + /// See `SQLQuery`. + public typealias TableIdentifier = GenericSQLTableIdentifier + + /// See `SQLQuery`. + public typealias Update = GenericSQLUpdate + + /// See `SQLQuery`. + public typealias RowDecoder = SQLiteRowDecoder + + /// See `SQLQuery`. +// case alterTable(AlterTable) + + /// See `SQLQuery`. + case _createTable(SQLiteCreateTable) + + /// See `SQLQuery`. + case _delete(Delete) + + /// See `SQLQuery`. + case _dropTable(DropTable) + + /// See `SQLQuery`. + case _insert(Insert) + + /// See `SQLQuery`. + case _select(Select) + + /// See `SQLQuery`. + case _update(Update) + + /// See `SQLQuery`. + case _raw(String, [Encodable]) + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._createTable(let createTable): return createTable.serialize(&binds) + case ._delete(let delete): return delete.serialize(&binds) + case ._dropTable(let dropTable): return dropTable.serialize(&binds) + case ._insert(let insert): return insert.serialize(&binds) + case ._select(let select): return select.serialize(&binds) + case ._update(let update): return update.serialize(&binds) + case ._raw(let sql, let values): + binds = values + return sql + } + } } extension SQLiteQuery: ExpressibleByStringLiteral { public init(stringLiteral value: String) { - self = .raw(value, []) - } -} - -extension SQLiteSerializer { - func serialize(_ query: SQLiteQuery, _ binds: inout [SQLiteData]) -> String { - switch query { - case .alterTable(let alterTable): return serialize(alterTable, &binds) - case .createTable(let createTable): return serialize(createTable, &binds) - case .delete(let delete): return serialize(delete, &binds) - case .dropTable(let dropTable): return serialize(dropTable) - case .select(let select): return serialize(select, &binds) - case .insert(let insert): return serialize(insert, &binds) - case .update(let update): return serialize(update, &binds) - case .raw(let string, let values): - binds = values - return string - } + self = ._raw(value, []) } } diff --git a/Sources/SQLite/Codable/SQLiteQueryExpressionRepresentable.swift b/Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift similarity index 100% rename from Sources/SQLite/Codable/SQLiteQueryExpressionRepresentable.swift rename to Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift diff --git a/Sources/SQLite/Query/SQLiteQuerySerializer.swift b/Sources/SQLite/Query/SQLiteQuerySerializer.swift deleted file mode 100644 index 89759d5..0000000 --- a/Sources/SQLite/Query/SQLiteQuerySerializer.swift +++ /dev/null @@ -1,57 +0,0 @@ -extension SQLiteQuery { - public func serialize(_ binds: inout [SQLiteData]) -> String { - return SQLiteSerializer().serialize(self, &binds) - } -} - -struct SQLiteSerializer { - init() { } - - func escapeString(_ string: String) -> String { - return "\"" + string + "\"" - } -} - -public protocol SQLitePredicateBuilder: class { - var connection: SQLiteConnection { get } - var predicate: SQLiteQuery.Expression? { get set } -} - -extension SQLitePredicateBuilder { - public func `where`(_ expressions: SQLiteQuery.Expression...) -> Self { - for expression in expressions { - self.predicate &= expression - } - return self - } - - public func orWhere(_ expressions: SQLiteQuery.Expression...) -> Self { - for expression in expressions { - self.predicate |= expression - } - return self - } - - public func `where`(_ lhs: SQLiteQuery.Expression, _ op: SQLiteQuery.Expression.BinaryOperator, _ rhs: SQLiteQuery.Expression) -> Self { - predicate &= .binary(lhs, op, rhs) - return self - } - - public func orWhere(_ lhs: SQLiteQuery.Expression, _ op: SQLiteQuery.Expression.BinaryOperator, _ rhs: SQLiteQuery.Expression) -> Self { - predicate |= .binary(lhs, op, rhs) - return self - } - - public func `where`(group: (SQLitePredicateBuilder) throws -> ()) rethrows -> Self { - let builder = SQLiteQuery.SelectBuilder(on: connection) - try group(builder) - switch (self.predicate, builder.select.predicate) { - case (.some(let a), .some(let b)): - self.predicate = a && .expressions([b]) - case (.none, .some(let b)): - self.predicate = .expressions([b]) - case (.some, .none), (.none, .none): break - } - return self - } -} diff --git a/Sources/SQLite/Query/SQLiteTable.swift b/Sources/SQLite/Query/SQLiteTable.swift new file mode 100644 index 0000000..b6e4bdd --- /dev/null +++ b/Sources/SQLite/Query/SQLiteTable.swift @@ -0,0 +1 @@ +public protocol SQLiteTable: SQLTable { } diff --git a/Sources/SQLite/Row/SQLiteData.swift b/Sources/SQLite/Row/SQLiteData.swift index 6541c6a..00bfd5a 100644 --- a/Sources/SQLite/Row/SQLiteData.swift +++ b/Sources/SQLite/Row/SQLiteData.swift @@ -1,9 +1,13 @@ -public enum SQLiteData: Equatable { +public enum SQLiteData: Equatable, Encodable { case integer(Int) case float(Double) case text(String) case blob(Foundation.Data) case null + + public func encode(to encoder: Encoder) throws { + fatalError() + } } extension SQLiteData: CustomStringConvertible { diff --git a/Sources/SQLite/Row/SQLiteDataType.swift b/Sources/SQLite/Row/SQLiteDataType.swift index 9566be5..1f92d12 100644 --- a/Sources/SQLite/Row/SQLiteDataType.swift +++ b/Sources/SQLite/Row/SQLiteDataType.swift @@ -1,7 +1,18 @@ -public enum SQLiteDataType { +public enum SQLiteDataType: SQLDataType { case integer case real case text case blob case null + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case .integer: return "INTEGER" + case .real: return "REAL" + case .text: return "TEXT" + case .blob: return "BLOB" + case .null: return "NULL" + } + } } diff --git a/Sources/SQLite/Utilities/Exports.swift b/Sources/SQLite/Utilities/Exports.swift index f2fb663..69ea5d8 100644 --- a/Sources/SQLite/Utilities/Exports.swift +++ b/Sources/SQLite/Utilities/Exports.swift @@ -1,2 +1,3 @@ @_exported import Core @_exported import DatabaseKit +@_exported import SQL diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index a9f27d8..8fe57f7 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -1,3 +1,4 @@ +import SQL import SQLite import XCTest @@ -32,236 +33,278 @@ class SQLiteTests: XCTestCase { print(res) } - func testVersionBuild() throws { - let conn = try SQLiteConnection.makeTest() - - let res = try conn.select() - .column(function: "sqlite_version", as: "version") - .run().wait() - print(res) - } +// func testVersionBuild() throws { +// let conn = try SQLiteConnection.makeTest() +// +// let res = try conn.select() +// .column(function: "sqlite_version", as: "version") +// .run().wait() +// print(res) +// } - func testSQLQuery() throws { + func testSQLSelect() throws { let conn = try SQLiteConnection.makeTest() - _ = try conn.query("PRAGMA foreign_keys = ON;") - .wait() - try conn.drop(table: Planet.self) .ifExists() .run().wait() try conn.drop(table: Galaxy.self) .ifExists() .run().wait() - + try conn.create(table: Galaxy.self) - .column(for: \.id, type: .integer, .primaryKey(), .notNull()) - .column(for: \.name, type: .text) + .column(for: \Galaxy.id, type: .integer, .primaryKey) + .column(for: \Galaxy.name, type: .text, .notNull) .run().wait() try conn.create(table: Planet.self) - .column(for: \.id, type: .integer, .primaryKey(), .notNull()) - .column(for: \.galaxyID, type: .integer, .notNull(), .references(\Galaxy.id)) - .run().wait() - - try conn.alter(table: Planet.self) - .addColumn(for: \.name, type: .text, .notNull(), .default(.literal("Unamed Planet"))) + .column(for: \Planet.id, type: .integer, .primaryKey) + .withoutRowID() + .column(for: \Planet.galaxyID, type: .integer, .notNull, .references(\Galaxy.id)) .run().wait() - + try conn.insert(into: Galaxy.self) .value(Galaxy(name: "Milky Way")) - .run().wait() - - let galaxyID = conn.lastAutoincrementID.flatMap(Int.init)! - - try conn.insert(into: Planet.self) - .value(Planet(name: "Earth", galaxyID: galaxyID)) + .value(Galaxy(id: 5, name: "Milky Way")) .run().wait() - try conn.insert(into: Planet.self) - .values([ - Planet(name: "Mercury", galaxyID: galaxyID), - Planet(name: "Venus", galaxyID: galaxyID), - Planet(name: "Mars", galaxyID: galaxyID), - Planet(name: "Jpuiter", galaxyID: galaxyID), - Planet(name: "Pluto", galaxyID: galaxyID) - ]) - .run().wait() - - try conn.update(Planet.self) - .where(\Planet.name == "Jpuiter") - .set(["name": "Jupiter"]) - .run().wait() + let a = try conn.select().all().from(Galaxy.self) + .where(\Galaxy.name == "Milky Way") + .groupBy(\Galaxy.name) + .orderBy(\Galaxy.name, .descending) + .all(decoding: Galaxy.self).wait() + print(a) - let selectA = try conn.select().all() - .from(Planet.self) - .orWhere(\Planet.name == "Mars", \Planet.name == "Venus", \Planet.name == "Earth") - .run(decoding: Planet.self).wait() - print(selectA) - - try conn.delete(from: Planet.self).where(\Planet.name == "Pluto") + try conn.update(Galaxy.self) + .set(\Galaxy.name, to: "Milky Way 2") + .where(\Galaxy.name == "Milky Way") .run().wait() - - let selectB = try conn.select().all().from(Planet.self) - .run(decoding: Planet.self).wait() - print(selectB) - - let selectC = try conn.select().all() - .from(Planet.self) - .join(Galaxy.self, on: \Planet.galaxyID == \Galaxy.id) - .run { try ($0.decode(Planet.self), $0.decode(Galaxy.self)) } - .wait() - print(selectC) - let res = try conn.select().all().from(Planet.self) - .where("name", .like, .bind("%rth")) - .orWhere(.literal(1), .equal, .literal(2)) + try conn.delete(from: Galaxy.self) + .where(\Galaxy.name == "Milky Way") .run().wait() - print(res) - try conn.create(table: Galaxy2.self).temporary().ifNotExists() - .as { $0.select().all().from(Galaxy.self) } - .run().wait() - } - - func testTables() throws { - let database = try SQLiteConnection.makeTest() - _ = try database.query("DROP TABLE IF EXISTS foo").wait() - _ = try database.query("CREATE TABLE foo (bar INT(4), baz VARCHAR(16), biz FLOAT)").wait() - _ = try database.query("INSERT INTO foo VALUES (42, 'Life', 0.44)").wait() - _ = try database.query("INSERT INTO foo VALUES (1337, 'Elite', 209.234)").wait() - _ = try database.query("INSERT INTO foo VALUES (9, NULL, 34.567)").wait() - - if let resultBar = try database.query("SELECT * FROM foo WHERE bar = 42").wait().first { - XCTAssertEqual(resultBar.firstValue(forColumn: "bar"), .integer(42)) - XCTAssertEqual(resultBar.firstValue(forColumn: "baz"), .text("Life")) - XCTAssertEqual(resultBar.firstValue(forColumn: "biz"), .float(0.44)) - } else { - XCTFail("Could not get bar result") - } - - - if let resultBaz = try database.query("SELECT * FROM foo where baz = 'Elite'").wait().first { - XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(1337)) - XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .text("Elite")) - } else { - XCTFail("Could not get baz result") - } - - if let resultBaz = try database.query("SELECT * FROM foo where bar = 9").wait().first { - XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(9)) - XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .null) - } else { - XCTFail("Could not get null result") - } - } - - func testUnicode() throws { - let database = try SQLiteConnection.makeTest() - /// This string includes characters from most Unicode categories - /// such as Latin, Latin-Extended-A/B, Cyrrilic, Greek etc. - let unicode = "®¿ÐØ×ĞƋƢǂNJǕǮȐȘȢȱȵẀˍΔῴЖ♆" - _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() - _ = try database.query("CREATE TABLE `foo` (bar TEXT)").wait() - - _ = try database.query(.raw("INSERT INTO `foo` VALUES(?)", [unicode.convertToSQLiteData()])).wait() - let selectAllResults = try database.query("SELECT * FROM `foo`").wait().first - XCTAssertNotNil(selectAllResults) - XCTAssertEqual(selectAllResults!.firstValue(forColumn: "bar"), .text(unicode)) - - let selectWhereResults = try database.query(.raw("SELECT * FROM `foo` WHERE bar = '\(unicode)'", [])).wait().first - XCTAssertNotNil(selectWhereResults) - XCTAssertEqual(selectWhereResults!.firstValue(forColumn: "bar"), .text(unicode)) - } - - func testBigInts() throws { - let database = try SQLiteConnection.makeTest() - let max = Int.max - - _ = try database.query("DROP TABLE IF EXISTS foo").wait() - _ = try database.query("CREATE TABLE foo (max INT)").wait() - _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [max.convertToSQLiteData()])).wait() - - if let result = try! database.query("SELECT * FROM foo").wait().first { - XCTAssertEqual(result.firstValue(forColumn: "max"), .integer(max)) - } - } - - func testBlob() throws { - let database = try SQLiteConnection.makeTest() - let data = Data(bytes: [0, 1, 2]) - - _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() - _ = try database.query("CREATE TABLE foo (bar BLOB(4))").wait() - _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [data.convertToSQLiteData()])).wait() - - if let result = try database.query("SELECT * FROM foo").wait().first { - XCTAssertEqual(result.firstValue(forColumn: "bar"), .blob(data)) - } else { - XCTFail() - } - } - - func testError() throws { - let database = try SQLiteConnection.makeTest() - do { - _ = try database.query("asdf").wait() - XCTFail("Should have errored") - } catch let error as SQLiteError { - print(error) - XCTAssert(error.reason.contains("syntax error")) - } catch { - XCTFail("wrong error") - } - } - - // https://github.com/vapor/sqlite/issues/33 - func testDecodeSameColumnName() throws { - let row: [SQLiteColumn: SQLiteData] = [ - SQLiteColumn(table: "foo", name: "id"): .text("foo"), - SQLiteColumn(table: "bar", name: "id"): .text("bar"), - ] - struct User: Decodable { - var id: String - } - try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "foo").id, "foo") - try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "bar").id, "bar") - } - - func testMultiThreading() throws { - let db = try SQLiteDatabase(storage: .memory) - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 2) - let a = elg.next() - let b = elg.next() - let group = DispatchGroup() - group.enter() - DispatchQueue.global().async { - let conn = try! db.newConnection(on: a).wait() - for i in 0..<100 { - print("a \(i)") - let res = try! conn.query("SELECT (1 + 1) as a;").wait() - print(res) - } - group.leave() - } - group.enter() - DispatchQueue.global().async { - let conn = try! db.newConnection(on: b).wait() - for i in 0..<100 { - print("b \(i)") - let res = try! conn.query("SELECT (1 + 1) as b;").wait() - print(res) - } - group.leave() - } - group.wait() + let b = try conn.select() + .column(.count(as: "c")) + .from(Galaxy.self) + .all().wait() + print(b) } - static let allTests = [ - ("testTables", testTables), - ("testUnicode", testUnicode), - ("testBigInts", testBigInts), - ("testBlob", testBlob), - ("testError", testError), - ("testDecodeSameColumnName", testDecodeSameColumnName) - ] +// func testSQLQuery() throws { +// let conn = try SQLiteConnection.makeTest() +// +// _ = try conn.query("PRAGMA foreign_keys = ON;") +// .wait() +// +// +// try conn.create(table: Galaxy.self) +// .column(for: \.id, type: .integer, .primaryKey(), .notNull()) +// .column(for: \.name, type: .text) +// .run().wait() +// try conn.create(table: Planet.self) +// .column(for: \.id, type: .integer, .primaryKey(), .notNull()) +// .column(for: \.galaxyID, type: .integer, .notNull(), .references(\Galaxy.id)) +// .run().wait() +// +// try conn.alter(table: Planet.self) +// .addColumn(for: \.name, type: .text, .notNull(), .default(.literal("Unamed Planet"))) +// .run().wait() +// +// try conn.insert(into: Galaxy.self) +// .value(Galaxy(name: "Milky Way")) +// .run().wait() +// +// let galaxyID = conn.lastAutoincrementID.flatMap(Int.init)! +// +// try conn.insert(into: Planet.self) +// .value(Planet(name: "Earth", galaxyID: galaxyID)) +// .run().wait() +// +// try conn.insert(into: Planet.self) +// .values([ +// Planet(name: "Mercury", galaxyID: galaxyID), +// Planet(name: "Venus", galaxyID: galaxyID), +// Planet(name: "Mars", galaxyID: galaxyID), +// Planet(name: "Jpuiter", galaxyID: galaxyID), +// Planet(name: "Pluto", galaxyID: galaxyID) +// ]) +// .run().wait() +// +// try conn.update(Planet.self) +// .where(\Planet.name == "Jpuiter") +// .set(["name": "Jupiter"]) +// .run().wait() +// +// let selectA = try conn.select().all() +// .from(Planet.self) +// .orWhere(\Planet.name == "Mars", \Planet.name == "Venus", \Planet.name == "Earth") +// .run(decoding: Planet.self).wait() +// print(selectA) +// +// try conn.delete(from: Planet.self).where(\Planet.name == "Pluto") +// .run().wait() +// +// let selectB = try conn.select().all().from(Planet.self) +// .run(decoding: Planet.self).wait() +// print(selectB) +// +// let selectC = try conn.select().all() +// .from(Planet.self) +// .join(Galaxy.self, on: \Planet.galaxyID == \Galaxy.id) +// .run { try ($0.decode(Planet.self), $0.decode(Galaxy.self)) } +// .wait() +// print(selectC) +// +// let res = try conn.select().all().from(Planet.self) +// .where("name", .like, .bind("%rth")) +// .orWhere(.literal(1), .equal, .literal(2)) +// .run().wait() +// print(res) +// +// try conn.create(table: Galaxy2.self).temporary().ifNotExists() +// .as { $0.select().all().from(Galaxy.self) } +// .run().wait() +// } +// +// func testTables() throws { +// let database = try SQLiteConnection.makeTest() +// _ = try database.query("DROP TABLE IF EXISTS foo").wait() +// _ = try database.query("CREATE TABLE foo (bar INT(4), baz VARCHAR(16), biz FLOAT)").wait() +// _ = try database.query("INSERT INTO foo VALUES (42, 'Life', 0.44)").wait() +// _ = try database.query("INSERT INTO foo VALUES (1337, 'Elite', 209.234)").wait() +// _ = try database.query("INSERT INTO foo VALUES (9, NULL, 34.567)").wait() +// +// if let resultBar = try database.query("SELECT * FROM foo WHERE bar = 42").wait().first { +// XCTAssertEqual(resultBar.firstValue(forColumn: "bar"), .integer(42)) +// XCTAssertEqual(resultBar.firstValue(forColumn: "baz"), .text("Life")) +// XCTAssertEqual(resultBar.firstValue(forColumn: "biz"), .float(0.44)) +// } else { +// XCTFail("Could not get bar result") +// } +// +// +// if let resultBaz = try database.query("SELECT * FROM foo where baz = 'Elite'").wait().first { +// XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(1337)) +// XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .text("Elite")) +// } else { +// XCTFail("Could not get baz result") +// } +// +// if let resultBaz = try database.query("SELECT * FROM foo where bar = 9").wait().first { +// XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(9)) +// XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .null) +// } else { +// XCTFail("Could not get null result") +// } +// } +// +// func testUnicode() throws { +// let database = try SQLiteConnection.makeTest() +// /// This string includes characters from most Unicode categories +// /// such as Latin, Latin-Extended-A/B, Cyrrilic, Greek etc. +// let unicode = "®¿ÐØ×ĞƋƢǂNJǕǮȐȘȢȱȵẀˍΔῴЖ♆" +// _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() +// _ = try database.query("CREATE TABLE `foo` (bar TEXT)").wait() +// +// _ = try database.query(.raw("INSERT INTO `foo` VALUES(?)", [unicode.convertToSQLiteData()])).wait() +// let selectAllResults = try database.query("SELECT * FROM `foo`").wait().first +// XCTAssertNotNil(selectAllResults) +// XCTAssertEqual(selectAllResults!.firstValue(forColumn: "bar"), .text(unicode)) +// +// let selectWhereResults = try database.query(.raw("SELECT * FROM `foo` WHERE bar = '\(unicode)'", [])).wait().first +// XCTAssertNotNil(selectWhereResults) +// XCTAssertEqual(selectWhereResults!.firstValue(forColumn: "bar"), .text(unicode)) +// } +// +// func testBigInts() throws { +// let database = try SQLiteConnection.makeTest() +// let max = Int.max +// +// _ = try database.query("DROP TABLE IF EXISTS foo").wait() +// _ = try database.query("CREATE TABLE foo (max INT)").wait() +// _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [max.convertToSQLiteData()])).wait() +// +// if let result = try! database.query("SELECT * FROM foo").wait().first { +// XCTAssertEqual(result.firstValue(forColumn: "max"), .integer(max)) +// } +// } +// +// func testBlob() throws { +// let database = try SQLiteConnection.makeTest() +// let data = Data(bytes: [0, 1, 2]) +// +// _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() +// _ = try database.query("CREATE TABLE foo (bar BLOB(4))").wait() +// _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [data.convertToSQLiteData()])).wait() +// +// if let result = try database.query("SELECT * FROM foo").wait().first { +// XCTAssertEqual(result.firstValue(forColumn: "bar"), .blob(data)) +// } else { +// XCTFail() +// } +// } +// +// func testError() throws { +// let database = try SQLiteConnection.makeTest() +// do { +// _ = try database.query("asdf").wait() +// XCTFail("Should have errored") +// } catch let error as SQLiteError { +// print(error) +// XCTAssert(error.reason.contains("syntax error")) +// } catch { +// XCTFail("wrong error") +// } +// } +// +// // https://github.com/vapor/sqlite/issues/33 +// func testDecodeSameColumnName() throws { +// let row: [SQLiteColumn: SQLiteData] = [ +// SQLiteColumn(table: "foo", name: "id"): .text("foo"), +// SQLiteColumn(table: "bar", name: "id"): .text("bar"), +// ] +// struct User: Decodable { +// var id: String +// } +// try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "foo").id, "foo") +// try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "bar").id, "bar") +// } +// +// func testMultiThreading() throws { +// let db = try SQLiteDatabase(storage: .memory) +// let elg = MultiThreadedEventLoopGroup(numberOfThreads: 2) +// let a = elg.next() +// let b = elg.next() +// let group = DispatchGroup() +// group.enter() +// DispatchQueue.global().async { +// let conn = try! db.newConnection(on: a).wait() +// for i in 0..<100 { +// print("a \(i)") +// let res = try! conn.query("SELECT (1 + 1) as a;").wait() +// print(res) +// } +// group.leave() +// } +// group.enter() +// DispatchQueue.global().async { +// let conn = try! db.newConnection(on: b).wait() +// for i in 0..<100 { +// print("b \(i)") +// let res = try! conn.query("SELECT (1 + 1) as b;").wait() +// print(res) +// } +// group.leave() +// } +// group.wait() +// } +// +// static let allTests = [ +// ("testTables", testTables), +// ("testUnicode", testUnicode), +// ("testBigInts", testBigInts), +// ("testBlob", testBlob), +// ("testError", testError), +// ("testDecodeSameColumnName", testDecodeSameColumnName) +// ] } From 6e4358bb76f28c150ea6fe0e712cc81b4a587a0c Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Sun, 17 Jun 2018 03:40:58 -0400 Subject: [PATCH 11/21] add SQLJoin + SQLJoinMethod --- Sources/SQL/SQLBinaryOperator.swift | 7 ++++ Sources/SQL/SQLDelete.swift | 16 ++++---- Sources/SQL/SQLDeleteBuilder.swift | 8 ++-- Sources/SQL/SQLJoin.swift | 26 +++++++++++++ Sources/SQL/SQLJoinMethod.swift | 24 ++++++++++++ Sources/SQL/SQLPredicateBuilder.swift | 45 ++++++++++++++++++++++ Sources/SQL/SQLQueryBuilder.swift | 41 ++++++++++++++++++++ Sources/SQL/SQLSelect.swift | 30 +++++++++------ Sources/SQL/SQLSelectBuilder.swift | 40 ++++++++++++++++--- Sources/SQL/SQLUpdate.swift | 10 ++--- Sources/SQL/SQLUpdateBuilder.swift | 8 ++-- Sources/SQL/SQLWhereBuilder.swift | 45 ---------------------- Sources/SQLite/Query/SQLiteQuery.swift | 8 +++- Tests/SQLiteTests/SQLiteTests.swift | 53 +++++++++++++++----------- 14 files changed, 254 insertions(+), 107 deletions(-) create mode 100644 Sources/SQL/SQLJoin.swift create mode 100644 Sources/SQL/SQLJoinMethod.swift create mode 100644 Sources/SQL/SQLPredicateBuilder.swift delete mode 100644 Sources/SQL/SQLWhereBuilder.swift diff --git a/Sources/SQL/SQLBinaryOperator.swift b/Sources/SQL/SQLBinaryOperator.swift index 27ebe9e..0a554bc 100644 --- a/Sources/SQL/SQLBinaryOperator.swift +++ b/Sources/SQL/SQLBinaryOperator.swift @@ -160,3 +160,10 @@ public func != (_ lhs: KeyPath, _ rhs: V) -> E { return E.binary(.column(.keyPath(lhs)), .notEqual, .bind(.encodable(rhs))) } + + +public func == (_ lhs: KeyPath, _ rhs: KeyPath) -> E + where A: SQLTable, B: Encodable, C: SQLTable, D: Encodable, E: SQLExpression +{ + return E.binary(.column(.keyPath(lhs)), .equal, .column(.keyPath(rhs))) +} diff --git a/Sources/SQL/SQLDelete.swift b/Sources/SQL/SQLDelete.swift index b87f411..09de4f7 100644 --- a/Sources/SQL/SQLDelete.swift +++ b/Sources/SQL/SQLDelete.swift @@ -4,12 +4,12 @@ public protocol SQLDelete: SQLSerializable { static func delete(_ table: TableIdentifier) -> Self - var from: TableIdentifier { get set } + var table: TableIdentifier { get set } /// If the WHERE clause is not present, all records in the table are deleted. If a WHERE clause is supplied, /// then only those rows for which the WHERE clause boolean expression is true are deleted. Rows for which /// the expression is false or NULL are retained. - var `where`: Expression? { get set } + var predicate: Expression? { get set } } // MARK: Generic @@ -19,23 +19,23 @@ public struct GenericSQLDelete: SQLDelete { /// See `SQLDelete`. public static func delete(_ table: TableIdentifier) -> GenericSQLDelete { - return .init(from: table, where: nil) + return .init(table: table, predicate: nil) } /// See `SQLDelete`. - public var from: TableIdentifier + public var table: TableIdentifier /// See `SQLDelete`. - public var `where`: Expression? + public var predicate: Expression? /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { var sql: [String] = [] sql.append("DELETE FROM") - sql.append(from.serialize(&binds)) - if let `where` = self.where { + sql.append(table.serialize(&binds)) + if let predicate = self.predicate { sql.append("WHERE") - sql.append(`where`.serialize(&binds)) + sql.append(predicate.serialize(&binds)) } return sql.joined(separator: " ") } diff --git a/Sources/SQL/SQLDeleteBuilder.swift b/Sources/SQL/SQLDeleteBuilder.swift index 7fe1ea8..22116bd 100644 --- a/Sources/SQL/SQLDeleteBuilder.swift +++ b/Sources/SQL/SQLDeleteBuilder.swift @@ -1,4 +1,4 @@ -public final class SQLDeleteBuilder: SQLQueryBuilder, SQLWhereBuilder +public final class SQLDeleteBuilder: SQLQueryBuilder, SQLPredicateBuilder where Connection: SQLConnection { /// `Delete` query being built. @@ -13,9 +13,9 @@ public final class SQLDeleteBuilder: SQLQueryBuilder, SQLWhereBuilde } /// See `SQLWhereBuilder`. - public var `where`: Connection.Query.Delete.Expression? { - get { return delete.where } - set { delete.where = newValue } + public var predicate: Connection.Query.Delete.Expression? { + get { return delete.predicate } + set { delete.predicate = newValue } } /// Creates a new `SQLDeleteBuilder`. diff --git a/Sources/SQL/SQLJoin.swift b/Sources/SQL/SQLJoin.swift new file mode 100644 index 0000000..fb9cf78 --- /dev/null +++ b/Sources/SQL/SQLJoin.swift @@ -0,0 +1,26 @@ +public protocol SQLJoin: SQLSerializable { + associatedtype Method: SQLJoinMethod + associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Expression: SQLExpression + + static func join(_ method: Method, _ table: TableIdentifier, _ expression: Expression) -> Self +} + +public struct GenericSQLJoin: SQLJoin + where Method: SQLJoinMethod, TableIdentifier: SQLTableIdentifier, Expression: SQLExpression + +{ + /// See `SQLJoin`. + public static func join(_ method: Method, _ table: TableIdentifier, _ expression: Expression) -> GenericSQLJoin { + return .init(method: method, table: table, expression: expression) + } + + public var method: Method + public var table: TableIdentifier + public var expression: Expression + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + return method.serialize(&binds) + " JOIN " + table.serialize(&binds) + " ON " + expression.serialize(&binds) + } +} diff --git a/Sources/SQL/SQLJoinMethod.swift b/Sources/SQL/SQLJoinMethod.swift new file mode 100644 index 0000000..a70d758 --- /dev/null +++ b/Sources/SQL/SQLJoinMethod.swift @@ -0,0 +1,24 @@ +public protocol SQLJoinMethod: SQLSerializable { + static var `default`: Self { get } +} + +public enum GenericSQLJoinMethod: SQLJoinMethod { + /// See `SQLJoinMethod`. + public static var `default`: GenericSQLJoinMethod { + return .inner + } + + case inner + case left + case right + case full + + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case .inner: return "INNER" + case .left: return "LEFT" + case .right: return "RIGHT" + case .full: return "FULL" + } + } +} diff --git a/Sources/SQL/SQLPredicateBuilder.swift b/Sources/SQL/SQLPredicateBuilder.swift new file mode 100644 index 0000000..1429ed5 --- /dev/null +++ b/Sources/SQL/SQLPredicateBuilder.swift @@ -0,0 +1,45 @@ +public protocol SQLPredicateBuilder: class { + associatedtype Expression: SQLExpression + var predicate: Expression? { get set } +} + +extension SQLPredicateBuilder { + public func `where`(_ expressions: Expression...) -> Self { + for expression in expressions { + self.predicate &= expression + } + return self + } + + public func orWhere(_ expressions: Expression...) -> Self { + for expression in expressions { + self.predicate |= expression + } + return self + } + + public func `where`(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { + self.predicate &= .binary(lhs, op, rhs) + return self + } + + public func orWhere(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { + self.predicate |= .binary(lhs, op, rhs) + return self + } + + public func `where`(group: (NestedSQLPredicateBuilder) throws -> ()) rethrows -> Self { + let builder = NestedSQLPredicateBuilder(Self.self) + try group(builder) + if let sub = builder.predicate { + self.predicate &= sub + } + return self + } +} + +public final class NestedSQLPredicateBuilder: SQLPredicateBuilder where PredicateBuilder: SQLPredicateBuilder { + public typealias Expression = PredicateBuilder.Expression + public var predicate: PredicateBuilder.Expression? + internal init(_ type: PredicateBuilder.Type) { } +} diff --git a/Sources/SQL/SQLQueryBuilder.swift b/Sources/SQL/SQLQueryBuilder.swift index 3af1c34..d1d7f4e 100644 --- a/Sources/SQL/SQLQueryBuilder.swift +++ b/Sources/SQL/SQLQueryBuilder.swift @@ -24,6 +24,47 @@ extension SQLQueryFetcher { return run(decoding: D.self) { all.append($0) }.map { all } } + public func all(decoding a: A.Type, _ b: B.Type) -> Future<[(A, B)]> + where A: SQLTable, B: SQLTable + { + var all: [(A, B)] = [] + return run(decoding: A.self, B.self) { all.append(($0, $1)) }.map { all } + } + + public func all(decoding a: A.Type, _ b: B.Type, _ c: C.Type) -> Future<[(A, B, C)]> + where A: SQLTable, B: SQLTable, C: SQLTable + { + var all: [(A, B, C)] = [] + return run(decoding: A.self, B.self, C.self) { all.append(($0, $1, $2)) }.map { all } + } + + public func run( + decoding a: A.Type, _ b: B.Type, _ c: C.Type, + into handler: @escaping (A, B, C) throws -> () + ) -> Future + where A: SQLTable, B: SQLTable, C: SQLTable + { + return run { row in + let a = try Connection.Query.RowDecoder.init().decode(A.self, from: row, table: .table(A.self)) + let b = try Connection.Query.RowDecoder.init().decode(B.self, from: row, table: .table(B.self)) + let c = try Connection.Query.RowDecoder.init().decode(C.self, from: row, table: .table(C.self)) + try handler(a, b, c) + } + } + + public func run( + decoding a: A.Type, _ b: B.Type, + into handler: @escaping (A, B) throws -> () + ) -> Future + where A: SQLTable, B: SQLTable + { + return run { row in + let a = try Connection.Query.RowDecoder.init().decode(A.self, from: row, table: .table(A.self)) + let b = try Connection.Query.RowDecoder.init().decode(B.self, from: row, table: .table(B.self)) + try handler(a, b) + } + } + public func run( decoding type: D.Type, into handler: @escaping (D) throws -> () diff --git a/Sources/SQL/SQLSelect.swift b/Sources/SQL/SQLSelect.swift index ac2161b..845f9d6 100644 --- a/Sources/SQL/SQLSelect.swift +++ b/Sources/SQL/SQLSelect.swift @@ -2,6 +2,7 @@ public protocol SQLSelect: SQLSerializable { associatedtype Distinct: SQLDistinct associatedtype SelectExpression: SQLSelectExpression associatedtype TableIdentifier: SQLTableIdentifier + associatedtype Join: SQLJoin associatedtype Expression: SQLExpression associatedtype GroupBy: SQLGroupBy associatedtype OrderBy: SQLOrderBy @@ -10,29 +11,31 @@ public protocol SQLSelect: SQLSerializable { var distinct: Distinct? { get set } var columns: [SelectExpression] { get set } - var from: [TableIdentifier] { get set } - var `where`: Expression? { get set } + var tables: [TableIdentifier] { get set } + var joins: [Join] { get set } + var predicate: Expression? { get set } var groupBy: [GroupBy] { get set } var orderBy: [OrderBy] { get set } } // MARK: Generic -public struct GenericSQLSelect: SQLSelect - where Distinct: SQLDistinct, SelectExpression: SQLSelectExpression, TableIdentifier: SQLTableIdentifier, Expression: SQLExpression, GroupBy: SQLGroupBy, OrderBy: SQLOrderBy +public struct GenericSQLSelect: SQLSelect +where Distinct: SQLDistinct, SelectExpression: SQLSelectExpression, TableIdentifier: SQLTableIdentifier, Join: SQLJoin, Expression: SQLExpression, GroupBy: SQLGroupBy, OrderBy: SQLOrderBy { - public typealias `Self` = GenericSQLSelect + public typealias `Self` = GenericSQLSelect public var distinct: Distinct? public var columns: [SelectExpression] - public var from: [TableIdentifier] - public var `where`: Expression? + public var tables: [TableIdentifier] + public var joins: [Join] + public var predicate: Expression? public var groupBy: [GroupBy] public var orderBy: [OrderBy] /// See `SQLSelect`. public static func select() -> Self { - return .init(distinct: nil, columns: [], from: [], where: nil, groupBy: [], orderBy: []) + return .init(distinct: nil, columns: [], tables: [], joins: [], predicate: nil, groupBy: [], orderBy: []) } /// See `SQLSerializable`. @@ -43,13 +46,16 @@ public struct GenericSQLSelect: SQLQueryFetcher, SQLWhereBuilder +public final class SQLSelectBuilder: SQLQueryFetcher, SQLPredicateBuilder where Connection: SQLConnection { /// `Select` query being built. @@ -13,9 +13,9 @@ public final class SQLSelectBuilder: SQLQueryFetcher, SQLWhereBuilde } /// See `SQLWhereBuilder`. - public var `where`: Connection.Query.Select.Expression? { - get { return select.where } - set { select.where = newValue } + public var predicate: Connection.Query.Select.Expression? { + get { return select.predicate } + set { select.predicate = newValue } } /// Creates a new `SQLCreateTableBuilder`. @@ -50,14 +50,42 @@ public final class SQLSelectBuilder: SQLQueryFetcher, SQLWhereBuilde } public func from(_ tables: Connection.Query.Select.TableIdentifier...) -> Self { - select.from += tables + select.tables += tables return self } public func from
(_ table: Table.Type) -> Self where Table: SQLTable { - select.from.append(.table(.identifier(Table.sqlTableIdentifierString))) + select.tables.append(.table(.identifier(Table.sqlTableIdentifierString))) + return self + } + + public func join( + _ local: KeyPath, + to foreign: KeyPath + ) -> Self where A: SQLTable, B: Encodable, C: SQLTable, D: Encodable { + return join(.default, local, to: foreign) + } + + public func join( + _ method: Connection.Query.Select.Join.Method, + _ local: KeyPath, + to foreign: KeyPath + ) -> Self where A: SQLTable, B: Encodable, C: SQLTable, D: Encodable { + return join(method, C.self, on: local == foreign) + } + + public func join
(_ table: Table.Type, on expression: Connection.Query.Select.Join.Expression) -> Self + where Table: SQLTable + { + return join(.default, table, on: expression) + } + + public func join
(_ method: Connection.Query.Select.Join.Method, _ table: Table.Type, on expression: Connection.Query.Select.Join.Expression) -> Self + where Table: SQLTable + { + select.joins.append(.join(method, .table(Table.self), expression)) return self } diff --git a/Sources/SQL/SQLUpdate.swift b/Sources/SQL/SQLUpdate.swift index 3436d9c..74eb402 100644 --- a/Sources/SQL/SQLUpdate.swift +++ b/Sources/SQL/SQLUpdate.swift @@ -7,7 +7,7 @@ public protocol SQLUpdate: SQLSerializable { var table: TableIdentifier { get set } var values: [(Identifier, Expression)] { get set } - var `where`: Expression? { get set } + var predicate: Expression? { get set } } // MARK: Generic @@ -18,12 +18,12 @@ public struct GenericSQLUpdate: SQLUpda public typealias `Self` = GenericSQLUpdate public static func update(_ table: TableIdentifier) -> Self { - return .init(table: table, values: [], where: nil) + return .init(table: table, values: [], predicate: nil) } public var table: TableIdentifier public var values: [(Identifier, Expression)] - public var `where`: Expression? + public var predicate: Expression? /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { @@ -32,9 +32,9 @@ public struct GenericSQLUpdate: SQLUpda sql.append(table.serialize(&binds)) sql.append("SET") sql.append(values.map { $0.0.serialize(&binds) + " = " + $0.1.serialize(&binds) }.joined(separator: ", ")) - if let `where` = self.where { + if let predicate = self.predicate { sql.append("WHERE") - sql.append(`where`.serialize(&binds)) + sql.append(predicate.serialize(&binds)) } return sql.joined(separator: " ") } diff --git a/Sources/SQL/SQLUpdateBuilder.swift b/Sources/SQL/SQLUpdateBuilder.swift index 996e346..87b2b0e 100644 --- a/Sources/SQL/SQLUpdateBuilder.swift +++ b/Sources/SQL/SQLUpdateBuilder.swift @@ -1,4 +1,4 @@ -public final class SQLUpdateBuilder: SQLQueryBuilder, SQLWhereBuilder +public final class SQLUpdateBuilder: SQLQueryBuilder, SQLPredicateBuilder where Connection: SQLConnection { /// `Update` query being built. @@ -13,9 +13,9 @@ public final class SQLUpdateBuilder: SQLQueryBuilder, SQLWhereBuilde } /// See `SQLWhereBuilder`. - public var `where`: Connection.Query.Update.Expression? { - get { return update.where } - set { update.where = newValue } + public var predicate: Connection.Query.Update.Expression? { + get { return update.predicate } + set { update.predicate = newValue } } /// Creates a new `SQLDeleteBuilder`. diff --git a/Sources/SQL/SQLWhereBuilder.swift b/Sources/SQL/SQLWhereBuilder.swift deleted file mode 100644 index fef32a3..0000000 --- a/Sources/SQL/SQLWhereBuilder.swift +++ /dev/null @@ -1,45 +0,0 @@ -public protocol SQLWhereBuilder: class { - associatedtype Expression: SQLExpression - var `where`: Expression? { get set } -} - -extension SQLWhereBuilder { - public func `where`(_ expressions: Expression...) -> Self { - for expression in expressions { - self.where &= expression - } - return self - } - - public func orWhere(_ expressions: Expression...) -> Self { - for expression in expressions { - self.where |= expression - } - return self - } - - public func `where`(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { - self.where &= .binary(lhs, op, rhs) - return self - } - - public func orWhere(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { - self.where |= .binary(lhs, op, rhs) - return self - } - - public func `where`(group: (NestedSQLWhereBuilder) throws -> ()) rethrows -> Self { - let builder = NestedSQLWhereBuilder(Self.self) - try group(builder) - if let sub = builder.where { - self.where &= sub - } - return self - } -} - -public final class NestedSQLWhereBuilder: SQLWhereBuilder where WhereBuilder: SQLWhereBuilder { - public typealias Expression = WhereBuilder.Expression - public var `where`: WhereBuilder.Expression? - internal init(_ type: WhereBuilder.Type) { } -} diff --git a/Sources/SQLite/Query/SQLiteQuery.swift b/Sources/SQLite/Query/SQLiteQuery.swift index 3ff08fc..1f9054e 100644 --- a/Sources/SQLite/Query/SQLiteQuery.swift +++ b/Sources/SQLite/Query/SQLiteQuery.swift @@ -83,6 +83,12 @@ public enum SQLiteQuery: SQLQuery { /// See `SQLQuery`. public typealias Insert = GenericSQLInsert + /// See `SQLQuery`. + public typealias Join = GenericSQLJoin + + /// See `SQLQuery`. + public typealias JoinMethod = GenericSQLJoinMethod + /// See `SQLQuery`. public typealias Literal = GenericSQLLiteral @@ -93,7 +99,7 @@ public enum SQLiteQuery: SQLQuery { public typealias PrimaryKey = SQLitePrimaryKey /// See `SQLQuery`. - public typealias Select = GenericSQLSelect + public typealias Select = GenericSQLSelect /// See `SQLQuery`. public typealias SelectExpression = GenericSQLSelectExpression diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 8fe57f7..a6b5c16 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -57,14 +57,15 @@ class SQLiteTests: XCTestCase { .column(for: \Galaxy.name, type: .text, .notNull) .run().wait() try conn.create(table: Planet.self) + .temporary() + .ifNotExists() .column(for: \Planet.id, type: .integer, .primaryKey) - .withoutRowID() + .column(for: \Planet.name, type: .text, .notNull) .column(for: \Planet.galaxyID, type: .integer, .notNull, .references(\Galaxy.id)) .run().wait() try conn.insert(into: Galaxy.self) .value(Galaxy(name: "Milky Way")) - .value(Galaxy(id: 5, name: "Milky Way")) .run().wait() let a = try conn.select().all().from(Galaxy.self) @@ -74,6 +75,33 @@ class SQLiteTests: XCTestCase { .all(decoding: Galaxy.self).wait() print(a) + let galaxyID = conn.lastAutoincrementID.flatMap(Int.init)! + try conn.insert(into: Planet.self) + .value(Planet(name: "Earth", galaxyID: galaxyID)) + .run().wait() + + try conn.insert(into: Planet.self) + .values([ + Planet(name: "Mercury", galaxyID: galaxyID), + Planet(name: "Venus", galaxyID: galaxyID), + Planet(name: "Mars", galaxyID: galaxyID), + Planet(name: "Jpuiter", galaxyID: galaxyID), + Planet(name: "Pluto", galaxyID: galaxyID) + ]) + .run().wait() + + try conn.update(Planet.self) + .where(\Planet.name == "Jpuiter") + .set(["name": "Jupiter"]) + .run().wait() + + let selectC = try conn.select().all() + .from(Planet.self) + .join(\Planet.galaxyID, to: \Galaxy.id) + .all(decoding: Planet.self, Galaxy.self) + .wait() + print(selectC) + try conn.update(Galaxy.self) .set(\Galaxy.name, to: "Milky Way 2") .where(\Galaxy.name == "Milky Way") @@ -114,26 +142,7 @@ class SQLiteTests: XCTestCase { // .value(Galaxy(name: "Milky Way")) // .run().wait() // -// let galaxyID = conn.lastAutoincrementID.flatMap(Int.init)! -// -// try conn.insert(into: Planet.self) -// .value(Planet(name: "Earth", galaxyID: galaxyID)) -// .run().wait() -// -// try conn.insert(into: Planet.self) -// .values([ -// Planet(name: "Mercury", galaxyID: galaxyID), -// Planet(name: "Venus", galaxyID: galaxyID), -// Planet(name: "Mars", galaxyID: galaxyID), -// Planet(name: "Jpuiter", galaxyID: galaxyID), -// Planet(name: "Pluto", galaxyID: galaxyID) -// ]) -// .run().wait() -// -// try conn.update(Planet.self) -// .where(\Planet.name == "Jpuiter") -// .set(["name": "Jupiter"]) -// .run().wait() + // // let selectA = try conn.select().all() // .from(Planet.self) From ae454c7920234066eb372d4c218ff48d55d3fe29 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Sun, 17 Jun 2018 19:06:18 -0400 Subject: [PATCH 12/21] implement SQLAlterTable + cleanup old files --- Sources/SQL/SQLAlterTable.swift | 8 + Sources/SQL/SQLAlterTableBuilder.swift | 38 ++ Sources/SQL/SQLColumnConstraint.swift | 2 +- Sources/SQL/SQLColumnIdentifier.swift | 6 +- Sources/SQL/SQLConnection.swift | 22 ++ Sources/SQL/SQLLiteral.swift | 19 +- Sources/SQL/SQLQuery.swift | 21 +- Sources/SQL/SQLSelectBuilder.swift | 10 +- Sources/SQL/SQLTableIdentifier.swift | 16 +- Sources/SQLite/Codable/SQLiteRowDecoder.swift | 2 +- .../SQLite/Database/SQLiteConnection.swift | 12 - Sources/SQLite/Query/SQLiteAlterTable.swift | 58 +++ .../Query/SQLiteAlterTableBuilder.swift | 42 ++ Sources/SQLite/Query/SQLiteBind.swift | 2 +- ...ateTable.swift => SQLiteCreateTable.swift} | 12 +- Sources/SQLite/Query/SQLiteFunction.swift | 2 +- Sources/SQLite/Query/SQLiteGeneric.swift | 100 +++++ .../SQLite/Query/SQLiteQuery+AlterTable.swift | 56 --- .../Query/SQLiteQuery+AlterTableBuilder.swift | 84 ---- .../SQLiteQuery+Expression+Literal.swift | 51 --- .../SQLiteQuery+Expression+Operators.swift | 139 ------- Sources/SQLite/Query/SQLiteQuery+Join.swift | 83 ---- Sources/SQLite/Query/SQLiteQuery.swift | 149 +++---- .../SQLiteQueryExpressionRepresentable.swift | 2 +- Tests/SQLiteTests/SQLiteTests.swift | 371 ++++++++---------- 25 files changed, 530 insertions(+), 777 deletions(-) create mode 100644 Sources/SQL/SQLAlterTable.swift create mode 100644 Sources/SQL/SQLAlterTableBuilder.swift create mode 100644 Sources/SQLite/Query/SQLiteAlterTable.swift create mode 100644 Sources/SQLite/Query/SQLiteAlterTableBuilder.swift rename Sources/SQLite/Query/{SQLiteQuery+CreateTable.swift => SQLiteCreateTable.swift} (86%) create mode 100644 Sources/SQLite/Query/SQLiteGeneric.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+AlterTable.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift delete mode 100644 Sources/SQLite/Query/SQLiteQuery+Join.swift diff --git a/Sources/SQL/SQLAlterTable.swift b/Sources/SQL/SQLAlterTable.swift new file mode 100644 index 0000000..7c38518 --- /dev/null +++ b/Sources/SQL/SQLAlterTable.swift @@ -0,0 +1,8 @@ +public protocol SQLAlterTable: SQLSerializable { + associatedtype TableIdentifier: SQLTableIdentifier + + static func alterTable(_ table: TableIdentifier) -> Self +} + +// No generic ALTER table is offered since they differ too much +// between SQL dialects diff --git a/Sources/SQL/SQLAlterTableBuilder.swift b/Sources/SQL/SQLAlterTableBuilder.swift new file mode 100644 index 0000000..31e8b17 --- /dev/null +++ b/Sources/SQL/SQLAlterTableBuilder.swift @@ -0,0 +1,38 @@ +public final class SQLAlterTableBuilder: SQLQueryBuilder + where Connection: SQLConnection +{ + /// `AlterTable` query being built. + public var alterTable: Connection.Query.AlterTable + + /// See `SQLQueryBuilder`. + public var connection: Connection + + /// See `SQLQueryBuilder`. + public var query: Connection.Query { + return .alterTable(alterTable) + } + + /// Creates a new `SQLAlterTableBuilder`. + public init(_ alterTable: Connection.Query.AlterTable, on connection: Connection) { + self.alterTable = alterTable + self.connection = connection + } +} + +// MARK: Connection + +extension SQLConnection { + /// Creates a new `AlterTableBuilder`. + /// + /// conn.alter(table: Planet.self)... + /// + /// - parameters: + /// - table: Table to alter. + /// - returns: `AlterTableBuilder`. + public func alter
(table: Table.Type) -> SQLAlterTableBuilder + where Table: SQLTable + { + return .init(.alterTable(.table(Table.self)), on: self) + } +} + diff --git a/Sources/SQL/SQLColumnConstraint.swift b/Sources/SQL/SQLColumnConstraint.swift index 9517f1e..87324d3 100644 --- a/Sources/SQL/SQLColumnConstraint.swift +++ b/Sources/SQL/SQLColumnConstraint.swift @@ -51,7 +51,7 @@ extension SQLColumnConstraint { public static func `default`( _ expression: ColumnConstraintAlgorithm.Expression, identifier: Identifier? = nil - ) -> Self { + ) -> Self { return .constraint(.default(expression), identifier) } diff --git a/Sources/SQL/SQLColumnIdentifier.swift b/Sources/SQL/SQLColumnIdentifier.swift index 3c3eed0..8bb0f20 100644 --- a/Sources/SQL/SQLColumnIdentifier.swift +++ b/Sources/SQL/SQLColumnIdentifier.swift @@ -12,7 +12,7 @@ public protocol SQLColumnIdentifier: SQLSerializable { extension SQLColumnIdentifier { - static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { + public static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { guard let property = try! T.reflectProperty(forKey: keyPath) else { fatalError("Could not reflect property of type \(V.self) on \(T.self): \(keyPath)") } @@ -20,13 +20,13 @@ extension SQLColumnIdentifier { } } extension SQLTableIdentifier { - static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { + public static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { return .table(.identifier(T.sqlTableIdentifierString)) } } extension SQLIdentifier { - static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { + public static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { guard let property = try! T.reflectProperty(forKey: keyPath) else { fatalError("Could not reflect property of type \(V.self) on \(T.self): \(keyPath)") } diff --git a/Sources/SQL/SQLConnection.swift b/Sources/SQL/SQLConnection.swift index e294a78..6da355d 100644 --- a/Sources/SQL/SQLConnection.swift +++ b/Sources/SQL/SQLConnection.swift @@ -6,3 +6,25 @@ public protocol SQLConnection { where Query.RowDecoder.Row == Output func query(_ query: Query, _ handler: @escaping (Output) throws -> ()) -> Future } + +extension SQLConnection { + public func query(_ sql: String, _ binds: [Encodable] = [], _ handler: @escaping (Output) throws -> ()) -> Future { + return query(.raw(sql, binds: binds), handler) + } + + public func query(_ sql: String, _ binds: [Encodable] = []) -> Future<[Output]> { + return query(.raw(sql, binds: binds)) + } + + /// Executes the supplied `SQLiteQuery` on the connection, aggregating the results into an array. + /// + /// let rows = try conn.query("SELECT * FROM users").wait() + /// + /// - parameters: + /// - query: `SQLiteQuery` to execute. + /// - returns: A `Future` containing array of rows. + public func query(_ query: Query) -> Future<[Output]> { + var rows: [Output] = [] + return self.query(query) { rows.append($0) }.map { rows } + } +} diff --git a/Sources/SQL/SQLLiteral.swift b/Sources/SQL/SQLLiteral.swift index 7dda80a..8e2e878 100644 --- a/Sources/SQL/SQLLiteral.swift +++ b/Sources/SQL/SQLLiteral.swift @@ -10,7 +10,7 @@ public protocol SQLLiteral: SQLSerializable { // MARK: Generic -public enum GenericSQLLiteral: SQLLiteral { +public enum GenericSQLLiteral: SQLLiteral, ExpressibleByStringLiteral, ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral { /// See `SQLLiteral`. public static func string(_ string: String) -> GenericSQLLiteral { return ._string(string) @@ -35,7 +35,22 @@ public enum GenericSQLLiteral: SQLLiteral { public static func boolean(_ bool: Bool) -> GenericSQLLiteral { return ._boolean(bool) } - + + /// See `ExpressibleByStringLiteral`. + public init(stringLiteral value: String) { + self = .string(value) + } + + /// See `ExpressibleByFloatLiteral`. + public init(floatLiteral value: Double) { + self = .numeric(value.description) + } + + /// See `ExpressibleByIntegerLiteral`. + public init(integerLiteral value: Int) { + self = .numeric(value.description) + } + case _string(String) case _numeric(String) case _null diff --git a/Sources/SQL/SQLQuery.swift b/Sources/SQL/SQLQuery.swift index caf6539..df29a3e 100644 --- a/Sources/SQL/SQLQuery.swift +++ b/Sources/SQL/SQLQuery.swift @@ -1,4 +1,5 @@ public protocol SQLQuery: SQLSerializable { + associatedtype AlterTable: SQLAlterTable associatedtype CreateTable: SQLCreateTable associatedtype Delete: SQLDelete associatedtype DropTable: SQLDropTable @@ -7,27 +8,13 @@ public protocol SQLQuery: SQLSerializable { associatedtype Update: SQLUpdate associatedtype RowDecoder: SQLRowDecoder - + + static func alterTable(_ alterTable: AlterTable) -> Self static func createTable(_ createTable: CreateTable) -> Self static func delete(_ delete: Delete) -> Self static func dropTable(_ dropTable: DropTable) -> Self static func insert(_ insert: Insert) -> Self static func select(_ select: Select) -> Self static func update(_ update: Update) -> Self + static func raw(_ sql: String, binds: [Encodable]) -> Self } - - -/// A `CREATE TABLE ... AS SELECT` statement creates and populates a database table based on the results of a SELECT statement. -/// The table has the same number of columns as the rows returned by the SELECT statement. The name of each column is the same -/// as the name of the corresponding column in the result set of the SELECT statement. -/// -/// conn.create(table: GalaxyCopy.self).as { $0.select().all().from(Galaxy.self) }.run() -/// -/// - parameters: -/// - closure: Closure accepting a `SQLiteConnection` and returning a `SelectBuilder`. -/// - returns: Self for chaining. -//public func `as`(_ closure: (SQLiteConnection) -> SelectBuilder) -> Self { -// create.schemaSource = .select(closure(connection).select) -// return self -//} -// diff --git a/Sources/SQL/SQLSelectBuilder.swift b/Sources/SQL/SQLSelectBuilder.swift index b01c312..b91d4b1 100644 --- a/Sources/SQL/SQLSelectBuilder.swift +++ b/Sources/SQL/SQLSelectBuilder.swift @@ -24,11 +24,11 @@ public final class SQLSelectBuilder: SQLQueryFetcher, SQLPredicateBu self.connection = connection } - public func column(function: String, as alias: String? = nil) -> Self { - return column(function: function, as: alias) - } - - public func column(function: String, _ arguments: Connection.Query.Select.SelectExpression.Expression.Function.Argument..., as alias: String? = nil) -> Self { + public func column( + function: String, + _ arguments: Connection.Query.Select.SelectExpression.Expression.Function.Argument..., + as alias: String? = nil + ) -> Self { return column(expression: .function(.function(function, arguments)), as: alias) } diff --git a/Sources/SQL/SQLTableIdentifier.swift b/Sources/SQL/SQLTableIdentifier.swift index 251e459..3ebd467 100644 --- a/Sources/SQL/SQLTableIdentifier.swift +++ b/Sources/SQL/SQLTableIdentifier.swift @@ -17,17 +17,27 @@ extension SQLTableIdentifier { // MARK: Generic -public struct GenericSQLTableIdentifier: SQLTableIdentifier +public struct GenericSQLTableIdentifier: SQLTableIdentifier, ExpressibleByStringLiteral where Identifier: SQLIdentifier { /// See `SQLTableIdentifier`. public static func table(_ identifier: Identifier) -> GenericSQLTableIdentifier { - return .init(identifier: identifier) + return .init(identifier) } /// See `SQLTableIdentifier`. public var identifier: Identifier - + + /// Creates a new `GenericSQLTableIdentifier`. + public init(_ identifier: Identifier) { + self.identifier = identifier + } + + /// See `ExpressibleByStringLiteral`. + public init(stringLiteral value: String) { + self.identifier = .identifier(value) + } + /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { return identifier.serialize(&binds) diff --git a/Sources/SQLite/Codable/SQLiteRowDecoder.swift b/Sources/SQLite/Codable/SQLiteRowDecoder.swift index 66b5432..ca561e3 100644 --- a/Sources/SQLite/Codable/SQLiteRowDecoder.swift +++ b/Sources/SQLite/Codable/SQLiteRowDecoder.swift @@ -26,7 +26,7 @@ public struct SQLiteRowDecoder: SQLRowDecoder { /// - type: `Decodable` type to decode. /// - data: SQLite row (`[SQLiteColumn: SQLiteData]`) to decode. /// - returns: Instance of decoded type. - public func decode(_ type: D.Type, from row: [SQLiteColumn: SQLiteData], table: SQLiteQuery.TableIdentifier? = nil) throws -> D + public func decode(_ type: D.Type, from row: [SQLiteColumn: SQLiteData], table: SQLiteTableIdentifier? = nil) throws -> D where D: Decodable { return try D(from: _Decoder(row: row, table: table?.identifier.string)) diff --git a/Sources/SQLite/Database/SQLiteConnection.swift b/Sources/SQLite/Database/SQLiteConnection.swift index a8bec4c..5b968f0 100644 --- a/Sources/SQLite/Database/SQLiteConnection.swift +++ b/Sources/SQLite/Database/SQLiteConnection.swift @@ -60,18 +60,6 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection, SQLConnect return String(cString: raw) } - /// Executes the supplied `SQLiteQuery` on the connection, aggregating the results into an array. - /// - /// let rows = try conn.query("SELECT * FROM users").wait() - /// - /// - parameters: - /// - query: `SQLiteQuery` to execute. - /// - returns: A `Future` containing array of rows. - public func query(_ query: SQLiteQuery) -> Future<[[SQLiteColumn: SQLiteData]]> { - var rows: [[SQLiteColumn: SQLiteData]] = [] - return self.query(query) { rows.append($0) }.map { rows } - } - /// Executes the supplied `SQLiteQuery` on the connection, calling the supplied closure for each row returned. /// /// try conn.query("SELECT * FROM users") { row in diff --git a/Sources/SQLite/Query/SQLiteAlterTable.swift b/Sources/SQLite/Query/SQLiteAlterTable.swift new file mode 100644 index 0000000..cf9ddfa --- /dev/null +++ b/Sources/SQLite/Query/SQLiteAlterTable.swift @@ -0,0 +1,58 @@ +/// Represents an `ALTER TABLE ...` query. +public struct SQLiteAlterTable: SQLAlterTable { + /// See `SQLAlterTable`. + public typealias TableIdentifier = SQLiteTableIdentifier + + /// See `SQLAlterTable`. + public static func alterTable(_ table: SQLiteTableIdentifier) -> SQLiteAlterTable { + return .init(table: table, value: .rename(table)) + } + + /// Supported `ALTER TABLE` methods. + public enum Value: SQLSerializable { + /// Renames the table. + case rename(SQLiteTableIdentifier) + + /// Adds a new column to the table. + case addColumn(SQLiteColumnDefinition) + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + switch self { + case .rename(let name): + sql.append("RENAME TO") + sql.append(name.serialize(&binds)) + case .addColumn(let columnDefinition): + sql.append("ADD") + sql.append(columnDefinition.serialize(&binds)) + } + return sql.joined(separator: " ") + } + } + + /// Name of table to alter. + public var table: SQLiteTableIdentifier + + /// Type of `ALTER` to perform. + public var value: Value + + /// Creates a new `AlterTable`. + /// + /// - parameters: + /// - table: Name of table to alter. + /// - value: Type of `ALTER` to perform. + public init(table: SQLiteTableIdentifier, value: Value) { + self.table = table + self.value = value + } + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + var sql: [String] = [] + sql.append("ALTER TABLE") + sql.append(table.serialize(&binds)) + sql.append(value.serialize(&binds)) + return sql.joined(separator: " ") + } +} diff --git a/Sources/SQLite/Query/SQLiteAlterTableBuilder.swift b/Sources/SQLite/Query/SQLiteAlterTableBuilder.swift new file mode 100644 index 0000000..f5d0239 --- /dev/null +++ b/Sources/SQLite/Query/SQLiteAlterTableBuilder.swift @@ -0,0 +1,42 @@ +extension SQLAlterTableBuilder where Connection.Query.AlterTable == SQLiteAlterTable { + /// Renames the table. + /// + /// conn.alter(table: Bar.self).rename(to: "foo").run() + /// + /// - parameters: + /// - to: New table name. + /// - returns: Self for chaining. + public func rename(to tableName: SQLiteTableIdentifier) -> Self { + alterTable.value = .rename(tableName) + return self + } + + /// Adds a new column to the table. Only one column can be added per `ALTER` statement. + /// + /// conn.alter(table: Planet.self).addColumn(for: \.name, type: .text, .notNull).run() + /// + /// - parameters: + /// - keyPath: Swift `KeyPath` to property that should be added. + /// - type: Name of type to use for this column. + /// - constraints: Zero or more column constraints to add. + /// - returns: Self for chaining. + public func addColumn( + for keyPath: KeyPath, + type dataType: SQLiteDataType, + _ constraints: SQLiteColumnConstraint... + ) -> Self where T: SQLiteTable { + return addColumn(.columnDefinition(.keyPath(keyPath), dataType, constraints)) + } + + /// Adds a new column to the table. Only one column can be added per `ALTER` statement. + /// + /// conn.alter(table: Planet.self).addColumn(...).run() + /// + /// - parameters: + /// - columnDefinition: Column definition to add. + /// - returns: Self for chaining. + public func addColumn(_ columnDefinition: SQLiteColumnDefinition) -> Self { + alterTable.value = .addColumn(columnDefinition) + return self + } +} diff --git a/Sources/SQLite/Query/SQLiteBind.swift b/Sources/SQLite/Query/SQLiteBind.swift index 90ae9af..ba30acd 100644 --- a/Sources/SQLite/Query/SQLiteBind.swift +++ b/Sources/SQLite/Query/SQLiteBind.swift @@ -11,7 +11,7 @@ public struct SQLiteBind: SQLBind { } public enum Value { - case expression(SQLiteQuery.Expression) + case expression(SQLiteExpression) case encodable(Encodable) } diff --git a/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift b/Sources/SQLite/Query/SQLiteCreateTable.swift similarity index 86% rename from Sources/SQLite/Query/SQLiteQuery+CreateTable.swift rename to Sources/SQLite/Query/SQLiteCreateTable.swift index 404ec95..99a5cda 100644 --- a/Sources/SQLite/Query/SQLiteQuery+CreateTable.swift +++ b/Sources/SQLite/Query/SQLiteCreateTable.swift @@ -15,12 +15,14 @@ extension SQLCreateTableBuilder where Connection.Query.CreateTable == SQLiteCrea /// https://www.sqlite.org/lang_createtable.html public struct SQLiteCreateTable: SQLCreateTable { /// See `SQLCreateTable`. - public static func createTable(_ table: SQLiteQuery.TableIdentifier) -> SQLiteCreateTable { + public static func createTable(_ table: SQLiteTableIdentifier) -> SQLiteCreateTable { return .init(createTable: .createTable(table), withoutRowID: false) } /// See `SQLCreateTable`. - public var createTable: GenericSQLCreateTable + public var createTable: GenericSQLCreateTable< + SQLiteTableIdentifier, SQLiteColumnDefinition, SQLiteTableConstraint + > /// See `SQLCreateTable`. @@ -36,19 +38,19 @@ public struct SQLiteCreateTable: SQLCreateTable { } /// See `SQLCreateTable`. - public var table: SQLiteQuery.TableIdentifier { + public var table: SQLiteTableIdentifier { get { return createTable.table } set { return createTable.table = newValue } } /// See `SQLCreateTable`. - public var columns: [SQLiteQuery.ColumnDefinition] { + public var columns: [SQLiteColumnDefinition] { get { return createTable.columns } set { return createTable.columns = newValue } } /// See `SQLCreateTable`. - public var tableConstraints: [SQLiteQuery.TableConstraint] { + public var tableConstraints: [SQLiteTableConstraint] { get { return createTable.tableConstraints } set { return createTable.tableConstraints = newValue } } diff --git a/Sources/SQLite/Query/SQLiteFunction.swift b/Sources/SQLite/Query/SQLiteFunction.swift index 269806f..8730e0f 100644 --- a/Sources/SQLite/Query/SQLiteFunction.swift +++ b/Sources/SQLite/Query/SQLiteFunction.swift @@ -1,5 +1,5 @@ public struct SQLiteFunction: SQLFunction { - public typealias Argument = GenericSQLFunctionArgument + public typealias Argument = GenericSQLFunctionArgument public static var count: SQLiteFunction { return .init(name: "COUNT", arguments: [.all]) diff --git a/Sources/SQLite/Query/SQLiteGeneric.swift b/Sources/SQLite/Query/SQLiteGeneric.swift new file mode 100644 index 0000000..608e9fd --- /dev/null +++ b/Sources/SQLite/Query/SQLiteGeneric.swift @@ -0,0 +1,100 @@ +/// See `SQLQuery`. +public typealias SQLiteBinaryOperator = GenericSQLBinaryOperator + +/// See `SQLQuery`. +public typealias SQLiteColumnConstraintAlgorithm = GenericSQLColumnConstraintAlgorithm< + SQLiteExpression, SQLiteCollation, SQLitePrimaryKey, SQLiteForeignKey +> + +/// See `SQLQuery`. +public typealias SQLiteColumnConstraint = GenericSQLColumnConstraint< + SQLiteIdentifier, SQLiteColumnConstraintAlgorithm +> + +/// See `SQLQuery`. +public typealias SQLiteColumnDefinition = GenericSQLColumnDefinition< + SQLiteColumnIdentifier, SQLiteDataType, SQLiteColumnConstraint +> + +/// See `SQLQuery`. +public typealias SQLiteColumnIdentifier = GenericSQLColumnIdentifier< + SQLiteTableIdentifier, SQLiteIdentifier +> + +/// See `SQLQuery`. +public typealias SQLiteConflictResolution = GenericSQLConflictResolution + +/// See `SQLQuery`. +public typealias SQLiteDelete = GenericSQLDelete< + SQLiteTableIdentifier, SQLiteExpression +> + +/// See `SQLQuery`. +public typealias SQLiteDirection = GenericSQLDirection + +/// See `SQLQuery`. +public typealias SQLiteDistinct = GenericSQLDistinct + +/// See `SQLQuery`. +public typealias SQLiteDropTable = GenericSQLDropTable + +/// See `SQLQuery`. +public typealias SQLiteExpression = GenericSQLExpression< + SQLiteLiteral, SQLiteBind, SQLiteColumnIdentifier, SQLiteBinaryOperator, SQLiteFunction, SQLiteQuery +> + +/// See `SQLQuery`. +public typealias SQLiteForeignKey = GenericSQLForeignKey< + SQLiteTableIdentifier, SQLiteIdentifier, SQLiteConflictResolution +> + +/// See `SQLQuery`. +public typealias SQLiteGroupBy = GenericSQLGroupBy + +/// See `SQLQuery`. +public typealias SQLiteIdentifier = GenericSQLIdentifier + +/// See `SQLQuery`. +public typealias SQLiteInsert = GenericSQLInsert< + SQLiteTableIdentifier, SQLiteColumnIdentifier, SQLiteExpression +> + +/// See `SQLQuery`. +public typealias SQLiteJoin = GenericSQLJoin< + SQLiteJoinMethod, SQLiteTableIdentifier, SQLiteExpression +> + +/// See `SQLQuery`. +public typealias SQLiteJoinMethod = GenericSQLJoinMethod + +/// See `SQLQuery`. +public typealias SQLiteLiteral = GenericSQLLiteral + +/// See `SQLQuery`. +public typealias SQLiteOrderBy = GenericSQLOrderBy + +/// See `SQLQuery`. +public typealias SQLiteSelect = GenericSQLSelect< + SQLiteDistinct, SQLiteSelectExpression, SQLiteTableIdentifier, SQLiteJoin, SQLiteExpression, SQLiteGroupBy, SQLiteOrderBy +> + +/// See `SQLQuery`. +public typealias SQLiteSelectExpression = GenericSQLSelectExpression + +/// See `SQLQuery`. +public typealias SQLiteTableConstraintAlgorithm = GenericSQLTableConstraintAlgorithm< + SQLiteColumnIdentifier, SQLiteExpression, SQLiteCollation, SQLitePrimaryKey, SQLiteForeignKey +> + +/// See `SQLQuery`. +public typealias SQLiteTableConstraint = GenericSQLTableConstraint< + SQLiteIdentifier, SQLiteTableConstraintAlgorithm +> + +/// See `SQLQuery`. +public typealias SQLiteTableIdentifier = GenericSQLTableIdentifier + +/// See `SQLQuery`. +public typealias SQLiteUpdate = GenericSQLUpdate< + SQLiteTableIdentifier, SQLiteIdentifier, SQLiteExpression +> diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift deleted file mode 100644 index b51acb5..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTable.swift +++ /dev/null @@ -1,56 +0,0 @@ -//extension SQLiteQuery { -// /// Represents an `ALTER TABLE ...` query. -// /// -// /// See `SQLiteQuery.AlterTableBuilder` to build this query. -// public struct AlterTable { -// /// Supported `ALTER TABLE` methods. -// public enum Value { -// /// Renames the table. -// case rename(Name) -// -// /// Adds a new column to the table. -// case addColumn(ColumnDefinition) -// } -// -// /// Name of table to alter. -// public var table: TableName -// -// /// Type of `ALTER` to perform. -// public var value: Value -// -// /// Creates a new `AlterTable`. -// /// -// /// - parameters: -// /// - table: Name of table to alter. -// /// - value: Type of `ALTER` to perform. -// public init(table: TableName, value: Value) { -// self.table = table -// self.value = value -// } -// } -//} -// -//// MARK: Serialize -// -////extension SQLiteSerializer { -//// internal func serialize(_ alter: SQLiteQuery.AlterTable, _ binds: inout [SQLiteData]) -> String { -//// var sql: [String] = [] -//// sql.append("ALTER TABLE") -//// sql.append(serialize(alter.table)) -//// sql.append(serialize(alter.value, &binds)) -//// return sql.joined(separator: " ") -//// } -//// -//// internal func serialize(_ value: SQLiteQuery.AlterTable.Value, _ binds: inout [SQLiteData]) -> String { -//// var sql: [String] = [] -//// switch value { -//// case .rename(let name): -//// sql.append("RENAME TO") -//// sql.append(serialize(name)) -//// case .addColumn(let columnDefinition): -//// sql.append("ADD") -//// sql.append(serialize(columnDefinition, &binds)) -//// } -//// return sql.joined(separator: " ") -//// } -////} diff --git a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift b/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift deleted file mode 100644 index 6d18de5..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+AlterTableBuilder.swift +++ /dev/null @@ -1,84 +0,0 @@ -//extension SQLiteQuery { -// /// Builds `AlterTable` queries. -// public final class AlterTableBuilder
where Table: SQLiteTable { -// /// Query being built. -// public var alter: AlterTable -// -// /// Database connection to execute the query on. -// public let connection: SQLiteConnection -// -// /// Creates a new `AlterTableBuilder`. -// /// -// /// - parameters: -// /// - table: Name of existing table to alter. -// /// - connection: `SQLiteConnection` to perform the query on. -// init(table: Table.Type, on connection: SQLiteConnection) { -// fatalError() -//// self.alter = .init(table: Table.sqliTableName, value: .rename(Table.sqliteTableName.name)) -// self.connection = connection -// } -// -// /// Renames the table. -// /// -// /// conn.alter(table: Bar.self).rename(to: "foo").run() -// /// -// /// - parameters: -// /// - to: New table name. -// /// - returns: Self for chaining. -// public func rename(to tableName: Name) -> Self { -// alter.value = .rename(tableName) -// return self -// } -// -// /// Adds a new column to the table. Only one column can be added per `ALTER` statement. -// /// -// /// conn.alter(table: Planet.self).addColumn(for: \.name, type: .text, .notNull).run() -// /// -// /// - parameters: -// /// - keyPath: Swift `KeyPath` to property that should be added. -// /// - type: Name of type to use for this column. -// /// - constraints: Zero or more column constraints to add. -// /// - returns: Self for chaining. -// public func addColumn( -// for keyPath: KeyPath, -// type typeName: TypeName, -// _ constraints: SQLiteQuery.ColumnConstraint... -// ) -> Self { -// return addColumn(.init(name: keyPath.sqlColumnName.name, typeName: typeName, constraints: constraints)) -// } -// -// /// Adds a new column to the table. Only one column can be added per `ALTER` statement. -// /// -// /// conn.alter(table: Planet.self).addColumn(...).run() -// /// -// /// - parameters: -// /// - columnDefinition: Column definition to add. -// /// - returns: Self for chaining. -// public func addColumn(_ columnDefinition: ColumnDefinition) -> Self { -// alter.value = .addColumn(columnDefinition) -// return self -// } -// -// /// Runs the `ALTER` query. -// /// -// /// - returns: A `Future` that signals completion. -// public func run() -> Future { -// return connection.query(.alterTable(alter)).transform(to: ()) -// } -// } -//} -// -//extension SQLiteConnection { -// /// Creates a new `AlterTableBuilder`. -// /// -// /// conn.alter(table: Planet.self)... -// /// -// /// - parameters: -// /// - table: Table to alter. -// /// - returns: `AlterTableBuilder`. -// public func alter
(table: Table.Type) -> SQLiteQuery.AlterTableBuilder
-// where Table: SQLiteTable -// { -// return .init(table: Table.self, on: self) -// } -//} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift deleted file mode 100644 index ebd2950..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Literal.swift +++ /dev/null @@ -1,51 +0,0 @@ -//extension SQLiteQuery.Expression { -// public enum Literal: Equatable { -// case numeric(String) -// case string(String) -// case blob(Data) -// case bool(Bool) -// case null -// case currentTime -// case currentDate -// case currentTimestamp -// } -//} -// -//extension SQLiteQuery.Expression.Literal: ExpressibleByStringLiteral { -// public init(stringLiteral value: String) { -// self = .string(value) -// } -//} -// -//extension SQLiteQuery.Expression.Literal: ExpressibleByIntegerLiteral { -// public init(integerLiteral value: Int) { -// self = .numeric(value.description) -// } -//} -// -//extension SQLiteQuery.Expression.Literal: ExpressibleByFloatLiteral { -// public init(floatLiteral value: Double) { -// self = .numeric(value.description) -// } -//} -// -//extension SQLiteQuery.Expression.Literal: ExpressibleByBooleanLiteral { -// public init(booleanLiteral value: Bool) { -// self = .bool(value) -// } -//} -// -//extension SQLiteSerializer { -// func serialize(_ literal: SQLiteQuery.Expression.Literal) -> String { -// switch literal { -// case .numeric(let string): return string -// case .string(let string): return "'" + string + "'" -// case .blob(let blob): return "0x" + blob.hexEncodedString() -// case .null: return "NULL" -// case .bool(let bool): return bool.description.uppercased() -// case .currentTime: return "CURRENT_TIME" -// case .currentDate: return "CURRENT_DATE" -// case .currentTimestamp: return "CURRENT_TIMESTAMP" -// } -// } -//} diff --git a/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift b/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift deleted file mode 100644 index b603762..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Expression+Operators.swift +++ /dev/null @@ -1,139 +0,0 @@ -infix operator ~~ -infix operator !~ -// -//// MARK: Expression to Expression operators -// -//public func < (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { -// return .binary(lhs, .lessThan, rhs) -//} -// -//public func <= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { -// return .binary(lhs, .lessThanOrEqual, rhs) -//} -// -//public func > (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { -// return .binary(lhs, .greaterThan, rhs) -//} -// -//public func >= (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { -// return .binary(lhs, .greaterThanOrEqual, rhs) -//} -// -//public func == (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { -// return .binary(lhs, .equal, rhs) -//} -// -//public func != (_ lhs: SQLiteQuery.Expression, _ rhs: SQLiteQuery.Expression) -> SQLiteQuery.Expression { -// return .binary(lhs, .notEqual, rhs) -//} -// -//public func ~~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { -// return .binary(lhs, .in, .expressions(rhs)) -//} -// -//public func !~ (_ lhs: SQLiteQuery.Expression, _ rhs: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { -// return .binary(lhs, .notIn, .expressions(rhs)) -//} -// -//// MARK: KeyPath to Value operators -// -//public func < (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .lessThan, .bind(rhs)) -//} -// -//public func <= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .bind(rhs)) -//} -// -//public func > (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .greaterThan, .bind(rhs)) -//} -// -//public func >= (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .bind(rhs)) -//} -// -//public func == (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .equal, .bind(rhs)) -//} -// -//public func != (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .notEqual, .bind(rhs)) -//} -// -//public func ~~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .in, .bind(rhs)) -//} -// -//public func !~ (_ lhs: KeyPath, _ rhs: Value) throws -> SQLiteQuery.Expression -// where Table: SQLiteTable, Value: Encodable -//{ -// return try .binary(.column(lhs.sqliteColumnName), .notIn, .bind(rhs)) -//} -// -//// MARK: KeyPath to KeyPath operators -// -//public func < (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .lessThan, .column(rhs.sqliteColumnName)) -//} -// -//public func <= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .lessThanOrEqual, .column(rhs.sqliteColumnName)) -//} -// -//public func > (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .greaterThan, .column(rhs.sqliteColumnName)) -//} -// -//public func >= (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .greaterThanOrEqual, .column(rhs.sqliteColumnName)) -//} -// -//public func == (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .equal, .column(rhs.sqliteColumnName)) -//} -// -//public func != (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .notEqual, .column(rhs.sqliteColumnName)) -//} -// -//public func ~~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .in, .column(rhs.sqliteColumnName)) -//} -// -//public func !~ (_ lhs: KeyPath, _ rhs: KeyPath) -> SQLiteQuery.Expression -// where TableA: SQLiteTable, ValueA: Encodable, TableB: SQLiteTable, ValueB: Encodable -//{ -// return .binary(.column(lhs.sqliteColumnName), .notIn, .column(rhs.sqliteColumnName)) -//} -// -//// MARK: AND / OR operators -// diff --git a/Sources/SQLite/Query/SQLiteQuery+Join.swift b/Sources/SQLite/Query/SQLiteQuery+Join.swift deleted file mode 100644 index b292e0a..0000000 --- a/Sources/SQLite/Query/SQLiteQuery+Join.swift +++ /dev/null @@ -1,83 +0,0 @@ -//extension SQLiteQuery { -// public struct JoinClause { -// public struct Join { -// public enum Operator { -// case inner -// case outer -// case cross -// -// } -// -// public enum Constraint { -// case condition(Expression) -// case using([Name]) -// } -// -// public var natural: Bool -// public var op: Operator? -// public var table: TableOrSubquery -// public var constraint: Constraint? -// -// public init(natural: Bool = false, _ op: Operator? = nil, table: TableOrSubquery, constraint: Constraint? = nil) { -// self.natural = natural -// self.op = op -// self.table = table -// self.constraint = constraint -// } -// } -// public var table: TableOrSubquery -// public var joins: [Join] -// -// public init(table: TableOrSubquery, joins: [Join] = []) { -// self.table = table -// self.joins = joins -// } -// } -//} -// -//extension SQLiteSerializer { -// func serialize(_ join: SQLiteQuery.JoinClause, _ binds: inout [SQLiteData]) -> String { -// var sql: [String] = [] -// sql.append(serialize(join.table, &binds)) -// sql += join.joins.map { serialize($0, &binds) } -// return sql.joined(separator: " ") -// } -// -// func serialize(_ join: SQLiteQuery.JoinClause.Join, _ binds: inout [SQLiteData]) -> String { -// var sql: [String] = [] -// if join.natural { -// sql.append("NATURAL") -// } -// if let op = join.op { -// sql.append(serialize(op)) -// sql.append("JOIN") -// } -// sql.append(serialize(join.table, &binds)) -// if let constraint = join.constraint { -// sql.append(serialize(constraint, &binds)) -// } -// return sql.joined(separator: " ") -// -// } -// -// func serialize(_ constraint: SQLiteQuery.JoinClause.Join.Constraint, _ binds: inout [SQLiteData]) -> String { -// var sql: [String] = [] -// switch constraint { -// case .condition(let expr): -// sql.append("ON") -// sql.append(serialize(expr, &binds)) -// case .using(let columns): -// sql.append("USING") -// sql.append(serialize(columns)) -// } -// return sql.joined(separator: " ") -// } -// -// func serialize(_ op: SQLiteQuery.JoinClause.Join.Operator) -> String { -// switch op { -// case .outer: return "LEFT OUTER" -// case .inner: return "INNER" -// case .cross: return "CROSS" -// } -// } -//} diff --git a/Sources/SQLite/Query/SQLiteQuery.swift b/Sources/SQLite/Query/SQLiteQuery.swift index 1f9054e..6e8456b 100644 --- a/Sources/SQLite/Query/SQLiteQuery.swift +++ b/Sources/SQLite/Query/SQLiteQuery.swift @@ -1,144 +1,88 @@ -public enum SQLiteQuery: SQLQuery { +public enum SQLiteQuery: SQLQuery { + /// See `SQLQuery`. + public typealias AlterTable = SQLiteAlterTable + + /// See `SQLQuery`. + public typealias CreateTable = SQLiteCreateTable + + /// See `SQLQuery`. + public typealias Delete = SQLiteDelete + + /// See `SQLQuery`. + public typealias DropTable = SQLiteDropTable + + /// See `SQLQuery`. + public typealias Insert = SQLiteInsert + + /// See `SQLQuery`. + public typealias Select = SQLiteSelect + + /// See `SQLQuery`. + public typealias Update = SQLiteUpdate + + /// See `SQLQuery`. + public typealias RowDecoder = SQLiteRowDecoder + + /// See `SQLQuery`. + public static func alterTable(_ alterTable: SQLiteAlterTable) -> SQLiteQuery { + return ._alterTable(alterTable) + } + /// See `SQLQuery`. public static func createTable(_ createTable: SQLiteCreateTable) -> SQLiteQuery { return ._createTable(createTable) } /// See `SQLQuery`. - public static func delete(_ delete: SQLiteQuery.Delete) -> SQLiteQuery { + public static func delete(_ delete: SQLiteDelete) -> SQLiteQuery { return ._delete(delete) } /// See `SQLQuery`. - public static func dropTable(_ dropTable: GenericSQLDropTable) -> SQLiteQuery { + public static func dropTable(_ dropTable: SQLiteDropTable) -> SQLiteQuery { return ._dropTable(dropTable) } /// See `SQLQuery`. - public static func insert(_ insert: Insert) -> SQLiteQuery { + public static func insert(_ insert: SQLiteInsert) -> SQLiteQuery { return ._insert(insert) } /// See `SQLQuery`. - public static func select(_ select: Select) -> SQLiteQuery { + public static func select(_ select: SQLiteSelect) -> SQLiteQuery { return ._select(select) } /// See `SQLQuery`. - public static func update(_ update: SQLiteQuery.Update) -> SQLiteQuery { + public static func update(_ update: SQLiteUpdate) -> SQLiteQuery { return ._update(update) } - - /// See `SQLQuery`. - public typealias BinaryOperator = GenericSQLBinaryOperator - - /// See `SQLQuery`. - public typealias Collation = SQLiteCollation - - /// See `SQLQuery`. - public typealias ColumnConstraintAlgorithm = GenericSQLColumnConstraintAlgorithm - - /// See `SQLQuery`. - public typealias ColumnConstraint = GenericSQLColumnConstraint - - /// See `SQLQuery`. - public typealias ColumnDefinition = GenericSQLColumnDefinition - - /// See `SQLQuery`. - public typealias ColumnIdentifier = GenericSQLColumnIdentifier - - /// See `SQLQuery`. - public typealias ConflictResolution = GenericSQLConflictResolution - - /// See `SQLQuery`. - public typealias CreateTable = SQLiteCreateTable - - /// See `SQLQuery`. - public typealias DataType = SQLiteDataType - - /// See `SQLQuery`. - public typealias Delete = GenericSQLDelete - - /// See `SQLQuery`. - public typealias Direction = GenericSQLDirection - - /// See `SQLQuery`. - public typealias Distinct = GenericSQLDistinct - - /// See `SQLQuery`. - public typealias DropTable = GenericSQLDropTable - - /// See `SQLQuery`. - public typealias Expression = GenericSQLExpression - - /// See `SQLQuery`. - public typealias ForeignKey = GenericSQLForeignKey - - /// See `SQLQuery`. - public typealias GroupBy = GenericSQLGroupBy - - /// See `SQLQuery`. - public typealias Identifier = GenericSQLIdentifier - - /// See `SQLQuery`. - public typealias Insert = GenericSQLInsert - - /// See `SQLQuery`. - public typealias Join = GenericSQLJoin - - /// See `SQLQuery`. - public typealias JoinMethod = GenericSQLJoinMethod - - /// See `SQLQuery`. - public typealias Literal = GenericSQLLiteral - - /// See `SQLQuery`. - public typealias OrderBy = GenericSQLOrderBy - - /// See `SQLQuery`. - public typealias PrimaryKey = SQLitePrimaryKey - - /// See `SQLQuery`. - public typealias Select = GenericSQLSelect - - /// See `SQLQuery`. - public typealias SelectExpression = GenericSQLSelectExpression - - /// See `SQLQuery`. - public typealias TableConstraintAlgorithm = GenericSQLTableConstraintAlgorithm - - /// See `SQLQuery`. - public typealias TableConstraint = GenericSQLTableConstraint - - /// See `SQLQuery`. - public typealias TableIdentifier = GenericSQLTableIdentifier - - /// See `SQLQuery`. - public typealias Update = GenericSQLUpdate - + /// See `SQLQuery`. - public typealias RowDecoder = SQLiteRowDecoder + public static func raw(_ sql: String, binds: [Encodable]) -> SQLiteQuery { + return ._raw(sql, binds) + } /// See `SQLQuery`. -// case alterTable(AlterTable) - + case _alterTable(SQLiteAlterTable) + /// See `SQLQuery`. case _createTable(SQLiteCreateTable) /// See `SQLQuery`. - case _delete(Delete) + case _delete(SQLiteDelete) /// See `SQLQuery`. - case _dropTable(DropTable) + case _dropTable(SQLiteDropTable) /// See `SQLQuery`. - case _insert(Insert) + case _insert(SQLiteInsert) /// See `SQLQuery`. - case _select(Select) + case _select(SQLiteSelect) /// See `SQLQuery`. - case _update(Update) + case _update(SQLiteUpdate) /// See `SQLQuery`. case _raw(String, [Encodable]) @@ -146,6 +90,7 @@ public enum SQLiteQuery: SQLQuery { /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { switch self { + case ._alterTable(let alterTable): return alterTable.serialize(&binds) case ._createTable(let createTable): return createTable.serialize(&binds) case ._delete(let delete): return delete.serialize(&binds) case ._dropTable(let dropTable): return dropTable.serialize(&binds) diff --git a/Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift b/Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift index 214ae45..d7a4c53 100644 --- a/Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift +++ b/Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift @@ -5,5 +5,5 @@ /// By default, types will encode to `SQLiteQuery.Expression.data(...)`. public protocol SQLiteQueryExpressionRepresentable { /// Custom `SQLiteQuery.Expression` to encode to. - var sqliteQueryExpression: SQLiteQuery.Expression { get } + var sqliteQueryExpression: SQLiteExpression { get } } diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index a6b5c16..57ca975 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -33,16 +33,16 @@ class SQLiteTests: XCTestCase { print(res) } -// func testVersionBuild() throws { -// let conn = try SQLiteConnection.makeTest() -// -// let res = try conn.select() -// .column(function: "sqlite_version", as: "version") -// .run().wait() -// print(res) -// } - - func testSQLSelect() throws { + func testVersionBuild() throws { + let conn = try SQLiteConnection.makeTest() + + let res = try conn.select() + .column(function: "sqlite_version", as: "version") + .all().wait() + print(res) + } + + func testSQL() throws { let conn = try SQLiteConnection.makeTest() try conn.drop(table: Planet.self) @@ -60,10 +60,13 @@ class SQLiteTests: XCTestCase { .temporary() .ifNotExists() .column(for: \Planet.id, type: .integer, .primaryKey) - .column(for: \Planet.name, type: .text, .notNull) .column(for: \Planet.galaxyID, type: .integer, .notNull, .references(\Galaxy.id)) .run().wait() - + + try conn.alter(table: Planet.self) + .addColumn(for: \Planet.name, type: .text, .notNull, .default(.literal("Unamed Planet"))) + .run().wait() + try conn.insert(into: Galaxy.self) .value(Galaxy(name: "Milky Way")) .run().wait() @@ -117,203 +120,151 @@ class SQLiteTests: XCTestCase { .all().wait() print(b) } - -// func testSQLQuery() throws { -// let conn = try SQLiteConnection.makeTest() -// -// _ = try conn.query("PRAGMA foreign_keys = ON;") -// .wait() -// -// -// try conn.create(table: Galaxy.self) -// .column(for: \.id, type: .integer, .primaryKey(), .notNull()) -// .column(for: \.name, type: .text) -// .run().wait() -// try conn.create(table: Planet.self) -// .column(for: \.id, type: .integer, .primaryKey(), .notNull()) -// .column(for: \.galaxyID, type: .integer, .notNull(), .references(\Galaxy.id)) -// .run().wait() -// -// try conn.alter(table: Planet.self) -// .addColumn(for: \.name, type: .text, .notNull(), .default(.literal("Unamed Planet"))) -// .run().wait() -// -// try conn.insert(into: Galaxy.self) -// .value(Galaxy(name: "Milky Way")) -// .run().wait() -// -// -// let selectA = try conn.select().all() -// .from(Planet.self) -// .orWhere(\Planet.name == "Mars", \Planet.name == "Venus", \Planet.name == "Earth") -// .run(decoding: Planet.self).wait() -// print(selectA) -// -// try conn.delete(from: Planet.self).where(\Planet.name == "Pluto") -// .run().wait() -// -// let selectB = try conn.select().all().from(Planet.self) -// .run(decoding: Planet.self).wait() -// print(selectB) -// -// let selectC = try conn.select().all() -// .from(Planet.self) -// .join(Galaxy.self, on: \Planet.galaxyID == \Galaxy.id) -// .run { try ($0.decode(Planet.self), $0.decode(Galaxy.self)) } -// .wait() -// print(selectC) -// -// let res = try conn.select().all().from(Planet.self) -// .where("name", .like, .bind("%rth")) -// .orWhere(.literal(1), .equal, .literal(2)) -// .run().wait() -// print(res) -// -// try conn.create(table: Galaxy2.self).temporary().ifNotExists() -// .as { $0.select().all().from(Galaxy.self) } -// .run().wait() -// } -// -// func testTables() throws { -// let database = try SQLiteConnection.makeTest() -// _ = try database.query("DROP TABLE IF EXISTS foo").wait() -// _ = try database.query("CREATE TABLE foo (bar INT(4), baz VARCHAR(16), biz FLOAT)").wait() -// _ = try database.query("INSERT INTO foo VALUES (42, 'Life', 0.44)").wait() -// _ = try database.query("INSERT INTO foo VALUES (1337, 'Elite', 209.234)").wait() -// _ = try database.query("INSERT INTO foo VALUES (9, NULL, 34.567)").wait() -// -// if let resultBar = try database.query("SELECT * FROM foo WHERE bar = 42").wait().first { -// XCTAssertEqual(resultBar.firstValue(forColumn: "bar"), .integer(42)) -// XCTAssertEqual(resultBar.firstValue(forColumn: "baz"), .text("Life")) -// XCTAssertEqual(resultBar.firstValue(forColumn: "biz"), .float(0.44)) -// } else { -// XCTFail("Could not get bar result") -// } -// -// -// if let resultBaz = try database.query("SELECT * FROM foo where baz = 'Elite'").wait().first { -// XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(1337)) -// XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .text("Elite")) -// } else { -// XCTFail("Could not get baz result") -// } -// -// if let resultBaz = try database.query("SELECT * FROM foo where bar = 9").wait().first { -// XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(9)) -// XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .null) -// } else { -// XCTFail("Could not get null result") -// } -// } -// -// func testUnicode() throws { -// let database = try SQLiteConnection.makeTest() -// /// This string includes characters from most Unicode categories -// /// such as Latin, Latin-Extended-A/B, Cyrrilic, Greek etc. -// let unicode = "®¿ÐØ×ĞƋƢǂNJǕǮȐȘȢȱȵẀˍΔῴЖ♆" -// _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() -// _ = try database.query("CREATE TABLE `foo` (bar TEXT)").wait() -// -// _ = try database.query(.raw("INSERT INTO `foo` VALUES(?)", [unicode.convertToSQLiteData()])).wait() -// let selectAllResults = try database.query("SELECT * FROM `foo`").wait().first -// XCTAssertNotNil(selectAllResults) -// XCTAssertEqual(selectAllResults!.firstValue(forColumn: "bar"), .text(unicode)) -// -// let selectWhereResults = try database.query(.raw("SELECT * FROM `foo` WHERE bar = '\(unicode)'", [])).wait().first -// XCTAssertNotNil(selectWhereResults) -// XCTAssertEqual(selectWhereResults!.firstValue(forColumn: "bar"), .text(unicode)) -// } -// -// func testBigInts() throws { -// let database = try SQLiteConnection.makeTest() -// let max = Int.max -// -// _ = try database.query("DROP TABLE IF EXISTS foo").wait() -// _ = try database.query("CREATE TABLE foo (max INT)").wait() -// _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [max.convertToSQLiteData()])).wait() -// -// if let result = try! database.query("SELECT * FROM foo").wait().first { -// XCTAssertEqual(result.firstValue(forColumn: "max"), .integer(max)) -// } -// } -// -// func testBlob() throws { -// let database = try SQLiteConnection.makeTest() -// let data = Data(bytes: [0, 1, 2]) -// -// _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() -// _ = try database.query("CREATE TABLE foo (bar BLOB(4))").wait() -// _ = try database.query(.raw("INSERT INTO foo VALUES (?)", [data.convertToSQLiteData()])).wait() -// -// if let result = try database.query("SELECT * FROM foo").wait().first { -// XCTAssertEqual(result.firstValue(forColumn: "bar"), .blob(data)) -// } else { -// XCTFail() -// } -// } -// -// func testError() throws { -// let database = try SQLiteConnection.makeTest() -// do { -// _ = try database.query("asdf").wait() -// XCTFail("Should have errored") -// } catch let error as SQLiteError { -// print(error) -// XCTAssert(error.reason.contains("syntax error")) -// } catch { -// XCTFail("wrong error") -// } -// } -// -// // https://github.com/vapor/sqlite/issues/33 -// func testDecodeSameColumnName() throws { -// let row: [SQLiteColumn: SQLiteData] = [ -// SQLiteColumn(table: "foo", name: "id"): .text("foo"), -// SQLiteColumn(table: "bar", name: "id"): .text("bar"), -// ] -// struct User: Decodable { -// var id: String -// } -// try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "foo").id, "foo") -// try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "bar").id, "bar") -// } -// -// func testMultiThreading() throws { -// let db = try SQLiteDatabase(storage: .memory) -// let elg = MultiThreadedEventLoopGroup(numberOfThreads: 2) -// let a = elg.next() -// let b = elg.next() -// let group = DispatchGroup() -// group.enter() -// DispatchQueue.global().async { -// let conn = try! db.newConnection(on: a).wait() -// for i in 0..<100 { -// print("a \(i)") -// let res = try! conn.query("SELECT (1 + 1) as a;").wait() -// print(res) -// } -// group.leave() -// } -// group.enter() -// DispatchQueue.global().async { -// let conn = try! db.newConnection(on: b).wait() -// for i in 0..<100 { -// print("b \(i)") -// let res = try! conn.query("SELECT (1 + 1) as b;").wait() -// print(res) -// } -// group.leave() -// } -// group.wait() -// } -// -// static let allTests = [ -// ("testTables", testTables), -// ("testUnicode", testUnicode), -// ("testBigInts", testBigInts), -// ("testBlob", testBlob), -// ("testError", testError), -// ("testDecodeSameColumnName", testDecodeSameColumnName) -// ] + + func testTables() throws { + let database = try SQLiteConnection.makeTest() + _ = try database.query("DROP TABLE IF EXISTS foo").wait() + _ = try database.query("CREATE TABLE foo (bar INT(4), baz VARCHAR(16), biz FLOAT)").wait() + _ = try database.query("INSERT INTO foo VALUES (42, 'Life', 0.44)").wait() + _ = try database.query("INSERT INTO foo VALUES (1337, 'Elite', 209.234)").wait() + _ = try database.query("INSERT INTO foo VALUES (9, NULL, 34.567)").wait() + + if let resultBar = try database.query("SELECT * FROM foo WHERE bar = 42").wait().first { + XCTAssertEqual(resultBar.firstValue(forColumn: "bar"), .integer(42)) + XCTAssertEqual(resultBar.firstValue(forColumn: "baz"), .text("Life")) + XCTAssertEqual(resultBar.firstValue(forColumn: "biz"), .float(0.44)) + } else { + XCTFail("Could not get bar result") + } + + + if let resultBaz = try database.query("SELECT * FROM foo where baz = 'Elite'").wait().first { + XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(1337)) + XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .text("Elite")) + } else { + XCTFail("Could not get baz result") + } + + if let resultBaz = try database.query("SELECT * FROM foo where bar = 9").wait().first { + XCTAssertEqual(resultBaz.firstValue(forColumn: "bar"), .integer(9)) + XCTAssertEqual(resultBaz.firstValue(forColumn: "baz"), .null) + } else { + XCTFail("Could not get null result") + } + } + + func testUnicode() throws { + let database = try SQLiteConnection.makeTest() + /// This string includes characters from most Unicode categories + /// such as Latin, Latin-Extended-A/B, Cyrrilic, Greek etc. + let unicode = "®¿ÐØ×ĞƋƢǂNJǕǮȐȘȢȱȵẀˍΔῴЖ♆" + _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() + _ = try database.query("CREATE TABLE `foo` (bar TEXT)").wait() + + _ = try database.query("INSERT INTO `foo` VALUES(?)", [unicode]).wait() + let selectAllResults = try database.query("SELECT * FROM `foo`").wait().first + XCTAssertNotNil(selectAllResults) + XCTAssertEqual(selectAllResults!.firstValue(forColumn: "bar"), .text(unicode)) + + let selectWhereResults = try database.query("SELECT * FROM `foo` WHERE bar = '\(unicode)'").wait().first + XCTAssertNotNil(selectWhereResults) + XCTAssertEqual(selectWhereResults!.firstValue(forColumn: "bar"), .text(unicode)) + } + + func testBigInts() throws { + let database = try SQLiteConnection.makeTest() + let max = Int.max + + _ = try database.query("DROP TABLE IF EXISTS foo").wait() + _ = try database.query("CREATE TABLE foo (max INT)").wait() + _ = try database.query("INSERT INTO foo VALUES (?)", [max]).wait() + + if let result = try! database.query("SELECT * FROM foo").wait().first { + XCTAssertEqual(result.firstValue(forColumn: "max"), .integer(max)) + } + } + + func testBlob() throws { + let database = try SQLiteConnection.makeTest() + let data = Data(bytes: [0, 1, 2]) + + _ = try database.query("DROP TABLE IF EXISTS `foo`").wait() + _ = try database.query("CREATE TABLE foo (bar BLOB(4))").wait() + _ = try database.query("INSERT INTO foo VALUES (?)", [data]).wait() + + if let result = try database.query("SELECT * FROM foo").wait().first { + XCTAssertEqual(result.firstValue(forColumn: "bar"), .blob(data)) + } else { + XCTFail() + } + } + + func testError() throws { + let database = try SQLiteConnection.makeTest() + do { + _ = try database.query("asdf").wait() + XCTFail("Should have errored") + } catch let error as SQLiteError { + print(error) + XCTAssert(error.reason.contains("syntax error")) + } catch { + XCTFail("wrong error") + } + } + + // https://github.com/vapor/sqlite/issues/33 + func testDecodeSameColumnName() throws { + let row: [SQLiteColumn: SQLiteData] = [ + SQLiteColumn(table: "foo", name: "id"): .text("foo"), + SQLiteColumn(table: "bar", name: "id"): .text("bar"), + ] + struct User: Decodable { + var id: String + } + try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "foo").id, "foo") + try XCTAssertEqual(SQLiteRowDecoder().decode(User.self, from: row, table: "bar").id, "bar") + } + + func testMultiThreading() throws { + let db = try SQLiteDatabase(storage: .memory) + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 2) + let a = elg.next() + let b = elg.next() + let group = DispatchGroup() + group.enter() + DispatchQueue.global().async { + let conn = try! db.newConnection(on: a).wait() + for i in 0..<100 { + print("a \(i)") + let res = try! conn.query("SELECT (1 + 1) as a;").wait() + print(res) + } + group.leave() + } + group.enter() + DispatchQueue.global().async { + let conn = try! db.newConnection(on: b).wait() + for i in 0..<100 { + print("b \(i)") + let res = try! conn.query("SELECT (1 + 1) as b;").wait() + print(res) + } + group.leave() + } + group.wait() + } + + static let allTests = [ + ("testVersion", testVersion), + ("testVersionBuild", testVersionBuild), + ("testSQL", testSQL), + ("testTables", testTables), + ("testUnicode", testUnicode), + ("testBigInts", testBigInts), + ("testBlob", testBlob), + ("testError", testError), + ("testDecodeSameColumnName", testDecodeSameColumnName), + ("testMultiThreading", testMultiThreading), + ] } From 28760a2a1942c2defb6b73d3ab45effe24c26f9f Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Mon, 18 Jun 2018 14:38:26 -0400 Subject: [PATCH 13/21] Expose sql --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index f5c60df..809ed92 100644 --- a/Package.swift +++ b/Package.swift @@ -4,6 +4,7 @@ import PackageDescription let package = Package( name: "SQLite", products: [ + .library(name: "SQL", targets: ["SQL"]), .library(name: "SQLite", targets: ["SQLite"]), ], dependencies: [ From 8a5b38a20e91e952b721043711db23da92ca2658 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Mon, 18 Jun 2018 17:14:02 -0400 Subject: [PATCH 14/21] FluentSQL integration cleanup --- Sources/SQL/SQLBinaryOperator.swift | 30 +++++++- Sources/SQL/SQLCreateTable.swift | 3 +- Sources/SQL/SQLDrop.swift | 6 +- Sources/SQL/SQLDropTableBuilder.swift | 2 +- Sources/SQL/SQLExpression.swift | 71 ++++--------------- Sources/SQL/SQLIdentifier.swift | 3 +- Sources/SQL/SQLQueryEncoder.swift | 6 +- Sources/SQL/SQLSelectBuilder.swift | 7 +- Sources/SQL/SQLSelectExpression.swift | 54 +++++++------- Sources/SQL/SQLTableConstraint.swift | 28 ++++---- Sources/SQL/SQLTableConstraintAlgorithm.swift | 26 +++---- Sources/SQLite/Query/SQLiteFunction.swift | 4 +- Sources/SQLite/Query/SQLiteGeneric.swift | 4 +- 13 files changed, 117 insertions(+), 127 deletions(-) diff --git a/Sources/SQL/SQLBinaryOperator.swift b/Sources/SQL/SQLBinaryOperator.swift index 0a554bc..ca08e32 100644 --- a/Sources/SQL/SQLBinaryOperator.swift +++ b/Sources/SQL/SQLBinaryOperator.swift @@ -1,19 +1,47 @@ public protocol SQLBinaryOperator: SQLSerializable { static var equal: Self { get } static var notEqual: Self { get } + static var greaterThan: Self { get } + static var lessThan: Self { get } + static var greaterThanOrEqual: Self { get } + static var lessThanOrEqual: Self { get } + static var like: Self { get } + static var `in`: Self { get } + static var `notIn`: Self { get } static var and: Self { get } static var or: Self { get } } // MARK: Generic -public enum GenericSQLBinaryOperator: SQLBinaryOperator { +public enum GenericSQLBinaryOperator: SQLBinaryOperator, Equatable { /// See `SQLBinaryOperator`. public static var equal: GenericSQLBinaryOperator { return ._equal } /// See `SQLBinaryOperator`. public static var notEqual: GenericSQLBinaryOperator { return ._notEqual } + /// See `SQLBinaryOperator`. + public static var greaterThan: GenericSQLBinaryOperator { return ._greaterThan } + + /// See `SQLBinaryOperator`. + public static var lessThan: GenericSQLBinaryOperator { return ._lessThan } + + /// See `SQLBinaryOperator`. + public static var greaterThanOrEqual: GenericSQLBinaryOperator { return ._greaterThanOrEqual } + + /// See `SQLBinaryOperator`. + public static var lessThanOrEqual: GenericSQLBinaryOperator { return ._lessThanOrEqual } + + /// See `SQLBinaryOperator`. + public static var like: GenericSQLBinaryOperator { return ._like } + + /// See `SQLBinaryOperator`. + public static var `in`: GenericSQLBinaryOperator { return ._in } + + /// See `SQLBinaryOperator`. + public static var `notIn`: GenericSQLBinaryOperator { return ._notIn } + /// See `SQLBinaryOperator`. public static var and: GenericSQLBinaryOperator { return ._and } diff --git a/Sources/SQL/SQLCreateTable.swift b/Sources/SQL/SQLCreateTable.swift index 808ceea..e637c54 100644 --- a/Sources/SQL/SQLCreateTable.swift +++ b/Sources/SQL/SQLCreateTable.swift @@ -58,7 +58,8 @@ public struct GenericSQLCreateTable Self + static func dropTable(_ table: TableIdentifier) -> Self /// Table to drop. var table: TableIdentifier { get set } @@ -15,8 +15,8 @@ public struct GenericSQLDropTable: SQLDropTable where TableIdentifier: SQLTableIdentifier { /// See `SQLDropTable`. - public static func dropTable(_ table: TableIdentifier, ifExists: Bool) -> GenericSQLDropTable { - return .init(table: table, ifExists: ifExists) + public static func dropTable(_ table: TableIdentifier) -> GenericSQLDropTable { + return .init(table: table, ifExists: false) } /// See `SQLDropTable`. diff --git a/Sources/SQL/SQLDropTableBuilder.swift b/Sources/SQL/SQLDropTableBuilder.swift index 350ed2d..131f771 100644 --- a/Sources/SQL/SQLDropTableBuilder.swift +++ b/Sources/SQL/SQLDropTableBuilder.swift @@ -30,6 +30,6 @@ extension SQLConnection { public func drop
(table: Table.Type) -> SQLDropTableBuilder where Table: SQLTable { - return .init(.dropTable(.table(Table.self), ifExists: false), on: self) + return .init(.dropTable(.table(Table.self)), on: self) } } diff --git a/Sources/SQL/SQLExpression.swift b/Sources/SQL/SQLExpression.swift index 99c3e5f..c778ca2 100644 --- a/Sources/SQL/SQLExpression.swift +++ b/Sources/SQL/SQLExpression.swift @@ -31,13 +31,6 @@ public protocol SQLExpression: SQLSerializable { // FIXME: cast var isNull: Bool { get } -// var literal: Literal? { get } -// var bind: Bind? { get } -// var column: ColumnIdentifier? { get } -// var binary: (Self, BinaryOperator, Self)? { get } -// var function: Function? { get } -// var group: [Self]? { get } -// var select: Select? { get } } // MARK: Convenience @@ -70,7 +63,7 @@ public func |= (_ lhs: inout E?, _ rhs: E) where E: SQLExpression { // MARK: Generic public indirect enum GenericSQLExpression: SQLExpression - where Literal: SQLLiteral, Bind: SQLBind, ColumnIdentifier: SQLColumnIdentifier, BinaryOperator: SQLBinaryOperator, Function: SQLFunction, Subquery: SQLSerializable + where Literal: SQLLiteral, Bind: SQLBind, ColumnIdentifier: SQLColumnIdentifier, BinaryOperator: SQLBinaryOperator & Equatable, Function: SQLFunction, Subquery: SQLSerializable { public typealias `Self` = GenericSQLExpression @@ -102,55 +95,6 @@ public indirect enum GenericSQLExpression Self + var string: String { get set } } // MARK: Generic @@ -9,7 +10,7 @@ public struct GenericSQLIdentifier: SQLIdentifier { return self.init(string: string) } - public let string: String + public var string: String public func serialize(_ binds: inout [Encodable]) -> String { return "\"" + string + "\"" diff --git a/Sources/SQL/SQLQueryEncoder.swift b/Sources/SQL/SQLQueryEncoder.swift index 068e94d..cc7dee7 100644 --- a/Sources/SQL/SQLQueryEncoder.swift +++ b/Sources/SQL/SQLQueryEncoder.swift @@ -12,9 +12,9 @@ /// or `SQLiteDataConvertible` will be specially encoded. /// /// This encoder does _not_ support unkeyed or single value codable objects. -internal struct SQLQueryEncoder where Expression: SQLExpression { +public struct SQLQueryEncoder where Expression: SQLExpression { /// Creates a new `SQLQueryEncoder`. - internal init(_ type: Expression.Type) { } + public init(_ type: Expression.Type) { } /// Encodes keyed `Encodable` values to a `SQLiteQuery` expression dictionary. /// @@ -29,7 +29,7 @@ internal struct SQLQueryEncoder where Expression: SQLExpression { /// - parameters: /// - value: `Encodable` value to encode. /// - returns: `SQLiteQuery` compatible data. - internal func encode(_ value: E) -> [String: Expression] + public func encode(_ value: E) -> [String: Expression] where E: Encodable { let encoder = _Encoder() diff --git a/Sources/SQL/SQLSelectBuilder.swift b/Sources/SQL/SQLSelectBuilder.swift index b91d4b1..e21f771 100644 --- a/Sources/SQL/SQLSelectBuilder.swift +++ b/Sources/SQL/SQLSelectBuilder.swift @@ -27,12 +27,15 @@ public final class SQLSelectBuilder: SQLQueryFetcher, SQLPredicateBu public func column( function: String, _ arguments: Connection.Query.Select.SelectExpression.Expression.Function.Argument..., - as alias: String? = nil + as alias: Connection.Query.Select.SelectExpression.Identifier? = nil ) -> Self { return column(expression: .function(.function(function, arguments)), as: alias) } - public func column(expression: Connection.Query.Select.SelectExpression.Expression, as alias: String? = nil) -> Self { + public func column( + expression: Connection.Query.Select.SelectExpression.Expression, + as alias: Connection.Query.Select.SelectExpression.Identifier? = nil + ) -> Self { return column(.expression(expression, alias: alias)) } diff --git a/Sources/SQL/SQLSelectExpression.swift b/Sources/SQL/SQLSelectExpression.swift index de6712e..28b4059 100644 --- a/Sources/SQL/SQLSelectExpression.swift +++ b/Sources/SQL/SQLSelectExpression.swift @@ -1,56 +1,43 @@ public protocol SQLSelectExpression: SQLSerializable { associatedtype Expression: SQLExpression + associatedtype Identifier: SQLIdentifier static var all: Self { get } static func allTable(_ table: String) -> Self - static func expression(_ expression: Expression, alias: String?) -> Self + static func expression(_ expression: Expression, alias: Identifier?) -> Self var isAll: Bool { get } var allTable: String? { get } - var expression: (expression: Expression, alias: String?)? { get } + var expression: (expression: Expression, alias: Identifier?)? { get } } -// MARK: Default - -extension SQLSelectExpression { - public func serialize(_ binds: inout [Encodable]) -> String { - switch (isAll, allTable, expression) { - case (true, .none, .none): return "*" - case (false, .some(let table), .none): return table + ".*" - case (false, .none, .some(let e)): - switch e.alias { - case .none: return e.expression.serialize(&binds) - case .some(let alias): return e.expression.serialize(&binds) + " AS " + alias - } - default: fatalError("Unsupported SQLSelectExpression.") - } - } -} - - // MARK: Convenience extension SQLSelectExpression { - public static func function(_ function: Expression, as alias: String? = nil) -> Self { + public static func function(_ function: Expression, as alias: Identifier? = nil) -> Self { return .expression(function, alias: alias) } } // MARK: Generic -public enum GenericSQLSelectExpression: SQLSelectExpression where Expression: SQLExpression { +public enum GenericSQLSelectExpression: SQLSelectExpression where + Expression: SQLExpression, Identifier: SQLIdentifier +{ + public typealias `Self` = GenericSQLSelectExpression + /// See `SQLSelectExpression`. - public static var all: GenericSQLSelectExpression { + public static var all: Self { return ._all } /// See `SQLSelectExpression`. - public static func allTable(_ table: String) -> GenericSQLSelectExpression { + public static func allTable(_ table: String) ->Self { return ._allTable(table) } /// See `SQLSelectExpression`. - public static func expression(_ expression: Expression, alias: String?) -> GenericSQLSelectExpression { + public static func expression(_ expression: Expression, alias: Identifier?) -> Self { return ._expression(expression, alias: alias) } @@ -71,7 +58,7 @@ public enum GenericSQLSelectExpression: SQLSelectExpression where Ex } /// See `SQLSelectExpression`. - public var expression: (expression: Expression, alias: String?)? { + public var expression: (expression: Expression, alias: Identifier?)? { switch self { case ._expression(let expr, let alias): return (expr, alias) default: return nil @@ -85,5 +72,18 @@ public enum GenericSQLSelectExpression: SQLSelectExpression where Ex case _allTable(String) /// `md5(a) AS hash` - case _expression(Expression, alias: String?) + case _expression(Expression, alias: Identifier?) + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._all: return "*" + case ._allTable(let table): return table + ".*" + case ._expression(let expr, let alias): + switch alias { + case .none: return expr.serialize(&binds) + case .some(let alias): return expr.serialize(&binds) + " AS " + alias.serialize(&binds) + } + } + } } diff --git a/Sources/SQL/SQLTableConstraint.swift b/Sources/SQL/SQLTableConstraint.swift index 75715e0..023ae4f 100644 --- a/Sources/SQL/SQLTableConstraint.swift +++ b/Sources/SQL/SQLTableConstraint.swift @@ -1,31 +1,31 @@ public protocol SQLTableConstraint: SQLSerializable { associatedtype Identifier: SQLIdentifier - associatedtype TableConstraintAlgorithm: SQLTableConstraintAlgorithm - static func constraint(_ algorithm: TableConstraintAlgorithm, _ identifier: Identifier?) -> Self + associatedtype Algorithm: SQLTableConstraintAlgorithm + static func constraint(_ algorithm: Algorithm, _ identifier: Identifier?) -> Self } // MARK: Convenience extension SQLTableConstraint { public static func primaryKey( - _ columns: TableConstraintAlgorithm.ColumnIdentifier..., + _ columns: Algorithm.Identifier..., identifier: Identifier? = nil ) -> Self { return .constraint(.primaryKey(columns, .primaryKey()), identifier) } public static func unique( - _ columns: TableConstraintAlgorithm.ColumnIdentifier..., + _ columns: Algorithm.Identifier..., identifier: Identifier? = nil ) -> Self { return .constraint(.unique(columns), identifier) } public static func foreignKey( - _ columns: [TableConstraintAlgorithm.ColumnIdentifier], - references foreignTable: TableConstraintAlgorithm.ForeignKey.TableIdentifier, - _ foreignColumns: [TableConstraintAlgorithm.ForeignKey.Identifier], - onDelete: TableConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, - onUpdate: TableConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, + _ columns: [Algorithm.Identifier], + references foreignTable: Algorithm.ForeignKey.TableIdentifier, + _ foreignColumns: [Algorithm.ForeignKey.Identifier], + onDelete: Algorithm.ForeignKey.ConflictResolution? = nil, + onUpdate: Algorithm.ForeignKey.ConflictResolution? = nil, identifier: Identifier? = nil ) -> Self { return .constraint(.foreignKey(columns, .foreignKey(foreignTable, foreignColumns, onDelete: onDelete, onUpdate: onUpdate)), identifier) @@ -34,19 +34,19 @@ extension SQLTableConstraint { // MARK: Generic -public struct GenericSQLTableConstraint: SQLTableConstraint - where Identifier: SQLIdentifier, TableConstraintAlgorithm: SQLTableConstraintAlgorithm +public struct GenericSQLTableConstraint: SQLTableConstraint + where Identifier: SQLIdentifier, Algorithm: SQLTableConstraintAlgorithm { - public typealias `Self` = GenericSQLTableConstraint + public typealias `Self` = GenericSQLTableConstraint /// See `SQLColumnConstraint`. - public static func constraint(_ algorithm: TableConstraintAlgorithm, _ identifier: Identifier?) -> Self { + public static func constraint(_ algorithm: Algorithm, _ identifier: Identifier?) -> Self { return .init(identifier: identifier, algorithm: algorithm) } public var identifier: Identifier? - public var algorithm: TableConstraintAlgorithm + public var algorithm: Algorithm /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { diff --git a/Sources/SQL/SQLTableConstraintAlgorithm.swift b/Sources/SQL/SQLTableConstraintAlgorithm.swift index 67395df..b30edc8 100644 --- a/Sources/SQL/SQLTableConstraintAlgorithm.swift +++ b/Sources/SQL/SQLTableConstraintAlgorithm.swift @@ -1,25 +1,25 @@ public protocol SQLTableConstraintAlgorithm: SQLSerializable { - associatedtype ColumnIdentifier: SQLColumnIdentifier + associatedtype Identifier: SQLIdentifier associatedtype Expression: SQLExpression associatedtype Collation: SQLCollation associatedtype PrimaryKey: SQLPrimaryKey associatedtype ForeignKey: SQLForeignKey - static func primaryKey(_ columns: [ColumnIdentifier],_ primaryKey: PrimaryKey) -> Self + static func primaryKey(_ columns: [Identifier],_ primaryKey: PrimaryKey) -> Self static var notNull: Self { get } - static func unique(_ columns: [ColumnIdentifier]) -> Self + static func unique(_ columns: [Identifier]) -> Self static func check(_ expression: Expression) -> Self - static func foreignKey(_ columns: [ColumnIdentifier], _ foreignKey: ForeignKey) -> Self + static func foreignKey(_ columns: [Identifier], _ foreignKey: ForeignKey) -> Self } // MARK: Generic -public enum GenericSQLTableConstraintAlgorithm: SQLTableConstraintAlgorithm - where ColumnIdentifier: SQLColumnIdentifier, Expression: SQLExpression, Collation: SQLCollation, PrimaryKey: SQLPrimaryKey, ForeignKey: SQLForeignKey +public enum GenericSQLTableConstraintAlgorithm: SQLTableConstraintAlgorithm + where Identifier: SQLIdentifier, Expression: SQLExpression, Collation: SQLCollation, PrimaryKey: SQLPrimaryKey, ForeignKey: SQLForeignKey { - public typealias `Self` = GenericSQLTableConstraintAlgorithm + public typealias `Self` = GenericSQLTableConstraintAlgorithm /// See `SQLColumnConstraintAlgorithm`. - public static func primaryKey(_ columns: [ColumnIdentifier], _ primaryKey: PrimaryKey) -> Self { + public static func primaryKey(_ columns: [Identifier], _ primaryKey: PrimaryKey) -> Self { return ._primaryKey(columns, primaryKey) } @@ -29,7 +29,7 @@ public enum GenericSQLTableConstraintAlgorithm Self { + public static func unique(_ columns: [Identifier]) -> Self { return ._unique(columns) } @@ -39,15 +39,15 @@ public enum GenericSQLTableConstraintAlgorithm Self { + public static func foreignKey(_ columns: [Identifier], _ foreignKey: ForeignKey) -> Self { return ._foreignKey(columns, foreignKey) } - case _primaryKey([ColumnIdentifier], PrimaryKey) + case _primaryKey([Identifier], PrimaryKey) case _notNull - case _unique([ColumnIdentifier]) + case _unique([Identifier]) case _check(Expression) - case _foreignKey([ColumnIdentifier], ForeignKey) + case _foreignKey([Identifier], ForeignKey) /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { diff --git a/Sources/SQLite/Query/SQLiteFunction.swift b/Sources/SQLite/Query/SQLiteFunction.swift index 8730e0f..ce868f7 100644 --- a/Sources/SQLite/Query/SQLiteFunction.swift +++ b/Sources/SQLite/Query/SQLiteFunction.swift @@ -17,8 +17,8 @@ public struct SQLiteFunction: SQLFunction { } } -extension SQLSelectExpression where Expression.Function == SQLiteFunction { - public static func count(as alias: String? = nil) -> Self { +extension SQLSelectExpression where Expression.Function == SQLiteFunction, Identifier == SQLiteIdentifier { + public static func count(as alias: SQLiteIdentifier? = nil) -> Self { return .expression(.function(.count), alias: alias) } } diff --git a/Sources/SQLite/Query/SQLiteGeneric.swift b/Sources/SQLite/Query/SQLiteGeneric.swift index 608e9fd..6207199 100644 --- a/Sources/SQLite/Query/SQLiteGeneric.swift +++ b/Sources/SQLite/Query/SQLiteGeneric.swift @@ -79,11 +79,11 @@ public typealias SQLiteSelect = GenericSQLSelect< > /// See `SQLQuery`. -public typealias SQLiteSelectExpression = GenericSQLSelectExpression +public typealias SQLiteSelectExpression = GenericSQLSelectExpression /// See `SQLQuery`. public typealias SQLiteTableConstraintAlgorithm = GenericSQLTableConstraintAlgorithm< - SQLiteColumnIdentifier, SQLiteExpression, SQLiteCollation, SQLitePrimaryKey, SQLiteForeignKey + SQLiteIdentifier, SQLiteExpression, SQLiteCollation, SQLitePrimaryKey, SQLiteForeignKey > /// See `SQLQuery`. From bc69ac813d43961213b9dbd8b39353838d0c9a85 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Mon, 18 Jun 2018 18:47:30 -0400 Subject: [PATCH 15/21] move to SQL, sql branch --- Package.swift | 5 +- Sources/SQL/SQLAlterTable.swift | 8 - Sources/SQL/SQLAlterTableBuilder.swift | 38 ---- Sources/SQL/SQLBinaryOperator.swift | 197 ------------------ Sources/SQL/SQLBind.swift | 23 -- Sources/SQL/SQLCollation.swift | 2 - Sources/SQL/SQLColumnConstraint.swift | 105 ---------- .../SQL/SQLColumnConstraintAlgorithm.swift | 86 -------- Sources/SQL/SQLColumnDefinition.swift | 32 --- Sources/SQL/SQLColumnIdentifier.swift | 60 ------ Sources/SQL/SQLConflictResolution.swift | 51 ----- Sources/SQL/SQLConnection.swift | 30 --- Sources/SQL/SQLCreateTable.swift | 65 ------ Sources/SQL/SQLCreateTableBuilder.swift | 83 -------- Sources/SQL/SQLDataType.swift | 3 - Sources/SQL/SQLDelete.swift | 42 ---- Sources/SQL/SQLDeleteBuilder.swift | 43 ---- Sources/SQL/SQLDirection.swift | 29 --- Sources/SQL/SQLDistinct.swift | 42 ---- Sources/SQL/SQLDrop.swift | 38 ---- Sources/SQL/SQLDropTableBuilder.swift | 35 ---- Sources/SQL/SQLExpression.swift | 140 ------------- Sources/SQL/SQLForeignKey.swift | 54 ----- Sources/SQL/SQLFunction.swift | 21 -- Sources/SQL/SQLFunctionArgument.swift | 30 --- Sources/SQL/SQLGroupBy.swift | 18 -- Sources/SQL/SQLIdentifier.swift | 18 -- Sources/SQL/SQLInsert.swift | 37 ---- Sources/SQL/SQLInsertBuilder.swift | 67 ------ Sources/SQL/SQLJoin.swift | 26 --- Sources/SQL/SQLJoinMethod.swift | 24 --- Sources/SQL/SQLLiteral.swift | 78 ------- Sources/SQL/SQLOrderBy.swift | 20 -- Sources/SQL/SQLPredicateBuilder.swift | 45 ---- Sources/SQL/SQLPrimaryKey.swift | 17 -- Sources/SQL/SQLQuery.swift | 20 -- Sources/SQL/SQLQueryBuilder.swift | 101 --------- Sources/SQL/SQLQueryEncoder.swift | 123 ----------- Sources/SQL/SQLRowDecoder.swift | 8 - Sources/SQL/SQLSelect.swift | 71 ------- Sources/SQL/SQLSelectBuilder.swift | 146 ------------- Sources/SQL/SQLSelectExpression.swift | 89 -------- Sources/SQL/SQLSerializable.swift | 9 - Sources/SQL/SQLTable.swift | 11 - Sources/SQL/SQLTableConstraint.swift | 59 ------ Sources/SQL/SQLTableConstraintAlgorithm.swift | 78 ------- Sources/SQL/SQLTableIdentifier.swift | 45 ---- Sources/SQL/SQLUpdate.swift | 41 ---- Sources/SQL/SQLUpdateBuilder.swift | 60 ------ Sources/SQL/SQLWith.swift | 10 - Tests/SQLiteTests/SQLiteTests.swift | 97 +-------- 51 files changed, 4 insertions(+), 2576 deletions(-) delete mode 100644 Sources/SQL/SQLAlterTable.swift delete mode 100644 Sources/SQL/SQLAlterTableBuilder.swift delete mode 100644 Sources/SQL/SQLBinaryOperator.swift delete mode 100644 Sources/SQL/SQLBind.swift delete mode 100644 Sources/SQL/SQLCollation.swift delete mode 100644 Sources/SQL/SQLColumnConstraint.swift delete mode 100644 Sources/SQL/SQLColumnConstraintAlgorithm.swift delete mode 100644 Sources/SQL/SQLColumnDefinition.swift delete mode 100644 Sources/SQL/SQLColumnIdentifier.swift delete mode 100644 Sources/SQL/SQLConflictResolution.swift delete mode 100644 Sources/SQL/SQLConnection.swift delete mode 100644 Sources/SQL/SQLCreateTable.swift delete mode 100644 Sources/SQL/SQLCreateTableBuilder.swift delete mode 100644 Sources/SQL/SQLDataType.swift delete mode 100644 Sources/SQL/SQLDelete.swift delete mode 100644 Sources/SQL/SQLDeleteBuilder.swift delete mode 100644 Sources/SQL/SQLDirection.swift delete mode 100644 Sources/SQL/SQLDistinct.swift delete mode 100644 Sources/SQL/SQLDrop.swift delete mode 100644 Sources/SQL/SQLDropTableBuilder.swift delete mode 100644 Sources/SQL/SQLExpression.swift delete mode 100644 Sources/SQL/SQLForeignKey.swift delete mode 100644 Sources/SQL/SQLFunction.swift delete mode 100644 Sources/SQL/SQLFunctionArgument.swift delete mode 100644 Sources/SQL/SQLGroupBy.swift delete mode 100644 Sources/SQL/SQLIdentifier.swift delete mode 100644 Sources/SQL/SQLInsert.swift delete mode 100644 Sources/SQL/SQLInsertBuilder.swift delete mode 100644 Sources/SQL/SQLJoin.swift delete mode 100644 Sources/SQL/SQLJoinMethod.swift delete mode 100644 Sources/SQL/SQLLiteral.swift delete mode 100644 Sources/SQL/SQLOrderBy.swift delete mode 100644 Sources/SQL/SQLPredicateBuilder.swift delete mode 100644 Sources/SQL/SQLPrimaryKey.swift delete mode 100644 Sources/SQL/SQLQuery.swift delete mode 100644 Sources/SQL/SQLQueryBuilder.swift delete mode 100644 Sources/SQL/SQLQueryEncoder.swift delete mode 100644 Sources/SQL/SQLRowDecoder.swift delete mode 100644 Sources/SQL/SQLSelect.swift delete mode 100644 Sources/SQL/SQLSelectBuilder.swift delete mode 100644 Sources/SQL/SQLSelectExpression.swift delete mode 100644 Sources/SQL/SQLSerializable.swift delete mode 100644 Sources/SQL/SQLTable.swift delete mode 100644 Sources/SQL/SQLTableConstraint.swift delete mode 100644 Sources/SQL/SQLTableConstraintAlgorithm.swift delete mode 100644 Sources/SQL/SQLTableIdentifier.swift delete mode 100644 Sources/SQL/SQLUpdate.swift delete mode 100644 Sources/SQL/SQLUpdateBuilder.swift delete mode 100644 Sources/SQL/SQLWith.swift diff --git a/Package.swift b/Package.swift index 809ed92..756ca7b 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,6 @@ import PackageDescription let package = Package( name: "SQLite", products: [ - .library(name: "SQL", targets: ["SQL"]), .library(name: "SQLite", targets: ["SQLite"]), ], dependencies: [ @@ -13,9 +12,11 @@ let package = Package( // 🗄 Core services for creating database integrations. .package(url: "https://github.com/vapor/database-kit.git", from: "1.0.0"), + + // *️⃣ Build SQL queries in Swift. Extensible, protocol-based design that supports DQL, DML, and DDL. + .package(url: "https://github.com/vapor/sql.git", .branch("sql")), ], targets: [ - .target(name: "SQL", dependencies: ["Core"]), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"]), ] ) diff --git a/Sources/SQL/SQLAlterTable.swift b/Sources/SQL/SQLAlterTable.swift deleted file mode 100644 index 7c38518..0000000 --- a/Sources/SQL/SQLAlterTable.swift +++ /dev/null @@ -1,8 +0,0 @@ -public protocol SQLAlterTable: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - - static func alterTable(_ table: TableIdentifier) -> Self -} - -// No generic ALTER table is offered since they differ too much -// between SQL dialects diff --git a/Sources/SQL/SQLAlterTableBuilder.swift b/Sources/SQL/SQLAlterTableBuilder.swift deleted file mode 100644 index 31e8b17..0000000 --- a/Sources/SQL/SQLAlterTableBuilder.swift +++ /dev/null @@ -1,38 +0,0 @@ -public final class SQLAlterTableBuilder: SQLQueryBuilder - where Connection: SQLConnection -{ - /// `AlterTable` query being built. - public var alterTable: Connection.Query.AlterTable - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .alterTable(alterTable) - } - - /// Creates a new `SQLAlterTableBuilder`. - public init(_ alterTable: Connection.Query.AlterTable, on connection: Connection) { - self.alterTable = alterTable - self.connection = connection - } -} - -// MARK: Connection - -extension SQLConnection { - /// Creates a new `AlterTableBuilder`. - /// - /// conn.alter(table: Planet.self)... - /// - /// - parameters: - /// - table: Table to alter. - /// - returns: `AlterTableBuilder`. - public func alter
(table: Table.Type) -> SQLAlterTableBuilder - where Table: SQLTable - { - return .init(.alterTable(.table(Table.self)), on: self) - } -} - diff --git a/Sources/SQL/SQLBinaryOperator.swift b/Sources/SQL/SQLBinaryOperator.swift deleted file mode 100644 index ca08e32..0000000 --- a/Sources/SQL/SQLBinaryOperator.swift +++ /dev/null @@ -1,197 +0,0 @@ -public protocol SQLBinaryOperator: SQLSerializable { - static var equal: Self { get } - static var notEqual: Self { get } - static var greaterThan: Self { get } - static var lessThan: Self { get } - static var greaterThanOrEqual: Self { get } - static var lessThanOrEqual: Self { get } - static var like: Self { get } - static var `in`: Self { get } - static var `notIn`: Self { get } - static var and: Self { get } - static var or: Self { get } -} - -// MARK: Generic - -public enum GenericSQLBinaryOperator: SQLBinaryOperator, Equatable { - /// See `SQLBinaryOperator`. - public static var equal: GenericSQLBinaryOperator { return ._equal } - - /// See `SQLBinaryOperator`. - public static var notEqual: GenericSQLBinaryOperator { return ._notEqual } - - /// See `SQLBinaryOperator`. - public static var greaterThan: GenericSQLBinaryOperator { return ._greaterThan } - - /// See `SQLBinaryOperator`. - public static var lessThan: GenericSQLBinaryOperator { return ._lessThan } - - /// See `SQLBinaryOperator`. - public static var greaterThanOrEqual: GenericSQLBinaryOperator { return ._greaterThanOrEqual } - - /// See `SQLBinaryOperator`. - public static var lessThanOrEqual: GenericSQLBinaryOperator { return ._lessThanOrEqual } - - /// See `SQLBinaryOperator`. - public static var like: GenericSQLBinaryOperator { return ._like } - - /// See `SQLBinaryOperator`. - public static var `in`: GenericSQLBinaryOperator { return ._in } - - /// See `SQLBinaryOperator`. - public static var `notIn`: GenericSQLBinaryOperator { return ._notIn } - - /// See `SQLBinaryOperator`. - public static var and: GenericSQLBinaryOperator { return ._and } - - /// See `SQLBinaryOperator`. - public static var or: GenericSQLBinaryOperator { return ._or } - - /// `||` - case _concatenate - - /// `*` - case _multiply - - /// `/` - case _divide - - /// `%` - case _modulo - - /// `+` - case _add - - /// `-` - case _subtract - - /// `<<` - case _bitwiseShiftLeft - - /// `>>` - case _bitwiseShiftRight - - /// `&` - case _bitwiseAnd - - /// `|` - case _bitwiseOr - - /// `<` - case _lessThan - - /// `<=` - case _lessThanOrEqual - - /// `>` - case _greaterThan - - /// `>=` - case _greaterThanOrEqual - - /// `=` or `==` - case _equal - - /// `!=` or `<>` - case _notEqual - - /// `AND` - case _and - - /// `OR` - case _or - - /// `IS` - case _is - - /// `IS NOT` - case _isNot - - /// `IN` - case _in - - /// `NOT IN` - case _notIn - - /// `LIKE` - case _like - - /// `NOT LIKE` - case _notLike - - /// `GLOB` - case _glob - - /// `NOT GLOB` - case _notGlob - - /// `MATCH` - case _match - - /// `NOT MATCH` - case _notMatch - - /// `REGEXP` - case _regexp - - /// `NOT REGEXP` - case _notRegexp - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._add: return "+" - case ._bitwiseAnd: return "&" - case ._bitwiseOr: return "|" - case ._bitwiseShiftLeft: return "<<" - case ._bitwiseShiftRight: return ">>" - case ._concatenate: return "||" - case ._divide: return "/" - case ._equal: return "=" - case ._greaterThan: return ">" - case ._greaterThanOrEqual: return ">=" - case ._lessThan: return "<" - case ._lessThanOrEqual: return "<=" - case ._modulo: return "%" - case ._multiply: return "*" - case ._notEqual: return "!=" - case ._subtract: return "-" - case ._and: return "AND" - case ._or: return "OR" - case ._in: return "IN" - case ._notIn: return "NOT IN" - case ._is: return "IS" - case ._isNot: return "IS NOT" - case ._like: return "LIKE" - case ._glob: return "GLOB" - case ._match: return "MATCH" - case ._regexp: return "REGEXP" - case ._notLike: return "NOT LIKE" - case ._notGlob: return "NOT GLOB" - case ._notMatch: return "NOT MATCH" - case ._notRegexp: return "NOT REGEXP" - } - } -} - -// MARK: Operator - -public func == (_ lhs: KeyPath, _ rhs: V) -> E - where T: SQLTable, V: Encodable, E: SQLExpression -{ - return E.binary(.column(.keyPath(lhs)), .equal, .bind(.encodable(rhs))) -} - -public func != (_ lhs: KeyPath, _ rhs: V) -> E - where T: SQLTable, V: Encodable, E: SQLExpression -{ - return E.binary(.column(.keyPath(lhs)), .notEqual, .bind(.encodable(rhs))) -} - - -public func == (_ lhs: KeyPath, _ rhs: KeyPath) -> E - where A: SQLTable, B: Encodable, C: SQLTable, D: Encodable, E: SQLExpression -{ - return E.binary(.column(.keyPath(lhs)), .equal, .column(.keyPath(rhs))) -} diff --git a/Sources/SQL/SQLBind.swift b/Sources/SQL/SQLBind.swift deleted file mode 100644 index 042f477..0000000 --- a/Sources/SQL/SQLBind.swift +++ /dev/null @@ -1,23 +0,0 @@ -public protocol SQLBind: SQLSerializable { - static func encodable(_ value: E) -> Self - where E: Encodable -} - -// MARK: Generic - -public struct GenericSQLBind: SQLBind { - /// See `SQLBind`. - public static func encodable(_ value: E) -> GenericSQLBind - where E: Encodable - { - return self.init(value: value) - } - - public var value: Encodable - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - binds.append(value) - return "?" - } -} diff --git a/Sources/SQL/SQLCollation.swift b/Sources/SQL/SQLCollation.swift deleted file mode 100644 index 2a59c26..0000000 --- a/Sources/SQL/SQLCollation.swift +++ /dev/null @@ -1,2 +0,0 @@ - -public protocol SQLCollation: SQLSerializable { } diff --git a/Sources/SQL/SQLColumnConstraint.swift b/Sources/SQL/SQLColumnConstraint.swift deleted file mode 100644 index 87324d3..0000000 --- a/Sources/SQL/SQLColumnConstraint.swift +++ /dev/null @@ -1,105 +0,0 @@ -public protocol SQLColumnConstraint: SQLSerializable { - associatedtype Identifier: SQLIdentifier - associatedtype ColumnConstraintAlgorithm: SQLColumnConstraintAlgorithm - static func constraint(_ algorithm: ColumnConstraintAlgorithm, _ identifier: Identifier?) -> Self -} - -// MARK: Convenience - -extension SQLColumnConstraint { - public static var primaryKey: Self { - return .primaryKey(identifier: nil) - } - - /// Creates a new `PRIMARY KEY` column constraint. - /// - /// - parameters: - /// - identifier: Optional constraint name. - /// - returns: New column constraint. - public static func primaryKey(identifier: Identifier?) -> Self { - return .constraint(.primaryKey(.primaryKey()), identifier) - } - - public static var notNull: Self { - return .notNull(identifier: nil) - } - - /// Creates a new `NOT NULL` column constraint. - /// - /// - parameters: - /// - identifier: Optional constraint name. - /// - returns: New column constraint. - public static func notNull(identifier: Identifier?) -> Self { - return .constraint(.notNull, identifier) - } - - /// Creates a new `UNIQUE` column constraint. - /// - /// - parameters: - /// - identifier: Optional constraint name. - /// - returns: New column constraint. - public static func unique(identifier: Identifier? = nil) -> Self { - return .constraint(.unique, identifier) - } - - /// Creates a new `DEFAULT ` column constraint. - /// - /// - parameters - /// - expression: Expression to evaluate when setting the default value. - /// - identifier: Optional constraint name. - /// - returns: New column constraint. - public static func `default`( - _ expression: ColumnConstraintAlgorithm.Expression, - identifier: Identifier? = nil - ) -> Self { - return .constraint(.default(expression), identifier) - } - - public static func references( - _ keyPath: KeyPath, - onDelete: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, - onUpdate: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, - identifier: Identifier? = nil - ) -> Self - where T: SQLTable - { - return references(.keyPath(keyPath), [.keyPath(keyPath)], onDelete: onDelete, onUpdate: onUpdate, identifier: identifier) - } - - public static func references( - _ foreignTable: ColumnConstraintAlgorithm.ForeignKey.TableIdentifier, - _ foreignColumns: [ColumnConstraintAlgorithm.ForeignKey.Identifier], - onDelete: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, - onUpdate: ColumnConstraintAlgorithm.ForeignKey.ConflictResolution? = nil, - identifier: Identifier? = nil - ) -> Self { - return .constraint(.foreignKey(.foreignKey(foreignTable, foreignColumns, onDelete: onDelete, onUpdate: onUpdate)), identifier) - } -} - - -// MARK: Generic - -public struct GenericSQLColumnConstraint: SQLColumnConstraint - where Identifier: SQLIdentifier, ColumnConstraintAlgorithm: SQLColumnConstraintAlgorithm -{ - public typealias `Self` = GenericSQLColumnConstraint - - /// See `SQLColumnConstraint`. - public static func constraint(_ algorithm: ColumnConstraintAlgorithm, _ identifier: Identifier?) -> Self { - return .init(identifier: identifier, algorithm: algorithm) - } - - public var identifier: Identifier? - - public var algorithm: ColumnConstraintAlgorithm - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - if let identifier = self.identifier { - return "CONSTRAINT " + identifier.serialize(&binds) + " " + algorithm.serialize(&binds) - } else { - return algorithm.serialize(&binds) - } - } -} diff --git a/Sources/SQL/SQLColumnConstraintAlgorithm.swift b/Sources/SQL/SQLColumnConstraintAlgorithm.swift deleted file mode 100644 index dce9d89..0000000 --- a/Sources/SQL/SQLColumnConstraintAlgorithm.swift +++ /dev/null @@ -1,86 +0,0 @@ -public protocol SQLColumnConstraintAlgorithm: SQLSerializable { - associatedtype Expression: SQLExpression - associatedtype Collation: SQLCollation - associatedtype PrimaryKey: SQLPrimaryKey - associatedtype ForeignKey: SQLForeignKey - static func primaryKey(_ primaryKey: PrimaryKey) -> Self - static var notNull: Self { get } - static var unique: Self { get } - static func check(_ expression: Expression) -> Self - static func collate(_ collation: Collation) -> Self - static func `default`(_ expression: Expression) -> Self - static func foreignKey(_ foreignKey: ForeignKey) -> Self -} - -// MARK: Generic - -public enum GenericSQLColumnConstraintAlgorithm: SQLColumnConstraintAlgorithm - where Expression: SQLExpression, Collation: SQLCollation, PrimaryKey: SQLPrimaryKey, ForeignKey: SQLForeignKey -{ - public typealias `Self` = GenericSQLColumnConstraintAlgorithm - - /// See `SQLColumnConstraintAlgorithm`. - public static func primaryKey(_ primaryKey: PrimaryKey) -> Self { - return ._primaryKey(primaryKey) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static var notNull: Self { - return ._notNull - } - - /// See `SQLColumnConstraintAlgorithm`. - public static var unique: Self { - return ._unique - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func check(_ expression: Expression) -> Self { - return ._check(expression) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func collate(_ collation: Collation) -> Self { - return .collate(collation) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func `default`(_ expression: Expression) -> Self { - return ._default(expression) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func foreignKey(_ foreignKey: ForeignKey) -> Self { - return ._foreignKey(foreignKey) - } - - case _primaryKey(PrimaryKey) - case _notNull - case _unique - case _check(Expression) - case _collate(Collation) - case _default(Expression) - case _foreignKey(ForeignKey) - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._primaryKey(let primaryKey): - let pk = primaryKey.serialize(&binds) - if pk.isEmpty { - return "PRIMARY KEY" - } else { - return "PRIMARY KEY " + pk - } - case ._notNull: return "NOT NULL" - case ._unique: return "UNIQUE" - case ._check(let expression): - return "CHECK (" + expression.serialize(&binds) + ")" - case ._collate(let collation): - return "COLLATE " + collation.serialize(&binds) - case ._default(let expression): - return "DEFAULT (" + expression.serialize(&binds) + ")" - case ._foreignKey(let foreignKey): return "REFERENCES " + foreignKey.serialize(&binds) - } - } -} diff --git a/Sources/SQL/SQLColumnDefinition.swift b/Sources/SQL/SQLColumnDefinition.swift deleted file mode 100644 index 75b9bf3..0000000 --- a/Sources/SQL/SQLColumnDefinition.swift +++ /dev/null @@ -1,32 +0,0 @@ -public protocol SQLColumnDefinition: SQLSerializable { - associatedtype ColumnIdentifier: SQLColumnIdentifier - associatedtype DataType: SQLDataType - associatedtype ColumnConstraint: SQLColumnConstraint - static func columnDefinition(_ column: ColumnIdentifier, _ dataType: DataType, _ constraints: [ColumnConstraint]) -> Self -} - -// MARK: Generic - -public struct GenericSQLColumnDefinition: SQLColumnDefinition - where ColumnIdentifier: SQLColumnIdentifier, DataType: SQLDataType, ColumnConstraint: SQLColumnConstraint -{ - public typealias `Self` = GenericSQLColumnDefinition - - /// See `SQLColumnDefinition`. - public static func columnDefinition(_ column: ColumnIdentifier, _ dataType: DataType, _ constraints: [ColumnConstraint]) -> Self { - return .init(column: column, dataType: dataType, constraints: constraints) - } - - public var column: ColumnIdentifier - public var dataType: DataType - public var constraints: [ColumnConstraint] - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append(column.identifier.serialize(&binds)) - sql.append(dataType.serialize(&binds)) - sql.append(constraints.serialize(&binds, joinedBy: " ")) - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLColumnIdentifier.swift b/Sources/SQL/SQLColumnIdentifier.swift deleted file mode 100644 index 8bb0f20..0000000 --- a/Sources/SQL/SQLColumnIdentifier.swift +++ /dev/null @@ -1,60 +0,0 @@ -public protocol SQLColumnIdentifier: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype Identifier: SQLIdentifier - - static func column(_ table: TableIdentifier?, _ identifier: Identifier) -> Self - - var table: TableIdentifier? { get set } - var identifier: Identifier { get set } -} - -// MARK: Convenience - - -extension SQLColumnIdentifier { - public static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { - guard let property = try! T.reflectProperty(forKey: keyPath) else { - fatalError("Could not reflect property of type \(V.self) on \(T.self): \(keyPath)") - } - return .column(.table(.identifier(T.sqlTableIdentifierString)), .identifier(property.path[0])) - } -} -extension SQLTableIdentifier { - public static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { - return .table(.identifier(T.sqlTableIdentifierString)) - } -} - -extension SQLIdentifier { - public static func keyPath(_ keyPath: KeyPath) -> Self where T: SQLTable { - guard let property = try! T.reflectProperty(forKey: keyPath) else { - fatalError("Could not reflect property of type \(V.self) on \(T.self): \(keyPath)") - } - return .identifier(property.path[0]) - } -} - -// MARK: Generic - -public struct GenericSQLColumnIdentifier: SQLColumnIdentifier - where TableIdentifier: SQLTableIdentifier, Identifier: SQLIdentifier -{ - /// See `SQLColumnIdentifier`. - public static func column(_ table: TableIdentifier?, _ identifier: Identifier) -> GenericSQLColumnIdentifier { - return self.init(table: table, identifier: identifier) - } - - /// See `SQLColumnIdentifier`. - public var table: TableIdentifier? - - /// See `SQLColumnIdentifier`. - public var identifier: Identifier - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch table { - case .some(let table): return table.serialize(&binds) + "." + identifier.serialize(&binds) - case .none: return identifier.serialize(&binds) - } - } -} diff --git a/Sources/SQL/SQLConflictResolution.swift b/Sources/SQL/SQLConflictResolution.swift deleted file mode 100644 index 68cd043..0000000 --- a/Sources/SQL/SQLConflictResolution.swift +++ /dev/null @@ -1,51 +0,0 @@ -public protocol SQLConflictResolution: SQLSerializable { } - -/// `ON CONFLICT` clause. Supported constraint conflict resolution algorithms. -public enum GenericSQLConflictResolution: SQLConflictResolution { - /// When a UNIQUE or PRIMARY KEY constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing - /// the constraint violation prior to inserting or updating the current row and the command continues executing normally. If a - /// NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with the default value for - /// that column, or if the column has no default value, then the ABORT algorithm is used. If a CHECK constraint violation - /// occurs, the REPLACE conflict resolution algorithm always works like ABORT. - /// - /// When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and - /// only if recursive triggers are enabled. - /// - /// The update hook is not invoked for rows that are deleted by the REPLACE conflict resolution strategy. Nor does REPLACE - /// increment the change counter. The exceptional behaviors defined in this paragraph might change in a future release. - case replace - - /// When an applicable constraint violation occurs, the ROLLBACK resolution algorithm aborts the current SQL statement with - /// an SQLITE_CONSTRAINT error and rolls back the current transaction. If no transaction is active (other than the implied - /// transaction that is created on every command) then the ROLLBACK resolution algorithm works the same as the ABORT algorithm. - case rollback - - /// When an applicable constraint violation occurs, the ABORT resolution algorithm aborts the current SQL statement with an - /// SQLITE_CONSTRAINT error and backs out any changes made by the current SQL statement; but changes caused by prior SQL - /// statements within the same transaction are preserved and the transaction remains active. This is the default behavior and - /// the behavior specified by the SQL standard. - case abort - - /// When an applicable constraint violation occurs, the FAIL resolution algorithm aborts the current SQL statement with an - /// SQLITE_CONSTRAINT error. But the FAIL resolution does not back out prior changes of the SQL statement that failed nor does - /// it end the transaction. For example, if an UPDATE statement encountered a constraint violation on the 100th row that it - /// attempts to update, then the first 99 row changes are preserved but changes to rows 100 and beyond never occur. - case fail - - /// When an applicable constraint violation occurs, the IGNORE resolution algorithm skips the one row that contains the - /// constraint violation and continues processing subsequent rows of the SQL statement as if nothing went wrong. Other rows - /// before and after the row that contained the constraint violation are inserted or updated normally. No error is returned - /// when the IGNORE conflict resolution algorithm is used. - case ignore - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case .abort: return "ABORT" - case .fail: return "FAIL" - case .ignore: return "IGNORE" - case .replace: return "REPLACE" - case .rollback: return "ROLLBACK" - } - } -} diff --git a/Sources/SQL/SQLConnection.swift b/Sources/SQL/SQLConnection.swift deleted file mode 100644 index 6da355d..0000000 --- a/Sources/SQL/SQLConnection.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Async - -public protocol SQLConnection { - associatedtype Output - associatedtype Query: SQLQuery - where Query.RowDecoder.Row == Output - func query(_ query: Query, _ handler: @escaping (Output) throws -> ()) -> Future -} - -extension SQLConnection { - public func query(_ sql: String, _ binds: [Encodable] = [], _ handler: @escaping (Output) throws -> ()) -> Future { - return query(.raw(sql, binds: binds), handler) - } - - public func query(_ sql: String, _ binds: [Encodable] = []) -> Future<[Output]> { - return query(.raw(sql, binds: binds)) - } - - /// Executes the supplied `SQLiteQuery` on the connection, aggregating the results into an array. - /// - /// let rows = try conn.query("SELECT * FROM users").wait() - /// - /// - parameters: - /// - query: `SQLiteQuery` to execute. - /// - returns: A `Future` containing array of rows. - public func query(_ query: Query) -> Future<[Output]> { - var rows: [Output] = [] - return self.query(query) { rows.append($0) }.map { rows } - } -} diff --git a/Sources/SQL/SQLCreateTable.swift b/Sources/SQL/SQLCreateTable.swift deleted file mode 100644 index e637c54..0000000 --- a/Sources/SQL/SQLCreateTable.swift +++ /dev/null @@ -1,65 +0,0 @@ -public protocol SQLCreateTable: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype ColumnDefinition: SQLColumnDefinition - associatedtype TableConstraint: SQLTableConstraint - - static func createTable(_ table: TableIdentifier) -> Self - - /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. - var temporary: Bool { get set } - - /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the - /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view - /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An - /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is - /// specified. - var ifNotExists: Bool { get set } - - var table: TableIdentifier { get set } - var columns: [ColumnDefinition] { get set } - var tableConstraints: [TableConstraint] { get set } -} - -/// The `CREATE TABLE` command is used to create a new table in a database. -public struct GenericSQLCreateTable: SQLCreateTable - where TableIdentifier: SQLTableIdentifier, ColumnDefinition: SQLColumnDefinition, TableConstraint: SQLTableConstraint -{ - public typealias `Self` = GenericSQLCreateTable - - /// See `SQLCreateTable`. - public static func createTable(_ table: TableIdentifier) -> Self { - return .init(temporary: false, ifNotExists: false, table: table, columns: [], tableConstraints: []) - } - - /// See `SQLCreateTable`. - public var temporary: Bool - - /// See `SQLCreateTable`. - public var ifNotExists: Bool - - /// See `SQLCreateTable`. - public var table: TableIdentifier - - /// See `SQLCreateTable`. - public var columns: [ColumnDefinition] - - /// See `SQLCreateTable`. - public var tableConstraints: [TableConstraint] - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append("CREATE") - if temporary { - sql.append("TEMPORARY") - } - sql.append("TABLE") - if ifNotExists { - sql.append("IF NOT EXISTS") - } - sql.append(table.serialize(&binds)) - let actions = columns.map { $0.serialize(&binds) } + tableConstraints.map { $0.serialize(&binds) } - sql.append("(" + actions.joined(separator: ", ") + ")") - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLCreateTableBuilder.swift b/Sources/SQL/SQLCreateTableBuilder.swift deleted file mode 100644 index 28202df..0000000 --- a/Sources/SQL/SQLCreateTableBuilder.swift +++ /dev/null @@ -1,83 +0,0 @@ -public final class SQLCreateTableBuilder: SQLQueryBuilder - where Connection: SQLConnection -{ - /// `CreateTable` query being built. - public var createTable: Connection.Query.CreateTable - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .createTable(createTable) - } - - /// Creates a new `SQLCreateTableBuilder`. - public init(_ createTable: Connection.Query.CreateTable, on connection: Connection) { - self.createTable = createTable - self.connection = connection - } - - - /// If the "TEMP" or "TEMPORARY" keyword occurs between the "CREATE" and "TABLE" then the new table is created in the temp database. - public func temporary() -> Self { - createTable.temporary = true - return self - } - - /// It is usually an error to attempt to create a new table in a database that already contains a table, index or view of the - /// same name. However, if the "IF NOT EXISTS" clause is specified as part of the CREATE TABLE statement and a table or view - /// of the same name already exists, the CREATE TABLE command simply has no effect (and no error message is returned). An - /// error is still returned if the table cannot be created because of an existing index, even if the "IF NOT EXISTS" clause is - /// specified. - public func ifNotExists() -> Self { - createTable.ifNotExists = true - return self - } - - /// Adds a column to the table. - /// - /// conn.create(table: Planet.self).column(for: \.name, type: .text, .notNull).run() - /// - /// - parameters: - /// - keyPath: Swift `KeyPath` to property that should be added. - /// - type: Name of type to use for this column. - /// - constraints: Zero or more column constraints to add. - /// - returns: Self for chaining. - public func column( - for keyPath: KeyPath, - type typeName: Connection.Query.CreateTable.ColumnDefinition.DataType, - _ constraints: Connection.Query.CreateTable.ColumnDefinition.ColumnConstraint... - ) -> Self where T: SQLTable { - return column(.columnDefinition(.keyPath(keyPath), typeName, constraints)) - } - - /// Adds a column to the table. - /// - /// conn.create(table: Planet.self).column(...).run() - /// - /// - parameters: - /// - columnDefinition: Column definition to add. - /// - returns: Self for chaining. - public func column(_ columnDefinition: Connection.Query.CreateTable.ColumnDefinition) -> Self { - createTable.columns.append(columnDefinition) - return self - } -} - -// MARK: Connection - -extension SQLConnection { - /// Creates a new `SQLCreateTableBuilder`. - /// - /// conn.create(table: Planet.self)... - /// - /// - parameters: - /// - table: Table to create. - /// - returns: `CreateTableBuilder`. - public func create
(table: Table.Type) -> SQLCreateTableBuilder - where Table: SQLTable - { - return .init(.createTable(.table(Table.self)), on: self) - } -} diff --git a/Sources/SQL/SQLDataType.swift b/Sources/SQL/SQLDataType.swift deleted file mode 100644 index 00862de..0000000 --- a/Sources/SQL/SQLDataType.swift +++ /dev/null @@ -1,3 +0,0 @@ -public protocol SQLDataType: SQLSerializable { - -} diff --git a/Sources/SQL/SQLDelete.swift b/Sources/SQL/SQLDelete.swift deleted file mode 100644 index 09de4f7..0000000 --- a/Sources/SQL/SQLDelete.swift +++ /dev/null @@ -1,42 +0,0 @@ -public protocol SQLDelete: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype Expression: SQLExpression - - static func delete(_ table: TableIdentifier) -> Self - - var table: TableIdentifier { get set } - - /// If the WHERE clause is not present, all records in the table are deleted. If a WHERE clause is supplied, - /// then only those rows for which the WHERE clause boolean expression is true are deleted. Rows for which - /// the expression is false or NULL are retained. - var predicate: Expression? { get set } -} - -// MARK: Generic - -public struct GenericSQLDelete: SQLDelete - where TableIdentifier: SQLTableIdentifier, Expression: SQLExpression -{ - /// See `SQLDelete`. - public static func delete(_ table: TableIdentifier) -> GenericSQLDelete { - return .init(table: table, predicate: nil) - } - - /// See `SQLDelete`. - public var table: TableIdentifier - - /// See `SQLDelete`. - public var predicate: Expression? - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append("DELETE FROM") - sql.append(table.serialize(&binds)) - if let predicate = self.predicate { - sql.append("WHERE") - sql.append(predicate.serialize(&binds)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLDeleteBuilder.swift b/Sources/SQL/SQLDeleteBuilder.swift deleted file mode 100644 index 22116bd..0000000 --- a/Sources/SQL/SQLDeleteBuilder.swift +++ /dev/null @@ -1,43 +0,0 @@ -public final class SQLDeleteBuilder: SQLQueryBuilder, SQLPredicateBuilder - where Connection: SQLConnection -{ - /// `Delete` query being built. - public var delete: Connection.Query.Delete - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .delete(delete) - } - - /// See `SQLWhereBuilder`. - public var predicate: Connection.Query.Delete.Expression? { - get { return delete.predicate } - set { delete.predicate = newValue } - } - - /// Creates a new `SQLDeleteBuilder`. - public init(_ delete: Connection.Query.Delete, on connection: Connection) { - self.delete = delete - self.connection = connection - } -} - -// MARK: Connection - -extension SQLConnection { - /// Creates a new `SQLDeleteBuilder`. - /// - /// conn.delete(from: Planet.self)... - /// - /// - parameters: - /// - table: Table to delete from. - /// - returns: Newly created `SQLDeleteBuilder`. - public func delete
(from table: Table.Type) -> SQLDeleteBuilder - where Table: SQLTable - { - return .init(.delete(.table(Table.self)), on: self) - } -} diff --git a/Sources/SQL/SQLDirection.swift b/Sources/SQL/SQLDirection.swift deleted file mode 100644 index b15496b..0000000 --- a/Sources/SQL/SQLDirection.swift +++ /dev/null @@ -1,29 +0,0 @@ -public protocol SQLDirection: SQLSerializable { - static var ascending: Self { get } - static var descending: Self { get } -} - -// MARK: Generic - -public enum GenericSQLDirection: SQLDirection { - /// See `SQLDirection`. - public static var ascending: GenericSQLDirection { - return ._ascending - } - - /// See `SQLDirection`. - public static var descending: GenericSQLDirection { - return ._descending - } - - case _ascending - case _descending - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._ascending: return "ASC" - case ._descending: return "DESC" - } - } -} diff --git a/Sources/SQL/SQLDistinct.swift b/Sources/SQL/SQLDistinct.swift deleted file mode 100644 index f351536..0000000 --- a/Sources/SQL/SQLDistinct.swift +++ /dev/null @@ -1,42 +0,0 @@ -public protocol SQLDistinct: SQLSerializable { - static var all: Self { get } - static var distinct: Self { get } - var isDistinct: Bool { get } -} - -// MARK: Default - -extension SQLDistinct { - public func serialize(_ binds: inout [Encodable]) -> String { - if isDistinct { - return "DISTINCT" - } else { - return "ALL" - } - } -} - -// MARK: Generic - -public enum GenericSQLDistinct: SQLDistinct { - /// See `SQLDistinct`. - public static var all: GenericSQLDistinct { - return ._all - } - - /// See `SQLDistinct`. - public static var distinct: GenericSQLDistinct { - return ._distinct - } - - /// See `SQLDistinct`. - public var isDistinct: Bool { - switch self { - case ._all: return false - case ._distinct: return true - } - } - - case _distinct - case _all -} diff --git a/Sources/SQL/SQLDrop.swift b/Sources/SQL/SQLDrop.swift deleted file mode 100644 index 8c01457..0000000 --- a/Sources/SQL/SQLDrop.swift +++ /dev/null @@ -1,38 +0,0 @@ -public protocol SQLDropTable: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - - /// Creates a new `SQLDropTable`. - static func dropTable(_ table: TableIdentifier) -> Self - - /// Table to drop. - var table: TableIdentifier { get set } - - /// The optional IF EXISTS clause suppresses the error that would normally result if the table does not exist. - var ifExists: Bool { get set } -} - -public struct GenericSQLDropTable: SQLDropTable - where TableIdentifier: SQLTableIdentifier -{ - /// See `SQLDropTable`. - public static func dropTable(_ table: TableIdentifier) -> GenericSQLDropTable { - return .init(table: table, ifExists: false) - } - - /// See `SQLDropTable`. - public var table: TableIdentifier - - /// See `SQLDropTable`. - public var ifExists: Bool - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append("DROP TABLE") - if ifExists { - sql.append("IF EXISTS") - } - sql.append(table.serialize(&binds)) - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLDropTableBuilder.swift b/Sources/SQL/SQLDropTableBuilder.swift deleted file mode 100644 index 131f771..0000000 --- a/Sources/SQL/SQLDropTableBuilder.swift +++ /dev/null @@ -1,35 +0,0 @@ -public final class SQLDropTableBuilder: SQLQueryBuilder - where Connection: SQLConnection -{ - /// `DropTable` query being built. - public var dropTable: Connection.Query.DropTable - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .dropTable(dropTable) - } - - /// Creates a new `SQLDropTableBuilder`. - public init(_ dropTable: Connection.Query.DropTable, on connection: Connection) { - self.dropTable = dropTable - self.connection = connection - } - - public func ifExists() -> Self { - dropTable.ifExists = true - return self - } -} - -// MARK: Connection - -extension SQLConnection { - public func drop
(table: Table.Type) -> SQLDropTableBuilder - where Table: SQLTable - { - return .init(.dropTable(.table(Table.self)), on: self) - } -} diff --git a/Sources/SQL/SQLExpression.swift b/Sources/SQL/SQLExpression.swift deleted file mode 100644 index c778ca2..0000000 --- a/Sources/SQL/SQLExpression.swift +++ /dev/null @@ -1,140 +0,0 @@ -public protocol SQLExpression: SQLSerializable { - associatedtype Literal: SQLLiteral - associatedtype Bind: SQLBind - associatedtype ColumnIdentifier: SQLColumnIdentifier - associatedtype BinaryOperator: SQLBinaryOperator - associatedtype Function: SQLFunction - associatedtype Subquery: SQLSerializable - - /// Literal strings, integers, and constants. - static func literal(_ literal: Literal) -> Self - - /// Bound value. - static func bind(_ bind: Bind) -> Self - - /// Column name. - static func column(_ column: ColumnIdentifier) -> Self - - /// Binary expression. - static func binary(_ lhs: Self, _ op: BinaryOperator, _ rhs: Self) -> Self - - /// Function. - static func function(_ function: Function) -> Self - - /// Group of expressions. - static func group(_ expressions: [Self]) -> Self - - /// `(SELECT ...)` - static func subquery(_ subquery: Subquery) -> Self - - // FIXME: collate - // FIXME: cast - - var isNull: Bool { get } -} - -// MARK: Convenience - -public func && (_ lhs: E, _ rhs: E) -> E where E: SQLExpression { - return E.binary(lhs, .and, rhs) -} - -public func || (_ lhs: E, _ rhs: E) -> E where E: SQLExpression { - return E.binary(lhs, .or, rhs) -} - -public func &= (_ lhs: inout E?, _ rhs: E) where E: SQLExpression { - if let l = lhs { - lhs = l && rhs - } else { - lhs = rhs - } -} - -public func |= (_ lhs: inout E?, _ rhs: E) where E: SQLExpression { - if let l = lhs { - lhs = l || rhs - } else { - lhs = rhs - } -} - - -// MARK: Generic - -public indirect enum GenericSQLExpression: SQLExpression - where Literal: SQLLiteral, Bind: SQLBind, ColumnIdentifier: SQLColumnIdentifier, BinaryOperator: SQLBinaryOperator & Equatable, Function: SQLFunction, Subquery: SQLSerializable -{ - public typealias `Self` = GenericSQLExpression - - public static func literal(_ literal: Literal) -> Self { - return ._literal(literal) - } - - public static func bind(_ bind: Bind) -> Self { - return ._bind(bind) - } - - public static func column(_ column: ColumnIdentifier) -> Self { - return ._column(column) - } - - public static func binary(_ lhs: Self, _ op: BinaryOperator, _ rhs: Self) -> Self { - return ._binary(lhs, op, rhs) - } - - public static func function(_ function: Function) -> Self { - return ._function(function) - } - - public static func group(_ expressions: [Self]) -> Self { - return ._group(expressions) - } - - public static func subquery(_ subquery: Subquery) -> Self { - return ._subquery(subquery) - } - - case _literal(Literal) - case _bind(Bind) - case _column(ColumnIdentifier) - case _binary(`Self`, BinaryOperator, `Self`) - case _function(Function) - case _group([`Self`]) - case _subquery(Subquery) - - public var isNull: Bool { - switch self { - case ._literal(let literal): return literal.isNull - default: return false - } - } - - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._literal(let literal): return literal.serialize(&binds) - case ._bind(let bind): return bind.serialize(&binds) - case ._column(let column): return column.serialize(&binds) - case ._binary(let lhs, let op, let rhs): - switch rhs { - case ._literal(let literal): - if literal.isNull { - switch op { - case .equal: - return lhs.serialize(&binds) + " IS NULL" - case .notEqual: - return lhs.serialize(&binds) + " IS NOT NULL" - default: break - } - } - default: break - } - return lhs.serialize(&binds) + " " + op.serialize(&binds) + " " + rhs.serialize(&binds) - case ._function(let function): return function.serialize(&binds) - case ._group(let group): - return "(" + group.map { $0.serialize(&binds) }.joined(separator: ", ") + ")" - case ._subquery(let subquery): - return "(" + subquery.serialize(&binds) + ")" - } - } -} diff --git a/Sources/SQL/SQLForeignKey.swift b/Sources/SQL/SQLForeignKey.swift deleted file mode 100644 index 0af6efe..0000000 --- a/Sources/SQL/SQLForeignKey.swift +++ /dev/null @@ -1,54 +0,0 @@ -public protocol SQLForeignKey: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype Identifier: SQLIdentifier - associatedtype ConflictResolution: SQLConflictResolution - - static func foreignKey( - _ foreignTable: TableIdentifier, - _ foreignColumns: [Identifier], - onDelete: ConflictResolution?, - onUpdate: ConflictResolution? - ) -> Self -} - -// MARK: Generic - -public struct GenericSQLForeignKey: SQLForeignKey - where TableIdentifier: SQLTableIdentifier, Identifier: SQLIdentifier, ConflictResolution: SQLConflictResolution -{ - public typealias `Self` = GenericSQLForeignKey - - /// See `SQLForeignKey`. - public static func foreignKey( - _ foreignTable: TableIdentifier, - _ foreignColumns: [Identifier], - onDelete: ConflictResolution?, - onUpdate: ConflictResolution? - ) -> Self { - return .init(foreignTable: foreignTable, foreignColumns: foreignColumns, onDelete: onDelete, onUpdate: onUpdate) - } - - public var foreignTable: TableIdentifier - - public var foreignColumns: [Identifier] - - public var onDelete: ConflictResolution? - - public var onUpdate: ConflictResolution? - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append(foreignTable.serialize(&binds)) - sql.append("(" + foreignColumns.serialize(&binds) + ")") - if let onDelete = onDelete { - sql.append("ON DELETE") - sql.append(onDelete.serialize(&binds)) - } - if let onUpdate = onUpdate { - sql.append("ON UPDATE") - sql.append(onUpdate.serialize(&binds)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLFunction.swift b/Sources/SQL/SQLFunction.swift deleted file mode 100644 index 926d0e0..0000000 --- a/Sources/SQL/SQLFunction.swift +++ /dev/null @@ -1,21 +0,0 @@ -public protocol SQLFunction: SQLSerializable { - associatedtype Argument: SQLFunctionArgument - static func function(_ name: String, _ args: [Argument]) -> Self -} - -// MARK: Generic - -public struct GenericSQLFunction: SQLFunction where Argument: SQLFunctionArgument { - /// See `SQLFunction`. - public static func function(_ name: String, _ args: [Argument]) -> GenericSQLFunction { - return .init(name: name, arguments: args) - } - - public var name: String - public var arguments: [Argument] - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - return name + "(" + arguments.map { $0.serialize(&binds) }.joined(separator: ", ") + ")" - } -} diff --git a/Sources/SQL/SQLFunctionArgument.swift b/Sources/SQL/SQLFunctionArgument.swift deleted file mode 100644 index e018ed0..0000000 --- a/Sources/SQL/SQLFunctionArgument.swift +++ /dev/null @@ -1,30 +0,0 @@ -public protocol SQLFunctionArgument: SQLSerializable { - associatedtype Expression: SQLExpression - static var all: Self { get } - static func expression(_ expression: Expression) -> Self -} - -// MARK: Generic - -public enum GenericSQLFunctionArgument: SQLFunctionArgument where Expression: SQLExpression { - /// See `SQLFunctionArgument`. - public static var all: GenericSQLFunctionArgument { - return ._all - } - - /// See `SQLFunctionArgument`. - public static func expression(_ expression: Expression) -> GenericSQLFunctionArgument { - return ._expression(expression) - } - - case _all - case _expression(Expression) - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._all: return "*" - case ._expression(let expr): return expr.serialize(&binds) - } - } -} diff --git a/Sources/SQL/SQLGroupBy.swift b/Sources/SQL/SQLGroupBy.swift deleted file mode 100644 index b41969f..0000000 --- a/Sources/SQL/SQLGroupBy.swift +++ /dev/null @@ -1,18 +0,0 @@ -public protocol SQLGroupBy: SQLSerializable { - associatedtype Expression: SQLExpression - static func groupBy(_ expression: Expression) -> Self -} - -// MARK: Generic - -public struct GenericSQLGroupBy: SQLGroupBy where Expression: SQLExpression { - public static func groupBy(_ expression: Expression) -> GenericSQLGroupBy { - return .init(expression: expression) - } - - public var expression: Expression - - public func serialize(_ binds: inout [Encodable]) -> String { - return expression.serialize(&binds) - } -} diff --git a/Sources/SQL/SQLIdentifier.swift b/Sources/SQL/SQLIdentifier.swift deleted file mode 100644 index e3b242e..0000000 --- a/Sources/SQL/SQLIdentifier.swift +++ /dev/null @@ -1,18 +0,0 @@ -public protocol SQLIdentifier: SQLSerializable { - static func identifier(_ string: String) -> Self - var string: String { get set } -} - -// MARK: Generic - -public struct GenericSQLIdentifier: SQLIdentifier { - public static func identifier(_ string: String) -> GenericSQLIdentifier { - return self.init(string: string) - } - - public var string: String - - public func serialize(_ binds: inout [Encodable]) -> String { - return "\"" + string + "\"" - } -} diff --git a/Sources/SQL/SQLInsert.swift b/Sources/SQL/SQLInsert.swift deleted file mode 100644 index 75ef01e..0000000 --- a/Sources/SQL/SQLInsert.swift +++ /dev/null @@ -1,37 +0,0 @@ -public protocol SQLInsert: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype ColumnIdentifier: SQLColumnIdentifier - associatedtype Expression: SQLExpression - - static func insert(_ table: TableIdentifier) -> Self - var columns: [ColumnIdentifier] { get set } - var values: [[Expression]] { get set } -} - -// MARK: Generic - -public struct GenericSQLInsert: SQLInsert - where TableIdentifier: SQLTableIdentifier, ColumnIdentifier: SQLColumnIdentifier, Expression: SQLExpression -{ - public typealias `Self` = GenericSQLInsert - - /// See `SQLInsert`. - public static func insert(_ table: TableIdentifier) -> Self { - return .init(table: table, columns: [], values: []) - } - - public var table: TableIdentifier - public var columns: [ColumnIdentifier] - public var values: [[Expression]] - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append("INSERT INTO") - sql.append(table.serialize(&binds)) - sql.append("(" + columns.serialize(&binds) + ")") - sql.append("VALUES") - sql.append(values.map { "(" + $0.serialize(&binds) + ")"}.joined(separator: ", ")) - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLInsertBuilder.swift b/Sources/SQL/SQLInsertBuilder.swift deleted file mode 100644 index a96cf1a..0000000 --- a/Sources/SQL/SQLInsertBuilder.swift +++ /dev/null @@ -1,67 +0,0 @@ -public final class SQLInsertBuilder: SQLQueryBuilder - where Connection: SQLConnection -{ - /// `Insert` query being built. - public var insert: Connection.Query.Insert - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .insert(insert) - } - - /// Creates a new `SQLInsertBuilder`. - public init(_ insert: Connection.Query.Insert, on connection: Connection) { - self.insert = insert - self.connection = connection - } - - public func value(_ value: E) throws -> Self - where E: Encodable - { - return values([value]) - } - - public func values(_ values: [E]) -> Self - where E: Encodable - { - values.forEach { model in - let row = SQLQueryEncoder(Connection.Query.Insert.Expression.self).encode(model) - if insert.columns.isEmpty { - insert.columns += row.map { .column(nil, .identifier($0.key)) } - } else { - assert( - insert.columns.count == row.count, - "Column count (\(insert.columns.count)) did not equal value count (\(row.count)): \(model)." - ) - } - insert.values.append(row.map { row in - if row.value.isNull { - return .literal(.null) // FIXME: use `DEFAULT` value - } else { - return row.value - } - }) - } - return self - } -} - -// MARK: Connection - -extension SQLConnection { - /// Creates a new `SQLInsertBuilder`. - /// - /// conn.insert(into: Planet.self)... - /// - /// - parameters: - /// - table: Table to insert into. - /// - returns: Newly created `SQLInsertBuilder`. - public func insert
(into table: Table.Type) -> SQLInsertBuilder - where Table: SQLTable - { - return .init(.insert(.table(Table.self)), on: self) - } -} diff --git a/Sources/SQL/SQLJoin.swift b/Sources/SQL/SQLJoin.swift deleted file mode 100644 index fb9cf78..0000000 --- a/Sources/SQL/SQLJoin.swift +++ /dev/null @@ -1,26 +0,0 @@ -public protocol SQLJoin: SQLSerializable { - associatedtype Method: SQLJoinMethod - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype Expression: SQLExpression - - static func join(_ method: Method, _ table: TableIdentifier, _ expression: Expression) -> Self -} - -public struct GenericSQLJoin: SQLJoin - where Method: SQLJoinMethod, TableIdentifier: SQLTableIdentifier, Expression: SQLExpression - -{ - /// See `SQLJoin`. - public static func join(_ method: Method, _ table: TableIdentifier, _ expression: Expression) -> GenericSQLJoin { - return .init(method: method, table: table, expression: expression) - } - - public var method: Method - public var table: TableIdentifier - public var expression: Expression - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - return method.serialize(&binds) + " JOIN " + table.serialize(&binds) + " ON " + expression.serialize(&binds) - } -} diff --git a/Sources/SQL/SQLJoinMethod.swift b/Sources/SQL/SQLJoinMethod.swift deleted file mode 100644 index a70d758..0000000 --- a/Sources/SQL/SQLJoinMethod.swift +++ /dev/null @@ -1,24 +0,0 @@ -public protocol SQLJoinMethod: SQLSerializable { - static var `default`: Self { get } -} - -public enum GenericSQLJoinMethod: SQLJoinMethod { - /// See `SQLJoinMethod`. - public static var `default`: GenericSQLJoinMethod { - return .inner - } - - case inner - case left - case right - case full - - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case .inner: return "INNER" - case .left: return "LEFT" - case .right: return "RIGHT" - case .full: return "FULL" - } - } -} diff --git a/Sources/SQL/SQLLiteral.swift b/Sources/SQL/SQLLiteral.swift deleted file mode 100644 index 8e2e878..0000000 --- a/Sources/SQL/SQLLiteral.swift +++ /dev/null @@ -1,78 +0,0 @@ -public protocol SQLLiteral: SQLSerializable { - static func string(_ string: String) -> Self - static func numeric(_ string: String) -> Self - static var null: Self { get } - static var `default`: Self { get } - static func boolean(_ bool: Bool) -> Self - - var isNull: Bool { get } -} - -// MARK: Generic - -public enum GenericSQLLiteral: SQLLiteral, ExpressibleByStringLiteral, ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral { - /// See `SQLLiteral`. - public static func string(_ string: String) -> GenericSQLLiteral { - return ._string(string) - } - - /// See `SQLLiteral`. - public static func numeric(_ string: String) -> GenericSQLLiteral { - return ._numeric(string) - } - - /// See `SQLLiteral`. - public static var null: GenericSQLLiteral { - return ._null - } - - /// See `SQLLiteral`. - public static var `default`: GenericSQLLiteral { - return ._default - } - - /// See `SQLLiteral`. - public static func boolean(_ bool: Bool) -> GenericSQLLiteral { - return ._boolean(bool) - } - - /// See `ExpressibleByStringLiteral`. - public init(stringLiteral value: String) { - self = .string(value) - } - - /// See `ExpressibleByFloatLiteral`. - public init(floatLiteral value: Double) { - self = .numeric(value.description) - } - - /// See `ExpressibleByIntegerLiteral`. - public init(integerLiteral value: Int) { - self = .numeric(value.description) - } - - case _string(String) - case _numeric(String) - case _null - case _default - case _boolean(Bool) - - /// See `SQLLiteral`. - public var isNull: Bool { - switch self { - case ._null: return true - default: return false - } - } - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._boolean(let bool): return bool.description.uppercased() - case ._null: return "NULL" - case ._default: return "DEFAULT" - case ._numeric(let string): return string - case ._string(let string): return "'" + string + "'" - } - } -} diff --git a/Sources/SQL/SQLOrderBy.swift b/Sources/SQL/SQLOrderBy.swift deleted file mode 100644 index 55ce93c..0000000 --- a/Sources/SQL/SQLOrderBy.swift +++ /dev/null @@ -1,20 +0,0 @@ -public protocol SQLOrderBy: SQLSerializable { - associatedtype Expression: SQLExpression - associatedtype Direction: SQLDirection - static func orderBy(_ expression: Expression, _ direction: Direction) -> Self -} - -// MARK: Generic - -public struct GenericSQLOrderBy: SQLOrderBy where Expression: SQLExpression, Direction: SQLDirection { - public static func orderBy(_ expression: Expression, _ direction: Direction) -> GenericSQLOrderBy { - return .init(expression: expression, direction: direction) - } - - public var expression: Expression - public var direction: Direction - - public func serialize(_ binds: inout [Encodable]) -> String { - return expression.serialize(&binds) + " " + direction.serialize(&binds) - } -} diff --git a/Sources/SQL/SQLPredicateBuilder.swift b/Sources/SQL/SQLPredicateBuilder.swift deleted file mode 100644 index 1429ed5..0000000 --- a/Sources/SQL/SQLPredicateBuilder.swift +++ /dev/null @@ -1,45 +0,0 @@ -public protocol SQLPredicateBuilder: class { - associatedtype Expression: SQLExpression - var predicate: Expression? { get set } -} - -extension SQLPredicateBuilder { - public func `where`(_ expressions: Expression...) -> Self { - for expression in expressions { - self.predicate &= expression - } - return self - } - - public func orWhere(_ expressions: Expression...) -> Self { - for expression in expressions { - self.predicate |= expression - } - return self - } - - public func `where`(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { - self.predicate &= .binary(lhs, op, rhs) - return self - } - - public func orWhere(_ lhs: Expression, _ op: Expression.BinaryOperator, _ rhs: Expression) -> Self { - self.predicate |= .binary(lhs, op, rhs) - return self - } - - public func `where`(group: (NestedSQLPredicateBuilder) throws -> ()) rethrows -> Self { - let builder = NestedSQLPredicateBuilder(Self.self) - try group(builder) - if let sub = builder.predicate { - self.predicate &= sub - } - return self - } -} - -public final class NestedSQLPredicateBuilder: SQLPredicateBuilder where PredicateBuilder: SQLPredicateBuilder { - public typealias Expression = PredicateBuilder.Expression - public var predicate: PredicateBuilder.Expression? - internal init(_ type: PredicateBuilder.Type) { } -} diff --git a/Sources/SQL/SQLPrimaryKey.swift b/Sources/SQL/SQLPrimaryKey.swift deleted file mode 100644 index d879cc4..0000000 --- a/Sources/SQL/SQLPrimaryKey.swift +++ /dev/null @@ -1,17 +0,0 @@ -public protocol SQLPrimaryKey: SQLSerializable { - static func primaryKey() -> Self -} - -// MARK: Generic - -public struct GenericSQLPrimaryKey: SQLPrimaryKey { - /// See `SQLPrimaryKey`. - public static func primaryKey() -> GenericSQLPrimaryKey { - return .init() - } - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - return "" - } -} diff --git a/Sources/SQL/SQLQuery.swift b/Sources/SQL/SQLQuery.swift deleted file mode 100644 index df29a3e..0000000 --- a/Sources/SQL/SQLQuery.swift +++ /dev/null @@ -1,20 +0,0 @@ -public protocol SQLQuery: SQLSerializable { - associatedtype AlterTable: SQLAlterTable - associatedtype CreateTable: SQLCreateTable - associatedtype Delete: SQLDelete - associatedtype DropTable: SQLDropTable - associatedtype Insert: SQLInsert - associatedtype Select: SQLSelect - associatedtype Update: SQLUpdate - - associatedtype RowDecoder: SQLRowDecoder - - static func alterTable(_ alterTable: AlterTable) -> Self - static func createTable(_ createTable: CreateTable) -> Self - static func delete(_ delete: Delete) -> Self - static func dropTable(_ dropTable: DropTable) -> Self - static func insert(_ insert: Insert) -> Self - static func select(_ select: Select) -> Self - static func update(_ update: Update) -> Self - static func raw(_ sql: String, binds: [Encodable]) -> Self -} diff --git a/Sources/SQL/SQLQueryBuilder.swift b/Sources/SQL/SQLQueryBuilder.swift deleted file mode 100644 index d1d7f4e..0000000 --- a/Sources/SQL/SQLQueryBuilder.swift +++ /dev/null @@ -1,101 +0,0 @@ -import Async - -public protocol SQLQueryBuilder: class { - associatedtype Connection: SQLConnection - var query: Connection.Query { get } - var connection: Connection { get } -} - -extension SQLQueryBuilder { - public func run() -> Future { - return connection.query(query) { _ in } - } -} - -public protocol SQLQueryFetcher: SQLQueryBuilder { } - -extension SQLQueryFetcher { - // MARK: Decode - - public func all(decoding type: D.Type) -> Future<[D]> - where D: Decodable - { - var all: [D] = [] - return run(decoding: D.self) { all.append($0) }.map { all } - } - - public func all(decoding a: A.Type, _ b: B.Type) -> Future<[(A, B)]> - where A: SQLTable, B: SQLTable - { - var all: [(A, B)] = [] - return run(decoding: A.self, B.self) { all.append(($0, $1)) }.map { all } - } - - public func all(decoding a: A.Type, _ b: B.Type, _ c: C.Type) -> Future<[(A, B, C)]> - where A: SQLTable, B: SQLTable, C: SQLTable - { - var all: [(A, B, C)] = [] - return run(decoding: A.self, B.self, C.self) { all.append(($0, $1, $2)) }.map { all } - } - - public func run( - decoding a: A.Type, _ b: B.Type, _ c: C.Type, - into handler: @escaping (A, B, C) throws -> () - ) -> Future - where A: SQLTable, B: SQLTable, C: SQLTable - { - return run { row in - let a = try Connection.Query.RowDecoder.init().decode(A.self, from: row, table: .table(A.self)) - let b = try Connection.Query.RowDecoder.init().decode(B.self, from: row, table: .table(B.self)) - let c = try Connection.Query.RowDecoder.init().decode(C.self, from: row, table: .table(C.self)) - try handler(a, b, c) - } - } - - public func run( - decoding a: A.Type, _ b: B.Type, - into handler: @escaping (A, B) throws -> () - ) -> Future - where A: SQLTable, B: SQLTable - { - return run { row in - let a = try Connection.Query.RowDecoder.init().decode(A.self, from: row, table: .table(A.self)) - let b = try Connection.Query.RowDecoder.init().decode(B.self, from: row, table: .table(B.self)) - try handler(a, b) - } - } - - public func run( - decoding type: D.Type, - into handler: @escaping (D) throws -> () - ) -> Future - where D: Decodable - { - return run { row in - let d = try Connection.Query.RowDecoder.init().decode(D.self, from: row, table: nil) - try handler(d) - } - } - - // MARK: All - - public func all() -> Future<[Connection.Output]> { - var all: [Connection.Output] = [] - return connection.query(query) { all.append($0) }.map { all } - } - - public func run(_ handler: @escaping (Connection.Output) throws -> ()) -> Future { - return connection.query(query, handler) - } -} - - -//extension Dictionary where Key == SQLiteColumn, Value == SQLiteData { -// public func decode
(_ type: Table.Type) throws -> Table where Table: SQLiteTable { -// return try decode(Table.self, from: Table.sqlTableIdentifier.name.string) -// } -// -// public func decode(_ type: D.Type, from table: SQLiteQuery.Expression.ColumnIdentifier.TableIdentifier) throws -> D where D: Decodable { -// return try SQLiteRowDecoder().decode(D.self, from: self, table: table) -// } -//} diff --git a/Sources/SQL/SQLQueryEncoder.swift b/Sources/SQL/SQLQueryEncoder.swift deleted file mode 100644 index cc7dee7..0000000 --- a/Sources/SQL/SQLQueryEncoder.swift +++ /dev/null @@ -1,123 +0,0 @@ -/// Encodes keyed `Encodable` values to a `SQLiteQuery` expression dictionary. -/// -/// struct User: Codable { -/// var name: String -/// } -/// -/// let user = User(name: "Vapor") -/// let data = try SQLiteQueryEncoder().encode(user) -/// print(data) // ["name": .data(.text("Vapor"))] -/// -/// This encoder uses `SQLiteQueryExpressionEncoder` internally. Any types conforming to `SQLiteQueryExpressionRepresentable` -/// or `SQLiteDataConvertible` will be specially encoded. -/// -/// This encoder does _not_ support unkeyed or single value codable objects. -public struct SQLQueryEncoder where Expression: SQLExpression { - /// Creates a new `SQLQueryEncoder`. - public init(_ type: Expression.Type) { } - - /// Encodes keyed `Encodable` values to a `SQLiteQuery` expression dictionary. - /// - /// struct User: Codable { - /// var name: String - /// } - /// - /// let user = User(name: "Vapor") - /// let data = try SQLiteQueryEncoder().encode(user) - /// print(data) // ["name": .data(.text("Vapor"))] - /// - /// - parameters: - /// - value: `Encodable` value to encode. - /// - returns: `SQLiteQuery` compatible data. - public func encode(_ value: E) -> [String: Expression] - where E: Encodable - { - let encoder = _Encoder() - do { - try value.encode(to: encoder) - } catch { - assertionFailure("Failed to encode \(value) to SQL query expression.") - return [:] - } - return encoder.row - } - - // MARK: Private - - private final class _Encoder: Encoder { - let codingPath: [CodingKey] = [] - var userInfo: [CodingUserInfoKey: Any] = [:] - var row: [String: Expression] - - init() { - self.row = [:] - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - return .init(_KeyedEncodingContainer(encoder: self)) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - fatalError() - } - - func singleValueContainer() -> SingleValueEncodingContainer { - fatalError() - } - } - - private struct _KeyedEncodingContainer: KeyedEncodingContainerProtocol where Key: CodingKey { - let codingPath: [CodingKey] = [] - let encoder: _Encoder - init(encoder: _Encoder) { - self.encoder = encoder - } - - mutating func encodeNil(forKey key: Key) throws { - encoder.row[key.stringValue] = .literal(.null) - } - - mutating func encode(_ encodable: T, forKey key: Key) throws where T : Encodable { - encoder.row[key.stringValue] = .bind(.encodable(encodable)) - } - - mutating func _encodeIfPresent(_ value: T?, forKey key: Key) throws where T : Encodable { - if let value = value { - try encode(value, forKey: key) - } else { - try encodeNil(forKey: key) - } - } - - mutating func encodeIfPresent(_ value: T?, forKey key: Key) throws where T : Encodable { try _encodeIfPresent(value, forKey: key)} - mutating func encodeIfPresent(_ value: Int?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Double?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Float?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: String?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - mutating func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) } - - mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - fatalError() - } - - mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - fatalError() - } - - mutating func superEncoder() -> Encoder { - fatalError() - } - - mutating func superEncoder(forKey key: Key) -> Encoder { - fatalError() - } - } -} diff --git a/Sources/SQL/SQLRowDecoder.swift b/Sources/SQL/SQLRowDecoder.swift deleted file mode 100644 index f1ae60e..0000000 --- a/Sources/SQL/SQLRowDecoder.swift +++ /dev/null @@ -1,8 +0,0 @@ -public protocol SQLRowDecoder { - associatedtype Row - associatedtype TableIdentifier: SQLTableIdentifier - - init() - func decode(_ type: D.Type, from row: Row, table: TableIdentifier?) throws -> D - where D: Decodable -} diff --git a/Sources/SQL/SQLSelect.swift b/Sources/SQL/SQLSelect.swift deleted file mode 100644 index 845f9d6..0000000 --- a/Sources/SQL/SQLSelect.swift +++ /dev/null @@ -1,71 +0,0 @@ -public protocol SQLSelect: SQLSerializable { - associatedtype Distinct: SQLDistinct - associatedtype SelectExpression: SQLSelectExpression - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype Join: SQLJoin - associatedtype Expression: SQLExpression - associatedtype GroupBy: SQLGroupBy - associatedtype OrderBy: SQLOrderBy - - static func select() -> Self - - var distinct: Distinct? { get set } - var columns: [SelectExpression] { get set } - var tables: [TableIdentifier] { get set } - var joins: [Join] { get set } - var predicate: Expression? { get set } - var groupBy: [GroupBy] { get set } - var orderBy: [OrderBy] { get set } -} - -// MARK: Generic - -public struct GenericSQLSelect: SQLSelect -where Distinct: SQLDistinct, SelectExpression: SQLSelectExpression, TableIdentifier: SQLTableIdentifier, Join: SQLJoin, Expression: SQLExpression, GroupBy: SQLGroupBy, OrderBy: SQLOrderBy -{ - public typealias `Self` = GenericSQLSelect - - public var distinct: Distinct? - public var columns: [SelectExpression] - public var tables: [TableIdentifier] - public var joins: [Join] - public var predicate: Expression? - public var groupBy: [GroupBy] - public var orderBy: [OrderBy] - - /// See `SQLSelect`. - public static func select() -> Self { - return .init(distinct: nil, columns: [], tables: [], joins: [], predicate: nil, groupBy: [], orderBy: []) - } - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append("SELECT") - if let distinct = self.distinct { - sql.append(distinct.serialize(&binds)) - } - sql.append(columns.serialize(&binds)) - if !tables.isEmpty { - sql.append("FROM") - sql.append(tables.serialize(&binds)) - } - if !joins.isEmpty { - sql.append(joins.serialize(&binds)) - } - if let predicate = self.predicate { - sql.append("WHERE") - sql.append(predicate.serialize(&binds)) - } - if !groupBy.isEmpty { - sql.append("GROUP BY") - sql.append(groupBy.serialize(&binds)) - } - if !orderBy.isEmpty { - sql.append("ORDER BY") - sql.append(orderBy.serialize(&binds)) - } - return sql.joined(separator: " ") - - } -} diff --git a/Sources/SQL/SQLSelectBuilder.swift b/Sources/SQL/SQLSelectBuilder.swift deleted file mode 100644 index e21f771..0000000 --- a/Sources/SQL/SQLSelectBuilder.swift +++ /dev/null @@ -1,146 +0,0 @@ -public final class SQLSelectBuilder: SQLQueryFetcher, SQLPredicateBuilder - where Connection: SQLConnection -{ - /// `Select` query being built. - public var select: Connection.Query.Select - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .select(select) - } - - /// See `SQLWhereBuilder`. - public var predicate: Connection.Query.Select.Expression? { - get { return select.predicate } - set { select.predicate = newValue } - } - - /// Creates a new `SQLCreateTableBuilder`. - public init(_ select: Connection.Query.Select, on connection: Connection) { - self.select = select - self.connection = connection - } - - public func column( - function: String, - _ arguments: Connection.Query.Select.SelectExpression.Expression.Function.Argument..., - as alias: Connection.Query.Select.SelectExpression.Identifier? = nil - ) -> Self { - return column(expression: .function(.function(function, arguments)), as: alias) - } - - public func column( - expression: Connection.Query.Select.SelectExpression.Expression, - as alias: Connection.Query.Select.SelectExpression.Identifier? = nil - ) -> Self { - return column(.expression(expression, alias: alias)) - } - - public func all() -> Self { - return column(.all) - } - - public func all(table: String) -> Self { - return column(.allTable(table)) - } - - public func column(_ column: Connection.Query.Select.SelectExpression) -> Self { - select.columns.append(column) - return self - } - - public func from(_ tables: Connection.Query.Select.TableIdentifier...) -> Self { - select.tables += tables - return self - } - - public func from
(_ table: Table.Type) -> Self - where Table: SQLTable - { - select.tables.append(.table(.identifier(Table.sqlTableIdentifierString))) - return self - } - - public func join( - _ local: KeyPath, - to foreign: KeyPath - ) -> Self where A: SQLTable, B: Encodable, C: SQLTable, D: Encodable { - return join(.default, local, to: foreign) - } - - public func join( - _ method: Connection.Query.Select.Join.Method, - _ local: KeyPath, - to foreign: KeyPath - ) -> Self where A: SQLTable, B: Encodable, C: SQLTable, D: Encodable { - return join(method, C.self, on: local == foreign) - } - - public func join
(_ table: Table.Type, on expression: Connection.Query.Select.Join.Expression) -> Self - where Table: SQLTable - { - return join(.default, table, on: expression) - } - - public func join
(_ method: Connection.Query.Select.Join.Method, _ table: Table.Type, on expression: Connection.Query.Select.Join.Expression) -> Self - where Table: SQLTable - { - select.joins.append(.join(method, .table(Table.self), expression)) - return self - } - - public func groupBy(_ keyPath: KeyPath) -> Self - where T: SQLTable - { - return groupBy(.column(.keyPath(keyPath))) - } - - public func groupBy(_ expression: Connection.Query.Select.GroupBy.Expression) -> Self { - select.groupBy.append(.groupBy(expression)) - return self - } - - public func orderBy(_ keyPath: KeyPath, _ direction: Connection.Query.Select.OrderBy.Direction = .ascending) -> Self - where T: SQLTable - { - return orderBy(.column(.keyPath(keyPath)), direction) - } - - public func orderBy(_ expression: Connection.Query.Select.OrderBy.Expression, _ direction: Connection.Query.Select.OrderBy.Direction = .ascending) -> Self { - select.orderBy.append(.orderBy(expression, direction)) - return self - } - - // public func join
(_ table: Table.Type, on expr: Expression) -> Self - // where Table: SQLiteTable - // { - // switch select.tables.count { - // case 0: fatalError("Must select from a atable before joining.") - // default: - // let join = SQLiteQuery.JoinClause.init( - // table: select.tables[0], - // joins: [ - // SQLiteQuery.JoinClause.Join( - // natural: false, - // .inner, - // table: .table(.init(table:.init(table: Table.sqliteTableName))), - // constraint: .condition(expr) - // ) - // ] - // ) - // select.tables[0] = .joinClause(join) - // } - // return self - // } -} - -// MARK: Connection - -extension SQLConnection { - public func select() -> SQLSelectBuilder { - return .init(.select(), on: self) - } -} diff --git a/Sources/SQL/SQLSelectExpression.swift b/Sources/SQL/SQLSelectExpression.swift deleted file mode 100644 index 28b4059..0000000 --- a/Sources/SQL/SQLSelectExpression.swift +++ /dev/null @@ -1,89 +0,0 @@ -public protocol SQLSelectExpression: SQLSerializable { - associatedtype Expression: SQLExpression - associatedtype Identifier: SQLIdentifier - - static var all: Self { get } - static func allTable(_ table: String) -> Self - static func expression(_ expression: Expression, alias: Identifier?) -> Self - - var isAll: Bool { get } - var allTable: String? { get } - var expression: (expression: Expression, alias: Identifier?)? { get } -} - -// MARK: Convenience - -extension SQLSelectExpression { - public static func function(_ function: Expression, as alias: Identifier? = nil) -> Self { - return .expression(function, alias: alias) - } -} - -// MARK: Generic - -public enum GenericSQLSelectExpression: SQLSelectExpression where - Expression: SQLExpression, Identifier: SQLIdentifier -{ - public typealias `Self` = GenericSQLSelectExpression - - /// See `SQLSelectExpression`. - public static var all: Self { - return ._all - } - - /// See `SQLSelectExpression`. - public static func allTable(_ table: String) ->Self { - return ._allTable(table) - } - - /// See `SQLSelectExpression`. - public static func expression(_ expression: Expression, alias: Identifier?) -> Self { - return ._expression(expression, alias: alias) - } - - /// See `SQLSelectExpression`. - public var isAll: Bool { - switch self { - case ._all: return true - default: return false - } - } - - /// See `SQLSelectExpression`. - public var allTable: String? { - switch self { - case ._allTable(let table): return table - default: return nil - } - } - - /// See `SQLSelectExpression`. - public var expression: (expression: Expression, alias: Identifier?)? { - switch self { - case ._expression(let expr, let alias): return (expr, alias) - default: return nil - } - } - - /// `*` - case _all - - /// `table.*` - case _allTable(String) - - /// `md5(a) AS hash` - case _expression(Expression, alias: Identifier?) - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._all: return "*" - case ._allTable(let table): return table + ".*" - case ._expression(let expr, let alias): - switch alias { - case .none: return expr.serialize(&binds) - case .some(let alias): return expr.serialize(&binds) + " AS " + alias.serialize(&binds) - } - } - } -} diff --git a/Sources/SQL/SQLSerializable.swift b/Sources/SQL/SQLSerializable.swift deleted file mode 100644 index 9bedd78..0000000 --- a/Sources/SQL/SQLSerializable.swift +++ /dev/null @@ -1,9 +0,0 @@ -public protocol SQLSerializable { - func serialize(_ binds: inout [Encodable]) -> String -} - -extension Array where Element: SQLSerializable { - public func serialize(_ binds: inout [Encodable], joinedBy separator: String = ", ") -> String { - return map { $0.serialize(&binds) }.joined(separator: separator) - } -} diff --git a/Sources/SQL/SQLTable.swift b/Sources/SQL/SQLTable.swift deleted file mode 100644 index a7e57f5..0000000 --- a/Sources/SQL/SQLTable.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Core - -public protocol SQLTable: Codable, Reflectable { - static var sqlTableIdentifierString: String { get } -} - -extension SQLTable { - public static var sqlTableIdentifierString: String { - return "\(Self.self)" - } -} diff --git a/Sources/SQL/SQLTableConstraint.swift b/Sources/SQL/SQLTableConstraint.swift deleted file mode 100644 index 023ae4f..0000000 --- a/Sources/SQL/SQLTableConstraint.swift +++ /dev/null @@ -1,59 +0,0 @@ -public protocol SQLTableConstraint: SQLSerializable { - associatedtype Identifier: SQLIdentifier - associatedtype Algorithm: SQLTableConstraintAlgorithm - static func constraint(_ algorithm: Algorithm, _ identifier: Identifier?) -> Self -} - -// MARK: Convenience - -extension SQLTableConstraint { - public static func primaryKey( - _ columns: Algorithm.Identifier..., - identifier: Identifier? = nil - ) -> Self { - return .constraint(.primaryKey(columns, .primaryKey()), identifier) - } - public static func unique( - _ columns: Algorithm.Identifier..., - identifier: Identifier? = nil - ) -> Self { - return .constraint(.unique(columns), identifier) - } - - public static func foreignKey( - _ columns: [Algorithm.Identifier], - references foreignTable: Algorithm.ForeignKey.TableIdentifier, - _ foreignColumns: [Algorithm.ForeignKey.Identifier], - onDelete: Algorithm.ForeignKey.ConflictResolution? = nil, - onUpdate: Algorithm.ForeignKey.ConflictResolution? = nil, - identifier: Identifier? = nil - ) -> Self { - return .constraint(.foreignKey(columns, .foreignKey(foreignTable, foreignColumns, onDelete: onDelete, onUpdate: onUpdate)), identifier) - } -} - -// MARK: Generic - -public struct GenericSQLTableConstraint: SQLTableConstraint - where Identifier: SQLIdentifier, Algorithm: SQLTableConstraintAlgorithm -{ - public typealias `Self` = GenericSQLTableConstraint - - /// See `SQLColumnConstraint`. - public static func constraint(_ algorithm: Algorithm, _ identifier: Identifier?) -> Self { - return .init(identifier: identifier, algorithm: algorithm) - } - - public var identifier: Identifier? - - public var algorithm: Algorithm - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - if let identifier = self.identifier { - return "CONSTRAINT " + identifier.serialize(&binds) + " " + algorithm.serialize(&binds) - } else { - return algorithm.serialize(&binds) - } - } -} diff --git a/Sources/SQL/SQLTableConstraintAlgorithm.swift b/Sources/SQL/SQLTableConstraintAlgorithm.swift deleted file mode 100644 index b30edc8..0000000 --- a/Sources/SQL/SQLTableConstraintAlgorithm.swift +++ /dev/null @@ -1,78 +0,0 @@ -public protocol SQLTableConstraintAlgorithm: SQLSerializable { - associatedtype Identifier: SQLIdentifier - associatedtype Expression: SQLExpression - associatedtype Collation: SQLCollation - associatedtype PrimaryKey: SQLPrimaryKey - associatedtype ForeignKey: SQLForeignKey - static func primaryKey(_ columns: [Identifier],_ primaryKey: PrimaryKey) -> Self - static var notNull: Self { get } - static func unique(_ columns: [Identifier]) -> Self - static func check(_ expression: Expression) -> Self - static func foreignKey(_ columns: [Identifier], _ foreignKey: ForeignKey) -> Self -} - -// MARK: Generic - -public enum GenericSQLTableConstraintAlgorithm: SQLTableConstraintAlgorithm - where Identifier: SQLIdentifier, Expression: SQLExpression, Collation: SQLCollation, PrimaryKey: SQLPrimaryKey, ForeignKey: SQLForeignKey -{ - public typealias `Self` = GenericSQLTableConstraintAlgorithm - - /// See `SQLColumnConstraintAlgorithm`. - public static func primaryKey(_ columns: [Identifier], _ primaryKey: PrimaryKey) -> Self { - return ._primaryKey(columns, primaryKey) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static var notNull: Self { - return ._notNull - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func unique(_ columns: [Identifier]) -> Self { - return ._unique(columns) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func check(_ expression: Expression) -> Self { - return ._check(expression) - } - - /// See `SQLColumnConstraintAlgorithm`. - public static func foreignKey(_ columns: [Identifier], _ foreignKey: ForeignKey) -> Self { - return ._foreignKey(columns, foreignKey) - } - - case _primaryKey([Identifier], PrimaryKey) - case _notNull - case _unique([Identifier]) - case _check(Expression) - case _foreignKey([Identifier], ForeignKey) - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - switch self { - case ._primaryKey(let columns, let primaryKey): - var sql: [String] = [] - sql.append("PRIMARY KEY") - sql.append("(" + columns.serialize(&binds) + ")") - sql.append(primaryKey.serialize(&binds)) - return sql.joined(separator: " ") - case ._notNull: return "NOT NULL" - case ._unique(let columns): - var sql: [String] = [] - sql.append("UNIQUE") - sql.append("(" + columns.serialize(&binds) + ")") - return sql.joined(separator: " ") - case ._check(let expression): - return "CHECK (" + expression.serialize(&binds) + ")" - case ._foreignKey(let columns, let foreignKey): - var sql: [String] = [] - sql.append("FOREIGN KEY") - sql.append("(" + columns.serialize(&binds) + ")") - sql.append("REFERENCES") - sql.append(foreignKey.serialize(&binds)) - return sql.joined(separator: " ") - } - } -} diff --git a/Sources/SQL/SQLTableIdentifier.swift b/Sources/SQL/SQLTableIdentifier.swift deleted file mode 100644 index 3ebd467..0000000 --- a/Sources/SQL/SQLTableIdentifier.swift +++ /dev/null @@ -1,45 +0,0 @@ -public protocol SQLTableIdentifier: SQLSerializable { - associatedtype Identifier: SQLIdentifier - - static func table(_ identifier: Identifier) -> Self - var identifier: Identifier { get set } -} - -// MARK: Convenience - -extension SQLTableIdentifier { - static func table
(_ table: Table.Type) -> Self - where Table: SQLTable - { - return .table(.identifier(Table.sqlTableIdentifierString)) - } -} - -// MARK: Generic - -public struct GenericSQLTableIdentifier: SQLTableIdentifier, ExpressibleByStringLiteral - where Identifier: SQLIdentifier -{ - /// See `SQLTableIdentifier`. - public static func table(_ identifier: Identifier) -> GenericSQLTableIdentifier { - return .init(identifier) - } - - /// See `SQLTableIdentifier`. - public var identifier: Identifier - - /// Creates a new `GenericSQLTableIdentifier`. - public init(_ identifier: Identifier) { - self.identifier = identifier - } - - /// See `ExpressibleByStringLiteral`. - public init(stringLiteral value: String) { - self.identifier = .identifier(value) - } - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - return identifier.serialize(&binds) - } -} diff --git a/Sources/SQL/SQLUpdate.swift b/Sources/SQL/SQLUpdate.swift deleted file mode 100644 index 74eb402..0000000 --- a/Sources/SQL/SQLUpdate.swift +++ /dev/null @@ -1,41 +0,0 @@ -public protocol SQLUpdate: SQLSerializable { - associatedtype TableIdentifier: SQLTableIdentifier - associatedtype Identifier: SQLIdentifier - associatedtype Expression: SQLExpression - - static func update(_ table: TableIdentifier) -> Self - - var table: TableIdentifier { get set } - var values: [(Identifier, Expression)] { get set } - var predicate: Expression? { get set } -} - -// MARK: Generic - -public struct GenericSQLUpdate: SQLUpdate - where TableIdentifier: SQLTableIdentifier, Identifier: SQLIdentifier, Expression: SQLExpression -{ - public typealias `Self` = GenericSQLUpdate - - public static func update(_ table: TableIdentifier) -> Self { - return .init(table: table, values: [], predicate: nil) - } - - public var table: TableIdentifier - public var values: [(Identifier, Expression)] - public var predicate: Expression? - - /// See `SQLSerializable`. - public func serialize(_ binds: inout [Encodable]) -> String { - var sql: [String] = [] - sql.append("UPDATE") - sql.append(table.serialize(&binds)) - sql.append("SET") - sql.append(values.map { $0.0.serialize(&binds) + " = " + $0.1.serialize(&binds) }.joined(separator: ", ")) - if let predicate = self.predicate { - sql.append("WHERE") - sql.append(predicate.serialize(&binds)) - } - return sql.joined(separator: " ") - } -} diff --git a/Sources/SQL/SQLUpdateBuilder.swift b/Sources/SQL/SQLUpdateBuilder.swift deleted file mode 100644 index 87b2b0e..0000000 --- a/Sources/SQL/SQLUpdateBuilder.swift +++ /dev/null @@ -1,60 +0,0 @@ -public final class SQLUpdateBuilder: SQLQueryBuilder, SQLPredicateBuilder - where Connection: SQLConnection -{ - /// `Update` query being built. - public var update: Connection.Query.Update - - /// See `SQLQueryBuilder`. - public var connection: Connection - - /// See `SQLQueryBuilder`. - public var query: Connection.Query { - return .update(update) - } - - /// See `SQLWhereBuilder`. - public var predicate: Connection.Query.Update.Expression? { - get { return update.predicate } - set { update.predicate = newValue } - } - - /// Creates a new `SQLDeleteBuilder`. - public init(_ update: Connection.Query.Update, on connection: Connection) { - self.update = update - self.connection = connection - } - - public func set(_ model: E)-> Self - where E: Encodable - { - let row = SQLQueryEncoder(Connection.Query.Update.Expression.self).encode(model) - update.values += row.map { row -> (Connection.Query.Update.Identifier, Connection.Query.Update.Expression) in - return (.identifier(row.key), row.value) - } - return self - } - - public func set(_ keyPath: KeyPath, to value: V) -> Self - where V: Encodable, T: SQLTable - { - update.values.append((.keyPath(keyPath), .bind(.encodable(value)))) - return self - } -} - -// MARK: Connection - -extension SQLConnection { - /// Creates a new `SQLUpdateBuilder`. - /// - /// conn.update(Planet.self)... - /// - /// - parameters: - /// - table: Table to update. - /// - returns: Newly created `SQLUpdateBuilder`. - public func update
(_ table: Table.Type) -> SQLUpdateBuilder - where Table: SQLTable - { - return .init(.update(.table(Table.self)), on: self) - } -} diff --git a/Sources/SQL/SQLWith.swift b/Sources/SQL/SQLWith.swift deleted file mode 100644 index 72ee358..0000000 --- a/Sources/SQL/SQLWith.swift +++ /dev/null @@ -1,10 +0,0 @@ -//public struct WithClause { -// public struct CommonTableExpression { -// public var table: String -// public var columns: [Name] -// public var select: Select -// } -// -// public var recursive: Bool -// public var expressions: [CommonTableExpression] -//} diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 57ca975..0598887 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -2,24 +2,7 @@ import SQL import SQLite import XCTest -struct Planet: SQLiteTable { - var id: Int? - var name: String - var galaxyID: Int - init(id: Int? = nil, name: String, galaxyID: Int) { - self.id = id - self.name = name - self.galaxyID = galaxyID - } -} -struct Galaxy: SQLiteTable { - var id: Int? - var name: String - init(id: Int? = nil, name: String) { - self.id = id - self.name = name - } -} + struct Galaxy2: SQLiteTable { @@ -42,84 +25,6 @@ class SQLiteTests: XCTestCase { print(res) } - func testSQL() throws { - let conn = try SQLiteConnection.makeTest() - - try conn.drop(table: Planet.self) - .ifExists() - .run().wait() - try conn.drop(table: Galaxy.self) - .ifExists() - .run().wait() - - try conn.create(table: Galaxy.self) - .column(for: \Galaxy.id, type: .integer, .primaryKey) - .column(for: \Galaxy.name, type: .text, .notNull) - .run().wait() - try conn.create(table: Planet.self) - .temporary() - .ifNotExists() - .column(for: \Planet.id, type: .integer, .primaryKey) - .column(for: \Planet.galaxyID, type: .integer, .notNull, .references(\Galaxy.id)) - .run().wait() - - try conn.alter(table: Planet.self) - .addColumn(for: \Planet.name, type: .text, .notNull, .default(.literal("Unamed Planet"))) - .run().wait() - - try conn.insert(into: Galaxy.self) - .value(Galaxy(name: "Milky Way")) - .run().wait() - - let a = try conn.select().all().from(Galaxy.self) - .where(\Galaxy.name == "Milky Way") - .groupBy(\Galaxy.name) - .orderBy(\Galaxy.name, .descending) - .all(decoding: Galaxy.self).wait() - print(a) - - let galaxyID = conn.lastAutoincrementID.flatMap(Int.init)! - try conn.insert(into: Planet.self) - .value(Planet(name: "Earth", galaxyID: galaxyID)) - .run().wait() - - try conn.insert(into: Planet.self) - .values([ - Planet(name: "Mercury", galaxyID: galaxyID), - Planet(name: "Venus", galaxyID: galaxyID), - Planet(name: "Mars", galaxyID: galaxyID), - Planet(name: "Jpuiter", galaxyID: galaxyID), - Planet(name: "Pluto", galaxyID: galaxyID) - ]) - .run().wait() - - try conn.update(Planet.self) - .where(\Planet.name == "Jpuiter") - .set(["name": "Jupiter"]) - .run().wait() - - let selectC = try conn.select().all() - .from(Planet.self) - .join(\Planet.galaxyID, to: \Galaxy.id) - .all(decoding: Planet.self, Galaxy.self) - .wait() - print(selectC) - - try conn.update(Galaxy.self) - .set(\Galaxy.name, to: "Milky Way 2") - .where(\Galaxy.name == "Milky Way") - .run().wait() - - try conn.delete(from: Galaxy.self) - .where(\Galaxy.name == "Milky Way") - .run().wait() - - let b = try conn.select() - .column(.count(as: "c")) - .from(Galaxy.self) - .all().wait() - print(b) - } func testTables() throws { From 587b385f1ca2aec89824ebbd93bfeec5f4c0ba1d Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Mon, 18 Jun 2018 18:58:09 -0400 Subject: [PATCH 16/21] add SQLBenchmark supportg --- Package.swift | 4 +- .../SQLite/Database/SQLiteConnection.swift | 2 +- Sources/SQLite/Row/SQLiteDataType.swift | 8 +++ .../{Query => SQL}/SQLiteAlterTable.swift | 22 +++++++ .../SQLiteAlterTableBuilder.swift | 0 .../SQLite/{Query => SQL}/SQLiteBind.swift | 0 .../{Query => SQL}/SQLiteCollation.swift | 0 .../{Query => SQL}/SQLiteCreateTable.swift | 0 .../SQLiteDataTypeStaticRepresentable.swift | 63 +++++++++++++++++++ .../{Query => SQL}/SQLiteFunction.swift | 0 .../SQLite/{Query => SQL}/SQLiteGeneric.swift | 0 .../{Query => SQL}/SQLitePrimaryKey.swift | 0 .../SQLite/{Query => SQL}/SQLiteQuery.swift | 0 .../SQLiteQueryExpressionRepresentable.swift | 0 .../SQLite/{Query => SQL}/SQLiteTable.swift | 0 Tests/SQLiteTests/SQLiteTests.swift | 20 +++--- 16 files changed, 105 insertions(+), 14 deletions(-) rename Sources/SQLite/{Query => SQL}/SQLiteAlterTable.swift (75%) rename Sources/SQLite/{Query => SQL}/SQLiteAlterTableBuilder.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteBind.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteCollation.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteCreateTable.swift (100%) create mode 100644 Sources/SQLite/SQL/SQLiteDataTypeStaticRepresentable.swift rename Sources/SQLite/{Query => SQL}/SQLiteFunction.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteGeneric.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLitePrimaryKey.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteQuery.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteQueryExpressionRepresentable.swift (100%) rename Sources/SQLite/{Query => SQL}/SQLiteTable.swift (100%) diff --git a/Package.swift b/Package.swift index 756ca7b..55d4d63 100644 --- a/Package.swift +++ b/Package.swift @@ -11,13 +11,13 @@ let package = Package( .package(url: "https://github.com/vapor/core.git", from: "3.0.0"), // 🗄 Core services for creating database integrations. - .package(url: "https://github.com/vapor/database-kit.git", from: "1.0.0"), + .package(url: "https://github.com/vapor/database-kit.git", .branch("sql")), // *️⃣ Build SQL queries in Swift. Extensible, protocol-based design that supports DQL, DML, and DDL. .package(url: "https://github.com/vapor/sql.git", .branch("sql")), ], targets: [ - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"]), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite", "SQLBenchmark"]), ] ) diff --git a/Sources/SQLite/Database/SQLiteConnection.swift b/Sources/SQLite/Database/SQLiteConnection.swift index 5b968f0..c50022b 100644 --- a/Sources/SQLite/Database/SQLiteConnection.swift +++ b/Sources/SQLite/Database/SQLiteConnection.swift @@ -18,7 +18,7 @@ import SQLite3 /// .column(function: "sqlite_version", as: "version") /// .run().wait() /// -public final class SQLiteConnection: BasicWorker, DatabaseConnection, SQLConnection { +public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQueryable { /// See `DatabaseConnection`. public typealias Database = SQLiteDatabase diff --git a/Sources/SQLite/Row/SQLiteDataType.swift b/Sources/SQLite/Row/SQLiteDataType.swift index 1f92d12..2d18ebf 100644 --- a/Sources/SQLite/Row/SQLiteDataType.swift +++ b/Sources/SQLite/Row/SQLiteDataType.swift @@ -1,4 +1,12 @@ public enum SQLiteDataType: SQLDataType { + /// See `SQLDataType`. + public static func dataType(appropriateFor type: Any.Type) -> SQLiteDataType? { + if let type = type as? SQLiteDataTypeStaticRepresentable.Type { + return type.sqliteDataType + } + return nil + } + case integer case real case text diff --git a/Sources/SQLite/Query/SQLiteAlterTable.swift b/Sources/SQLite/SQL/SQLiteAlterTable.swift similarity index 75% rename from Sources/SQLite/Query/SQLiteAlterTable.swift rename to Sources/SQLite/SQL/SQLiteAlterTable.swift index cf9ddfa..2947f2f 100644 --- a/Sources/SQLite/Query/SQLiteAlterTable.swift +++ b/Sources/SQLite/SQL/SQLiteAlterTable.swift @@ -1,5 +1,8 @@ /// Represents an `ALTER TABLE ...` query. public struct SQLiteAlterTable: SQLAlterTable { + /// See `SQLAlterTable`. + public typealias ColumnDefinition = SQLiteColumnDefinition + /// See `SQLAlterTable`. public typealias TableIdentifier = SQLiteTableIdentifier @@ -36,6 +39,25 @@ public struct SQLiteAlterTable: SQLAlterTable { /// Type of `ALTER` to perform. public var value: Value + + /// See `SQLAlterTable`. + public var columns: [SQLiteColumnDefinition] { + get { + switch value { + case .addColumn(let col): return [col] + default: return [] + } + } + set { + switch newValue.count { + case 1: value = .addColumn(newValue[0]) + default: + assertionFailure("SQLite only supports adding one column during ALTER TABLE query.") + break + } + } + } + /// Creates a new `AlterTable`. /// diff --git a/Sources/SQLite/Query/SQLiteAlterTableBuilder.swift b/Sources/SQLite/SQL/SQLiteAlterTableBuilder.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteAlterTableBuilder.swift rename to Sources/SQLite/SQL/SQLiteAlterTableBuilder.swift diff --git a/Sources/SQLite/Query/SQLiteBind.swift b/Sources/SQLite/SQL/SQLiteBind.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteBind.swift rename to Sources/SQLite/SQL/SQLiteBind.swift diff --git a/Sources/SQLite/Query/SQLiteCollation.swift b/Sources/SQLite/SQL/SQLiteCollation.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteCollation.swift rename to Sources/SQLite/SQL/SQLiteCollation.swift diff --git a/Sources/SQLite/Query/SQLiteCreateTable.swift b/Sources/SQLite/SQL/SQLiteCreateTable.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteCreateTable.swift rename to Sources/SQLite/SQL/SQLiteCreateTable.swift diff --git a/Sources/SQLite/SQL/SQLiteDataTypeStaticRepresentable.swift b/Sources/SQLite/SQL/SQLiteDataTypeStaticRepresentable.swift new file mode 100644 index 0000000..e7fa50c --- /dev/null +++ b/Sources/SQLite/SQL/SQLiteDataTypeStaticRepresentable.swift @@ -0,0 +1,63 @@ +/// A type that is capable of being represented by a `SQLiteFieldType`. +/// +/// Types conforming to this protocol can be automatically migrated by `FluentSQLite`. +/// +/// See `SQLiteType` for more information. +public protocol SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + static var sqliteDataType: SQLiteDataType { get } +} + +extension FixedWidthInteger { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return .integer } +} + +extension UInt: SQLiteDataTypeStaticRepresentable { } +extension UInt8: SQLiteDataTypeStaticRepresentable { } +extension UInt16: SQLiteDataTypeStaticRepresentable { } +extension UInt32: SQLiteDataTypeStaticRepresentable { } +extension UInt64: SQLiteDataTypeStaticRepresentable { } +extension Int: SQLiteDataTypeStaticRepresentable { } +extension Int8: SQLiteDataTypeStaticRepresentable { } +extension Int16: SQLiteDataTypeStaticRepresentable { } +extension Int32: SQLiteDataTypeStaticRepresentable { } +extension Int64: SQLiteDataTypeStaticRepresentable { } + +extension Date: SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return Double.sqliteDataType } +} + +extension BinaryFloatingPoint { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return .real } +} + +extension Float: SQLiteDataTypeStaticRepresentable { } +extension Double: SQLiteDataTypeStaticRepresentable { } + +extension Bool: SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return Int.sqliteDataType } +} + +extension UUID: SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return .blob } +} + +extension Data: SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return .blob } +} + +extension String: SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return .text } +} + +extension URL: SQLiteDataTypeStaticRepresentable { + /// See `SQLiteDataTypeStaticRepresentable`. + public static var sqliteDataType: SQLiteDataType { return String.sqliteDataType } +} diff --git a/Sources/SQLite/Query/SQLiteFunction.swift b/Sources/SQLite/SQL/SQLiteFunction.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteFunction.swift rename to Sources/SQLite/SQL/SQLiteFunction.swift diff --git a/Sources/SQLite/Query/SQLiteGeneric.swift b/Sources/SQLite/SQL/SQLiteGeneric.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteGeneric.swift rename to Sources/SQLite/SQL/SQLiteGeneric.swift diff --git a/Sources/SQLite/Query/SQLitePrimaryKey.swift b/Sources/SQLite/SQL/SQLitePrimaryKey.swift similarity index 100% rename from Sources/SQLite/Query/SQLitePrimaryKey.swift rename to Sources/SQLite/SQL/SQLitePrimaryKey.swift diff --git a/Sources/SQLite/Query/SQLiteQuery.swift b/Sources/SQLite/SQL/SQLiteQuery.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteQuery.swift rename to Sources/SQLite/SQL/SQLiteQuery.swift diff --git a/Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift b/Sources/SQLite/SQL/SQLiteQueryExpressionRepresentable.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteQueryExpressionRepresentable.swift rename to Sources/SQLite/SQL/SQLiteQueryExpressionRepresentable.swift diff --git a/Sources/SQLite/Query/SQLiteTable.swift b/Sources/SQLite/SQL/SQLiteTable.swift similarity index 100% rename from Sources/SQLite/Query/SQLiteTable.swift rename to Sources/SQLite/SQL/SQLiteTable.swift diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 0598887..5b89351 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -1,14 +1,14 @@ -import SQL import SQLite +import SQLBenchmark import XCTest - - -struct Galaxy2: SQLiteTable { - -} - class SQLiteTests: XCTestCase { + func testBenchmark() throws { + let conn = try SQLiteConnection.makeTest() + let benchmark = SQLBenchmark(on: conn) + try benchmark.run() + } + func testVersion() throws { let conn = try SQLiteConnection.makeTest() @@ -20,13 +20,11 @@ class SQLiteTests: XCTestCase { let conn = try SQLiteConnection.makeTest() let res = try conn.select() - .column(function: "sqlite_version", as: "version") + .column(.function("sqlite_version", [], as: .identifier("version"))) .all().wait() print(res) } - - func testTables() throws { let database = try SQLiteConnection.makeTest() _ = try database.query("DROP TABLE IF EXISTS foo").wait() @@ -161,9 +159,9 @@ class SQLiteTests: XCTestCase { } static let allTests = [ + ("testBenchmark", testBenchmark), ("testVersion", testVersion), ("testVersionBuild", testVersionBuild), - ("testSQL", testSQL), ("testTables", testTables), ("testUnicode", testUnicode), ("testBigInts", testBigInts), From b11a3f6112ac5e69ed7b69ab4db772d7819479c4 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Mon, 18 Jun 2018 18:59:29 -0400 Subject: [PATCH 17/21] rename to benchmarker --- Tests/SQLiteTests/SQLiteTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/SQLiteTests.swift b/Tests/SQLiteTests/SQLiteTests.swift index 5b89351..0f2a523 100644 --- a/Tests/SQLiteTests/SQLiteTests.swift +++ b/Tests/SQLiteTests/SQLiteTests.swift @@ -5,8 +5,8 @@ import XCTest class SQLiteTests: XCTestCase { func testBenchmark() throws { let conn = try SQLiteConnection.makeTest() - let benchmark = SQLBenchmark(on: conn) - try benchmark.run() + let benchmarker = SQLBenchmarker(on: conn) + try benchmarker.run() } func testVersion() throws { From 2b4799f5bdf54fdc5d4702374dd7ff56b91688d5 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Mon, 18 Jun 2018 22:09:27 -0400 Subject: [PATCH 18/21] SQLPrimaryKeyDefault + SQLDefaultLiteral updates --- Sources/SQLite/Codable/SQLiteRowDecoder.swift | 2 +- .../SQLite/Database/SQLiteConnection.swift | 7 ++++- Sources/SQLite/SQL/SQLiteDefaultLiteral.swift | 11 ++++++++ Sources/SQLite/SQL/SQLiteGeneric.swift | 6 ++--- Sources/SQLite/SQL/SQLitePrimaryKey.swift | 27 +++++++------------ 5 files changed, 30 insertions(+), 23 deletions(-) create mode 100644 Sources/SQLite/SQL/SQLiteDefaultLiteral.swift diff --git a/Sources/SQLite/Codable/SQLiteRowDecoder.swift b/Sources/SQLite/Codable/SQLiteRowDecoder.swift index ca561e3..19ef33b 100644 --- a/Sources/SQLite/Codable/SQLiteRowDecoder.swift +++ b/Sources/SQLite/Codable/SQLiteRowDecoder.swift @@ -9,7 +9,7 @@ /// /// Uses `SQLiteDataDecoder` internally to decode each column. Use `SQLiteDataConvertible` to /// customize how your types are decoded. -public struct SQLiteRowDecoder: SQLRowDecoder { +public struct SQLiteRowDecoder { /// Creates a new `SQLiteRowDecoder`. public init() { } diff --git a/Sources/SQLite/Database/SQLiteConnection.swift b/Sources/SQLite/Database/SQLiteConnection.swift index c50022b..cc40dec 100644 --- a/Sources/SQLite/Database/SQLiteConnection.swift +++ b/Sources/SQLite/Database/SQLiteConnection.swift @@ -18,7 +18,7 @@ import SQLite3 /// .column(function: "sqlite_version", as: "version") /// .run().wait() /// -public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQueryable { +public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQueryable, SQLConnection { /// See `DatabaseConnection`. public typealias Database = SQLiteDatabase @@ -60,6 +60,11 @@ public final class SQLiteConnection: BasicWorker, DatabaseConnection, DatabaseQu return String(cString: raw) } + /// See `SQLConnection`. + public func decode(_ type: D.Type, from row: [SQLiteColumn : SQLiteData], table: GenericSQLTableIdentifier?) throws -> D where D : Decodable { + return try SQLiteRowDecoder().decode(D.self, from: row, table: table) + } + /// Executes the supplied `SQLiteQuery` on the connection, calling the supplied closure for each row returned. /// /// try conn.query("SELECT * FROM users") { row in diff --git a/Sources/SQLite/SQL/SQLiteDefaultLiteral.swift b/Sources/SQLite/SQL/SQLiteDefaultLiteral.swift new file mode 100644 index 0000000..179e0ea --- /dev/null +++ b/Sources/SQLite/SQL/SQLiteDefaultLiteral.swift @@ -0,0 +1,11 @@ +public struct SQLiteDefaultLiteral: SQLDefaultLiteral { + /// See `SQLDefaultLiteral`. + public static func `default`() -> SQLiteDefaultLiteral { + return self.init() + } + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + return "NULL" + } +} diff --git a/Sources/SQLite/SQL/SQLiteGeneric.swift b/Sources/SQLite/SQL/SQLiteGeneric.swift index 6207199..9428a35 100644 --- a/Sources/SQLite/SQL/SQLiteGeneric.swift +++ b/Sources/SQLite/SQL/SQLiteGeneric.swift @@ -3,7 +3,7 @@ public typealias SQLiteBinaryOperator = GenericSQLBinaryOperator /// See `SQLQuery`. public typealias SQLiteColumnConstraintAlgorithm = GenericSQLColumnConstraintAlgorithm< - SQLiteExpression, SQLiteCollation, SQLitePrimaryKey, SQLiteForeignKey + SQLiteExpression, SQLiteCollation, SQLitePrimaryKeyDefault, SQLiteForeignKey > /// See `SQLQuery`. @@ -68,7 +68,7 @@ public typealias SQLiteJoin = GenericSQLJoin< public typealias SQLiteJoinMethod = GenericSQLJoinMethod /// See `SQLQuery`. -public typealias SQLiteLiteral = GenericSQLLiteral +public typealias SQLiteLiteral = GenericSQLLiteral /// See `SQLQuery`. public typealias SQLiteOrderBy = GenericSQLOrderBy @@ -83,7 +83,7 @@ public typealias SQLiteSelectExpression = GenericSQLSelectExpression /// See `SQLQuery`. diff --git a/Sources/SQLite/SQL/SQLitePrimaryKey.swift b/Sources/SQLite/SQL/SQLitePrimaryKey.swift index 00cdf48..0a74de0 100644 --- a/Sources/SQLite/SQL/SQLitePrimaryKey.swift +++ b/Sources/SQLite/SQL/SQLitePrimaryKey.swift @@ -1,13 +1,11 @@ -public struct SQLitePrimaryKey: SQLPrimaryKey { +public enum SQLitePrimaryKeyDefault: SQLPrimaryKeyDefault { /// See `SQLPrimaryKey`. - public static func primaryKey() -> SQLitePrimaryKey { - return .init(autoIncrement: false) + public static var `default`: SQLitePrimaryKeyDefault { + return .autoIncrement } - /// See `SQLPrimaryKey`. - public static func primaryKey(autoIncrement: Bool) -> SQLitePrimaryKey { - return .init(autoIncrement: autoIncrement) - } + /// Default. Uses ROWID as default primary key. + case rowID /// The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. /// It is usually not needed. @@ -16,20 +14,13 @@ public struct SQLitePrimaryKey: SQLPrimaryKey { /// signed integer. /// /// https://www.sqlite.org/autoinc.html - public var autoIncrement: Bool + case autoIncrement /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { - if autoIncrement { - return "AUTOINCREMENT" - } else { - return "" + switch self { + case .rowID: return "" + case .autoIncrement: return "AUTOINCREMENT" } } } - -extension SQLColumnConstraint where ColumnConstraintAlgorithm.PrimaryKey == SQLitePrimaryKey { - public static func primaryKey(autoIncrement: Bool) -> Self { - return .constraint(.primaryKey(.primaryKey(autoIncrement: autoIncrement)), nil) - } -} From 0cbb55db37ed6001c8a1b632ef5507dc26b40f2f Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 19 Jun 2018 11:05:45 -0400 Subject: [PATCH 19/21] add SQLiteBoolLiteral --- Sources/SQLite/SQL/SQLiteBoolLiteral.swift | 22 ++++++++++++++++++++++ Sources/SQLite/SQL/SQLiteGeneric.swift | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Sources/SQLite/SQL/SQLiteBoolLiteral.swift diff --git a/Sources/SQLite/SQL/SQLiteBoolLiteral.swift b/Sources/SQLite/SQL/SQLiteBoolLiteral.swift new file mode 100644 index 0000000..ed409d5 --- /dev/null +++ b/Sources/SQLite/SQL/SQLiteBoolLiteral.swift @@ -0,0 +1,22 @@ +public enum SQLiteBoolLiteral: SQLBoolLiteral { + /// See `SQLBoolLiteral`. + public static var `true`: SQLiteBoolLiteral { + return ._false + } + + /// See `SQLBoolLiteral`. + public static var `false`: SQLiteBoolLiteral { + return ._true + } + + case _true + case _false + + /// See `SQLSerializable`. + public func serialize(_ binds: inout [Encodable]) -> String { + switch self { + case ._false: return "FALSE" + case ._true: return "TRUE" + } + } +} diff --git a/Sources/SQLite/SQL/SQLiteGeneric.swift b/Sources/SQLite/SQL/SQLiteGeneric.swift index 9428a35..03bbcda 100644 --- a/Sources/SQLite/SQL/SQLiteGeneric.swift +++ b/Sources/SQLite/SQL/SQLiteGeneric.swift @@ -68,7 +68,7 @@ public typealias SQLiteJoin = GenericSQLJoin< public typealias SQLiteJoinMethod = GenericSQLJoinMethod /// See `SQLQuery`. -public typealias SQLiteLiteral = GenericSQLLiteral +public typealias SQLiteLiteral = GenericSQLLiteral /// See `SQLQuery`. public typealias SQLiteOrderBy = GenericSQLOrderBy From 530728d88c7f28d8e46585234b70e391f6892116 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 19 Jun 2018 11:09:07 -0400 Subject: [PATCH 20/21] fix bool literal --- Sources/SQLite/SQL/SQLiteBoolLiteral.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/SQL/SQLiteBoolLiteral.swift b/Sources/SQLite/SQL/SQLiteBoolLiteral.swift index ed409d5..a7954e2 100644 --- a/Sources/SQLite/SQL/SQLiteBoolLiteral.swift +++ b/Sources/SQLite/SQL/SQLiteBoolLiteral.swift @@ -1,12 +1,12 @@ public enum SQLiteBoolLiteral: SQLBoolLiteral { /// See `SQLBoolLiteral`. public static var `true`: SQLiteBoolLiteral { - return ._false + return ._true } /// See `SQLBoolLiteral`. public static var `false`: SQLiteBoolLiteral { - return ._true + return ._false } case _true @@ -15,8 +15,8 @@ public enum SQLiteBoolLiteral: SQLBoolLiteral { /// See `SQLSerializable`. public func serialize(_ binds: inout [Encodable]) -> String { switch self { - case ._false: return "FALSE" - case ._true: return "TRUE" + case ._false: return "0" + case ._true: return "1" } } } From b3c47b8d8103c96ef8b84e47bc769bbeec16d73a Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 19 Jun 2018 18:42:22 -0400 Subject: [PATCH 21/21] change deps to tags --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 55d4d63..1399661 100644 --- a/Package.swift +++ b/Package.swift @@ -11,10 +11,10 @@ let package = Package( .package(url: "https://github.com/vapor/core.git", from: "3.0.0"), // 🗄 Core services for creating database integrations. - .package(url: "https://github.com/vapor/database-kit.git", .branch("sql")), + .package(url: "https://github.com/vapor/database-kit.git", from: "1.2.0"), // *️⃣ Build SQL queries in Swift. Extensible, protocol-based design that supports DQL, DML, and DDL. - .package(url: "https://github.com/vapor/sql.git", .branch("sql")), + .package(url: "https://github.com/vapor/sql.git", from: "2.0.0-beta"), ], targets: [ .testTarget(name: "SQLiteTests", dependencies: ["SQLite", "SQLBenchmark"]),