diff --git a/Package.swift b/Package.swift index 5368ff7..5bd8b14 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( .package(url: "https://github.com/vapor/postgresql.git", from: "1.0.0-rc"), ], targets: [ - .target(name: "FluentPostgreSQL", dependencies: ["Async", "Fluent", "PostgreSQL"]), + .target(name: "FluentPostgreSQL", dependencies: ["Async", "FluentSQL", "PostgreSQL"]), .testTarget(name: "FluentPostgreSQLTests", dependencies: ["FluentBenchmark", "FluentPostgreSQL"]), ] ) diff --git a/Sources/FluentPostgreSQL/Deprecated.swift b/Sources/FluentPostgreSQL/Deprecated.swift index c59080b..00e343e 100644 --- a/Sources/FluentPostgreSQL/Deprecated.swift +++ b/Sources/FluentPostgreSQL/Deprecated.swift @@ -16,12 +16,3 @@ public protocol PostgreSQLEnumType { } // - warning: Deprecated. @available(*, deprecated, message: "Use custom migration instead.") public protocol PostgreSQLType { } - - -extension QueryBuilder where Database == PostgreSQLDatabase { - /// - warning: Deprecated. - @available(*, deprecated, renamed: "groupBy(_:)") - public func group(by field: KeyPath) -> Self { - return groupBy(field) - } -} diff --git a/Sources/FluentPostgreSQL/Exports.swift b/Sources/FluentPostgreSQL/Exports.swift index 91e1e89..5d37991 100644 --- a/Sources/FluentPostgreSQL/Exports.swift +++ b/Sources/FluentPostgreSQL/Exports.swift @@ -1,2 +1,31 @@ -@_exported import Fluent +@_exported import FluentSQL @_exported import PostgreSQL + +//extension QueryBuilder where Database == PostgreSQLDatabase { +// public func keys(_ keys: PostgreSQLQuery.Key...) -> Self { +// query.keys = keys +// return self +// } +// +// // MARK: Group By +// +// /// Adds a group by to the query builder. +// /// +// /// query.groupBy(\.name) +// /// +// /// - parameters: +// /// - field: Swift `KeyPath` to field on model to group by. +// /// - returns: Query builder for chaining. +// public func groupBy(_ field: KeyPath) -> Self { +// return groupBy(.expression(.column(PostgreSQLDatabase.queryField(.keyPath(field))), alias: nil)) +// } +// +// /// Adds a manually created group by to the query builder. +// /// - parameters: +// /// - groupBy: New `Query.GroupBy` to add. +// /// - returns: Query builder for chaining. +// public func groupBy(_ groupBy: PostgreSQLQuery.Key) -> Self { +// query.groupBy.append(groupBy) +// return self +// } +//} diff --git a/Sources/FluentPostgreSQL/Fluent+PostgreSQLUpsert.swift b/Sources/FluentPostgreSQL/Fluent+PostgreSQLUpsert.swift new file mode 100644 index 0000000..bb3c749 --- /dev/null +++ b/Sources/FluentPostgreSQL/Fluent+PostgreSQLUpsert.swift @@ -0,0 +1,19 @@ +extension _PostgreSQLModel { + public func create(orUpdate: Bool, on conn: DatabaseConnectable) -> Future { + return Self.query(on: conn).create(orUpdate: orUpdate, self) + } +} + +extension QueryBuilder where Result: _PostgreSQLModel, Result.Database == Database { + public func create(orUpdate: Bool, _ model: Result) -> Future { + if orUpdate { + let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model) + let values = row.map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in + return (.identifier(row.key), row.value) + } + self.query.upsert = .upsert([.keyPath(Result.idKey)], values) + } + return create(model) + } + +} diff --git a/Sources/FluentPostgreSQL/FluentPostgreSQLProvider.swift b/Sources/FluentPostgreSQL/FluentPostgreSQLProvider.swift index da6f4d6..ef2281f 100644 --- a/Sources/FluentPostgreSQL/FluentPostgreSQLProvider.swift +++ b/Sources/FluentPostgreSQL/FluentPostgreSQLProvider.swift @@ -23,7 +23,7 @@ public final class FluentPostgreSQLProvider: Provider { } return worker.withPooledConnection(to: .psql) { conn in - return conn.simpleQuery("SELECT current_setting('server_version') as version", decoding: Setting.self).map { rows in + return conn.select().column(.function("current_setting", [.expression(.literal(.string("server_version")))], as: .identifier("version"))).all(decoding: Setting.self).map { rows in _serverVersion = rows[0].version if let versionString = _serverVersion { let pointIndex = versionString.index(of: ".") ?? versionString.endIndex diff --git a/Sources/FluentPostgreSQL/FluentPostgreSQLQuery.swift b/Sources/FluentPostgreSQL/FluentPostgreSQLQuery.swift new file mode 100644 index 0000000..12bbd90 --- /dev/null +++ b/Sources/FluentPostgreSQL/FluentPostgreSQLQuery.swift @@ -0,0 +1,59 @@ +public enum FluentPostgreSQLQueryStatement: FluentSQLQueryStatement { + public static var insert: FluentPostgreSQLQueryStatement { return ._insert } + public static var select: FluentPostgreSQLQueryStatement { return ._select } + public static var update: FluentPostgreSQLQueryStatement { return ._update } + public static var delete: FluentPostgreSQLQueryStatement { 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 FluentPostgreSQLQuery: FluentSQLQuery { + public typealias Statement = FluentPostgreSQLQueryStatement + public typealias TableIdentifier = PostgreSQLTableIdentifier + public typealias Expression = PostgreSQLExpression + public typealias SelectExpression = PostgreSQLSelectExpression + public typealias Join = PostgreSQLJoin + public typealias OrderBy = PostgreSQLOrderBy + public typealias GroupBy = PostgreSQLGroupBy + public typealias Upsert = PostgreSQLUpsert + + 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 upsert: PostgreSQLUpsert? + public var defaultBinaryOperator: PostgreSQLBinaryOperator + + public static func query(_ statement: Statement, _ table: TableIdentifier) -> FluentPostgreSQLQuery { + return .init( + statement: statement, + table: table, + keys: [], + values: [:], + joins: [], + predicate: nil, + orderBy: [], + groupBy: [], + limit: nil, + offset: nil, + upsert: nil, + defaultBinaryOperator: .and + ) + } +} diff --git a/Sources/FluentPostgreSQL/FluentPostgreSQLSchema.swift b/Sources/FluentPostgreSQL/FluentPostgreSQLSchema.swift new file mode 100644 index 0000000..08d51fe --- /dev/null +++ b/Sources/FluentPostgreSQL/FluentPostgreSQLSchema.swift @@ -0,0 +1,34 @@ +public enum FluentPostgreSQLSchemaStatement: FluentSQLSchemaStatement { + public static var createTable: FluentPostgreSQLSchemaStatement { return ._createTable } + public static var alterTable: FluentPostgreSQLSchemaStatement { return ._alterTable } + public static var dropTable: FluentPostgreSQLSchemaStatement { return ._dropTable } + + case _createTable + case _alterTable + case _dropTable +} + +public struct FluentPostgreSQLSchema: FluentSQLSchema { + public typealias Statement = FluentPostgreSQLSchemaStatement + public typealias TableIdentifier = PostgreSQLTableIdentifier + public typealias ColumnDefinition = PostgreSQLColumnDefinition + public typealias TableConstraint = PostgreSQLTableConstraint + + public var statement: Statement + public var table: TableIdentifier + public var columns: [PostgreSQLColumnDefinition] + public var deleteColumns: [PostgreSQLColumnIdentifier] + public var constraints: [PostgreSQLTableConstraint] + public var deleteConstraints: [PostgreSQLTableConstraint] + + public static func schema(_ statement: Statement, _ table: TableIdentifier) -> FluentPostgreSQLSchema { + return .init( + statement: statement, + table: table, + columns: [], + deleteColumns: [], + constraints: [], + deleteConstraints: [] + ) + } +} diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+Fluent.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+Fluent.swift deleted file mode 100644 index e6660cd..0000000 --- a/Sources/FluentPostgreSQL/PostgreSQLDatabase+Fluent.swift +++ /dev/null @@ -1,499 +0,0 @@ -infix operator ~= -/// Has prefix -public func ~= (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .like, ["%" + rhs]) -} -/// Has prefix -public func ~= (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .like, ["%" + rhs]) -} - -infix operator =~ -/// Has suffix. -public func =~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .like, [rhs + "%"]) -} -/// Has suffix. -public func =~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .like, [rhs + "%"]) -} - -infix operator ~~ -/// Contains. -public func ~~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .like, ["%" + rhs + "%"]) -} -/// Contains. -public func ~~ (lhs: KeyPath, rhs: String) -> FilterOperator { - return .make(lhs, .like, ["%" + rhs + "%"]) -} - -extension PostgreSQLQuery { - public struct FluentQuery { - public enum Statement { - case insert - case select - case update - case delete - } - - public var statement: Statement - public var table: TableName - public var keys: [Key] - public var values: [String: Value] - public var joins: [Join] - public var predicate: Predicate? - public var orderBy: [OrderBy] - public var groupBy: [Key] - public var limit: Int? - public var offset: Int? - public var returning: [Key] - - public init( - statement: Statement = .select, - table: TableName, - keys: [Key] = [], - values: [String: Value] = [:], - joins: [Join] = [], - predicate: Predicate? = nil, - orderBy: [OrderBy] = [], - groupBy: [Key] = [], - limit: Int? = nil, - offset: Int? = nil, - returning: [Key] = [] - ) { - self.statement = statement - self.table = table - self.keys = keys - self.values = values - self.joins = joins - self.predicate = predicate - self.orderBy = orderBy - self.groupBy = groupBy - self.offset = offset - self.limit = limit - self.returning = returning - } - } - - static func fluent(_ fluent: FluentQuery) -> PostgreSQLQuery { - let query: PostgreSQLQuery - switch fluent.statement { - case .insert: - query = .insert(.init( - table: fluent.table, - columns: .init(fluent.values.keys), - values: [.init(fluent.values.values)], - returning: fluent.returning - )) - case .select: - query = .select(.init( - candidates: .all, - keys: fluent.keys, - tables: [fluent.table], - joins: fluent.joins, - predicate: fluent.predicate, - orderBy: fluent.orderBy, - groupBy: fluent.groupBy, - limit: fluent.limit, - offset: fluent.offset - )) - case .update: - query = .update(.init( - locality: .inherited, - table: fluent.table, - values: fluent.values, - predicate: fluent.predicate, - returning: fluent.returning - )) - case .delete: - query = .delete(.init( - locality: .inherited, - table: fluent.table, - predicate: fluent.predicate, - returning: fluent.returning - )) - } - return query - } -} - -extension QueryBuilder where Database == PostgreSQLDatabase { - public func keys(_ keys: PostgreSQLQuery.Key...) -> Self { - query.keys = keys - return self - } - - // MARK: Group By - - /// Adds a group by to the query builder. - /// - /// query.groupBy(\.name) - /// - /// - parameters: - /// - field: Swift `KeyPath` to field on model to group by. - /// - returns: Query builder for chaining. - public func groupBy(_ field: KeyPath) -> Self { - return groupBy(.expression(.column(PostgreSQLDatabase.queryField(.keyPath(field))), alias: nil)) - } - - /// Adds a manually created group by to the query builder. - /// - parameters: - /// - groupBy: New `Query.GroupBy` to add. - /// - returns: Query builder for chaining. - public func groupBy(_ groupBy: PostgreSQLQuery.Key) -> Self { - query.groupBy.append(groupBy) - return self - } -} - - -/// Adds ability to do basic Fluent queries using a `PostgreSQLDatabase`. -extension PostgreSQLDatabase: QuerySupporting & JoinSupporting & MigrationSupporting & TransactionSupporting & KeyedCacheSupporting { - public static var queryJoinMethodDefault: PostgreSQLQuery.Join.Method { - return .inner - } - - public static func queryJoin(_ method: PostgreSQLQuery.Join.Method, base: PostgreSQLQuery.Column, joined: PostgreSQLQuery.Column) -> PostgreSQLQuery.Join { - return .init( - method: method, - table: .init(name: joined.table!), - condition: .predicate(base, .equal, .expression(.column(joined))) - ) - } - - public static func queryJoinApply(_ join: PostgreSQLQuery.Join, to query: inout PostgreSQLQuery.FluentQuery) { - query.joins.append(join) - } - - public static func query(_ entity: String) -> PostgreSQLQuery.FluentQuery { - return .init(table: .init(name: entity)) - } - - public static func queryEntity(for query: PostgreSQLQuery.FluentQuery) -> String { - return query.table.name - } - - public static func queryEncode(_ encodable: E, entity: String) throws -> [String : PostgreSQLQuery.Value] where E : Encodable { - return try PostgreSQLQueryEncoder().encode(encodable) - } - - public static var queryActionCreate: PostgreSQLQuery.FluentQuery.Statement { - return .insert - } - - public static var queryActionRead: PostgreSQLQuery.FluentQuery.Statement { - return .select - } - - public static var queryActionUpdate: PostgreSQLQuery.FluentQuery.Statement { - return .update - } - - public static var queryActionDelete: PostgreSQLQuery.FluentQuery.Statement { - return .delete - } - - public static func queryActionIsCreate(_ action: PostgreSQLQuery.FluentQuery.Statement) -> Bool { - switch action { - case .insert: return true - default: return false - } - } - - public static func queryActionApply(_ statement: PostgreSQLQuery.FluentQuery.Statement, to query: inout PostgreSQLQuery.FluentQuery) { - query.statement = statement - } - - 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(_ column: PostgreSQLQuery.Column, to data: E, on query: inout PostgreSQLQuery.FluentQuery) - where E: Encodable - { - // FIXME: ("Allow query data set to throw if needed.") - query.values[column.name] = try! .bind(data) - } - - public static func queryDataApply(_ data: [String : PostgreSQLQuery.Value], to query: inout PostgreSQLQuery.FluentQuery) { - query.values = data - } - - public static func queryField(_ property: FluentProperty) -> PostgreSQLQuery.Column { - guard let model = property.rootType as? AnyModel.Type else { - fatalError("`\(property.rootType)` does not conform to `Model`.") - } - return .init(table: model.entity, name: property.path.first ?? "") - } - - public static var queryFilterMethodEqual: PostgreSQLQuery.Predicate.Comparison { - return .equal - } - - public static var queryFilterMethodNotEqual: PostgreSQLQuery.Predicate.Comparison { - return .notEqual - } - - public static var queryFilterMethodGreaterThan: PostgreSQLQuery.Predicate.Comparison { - return .greaterThan - } - - public static var queryFilterMethodLessThan: PostgreSQLQuery.Predicate.Comparison { - return .lessThan - } - - public static var queryFilterMethodGreaterThanOrEqual: PostgreSQLQuery.Predicate.Comparison { - return .greaterThanOrEqual - } - - public static var queryFilterMethodLessThanOrEqual: PostgreSQLQuery.Predicate.Comparison { - return .lessThanOrEqual - } - - public static var queryFilterMethodInSubset: PostgreSQLQuery.Predicate.Comparison { - return .in - } - - public static var queryFilterMethodNotInSubset: PostgreSQLQuery.Predicate.Comparison { - return .notIn - } - - public static func queryFilterValue(_ encodables: [E]) -> PostgreSQLQuery.Value - where E: Encodable - { - // FIXME: ("Fix non throwing binds conversion.") - return try! .binds(encodables) - } - - public static var queryFilterValueNil: PostgreSQLQuery.Value { - return .null - } - - public static func queryFilter(_ column: PostgreSQLQuery.Column, _ comparison: PostgreSQLQuery.Predicate.Comparison, _ value: PostgreSQLQuery.Value) -> PostgreSQLQuery.Predicate { - return .predicate(column, comparison, value) - } - - public static func queryFilters(for query: PostgreSQLQuery.FluentQuery) -> [PostgreSQLQuery.Predicate] { - - if let predicate = query.predicate { - switch predicate { - case .group(_, let predicates): return predicates - default: return [predicate] - } - } else { - return [] - } - } - - public static func queryFilterApply(_ filter: PostgreSQLQuery.Predicate, to query: inout PostgreSQLQuery.FluentQuery) { - if let predicate = query.predicate { - query.predicate = .group(.and, [predicate, filter]) - } else { - query.predicate = filter - } - } - - public static var queryFilterRelationAnd: PostgreSQLQuery.Predicate.Relation { - return .and - } - - public static var queryFilterRelationOr: PostgreSQLQuery.Predicate.Relation { - return .or - } - - public static func queryDefaultFilterRelation(_ relation: PostgreSQLQuery.Predicate.Relation, on: inout PostgreSQLQuery.FluentQuery) { - // skip - } - - public static func queryFilterGroup(_ op: PostgreSQLQuery.Predicate.Relation, _ filters: [PostgreSQLQuery.Predicate]) -> PostgreSQLQuery.Predicate { - return .group(op, filters) - } - - public static var queryKeyAll: PostgreSQLQuery.Key { - return .all - } - - public static func queryAggregate(_ aggregate: String, _ fields: [PostgreSQLQuery.Key]) -> PostgreSQLQuery.Key { - return .expression(.function(.init( - name: aggregate, - parameters: fields.map { key in - switch key { - case .all: return .all - case .expression(let expression, _): return expression - } - } - )), alias: "fluentAggregate") - } - - public static func queryKey(_ column: PostgreSQLQuery.Column) -> PostgreSQLQuery.Key { - return .expression(.column(column), alias: nil) - } - - public static func queryKeyApply(_ key: PostgreSQLQuery.Key, to query: inout PostgreSQLQuery.FluentQuery) { - query.keys.append(key) - } - - public static func queryRangeApply(lower: Int, upper: Int?, to query: inout PostgreSQLQuery.FluentQuery) { - if let upper = upper { - query.limit = upper - lower - query.offset = lower - } else { - query.offset = lower - } - } - - public static func querySort(_ column: PostgreSQLQuery.Column, _ direction: PostgreSQLQuery.OrderBy.Direction) -> PostgreSQLQuery.OrderBy { - return .init(.column(column), direction: direction) - } - - public static var querySortDirectionAscending: PostgreSQLQuery.OrderBy.Direction { - return .ascending - } - - public static var querySortDirectionDescending: PostgreSQLQuery.OrderBy.Direction { - return .descending - } - - public static func querySortApply(_ orderBy: PostgreSQLQuery.OrderBy, to query: inout PostgreSQLQuery.FluentQuery) { - query.orderBy.append(orderBy) - } - - /// See `SQLDatabase`. - public typealias QueryJoin = PostgreSQLQuery.Join - - /// See `SQLDatabase`. - public typealias QueryJoinMethod = PostgreSQLQuery.Join.Method - - /// See `SQLDatabase`. - public typealias Query = PostgreSQLQuery.FluentQuery - - /// See `SQLDatabase`. - public typealias Output = [PostgreSQLColumn: PostgreSQLData] - - /// See `SQLDatabase`. - public typealias QueryAction = PostgreSQLQuery.FluentQuery.Statement - - /// See `SQLDatabase`. - public typealias QueryAggregate = String - - /// See `SQLDatabase`. - public typealias QueryData = [String: PostgreSQLQuery.Value] - - /// See `SQLDatabase`. - public typealias QueryField = PostgreSQLQuery.Column - - /// See `SQLDatabase`. - public typealias QueryFilterMethod = PostgreSQLQuery.Predicate.Comparison - - /// See `SQLDatabase`. - public typealias QueryFilterValue = PostgreSQLQuery.Value - - /// See `SQLDatabase`. - public typealias QueryFilter = PostgreSQLQuery.Predicate - - /// See `SQLDatabase`. - public typealias QueryFilterRelation = PostgreSQLQuery.Predicate.Relation - - /// See `SQLDatabase`. - public typealias QueryKey = PostgreSQLQuery.Key - - /// See `SQLDatabase`. - public typealias QuerySort = PostgreSQLQuery.OrderBy - - /// See `SQLDatabase`. - public typealias QuerySortDirection = PostgreSQLQuery.OrderBy.Direction - - /// See `SQLDatabase`. - public static func queryExecute( - _ query: PostgreSQLQuery.FluentQuery, - on conn: PostgreSQLConnection, - into handler: @escaping ([PostgreSQLColumn: PostgreSQLData], PostgreSQLConnection) throws -> () - ) -> Future { - var query = query - switch query.statement { - case .insert: - // if statement is `INSERT`, then replace any `NULL` values with `DEFAULT` - // that will act as `NULL` while not causing problems with primary keys or fields - // with NOT NULL constraint _and_ default values. - query.values = query.values.mapValues { value in - switch value { - case .null: return .default - default: return value - } - } - query.returning.append(.all) - default: break - } - // always cache the names first - return conn.tableNames().flatMap { names in - return conn.query(.fluent(query)) { row in - try handler(row, conn) - } - } - } - - /// See `SQLDatabase`. - public static func queryDecode(_ data: [PostgreSQLColumn: PostgreSQLData], entity: String, as decodable: D.Type, on conn: PostgreSQLConnection) -> Future - where D: Decodable - { - return conn.tableNames().map { names in - return try PostgreSQLRowDecoder().decode(D.self, from: data, tableOID: names.tableOID(name: entity) ?? 0) - } - } - - struct InsertMetadata: Codable where ID: Codable { - var lastval: ID - } - - /// See `QuerySupporting.modelEvent` - public static func modelEvent(event: ModelEvent, model: M, on conn: PostgreSQLConnection) -> Future - where PostgreSQLDatabase == M.Database, M: Model - { - switch event { - case .willCreate: - if M.ID.self == UUID.self { - var model = model - model.fluentID = UUID() as? M.ID - return conn.future(model) - } - default: break - } - - return conn.future(model) - } - - /// See `SchemaSupporting`. - public static func schemaExecute(_ schema: PostgreSQLQuery.FluentSchema, on connection: PostgreSQLConnection) -> Future { - return connection.query(.fluent(schema)).transform(to: ()) - } - - /// See `SchemaSupporting`. - public static func transactionExecute(_ transaction: @escaping (PostgreSQLConnection) throws -> Future, on connection: PostgreSQLConnection) -> Future { - return connection.simpleQuery("BEGIN TRANSACTION").flatMap { results in - return try transaction(connection).flatMap { res in - return connection.simpleQuery("END TRANSACTION").transform(to: res) - }.catchFlatMap { error in - return connection.simpleQuery("ROLLBACK").map { results in - throw error - } - } - } - } -} diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+JoinSupporting.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+JoinSupporting.swift new file mode 100644 index 0000000..adaff3c --- /dev/null +++ b/Sources/FluentPostgreSQL/PostgreSQLDatabase+JoinSupporting.swift @@ -0,0 +1,7 @@ +extension PostgreSQLDatabase: JoinSupporting { + /// See `SQLDatabase`. + public typealias QueryJoin = PostgreSQLJoin + + /// See `SQLDatabase`. + public typealias QueryJoinMethod = PostgreSQLJoinMethod +} diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+KeyedCacheSupporting.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+KeyedCacheSupporting.swift new file mode 100644 index 0000000..5fef645 --- /dev/null +++ b/Sources/FluentPostgreSQL/PostgreSQLDatabase+KeyedCacheSupporting.swift @@ -0,0 +1 @@ +extension PostgreSQLDatabase: KeyedCacheSupporting { } diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+MigrationSupporting.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+MigrationSupporting.swift new file mode 100644 index 0000000..9c01d7e --- /dev/null +++ b/Sources/FluentPostgreSQL/PostgreSQLDatabase+MigrationSupporting.swift @@ -0,0 +1 @@ +extension PostgreSQLDatabase: MigrationSupporting { } diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+QuerySupporting.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+QuerySupporting.swift new file mode 100644 index 0000000..a85cf7e --- /dev/null +++ b/Sources/FluentPostgreSQL/PostgreSQLDatabase+QuerySupporting.swift @@ -0,0 +1,139 @@ +/// Adds ability to do basic Fluent queries using a `PostgreSQLDatabase`. +extension PostgreSQLDatabase: QuerySupporting { + /// See `SQLDatabase`. + public typealias Query = FluentPostgreSQLQuery + + /// See `SQLDatabase`. + public typealias Output = [PostgreSQLColumn: PostgreSQLData] + + /// See `SQLDatabase`. + public typealias QueryAction = FluentPostgreSQLQueryStatement + + /// See `SQLDatabase`. + public typealias QueryAggregate = String + + /// See `SQLDatabase`. + public typealias QueryData = [String: PostgreSQLExpression] + + /// See `SQLDatabase`. + public typealias QueryField = PostgreSQLColumnIdentifier + + /// See `SQLDatabase`. + public typealias QueryFilterMethod = PostgreSQLBinaryOperator + + /// See `SQLDatabase`. + public typealias QueryFilterValue = PostgreSQLExpression + + /// See `SQLDatabase`. + public typealias QueryFilter = PostgreSQLExpression + + /// See `SQLDatabase`. + public typealias QueryFilterRelation = PostgreSQLBinaryOperator + + /// See `SQLDatabase`. + public typealias QueryKey = PostgreSQLSelectExpression + + /// See `SQLDatabase`. + public typealias QuerySort = PostgreSQLOrderBy + + /// See `SQLDatabase`. + public typealias QuerySortDirection = PostgreSQLDirection + + /// See `SQLDatabase`. + public static func queryExecute( + _ fluent: FluentPostgreSQLQuery, + on conn: PostgreSQLConnection, + into handler: @escaping ([PostgreSQLColumn: PostgreSQLData], PostgreSQLConnection) throws -> () + ) -> Future { + let query: PostgreSQLQuery + switch fluent.statement { + case ._insert: + var insert: PostgreSQLInsert = .insert(fluent.table) + var values: [PostgreSQLExpression] = [] + 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) + } + insert.values.append(values) + insert.upsert = fluent.upsert + insert.returning.append(.all) + query = .insert(insert) + case ._select: + var select: PostgreSQLSelect = .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: PostgreSQLUpdate = .update(fluent.table) + update.table = fluent.table + update.values = fluent.values.map { val in + return (.identifier(val.key), val.value) + } + update.predicate = fluent.predicate + query = .update(update) + case ._delete: + var delete: PostgreSQLDelete = .delete(fluent.table) + delete.predicate = fluent.predicate + query = .delete(delete) + } + return conn.query(query) { try handler($0, conn) } + } + + struct InsertMetadata: Codable where ID: Codable { + var lastval: ID + } + + /// See `QuerySupporting`. + public static func modelEvent(event: ModelEvent, model: M, on conn: PostgreSQLConnection) -> Future + where PostgreSQLDatabase == M.Database, M: Model + { + switch event { + case .willCreate: + if M.ID.self == UUID.self { + var model = model + model.fluentID = UUID() as? M.ID + return conn.future(model) + } + default: break + } + + return conn.future(model) + } + + /// See `SchemaSupporting`. + public static func schemaExecute(_ fluent: FluentPostgreSQLSchema, on conn: PostgreSQLConnection) -> Future { + let query: PostgreSQLQuery + switch fluent.statement { + case ._createTable: + var createTable: PostgreSQLCreateTable = .createTable(fluent.table) + createTable.columns = fluent.columns + createTable.tableConstraints = fluent.constraints + query = ._createTable(createTable) + case ._alterTable: + var alterTable: PostgreSQLAlterTable = .alterTable(fluent.table) + alterTable.columns = fluent.columns + alterTable.constraints = fluent.constraints + query = ._alterTable(alterTable) + case ._dropTable: + let dropTable: PostgreSQLDropTable = .dropTable(fluent.table) + query = ._dropTable(dropTable) + } + return conn.query(query).transform(to: ()) + } +} diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+SchemaSupporting.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+SchemaSupporting.swift index 7e95d5c..7cec803 100644 --- a/Sources/FluentPostgreSQL/PostgreSQLDatabase+SchemaSupporting.swift +++ b/Sources/FluentPostgreSQL/PostgreSQLDatabase+SchemaSupporting.swift @@ -1,83 +1,39 @@ -extension PostgreSQLQuery { - public struct FluentSchema { - public enum Statement { - case create - case alter - case drop - } - - public var statement: Statement - public var table: String - public var createColumns: [ColumnDefinition] - public var deleteColumns: [Column] - public var createConstraints: [TableConstraint] - public var deleteConstraints: [TableConstraint] - - public init(statement: Statement = .create, table: String) { - self.statement = statement - self.table = table - self.createColumns = [] - self.deleteColumns = [] - self.createConstraints = [] - self.deleteConstraints = [] - } - } - - static func fluent(_ fluent: FluentSchema) -> PostgreSQLQuery { - let query: PostgreSQLQuery - switch fluent.statement { - case .create: - query = .createTable(.init( - storage: .permanent, - ifNotExists: false, - name: fluent.table, - columns: fluent.createColumns, - constraints: fluent.createConstraints - )) - case .alter: - query = .alterTable(.init( - ifExists: false, - name: fluent.table, - addColumns: fluent.createColumns, - dropColumns: fluent.deleteColumns.map { $0.name }, - addConstraints: fluent.createConstraints, - dropConstraints: fluent.deleteConstraints.map { $0.name ?? "unknown" } - )) - case .drop: - query = .dropTable(.init(name: fluent.table, ifExists: false)) - } - return query +extension PostgreSQLDatabase: SQLConstraintIdentifierNormalizer { + /// See `SQLConstraintIdentifierNormalizer`. + public static func normalizeSQLConstraintIdentifier(_ identifier: String) -> String { + return identifier } } extension PostgreSQLDatabase: SchemaSupporting { - public static var schemaActionCreate: PostgreSQLQuery.FluentSchema.Statement { - return .create - } + /// See `SchemaSupporting`. + public typealias Schema = FluentPostgreSQLSchema - public static var schemaActionUpdate: PostgreSQLQuery.FluentSchema.Statement { - return .alter - } + /// See `SchemaSupporting`. + public typealias SchemaAction = FluentPostgreSQLSchemaStatement - public static var schemaActionDelete: PostgreSQLQuery.FluentSchema.Statement { - return .drop - } + /// See `SchemaSupporting`. + public typealias SchemaField = PostgreSQLColumnDefinition - public static func schemaCreate(_ statement: PostgreSQLQuery.FluentSchema.Statement, _ table: String) -> PostgreSQLQuery.FluentSchema { - return .init(statement: statement, table: table) - } + /// See `SchemaSupporting`. + public typealias SchemaFieldType = PostgreSQLDataType + + /// See `SchemaSupporting`. + public typealias SchemaConstraint = PostgreSQLTableConstraint + + /// See `SchemaSupporting`. + public typealias SchemaReferenceAction = PostgreSQLConflictResolution - public static func schemaField(for type: Any.Type, isIdentifier: Bool, _ column: PostgreSQLQuery.Column) -> PostgreSQLQuery.ColumnDefinition { - var constraints: [PostgreSQLQuery.ColumnConstraint] = [] - var dataType: PostgreSQLQuery.DataType + /// See `SchemaSupporting`. + public static func schemaField(for type: Any.Type, isIdentifier: Bool, _ column: PostgreSQLColumnIdentifier) -> PostgreSQLColumnDefinition { + var constraints: [PostgreSQLColumnConstraint] = [] + var dataType: PostgreSQLDataType var type = type if let optional = type as? AnyOptionalType.Type { type = optional.anyWrappedType if isIdentifier { constraints.append(.notNull) - } else { - constraints.append(.null) } } else { constraints.append(.notNull) @@ -91,19 +47,30 @@ extension PostgreSQLDatabase: SchemaSupporting { isArray = false } - if let type = type as? PostgreSQLStaticColumnTypeRepresentable.Type { - dataType = type.postgreSQLColumnType + if let type = type as? PostgreSQLDataTypeStaticRepresentable.Type { + dataType = type.postgreSQLDataType } else { dataType = .jsonb } if isIdentifier { if _globalEnableIdentityColumns { + let pkDefault: PostgreSQLPrimaryKeyDefault? + // create a unique name for the primary key since it will be added + // as a separate index. + let unique: String + if let table = column.table { + unique = table.identifier.string + "." + column.identifier.string + } else { + unique = column.identifier.string + } switch dataType { case .smallint, .integer, .bigint: - constraints.append(.init(.generated(.byDefault))) - default: break + pkDefault = .generated(.byDefault) + default: + pkDefault = nil } + constraints.append(.primaryKey(default: pkDefault, identifier: .identifier("pk:\(unique)"))) } else { switch dataType { case .smallint: dataType = .smallserial @@ -112,71 +79,15 @@ extension PostgreSQLDatabase: SchemaSupporting { default: break } } - - // create a unique name for the primary key since it will be added - // as a separate index. - let unique: String - if let table = column.table { - unique = table + "." + column.name - } else { - unique = column.name - } - constraints.append(.init(.primaryKey, name: "pk:" + unique)) } - return .init(name: column.name, dataType: dataType, isArray: isArray, constraints: constraints) - } - - public static func schemaField(_ column: PostgreSQLQuery.Column, _ dataType: PostgreSQLQuery.DataType) -> PostgreSQLQuery.ColumnDefinition { - return .init(name: column.name, dataType: dataType) - } - - public static func schemaFieldCreate(_ field: PostgreSQLQuery.ColumnDefinition, to query: inout PostgreSQLQuery.FluentSchema) { - query.createColumns.append(field) - } - - public static func schemaFieldDelete(_ field: PostgreSQLQuery.Column, to query: inout PostgreSQLQuery.FluentSchema) { - query.deleteColumns.append(field) - } - - public static func schemaReference(from: PostgreSQLQuery.Column, to: PostgreSQLQuery.Column, onUpdate: PostgreSQLQuery.ForeignKeyAction?, onDelete: PostgreSQLQuery.ForeignKeyAction?) -> PostgreSQLQuery.TableConstraint { - return .init(.foreignKey(.init( - columns: [from.name], - foreignTable: to.table!, - foreignColumns: [to.name], - onDelete: onDelete, - onUpdate: onUpdate - )), as: "fk:" + from.name + "+" + to.table! + "." + to.name) - } - - public static func schemaUnique(on columns: [PostgreSQLQuery.Column]) -> PostgreSQLQuery.TableConstraint { - let names = columns.map { $0.name } - let uid = names.joined(separator: "+") - return .init(.unique(.init( - columns: names - )), as: "uq:" + uid) - } - - public static func schemaConstraintCreate(_ constraint: PostgreSQLQuery.TableConstraint, to query: inout PostgreSQLQuery.FluentSchema) { - query.createConstraints.append(constraint) - } - - public static func schemaConstraintDelete(_ constraint: PostgreSQLQuery.TableConstraint, to query: inout PostgreSQLQuery.FluentSchema) { - query.deleteConstraints.append(constraint) + if isArray { + dataType = .array(dataType) + } + + return .columnDefinition(column, dataType, constraints) } - public typealias Schema = PostgreSQLQuery.FluentSchema - - public typealias SchemaAction = PostgreSQLQuery.FluentSchema.Statement - - public typealias SchemaField = PostgreSQLQuery.ColumnDefinition - - public typealias SchemaFieldType = PostgreSQLQuery.DataType - - public typealias SchemaConstraint = PostgreSQLQuery.TableConstraint - - public typealias SchemaReferenceAction = PostgreSQLQuery.ForeignKeyAction - /// See `SchemaSupporting`. public static func enableReferences(on connection: PostgreSQLConnection) -> Future { // enabled by default diff --git a/Sources/FluentPostgreSQL/PostgreSQLDatabase+TransactionSupporting.swift b/Sources/FluentPostgreSQL/PostgreSQLDatabase+TransactionSupporting.swift new file mode 100644 index 0000000..86a7a6a --- /dev/null +++ b/Sources/FluentPostgreSQL/PostgreSQLDatabase+TransactionSupporting.swift @@ -0,0 +1,14 @@ +extension PostgreSQLDatabase: TransactionSupporting { + /// See `TransactionSupporting`. + public static func transactionExecute(_ transaction: @escaping (PostgreSQLConnection) throws -> Future, on connection: PostgreSQLConnection) -> Future { + return connection.simpleQuery("BEGIN TRANSACTION").flatMap { results in + return try transaction(connection).flatMap { res in + return connection.simpleQuery("END TRANSACTION").transform(to: res) + }.catchFlatMap { error in + return connection.simpleQuery("ROLLBACK").map { results in + throw error + } + } + } + } +} diff --git a/Sources/FluentPostgreSQL/PostgreSQLEnum.swift b/Sources/FluentPostgreSQL/PostgreSQLEnum.swift index 95e71f5..7a03321 100644 --- a/Sources/FluentPostgreSQL/PostgreSQLEnum.swift +++ b/Sources/FluentPostgreSQL/PostgreSQLEnum.swift @@ -1,26 +1,31 @@ -public protocol PostgreSQLEnum: PostgreSQLValueRepresentable, CaseIterable, Codable, ReflectionDecodable, PostgreSQLStaticColumnTypeRepresentable, RawRepresentable where Self.RawValue: LosslessStringConvertible { +public protocol PostgreSQLEnum: PostgreSQLExpressionRepresentable, CaseIterable, Codable, ReflectionDecodable, PostgreSQLDataTypeStaticRepresentable, RawRepresentable where Self.RawValue: LosslessStringConvertible { static var postgreSQLEnumTypeName: String { get } } extension PostgreSQLEnum { + /// See `PostgreSQLEnum`. public static var postgreSQLEnumTypeName: String { return "\(self)".uppercased() } - public static var postgreSQLColumnType: PostgreSQLColumnType { + /// See `PostgreSQLDataTypeStaticRepresentable`. + public static var postgreSQLDataType: PostgreSQLDataType { return .custom(postgreSQLEnumTypeName) } - public var postgreSQLValue: PostgreSQLQuery.Value { - return .expression(.stringLiteral(rawValue.description)) + /// See `PostgreSQLExpressionRepresentable`. + public var postgreSQLExpression: PostgreSQLExpression { + return .literal(.string(rawValue.description)) } } extension PostgreSQLEnum where Self: PostgreSQLMigration { + /// See `PostgreSQLMigration`. public static func prepare(on conn: PostgreSQLConnection) -> Future { return PostgreSQLDatabase.create(enum: self, on: conn) } + /// See `PostgreSQLMigration`. public static func revert(on conn: PostgreSQLConnection) -> Future { return PostgreSQLDatabase.drop(enum: self, on: conn) } @@ -29,23 +34,14 @@ extension PostgreSQLEnum where Self: PostgreSQLMigration { extension PostgreSQLDatabase { public static func create(enum: E.Type, on conn: PostgreSQLConnection) -> Future where E: PostgreSQLEnum { let cases = E.allCases.map { "'" + $0.rawValue.description + "'" }.joined(separator: ", ") - return conn.simpleQuery(.raw( - query: "CREATE TYPE \(E.postgreSQLEnumTypeName) AS ENUM (\(cases))", - binds: [] - )).transform(to: ()) + return conn.simpleQuery("CREATE TYPE \(E.postgreSQLEnumTypeName) AS ENUM (\(cases))") } public static func alter(enum: E.Type, add value: E, on conn: PostgreSQLConnection) -> Future where E: PostgreSQLEnum { - return conn.simpleQuery(.raw( - query: "ALTER TYPE \(E.postgreSQLEnumTypeName) ADD VALUE '\(value.rawValue.description)'", - binds: [] - )).transform(to: ()) + return conn.simpleQuery("ALTER TYPE \(E.postgreSQLEnumTypeName) ADD VALUE '\(value.rawValue.description)'") } public static func drop(enum: E.Type, on conn: PostgreSQLConnection) -> Future where E: PostgreSQLEnum { - return conn.simpleQuery(.raw( - query: "DROP TYPE \(E.postgreSQLEnumTypeName)", - binds: [] - )).transform(to: ()) + return conn.simpleQuery("DROP TYPE \(E.postgreSQLEnumTypeName)") } } diff --git a/Sources/FluentPostgreSQL/PostgreSQLModel.swift b/Sources/FluentPostgreSQL/PostgreSQLModel.swift index 3a2d402..4827f89 100644 --- a/Sources/FluentPostgreSQL/PostgreSQLModel.swift +++ b/Sources/FluentPostgreSQL/PostgreSQLModel.swift @@ -1,4 +1,13 @@ -public protocol PostgreSQLModel: Model where Self.Database == PostgreSQLDatabase, Self.ID == Int { +public protocol _PostgreSQLModel: Model, PostgreSQLTable where Self.Database == PostgreSQLDatabase { } + +extension _PostgreSQLModel { + /// See `SQLTable`. + public static var sqlTableIdentifierString: String { + return entity + } +} + +public protocol PostgreSQLModel: _PostgreSQLModel where Self.ID == Int { /// This model's unique identifier. var id: Int? { get set } } diff --git a/Sources/FluentPostgreSQL/PostgreSQLStringModel.swift b/Sources/FluentPostgreSQL/PostgreSQLStringModel.swift index 27f5051..d0d2d51 100644 --- a/Sources/FluentPostgreSQL/PostgreSQLStringModel.swift +++ b/Sources/FluentPostgreSQL/PostgreSQLStringModel.swift @@ -1,6 +1,4 @@ -import Foundation - -public protocol PostgreSQLStringModel: Model where Self.Database == PostgreSQLDatabase, Self.ID == String { +public protocol PostgreSQLStringModel: _PostgreSQLModel where Self.ID == String { /// This model's unique identifier. var id: String? { get set } } diff --git a/Sources/FluentPostgreSQL/PostgreSQLType.swift b/Sources/FluentPostgreSQL/PostgreSQLType.swift index 8d24289..8b13789 100644 --- a/Sources/FluentPostgreSQL/PostgreSQLType.swift +++ b/Sources/FluentPostgreSQL/PostgreSQLType.swift @@ -1,103 +1 @@ -public typealias PostgreSQLColumnType = PostgreSQLQuery.DataType -public protocol PostgreSQLStaticColumnTypeRepresentable { - /// Appropriate PostgreSQL column type for storing this type. - static var postgreSQLColumnType: PostgreSQLColumnType { get } -} - -/// MARK: Default Implementations - -extension Data: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .bytea } -} - -extension UUID: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .uuid } -} - -extension Date: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .timestamp(nil) } -} - -extension Int: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .bigint } -} - -extension Int8: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .char(nil) } -} - -extension Int16: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .smallint } -} - -extension Int32: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .int } -} - -extension Int64: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .bigint } -} - -extension UInt: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .bigint } -} - -extension UInt8: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .char(nil) } -} - -extension UInt16: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .smallint } -} - -extension UInt32: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .int } -} - -extension UInt64: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .bigint } -} - -extension Float: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .real } -} - -extension Double: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .doublePrecision } -} - -extension String: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .text } -} - -extension Bool: PostgreSQLStaticColumnTypeRepresentable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .bool } -} - -extension PostgreSQLPoint: PostgreSQLStaticColumnTypeRepresentable, ReflectionDecodable { - /// See `PostgreSQLStaticColumnTypeRepresentable`. - public static var postgreSQLColumnType: PostgreSQLColumnType { return .point } - - /// See `ReflectionDecodable`. - public static func reflectDecoded() throws -> (PostgreSQLPoint, PostgreSQLPoint) { - return (.init(x: 0, y: 0), .init(x: 1, y: 1)) - } -} diff --git a/Sources/FluentPostgreSQL/PostgreSQLUUIDModel.swift b/Sources/FluentPostgreSQL/PostgreSQLUUIDModel.swift index acf4ec0..6451a5f 100644 --- a/Sources/FluentPostgreSQL/PostgreSQLUUIDModel.swift +++ b/Sources/FluentPostgreSQL/PostgreSQLUUIDModel.swift @@ -1,4 +1,4 @@ -public protocol PostgreSQLUUIDModel: Model where Self.Database == PostgreSQLDatabase, Self.ID == UUID { +public protocol PostgreSQLUUIDModel: _PostgreSQLModel where Self.ID == UUID { /// This model's unique identifier. var id: UUID? { get set } } diff --git a/Sources/FluentPostgreSQL/SchemaBuilder+PostgreSQL.swift b/Sources/FluentPostgreSQL/SchemaBuilder+PostgreSQL.swift deleted file mode 100644 index 772052c..0000000 --- a/Sources/FluentPostgreSQL/SchemaBuilder+PostgreSQL.swift +++ /dev/null @@ -1,19 +0,0 @@ -extension SchemaBuilder where Model.Database == PostgreSQLDatabase { - public func field( - for keyPath: KeyPath, - type dataType: PostgreSQLQuery.DataType, - isArray: Bool = false, - collate: String? = nil, - _ constraints: PostgreSQLQuery.ColumnConstraint... - ) { - let property = FluentProperty.keyPath(keyPath) - let columnDefinition = PostgreSQLQuery.ColumnDefinition.init( - name: property.path[0], - dataType: dataType, - isArray: isArray, - collate: collate, - constraints: constraints - ) - schema.createColumns.append(columnDefinition) - } -} diff --git a/Tests/FluentPostgreSQLTests/FluentPostgreSQLTests.swift b/Tests/FluentPostgreSQLTests/FluentPostgreSQLTests.swift index e4d6825..a748d62 100644 --- a/Tests/FluentPostgreSQLTests/FluentPostgreSQLTests.swift +++ b/Tests/FluentPostgreSQLTests/FluentPostgreSQLTests.swift @@ -28,49 +28,9 @@ class FluentPostgreSQLTests: XCTestCase { let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1) benchmarker = try! Benchmarker(database, on: eventLoop, onFail: XCTFail) } - - func testSchema() throws { - try benchmarker.benchmarkSchema() - } - - 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 testBenchmark() throws { + try benchmarker.runAll() } func testNestedStruct() throws { @@ -91,10 +51,6 @@ class FluentPostgreSQLTests: XCTestCase { benchmarker.pool.releaseConnection(conn) } - func testIndexSupporting() throws { - try benchmarker.benchmarkIndexSupporting_withSchema() - } - func testMinimumViableModelDeclaration() throws { /// NOTE: these must never fail to build struct Foo: PostgreSQLModel { @@ -175,7 +131,7 @@ class FluentPostgreSQLTests: XCTestCase { static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture { return PostgreSQLDatabase.create(Pet.self, on: conn) { builder in - builder.field(for: \.id, type: .bigint, .notNull, .primaryKey, .generated(.byDefault)) + builder.field(for: \.id, type: .bigint, .notNull, .primaryKey(default: .generated(.byDefault))) builder.field(for: \.type, type: .bigint) builder.field(for: \.name, type: .text) } @@ -213,9 +169,7 @@ class FluentPostgreSQLTests: XCTestCase { static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture { return PostgreSQLDatabase.create(DefaultTest.self, on: conn) { builder in builder.field(for: \.id, isIdentifier: true) - builder.field(.init(name: "date", dataType: .timestamp(nil), constraints: [ - .default(.function("current_timestamp")) - ])) + builder.field(for: \.date, type: .timestamp, .default(.literal(.numeric("current_timestamp")))) builder.field(for: \.foo) } } @@ -250,46 +204,6 @@ class FluentPostgreSQLTests: XCTestCase { XCTAssertEqual(foo.location?.x, 1) XCTAssertEqual(foo.location?.y, 3.14) } - - func testBugs() throws { - try benchmarker.benchmarkBugs_withSchema() - } - - func testCodableSimple() throws { - let conn = try benchmarker.pool.requestConnection().wait() - defer { benchmarker.pool.releaseConnection(conn) } - - _ = try conn.simpleQuery("create table users (id int, name text)").wait() - defer { _ = try! conn.simpleQuery("drop table users").wait() } - - struct SimpleUser: Codable { - var id: Int - var name: String - } - - try XCTAssertEqual(SimpleUser.query("users", on: conn).count().wait(), 0) - - let user = SimpleUser(id: 1, name: "Vapor") - try SimpleUser.query("users", on: conn).create(data: user).wait() - - try XCTAssertEqual(SimpleUser.query("users", on: conn).count().wait(), 1) - try XCTAssertEqual(SimpleUser.query("users", on: conn).all().wait().count, 1) - - - struct SimpleUserComputed: Codable { - var id: Int - var name: String - var computed: String - } - - let res = try SimpleUserComputed - .query("users", on: conn) - .keys("id", "name", .expression(.function(.init(name: "md5", parameters: ["name"])), alias: "computed")) - .all() - .wait() - - print(res) - } // https://github.com/vapor/fluent-postgresql/issues/32 func testURL() throws { @@ -373,14 +287,14 @@ class FluentPostgreSQLTests: XCTestCase { let tanner3 = User(id: nil, name: "tan", age: 23) _ = try tanner3.save(on: conn).wait() - let tas = try User.query(on: conn).filter(\.name =~ "ta").count().wait() + let tas = try User.query(on: conn).filter(\.name, .like, "ta%").count().wait() if tas != 2 { XCTFail("tas == \(tas)") } -// let ers = try User.query(on: conn).filter(\.name ~= "er").count().wait() -// if ers != 2 { -// XCTFail("ers == \(tas)") -// } + let ers = try User.query(on: conn).filter(\.name, .like, "%er").count().wait() + if ers != 2 { + XCTFail("ers == \(tas)") + } let annes = try User.query(on: conn).filter(\.name ~~ "anne").count().wait() if annes != 1 { XCTFail("annes == \(tas)") @@ -445,34 +359,37 @@ class FluentPostgreSQLTests: XCTestCase { let earth = try Planet.query(on: conn).filter(\.name, .ilike, "earth").first().wait() XCTAssertEqual(earth?.name, "Earth") } + + func testCreateOrUpdate() throws { + let conn = try benchmarker.pool.requestConnection().wait() + defer { benchmarker.pool.releaseConnection(conn) } + defer { try? Planet.revert(on: conn).wait() } + try Planet.prepare(on: conn).wait() + + let a = Planet(id: 1, name: "Mars") + let b = Planet(id: 1, name: "Earth") + _ = try a.create(orUpdate: true, on: conn).wait() + _ = try b.create(orUpdate: true, on: conn).wait() + + let c = try Planet.find(1, on: conn).wait() + XCTAssertEqual(c?.name, "Earth") + } + 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), - ("testIndexSupporting", testIndexSupporting), ("testMinimumViableModelDeclaration", testMinimumViableModelDeclaration), ("testGH24", testGH24), ("testGH21", testGH21), ("testPersistsDateMillisecondPart", testPersistsDateMillisecondPart), ("testContains", testContains), ("testGH30", testGH30), - ("testBugs", testBugs), - ("testCodableSimple", testCodableSimple), ("testURL", testURL), ("testDocs_type", testDocs_type), ("testContains", testContains), ("testEmptySubset", testEmptySubset), ("testSort", testSort), ("testCustomFilter", testCustomFilter), + ("testCreateOrUpdate", testCreateOrUpdate), ] } @@ -485,8 +402,8 @@ struct Planet: PostgreSQLModel, PostgreSQLMigration, Equatable { } } -extension PostgreSQLColumnType { - static var planetType: PostgreSQLColumnType { +extension PostgreSQLDataType { + static var planetType: PostgreSQLDataType { return .custom("PLANET_TYPE") } }