~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to plugin/js/js.cc

  • Committer: Mark Atwood
  • Date: 2011-10-14 16:03:01 UTC
  • mfrom: (2370.2.18 drizzle-js)
  • Revision ID: me@mark.atwood.name-20111014160301-f1oljkg7yiv6906v
mergeĀ lp:~hingo/drizzle/drizzle-js_eval

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
 
2
 *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
 
3
 *
 
4
 *  Copyright (C) 2011, Henrik Ingo.
 
5
 *
 
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.
 
9
 *
 
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.
 
14
 *
 
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
 
18
 */
 
19
 
 
20
#include <config.h>
 
21
#include <stdio.h>
 
22
 
 
23
#include <drizzled/error.h>
 
24
#include <drizzled/plugin/function.h>
 
25
#include <drizzled/function/str/strfunc.h>
 
26
#include <drizzled/temporal.h>
 
27
 
 
28
#include <v8.h>
 
29
#define JS_ENGINE "v8"
 
30
 
 
31
using namespace std;
 
32
using namespace drizzled;
 
33
 
 
34
 
 
35
namespace drizzle_plugin {
 
36
namespace js {
 
37
 
 
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);
 
42
 
 
43
 
 
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.
 
47
 
 
48
class JsFunction : public Item_str_func
 
49
{
 
50
public:
 
51
  String *val_str(String *);
 
52
 
 
53
  const char *func_name() const 
 
54
  { 
 
55
    return "js"; 
 
56
  }
 
57
 
 
58
  void fix_length_and_dec() 
 
59
  { 
 
60
    maybe_null= 1;
 
61
    max_length= MAX_BLOB_WIDTH;   
 
62
  }
 
63
 
 
64
  bool check_argument_count(int n)
 
65
  {
 
66
    return (n >= 1);
 
67
  }
 
68
};
 
69
 
 
70
/**
 
71
 * @brief Extracts a C string from a V8 Utf8Value
 
72
 * 
 
73
 * Idea copied from v8 sources, samples/shell.cc. Makes code easier to read than
 
74
 * (char *)(*utf8value)
 
75
 */
 
76
const char* v8_to_char(const v8::String::Utf8Value& value) {
 
77
  return *value ? *value : "<javascript v8 string conversion failed>";
 
78
}
 
79
 
 
80
/**
 
81
 * @brief Take v8 exception and emit Drizzle error to client
 
82
 * 
 
83
 * This is adapted from ReportException() in v8 samples/shell.cc. 
 
84
 */
 
85
void emit_drizzle_error(v8::TryCatch* try_catch)
 
86
{
 
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);
 
95
  } else {
 
96
    char buf[2048]; 
 
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);
 
112
    }
 
113
  }
 
114
}
 
115
 
 
116
/**
 
117
 * @brief Implements js() - execute JavaScript code
 
118
 * 
 
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
 
126
 * many times
 
127
 *  * Some of the v8 stuff should be done in initialize()
 
128
 * 
 
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.
 
135
 * 
 
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) 
 
138
 */
 
139
String *JsFunction::val_str( String *str )
 
140
{
 
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.
 
144
  null_value= true; 
 
145
  
 
146
  String *source_str=NULL;
 
147
  source_str = args[0]->val_str( str ); 
 
148
  
 
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 
 
153
  // not yet offer it.
 
154
  v8::Locker locker;
 
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) );
 
167
  
 
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() ) {
 
172
    char buf[100];
 
173
    sprintf(buf, "Error in js() while creating JavaScript context in %s.", JS_ENGINE);
 
174
    my_error(ER_SCRIPT, MYF(0), buf);
 
175
    return NULL;
 
176
  }
 
177
  context->Enter();
 
178
   
 
179
  v8::Handle<v8::Array> a = v8::Array::New(arg_count-1);
 
180
  for( uint64_t n = 1; n < arg_count; n++ )
 
181
  {
 
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() ) );
 
188
      } else {
 
189
        a->Set( n-1, v8::Integer::New((int32_t)args[n]->val_int() ) );
 
190
      }
 
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.
 
198
        type::Time ltime;
 
199
        Timestamp temporal;
 
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())
 
209
        {
 
210
          time_t tmp;
 
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) );
 
215
        } else {
 
216
          a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) );
 
217
        }
 
218
      } else {
 
219
        // Default to creating string values in JavaScript
 
220
        a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) );
 
221
      }
 
222
    }
 
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() ) );
 
228
        } else {
 
229
          context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::New((int32_t)args[n]->val_int() ) );
 
230
        }
 
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.
 
238
        type::Time ltime;
 
239
        Timestamp temporal;
 
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())
 
249
        {
 
250
          time_t tmp;
 
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) );
 
255
        } else {
 
256
          context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) );
 
257
        }
 
258
      } else {
 
259
        context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) );
 
260
      }
 
261
      }
 
262
    }
 
263
  }
 
264
  //Need to fetch the global element back from context, global doesn't work anymore
 
265
  context->Global()->Set( v8::String::New("arguments"), a );
 
266
 
 
267
  
 
268
  
 
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);
 
278
    return NULL;
 
279
  } else {
 
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?)
 
285
      context->Exit();
 
286
      context.Dispose();
 
287
      return NULL;
 
288
    } else {
 
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?)
 
293
        context->Exit();
 
294
        context.Dispose();
 
295
        return NULL;
 
296
      }
 
297
    }
 
298
  }
 
299
    
 
300
  // Run the script to get the result.
 
301
  //v8::Handle<v8::Value> foo = script->Run();
 
302
  v8::Handle<v8::String> rstring = result->ToString();
 
303
  
 
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() );
 
312
 
 
313
  context->Exit();
 
314
  context.Dispose();
 
315
 
 
316
  // There was no error and value returned is not undefined, so it's not null.
 
317
  null_value= false;
 
318
  return str;
 
319
}
 
320
 
 
321
 
 
322
 
 
323
 
 
324
plugin::Create_function<JsFunction> *js_function = NULL;
 
325
 
 
326
static int initialize( module::Context &context )
 
327
{
 
328
  js_function = new plugin::Create_function<JsFunction>("js");
 
329
  context.add( js_function );
 
330
  // Initialize V8
 
331
  v8::V8::Initialize();
 
332
  return 0;
 
333
}
 
334
 
 
335
 
 
336
/* Functions that are part of the JavaScript API ***************************/
 
337
 
 
338
/**
 
339
 * @brief Binds as db.js.version() inside JavaScript.
 
340
 * @return Version number of v8 engine
 
341
 */
 
342
v8::Handle<v8::Value> V8Version( const v8::Arguments& ) {
 
343
  return v8::String::New( v8::V8::GetVersion() );
 
344
}
 
345
 
 
346
/**
 
347
 * @brief Binds as db.js.engine() inside JavaScript.
 
348
 * @return The string "v8"
 
349
 */
 
350
v8::Handle<v8::Value> JsEngine( const v8::Arguments& ) {
 
351
  return v8::String::New( JS_ENGINE );
 
352
}
 
353
 
 
354
} // namespace js
 
355
 
 
356
} // namespace drizzle_plugin
 
357
 
 
358
DRIZZLE_DECLARE_PLUGIN
 
359
{
 
360
  DRIZZLE_VERSION_ID,
 
361
  "js",
 
362
  "0.9",
 
363
  "Henrik Ingo",
 
364
  "Execute JavaScript code with supplied arguments",
 
365
  PLUGIN_LICENSE_GPL,
 
366
  drizzle_plugin::js::initialize, /* Plugin Init */
 
367
  NULL,   /* depends */              
 
368
  NULL    /* config options */
 
369
}
 
370
DRIZZLE_DECLARE_PLUGIN_END;
 
371
       
 
 
b'\\ No newline at end of file'