原文

この記事を読むためには SQL の機能である Create Function を理解している必要があります。そのために この記事 も参照してください。

What are custom SQL functions?

Custom SQL function は ユーザーが定義した SQL 関数 です。これを用いることで、独自定義のビジネスロジックをカプセル化したり、組み込み SQL 関数や operator を拡張することが可能です。

Custom SQL functions are user-defined SQL functions that can be used to either encapsulate some custom business logic or extend the built-in SQL functions and operators.

Hasura GraphQL エンジンは、特定の形式の custom function が追加された場合に、GraphQL API からその関数へアクセスできるようにします。結果として GraphQL API に対して query もしくは subscription を発行することで、custom function からデータを取得することができます。

Hasura GraphQL engine lets you expose certain types of custom functions as top level fields in the GraphQL API to allow querying them using both queries and subscriptions.

Custom SQL functions は computed fields 機能 の中で使用することも可能です。

Note

Custom SQL functions can also be queried as computed fields of tables.

Supported SQL functions

今のところは以下の条件を満たしている場合にのみ GraphQL API の top level fields に露出させることが可能です。(条件内で使われている用語に関してはこちらを参照ください)

Currently, only functions which satisfy the following constraints can be exposed as top level fields in the GraphQL API (terminology from Postgres docs):

  • Function behaviour: STABLE もしくは IMMUTABLE のどちらかであること
  • Return type: 必ず SETOF <table-name> であること
  • Argument modes: IN の場合のみ
  • Function behaviour: ONLY STABLE or IMMUTABLE
  • Return type: MUST be SETOF <table-name>
  • Argument modes: ONLY IN

Creating & exposing SQL functions

Custom SQL functions は SQL を Hasura console で実行することで作成することができます。

Custom SQL functions can be created using SQL which can be run in the Hasura console:

  • Hasura console から Data -> SQL セクションに移動

  • function SQL statement を入力する

  • Track this チェックボックスを選択済みにして、追加される関数を GraphQL API に露出させる

  • Run ボタンを押す

  • Head to the Data -> SQL section of the Hasura console
  • Enter your create function SQL statement
  • Select the Track this checkbox to expose the new function over the GraphQL API
  • Hit the Run button

注記: Custum function 内の SETOF で指定したテーブルが存在しない、もしくは custum function が custom type(つまり row set の場合)を返す場合には、その schema を持ったテーブルを作成して Hasura から track させる必要があります。この操作は、上記の操作よりも前もって行う必要があります。

Note

If the SETOF table doesn’t already exist or your function needs to return a custom type i.e. row set, create and track an empty table with the required schema to support the function before executing the above steps.

Use cases

Custom functions は、なんらかの独自に定義したロジックに基づいてデータを扱いたい場合で、かつそのロジックがユーザーからの入力を必要とし、入力に基づいて演算する場合に、一番適切な手法です。ですが、もしそのロジックがユーザーからの情報の入力を必要としていない場合には views を使用してください。

Custom functions are ideal solutions for retrieving some derived data based on some custom business logic that requires user input to be calculated. If your custom logic does not require any user input, you can use views instead.

では実際のケースを例にして Custrom function の使い方をみていきましょう。

Let’s see a few example use cases for custom functions:

Example: Text-search functions

SETOF で指定されているテーブルが既に schema として存在している場合の例をみてみましょう。

Let’s take a look at an example where the SETOF table is already part of the existing schema.

では search_articles という名称の custom function を作成し(この関数は article/author schema に関連しています)、それを track していきましょう。この関数の定義は以下とします。

In our article/author schema, let’s say we’ve created and tracked a custom function, search_articles, with the following definition:

関数
CREATE FUNCTION search_articles(search text)
RETURNS SETOF article AS $$
    SELECT *
    FROM article
    WHERE
      title ilike ('%' || search || '%')
      OR content ilike ('%' || search || '%')
$$ LANGUAGE sql STABLE;

この関数は article テーブルから取得した row を、この関数の入力として受け取った引数である search に基づいて filter します。結果として SETOF article が返ってきます。article テーブルは既に track されている前提ですので、以下のようにしてこの custom function を実行することができます。(訳注:対応するテーブルが追加されていない場合には、この記事内の Example: PostGIS functions のケースを参照ください)

This function filters rows from the article table based on the input text argument, search i.e. it returns SETOF article. Assuming the article table is being tracked, you can use the custom function as follows:

Screen Shot 2020-08-05 at 11.18.09

Example: Fuzzy match search functions

一部内容が理解できなかったので翻訳飛ばします。基本的には処理している内容は Example: Text-search functions と同じです。

Example: PostGIS functions

関数内の SETOF で指定したテーブルが、まだ schema として存在していないケースをみていきましょう。

Let’s take a look at an example where the SETOF table is not part of the existing schema.

まず二つのテーブルは存在しているとしましょう。user と landmark location data のためのテーブルです。(今回の例は空間テータ処理用の拡張機能として PostGIS を使用しています。)

Say you have 2 tables, for user and landmark location data, with the following definitions (this example uses the popular spatial database extension, PostGIS):

テーブル定義
-- User location data
CREATE TABLE user_location (
  user_id INTEGER PRIMARY KEY,
  location GEOGRAPHY(Point)
);

-- Landmark location data
CREATE TABLE landmark (
  id SERIAL PRIMARY KEY,
  name TEXT,
  type TEXT,
  location GEOGRAPHY(Point)
);

私たちが実装したい機能は、landmark のうち、引数から指定された user から近いもののリストとuser 詳細を、query で取得する機能です。今回のケースは PostGIS の組み込み関数である ST_Distance を使用しています。

In this example, we want to fetch a list of landmarks that are near a given user, along with the user’s details in the same query. PostGIS’ built-in function ST_Distance can be used to implement this use case.

今回作成する関数の SETOF に対応するテーブルがまだ存在しませんので、まずはそのためのテーブルを作成しましょう。そのあと location search function を作成します。

Since our use case requires an output that isn’t a “subset” of any of the existing tables i.e. the SETOF table doesn’t exist, let’s first create this table and then create our location search function.

  • 以下のテーブルを作成し、track します。
  • create and track the following table:
-- SETOF table
CREATE TABLE user_landmarks (
  user_id INTEGER,
  location GEOGRAPHY(Point),
  nearby_landmarks JSON
);
  • 以下の関数を作成し track します。
  • create and track the following function:
-- この関数は input として受け取った userid と distance_kms をもとにして、
-- user の近くにある landmark のリストを返す関数です

-- function returns a list of landmarks near a user based on the
-- input arguments distance_kms and userid
CREATE FUNCTION search_landmarks_near_user(userid integer, distance_kms integer)
RETURNS SETOF user_landmarks AS $$
  SELECT  A.user_id, A.location,
  (SELECT json_agg(row_to_json(B)) FROM landmark B
   WHERE (
     ST_Distance(
       ST_Transform(B.location::Geometry, 3857),
       ST_Transform(A.location::Geometry, 3857)
     ) /1000) < distance_kms
   ) AS nearby_landmarks
  FROM user_location A where A.user_id = userid
$$ LANGUAGE sql STABLE;

この関数は input として受け取った userid をもとにユーザー情報を取得し、さらに user の現在位置から distance_kms 以内にある landmark のリストを JSON field の形式で返します。実装が終わりましたので、GraphQL API に対して以下の query を発行することで関数を参照することができます。

This function fetches user information (for the given input userid) and a list of landmarks which are less than distance_kms kilometers away from the user’s location as a JSON field. We can now refer to this function in our GraphQL API as follows:

Screen Shot 2020-08-05 at 11.54.34

Querying custom functions using GraphQL queries

Aggregations on custom functions

<function-name>_aggregate という名称の query を使って、カスタム関数の結果に対して aggregation を行うことができます。

You can query aggregations on a function result using the <function-name>_aggregate field.

For example, count the number of articles returned by the function defined in the text-search example above:

query {
  search_articles_aggregate(
    args: {search: "hasura"}
  ){
    aggregate {
      count
    }
  }
}

Using arguments with custom functions

テーブルの場合と同様に、where, limit, order_by, offset といった変数をカスタム関数によって生成された query に対して用いることが可能です。

As with tables, arguments like where, limit, order_by, offset, etc. are also available for use with function-based queries.

例えば以下の例では text-search 関数が返す article の数を limit を使って制限しています。

For example, limit the number of articles returned by the function defined in the text-search example above:

query {
  search_articles(
    args: {search: "hasura"},
    limit: 5
  ){
    id
    title
    content
  }
}

Using argument default values for custom functions

Args input field を省略して実行すると、GraphQL engine は argument をなしで SQL function を実行します。ですが、関数定義で default 値を定義することも可能です。

If you omit an argument in the args input field then the GraphQL engine executes the SQL function without the argument. Hence, the function will use the default value of that argument set in its definition.

例えば既に扱った PostGIS functions の定義を以下のように更新することで、distance_kms の default 値として 2 が使われるようになります。

For example: In the above PostGIS functions example, the function definition can be updated as follows:

-- input arguments distance_kms (default: 2) and userid
CREATE FUNCTION search_landmarks_near_user(userid integer, distance_kms integer default 2)
Search nearby landmarks with distance_kms default value which is 2 kms:

Accessing Hasura session variables in custom functions

Hasura の session 情報を用いる関数を作成し、これを trackfunctionv2 API を使って session_argument config を設定した上で track することで、Custom Functions の中で Hasura の session 情報の利用が可能になります。session argument には、key が session varibale の name であり、value が文字列である、JSON オブジェクトが入ってきます。->> JSON オペレーターを用いることで、session variable 内の value を取得することができます。以下の例をご覧ください。

Create a function with an argument for session variables and track it with the trackfunctionv2 API with the session_argument config set. The session argument will be a JSON object where keys are session variable names (in lower case) and values are strings. Use the ->> JSON operator to fetch the value of a session variable as shown in the following example.

Create a function with an argument for session variables and track it with the trackfunctionv2 API with the session_argument config set. The session argument will be a JSON object where keys are session variable names (in lower case) and values are strings. Use the ->> JSON operator to fetch the value of a session variable as shown in the following example.

-- single text column table
CREATE TABLE text_result(
  result text
);

-- hasura role を返すだけのシンプルな関数
-- hasura_session には session argument が入ってきます

-- simple function which returns the hasura role
-- where 'hasura_session' will be session argument
CREATE FUNCTION get_session_role(hasura_session json)
RETURNS SETOF text_result AS $$
    SELECT q.* FROM (VALUES (hasura_session ->> 'x-hasura-role')) q
$$ LANGUAGE sql STABLE;

上記で指定した hasura_session という引数は、実際にはこれによって作られる GraphQL の query の input には含まれなくなります。

訳注:通常であれば上記の sql で作られる query は以下のようになるはずですが、今回はなりません。

query {
  get_session_role(args: {hasura_session: "..."}) {
    result
  }
}

なぜならば、trackfunctionv2 API を使って通常とは異なる方法で関数を track させたからです。

Note

The specified session argument will not be included in the <function-name>_args input object in the GraphQL schema.

Permissions for custom function queries

定義した custom function の SETOF tableAccess control permissions を設けると、custom function に対してもその制限が適応されます。

例えば今回扱かった text-search の例でいうと、作成した関数は SETOF article を返すと定義されているので、article テーブルにどのような Access control permission が定義されているかが重要になります。ですので search_articles query を使用する際に role = user でアクセスしようとした場合、article テーブルが user role のアクセスを許可していないのであれば、結果として validation error が返ってきます。

Access control permissions configured for the SETOF table of a function are also applicable to the function itself.

For example, in our text-search example above, if the role user doesn’t have the requisite permissions to view the table article, a validation error will be thrown if the search_articles query is run using the user role.