#
#   コンテンツ
#
#  1. JSON ドキュメント
#  2. CRUD - Create / Read / Update / Delete
#     a. 作成
#       - インデックスする2つの方法。insertとcreate
#       - ドキュメントのバルク登録
#     b. 取得
#       - 基本的な検索
#       - Intermediate searches
#       - ElasticsearchでのSQLのサンプル
#       - ファセットとアグリゲーション
#       - アグリゲーションのユースケース(doc valuesと転置インデックス)
#       - 緯度経度検索のサンプル
#     c. 更新
#       - ドキュメント更新
#     d. 削除
#       - ドキュメント削除
#  3. マッピング
#  4. アナライザー

GET /


# これがKibanaのDev toolsコンソールです。これを使用してElasticsearchとやりとりします。
#
# ElasticsearchはJSONフォーマットのドキュメントを保存します。
# 簡単にJSONについて説明します。こちらがJSONドキュメントです。

{
  "name" : "Elastic",
  "location" : {
    "state" : "CA",
    "zipcode" : 94123
  }
}

# ドキュメントはさまざまなフィールドを値と一緒に保存できます。

{
  "name" : "Elastic",
  ...
   : 
}


# JSONで有効な値は次の6種類(文字列、数字、Object、配列、boolean、null)になります。
# http://www.json.org/

# 最初のJSONドキュメントをインデックスしてみましょう。
# インデックスとは、Elasticsearchにデータを登録することを意味します。
# サンフランシスコ市が公開しているレストランのデータを使用します。まずは1件登録しましょう。

POST /inspections/_doc
{
  "business_address": "660 Sacramento St",
  "business_city": "San Francisco",
  "business_id": "2228",
  "business_latitude": "37.793698",
  "business_location": {
    "type": "Point",
    "coordinates": [
      -122.403984,
      37.793698
    ]
  },
  "business_longitude": "-122.403984",
  "business_name": "Tokyo Express",
  "business_postal_code": "94111",
  "business_state": "CA",
  "inspection_date": "2016-02-04T00:00:00.000",
  "inspection_id": "2228_20160204",
  "inspection_type": "Routine",
  "inspection_score":96,
  "risk_category": "Low Risk",
  "violation_description": "Unclean nonfood contact surfaces",
  "violation_id": "2228_20160204_103142"
}

# JSONドキュメントの構造をみてみましょう。緯度経度情報、日付、数値といったデータが入っています。

# では、GETでインデックスを検索しましょう。

GET /inspections/_search

# 検索APIについては、あとで詳しくみていきます。まずは、ドキュメントの登録についてみていきましょう。

# 多くのことが実行されていますので、まずはこれをみていきます。
# ElasticsearchはREST APIを使用します。まずは、POSTとPUTの違いを説明します。
# PUTはドキュメントのIDが必須となります。これは、URLで指定します。
# 次のコマンドを実行するとエラーになります。

PUT /inspections/_doc/2228
{
  "business_address": "660 Sacramento St",
  "business_city": "San Francisco",
  "business_id": "2228",
  "business_latitude": "37.793698",
  "business_location": {
    "type": "Point",
    "coordinates": [
      -122.403984,
      37.793698
    ]
  },
  "business_longitude": "-122.403984",
  "business_name": "Tokyo Express",
  "business_postal_code": "94111",
  "business_state": "CA",
  "inspection_date": "2016-02-04T00:00:00.000",
  "inspection_id": "2228_20160204",
  "inspection_type": "Routine",
  "inspection_score":96,
  "risk_category": "Low Risk",
  "violation_description": "Unclean nonfood contact surfaces",
  "violation_id": "2228_20160204_103142"
}

# POST はElasticsearchがドキュメントのIDを生成してくれます。

POST /inspections/_doc
{
  "business_address": "660 Sacramento St",
  "business_city": "San Francisco",
  "business_id": "2228",
  "business_latitude": "37.793698",
  "business_location": {
    "type": "Point",
    "coordinates": [
      -122.403984,
      37.793698
    ]
  },
  "business_longitude": "-122.403984",
  "business_name": "Tokyo Express",
  "business_postal_code": "94111",
  "business_state": "CA",
  "inspection_date": "2016-02-04T00:00:00.000",
  "inspection_id": "2228_20160204",
  "inspection_type": "Routine",
  "inspection_score":96,
  "risk_category": "Low Risk",
  "violation_description": "Unclean nonfood contact surfaces",
  "violation_id": "2228_20160204_103142"
}

# もちろんPUTでIDを指定すればエラーは出ません。

PUT /inspections/_doc/12345
{
  "business_address": "660 Sacramento St",
  "business_city": "San Francisco",
  "business_id": "2228",
  "business_latitude": "37.793698",
  "business_location": {
    "type": "Point",
    "coordinates": [
      -122.403984,
      37.793698
    ]
  },
  "business_longitude": "-122.403984",
  "business_name": "Tokyo Express",
  "business_postal_code": "94111",
  "business_state": "CA",
  "inspection_date": "2016-02-04T00:00:00.000",
  "inspection_id": "2228_20160204",
  "inspection_type": "Routine",
  "inspection_score":96,
  "risk_category": "Low Risk",
  "violation_description": "Unclean nonfood contact surfaces",
  "violation_id": "2228_20160204_103142"
}


# ドキュメントをインデックスすると、Elasticsearchはインデックス(ここではデータが保存される場所)を自動で生成します。今回の例では"inspections"という名前です。
# インデックスの次の"_doc"はタイプになります。古いバージョンでは、このタイプを複数指定できましたが、今後は廃止される予定です。7.xでは"_doc"しか使用できません。

# 1件目のデータを登録した時にElasticsearchはインデックスを自動的に作成しました。
# もちろん、インデックスを事前に個別の設定を使って作成することも可能です。

DELETE /inspections

PUT /inspections
{
  "settings": {
    "index.number_of_shards": 1,
    "index.number_of_replicas": 0
  }
}

GET /inspections/_settings

# このサンプルは1つのシャードで、レプリカがないインデックスを作成しました。本番環境ではこのようなインデックスはまず作らないでしょうが。

# 多くのドキュメントを登録したい場合は、bulk APIを使用します。
# パフォーマンス的に大きな利点があります。

POST /inspections/_bulk
{ "index": { "_id": 1 }}
{"business_address":"315 California St","business_city":"San Francisco","business_id":"24936","business_latitude":"37.793199","business_location":{"type":"Point","coordinates":[-122.400152,37.793199]},"business_longitude":"-122.400152","business_name":"San Francisco Soup Company","business_postal_code":"94104","business_state":"CA","inspection_date":"2016-06-09T00:00:00.000","inspection_id":"24936_20160609","inspection_score":77,"inspection_type":"Routine - Unscheduled","risk_category":"Low Risk","violation_description":"Improper food labeling or menu misrepresentation","violation_id":"24936_20160609_103141"}
{ "index": { "_id": 2 }}
{"business_address":"10 Mason St","business_city":"San Francisco","business_id":"60354","business_latitude":"37.783527","business_location":{"type":"Point","coordinates":[-122.409061,37.783527]},"business_longitude":"-122.409061","business_name":"Soup Unlimited","business_postal_code":"94102","business_state":"CA","inspection_date":"2016-11-23T00:00:00.000","inspection_id":"60354_20161123","inspection_type":"Routine", "inspection_score": 95}
{ "index": { "_id": 3 }}
{"business_address":"2872 24th St","business_city":"San Francisco","business_id":"1797","business_latitude":"37.752807","business_location":{"type":"Point","coordinates":[-122.409752,37.752807]},"business_longitude":"-122.409752","business_name":"TIO CHILOS GRILL","business_postal_code":"94110","business_state":"CA","inspection_date":"2016-07-05T00:00:00.000","inspection_id":"1797_20160705","inspection_score":90,"inspection_type":"Routine - Unscheduled","risk_category":"Low Risk","violation_description":"Unclean nonfood contact surfaces","violation_id":"1797_20160705_103142"}
{ "index": { "_id": 4 }}
{"business_address":"1661 Tennessee St Suite 3B","business_city":"San Francisco Whard Restaurant","business_id":"66198","business_latitude":"37.75072","business_location":{"type":"Point","coordinates":[-122.388478,37.75072]},"business_longitude":"-122.388478","business_name":"San Francisco Restaurant","business_postal_code":"94107","business_state":"CA","inspection_date":"2016-05-27T00:00:00.000","inspection_id":"66198_20160527","inspection_type":"Routine","inspection_score":56 }
{ "index": { "_id": 5 }}
{"business_address":"2162 24th Ave","business_city":"San Francisco","business_id":"5794","business_latitude":"37.747228","business_location":{"type":"Point","coordinates":[-122.481299,37.747228]},"business_longitude":"-122.481299","business_name":"Soup House","business_phone_number":"+14155752700","business_postal_code":"94116","business_state":"CA","inspection_date":"2016-09-07T00:00:00.000","inspection_id":"5794_20160907","inspection_score":96,"inspection_type":"Routine - Unscheduled","risk_category":"Low Risk","violation_description":"Unapproved or unmaintained equipment or utensils","violation_id":"5794_20160907_103144"}
{ "index": { "_id": 6 }}
{"business_address":"2162 24th Ave","business_city":"San Francisco","business_id":"5794","business_latitude":"37.747228","business_location":{"type":"Point","coordinates":[-122.481299,37.747228]},"business_longitude":"-122.481299","business_name":"Soup-or-Salad","business_phone_number":"+14155752700","business_postal_code":"94116","business_state":"CA","inspection_date":"2016-09-07T00:00:00.000","inspection_id":"5794_20160907","inspection_score":96,"inspection_type":"Routine - Unscheduled","risk_category":"Low Risk","violation_description":"Unapproved or unmaintained equipment or utensils","violation_id":"5794_20160907_103144"}

# 詳細はこちら。https://www.elastic.co/guide/en/elasticsearch/guide/7.2/bulk.html

#__________________________________________________
# では、基本的な検索の方法に戻りましょう。
# まずは、*全て*のドキュメントの検索です。

GET /inspections/_search


#__________________________________________________
# "soup"を販売している場所に関する情報を検索してみましょう。

GET /inspections/_search
{
  "query": {
    "match": {
      "business_name": "SOUP"
    }
  }
}


# San Franciscoという名前を持っているレストランを探してみましょう。
# San Franciscoはフレーズですので、match_phraseを利用します。

GET /inspections/_search
{
  "query": {
    "match_phrase": {
      "business_name": "san francisco"
    }
  }
}

# 検索結果は、"relevance"(関連度) (_score)でソートされています。
# 検索結果を見てみましょう。
GET /inspections/_search
{
  "query": {
    "match": {
      "business_name": "soup"
    }
  }
}


# 関連度の詳細はこちら: https://www.elastic.co/guide/en/elasticsearch/guide/7.2/relevance-intro.html


#__________________________________________________
# クエリは論理式として組み合わせることも可能です。
# "soup"と"san francisco"の両方をbusiness nameに持っているドキュメントを検索しましょう。

GET /inspections/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "business_name": "soup"
          }
        },
        {
          "match_phrase": {
            "business_name": "san francisco"
          }
        }
      ]
    }
  }
}

#
# また、クエリの否定も可能です。"soup"をbusiness nameに持っていないドキュメントのクエリです。

GET /inspections/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "business_name": "soup"
          }
        }
      ]
    }
  }
}

#__________________________________________________
# クエリごとに異なるブーストを利用することも可能です。
# "soup"という単語により重きを置いてみましょう。

GET /inspections/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "business_name": {
              "query": "soup",
              "boost": 5
            }
          }
        },
        {
          "match_phrase": {
            "business_name": {
              "query": "san francisco"
            }
          }
        }
      ]
    }
  }
}

# どこに単語多ヒットしたかが不明な場合があります。
# このような場合、ハイライトを使用してヒットした部分を強調表示できます。

GET /inspections/_search
{
  "query" : {
    "match": {
      "business_name": "soup"
    }
  },
  "highlight": {
    "fields": {
      "business_name": {}
    }
  }
}


#__________________________________________________
# 最後に、絞り込みをしてみましょう。文章に関する検索でない場合(例えば完全一致や範囲検索など)は、スコアに影響がありません。
# 80以上の"inspection_score"を持ったドキュメントを検索してみましょう。

GET /inspections/_search
{
  "query": {
      "range": {
        "inspection_score": {
          "gte": 80
        }
      }
  },
  "sort": [
    {
      "inspection_score": {"order": "desc"}
    }
  ]
}

# 構造化検索についてはこちら。: https://www.elastic.co/guide/en/elasticsearch/guide/7.2/structured-search.html

# また、このクエリは"inspection_score"で検索結果をソートしています。

# ElasticsearchでのSQLクエリのサンプルです。

POST /_sql?format=txt
{
    "query": "SELECT business_name, inspection_score FROM inspections ORDER BY inspection_score DESC LIMIT 5"
}

# ElasticsearchのSQLは複数の方法で利用できます。
# - RESTエンドポイント経由(上記サンプル)
# - Elasticsearchの'/bin'ディレクトリに含まれているCLIツール
# - JDBC Elasticsearch クライアント
# - ODBC Elasticsearch クライアント

# 詳細については、こちらをご覧ください。https://www.elastic.co/guide/en/elasticsearch/reference/7.2/sql-overview.html


# アグリゲーション(1つのユースケースはデータの分類)
# アグリゲーションの詳細を紹介する時間はないので、
# 詳細については、ドキュメントを見ながら学習してください。

# "soup"という単語で検索して、health scoreをいくつかの範囲ごとに分類した件数を見てみましょう。(ECサイトなどでよく目にする絞り込みのための情報です)

GET /inspections/_search
{
  "query": {
    "match": {
      "business_name": "soup"
    }
  }
 ,"aggs" : {
    "inspection_score_bucket" : {
      "range" : {
        "field" : "inspection_score",
        "ranges" : [
          {
            "key" : "0-80",
            "from" : 0,
            "to" : 80
          },
          {
            "key" : "81-90",
            "from" : 81,
            "to" : 90
          },
          {
            "key" : "91-100",
            "from" : 91,
            "to" : 100
          }
        ]
      }
    }
  }
}

# アグリゲーションの詳細はこちら。https://www.elastic.co/guide/en/elasticsearch/reference/7.2/search-aggregations.html

# 緯度経度検索は検索の便利な道具の1つです。
# "soup"レストランを近い順で並べてみましょう。
# 今回のサンプルデータは緯度経度をgeo pointとして持っています。これを使用します。

GET /inspections/_search

# 次のgeoクエリを実行するとレストランを指定した地点からの距離でソートできます。

GET /inspections/_search
{
  "query": {
    "match": { "business_name": "soup"}
  },
  "sort": [
    {
      "_geo_distance": {
        "coordinates": {
          "lat":  37.783527,
          "lon": -122.409061
        },
        "order":         "asc",
        "unit":          "km"
      }
    }
    ]
}

# おっと、エラーが出ましたね。Elasticsearchが指定したフィールドが緯度経度の情報であることを知らないからです。
# 緯度経度情報を使用する場合は、マッピングでgeo pointというフィールドを定義する必要があります。
# マッピングはドキュメントの構造を定義するもので、インデックスのデータをより効果的に保存、検索するために重要なものです。
# 数値/日付/文字列/緯度経度といったデータを扱いますが、
# まずは、現在のマッピングがどのようなものかを次のコマンドで見てみましょう。

GET /inspections/_mapping

# では、インデックスを削除して、マッピングを変更し、もう一度ドキュメントをバルク登録しましょう。
# 本番環境で作業を行う場合は、reindex APIの使用をお勧めします。
# すでにあるインデックスから、新しいインデックスにデータ登録することができます。

DELETE inspections

PUT /inspections

PUT inspections/_mapping
{
      "properties": {
          "business_address": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_city": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_id": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_latitude": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_location.coordinates": {
              "type": "geo_point"
          },
          "business_longitude": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_name": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_phone_number": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_postal_code": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "business_state": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "inspection_date": {
            "type": "date"
          },
          "inspection_id": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "inspection_score": {
            "type": "long"
          },
          "inspection_type": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "risk_category": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "violation_description": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "violation_id": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
}

GET /inspections/_search

# 今回は、geoクエリがエラーなく実行できます。

GET /inspections/_search
{
  "query": {
    "match": { "business_name": "soup"}
  },
  "sort": [
    {
      "_geo_distance": {
        "business_location.coordinates": {
          "lat":  37.800175,
          "lon": -122.409081
        },
        "order":         "asc",
        "unit":          "km",
        "distance_type": "plane"
      }
    }
    ]
}

# 非常に簡単でしたが、geoクエリの使い方とマッピングについて紹介しました。
# マッピングの理解の第一歩となればと思います。


# では、CRUDについての説明に戻りましょう。ここまで、CとRについて説明しました。
# 残りのU(更新)とD(削除)について説明しましょう。

# ドキュメントにフラグ用のフィールドを追加しましょう。部分更新の機能を使用します。

GET /inspections/_search

POST /inspections/_update/5
{
   "doc" : {
      "flagged" : true,
      "views": 0
   }
}

# ドキュメントの削除は、DELETE APIに対してドキュメントのIDを教えるだけです。

DELETE /inspections/_doc/5

# これでCRUDの説明は終わりです。

# - アナライザー(Analyzers)
# テキストアナライズはElasticsearchのコア機能であり、理解するために重要です。
# 先ほどのサンプルで、データタイプのマッピングを設定しましたが、
# フィールドごとにアナライザーの設定も可能です。
# Analysis = tokenization + token filters

# 文章をトークンと呼ばれる単語に区切る機能がトークナイズです。

GET /inspections/_analyze
{
  "tokenizer": "standard",
  "text": "my email address test123@company.com"
}

GET /inspections/_analyze
{
  "tokenizer": "whitespace",
  "text": "my email address test123@company.com"
}

GET /inspections/_analyze
{
  "tokenizer": "standard",
  "text": "Brown fox brown dog"
}

# トークンに対して操作をするためのものがFilterです。

GET /inspections/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "Brown fox brown dog"
}

# filterには様々なものがあります。

GET /inspections/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase", "unique"],
  "text": "Brown brown brown fox brown dog"
}

# 詳細はこちら: https://www.elastic.co/guide/en/elasticsearch/guide/7.2/_controlling_analysis.html

# 日本語用の形態素解析ライブラリKuromojiの利用
# プラグインのインストール:
#   ./bin/elasticsearch-plugin install analysis-kuromoji

GET /_analyze
{
  "analyzer": "kuromoji",
  "text": "茶色の狐と茶色の犬"
}

# 詳細はこちら: https://www.elastic.co/guide/en/elasticsearch/plugins/7.2/analysis-kuromoji.html