HasuraでPostGISにGraphQLする

# Hasuraって？
HasuraとはPostgreSQLやMySQLに直接繋がるGraphQLエンジンであり、API Gatewayでもあるプロダクト。
SaaS版とOSS（コミュニティ）版があり、SaaS版でもFree tierが用意されているので簡単に試すことができる。

元々はPostgreSQLをターゲットに開発されており、後追いでMySQLにも対応したみたい。

また、PostgreSQLの地理空間情報拡張機能であるPostGISにも対応しているようなので、使い勝手を見てみる。

# 前提条件
* OSS版のDockerでも、SaaS版のFree tierでも良いのでHasura及びPostgreSQLの動く環境を用意しておくこと。
* PostgreSQLでPostGISを使えるようにしておくこと

  ```
$ CREATE EXTENSION postgis;
  ```

# データ準備

## 入手

国土数値情報のサイトから東京都のバス停データを入手する。
https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P11.html

## 変換

国土数値情報からダウンロードしたデータはShapeファイルなのでそのままではPostgreSQLに入らない。
そのため`shp2pgsql`コマンドを用いてSQLファイルにする。

```
shp2pgsql -s 4612 -D -i -I -W cp932 P11-10_13-jgd-g_BusStop.shp busstop > busstop.sql
```

## 登録

以下のようなコマンドで登録する。

```
psql -U <user> -h <host> -f busstop.sql <db name>
```

# GraphiQLでクエリ発行
## 全件カウントクエリ
```graphql
query BusstopAll {
  busstop_aggregate {
    aggregate {
      count
    }
  }
}
```

## 結果

```
{
  "data": {
    "busstop_aggregate": {
      "aggregate": {
        "count": 9446
      }
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44921/bc3deb1b-055b-376b-76de-db79f49d73d2.png)

## 特定のポリゴンに含まれる件数をカウントするクエリ
```graphql
query BusstopWithin($polygon: geometry = "{'type': 'Polygon','coordinates': [[[139.6,35.6],[139.6,35.7],[139.7,35.7],[139.7,35.6],[139.6,35.6]]]}") {
  busstop_aggregate(where: {geom: {_st_within: $polygon}}) {
    aggregate {
      count
    }
  }
}
```

## 結果
```
{
  "data": {
    "busstop_aggregate": {
      "aggregate": {
        "count": 822
      }
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44921/05191bf3-65e6-e31f-9ff3-0603b6b111d4.png)

### 現在HasuraでサポートされているGraphQLから使えるPostGIS関数は以下の通り。

| PostGIS topology operator | equivalent GraphQL operator |
|---------------------------|-----------------------------|
| `ST_Contains`             | `_st_contains`              |
| `ST_Crosses`              | `_st_crosses`               |
| `ST_Equals`               | `_st_equals`                |
| `ST_Intersects`           | `_st_intersects`            |
| `ST_Overlaps`             | `_st_overlaps`              |
| `ST_Touches`              | `_st_touches`               |
| `ST_Within`               | `_st_within`                |
| `ST_DWithin`              | `_st_d_within`              |
(https://hasura.io/blog/native-support-for-postgis-topology-operators-now-in-graphql-engine/)

# 関数を呼び出す

## 関数の作成

2点（xmin, ymin, xmax, ymax）の座標からBBOXを作り、そこに含まれるバス停を返す関数を作る。
動作としては上記の`ST_Within`を使うのと同じ結果。

```sql
CREATE OR REPLACE FUNCTION busstop_filter_bbox(double precision, double precision, double precision, double precision)
RETURNS SETOF busstop AS $$
    SELECT *
    FROM busstop
    WHERE geom && ST_MakeEnvelope($1, $2, $3, $4, 4326);
$$ LANGUAGE sql STABLE;
```
※関数宣言は`STABLE`で行うこと


## GraphQLで呼び出し

```graphql
query MyQuery {
  busstop_filter_bbox_aggregate(
    args: {
      arg_1: 139.6,
      arg_2: 35.6,
      arg_3: 139.7,
      arg_4: 35.7}) {
    aggregate {
      count
    }
  }
}
```

## 結果

```
{
  "data": {
    "busstop_filter_bbox_aggregate": {
      "aggregate": {
        "count": 822
      }
    }
  }
}
```

# まとめ
* PostGIS関数のいくつかは標準サポートされているので簡単なフィルタリングなら何もしなくても使える
* 複雑なクエリを発行したい場合はPostgreSQLの関数を作ることによって何とかなりそう
    * テーブル+標準関数で難しい場合はビューまたは関数で乗り切る2段構えで大体の要件はクリアできそう

とにかくHasuraが優秀すぎる。。。