Swift:VaporによるSQLiteの操作(Ubuntu)

今回は、Vapor を使って SQLite を操作する方法(CRUD)を紹介します。モジュールは FluentSQLite を使います。



SQLite3 のインストールとデータベースの作成

まず、SQLite3 をインストールします。Swift や Vapor Toolbox のインストールは、Swift:AjaxとVaporの連携(Ubuntu)を参考にしてください。

OS:Ubuntu 18.04


$ sudo apt update
$ sudo apt install sqlite3

 

今回使用する FluentSQLite では、フィールド名(カラム名) id の整数型フィールドが必須になるので、予め次のようなテーブルを作成しておきます。

 

データベース名:sample.sqlite、テーブル名:users

$ sqlite3 sample.sqlite
sqlite> create table users (id integer, name text);
sqlite> .exit



Vapor アプリケーションの作成

今回も、Vapor Toolbox を使わずに、Swift に標準搭載されている Swift Package Manager だけで作成します。

 

〈実行形式のパッケージの作成〉

$ mkdir fluentsqlite
$ cd fluentsqlite
$ swift package init --type executable

 

続いて、Vapor Docs: Fluent Models を参考に、main.swift と Package.swift を次のように作成します。

データベースは let sqlite = try SQLiteDatabase(・・・) の所で、テーブルは User クラスの entity で設定します。

 

main.swift

import FluentSQLite
import Vapor

var services = Services.default()

services.register(NIOServerConfig.default(hostname:"127.0.0.1", port:8080))

try services.register(FluentSQLiteProvider())

var databases = DatabasesConfig()
let sqlite = try SQLiteDatabase(storage:.file(path:"/home/tomato/sqlite/sample.sqlite"))
databases.add(database:sqlite, as:DatabaseIdentifier<SQLiteDatabase>.sqlite)
services.register(databases)

let application = try Application(config:Config.default(), environment:Environment.detect(), services:services)

final class User {
  static let entity = "users"
  var id:Int?
  var name:String
  init(id:Int? = nil, name:String) {
    self.id = id
    self.name = name
  }
}

extension User:Content {}
extension User:SQLiteModel {}

User.defaultDatabase = DatabaseIdentifier<SQLiteDatabase>.sqlite

let router = try application.make(Router.self)

router.get("create") { request -> Future<[User]> in
  let user1 = User(id:1, name:"mimi")
  let _ = user1.create(on:request)
  let user2 = User(id:2, name:"nana")
  let _ = user2.create(on:request)
  let user3 = User(id:3, name:"toto")
  let _ = user3.create(on:request)
  return User.query(on:request).all()
}

router.get("readAll") { request -> Future<[User]> in
  return User.query(on:request).all()
}

router.get("read") { request -> Future<[User]> in
  return User.query(on:request).filter(\User.name == "mimi").all()
}

router.get("update") { request -> Future<[User]> in
  let user = User(id:1, name:"momo")
  let _ = user.update(on:request)
  return User.query(on:request).all()
}

router.get("delete") { request -> Future<[User]> in
  let user = User(id:3, name:"toto")
  let _ = user.delete(on:request)
  return User.query(on:request).all()
}

try application.run()
 

注)Future<[User]> は、Future<Array<User>> のことです。
注)"let _ = ・・・" は、戻り値があるけれど、戻り値を使わない場合の表記法です。もちろん、"let _ =" を外しても構いません。

 

Package.swift

import PackageDescription

let package = Package(
  name:"fluentsqlite",
  dependencies:[
    .package(url:"https://github.com/vapor/fluent-sqlite.git", from:"3.0.0"),
    .package(url:"https://github.com/vapor/vapor.git", from:"3.1.0"),
  ],
  targets:[
    .target(name:"fluentsqlite", dependencies:["FluentSQLite", "Vapor"]),
  ]
)
 

Vapor Framework 及び Fluent SQLite の最新バージョンは、Vapor releases 及び Fluent SQLite releases で確認してください。

 

ファイルの配置は以下の通りです。


  /home/tomato/fluentsqlite/Sources/fluentsqlite/main.swift
  /home/tomato/fluentsqlite/Package.swift
  /home/tomato/sqlite/sample.sqlite



Vapor アプリケーションのビルドと起動

Package.swift のあるディレクトリで以下のコマンドを実行してください。

 

デバッグビルドと起動〉

$ swift package tools-version --set-current
$ swift build
$ .build/debug/fluentsqlite

 

ビルドに成功し、アプリケーションを起動したら、ブラウザのアドレスバーに以下のように入力してください。


http://127.0.0.1:8080/create


http://127.0.0.1:8080/readAll


http://127.0.0.1:8080/read


http://127.0.0.1:8080/update


http://127.0.0.1:8080/delete

 

以上が CRUD の基本的な使い方です。
次にテーブルのセルの選択の仕方です。まず、filter でレコード(行)を選択し、続いて map や flatMap でフィールド(列)を選択します。次の例は、flatMap を使って、選択したフィールドを更新して保存する例です。

 

 filter(・・・).first() を使った場合

router.get("filter_first") { request -> Future<User> in
  let futureUser = User.query(on:request).filter(\User.id == 1).first().flatMap(to:User.self) { user -> Future<User> in
    user!.name = "momo"
    return user!.save(on:request)
  }
  return futureUser
}
 

 

 filter(・・・).all() を使った場合

router.get("filter_all") { request -> Future<User> in
  let futureUser = User.query(on:request).filter(\User.id == 1).all().flatMap(to:User.self) { users -> Future<User> in
    users[0].name = "momo"
    return users[0].save(on:request)
  }
  return futureUser
}
 

データの更新はよくやることなので、もう少し簡潔に記述できると嬉しいんですが、Future パターンにしようとするとこういう記述になるんでしょうか。

map と flatMap の違いは、map の場合はクロージャの戻り値の型が何でもいいのに対し、flatMap の場合は戻り値の型が Future<T> だという点です。 flatMap は、Future から Future への map で、Future 型を変更しないことから、このように命名されています。


instance_of_Future<A>.flatMap(to:B.self) { a -> Future<B> in
  return instance_of_Future<B>
}
 

 

以上のプログラムでは戻り値の型を明記していますが、Swift には型推論があるので適宜省略してください。



感想

Ubuntu 18.04 でもあっさり動作してくれました。 実戦で使えそうな感触を得たので、私は実戦で使います!^^

 


参考サイト