Présentation des UDFs JavaScript¶

Vous pouvez Ă©crire le gestionnaire d’une fonction dĂ©finie par l’utilisateur (UDF) en JavaScript. Les rubriques de cette section dĂ©crivent comment concevoir et Ă©crire un gestionnaire JavaScript.

Pour une introduction aux UDFs, y compris une liste de langages dans lesquels vous pouvez Ă©crire un gestionnaire d’UDF, reportez-vous Ă  Vue d’ensemble des fonctions dĂ©finies par l’utilisateur.

Une fois que vous avez un gestionnaire, vous crĂ©ez l’UDF avec SQL. Pour plus d’informations sur l’utilisation de SQL pour crĂ©er ou appeler une UDF, reportez-vous Ă  CrĂ©er une fonction dĂ©finie par l’utilisateur ou ExĂ©cutez une UDF.

Vous pouvez capturer des donnĂ©es d’enregistrement et de trace pendant l’exĂ©cution du code de votre gestionnaire. Pour plus d’informations, reportez-vous Ă  Journalisation, traçage et mĂ©triques.

Note

Pour les limitations liĂ©es aux gestionnaires d’UDF JavaScript, reportez-vous Ă  Limites liĂ©es aux UDF JavaScript.

Dans ce chapitre :

Comment fonctionne un gestionnaire JavaScript¶

Lorsqu’un utilisateur appelle une UDF, il transmet le nom et les arguments de l’UDF Ă  Snowflake. Snowflake appelle le code du gestionnaire associĂ© (avec des arguments, le cas Ă©chĂ©ant) pour exĂ©cuter la logique de l’UDF. La fonction de gestionnaire renvoie ensuite la sortie Ă  Snowflake, qui la renvoie au client.

Pour chaque ligne transmise Ă  une UDF, l’UDF renvoie soit une valeur scalaire (c’est-Ă -dire unique), soit, si elle est dĂ©finie comme une fonction de table, un ensemble de lignes.

Exemple¶

Le code de l’exemple suivant crĂ©e une UDF appelĂ©e my_array_reverse avec un code de gestionnaire qui accepte un ARRAY d’entrĂ©e et renvoie un ARRAY contenant les Ă©lĂ©ments dans l’ordre inverse. Les types d’argument et de retour JavaScript sont convertis de et vers SQL par Snowflake, selon les mappages dĂ©crits dans Mappages de type de donnĂ©es SQL-JavaScript.

Notez que le code JavaScript doit faire rĂ©fĂ©rence aux noms de paramĂštres d’entrĂ©e en tant que majuscules, mĂȘme si les noms ne sont pas en majuscules dans le code SQL.

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

Types de données JavaScript¶

Les UDFs SQL et JavaScript fournissent des types de données similaires, mais différents, en fonction de leur prise en charge native des types de données. Les objets dans Snowflake et JavaScript sont transférés en utilisant les mappages suivants.

Entiers et doubles¶

JavaScript n’a pas de type entier ; tous les nombres sont reprĂ©sentĂ©s en double. Les UDFs JavaScript n’acceptent pas ou ne retournent pas de valeurs entiĂšres, sauf par le biais de la conversion de type (c’est-Ă -dire que vous pouvez transmettre un entier dans un UDF JavaScript qui accepte un double).

SQL et JavaScript sur Snowflake acceptent des valeurs doubles. Ces valeurs sont transférées telles quelles.

Chaßnes¶

SQL et JavaScript sur Snowflake acceptent des valeurs de chaßnes. Ces valeurs sont transférées telles quelles.

Valeurs binaires¶

Toutes les valeurs binaires sont converties en objets JavaScript Uint8Array. Ces tableaux de types peuvent ĂȘtre accessibles de la mĂȘme maniĂšre que des tableaux JavaScript normaux, mais ils sont plus efficaces et acceptent des mĂ©thodes supplĂ©mentaires.

Si un UDF JavaScript retourne un objet Uint8Array, il est converti en une valeur binaire SQL Snowflake.

Dates¶

Tous les types d’horodatage et de date sont convertis en objets Date() JavaScript. Le type de date JavaScript est Ă©quivalent Ă  TIMESTAMP_LTZ(3) dans le langage SQL Snowflake.

ConsidĂ©rez les notes suivantes pour les UDFs JavaScript qui acceptent une date ou une heure :

  • Toute prĂ©cision au-delĂ  de la milliseconde est perdue.

  • Un Date JavaScript gĂ©nĂ©rĂ© Ă  partir de SQL TIMESTAMP_NTZ n’agit plus comme une durĂ©e chronomĂ©trĂ©e ; il est influencĂ© par l’heure d’étĂ©. Ceci est similaire au comportement qui survient lors de la conversion de TIMESTAMP_NTZ en TIMESTAMP_LTZ.

  • Un Date JavaScript gĂ©nĂ©rĂ© Ă  partir de SQL TIMESTAMP_TZ perd des informations de fuseau horaire, mais reprĂ©sente le mĂȘme moment dans le temps que l’entrĂ©e (similaire Ă  la conversion de TIMESTAMP_TZ en TIMESTAMP_LTZ).

  • DATE SQL est converti en Date JavaScript reprĂ©sentant minuit du jour actuel dans le fuseau horaire local.

De plus, considĂ©rez les notes suivantes pour les UDFs JavaScript qui retournent les types DATE et TIMESTAMP :

  • Les objets Date JavaScript sont convertis dans le type de donnĂ©es du rĂ©sultat de l’UDF, selon la mĂȘme sĂ©mantique de conversion que les conversions de TIMESTAMP_LTZ(3) vers le type de donnĂ©es de retour.

  • Les objets JavaScript Date imbriquĂ©s dans des objets VARIANT sont toujours de type TIMESTAMP_LTZ(3).

Variante, objets et tableaux¶

Les UDFs JavaScript permettent une manipulation simple et intuitive des variantes et des donnĂ©es JSON. Les objets de variantes transmis dans un UDF sont transformĂ©s en types et valeurs JavaScript natifs. Toutes les valeurs prĂ©cĂ©demment listĂ©es sont traduites dans leurs types JavaScript correspondants. Les objets et tableaux de variantes sont convertis en objets et tableaux JavaScript. De mĂȘme, toutes les valeurs renvoyĂ©es par l’UDF sont transformĂ©es en valeurs de variantes appropriĂ©es. Notez que les objets et les tableaux retournĂ©s par l’UDF sont soumis Ă  des limitations de taille et de profondeur.

-- 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

Arguments JavaScript et valeurs retournées¶

Les arguments peuvent ĂȘtre rĂ©fĂ©rencĂ©s directement par leur nom dans JavaScript. Notez qu’un identificateur sans guillemet doit ĂȘtre rĂ©fĂ©rencĂ© avec le nom de la variable en majuscule. Comme les arguments et les UDF sont rĂ©fĂ©rencĂ©s depuis JavaScript, ils doivent ĂȘtre des identificateurs JavaScript lĂ©gaux. Plus prĂ©cisĂ©ment, les noms d’UDF et d’arguments doivent commencer par une lettre ou $, tandis que les caractĂšres suivants peuvent ĂȘtre alphanumĂ©riques, $, ou _. De plus, les noms ne peuvent pas ĂȘtre des mots rĂ©servĂ©s JavaScript.

Les trois exemples suivants illustrent des UDFs qui utilisent des arguments rĂ©fĂ©rencĂ©s par leur nom :

-- 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

Valeurs NULL et non définies¶

Lorsque vous utilisez des UDFs JavaScript, vous devez porter une attention particuliÚre aux lignes et aux variables qui peuvent contenir des valeurs NULL. Plus précisément, Snowflake contient deux valeurs NULL distinctes (SQL NULL et variante JSON null), tandis que JavaScript contient la valeur undefined en plus de la valeur null.

Les arguments SQL NULL vers un UDF JavaScript se traduiront par la valeur undefined JavaScript. De mĂȘme, les valeurs undefined JavaScript renvoyĂ©es se traduisent par des valeurs SQL NULL. Ceci est vrai pour tous les types de donnĂ©es, y compris la variante. Pour les types qui ne sont pas des variantes, une valeur JavaScript null renvoyĂ©e donnera Ă©galement une valeur SQL NULL.

Les arguments et les valeurs renvoyĂ©es du type de variante font la distinction entre les valeurs undefined et les valeurs null de JavaScript. SQL NULL continue de se traduire par JavaScript undefined (et JavaScript undefined vers SQL NULL) ; la variante JSON null se traduit par JavaScript null (et JavaScript null par variante JSON null). Une valeur undefined incorporĂ©e dans un objet JavaScript (comme valeur) ou un tableau entraĂźnera l’omission de l’élĂ©ment.

Créez une table avec une chaßne et une valeur NULL :

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

CrĂ©ez une fonction qui convertit une chaĂźne en NULL et un NULL en chaĂźne :

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

Appelez la fonction :

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

Créez une fonction qui montre la différence entre transmettre un NULL SQL et une variante null JSON :

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

CrĂ©ez une fonction qui montre la diffĂ©rence entre le renvoi de undefined, null et une variante contenant undefined et null (notez que la valeur undefined est supprimĂ©e de la variante renvoyĂ©e) :

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

Conversion de type dans JavaScript¶

JavaScript convertira implicitement les valeurs entre de nombreux types diffĂ©rents. Lorsqu’une valeur est retournĂ©e, elle est d’abord convertie dansa le type de retour demandĂ© avant d’ĂȘtre convertie en une valeur SQL. Par exemple, si un nombre est retournĂ©, mais que l’UDF est dĂ©clarĂ© comme retournant une chaĂźne, ce nombre sera converti en chaĂźne dans JavaScript. Gardez Ă  l’esprit que les erreurs de programmation JavaScript, comme le retour du type incorrect, peuvent ĂȘtre masquĂ©es par ce comportement. De plus, si une erreur est gĂ©nĂ©rĂ©e lors de la conversion du type de la valeur, une erreur se produira.

Plage de nombres JavaScript¶

La plage pour les nombres dont la précision est intacte est de

-(2^53 -1)

dans

(2^53 -1)

La plage de valeurs valides dans les types de donnĂ©es Snowflake NUMBER(p, s) et DOUBLE est plus grande. La rĂ©cupĂ©ration d’une valeur de Snowflake et son stockage dans une variable numĂ©rique JavaScript peut entraĂźner une perte de prĂ©cision. Par exemple :

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

Les deux premiĂšres colonnes doivent correspondre, et la troisiĂšme doit contenir 0.0.

Le problĂšme s’applique aux fonctions JavaScript dĂ©finies par l’utilisateur (UDFs) et aux procĂ©dures stockĂ©es.

Si vous rencontrez le problĂšme dans les procĂ©dures stockĂ©es avec getColumnValue(), vous pouvez l’éviter en rĂ©cupĂ©rant une valeur sous forme de chaĂźne, par exemple avec :

getColumnValueAsString()
Copy

Vous pouvez ensuite renvoyer la chaßne à partir de la procédure stockée et la convertir en un type de données numérique dans SQL.

Erreurs JavaScript¶

Toutes les erreurs rencontrĂ©es lors de l’exĂ©cution de JavaScript apparaissent Ă  l’utilisateur comme des erreurs SQL. Cela inclut les erreurs d’analyse, les erreurs d’exĂ©cution et les erreurs non dĂ©tectĂ©es lancĂ©es dans l’UDF. Si l’erreur contient une trace d’appels, celle-ci sera imprimĂ©e avec le message d’erreur. Il est acceptable de lancer une erreur sans la capturer afin de terminer la requĂȘte et de produire une erreur SQL.

Lors du dĂ©bogage, vous pouvez trouver utile d’imprimer les valeurs des arguments avec le message d’erreur pour qu’elles apparaissent dans le texte du message d’erreur SQL. Pour des UDFs dĂ©terministes, cela fournit les donnĂ©es nĂ©cessaires pour reproduire les erreurs dans un moteur JavaScript local. Un modĂšle commun est de placer un corps d’UDF JavaScript entier dans un bloc try-catch, d’ajouter des valeurs d’argument au message de l’erreur capturĂ©e, et de lancer une erreur avec le message Ă©tendu. Vous devriez envisager de supprimer de tels mĂ©canismes avant de dĂ©ployer des UDFs dans un environnement de production ; l’enregistrement de valeurs dans des messages d’erreur peut rĂ©vĂ©ler involontairement des donnĂ©es sensibles.

La fonction peut gĂ©nĂ©rer et intercepter des exceptions prĂ©dĂ©finies ou des exceptions personnalisĂ©es. Un exemple simple de levĂ©e d’une exception personnalisĂ©e est disponible ici.

Voir aussi DĂ©pannage d’UDFs JavaScript.

Sécurité des UDF JavaScript¶

Les UDFs JavaScript sont conçus pour ĂȘtre sĂ»res et sĂ©curitaires en fournissant plusieurs couches d’interrogation et d’isolation des donnĂ©es :

  • Les ressources de calcul de l’entrepĂŽt virtuel qui exĂ©cutent une UDF JavaScript ne sont accessibles qu’à partir de votre compte (c’est-Ă -dire que les entrepĂŽts virtuels ne partagent pas les ressources avec d’autres comptes Snowflake).

  • Les donnĂ©es des tables sont cryptĂ©es dans l’entrepĂŽt virtuel pour empĂȘcher tout accĂšs non autorisĂ©.

  • Le code JavaScript est exĂ©cutĂ© dans un moteur restreint, empĂȘchant les appels systĂšme du contexte JavaScript (par exemple, pas d’accĂšs au rĂ©seau et au disque) et limitant les ressources systĂšme disponibles pour le moteur, en particulier la mĂ©moire.

Par consĂ©quent, les UDFs JavaScript ne peuvent accĂ©der qu’aux donnĂ©es nĂ©cessaires Ă  l’exĂ©cution de la fonction dĂ©finie et ne peuvent pas affecter l’état du systĂšme sous-jacent, si ce n’est en consommant une quantitĂ© raisonnable de mĂ©moire et de temps processeur.