From 91749f9b01bbf8b57567bc04d3e575c23e39f48e Mon Sep 17 00:00:00 2001 From: Rockford Wei Date: Tue, 21 Jul 2020 13:56:47 -0400 Subject: [PATCH 1/2] fixing the dangling ponters. --- .../PerfectPostgreSQL/PerfectPostgreSQL.swift | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift b/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift index d893370..8358308 100644 --- a/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift +++ b/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift @@ -329,6 +329,20 @@ public final class PGResult { } } +fileprivate extension Array where Element == UInt8 { + func getCCPointer() -> UnsafePointer? { + return self.withUnsafeBufferPointer { buffered -> UnsafePointer? in + return buffered.baseAddress?.withMemoryRebound(to: Int8.self, capacity: count) { $0 } + } + } +} + +fileprivate extension Array where Element == Int8 { + func getPointer() -> UnsafePointer? { + return self.withUnsafeBufferPointer { $0.baseAddress } + } +} + /// connection management class public final class PGConnection { @@ -421,19 +435,19 @@ public final class PGConnection { var aa = [UInt8](s.utf8) aa.append(0) temps.append(aa) - values[idx] = UnsafePointer(OpaquePointer(temps.last!)) + values[idx] = temps.last!.getCCPointer()! types[idx] = 0 lengths[idx] = 0 formats[idx] = 0 case let a as [UInt8]: let length = Int32(a.count) - values[idx] = UnsafePointer(OpaquePointer(a)) + values[idx] = a.getCCPointer()! types[idx] = 17 lengths[idx] = length formats[idx] = 1 case let a as [Int8]: let length = Int32(a.count) - values[idx] = UnsafePointer(OpaquePointer(a)) + values[idx] = a.getPointer()! types[idx] = 17 lengths[idx] = length formats[idx] = 1 @@ -441,7 +455,7 @@ public final class PGConnection { let a = d.map { $0 } let length = Int32(a.count) temps.append(a) - values[idx] = UnsafePointer(OpaquePointer(temps.last!)) + values[idx] = temps.last!.getCCPointer()! types[idx] = 17 lengths[idx] = length formats[idx] = 1 @@ -451,7 +465,7 @@ public final class PGConnection { var aa = [UInt8](asStrings.last!.utf8) aa.append(0) temps.append(aa) - values[idx] = UnsafePointer(OpaquePointer(temps.last!)) + values[idx] = temps.last!.getCCPointer()! } else { values[idx] = nil }//end if From a34d1383a1f8c907db23cd16641c26b1815cba45 Mon Sep 17 00:00:00 2001 From: Rockford Wei Date: Mon, 18 Jul 2022 12:07:37 -0400 Subject: [PATCH 2/2] remastering. --- .gitignore | 1 + .jazzy.yaml | 8 - .swiftlint.yml | 34 +++ Dockerfile | 4 + Package.swift | 32 +-- README.md | 26 ++ .../PerfectPostgreSQL/PerfectPostgreSQL.swift | 128 +++++----- Sources/PerfectPostgreSQL/PostgresCRUD.swift | 44 ++-- .../PostgresCRUDInsert.swift | 14 +- .../PostgresCRUDUpdate.swift | 4 +- Sources/libpq/libpq.h | 1 + Sources/libpq/module.modulemap | 5 + Sources/libpq/pq.c | 0 Tests/LinuxMain.swift | 6 - .../PerfectPostgreSQLTests.swift | 225 ++++++++---------- .../XCTestManifests.swift | 27 --- docker-compose.yml | 10 + docker-test.sh | 6 + 18 files changed, 289 insertions(+), 286 deletions(-) delete mode 100644 .jazzy.yaml create mode 100644 .swiftlint.yml create mode 100644 Dockerfile create mode 100644 Sources/libpq/libpq.h create mode 100644 Sources/libpq/module.modulemap create mode 100644 Sources/libpq/pq.c delete mode 100644 Tests/LinuxMain.swift delete mode 100644 Tests/PerfectPostgreSQLTests/XCTestManifests.swift create mode 100644 docker-compose.yml create mode 100755 docker-test.sh diff --git a/.gitignore b/.gitignore index d14c04b..d2ad8a7 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ fastlane/screenshots Packages/ PostgreSQL.xcodeproj/ .DS_Store +.swiftpm \ No newline at end of file diff --git a/.jazzy.yaml b/.jazzy.yaml deleted file mode 100644 index 44f457a..0000000 --- a/.jazzy.yaml +++ /dev/null @@ -1,8 +0,0 @@ -module: PerfectPostgreSQL -author: PerfectlySoft -author_url: https://perfect.org -github_url: https://github.com/PerfectlySoft/Perfect-PostgreSQL -copyright: '© 2016 PerfectlySoft Inc. and the Perfect project authors' -theme: fullwidth -xcodebuild_arguments: [-target, PostgreSQL, -toolchain, org.swift.3020160620a] -clean: true diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..8411c14 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,34 @@ +cyclomatic_complexity: + - 64 # warning + - 128 # error +file_length: + - 2048 # warning + - 4096 # error +function_body_length: + - 128 # warning + - 256 # error +line_length: + - 1024 # warning + - 2048 # error +type_body_length: + - 1024 # warning + - 2048 # error +disabled_rules: + - empty_enum_arguments + - force_cast + - function_parameter_count + - identifier_name + - inclusive_language + - large_tuple + - multiple_closures_with_trailing_closure + - nesting + - opening_brace + - redundant_optional_initialization + - syntactic_sugar + - unused_optional_binding + - vertical_parameter_alignment + - void_return +excluded: + - .build/ + - Sources/PerfectHTTP/Mime*.swift + - Sources/PerfectHTTPServer/HTTP2/HPACK.swift \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..326cb5d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +ARG arch +FROM rockywei/swift:5.6.$arch +RUN apt-get update -y +RUN apt-get install -y pkg-config libpq-dev diff --git a/Package.swift b/Package.swift index 8f0e8f0..ba589d8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.4 // Package.swift // Perfect-PostgreSQL // // Created by Kyle Jessup on 3/22/16. // Copyright (C) 2016 PerfectlySoft, Inc. // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // // This source file is part of the Perfect.org open source project // @@ -14,12 +14,11 @@ // // See http://perfect.org/licensing.html for license information // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // import PackageDescription -#if os(macOS) let package = Package( name: "PerfectPostgreSQL", platforms: [ @@ -29,27 +28,14 @@ let package = Package( .library(name: "PerfectPostgreSQL", targets: ["PerfectPostgreSQL"]) ], dependencies: [ - .package(url: "https://github.com/PerfectlySoft/Perfect-CRUD.git", from: "2.0.0"), - .package(url: "https://github.com/PerfectlySoft/Perfect-libpq.git", from: "2.0.0"), - ], - targets: [ - .target(name: "PerfectPostgreSQL", dependencies: ["PerfectCRUD"]), - .testTarget(name: "PerfectPostgreSQLTests", dependencies: ["PerfectPostgreSQL"]) - ] -) -#else -let package = Package( - name: "PerfectPostgreSQL", - products: [ - .library(name: "PerfectPostgreSQL", targets: ["PerfectPostgreSQL"]) + .package(url: "https://github.com/RockfordWei/Perfect.git", from: "5.6.13") ], - dependencies: [ - .package(url: "https://github.com/PerfectlySoft/Perfect-CRUD.git", from: "2.0.0"), - .package(url: "https://github.com/PerfectlySoft/Perfect-libpq-linux.git", from: "2.0.0"), - ], targets: [ - .target(name: "PerfectPostgreSQL", dependencies: ["PerfectCRUD"]), + .systemLibrary(name: "libpq", pkgConfig: "libpq", providers: [ + .apt(["libpq-dev"]), + .brew(["openssl", "postgres"]) + ]), + .target(name: "PerfectPostgreSQL", dependencies: ["libpq", .product(name: "PerfectCRUD", package: "Perfect")]), .testTarget(name: "PerfectPostgreSQLTests", dependencies: ["PerfectPostgreSQL"]) ] ) -#endif diff --git a/README.md b/README.md index 3ffad5b..93d56a5 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,29 @@ Add this project as a dependency in your Package.swift file. ## Documentation For more information, please visit [perfect.org](http://www.perfect.org/docs/PostgreSQL.html). + +## Update for Swift 5.4 + Perfect Classic + +``` swift +// swift-tools-version: 5.4 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "DemoApp", + dependencies: [ + .package(url: "https://github.com/RockfordWei/Perfect.git", from: "5.6.13"), + .package(url: "https://github.com/RockfordWei/Perfect-PostgreSQL.git", from: "5.6.0"), + ], + targets: [ + .executableTarget( + name: "DemoApp", + dependencies: [ + .product(name: "PerfectLib", package: "Perfect"), + .product(name: "PerfectPostgreSQL", package: "Perfect-PostgreSQL") + ]), + ] +) + +``` diff --git a/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift b/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift index 8358308..02b6152 100644 --- a/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift +++ b/Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift @@ -5,7 +5,7 @@ // Created by Kyle Jessup on 2015-07-29. // Copyright (C) 2015 PerfectlySoft, Inc. // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // // This source file is part of the Perfect.org open source project // @@ -14,21 +14,21 @@ // // See http://perfect.org/licensing.html for license information // -//===----------------------------------------------------------------------===// +// ===----------------------------------------------------------------------===// // import Foundation import libpq /// This enum type indicates an exception when dealing with a PostgreSQL database -public enum PostgreSQLError : Error { +public enum PostgreSQLError: Error { /// Error with detail message. case error(String) } /// result object public final class PGResult { - + /// Result Status enum public enum StatusType { case emptyQuery @@ -40,19 +40,19 @@ public final class PGResult { case singleTuple case unknown } - + private var res: OpaquePointer? = OpaquePointer(bitPattern: 0) private var borrowed = false - + init(_ res: OpaquePointer?, isBorrowed: Bool = false) { self.res = res self.borrowed = isBorrowed } - + deinit { close() } - + func isValid() -> Bool { return res != nil } @@ -61,7 +61,7 @@ public final class PGResult { public func close() { clear() } - + /// clear and disconnect result object public func clear() { if let res = self.res { @@ -71,7 +71,7 @@ public final class PGResult { self.res = OpaquePointer(bitPattern: 0) } } - + /// Result Status number as Int public func statusInt() -> Int { guard let res = self.res else { @@ -80,14 +80,14 @@ public final class PGResult { let s = PQresultStatus(res) return Int(s.rawValue) } - + /// Result Status Value public func status() -> StatusType { guard let res = self.res else { return .unknown } let s = PQresultStatus(res) - switch(s.rawValue) { + switch s.rawValue { case PGRES_EMPTY_QUERY.rawValue: return .emptyQuery case PGRES_COMMAND_OK.rawValue: @@ -107,7 +107,7 @@ public final class PGResult { } return .unknown } - + /// Result Status Message public func errorMessage() -> String { guard let res = self.res else { @@ -115,7 +115,7 @@ public final class PGResult { } return String(validatingUTF8: PQresultErrorMessage(res)) ?? "" } - + /// Result field count public func numFields() -> Int { guard let res = self.res else { @@ -123,7 +123,7 @@ public final class PGResult { } return Int(PQnfields(res)) } - + /// Field name for index value public func fieldName(index: Int) -> String? { guard let res = self.res, @@ -133,7 +133,7 @@ public final class PGResult { } return ret } - + /// Field type for index value public func fieldType(index: Int) -> Oid? { guard let res = self.res else { @@ -142,7 +142,7 @@ public final class PGResult { let fn = PQftype(res, Int32(index)) return fn } - + /// number of rows (Tuples) returned in result public func numTuples() -> Int { guard let res = self.res else { @@ -150,12 +150,12 @@ public final class PGResult { } return Int(PQntuples(res)) } - + /// test null field at row index for field index public func fieldIsNull(tupleIndex: Int, fieldIndex: Int) -> Bool { return 1 == PQgetisnull(res, Int32(tupleIndex), Int32(fieldIndex)) } - + /// return value for String field type with row and field indexes provided public func getFieldString(tupleIndex: Int, fieldIndex: Int) -> String? { guard !fieldIsNull(tupleIndex: tupleIndex, fieldIndex: fieldIndex), @@ -164,7 +164,7 @@ public final class PGResult { } return String(validatingUTF8: v) } - + /// return value for Bool field type with row and field indexes provided public func getFieldBool(tupleIndex: Int, fieldIndex: Int) -> Bool? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -172,7 +172,7 @@ public final class PGResult { } return s == "t" } - + /// return value for Int field type with row and field indexes provided public func getFieldInt(tupleIndex: Int, fieldIndex: Int) -> Int? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -180,7 +180,7 @@ public final class PGResult { } return Int(s) } - + /// return value for Int8 field type with row and field indexes provided public func getFieldInt8(tupleIndex: Int, fieldIndex: Int) -> Int8? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -188,7 +188,7 @@ public final class PGResult { } return Int8(s) } - + /// return value for Int16 field type with row and field indexes provided public func getFieldInt16(tupleIndex: Int, fieldIndex: Int) -> Int16? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -196,7 +196,7 @@ public final class PGResult { } return Int16(s) } - + /// return value for Int32 field type with row and field indexes provided public func getFieldInt32(tupleIndex: Int, fieldIndex: Int) -> Int32? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -204,7 +204,7 @@ public final class PGResult { } return Int32(s) } - + /// return value for Int64 field type with row and field indexes provided public func getFieldInt64(tupleIndex: Int, fieldIndex: Int) -> Int64? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -212,7 +212,7 @@ public final class PGResult { } return Int64(s) } - + /// return value for Int field type with row and field indexes provided public func getFieldUInt(tupleIndex: Int, fieldIndex: Int) -> UInt? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -220,7 +220,7 @@ public final class PGResult { } return UInt(s) } - + /// return value for Int8 field type with row and field indexes provided public func getFieldUInt8(tupleIndex: Int, fieldIndex: Int) -> UInt8? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -228,7 +228,7 @@ public final class PGResult { } return UInt8(s) } - + /// return value for Int16 field type with row and field indexes provided public func getFieldUInt16(tupleIndex: Int, fieldIndex: Int) -> UInt16? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -236,7 +236,7 @@ public final class PGResult { } return UInt16(s) } - + /// return value for Int32 field type with row and field indexes provided public func getFieldUInt32(tupleIndex: Int, fieldIndex: Int) -> UInt32? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -244,7 +244,7 @@ public final class PGResult { } return UInt32(s) } - + /// return value for Int64 field type with row and field indexes provided public func getFieldUInt64(tupleIndex: Int, fieldIndex: Int) -> UInt64? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -252,7 +252,7 @@ public final class PGResult { } return UInt64(s) } - + /// return value for Double field type with row and field indexes provided public func getFieldDouble(tupleIndex: Int, fieldIndex: Int) -> Double? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -260,7 +260,7 @@ public final class PGResult { } return Double(s) } - + /// return value for Float field type with row and field indexes provided public func getFieldFloat(tupleIndex: Int, fieldIndex: Int) -> Float? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -268,7 +268,7 @@ public final class PGResult { } return Float(s) } - + /// return value for Blob field type with row and field indexes provided public func getFieldBlob(tupleIndex: Int, fieldIndex: Int) -> [Int8]? { guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { @@ -292,18 +292,18 @@ public final class PGResult { } return ret } - + private func byteFromHexDigits(one c1v: Int8, two c2v: Int8) -> Int8? { - + let capA: Int8 = 65 let capF: Int8 = 70 let lowA: Int8 = 97 let lowF: Int8 = 102 let zero: Int8 = 48 let nine: Int8 = 57 - + var newChar = Int8(0) - + if c1v >= capA && c1v <= capF { newChar = c1v - capA + 10 } else if c1v >= lowA && c1v <= lowF { @@ -313,9 +313,9 @@ public final class PGResult { } else { return nil } - + newChar = newChar &* 16 - + if c2v >= capA && c2v <= capF { newChar += c2v - capA + 10 } else if c2v >= lowA && c2v <= lowF { @@ -345,37 +345,37 @@ fileprivate extension Array where Element == Int8 { /// connection management class public final class PGConnection { - + /// Connection Status enum public enum StatusType { case ok case bad } - + var conn = OpaquePointer(bitPattern: 0) var connectInfo: String = "" - + /// empty init public init() { - + } - + deinit { close() } - + /// Makes a new connection to the database server. public func connectdb(_ info: String) -> StatusType { conn = PQconnectdb(info) connectInfo = info return status() } - + /// Close db connection public func close() { finish() } - + /// Closes the connection to the server. Also frees memory used by the PGconn object. public func finish() { if conn != nil { @@ -383,28 +383,28 @@ public final class PGConnection { conn = OpaquePointer(bitPattern: 0) } } - + /// Returns the status of the connection. public func status() -> StatusType { let status = PQstatus(conn) return status == CONNECTION_OK ? .ok : .bad } - + /// Returns the error message most recently generated by an operation on the connection. public func errorMessage() -> String { return String(validatingUTF8: PQerrorMessage(conn)) ?? "" } - + /// Submits a command to the server and waits for the result. public func exec(statement: String) -> PGResult { return PGResult(PQexec(conn, statement)) } - + /// Sends data to the server during COPY_IN state. public func putCopyData(data: String) { PQputCopyData(self.conn, data, Int32(data.count)) } - + /// Sends end-of-data indication to the server during COPY_IN state. /// If withError is set, the copy is forced to fail with the error description supplied. public func putCopyEnd(withError: String? = nil) -> PGResult { @@ -412,7 +412,7 @@ public final class PGConnection { let result = PGResult(PQgetResult(self.conn)) return result } - + // !FIX! does not handle binary data /// Submits a command to the server and waits for the result, with the ability to pass parameters separately from the SQL command text. public func exec(statement: String, params: [Any?]) -> PGResult { @@ -468,7 +468,7 @@ public final class PGConnection { values[idx] = temps.last!.getCCPointer()! } else { values[idx] = nil - }//end if + } // end if types[idx] = 0 lengths[idx] = 0 formats[idx] = 0 @@ -477,7 +477,7 @@ public final class PGConnection { let r = PQexecParams(conn, statement, Int32(count), nil, values, lengths, formats, Int32(0)) return PGResult(r) } - + /// Assert that the connection status is OK /// /// - throws: If the connection status is bad @@ -490,10 +490,10 @@ public final class PGConnection { throw PostgreSQLError.error("Connection status is bad") } } - + /// The binding values public typealias ExecuteParameterArray = [Any?] - + /// Execute the given statement. /// /// - parameter statement: String statement to be executed @@ -516,7 +516,7 @@ public final class PGConnection { throw PostgreSQLError.error("Failed to execute statement. status: \(status)") } } - + /// Executes a BEGIN, calls the provided closure and executes a ROLLBACK if an exception occurs or a COMMIT if no exception occurs. /// /// - parameter closure: Block to be executed inside transaction @@ -534,19 +534,19 @@ public final class PGConnection { throw error } } - + /// Handler for receiving a PGResult public typealias ReceiverProc = (PGResult) -> Void - + /// Handler for processing a text message public typealias ProcessorProc = (String) -> Void - + /// internal callback for notice receiving internal var receiver: ReceiverProc = { _ in } - + /// internal callback for notice processing internal var processor: ProcessorProc = { _ in } - + /// Set a new notice receiver /// - parameter handler: a closure to handle the incoming notice /// - returns: a C convention function pointer; would be nil if failed to set. @@ -565,7 +565,7 @@ public final class PGConnection { this.receiver(pgresult) }, me) } - + /// Set a new notice processor /// - parameter handler: a closure to handle the incoming notice /// - returns: a C convention function pointer; would be nil if failed to set. diff --git a/Sources/PerfectPostgreSQL/PostgresCRUD.swift b/Sources/PerfectPostgreSQL/PostgresCRUD.swift index cf59816..f417863 100644 --- a/Sources/PerfectPostgreSQL/PostgresCRUD.swift +++ b/Sources/PerfectPostgreSQL/PostgresCRUD.swift @@ -39,7 +39,7 @@ extension PGResult { } return ret } - + private func byteFromHexDigits(one c1v: UInt8, two c2v: UInt8) -> UInt8? { let capA: UInt8 = 65 let capF: UInt8 = 70 @@ -71,14 +71,14 @@ extension PGResult { } } -class PostgresCRUDRowReader: KeyedDecodingContainerProtocol { +class PostgresCRUDRowReader: KeyedDecodingContainerProtocol { typealias Key = K var codingPath: [CodingKey] = [] var allKeys: [Key] = [] let results: PGResult let tupleIndex: Int - let fieldNames: [String:Int] - init(results r: PGResult, tupleIndex ti: Int, fieldNames fn: [String:Int]) { + let fieldNames: [String: Int] + init(results r: PGResult, tupleIndex ti: Int, fieldNames fn: [String: Int]) { results = r tupleIndex = ti fieldNames = fn @@ -137,7 +137,7 @@ class PostgresCRUDRowReader: KeyedDecodingContainerProtocol { func decode(_ type: String.Type, forKey key: Key) throws -> String { return results.getFieldString(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? "" } - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { let index = try ensureIndex(forKey: key) guard let special = SpecialType(type) else { throw CRUDDecoderError("Unsupported type: \(type) for key: \(key.stringValue)") @@ -193,7 +193,7 @@ class PostgresCRUDRowReader: KeyedDecodingContainerProtocol { return try T(from: decoder) } } - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { throw CRUDDecoderError("Unimplimented nestedContainer") } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { @@ -216,7 +216,7 @@ class PostgresGenDelegate: SQLGenDelegate { let connection: PGConnection var parentTableStack: [TableStructure] = [] var bindings: Bindings = [] - + init(connection c: PGConnection) { connection = c } @@ -240,12 +240,12 @@ class PostgresGenDelegate: SQLGenDelegate { if !policy.contains(.dropTable), policy.contains(.reconcileTable), let existingColumns = getExistingColumnData(forTable: forTable.tableName) { - let existingColumnMap: [String:PostgresColumnInfo] = .init(uniqueKeysWithValues: existingColumns.map { ($0.column_name, $0) }) - let newColumnMap: [String:TableStructure.Column] = .init(uniqueKeysWithValues: forTable.columns.map { ($0.name.lowercased(), $0) }) - + let existingColumnMap: [String: PostgresColumnInfo] = .init(uniqueKeysWithValues: existingColumns.map { ($0.column_name, $0) }) + let newColumnMap: [String: TableStructure.Column] = .init(uniqueKeysWithValues: forTable.columns.map { ($0.name.lowercased(), $0) }) + let addColumns = newColumnMap.keys.filter { existingColumnMap[$0] == nil } let removeColumns: [String] = existingColumnMap.keys.filter { newColumnMap[$0] == nil } - + sub += try removeColumns.map { return """ ALTER TABLE \(try quote(identifier: forTable.tableName)) DROP COLUMN \(try quote(identifier: $0)) @@ -271,7 +271,7 @@ class PostgresGenDelegate: SQLGenDelegate { try getCreateTableSQL(forTable: $0, policy: policy) } } - + return sub } func getExistingColumnData(forTable: String) -> [PostgresColumnInfo]? { @@ -401,13 +401,17 @@ class PostgresGenDelegate: SQLGenDelegate { } class PostgresExeDelegate: SQLExeDelegate { + func asyncExecute(completion: @escaping (SQLExeDelegate) -> ()) { + completion(self) + } + var nextBindings: Bindings = [] let connection: PGConnection let sql: String var results: PGResult? var tupleIndex = -1 var numTuples = 0 - var fieldNames: [String:Int] = [:] + var fieldNames: [String: Int] = [:] init(connection c: PGConnection, sql s: String) { connection = c sql = s @@ -420,13 +424,13 @@ class PostgresExeDelegate: SQLExeDelegate { nextBindings = nextBindings[0.. Bool { tupleIndex += 1 if nil == results { @@ -464,8 +468,8 @@ class PostgresExeDelegate: SQLExeDelegate { } return tupleIndex < numTuples } - - func next() throws -> KeyedDecodingContainer? where A : CodingKey { + + func next() throws -> KeyedDecodingContainer? where A: CodingKey { guard let results = self.results else { return nil } @@ -474,7 +478,7 @@ class PostgresExeDelegate: SQLExeDelegate { fieldNames: fieldNames)) return ret } - + private func bindOne(expr: CRUDExpression) throws -> Any? { switch expr { case .lazy(let e): @@ -531,7 +535,7 @@ class PostgresExeDelegate: SQLExeDelegate { public struct PostgresDatabaseConfiguration: DatabaseConfigurationProtocol { let connection: PGConnection - + public init(url: String?, name: String?, host: String?, @@ -547,7 +551,7 @@ public struct PostgresDatabaseConfiguration: DatabaseConfigurationProtocol { try self.init(database: database, host: host, port: port, username: user, password: pass) } } - + public init(database: String, host: String, port: Int? = nil, username: String? = nil, password: String? = nil) throws { var s = "host=\(host) dbname=\(database)" if let p = port { diff --git a/Sources/PerfectPostgreSQL/PostgresCRUDInsert.swift b/Sources/PerfectPostgreSQL/PostgresCRUDInsert.swift index 1185851..81d4279 100644 --- a/Sources/PerfectPostgreSQL/PostgresCRUDInsert.swift +++ b/Sources/PerfectPostgreSQL/PostgresCRUDInsert.swift @@ -46,14 +46,14 @@ private func _insert(fromTab } return n } - + let encoder = try CRUDBindingsEncoder(delegate: delegate) try instances[0].encode(to: encoder) - + let bindings = try encoder.completedBindings(allKeys: includeNames, ignoreKeys: Set(excludeNames)) let columnNames = try bindings.map { try delegate.quote(identifier: $0.column) } let bindIdentifiers = bindings.map { $0.identifier } - + let nameQ = try delegate.quote(identifier: "\(OAF.CRUDTableName)") let sqlStr = """ INSERT INTO \(nameQ) (\(columnNames.joined(separator: ", "))) @@ -125,14 +125,14 @@ private func _insert(fromTab } return n } - + let encoder = try CRUDBindingsEncoder(delegate: delegate) try instance.encode(to: encoder) state.bindingsEncoder = encoder @@ -106,7 +106,7 @@ private func _update Database { if reset { @@ -43,7 +52,7 @@ class PerfectPostgreSQLTests: XCTestCase { case id, name, integer = "int", double = "doub", blob, subTables } static let tableName = "test_table_1" - + @PrimaryKey var id: Int let name: String? let integer: Int? @@ -64,7 +73,7 @@ class PerfectPostgreSQLTests: XCTestCase { self.subTables = subTables } } - + struct TestTable2: Codable { @PrimaryKey var id: UUID @ForeignKey(TestTable1.self, onDelete: cascade, onUpdate: cascade) var parentId: Int @@ -89,7 +98,7 @@ class PerfectPostgreSQLTests: XCTestCase { self.parentId = parentId } } - + override func setUp() { super.setUp() CRUDClearTableStructureCache() @@ -98,7 +107,7 @@ class PerfectPostgreSQLTests: XCTestCase { CRUDLogging.flush() super.tearDown() } - + func testCreate1() { do { let db = try getDB() @@ -152,7 +161,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testCreate2() { do { let db = try getTestDB() @@ -187,7 +196,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testCreate3() { struct FakeTestTable1: Codable, TableNameProvider { enum CodingKeys: String, CodingKey { @@ -204,7 +213,7 @@ class PerfectPostgreSQLTests: XCTestCase { do { let db = try getTestDB() try db.create(TestTable1.self, policy: [.dropTable, .shallow]) - + do { let t1 = db.table(TestTable1.self) let newOne = TestTable1(id: 2000, name: "New One", integer: 40) @@ -224,16 +233,14 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func getTestDB() throws -> Database { do { let db = try getDB() try db.create(TestTable1.self, policy: .dropTable) - try db.transaction { - () -> () in + try db.transaction { () -> () in try db.table(TestTable1.self) - .insert((1...testDBRowCount).map { - num -> TestTable1 in + .insert((1...testDBRowCount).map { num -> TestTable1 in let n = UInt8(num) let blob: [UInt8]? = (num % 2 != 0) ? nil : [UInt8](arrayLiteral: n+1, n+2, n+3, n+4, n+5) return TestTable1(id: num, @@ -246,10 +253,8 @@ class PerfectPostgreSQLTests: XCTestCase { try db.transaction { () -> () in try db.table(TestTable2.self) - .insert((1...testDBRowCount).flatMap { - parentId -> [TestTable2] in - return (1...testDBRowCount).map { - num -> TestTable2 in + .insert((1...testDBRowCount).flatMap { parentId -> [TestTable2] in + return (1...testDBRowCount).map { num -> TestTable2 in let n = UInt8(num) let blob: [UInt8]? = [UInt8](arrayLiteral: n+1, n+2, n+3, n+4, n+5) return TestTable2(id: UUID(), @@ -267,7 +272,7 @@ class PerfectPostgreSQLTests: XCTestCase { } return try getDB(reset: false) } - + func testSelectAll() { do { let db = try getTestDB() @@ -279,7 +284,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectIn() { do { let db = try getTestDB() @@ -290,7 +295,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectLikeString() { do { let db = try getTestDB() @@ -305,7 +310,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectJoin() { do { let db = try getTestDB() @@ -314,7 +319,7 @@ class PerfectPostgreSQLTests: XCTestCase { .join(\.subTables, on: \.id, equals: \.parentId) .order(by: \.id) .where(\TestTable2.name == "me") - + let j2c = try j2.count() let j2a = try j2.select().map{$0} let j2ac = j2a.count @@ -327,7 +332,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testInsert1() { do { let db = try getTestDB() @@ -342,7 +347,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testInsert2() { do { let db = try getTestDB() @@ -358,7 +363,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testInsert3() { do { let db = try getTestDB() @@ -376,7 +381,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testUpdate() { do { let db = try getTestDB() @@ -400,7 +405,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testDelete() { do { let db = try getTestDB() @@ -417,7 +422,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectLimit() { do { let db = try getTestDB() @@ -473,7 +478,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectLimitWhere() { do { let db = try getTestDB() @@ -484,7 +489,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectOrderLimitWhere() { do { let db = try getTestDB() @@ -495,7 +500,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testSelectWhereNULL() { do { let db = try getTestDB() @@ -509,7 +514,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + // this is the general-overview example used in the readme func testPersonThing() { do { @@ -525,40 +530,40 @@ class PerfectPostgreSQLTests: XCTestCase { let lastName: String let phoneNumbers: [PhoneNumber]? } - + // CRUD usage begins by creating a database connection. // The inputs for connecting to a database will differ depending on your client library. // Create a `Database` object by providing a configuration. // All code would be identical regardless of the datasource type. let db = try getTestDB() - + // Create the table if it hasn't been done already. // Table creates are recursive by default, so "PhoneNumber" is also created here. try db.create(Person.self, policy: .reconcileTable) - + // Get a reference to the tables we will be inserting data into. let personTable = db.table(Person.self) let numbersTable = db.table(PhoneNumber.self) - + // Add an index for personId, if it does not already exist. try numbersTable.index(\.personId) - + // Insert some sample data. do { // Insert some sample data. let owen = Person(id: UUID(), firstName: "Owen", lastName: "Lars", phoneNumbers: nil) let beru = Person(id: UUID(), firstName: "Beru", lastName: "Lars", phoneNumbers: nil) - + // Insert the people try personTable.insert([owen, beru]) - + // Give them some phone numbers try numbersTable.insert([ PhoneNumber(personId: owen.id, planetCode: 12, number: "555-555-1212"), PhoneNumber(personId: owen.id, planetCode: 15, number: "555-555-2222"), PhoneNumber(personId: beru.id, planetCode: 12, number: "555-555-1212")]) } - + // Perform a query. // Let's find all people with the last name of Lars which have a phone number on planet 12. let query = try personTable @@ -567,7 +572,7 @@ class PerfectPostgreSQLTests: XCTestCase { .order(descending: \.planetCode) .where(\Person.lastName == "Lars" && \PhoneNumber.planetCode == 12) .select() - + // Loop through the results and print the names. for user in query { // We joined PhoneNumbers, so we should have values here. @@ -583,7 +588,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testStandardJoin() { do { let db = try getTestDB() @@ -612,7 +617,7 @@ class PerfectPostgreSQLTests: XCTestCase { on: \.id, equals: \.parentId) .where(\Parent.id == 1) - + guard let parent = try join.first() else { return XCTFail("Failed to find parent id: 1") } @@ -628,7 +633,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testJunctionJoin() { do { struct Student: Codable { @@ -702,7 +707,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + // swiftlint:disable type_name func testSelfJoin() { do { struct Me: Codable { @@ -740,7 +745,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + // swiftlint:disable type_name func testSelfJunctionJoin() { do { struct Me: Codable { @@ -782,7 +787,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testCodableProperty() { do { struct Sub: Codable { @@ -805,7 +810,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testBadDecoding() { do { struct Top: Codable, TableNameProvider { @@ -824,7 +829,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("Should not have a valid object.") } catch {} } - + func testAllPrimTypes1() { struct AllTypes: Codable { let int: Int @@ -849,7 +854,7 @@ class PerfectPostgreSQLTests: XCTestCase { try db.create(AllTypes.self, policy: .dropTable) let model = AllTypes(int: 1, uint: 2, int64: 3, uint64: 4, int32: 5, uint32: 6, int16: 7, uint16: 8, int8: 9, uint8: 10, double: 11, float: 12, string: "13", bytes: [1, 4], ubytes: [1, 4], b: true) try db.table(AllTypes.self).insert(model) - + guard let f = try db.table(AllTypes.self).where(\AllTypes.int == 1).first() else { return XCTFail("Nil result.") } @@ -877,7 +882,7 @@ class PerfectPostgreSQLTests: XCTestCase { try db.create(AllTypes.self, policy: .dropTable) let model = AllTypes(int: 1, uint: 2, int64: -3, uint64: 4, int32: nil, uint32: nil, int16: -7, uint16: 8, int8: nil, uint8: nil, double: -11, float: -12, string: "13", bytes: [1, 4], ubytes: nil, b: true) try db.table(AllTypes.self).insert(model) - + guard let f = try db.table(AllTypes.self) .where(\AllTypes.int == 1).first() else { return XCTFail("Nil result.") @@ -902,7 +907,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testAllPrimTypes2() { struct AllTypes2: Codable { func equals(rhs: AllTypes2) -> Bool { @@ -953,7 +958,7 @@ class PerfectPostgreSQLTests: XCTestCase { let ubytes: [UInt8]? let b: Bool? } - + do { let db = try getTestDB() try db.create(AllTypes2.self, policy: .dropTable) @@ -1033,7 +1038,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testBespokeSQL() { do { let db = try getTestDB() @@ -1049,7 +1054,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testModelClasses() { class BaseClass: Codable { let id: Int @@ -1072,7 +1077,7 @@ class PerfectPostgreSQLTests: XCTestCase { try container.encode(name, forKey: .name) } } - + class SubClass: BaseClass { let another: String private enum CodingKeys: String, CodingKey { @@ -1093,14 +1098,14 @@ class PerfectPostgreSQLTests: XCTestCase { try super.encode(to: encoder) } } - + do { let db = try getTestDB() try db.create(SubClass.self) let table = db.table(SubClass.self) let obj = SubClass(id: 1, name: "The name", another: "And another thing") try table.insert(obj) - + guard let found = try table.where(\SubClass.id == 1).first() else { return XCTFail("Did not find SubClass") } @@ -1110,7 +1115,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testURL() { do { let db = try getTestDB() @@ -1131,51 +1136,51 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testManyJoins() { do { let db = try getTestDB() - struct Person2 : Codable{ - var id : UUID - var name : String - let cars : [Car]? - let boats : [Boat]? - let houses : [House]? + struct Person2: Codable{ + var id: UUID + var name: String + let cars: [Car]? + let boats: [Boat]? + let houses: [House]? } - struct Car : Codable{ - var id : UUID - var owner : UUID + struct Car: Codable{ + var id: UUID + var owner: UUID } - struct Boat : Codable{ - var id : UUID - var owner : UUID + struct Boat: Codable{ + var id: UUID + var owner: UUID } - struct House : Codable{ - var id : UUID - var owner : UUID + struct House: Codable{ + var id: UUID + var owner: UUID } try db.create(Person2.self) try db.table(Car.self).index(\.owner) try db.table(Boat.self).index(\.owner) try db.table(House.self).index(\.owner) - + let t1 = db.table(Person2.self) let parentId = UUID() let person = Person2(id: parentId, name: "The Person", cars: nil, boats: nil, houses: nil) try t1.insert(person) - + for _ in 0..<5 { try db.table(Car.self).insert(.init(id: UUID(), owner: parentId)) try db.table(Boat.self).insert(.init(id: UUID(), owner: parentId)) try db.table(House.self).insert(.init(id: UUID(), owner: parentId)) } - + let j1 = try t1.join(\.cars, on: \.id, equals: \.owner) .join(\.boats, on: \.id, equals: \.owner) .join(\.houses, on: \.id, equals: \.owner) .where(\Person2.id == parentId) guard let j2 = try j1.first() else { - return XCTFail() + return XCTFail("many join fault") } XCTAssertEqual(5, j2.cars?.count) XCTAssertEqual(5, j2.boats?.count) @@ -1184,7 +1189,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testDateFormat() { let fmt = Date(fromISO8601: "2018-08-18 08:10:51-04") XCTAssertNotNil(fmt) @@ -1195,7 +1200,7 @@ class PerfectPostgreSQLTests: XCTestCase { let fmt3 = Date(fromISO8601: "2018-08-18 08:10:51.43Z") XCTAssertNotNil(fmt3) } - + func testAssets() { struct Asset: Codable { let id: UUID @@ -1209,7 +1214,7 @@ class PerfectPostgreSQLTests: XCTestCase { assetLog = log } } - + struct AssetLog: Codable { let assetId: UUID let userId: UUID @@ -1222,7 +1227,7 @@ class PerfectPostgreSQLTests: XCTestCase { self.returned = returned } } - + do { let db = try getTestDB() try db.create(Asset.self, policy: .dropTable) @@ -1244,9 +1249,9 @@ class PerfectPostgreSQLTests: XCTestCase { } catch { XCTFail("\(error)") } - + } - + func testReturningInsert() { do { let db = try getTestDB() @@ -1291,7 +1296,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testReturningUpdate() { do { let db = try getTestDB() @@ -1319,7 +1324,7 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - + func testEmptyInsert() { do { let db = try getTestDB() @@ -1334,7 +1339,7 @@ class PerfectPostgreSQLTests: XCTestCase { try db.sql("DROP TABLE IF EXISTS \(ReturningItem.CRUDTableName)") try db.sql("CREATE TABLE \(ReturningItem.CRUDTableName) (id SERIAL PRIMARY KEY, def INT DEFAULT 42)") let table = db.table(ReturningItem.self) - + try table .insert(ReturningItem(id: 0, def: 0), ignoreKeys: \ReturningItem.id, \ReturningItem.def) @@ -1345,42 +1350,4 @@ class PerfectPostgreSQLTests: XCTestCase { XCTFail("\(error)") } } - - static var allTests = [ - ("testCreate1", testCreate1), - ("testCreate2", testCreate2), - ("testCreate3", testCreate3), - ("testSelectAll", testSelectAll), - ("testSelectIn", testSelectIn), - ("testSelectLikeString", testSelectLikeString), - ("testSelectJoin", testSelectJoin), - ("testInsert1", testInsert1), - ("testInsert2", testInsert2), - ("testInsert3", testInsert3), - ("testUpdate", testUpdate), - ("testDelete", testDelete), - ("testSelectLimit", testSelectLimit), - ("testSelectLimitWhere", testSelectLimitWhere), - ("testSelectOrderLimitWhere", testSelectOrderLimitWhere), - ("testSelectWhereNULL", testSelectWhereNULL), - ("testPersonThing", testPersonThing), - ("testStandardJoin", testStandardJoin), - ("testJunctionJoin", testJunctionJoin), - ("testSelfJoin", testSelfJoin), - ("testSelfJunctionJoin", testSelfJunctionJoin), - ("testCodableProperty", testCodableProperty), - ("testBadDecoding", testBadDecoding), - ("testAllPrimTypes1", testAllPrimTypes1), - ("testAllPrimTypes2", testAllPrimTypes2), - ("testBespokeSQL", testBespokeSQL), - ("testModelClasses", testModelClasses), - ("testURL", testURL), - ("testManyJoins", testManyJoins), - ("testDateFormat", testDateFormat), - ("testAssets", testAssets), - ("testReturningInsert", testReturningInsert), - ("testReturningUpdate", testReturningUpdate), - ("testEmptyInsert", testEmptyInsert) - ] } - diff --git a/Tests/PerfectPostgreSQLTests/XCTestManifests.swift b/Tests/PerfectPostgreSQLTests/XCTestManifests.swift deleted file mode 100644 index 0edb911..0000000 --- a/Tests/PerfectPostgreSQLTests/XCTestManifests.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// XCTestManifests.swift -// -// Created by Kyle Jessup on 2015-10-19. -// Copyright © 2015 PerfectlySoft. All rights reserved. -// -//===----------------------------------------------------------------------===// -// -// This source file is part of the Perfect.org open source project -// -// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors -// Licensed under Apache License v2.0 -// -// See http://perfect.org/licensing.html for license information -// -//===----------------------------------------------------------------------===// -// - -import XCTest - -#if !os(OSX) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(PerfectPostgreSQLTests.allTests) - ] -} -#endif diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..093233b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.3' +services: + postgres: + image: postgres:latest + ports: + - 5432:5432 + environment: + - POSTGRES_DB=testing123 + - POSTGRES_USER=root + - POSTGRES_PASSWORD=secret diff --git a/docker-test.sh b/docker-test.sh new file mode 100755 index 0000000..d149f32 --- /dev/null +++ b/docker-test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +arc=$(arch) +repo=rockywei/perfectpostgres:5.6.$arc +docker build -t $repo --build-arg arch=$arc . +docker run -it -v $PWD:/home -w /home --network perfect-postgresql_default $repo /bin/bash -c \ +"swift test"