Drizzled Public API Documentation

js.cc
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 
76 const char* v8_to_char(const v8::String::Utf8Value& value) {
77  return *value ? *value : "<javascript v8 string conversion failed>";
78 }
79 
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 
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 
342 v8::Handle<v8::Value> V8Version( const v8::Arguments& ) {
343  return v8::String::New( v8::V8::GetVersion() );
344 }
345 
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  N_("Executes JavaScript code with supplied arguments"),
365  PLUGIN_LICENSE_GPL,
366  drizzle_plugin::js::initialize,
367  NULL,
368  NULL
369 }
370 DRIZZLE_DECLARE_PLUGIN_END;
371