27.3.11 Примеры JavaScript-хранимых программ
В этом разделе приведены примеры, иллюстрирующие различные аспекты использования JavaScript-программ в различных ситуациях.
Следующий пример демонстрирует использование JavaScript-хранимой функции с значениями столбцов таблицы. Сначала мы определяем хранимую функцию gcd(), которая находит наибольший общий делитель двух целых чисел, как показано здесь:
mysql> CREATE FUNCTION gcd(a INT, b INT)
-> RETURNS INT NO SQL LANGUAGE JAVASCRIPT AS
-> $mle$
$> let x = Math.abs(a)
$> let y = Math.abs(b)
$> while(y) {
$> var t = y
$> y = x % y
$> x = t
$> }
$> return x
$> $mle$
-> ;
Query OK, 0 rows affected (0.01 sec)
Мы можем проверить хранимую функцию следующим образом:
mysql> SELECT gcd(75, 220), gcd(75, 225);
+--------------+--------------+
| gcd(75, 220) | gcd(75, 225) |
+--------------+--------------+
| 5 | 75 |
+--------------+--------------+
1 row in set (0.00 sec)
Далее мы создаём таблицу t1 с двумя столбцами целых чисел и заполняем её несколькими строками, как показано ниже:
mysql> CREATE TABLE t1 (c1 INT, c2 INT);
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO t1 VALUES ROW(12,70), ROW(17,3), ROW(81,9);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> TABLE t1;
+------+------+
| c1 | c2 |
+------+------+
| 12 | 70 |
| 17 | 3 |
| 81 | 9 |
+------+------+
3 rows in set (0.00 sec)
Теперь мы можем выполнить выбор из t1, используя функцию gcd(), при этом значения столбцов служат аргументами в вызове функции, как показано ниже:
mysql> SELECT c1, c2, gcd(c1, c2) AS G
-> FROM t1
-> WHERE gcd(c1, c2) > 1
-> ORDER BY gcd(c1, c2);
+----+----+---+
| c1 | c2 | G |
+----+----+---+
| 12 | 70 | 2 |
| 81 | 9 | 9 |
+----+----+---+
8 rows in set (0.01 sec)
Значение аргумента, которое не является указанного типа, при возможности преобразуется к правильному типу, как показано здесь:
mysql> SELECT gcd(500.3, 600), gcd(500.5, 600);
+-----------------+-----------------+
| gcd(500.3, 600) | gcd(500.5, 600) |
+-----------------+-----------------+
| 100 | 3 |
+-----------------+-----------------+
1 row in set (0.01 sec)
Округление значений с плавающей запятой до целых чисел выполняется с помощью Math.round(); в этом случае 500.3 округляется до 500, а 500.5 — до 501.
Далее, мы создаем простую JavaScript-хранимую процедуру с помощью оператора CREATE PROCEDURE, который включает параметр OUT для передачи текущей даты и времени в удобочитаемом формате в переменную пользователя. Поскольку мы не уверены, какой длины будет эта строка, мы используем VARCHAR(25) для типа параметра.
mysql> CREATE PROCEDURE d1 (OUT res VARCHAR(25))
-> LANGUAGE JAVASCRIPT
-> AS
-> $$
$> let d = new Date().toString()
$> res = d
$> $$
-> ;
Query OK, 0 rows affected (0.01 sec)
Теперь мы можем проверить хранимую процедуру, сначала убедившись, что переменная пользователя @today еще не назначено значение, как показано ниже:
mysql> SELECT @today;
+----------------------+
| @today |
+----------------------+
| NULL |
+----------------------+
1 row in set (0.01 sec)
mysql> CALL d1(@today);
ERROR 1406 (22001): Data too long for column 'res' at row 1
Процедура синтаксически корректна, но тип данных параметра INOUT (res) не позволяет сохранить достаточное количество символов; вместо усечения значения, хранимая программа его отклоняет. Поскольку изменить код процедуры на месте невозможно, мы должны удалить процедуру и пересоздать ее; на этот раз мы попробуем удвоить длину, указанную для параметра INOUT:
mysql> DROP PROCEDURE d1;
Query OK, 0 rows affected (0.02 sec)
mysql> CREATE PROCEDURE d1 (OUT res VARCHAR(50))
-> LANGUAGE JAVASCRIPT
-> AS
-> $$
$> let d = new Date().toString()
$> res = d
$> $$
-> ;
Query OK, 0 rows affected (0.01 sec)
Теперь мы можем повторить тест так:
mysql> SELECT @today;
+----------------------+
| @today |
+----------------------+
| NULL |
+----------------------+
1 row in set (0.01 sec)
Перед вызовом обновленной процедуры с помощью CALL значение @today остается незаданным, так как исходная версия d1() не выполнилась успешно. Обновленная версия выполняется успешно, и мы видим, что на этот раз значение переменной пользователя задается как ожидалось:
mysql> CALL d1(@today);
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @today;
+-----------------------------------------+
| @today |
+-----------------------------------------+
| Mon Oct 30 2023 20:47:29 GMT+0000 (GMT) |
+-----------------------------------------+
1 row in set (0.00 sec)
Полученное при выполнении этого примера значение, вероятно, будет несколько отличаться от показанного здесь, так как точное представление дат зависит от системного региона и, возможно, других настроек. Дополнительную информацию см. в документации к объекту JavaScript Date.
Следующий пример демонстрирует использование JavaScript-хранимой функции в триггере.
Сначала мы создаем таблицу t2, содержащую три столбца целых чисел, как показано ниже:
mysql> CREATE TABLE t2 (c1 INT, c2 INT, c3 INT);
Query OK, 0 rows affected (0.04 sec)
Теперь мы можем создать триггер для этой таблицы. Это необходимо сделать с помощью оператора CREATE TRIGGER, написанного обычным способом на SQL (см. раздел 27.4, “Использование триггеров”), но он может использовать хранимые процедуры, написанные на JavaScript, такие как функция js_pow(), показанная ранее в этом разделе.
mysql> delimiter //
mysql> CREATE TRIGGER jst BEFORE INSERT ON t2
-> FOR EACH ROW
-> BEGIN
-> SET NEW.c2 = js_pow(NEW.c1, 2);
-> SET NEW.c3 = js_pow(NEW.c1, 3);
-> END;
-> //
Query OK, 0 rows affected (0.02 sec)
mysql> delimiter ;
mysql>
Этот триггер срабатывает при вставке строки в t2, берёт значение, вставленное в первый столбец, и вставляет квадрат этого значения во второй столбец, и его куб — в третий. Мы проверяем триггер, вставляя несколько строк в таблицу; поскольку единственное не отброшенное значение - это то, которое мы предоставляем для столбца c1, мы можем просто использовать NULL для каждого из оставшихся двух столбцов, как показано ниже:
mysql> INSERT INTO t2
-> VALUES
-> ROW(1, NULL, NULL),
-> ROW(2.49, NULL, NULL),
-> ROW(-3, NULL, NULL),
-> ROW(4.725, NULL, NULL);
Query OK, 4 rows affected (0.01 sec)
Records: 4 Duplicates: 0 Warnings: 0
Поскольку функция, вызываемая триггером, написана на JavaScript, применяются правила округления JavaScript, так что 2.49 округляется до 2, а 4.75 — до 5. Мы можем увидеть, что это так, если проверим результат с помощью оператора TABLE:
mysql> TABLE t2;
+------+------+------+
| c1 | c2 | c3 |
+------+------+------+
| 1 | 1 | 1 |
| 2 | 4 | 8 |
| -3 | 9 | -27 |
| 5 | 25 | 125 |
+------+------+------+
4 rows in set (0.00 sec)
Следующие примеры демонстрируют некоторые основы работы со значениями VECTOR в хранимых JavaScript-программах MySQL. Мы начинаем с создания таблицы v1, которая содержит столбец VECTOR c1, как показано ниже:
mysql> CREATE TABLE v1 (
-> c1 VECTOR(3)
-> );
Query OK, 0 rows affected (0.02 sec)
Для вставки некоторых значений в эту таблицу мы создаём JavaScript-хранимую процедуру vxin, которая принимает в качестве аргумента строковое представление вектора, преобразует его в значение VECTOR и вставляет его. Затем мы вызываем эту процедуру несколько раз, как показано ниже:
mysql> CREATE PROCEDURE vxin (IN val VARCHAR(100))
-> LANGUAGE JAVASCRIPT
-> AS $$
$> let q = "INSERT INTO v1 VALUES("
$> q += "STRING_TO_VECTOR(\"" + val + "\")"
$> q += ")"
$>
$> let s = session.sql(q)
$>
$> s.execute()
$> $$;
Query OK, 0 rows affected (0.00 sec)
mysql> CALL vxin("[50, 100, 50]");
Query OK, 1 row affected (0.01 sec)
mysql> CALL vxin("[100, 0, -50]");
Query OK, 1 row affected (0.00 sec)
mysql> CALL vxin("[250, 350, 450]");
Query OK, 1 row affected (0.01 sec)
После того, как v1 была заполнена, вывод оператора TABLE v1 выглядит следующим образом:
mysql> TABLE v1;
+----------------------------+
| c1 |
+----------------------------+
| 0x000048420000C84200004842 |
| 0x0000C84200000000000048C2 |
| 0x00007A430000AF430000E143 |
+----------------------------+
3 rows in set (0.00 sec)
Далее, мы создаем хранимую JavaScript-процедуру vxout1, которая выбирает все строки в v1, извлекает значения столбцов и записывает их в stdout. Оператор CREATE PROCEDURE, используемый для создания этой процедуры, показан ниже:
mysql> CREATE PROCEDURE vxout1 ()
-> LANGUAGE JAVASCRIPT
-> AS $$
$> let s = session.sql("TABLE v1");
$>
$> let res = s.execute()
$>
$> console.log(res.getColumnNames())
$>
$> let row = res.fetchOne()
$>
$> while(row) {
$> console.log(row.toArray())
$> row = res.fetchOne()
$> }
$> $$;
Query OK, 0 rows affected (0.00 sec)
Мы можем проверить эту процедуру так:
mysql> SELECT mle_session_reset();
+------------------------------------------+
| mle_session_reset() |
+------------------------------------------+
| The session state is successfully reset. |
+------------------------------------------+
1 row in set (0.00 sec)
mysql> CALL vxout1();
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT mle_session_state("stdout")\G
*************************** 1. row ***************************
mle_session_state("stdout"): c1
50,100,50
100,0,-50
250,350,450
1 row in set (0.00 sec)
Вы ожидаете увидеть те же двоичные представления, что и в выводе оператора TABLE. Поскольку JavaScript рассматривает значение VECTOR как массив (экземпляр Float32Array), он отображается в формате массива. Вы можете использовать SQL-функцию HEX(), чтобы форсировать отображение таких значений в двоичном формате, если это необходимо, как показано ниже:
mysql> CREATE PROCEDURE vxout2 ()
-> LANGUAGE JAVASCRIPT
-> AS $$
$> let s = session.sql("SELECT HEX(c1) FROM v1");
$>
$> let res = s.execute()
$>
$> console.log(res.getColumnNames())
$>
$> let row = res.fetchOne()
$>
$> while(row) {
$> console.log(row.toArray())
$> row = res.fetchOne()
$> }
$> $$;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT mle_session_reset();
+------------------------------------------+
| mle_session_reset() |
+------------------------------------------+
| The session state is successfully reset. |
+------------------------------------------+
1 row in set (0.00 sec)
mysql> CALL vxout2();
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT mle_session_state("stdout")\G
*************************** 1. row ***************************
mle_session_state("stdout"): HEX(c1)
000048420000C84200004842
0000C84200000000000048C2
00007A430000AF430000E143
1 row in set (0.00 sec)
Несмотря на формат отображения, значения VECTOR обрабатываются как векторы, а не как строки или массивы, как видно из следующего примера, использующего (только HeatWave) функцию DISTANCE(). Сначала мы создаём и заполняем таблицу v2, содержащую два столбца VECTOR, используя SQL-операторы, показанные ниже:
CREATE TABLE v2 (
c1 VECTOR(3),
c2 VECTOR(3)
);
INSERT INTO v2 VALUES
ROW(STRING_TO_VECTOR("[50, 100, 50]"), STRING_TO_VECTOR("[0, 200, 0]")),
ROW(STRING_TO_VECTOR("[100, 0, -50]"), STRING_TO_VECTOR("[5, 10, 5]")),
ROW(STRING_TO_VECTOR("[250, 350, 450]"), STRING_TO_VECTOR("[-150, 1000, 50]"))
;
Следующий запрос показывает скалярное произведение двух векторов в каждой строке:
mysql> SELECT VECTOR_DISTANCE(c1, c2, "DOT") AS d FROM v2;
+--------+
| d |
+--------+
| 20000 |
| 250 |
| 335000 |
+--------+
3 rows in set (0.00 sec)
Скалярное произведение двух векторов определяется как сумма произведений их компонент по порядку. Например, для векторов во второй строке в таблице v5 ([100, 0, -50] и [5, 10,
5]) скалярное произведение равно (100)(5) + (0)(10) +
(-50)(5) = 500 + 0 - 250 = 250.
Вы можете написать собственную JavaScript-функцию для вычисления скалярного произведения двух векторов, аналогичную этой:
mysql> CREATE FUNCTION dot_product (v1 VECTOR, v2 VECTOR)
-> RETURNS FLOAT DETERMINISTIC
-> LANGUAGE JAVASCRIPT
-> AS $$
$> if(v1.length !== v2.length)
$> throw new Error('Vectors must be of the same length')
$>
$> let dot = 0, i=0
$>
$> for(i=0; i<v1.length; i++)
$> dot += v1[i]*v2[i]
$>
$> return dot
$> $$;
mysql> SELECT dot_product(c1, c2) AS dot FROM v5\G
*************************** 1. row ***************************
dot: 20000
*************************** 2. row ***************************
dot: 250
*************************** 3. row ***************************
dot: 335000
Далее, мы создаём JavaScript-хранимую процедуру vxout5, которая выполняет тот же запрос, как показано ниже:
mysql> CREATE PROCEDURE vxout5 ()
-> LANGUAGE JAVASCRIPT
-> AS $$
$> let s = session.sql("SELECT VECTOR_DISTANCE(c1, c2, \"DOT\") AS d FROM v5");
$>
$> let res = s.execute()
$>
$> console.log(res.getColumnNames())
$>
$> let row = res.fetchOne()
$>
$> while(row) {
$> console.log(row.toArray())
$> row = res.fetchOne()
$> }
$> $$;
Query OK, 0 rows affected (0.01 sec)
При выполнении vxout5 (сначала очищая состояние сеанса с помощью mle_session_reset(), как и раньше), мы видим, что значения столбцов из v5 обрабатываются как векторы с тем же результатом, что и при выполнении запроса непосредственно в клиенте mysql:
mysql> SELECT mle_session_reset();
+------------------------------------------+
| mle_session_reset() |
+------------------------------------------+
| The session state is successfully reset. |
+------------------------------------------+
1 row in set (0.00 sec)
mysql> CALL vxout5();
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT mle_session_state("stdout")\G
*************************** 1. row ***************************
mle_session_state("stdout"): d
20000
250
335000
1 row in set (0.00 sec)
© 2025 Oracle
Licensed under the GPLv2 License.