Skip to content

Commit

Permalink
Support Swift 5 (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
kilnerm committed Apr 10, 2019
1 parent b7a0aa0 commit b0d44d2
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2.3
5.0
15 changes: 12 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ matrix:
dist: xenial
sudo: required
services: docker
env: DOCKER_IMAGE=swift:4.2.3 DOCKER_PACKAGES="libpq-dev postgresql postgresql-contrib locales locales-all" CUSTOM_BUILD_SCRIPT=.build-docker
env: DOCKER_IMAGE=swift:4.2.4 DOCKER_PACKAGES="libpq-dev postgresql postgresql-contrib locales locales-all" CUSTOM_BUILD_SCRIPT=.build-docker SWIFT_SNAPSHOT=4.2.4
- os: linux
dist: xenial
sudo: required
services: docker
env: DOCKER_IMAGE=swift:4.2.3 SWIFT_SNAPSHOT=$SWIFT_DEVELOPMENT_SNAPSHOT DOCKER_PACKAGES="libpq-dev postgresql postgresql-contrib locales locales-all" CUSTOM_BUILD_SCRIPT=.build-docker
env: DOCKER_IMAGE=swift:5.0-xenial DOCKER_PACKAGES="libpq-dev postgresql postgresql-contrib locales locales-all" CUSTOM_BUILD_SCRIPT=.build-docker
- os: linux
dist: xenial
sudo: required
services: docker
env: DOCKER_IMAGE=swift:5.0 SWIFT_SNAPSHOT=$SWIFT_DEVELOPMENT_SNAPSHOT DOCKER_PACKAGES="libpq-dev postgresql postgresql-contrib locales locales-all" CUSTOM_BUILD_SCRIPT=.build-docker
- os: osx
osx_image: xcode9.2
sudo: required
Expand All @@ -44,8 +49,12 @@ matrix:
- os: osx
osx_image: xcode10.1
sudo: required
env: SWIFT_SNAPSHOT=4.2.1
- os: osx
osx_image: xcode10.1
osx_image: xcode10.2
sudo: required
- os: osx
osx_image: xcode10.2
sudo: required
env: SWIFT_SNAPSHOT=$SWIFT_DEVELOPMENT_SNAPSHOT

Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// swift-tools-version:4.2
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

/**
* Copyright IBM Corporation 2016, 2018
* Copyright IBM Corporation 2016, 2018, 2019
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
53 changes: 53 additions & 0 deletions Package@swift-4.2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// swift-tools-version:4.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

/**
* Copyright IBM Corporation 2016, 2018, 2019
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import PackageDescription

let package = Package(
name: "SwiftKueryPostgreSQL",
products: [
.library(
name: "SwiftKueryPostgreSQL",
targets: ["SwiftKueryPostgreSQL"]
)
],
dependencies: [
.package(url: "https://github.com/IBM-Swift/Swift-Kuery.git", from: "3.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.systemLibrary(
name: "CLibpq",
pkgConfig: "libpq",
providers: [
.brew(["postgresql"]),
.apt(["libpq-dev"])
]
),
.target(
name: "SwiftKueryPostgreSQL",
dependencies: ["SwiftKuery", "CLibpq"]
),
.testTarget(
name: "SwiftKueryPostgreSQLTests",
dependencies: ["SwiftKueryPostgreSQL"]
)
]
)
10 changes: 9 additions & 1 deletion Sources/SwiftKueryPostgreSQL/PostgreSQLConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,15 @@ public class PostgreSQLConnection: Connection {
runCompletionHandler(.successNoData, onCompletion: onCompletion)
}
else if status == PGRES_SINGLE_TUPLE {
PostgreSQLResultFetcher.create(queryResult: result, connection: self) { resultFetcher in
PostgreSQLResultFetcher.create(queryResult: result, connection: self) { resultFetcher, error in
guard let resultFetcher = resultFetcher else {
guard let error = error else {
clearResult(result, connection: self)
return runCompletionHandler(.error(QueryError.databaseError("Unknown error creating ResultFetcher")), onCompletion: onCompletion)
}
clearResult(result, connection: self)
return runCompletionHandler(.error(error), onCompletion: onCompletion)
}
self.setState(.fetchingResultSet)
self.currentResultFetcher = resultFetcher
runCompletionHandler(.resultSet(ResultSet(resultFetcher, connection: self)), onCompletion: onCompletion)
Expand Down
84 changes: 58 additions & 26 deletions Sources/SwiftKueryPostgreSQL/PostgreSQLResultFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ public class PostgreSQLResultFetcher: ResultFetcher {
clearResult(queryResult, connection: self.connection)
return callback((nil, QueryError.noResult("Error retrieving result from database")))
}
return callback((self.buildRow(queryResult: queryResult), nil))
let builtRow = self.buildRow(queryResult: queryResult)
guard let row = builtRow.row else {
return callback((nil, builtRow.error))
}
return callback((row, nil))
}
}

Expand All @@ -68,19 +72,26 @@ public class PostgreSQLResultFetcher: ResultFetcher {
callback((self.titles, nil))
}

private func buildRow(queryResult: OpaquePointer) -> [Any?] {
private func buildRow(queryResult: OpaquePointer) -> (row: [Any?]?, error: Error?) {
let columns = PQnfields(queryResult)
defer {
PQclear(queryResult)
}
var row = [Any?]()
for column in 0 ..< columns {
if PQgetisnull(queryResult, 0, column) == 1 {
row.append(nil)
}
else {
row.append(PostgreSQLResultFetcher.convert(queryResult, row: 0, column: column))
let convertedValue = PostgreSQLResultFetcher.convert(queryResult, row: 0, column: column)
guard let value = convertedValue.value else {
return (nil, convertedValue.error)
}

row.append(value)
}
}
PQclear(queryResult)
return row
return (row, nil)
}

/// Indicate no further calls will be made to this ResultFetcher allowing the connection in use to be released.
Expand All @@ -89,19 +100,19 @@ public class PostgreSQLResultFetcher: ResultFetcher {
clearResult(nil, connection: connection)
}

private static func convert(_ queryResult: OpaquePointer, row: Int32, column: Int32) -> Any {
private static func convert(_ queryResult: OpaquePointer, row: Int32, column: Int32) -> (value: Any?, error: Error?) {
let value = PQgetvalue(queryResult, row, column)
let count = Int(PQgetlength(queryResult, row, column))
let data = Data(bytes: value!, count: count)

let type = PostgreSQLType(rawValue: PQftype(queryResult, column))

if PQfformat(queryResult, column) == 0 {
return String(data: data, encoding: String.Encoding.utf8) ?? ""
return (String(data: data, encoding: String.Encoding.utf8) ?? "", nil)
}
else {
guard let type = type, let value = value else {
return data
return (data, nil)
}

switch type {
Expand All @@ -118,23 +129,40 @@ public class PostgreSQLResultFetcher: ResultFetcher {
case .json:
fallthrough
case .xml:
return String(cString: value)
return (String(cString: value), nil)

case .int2:
return PostgreSQLResultFetcher.int16NetworkToHost(from: value)
return (PostgreSQLResultFetcher.int16NetworkToHost(from: value), nil)

case .int4:
return Int32(bigEndian: value.withMemoryRebound(to: Int32.self, capacity: 1) { $0.pointee })
return (Int32(bigEndian: value.withMemoryRebound(to: Int32.self, capacity: 1) { $0.pointee }), nil)

case .int8:
return Int64(bigEndian: value.withMemoryRebound(to: Int64.self, capacity: 1) { $0.pointee })
return (Int64(bigEndian: value.withMemoryRebound(to: Int64.self, capacity: 1) { $0.pointee }), nil)

case .float4:
return Float32(bitPattern: UInt32(bigEndian: data.withUnsafeBytes { $0.pointee } ))
#if swift(>=5)
let uintValue = data.withUnsafeBytes( { (address: UnsafeRawBufferPointer) in (address.baseAddress?.assumingMemoryBound(to: UInt32.self).pointee) } )
guard let bits = uintValue else {
return(nil, QueryError.databaseError("Unable to convert value to Float32 while reading result"))
}
return (Float32(bitPattern: UInt32(bigEndian: bits)), nil)
#else
return (Float32(bitPattern: UInt32(bigEndian: data.withUnsafeBytes { $0.pointee } )), nil)
#endif

case .float8:
return Float64(bitPattern: UInt64(bigEndian: data.withUnsafeBytes { $0.pointee } ))


#if swift(>=5)
let uintValue = data.withUnsafeBytes( { (address: UnsafeRawBufferPointer) in (address.baseAddress?.assumingMemoryBound(to: UInt64.self).pointee) } )
guard let bits = uintValue else {
return(nil, QueryError.databaseError("Unable to convert value to Float64 while reading result"))
}
return (Float64(bitPattern: UInt64(bigEndian: bits)), nil)
#else
return (Float64(bitPattern: UInt64(bigEndian: data.withUnsafeBytes { $0.pointee } )), nil)
#endif

case .numeric:
// 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
Expand All @@ -148,12 +176,12 @@ public class PostgreSQLResultFetcher: ResultFetcher {
// 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"
return ("NaN", nil)
}

let numberOfDigits = PostgreSQLResultFetcher.int16NetworkToHost(from: value)
if numberOfDigits <= 0 {
return "0"
return ("0", nil)
}

var result: String = ""
Expand Down Expand Up @@ -213,12 +241,12 @@ public class PostgreSQLResultFetcher: ResultFetcher {
result = "-" + result
}

return result
return (result, nil)

case .date:
let days = Int32(bigEndian: value.withMemoryRebound(to: Int32.self, capacity: 1) { $0.pointee })
let timeInterval = TimeInterval(days * secondsInDay)
return Date(timeIntervalSince1970: timeInterval + timeIntervalBetween1970AndPostgresReferenceDate)
return (Date(timeIntervalSince1970: timeInterval + timeIntervalBetween1970AndPostgresReferenceDate), nil)

case .time:
fallthrough
Expand All @@ -229,14 +257,14 @@ public class PostgreSQLResultFetcher: ResultFetcher {
case .timestamptz:
let microseconds = Int64(bigEndian: value.withMemoryRebound(to: Int64.self, capacity: 1) { $0.pointee })
let timeInterval = TimeInterval(microseconds / 1000000)
return Date(timeIntervalSince1970: timeInterval + timeIntervalBetween1970AndPostgresReferenceDate)
return (Date(timeIntervalSince1970: timeInterval + timeIntervalBetween1970AndPostgresReferenceDate), nil)

case .bool:
return Bool(value.withMemoryRebound(to: Bool.self, capacity: 1) { $0.pointee })
return (Bool(value.withMemoryRebound(to: Bool.self, capacity: 1) { $0.pointee }), nil)

case .uuid:
let uuid = UUID(uuid: uuid_t(value.withMemoryRebound(to: uuid_t.self, capacity: 1) { $0.pointee }))
return uuid.uuidString
return (uuid.uuidString, nil)
}
}
}
Expand All @@ -251,16 +279,20 @@ public class PostgreSQLResultFetcher: ResultFetcher {

// Static factory method to create an instance of PostgreSQLQueryBuilder
// This exists to allow the column names from the returned result to be retrieved while we have access to a result set as part of an asynchronous callback chain.
internal static func create(queryResult: OpaquePointer, connection: PostgreSQLConnection, callback: (PostgreSQLResultFetcher) ->()) {
internal static func create(queryResult: OpaquePointer, connection: PostgreSQLConnection, callback: ((PostgreSQLResultFetcher?, Error?)) ->()) {
let resultFetcher = PostgreSQLResultFetcher(connection: connection)
let columns = PQnfields(queryResult)
var columnNames = [String]()
for column in 0 ..< columns {
columnNames.append(String(validatingUTF8: PQfname(queryResult, column))!)
}
resultFetcher.titles = columnNames
resultFetcher.row = resultFetcher.buildRow(queryResult: queryResult)
callback(resultFetcher)
let rowResult = resultFetcher.buildRow(queryResult: queryResult)
guard let row = rowResult.row else {
return callback((nil, rowResult.error))
}
resultFetcher.row = row
callback((resultFetcher, nil))
}
}

Expand Down

0 comments on commit b0d44d2

Please sign in to comment.