Skip to content
This repository has been archived by the owner on Mar 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #36 from natebird/2.0.0-beta2
Browse files Browse the repository at this point in the history
Most of changes below are to make things consistent with the Vapor MySQL counterpart and to help support features in the PostgreSQL Driver.

Changes:

Rename connection parameter from host to hostname. This allows for supporting master and read replica in PostgreSQL Driver.
Rename connected boolean to isConnected.
Rename connection pointer to cConnection.
Rename error var to lastError.
Move all connection tests into the ConnectionTests file.
New:

Add Error class for better error messaging.
Add Context class with isPostgreSQL var.
  • Loading branch information
natebird authored Apr 7, 2017
2 parents 9b11b11 + 8569400 commit ea4c27b
Show file tree
Hide file tree
Showing 10 changed files with 686 additions and 230 deletions.
61 changes: 39 additions & 22 deletions Sources/PostgreSQL/Connection.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import CPostgreSQL

// This structure represents a handle to one database connection.
// It is used for almost all PostgreSQL functions.
// Do not try to make a copy of a PostgreSQL structure.
// There is no guarantee that such a copy will be usable.
public final class Connection: ConnInfoInitializable {
public typealias ConnectionPointer = OpaquePointer
public let cConnection: OpaquePointer
public var configuration: Configuration?
private(set) var connection: ConnectionPointer!

public var connected: Bool {
if let connection = connection, PQstatus(connection) == CONNECTION_OK {
public var isConnected: Bool {
if PQstatus(cConnection) == CONNECTION_OK {
return true
}
return false
Expand All @@ -20,13 +22,13 @@ public final class Connection: ConnInfoInitializable {
string = info
case .params(let params):
string = params.map({ "\($0)='\($1)'" }).joined()
case .basic(let host, let port, let database, let user, let password):
string = "host='\(host)' port='\(port)' dbname='\(database)' user='\(user)' password='\(password)' client_encoding='UTF8'"
case .basic(let hostname, let port, let database, let user, let password):
string = "host='\(hostname)' port='\(port)' dbname='\(database)' user='\(user)' password='\(password)' client_encoding='UTF8'"
}

self.connection = PQconnectdb(string)
if !self.connected {
throw DatabaseError.cannotEstablishConnection(error)
self.cConnection = PQconnectdb(string)
if isConnected == false {
throw DatabaseError.cannotEstablishConnection(lastError)
}
}

Expand Down Expand Up @@ -59,7 +61,7 @@ public final class Connection: ConnInfoInitializable {
}

let res: Result.Pointer = PQexecParams(
connection, query,
cConnection, query,
Int32(values.count),
types, paramValues.map {
UnsafePointer<Int8>($0)
Expand All @@ -84,35 +86,39 @@ public final class Connection: ConnInfoInitializable {
}
}

public func status() -> ConnStatusType {
return PQstatus(cConnection)
}

public func reset() throws {
guard self.connected else {
throw DatabaseError.cannotEstablishConnection(error)
guard self.isConnected else {
throw PostgreSQLError(.connection_failure, reason: lastError)
}

PQreset(connection)
PQreset(cConnection)
}

public func close() throws {
guard self.connected else {
throw DatabaseError.cannotEstablishConnection(error)
guard self.isConnected else {
throw PostgreSQLError(.connection_does_not_exist, reason: lastError)
}

PQfinish(connection)
PQfinish(cConnection)
}

public var error: String {
guard let s = PQerrorMessage(connection) else {
// Contains the last error message generated by the PostgreSQL connection.
public var lastError: String {
guard let errorMessage = PQerrorMessage(cConnection) else {
return ""
}
return String(cString: s)
return String(cString: errorMessage)
}

deinit {
try? close()
}

// MARK: - Load Configuration

private func getConfiguration() throws -> Configuration {
if let configuration = self.configuration {
return configuration
Expand All @@ -127,9 +133,20 @@ public final class Connection: ConnInfoInitializable {
}

private func getBooleanParameterStatus(key: String, `default` defaultValue: Bool = false) -> Bool {
guard let value = PQparameterStatus(connection, "integer_datetimes") else {
guard let value = PQparameterStatus(cConnection, "integer_datetimes") else {
return defaultValue
}
return String(cString: value) == "on"
}
}

extension Connection {
@discardableResult
public func execute(_ query: String, _ representable: [NodeRepresentable]) throws -> Node {
let values = try representable.map {
return try $0.makeNode(in: PostgreSQLContext.shared)
}

return try execute(query, values)
}
}
10 changes: 5 additions & 5 deletions Sources/PostgreSQL/ConnectionInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import CPostgreSQL
public enum ConnInfo {
case raw(String)
case params([String: String])
case basic(host: String, port: Int, database: String, user: String, password: String)
case basic(hostname: String, port: Int, database: String, user: String, password: String)
}

public protocol ConnInfoInitializable {
Expand All @@ -14,11 +14,11 @@ extension ConnInfoInitializable {
public init(params: [String: String]) throws {
try self.init(conninfo: .params(params))
}
public init(host: String, port: Int, database: String, user: String, password: String) throws {
try self.init(conninfo: .basic(host: host, port: port, database: database, user: user, password: password))

public init(hostname: String, port: Int, database: String, user: String, password: String) throws {
try self.init(conninfo: .basic(hostname: hostname, port: port, database: database, user: user, password: password))
}

public init(conninfo: String) throws {
try self.init(conninfo: .raw(conninfo))
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/PostgreSQL/Context.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Node

public final class PostgreSQLContext: Context {
internal static let shared = PostgreSQLContext()
fileprivate init() {}
}

extension Context {
public var isPostgreSQL: Bool {
guard let _ = self as? PostgreSQLContext else { return false }
return true
}
}
95 changes: 49 additions & 46 deletions Sources/PostgreSQL/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,65 +18,68 @@ enum DataFormat : Int32 {
public final class Database: ConnInfoInitializable {
// MARK: - Properties
public let conninfo: ConnInfo

// MARK: - Init
public init(conninfo: ConnInfo) throws {
self.conninfo = conninfo
}

// MARK: - Connection
public func makeConnection() throws -> Connection {
return try Connection(conninfo: conninfo)
}

// MARK: - Query Execution
@discardableResult
public func execute(_ query: String, _ values: [Node]? = [], on connection: Connection? = nil) throws -> [[String: Node]] {
guard !query.isEmpty else {
throw DatabaseError.noQuery
}

let connection = try connection ?? makeConnection()

return try connection.execute(query, values)
}

public func listen(to channel: String, callback: @escaping (Notification) -> Void) {
background {
do {
let connection = try self.makeConnection()

try self.execute("LISTEN \(channel)", on: connection)

while true {
if connection.connected != true {
throw DatabaseError.cannotEstablishConnection(connection.error)
}

PQconsumeInput(connection.connection)

while let pgNotify = PQnotifies(connection.connection) {
let notification = Notification(relname: pgNotify.pointee.relname, extra: pgNotify.pointee.extra, be_pid: pgNotify.pointee.be_pid)

callback(notification)

PQfreemem(pgNotify)
}
}
}
catch {
fatalError("\(error)")
}
}
}

public func notify(channel: String, payload: String?, on connection: Connection? = nil) throws {
let connection = try connection ?? makeConnection()

if let payload = payload {
try execute("NOTIFY \(channel), '\(payload)'", on: connection)
}
else {
try execute("NOTIFY \(channel)", on: connection)
}
}

public func makeConnection() throws -> Connection {
return try Connection(conninfo: conninfo)

// MARK: - LISTEN
public func listen(to channel: String, callback: @escaping (Notification) -> Void) {
background {
do {
let connection = try self.makeConnection()

try self.execute("LISTEN \(channel)", on: connection)

while true {
if connection.isConnected == false {
throw DatabaseError.cannotEstablishConnection(connection.lastError)
}

PQconsumeInput(connection.cConnection)

while let pgNotify = PQnotifies(connection.cConnection) {
let notification = Notification(relname: pgNotify.pointee.relname, extra: pgNotify.pointee.extra, be_pid: pgNotify.pointee.be_pid)

callback(notification)

PQfreemem(pgNotify)
}
}
}
catch {
fatalError("\(error)")
}
}
}

// MARK: - NOTIFY
public func notify(channel: String, payload: String?, on connection: Connection? = nil) throws {
let connection = try connection ?? makeConnection()

if let payload = payload {
try execute("NOTIFY \(channel), '\(payload)'", on: connection)
}
else {
try execute("NOTIFY \(channel)", on: connection)
}
}
}
Loading

0 comments on commit ea4c27b

Please sign in to comment.