forked from vapor/postgres-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPostgreSQLData+String.swift
114 lines (100 loc) · 4.81 KB
/
PostgreSQLData+String.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import Foundation
extension String: PostgreSQLDataCustomConvertible {
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataType`
public static var postgreSQLDataType: PostgreSQLDataType { return .text }
/// See `PostgreSQLDataCustomConvertible.postgreSQLDataArrayType`
public static var postgreSQLDataArrayType: PostgreSQLDataType { return ._text }
/// See `PostgreSQLDataCustomConvertible.convertFromPostgreSQLData(_:)`
public static func convertFromPostgreSQLData(_ data: PostgreSQLData) throws -> String {
guard let value = data.data else {
throw PostgreSQLError(identifier: "string", reason: "Could not decode String from `null` data.")
}
switch data.format {
case .text:
guard let string = String(data: value, encoding: .utf8) else {
throw PostgreSQLError(identifier: "string", reason: "Non-UTF8 string: \(value.hexDebug).")
}
return string
case .binary:
switch data.type {
case .text, .name, .varchar, .bpchar:
guard let string = String(data: value, encoding: .utf8) else {
throw PostgreSQLError(identifier: "string", reason: "Non-UTF8 string: \(value.hexDebug).")
}
return string
case .point:
let point = try PostgreSQLPoint.convertFromPostgreSQLData(data)
return point.description
case .uuid:
return try UUID.convertFromPostgreSQLData(data).uuidString
case .numeric:
/// create mutable value since we will be using `.extract` which advances the buffer's view
var value = value
/// grab the numeric metadata from the beginning of the array
let metadata = value.extract(PostgreSQLNumericMetadata.self)
var integer = ""
var fractional = ""
for offset in 0..<metadata.ndigits.bigEndian {
/// extract current char and advance memory
let char = value.extract(Int16.self).bigEndian
/// conver the current char to its string form
let string: String
if char == 0 {
/// 0 means 4 zeros
string = "0000"
} else {
string = char.description
}
/// depending on our offset, append the string to before or after the decimal point
if offset < metadata.weight.bigEndian + 1 {
integer += string
} else {
fractional += string
}
}
/// use the dscale to remove extraneous zeroes at the end of the fractional part
let lastSignificantIndex = fractional.index(fractional.startIndex, offsetBy: Int(metadata.dscale.bigEndian))
fractional = String(fractional[..<lastSignificantIndex])
/// determine whether fraction is empty and dynamically add `.`
let numeric: String
if fractional != "" {
numeric = integer + "." + fractional
} else {
numeric = integer
}
/// use sign to determine adding a leading `-`
if metadata.sign.bigEndian == 1 {
return "-" + numeric
} else {
return numeric
}
default: throw PostgreSQLError(identifier: "string", reason: "Could not decode String from binary data type: \(data.type)")
}
}
}
/// See `PostgreSQLDataCustomConvertible.convertToPostgreSQLData()`
public func convertToPostgreSQLData() throws -> PostgreSQLData {
return PostgreSQLData(type: .text, format: .binary, data: Data(utf8))
}
}
/// Represents the meta information preceeding a numeric value.
/// Note: all values must be accessed adding `.bigEndian`
struct PostgreSQLNumericMetadata {
/// The number of digits after this metadata
var ndigits: Int16
/// How many of the digits are before the decimal point (always add 1)
var weight: Int16
/// If 1, this number is negative. Otherwise, positive.
var sign: Int16
/// The number of sig digits after the decimal place (get rid of trailing 0s)
var dscale: Int16
}
extension Data {
/// Convert the row's data into a string, throwing if invalid encoding.
internal func makeString(encoding: String.Encoding = .utf8) throws -> String {
guard let string = String(data: self, encoding: encoding) else {
throw PostgreSQLError(identifier: "utf8String", reason: "Unexpected non-UTF8 string: \(hexDebug).")
}
return string
}
}