~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to plugin/js/js.cc

  • Committer: Mark Atwood
  • Date: 2011-10-08 04:50:51 UTC
  • mfrom: (2430.1.1 rf)
  • Revision ID: me@mark.atwood.name-20111008045051-6ha1qiy7k2a9c3jv
Tags: 2011.10.27
mergeĀ lp:~olafvdspek/drizzle/refactor2

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'