🧽
Swift Fluent 4 ORMのPostgreSQLでJSONB型を使用する方法
概要
RDBでJSON型を使用したいときは時々あると思います。(例えばEvent SourcingをRDBでサクッとしたいときなど)
そんなとき、Server-Side Swiftフレームワーク Vaporの公式ORMであるFluentでJSONの使い方に少し詰まったのでこの記事を記述しました。
結論
- JSONとして格納したい型はCodableに準拠する
- モデルのプロパティにその型をそのまま使う
- Migration時に
.json
をカラム型として指定する
EventDAO.swift
import Fluent
import Foundation
final class EventDAO: Fluent.Model, @unchecked Sendable {
static let schema = "events"
@ID(key: .id)
var id: UUID?
@Field(key: "topic")
var topic: String
@Field(key: "entity_name")
var entityName: String
@Field(key: "entity_id")
var entityId: UUID
@Field(key: "event")
var event: Event // ← JSONB型に格納される。Event型はCodableに準拠
@Field(key: "created_at")
var createdAt: Date
init() { }
init(id: UUID? = nil, topic: String, entityName: String, entityId: UUID, event: Event, createdAt: Date) {
self.id = id
self.topic = topic
self.entityName = entityName
self.entityId = entityId
self.event = event
self.createdAt = createdAt
}
}
EventDAOMigration.swift
import Fluent
struct EventDAOMigration: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema("events")
.id()
.field("topic", .string, .required)
.field("entity_name", .string, .required)
.field("entity_id", .uuid, .required)
.field("event", .json, .required)
.field("created_at", .datetime, .required)
.create()
}
func revert(on database: any Database) async throws {
try await database.schema("events").delete()
}
}
解説
当初、@Field
プロパティラッパーで定義するプロパティをData型やString型にして試してみましたが、ORM側でJSON型に変換されず、以下のPSQLErrorが発生しました。
Data型、あるいはString型をJSONカラムに入れようとした場合のエラー
PSQLError(code: server, serverInfo: [sqlState: 42804, file: parse_target.c, hint: You will need to rewrite or cast the expression., line: 586, message: column "event" is of type jsonb but expression is of type bytea, position: 113, routine: transformAssignedExpr, localizedSeverity: ERROR, severity: ERROR], triggeredFromRequestInFile: PostgresKit/PostgresDatabase+SQL.swift, line: 57, query: PostgresQuery(sql: INSERT INTO "events" ("id", "topic", "entity_name", "entity_id", "event", "created_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id", binds: [(****; UUID; format: binary), (****; TEXT; format: binary), (****; TEXT; format: binary), (****; UUID; format: binary), (****; BYTEA; format: binary), (****; TIMESTAMPTZ; format: binary)]))
Codableに準拠した型をそのままプロパティに用いたところ、JSONとして挿入することができました。FluentではJSONカラムにはCodableに準拠したオブジェクト型のプロパティにする必要があるようです。
また、PostgreSQLにはJSON型とJSONB型がありますが、FluentKitの定義でマイグレーション時の.json
の型は.dictionary
と同じになるように設定されています
FluentPostgresDriverで.dictionary
はjsonb
型に指定されているので.json
指定するとカラムの型としてはjsonb
になります。
あとがき
あまりにも使い勝手が良いので、ついすべてをJSONで格納したくなる衝動に駆られます。特に、集約単位でのデータ管理やキャッシュ的な用途に、RDBでJSON型を使うのは非常に有効だと感じました。
Discussion