Drizzled Public API Documentation

js.cc
00001 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright (C) 2011, Henrik Ingo.
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; version 2 of the License.
00009  *
00010  *  This program is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  *  GNU General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU General Public License
00016  *  along with this program; if not, write to the Free Software
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00018  */
00019 
00020 #include <config.h>
00021 #include <stdio.h>
00022 
00023 #include <drizzled/error.h>
00024 #include <drizzled/plugin/function.h>
00025 #include <drizzled/function/str/strfunc.h>
00026 #include <drizzled/temporal.h>
00027 
00028 #include <v8.h>
00029 #define JS_ENGINE "v8"
00030 
00031 using namespace std;
00032 using namespace drizzled;
00033 
00034 
00035 namespace drizzle_plugin {
00036 namespace js {
00037 
00038 v8::Handle<v8::Value> V8Version(const v8::Arguments& args);
00039 v8::Handle<v8::Value> JsEngine(const v8::Arguments& args);
00040 const char* v8_to_char(const v8::String::Utf8Value& value);
00041 void emit_drizzle_error(v8::TryCatch* try_catch);
00042 
00043 
00044 // TODO: So this is a function that returns strings? 
00045 // What is the class for functions that return mixed types?
00046 // Or is this as it should be, apparently js('1') + js('2') does the right thing already.
00047 
00048 class JsFunction : public Item_str_func
00049 {
00050 public:
00051   String *val_str(String *);
00052 
00053   const char *func_name() const 
00054   { 
00055     return "js"; 
00056   }
00057 
00058   void fix_length_and_dec() 
00059   { 
00060     maybe_null= 1;
00061     max_length= MAX_BLOB_WIDTH;   
00062   }
00063 
00064   bool check_argument_count(int n)
00065   {
00066     return (n >= 1);
00067   }
00068 };
00069 
00076 const char* v8_to_char(const v8::String::Utf8Value& value) {
00077   return *value ? *value : "<javascript v8 string conversion failed>";
00078 }
00079 
00085 void emit_drizzle_error(v8::TryCatch* try_catch)
00086 {
00087   v8::HandleScope handle_scope;
00088   v8::String::Utf8Value exception(try_catch->Exception());
00089   const char* exception_string = v8_to_char(exception);
00090   v8::Handle<v8::Message> message = try_catch->Message();
00091   if (message.IsEmpty()) {
00092     // V8 didn't provide any extra information about this error; just
00093     // print the exception.
00094     my_error(ER_SCRIPT, MYF(0), exception_string);
00095   } else {
00096     char buf[2048]; 
00097     int linenum = message->GetLineNumber();
00098     sprintf(buf, "At line %i: %.1900s (Do SHOW ERRORS for more information.)", linenum, exception_string);
00099     my_error(ER_SCRIPT, MYF(0), buf);
00100     // Print line of source code and where error happened.
00101     v8::String::Utf8Value sourceline(message->GetSourceLine());
00102     const char* sourceline_string = v8_to_char(sourceline);
00103     sprintf(buf, "Line %i: %.160s", linenum, sourceline_string);
00104     my_error(ER_SCRIPT, MYF(0), buf);    
00105     int start = message->GetStartColumn();
00106     sprintf(buf, "Check your script starting at: '%.50s'", &sourceline_string[start]);
00107     my_error(ER_SCRIPT, MYF(0), buf);
00108     v8::String::Utf8Value stack_trace(try_catch->StackTrace());
00109     if (stack_trace.length() > 0) {
00110       const char* stack_trace_string = v8_to_char(stack_trace);
00111       my_error(ER_SCRIPT, MYF(0), stack_trace_string);
00112     }
00113   }
00114 }
00115 
00139 String *JsFunction::val_str( String *str )
00140 {
00141   assert( fixed == 1 );
00142   // If we return from any of the error conditions during method, then 
00143   // return value of the drizzle function is null.
00144   null_value= true; 
00145   
00146   String *source_str=NULL;
00147   source_str = args[0]->val_str( str ); 
00148   
00149   // Need to use Locker in multi-threaded app. v8 is unlocked by the destructor 
00150   // when locker goes out of scope.
00151   // TODO: Newer versions of v8 provide an Isolate class where you can get a 
00152   // separate instance of v8 (ie one per thread). v8 2.5.9.9 in Ubuntu 11.04 does 
00153   // not yet offer it.
00154   v8::Locker locker;
00155   // Pass code and arguments into v8...
00156   v8::HandleScope handle_scope;
00157   // Create a template for the global object and populate a drizzle object.
00158   v8::Handle<v8::ObjectTemplate> global  = v8::ObjectTemplate::New();
00159   // Drizzle will contain API's to drizzle variables, functions and tables
00160   v8::Handle<v8::ObjectTemplate> db = v8::ObjectTemplate::New();
00161   v8::Handle<v8::ObjectTemplate> js = v8::ObjectTemplate::New();
00162   // Bind the 'version' function 
00163   global->Set( v8::String::New("db"), db );
00164   db->Set( v8::String::New("js"), js );
00165   js->Set( v8::String::New("version"), v8::FunctionTemplate::New(V8Version) );
00166   js->Set( v8::String::New("engine"), v8::FunctionTemplate::New(JsEngine) );
00167   
00168   // Now bind the arguments into argv[]
00169   // v8::Array can only be created when context is already entered (otherwise v8 segfaults!)
00170   v8::Persistent<v8::Context> context = v8::Context::New( NULL, global );
00171   if ( context.IsEmpty() ) {
00172     char buf[100];
00173     sprintf(buf, "Error in js() while creating JavaScript context in %s.", JS_ENGINE);
00174     my_error(ER_SCRIPT, MYF(0), buf);
00175     return NULL;
00176   }
00177   context->Enter();
00178    
00179   v8::Handle<v8::Array> a = v8::Array::New(arg_count-1);
00180   for( uint64_t n = 1; n < arg_count; n++ )
00181   {
00182     // Need to do this differently for ints, doubles and strings
00183     // TODO: There is also ROW_RESULT. Is that relevant here? What does it look like? I could pass rows as an array or object.
00184     if( args[n]->result_type() == INT_RESULT ){
00185       // TODO: Turns out Drizzle doesn't do unsigned. So this code path can never happen? (I can't test it at least...)
00186       if( args[n]->is_unsigned() ) {
00187         a->Set( n-1, v8::Integer::NewFromUnsigned( (uint32_t) args[n]->val_uint() ) );
00188       } else {
00189         a->Set( n-1, v8::Integer::New((int32_t)args[n]->val_int() ) );
00190       }
00191     } else if ( args[n]->result_type() == REAL_RESULT || args[n]->result_type() == DECIMAL_RESULT ) {
00192       a->Set( n-1, v8::Number::New(args[n]->val_real() ) );
00193     } else if ( true || args[n]->result_type() == STRING_RESULT ) {
00194       if ( args[n]->is_datetime() ) {
00195         // DATE/TIME values are also STRING_RESULT, make them a Date type in v8
00196         // Now we need to get the unix timestamp integer, surprisingly tricky...
00197         // 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.
00198         type::Time ltime;
00199         Timestamp temporal;
00200         args[n]->get_date(ltime, 0);
00201         temporal.set_years(ltime.year);
00202         temporal.set_months(ltime.month);
00203         temporal.set_days(ltime.day);
00204         temporal.set_hours(ltime.hour);
00205         temporal.set_minutes(ltime.minute);
00206         temporal.set_seconds(ltime.second);
00207         temporal.set_epoch_seconds();
00208         if (temporal.is_valid())
00209         {
00210           time_t tmp;
00211           temporal.to_time_t(tmp);
00212           // Pay attention, Ecmascript defines a date as *milliseconds* since unix epoch
00213           // Also, on platforms where time_t is 32 bit, we need explicit cast to 64 bit integer
00214           a->Set( n-1, v8::Date::New(((uint64_t)tmp)*1000) );
00215         } else {
00216           a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) );
00217         }
00218       } else {
00219         // Default to creating string values in JavaScript
00220         a->Set( n-1, v8::String::New(args[n]->val_str(str)->c_str() ) );
00221       }
00222     }
00223     // If user has given a name to the arguments, pass these as global variables
00224     if( ! args[n]->is_autogenerated_name ) {
00225       if( args[n]->result_type() == INT_RESULT ){
00226         if( args[n]->is_unsigned() ) {
00227           context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::NewFromUnsigned( (uint32_t) args[n]->val_uint() ) );
00228         } else {
00229           context->Global()->Set( v8::String::New( args[n]->name ), v8::Integer::New((int32_t)args[n]->val_int() ) );
00230         }
00231       } else if ( args[n]->result_type() == REAL_RESULT || args[n]->result_type() == DECIMAL_RESULT ) {
00232         context->Global()->Set( v8::String::New( args[n]->name ), v8::Number::New(args[n]->val_real() ) );
00233       } else if ( true || args[n]->result_type() == STRING_RESULT ) {
00234       if ( args[n]->is_datetime() ) {
00235         // DATE/TIME values are also STRING_RESULT, make them a Date type in v8
00236         // Now we need to get the unix timestamp integer, surprisingly tricky...
00237         // 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.
00238         type::Time ltime;
00239         Timestamp temporal;
00240         args[n]->get_date(ltime, 0);
00241         temporal.set_years(ltime.year);
00242         temporal.set_months(ltime.month);
00243         temporal.set_days(ltime.day);
00244         temporal.set_hours(ltime.hour);
00245         temporal.set_minutes(ltime.minute);
00246         temporal.set_seconds(ltime.second);
00247         temporal.set_epoch_seconds();
00248         if (temporal.is_valid())
00249         {
00250           time_t tmp;
00251           temporal.to_time_t(tmp);
00252           // Pay attention, Ecmascript defines a date as *milliseconds* since unix epoch
00253           // Also, on platforms where time_t is 32 bit, we need explicit cast to 64 bit integer
00254           context->Global()->Set( v8::String::New( args[n]->name ), v8::Date::New(((uint64_t)tmp)*1000) );
00255         } else {
00256           context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) );
00257         }
00258       } else {
00259         context->Global()->Set( v8::String::New( args[n]->name ), v8::String::New(args[n]->val_str(str)->c_str() ) );
00260       }
00261       }
00262     }
00263   }
00264   //Need to fetch the global element back from context, global doesn't work anymore
00265   context->Global()->Set( v8::String::New("arguments"), a );
00266 
00267   
00268   
00269   // Compile the source code.
00270   v8::TryCatch try_catch;
00271   v8::Handle<v8::Value> result;
00272   // Create a v8 string containing the JavaScript source code.
00273   // Convert from drizzled::String to char* string to v8::String.
00274   v8::Handle<v8::String> source = v8::String::New(source_str->c_str());
00275   v8::Handle<v8::Script> script = v8::Script::Compile(source);
00276   if ( script.IsEmpty() ) {
00277     emit_drizzle_error(&try_catch);
00278     return NULL;
00279   } else {
00280     result = script->Run();
00281     if ( result.IsEmpty() ) {
00282       assert( try_catch.HasCaught() );
00283       emit_drizzle_error( &try_catch );
00284       // Dispose of Persistent objects before returning. (Is it needed?)
00285       context->Exit();
00286       context.Dispose();
00287       return NULL;
00288     } else {
00289       assert( !try_catch.HasCaught() );
00290       if ( result->IsUndefined() ) {
00291         // Nothing wrong here, but we return Undefined as NULL.
00292         // Dispose of Persistent objects before returning. (Is it needed?)
00293         context->Exit();
00294         context.Dispose();
00295         return NULL;
00296       }
00297     }
00298   }
00299     
00300   // Run the script to get the result.
00301   //v8::Handle<v8::Value> foo = script->Run();
00302   v8::Handle<v8::String> rstring = result->ToString();
00303   
00304   // Convert the result to a drizzled::String and print it.
00305   // Allocate space to the drizzled::String 
00306   str->free(); //TODO: Check the source for alloc(), but apparently I don't need this line?
00307   str->alloc( rstring->Utf8Length() );
00308   // Now copy string from v8 heap to drizzled heap
00309   rstring->WriteUtf8( str->ptr() );
00310   // drizzled::String doesn't actually set string length properly in alloc(), so set it now
00311   str->length( rstring->Utf8Length() );
00312  
00313   context->Exit();
00314   context.Dispose();
00315 
00316   // There was no error and value returned is not undefined, so it's not null.
00317   null_value= false;
00318   return str;
00319 }
00320 
00321 
00322 
00323 
00324 plugin::Create_function<JsFunction> *js_function = NULL;
00325 
00326 static int initialize( module::Context &context )
00327 {
00328   js_function = new plugin::Create_function<JsFunction>("js");
00329   context.add( js_function );
00330   // Initialize V8
00331   v8::V8::Initialize();
00332   return 0;
00333 }
00334 
00335 
00336 /* Functions that are part of the JavaScript API ***************************/
00337 
00342 v8::Handle<v8::Value> V8Version( const v8::Arguments& ) {
00343   return v8::String::New( v8::V8::GetVersion() );
00344 }
00345 
00350 v8::Handle<v8::Value> JsEngine( const v8::Arguments& ) {
00351   return v8::String::New( JS_ENGINE );
00352 }
00353 
00354 } // namespace js
00355 
00356 } // namespace drizzle_plugin
00357 
00358 DRIZZLE_DECLARE_PLUGIN
00359 {
00360   DRIZZLE_VERSION_ID,
00361   "js",
00362   "0.9",
00363   "Henrik Ingo",
00364   "Execute JavaScript code with supplied arguments",
00365   PLUGIN_LICENSE_GPL,
00366   drizzle_plugin::js::initialize, /* Plugin Init */
00367   NULL,   /* depends */              
00368   NULL    /* config options */
00369 }
00370 DRIZZLE_DECLARE_PLUGIN_END;
00371