Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fluent 1.0.0 gm, add decoders + sql updates #72

Merged
merged 34 commits into from Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bc2d03f
fluent 3.0.0 gm, add decoders + sql updates
tanner0101 May 31, 2018
df25fcd
Merge branch 'master' into gm
tanner0101 May 31, 2018
2c311c1
small test fixes
tanner0101 May 31, 2018
3474089
data + message refactor
tanner0101 May 31, 2018
ec852e0
postgresql message parse refactor
tanner0101 Jun 1, 2018
d6a3d90
make tests self-cleanup
tanner0101 Jun 1, 2018
ebfe647
add CodableDataEncoder
tanner0101 Jun 1, 2018
d82d1f0
improve psql coders
tanner0101 Jun 1, 2018
669a9a6
uncomment tests + add new tests
tanner0101 Jun 1, 2018
7a9bd8c
PostgreSQLData.null
tanner0101 Jun 1, 2018
543885d
remove sql row encoder
tanner0101 Jun 1, 2018
f3f02b1
array parsing
tanner0101 Jun 2, 2018
82f1321
test fixes
tanner0101 Jun 2, 2018
4ff3fcf
add TIMESTAMPTZ support, fixes #63
tanner0101 Jun 2, 2018
1cd5eef
add decimal test, see #37
tanner0101 Jun 2, 2018
0fdca43
publicize listen/notify methods, fixes #40
tanner0101 Jun 2, 2018
3b977c0
use fluent gm branch for circle ci
tanner0101 Jun 2, 2018
66aa574
add numeric parsing for binary floating point, fixes #56
tanner0101 Jun 2, 2018
fa42c43
cleanup extraneous files
tanner0101 Jun 2, 2018
8e9c22e
fix crash in numeric parsing if no fraction
tanner0101 Jun 2, 2018
e53bc75
column type updates
tanner0101 Jun 5, 2018
9896172
add PostgreSQLQuery
tanner0101 Jun 6, 2018
399d24a
PostgreSQLQuery update
tanner0101 Jun 6, 2018
8109bad
PostgreSQLQuery file organization
tanner0101 Jun 6, 2018
d4fec1e
add additional SQL features + test fixes
tanner0101 Jun 6, 2018
3cc2fe0
fix tests + SelectBuilder
tanner0101 Jun 6, 2018
f84bd6a
remove usages of #warning
tanner0101 Jun 6, 2018
01fe965
remove conflicting enum cases for 4.1 mode
tanner0101 Jun 6, 2018
b9525f3
empty subset fix
tanner0101 Jun 7, 2018
41af608
add space fix before normal predicate serialization
tanner0101 Jun 7, 2018
6764696
small fixes + add orderby
tanner0101 Jun 8, 2018
9b488cd
add LIKE and ILIKE
tanner0101 Jun 8, 2018
8e5b40d
use Int64 for date math, fixes #53
tanner0101 Jun 15, 2018
80ddc3c
test FluentPostgreSQL gm
tanner0101 Jun 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 3 additions & 6 deletions Package.swift
Expand Up @@ -8,25 +8,22 @@ let package = Package(
],
dependencies: [
// 🌎 Utility package containing tools for byte manipulation, Codable, OS APIs, and debugging.
.package(url: "https://github.com/vapor/core.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/core.git", .branch("fluent-gm")),

// 🔑 Hashing (BCrypt, SHA, HMAC, etc), encryption, and randomness.
.package(url: "https://github.com/vapor/crypto.git", from: "3.0.0"),

// 🗄 Core services for creating database integrations.
.package(url: "https://github.com/vapor/database-kit.git", from: "1.0.0"),
.package(url: "https://github.com/vapor/database-kit.git", .branch("fluent-gm")),

// 📦 Dependency injection / inversion of control framework.
.package(url: "https://github.com/vapor/service.git", from: "1.0.0"),

// *️⃣ Build SQL queries in Swift.
.package(url: "https://github.com/vapor/sql.git", from: "1.0.0"),

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
],
targets: [
.target(name: "PostgreSQL", dependencies: ["Async", "Bits", "Core", "Crypto", "DatabaseKit", "NIO", "Service", "SQL"]),
.target(name: "PostgreSQL", dependencies: ["Async", "Bits", "Core", "Crypto", "DatabaseKit", "NIO", "Service"]),
.testTarget(name: "PostgreSQLTests", dependencies: ["Core", "PostgreSQL"]),
]
)
145 changes: 145 additions & 0 deletions Sources/PostgreSQL/Codable/PostgreSQLDataDecoder.swift
@@ -0,0 +1,145 @@
public struct PostgreSQLDataDecoder {
/// Creates a new `PostgreSQLDataDecoder`.
public init() {}

public func decode<D>(_ type: D.Type, from data: PostgreSQLData) throws -> D where D: Decodable {
if let convertible = type as? PostgreSQLDataConvertible.Type {
return try convertible.convertFromPostgreSQLData(data) as! D
}
return try D(from: _Decoder(data: data))
}

// MARK: Private

private struct _Decoder: Decoder {
let codingPath: [CodingKey] = []
var userInfo: [CodingUserInfoKey: Any] = [:]
let data: PostgreSQLData

init(data: PostgreSQLData) {
self.data = data
}

struct DecoderUnwrapper: Decodable {
let decoder: Decoder
init(from decoder: Decoder) throws {
self.decoder = decoder
}
}

struct JSON { }

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
let json: Data
switch data.type {
case .jsonb, .json:
switch data.storage {
case .binary(let data):
assert(data[data.startIndex] == 0x01, "invalid JSONB data format")
json = data.advanced(by: 1)
case .text(let string): json = Data(string.utf8)
default: throw PostgreSQLError.decode(JSON.self, from: data)
}
default: throw PostgreSQLError.decode(JSON.self, from: data)
}
let unwrapper = try JSONDecoder().decode(DecoderUnwrapper.self, from: json)
return try unwrapper.decoder.container(keyedBy: Key.self)
}

func unkeyedContainer() throws -> UnkeyedDecodingContainer {
switch data.storage {
case .binary(var value):
/// Extract and convert each element.
var array: [PostgreSQLData] = []
let hasData = value.extract(Int32.self).bigEndian
if hasData == 1 {
/// Unknown
let _ = value.extract(Int32.self).bigEndian
/// The big-endian array element type
let type: PostgreSQLDataType = .init(value.extract(Int32.self).bigEndian)
/// The big-endian length of the array
let count = value.extract(Int32.self).bigEndian
/// The big-endian number of dimensions
let _ = value.extract(Int32.self).bigEndian
for _ in 0..<count {
let count = Int(value.extract(Int32.self).bigEndian)
let subValue = value.extract(count: count)
let psqlData = PostgreSQLData(type, binary: subValue)
array.append(psqlData)
}
} else {
array = []
}
return _UnkeyedDecodingContainer(data: array)
default: throw PostgreSQLError(identifier: "array", reason: "Cannot decode array from: \(data).")
}
}

func singleValueContainer() throws -> SingleValueDecodingContainer {
return _SingleValueDecodingContainer(data: data)
}
}

private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer {
let codingPath: [CodingKey] = []
let data: [PostgreSQLData]
var count: Int? {
return data.count
}
var isAtEnd: Bool {
return currentIndex >= data.count
}
var currentIndex: Int

init(data: [PostgreSQLData]) {
self.data = data
self.currentIndex = 0
}

mutating func decodeNil() throws -> Bool {
defer { currentIndex += 1 }
return data[currentIndex].isNull
}

mutating func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
defer { currentIndex += 1 }
return try PostgreSQLDataDecoder().decode(T.self, from: data[currentIndex])
}

mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
fatalError()
}

mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
fatalError()
}

mutating func superDecoder() throws -> Decoder {
fatalError()
}

}

private struct _SingleValueDecodingContainer: SingleValueDecodingContainer {
let codingPath: [CodingKey] = []
let data: PostgreSQLData

init(data: PostgreSQLData) {
self.data = data
}

public func decodeNil() -> Bool {
switch data.storage {
case .null: return true
default: return false
}
}

func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
guard let convertible = type as? PostgreSQLDataConvertible.Type else {
return try T(from: _Decoder(data: data))
}
return try convertible.convertFromPostgreSQLData(data) as! T
}
}
}
96 changes: 96 additions & 0 deletions Sources/PostgreSQL/Codable/PostgreSQLQueryEncoder.swift
@@ -0,0 +1,96 @@
/// Encodes `Encodable` objects to PostgreSQL row data.
public struct PostgreSQLQueryEncoder {
/// Creates a new `PostgreSQLRowEncoder`.
public init() { }

/// Encodes an `Encodable` object to `[String: PostgreSQLQuery.DML.Value]`.
///
/// - parameters:
/// - encodable: Item to encode.
public func encode<E>(_ encodable: E) throws -> [String: PostgreSQLQuery.Value]
where E: Encodable
{
let encoder = _Encoder()
try encodable.encode(to: encoder)
return encoder.row
}

// MARK: Private

private final class _Encoder: Encoder {
let codingPath: [CodingKey] = []
var userInfo: [CodingUserInfoKey: Any] = [:]
var row: [String: PostgreSQLQuery.Value]

init() {
self.row = [:]
}

func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
return .init(_KeyedEncodingContainer(encoder: self))
}

func unkeyedContainer() -> UnkeyedEncodingContainer {
fatalError()
}

func singleValueContainer() -> SingleValueEncodingContainer {
fatalError()
}
}

private struct _KeyedEncodingContainer<Key>: KeyedEncodingContainerProtocol where Key: CodingKey {
let codingPath: [CodingKey] = []
let encoder: _Encoder
init(encoder: _Encoder) {
self.encoder = encoder
}

mutating func encodeNil(forKey key: Key) throws {
encoder.row[key.stringValue] = .null
}

mutating func encode<T>(_ encodable: T, forKey key: Key) throws where T : Encodable {
encoder.row[key.stringValue] = try PostgreSQLValueEncoder().encode(encodable)
}

mutating func _encodeIfPresent<T>(_ value: T?, forKey key: Key) throws where T : Encodable {
if let value = value {
try encode(value, forKey: key)
} else {
try encodeNil(forKey: key)
}
}

mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key) throws where T : Encodable { try _encodeIfPresent(value, forKey: key)}
mutating func encodeIfPresent(_ value: Int?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Double?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Float?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: String?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }
mutating func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { try _encodeIfPresent(value, forKey: key) }

mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
fatalError()
}

mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
fatalError()
}

mutating func superEncoder() -> Encoder {
fatalError()
}

mutating func superEncoder(forKey key: Key) -> Encoder {
fatalError()
}
}
}
101 changes: 101 additions & 0 deletions Sources/PostgreSQL/Codable/PostgreSQLRowDecoder.swift
@@ -0,0 +1,101 @@
/// Decodes `Decodable` types from PostgreSQL row data.
public struct PostgreSQLRowDecoder {
/// Creates a new `PostgreSQLRowDecoder`.
public init() { }

/// Decodes a `Decodable` object from `[DataColumn: PostgreSQLData]`.
///
/// - parameters:
/// - decodable: Type to decode.
/// - row: PostgreSQL row to decode.
/// - tableName: Optional table OID to use when decoding. If supplied, columns with table OIDs
/// can be matched while decoding. Columns without table OIDs will always match if the column name matches.
/// - returns: Instance of Decodable type.
public func decode<D>(_ decodable: D.Type, from row: [PostgreSQLColumn: PostgreSQLData], tableOID: UInt32 = 0) throws -> D
where D: Decodable
{
return try D(from: _Decoder(row: row, tableOID: tableOID))
}

// MARK: Private

private struct _Decoder: Decoder {
let codingPath: [CodingKey] = []
var userInfo: [CodingUserInfoKey: Any] = [:]
let row: [PostgreSQLColumn: PostgreSQLData]
let tableOID: UInt32

init(row: [PostgreSQLColumn: PostgreSQLData], tableOID: UInt32) {
self.row = row
self.tableOID = tableOID
}

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
return .init(_KeyedDecodingContainer(row: row, tableOID: tableOID))
}

func unkeyedContainer() throws -> UnkeyedDecodingContainer {
fatalError()
}

func singleValueContainer() throws -> SingleValueDecodingContainer {
fatalError()
}
}

private struct _KeyedDecodingContainer<Key>: KeyedDecodingContainerProtocol where Key: CodingKey {
let codingPath: [CodingKey] = []
let row: [PostgreSQLColumn: PostgreSQLData]
let tableOID: UInt32
let allKeys: [Key]

init(row: [PostgreSQLColumn: PostgreSQLData], tableOID: UInt32) {
self.row = row
self.tableOID = tableOID
self.allKeys = row.keys.compactMap { col in
if tableOID == 0 || col.tableOID == tableOID || col.tableOID == 0 {
return col.name
} else {
return nil
}
}.compactMap(Key.init(stringValue:))
}

func contains(_ key: Key) -> Bool {
return allKeys.contains { $0.stringValue == key.stringValue }
}

func decodeNil(forKey key: Key) throws -> Bool {
guard let data = row.firstValue(tableOID: tableOID, name: key.stringValue) else {
return true
}
switch data.storage {
case .null: return true
default: return false
}
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
guard let data = row.firstValue(tableOID: tableOID, name: key.stringValue) else {
throw DecodingError.valueNotFound(T.self, .init(codingPath: codingPath + [key], debugDescription: "Could not decode \(T.self)."))
}
return try PostgreSQLDataDecoder().decode(T.self, from: data)
}

func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
fatalError()
}

func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
fatalError()
}

func superDecoder() throws -> Decoder {
fatalError()
}

func superDecoder(forKey key: Key) throws -> Decoder {
fatalError()
}
}
}