Skip to content

Commit a565178

Browse files
committed
add bool support
1 parent 5f5feca commit a565178

File tree

6 files changed

+194
-126
lines changed

6 files changed

+194
-126
lines changed

Sources/PostgreSQL/Data/PostgreSQLData+BinaryFloatingPoint.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ extension BinaryFloatingPoint {
5454
}
5555
}
5656

57+
extension Double: PostgreSQLDataCustomConvertible { }
58+
extension Float: PostgreSQLDataCustomConvertible { }
59+
5760
extension Data {
5861
/// Converts this data to a floating-point number.
5962
internal func makeFloatingPoint<F>(_ type: F.Type = F.self) -> F where F: FloatingPoint {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Foundation
2+
3+
extension Bool: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.preferredDataType`
5+
public static var preferredDataType: PostgreSQLDataType? { return .bool }
6+
7+
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
8+
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Bool {
9+
guard let value = data.data else {
10+
throw PostgreSQLError(identifier: "bool", reason: "Could not decode String from `null` data.")
11+
}
12+
guard value.count == 1 else {
13+
throw PostgreSQLError(identifier: "bool", reason: "Could not decode Bool from value: \(value)")
14+
}
15+
switch data.format {
16+
case .text:
17+
switch value[0] {
18+
case .t: return true
19+
case .f: return false
20+
default: throw PostgreSQLError(identifier: "bool", reason: "Could not decode Bool from text: \(value)")
21+
}
22+
case .binary:
23+
switch value[0] {
24+
case 1: return true
25+
case 0: return false
26+
default: throw PostgreSQLError(identifier: "bool", reason: "Could not decode Bool from binary: \(value)")
27+
}
28+
}
29+
}
30+
31+
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
32+
public func convertToPostgreSQLData() throws -> PostgreSQLData {
33+
return PostgreSQLData(type: .bool, format: .binary, data: self ? _true : _false)
34+
}
35+
}
36+
37+
private let _true = Data([0x01])
38+
private let _false = Data([0x00])
39+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Foundation
2+
3+
extension Data: PostgreSQLDataCustomConvertible {
4+
/// See `PostgreSQLDataCustomConvertible.preferredDataType`
5+
public static var preferredDataType: PostgreSQLDataType? { return .bytea }
6+
7+
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
8+
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Data {
9+
guard let value = data.data else {
10+
throw PostgreSQLError(identifier: "data", reason: "Could not decode Data from `null` data.")
11+
}
12+
13+
switch data.type {
14+
case .bytea:
15+
switch data.format {
16+
case .text: return try Data(hexString: value[2...].makeString())
17+
case .binary: return value
18+
}
19+
default: throw PostgreSQLError(identifier: "data", reason: "Could not decode Data from data type: \(data.type)")
20+
}
21+
}
22+
23+
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
24+
public func convertToPostgreSQLData() throws -> PostgreSQLData {
25+
return PostgreSQLData(type: .bytea, format: .binary, data: self)
26+
}
27+
}
28+
29+
extension Data {
30+
/// Initialize data from a hex string.
31+
internal init(hexString: String) {
32+
var data = Data()
33+
34+
var gen = hexString.makeIterator()
35+
while let c1 = gen.next(), let c2 = gen.next() {
36+
let s = String([c1, c2])
37+
guard let d = UInt8(s, radix: 16) else {
38+
break
39+
}
40+
41+
data.append(d)
42+
}
43+
44+
self.init(data)
45+
}
46+
}

Sources/PostgreSQL/Data/PostgreSQLData+FixedWidthInteger.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension FixedWidthInteger {
1515
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
1616
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> Self {
1717
guard let value = data.data else {
18-
throw PostgreSQLError(identifier: "data", reason: "Could not decode String from `null` data.")
18+
throw PostgreSQLError(identifier: "fixedWidthInteger", reason: "Could not decode \(Self.self) from `null` data.")
1919
}
2020
switch data.format {
2121
case .binary:
@@ -27,8 +27,9 @@ extension FixedWidthInteger {
2727
default: throw DecodingError.typeMismatch(Self.self, .init(codingPath: [], debugDescription: ""))
2828
}
2929
case .text:
30-
guard let converted = try Self(data.decode(String.self)) else {
31-
fatalError()
30+
let string = try value.makeString()
31+
guard let converted = Self(string) else {
32+
throw PostgreSQLError(identifier: "fixedWidthInteger", reason: "Could not decode \(Self.self) from text: \(string).")
3233
}
3334
return converted
3435
}

Sources/PostgreSQL/DataType/PostgreSQLDataType+Parse.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,4 @@ extension String {
114114
}
115115
}
116116

117-
extension Data {
118-
/// Initialize data from a hex string.
119-
fileprivate init(hexString: String) {
120-
var data = Data()
121117

122-
var gen = hexString.makeIterator()
123-
while let c1 = gen.next(), let c2 = gen.next() {
124-
let s = String([c1, c2])
125-
guard let d = UInt8(s, radix: 16) else {
126-
break
127-
}
128-
129-
data.append(d)
130-
}
131-
132-
self.init(data)
133-
}
134-
135-
}

Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift

Lines changed: 102 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -21,111 +21,108 @@ class PostgreSQLConnectionTests: XCTestCase {
2121
XCTFail("Results count not large enough: \(results.count)")
2222
}
2323
}
24-
//
25-
// func testParse() throws {
26-
// let (client, eventLoop) = try PostgreSQLConnection.makeTest()
27-
// let query = """
28-
// select * from "pg_type" where "typlen" = $1 or "typlen" = $2
29-
// """
30-
// let rows = try client.query(query, [
31-
// .int32(1),
32-
// .int32(2),
33-
// ]).await(on: eventLoop)
34-
//
35-
// for row in rows {
36-
// XCTAssert(row["typlen"]?.int == 1 || row["typlen"]?.int == 2)
37-
// }
38-
// }
39-
//
40-
// func testTypes() throws {
41-
// let (client, eventLoop) = try PostgreSQLConnection.makeTest()
42-
// let createQuery = """
43-
// create table kitchen_sink (
44-
// "smallint" smallint,
45-
// "integer" integer,
46-
// "bigint" bigint,
47-
// "decimal" decimal,
48-
// "numeric" numeric,
49-
// "real" real,
50-
// "double" double precision,
51-
// "varchar" varchar(64),
52-
// "char" char(4),
53-
// "text" text,
54-
// "bytea" bytea,
55-
// "timestamp" timestamp,
56-
// "date" date,
57-
// "time" time,
58-
// "boolean" boolean,
59-
// "point" point
60-
// -- "line" line,
61-
// -- "lseg" lseg,
62-
// -- "box" box,
63-
// -- "path" path,
64-
// -- "polygon" polygon,
65-
// -- "circle" circle,
66-
// -- "cidr" cidr,
67-
// -- "inet" inet,
68-
// -- "macaddr" macaddr,
69-
// -- "bit" bit(16),
70-
// -- "uuid" uuid
71-
// );
72-
// """
73-
// _ = try client.query("drop table if exists kitchen_sink;").await(on: eventLoop)
74-
// let createResult = try client.query(createQuery).await(on: eventLoop)
75-
// XCTAssertEqual(createResult.count, 0)
76-
//
77-
// let insertQuery = """
78-
// insert into kitchen_sink values (
79-
// 1, -- "smallint" smallint
80-
// 2, -- "integer" integer
81-
// 3, -- "bigint" bigint
82-
// 4, -- "decimal" decimal
83-
// 5.3, -- "numeric" numeric
84-
// 6, -- "real" real
85-
// 7, -- "double" double precision
86-
// '9', -- "varchar" varchar(64)
87-
// '10', -- "char" char(4)
88-
// '11', -- "text" text
89-
// '12', -- "bytea" bytea
90-
// now(), -- "timestamp" timestamp
91-
// current_date, -- "date" date
92-
// localtime, -- "time" time
93-
// true, -- "boolean" boolean
94-
// point(13.5,14) -- "point" point,
95-
// -- "line" line,
96-
// -- "lseg" lseg,
97-
// -- "box" box,
98-
// -- "path" path,
99-
// -- "polygon" polygon,
100-
// -- "circle" circle,
101-
// -- "cidr" cidr,
102-
// -- "inet" inet,
103-
// -- "macaddr" macaddr,
104-
// -- "bit" bit(16),
105-
// -- "uuid" uuid
106-
// );
107-
// """
108-
// let insertResult = try! client.query(insertQuery).await(on: eventLoop)
109-
// XCTAssertEqual(insertResult.count, 0)
110-
// let queryResult = try client.query("select * from kitchen_sink").await(on: eventLoop)
111-
// if queryResult.count == 1 {
112-
// let row = queryResult[0]
113-
// XCTAssertEqual(row["smallint"], .int16(1))
114-
// XCTAssertEqual(row["integer"], .int32(2))
115-
// XCTAssertEqual(row["bigint"], .int64(3))
116-
// XCTAssertEqual(row["decimal"], .string("4"))
117-
// XCTAssertEqual(row["real"], .float(6))
118-
// XCTAssertEqual(row["double"], .double(7))
119-
// XCTAssertEqual(row["varchar"], .string("9"))
120-
// XCTAssertEqual(row["char"], .string("10 "))
121-
// XCTAssertEqual(row["text"], .string("11"))
122-
// XCTAssertEqual(row["bytea"], .data(Data([0x31, 0x32])))
123-
// XCTAssertEqual(row["boolean"], .int8(0x01))
124-
// XCTAssertEqual(row["point"], .point(x: 13.5, y: 14))
125-
// } else {
126-
// XCTFail("query result count is: \(queryResult.count)")
127-
// }
128-
// }
24+
25+
func testParse() throws {
26+
let (client, eventLoop) = try PostgreSQLConnection.makeTest()
27+
let query = """
28+
select * from "pg_type" where "typlen" = $1 or "typlen" = $2
29+
"""
30+
let rows = try client.query(query, [1, 2]).await(on: eventLoop)
31+
32+
for row in rows {
33+
try XCTAssert(row["typlen"]?.decode(Int.self) == 1 || row["typlen"]?.decode(Int.self) == 2)
34+
}
35+
}
36+
37+
func testTypes() throws {
38+
let (client, eventLoop) = try PostgreSQLConnection.makeTest()
39+
let createQuery = """
40+
create table kitchen_sink (
41+
"smallint" smallint,
42+
"integer" integer,
43+
"bigint" bigint,
44+
"decimal" decimal,
45+
"numeric" numeric,
46+
"real" real,
47+
"double" double precision,
48+
"varchar" varchar(64),
49+
"char" char(4),
50+
"text" text,
51+
"bytea" bytea,
52+
"timestamp" timestamp,
53+
"date" date,
54+
"time" time,
55+
"boolean" boolean,
56+
"point" point
57+
-- "line" line,
58+
-- "lseg" lseg,
59+
-- "box" box,
60+
-- "path" path,
61+
-- "polygon" polygon,
62+
-- "circle" circle,
63+
-- "cidr" cidr,
64+
-- "inet" inet,
65+
-- "macaddr" macaddr,
66+
-- "bit" bit(16),
67+
-- "uuid" uuid
68+
);
69+
"""
70+
_ = try client.query("drop table if exists kitchen_sink;").await(on: eventLoop)
71+
let createResult = try client.query(createQuery).await(on: eventLoop)
72+
XCTAssertEqual(createResult.count, 0)
73+
74+
let insertQuery = """
75+
insert into kitchen_sink values (
76+
1, -- "smallint" smallint
77+
2, -- "integer" integer
78+
3, -- "bigint" bigint
79+
4, -- "decimal" decimal
80+
5.3, -- "numeric" numeric
81+
6, -- "real" real
82+
7, -- "double" double precision
83+
'9', -- "varchar" varchar(64)
84+
'10', -- "char" char(4)
85+
'11', -- "text" text
86+
'12', -- "bytea" bytea
87+
now(), -- "timestamp" timestamp
88+
current_date, -- "date" date
89+
localtime, -- "time" time
90+
true, -- "boolean" boolean
91+
point(13.5,14) -- "point" point,
92+
-- "line" line,
93+
-- "lseg" lseg,
94+
-- "box" box,
95+
-- "path" path,
96+
-- "polygon" polygon,
97+
-- "circle" circle,
98+
-- "cidr" cidr,
99+
-- "inet" inet,
100+
-- "macaddr" macaddr,
101+
-- "bit" bit(16),
102+
-- "uuid" uuid
103+
);
104+
"""
105+
let insertResult = try! client.query(insertQuery).await(on: eventLoop)
106+
XCTAssertEqual(insertResult.count, 0)
107+
let queryResult = try client.query("select * from kitchen_sink").await(on: eventLoop)
108+
if queryResult.count == 1 {
109+
let row = queryResult[0]
110+
try XCTAssertEqual(row["smallint"]?.decode(Int16.self), 1)
111+
try XCTAssertEqual(row["integer"]?.decode(Int32.self), 2)
112+
try XCTAssertEqual(row["bigint"]?.decode(Int64.self), 3)
113+
try XCTAssertEqual(row["decimal"]?.decode(String.self), "4")
114+
try XCTAssertEqual(row["real"]?.decode(Float.self), 6)
115+
try XCTAssertEqual(row["double"]?.decode(Double.self), 7)
116+
try XCTAssertEqual(row["varchar"]?.decode(String.self), "9")
117+
try XCTAssertEqual(row["char"]?.decode(String.self), "10 ")
118+
try XCTAssertEqual(row["text"]?.decode(String.self), "11")
119+
try XCTAssertEqual(row["bytea"]?.decode(Data.self), Data([0x31, 0x32]))
120+
try XCTAssertEqual(row["boolean"]?.decode(Bool.self), true)
121+
// try XCTAssertEqual(row["point"]?.decode(Int16.self), .point(x: 13.5, y: 14)) // FIXME: decode point?
122+
} else {
123+
XCTFail("query result count is: \(queryResult.count)")
124+
}
125+
}
129126
//
130127
// func testParameterizedTypes() throws {
131128
// let (client, eventLoop) = try PostgreSQLConnection.makeTest()

0 commit comments

Comments
 (0)