Skip to content

Commit d4fec1e

Browse files
committed
add additional SQL features + test fixes
1 parent 8109bad commit d4fec1e

13 files changed

+464
-105
lines changed

Sources/PostgreSQL/Codable/PostgreSQLQueryEncoder.swift

+23
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,29 @@ public struct PostgreSQLQueryEncoder {
5454
encoder.row[key.stringValue] = try PostgreSQLValueEncoder().encode(encodable)
5555
}
5656

57+
mutating func _encodeIfPresent<T>(_ value: T?, forKey key: Key) throws where T : Encodable {
58+
if let value = value {
59+
try encode(value, forKey: key)
60+
} else {
61+
try encodeNil(forKey: key)
62+
}
63+
}
64+
65+
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key) throws where T : Encodable { try _encodeIfPresent(value, forKey: key)}
66+
mutating func encodeIfPresent(_ value: Int?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
67+
mutating func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
68+
mutating func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
69+
mutating func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
70+
mutating func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
71+
mutating func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
72+
mutating func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
73+
mutating func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
74+
mutating func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
75+
mutating func encodeIfPresent(_ value: Double?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
76+
mutating func encodeIfPresent(_ value: Float?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
77+
mutating func encodeIfPresent(_ value: String?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
78+
mutating func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
79+
5780
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
5881
fatalError()
5982
}

Sources/PostgreSQL/Data/PostgreSQLData.swift

+8-3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public struct PostgreSQLData: Equatable {
7171
extension PostgreSQLData: CustomStringConvertible {
7272
/// See `CustomStringConvertible`.
7373
public var description: String {
74+
let readable: String
7475
switch storage {
7576
case .binary(let data):
7677
var override: String?
@@ -83,13 +84,17 @@ extension PostgreSQLData: CustomStringConvertible {
8384
if let utf8 = String(data: data.dropFirst(), encoding: .utf8) {
8485
override = utf8
8586
}
87+
case .int8: override = data.as(Int64.self, default: 0).bigEndian.description
88+
case .int4: override = data.as(Int32.self, default: 0).bigEndian.description
89+
case .int2: override = data.as(Int16.self, default: 0).bigEndian.description
90+
case .uuid: override = UUID.init(uuid: data.as(uuid_t.self, default: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))).description
8691
default: break
8792
}
8893

89-
let readable = override ?? "0x" + data.hexEncodedString()
90-
return readable + ":" + type.description
91-
case .text(let string): return "\"" + string + "\":" + type.description
94+
readable = override ?? "0x" + data.hexEncodedString()
95+
case .text(let string): readable = "\"" + string + "\""
9296
case .null: return "null"
9397
}
98+
return readable + " (" + type.description + ")"
9499
}
95100
}

Sources/PostgreSQL/SQL/PostgreSQLQuery+ColumnConstraint.swift

+34-20
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension PostgreSQLQuery {
2323
public enum Constraint {
2424
case notNull
2525
case null
26-
case check(Expression, noInherit: Bool)
26+
case check(Key, noInherit: Bool)
2727
case `default`(Expression)
2828
public enum Generated {
2929
case always
@@ -33,12 +33,26 @@ extension PostgreSQLQuery {
3333
case generated(Generated)
3434
case unique
3535
case primaryKey
36-
case references(reftable: String, refcolumn: String, onDelete: ForeignKeyAction?, onUpdate: ForeignKeyAction?)
36+
case references(Reference)
37+
}
38+
39+
public struct Reference {
40+
public var foreignTable: String
41+
public var foreignColumn: String
42+
public var onDelete: ForeignKeyAction?
43+
public var onUpdate: ForeignKeyAction?
44+
45+
public init(foreignTable: String, foreignColumn: String, onDelete: ForeignKeyAction? = nil, onUpdate: ForeignKeyAction? = nil) {
46+
self.foreignTable = foreignTable
47+
self.foreignColumn = foreignColumn
48+
self.onDelete = onDelete
49+
self.onUpdate = onUpdate
50+
}
3751
}
3852

3953
public var name: String?
4054
public var constraint: Constraint
41-
public init(_ constraint: Constraint, as name: String? = nil) {
55+
public init(_ constraint: Constraint, name: String? = nil) {
4256
self.constraint = constraint
4357
self.name = name
4458
}
@@ -48,7 +62,7 @@ extension PostgreSQLQuery {
4862
extension PostgreSQLSerializer {
4963
internal func serialize(_ constraint: PostgreSQLQuery.ColumnConstraint) -> String {
5064
if let name = constraint.name {
51-
return "CONSTRAINT " + name + " " + serialize(constraint.constraint)
65+
return "CONSTRAINT " + escapeString(name) + " " + serialize(constraint.constraint)
5266
} else {
5367
return serialize(constraint.constraint)
5468
}
@@ -72,22 +86,22 @@ extension PostgreSQLSerializer {
7286
}
7387
case .unique: return "UNIQUE"
7488
case .primaryKey: return "PRIMARY KEY"
75-
case .references(let reftable, let refcolumn, let onDelete, let onUpdate):
76-
// REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
77-
// [ ON DELETE action ] [ ON UPDATE action ] }
78-
var sql: [String] = []
79-
sql.append("REFERENCES")
80-
sql.append(reftable)
81-
sql.append("(" + refcolumn + ")")
82-
if let onDelete = onDelete {
83-
sql.append("ON DELETE")
84-
sql.append(serialize(onDelete))
85-
}
86-
if let onUpdate = onUpdate {
87-
sql.append("ON UPDATE")
88-
sql.append(serialize(onUpdate))
89-
}
90-
return sql.joined(separator: " ")
89+
case .references(let reference): return serialize(reference)
90+
}
91+
}
92+
internal func serialize(_ reference: PostgreSQLQuery.ColumnConstraint.Reference) -> String {
93+
var sql: [String] = []
94+
sql.append("REFERENCES")
95+
sql.append(escapeString(reference.foreignTable))
96+
sql.append(group([escapeString(reference.foreignColumn)]))
97+
if let onDelete = reference.onDelete {
98+
sql.append("ON DELETE")
99+
sql.append(serialize(onDelete))
100+
}
101+
if let onUpdate = reference.onUpdate {
102+
sql.append("ON UPDATE")
103+
sql.append(serialize(onUpdate))
91104
}
105+
return sql.joined(separator: " ")
92106
}
93107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
extension PostgreSQLQuery {
2+
public static func delete(
3+
locality: UpdateLocality = .inherited,
4+
from table: TableName,
5+
where predicate: Predicate? = nil,
6+
returning keys: Key...
7+
) -> PostgreSQLQuery {
8+
return .delete(.init(locality: locality, table: table, predicate: predicate, returning: keys))
9+
}
10+
11+
/// `DELETE` query.
12+
///
13+
/// [ WITH [ RECURSIVE ] with_query [, ...] ]
14+
/// DELETE FROM [ ONLY ] table_name [ * ] [ [ AS ] alias ]
15+
/// [ USING using_list ]
16+
/// [ WHERE condition | WHERE CURRENT OF cursor_name ]
17+
/// [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]
18+
///
19+
/// https://www.postgresql.org/docs/10/static/sql-delete.html
20+
public struct Delete {
21+
/// See `UpdateLocality`.
22+
public var locality: UpdateLocality
23+
24+
/// The name (optionally schema-qualified) of the table to delete rows from.
25+
public var table: TableName
26+
27+
#warning("Add USING to delete query.")
28+
29+
/// DELETE deletes rows that satisfy the WHERE clause from the specified table.
30+
/// If the WHERE clause is absent, the effect is to delete all rows in the table.
31+
/// The result is a valid, but empty table.
32+
public var predicate: Predicate?
33+
34+
/// The optional RETURNING clause causes DELETE to compute and return value(s) based on each row actually deleted.
35+
/// Any expression using the table's columns, and/or columns of other tables mentioned in USING, can be computed.
36+
/// The syntax of the RETURNING list is identical to that of the output list of SELECT.
37+
public var returning: [Key]
38+
39+
/// Creates a new `Delete`.
40+
public init(locality: UpdateLocality = .inherited, table: TableName, predicate: Predicate? = nil, returning: [Key] = []) {
41+
self.locality = locality
42+
self.table = table
43+
self.predicate = predicate
44+
self.returning = returning
45+
}
46+
}
47+
}
48+
49+
extension PostgreSQLSerializer {
50+
mutating func serialize(_ delete: PostgreSQLQuery.Delete, _ binds: inout [PostgreSQLData]) -> String {
51+
var sql: [String] = []
52+
sql.append("DELETE FROM")
53+
switch delete.locality {
54+
case .inherited: break
55+
case .selfOnly: sql.append("ONLY")
56+
}
57+
sql.append(serialize(delete.table))
58+
if let predicate = delete.predicate {
59+
sql.append("WHERE")
60+
sql.append(serialize(predicate, &binds))
61+
}
62+
if !delete.returning.isEmpty {
63+
sql.append("RETURNING")
64+
sql.append(delete.returning.map(serialize).joined(separator: ", "))
65+
}
66+
return sql.joined(separator: " ")
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,40 @@
11
extension PostgreSQLQuery {
2-
public enum Expression {
3-
public static func function(_ name: String, _ parameters: Expression..., as alias: String? = nil) -> Expression {
4-
return .function(name, parameters, as: alias)
5-
}
6-
7-
public static func function(_ name: String, _ parameters: [Expression], as alias: String? = nil) -> Expression {
8-
return .function(.init(name: name, parameters: parameters), alias: alias)
2+
public enum Key {
3+
public static func expression(_ expression: Expression) -> Key {
4+
return .expression(expression, alias: nil)
95
}
106

11-
public static func column(_ column: Column, as alias: String? = nil) -> Expression {
12-
return .column(column, alias: alias)
13-
}
7+
/// *
8+
case all
9+
case expression(Expression, alias: String?)
10+
}
11+
12+
13+
public struct Function {
14+
var name: String
15+
var parameters: [Expression]?
1416

15-
public struct Function {
16-
var name: String
17-
var parameters: [Expression]
17+
public init(name: String, parameters: [Expression]? = nil) {
18+
self.name = name
19+
self.parameters = parameters
1820
}
19-
20-
/// *
21+
}
22+
23+
public enum Expression {
2124
case all
22-
case column(Column, alias: String?)
23-
case function(Function, alias: String?)
25+
case column(Column)
26+
case function(Function)
2427
case stringLiteral(String)
2528
case literal(String)
2629
}
2730
}
2831

32+
extension PostgreSQLQuery.Function: ExpressibleByStringLiteral {
33+
public init(stringLiteral value: String) {
34+
self.init(name: value)
35+
}
36+
}
37+
2938
extension PostgreSQLQuery.Expression: ExpressibleByIntegerLiteral {
3039
public init(integerLiteral value: Int) {
3140
self = .literal(value.description)
@@ -40,32 +49,56 @@ extension PostgreSQLQuery.Expression: ExpressibleByFloatLiteral {
4049

4150
extension PostgreSQLQuery.Expression: ExpressibleByStringLiteral {
4251
public init(stringLiteral value: String) {
43-
self = .column(.init(stringLiteral: value), alias: nil)
52+
self = .stringLiteral(value.description)
53+
}
54+
}
55+
56+
extension PostgreSQLQuery.Key: ExpressibleByIntegerLiteral {
57+
public init(integerLiteral value: Int) {
58+
self = .expression(.init(integerLiteral: value), alias: nil)
59+
}
60+
}
61+
62+
extension PostgreSQLQuery.Key: ExpressibleByFloatLiteral {
63+
public init(floatLiteral value: Double) {
64+
self = .expression(.init(floatLiteral: value), alias: nil)
65+
}
66+
}
67+
68+
extension PostgreSQLQuery.Key: ExpressibleByStringLiteral {
69+
public init(stringLiteral value: String) {
70+
self = .expression(.column(.init(stringLiteral: value)), alias: nil)
4471
}
4572
}
4673

4774
extension PostgreSQLSerializer {
48-
internal func serialize(_ expression: PostgreSQLQuery.Expression) -> String {
49-
switch expression {
50-
case .stringLiteral(let string): return stringLiteral(string)
51-
case .literal(let literal): return literal
52-
case .column(let column, let alias):
75+
internal func serialize(_ key: PostgreSQLQuery.Key) -> String {
76+
switch key {
77+
case .expression(let expression, let alias):
5378
if let alias = alias {
54-
return serialize(column) + " AS " + escapeString(alias)
79+
return serialize(expression) + " AS " + escapeString(alias)
5580
} else {
56-
return serialize(column)
57-
}
58-
case .function(let function, let alias):
59-
if let alias = alias {
60-
return serialize(function) + " AS " + escapeString(alias)
61-
} else {
62-
return serialize(function)
81+
return serialize(expression)
6382
}
6483
case .all: return "*"
6584
}
6685
}
6786

68-
internal func serialize(_ function: PostgreSQLQuery.Expression.Function) -> String {
69-
return function.name + group(function.parameters.map(serialize))
87+
internal func serialize(_ expression: PostgreSQLQuery.Expression) -> String {
88+
switch expression {
89+
case .all: return "*"
90+
case .stringLiteral(let string): return stringLiteral(string)
91+
case .literal(let literal): return literal
92+
case .column(let column): return serialize(column)
93+
case .function(let function): return serialize(function)
94+
}
95+
}
96+
97+
internal func serialize(_ function: PostgreSQLQuery.Function) -> String {
98+
if let parameters = function.parameters {
99+
return function.name + group(parameters.map(serialize))
100+
} else {
101+
return function.name
102+
}
70103
}
71104
}

Sources/PostgreSQL/SQL/PostgreSQLQuery+Insert.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ extension PostgreSQLQuery {
22
public static func insert(
33
into table: TableName,
44
values: [String: Value],
5-
returning keys: Expression...
5+
returning keys: Key...
66
) -> PostgreSQLQuery {
77
let insert = Insert(table: table, values: values, returning: keys)
88
return .insert(insert)
@@ -11,9 +11,9 @@ extension PostgreSQLQuery {
1111
public struct Insert {
1212
public var table: TableName
1313
public var values: [String: Value]
14-
public var returning: [Expression]
14+
public var returning: [Key]
1515

16-
public init(table: TableName, values: [String: Value], returning: [Expression] = []) {
16+
public init(table: TableName, values: [String: Value], returning: [Key] = []) {
1717
self.table = table
1818
self.values = values
1919
self.returning = returning

0 commit comments

Comments
 (0)