次世代データベースを謳う「edgedb」チュートリアル

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45617/d3b2a9b4-6d1f-d5d2-99b8-b07c4c927e72.png)

GitHub
https://github.com/edgedb/edgedb

Blog
https://edgedb.com/blog/

Docs
https://edgedb.com/docs



雑な意訳が含まれているのでより詳しい説明をみたい方は公式ドキュメントを確認してください。

データベースって何？というとこはすっ飛ばして説明します。

# 概要

この章は[EdgeDB: A New Beginning](https://edgedb.com/blog/edgedb-a-new-beginning)の意訳と概要です

現行のデータベースをプロダクトに組み込む際にはリレーショナルデータベースもしくはNoSQLを使用することが多いです。

後者のNoSQL、例えばMongoDBはスキーマレスを活かした柔軟な設計により素早い開発を行える反面、時間の経過と共に技術的な負債を抱えることが多くなります。
対してリレーショナルデータベースは使用予定のリソースや強力な構文(訳注：これは標準SQL構文を指しているの各データーベスの独自記法を指しているのかは不明)一貫性の保証などが働きアプリケーションとして堅牢な作りとなり市場を独占する主な理由となります。

しかしリレーショナルデータベースは数十年前の設計志向にもどいたプロダクションであり、現在のような急激に変化する技術要件に対して追いついてこれていないという現実があります。
PostgreSQLはJSON対応やクエリの並列化など改善を行いつつも基幹となる手法は全く変わっておらず我々は未だ遅いORMに頼ることになり、スキーマの移植に消耗しています。

殆どの開発者はテーブルについては特に意識せずコーディングを考えます。例えばオブジェクトA,Bが存在するとして、これらをお互いが参照したい場合は

```
A.b = B
```

と書くだけです。リレーショナルデータベースでこれを行おうとする場合は外部キー制約、JOIN、中間テーブルなど様々なこを考えなくてはいけません

(訳注:標準SQL構文の場合ぱっと思いつく限りは以下のクエリのような発行が必要です)

```SQL
  FROM *
SELECT A
  JOIN B ON A.id = B.id
```

リレーショナルデータベースと現代のプログラミング言語にはこの大きな壁・矛盾が発生しそれらを object-relational impedance mismatch. と呼ばれています。
この現象が現代のORMが人気の理由で、GraphQLといった技術に関して話題になる理由となっています。


EdgeDBは次世代の object-relational database(オブジェクトリレーショナルデータベース)です。
リレーショナルモデルの代わりにobject graph modelを実装し、このmodelではデータは強い型指定を受けオブジェクトの関係・それらの間のリンクを記述として格納されます。オブジェクトとリンクはプロパティを保持できます。これは名前付きスカラー値のセットになります。

EdgeDBは graph databaseでもdocument databaseでもありませんが、リレーショナルデータベースのようなスキーマの挿入参照を行え、階層的なdocumentの挿入変更参照も容易に行なえます。

サブクエリ、高度な集計、window関数などの最新のSQL機能に匹敵する構文を持ち、それらを上回ることを目標としたEdgeQLという表現豊かな構文を備えています。

EdgeDBではPostgreSQLをベースとしたreliability, ACID compliance, そして performanceという長所を継承しております。


# インストール

[EdgeDB Download](https://edgedb.com/download/)

上記ページから使用しているOSを選択して手順に則って試してください。

注意点としてDockerを使用する際公式の手順に則った場合コケる場合があります。

```
$ docker run -it --rm -p 5656:5656 \
            -v <datadir>:/var/lib/edgedb/data \
            edgedb/edgedb
```

自分の場合以下のようなエラーが帰ってきました

```sehll
$docker run -it --rm -p 5656:5656 -v data:/var/lib/edgedb/data edgedb/edgedb
rm: cannot remove '/var/run/edgedb/*': No such file or directory
```

コードを見る限り`docker-entrypoint.sh` の記述に誤りがあるのでもしエラーが起きた場合はDockerfileをcloneし該当箇所を変更してビルドし直してください。

対象箇所は現在PRを提出してレビュー待ちという状態です。

[Fix remove directory return error by yoshiken · Pull Request #1 · edgedb/edgedb-docker](https://github.com/edgedb/edgedb-docker/pull/1)

(注意)

シンタックスハイライトの`.........` の部分は入力待機時に表示されるやつなので無視してください

# チュートリアル

兎に角にも記述してみないと何もわからなんという感じだとは思うのでやっていきましょう。


## 環境構築

dockerの起動が終わったら別ターミナルで立ち上がったコンテナにexecでログインし、以下のコマンドを実行しましょう

```
$root@7c1f7d5c6def:/# edgedb --admin alter role edgedb --password
Password: 
Repeat for confirmation: 

```

パスワードが聞かれるのでお好きなパスワードを設定してください。

設定が終わったらedgedbにログインします


```
root@7c1f7d5c6def:/# edgedb -u edgedb
EdgeDB 1.0.alpha.1
Type "\?" for help.

edgedb>  

```

これで対話式のコンソールに入ることができました。

早速データベースを作成します

```
edgedb> CREATE DATABASE tutorial;
CREATE
edgedb> \c tutorial 
tutorial> 
```

これはもう見慣れたもののはずなので細かいところは省略します


次にスキーマを設定するのですが記法が2つあり、SDLとDDLというに種類の記法が使用できます。




## SDL 


EdgeDB schema definition language　もしくはSDLと呼ばれているものです。

SDLは宣言型高水準言語となっており、人間が読みやすいように最適化されています。

SDLでのmygrationは以下の構文となります

```
tutorial> START TRANSACTION; 
START TRANSACTION
tutorial> CREATE MIGRATION movies TO { 
.........     type Movie { 
.........         required property title -> str; 
.........         # the year of release 
.........         property year -> int64; 
.........         required link director -> Person; 
.........         multi link cast -> Person; 
.........     } 
.........     type Person { 
.........         required property first_name -> str; 
.........         required property last_name -> str; 
.........     } 
......... };                                                                                                                                                   
CREATE MIGRATION
tutorial> COMMIT MIGRATION movies;                                                                                                                             
COMMIT MIGRATION
tutorial> COMMIT;
COMMIT TRANSACTION
```


## DDL

Data definition languageもしくはDDLと呼ばれるものです。

命令形低水準に値するものです。SDLと比べてスキーマ変更のため低レベルでより確実な実行を行いたい場合はこちらを使用するべきです。

DDLでの構文は以下のようになります。

```
tutorial> CREATE TYPE Person { 
.........     CREATE REQUIRED PROPERTY first_name -> str; 
.........     CREATE REQUIRED PROPERTY last_name -> str; 
......... };                                                                                              
CREATE
tutorial> CREATE TYPE Movie { 
.........     CREATE REQUIRED PROPERTY title -> str; 
.........     # the year of release 
.........     CREATE PROPERTY year -> int64; 
.........     CREATE REQUIRED LINK director -> Person; 
.........     CREATE MULTI LINK cast -> Person; 
......... };                                                                                              
CREATE

```


## 現状確認

上記のどちらかを実行し、スキーマを定義したところで実際にデータを挿入してみましょう。

現在の状態はpostgreSQLの構文に例えると以下のようなテーブル構成となっています。
(directorは複数いるケースがあるんじゃねみたいなお気持ちはそっと閉まっておきましょう)

```sql
postgres=# CREATE DATABASE tutorial;
CREATE DATABASE
postgres=# \c tutorial 
You are now connected to database "tutorial" as user "postgres".
tutorial=# CREATE TABLE Person (
    person_id SERIAL PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL
);
CREATE TABLE
tutorial=# CREATE TABLE PerformerBundle(
    bundle_id SERIAL PRIMARY KEY,
    movie_id INTEGER NOT NULL,
    performer_id INTEGER NOT NULL REFERENCES Person(person_id)
);
CREATE TABLE
tutorial=# CREATE TABLE Movie (
    movie_id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    year INTEGER,
    director INTEGER NOT NULL REFERENCES Person(person_id),
    performer INTEGER NOT NULL REFERENCES PerformerBundle(bundle_id)
);
CREATE TABLE
tutorial=# ALTER TABLE PerformerBundle ADD FOREIGN KEY (movie_id) REFERENCES Movie(movie_id);
ALTER TABLE

```

どちらかというとリレーショナルデータベースよりNoSQLのほうが例としてわかりやすいかもしれませんがサッと手元で試しやすかったpostgresqlを使用しています

## EdgeQL

ここからは駆け足で進めます。

### INSERT

映画(Movie)と出演者(Person)を同時にINSERTを行えます

```
tutorial> INSERT Movie { 
.........     title := 'Blade Runner 2049', 
.........     year := 2017, 
.........     director := ( 
.........         INSERT Person { 
.........             first_name := 'Denis', 
.........             last_name := 'Villeneuve', 
.........         } 
.........     ), 
.........     cast := { 
.........         (INSERT Person { 
.........             first_name := 'Harrison', 
.........             last_name := 'Ford', 
.........         }), 
.........         (INSERT Person { 
.........             first_name := 'Ryan', 
.........             last_name := 'Gosling', 
.........         }), 
.........         (INSERT Person { 
.........             first_name := 'Ana', 
.........             last_name := 'de Armas', 
.........         }), 
.........     } 
......... };                                                                                              
{Object { id: <uuid>'a8218f8c-655b-11e9-8815-2b3c434e8fbf' }}
```

```
tutorial> SELECT Movie {  
.........     title, 
.........     year, 
.........     director: { 
.........         first_name, 
.........         last_name 
.........     }, 
.........     cast: { 
.........         first_name, 
.........         last_name 
.........     } 
......... };                                                                                              
{
    Object {
        title: 'Blade Runner 2049',
        year: 2017,
        director: Object { first_name: 'Denis', last_name: 'Villeneuve' },
        cast: {
            Object { first_name: 'Harrison', last_name: 'Ford' },
            Object { first_name: 'Ryan', last_name: 'Gosling' },
            Object { first_name: 'Ana', last_name: 'de Armas' }
        }
    }
}

```

すでにPerson内に存在している人物を使用する場合はサブクエリを使用することで重複せずに使いまわすことができます。


```
tutorial> INSERT Movie { 
.........     title := 'Dune', 
.........     director := ( 
.........         SELECT Person 
.........         FILTER 
.........             # the last name is sufficient 
.........             # to identify the right person 
.........             .last_name = 'Villeneuve' 
.........         # the LIMIT is needed to satisfy the single 
.........         # link requirement validation 
.........         LIMIT 1 
.........     ) 
......... };                                                                                              
{Object { id: <uuid>'6ddd34a4-6563-11e9-8815-7fc823af2d49' }}
tutorial> SELECT Movie {  
.........     title, 
.........     year, 
.........     director: { 
.........         first_name, 
.........         last_name 
.........     }, 
.........     cast: { 
.........         first_name, 
.........         last_name 
.........     } 
......... };                                                                                              
{
    Object {
        title: 'Blade Runner 2049',
        year: 2017,
        director: Object { first_name: 'Denis', last_name: 'Villeneuve' },
        cast: {
            Object { first_name: 'Harrison', last_name: 'Ford' },
            Object { first_name: 'Ryan', last_name: 'Gosling' },
            Object { first_name: 'Ana', last_name: 'de Armas' }
        }
    },
    Object {
        title: 'Dune',
        year: {},
        director: Object { first_name: 'Denis', last_name: 'Villeneuve' },
        cast: {}
    }
}
```

### SELECT

先程も少し出ましたが、SELECT文を用いて内容を見ることが可能です

```
tutorial> SELECT Movie;                                                                                   
{
    Object { id: <uuid>'a8218f8c-655b-11e9-8815-2b3c434e8fbf' },
    Object { id: <uuid>'6ddd34a4-6563-11e9-8815-7fc823af2d49' }
}
tutorial> SELECT Movie { 
.........     title, 
.........     year 
......... };                                                                                              
{Object { title: 'Blade Runner 2049', year: 2017 }, Object { title: 'Dune', year: {} }}

```

条件(WHERE句)で絞る際には `FILTER` を使用します

```
tutorial> SELECT Movie { 
.........     title, 
.........     year 
......... } 
......... FILTER .title ILIKE 'blade runner%';                                                            
{Object { title: 'Blade Runner 2049', year: 2017 }}

```

### ALTER

属性の変更などを加えたい場合はALTER句を使用します

現在 `Person` では `first_name` が `REQUIRED` になっていますが諸々の事情で無くても問題ない。といった場合は `REQUIRED` 属性を外します

```
tutorial> ALTER TYPE Person { 
.........     ALTER PROPERTY first_name { 
.........         DROP REQUIRED; 
.........     } 
......... };                                                                                              
ALTER
tutorial> INSERT Person { 
.........     last_name := 'Zendaya' 
......... };                                                                                              
{Object { id: <uuid>'a6ebef0a-6564-11e9-8815-0f556b5186ca' }}
tutorial> SELECT Person { 
.........     first_name, 
.........     last_name 
......... };                                                                                              
{
    Object { first_name: 'Denis', last_name: 'Villeneuve' },
    Object { first_name: 'Harrison', last_name: 'Ford' },
    Object { first_name: 'Ryan', last_name: 'Gosling' },
    Object { first_name: 'Ana', last_name: 'de Armas' },
    Object { first_name: {}, last_name: 'Zendaya' }
}
tutorial>  
```


## GraphQL

すみませんGraphQLはまだ詳しく理解していないので曖昧になってしまうかもです。

edgeDBではネイティブでGraphQLを対応しており、テスト検証環境も自前で持っています。

これでサードパーティ製のややこしいORMからおさらばできます。

まずedgeDBでGraphQLを使用する場合は専用のportの穴あけが必要なので一旦dockerを落とすなり別途portを開けてあげるなりして `88888` を開けます。


↓一旦落として開けてあげる人

```
docker run -it --rm -p 5656:5656 -p 8888:8888 -v data:/var/lib/edgedb/data edgedb:1.0
```

次にCONFIGでGraphQLをしようする設定を記述します

```
tutorial> CONFIGURE SYSTEM INSERT Port { 
.........     protocol := "graphql+http", 
.........     database := "tutorial", 
.........     address := "0.0.0.0", 
.........     port := 8888, 
.........     user := "http", 
.........     concurrency := 4, 
......... };
CONFIGURE SYSTEM

```

docker以外のホストで起動している方はaddressのところを `127.0.0.1` で記述してください

つぎに http://127.0.0.1:8888/explore を開きます

こちらのページはさくっとGraphQLを調査したり試したりすることができるインターフェースです。
![screencapture-localhost-8888-explore-2019-04-23-10_33_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45617/59470cf8-b58b-e275-6582-3046cc165675.png)

試しにMovieの情報を取得してみます

```:request
{
    Movie {
        title
        year
    }
}
```
```:response
{
  "data": {
    "Movie": [
      {
        "title": "Blade Runner 2049",
        "year": 2017
      },
      {
        "title": "Dune",
        "year": null
      }
    ]
  }
}
```

# おわり

まだまだalpha版ということで足りない機能やバグも多いですが **珍しくPythonにしては早い** ということもありかなり今後の進化に期待できると思っています。
ここでは紹介しなかったJSON Support・Type Safety・Binary Protocol…色々機能も充実してきています。

個人的には既存のDBでは色々お膳立てしないといけないのでなんだかんだ触っていなかったGraphQLのネイティブ対応がアツいので今後もGraphQLの勉強に使っていこうかなと思っています。

設計思想などは開発者ブログに書いてあるのでぜひ読んでみてください
https://edgedb.com/blog/edgedb-a-new-beginning