Einführung in JavaScript-UDFs

Sie können den Handler für eine benutzerdefinierte Funktion (UDF) in JavaScript schreiben. Unter den Themen in diesem Abschnitt wird beschrieben, wie Sie einen JavaScript-Handler entwerfen und schreiben.

Eine Einführung in UDFs, einschließlich einer Liste der Sprachen, in denen Sie einen UDF-Handler schreiben können, finden Sie unter Übersicht zu benutzerdefinierten Funktionen.

Sobald der Handler verfügbar ist, erstellen Sie die UDF mit SQL. Weitere Informationen zur Verwendung von SQL für das Erstellen oder Aufrufen einer UDF finden Sie unter Erstellen einer UDF bzw. Aufrufen einer UDF.

Sie können während der Ausführung Ihres Handler-Codes Protokoll- und Ablaufverfolgungsdaten erfassen. Weitere Informationen dazu finden Sie unter Übersicht zu Protokollierung und Ablaufverfolgung.

Bemerkung

Informationen zu Einschränkungen bezüglich JavaScript-UDF-Handlern finden Sie unter JavaScript-UDF-Einschränkungen.

Unter diesem Thema:

Funktionsweise eines JavaScript-Handlers

Wenn ein Benutzer eine UDF aufruft, übergibt der Benutzer den Namen und die Argumente der UDF an Snowflake. Snowflake ruft den zugehörigen Handler-Code (ggf. mit Argumenten) auf, um die Verarbeitungslogik der UDF auszuführen. Die Handler-Methode gibt dann die Ausgabe an Snowflake zurück, und Snowflake gibt die Ausgabe an den Client zurückgibt.

Für jede Zeile, die an eine UDF übergeben wird, gibt UDF entweder einen Einzelwert oder, falls als Tabellenfunktion definiert, eine Menge von Zeilen zurück.

Beispiel

Der Code im folgenden Beispiel erstellt eine UDF namens my_array_reverse mit einem Handler-Code, der eine Eingabe ARRAY akzeptiert und ein ARRAY zurückgibt, das die Elemente in umgekehrter Reihenfolge enthält. Die JavaScript-Argumente und -Rückgabetypen werden von Snowflake in und aus SQL gemäß den unter Zuordnung von Datentypen zwischen SQL und JavaScript beschriebenen Zuordnungen konvertiert.

Beachten Sie, dass der JavaScript-Code die Namen der Eingabeparameter komplett in Großbuchstaben angeben muss, auch wenn die Namen im SQL-Code keine Großbuchstaben sind.

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

JavaScript-Datentypen

SQL und JavaScript-UDFs stellen ähnliche, jedoch unterschiedliche Datentypen bereit, basierend auf der Unterstützung ihres nativen Datentyps. Objekte innerhalb von Snowflake und JavaScript werden mithilfe der folgenden Zuordnungen übertragen.

Ganze Zahlen und Duplikate

JavaScript hat keinen Ganzzahltyp; alle Zahlen werden als Duplikate dargestellt. JavaScript-UDFs akzeptieren oder geben keine Ganzzahlwerte zurück, außer durch Typkonvertierung (d. h. Sie können eine Ganzzahl an eine JavaScript-UDF übergeben, die ein Duplikat akzeptiert).

Sowohl Snowflake-SQL als auch JavaScript unterstützen doppelte Werte. Diese Werte werden unverändert übernommen.

Zeichenfolgen

Sowohl Snowflake-SQL als auch JavaScript unterstützen Zeichenfolgenwerte. Diese Werte werden unverändert übernommen.

Binärwerte

Alle Binärwerte werden in JavaScript Uint8Array-Objekte konvertiert. Auf diese typisierten Arrays kann auf die gleiche Weise wie auf reguläre JavaScript-Arrays zugegriffen werden, sie sind jedoch effizienter und unterstützen zusätzliche Methoden.

Wenn ein JavaScript-UDF ein Uint8Array-Objekt zurückgibt, wird es in einen Snowflake SQL-Binärwert konvertiert.

Datumsangaben

Alle Zeitstempel- und Datumstypen werden in JavaScript Date()-Objekte konvertiert. Der JavaScript-Datumstyp entspricht TIMESTAMP_LTZ(3) in Snowflake-SQL.

Beachten Sie die folgenden Hinweise für JavaScript-UDFs, die ein Datum oder eine Uhrzeit akzeptieren:

  • Alle Detailangaben über Millisekunden hinaus gehen verloren.

  • Ein aus SQL TIMESTAMP_NTZ generiertes JavaScript Date fungiert nicht mehr als „Wanduhr“-Zeit; es wird von der Sommerzeit beeinflusst. Dies entspricht dem Verhalten beim Konvertieren von TIMESTAMP_NTZ in TIMESTAMP_LTZ.

  • Ein aus SQL-TIMESTAMP_TZ generiertes JavaScript-Date verliert Zeitzoneninformationen, stellt jedoch den gleichen Zeitpunkt wie die Eingabe dar (ähnlich wie beim Konvertieren von TIMESTAMP_TZ in TIMESTAMP_LTZ).

  • SQL-DATE wird in JavaScript-Date umgewandelt und stellt Mitternacht des aktuellen Tages in der lokalen Zeitzone dar.

Beachten Sie außerdem die folgenden Hinweise für JavaScript-UDFs, die die Typen DATE und TIMESTAMP zurückgeben:

  • JavaScript-Date-Objekte werden in den Ergebnisdatentyp der UDF konvertiert, wobei sie die gleiche Konvertierungssemantik wie Umwandlungen von TIMESTAMP_LTZ(3) in den Rückgabedatentyp einhalten.

  • JavaScript-Date-Objekte, die in VARIANT-Objekten verschachtelt sind, haben immer den Typ TIMESTAMP_LTZ(3).

Variant-Werte, Objekte und Arrays

JavaScript-UDFs ermöglichen eine einfache und intuitive Bearbeitung von Variant- und JSON-Daten. An eine UDF übergebene Variant-Objekte werden in native JavaScript-Typen und -Werte umgewandelt. Alle zuvor aufgelisteten Werte werden in die entsprechenden JavaScript-Typen übersetzt. Variant-Objekt und -Arrays werden in JavaScript-Objekte und -Arrays konvertiert. In ähnlicher Weise werden alle von der UDF zurückgegebenen Werte in die entsprechenden Variant-Werte umgewandelt. Beachten Sie, dass von der UDF zurückgegebene Objekte und Arrays Größen- und Tiefenbeschränkungen unterliegen.

-- 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-Argumente und zurückgegebene Werte

Argumente können innerhalb von JavaScript direkt über den Namen referenziert werden. Beachten Sie, dass ein Bezeichner ohne Anführungszeichen mit dem Variablennamen in Großbuchstaben referenziert werden muss. Da Argumente und die UDF von JavaScript aus referenziert werden, müssen sie gültige JavaScript-Bezeichner sein. UDF- und Argumentnamen müssen insbesondere mit einem Buchstaben oder $ beginnen, während nachfolgende Zeichen alphanumerisch, $ oder _ sein können. Darüber hinaus können Namen keine JavaScript-reservierten Wörter sein.

Die folgenden drei Beispiele veranschaulichen UDFs, die Argumente verwenden, die über den Namen referenziert werden:

-- 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- und undefinierte Werte

Achten Sie bei der Verwendung von JavaScript-UDFs besonders auf Zeilen und Variablen, die möglicherweise NULL-Werte enthalten: Snowflake enthält zwei unterschiedliche NULL-Werte (SQL-NULL und Variant-JSON-null), während JavaScript zusätzlich zu null den Wert undefined enthält.

SQL-NULL-Argumente für eine JavaScript-UDF werden in den JavaScript-undefined-Wert konvertiert. Ebenso werden zurückgegebene JavaScript-undefined-Werte zurück in SQL-NULL konvertiert. Dies gilt für alle Datentypen einschließlich Variant. Bei Nicht-Variant-Typen führt ein zurückgegebener JavaScript-null-Wert ebenfalls zu einem SQL-NULL-Wert.

Argumente und zurückgegebene Werte des Variant-Typs unterscheiden in JavaScript zwischen undefined- und null-Werten. SQL-NULL wird weiterhin in JavaScript-undefined (und JavaScript-undefined zurück in SQL-NULL) konvertiert. JSON-Variant-null wird in JavaScript-null (und JavaScript-null zurück in JSON-Variant-null) konvertiert. Ein undefined-Wert, der in einem JavaScript-Objekt (als Wert) oder Array eingebettet ist, führt dazu, dass das Element weggelassen wird.

Erstellen Sie eine Tabelle mit einer Zeichenfolge und einem NULL-Wert:

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

Erstellen Sie eine Funktion, die eine Zeichenfolge in einen NULL-Wert und eine NULL-Wert in eine Zeichenfolge konvertiert:

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

Rufen Sie die Funktion auf:

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

Erstellen Sie eine Funktion, die den Unterschied zwischen der Übergabe eines SQL-NULL-Wertes und eines JSON-Variant-null-Wertes zeigt:

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

Erstellen Sie eine Funktion, die den Unterschied zwischen der Rückgabe eines undefined-Werts, eines null-Werts und eines Variant-Werts, der einen undefined-Wert und einen null-Wert enthält, anzeigt (beachten Sie, dass der undefined-Wert aus dem zurückgegebenen Variant-Wert entfernt wird):

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

Typkonvertierung in JavaScript

JavaScript konvertiert Werte implizit zwischen vielen verschiedenen Typen. Wenn ein Wert zurückgegeben wird, wird der Wert zuerst in den angeforderten Rückgabetyp konvertiert, bevor er in einen SQL-Wert übersetzt wird. Wenn beispielsweise eine Zahl zurückgegeben wird, die UDF jedoch als zurückgegebene Zeichenfolge deklariert wird, wird diese Zahl in eine Zeichenfolge innerhalb von JavaScript umgewandelt. Beachten Sie, dass JavaScript-Programmierfehler, z. B. das Zurückgeben des falschen Typs, durch dieses Verhalten verborgen werden können. Wenn beim Konvertieren des Werttyps ein Fehler ausgelöst wird, wird ein Fehler zurückgegeben.

JavaScript-Nummernbereich

Der Bereich für Zahlen mit intakter Genauigkeit reicht von

-(2^53 -1)

bis

(2^53 -1)

Der Bereich der gültigen Werte in den Snowflake-Datentypen NUMBER(p, s) und DOUBLE ist größer. Wenn Sie einen Wert aus Snowflake abrufen und in einer numerischen JavaScript-Variablen speichern, kann dies zu Genauigkeitsverlusten führen. Beispiel:

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

Die ersten beiden Spalten sollten übereinstimmen und die dritte sollte 0,0 enthalten.

Das Problem betrifft benutzerdefinierte JavaScript-Funktionen (UDFs) und gespeicherte Prozeduren.

Wenn das Problem in gespeicherten Prozeduren bei Verwendung von getColumnValue() auftritt, können Sie das Problem möglicherweise vermeiden, indem Sie einen Wert als Zeichenfolge abrufen, z. B. mit:

getColumnValueAsString()
Copy

Sie können dann die Zeichenfolge aus der gespeicherten Prozedur zurückgeben und die Zeichenfolge in einen numerischen Datentyp in SQL umwandeln.

JavaScript-Fehler

Alle Fehler, die während der Ausführung von JavaScript aufgetreten sind, werden dem Benutzer als SQL-Fehler angezeigt. Dazu gehören Fehler beim Parsen, Laufzeitfehler und nicht erfasste Fehler, die innerhalb der UDF ausgegeben werden. Wenn der Fehler ein Stacktrace enthält, wird es zusammen mit der Fehlermeldung gedruckt. Es ist zulässig, einen Fehler auszulösen, ohne ihn abzufangen, um die Abfrage zu beenden und einen SQL-Fehler zu erzeugen.

Beim Debuggen kann es hilfreich sein, Argumentwerte zusammen mit der Fehlermeldung zu drucken, sodass sie im Text der SQL-Fehlermeldung angezeigt werden. Bei deterministischen UDFs liefert dies die erforderlichen Daten, um Fehler in einem lokalen JavaScript-Modul zu reproduzieren. Ein allgemeines Muster besteht darin, einen gesamten JavaScript-UDF-Textkörper in einen „try-catch“-Block zu setzen, der angefangenen Fehlermeldung Argumentwerte anzufügen und einen Fehler mit der erweiterten Meldung auszulösen. Sie sollten in Betracht ziehen, solche Mechanismen zu entfernen, bevor Sie UDFs in einer Produktionsumgebung bereitstellen, denn bei Aufzeichnung von Werten in Fehlermeldungen kann es zur unbeabsichtigten Offenlegung sensibler Daten kommen.

Die Funktion kann vordefinierte Ausnahmen oder kundenspezifische Ausnahmen auslösen und abfangen. Ein einfaches Beispiel für das Auslösen einer kundenspezifischen Ausnahme finden Sie hier.

Siehe auch Problembehandlung bei JavaScript-UDFs.

JavaScript-UDF-Sicherheit

JavaScript-UDFs sind so konzipiert, dass sie mehrere Abfrage- und Datenisolationsebenen bieten und dadurch sicher und geschützt sind:

  • Computeressourcen innerhalb des virtuellen Warehouses, auf denen eine JavaScript-UDF ausgeführt wird, können nur von Ihrem Konto aus aufgerufen werden (d. h. Warehouses teilen sich keine Ressourcen mit anderen Snowflake-Konten).

  • Tabellendaten werden innerhalb des virtuellen Warehouses verschlüsselt, um den Zugriff durch Unbefugte zu verhindern.

  • JavaScript-Code wird in einem eingeschränkten Modul ausgeführt, wodurch Systemaufrufe aus dem JavaScript-Kontext (z. B. ohne Netzwerk- und Festplattenzugriff) verhindert werden und die für das Modul verfügbare Systemressourcen, insbesondere der Arbeitsspeicher, eingeschränkt werden.

Aus diesem Grund können JavaScript-UDFs nur auf die Daten zugreifen, die zur Ausführung der definierten Funktion erforderlich sind. Sie können den Status des zugrunde liegenden Systems nur beeinflussen, wenn sie eine ausreichende Menge an Speicher und Prozessorzeit verbrauchen.