DEV Community

gumi TECH for gumi TECH Blog

Posted on

Ecto 3.0: クエリーAPIの改善を予定[前編]

Ecto 3.0のリリースに先立ち、plataformatec社開発チームのblog記事「A sneak peek at Ecto 3.0: query improvements (part 1)」が公開されました。本稿はplataformatec社の許諾によりこの記事にもとづいて、Ecto 3.0のもっとも大きな改善項目であるEcto.Query APIについてご紹介します。blog記事は次回part 2が予定されていますので、本稿は前編としました。

名前つきバインディングにより結合の構成を改善

Ectoはjoinにより、複数のスキーマとテーブルを結合することがつねにできます。

query =
  from p in Post,
    join: c in Comment,
    where: p.id == c.post_id,
    select: c

ここで、クエリに手を加えて、公開されたコメントだけが返されるようにしましょう。前掲のクエリは、つぎのように構成できます。

from [_, c] in query, where: c.public

前掲コード例からわかるように、クエリ(pおよびc)の中にあるバインディングを抽出してから、フィルタが適用できます。この例のバインディングは位置をともない、inの左辺のリストにおける順序に依存します。pcという名前は暫定で、クエリ全体には関係しません。つまり、つぎのように書き替えても、クエリの意味は同じです。

from [_, comment] in query, where: comment.public

位置をともなうバインディングで問題なのは、クエリの構成がきわめてむずかしくなる場合があることです。込み入った検索の機能を構築しようとして、複数のテーブルを結合し、順序を変えて、位置バインディングを追跡することは、複雑で脆弱性が生じやすくなります。

Ecto 3.0は、fromjoinそれぞれに名前がつけられるように改めます。前掲のクエリがつぎのように書き替えられるのです。

query =
  from p in Post,
    join: c in Comment,
    as: :comments,
    where: p.id == c.post_id,
    select: c

joinのあとにadオプションが加わえられたことにご注目ください。既存の:commentsをフィルタリングするときも、クエリ内における順序にかかわらず、つぎのように書けます。

from [comments: c] in query, where: c.public

位置バインディングをキーワードリストに置き替えます。キーがバインディング名で、値は結合を割り当てる変数です。ここでも、変数cは暫定ですので、どのような名前でも構いません。重要なのは、既存の:commentsにバインディングするということです。

Ecto 3.0は明示的な名前の仕組みとして、変数名によるのでなく、:asオプションを採り入れました。変数名はうっかり重複してしまうことがあるからです。とくに、開発者がクエリの変数名をひと文字に省いているとそのおそれは高まるでしょう。さらに、同じ名前に何度もバインディングしようとすれば、エラーが生じます。

もうひとつ挙げておきたいのは、:asオプションはfromにも与えられるということです。

query =
  from p in Post,
    as: :posts,
    join: c in Comment,
    as: :comments,
    where: p.id == c.post_id,
    select: c

名前つきバインディングにより、複雑な検索フォームや検索APIなど動的なクエリが、Ectoでさらに柔軟に構築できます。

データベース接頭辞とインデックスヒント

名前つきバインディングを加えるためにつくったEctoの基盤に、さらにふたつの機能が加えられます。from/join接頭辞とインデックスヒントです。

Ecto v2.0に接頭辞という考え方が採り入れられました。接頭辞(プレフィックス)の意味は、データベースエンジンによって異なります。Postgresでは、接頭辞はPostgresスキーマのことです。Postgresのデータベースには複数のスキーマがあり、デフォルトのスキーマは「public」と呼ばれます。MySQLはスキーマをサポートしていないので、接頭辞の機能は単に異なるデータベースの意味になります。

Ecto v2.0に接頭辞が採り入れられた目的は、異なる接頭辞からデータを簡単に選択、挿入、更新、あるいは削除することでした。目指したのは、マルチテナントアプリケーションをサポートすることです。けれど、Ecto v2.0では、一度に扱える接頭辞がひとつにかぎられていました。たとえば、クエリをひとつ書いて、異なるふたつの接頭辞の間でデータを結合することができません。

Ecto v3.0ではこの制限が外れます。from/joinに、:asオプションと同じように、prefixオプションが与えられるのです。たとえば、すべての投稿が公開されているシステムを考えましょう。けれど、コメントはシステムを使うクライアントに固有だとします。それは、システムに複数の接頭辞がクライアントごとにあり、各接頭辞がそれぞれのコメントテーブルをもつということです。すると、それらの接頭辞にわたるつぎのようなクエリが書けます。

from p in Post,
  prefix: "public",
  join: c in Comment,
  prefix: "client1",
  where: p.id == c.post_id,
  select: c

さらに、Ecto 3.0では同様のAPIにより、MySQLMSSQLデータベースにもあるインデックスヒントが使えるようになります。

from p in Post,
  join: c in Comment,
  hints: ["USE INDEX FOO", "USE INDEX BAR"],
  where: p.id == c.post_id,
  select: c

ヒントはあまり使わないかもしれません。こうした機能については、データベース免責事項を必ずお読みください。

接頭辞とヒントのオプションにより、開発者はより柔軟にクエリを組み立てて最適化し、Ecto.Queryの力を余すことなく活用することができます。SQLへのフォールバックも避けられます。

その他の変更

Ecto.Querywherehavingにタプルが使えるようになります。たとえば、where: {p.foo, p.bar} > {^foo, ^bar}といったクエリが書けるのです。カーソルベースのページング処理などに使えるでしょう。

+-*/などの算術演算子もサポートされます。ただし、これらの演算子はもとにえるデータベースエンジンに委任するだけです。したがって、データベースを必ず調べ、どの型が被演算子にできるかお確かめください。

最後に、テーブルやソース全体を引数とするデータベース関数が呼び出せるようになります。fragment("some_function(?)", p)という使い方です。

次回の[後編]では、Ecto 3.0のEcto.Queryに加えられた改善や機能についてさらにご説明します。

関連記事

Discussion (0)