Skip to content

Commit 5d9b4ec

Browse files
calebklevetertanner0101
authored andcommitted
Custom JSON Coder for Postgres Coders (#129)
* Changed dependencies to Skelpo forks * Added .encoder and .decoder properties to FluentPostgresDatabase * Replaced PostgresRow:DatabaseRow with .databaseRow(using:) methods * Pass custom PostgresData<Coder> instances as properties of _FluentPostgresDatabase * Made PostgresRow.databaseRow(using:) method internal * Created FluentPostgresDriverTests.testCustomJSON test case * Replaced uses of PostgresRow.sqlRow with .sql * Link to vapor/postgres-kit instead of Skelpo fork * Fixed parameter name for PostgreDataDecoder parameter in .sql call
1 parent e348183 commit 5d9b4ec

File tree

4 files changed

+94
-14
lines changed

4 files changed

+94
-14
lines changed

Sources/FluentPostgresDriver/FluentPostgresDatabase.swift

+8-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import FluentSQL
33
struct _FluentPostgresDatabase {
44
let database: PostgresDatabase
55
let context: DatabaseContext
6+
7+
let encoder: PostgresDataEncoder
8+
let decoder: PostgresDataDecoder
69
}
710

811
extension _FluentPostgresDatabase: Database {
@@ -16,8 +19,8 @@ extension _FluentPostgresDatabase: Database {
1619
}
1720
let (sql, binds) = self.serialize(expression)
1821
do {
19-
return try self.query(sql, binds.map { try PostgresDataEncoder().encode($0) }) {
20-
onRow($0)
22+
return try self.query(sql, binds.map { try self.encoder.encode($0) }) {
23+
onRow($0.databaseRow(using: self.decoder))
2124
}
2225
} catch {
2326
return self.eventLoop.makeFailedFuture(error)
@@ -29,7 +32,7 @@ extension _FluentPostgresDatabase: Database {
2932
.convert(schema)
3033
let (sql, binds) = self.serialize(expression)
3134
do {
32-
return try self.query(sql, binds.map { try PostgresDataEncoder().encode($0) }) {
35+
return try self.query(sql, binds.map { try self.encoder.encode($0) }) {
3336
fatalError("unexpected row: \($0)")
3437
}
3538
} catch {
@@ -39,7 +42,7 @@ extension _FluentPostgresDatabase: Database {
3942

4043
func withConnection<T>(_ closure: @escaping (Database) -> EventLoopFuture<T>) -> EventLoopFuture<T> {
4144
self.database.withConnection {
42-
closure(_FluentPostgresDatabase(database: $0, context: self.context))
45+
closure(_FluentPostgresDatabase(database: $0, context: self.context, encoder: self.encoder, decoder: self.decoder))
4346
}
4447
}
4548
}
@@ -53,7 +56,7 @@ extension _FluentPostgresDatabase: SQLDatabase {
5356
sql query: SQLExpression,
5457
_ onRow: @escaping (SQLRow) -> ()
5558
) -> EventLoopFuture<Void> {
56-
self.sql().execute(sql: query, onRow)
59+
self.sql(encoder: encoder, decoder: decoder).execute(sql: query, onRow)
5760
}
5861
}
5962

Sources/FluentPostgresDriver/FluentPostgresDriver.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ struct _FluentPostgresDriver: DatabaseDriver {
6666
func makeDatabase(with context: DatabaseContext) -> Database {
6767
_FluentPostgresDatabase(
6868
database: self.pool.pool(for: context.eventLoop).database(logger: context.logger),
69-
context: context
69+
context: context,
70+
encoder: self.pool.source.configuration.encoder,
71+
decoder: self.pool.source.configuration.decoder
7072
)
7173
}
7274

Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1-
extension PostgresRow: DatabaseRow {
2-
public func contains(field: String) -> Bool {
3-
return self.column(field) != nil
1+
import class Foundation.JSONDecoder
2+
3+
extension PostgresRow {
4+
internal func databaseRow(using decoder: PostgresDataDecoder) -> DatabaseRow {
5+
return _PostgresDatabaseRow(row: self, decoder: decoder)
6+
}
7+
}
8+
9+
private struct _PostgresDatabaseRow: DatabaseRow {
10+
let row: PostgresRow
11+
let decoder: PostgresDataDecoder
12+
13+
var description: String { self.row.description }
14+
15+
func contains(field: String) -> Bool {
16+
return self.row.column(field) != nil
417
}
518

6-
public func decode<T>(
19+
func decode<T>(
720
field: String,
821
as type: T.Type,
922
for database: Database
1023
) throws -> T where T : Decodable {
11-
return try self.decode(column: field, as: T.self)
24+
return try self.row.sql(decoder: self.decoder).decode(column: field, as: T.self)
1225
}
1326
}

Tests/FluentPostgresDriverTests/FluentPostgresDriverTests.swift

+65-3
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,52 @@ final class FluentPostgresDriverTests: XCTestCase {
230230
try new.save(on: self.db).wait()
231231
}
232232

233+
func testCustomJSON() throws {
234+
struct Metadata: Codable {
235+
let createdAt: Date
236+
}
237+
238+
final class Event: Model {
239+
static let schema = "events"
240+
241+
@ID(key: "id") var id: Int?
242+
@Field(key: "metadata") var metadata: Metadata
243+
}
244+
245+
struct EventMigration: Migration {
246+
func prepare(on database: Database) -> EventLoopFuture<Void> {
247+
return database.schema(Event.schema)
248+
.field("id", .int, .identifier(auto: true))
249+
.field("metadata", .json, .required)
250+
.create()
251+
}
252+
253+
func revert(on database: Database) -> EventLoopFuture<Void> {
254+
return database.schema(Event.schema).delete()
255+
}
256+
}
257+
258+
try EventMigration().prepare(on: self.db).wait()
259+
defer { try! EventMigration().revert(on: self.db).wait() }
260+
261+
let date = Date()
262+
let event = Event()
263+
event.id = 1
264+
event.metadata = Metadata(createdAt: date)
265+
try event.save(on: self.db).wait()
266+
267+
let orm = Event.query(on: self.db).filter(\.$id == 1)
268+
try self.db.execute(query: orm.query, onRow: { row in
269+
do {
270+
let metadata = try row.decode(field: "metadata", as: [String: String].self, for: self.db)
271+
let expected = ISO8601DateFormatter().string(from: date)
272+
XCTAssertEqual(metadata["createdAt"], expected)
273+
} catch let error {
274+
XCTFail(error.localizedDescription)
275+
}
276+
}).wait()
277+
}
278+
233279

234280
var benchmarker: FluentBenchmarker {
235281
return .init(database: self.db)
@@ -242,17 +288,33 @@ final class FluentPostgresDriverTests: XCTestCase {
242288
}
243289

244290
override func setUp() {
245-
XCTAssert(isLoggingConfigured)
246-
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
291+
let jsonEncoder = JSONEncoder()
292+
jsonEncoder.dateEncodingStrategy = .iso8601
293+
294+
let jsonDecoder = JSONDecoder()
295+
jsonDecoder.dateDecodingStrategy = .iso8601
296+
247297
let hostname: String
248298
#if os(Linux)
249299
hostname = "psql"
250300
#else
251301
hostname = "localhost"
252302
#endif
303+
304+
let configuration = PostgresConfiguration(
305+
hostname: hostname,
306+
username: "vapor_username",
307+
password: "vapor_password",
308+
database: "vapor_database",
309+
encoder: PostgresDataEncoder(json: jsonEncoder),
310+
decoder: PostgresDataDecoder(json: jsonDecoder)
311+
)
312+
313+
XCTAssert(isLoggingConfigured)
314+
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
253315
self.threadPool = NIOThreadPool(numberOfThreads: 1)
254316
self.dbs = Databases(threadPool: threadPool, on: self.eventLoopGroup)
255-
self.dbs.use(.postgres(hostname: hostname, username: "vapor_username", password: "vapor_password", database: "vapor_database"), as: .psql)
317+
self.dbs.use(.postgres(configuration: configuration), as: .psql)
256318
}
257319

258320
override func tearDown() {

0 commit comments

Comments
 (0)