/
user-defined-functions.rst
410 lines (291 loc) · 13.1 KB
/
user-defined-functions.rst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
.. _user-defined-functions:
======================
User-defined functions
======================
.. rubric:: Table of contents
.. contents::
:local:
.. _udf-create-replace:
``CREATE OR REPLACE``
=====================
CrateDB supports user-defined :ref:`functions <gloss-function>`. See
:ref:`ref-create-function` for a full syntax description.
``CREATE FUNCTION`` defines a new function::
cr> CREATE FUNCTION my_subtract_function(integer, integer)
... RETURNS integer
... LANGUAGE JAVASCRIPT
... AS 'function my_subtract_function(a, b) { return a - b; }';
CREATE OK, 1 row affected (... sec)
.. hide:
cr> _wait_for_function('my_subtract_function(1::integer, 1::integer)')
::
cr> SELECT doc.my_subtract_function(3, 1) AS col;
+-----+
| col |
+-----+
| 2 |
+-----+
SELECT 1 row in set (... sec)
``CREATE OR REPLACE FUNCTION`` will either create a new function or replace
an existing function definition::
cr> CREATE OR REPLACE FUNCTION log10(bigint)
... RETURNS double precision
... LANGUAGE JAVASCRIPT
... AS 'function log10(a) {return Math.log(a)/Math.log(10); }';
CREATE OK, 1 row affected (... sec)
.. hide:
cr> _wait_for_function('log10(1::bigint)')
::
cr> SELECT doc.log10(10) AS col;
+-----+
| col |
+-----+
| 1.0 |
+-----+
SELECT 1 row in set (... sec)
It is possible to use named function arguments in the function signature. For
example, the ``calculate_distance`` function signature has two ``geo_point``
arguments named ``start`` and ``end``::
cr> CREATE OR REPLACE FUNCTION calculate_distance("start" geo_point, "end" geo_point)
... RETURNS real
... LANGUAGE JAVASCRIPT
... AS 'function calculate_distance(start, end) {
... return Math.sqrt(
... Math.pow(end[0] - start[0], 2),
... Math.pow(end[1] - start[1], 2));
... }';
CREATE OK, 1 row affected (... sec)
.. NOTE::
Argument names are used for query documentation purposes only. You cannot
reference arguments by name in the function body.
Optionally, a schema-qualified function name can be defined. If you omit the
schema, the current session schema is used::
cr> CREATE OR REPLACE FUNCTION my_schema.log10(bigint)
... RETURNS double precision
... LANGUAGE JAVASCRIPT
... AS 'function log10(a) { return Math.log(a)/Math.log(10); }';
CREATE OK, 1 row affected (... sec)
.. NOTE::
In order to improve the PostgreSQL server compatibility CrateDB allows the
creation of user defined functions against the :ref:`postgres-pg_catalog`
schema. However, the creation of user defined functions against the
read-only :ref:`system-information` and :ref:`information_schema` schemas is
prohibited.
.. _udf-supported-types:
Supported types
===============
Function arguments and return values can be any of the supported :ref:`data
types <data-types>`. The values passed into a function must strictly
correspond to the specified argument data types.
.. NOTE::
The value returned by the function will be casted to the return type
provided in the definition if required. An exception will be thrown if the
cast is not successful.
.. _udf-overloading:
Overloading
===========
Within a specific schema, you can overload functions by defining functions
with the same name but a different set of arguments::
cr> CREATE FUNCTION my_schema.my_multiply(integer, integer)
... RETURNS integer
... LANGUAGE JAVASCRIPT
... AS 'function my_multiply(a, b) { return a * b; }';
CREATE OK, 1 row affected (... sec)
This would overload the ``my_multiply`` function with different argument
types::
cr> CREATE FUNCTION my_schema.my_multiply(bigint, bigint)
... RETURNS bigint
... LANGUAGE JAVASCRIPT
... AS 'function my_multiply(a, b) { return a * b; }';
CREATE OK, 1 row affected (... sec)
This would overload the ``my_multiply`` function with more arguments::
cr> CREATE FUNCTION my_schema.my_multiply(bigint, bigint, bigint)
... RETURNS bigint
... LANGUAGE JAVASCRIPT
... AS 'function my_multiply(a, b, c) { return a * b * c; }';
CREATE OK, 1 row affected (... sec)
.. CAUTION::
It is considered bad practice to create functions that have the same name
as the CrateDB built-in functions!
.. NOTE::
If you call a function without a schema name, CrateDB will look it up in
the built-in functions first and only then in the user-defined functions
available in the :ref:`search_path <conf-session-search-path>`.
**Therefore a built-in function with the same name as a user-defined
function will hide the latter, even if it contains a different set of
arguments!** However, such functions can still be called if the schema name
is explicitly provided.
.. _udf-determinism:
Determinism
===========
.. CAUTION::
User-defined functions need to be deterministic, meaning that they must
always return the same result value when called with the same argument
values, because CrateDB might cache the returned values and reuse the value
if the function is called multiple times with the same arguments.
.. _udf-drop-function:
``DROP FUNCTION``
=================
Functions can be dropped like this::
cr> DROP FUNCTION doc.log10(bigint);
DROP OK, 1 row affected (... sec)
Adding ``IF EXISTS`` prevents from raising an error if the function doesn't
exist::
cr> DROP FUNCTION IF EXISTS doc.log10(integer);
DROP OK, 1 row affected (... sec)
Optionally, argument names can be specified within the drop statement::
cr> DROP FUNCTION IF EXISTS doc.calculate_distance(start_point geo_point, end_point geo_point);
DROP OK, 1 row affected (... sec)
Optionally, you can provide a schema::
cr> DROP FUNCTION my_schema.log10(bigint);
DROP OK, 1 row affected (... sec)
.. _udf-supported-languages:
Supported languages
===================
Currently, CrateDB only supports JavaScript for user-defined functions.
.. _udf-js:
JavaScript
----------
The user defined function JavaScript is compatible with the `ECMAScript 2019`_
specification.
CrateDB uses the `GraalVM JavaScript`_ engine as a JavaScript (ECMAScript)
language execution runtime. The `GraalVM JavaScript`_ engine is a Java
application that works on the stock Java Virtual Machines (VMs). The
interoperability between Java code (host language) and JavaScript user-defined
functions (guest language) is guaranteed by the `GraalVM Polyglot API`_.
Please note: CrateDB does not use the GraalVM JIT compiler as optimizing
compiler. However, the `stock host Java VM JIT compilers`_ can JIT-compile,
optimize, and execute the GraalVM JavaScript codebase to a certain extent.
The execution context for guest JavaScript is created with restricted
privileges to allow for the safe execution of less trusted guest language
code. The guest language application context for each user-defined function
is created with default access modifiers, so any access to managed resources
is denied. The only exception is the host language interoperability
configuration which explicitly allows access to Java lists and arrays. Please
refer to `GraalVM Security Guide`_ for more detailed information.
Also, even though user-defined functions implemented with ECMA-compliant
JavaScript, objects that are normally accessible with a web browser
(e.g. ``window``, ``console``, and so on) are not available.
.. NOTE::
GraalVM treats objects provided to JavaScript user-defined functions as
close as possible to their respective counterparts and therefore by default
only a subset of prototype functions are available in user-defined
functions. For CrateDB 4.6 and earlier the object prototype was disabled.
Please refer to the `GraalVM JavaScript Compatibility FAQ`_ to learn more
about the compatibility.
.. _udf-js-supported-types:
JavaScript supported types
..........................
JavaScript functions can handle all CrateDB data types. However, for some
return types the function output must correspond to the certain format.
If a function requires ``geo_point`` as a return type, then the JavaScript
function must return a ``double precision`` array of size 2, ``WKT`` string or
``GeoJson`` object.
Here is an example of a JavaScript function returning a ``double array``::
cr> CREATE FUNCTION rotate_point(point geo_point, angle real)
... RETURNS geo_point
... LANGUAGE JAVASCRIPT
... AS 'function rotate_point(point, angle) {
... var cos = Math.cos(angle);
... var sin = Math.sin(angle);
... var x = cos * point[0] - sin * point[1];
... var y = sin * point[0] + cos * point[1];
... return [x, y];
... }';
CREATE OK, 1 row affected (... sec)
Below is an example of a JavaScript function returning a ``WKT`` string, which
will be cast to ``geo_point``::
cr> CREATE FUNCTION symmetric_point(point geo_point)
... RETURNS geo_point
... LANGUAGE JAVASCRIPT
... AS 'function symmetric_point (point, angle) {
... var x = - point[0],
... y = - point[1];
... return "POINT (\" + x + \", \" + y +\")";
... }';
CREATE OK, 1 row affected (... sec)
Similarly, if the function specifies the ``geo_shape`` return data type, then
the JavaScript function should return a ``GeoJson`` object or ``WKT`` string::
cr> CREATE FUNCTION line("start" array(double precision), "end" array(double precision))
... RETURNS object
... LANGUAGE JAVASCRIPT
... AS 'function line(start, end) {
... return { "type": "LineString", "coordinates" : [start_point, end_point] };
... }';
CREATE OK, 1 row affected (... sec)
.. NOTE::
If the return value of the JavaScript function is ``undefined``, it is
converted to ``NULL``.
.. _udf-js-numbers:
Working with ``NUMBERS``
........................
The JavaScript engine interprets numbers as ``java.lang.Double``,
``java.lang.Long``, or ``java.lang.Integer``, depending on the computation
performed. In most cases, this is not an issue, since the return type of the
JavaScript function will be cast to the return type specified in the ``CREATE
FUNCTION`` statement, although cast might result in a loss of precision.
However, when you try to cast ``DOUBLE PRECISION`` to
``TIMESTAMP WITH TIME ZONE``, it will be interpreted as UTC seconds and will
result in a wrong value::
cr> CREATE FUNCTION utc(bigint, bigint, bigint)
... RETURNS TIMESTAMP WITH TIME ZONE
... LANGUAGE JAVASCRIPT
... AS 'function utc(year, month, day) {
... return Date.UTC(year, month, day, 0, 0, 0);
... }';
CREATE OK, 1 row affected (... sec)
.. hide:
cr> _wait_for_function('utc(1::bigint, 1::bigint, 1::bigint)')
::
cr> SELECT date_format(utc(2016,04,6)) as epoque;
+------------------------------+
| epoque |
+------------------------------+
| 48314-07-22T00:00:00.000000Z |
+------------------------------+
SELECT 1 row in set (... sec)
.. hide:
cr> DROP FUNCTION utc(bigint, bigint, bigint);
DROP OK, 1 row affected (... sec)
To avoid this behavior, the numeric value should be divided by 1000 before it
is returned::
cr> CREATE FUNCTION utc(bigint, bigint, bigint)
... RETURNS TIMESTAMP WITH TIME ZONE
... LANGUAGE JAVASCRIPT
... AS 'function utc(year, month, day) {
... return Date.UTC(year, month, day, 0, 0, 0)/1000;
... }';
CREATE OK, 1 row affected (... sec)
.. hide:
cr> _wait_for_function('utc(1::bigint, 1::bigint, 1::bigint)')
::
cr> SELECT date_format(utc(2016,04,6)) as epoque;
+-----------------------------+
| epoque |
+-----------------------------+
| 2016-05-06T00:00:00.000000Z |
+-----------------------------+
SELECT 1 row in set (... sec)
.. hide:
cr> DROP FUNCTION my_subtract_function(integer, integer);
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION my_schema.my_multiply(integer, integer);
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION my_schema.my_multiply(bigint, bigint, bigint);
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION my_schema.my_multiply(bigint, bigint);
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION rotate_point(point geo_point, angle real);
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION symmetric_point(point geo_point);
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION line(start_point array(double precision), end_point array(double precision));
DROP OK, 1 row affected (... sec)
cr> DROP FUNCTION utc(bigint, bigint, bigint);
DROP OK, 1 row affected (... sec)
.. _ECMAScript 2019: https://262.ecma-international.org/10.0/index.html
.. _GraalVM JavaScript: https://www.graalvm.org/reference-manual/js/
.. _GraalVM JavaScript Compatibility FAQ: https://www.graalvm.org/22.0/reference-manual/js/FAQ/#compatibility
.. _GraalVM Polyglot API: https://www.graalvm.org/reference-manual/embed-languages/
.. _GraalVM Security Guide: https://www.graalvm.org/security-guide/
.. _stock host Java VM JIT compilers: https://www.graalvm.org/reference-manual/js/RunOnJDK/