diff --git a/Sources/PostgreSQLAdaptor/PostgreSQLAdaptor.swift b/Sources/PostgreSQLAdaptor/PostgreSQLAdaptor.swift index 9db746c..e5777bc 100644 --- a/Sources/PostgreSQLAdaptor/PostgreSQLAdaptor.swift +++ b/Sources/PostgreSQLAdaptor/PostgreSQLAdaptor.swift @@ -3,7 +3,7 @@ // ZeeQL // // Created by Helge Hess on 03/03/17. -// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // import Foundation @@ -22,7 +22,9 @@ open class PostgreSQLAdaptor : Adaptor, SmartDescription { /// The connectString the adaptor was configured with. open var connectString : String - + + private let pool : AdaptorChannelPool? + /** * Configure the adaptor with the given connect string. * @@ -48,9 +50,10 @@ open class PostgreSQLAdaptor : Adaptor, SmartDescription { * Note: The init doesn't validate the connect string, if it is malformed, * channel creation will fail. */ - public init(_ connectString: String) { + public init(_ connectString: String, pool: AdaptorChannelPool? = nil) { // TODO: could call PQconninfoParse(constr, &error) to validate self.connectString = connectString + self.pool = pool } /** @@ -63,7 +66,8 @@ open class PostgreSQLAdaptor : Adaptor, SmartDescription { */ public convenience init(host: String = "127.0.0.1", port: Int = 5432, database: String = "postgres", - user: String = "postgres", password: String = "") + user: String = "postgres", password: String = "", + pool: AdaptorChannelPool? = nil) { var s = "" if !host.isEmpty { s += " host=\(host)" } @@ -71,7 +75,7 @@ open class PostgreSQLAdaptor : Adaptor, SmartDescription { if !database.isEmpty { s += " dbname=\(database)" } if !user.isEmpty { s += " user=\(user)" } if !password.isEmpty { s += " password=\(password)" } - self.init(s) + self.init(s, pool: pool) } private func parseConnectionString(_ s: String) -> [ String : String ] { @@ -147,10 +151,37 @@ open class PostgreSQLAdaptor : Adaptor, SmartDescription { return PostgreSQLAdaptorChannel(adaptor: self, handle: handle) } - public func releaseChannel(_ channel: AdaptorChannel) { - // not maintaing a pool + public func openChannelFromPool() throws -> AdaptorChannel { + if let channel = pool?.grab() { + log.info("reusing pooled channel:", channel) + return channel + } + do { + let channel = try openChannel() + if pool != nil { + log.info("opened new channel:", channel) + } + return channel + } + catch { + throw error + } } + public func releaseChannel(_ channel: AdaptorChannel) { + guard let pool = pool else { + return + } + if let channel = channel as? PostgreSQLAdaptorChannel { + log.info("releasing channel:", ObjectIdentifier(channel)) + pool.add(channel) + } + else { + log.info("invalid channel type:", channel) + assert(channel is PostgreSQLAdaptorChannel) + } + } + // MARK: - Model @@ -172,8 +203,9 @@ open class PostgreSQLAdaptor : Adaptor, SmartDescription { public func appendToDescription(_ ms: inout String) { ms += " " + connectString - if model != nil { + if let model = model { ms += " has-model" + if model.isPattern { ms += "(pattern)" } } } } diff --git a/Sources/PostgreSQLAdaptor/PostgreSQLAdaptorChannel.swift b/Sources/PostgreSQLAdaptor/PostgreSQLAdaptorChannel.swift index f909e21..942d3f4 100644 --- a/Sources/PostgreSQLAdaptor/PostgreSQLAdaptorChannel.swift +++ b/Sources/PostgreSQLAdaptor/PostgreSQLAdaptorChannel.swift @@ -3,7 +3,7 @@ // ZeeQL // // Created by Helge Hess on 03/03/17. -// Copyright © 2017 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // #if os(Windows) @@ -13,7 +13,7 @@ #else import Darwin #endif -import struct Foundation.Data +import Foundation import ZeeQL import CLibPQ @@ -34,11 +34,12 @@ open class PostgreSQLAdaptorChannel : AdaptorChannel, SmartDescription { public let expressionFactory : SQLExpressionFactory public var handle : OpaquePointer? - final let logSQL = true + final let logSQL : Bool init(adaptor: Adaptor, handle: OpaquePointer) { self.expressionFactory = adaptor.expressionFactory self.handle = handle + self.logSQL = UserDefaults.standard.bool(forKey: "PGDebugEnabled") } deinit { @@ -317,6 +318,7 @@ open class PostgreSQLAdaptorChannel : AdaptorChannel, SmartDescription { // TODO: consider attribute! (e.g. for date, valueType in attr, if set) // TODO: decode actual types :-) + // TODO: do not crash on force unwrap switch type { case OIDs.INT2: return Int16(bigEndian: cast(value.baseAddress!)) @@ -335,11 +337,25 @@ open class PostgreSQLAdaptorChannel : AdaptorChannel, SmartDescription { case OIDs.NAME: // e.g. SELECT datname FROM pg_database return String(cString: value.baseAddress!) - case OIDs.TIMESTAMPTZ: + case OIDs.TIMESTAMPTZ: // 1184 // TODO: I think it is better to fix this during the query, that is, // to a SELECT unix_time(startDate) like thing. // hm. How to parse this? We used to have the format in the attribute? // http://www.linuxtopia.org/online_books/database_guides/Practical_PostgreSQL_database/PostgreSQL_x2632_005.htm + // 2024-11-08(hh): this seems to be 8 bytes aka UInt64 and the above + // for String representations + // https://postgrespro.com/list/thread-id/1482672 + // https://materialize.com/docs/sql/types/timestamp/ + // - Min value 4713 BC + // - Max value 294276 AD + // - Max resolution 1 microsecond + if value.count == 8 { + // 1_000_000 + let msecs = Double(UInt64(bigEndian: cast(value.baseAddress!))) + let date = Date(timeInterval: TimeInterval(msecs) / 1000000.0, + since: Date.pgReferenceDate) + return date + } return String(cString: value.baseAddress!) case OIDs.TIMESTAMP: return String(cString: value.baseAddress!) @@ -527,3 +543,9 @@ fileprivate func tdup(_ value: T) -> UnsafeBufferPointer { ptr.pointee = value return UnsafeBufferPointer(start: UnsafePointer(raw), count: len) } + +fileprivate extension Date { + + // 2000-01-01 + static let pgReferenceDate = Date(timeIntervalSince1970: 946684800) +}