Merge lp:~hingo/drizzle/drizzle-js_eval into lp:~drizzle-trunk/drizzle/development
- drizzle-js_eval
- Merge into development
Status: | Merged |
---|---|
Approved by: | Brian Aker |
Approved revision: | 2385 |
Merged at revision: | 2439 |
Proposed branch: | lp:~hingo/drizzle/drizzle-js_eval |
Merge into: | lp:~drizzle-trunk/drizzle/development |
Diff against target: |
877 lines (+747/-9) 10 files modified
configure.ac (+1/-1) drizzled/error.cc (+3/-0) drizzled/error_t.h (+2/-1) drizzled/temporal.h (+7/-7) m4/pandora_have_libv8.m4 (+53/-0) plugin/js/docs/index.rst (+230/-0) plugin/js/js.cc (+371/-0) plugin/js/plugin.ini (+10/-0) plugin/js/tests/r/js.result (+35/-0) plugin/js/tests/t/js.test (+35/-0) |
To merge this branch: | bzr merge lp:~hingo/drizzle/drizzle-js_eval |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Drizzle Merge Team | Pending | ||
Review via email: mp+76674@code.launchpad.net |
Commit message
Description of the change
Hi
I've written a plugin that embeds the v8 javascript engine and exposes it as a function JS(<javascript code snippet>, <arg1>, <arg2>, ...). The code is now feature complete for the first iteration. It still lacks test cases and documentation. I intend to complete those before this is merged, but I'm sending this to ask for a first review at this stage. This is my first Drizzle code I've ever written, so getting feedback at this stage would be appreciated. So please note, I'm asking for review but not yet asking to merge this.
The primary reason to do this is to get a JSON parser + ability to manipulate and return the JSON documents. This is analogous to the MySQL functions ExtractValue() and UpdateXML() for XML strings, there just isn't a counterpart to XPath in JSON. The side effect of being able to execute arbitrary Javascript inside Drizzle is cool, and can be built upon in the future. Currently there is no Drizzle functionality available inside the javascript environment, for instance you couldn't query any tables or do anything else you typically do with stored procedures.
The code comments still contain some todos which are more like questions - things I'd like to learn about Drizzle internals. If the reviewer can answer those in feedback via drizzle-discuss, that would be great.
The code comments also contain todos which I don't intend to fix in the first version, they are mostly performance work in particular related to the fact that v8 is very single threaded (Chrome browser has a separate browser and separate v8 instance for each tab). The intent is to focus short term on feature completeness and leave the performance work for later. (The current code will not degrade Drizzle performance in general, but it would not be a good idea to do a hundred simultaneous calls to JS().)
Usage examples can be seen in the comments to this blog post:
http://
Please note that the function is now called JS(...) and there is no JS_EVAL(...).
- 2385. By Henrik Ingo
-
Added tests for JS().
Olaf van der Spek (olafvdspek) wrote : | # |
- 2386. By Henrik Ingo
-
Add 2 tests I forgot:
- connect from 2 clients to make sure we are handling multi threaded
mode correctly.
- run a script that gives JavaScript syntax error. - 2387. By Henrik Ingo
-
Wrote end user documentation for JS().
Labeled this as version 0.9.
This is now ready to start the journey towards trunk! - 2388. By Henrik Ingo
-
Small style fixes from Olaf's review (thanks).
Removed @todo items that have been done.
Preview Diff
1 | === modified file 'configure.ac' | |||
2 | --- configure.ac 2011-08-16 01:07:54 +0000 | |||
3 | +++ configure.ac 2011-10-01 10:16:24 +0000 | |||
4 | @@ -155,7 +155,7 @@ | |||
5 | 155 | PANDORA_DRIZZLE_BUILD | 155 | PANDORA_DRIZZLE_BUILD |
6 | 156 | PANDORA_HAVE_BOOST_TEST | 156 | PANDORA_HAVE_BOOST_TEST |
7 | 157 | PANDORA_HAVE_LIBSQLITE3 | 157 | PANDORA_HAVE_LIBSQLITE3 |
9 | 158 | 158 | PANDORA_HAVE_LIBV8 | |
10 | 159 | 159 | ||
11 | 160 | 160 | ||
12 | 161 | ######################################################################### | 161 | ######################################################################### |
13 | 162 | 162 | ||
14 | === modified file 'drizzled/error.cc' | |||
15 | --- drizzled/error.cc 2011-08-16 11:47:29 +0000 | |||
16 | +++ drizzled/error.cc 2011-10-01 10:16:24 +0000 | |||
17 | @@ -639,6 +639,9 @@ | |||
18 | 639 | ADD_ERROR_MESSAGE(ER_USE_DATA_DICTIONARY, N_("Engine status is now stored in the data_dictionary tables, please use these instead.")); | 639 | ADD_ERROR_MESSAGE(ER_USE_DATA_DICTIONARY, N_("Engine status is now stored in the data_dictionary tables, please use these instead.")); |
19 | 640 | ADD_ERROR_MESSAGE(ER_TRANSACTION_ALREADY_STARTED, N_("There is already a transaction in progress")); | 640 | ADD_ERROR_MESSAGE(ER_TRANSACTION_ALREADY_STARTED, N_("There is already a transaction in progress")); |
20 | 641 | ADD_ERROR_MESSAGE(ER_NO_LOCK_HELD, N_("No lock is held by this connection.")); | 641 | ADD_ERROR_MESSAGE(ER_NO_LOCK_HELD, N_("No lock is held by this connection.")); |
21 | 642 | |||
22 | 643 | // Errors in scripts, such as JavaScript | ||
23 | 644 | ADD_ERROR_MESSAGE(ER_SCRIPT, N_("Script error: %s")); | ||
24 | 642 | } | 645 | } |
25 | 643 | 646 | ||
26 | 644 | } /* namespace drizzled */ | 647 | } /* namespace drizzled */ |
27 | 645 | 648 | ||
28 | === modified file 'drizzled/error_t.h' | |||
29 | --- drizzled/error_t.h 2011-08-16 11:47:29 +0000 | |||
30 | +++ drizzled/error_t.h 2011-10-01 10:16:24 +0000 | |||
31 | @@ -866,7 +866,8 @@ | |||
32 | 866 | ER_USE_DATA_DICTIONARY, | 866 | ER_USE_DATA_DICTIONARY, |
33 | 867 | ER_TRANSACTION_ALREADY_STARTED, | 867 | ER_TRANSACTION_ALREADY_STARTED, |
34 | 868 | ER_CARTESIAN_JOIN_ATTEMPTED, | 868 | ER_CARTESIAN_JOIN_ATTEMPTED, |
36 | 869 | ER_NO_LOCK_HELD | 869 | ER_NO_LOCK_HELD, |
37 | 870 | ER_SCRIPT /* Error executing script: (such as JavaScript) */ | ||
38 | 870 | }; | 871 | }; |
39 | 871 | 872 | ||
40 | 872 | 873 | ||
41 | 873 | 874 | ||
42 | === modified file 'drizzled/temporal.h' | |||
43 | --- drizzled/temporal.h 2011-03-29 12:45:08 +0000 | |||
44 | +++ drizzled/temporal.h 2011-10-01 10:16:24 +0000 | |||
45 | @@ -76,7 +76,7 @@ | |||
46 | 76 | /** | 76 | /** |
47 | 77 | * Base class for all temporal data classes. | 77 | * Base class for all temporal data classes. |
48 | 78 | */ | 78 | */ |
50 | 79 | class Temporal | 79 | class DRIZZLED_API Temporal |
51 | 80 | { | 80 | { |
52 | 81 | protected: | 81 | protected: |
53 | 82 | enum calendar _calendar; | 82 | enum calendar _calendar; |
54 | @@ -188,7 +188,7 @@ | |||
55 | 188 | * Class representing temporal components in a valid | 188 | * Class representing temporal components in a valid |
56 | 189 | * SQL date range, with no time component | 189 | * SQL date range, with no time component |
57 | 190 | */ | 190 | */ |
59 | 191 | class Date: public Temporal | 191 | class DRIZZLED_API Date: public Temporal |
60 | 192 | { | 192 | { |
61 | 193 | public: | 193 | public: |
62 | 194 | Date() :Temporal() {} | 194 | Date() :Temporal() {} |
63 | @@ -435,7 +435,7 @@ | |||
64 | 435 | * Class representing temporal components having only | 435 | * Class representing temporal components having only |
65 | 436 | * a time component, with no date structure | 436 | * a time component, with no date structure |
66 | 437 | */ | 437 | */ |
68 | 438 | class Time: public Temporal | 438 | class DRIZZLED_API Time: public Temporal |
69 | 439 | { | 439 | { |
70 | 440 | public: | 440 | public: |
71 | 441 | Time() :Temporal() {} | 441 | Time() :Temporal() {} |
72 | @@ -567,7 +567,7 @@ | |||
73 | 567 | * Class representing temporal components in a valid | 567 | * Class representing temporal components in a valid |
74 | 568 | * SQL datetime range, including a time component | 568 | * SQL datetime range, including a time component |
75 | 569 | */ | 569 | */ |
77 | 570 | class DateTime: public Date | 570 | class DRIZZLED_API DateTime: public Date |
78 | 571 | { | 571 | { |
79 | 572 | public: | 572 | public: |
80 | 573 | DateTime() :Date() {} | 573 | DateTime() :Date() {} |
81 | @@ -680,7 +680,7 @@ | |||
82 | 680 | /** | 680 | /** |
83 | 681 | * Class representing temporal components in the UNIX epoch | 681 | * Class representing temporal components in the UNIX epoch |
84 | 682 | */ | 682 | */ |
86 | 683 | class Timestamp: public DateTime | 683 | class DRIZZLED_API Timestamp: public DateTime |
87 | 684 | { | 684 | { |
88 | 685 | public: | 685 | public: |
89 | 686 | Timestamp() :DateTime() {} | 686 | Timestamp() :DateTime() {} |
90 | @@ -746,7 +746,7 @@ | |||
91 | 746 | * Class representing temporal components in the UNIX epoch | 746 | * Class representing temporal components in the UNIX epoch |
92 | 747 | * with an additional microsecond component. | 747 | * with an additional microsecond component. |
93 | 748 | */ | 748 | */ |
95 | 749 | class MicroTimestamp: public Timestamp | 749 | class DRIZZLED_API MicroTimestamp: public Timestamp |
96 | 750 | { | 750 | { |
97 | 751 | public: | 751 | public: |
98 | 752 | MicroTimestamp() :Timestamp() {} | 752 | MicroTimestamp() :Timestamp() {} |
99 | @@ -789,7 +789,7 @@ | |||
100 | 789 | * Class representing temporal components in the UNIX epoch | 789 | * Class representing temporal components in the UNIX epoch |
101 | 790 | * with an additional nanosecond component. | 790 | * with an additional nanosecond component. |
102 | 791 | */ | 791 | */ |
104 | 792 | class NanoTimestamp: public Timestamp | 792 | class DRIZZLED_API NanoTimestamp: public Timestamp |
105 | 793 | { | 793 | { |
106 | 794 | public: | 794 | public: |
107 | 795 | NanoTimestamp() :Timestamp() {} | 795 | NanoTimestamp() :Timestamp() {} |
108 | 796 | 796 | ||
109 | === added file 'm4/pandora_have_libv8.m4' | |||
110 | --- m4/pandora_have_libv8.m4 1970-01-01 00:00:00 +0000 | |||
111 | +++ m4/pandora_have_libv8.m4 2011-10-01 10:16:24 +0000 | |||
112 | @@ -0,0 +1,53 @@ | |||
113 | 1 | dnl Copyright (C) 2009 Sun Microsystems, Inc. | ||
114 | 2 | dnl This file is free software; Sun Microsystems, Inc. | ||
115 | 3 | dnl gives unlimited permission to copy and/or distribute it, | ||
116 | 4 | dnl with or without modifications, as long as this notice is preserved. | ||
117 | 5 | |||
118 | 6 | #-------------------------------------------------------------------- | ||
119 | 7 | # Check for libv8 | ||
120 | 8 | #-------------------------------------------------------------------- | ||
121 | 9 | |||
122 | 10 | |||
123 | 11 | AC_DEFUN([_PANDORA_SEARCH_LIBV8],[ | ||
124 | 12 | AC_REQUIRE([AC_LIB_PREFIX]) | ||
125 | 13 | |||
126 | 14 | # v8 is written in C++, need to use g++ for test link below | ||
127 | 15 | AC_LANG_CPLUSPLUS | ||
128 | 16 | |||
129 | 17 | AC_LIB_HAVE_LINKFLAGS(v8, pthread, | ||
130 | 18 | [ | ||
131 | 19 | #include <v8.h> | ||
132 | 20 | ],[ | ||
133 | 21 | v8::HandleScope handle_scope; | ||
134 | 22 | ]) | ||
135 | 23 | |||
136 | 24 | AM_CONDITIONAL(HAVE_LIBV8, [test "x${ac_cv_libv8}" = "xyes"]) | ||
137 | 25 | ]) | ||
138 | 26 | |||
139 | 27 | AC_DEFUN([_PANDORA_HAVE_LIBV8],[ | ||
140 | 28 | AC_ARG_ENABLE([libv8], | ||
141 | 29 | [AS_HELP_STRING([--disable-libv8], | ||
142 | 30 | [Build with libv8 support @<:@default=on@:>@])], | ||
143 | 31 | [ac_enable_libv8="$enableval"], | ||
144 | 32 | [ac_enable_libv8="yes"]) | ||
145 | 33 | |||
146 | 34 | _PANDORA_SEARCH_LIBV8 | ||
147 | 35 | ]) | ||
148 | 36 | |||
149 | 37 | |||
150 | 38 | AC_DEFUN([PANDORA_HAVE_LIBV8],[ | ||
151 | 39 | AC_REQUIRE([_PANDORA_HAVE_LIBV8]) | ||
152 | 40 | ]) | ||
153 | 41 | |||
154 | 42 | AC_DEFUN([_PANDORA_REQUIRE_LIBV8],[ | ||
155 | 43 | ac_enable_libv8="yes" | ||
156 | 44 | _PANDORA_SEARCH_LIBV8 | ||
157 | 45 | |||
158 | 46 | AS_IF([test x$ac_cv_libv8 = xno],[ | ||
159 | 47 | PANDORA_MSG_ERROR([libv8 is required for ${PACKAGE}. On Debian this can be found in libv8-dev. On RedHat this can be found in libv8-devel.]) | ||
160 | 48 | ]) | ||
161 | 49 | ]) | ||
162 | 50 | |||
163 | 51 | AC_DEFUN([PANDORA_REQUIRE_LIBV8],[ | ||
164 | 52 | AC_REQUIRE([_PANDORA_REQUIRE_LIBV8]) | ||
165 | 53 | ]) | ||
166 | 0 | 54 | ||
167 | === added directory 'plugin/js' | |||
168 | === added directory 'plugin/js/docs' | |||
169 | === added file 'plugin/js/docs/index.rst' | |||
170 | --- plugin/js/docs/index.rst 1970-01-01 00:00:00 +0000 | |||
171 | +++ plugin/js/docs/index.rst 2011-10-01 10:16:24 +0000 | |||
172 | @@ -0,0 +1,230 @@ | |||
173 | 1 | JS | ||
174 | 2 | =========== | ||
175 | 3 | |||
176 | 4 | .. code-block:: mysql | ||
177 | 5 | |||
178 | 6 | JS(javascript_code [, arg1 [AS arg_name]] [, ...]) | ||
179 | 7 | |||
180 | 8 | ``JS()`` executes a JavaScript code snippet and returns the value of the last executed statement. Additional arguments are passed to the JavaScript environment and are available in the ``arguments[]`` array. If the optional ``AS arg_name`` is used, the same argument value is made available as a global variable with that name. | ||
181 | 9 | |||
182 | 10 | |||
183 | 11 | .. _js_loading: | ||
184 | 12 | |||
185 | 13 | Loading | ||
186 | 14 | ------- | ||
187 | 15 | |||
188 | 16 | This plugin is loaded by default. | ||
189 | 17 | |||
190 | 18 | If you want to prevent the loading of this plugin, start :program:`drizzled` with:: | ||
191 | 19 | |||
192 | 20 | --plugin-remove=js | ||
193 | 21 | |||
194 | 22 | .. _js_examples: | ||
195 | 23 | |||
196 | 24 | Examples | ||
197 | 25 | -------- | ||
198 | 26 | |||
199 | 27 | The first argument is required and should be a string of valid JavaScript code. The value of the last statement is returned, note that you should not use a ``return`` keyword. This is a top level JavaScript code snippet, not a JavaScript function. | ||
200 | 28 | |||
201 | 29 | .. code-block:: mysql | ||
202 | 30 | |||
203 | 31 | SELECT JS('var d = new Date(); "Drizzle started running JavaScript at: " + d;'); | ||
204 | 32 | |||
205 | 33 | Will output | ||
206 | 34 | |||
207 | 35 | +----------------------------------------------------------------------------------+ | ||
208 | 36 | | JS('var d = new Date(); "Drizzle started running JavaScript at: " + d;') | | ||
209 | 37 | +==================================================================================+ | ||
210 | 38 | | Drizzle started running JavaScript at: Mon Aug 29 2011 00:23:31 GMT+0300 (EEST) | | ||
211 | 39 | +----------------------------------------------------------------------------------+ | ||
212 | 40 | |||
213 | 41 | |||
214 | 42 | Additional arguments are passed to the JavaScript environment and are available in the ``arguments[]`` array. | ||
215 | 43 | |||
216 | 44 | .. code-block:: mysql | ||
217 | 45 | |||
218 | 46 | SELECT JS("arguments[0] + arguments[1] + arguments[2];", 1, 2, 3) AS 'JS(...)'; | ||
219 | 47 | |||
220 | 48 | Will output | ||
221 | 49 | |||
222 | 50 | +--------------+ | ||
223 | 51 | | JS(...) | | ||
224 | 52 | +==============+ | ||
225 | 53 | | 6 | | ||
226 | 54 | +--------------+ | ||
227 | 55 | |||
228 | 56 | |||
229 | 57 | |||
230 | 58 | If the optional ``AS arg_name`` is used, the same argument value is made available as a global variable with that name. | ||
231 | 59 | |||
232 | 60 | .. code-block:: mysql | ||
233 | 61 | |||
234 | 62 | SELECT JS("first + second + third;", 1 AS 'first', 2.0 AS 'second', 3.5 AS 'third') AS 'JS(...)'; | ||
235 | 63 | |||
236 | 64 | Will output | ||
237 | 65 | |||
238 | 66 | +--------------+ | ||
239 | 67 | | JS(...) | | ||
240 | 68 | +==============+ | ||
241 | 69 | | 6.5 | | ||
242 | 70 | +--------------+ | ||
243 | 71 | |||
244 | 72 | .. _json_parse: | ||
245 | 73 | |||
246 | 74 | Using JS() to parse JSON documents | ||
247 | 75 | ----------------------------------- | ||
248 | 76 | |||
249 | 77 | JavaScript includes a JSON parser. This means you can use ``JS()`` as a JSON parser, and optionally use JavaScript to manipulate or select fragments of the JSON document. To do this, pass your JSON document as an argument, and use the ``JSON.parse()`` method to return it as a JavaScript object: | ||
250 | 78 | |||
251 | 79 | .. code-block:: mysql | ||
252 | 80 | |||
253 | 81 | SELECT JS("var jsondoc = JSON.parse(arguments[0]); jsondoc['name']['firstname'];", | ||
254 | 82 | '{ "name" : {"firstname" : "Henrik", "lastname" : "Ingo"} }') AS 'JS(...)'; | ||
255 | 83 | |||
256 | 84 | Will output | ||
257 | 85 | |||
258 | 86 | +--------------+ | ||
259 | 87 | | JS(...) | | ||
260 | 88 | +==============+ | ||
261 | 89 | | Henrik | | ||
262 | 90 | +--------------+ | ||
263 | 91 | |||
264 | 92 | |||
265 | 93 | To return a JSON document from JavaScript, use ``JSON.stringify()``: | ||
266 | 94 | |||
267 | 95 | .. code-block:: mysql | ||
268 | 96 | |||
269 | 97 | SELECT JS("var jsondoc = JSON.parse(arguments[0]); | ||
270 | 98 | JSON.stringify(jsondoc['name']);", | ||
271 | 99 | '{ "name" : {"firstname" : "Henrik", "lastname" : "Ingo"} }') AS 'JS(...)'; | ||
272 | 100 | |||
273 | 101 | |||
274 | 102 | Will output | ||
275 | 103 | |||
276 | 104 | +------------------------------------------+ | ||
277 | 105 | | JS(...) | | ||
278 | 106 | +==========================================+ | ||
279 | 107 | | {"firstname":"Henrik","lastname":"Ingo"} | | ||
280 | 108 | +------------------------------------------+ | ||
281 | 109 | |||
282 | 110 | Note that since a Drizzle function can only return scalar values, if you want to return arrays or objects from your JavaScript, JSON is a recommended way of doing that. | ||
283 | 111 | |||
284 | 112 | .. _js_queries: | ||
285 | 113 | |||
286 | 114 | Using JS in queries, passing columns as arguments | ||
287 | 115 | ------------------------------------------------- | ||
288 | 116 | |||
289 | 117 | Naturally, the arguments can also be columns in a query. For instance in the case of JSON data, if you have stored JSON documents as TEXT or BLOB in a table, you can now use ``JSON.parse()`` to select individual fields out of it: | ||
290 | 118 | |||
291 | 119 | .. code-block:: mysql | ||
292 | 120 | |||
293 | 121 | CREATE TABLE t (k INT PRIMARY KEY auto_increment, v TEXT); | ||
294 | 122 | INSERT INTO t (v) VALUES ('{ "person" : { "firstname" : "Roland", "lastname" : "Bouman" } }'); | ||
295 | 123 | INSERT INTO t (v) VALUES ('{ "person" : { "firstname" : "Henrik", "lastname" : "Ingo" } }'); | ||
296 | 124 | INSERT INTO t (v) VALUES ('{ "person" : { "firstname" : "Brian", "lastname" : "Aker" } }'); | ||
297 | 125 | SELECT JS('var person = JSON.parse(jsondoc); person["person"]["firstname"];', | ||
298 | 126 | v as jsondoc) AS 'JS(...)' | ||
299 | 127 | FROM t WHERE k=2; | ||
300 | 128 | |||
301 | 129 | |||
302 | 130 | Will output | ||
303 | 131 | |||
304 | 132 | +--------------+ | ||
305 | 133 | | JS(...) | | ||
306 | 134 | +==============+ | ||
307 | 135 | | Henrik | | ||
308 | 136 | +--------------+ | ||
309 | 137 | |||
310 | 138 | |||
311 | 139 | And | ||
312 | 140 | |||
313 | 141 | .. code-block:: mysql | ||
314 | 142 | |||
315 | 143 | SELECT k, JS('var person = JSON.parse(jsondoc); person["person"]["firstname"];', | ||
316 | 144 | v as jsondoc) AS 'firstname', | ||
317 | 145 | JS('var person = JSON.parse(jsondoc); person["person"]["lastname"];', | ||
318 | 146 | v as jsondoc) AS 'lastname' | ||
319 | 147 | FROM t; | ||
320 | 148 | |||
321 | 149 | Will break your unstructured JSON data back into a relational table: | ||
322 | 150 | |||
323 | 151 | +---+-----------+----------+ | ||
324 | 152 | | k | firstname | lastname | | ||
325 | 153 | +===+===========+==========+ | ||
326 | 154 | | 1 | Roland | Bouman | | ||
327 | 155 | +---+-----------+----------+ | ||
328 | 156 | | 2 | Henrik | Ingo | | ||
329 | 157 | +---+-----------+----------+ | ||
330 | 158 | | 3 | Brian | Aker | | ||
331 | 159 | +---+-----------+----------+ | ||
332 | 160 | |||
333 | 161 | .. _js_stored_procedure_surrogate: | ||
334 | 162 | |||
335 | 163 | Using JS as surrogate for stored procedures: | ||
336 | 164 | -------------------------------------------- | ||
337 | 165 | |||
338 | 166 | Especially if the JavaScript you want to use is more complex, it might be a good idea to store the javascript itself in a table in Drizzle, or alternatively a variable. This simplifies queries that use the script: | ||
339 | 167 | |||
340 | 168 | .. code-block:: mysql | ||
341 | 169 | |||
342 | 170 | CREATE TABLE sp (name VARCHAR(255) PRIMARY KEY, script TEXT); | ||
343 | 171 | INSERT INTO sp (name, script) VALUES ('get_person_property', 'var person = JSON.parse(jsondoc); person["person"][property];'); | ||
344 | 172 | SELECT k, JS( (SELECT script FROM sp WHERE name='get_person_property'), | ||
345 | 173 | v as jsondoc, 'firstname' as 'property') AS 'firstname', | ||
346 | 174 | JS( (SELECT script FROM sp WHERE name='get_person_property'), | ||
347 | 175 | v as jsondoc, 'lastname' as 'property') AS 'lastname' | ||
348 | 176 | FROM t; | ||
349 | 177 | |||
350 | 178 | |||
351 | 179 | Will output the same result as above: | ||
352 | 180 | |||
353 | 181 | +---+-----------+----------+ | ||
354 | 182 | | k | firstname | lastname | | ||
355 | 183 | +===+===========+==========+ | ||
356 | 184 | | 1 | Roland | Bouman | | ||
357 | 185 | +---+-----------+----------+ | ||
358 | 186 | | 2 | Henrik | Ingo | | ||
359 | 187 | +---+-----------+----------+ | ||
360 | 188 | | 3 | Brian | Aker | | ||
361 | 189 | +---+-----------+----------+ | ||
362 | 190 | |||
363 | 191 | .. _js_future_work: | ||
364 | 192 | |||
365 | 193 | Limitations and future work | ||
366 | 194 | --------------------------- | ||
367 | 195 | |||
368 | 196 | The current version of ``JS()`` is complete in the sense that any type of arguments (integer, real, decimal, string, date) can be used, JavaScript code can be of arbitrary complexity and scalar values of any type can be returned. However, apart from the input parameters and the return value, there is no way to interact with Drizzle from the JavaScript environment. The plan is that in a future version ``JS()`` will expose some Drizzle API's, such as the ``Execute()`` API, so that one could query Drizzle tables and call other Drizzle functions from the JavaScript environment. This would essentially make JS() a form of JavaScript stored procedures. Of course, a next step after that could be to actually support ``STORED PROCEDURE`` syntax and permissions. | ||
369 | 197 | |||
370 | 198 | Values of type ``DECIMAL`` will be passed as JavaScript ``Double`` values. This may lead to loss of precision. If you want to keep the precision, you must explicitly cast ``DECIMAL`` values into ``CHAR`` when you pass them as arguments. Note that this will affect how the JavaScript ``+`` operator works on the value (string concatenation instead of addition). | ||
371 | 199 | |||
372 | 200 | The current version lacks several obvious performance optimizations. Most importantly the v8 JavaScript engine is single threaded, so heavy use of ``JS()`` on busy production servers is not recommended. A future version will use the v8 Isolate class to run several instances of the single threaded v8 engine. | ||
373 | 201 | |||
374 | 202 | .. _js_authors: | ||
375 | 203 | |||
376 | 204 | Authors | ||
377 | 205 | ------- | ||
378 | 206 | |||
379 | 207 | Henrik Ingo | ||
380 | 208 | |||
381 | 209 | Thanks to Roland Bouman for suggesting to use v8 engine instead of just a JSON parser and for review and comments on JavaScript and JSON conventions. | ||
382 | 210 | |||
383 | 211 | .. _js_version: | ||
384 | 212 | |||
385 | 213 | Version | ||
386 | 214 | ------- | ||
387 | 215 | |||
388 | 216 | This documentation applies to **js 0.9**. | ||
389 | 217 | |||
390 | 218 | To see which version of the plugin a Drizzle server is running, execute: | ||
391 | 219 | |||
392 | 220 | .. code-block:: mysql | ||
393 | 221 | |||
394 | 222 | SELECT MODULE_VERSION FROM DATA_DICTIONARY.MODULES WHERE MODULE_NAME='js' | ||
395 | 223 | |||
396 | 224 | |||
397 | 225 | Changelog | ||
398 | 226 | --------- | ||
399 | 227 | |||
400 | 228 | v0.9 | ||
401 | 229 | ^^^^ | ||
402 | 230 | * First release. Complete JS() functionality, but no APIs back to Drizzle are exposed yet and several performance optimizations were left for later release. | ||
403 | 0 | 231 | ||
404 | === added file 'plugin/js/js.cc' | |||
405 | --- plugin/js/js.cc 1970-01-01 00:00:00 +0000 | |||
406 | +++ plugin/js/js.cc 2011-10-01 10:16:24 +0000 | |||
407 | @@ -0,0 +1,371 @@ | |||
408 | 1 | /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*- | ||
409 | 2 | * vim:expandtab:shiftwidth=2:tabstop=2:smarttab: | ||
410 | 3 | * | ||
411 | 4 | * Copyright (C) 2011, Henrik Ingo. | ||
412 | 5 | * | ||
413 | 6 | * This program is free software; you can redistribute it and/or modify | ||
414 | 7 | * it under the terms of the GNU General Public License as published by | ||
415 | 8 | * the Free Software Foundation; version 2 of the License. | ||
416 | 9 | * | ||
417 | 10 | * This program is distributed in the hope that it will be useful, | ||
418 | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
419 | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
420 | 13 | * GNU General Public License for more details. | ||
421 | 14 | * | ||
422 | 15 | * You should have received a copy of the GNU General Public License | ||
423 | 16 | * along with this program; if not, write to the Free Software | ||
424 | 17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
425 | 18 | */ | ||
426 | 19 | |||
427 | 20 | #include <config.h> | ||
428 | 21 | #include <stdio.h> | ||
429 | 22 | |||
430 | 23 | #include <drizzled/error.h> | ||
431 | 24 | #include <drizzled/plugin/function.h> | ||
432 | 25 | #include <drizzled/function/str/strfunc.h> | ||
433 | 26 | #include <drizzled/temporal.h> | ||
434 | 27 | |||
435 | 28 | #include <v8.h> | ||
436 | 29 | #define JS_ENGINE "v8" | ||
437 | 30 | |||
438 | 31 | using namespace std; | ||
439 | 32 | using namespace drizzled; | ||
440 | 33 | |||
441 | 34 | |||
442 | 35 | namespace drizzle_plugin { | ||
443 | 36 | namespace js { | ||
444 | 37 | |||
445 | 38 | v8::Handle<v8::Value> V8Version(const v8::Arguments& args); | ||
446 | 39 | v8::Handle<v8::Value> JsEngine(const v8::Arguments& args); | ||
447 | 40 | const char* v8_to_char(const v8::String::Utf8Value& value); | ||
448 | 41 | void emit_drizzle_error(v8::TryCatch* try_catch); | ||
449 | 42 | |||
450 | 43 | |||
451 | 44 | // TODO: So this is a function that returns strings? | ||
452 | 45 | // What is the class for functions that return mixed types? | ||
453 | 46 | // Or is this as it should be, apparently js('1') + js('2') does the right thing already. | ||
454 | 47 | |||
455 | 48 | class JsFunction : public Item_str_func | ||
456 | 49 | { | ||
457 | 50 | public: | ||
458 | 51 | String *val_str(String *); | ||
459 | 52 | |||
460 | 53 | const char *func_name() const | ||
461 | 54 | { | ||
462 | 55 | return "js"; | ||
463 | 56 | } | ||
464 | 57 | |||
465 | 58 | void fix_length_and_dec() | ||
466 | 59 | { | ||
467 | 60 | maybe_null= 1; | ||
468 | 61 | max_length= MAX_BLOB_WIDTH; | ||
469 | 62 | } | ||
470 | 63 | |||
471 | 64 | bool check_argument_count(int n) | ||
472 | 65 | { | ||
473 | 66 | return (n >= 1); | ||
474 | 67 | } | ||
475 | 68 | }; | ||
476 | 69 | |||
477 | 70 | /** | ||
478 | 71 | * @brief Extracts a C string from a V8 Utf8Value | ||
479 | 72 | * | ||
480 | 73 | * Idea copied from v8 sources, samples/shell.cc. Makes code easier to read than | ||
481 | 74 | * (char *)(*utf8value) | ||
482 | 75 | */ | ||
483 | 76 | const char* v8_to_char(const v8::String::Utf8Value& value) { | ||
484 | 77 | return *value ? *value : "<javascript v8 string conversion failed>"; | ||
485 | 78 | } | ||
486 | 79 | |||
487 | 80 | /** | ||
488 | 81 | * @brief Take v8 exception and emit Drizzle error to client | ||
489 | 82 | * | ||
490 | 83 | * This is adapted from ReportException() in v8 samples/shell.cc. | ||
491 | 84 | */ | ||
492 | 85 | void emit_drizzle_error(v8::TryCatch* try_catch) | ||
493 | 86 | { | ||
494 | 87 | v8::HandleScope handle_scope; | ||
495 | 88 | v8::String::Utf8Value exception(try_catch->Exception()); | ||
496 | 89 | const char* exception_string = v8_to_char(exception); | ||
497 | 90 | v8::Handle<v8::Message> message = try_catch->Message(); | ||
498 | 91 | if (message.IsEmpty()) { | ||
499 | 92 | // V8 didn't provide any extra information about this error; just | ||
500 | 93 | // print the exception. | ||
501 | 94 | my_error(ER_SCRIPT, MYF(0), exception_string); | ||
502 | 95 | } else { | ||
503 | 96 | char buf[2048]; | ||
504 | 97 | int linenum = message->GetLineNumber(); | ||
505 | 98 | sprintf(buf, "At line %i: %.1900s (Do SHOW ERRORS for more information.)", linenum, exception_string); | ||
506 | 99 | my_error(ER_SCRIPT, MYF(0), buf); | ||
507 | 100 | // Print line of source code and where error happened. | ||
508 | 101 | v8::String::Utf8Value sourceline(message->GetSourceLine()); | ||
509 | 102 | const char* sourceline_string = v8_to_char(sourceline); | ||
510 | 103 | sprintf(buf, "Line %i: %.160s", linenum, sourceline_string); | ||
511 | 104 | my_error(ER_SCRIPT, MYF(0), buf); | ||
512 | 105 | int start = message->GetStartColumn(); | ||
513 | 106 | sprintf(buf, "Check your script starting at: '%.50s'", &sourceline_string[start]); | ||
514 | 107 | my_error(ER_SCRIPT, MYF(0), buf); | ||
515 | 108 | v8::String::Utf8Value stack_trace(try_catch->StackTrace()); | ||
516 | 109 | if (stack_trace.length() > 0) { | ||
517 | 110 | const char* stack_trace_string = v8_to_char(stack_trace); | ||
518 | 111 | my_error(ER_SCRIPT, MYF(0), stack_trace_string); | ||
519 | 112 | } | ||
520 | 113 | } | ||
521 | 114 | } | ||
522 | 115 | |||
523 | 116 | /** | ||
524 | 117 | * @brief Implements js() - execute JavaScript code | ||
525 | 118 | * | ||
526 | 119 | * @todo row_result types are not yet handled, what are they anyway? | ||
527 | 120 | * @todo Lot's of performance optimizations postponed for later version: | ||
528 | 121 | * * When available, use v8::Isolate instead of v8::Locker for multithreading | ||
529 | 122 | * (or a mix of both). | ||
530 | 123 | * * As part of this work, refactor v8 stuff into separate | ||
531 | 124 | * function, proxy, factory or something... | ||
532 | 125 | * * Save the compiled script so it can be used again if same script is run | ||
533 | 126 | * many times | ||
534 | 127 | * * Some of the v8 stuff should be done in initialize() | ||
535 | 128 | * | ||
536 | 129 | * @note DECIMAL_RESULT type is now a double in JavaScript. This could lose | ||
537 | 130 | * precision. But to send them as strings would also be awkward (+ operator will | ||
538 | 131 | * do unexpected things). In any case, we'd need some biginteger (bigdecimal?) | ||
539 | 132 | * kind of library to do anything with higher precision values anyway. If you | ||
540 | 133 | * want to keep the precision, you can cast your decimal values to strings | ||
541 | 134 | * explicitly when passing them as arguments. | ||
542 | 135 | * | ||
543 | 136 | * @param res Pointer to the drizzled::String object that will contain the result | ||
544 | 137 | * @return a drizzled::String containing the value returned by executed JavaScript code (value of last executed statement) | ||
545 | 138 | */ | ||
546 | 139 | String *JsFunction::val_str( String *str ) | ||
547 | 140 | { | ||
548 | 141 | assert( fixed == 1 ); | ||
549 | 142 | // If we return from any of the error conditions during method, then | ||
550 | 143 | // return value of the drizzle function is null. | ||
551 | 144 | null_value= true; | ||
552 | 145 | |||
553 | 146 | String *source_str=NULL; | ||
554 | 147 | source_str = args[0]->val_str( str ); | ||
555 | 148 | |||
556 | 149 | // Need to use Locker in multi-threaded app. v8 is unlocked by the destructor | ||
557 | 150 | // when locker goes out of scope. | ||
558 | 151 | // TODO: Newer versions of v8 provide an Isolate class where you can get a | ||
559 | 152 | // separate instance of v8 (ie one per thread). v8 2.5.9.9 in Ubuntu 11.04 does | ||
560 | 153 | // not yet offer it. | ||
561 | 154 | v8::Locker locker; | ||
562 | 155 | // Pass code and arguments into v8... | ||
563 | 156 | v8::HandleScope handle_scope; | ||
564 | 157 | // Create a template for the global object and populate a drizzle object. | ||
565 | 158 | v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(); | ||
566 | 159 | // Drizzle will contain API's to drizzle variables, functions and tables | ||
567 | 160 | v8::Handle<v8::ObjectTemplate> db = v8::ObjectTemplate::New(); | ||
568 | 161 | v8::Handle<v8::ObjectTemplate> js = v8::ObjectTemplate::New(); | ||
569 | 162 | // Bind the 'version' function | ||
570 | 163 | global->Set( v8::String::New("db"), db ); | ||
571 | 164 | db->Set( v8::String::New("js"), js ); | ||
572 | 165 | js->Set( v8::String::New("version"), v8::FunctionTemplate::New(V8Version) ); | ||
573 | 166 | js->Set( v8::String::New("engine"), v8::FunctionTemplate::New(JsEngine) ); | ||
574 | 167 | |||
575 | 168 | // Now bind the arguments into argv[] | ||
576 | 169 | // v8::Array can only be created when context is already entered (otherwise v8 segfaults!) | ||
577 | 170 | v8::Persistent<v8::Context> context = v8::Context::New( NULL, global ); | ||
578 | 171 | if ( context.IsEmpty() ) { | ||
579 | 172 | char buf[100]; | ||
580 | 173 | sprintf(buf, "Error in js() while creating JavaScript context in %s.", JS_ENGINE); | ||
581 | 174 | my_error(ER_SCRIPT, MYF(0), buf); | ||
582 | 175 | return NULL; | ||
583 | 176 | } | ||
584 | 177 | context->Enter(); | ||
585 | 178 | |||
586 | 179 | v8::Handle<v8::Array> a = v8::Array::New(arg_count-1); | ||
587 | 180 | for( uint64_t n = 1; n < arg_count; n++ ) | ||
588 | 181 | { | ||
589 | 182 | // Need to do this differently for ints, doubles and strings | ||
590 | 183 | // TODO: There is also ROW_RESULT. Is that relevant here? What does it look like? I could pass rows as an array or object. | ||
591 | 184 | if( args[n]->result_type() == INT_RESULT ){ | ||
592 | 185 | // TODO: Turns out Drizzle doesn't do unsigned. So this code path can never happen? (I can't test it at least...) | ||
593 | 186 | if( args[n]->is_unsigned() ) { | ||
594 | 187 | a->Set( n-1, v8::Integer::NewFromUnsigned( (uint32_t) args[n]->val_uint() ) ); | ||
595 | 188 | } else { | ||
596 | 189 | a->Set( n-1, v8::Integer::New((int32_t)args[n]->val_int() ) ); | ||
597 | 190 | } | ||
598 | 191 | } else if ( args[n]->result_type() == REAL_RESULT || args[n]->result_type() == DECIMAL_RESULT ) { | ||
599 | 192 | a->Set( n-1, v8::Number::New(args[n]->val_real() ) ); | ||
600 | 193 | } else if ( true || args[n]->result_type() == STRING_RESULT ) { | ||
601 | 194 | if ( args[n]->is_datetime() ) { | ||
602 | 195 | // DATE/TIME values are also STRING_RESULT, make them a Date type in v8 | ||
603 | 196 | // Now we need to get the unix timestamp integer, surprisingly tricky... | ||
604 | 197 | // TODO: This should really be just args[n]->get_epoch_seconds(). I need to write a separate patch for Item class one of these days. | ||
605 | 198 | type::Time ltime; | ||
606 | 199 | Timestamp temporal; | ||
607 | 200 | args[n]->get_date(ltime, 0); | ||
608 | 201 | temporal.set_years(ltime.year); | ||
609 | 202 | temporal.set_months(ltime.month); | ||
610 | 203 | temporal.set_days(ltime.day); | ||
611 | 204 | temporal.set_hours(ltime.hour); | ||
612 | 205 | temporal.set_minutes(ltime.minute); | ||
613 | 206 | temporal.set_seconds(ltime.second); | ||
614 | 207 | temporal.set_epoch_seconds(); | ||
615 | 208 | if (temporal.is_valid()) | ||
616 | 209 | { | ||
617 | 210 | time_t tmp; | ||
618 | 211 | temporal.to_time_t(tmp); | ||
619 | 212 | // Pay attention, Ecmascript defines a date as *milliseconds* since unix epoch | ||
620 | 213 | // Also, on platforms where time_t is 32 bit, we need explicit cast to 64 bit integer | ||
621 | 214 | a->Set( n-1, v8::Date::New(((uint64_t)tmp)*1000) ); | ||
622 | 215 | } else { | ||
623 | 216 | a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) ); | ||
624 | 217 | } | ||
625 | 218 | } else { | ||
626 | 219 | // Default to creating string values in JavaScript | ||
627 | 220 | a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) ); | ||
628 | 221 | } | ||
629 | 222 | } | ||
630 | 223 | // If user has given a name to the arguments, pass these as global variables | ||
631 | 224 | if( ! args[n]->is_autogenerated_name ) { | ||
632 | 225 | if( args[n]->result_type() == INT_RESULT ){ | ||
633 | 226 | if( args[n]->is_unsigned() ) { | ||
634 | 227 | context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::NewFromUnsigned( (uint32_t) args[n]->val_uint() ) ); | ||
635 | 228 | } else { | ||
636 | 229 | context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::New((int32_t)args[n]->val_int() ) ); | ||
637 | 230 | } | ||
638 | 231 | } else if ( args[n]->result_type() == REAL_RESULT || args[n]->result_type() == DECIMAL_RESULT ) { | ||
639 | 232 | context->Global()->Set( v8::String::New( args[n]->name ), v8::Number::New(args[n]->val_real() ) ); | ||
640 | 233 | } else if ( true || args[n]->result_type() == STRING_RESULT ) { | ||
641 | 234 | if ( args[n]->is_datetime() ) { | ||
642 | 235 | // DATE/TIME values are also STRING_RESULT, make them a Date type in v8 | ||
643 | 236 | // Now we need to get the unix timestamp integer, surprisingly tricky... | ||
644 | 237 | // TODO: This should really be just args[n]->get_epoch_seconds(). I need to write a separate patch for Item class one of these days. | ||
645 | 238 | type::Time ltime; | ||
646 | 239 | Timestamp temporal; | ||
647 | 240 | args[n]->get_date(ltime, 0); | ||
648 | 241 | temporal.set_years(ltime.year); | ||
649 | 242 | temporal.set_months(ltime.month); | ||
650 | 243 | temporal.set_days(ltime.day); | ||
651 | 244 | temporal.set_hours(ltime.hour); | ||
652 | 245 | temporal.set_minutes(ltime.minute); | ||
653 | 246 | temporal.set_seconds(ltime.second); | ||
654 | 247 | temporal.set_epoch_seconds(); | ||
655 | 248 | if (temporal.is_valid()) | ||
656 | 249 | { | ||
657 | 250 | time_t tmp; | ||
658 | 251 | temporal.to_time_t(tmp); | ||
659 | 252 | // Pay attention, Ecmascript defines a date as *milliseconds* since unix epoch | ||
660 | 253 | // Also, on platforms where time_t is 32 bit, we need explicit cast to 64 bit integer | ||
661 | 254 | context->Global()->Set( v8::String::New( args[n]->name ), v8::Date::New(((uint64_t)tmp)*1000) ); | ||
662 | 255 | } else { | ||
663 | 256 | context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) ); | ||
664 | 257 | } | ||
665 | 258 | } else { | ||
666 | 259 | context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) ); | ||
667 | 260 | } | ||
668 | 261 | } | ||
669 | 262 | } | ||
670 | 263 | } | ||
671 | 264 | //Need to fetch the global element back from context, global doesn't work anymore | ||
672 | 265 | context->Global()->Set( v8::String::New("arguments"), a ); | ||
673 | 266 | |||
674 | 267 | |||
675 | 268 | |||
676 | 269 | // Compile the source code. | ||
677 | 270 | v8::TryCatch try_catch; | ||
678 | 271 | v8::Handle<v8::Value> result; | ||
679 | 272 | // Create a v8 string containing the JavaScript source code. | ||
680 | 273 | // Convert from drizzled::String to char* string to v8::String. | ||
681 | 274 | v8::Handle<v8::String> source = v8::String::New(source_str->c_str()); | ||
682 | 275 | v8::Handle<v8::Script> script = v8::Script::Compile(source); | ||
683 | 276 | if ( script.IsEmpty() ) { | ||
684 | 277 | emit_drizzle_error(&try_catch); | ||
685 | 278 | return NULL; | ||
686 | 279 | } else { | ||
687 | 280 | result = script->Run(); | ||
688 | 281 | if ( result.IsEmpty() ) { | ||
689 | 282 | assert( try_catch.HasCaught() ); | ||
690 | 283 | emit_drizzle_error( &try_catch ); | ||
691 | 284 | // Dispose of Persistent objects before returning. (Is it needed?) | ||
692 | 285 | context->Exit(); | ||
693 | 286 | context.Dispose(); | ||
694 | 287 | return NULL; | ||
695 | 288 | } else { | ||
696 | 289 | assert( !try_catch.HasCaught() ); | ||
697 | 290 | if ( result->IsUndefined() ) { | ||
698 | 291 | // Nothing wrong here, but we return Undefined as NULL. | ||
699 | 292 | // Dispose of Persistent objects before returning. (Is it needed?) | ||
700 | 293 | context->Exit(); | ||
701 | 294 | context.Dispose(); | ||
702 | 295 | return NULL; | ||
703 | 296 | } | ||
704 | 297 | } | ||
705 | 298 | } | ||
706 | 299 | |||
707 | 300 | // Run the script to get the result. | ||
708 | 301 | //v8::Handle<v8::Value> foo = script->Run(); | ||
709 | 302 | v8::Handle<v8::String> rstring = result->ToString(); | ||
710 | 303 | |||
711 | 304 | // Convert the result to a drizzled::String and print it. | ||
712 | 305 | // Allocate space to the drizzled::String | ||
713 | 306 | str->free(); //TODO: Check the source for alloc(), but apparently I don't need this line? | ||
714 | 307 | str->alloc( rstring->Utf8Length() ); | ||
715 | 308 | // Now copy string from v8 heap to drizzled heap | ||
716 | 309 | rstring->WriteUtf8( str->ptr() ); | ||
717 | 310 | // drizzled::String doesn't actually set string length properly in alloc(), so set it now | ||
718 | 311 | str->length( rstring->Utf8Length() ); | ||
719 | 312 | |||
720 | 313 | context->Exit(); | ||
721 | 314 | context.Dispose(); | ||
722 | 315 | |||
723 | 316 | // There was no error and value returned is not undefined, so it's not null. | ||
724 | 317 | null_value= false; | ||
725 | 318 | return str; | ||
726 | 319 | } | ||
727 | 320 | |||
728 | 321 | |||
729 | 322 | |||
730 | 323 | |||
731 | 324 | plugin::Create_function<JsFunction> *js_function = NULL; | ||
732 | 325 | |||
733 | 326 | static int initialize( module::Context &context ) | ||
734 | 327 | { | ||
735 | 328 | js_function = new plugin::Create_function<JsFunction>("js"); | ||
736 | 329 | context.add( js_function ); | ||
737 | 330 | // Initialize V8 | ||
738 | 331 | v8::V8::Initialize(); | ||
739 | 332 | return 0; | ||
740 | 333 | } | ||
741 | 334 | |||
742 | 335 | |||
743 | 336 | /* Functions that are part of the JavaScript API ***************************/ | ||
744 | 337 | |||
745 | 338 | /** | ||
746 | 339 | * @brief Binds as db.js.version() inside JavaScript. | ||
747 | 340 | * @return Version number of v8 engine | ||
748 | 341 | */ | ||
749 | 342 | v8::Handle<v8::Value> V8Version( const v8::Arguments& ) { | ||
750 | 343 | return v8::String::New( v8::V8::GetVersion() ); | ||
751 | 344 | } | ||
752 | 345 | |||
753 | 346 | /** | ||
754 | 347 | * @brief Binds as db.js.engine() inside JavaScript. | ||
755 | 348 | * @return The string "v8" | ||
756 | 349 | */ | ||
757 | 350 | v8::Handle<v8::Value> JsEngine( const v8::Arguments& ) { | ||
758 | 351 | return v8::String::New( JS_ENGINE ); | ||
759 | 352 | } | ||
760 | 353 | |||
761 | 354 | } // namespace js | ||
762 | 355 | |||
763 | 356 | } // namespace drizzle_plugin | ||
764 | 357 | |||
765 | 358 | DRIZZLE_DECLARE_PLUGIN | ||
766 | 359 | { | ||
767 | 360 | DRIZZLE_VERSION_ID, | ||
768 | 361 | "js", | ||
769 | 362 | "0.9", | ||
770 | 363 | "Henrik Ingo", | ||
771 | 364 | "Execute JavaScript code with supplied arguments", | ||
772 | 365 | PLUGIN_LICENSE_GPL, | ||
773 | 366 | drizzle_plugin::js::initialize, /* Plugin Init */ | ||
774 | 367 | NULL, /* depends */ | ||
775 | 368 | NULL /* config options */ | ||
776 | 369 | } | ||
777 | 370 | DRIZZLE_DECLARE_PLUGIN_END; | ||
778 | 371 | |||
779 | 0 | \ No newline at end of file | 372 | \ No newline at end of file |
780 | 1 | 373 | ||
781 | === added file 'plugin/js/plugin.ini' | |||
782 | --- plugin/js/plugin.ini 1970-01-01 00:00:00 +0000 | |||
783 | +++ plugin/js/plugin.ini 2011-10-01 10:16:24 +0000 | |||
784 | @@ -0,0 +1,10 @@ | |||
785 | 1 | [plugin] | ||
786 | 2 | name=js | ||
787 | 3 | version=0.9 | ||
788 | 4 | author=Henrik Ingo | ||
789 | 5 | license=PLUGIN_LICENSE_GPL | ||
790 | 6 | title=Execute JavaScript Code | ||
791 | 7 | description=Execute JavaScript code with supplied parameters | ||
792 | 8 | load_by_default=yes | ||
793 | 9 | build_conditional="$ac_cv_libv8" = "yes" | ||
794 | 10 | ldflags=${LTLIBV8} | ||
795 | 0 | 11 | ||
796 | === added directory 'plugin/js/tests' | |||
797 | === added directory 'plugin/js/tests/r' | |||
798 | === added file 'plugin/js/tests/r/js.result' | |||
799 | --- plugin/js/tests/r/js.result 1970-01-01 00:00:00 +0000 | |||
800 | +++ plugin/js/tests/r/js.result 2011-10-01 10:16:24 +0000 | |||
801 | @@ -0,0 +1,35 @@ | |||
802 | 1 | SELECT JS("var foo = 'Hello'; foo + ', World';"); | ||
803 | 2 | JS("var foo = 'Hello'; foo + ', World';") | ||
804 | 3 | Hello, World | ||
805 | 4 | SELECT JS("var foo = 'Hello'; foo + ', ' + arguments[0];", "World"); | ||
806 | 5 | JS("var foo = 'Hello'; foo + ', ' + arguments[0];", "World") | ||
807 | 6 | Hello, World | ||
808 | 7 | SELECT JS("var foo = 'Hello'; foo + ', ' + bar;", "World" AS 'bar'); | ||
809 | 8 | JS("var foo = 'Hello'; foo + ', ' + bar;", "World" AS 'bar') | ||
810 | 9 | Hello, World | ||
811 | 10 | CREATE TABLE jstest (id INT PRIMARY KEY auto_increment, i INT, d DOUBLE, t TIMESTAMP, dt DATETIME); | ||
812 | 11 | INSERT INTO jstest VALUES (1, -5, 7.5, '2001-02-16 20:38:40', '2011-09-24 22:26:31'); | ||
813 | 12 | SELECT JS("arguments[0] + 1", i) FROM jstest WHERE id=1; | ||
814 | 13 | JS("arguments[0] + 1", i) | ||
815 | 14 | -4 | ||
816 | 15 | SELECT JS("arguments[0] + 1.1", d) FROM jstest WHERE id=1; | ||
817 | 16 | JS("arguments[0] + 1.1", d) | ||
818 | 17 | 8.6 | ||
819 | 18 | SELECT JS("var d = arguments[0]; d.getUTCFullYear() + ' - ' + d.getUTCHours() + ' - ' + + d.getUTCSeconds();", t) FROM jstest WHERE id=1; | ||
820 | 19 | JS("var d = arguments[0]; d.getUTCFullYear() + ' - ' + d.getUTCHours() + ' - ' + + d.getUTCSeconds();", t) | ||
821 | 20 | 2001 - 20 - 40 | ||
822 | 21 | SELECT JS("var d = arguments[0]; d.getUTCDate() + ' - ' + d.getUTCHours() + ' - ' + d.getUTCMinutes();", dt) FROM jstest WHERE id=1; | ||
823 | 22 | JS("var d = arguments[0]; d.getUTCDate() + ' - ' + d.getUTCHours() + ' - ' + d.getUTCMinutes();", dt) | ||
824 | 23 | 24 - 22 - 26 | ||
825 | 24 | SELECT JS("var num = arguments[0] + arguments[1]; arguments[2] + num;", i, d, "The sum is: ") FROM jstest WHERE id=1; | ||
826 | 25 | JS("var num = arguments[0] + arguments[1]; arguments[2] + num;", i, d, "The sum is: ") | ||
827 | 26 | The sum is: 2.5 | ||
828 | 27 | SELECT JS('var jsondoc = JSON.parse(arguments[0]); JSON.stringify(jsondoc["name"]["firstname"]);', '{ "name" : {"firstname" : "Henrik", "lastname" : "Ingo"} }'); | ||
829 | 28 | JS('var jsondoc = JSON.parse(arguments[0]); JSON.stringify(jsondoc["name"]["firstname"]);', '{ "name" : {"firstname" : "Henrik", "lastname" : "Ingo"} }') | ||
830 | 29 | "Henrik" | ||
831 | 30 | DROP TABLE jstest; | ||
832 | 31 | SELECT JS("this is not javascript"); | ||
833 | 32 | ERROR HY000: Script error: At line 1: SyntaxError: Unexpected identifier (Do SHOW ERRORS for more information.) | ||
834 | 33 | SELECT JS("var foo = 'Another'; foo + ' thread';"); | ||
835 | 34 | JS("var foo = 'Another'; foo + ' thread';") | ||
836 | 35 | Another thread | ||
837 | 0 | 36 | ||
838 | === added directory 'plugin/js/tests/t' | |||
839 | === added file 'plugin/js/tests/t/js.test' | |||
840 | --- plugin/js/tests/t/js.test 1970-01-01 00:00:00 +0000 | |||
841 | +++ plugin/js/tests/t/js.test 2011-10-01 10:16:24 +0000 | |||
842 | @@ -0,0 +1,35 @@ | |||
843 | 1 | # Basic Hello world test plust testing arguments with and without name | ||
844 | 2 | SELECT JS("var foo = 'Hello'; foo + ', World';"); | ||
845 | 3 | SELECT JS("var foo = 'Hello'; foo + ', ' + arguments[0];", "World"); | ||
846 | 4 | SELECT JS("var foo = 'Hello'; foo + ', ' + bar;", "World" AS 'bar'); | ||
847 | 5 | |||
848 | 6 | # Test all data types are passed correctly as arguments (string was handled above) | ||
849 | 7 | |||
850 | 8 | CREATE TABLE jstest (id INT PRIMARY KEY auto_increment, i INT, d DOUBLE, t TIMESTAMP, dt DATETIME); | ||
851 | 9 | |||
852 | 10 | INSERT INTO jstest VALUES (1, -5, 7.5, '2001-02-16 20:38:40', '2011-09-24 22:26:31'); | ||
853 | 11 | SELECT JS("arguments[0] + 1", i) FROM jstest WHERE id=1; | ||
854 | 12 | SELECT JS("arguments[0] + 1.1", d) FROM jstest WHERE id=1; | ||
855 | 13 | SELECT JS("var d = arguments[0]; d.getUTCFullYear() + ' - ' + d.getUTCHours() + ' - ' + + d.getUTCSeconds();", t) FROM jstest WHERE id=1; | ||
856 | 14 | SELECT JS("var d = arguments[0]; d.getUTCDate() + ' - ' + d.getUTCHours() + ' - ' + d.getUTCMinutes();", dt) FROM jstest WHERE id=1; | ||
857 | 15 | |||
858 | 16 | # Test combinations | ||
859 | 17 | SELECT JS("var num = arguments[0] + arguments[1]; arguments[2] + num;", i, d, "The sum is: ") FROM jstest WHERE id=1; | ||
860 | 18 | |||
861 | 19 | # And the JSON test, why all this was created in the first place | ||
862 | 20 | SELECT JS('var jsondoc = JSON.parse(arguments[0]); JSON.stringify(jsondoc["name"]["firstname"]);', '{ "name" : {"firstname" : "Henrik", "lastname" : "Ingo"} }'); | ||
863 | 21 | |||
864 | 22 | DROP TABLE jstest; | ||
865 | 23 | |||
866 | 24 | # Make deliberate error | ||
867 | 25 | --error ER_SCRIPT | ||
868 | 26 | SELECT JS("this is not javascript"); | ||
869 | 27 | |||
870 | 28 | # Why does this crash drizzletest? | ||
871 | 29 | # SHOW ERRORS; | ||
872 | 30 | |||
873 | 31 | # Make another connection and make sure we are handling multi-threaded mode correctly | ||
874 | 32 | # (V8 is single threaded only by default.) | ||
875 | 33 | connect (con2,localhost,test,jstest,mysql); | ||
876 | 34 | SELECT JS("var foo = 'Another'; foo + ' thread';"); | ||
877 | 35 | disconnect con2; |
Some style comments, I hope you don't mind.
206 +namespace drizzle_plugin {
207 +
208 +namespace js {
Redundant empty line
220 +class JsFunction :public Item_str_func
Missing space after ':'
221 +{
222 +public:
223 + JsFunction() :Item_str_func() {}
224 + ~JsFunction() {}
Empty constructors and destructors aren't necessary.
260 +void emit_drizzle_ error(v8: :TryCatch* try_catch) {
'{' should be on the next line
Greetings,
Olaf