JavaScript UDF 소개

JavaScript로 UDF(사용자 정의 함수)의 처리기를 작성할 수 있습니다. 이 섹션의 항목에서는 JavaScript 처리기를 설계하고 작성하는 방법을 설명합니다.

UDF 처리기를 작성할 수 있는 언어 목록을 포함하여, UDF에 대한 소개는 사용자 정의 함수 개요 섹션을 참조하십시오.

처리기가 있으면 SQL로 UDF를 생성합니다. SQL을 사용하여 UDF을 만들거나 호출하는 방법에 대한 자세한 내용은 UDF 만들기 또는 UDF 호출하기 섹션을 참조하십시오.

처리기 코드가 실행될 때 로그 및 추적 데이터를 캡처할 수 있습니다. 자세한 내용은 로깅 및 추적 개요 섹션을 참조하십시오.

참고

JavaScript UDF 처리기와 관련된 제한 사항은 JavaScript UDF 제한 사항 섹션을 참조하십시오.

이 항목의 내용:

JavaScript 처리기 작동 방식

사용자는 UDF를 호출할 때 UDF의 이름과 인자를 Snowflake에 전달합니다. Snowflake는 UDF의 논리를 실행하기 위해 관련 처리기 코드(인자가 있는 경우에는 인자도 포함)를 호출합니다. 그런 다음 처리기 함수는 출력을 Snowflake로 반환하고 Snowflake는 이를 다시 클라이언트로 전달합니다.

UDF에 전달된 각 행에 대해 UDF는 스칼라(즉, 단일) 값을 반환하거나, 테이블 함수로 정의된 경우 행 세트를 반환합니다.

다음 예제의 코드는 입력 ARRAY를 받아 역순으로 정렬된 요소를 포함한 ARRAY를 반환하는 처리기 코드로 my_array_reverse 라는 UDF를 생성합니다. Snowflake는 SQL-JavaScript 데이터 타입 매핑 에 설명된 매핑에 따라 JavaScript 인자 및 반환 유형을 SQL로 변환하거나 그 반대로 변환합니다.

JavaScript 코드는 SQL 코드에서 이름이 대문자가 아닌 경우에도 입력 매개 변수 이름을 모두 대문자로 참조해야 합니다.

-- Create the UDF.
CREATE OR REPLACE FUNCTION my_array_reverse(a ARRAY)
  RETURNS ARRAY
  LANGUAGE JAVASCRIPT
AS
$$
  return A.reverse();
$$
;
Copy

JavaScript 데이터 타입

SQL 및 JavaScript UDF는 기본 데이터 타입 지원을 기반으로 유사하되 다른 데이터 타입을 제공합니다. Snowflake 및 JavaScript 내의 오브젝트는 다음 매핑을 사용하여 전송됩니다.

정수 및 Double

JavaScript에는 정수 형식이 없습니다. 모든 숫자는 Double로 표시됩니다. JavaScript UDF는 형식 변환을 통한 경우를 제외하고는 정수 값을 허용하거나 반환하지 않습니다(즉, Double을 허용하는 JavaScript UDF에 정수를 전달할 수 있음).

Snowflake SQL과 JavaScript는 둘 다 Double 값을 지원합니다. 이 값은 있는 그대로 전송됩니다.

문자열

Snowflake SQL과 JavaScript는 둘 다 문자열 값을 지원합니다. 이 값은 있는 그대로 전송됩니다.

이진 값

모든 이진 값은 JavaScript Uint8Array 오브젝트로 변환됩니다. 이러한 형식화된 배열은 일반 JavaScript 배열과 동일한 방식으로 액세스할 수 있지만, 더 효율적이고 추가 메서드를 지원합니다.

JavaScript UDF가 Uint8Array 오브젝트를 반환하면 이는 Snowflake SQL 이진 값으로 변환됩니다.

날짜

모든 타임스탬프 및 날짜 형식은 JavaScript Date() 오브젝트로 변환됩니다. JavaScript 날짜 형식은 Snowflake SQL의 TIMESTAMP_LTZ(3)에 해당합니다.

날짜 또는 시간을 허용하는 JavaScript UDF에 대해 다음 참고 사항을 고려하십시오.

  • 밀리초를 초과하는 모든 정밀도는 손실됩니다.

  • SQL TIMESTAMP_NTZ에서 생성된 JavaScript Date 는 더 이상 “벽시계” 시간으로 작동하지 않으며, 일광 절약 시간제의 영향을 받습니다. 이는 TIMESTAMP_NTZ를 TIMESTAMP_LTZ로 변환할 때의 동작과 유사합니다.

  • SQL TIMESTAMP_TZ에서 생성된 JavaScript Date 는 타임존 정보를 손실하지만, 입력과 동일한 시간을 나타냅니다(TIMESTAMP_TZ를 TIMESTAMP_LTZ로 변환할 때와 유사).

  • SQL DATE는 현지 타임존의 현재 자정을 나타내는 JavaScript Date 로 변환됩니다.

또한, DATE 및 TIMESTAMP 형식을 반환하는 JavaScript UDF에 대해 다음 참고 사항을 고려하십시오.

  • JavaScript Date 오브젝트는 TIMESTAMP_LTZ(3)에서 반환 데이터 타입으로의 캐스트와 동일한 변환 의미 체계를 준수하면서 UDF의 결과 데이터 타입으로 변환됩니다.

  • VARIANT 오브젝트 내부에 중첩된 JavaScript Date 오브젝트는 항상 TIMESTAMP_LTZ(3) 형식입니다.

베리언트, 오브젝트, 배열

JavaScript UDF를 사용하면 베리언트 및 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 예약어일 수 없습니다.

다음 세 가지 예는 이름으로 참조되는 인자를 사용하는 UDF를 보여줍니다.

-- 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 UDF를 사용할 때는 NULL 값을 포함할 수 있는 행 및 변수에 세심한 주의를 기울이십시오. 특히 Snowflake에는 두 개의 고유한 NULL 값(SQL NULL 및 베리언트의 JSON null)이 포함되어 있는 반면, JavaScript에는 null 외에 undefined 값이 포함되어 있습니다.

JavaScript UDF에 대한 SQL NULL 인자는 JavaScript undefined 값으로 변환됩니다. 마찬가지로, 반환된 JavaScript undefined 값은 SQL NULL 로 다시 변환됩니다. 이는 베리언트를 포함한 모든 데이터 타입에 해당됩니다. 베리언트 이외 형식의 경우, 반환된 JavaScript null 도 SQL NULL 값이 됩니다.

베리언트 형식의 인자 및 반환된 값은 JavaScript의 undefined 값과 null 값을 구분합니다 SQL NULL 은 JavaScript undefined 로 계속 변환됩니다(JavaScript undefined 는 SQL NULL 로 변환). 베리언트 JSON null 은 JavaScript null 로 변환됩니다(JavaScript null 은 Variant JSON null 로 다시 변환). JavaScript 오브젝트에 값으로 포함되거나 배열에 포함된 undefined 값으로 인해 요소가 생략됩니다.

다음과 같이 하나의 문자열과 하나의 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

undefined 반환, null 반환, 그리고 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

처음 두 열은 일치해야 하고 세 번째 열은 0.0을 포함해야 합니다.

이 문제는 JavaScript 사용자 정의 함수(UDF) 및 저장 프로시저에 적용됩니다.

getColumnValue() 를 사용할 때 저장 프로시저에서 문제가 발생하는 경우, 예를 들어 다음을 통해 값을 문자열로 검색하여 문제를 피할 수 있습니다.

getColumnValueAsString()
Copy

그런 다음 저장 프로시저에서 문자열을 반환하고 문자열을 SQL의 숫자 데이터 타입으로 캐스팅할 수 있습니다.

JavaScript 오류

JavaScript를 실행하는 동안 발생한 모든 오류는 사용자에게 SQL 오류로 나타납니다. 여기에는 구문 분석 오류, 런타임 오류, UDF 내에서 발생한 포착되지 않은 오류가 포함됩니다. 오류에 스택 추적이 포함된 경우, 오류 메시지와 함께 출력됩니다. 쿼리를 종료하고 SQL 오류를 생성하기 위해 오류를 포착하지 않고 오류를 발생시키는 것은 허용됩니다.

디버깅할 때 SQL 오류 메시지 텍스트에 나타나도록 오류 메시지와 함께 인자 값을 출력하는 것이 유용할 수 있습니다. 결정적 UDF의 경우, 이는 로컬 JavaScript 엔진에서 오류를 재현하는 데 필요한 데이터를 제공합니다. 한 가지 일반적인 패턴은 전체 JavaScript UDF 본문을 try-catch 블록에 배치하고, 포착된 오류 메시지에 인자 값을 추가하고, 확장된 메시지와 함께 오류를 발생시키는 것입니다. 프로덕션 환경에 UDF를 배치하기 전에 이러한 메커니즘을 제거하는 것을 고려해야 합니다. 오류 메시지에 값을 기록할 경우, 민감한 데이터가 의도치 않게 드러날 수 있습니다.

함수는 미리 정의된 예외 또는 사용자 지정 예외를 발생시키고 포착할 수 있습니다. 사용자 지정 예외를 발생시키는 간단한 예가 여기 에 있습니다.

JavaScript UDF 문제 해결 도 참조하십시오.

JavaScript UDF 보안

JavaScript UDF는 여러 계층의 쿼리 및 데이터 격리를 제공하여 안전과 보안을 갖추도록 설계되었습니다.

  • JavaScript UDF를 실행하는 가상 웨어하우스 내의 컴퓨팅 리소스는 사용자 계정 내에서만 액세스할 수 있습니다(즉, 웨어하우스는 리소스를 다른 Snowflake 계정과 공유하지 않음).

  • 무단 액세스를 방지하기 위해 테이블 데이터는 가상 웨어하우스 내에서 암호화되어 있습니다.

  • JavaScript 코드는 제한된 엔진 내에서 실행되어 JavaScript 컨텍스트에서 시스템 호출을 방지하고(예: 네트워크 및 디스크 액세스 없음) 엔진, 특히 메모리에서 사용할 수 있는 시스템 리소스를 제한합니다.

결과적으로, JavaScript UDF는 정의된 함수를 수행하는 데 필요한 데이터에만 액세스할 수 있으며, 합리적인 양의 메모리와 프로세서 시간을 사용하는 것 외에는 기본 시스템의 상태에 영향을 줄 수 없습니다.