From d6f70604a1253b6271bf8e56316c181a36afa0c9 Mon Sep 17 00:00:00 2001 From: Ira Rosen Date: Thu, 15 Jun 2017 14:21:06 +0300 Subject: [PATCH 1/6] IBM-Swift/Swift-Kuery-PostgreSQL#20 Don't read digits beyond the digits boundary --- .../PostgreSQLResultFetcher.swift | 18 ++++++++---- .../SwiftKueryPostgreSQLTests/TestTypes.swift | 29 ++++++++++++++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift b/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift index 673b3d0..bc0bdd4 100644 --- a/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift +++ b/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift @@ -154,6 +154,7 @@ public class PostgreSQLResultFetcher: ResultFetcher { case .numeric: // Numeric is a sequence of Int16's: number of digits, weight, sign, display scale, numeric digits + // https://www.postgresql.org/message-id/491DC5F3D279CD4EB4B157DDD62237F404E27FE9@zipwire.esri.com let sign = PostgreSQLResultFetcher.int16NetworkToHost(from: value.advanced(by: 4)) if sign == -16384 { // 0xC000 return "NaN" @@ -171,15 +172,20 @@ public class PostgreSQLResultFetcher: ResultFetcher { if weight >= 0 { for i in 0 ... weight { - let digitsAsInt16 = PostgreSQLResultFetcher.int16NetworkToHost(from: currentDigitData) - if i == 0 { - result += String(digitsAsInt16) + if currentDigitNumber < numberOfDigits { + let digitsAsInt16 = PostgreSQLResultFetcher.int16NetworkToHost(from: currentDigitData) + if i == 0 { + result += String(digitsAsInt16) + } + else { + result += String(format: "%04d", digitsAsInt16) + } + currentDigitData = currentDigitData.advanced(by: 2) + currentDigitNumber = i + 1 } else { - result += String(format: "%04d", digitsAsInt16) + result += "0000" } - currentDigitData = currentDigitData.advanced(by: 2) - currentDigitNumber = i + 1 } } diff --git a/Tests/SwiftKueryPostgreSQLTests/TestTypes.swift b/Tests/SwiftKueryPostgreSQLTests/TestTypes.swift index 40612ac..35eb8fe 100644 --- a/Tests/SwiftKueryPostgreSQLTests/TestTypes.swift +++ b/Tests/SwiftKueryPostgreSQLTests/TestTypes.swift @@ -267,10 +267,31 @@ class TestTypes: XCTestCase { XCTAssertEqual(rows![4][3]! as! String, negativeLongNumber, "Wrong value in row 4 column 3") XCTAssertEqual(rows![4][4]! as! String, "0", "Wrong value in row 4 column 4") - let drop = Raw(query: "DROP TABLE", table: t) - executeQuery(query: drop, connection: connection) { result, rows in - XCTAssertEqual(result.success, true, "DROP TABLE failed") - XCTAssertNil(result.asError, "Error in DELETE: \(result.asError!)") + i = Insert(into: t, values: "grape", "90000.0", "-400000000", "20000.000000000000000", "0.000000000000") + executeQuery(query: i, connection: connection) { result, rows in + XCTAssertEqual(result.success, true, "INSERT failed") + XCTAssertNil(result.asError, "Error in INSERT: \(result.asError!)") + + executeQuery(query: s, connection: connection) { result, rows in + XCTAssertEqual(result.success, true, "SELECT failed") + XCTAssertNil(result.asError, "Error in SELECT: \(result.asError!)") + XCTAssertNotNil(rows, "SELECT returned no rows") + XCTAssertEqual(rows!.count, 6, "SELECT returned wrong number of rows") + XCTAssertEqual(rows![5].count, 5, "SELECT returned wrong number of columns") + + XCTAssertEqual(rows![5][0]! as! String, "grape") + XCTAssertEqual(rows![5][1]! as! String, "90000") + XCTAssertEqual(rows![5][2]! as! String, "-400000000") + XCTAssertEqual(rows![5][3]! as! String, "20000") + XCTAssertEqual(rows![5][4]! as! String, "0") + + let drop = Raw(query: "DROP TABLE", table: t) + executeQuery(query: drop, connection: connection) { result, rows in + XCTAssertEqual(result.success, true, "DROP TABLE failed") + XCTAssertNil(result.asError, "Error in DELETE: \(result.asError!)") + + } + } } } } From 559f107a2231994d4de3128f2ae980eff270ed1b Mon Sep 17 00:00:00 2001 From: Ira Rosen Date: Thu, 15 Jun 2017 15:07:49 +0300 Subject: [PATCH 2/6] IBM-Swift/Swift-Kuery-PostgreSQL#20 Add a comment --- .../SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift b/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift index bc0bdd4..3589caf 100644 --- a/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift +++ b/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift @@ -153,7 +153,15 @@ public class PostgreSQLResultFetcher: ResultFetcher { return Float64(bitPattern: UInt64(bigEndian: data.withUnsafeBytes { $0.pointee } )) case .numeric: - // Numeric is a sequence of Int16's: number of digits, weight, sign, display scale, numeric digits + // Numeric is a sequence of Int16's: number of digits, weight, sign, display scale, numeric digits. + // The numeric digits are stored in the form of a series of 16 bit base-10000 numbers each representing + // four decimal digits of the original number. + // For example, for -12345.12 the numeric value received from PostgreSQL will be + // 00030001 40000002 00010929 04b0 + // The number of digits is 3, the digits are 0001 0929 04b0 (1 2345 12 decimal). + // The weight is 1, meaning there are two digits before the decimal point. + // The sign is 0x4000, meaning this is a negative number. + // The display scale is 2, meaning there are 2 "decimal" digits after the decimal point. // https://www.postgresql.org/message-id/491DC5F3D279CD4EB4B157DDD62237F404E27FE9@zipwire.esri.com let sign = PostgreSQLResultFetcher.int16NetworkToHost(from: value.advanced(by: 4)) if sign == -16384 { // 0xC000 From 255594b5aa66b233ac304cc83307498bb941817c Mon Sep 17 00:00:00 2001 From: Ira Rosen Date: Sun, 2 Jul 2017 14:59:06 +0300 Subject: [PATCH 3/6] IBM-Swift/Swift-Kuery-PostgreSQL#23 Add state to connection --- .../PostgreSQLConnection.swift | 75 ++++++++++++++++++- .../PostgreSQLResultFetcher.swift | 8 +- Sources/SwiftKueryPostgreSQL/Utils.swift | 11 ++- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift b/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift index a175a89..6fd62f6 100644 --- a/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift +++ b/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift @@ -19,16 +19,24 @@ import CLibpq import Foundation +enum ConnectionState { + case idle, runningQuery, fetchingResultSet +} + // MARK: PostgreSQLConnection /// An implementation of `SwiftKuery.Connection` protocol for PostgreSQL. /// Please see [PostgreSQL manual](https://www.postgresql.org/docs/8.0/static/libpq-exec.html) for details. public class PostgreSQLConnection: Connection { - private var connection: OpaquePointer? + var connection: OpaquePointer? private var connectionParameters: String = "" private var inTransaction = false + private var state: ConnectionState = .idle + private var stateLock = DispatchSemaphore(value: 1) + private weak var currentResultFetcher: PostgreSQLResultFetcher? + private var preparedStatements = Set() /// An indication whether there is a connection to the database. @@ -286,14 +294,20 @@ public class PostgreSQLConnection: Connection { } private func prepareStatement(name: String, for query: String) -> String? { + if let error = setUpForRunningQuery() { + return error + } + guard let result = PQprepare(connection, name, query, 0, nil), PQresultStatus(result) == PGRES_COMMAND_OK else { + setState(.idle) var errorMessage = "Failed to create prepared statement." if let error = String(validatingUTF8: PQerrorMessage(connection)) { errorMessage += " Error: \(error)." } return errorMessage } + setState(.idle) preparedStatements.insert(name) return nil } @@ -339,6 +353,11 @@ public class PostgreSQLConnection: Connection { return } + if let error = setUpForRunningQuery() { + onCompletion(.error(QueryError.connection(error))) + return + } + var parameterPointers = [UnsafeMutablePointer?]() var parameterData = [UnsafePointer?]() // At the moment we only create string parameters. Binary parameters should be added. @@ -388,6 +407,7 @@ public class PostgreSQLConnection: Connection { private func processQueryResult(query: String, onCompletion: @escaping ((QueryResult) -> ())) { guard let result = PQgetResult(connection) else { + setState(.idle) var errorMessage = "No result returned for query: \(query)." if let error = String(validatingUTF8: PQerrorMessage(connection)) { errorMessage += " Error: \(error)." @@ -400,16 +420,18 @@ public class PostgreSQLConnection: Connection { if status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK { // Since we set the single row mode, PGRES_TUPLES_OK means the result is empty, i.e. there are // no rows to return. - clearResult(result, connection: connection) + clearResult(result, connection: self) onCompletion(.successNoData) } else if status == PGRES_SINGLE_TUPLE { - let resultFetcher = PostgreSQLResultFetcher(queryResult: result, connection: connection) + let resultFetcher = PostgreSQLResultFetcher(queryResult: result, connection: self) + setState(.fetchingResultSet) + currentResultFetcher = resultFetcher onCompletion(.resultSet(ResultSet(resultFetcher))) } else { let errorMessage = String(validatingUTF8: PQresultErrorMessage(result)) ?? "Unknown" - clearResult(result, connection: connection) + clearResult(result, connection: self) onCompletion(.error(QueryError.databaseError("Query execution error:\n" + errorMessage + " For query: " + query))) } } @@ -472,6 +494,11 @@ public class PostgreSQLConnection: Connection { return } + if let error = setUpForRunningQuery() { + onCompletion(.error(QueryError.connection(error))) + return + } + let result = PQexec(connection, command) let status = PQresultStatus(result) if status != PGRES_COMMAND_OK { @@ -480,6 +507,7 @@ public class PostgreSQLConnection: Connection { message += " Error: \(error)." } PQclear(result) + setState(.idle) onCompletion(.error(QueryError.databaseError(message))) return } @@ -489,6 +517,45 @@ public class PostgreSQLConnection: Connection { } PQclear(result) + setState(.idle) onCompletion(.successNoData) } + + private func lockStateLock() { + _ = stateLock.wait(timeout: DispatchTime.distantFuture) + } + + private func unlockStateLock() { + stateLock.signal() + } + + func setState(_ newState: ConnectionState) { + lockStateLock() + if state == .fetchingResultSet { + currentResultFetcher = nil + } + state = newState + unlockStateLock() + } + + func setUpForRunningQuery() -> String? { + lockStateLock() + switch state { + case .runningQuery: + return "The connection is in the middle of running a query" + + case .fetchingResultSet: + currentResultFetcher?.hasMoreRows = false + clearResult(nil, connection: self) + + case .idle: + break + } + + state = .runningQuery + + unlockStateLock() + + return nil + } } diff --git a/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift b/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift index 3589caf..a57c131 100644 --- a/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift +++ b/Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift @@ -25,10 +25,10 @@ import Foundation public class PostgreSQLResultFetcher: ResultFetcher { private let titles: [String] private var row: [Any?]? - private var connection: OpaquePointer? - private var hasMoreRows = true + private var connection: PostgreSQLConnection + var hasMoreRows = true - init(queryResult: OpaquePointer, connection: OpaquePointer?) { + init(queryResult: OpaquePointer, connection: PostgreSQLConnection) { self.connection = connection let columns = PQnfields(queryResult) @@ -53,7 +53,7 @@ public class PostgreSQLResultFetcher: ResultFetcher { return nil } - guard let queryResult = PQgetResult(connection) else { + guard let queryResult = PQgetResult(connection.connection) else { // We are not supposed to get here, because we clear the result if we get PGRES_TUPLES_OK. hasMoreRows = false return nil diff --git a/Sources/SwiftKueryPostgreSQL/Utils.swift b/Sources/SwiftKueryPostgreSQL/Utils.swift index 1e4581c..1aa27d6 100644 --- a/Sources/SwiftKueryPostgreSQL/Utils.swift +++ b/Sources/SwiftKueryPostgreSQL/Utils.swift @@ -17,13 +17,16 @@ import CLibpq import Foundation -func clearResult(_ lastResult: OpaquePointer, connection: OpaquePointer?) { - PQclear(lastResult) - var result = PQgetResult(connection) +func clearResult(_ lastResult: OpaquePointer?, connection: PostgreSQLConnection) { + if let lastResult = lastResult { + PQclear(lastResult) + } + var result = PQgetResult(connection.connection) while result != nil { PQclear(result) - result = PQgetResult(connection) + result = PQgetResult(connection.connection) } + connection.setState(.idle) } From 605947ee7a02d3c708114406a9237e07968a7a61 Mon Sep 17 00:00:00 2001 From: Ira Rosen Date: Sun, 2 Jul 2017 15:17:57 +0300 Subject: [PATCH 4/6] IBM-Swift/Swift-Kuery-PostgreSQL#23 Import Dispatch for Linux --- Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift b/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift index 6fd62f6..f831085 100644 --- a/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift +++ b/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift @@ -17,6 +17,7 @@ import SwiftKuery import CLibpq +import Dispatch import Foundation enum ConnectionState { From 7b01f2dd21c2baa77bafbd7a8872eb2f25d2c02b Mon Sep 17 00:00:00 2001 From: Ira Rosen Date: Mon, 3 Jul 2017 10:30:54 +0300 Subject: [PATCH 5/6] IBM-Swift/Swift-Kuery-PostgreSQL#23 Add test, fix setUpForRunningQuery --- .../PostgreSQLConnection.swift | 4 ++ .../TestSelect.swift | 64 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift b/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift index f831085..a9b072f 100644 --- a/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift +++ b/Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift @@ -541,13 +541,17 @@ public class PostgreSQLConnection: Connection { func setUpForRunningQuery() -> String? { lockStateLock() + switch state { case .runningQuery: + unlockStateLock() return "The connection is in the middle of running a query" case .fetchingResultSet: currentResultFetcher?.hasMoreRows = false + unlockStateLock() clearResult(nil, connection: self) + lockStateLock() case .idle: break diff --git a/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift b/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift index e884470..bd2e1af 100644 --- a/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift +++ b/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift @@ -26,11 +26,13 @@ let tableSelect = "tableSelectLinux" let tableSelect2 = "tableSelect2Linux" let tableSelect3 = "tableSelect3Linux" let tableSelectDate = "tableSelectDateLinux" +let tableConnectionState = "tableConnectionStateLinux" #else let tableSelect = "tableSelectOSX" let tableSelect2 = "tableSelect2OSX" let tableSelect3 = "tableSelect3OSX" let tableSelectDate = "tableSelectDateOSX" +let tableConnectionState = "tableConnectionStateOSX" #endif class TestSelect: XCTestCase { @@ -40,6 +42,7 @@ class TestSelect: XCTestCase { ("testSelect", testSelect), ("testSelectDate", testSelectDate), ("testSelectFromMany", testSelectFromMany), + ("testConnectionState", testConnectionState), ] } @@ -367,4 +370,65 @@ class TestSelect: XCTestCase { }) } + + class ConnectionStateTable: Table { + let a = Column("a", Varchar.self, length: 30) + let b = Column("b", Int32.self) + + let tableName = tableConnectionState + } + + func testConnectionState() { + let t = ConnectionStateTable() + + let pool = CommonUtils.sharedInstance.getConnectionPool() + performTest(asyncTasks: { expectation in + + guard let connection = pool.getConnection() else { + XCTFail("Failed to get connection") + return + } + + cleanUp(table: t.tableName, connection: connection) { result in + + t.create(connection: connection) { result in + XCTAssertEqual(result.success, true, "CREATE TABLE failed") + XCTAssertNil(result.asError, "Error in CREATE TABLE: \(result.asError!)") + + let i = Insert(into: t, rows: [["apple", 1], ["apricot", 2], ["banana", 3], ["qiwi", -1], ["plum", -2], ["peach", -3]]) + executeQuery(query: i, connection: connection) { result, rows in + XCTAssertEqual(result.success, true, "INSERT failed") + + let s1 = Select(from: t).where(t.b > 0) + s1.execute(connection) { result1 in + print("executed select 1") + let s2 = Select(from: t).where(t.b < 0) + s2.execute(connection) { result2 in + print("executed select 2") + + XCTAssertEqual(result1.success, true, "SELECT 1 failed") + XCTAssertEqual(result2.success, true, "SELECT 2 failed") + + var rows: [[Any?]]? = nil + if let resultSet = result2.asResultSet { + rows = rowsAsArray(resultSet) + if let rows = rows { + for row in rows { + if let b = row[1] as? Int32 { + XCTAssertTrue(b < 0, "Bad result for SELECT") + } + else { + XCTFail("Wrong type in SELECT") + } + } + } + } + } + } + } + } + } + expectation.fulfill() + }) + } } From 9365b6b21423394e3da4a6ca0b24fe769e1e3664 Mon Sep 17 00:00:00 2001 From: Ira Rosen Date: Mon, 3 Jul 2017 10:34:29 +0300 Subject: [PATCH 6/6] Remove printings --- Tests/SwiftKueryPostgreSQLTests/TestSelect.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift b/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift index bd2e1af..66cab7a 100644 --- a/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift +++ b/Tests/SwiftKueryPostgreSQLTests/TestSelect.swift @@ -401,10 +401,8 @@ class TestSelect: XCTestCase { let s1 = Select(from: t).where(t.b > 0) s1.execute(connection) { result1 in - print("executed select 1") let s2 = Select(from: t).where(t.b < 0) s2.execute(connection) { result2 in - print("executed select 2") XCTAssertEqual(result1.success, true, "SELECT 1 failed") XCTAssertEqual(result2.success, true, "SELECT 2 failed")