1
/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2
* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
4
* Copyright (C) 2011, Henrik Ingo.
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; version 2 of the License.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
#include <drizzled/error.h>
24
#include <drizzled/plugin/function.h>
25
#include <drizzled/function/str/strfunc.h>
26
#include <drizzled/temporal.h>
29
#define JS_ENGINE "v8"
32
using namespace drizzled;
35
namespace drizzle_plugin {
38
v8::Handle<v8::Value> V8Version(const v8::Arguments& args);
39
v8::Handle<v8::Value> JsEngine(const v8::Arguments& args);
40
const char* v8_to_char(const v8::String::Utf8Value& value);
41
void emit_drizzle_error(v8::TryCatch* try_catch);
44
// TODO: So this is a function that returns strings?
45
// What is the class for functions that return mixed types?
46
// Or is this as it should be, apparently js('1') + js('2') does the right thing already.
48
class JsFunction : public Item_str_func
51
String *val_str(String *);
53
const char *func_name() const
58
void fix_length_and_dec()
61
max_length= MAX_BLOB_WIDTH;
64
bool check_argument_count(int n)
71
* @brief Extracts a C string from a V8 Utf8Value
73
* Idea copied from v8 sources, samples/shell.cc. Makes code easier to read than
74
* (char *)(*utf8value)
76
const char* v8_to_char(const v8::String::Utf8Value& value) {
77
return *value ? *value : "<javascript v8 string conversion failed>";
81
* @brief Take v8 exception and emit Drizzle error to client
83
* This is adapted from ReportException() in v8 samples/shell.cc.
85
void emit_drizzle_error(v8::TryCatch* try_catch)
87
v8::HandleScope handle_scope;
88
v8::String::Utf8Value exception(try_catch->Exception());
89
const char* exception_string = v8_to_char(exception);
90
v8::Handle<v8::Message> message = try_catch->Message();
91
if (message.IsEmpty()) {
92
// V8 didn't provide any extra information about this error; just
93
// print the exception.
94
my_error(ER_SCRIPT, MYF(0), exception_string);
97
int linenum = message->GetLineNumber();
98
sprintf(buf, "At line %i: %.1900s (Do SHOW ERRORS for more information.)", linenum, exception_string);
99
my_error(ER_SCRIPT, MYF(0), buf);
100
// Print line of source code and where error happened.
101
v8::String::Utf8Value sourceline(message->GetSourceLine());
102
const char* sourceline_string = v8_to_char(sourceline);
103
sprintf(buf, "Line %i: %.160s", linenum, sourceline_string);
104
my_error(ER_SCRIPT, MYF(0), buf);
105
int start = message->GetStartColumn();
106
sprintf(buf, "Check your script starting at: '%.50s'", &sourceline_string[start]);
107
my_error(ER_SCRIPT, MYF(0), buf);
108
v8::String::Utf8Value stack_trace(try_catch->StackTrace());
109
if (stack_trace.length() > 0) {
110
const char* stack_trace_string = v8_to_char(stack_trace);
111
my_error(ER_SCRIPT, MYF(0), stack_trace_string);
117
* @brief Implements js() - execute JavaScript code
119
* @todo row_result types are not yet handled, what are they anyway?
120
* @todo Lot's of performance optimizations postponed for later version:
121
* * When available, use v8::Isolate instead of v8::Locker for multithreading
122
* (or a mix of both).
123
* * As part of this work, refactor v8 stuff into separate
124
* function, proxy, factory or something...
125
* * Save the compiled script so it can be used again if same script is run
127
* * Some of the v8 stuff should be done in initialize()
129
* @note DECIMAL_RESULT type is now a double in JavaScript. This could lose
130
* precision. But to send them as strings would also be awkward (+ operator will
131
* do unexpected things). In any case, we'd need some biginteger (bigdecimal?)
132
* kind of library to do anything with higher precision values anyway. If you
133
* want to keep the precision, you can cast your decimal values to strings
134
* explicitly when passing them as arguments.
136
* @param res Pointer to the drizzled::String object that will contain the result
137
* @return a drizzled::String containing the value returned by executed JavaScript code (value of last executed statement)
139
String *JsFunction::val_str( String *str )
141
assert( fixed == 1 );
142
// If we return from any of the error conditions during method, then
143
// return value of the drizzle function is null.
146
String *source_str=NULL;
147
source_str = args[0]->val_str( str );
149
// Need to use Locker in multi-threaded app. v8 is unlocked by the destructor
150
// when locker goes out of scope.
151
// TODO: Newer versions of v8 provide an Isolate class where you can get a
152
// separate instance of v8 (ie one per thread). v8 2.5.9.9 in Ubuntu 11.04 does
155
// Pass code and arguments into v8...
156
v8::HandleScope handle_scope;
157
// Create a template for the global object and populate a drizzle object.
158
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
159
// Drizzle will contain API's to drizzle variables, functions and tables
160
v8::Handle<v8::ObjectTemplate> db = v8::ObjectTemplate::New();
161
v8::Handle<v8::ObjectTemplate> js = v8::ObjectTemplate::New();
162
// Bind the 'version' function
163
global->Set( v8::String::New("db"), db );
164
db->Set( v8::String::New("js"), js );
165
js->Set( v8::String::New("version"), v8::FunctionTemplate::New(V8Version) );
166
js->Set( v8::String::New("engine"), v8::FunctionTemplate::New(JsEngine) );
168
// Now bind the arguments into argv[]
169
// v8::Array can only be created when context is already entered (otherwise v8 segfaults!)
170
v8::Persistent<v8::Context> context = v8::Context::New( NULL, global );
171
if ( context.IsEmpty() ) {
173
sprintf(buf, "Error in js() while creating JavaScript context in %s.", JS_ENGINE);
174
my_error(ER_SCRIPT, MYF(0), buf);
179
v8::Handle<v8::Array> a = v8::Array::New(arg_count-1);
180
for( uint64_t n = 1; n < arg_count; n++ )
182
// Need to do this differently for ints, doubles and strings
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.
184
if( args[n]->result_type() == INT_RESULT ){
185
// TODO: Turns out Drizzle doesn't do unsigned. So this code path can never happen? (I can't test it at least...)
186
if( args[n]->is_unsigned() ) {
187
a->Set( n-1, v8::Integer::NewFromUnsigned( (uint32_t) args[n]->val_uint() ) );
189
a->Set( n-1, v8::Integer::New((int32_t)args[n]->val_int() ) );
191
} else if ( args[n]->result_type() == REAL_RESULT || args[n]->result_type() == DECIMAL_RESULT ) {
192
a->Set( n-1, v8::Number::New(args[n]->val_real() ) );
193
} else if ( true || args[n]->result_type() == STRING_RESULT ) {
194
if ( args[n]->is_datetime() ) {
195
// DATE/TIME values are also STRING_RESULT, make them a Date type in v8
196
// Now we need to get the unix timestamp integer, surprisingly tricky...
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.
200
args[n]->get_date(ltime, 0);
201
temporal.set_years(ltime.year);
202
temporal.set_months(ltime.month);
203
temporal.set_days(ltime.day);
204
temporal.set_hours(ltime.hour);
205
temporal.set_minutes(ltime.minute);
206
temporal.set_seconds(ltime.second);
207
temporal.set_epoch_seconds();
208
if (temporal.is_valid())
211
temporal.to_time_t(tmp);
212
// Pay attention, Ecmascript defines a date as *milliseconds* since unix epoch
213
// Also, on platforms where time_t is 32 bit, we need explicit cast to 64 bit integer
214
a->Set( n-1, v8::Date::New(((uint64_t)tmp)*1000) );
216
a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) );
219
// Default to creating string values in JavaScript
220
a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) );
223
// If user has given a name to the arguments, pass these as global variables
224
if( ! args[n]->is_autogenerated_name ) {
225
if( args[n]->result_type() == INT_RESULT ){
226
if( args[n]->is_unsigned() ) {
227
context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::NewFromUnsigned( (uint32_t) args[n]->val_uint() ) );
229
context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::New((int32_t)args[n]->val_int() ) );
231
} else if ( args[n]->result_type() == REAL_RESULT || args[n]->result_type() == DECIMAL_RESULT ) {
232
context->Global()->Set( v8::String::New( args[n]->name ), v8::Number::New(args[n]->val_real() ) );
233
} else if ( true || args[n]->result_type() == STRING_RESULT ) {
234
if ( args[n]->is_datetime() ) {
235
// DATE/TIME values are also STRING_RESULT, make them a Date type in v8
236
// Now we need to get the unix timestamp integer, surprisingly tricky...
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.
240
args[n]->get_date(ltime, 0);
241
temporal.set_years(ltime.year);
242
temporal.set_months(ltime.month);
243
temporal.set_days(ltime.day);
244
temporal.set_hours(ltime.hour);
245
temporal.set_minutes(ltime.minute);
246
temporal.set_seconds(ltime.second);
247
temporal.set_epoch_seconds();
248
if (temporal.is_valid())
251
temporal.to_time_t(tmp);
252
// Pay attention, Ecmascript defines a date as *milliseconds* since unix epoch
253
// Also, on platforms where time_t is 32 bit, we need explicit cast to 64 bit integer
254
context->Global()->Set( v8::String::New( args[n]->name ), v8::Date::New(((uint64_t)tmp)*1000) );
256
context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) );
259
context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) );
264
//Need to fetch the global element back from context, global doesn't work anymore
265
context->Global()->Set( v8::String::New("arguments"), a );
269
// Compile the source code.
270
v8::TryCatch try_catch;
271
v8::Handle<v8::Value> result;
272
// Create a v8 string containing the JavaScript source code.
273
// Convert from drizzled::String to char* string to v8::String.
274
v8::Handle<v8::String> source = v8::String::New(source_str->c_str());
275
v8::Handle<v8::Script> script = v8::Script::Compile(source);
276
if ( script.IsEmpty() ) {
277
emit_drizzle_error(&try_catch);
280
result = script->Run();
281
if ( result.IsEmpty() ) {
282
assert( try_catch.HasCaught() );
283
emit_drizzle_error( &try_catch );
284
// Dispose of Persistent objects before returning. (Is it needed?)
289
assert( !try_catch.HasCaught() );
290
if ( result->IsUndefined() ) {
291
// Nothing wrong here, but we return Undefined as NULL.
292
// Dispose of Persistent objects before returning. (Is it needed?)
300
// Run the script to get the result.
301
//v8::Handle<v8::Value> foo = script->Run();
302
v8::Handle<v8::String> rstring = result->ToString();
304
// Convert the result to a drizzled::String and print it.
305
// Allocate space to the drizzled::String
306
str->free(); //TODO: Check the source for alloc(), but apparently I don't need this line?
307
str->alloc( rstring->Utf8Length() );
308
// Now copy string from v8 heap to drizzled heap
309
rstring->WriteUtf8( str->ptr() );
310
// drizzled::String doesn't actually set string length properly in alloc(), so set it now
311
str->length( rstring->Utf8Length() );
316
// There was no error and value returned is not undefined, so it's not null.
324
plugin::Create_function<JsFunction> *js_function = NULL;
326
static int initialize( module::Context &context )
328
js_function = new plugin::Create_function<JsFunction>("js");
329
context.add( js_function );
331
v8::V8::Initialize();
336
/* Functions that are part of the JavaScript API ***************************/
339
* @brief Binds as db.js.version() inside JavaScript.
340
* @return Version number of v8 engine
342
v8::Handle<v8::Value> V8Version( const v8::Arguments& ) {
343
return v8::String::New( v8::V8::GetVersion() );
347
* @brief Binds as db.js.engine() inside JavaScript.
348
* @return The string "v8"
350
v8::Handle<v8::Value> JsEngine( const v8::Arguments& ) {
351
return v8::String::New( JS_ENGINE );
356
} // namespace drizzle_plugin
358
DRIZZLE_DECLARE_PLUGIN
364
"Execute JavaScript code with supplied arguments",
366
drizzle_plugin::js::initialize, /* Plugin Init */
368
NULL /* config options */
370
DRIZZLE_DECLARE_PLUGIN_END;
b'\\ No newline at end of file'