JavaScript UDFs の紹介

ユーザー定義関数(UDF)のハンドラーを JavaScript で記述できます。このセクションのトピックでは、 JavaScript ハンドラーを設計および記述する方法について説明します。

UDF ハンドラーを記述できる言語のリストを含む UDFs の概要については、 ユーザー定義関数の概要 をご参照ください。

ハンドラーを作成した後、 SQL で UDF を作成します。SQL を使用して UDF を作成または呼び出す方法については、 UDF の作成 または UDF の呼び出し をご参照ください。

注釈

JavaScript UDF ハンドラーに関連した制限については、 JavaScript UDF の制限 をご参照ください。

このトピックの内容:

JavaScript ハンドラーの仕組み

ユーザーが UDF を呼び出すと、ユーザーは UDFの名前と引数をSnowflakeに渡します。Snowflakeは、関連付けられたハンドラーコード(存在する場合は引数付き)を呼び出して、 UDF のロジックを実行します。次に、ハンドラー関数は出力をSnowflakeに返し、Snowflakeはそれをクライアントに返します。

UDF に渡される行ごとに、 UDF はスカラー(つまり単一)値を返すか、テーブル関数として定義されている場合は行のセットを返します。

次の例のコードは、受け取った配列を並べ替えるハンドラーコードを使用して、 array_sort という名前の UDF を作成します。JavaScript の引数と戻り値の型は、 SQL-JavaScript データ型マッピング で説明されているマッピングに従って、Snowflakeにより SQL との間で変換されます。

SQL コードで名前が大文字でなくても、 JavaScript コードは入力パラメーター名をすべて大文字として参照する必要があります。

-- Create the UDF.
CREATE OR REPLACE FUNCTION array_sort(a array)
  RETURNS array
  LANGUAGE JAVASCRIPT
AS
$$
  return A.sort();
$$
;
Copy

JavaScript データ型

SQL および JavaScript UDFs は、ネイティブデータ型のサポートに基づいて、類似するが異なるデータ型を提供します。Snowflakeおよび JavaScript 内のオブジェクトは、次のマッピングを使用して転送されます。

整数と倍精度

JavaScript には整数型はありません。すべての数値は倍精度で表されます。JavaScript UDFs は、型変換を除く整数値を受け入れることや返すことはありません(倍精度を受け入れる JavaScript UDF に整数を渡すことは可能)。

Snowflakeの SQL と JavaScript は両方とも倍精度の値をサポートしています。これらの値はそのまま転送されます。

文字列

Snowflake SQL と JavaScript は、どちらも文字列値をサポートしています。これらの値はそのまま転送されます。

バイナリ値

すべてのバイナリ値は JavaScript Uint8Array オブジェクトに変換されます。これらの型付き配列は、通常の JavaScript 配列と同じ方法でアクセスできますが、より効率的であり、追加のメソッドをサポートしています。

JavaScript UDF が Uint8Array オブジェクトを返す場合、Snowflake SQL バイナリ値に変換されます。

日付

すべてのタイムスタンプと日付型は JavaScript Date() オブジェクトに変換されます。JavaScript 日付型は、Snowflake SQL の TIMESTAMP_LTZ(3) と同等です。

日付または時刻を受け入れる JavaScript UDFs に関する次の注意事項を考慮してください。

  • ミリ秒を超える精度はすべて失われます。

  • SQL TIMESTAMP_NTZ から生成された JavaScript Date は、「ウォールクロック」時間として機能しなくなり、夏時間の影響を受けます。これは、 TIMESTAMP_NTZ を TIMESTAMP_LTZに変換するときの動作に似ています。

  • SQL TIMESTAMP_TZ から生成された JavaScript Date はタイムゾーン情報を失いますが、入力と同じ時点を表します(TIMESTAMP_TZ を TIMESTAMP_LTZ に変換する場合と類似)。

  • SQL DATE は、ローカルタイムゾーンの当日の午前0時を表す JavaScript Date に変換されます。

さらに、 DATE および TIMESTAMP 型を返す JavaScript UDFs に関する次の注意事項も考慮してください。

  • JavaScript Date オブジェクトは UDF の結果データ型に変換され、 TIMESTAMP_LTZ(3) から戻りデータ型へのキャストと同じ変換セマンティクスに従います。

  • JavaScript Date VARIANT オブジェクト内にネストされたオブジェクトは、常に TIMESTAMP_LTZ(3) 型です。

バリアント、オブジェクト、および配列

JavaScript UDFs により、バリアントと JSON データを簡単かつ直感的に操作できます。 UDF に渡されたバリアントオブジェクトは、ネイティブの JavaScript 型と値に変換されます。以前にリストされた値は、対応する JavaScript 型に変換されます。バリアントオブジェクトと配列は JavaScript オブジェクトと配列に変換されます。同様に、 UDF によって返されるすべての値は、適切なバリアント値に変換されます。 UDF によって返されるオブジェクトと配列は、サイズと深度の制限を受けます。

-- flatten all arrays and values of objects into a single array
-- order of objects may be lost
CREATE OR REPLACE FUNCTION flatten_complete(v variant)
  RETURNS variant
  LANGUAGE JAVASCRIPT
  AS '
  // Define a function flatten(), which always returns an array.
  function flatten(input) {
    var returnArray = [];
    if (Array.isArray(input)) {
      var arrayLength = input.length;
      for (var i = 0; i < arrayLength; i++) {
        returnArray.push.apply(returnArray, flatten(input[i]));
      }
    } else if (typeof input === "object") {
      for (var key in input) {
        if (input.hasOwnProperty(key)) {
          returnArray.push.apply(returnArray, flatten(input[key]));
        }
      }
    } else {
      returnArray.push(input);
    }
    return returnArray;
  }

  // Now call the function flatten() that we defined earlier.
  return flatten(V);
  ';

select value from table(flatten(flatten_complete(parse_json(
'[
  {"key1" : [1, 2], "key2" : ["string1", "string2"]},
  {"key3" : [{"inner key 1" : 10, "inner key 2" : 11}, 12]}
  ]'))));

-----------+
   VALUE   |
-----------+
 1         |
 2         |
 "string1" |
 "string2" |
 10        |
 11        |
 12        |
-----------+
Copy

JavaScript 引数と戻り値

引数は、 JavaScript内の名前で直接参照できます。引用符で囲まれていない識別子は、大文字の変数名で参照する必要があります。引数と UDF は、 JavaScript 内から参照されるため、有効な JavaScript 識別子でなければなりません。具体的には、 UDF および引数名は文字または $ で始まる必要がありますが、後続の文字は英数字、 $、または _ にできます。また、名前は JavaScript 予約語にすることはできません。

次の3つの例は、名前で参照される引数を使用する UDFs を示しています。

-- Valid UDF.  'N' must be capitalized.
CREATE OR REPLACE FUNCTION add5(n double)
  RETURNS double
  LANGUAGE JAVASCRIPT
  AS 'return N + 5;';

select add5(0.0);

-- Valid UDF. Lowercase argument is double-quoted.
CREATE OR REPLACE FUNCTION add5_quoted("n" double)
  RETURNS double
  LANGUAGE JAVASCRIPT
  AS 'return n + 5;';

select add5_quoted(0.0);

-- Invalid UDF. Error returned at runtime because JavaScript identifier 'n' cannot be resolved.
CREATE OR REPLACE FUNCTION add5_lowercase(n double)
  RETURNS double
  LANGUAGE JAVASCRIPT
  AS 'return n + 5;';

select add5_lowercase(0.0);
Copy

NULL 値および未定義値

JavaScript UDFs を使用する場合は、 NULL 値を含む可能性のある行と変数に細心の注意を払ってください。具体的には、Snowflakeには2つの異なる NULL 値(SQL NULL とバリアントの JSON null)が含まれ、 JavaScript には null に加えて undefined 値が含まれています。

JavaScript UDF への SQL NULL 引数は、 JavaScript undefined 値に変換されます。同様に、返された JavaScript undefined 値は SQL NULL に変換されます。これは、バリアントを含むすべてのデータ型に当てはまります。非バリアント型の場合は、返された JavaScript null も SQL NULL 値になります。

バリアント型の引数と戻り値は、 JavaScript の undefinednull 値を区別します。SQL NULL は、引き続きJavaScript undefined に変換され(また JavaScript undefined は SQL NULL に戻され)ます。バリアント JSON null は、 JavaScript null に変換され(また JavaScript null はバリアント JSON null に戻され)ます。JavaScript オブジェクト(値として)または配列に埋め込まれた undefined 値により、要素が省略されます。

1つの文字列と1つの NULL 値を持つテーブルを作成します。

create or replace table strings (s string);
insert into strings values (null), ('non-null string');
Copy

文字列を NULL に、また NULL を文字列に変換する関数を作成します。

CREATE OR REPLACE FUNCTION string_reverse_nulls(s string)
    RETURNS string
    LANGUAGE JAVASCRIPT
    AS '
    if (S === undefined) {
        return "string was null";
    } else
    {
        return undefined;
    }
    ';
Copy

関数を呼び出すには、

select string_reverse_nulls(s) 
    from strings
    order by 1;
+-------------------------+
| STRING_REVERSE_NULLS(S) |
|-------------------------|
| string was null         |
| NULL                    |
+-------------------------+
Copy

SQL NULL を渡すことと、バリアント JSON null を渡すことの違いを示す関数を作成します。

CREATE OR REPLACE FUNCTION variant_nulls(V VARIANT)
      RETURNS VARCHAR
      LANGUAGE JAVASCRIPT
      AS '
      if (V === undefined) {
        return "input was SQL null";
      } else if (V === null) {
        return "input was variant null";
      } else {
        return V;
      }
      ';
Copy
select null, 
       variant_nulls(cast(null as variant)),
       variant_nulls(PARSE_JSON('null'))
       ;
+------+--------------------------------------+-----------------------------------+
| NULL | VARIANT_NULLS(CAST(NULL AS VARIANT)) | VARIANT_NULLS(PARSE_JSON('NULL')) |
|------+--------------------------------------+-----------------------------------|
| NULL | input was SQL null                   | input was variant null            |
+------+--------------------------------------+-----------------------------------+
Copy

undefinednull、および undefinednull を含むバリアントを返すことの違いを示す関数を作成します(返されるバリアントから undefined 値が削除されることに注意してください)。

CREATE OR REPLACE FUNCTION variant_nulls(V VARIANT)
      RETURNS variant
      LANGUAGE JAVASCRIPT
      AS $$
      if (V == 'return undefined') {
        return undefined;
      } else if (V == 'return null') {
        return null;
      } else if (V == 3) {
        return {
            key1 : undefined,
            key2 : null
            };
      } else {
        return V;
      }
      $$;
Copy
select variant_nulls('return undefined'::VARIANT) AS "RETURNED UNDEFINED",
       variant_nulls('return null'::VARIANT) AS "RETURNED NULL",
       variant_nulls(3) AS "RETURNED VARIANT WITH UNDEFINED AND NULL; NOTE THAT UNDEFINED WAS REMOVED";
+--------------------+---------------+---------------------------------------------------------------------------+
| RETURNED UNDEFINED | RETURNED NULL | RETURNED VARIANT WITH UNDEFINED AND NULL; NOTE THAT UNDEFINED WAS REMOVED |
|--------------------+---------------+---------------------------------------------------------------------------|
| NULL               | null          | {                                                                         |
|                    |               |   "key2": null                                                            |
|                    |               | }                                                                         |
+--------------------+---------------+---------------------------------------------------------------------------+
Copy

JavaScript 内の型変換

JavaScript は、多くの異なる型間で値を暗黙的に変換します。値が返されると、値は最初に要求された戻り値の型に変換されてから SQL 値に変換されます。例えば、数値が返されるが、 UDF が文字列を返すと宣言されている場合、この数値は JavaScript内の文字列に変換されます。間違った型を返すなどの JavaScript プログラミングエラーは、この動作によって隠される可能性があります。さらに、値の型の変換中にエラーがスローされると、エラーが発生します。

JavaScript 数値範囲

精度がそのままの数値の範囲は

-(2^53 -1)

から

(2^53 -1)

Snowflake NUMBER(p、s)および DOUBLE データ型の有効な値の範囲が大きくなりました。Snowflakeから値を取得して JavaScript 数値変数に格納すると、精度が失われる可能性があります。例:

CREATE OR REPLACE FUNCTION num_test(a double)
  RETURNS string
  LANGUAGE JAVASCRIPT
AS
$$
  return A;
$$
;
Copy
select hash(1) AS a, 
       num_test(hash(1)) AS b, 
       a - b;
+----------------------+----------------------+------------+
|                    A | B                    |      A - B |
|----------------------+----------------------+------------|
| -4730168494964875235 | -4730168494964875000 | -235.00000 |
+----------------------+----------------------+------------+
Copy

最初の2つの列は一致し、3番目の列は0.0を含む必要があります。

この問題は、 JavaScript ユーザー定義関数(UDFs)およびストアドプロシージャに適用されます。

getColumnValue() を使用しているときにストアドプロシージャで問題が発生した場合、次のように値を文字列として取得することで問題を回避できる可能性があります。

getColumnValueAsString()
Copy

その後、ストアドプロシージャから文字列を返し、文字列を SQL の数値データ型にキャストできます。

JavaScript エラー

JavaScript の実行中に発生したエラーは、 SQL エラーとしてユーザーに表示されます。これには、 UDF内でスローされた解析エラー、ランタイムエラー、キャッチされなかったエラーが含まれます。エラーにスタックトレースが含まれている場合、エラーメッセージとともに出力されます。クエリを終了して SQL エラーを生成するために、エラーをキャッチせずにスローすることは許容されます。

デバッグの際、引数値をエラーメッセージとともに出力して SQL エラーメッセージテキストに表示すると便利な場合があります。決定的 UDFs の場合、これはローカル JavaScript エンジンでエラーを再現するために必要なデータを提供します。一般的なパターンの1つは、 JavaScript UDF 本文全体をtry-catchブロックに配置し、キャッチしたエラーのメッセージに引数値を追加し、拡張メッセージでエラーをスローすることです。 UDFs を運用環境に展開する前に、このようなメカニズムを削除することを検討する必要があります。エラーメッセージに値を記録すると、意図せずに機密データが表示される可能性があります。

この関数は、事前定義された例外またはカスタム例外をスローおよびキャッチできます。カスタム例外をスローする簡単な例は、 こちら です。

JavaScript UDFs のトラブルシューティング もご参照ください。

JavaScript UDF セキュリティ

JavaScript UDFs は、いくつかの層のクエリとデータ分離を提供することにより、安全性を確保するように設計されています。

  • JavaScript UDF を実行する仮想ウェアハウス内のコンピューティングリソースには、アカウント内からのみアクセスできます(つまり、ウェアハウスは他のSnowflakeアカウントとリソースを共有しない)。

  • テーブルデータは、不正アクセスを防ぐために仮想ウェアハウス内で暗号化されます。

  • JavaScript コードは制限されたエンジン内で実行され、 JavaScript コンテキストからのシステムコール(例: ネットワークおよびディスクアクセスなし)を防ぎ、エンジンで使用できるシステムリソース、特にメモリを制限します。

その結果、 JavaScript UDFs は、定義された機能を実行するために必要なデータのみにアクセスでき、適切な量のメモリとプロセッサ時間を消費する以外は、基になるシステムの状態に影響を与えることはできません。