From a5b2e67071950a8c098610c4495e656d6cdfd18f Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 07:40:40 -0400 Subject: [PATCH 01/10] remove white space. spaces instead of tabs. --- Sources/PostgreSQL/Database.swift | 88 ++++----- Sources/PostgreSQL/Notification.swift | 28 +-- Tests/PostgreSQLTests/PostgreSQLTests.swift | 208 ++++++++++---------- 3 files changed, 162 insertions(+), 162 deletions(-) diff --git a/Sources/PostgreSQL/Database.swift b/Sources/PostgreSQL/Database.swift index c385363..35c437a 100644 --- a/Sources/PostgreSQL/Database.swift +++ b/Sources/PostgreSQL/Database.swift @@ -18,64 +18,64 @@ 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 @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 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) } diff --git a/Sources/PostgreSQL/Notification.swift b/Sources/PostgreSQL/Notification.swift index 9579208..cae2b0c 100644 --- a/Sources/PostgreSQL/Notification.swift +++ b/Sources/PostgreSQL/Notification.swift @@ -1,19 +1,19 @@ public struct Notification { - let channel: String - let payload: String? - let pid: Int + let channel: String + let payload: String? + let pid: Int } extension Notification { - init(relname: UnsafeMutablePointer, extra: UnsafeMutablePointer, be_pid: Int32) { - self.channel = String(cString: relname) - self.pid = Int(be_pid) - - if (extra.pointee != 0) { - self.payload = String(cString: extra) - } - else { - self.payload = nil - } - } + init(relname: UnsafeMutablePointer, extra: UnsafeMutablePointer, be_pid: Int32) { + self.channel = String(cString: relname) + self.pid = Int(be_pid) + + if (extra.pointee != 0) { + self.payload = String(cString: extra) + } + else { + self.payload = nil + } + } } diff --git a/Tests/PostgreSQLTests/PostgreSQLTests.swift b/Tests/PostgreSQLTests/PostgreSQLTests.swift index 99471b3..7712819 100644 --- a/Tests/PostgreSQLTests/PostgreSQLTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLTests.swift @@ -35,7 +35,7 @@ class PostgreSQLTests: XCTestCase { override func setUp() { postgreSQL = PostgreSQL.Database.makeTestConnection() } - + func testConnectionFailure() throws { let database = try PostgreSQL.Database( host: "127.0.0.1", @@ -44,22 +44,22 @@ class PostgreSQLTests: XCTestCase { user: "postgres", password: "" ) - + try XCTAssertThrowsError(database.makeConnection()) { error in switch error { case DatabaseError.cannotEstablishConnection(_): break - + default: XCTFail("Invalid error") } } } - + func testConnection() throws { let connection = try postgreSQL.makeConnection() XCTAssertTrue(connection.connected) - + try connection.reset() try connection.close() XCTAssertFalse(connection.connected) @@ -124,7 +124,7 @@ class PostgreSQLTests: XCTestCase { do { try postgreSQL.execute("DROP TABLE IF EXISTS parameterization") try postgreSQL.execute("CREATE TABLE parameterization (d FLOAT8, i INT, s VARCHAR(16), u INT)") - + try postgreSQL.execute("INSERT INTO parameterization VALUES ($1, $2, $3, $4)", [.null, .null, "life".makeNode(in: nil), .null], on: nil) try postgreSQL.execute("INSERT INTO parameterization VALUES (3.14, NULL, 'pi', NULL)") @@ -170,37 +170,37 @@ class PostgreSQLTests: XCTestCase { XCTFail("Testing parameterization failed: \(error)") } } - + func testDataType() throws { let data: [UInt8] = [1, 2, 3, 4, 5, 0, 6, 7, 8, 9, 0] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (bar BYTEA)") try postgreSQL.execute("INSERT INTO foo VALUES ($1)", [.bytes(data)]) - + let result = try postgreSQL.execute("SELECT * FROM foo").first XCTAssertNotNil(result) - + let resultBytesNode = result!["bar"] XCTAssertNotNil(resultBytesNode) - + XCTAssertEqual(resultBytesNode!, .bytes(data)) } - + func testCustomType() throws { let uuidString = "7fe1743a-96a8-417c-b6c2-c8bb20d3017e" let dateString = "2016-10-24 23:04:19.223" - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (uuid UUID, date TIMESTAMP WITHOUT TIME ZONE)") try postgreSQL.execute("INSERT INTO foo VALUES ($1, $2)", [.string(uuidString), .string(dateString)]) - + let result = try postgreSQL.execute("SELECT * FROM foo").first XCTAssertNotNil(result) XCTAssertEqual(result!["uuid"]?.string, uuidString) XCTAssertEqual(result!["date"]?.string, dateString) } - + func testInts() throws { let rows: [(Int16, Int32, Int64)] = [ (1, 2, 3), @@ -208,20 +208,20 @@ class PostgreSQLTests: XCTestCase { (Int16.min, Int32.min, Int64.min), (Int16.max, Int32.max, Int64.max), ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, int2 int2, int4 int4, int8 int8)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1, $2, $3)", [row.0.makeNode(in: nil), row.1.makeNode(in: nil), row.2.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { let int2 = resultRow["int2"] XCTAssertNotNil(int2?.int) XCTAssertEqual(int2!.int!, Int(rows[i].0)) - + let int4 = resultRow["int4"] XCTAssertNotNil(int4?.int) XCTAssertEqual(int4!.int!, Int(rows[i].1)) @@ -231,7 +231,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(int8!.double!, Double(rows[i].2)) } } - + func testFloats() throws { let rows: [(Float32, Float64)] = [ (1, 2), @@ -241,26 +241,26 @@ class PostgreSQLTests: XCTestCase { (Float32.min, Float64.min), (Float32.max, Float64.max), ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, float4 float4, float8 float8)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1, $2)", [row.0.makeNode(in: nil), row.1.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { let float4 = resultRow["float4"] XCTAssertNotNil(float4?.double) XCTAssertEqual(float4!.double!, Double(rows[i].0)) - + let float8 = resultRow["float8"] XCTAssertNotNil(float8?.double) XCTAssertEqual(float8!.double!, Double(rows[i].1)) } } - + func testNumeric() throws { let rows: [String] = [ "0", @@ -273,13 +273,13 @@ class PostgreSQLTests: XCTestCase { "100000001000000000000.0000000001000000001", "NaN", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, numeric numeric)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -288,7 +288,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(numeric!.string!, rows[i]) } } - + func testJSON() throws { let rows: [String] = [ "{}", @@ -299,26 +299,26 @@ class PostgreSQLTests: XCTestCase { "[1, 2, 3, 4, 5, 6]", "{\"foo\": \"bar\"}", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, json json, jsonb jsonb)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1, $2)", [row.makeNode(in: nil), row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { let json = resultRow["json"] XCTAssertNotNil(json?.string) XCTAssertEqual(json!.string!, rows[i]) - + let jsonb = resultRow["jsonb"] XCTAssertNotNil(jsonb?.string) XCTAssertEqual(jsonb!.string!, rows[i]) } } - + func testIntervals() throws { let rows: [[String]] = [ ["00:00:01","0:0:1"], @@ -335,13 +335,13 @@ class PostgreSQLTests: XCTestCase { ["-1 days"], ["-11 mons +1 day"], ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, interval interval)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row[0].makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -350,20 +350,20 @@ class PostgreSQLTests: XCTestCase { XCTAssertTrue(rows[i].contains(interval!.string!)) } } - + func testPoints() throws { let rows = [ "(1.2,3.4)", "(-1.2,-3.4)", "(123.456,-298.135)", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, point point)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -372,20 +372,20 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(point!.string!, rows[i]) } } - + func testLineSegments() throws { let rows = [ "[(1.2,3.4),(-1.2,-3.4)]", "[(-1.2,-3.4),(123.467,-298.135)]", "[(123.47,-238.123),(1.2,3.4)]", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, lseg lseg)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -394,7 +394,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(lseg!.string!, rows[i]) } } - + func testPaths() throws { let rows = [ "[(1.2,3.4),(-1.2,-3.4),(123.67,-598.35)]", @@ -402,13 +402,13 @@ class PostgreSQLTests: XCTestCase { "((123.47,-235.35),(1.2,3.4))", "[(1.2,3.4)]", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, path path)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -417,20 +417,20 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(path!.string!, rows[i]) } } - + func testBoxes() throws { let rows = [ "(1.2,3.4),(-1.2,-3.4)", "(13.467,-3.4),(-1.2,-598.35)", "(12.467,3.4),(1.2,-358.15)", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, box box)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -439,7 +439,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(box!.string!, rows[i]) } } - + func testPolygons() throws { let rows = [ "((1.2,3.4),(-1.2,-3.4),(123.46,-358.25))", @@ -447,13 +447,13 @@ class PostgreSQLTests: XCTestCase { "((123.467,-98.123),(1.2,3.4))", "((1.2,3.4))", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, polygon polygon)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -462,20 +462,20 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(polygon!.string!, rows[i]) } } - + func testCircles() throws { let rows = [ "<(1.2,3.4),456.7>", "<(-1.2,-3.4),98>", "<(123.67,-598.15),0.123>", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, circle circle)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -484,7 +484,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(circle!.string!, rows[i]) } } - + func testInets() throws { let rows = [ "192.168.100.128", @@ -495,13 +495,13 @@ class PostgreSQLTests: XCTestCase { "0.0.0.0", "127.0.0.1", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, inet inet)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -510,7 +510,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(inet!.string!, rows[i]) } } - + func testCidrs() throws { let rows = [ "192.168.100.128/32", @@ -521,13 +521,13 @@ class PostgreSQLTests: XCTestCase { "0.0.0.0/32", "127.0.0.1/32", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, cidr cidr)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -536,7 +536,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(cidr!.string!, rows[i]) } } - + func testMacAddresses() throws { let rows = [ "5a:92:79:a1:ce:1a", @@ -550,13 +550,13 @@ class PostgreSQLTests: XCTestCase { "d8:39:78:9e:f6:fe", "58:ff:b8:e9:85:30", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, macaddr macaddr)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -565,7 +565,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(macaddr!.string!, rows[i]) } } - + func testBitStrings() throws { let rows = [ "01010", @@ -578,13 +578,13 @@ class PostgreSQLTests: XCTestCase { "00001", "10000", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, bits bit(5))") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -593,7 +593,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(bits!.string!, rows[i]) } } - + func testVarBitStrings() throws { let rows = [ "0", @@ -608,13 +608,13 @@ class PostgreSQLTests: XCTestCase { "00000000000", "1111111111", ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, bits bit varying)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row.makeNode(in: nil)]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for (i, resultRow) in result.enumerated() { @@ -623,7 +623,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(bits!.string!, rows[i]) } } - + func testUnsupportedObject() throws { let rows: [Node] = [ .object(["1":1, "2":2]), @@ -631,13 +631,13 @@ class PostgreSQLTests: XCTestCase { .object([:]), .object(["1":1]), ] - + try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, text text)") for row in rows { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, $1)", [row]) } - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, rows.count) for resultRow in result { @@ -646,7 +646,7 @@ class PostgreSQLTests: XCTestCase { XCTAssertEqual(value, Node.null) } } - + func testUnsupportedOID() throws { try postgreSQL.execute("DROP TABLE IF EXISTS foo") try postgreSQL.execute("CREATE TABLE foo (id serial, oid oid)") @@ -654,7 +654,7 @@ class PostgreSQLTests: XCTestCase { try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, 2)", nil) try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, 123)", nil) try postgreSQL.execute("INSERT INTO foo VALUES (DEFAULT, 456)", nil) - + let result = try postgreSQL.execute("SELECT * FROM foo ORDER BY id ASC") XCTAssertEqual(result.count, 4) for resultRow in result { @@ -662,38 +662,38 @@ class PostgreSQLTests: XCTestCase { XCTAssertNotNil(value) } } - - func testNotification() throws { - let testExpectation = expectation(description: "Receive notification") - - postgreSQL.listen(to: "test_channel1") { notification in - XCTAssertEqual(notification.channel, "test_channel1") - XCTAssertNil(notification.payload) - - testExpectation.fulfill() - } - - sleep(1) - - try postgreSQL.notify(channel: "test_channel1", payload: nil) - - waitForExpectations(timeout: 5) - } - - func testNotificationWithPayload() throws { - let testExpectation = expectation(description: "Receive notification with payload") - - postgreSQL.listen(to: "test_channel2") { notification in - XCTAssertEqual(notification.channel, "test_channel2") - XCTAssertEqual(notification.payload, "test_payload") - - testExpectation.fulfill() - } - - sleep(1) - - try postgreSQL.notify(channel: "test_channel2", payload: "test_payload") - - waitForExpectations(timeout: 5) - } + + func testNotification() throws { + let testExpectation = expectation(description: "Receive notification") + + postgreSQL.listen(to: "test_channel1") { notification in + XCTAssertEqual(notification.channel, "test_channel1") + XCTAssertNil(notification.payload) + + testExpectation.fulfill() + } + + sleep(1) + + try postgreSQL.notify(channel: "test_channel1", payload: nil) + + waitForExpectations(timeout: 5) + } + + func testNotificationWithPayload() throws { + let testExpectation = expectation(description: "Receive notification with payload") + + postgreSQL.listen(to: "test_channel2") { notification in + XCTAssertEqual(notification.channel, "test_channel2") + XCTAssertEqual(notification.payload, "test_payload") + + testExpectation.fulfill() + } + + sleep(1) + + try postgreSQL.notify(channel: "test_channel2", payload: "test_payload") + + waitForExpectations(timeout: 5) + } } From c69fa3bab77930a46d29f05425e21220a8f440d3 Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 12:23:48 -0400 Subject: [PATCH 02/10] Group connection tests. --- Tests/PostgreSQLTests/ConnectionTests.swift | 65 +++++++++++++++++++-- Tests/PostgreSQLTests/PostgreSQLTests.swift | 29 --------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/Tests/PostgreSQLTests/ConnectionTests.swift b/Tests/PostgreSQLTests/ConnectionTests.swift index 70a692f..b8054f0 100644 --- a/Tests/PostgreSQLTests/ConnectionTests.swift +++ b/Tests/PostgreSQLTests/ConnectionTests.swift @@ -2,17 +2,70 @@ import XCTest import PostgreSQL class ConnectionTests: XCTestCase { - + static let allTests = [ + ("testConnection", testConnection), + ("testConnInfoParams", testConnInfoParams), + ("testConnectionFailure", testConnectionFailure) + ] + + var postgreSQL: PostgreSQL.Database! + + func testConnection() throws { + postgreSQL = PostgreSQL.Database.makeTestConnection() + + let connection = try postgreSQL.makeConnection() + XCTAssertFalse(connection.isClosed) + + try connection.reset() + try connection.close() + XCTAssertTrue(connection.isClosed) + } + func testConnInfoParams() { do { - let postgreSQL = try PostgreSQL.Database(params: ["host": "127.0.0.1", - "port": "5432", - "dbname": "test", - "user": "postgres", - "password": ""]) + let postgreSQL = try PostgreSQL.Database( + params: ["host": "127.0.0.1", + "port": "5432", + "dbname": "test", + "user": "postgres", + "password": ""]) try postgreSQL.execute("SELECT version()") } catch { XCTFail("Could not connect to database") } } + + func testConnectionFailure() throws { + let database = try PostgreSQL.Database( + host: "127.0.0.1", + port: 5432, + database: "some_long_db_name_that_does_not_exist", + user: "postgres", + password: "" + ) + + try XCTAssertThrowsError(database.makeConnection()) { error in + switch error { + case DatabaseError.cannotEstablishConnection(_): + break + default: + XCTFail("Invalid error") + } + } + } + + func testConnectionSuccess() throws { + do { + let database = try PostgreSQL.Database( + host: "127.0.0.1", + port: 5432, + database: "test", + user: "postgres", + password: "" + ) + try database.execute("SELECT version()") + } catch { + XCTFail("Could not connect to database") + } + } } diff --git a/Tests/PostgreSQLTests/PostgreSQLTests.swift b/Tests/PostgreSQLTests/PostgreSQLTests.swift index 7712819..7127af4 100644 --- a/Tests/PostgreSQLTests/PostgreSQLTests.swift +++ b/Tests/PostgreSQLTests/PostgreSQLTests.swift @@ -36,35 +36,6 @@ class PostgreSQLTests: XCTestCase { postgreSQL = PostgreSQL.Database.makeTestConnection() } - func testConnectionFailure() throws { - let database = try PostgreSQL.Database( - host: "127.0.0.1", - port: 5432, - database: "some_long_db_name_that_does_not_exist", - user: "postgres", - password: "" - ) - - try XCTAssertThrowsError(database.makeConnection()) { error in - switch error { - case DatabaseError.cannotEstablishConnection(_): - break - - default: - XCTFail("Invalid error") - } - } - } - - func testConnection() throws { - let connection = try postgreSQL.makeConnection() - XCTAssertTrue(connection.connected) - - try connection.reset() - try connection.close() - XCTAssertFalse(connection.connected) - } - func testSelectVersion() { do { let results = try postgreSQL.execute("SELECT version(), version(), 1337, 3.14, 'what up', NULL") From f5294eb7667dd8b1eadfe9032930131b926d9b29 Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 12:31:19 -0400 Subject: [PATCH 03/10] Add isClosed boolean and rename connection to cConnection. --- Sources/PostgreSQL/Connection.swift | 67 ++++++++++++++++++----------- Sources/PostgreSQL/Database.swift | 19 ++++---- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/Sources/PostgreSQL/Connection.swift b/Sources/PostgreSQL/Connection.swift index e073b1d..45ea5e3 100644 --- a/Sources/PostgreSQL/Connection.swift +++ b/Sources/PostgreSQL/Connection.swift @@ -1,16 +1,13 @@ 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 { - return true - } - return false - } + public var isClosed: Bool public init(conninfo: ConnInfo) throws { let string: String @@ -24,10 +21,13 @@ public final class Connection: ConnInfoInitializable { string = "host='\(host)' 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 PQstatus(cConnection) == CONNECTION_OK { + isClosed = false + } else { + isClosed = true + throw DatabaseError.cannotEstablishConnection(lastError) + } } @discardableResult @@ -59,7 +59,7 @@ public final class Connection: ConnInfoInitializable { } let res: Result.Pointer = PQexecParams( - connection, query, + cConnection, query, Int32(values.count), types, paramValues.map { UnsafePointer($0) @@ -84,27 +84,34 @@ 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.isClosed else { + throw PostgreSQLError(.connection_failure, reason: "Connection failed.") } - PQreset(connection) + PQreset(cConnection) + self.isClosed = false } public func close() throws { - guard self.connected else { - throw DatabaseError.cannotEstablishConnection(error) + guard !self.isClosed else { + throw PostgreSQLError(.connection_does_not_exist, reason: "Connection does not exist.") } - PQfinish(connection) + PQfinish(cConnection) + self.isClosed = true } - 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 { @@ -112,7 +119,6 @@ public final class Connection: ConnInfoInitializable { } // MARK: - Load Configuration - private func getConfiguration() throws -> Configuration { if let configuration = self.configuration { return configuration @@ -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) + } +} diff --git a/Sources/PostgreSQL/Database.swift b/Sources/PostgreSQL/Database.swift index 35c437a..c7fd2b6 100644 --- a/Sources/PostgreSQL/Database.swift +++ b/Sources/PostgreSQL/Database.swift @@ -25,6 +25,11 @@ public final class Database: ConnInfoInitializable { } // 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 { @@ -36,6 +41,7 @@ public final class Database: ConnInfoInitializable { return try connection.execute(query, values) } + // MARK: - LISTEN public func listen(to channel: String, callback: @escaping (Notification) -> Void) { background { do { @@ -44,13 +50,13 @@ public final class Database: ConnInfoInitializable { try self.execute("LISTEN \(channel)", on: connection) while true { - if connection.connected != true { - throw DatabaseError.cannotEstablishConnection(connection.error) + if connection.isClosed == true { + throw DatabaseError.cannotEstablishConnection(connection.lastError) } - PQconsumeInput(connection.connection) + PQconsumeInput(connection.cConnection) - while let pgNotify = PQnotifies(connection.connection) { + 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) @@ -65,6 +71,7 @@ public final class Database: ConnInfoInitializable { } } + // MARK: - NOTIFY public func notify(channel: String, payload: String?, on connection: Connection? = nil) throws { let connection = try connection ?? makeConnection() @@ -75,8 +82,4 @@ public final class Database: ConnInfoInitializable { try execute("NOTIFY \(channel)", on: connection) } } - - public func makeConnection() throws -> Connection { - return try Connection(conninfo: conninfo) - } } From 8dcc4051997cd6c7e4f59d941589efdfd6d1898e Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 12:31:32 -0400 Subject: [PATCH 04/10] Add context --- Sources/PostgreSQL/Context.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Sources/PostgreSQL/Context.swift diff --git a/Sources/PostgreSQL/Context.swift b/Sources/PostgreSQL/Context.swift new file mode 100644 index 0000000..39dc10f --- /dev/null +++ b/Sources/PostgreSQL/Context.swift @@ -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 + } +} From c58e4497de5a6a702d17d84b616bbd9e29469929 Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 12:31:43 -0400 Subject: [PATCH 05/10] Add error enums. --- Sources/PostgreSQL/Error.swift | 384 +++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 Sources/PostgreSQL/Error.swift diff --git a/Sources/PostgreSQL/Error.swift b/Sources/PostgreSQL/Error.swift new file mode 100644 index 0000000..0f0a437 --- /dev/null +++ b/Sources/PostgreSQL/Error.swift @@ -0,0 +1,384 @@ +import CPostgreSQL + +/// A list of all Error messages that +/// can be thrown from calls to `Database`. +/// +/// All Error objects contain a String which +/// contains PostgreSQL's last error message. +public struct PostgreSQLError: Error { + public let code: Code + public let reason: String +} + +extension PostgreSQLError { + public enum Code: String { + case successful_completion = "00000" + // Class 01 — Warning + case warning = "01000" + case dynamic_result_sets_returned = "0100C" + case implicit_zero_bit_padding = "01008" + case null_value_eliminated_in_set_function = "01003" + case privilege_not_granted = "01007" + case privilege_not_revoked = "01006" + case string_data_right_truncation = "01004" + case deprecated_feature = "01P01" + // Class 02 — No Data (this is also a warning class per the SQL standard) + case no_data = "02000" + case no_additional_dynamic_result_sets_returned = "02001" + // Class 03 — SQL Statement Not Yet Complete + case sql_statement_not_yet_complete = "03000" + // Class 08 — Connection Exception + case connection_exception = "08000" + case connection_does_not_exist = "08003" + case connection_failure = "08006" + case sqlclient_unable_to_establish_sqlconnection = "08001" + case sqlserver_rejected_establishment_of_sqlconnection = "08004" + case transaction_resolution_unknown = "08007" + case protocol_violation = "08P01" + // Class 09 — Triggered Action Exception + case triggered_action_exception = "09000" + // Class 0A — Feature Not Supported + case feature_not_supported = "0A000" + // Class 0B — Invalid Transaction Initiation + case invalid_transaction_initiation = "0B000" + // Class 0F — Locator Exception + case locator_exception = "0F000" + case invalid_locator_specification = "0F001" + // Class 0L — Invalid Grantor + case invalid_grantor = "0L000" + case invalid_grant_operation = "0LP01" + // Class 0P — Invalid Role Specification + case invalid_role_specification = "0P000" + // Class 0Z — Diagnostics Exception + case diagnostics_exception = "0Z000" + case stacked_diagnostics_accessed_without_active_handler = "0Z002" + // Class 20 — Case Not Found + case case_not_found = "20000" + // Class 21 — Cardinality Violation + case cardinality_violation = "21000" + // Class 22 — Data Exception + case data_exception = "22000" + case array_subscript_error = "2202E" + case character_not_in_repertoire = "22021" + case datetime_field_overflow = "22008" + case division_by_zero = "22012" + case error_in_assignment = "22005" + case escape_character_conflict = "2200B" + case indicator_overflow = "22022" + case interval_field_overflow = "22015" + case invalid_argument_for_logarithm = "2201E" + case invalid_argument_for_ntile_function = "22014" + case invalid_argument_for_nth_value_function = "22016" + case invalid_argument_for_power_function = "2201F" + case invalid_argument_for_width_bucket_function = "2201G" + case invalid_character_value_for_cast = "22018" + case invalid_datetime_format = "22007" + case invalid_escape_character = "22019" + case invalid_escape_octet = "2200D" + case invalid_escape_sequence = "22025" + case nonstandard_use_of_escape_character = "22P06" + case invalid_indicator_parameter_value = "22010" + case invalid_parameter_value = "22023" + case invalid_regular_expression = "2201B" + case invalid_row_count_in_limit_clause = "2201W" + case invalid_row_count_in_result_offset_clause = "2201X" + case invalid_tablesample_argument = "2202H" + case invalid_tablesample_repeat = "2202G" + case invalid_time_zone_displacement_value = "22009" + case invalid_use_of_escape_character = "2200C" + case most_specific_type_mismatch = "2200G" + case null_value_not_allowed = "22004" + case null_value_no_indicator_parameter = "22002" + case numeric_value_out_of_range = "22003" + case string_data_length_mismatch = "22026" + // case string_data_right_truncation = "22001" + case substring_error = "22011" + case trim_error = "22027" + case unterminated_c_string = "22024" + case zero_length_character_string = "2200F" + case floating_point_exception = "22P01" + case invalid_text_representation = "22P02" + case invalid_binary_representation = "22P03" + case bad_copy_file_format = "22P04" + case untranslatable_character = "22P05" + case not_an_xml_document = "2200L" + case invalid_xml_document = "2200M" + case invalid_xml_content = "2200N" + case invalid_xml_comment = "2200S" + case invalid_xml_processing_instruction = "2200T" + // Class 23 — Integrity Constraint Violation + case integrity_constraint_violation = "23000" + case restrict_violation = "23001" + case not_null_violation = "23502" + case foreign_key_violation = "23503" + case unique_violation = "23505" + case check_violation = "23514" + case exclusion_violation = "23P01" + // Class 24 — Invalid Cursor State + case invalid_cursor_state = "24000" + // Class 25 — Invalid Transaction State + case invalid_transaction_state = "25000" + case active_sql_transaction = "25001" + case branch_transaction_already_active = "25002" + case held_cursor_requires_same_isolation_level = "25008" + case inappropriate_access_mode_for_branch_transaction = "25003" + case inappropriate_isolation_level_for_branch_transaction = "25004" + case no_active_sql_transaction_for_branch_transaction = "25005" + case read_only_sql_transaction = "25006" + case schema_and_data_statement_mixing_not_supported = "25007" + case no_active_sql_transaction = "25P01" + case in_failed_sql_transaction = "25P02" + case idle_in_transaction_session_timeout = "25P03" + // Class 26 — Invalid SQL Statement Name + case invalid_sql_statement_name = "26000" + // Class 27 — Triggered Data Change Violation + case triggered_data_change_violation = "27000" + // Class 28 — Invalid Authorization Specification + case invalid_authorization_specification = "28000" + case invalid_password = "28P01" + // Class 2B — Dependent Privilege Descriptors Still Exist + case dependent_privilege_descriptors_still_exist = "2B000" + case dependent_objects_still_exist = "2BP01" + // Class 2D — Invalid Transaction Termination + case invalid_transaction_termination = "2D000" + // Class 2F — SQL Routine Exception + case sql_routine_exception = "2F000" + case function_executed_no_return_statement = "2F005" + case modifying_sql_data_not_permitted = "2F002" + case prohibited_sql_statement_attempted = "2F003" + case reading_sql_data_not_permitted = "2F004" + // Class 34 — Invalid Cursor Name + case invalid_cursor_name = "34000" + // Class 38 — External Routine Exception + case external_routine_exception = "38000" + case containing_sql_not_permitted = "38001" + // case modifying_sql_data_not_permitted = "38002" + // case prohibited_sql_statement_attempted = "38003" + // case reading_sql_data_not_permitted = "38004" + // Class 39 — External Routine Invocation Exception + case external_routine_invocation_exception = "39000" + case invalid_sqlstate_returned = "39001" + // case null_value_not_allowed = "39004" + case trigger_protocol_violated = "39P01" + case srf_protocol_violated = "39P02" + case event_trigger_protocol_violated = "39P03" + // Class 3B — Savepoint Exception + case savepoint_exception = "3B000" + case invalid_savepoint_specification = "3B001" + // Class 3D — Invalid Catalog Name + case invalid_catalog_name = "3D000" + // Class 3F — Invalid Schema Name + case invalid_schema_name = "3F000" + // Class 40 — Transaction Rollback + case transaction_rollback = "40000" + case transaction_integrity_constraint_violation = "40002" + case serialization_failure = "40001" + case statement_completion_unknown = "40003" + case deadlock_detected = "40P01" + // Class 42 — Syntax Error or Access Rule Violation + case syntax_error_or_access_rule_violation = "42000" + case syntax_error = "42601" + case insufficient_privilege = "42501" + case cannot_coerce = "42846" + case grouping_error = "42803" + case windowing_error = "42P20" + case invalid_recursion = "42P19" + case invalid_foreign_key = "42830" + case invalid_name = "42602" + case name_too_long = "42622" + case reserved_name = "42939" + case datatype_mismatch = "42804" + case indeterminate_datatype = "42P18" + case collation_mismatch = "42P21" + case indeterminate_collation = "42P22" + case wrong_object_type = "42809" + case undefined_column = "42703" + case undefined_function = "42883" + case undefined_table = "42P01" + case undefined_parameter = "42P02" + case undefined_object = "42704" + case duplicate_column = "42701" + case duplicate_cursor = "42P03" + case duplicate_database = "42P04" + case duplicate_function = "42723" + case duplicate_prepared_statement = "42P05" + case duplicate_schema = "42P06" + case duplicate_table = "42P07" + case duplicate_alias = "42712" + case duplicate_object = "42710" + case ambiguous_column = "42702" + case ambiguous_function = "42725" + case ambiguous_parameter = "42P08" + case ambiguous_alias = "42P09" + case invalid_column_reference = "42P10" + case invalid_column_definition = "42611" + case invalid_cursor_definition = "42P11" + case invalid_database_definition = "42P12" + case invalid_function_definition = "42P13" + case invalid_prepared_statement_definition = "42P14" + case invalid_schema_definition = "42P15" + case invalid_table_definition = "42P16" + case invalid_object_definition = "42P17" + // Class 44 — WITH CHECK OPTION Violation + case with_check_option_violation = "44000" + // Class 53 — Insufficient Resources + case insufficient_resources = "53000" + case disk_full = "53100" + case out_of_memory = "53200" + case too_many_connections = "53300" + case configuration_limit_exceeded = "53400" + // Class 54 — Program Limit Exceeded + case program_limit_exceeded = "54000" + case statement_too_complex = "54001" + case too_many_columns = "54011" + case too_many_arguments = "54023" + // Class 55 — Object Not In Prerequisite State + case object_not_in_prerequisite_state = "55000" + case object_in_use = "55006" + case cant_change_runtime_param = "55P02" + case lock_not_available = "55P03" + // Class 57 — Operator Intervention + case operator_intervention = "57000" + case query_canceled = "57014" + case admin_shutdown = "57P01" + case crash_shutdown = "57P02" + case cannot_connect_now = "57P03" + case database_dropped = "57P04" + // Class 58 — System Error (errors external to PostgreSQL itself) + case system_error = "58000" + case io_error = "58030" + case undefined_file = "58P01" + case duplicate_file = "58P02" + // Class 72 — Snapshot Failure + case snapshot_too_old = "72000" + // Class F0 — Configuration File Error + case config_file_error = "F0000" + case lock_file_exists = "F0001" + // Class HV — Foreign Data Wrapper Error (SQL/MED) + case fdw_error = "HV000" + case fdw_column_name_not_found = "HV005" + case fdw_dynamic_parameter_value_needed = "HV002" + case fdw_function_sequence_error = "HV010" + case fdw_inconsistent_descriptor_information = "HV021" + case fdw_invalid_attribute_value = "HV024" + case fdw_invalid_column_name = "HV007" + case fdw_invalid_column_number = "HV008" + case fdw_invalid_data_type = "HV004" + case fdw_invalid_data_type_descriptors = "HV006" + case fdw_invalid_descriptor_field_identifier = "HV091" + case fdw_invalid_handle = "HV00B" + case fdw_invalid_option_index = "HV00C" + case fdw_invalid_option_name = "HV00D" + case fdw_invalid_string_length_or_buffer_length = "HV090" + case fdw_invalid_string_format = "HV00A" + case fdw_invalid_use_of_null_pointer = "HV009" + case fdw_too_many_handles = "HV014" + case fdw_out_of_memory = "HV001" + case fdw_no_schemas = "HV00P" + case fdw_option_name_not_found = "HV00J" + case fdw_reply_handle = "HV00K" + case fdw_schema_not_found = "HV00Q" + case fdw_table_not_found = "HV00R" + case fdw_unable_to_create_execution = "HV00L" + case fdw_unable_to_create_reply = "HV00M" + case fdw_unable_to_establish_connection = "HV00N" + case plpgsql_error = "P0000" + case raise_exception = "P0001" + case no_data_found = "P0002" + case too_many_rows = "P0003" + case assert_failure = "P0004" + // Class XX — Internal Error + case internal_error = "XX000" + case data_corrupted = "XX001" + case index_corrupted = "XX002" + case unknown = "Unknown" + } +} + +// MARK: Inits + +extension PostgreSQLError { + public init(_ connection: Connection) { + let raw = String(cString: PQerrorMessage(connection.cConnection)) + + let message: String + if let error = PQerrorMessage(connection.cConnection) { + message = String(cString: error) + } else { + message = "Unknown" + } + + self.init( + rawCode: raw, + reason: message + ) + } + + public init(_ code: Code, reason: String) { + self.code = code + self.reason = reason + } + + public init(rawCode: String, reason: String) { + self.code = Code(rawValue: rawCode) ?? .unknown + self.reason = reason + } +} + +// MARK: Debuggable +import Debugging + +extension PostgreSQLError: Debuggable { + public static var readableName: String { + return "PostgreSQL Error" + } + + public var identifier: String { + return "\(code.rawValue) (\(code))" + } + + public var possibleCauses: [String] { + switch code { + case .connection_exception, .connection_does_not_exist, .connection_failure: + return [ + "The connection to the server degraded during the query", + "The connection has been open for too long", + "Too much data has been sent through the connection" + ] + default: + return [] + } + } + + public var suggestedFixes: [String] { + switch code { + case .syntax_error: + return [ + "Fix the invalid syntax in your query", + "If an ORM has generated this error, report the issue to its GitHub page" + ] + case .connection_exception, .connection_does_not_exist, .connection_failure: + return [ + "Increase the `wait_timeout`", + "Increase the `max_allowed_packet`" + ] + default: + return [] + } + } + + public var stackOverflowQuestions: [String] { + switch code { + case .syntax_error: + return [ + ] + default: + return [] + } + } + + public var documentationLinks: [String] { + return [ + ] + } +} From e507a60860633c29e882c0d8e455596c96d384ee Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 23:33:51 -0400 Subject: [PATCH 06/10] Change `isClosed` to isConnected. Change `host` to `hostname`. --- Sources/PostgreSQL/Connection.swift | 26 ++++++++++++++------- Sources/PostgreSQL/ConnectionInfo.swift | 10 ++++---- Sources/PostgreSQL/Database.swift | 2 +- Tests/PostgreSQLTests/ConnectionTests.swift | 8 +++---- Tests/PostgreSQLTests/Utilities.swift | 16 ++++++------- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/Sources/PostgreSQL/Connection.swift b/Sources/PostgreSQL/Connection.swift index 45ea5e3..fbe7322 100644 --- a/Sources/PostgreSQL/Connection.swift +++ b/Sources/PostgreSQL/Connection.swift @@ -7,7 +7,7 @@ import CPostgreSQL public final class Connection: ConnInfoInitializable { public let cConnection: OpaquePointer public var configuration: Configuration? - public var isClosed: Bool + public var isConnected: Bool public init(conninfo: ConnInfo) throws { let string: String @@ -17,15 +17,15 @@ 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.cConnection = PQconnectdb(string) if PQstatus(cConnection) == CONNECTION_OK { - isClosed = false + isConnected = true } else { - isClosed = true + isConnected = false throw DatabaseError.cannotEstablishConnection(lastError) } } @@ -89,21 +89,21 @@ public final class Connection: ConnInfoInitializable { } public func reset() throws { - guard !self.isClosed else { + guard self.isConnected else { throw PostgreSQLError(.connection_failure, reason: "Connection failed.") } PQreset(cConnection) - self.isClosed = false + setConnectedStatus() } public func close() throws { - guard !self.isClosed else { + guard self.isConnected else { throw PostgreSQLError(.connection_does_not_exist, reason: "Connection does not exist.") } PQfinish(cConnection) - self.isClosed = true + setConnectedStatus() } // Contains the last error message generated by the PostgreSQL connection. @@ -118,6 +118,14 @@ public final class Connection: ConnInfoInitializable { try? close() } + private func setConnectedStatus() -> Void { + if status() == CONNECTION_OK { + self.isConnected = true + } else { + self.isConnected = false + } + } + // MARK: - Load Configuration private func getConfiguration() throws -> Configuration { if let configuration = self.configuration { diff --git a/Sources/PostgreSQL/ConnectionInfo.swift b/Sources/PostgreSQL/ConnectionInfo.swift index 831c202..27f587f 100644 --- a/Sources/PostgreSQL/ConnectionInfo.swift +++ b/Sources/PostgreSQL/ConnectionInfo.swift @@ -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 { @@ -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)) } diff --git a/Sources/PostgreSQL/Database.swift b/Sources/PostgreSQL/Database.swift index c7fd2b6..290ef54 100644 --- a/Sources/PostgreSQL/Database.swift +++ b/Sources/PostgreSQL/Database.swift @@ -50,7 +50,7 @@ public final class Database: ConnInfoInitializable { try self.execute("LISTEN \(channel)", on: connection) while true { - if connection.isClosed == true { + if connection.isConnected == false { throw DatabaseError.cannotEstablishConnection(connection.lastError) } diff --git a/Tests/PostgreSQLTests/ConnectionTests.swift b/Tests/PostgreSQLTests/ConnectionTests.swift index b8054f0..b085288 100644 --- a/Tests/PostgreSQLTests/ConnectionTests.swift +++ b/Tests/PostgreSQLTests/ConnectionTests.swift @@ -14,11 +14,11 @@ class ConnectionTests: XCTestCase { postgreSQL = PostgreSQL.Database.makeTestConnection() let connection = try postgreSQL.makeConnection() - XCTAssertFalse(connection.isClosed) + XCTAssertTrue(connection.isConnected) try connection.reset() try connection.close() - XCTAssertTrue(connection.isClosed) + XCTAssertFalse(connection.isConnected) } func testConnInfoParams() { @@ -37,7 +37,7 @@ class ConnectionTests: XCTestCase { func testConnectionFailure() throws { let database = try PostgreSQL.Database( - host: "127.0.0.1", + hostname: "127.0.0.1", port: 5432, database: "some_long_db_name_that_does_not_exist", user: "postgres", @@ -57,7 +57,7 @@ class ConnectionTests: XCTestCase { func testConnectionSuccess() throws { do { let database = try PostgreSQL.Database( - host: "127.0.0.1", + hostname: "127.0.0.1", port: 5432, database: "test", user: "postgres", diff --git a/Tests/PostgreSQLTests/Utilities.swift b/Tests/PostgreSQLTests/Utilities.swift index 3374370..e03df2f 100644 --- a/Tests/PostgreSQLTests/Utilities.swift +++ b/Tests/PostgreSQLTests/Utilities.swift @@ -6,7 +6,7 @@ extension PostgreSQL.Database { static func makeTestConnection() -> PostgreSQL.Database { do { let postgreSQL = try PostgreSQL.Database( - host: "127.0.0.1", + hostname: "127.0.0.1", port: 5432, database: "test", user: "postgres", @@ -24,7 +24,7 @@ extension PostgreSQL.Database { print("You must configure PostgreSQL to run with the following configuration: ") print(" user: 'postgres'") print(" password: '' // (empty)") - print(" host: '127.0.0.1'") + print(" hostname: '127.0.0.1'") print(" database: 'test'") print() print() @@ -40,26 +40,26 @@ extension String { guard let characters = cString(using: .utf8) else { return [] } - + var data: [Int8] = [] data.reserveCapacity(characters.count / 2) - + var byteChars: [CChar] = [0, 0, 0] for i in stride(from: 0, to: characters.count - 1, by: 2) { byteChars[0] = characters[i] byteChars[1] = characters[i+1] let byteValue = UInt8(strtol(byteChars, nil, 16)) - + guard byteValue != 0 || (byteChars[0] == 48 && byteChars[1] == 48) else { return [] } - + data.append(Int8(bitPattern: byteValue)) } - + return data } - + var postgreSQLParsedDate: Date { struct Formatter { static let `static`: DateFormatter = { From bebca482cf267b5f78750c737155b21995362ca3 Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 23:39:50 -0400 Subject: [PATCH 07/10] Pass lastError to message param for better error output. --- Sources/PostgreSQL/Connection.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PostgreSQL/Connection.swift b/Sources/PostgreSQL/Connection.swift index fbe7322..64d0dd9 100644 --- a/Sources/PostgreSQL/Connection.swift +++ b/Sources/PostgreSQL/Connection.swift @@ -90,7 +90,7 @@ public final class Connection: ConnInfoInitializable { public func reset() throws { guard self.isConnected else { - throw PostgreSQLError(.connection_failure, reason: "Connection failed.") + throw PostgreSQLError(.connection_failure, reason: lastError) } PQreset(cConnection) @@ -99,7 +99,7 @@ public final class Connection: ConnInfoInitializable { public func close() throws { guard self.isConnected else { - throw PostgreSQLError(.connection_does_not_exist, reason: "Connection does not exist.") + throw PostgreSQLError(.connection_does_not_exist, reason: lastError) } PQfinish(cConnection) From ac659e1f9d8c97ac4c12f18fe8df0273430733c6 Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Thu, 6 Apr 2017 23:46:10 -0400 Subject: [PATCH 08/10] Set the isConnected var lazily. --- Sources/PostgreSQL/Connection.swift | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/Sources/PostgreSQL/Connection.swift b/Sources/PostgreSQL/Connection.swift index 64d0dd9..49dd48d 100644 --- a/Sources/PostgreSQL/Connection.swift +++ b/Sources/PostgreSQL/Connection.swift @@ -7,7 +7,12 @@ import CPostgreSQL public final class Connection: ConnInfoInitializable { public let cConnection: OpaquePointer public var configuration: Configuration? - public var isConnected: Bool + public var isConnected: Bool { + if PQstatus(cConnection) == CONNECTION_OK { + return true + } + return false + } public init(conninfo: ConnInfo) throws { let string: String @@ -22,12 +27,9 @@ public final class Connection: ConnInfoInitializable { } self.cConnection = PQconnectdb(string) - if PQstatus(cConnection) == CONNECTION_OK { - isConnected = true - } else { - isConnected = false - throw DatabaseError.cannotEstablishConnection(lastError) - } + if isConnected == false { + throw DatabaseError.cannotEstablishConnection(lastError) + } } @discardableResult @@ -94,7 +96,6 @@ public final class Connection: ConnInfoInitializable { } PQreset(cConnection) - setConnectedStatus() } public func close() throws { @@ -103,7 +104,6 @@ public final class Connection: ConnInfoInitializable { } PQfinish(cConnection) - setConnectedStatus() } // Contains the last error message generated by the PostgreSQL connection. @@ -118,14 +118,6 @@ public final class Connection: ConnInfoInitializable { try? close() } - private func setConnectedStatus() -> Void { - if status() == CONNECTION_OK { - self.isConnected = true - } else { - self.isConnected = false - } - } - // MARK: - Load Configuration private func getConfiguration() throws -> Configuration { if let configuration = self.configuration { From 87095dbccfca598b5c01b1b4fdb7bd43cea8857e Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Fri, 7 Apr 2017 00:30:02 -0400 Subject: [PATCH 09/10] Add tests for Context class and Connection.status() function --- Sources/PostgreSQL/Connection.swift | 11 ----------- Tests/PostgreSQLTests/ConnectionTests.swift | 4 +++- Tests/PostgreSQLTests/MiscTests.swift | 13 +++++++++++++ 3 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 Tests/PostgreSQLTests/MiscTests.swift diff --git a/Sources/PostgreSQL/Connection.swift b/Sources/PostgreSQL/Connection.swift index 49dd48d..f4f3b34 100644 --- a/Sources/PostgreSQL/Connection.swift +++ b/Sources/PostgreSQL/Connection.swift @@ -139,14 +139,3 @@ public final class Connection: ConnInfoInitializable { 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) - } -} diff --git a/Tests/PostgreSQLTests/ConnectionTests.swift b/Tests/PostgreSQLTests/ConnectionTests.swift index b085288..9d5cc98 100644 --- a/Tests/PostgreSQLTests/ConnectionTests.swift +++ b/Tests/PostgreSQLTests/ConnectionTests.swift @@ -1,5 +1,6 @@ import XCTest -import PostgreSQL +import CPostgreSQL +@testable import PostgreSQL class ConnectionTests: XCTestCase { static let allTests = [ @@ -14,6 +15,7 @@ class ConnectionTests: XCTestCase { postgreSQL = PostgreSQL.Database.makeTestConnection() let connection = try postgreSQL.makeConnection() + XCTAssert(connection.status() == CONNECTION_OK) XCTAssertTrue(connection.isConnected) try connection.reset() diff --git a/Tests/PostgreSQLTests/MiscTests.swift b/Tests/PostgreSQLTests/MiscTests.swift new file mode 100644 index 0000000..4bcfad1 --- /dev/null +++ b/Tests/PostgreSQLTests/MiscTests.swift @@ -0,0 +1,13 @@ +import XCTest +@testable import PostgreSQL + +class MiscTests: XCTestCase { + static let allTests = [ + ("testContext", testContext) + ] + + func testContext() throws { + let context = PostgreSQLContext.shared.isPostgreSQL + XCTAssert(context == true) + } +} From 8569400a36a1aeefa899d8dbe21fbc55de9a1c39 Mon Sep 17 00:00:00 2001 From: Nate Bird Date: Fri, 7 Apr 2017 00:54:48 -0400 Subject: [PATCH 10/10] Add the additional execute function back --- Sources/PostgreSQL/Connection.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/PostgreSQL/Connection.swift b/Sources/PostgreSQL/Connection.swift index f4f3b34..49dd48d 100644 --- a/Sources/PostgreSQL/Connection.swift +++ b/Sources/PostgreSQL/Connection.swift @@ -139,3 +139,14 @@ public final class Connection: ConnInfoInitializable { 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) + } +}