iOSのORMの比較

## はじめに
`RealmSwift`を使うことが多いけど別なOSSや公式のCoreDataならもっと楽に実装できるのでは？
と思ったので今回は以下のフレームワークで

- CoreData 
- Realm v5.4.4
- GRDB v4.14.0
- FMDB v2.7.5

CRUD, migrationを実装してみるのと
それぞれで単純なオブジェクトをinsert/Selectした際のベンチマークを取ってみたい思います。
記事で扱うモデルの定義は以下のようにし

``` swift:
struct Publisher {
  let id: Int
  let name: String
  let owner: Owner
  let books: [Book]
}

struct Book {
  let id: Int
  let name: String
  let price: Int
}

struct Owner {
  let id: Int
  let name: String
  let age: Int
  let profile: String
}
```


テーブルの定義は以下のようにしました。

- publisher

| id | name | owner_id |
|:-:|:-:|:-:|
| INTEGER | TEXT | INTEGER |

- owner

| id | name | age | profile |
|:-:|:-:|:-:| :-: |
| INTEGER | TEXT | INTEGER | TEXT |

- book

| id | name | price | publisher_id |
|:-:|:-:|:-:|:-:|
| INTEGER | TEXT | INTEGER | INTEGER |

このモデルにした理由としては、ORMを扱う記事の多くはシンプルなモデルを扱っているので
1:多と1：1の説明が少なかったのでそのどちらもを含む構造にしたかったからです。

各フレームワークを使用して操作を行うクラスは以下のprotocolに準拠するようにしました。

``` swift
protocol PublisherStore {
  func create(_ publisher: Publisher)
  func read() -> [Publisher]
  func update(_ publisher: Publisher)
  func delete(_ publisher: Publisher)
}
```

XcodeとCocoaPodsのバージョンは以下

- Xcode v12.0
- CocoaPods v1.9.1

コードは[こちらです](https://github.com/Moriya-Taichi/ORMComparison-iOS)


## CoreData
Apple公式が提供するSQLiteのORMで`.xcdatamodel`でモデルを定義し、`NSPersistentContainer`のcontextを使用してCRUD操作を行います。
contextはfetchしたオブジェクトのキャッシュや変更などを記録しており`redo`,`undo`,`rollback`,`reset`などの操作を備えています。
テーブルの作成は明示的に行う必要はなく、自動的に作成されます。
Read以外の操作では変更を`.sqlite`のファイルに反映するために共通して操作後に`context.save()`を呼ぶ必要があります。

### Create
`init(context:)`で作成し、プロパティに値をセットした後に`context.insert(object:)`で挿入します。
重複させたくない場合は事前にクエリを投げて確認して回避することもできますが、AttributeにConstraintをつけることでユニークにすることができ回避できます。
今回はPublisher,Owner,Bookともに一意なidを持っているのでidに対してConstraintを設定します。
これによってオブジェクトが同じidを持つ場合は挿入時にConflictが発生するようになります。
Conflictは解決方法が設定でき、今回の場合では`NSMergeByPropertyObjectTrumpMergePolicy`を設定しているので上書きされるようになります。
大量の件数を処理したい時はiOS13以上に限定されますが`NSBatchInsertRequest`を使用することができます。こちらはrelationshipを持たないentityのみcontextを通さずにDBにinsertすることができ、メモリ消費量や速度の面で優れています。

``` swift
func create(_ publisher: Publisher) {
    let publisherEntity = PublisherEntity(context: context)

    publisherEntity.id = Int64(publisher.id)
    publisherEntity.name = publisher.name

    let ownerEntity = getOrCreateOwner(publisher.owner)
    ownerEntity.name = publisher.owner.name
    ownerEntity.age = Int32(publisher.owner.age)
    ownerEntity.profile = publisher.owner.profile
    publisherEntity.owner = ownerEntity

    publisher.books.forEach { book in
        let bookEntity = BookEntity(context: context)
        bookEntity.id = Int64(book.id)
        bookEntity.name = book.name
        bookEntity.price = Int64(book.price)
        publisherEntity.addToBooks(bookEntity)
    }

    context.insert(publisherEntity)
    try? context.save()
}
```

### Read
CoreDataのfetchでは単純に取得する方法と、`NSFetchResultController`を使う方法の２種類があります。
前者はその通りな振る舞いに対して後者はdelegateで変更の通知を受け取れたり
indexPathでfetchされたオブジェクトにアクセスできるのでAppleはTableViewとの連携をする際の使用をオススメしています。
また`NSFetchedResultsController`はinit時にキャッシュ名を設定するとキャッシュを作成でき、
更新日時を監視して必要に応じてキャッシュから読み込んでくれる機能も付いています。
しかしながら指定できる型はCoreDataで設定したEntityのみに限定されます。

どちらの方法でもベースは同じで、まずはリクエストを作成します。
作成するのは非常に簡単で取得したいタイプで`Hoge.fetchRequest()`で作成できます。
検索条件や取得数、ソートの設定はリクエストに対して行なっていきます。

検索条件の設定は`NSPredicate`で行います。概ねSQLと同じように書けば特に問題なく動作します。
注意点としては`IN`のようにリストで渡す場合はIntやStringのままでいいのですが
単体で渡す場合にはその型に応じて`NSNumber`, `NSString`, `NSDate`などに変換する必要があります。

- 普通にfetchする方法

``` swift
func read() -> [Publisher] {
    let context = container.viewContext
    let request: NSFetchRequest<PublisherEntity> = PublisherEntity.fetchRequest()
    //検索条件の設定
    //request.predicate = .init(format: "id < %@", NSNumber(10))
    //request.predicate = .init(format: "id IN %@", [0, 1, 2, 3])
    guard let publisherEntities = try? context.fetch(request) else {
        return []
    }
    return publisherEntities.map { publisherEntity -> Publisher in 
        //ただのmapなので省略
    }
}
```

- NSFetchControllerを使う方法

``` swift
func read() -> NSFetchController<PublisherEntity> {
    let context = container.viewContext
    let request: NSFetchRequest<PublisherEntity>  = PublisherEntity.fetchReqest()
    let controller = NSFetchedResultsController(
            fetchRequest: request,
            managedObjectContext: context,
            sectionNameKeyPath: nil,
            cacheName: nil
    )

    try? controller.performFetch()
    return controller
}
//fetchした結果にアクセスするには下記でできる
controller.fetchedObjects

//またIndexPathでもアクセスできる
controller.object(at: IndexPath(row: 0, section: 0))
```

- `@FetchRequest`

SwiftUIを使用している場合では`@FetchRequest`のAttributeで自動的にfetchすることができます。

``` swift
@FetchRequest(
    sortDescriptors: [.init(key: "id", ascending: true)],
    predicate: .init(format: "id < %@", NSNumber(10)),
    animation: .default
)
private var result: FetchedResults<PublisherEntity>
```

### Update
アップデートは既に保存されているオブジェクトのプロパティを変更してcontextをセーブすれば反映されます
relationshipのbooksに関しては差分更新、全部消してinsertし直すなどの様々な更新方法があると思いますが
今回はinsertし直す方法を載せています。他の方法も元のコードには書いています。
`NSBatchInsertRequest`のように`NSBatchUpdateRequest`がiOS8以上で使用可能です

``` swift
func update(_ publisher: Publisher) {
    let context = container.viewContext()

    //idが同じobjectを1個fetchするようにrequestを作成
    let request: NSFetchRequest<PublisherEntity> = PublisherEntity.fetchRequest()
    request.predicate = .init("id = @%", id)
    request.fetchLimit = 1
    guard let publisherEntity = try? context.fetch(request).first else {
        return
    }
    publisherEntity.name = publisher.name
    publisherEntity.owner.name = publisher.owner.name
    publisherEntity.owner.age = Int32(publisher.owner.age)
    publisherEntity.owner.profile = publisher.owner.profile
    
    bookEntities.forEach { bookEntity in
        publisherEntity.removeFromBooks(bookEntity)
        context.delete(bookEntity)
    }

    publisher.books.forEach { book in
        let bookEntity = BookEntity(context: context)
        bookEntity.id = Int64(book.id)
        bookEntity.name = book.name
        bookEntity.price = Int64(book.price)
        context.insert(bookEntity)
        publisherEntity.addToBooks(bookEntity)
    }
}
```

### Delete
`context.delete(object:)`でdeleteすることができます。
Relationshipを持っているオブジェクトを削除した場合は設定してあるDeleteRuleによって挙動が変わります。

- No Action
 - 何もしない
- Nullify
 - 参照された場合nilになる
- Cascade
 - 参照先も削除
- Deny
 - 参照先がされている場合は削除されない

``` swift
func delete(_ publisher: Publisher) {
        let context = container.viewContext
        let request: NSFetchRequest<PublisherEntity> = PublisherEntity.fetchRequest()
        request.fetchLimit = 1
        request.predicate = .init(format: "id = %@", NSNumber(value: publisher.id))
        guard
            let publisherEntity = try? context.fetch(request).first
        else {
            return
        }
        context.delete(publisherEntity)
        try? context.save()
}
```

特定のEntityを全て削除したい場合は以下の２通りの方法があります。
例として今回扱っている`PublisherEntity`を全て削除する場合は次のコードになります

``` swift
func deleteAll() {
        let context = container.viewContext
        //単純に全部fetchしてループで削除する方法
        let request: NSFetchRequest<PublisherEntity> = PublisherEntity.fetchRequest()
        let publisherEntities = try? context.fetch(request)
        publisherEntities?.forEach { publisherEntity in
            context.delete(publisherEntity)
        }
        try? context.save()

        //NSBatchDeleteRequestを使う方法(iOS9以上)
        let fetchRequest: NSFetchRequest<NSFetchRequestResult> = .init(
            entityName: String(describing: PublisherEntity.self)
        )
        let deletePublisherEntityRequest = NSBatchDeleteRequest(
            fetchRequest: fetchRequest
        )
        try? context.execute(deletePublisherEntityRequest)
} 
```

### migration
CoreDataのmigrationにはLightWeightMigrationとHeavyWeightMigrationの2種類があります。
LightWeightMigrationの条件は以下にあたり、コードを書かなくてもcontainerのload時に自動的に行われます。

- Attributeの追加
- Attributeの削除
- AttributeのOptional/非Optionalの変更
- EntityとAttributeの名前の変更

それに対してHeavyWeightMigrationではMappingModelやコードでどのように対応しているかを示す必要があります。
手順としてはフラグを設定、以下のように`File -> New -> MappingModel`でMappingModelを作成、必要に応じてCustom Policyを設定、といったようになります
まずはcontainerに対して、以下のように`NSPersistentStoreDescription`でLightWeightMigrationを使わないように設定します。
`NSPersistentStoreDescription`のデフォルトのurlは`/Dev/null`になっており、init時に指定しないと`inMemory`の動作になってしまうため注意が必要です。

```swift
let path = try! FileManager.default
        .url(
            for: .applicationSupportDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: false
        )
        .appendingPathComponent("<ModuleName>.sqlite")
//もしくは
let path = (container.persistentStoreDescriptions.last?.url!)!
let description = NSPersistentStoreDescription(url: path)
description.shouldInferMappingModelAutomatically = false
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { _,_ in 
//HogeHoge
}
```

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289843/1cf84d62-a374-e9b6-e466-66eaeb0219fa.png width=50%>

MappingModelは以下の画像のように以降元のモデルから移行先のモデルへの対応づけが記述されています。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289843/9ec68a26-934f-e439-82e6-57b385d7b06b.png width=60%>

しかしながら複雑な処理を行いたい場合はCustom Policyを使う必要があります。
実装としては以下のように`NSEntityMigrationPolicy `のサブクラスを作成します。

``` swift
final class PublisherMigration: NSEntityMigrationPolicy {
    override func createDestinationInstances(
        forSource sInstance: NSManagedObject,
        in mapping: NSEntityMapping,
        manager: NSMigrationManager
    ) throws {
        //Migration
        if sInstance.entity.name == "Publisher" {
            let context = manager.destinationContext

            let publisherName = sInstance.primitiveValue(forKey: "name") as? String
            let publisherID = sInstance.primitiveValue(forKey: "id") as? Int

//            example
//            let newEntity = NSEntityDescription
//                .insertNewObject(
//                    forEntityName: .init(describing: NewEntity.self),
//                    into: context
//                )
//            newEntity.setValue(publisherName, forKey: "name")
        }
    }
}
```
注意点として、`createDestinationInstance`において新しいオブジェクトをCreateの際に使っていた`.init(context: )`は使えないので
`insertedObject`を使ってオブジェクトを作成する必要があります。

その後、MappingModelの任意のMappingのCustomPolicyに対してclass名をセットすれば完了です。
この時モジュール名をつけるのを忘れないでください。
また、Sourceの欄に何もセットしていない場合、MappingModelが無視されます。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289843/51f8ba5d-48d4-bc0c-f2aa-a41de02d542e.png width=60%>


上記で使われている`NSEntityMigrationPolicy`にはViewControllerのようにライフサイクルがあり、以下のようになっています。

- `begin`: Migration開始
- `createDestinationInstance`: 移行先のインスタンスの作成 
- `endInstanceCreation`: 移行先のインスタンスの作成の完了
- `createRelationship`: relationshipの作成
- `endRelationshipCreation`: relationshipの作成完了
- `performCustomValidation`: 移行先のインスタンスのValidation
- `end`: Migration終了


## Realm
定番なのであまり説明は要らないと思いますが、モバイル向けに作られた軽量なデータベースです。
コードでモデルの定義がかけることや、パフォーマンスの高さから採用が多いと思います。

###モデルの定義
`Object`を継承したClassで定義します。1 : 1の関係を持つ際は必ずOptionalで宣言します。
1 : 多では`List<Object>`を使います。
PrimaryKeyは`primaryKey() -> String? `でプロパティ名を返すことで設定できます。

```swift
class PublisherObject: Object {
    @objc dynamic var id: Int = -1
    @objc dynamic var name: String = ""
    @objc dynamic var owner: OwnerObject?
    let books = List<BookObject>()

    static override func primaryKey() -> String? {
        return "id"
    }
}

```


### Create
基本的にはCoredataとほとんど同じで、Objectをinitした後に値をセットし
`realm.add(object: )`でinsertします。

``` swift
func create(_ publisher: Publisher) {
    try? realm.write {
        let newPublisherObject = PublisherObject()
        newPublisherObject.id = publisher.id
        newPublisherObject.name = publisher.name

        let newOwnerObject = OwnerObject()
        newOwnerObject.id = owner.id
        newOwnerObject.name = owner.name
        newOwnerObject.profile = owner.profile
        newOwnerObject.age = owner.age

        newPublisherObject.owner = newOwnerObject

        publisher.books.forEach { book in
            let bookObject = BookObject()
            bookObject.id = book.id
            bookObject.name = book.name
            bookObject.price = book.price
            newPublisherObject.books.append(bookObject)
        }
        try? realm.add(newPublisherObject)
    }
}
```

### Read
Readに関しては非常に簡単で`realm.objects(objectType: )`でfetchすることができます。

``` swift
func read() -> [Publisher] {
    let publisherObjects = realm.objects(PublisherObject.self)
    return publisherObjects.map { publisherObject -> Publisher in
         //ただのmapなので省略
    }
}
```

### Update
realmでは二つの方法でupdateができます。
一つはトランザクションの中でオブジェクトのプロパティを変更する方法です。
これはCoredataとほとんど変わらずに実装することができます。
もう一つは単純に`realm.add(object: , update: )`を使う方法です。
こちらでは、updateに対して`.all`を設定することでadd時に重複した場合は、自動的にupdateしてくれます。

``` swift
func update(_ publisher: Publisher) {
    guard
        let publisherObjects = realm.object(
            ofType: PublisherObject.self,
            forPrimaryKey: publisher.id
        )
    else {
        return
    }
    try? realm.write {
        publisherObject.name = publisher.name

        publisherObject.owner?.age = publisher.owner.age
        publisherObject.owner?.name = publisher.owner.name
        publisherObject.owner?.profile = publisher.owner.profile

        realm.delete(publisherObject.books)
        publisher.books.forEach { book in
            let bookObject = BookObject()
            bookObject.id = book.id
            bookObject.name = book.name
            bookObject.price = book.price
            publisherObject.books.append(bookObject)
        }
    }    
}
```

### Delete
Deleteに関してもCoredataと似ていて、Delete対象をfetchしてきて
`realm.delete(object: )`で消去することができます。　

``` swift
func delete(_ publisher: Publisher) {
    guard let publisherObject = realm.object(
            ofType: PublisherObject.self,
            forPrimaryKey: publisher.id)
    else {
        return
    }
    try? realm.write {
        realm.delete(publisherObject)
    }
}
```

特定のObjectを全て消したい時は、一度全てをfetchする必要があります。

``` swift
func deleteAll() {
    realm.delete(realm.objects(PublisherObjects.self))
}
```

### migration
migrationはrealmのinit時に渡すConfigurationで設定します。
`(Migration, UInt64)`のクロージャーであれば渡せるのでそのままConfigurationに書くのもいいですし、分割して書くのもいいと思います。
値の変更を伴う処理では`migration.enumerateObjects(ofType: )`で特定のObjectのmigrationの処理を書くことができます。
このクロージャーの中では、第一引数に以前のオブジェクトが第二引数に新しいオブジェクトが渡されるので、
以下のコードのように`object!["プロパティ名"]`で値にアクセスして任意の処理を行いましょう。
またプロパティのリネームのみの場合は`migration.renameProperty(onType: , from: , to: )`で変更前のプロパティ名と変更後のプロパティ名を指定するだけで行えます。

`schemevesion`は手動で管理しないといけないので気をつけて扱いましょう。
もしデータがなくなってもいい場合は、 `deleteRealmIfMigrationNeeded`をtrueにすることでデータを消して新しく作り直してくれます。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         


``` swift
let realmConfiguration = Realm.Configuration(
    schemaVersion: 1,
    migrationBlock: { migration, schemaVersion in
        if schemaVersion < 2 {
            migration.enumerateObjects(ofType: PublisherObject.className()) { oldObject, newObject in
                    //let publisherName = oldObject!["name"]
                    //newObject!["Hoge"] = publisherName + "hogehoge" 
            }

            migration.renameProperty(
                onType: PublisherObject.className(),
                from: "name",
                to: "hoge"
            )
        }
    },
    deleteRealmIfMigrationNeeded: false
)
```


## GRDB.swift
2015にリリースされて今も活発に更新されているSQLiteのORMです。
モデルに対してProtocolを準拠させていくだけでCRUDが可能でSwiftらしく書けるのが特徴です。
またドキュメントが豊富なので一通り読んで軽く動かしてみれば、
実戦で使用できる難易度の低さもいい点だと思います。

テスト時やデバッグ時でデータベースをInMemoryにしたい場合はpathの指定なしで
`DatabaseQueue()`でinitすることで可能です。
Databaseへのアクセスは`Queue`と`Pool`の2つが用意されています。

### Tableの作成方法

テーブルの作成は以下のように行います。
カラムはカラム名と型を渡して設定します。
デフォルトではnullを許容しているので許容したくない場合は`.notNull()`
indexの貼り方もコードでわかると思いますが`.indexd()`ではれます。
外部キーは`references(tableName:)`でテーブル名を指定すると設定できます。
下記ではownerIdをownerテーブルのidに対して外部キーを設定しています。
`ownerId`がキャメルケースなのは後述しますがCodingKeyを使用して省略している為です。

``` swift
try? databaseQueue.write { database in
            try? database.create(
                table: GRDBObject
                    .Publisher
                    .databaseTableName,
                temporary: false,
                ifNotExists: true
            ) { table in
                table.column("id", .integer)
                    .indexed()
                    .notNull()
                    .primaryKey()
                table.column("name", .text).notNull()
                table.column("ownerId", .integer)
                    .notNull()
                    .indexed()
                    .references(
                        Owner.databaseTableName
                    )
            }
}

```

### CRUD
CRUDを行うにはモデルが`FetchableRecord`に加えて`MutablePersistableRecord`か`PersistableRecord`のどちらに準拠させる必要があります。

まずは前者の`FetchableRecord`について

こちらはその名の通りで準拠したモデルをfetch可能にするprotocolです。
準拠するとDBのカラムからinitするための`init(row:)`が追加されます。

`TableRecord`というPrimaryKeyでの検索を可能にするprotocolも存在しますが、
後述する`MutablePersistableRecord`と`PersistableRecord`に内包されているのでfetchのみの場合をのぞいて単体で使うことはあまりありません。

次に後者の`MutablePersistableRecord`と`PersistableRecord`について

これはinsertやdelete,updateといった操作を可能にするためのprotocolで、
準拠すると`encode()`というDBへ保存する際にどのプロパティがカラムに対応しているかを設定するメソッドが追加されます。
また`DidInsert(rowID: UInt64)`というメソッドも追加され、Insert時にIDが渡されるのでそれを自身のIDにセットすることができます。

使い分けについては

- モデルがstructで`AUTO INCREMENT`な主キーを使いたい -> `MutablePersistableRecord`を使用、DidInsertを実装
- モデルがclass or structで主キーは別に設定 -> `PersistableRecord`を使用、DidInsertは実装しない

という使い分けが推奨されています。

`FetchableRecord`と`TableRecord`はDecodableに対応していれば`init(row: )`を
`MutablePersistableRecord`と`PersistableRecord`はEncodableに対応していれば`encode()`を省略することができます。　
つまりはCodableに対応させると特に処理を書かないでCRUDが可能になります。
このようにして省略した場合はエンコード、デコードどちらでもCodingKeyを使って操作するのでCodingKeyかプロパティ名をスネークケースにしない場合はキャメルケースが使われます。
今回の例ではCodableに準拠させているので、カラム名をキャメルケースにしています。

### モデルの定義
以上を踏まえて今回使用するモデルは以下のようになります。
1 : 1の場合は`belongsTo() or hasOne()`、1 : 多の場合は`hasMany()`を使用することで関連づけが可能です。
GRDBObjectの下に置いてるのは名前の関係上そうしてるだけなので実際に使う場合は普通に宣言して構わないです。

```swift
final class GRDBObject {
    struct Publisher: Codable {
        let id: Int
        let name: String
        let ownerId: Int
    }

    struct Book: Codable {
        let id: Int
        let name: String
        let price: Int
        let publisherId: Int
    }
}

extension Publisher: FetchableRecord {}

extension Owner: FetchableRecord, PersistableRecord {
    static let publishers = hasMany(GRDBObject.Publisher.self)
    var publishers: QueryInterfaceRequest<GRDBObject.Publisher> {
        request(for: Owner.publishers)
    }
}

extension GRDBObject.Book: FetchableRecord, PersistableRecord {
    static let publisher = belongsTo(GRDBObject.Publisher.self)
    var publisher: QueryInterfaceRequest<GRDBObject.Publisher> {
        request(for: Self.publisher)
    }

//    enum Columns: String, ColumnExpression {
//        case id, name, price, publisherId
//    }

//    CodingKeyを使わずに独自に設定したい場合
//    func encode(to container: inout PersistenceContainer) {
//        container[Columns.id] = id
//        container[Columns.name] = name
//        container["price"] = price
//        container["publisherId"] = publisherId
//    }

//    CodingKeyを使わずに独自に設定したい場合
//    init(row: Row) {
//        id = row["id"]
//        name = row["name"]
//        price = row["price"]
//        publisherId = row["publisherId"]
//    }
}

extension GRDBObject.Publisher: FetchableRecord, PersistableRecord {
    static let books = hasMany(GRDBObject.Book.self)
    static let owner = belongsTo(Owner.self)

    var books: QueryInterfaceRequest<GRDBObject.Book> {
        request(for: Self.books)
    }

    var owner:QueryInterfaceRequest<Owner> {
        request(for: Self.owner)
    }
}
```


### Create

``` swift
func create(_ publisher: Publisher) {
    try? databaseQueue.write { database in 
        var owner = publisher.owner
        try? owner.insert(database)
        let publisherObject = GRDBObject.Publisher(
            id: publisher.id,
            name: publisher.name,
            ownerId: publisher.owner.id
        )
        try? publisherObject.insert(database)
        publisher.books.forEach { book in
            let bookObject = GRDBObject.Book(
                id: book.id,
                name: book.name,
                price: book.price,
                publisherId: publisher.id
            )
            try? bookObject.insert(database)
        }
    }
}
```

### Read

``` swift
func read() -> [Publisher] {
    return databaseQueue.read { database in
        let books = GRDBObject.Publisher.books.forKey("books")
        let request = GRDBObject.Publisher
            .including(all: books)
            .including(required: GRDBObject.Publisher.owner)
        return (try? Publisher.fetchAll(database, request)) ?? []
    }
}
```
`books.forKey("books")`について
GRDBはrelationの場合はテーブル名、テーブル名の複数形、テーブル名 + countの3つのパターンでデコードを試みます。
(例: テーブル名がbookなら`book`,`books`,`bookItem`の3つでデコードを試みる)
しかしながら、プロパティ名が違いそのどれもでデコードできない場合はデコードエラーが起きてしまいます。
例えば今回ではFetchableRecordを使用し`GRDBObject.Publisher`と`GRDBObject.Book`,`Owner`から`Publisher`をFetchしようとしていますが、
テーブル名に`GRDBObject.Book.databaseTableName`を使用しているので、Publisherのbooksに対応していません。
GRDBではこれに対処するために一時的に別なCodingKeyの使用を設定できます。
これが今回使った`books.forKey("books")`で、
このようにして一時的に別なkeyを使うことができるので元のモデルを書き変えずにfetchすることが可能です。

### Update
プロトコルに準拠すると`update`と`save`の2つが追加されるのでこれを使用するだけでupdateが可能です。
`update`はその通りに値をアップデートし対象がない場合は失敗するのに対して、
`save`ではアップデート対象が存在する場合はアップデートを行い無い場合には`create`を行うといった違いがあります。

``` swift
func update(_ publisher: Publisher) {
    try? databaseQueue.write { database in
        try? publisher.owner.update(database)

        publisher.books.forEach { book in
            let bookObject = GRDBObject.Book(
                id: book.id,
                name: book.name,
                price: book.price,
                publisherId: publisher.id
            )
            try? bookObject.save(database)
        }

        try? GRDBObject.Book
                .filter(
                    !publisher.books.map { $0.id }.contains(Column("id")) &&
                    Column("publisherId") == publisher.id
                )
                .deleteAll(database)

        let publisherObject = GRDBObject.Publisher(
            id: publisher.id,
            name: publisher.name,
            ownerId: publisher.owner.id
        )
        try? publisherObject.update(database)     
    }
}
```

### Delete
Deleteに関しては今まで紹介したフレームワークと似ていて`Object.delete(database: )`で消去可能です。
また`型名.filter()`で様々な条件を設定できるのでfetchせずに下記のようにfilterにidを設定して消去することも可能です。

``` swift
func delete(_ publisher: Publisher) {
    try? databaseQueue.write { database in
        try? GRDBObject.Book
            .filter(Column("publisherId") == publisher.id)
            .deleteAll(database)
            
        try? GRDBObject.Publisher
            .filter(Column("id") == publisher.id)
            .deleteAll(database)
        try? publisher.owner.delete(database)
    }
}
```

テーブル内のデータを全て削除するのはとても簡単で
`型名.deleteAll`で実行できます。

```swift
func deleteAll() {
    try? databaseQueue.write { database in
        try? GRDBObject.Book.deleteAll(database)
        try? GRDBObject.Publisher.deleteAll(database)
    }
}
```

### SQLの実行

パフォーマンスに不満がある場合や自分でSQLを書いて使用したい場合は`SELECT`では`Row.fetchCursor(database: , sql: )`で
その他の操作では`execute(database: ,sql: )`で実行することが可能です。
`SELECT`では`Row.fetchCursor`の他に`Type.fetchAll`も使用することができますが
Rowの特徴としては次のようになっており

- 1回だけ繰り返せる
- メモリの消費量が少ない
- Sequenceと同じような操作はできるがSequenceではない(SQLiteのエラーを投げるため)
- スレッドセーフではない

単純に言えば、パフォーマンスを求めている時は`fetchCursor`で求めていない場合は`fetchAll`を使うべきと公式は書いています。
次にRowについての説明ですが、RowはDictionaryに似た構造でkeyを渡すことで値にアクセスすることができます。
しかしながらDictionaryとは違いkeyは一意ではありません。これは異なるTableをJOINで結合された場合も想定されているためです。
例えばPublisherテーブルとOwnerテーブルをJOINした場合、どちらもカラムとしてidを持っているのでRowは`[id: 0, id:0]`のようになります。
この時に`Row["id"]`で値にアクセスした場合には一番最初の値にアクセスすることになります。
また`Row[index]`でもアクセスできるので状況に応じて使い分けるようにしましょう。
特定の一列しか取得しない場合は型を決めてfetchすることが可能です。
例えばPublisherのidの列しかfedchしない場合は型がIntであるのがわかり切っているので`Int.fetchAll(database: , sql: )`で取得することができます。

```swift
func executeSQL() {
    try? databaseQueue.read { database in
        let sql = "SELECT * FROM publishers;"
        if let rows = try? Row.fetchCursor(database, sql: sql) {
            while let row = rows.next() {
                let id = row[0]
                let name = row["name"]
            }
        }
    }
}
```

### migration
migrationは以下のように非常にシンプルで
migratorに対してmigrationの処理を`v1`、`v2`とregisterしていき
`migrator.migrate(db)`で実行します

``` swift
var migrator = DatabaseMigrator()
let database = DatabaseQueue("hogehoge")

migrator.registerMigration("v1") { database in
    // something do on v1
}

migrator.migration(database)
```


## FMDB
Obj-C時代からあるSQLiteのWrapperで現在は更新頻度が少なく、同様のことが`GRDB`でも可能なので採用すべきではないと思います。
他のフレームワークとは違い、薄いWrapperであるためマルチスレッドにおける動作やN+1に対してはエンジニアが頑張って対処する必要があります。

説明することは特になく、SQLiteの文法に従ってSQLを書いて`database.executeUpdate(sql: , values: )`で実行するか、
`database.executeQuery(sql: )`で取得するかのみです。
注意するとしたら`executeUpdate(sql: , values: )`の度にトランザクションが実行されるので大量にupdateしたい場合などのクエリを大量に実行する場面では
`beginTransaction`を使用して自分でトランザクションを実行することと
`FMDatabase`のインスタンスを共有するのではなく、`FMDatabaseQueue`か`FMDatabasePool`を共有することの2点になります。
例としてDeleteを挙げると以下のようになります。
残りはプロジェクトの方には詳しい実装は書いてあるので気になった方は見ていただければ

```swift
func delete(publisher: Publisher) {
    let deletePublisherSQL =
            "DELETE FROM publishers WHERE id = ?;"
    let deleteBookSQL =
            "DELETE FROM books WHERE publisher_id = ?;"
    fmDatabaseQueue.inDatabase { database in
        database.open()
        try? database.executeUpdate(
            deletePublisherSQL,
            values: [
                publisher.id
            ]
        )

        try? database.executeUpdate(
            deleteBookSQL,
            values: [
                publisher.id
            ]
        )
        database.close()
    }
}
```


## 性能比較
シンプルなオブジェクト、1 : 1の関係を持つオブジェクト、1 : 多の関係を持つオブジェクトをInsert/Readともに1000件でXCTestのmeasureを使用して処理時間の計測し比較しました。
また1 : 多の関係では子のオブジェクトを10個持つように設定しています。
CoreDataはシンプルなオブジェクトでは`NSBatchInsertRequest(BIR)`が使えるので計測しました。
GRDBはSQLを実行した場合も計測しました。

計測結果は以下のようになりました。

### Read
|  | シンプル | 1 : 1 | 1 : 多 |
|:-:|:-:|:-:|:-:|
| CoreData | 1 ms | 1 ms | 2 ms |
| GRDB | 3 ms | 15 ms | 121 ms |
| GRDB(SQL) | 2ms | 4ms | 30ms |
| Realm | 0.022 ms | 0.024 ms | 0.025 ms |
| FMDB | 1 ms | 3 ms | 22 ms |

### Insert
|  | シンプル | 1 : 1 | 1 : 多 |
|:-:|:-:|:-:|:-:|
| CoreData | 15 ms (BIR: 14ms) | 30 ms | 202 ms |
| GRDB | 30 ms | 152 ms | 872 ms |
| GRDB(SQL) | 5ms | 31ms | 378ms |
| Realm | 13 ms | 30 ms | 158 ms |
| FMDB | 6 ms |  11 ms | 56ms |



## 終わりに
今回は様々なORMを比較してみました。
この記事が何かの役に立てれば幸いです。