diff --git a/Package.swift b/Package.swift index b5acbff..87c017d 100644 --- a/Package.swift +++ b/Package.swift @@ -20,7 +20,7 @@ let package = Package( .package(url: "https://github.com/vapor/sqlite.git", from: "3.0.0-rc"), ], targets: [ - .target(name: "FluentSQLite", dependencies: ["Async", "Fluent", "Service", "SQLite"]), + .target(name: "FluentSQLite", dependencies: ["Async", "FluentSQL", "Service", "SQLite"]), .testTarget(name: "FluentSQLiteTests", dependencies: ["FluentBenchmark", "FluentSQLite", "SQLite"]), ] ) diff --git a/Sources/FluentSQLite/Exports.swift b/Sources/FluentSQLite/Exports.swift index 883e343..314ccff 100644 --- a/Sources/FluentSQLite/Exports.swift +++ b/Sources/FluentSQLite/Exports.swift @@ -1,149 +1,2 @@ -@_exported import Fluent +@_exported import FluentSQL @_exported import SQLite - - -//extension SQLiteDatabase: QuerySupporting { -// /// See `SQLDatabase`. -// public typealias QueryJoin = SQLQuery.DML.Join -// -// /// See `SQLDatabase`. -// public typealias QueryJoinMethod = SQLQuery.DML.Join.Method -// -// /// See `SQLDatabase`. -// public typealias Query = SQLQuery.DML -// -// /// See `SQLDatabase`. -// public typealias Output = [SQLiteColumn: SQLiteData] -// -// /// See `SQLDatabase`. -// public typealias QueryAction = SQLQuery.DML.Statement -// -// /// See `SQLDatabase`. -// public typealias QueryAggregate = String -// -// /// See `SQLDatabase`. -// public typealias QueryData = [SQLQuery.DML.Column: SQLQuery.DML.Value] -// -// /// See `SQLDatabase`. -// public typealias QueryField = SQLQuery.DML.Column -// -// /// See `SQLDatabase`. -// public typealias QueryFilterMethod = SQLQuery.DML.Predicate.Comparison -// -// /// See `SQLDatabase`. -// public typealias QueryFilterValue = SQLQuery.DML.Value -// -// /// See `SQLDatabase`. -// public typealias QueryFilter = SQLQuery.DML.Predicate -// -// /// See `SQLDatabase`. -// public typealias QueryFilterRelation = SQLQuery.DML.Predicate.Relation -// -// /// See `SQLDatabase`. -// public typealias QueryKey = SQLQuery.DML.Key -// -// /// See `SQLDatabase`. -// public typealias QuerySort = SQLQuery.DML.OrderBy -// -// /// See `SQLDatabase`. -// public typealias QuerySortDirection = SQLQuery.DML.OrderBy.Direction -// -// /// See `SQLDatabase`. -// public static func queryExecute(_ dml: SQLQuery.DML, on conn: SQLiteConnection, into handler: @escaping ([SQLiteColumn: SQLiteData], SQLiteConnection) throws -> ()) -> Future { -// // always cache the names first -// return conn.query(.init(.dml(dml))) { row in -// try handler(row, conn) -// } -// } -// -// /// See `SQLDatabase`. -// public static func queryDecode(_ data: [SQLiteColumn: SQLiteData], entity: String, as decodable: D.Type, on conn: SQLiteConnection) -> Future -// where D: Decodable -// { -// do { -// let decoded = try SQLiteRowDecoder().decode(D.self, from: data, table: entity) -// return conn.future(decoded) -// } catch { -// return conn.future(error: error) -// } -// } -// -// /// See `SQLDatabase`. -// public static func schemaColumnType(for type: Any.Type, primaryKey: Bool) -> SQLQuery.DDL.ColumnDefinition.ColumnType { -// var sqliteType: SQLiteFieldTypeStaticRepresentable.Type? -// -// if let optionalType = type as? AnyOptionalType.Type { -// sqliteType = optionalType.anyWrappedType as? SQLiteFieldTypeStaticRepresentable.Type -// } else { -// sqliteType = type as? SQLiteFieldTypeStaticRepresentable.Type -// } -// -// if let type = sqliteType { -// var name: String -// var attributes: [String] = [] -// -// switch type.sqliteFieldType { -// case .blob: name = "BLOB" -// case .integer: name = "INTEGER" -// case .null: name = "NULL" -// case .real: name = "REAL" -// case .text: name = "TEXT" -// } -// -// if primaryKey { -// attributes.append("PRIMARY KEY") -// } -// -// return .init(name: name, attributes: attributes) -// } else { -// fatalError("Unsupported SQLite type: \(type).") -// } -// } -// -// /// See `SQLDatabase`. -// public static func schemaExecute(_ ddl: SQLQuery.DDL, on conn: SQLiteConnection) -> EventLoopFuture { -// // always cache the names first -// return conn.query(.init(.ddl(ddl))).transform(to: ()) -// } -// -// /// See `SQLSupporting`. -// public static func enableForeignKeys(on conn: SQLiteConnection) -> Future { -// return conn.query("PRAGMA foreign_keys = ON;").transform(to: ()) -// } -// -// /// See `SQLSupporting`. -// public static func disableForeignKeys(on conn: SQLiteConnection) -> Future { -// return conn.query("PRAGMA foreign_keys = OFF;").transform(to: ()) -// } -// -// /// See `SQLSupporting`. -// public static func modelEvent(event: ModelEvent, model: M, on conn: SQLiteConnection) -> Future where SQLiteDatabase == M.Database, M: Model { -// var copy = model -// switch event { -// case .willCreate: -// if M.ID.self is UUID.Type { -// copy.fluentID = UUID() as? M.ID -// } -// case .didCreate: -// if M.ID.self is Int.Type { -// copy.fluentID = conn.lastAutoincrementID as? M.ID -// } -// default: break -// } -// return conn.future(copy) -// } -// -// /// See `SQLSupporting`. -// public static func transactionExecute(_ transaction: @escaping (SQLiteConnection) throws -> Future, on conn: SQLiteConnection) -> Future { -// return conn.query("BEGIN TRANSACTION").flatMap { _ -> Future in -// return try transaction(conn).flatMap { res -> Future in -// return conn.query("COMMIT TRANSACTION").transform(to: res) -// }.catchFlatMap { err -> Future in -// return conn.query("ROLLBACK TRANSACTION").map { query -> T in -// // still fail even tho rollback succeeded -// throw err -// } -// } -// } -// } -//} diff --git a/Sources/FluentSQLite/FluentSQLiteQuery.swift b/Sources/FluentSQLite/FluentSQLiteQuery.swift new file mode 100644 index 0000000..0f26c28 --- /dev/null +++ b/Sources/FluentSQLite/FluentSQLiteQuery.swift @@ -0,0 +1,57 @@ +public enum FluentSQLiteQueryStatement: FluentSQLQueryStatement { + public static var insert: FluentSQLiteQueryStatement { return ._insert } + public static var select: FluentSQLiteQueryStatement { return ._select } + public static var update: FluentSQLiteQueryStatement { return ._update } + public static var delete: FluentSQLiteQueryStatement { return ._delete } + + public var isInsert: Bool { + switch self { + case ._insert: return true + default: return false + } + } + + case _insert + case _select + case _update + case _delete +} + +public struct FluentSQLiteQuery: FluentSQLQuery { + public typealias Statement = FluentSQLiteQueryStatement + public typealias TableIdentifier = SQLiteTableIdentifier + public typealias Expression = SQLiteExpression + public typealias SelectExpression = SQLiteSelectExpression + public typealias Join = SQLiteJoin + public typealias OrderBy = SQLiteOrderBy + public typealias GroupBy = SQLiteGroupBy + public typealias RowDecoder = SQLiteRowDecoder + + public var statement: Statement + public var table: TableIdentifier + public var keys: [SelectExpression] + public var values: [String : Expression] + public var joins: [Join] + public var predicate: Expression? + public var orderBy: [OrderBy] + public var groupBy: [GroupBy] + public var limit: Int? + public var offset: Int? + public var defaultBinaryOperator: GenericSQLBinaryOperator + + public static func query(_ statement: Statement, _ table: TableIdentifier) -> FluentSQLiteQuery { + return .init( + statement: statement, + table: table, + keys: [], + values: [:], + joins: [], + predicate: nil, + orderBy: [], + groupBy: [], + limit: nil, + offset: nil, + defaultBinaryOperator: .and + ) + } +} diff --git a/Sources/FluentSQLite/FluentSQLiteSchema.swift b/Sources/FluentSQLite/FluentSQLiteSchema.swift new file mode 100644 index 0000000..e82e047 --- /dev/null +++ b/Sources/FluentSQLite/FluentSQLiteSchema.swift @@ -0,0 +1,34 @@ +public enum FluentSQLiteSchemaStatement: FluentSQLSchemaStatement { + public static var createTable: FluentSQLiteSchemaStatement { return ._createTable } + public static var alterTable: FluentSQLiteSchemaStatement { return ._alterTable } + public static var dropTable: FluentSQLiteSchemaStatement { return ._dropTable } + + case _createTable + case _alterTable + case _dropTable +} + +public struct FluentSQLiteSchema: FluentSQLSchema { + public typealias Statement = FluentSQLiteSchemaStatement + public typealias TableIdentifier = SQLiteTableIdentifier + public typealias ColumnDefinition = SQLiteColumnDefinition + public typealias TableConstraint = SQLiteTableConstraint + + public var statement: Statement + public var table: TableIdentifier + public var columns: [SQLiteColumnDefinition] + public var deleteColumns: [SQLiteColumnIdentifier] + public var constraints: [SQLiteTableConstraint] + public var deleteConstraints: [SQLiteTableConstraint] + + public static func schema(_ statement: Statement, _ table: TableIdentifier) -> FluentSQLiteSchema { + return .init( + statement: statement, + table: table, + columns: [], + deleteColumns: [], + constraints: [], + deleteConstraints: [] + ) + } +} diff --git a/Sources/FluentSQLite/SQLiteDatabase+Contains.swift b/Sources/FluentSQLite/SQLiteDatabase+Contains.swift deleted file mode 100644 index f025582..0000000 --- a/Sources/FluentSQLite/SQLiteDatabase+Contains.swift +++ /dev/null @@ -1,29 +0,0 @@ -infix operator ~= -/// Has prefix -public func ~= (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .compare(.like), ["%" + rhs]) -} -/// Has prefix -public func ~= (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .compare(.like), ["%" + rhs]) -} - -infix operator =~ -/// Has suffix. -public func =~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .compare(.like), [rhs + "%"]) -} -/// Has suffix. -public func =~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .compare(.like), [rhs + "%"]) -} - -infix operator ~~ -/// Contains. -public func ~~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .compare(.like), ["%" + rhs + "%"]) -} -/// Contains. -public func ~~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .compare(.like), ["%" + rhs + "%"]) -} diff --git a/Sources/FluentSQLite/SQLiteDatabase+JoinSupporting.swift b/Sources/FluentSQLite/SQLiteDatabase+JoinSupporting.swift index 964fb62..915d506 100644 --- a/Sources/FluentSQLite/SQLiteDatabase+JoinSupporting.swift +++ b/Sources/FluentSQLite/SQLiteDatabase+JoinSupporting.swift @@ -1,27 +1,7 @@ extension SQLiteDatabase: JoinSupporting { /// See `JoinSupporting`. - public typealias QueryJoin = SQLiteQuery.JoinClause.Join + public typealias QueryJoin = SQLiteJoin /// See `JoinSupporting`. - public typealias QueryJoinMethod = SQLiteQuery.JoinClause.Join.Operator - - /// See `JoinSupporting`. - public static var queryJoinMethodDefault: SQLiteQuery.JoinClause.Join.Operator { - return .inner - } - - /// See `JoinSupporting`. - public static func queryJoin(_ method: SQLiteQuery.JoinClause.Join.Operator, base: SQLiteQuery.QualifiedColumnName, joined: SQLiteQuery.QualifiedColumnName) -> SQLiteQuery.JoinClause.Join { - return .init( - natural: false, - method, - table: .table(.init(table: .init(table: .init(name: joined.table!)))), - constraint: .condition(.binary(.column(base), .equal, .column(joined))) - ) - } - - /// See `JoinSupporting`. - public static func queryJoinApply(_ join: SQLiteQuery.JoinClause.Join, to query: inout SQLiteQuery.FluentQuery) { - query.joins.append(join) - } + public typealias QueryJoinMethod = SQLiteJoinMethod } diff --git a/Sources/FluentSQLite/SQLiteDatabase+QuerySupporting.swift b/Sources/FluentSQLite/SQLiteDatabase+QuerySupporting.swift index 6641532..1d2301d 100644 --- a/Sources/FluentSQLite/SQLiteDatabase+QuerySupporting.swift +++ b/Sources/FluentSQLite/SQLiteDatabase+QuerySupporting.swift @@ -1,163 +1,94 @@ -extension SQLiteQuery { - public struct FluentQuery { - public enum Statement { - case insert - case select - case update - case delete - } - - public enum Comparison { - case binary(Expression.BinaryOperator) - case subset(Expression.SubsetOperator) - case compare(Expression.Compare.Operator) - } - - public var statement: Statement - public var table: TableName - public var joins: [JoinClause.Join] - public var keys: [SQLiteQuery.Select.ResultColumn] - public var values: [String: SQLiteQuery.Expression] - public var predicate: Expression? - public var orderBy: [SQLiteQuery.OrderBy] - public var limit: Int? - public var offset: Int? - public var defaultRelation: Expression.BinaryOperator - - public init(_ statement: Statement, table: TableName) { - self.statement = statement - self.table = table - self.joins = [] - self.keys = [] - self.values = [:] - self.predicate = nil - self.orderBy = [] - self.limit = nil - self.offset = nil - defaultRelation = .and - } - } -} - extension SQLiteDatabase: QuerySupporting { /// See `QuerySupporting`. - public typealias Query = SQLiteQuery.FluentQuery + public typealias Query = FluentSQLiteQuery /// See `QuerySupporting`. public typealias Output = [SQLiteColumn: SQLiteData] /// See `QuerySupporting`. - public typealias QueryAction = SQLiteQuery.FluentQuery.Statement + public typealias QueryAction = FluentSQLiteQueryStatement /// See `QuerySupporting`. public typealias QueryAggregate = String /// See `QuerySupporting`. - public typealias QueryData = [String: SQLiteQuery.Expression] + public typealias QueryData = [String: SQLiteExpression] /// See `QuerySupporting`. - public typealias QueryField = SQLiteQuery.QualifiedColumnName + public typealias QueryField = SQLiteColumnIdentifier /// See `QuerySupporting`. - public typealias QueryFilterMethod = SQLiteQuery.FluentQuery.Comparison + public typealias QueryFilterMethod = SQLiteBinaryOperator /// See `QuerySupporting`. - public typealias QueryFilterValue = SQLiteQuery.Expression + public typealias QueryFilterValue = SQLiteExpression /// See `QuerySupporting`. - public typealias QueryFilter = SQLiteQuery.Expression + public typealias QueryFilter = SQLiteExpression /// See `QuerySupporting`. - public typealias QueryFilterRelation = SQLiteQuery.Expression.BinaryOperator + public typealias QueryFilterRelation = SQLiteBinaryOperator /// See `QuerySupporting`. - public typealias QueryKey = SQLiteQuery.Select.ResultColumn + public typealias QueryKey = SQLiteSelectExpression /// See `QuerySupporting`. - public typealias QuerySort = SQLiteQuery.OrderBy + public typealias QuerySort = SQLiteOrderBy /// See `QuerySupporting`. - public typealias QuerySortDirection = SQLiteQuery.Direction - - public static func query(_ entity: String) -> SQLiteQuery.FluentQuery { - return .init(.select, table: .init(name: entity)) - } - - public static func queryEntity(for query: SQLiteQuery.FluentQuery) -> String { - return query.table.name - } + public typealias QuerySortDirection = SQLiteDirection - public static func queryExecute(_ fluent: SQLiteQuery.FluentQuery, on conn: SQLiteConnection, into handler: @escaping ([SQLiteColumn : SQLiteData], SQLiteConnection) throws -> ()) -> Future { + /// See `QuerySupporting`. + public static func queryExecute(_ fluent: Query, on conn: SQLiteConnection, into handler: @escaping ([SQLiteColumn : SQLiteData], SQLiteConnection) throws -> ()) -> Future { let query: SQLiteQuery switch fluent.statement { - case .insert: - // filter out all `NULL` values, no need to insert them since - // they could override default values that we want to keep - let values = fluent.values.filter { (key, val) in - switch val { - case .literal(let literal) where literal == .null: return false - default: return true + case ._insert: + var insert: SQLiteInsert = .insert(fluent.table) + var values: [SQLiteExpression] = [] + fluent.values.forEach { row in + // filter out all `NULL` values, no need to insert them since + // they could override default values that we want to keep + switch row.value { + case ._literal(let literal): + switch literal { + case ._null: return + default: break + } + default: break } + insert.columns.append(.column(nil, .identifier(row.key))) + values.append(row.value) } - query = .insert(.init( - with: nil, - conflictResolution: nil, - table: .init(table: fluent.table, alias: nil), - columns: values.keys.map { .init($0) }, - values: .values([.init(values.values)]), - upsert: nil - )) - case .select: - var table: SQLiteQuery.TableOrSubquery - switch fluent.joins.count { - case 0: table = .table(.init(table: .init(table: fluent.table, alias: nil), indexing: nil)) - default: - table = .joinClause(.init( - table: .table(.init(table: .init(table: fluent.table, alias: nil), indexing: nil)), - joins: fluent.joins - )) + insert.values.append(values) + query = .insert(insert) + case ._select: + var select: SQLiteSelect = .select() + select.columns = fluent.keys.isEmpty ? [.all] : fluent.keys + select.tables = [fluent.table] + select.joins = fluent.joins + select.predicate = fluent.predicate + select.orderBy = fluent.orderBy + select.groupBy = fluent.groupBy + select.limit = fluent.limit + select.offset = fluent.offset + query = .select(select) + case ._update: + var update: SQLiteUpdate = .update(fluent.table) + update.table = fluent.table + update.values = fluent.values.map { val in + return (.identifier(val.key), val.value) } - - query = .select(.init( - with: nil, - distinct: nil, - columns: fluent.keys.isEmpty ? [.all(nil)] : fluent.keys, - tables: [table], - predicate: fluent.predicate, - orderBy: fluent.orderBy - )) - case .update: - query = .update(.init( - with: nil, - conflictResolution: nil, - table: .init(table: .init(table: fluent.table, alias: nil), indexing: nil), - values: .init(columns: fluent.values.map { (col, expr) in - return .init(columns: [.init(col)], value: expr) - }, predicate: nil), - predicate: fluent.predicate - )) - case .delete: - query = .delete(.init( - with: nil, - table: .init(table: .init(table: fluent.table, alias: nil), indexing: nil), - predicate: fluent.predicate - )) + update.predicate = fluent.predicate + query = .update(update) + case ._delete: + var delete: SQLiteDelete = .delete(fluent.table) + delete.predicate = fluent.predicate + query = .delete(delete) } return conn.query(query) { try handler($0, conn) } } - public static func queryDecode(_ output: [SQLiteColumn : SQLiteData], entity: String, as decodable: D.Type, on conn: SQLiteConnection) -> Future where D : Decodable { - do { - return try conn.future(output.decode(D.self, from: entity)) - } catch { - return conn.future(error: error) - } - } - - public static func queryEncode(_ encodable: E, entity: String) throws -> [String: SQLiteQuery.Expression] where E : Encodable { - return try SQLiteQueryEncoder().encode(encodable) - } - + /// See `QuerySupporting`. public static func modelEvent(event: ModelEvent, model: M, on conn: SQLiteConnection) -> EventLoopFuture where SQLiteDatabase == M.Database, M : Model { var copy = model switch event { @@ -166,225 +97,22 @@ extension SQLiteDatabase: QuerySupporting { copy.fluentID = UUID() as? M.ID } case .didCreate: - if M.ID.self is Int.Type { - copy.fluentID = conn.lastAutoincrementID as? M.ID + if let intType = M.ID.self as? Int64Initializable.Type, copy.fluentID == nil { + copy.fluentID = conn.lastAutoincrementID.flatMap { intType.init($0) as? M.ID } } default: break } return conn.future(copy) } - - public static var queryActionCreate: SQLiteQuery.FluentQuery.Statement { - return .insert - } - - public static var queryActionRead: SQLiteQuery.FluentQuery.Statement { - return .select - } - - public static var queryActionUpdate: SQLiteQuery.FluentQuery.Statement { - return .update - } - - public static var queryActionDelete: SQLiteQuery.FluentQuery.Statement { - return .delete - } - - public static func queryActionIsCreate(_ action: SQLiteQuery.FluentQuery.Statement) -> Bool { - switch action { - case .insert: return true - default: return false - } - } - - public static func queryActionApply(_ action: SQLiteQuery.FluentQuery.Statement, to query: inout SQLiteQuery.FluentQuery) { - query.statement = action - } - - public static var queryAggregateCount: String { - return "COUNT" - } - - public static var queryAggregateSum: String { - return "SUM" - } - - public static var queryAggregateAverage: String { - return "AVG" - } - - public static var queryAggregateMinimum: String { - return "MIN" - } - - public static var queryAggregateMaximum: String { - return "MAX" - } - - public static func queryDataSet(_ field: SQLiteQuery.QualifiedColumnName, to data: E, on query: inout SQLiteQuery.FluentQuery) - where E: Encodable - { - query.values[field.name.string] = try! .bind(data) - } - - public static func queryDataApply(_ data: [String: SQLiteQuery.Expression], to query: inout SQLiteQuery.FluentQuery) { - query.values = data - } - - public static func queryField(_ property: FluentProperty) -> SQLiteQuery.QualifiedColumnName { - return .init(schema: nil, table: property.entity, name: .init(property.path[0])) - } - - public static var queryFilterMethodEqual: SQLiteQuery.FluentQuery.Comparison { - return .binary(.equal) - } - - public static var queryFilterMethodNotEqual: SQLiteQuery.FluentQuery.Comparison { - return .binary(.notEqual) - } - - public static var queryFilterMethodGreaterThan: SQLiteQuery.FluentQuery.Comparison { - return .binary(.greaterThan) - } - - public static var queryFilterMethodLessThan: SQLiteQuery.FluentQuery.Comparison { - return .binary(.lessThan) - } - - public static var queryFilterMethodGreaterThanOrEqual: SQLiteQuery.FluentQuery.Comparison { - return .binary(.greaterThanOrEqual) - } - - public static var queryFilterMethodLessThanOrEqual: SQLiteQuery.FluentQuery.Comparison { - return .binary(.lessThanOrEqual) - } - - public static var queryFilterMethodInSubset: SQLiteQuery.FluentQuery.Comparison { - return .subset(.in) - } - - public static var queryFilterMethodNotInSubset: SQLiteQuery.FluentQuery.Comparison { - return .subset(.notIn) - } - - public static func queryFilterValue(_ encodables: [E]) -> SQLiteQuery.Expression - where E: Encodable - { - return try! .expressions(encodables.map { try .bind($0) }) - } - - public static var queryFilterValueNil: SQLiteQuery.Expression { - return .literal(.null) - } - - public static func queryFilter(_ field: SQLiteQuery.QualifiedColumnName, _ method: SQLiteQuery.FluentQuery.Comparison, _ value: SQLiteQuery.Expression) -> SQLiteQuery.Expression { - switch method { - case .binary(let binary): return .binary(.column(field), binary, value) - case .compare(let compare): return .compare(.init(.column(field), not: false, compare, value, escape: nil)) - case .subset(let subset): return .subset(.column(field), subset, .expressions([value])) - } - - } - - public static func queryFilters(for query: SQLiteQuery.FluentQuery) -> [SQLiteQuery.Expression] { - switch query.predicate { - case .none: return [] - case .some(let wrapped): return [wrapped] - } - } - - public static func queryFilterApply(_ filter: SQLiteQuery.Expression, to query: inout SQLiteQuery.FluentQuery) { - switch query.defaultRelation { - case .or: query.predicate |= filter - default: query.predicate &= filter - } - } - - public static var queryFilterRelationAnd: SQLiteQuery.Expression.BinaryOperator { - return .and - } - - public static var queryFilterRelationOr: SQLiteQuery.Expression.BinaryOperator { - return .or - } - - - public static func queryDefaultFilterRelation(_ relation: SQLiteQuery.Expression.BinaryOperator, on query: inout SQLiteQuery.FluentQuery) { - query.defaultRelation = relation - } - - public static func queryFilterGroup(_ relation: SQLiteQuery.Expression.BinaryOperator, _ filters: [SQLiteQuery.Expression]) -> SQLiteQuery.Expression { - var current: SQLiteQuery.Expression? - for next in filters { - switch relation { - case .or: current |= next - case .and: current &= next - default: break - } - } - if let predicate = current { - return .expressions([predicate]) - } else { - return .expressions([]) - } - } - - public static var queryKeyAll: SQLiteQuery.Select.ResultColumn { - return .all(nil) - } - - public static func queryAggregate(_ aggregate: String, _ fields: [SQLiteQuery.Select.ResultColumn]) -> SQLiteQuery.Select.ResultColumn { - let parameters: SQLiteQuery.Expression.Function.Parameters - switch fields.count { - case 1: - switch fields[0] { - case .all: parameters = .all - case .expression(let expr, _): parameters = .expressions(distinct: false, [expr]) - } - default: - parameters = .expressions(distinct: false, fields.compactMap { field in - switch field { - case .all: return nil - case .expression(let expr, _): return expr - } - }) - } - return .expression(.function(.init( - name: aggregate, - parameters: parameters - )), alias: "fluentAggregate") - } - - public static func queryKey(_ field: SQLiteQuery.QualifiedColumnName) -> SQLiteQuery.Select.ResultColumn { - return .expression(.column(field), alias: nil) - } - - public static func queryKeyApply(_ key: SQLiteQuery.Select.ResultColumn, to query: inout SQLiteQuery.FluentQuery) { - query.keys.append(key) - } - - public static func queryRangeApply(lower: Int, upper: Int?, to query: inout SQLiteQuery.FluentQuery) { - if let upper = upper { - query.limit = upper - lower - query.offset = lower - } else { - query.offset = lower - } - } - - public static func querySort(_ column: SQLiteQuery.QualifiedColumnName, _ direction: SQLiteQuery.Direction) -> SQLiteQuery.OrderBy { - return .init(expression: .column(column), direction: direction) - } - - public static var querySortDirectionAscending: SQLiteQuery.Direction { - return .ascending - } - - public static var querySortDirectionDescending: SQLiteQuery.Direction { - return .descending - } - - public static func querySortApply(_ orderBy: SQLiteQuery.OrderBy, to query: inout SQLiteQuery.FluentQuery) { - query.orderBy.append(orderBy) - } } + +internal protocol Int64Initializable { + init(_ int64: Int64) +} + +extension Int: Int64Initializable { } +extension UInt: Int64Initializable { } +extension Int64: Int64Initializable { } +extension UInt64: Int64Initializable { } +extension Int32: Int64Initializable { } +extension UInt32: Int64Initializable { } diff --git a/Sources/FluentSQLite/SQLiteDatabase+SchemaSupporting.swift b/Sources/FluentSQLite/SQLiteDatabase+SchemaSupporting.swift index 3727a4c..ad9a6ff 100644 --- a/Sources/FluentSQLite/SQLiteDatabase+SchemaSupporting.swift +++ b/Sources/FluentSQLite/SQLiteDatabase+SchemaSupporting.swift @@ -1,67 +1,33 @@ -extension SQLiteQuery { - public struct FluentSchema { - public enum Statement { - case create - case alter - case drop - } - - public var statement: Statement - public var table: TableName - public var columns: [SQLiteQuery.ColumnDefinition] - public var constraints: [SQLiteQuery.TableConstraint] - public init(_ statement: Statement, table: TableName) { - self.statement = statement - self.table = table - self.columns = [] - self.constraints = [] - } +extension SQLiteDatabase: SQLConstraintIdentifierNormalizer { + /// See `SQLConstraintIdentifierNormalizer`. + public static func normalizeSQLConstraintIdentifier(_ identifier: String) -> String { + return identifier } } extension SQLiteDatabase: SchemaSupporting { /// See `SchemaSupporting`. - public typealias Schema = SQLiteQuery.FluentSchema - - /// See `SchemaSupporting`. - public typealias SchemaAction = SQLiteQuery.FluentSchema.Statement - - /// See `SchemaSupporting`. - public typealias SchemaField = SQLiteQuery.ColumnDefinition - - /// See `SchemaSupporting`. - public typealias SchemaFieldType = SQLiteQuery.TypeName - - /// See `SchemaSupporting`. - public typealias SchemaConstraint = SQLiteQuery.TableConstraint + public typealias Schema = FluentSQLiteSchema /// See `SchemaSupporting`. - public typealias SchemaReferenceAction = SQLiteQuery.ForeignKeyReference.Action + public typealias SchemaAction = FluentSQLiteSchemaStatement /// See `SchemaSupporting`. - public static var schemaActionCreate: SQLiteQuery.FluentSchema.Statement { - return .create - } + public typealias SchemaField = SQLiteColumnDefinition /// See `SchemaSupporting`. - public static var schemaActionUpdate: SQLiteQuery.FluentSchema.Statement { - return .alter - } + public typealias SchemaFieldType = SQLiteDataType /// See `SchemaSupporting`. - public static var schemaActionDelete: SQLiteQuery.FluentSchema.Statement { - return .drop - } + public typealias SchemaConstraint = SQLiteTableConstraint /// See `SchemaSupporting`. - public static func schemaCreate(_ action: SQLiteQuery.FluentSchema.Statement, _ entity: String) -> SQLiteQuery.FluentSchema { - return .init(action, table: .init(name: entity)) - } + public typealias SchemaReferenceAction = SQLiteConflictResolution /// See `SchemaSupporting`. - public static func schemaField(for type: Any.Type, isIdentifier: Bool, _ field: SQLiteQuery.QualifiedColumnName) -> SQLiteQuery.ColumnDefinition { + public static func schemaField(for type: Any.Type, isIdentifier: Bool, _ field: QueryField) -> SchemaField { var type = type - var constraints: [SQLiteQuery.ColumnConstraint] = [] + var constraints: [SQLiteColumnConstraint] = [] if let optional = type as? AnyOptionalType.Type { type = optional.anyWrappedType @@ -69,12 +35,12 @@ extension SQLiteDatabase: SchemaSupporting { constraints.append(.notNull) } - let typeName: SQLiteQuery.TypeName - if let sqlite = type as? SQLiteFieldTypeStaticRepresentable.Type { - switch sqlite.sqliteFieldType { - case .blob: typeName = .none + let typeName: SQLiteDataType + if let sqlite = type as? SQLiteDataTypeStaticRepresentable.Type { + switch sqlite.sqliteDataType { + case .blob: typeName = .blob case .integer: typeName = .integer - case .null: typeName = .none + case .null: typeName = .null case .real: typeName = .real case .text: typeName = .text } @@ -84,85 +50,23 @@ extension SQLiteDatabase: SchemaSupporting { if isIdentifier { constraints.append(.notNull) - switch typeName { - case .integer: constraints.append(.primaryKey(autoIncrement: true)) - default: constraints.append(.primaryKey(autoIncrement: false)) - } + // SQLite should not use AUTOINCREMENT for INTEGER PRIMARY KEY since it is an alias for ROWID + constraints.append(.primaryKey(default: nil)) } - return .init(name: field.name, typeName: typeName, constraints: constraints) - } - - /// See `SchemaSupporting`. - public static func schemaField(_ field: SQLiteQuery.QualifiedColumnName, _ type: SQLiteQuery.TypeName) -> SQLiteQuery.ColumnDefinition { - return .init(name: field.name, typeName: type, constraints: []) - } - - /// See `SchemaSupporting`. - public static func schemaFieldCreate(_ field: SQLiteQuery.ColumnDefinition, to query: inout SQLiteQuery.FluentSchema) { - query.columns.append(field) + return .columnDefinition(field, typeName, constraints) } /// See `SchemaSupporting`. - public static func schemaFieldDelete(_ field: SQLiteQuery.QualifiedColumnName, to query: inout SQLiteQuery.FluentSchema) { - fatalError("SQLite does not support deleting columns from tables.") - } - - /// See `SchemaSupporting`. - public static func schemaReference(from: SQLiteQuery.QualifiedColumnName, to: SQLiteQuery.QualifiedColumnName, onUpdate: SQLiteQuery.ForeignKeyReference.Action?, onDelete: SQLiteQuery.ForeignKeyReference.Action?) -> SQLiteQuery.TableConstraint { - return .init( - name: "fk", - value: .foreignKey(.init( - columns: [from.name], - reference: .init( - foreignTable: .init(name: to.table!), - foreignColumns: [to.name], - onDelete: onDelete, - onUpdate: onUpdate, - match: nil, - deferrence: nil - ) - )) - ) - } - - /// See `SchemaSupporting`. - public static func schemaUnique(on: [SQLiteQuery.QualifiedColumnName]) -> SQLiteQuery.TableConstraint { - return .init( - name: "uq", - value: .unique(.init( - columns: on.map { .init(value: .column($0.name)) }, - conflictResolution: nil - )) - ) - } - - /// See `SchemaSupporting`. - public static func schemaConstraintCreate(_ constraint: SQLiteQuery.TableConstraint, to query: inout SQLiteQuery.FluentSchema) { - query.constraints.append(constraint) - } - - /// See `SchemaSupporting`. - public static func schemaConstraintDelete(_ constraint: SQLiteQuery.TableConstraint, to query: inout SQLiteQuery.FluentSchema) { - fatalError("SQLite does not support deleting constraints from tables.") - } - - /// See `SchemaSupporting`. - public static func schemaExecute(_ fluent: SQLiteQuery.FluentSchema, on conn: SQLiteConnection) -> Future { + public static func schemaExecute(_ fluent: Schema, on conn: SQLiteConnection) -> Future { let query: SQLiteQuery switch fluent.statement { - case .create: - query = .createTable(.init( - temporary: false, - ifNotExists: false, - table: fluent.table, - source: .schema(.init( - columns: fluent.columns, - tableConstraints: fluent.constraints, - withoutRowID: false - )) - )) - case .alter: + case ._createTable: + var createTable: SQLiteCreateTable = .createTable(fluent.table) + createTable.columns = fluent.columns + createTable.tableConstraints = fluent.constraints + query = ._createTable(createTable) + case ._alterTable: guard fluent.columns.count == 1 && fluent.constraints.count == 0 else { /// See https://www.sqlite.org/lang_altertable.html fatalError("SQLite only supports adding one (1) column in an ALTER query.") @@ -171,11 +75,9 @@ extension SQLiteDatabase: SchemaSupporting { table: fluent.table, value: .addColumn(fluent.columns[0]) )) - case .drop: - query = .dropTable(.init( - table: fluent.table, - ifExists: false - )) + case ._dropTable: + let dropTable: SQLiteDropTable = .dropTable(fluent.table) + query = ._dropTable(dropTable) } return conn.query(query).transform(to: ()) } diff --git a/Sources/FluentSQLite/SQLiteFieldTypes.swift b/Sources/FluentSQLite/SQLiteFieldTypes.swift deleted file mode 100644 index 557c2fa..0000000 --- a/Sources/FluentSQLite/SQLiteFieldTypes.swift +++ /dev/null @@ -1,63 +0,0 @@ -/// 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 SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - static var sqliteFieldType: SQLiteDataType { get } -} - -extension FixedWidthInteger { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return .integer } -} - -extension UInt: SQLiteFieldTypeStaticRepresentable { } -extension UInt8: SQLiteFieldTypeStaticRepresentable { } -extension UInt16: SQLiteFieldTypeStaticRepresentable { } -extension UInt32: SQLiteFieldTypeStaticRepresentable { } -extension UInt64: SQLiteFieldTypeStaticRepresentable { } -extension Int: SQLiteFieldTypeStaticRepresentable { } -extension Int8: SQLiteFieldTypeStaticRepresentable { } -extension Int16: SQLiteFieldTypeStaticRepresentable { } -extension Int32: SQLiteFieldTypeStaticRepresentable { } -extension Int64: SQLiteFieldTypeStaticRepresentable { } - -extension Date: SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return Double.sqliteFieldType } -} - -extension BinaryFloatingPoint { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return .real } -} - -extension Float: SQLiteFieldTypeStaticRepresentable { } -extension Double: SQLiteFieldTypeStaticRepresentable { } - -extension Bool: SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return Int.sqliteFieldType } -} - -extension UUID: SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return .blob } -} - -extension Data: SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return .blob } -} - -extension String: SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return .text } -} - -extension URL: SQLiteFieldTypeStaticRepresentable { - /// See `SQLiteFieldTypeStaticRepresentable`. - public static var sqliteFieldType: SQLiteDataType { return String.sqliteFieldType } -} diff --git a/Sources/FluentSQLite/SQLiteSQLSerializer.swift b/Sources/FluentSQLite/SQLiteSQLSerializer.swift deleted file mode 100644 index 98e6273..0000000 --- a/Sources/FluentSQLite/SQLiteSQLSerializer.swift +++ /dev/null @@ -1,4 +0,0 @@ -///// A SQLite flavored SQL serializer. -//final class SQLiteSQLSerializer: SQLSerializer { -// init() { } -//} diff --git a/Sources/FluentSQLite/SQLiteTypes.swift b/Sources/FluentSQLite/SQLiteTypes.swift index 8bb4dac..0165334 100644 --- a/Sources/FluentSQLite/SQLiteTypes.swift +++ b/Sources/FluentSQLite/SQLiteTypes.swift @@ -3,7 +3,7 @@ /// This protocol defines which `SQLiteFieldType` (TEXT, BLOB, etc) a type uses and how it converts to/from `SQLiteData`. /// /// See `SQLiteEnumType` and `SQLiteJSONType` for more specialized use-cases. -public typealias SQLiteType = Codable & SQLiteFieldTypeStaticRepresentable & SQLiteDataConvertible +public typealias SQLiteType = Codable & SQLiteDataTypeStaticRepresentable & SQLiteDataConvertible // MARK: JSON @@ -29,7 +29,7 @@ extension SQLiteJSONType { /// Use the `Data`'s `SQLiteFieldType` to store the JSON-encoded data. /// /// See `SQLiteFieldTypeStaticRepresentable.sqliteFieldType` for more information. - public static var sqliteFieldType: SQLiteDataType { return Data.sqliteFieldType } + public static var sqliteDataType: SQLiteDataType { return Data.sqliteDataType } /// JSON-encode `Self` to `Data`. /// @@ -71,13 +71,13 @@ public typealias SQLiteEnumType = SQLiteType & ReflectionDecodable & RawRepresen /// Provides a default `SQLiteFieldTypeStaticRepresentable` implementation where the type is also /// `RawRepresentable` by a `SQLiteFieldTypeStaticRepresentable` type. -extension SQLiteFieldTypeStaticRepresentable - where Self: RawRepresentable, Self.RawValue: SQLiteFieldTypeStaticRepresentable +extension SQLiteDataTypeStaticRepresentable + where Self: RawRepresentable, Self.RawValue: SQLiteDataTypeStaticRepresentable { /// Use the `RawValue`'s `SQLiteFieldType`. /// /// See `SQLiteFieldTypeStaticRepresentable.sqliteFieldType` for more information. - public static var sqliteFieldType: SQLiteDataType { return RawValue.sqliteFieldType } + public static var sqliteDataType: SQLiteDataType { return RawValue.sqliteDataType } } /// Provides a default `SQLiteDataConvertible` implementation where the type is also diff --git a/Tests/FluentSQLiteTests/SQLiteBenchmarkTests.swift b/Tests/FluentSQLiteTests/SQLiteBenchmarkTests.swift index c0f34c7..2de6a0a 100644 --- a/Tests/FluentSQLiteTests/SQLiteBenchmarkTests.swift +++ b/Tests/FluentSQLiteTests/SQLiteBenchmarkTests.swift @@ -4,6 +4,7 @@ import FluentBenchmark import FluentSQLite import SQLite import XCTest +import FluentSQL final class SQLiteBenchmarkTests: XCTestCase { var benchmarker: Benchmarker! @@ -15,50 +16,10 @@ final class SQLiteBenchmarkTests: XCTestCase { benchmarker = try! Benchmarker(database, on: group, onFail: XCTFail) } - func testSchema() throws { - try benchmarker.benchmarkSchema() + func testBenchmark() throws { + try benchmarker.runAll() } - - func testModels() throws { - try benchmarker.benchmarkModels_withSchema() - } - - func testRelations() throws { - try benchmarker.benchmarkRelations_withSchema() - } - - func testTimestampable() throws { - try benchmarker.benchmarkTimestampable_withSchema() - } - - func testTransactions() throws { - try benchmarker.benchmarkTransactions_withSchema() - } - - func testChunking() throws { - try benchmarker.benchmarkChunking_withSchema() - } - - func testAutoincrement() throws { - try benchmarker.benchmarkAutoincrement_withSchema() - } - - func testCache() throws { - try benchmarker.benchmarkCache_withSchema() - } - - func testJoins() throws { - try benchmarker.benchmarkJoins_withSchema() - } - - func testSoftDeletable() throws { - try benchmarker.benchmarkSoftDeletable_withSchema() - } - - func testReferentialActions() throws { - try benchmarker.benchmarkReferentialActions_withSchema() - } - + func testMinimumViableModelDeclaration() throws { /// NOTE: these must never fail to build struct Foo: SQLiteModel { @@ -82,10 +43,6 @@ final class SQLiteBenchmarkTests: XCTestCase { var name: String } } - - func testIndexSupporting() throws { - try benchmarker.benchmarkIndexSupporting_withSchema() - } func testContains() throws { struct User: SQLiteModel, SQLiteMigration { @@ -136,7 +93,7 @@ final class SQLiteBenchmarkTests: XCTestCase { } func testSQLiteEnums() throws { - enum PetType: Int, SQLiteEnumType, CaseIterable { + enum PetType: Int, Codable, CaseIterable { static let allCases: [PetType] = [.cat, .dog] case cat, dog } @@ -266,7 +223,7 @@ final class SQLiteBenchmarkTests: XCTestCase { return SQLiteDatabase.create(User.self, on: conn) { builder in builder.field(for: \.id, isIdentifier: true) builder.field(for: \.name) - builder.field(.init(name: "test", typeName: .text, constraints: [.default(.literal("foo"))])) + builder.field(for: \.test, type: .text, .default(.literal("foo"))) } } @@ -309,7 +266,7 @@ final class SQLiteBenchmarkTests: XCTestCase { do { var databases = DatabasesConfig() try! databases.add(database: SQLiteDatabase(storage: .memory), as: .sqlite) - databases.enableReferebces(on: .sqlite) + databases.enableReferences(on: .sqlite) let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) let dbs = try databases.resolve(on: BasicContainer(config: .init(), environment: .testing, services: .init(), on: group)) sqlite = try dbs.requireDatabase(for: .sqlite).newConnectionPool(config: .init(maxConnections: 4), on: group) @@ -331,19 +288,8 @@ final class SQLiteBenchmarkTests: XCTestCase { } static let allTests = [ - ("testSchema", testSchema), - ("testModels", testModels), - ("testRelations", testRelations), - ("testTimestampable", testTimestampable), - ("testTransactions", testTransactions), - ("testChunking", testChunking), - ("testAutoincrement", testAutoincrement), - ("testCache", testCache), - ("testJoins", testJoins), - ("testSoftDeletable", testSoftDeletable), - ("testReferentialActions", testReferentialActions), + ("testBenchmark", testBenchmark), ("testMinimumViableModelDeclaration", testMinimumViableModelDeclaration), - ("testIndexSupporting", testIndexSupporting), ("testUUIDPivot", testUUIDPivot), ("testSQLiteEnums", testSQLiteEnums), ("testSQLiteJSON", testSQLiteJSON),