// ***************************************************************************
// Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
// ***************************************************************************
#include "nodever_cover.h"
#include "sqlany_utils.h"
#include "nan.h"
#if !v010
using namespace v8;
using namespace node;
void getErrorMsg( int code, std::string &str )
/********************************************/
{
std::ostringstream message;
message ThrowException(
Exception::Error( String::NewFromUtf8( isolate, str->c_str() ) ) );
}
}
}
static bool getWideBindParameters( std::vector &execData,
Handle arg,
std::vector & params,
unsigned &num_rows )
/*********************************************************************************/
{
Handle rows = Handle::Cast( arg );
num_rows = rows->Length();
Handle row0 = Handle::Cast( rows->Get(0) );
unsigned num_cols = row0->Length();
unsigned c;
if( num_cols == 0 ) {
// if an empty array was passed in, we still need ExecuteData
ExecuteData *ex = new ExecuteData;
execData.push_back( ex );
return true;
}
// Make sure that each array in the list has the same number and types
// of values
for( unsigned int r = 1; r < num_rows; r++ ) {
Handle row = Handle::Cast( rows->Get(r) );
for( c = 0; c < num_cols; c++ ) {
Handle val0 = row0->Get(c);
Handle val = row->Get(c);
if( ( val0->IsInt32() || val0->IsNumber() ) &&
( !val->IsInt32() && !val->IsNumber() && !val->IsNull() ) ) {
return false;
}
if( val0->IsString() &&
!val->IsString() && !val->IsNull() ) {
return false;
}
if( Buffer::HasInstance( val0 ) &&
!Buffer::HasInstance( val ) && !val->IsNull() ) {
return false;
}
}
}
for( c = 0; c < num_cols; c++ ) {
a_sqlany_bind_param param;
memset( ¶m, 0, sizeof( param ) );
ExecuteData *ex = new ExecuteData;
execData.push_back( ex );
double * param_double = new double[num_rows];
ex->addNum( param_double );
char ** char_arr = new char *[num_rows];
size_t * len = new size_t[num_rows];
ex->addStrings( char_arr, len );
sacapi_bool * is_null = new sacapi_bool[num_rows];
ex->addNull( is_null );
param.value.is_null = is_null;
param.value.is_address = false;
if( row0->Get(c)->IsInt32() || row0->Get(c)->IsNumber() ) {
param.value.type = A_DOUBLE;
param.value.buffer = (char *)( param_double );
} else if( row0->Get(c)->IsString() ) {
param.value.type = A_STRING;
param.value.buffer = (char *)char_arr;
param.value.length = len;
param.value.is_address = true;
} else if( Buffer::HasInstance( row0->Get(c) ) ) {
param.value.type = A_BINARY;
param.value.buffer = (char *)char_arr;
param.value.length = len;
param.value.is_address = true;
} else if( row0->Get(c)->IsNull() ) {
} else{
return false;
}
for( unsigned int r = 0; r < num_rows; r++ ) {
Handle bind_params = Handle::Cast( rows->Get(r) );
is_null[r] = false;
if( bind_params->Get(c)->IsInt32() || bind_params->Get(c)->IsNumber() ) {
param_double[r] = bind_params->Get(c)->NumberValue();
} else if( bind_params->Get(c)->IsString() ) {
String::Utf8Value paramValue( bind_params->Get(c)->ToString() );
const char* param_string = (*paramValue);
len[r] = (size_t)paramValue.length();
char *param_char = new char[len[r] + 1];
char_arr[r] = param_char;
memcpy( param_char, param_string, len[r] + 1 );
} else if( Buffer::HasInstance( bind_params->Get(c) ) ) {
len[r] = Buffer::Length( bind_params->Get(c) );
char *param_char = new char[len[r]];
char_arr[r] = param_char;
memcpy( param_char, Buffer::Data( bind_params->Get(c) ), len[r] );
} else if( bind_params->Get(c)->IsNull() ) {
is_null[r] = true;
}
}
params.push_back( param );
}
return true;
}
bool getBindParameters( std::vector &execData,
Handle arg,
std::vector & params,
unsigned &num_rows )
/*************************************************************************/
{
Handle bind_params = Handle::Cast( arg );
if( bind_params->Length() == 0 ) {
// if an empty array was passed in, we still need ExecuteData
ExecuteData *ex = new ExecuteData;
execData.push_back( ex );
return true;
}
if( bind_params->Get(0)->IsArray() ) {
return getWideBindParameters( execData, arg, params, num_rows );
}
num_rows = 1;
ExecuteData *ex = new ExecuteData;
execData.push_back( ex );
for( unsigned int i = 0; i < bind_params->Length(); i++ ) {
a_sqlany_bind_param param;
memset( ¶m, 0, sizeof( param ) );
if( bind_params->Get(i)->IsInt32() ) {
int *param_int = new int;
*param_int = bind_params->Get(i)->Int32Value();
ex->addInt( param_int );
param.value.buffer = (char *)( param_int );
param.value.type = A_VAL32;
} else if( bind_params->Get(i)->IsNumber() ) {
double *param_double = new double;
*param_double = bind_params->Get(i)->NumberValue(); // Remove Round off Error
ex->addNum( param_double );
param.value.buffer = (char *)( param_double );
param.value.type = A_DOUBLE;
} else if( bind_params->Get(i)->IsString() ) {
String::Utf8Value paramValue( bind_params->Get(i)->ToString() );
const char* param_string = (*paramValue);
size_t *len = new size_t;
*len = (size_t)paramValue.length();
char **char_arr = new char *;
char *param_char = new char[*len + 1];
*char_arr = param_char;
memcpy( param_char, param_string, ( *len ) + 1 );
ex->addStrings( char_arr, len );
param.value.type = A_STRING;
param.value.buffer = param_char;
param.value.length = len;
param.value.buffer_size = *len + 1;
} else if( Buffer::HasInstance( bind_params->Get(i) ) ) {
size_t *len = new size_t;
*len = Buffer::Length( bind_params->Get(i) );
char **char_arr = new char *;
char *param_char = new char[*len];
*char_arr = param_char;
memcpy( param_char, Buffer::Data( bind_params->Get(i) ), *len );
ex->addStrings( char_arr, len );
param.value.type = A_BINARY;
param.value.buffer = param_char;
param.value.length = len;
param.value.buffer_size = sizeof( param_char );
} else if( bind_params->Get(i)->IsNull() ) {
param.value.type = A_STRING;
sacapi_bool *is_null = new sacapi_bool;
param.value.is_null = is_null;
ex->addNull( is_null );
is_null[0] = true;
} else{
return false;
}
params.push_back( param );
}
return true;
}
bool getResultSet( Persistent & Result,
int & rows_affected,
std::vector & colNames,
ExecuteData * execData,
std::vector & col_types )
/*****************************************************************/
{
Isolate *isolate = Isolate::GetCurrent();
HandleScope scope( isolate );
int num_rows = 0;
size_t num_cols = colNames.size();
if( rows_affected >= 0 ) {
Result.Reset( isolate, Integer::New( isolate, rows_affected ) );
return true;
}
if( num_cols > 0 ) {
size_t count = 0;
size_t count_int = 0, count_num = 0, count_string = 0;
Local ResultSet = Array::New( isolate );
while( count_int < execData->intSize() ||
count_num < execData->numSize() ||
count_string < execData->stringSize() ) {
Local curr_row = Object::New( isolate );
num_rows++;
for( size_t i = 0; i < num_cols; i++ ) {
switch( col_types[count] ) {
case A_INVALID_TYPE:
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Null( isolate ) );
break;
case A_VAL32:
case A_VAL16:
case A_UVAL16:
case A_VAL8:
case A_UVAL8:
if( execData->intIsNull( count_int ) ) {
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Null( isolate ) );
} else {
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Integer::New( isolate, execData->getInt( count_int ) ) );
}
count_int++;
break;
case A_UVAL32:
case A_UVAL64:
case A_VAL64:
case A_DOUBLE:
if( execData->numIsNull( count_num ) ) {
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Null( isolate ) );
} else {
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Number::New( isolate, execData->getNum( count_num ) ) );
}
count_num++;
break;
case A_BINARY:
if( execData->stringIsNull( count_string ) ) {
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Null( isolate ) );
} else {
#if v012
Local buf = node::Buffer::New(
isolate, execData->getString( count_string ),
execData->getLen( count_string ) );
curr_row->Set( String::NewFromUtf8( isolate,
colNames[i] ),
buf );
#else
MaybeLocal mbuf = node::Buffer::Copy(
isolate, execData->getString( count_string ),
execData->getLen( count_string ) );
Local buf = mbuf.ToLocalChecked();
#endif
curr_row->Set( String::NewFromUtf8( isolate,
colNames[i] ),
buf );
}
count_string++;
break;
case A_STRING:
if( execData->stringIsNull( count_string ) ) {
curr_row->Set( String::NewFromUtf8( isolate, colNames[i] ),
Null( isolate ) );
} else {
curr_row->Set( String::NewFromUtf8( isolate,
colNames[i] ),
#if v012
String::NewFromUtf8( isolate,
execData->getString( count_string ),
String::NewStringType::kNormalString,
(int)execData->getLen( count_string ) )
#else
String::NewFromUtf8( isolate,
execData->getString( count_string ),
NewStringType::kNormal,
(int)execData->getLen( count_string ) ).ToLocalChecked()
#endif
);
}
count_string++;
break;
default:
return false;
}
count++;
}
ResultSet->Set( num_rows - 1, curr_row );
}
Result.Reset( isolate, ResultSet );
} else {
Result.Reset( isolate, Local::New( isolate,
Undefined( isolate ) ) );
}
return true;
}
bool fetchResultSet( a_sqlany_stmt * sqlany_stmt,
int & rows_affected,
std::vector & colNames,
ExecuteData * execData,
std::vector & col_types )
/*****************************************************************/
{
a_sqlany_data_value value;
int num_cols = 0;
rows_affected = api.sqlany_affected_rows( sqlany_stmt );
num_cols = api.sqlany_num_cols( sqlany_stmt );
if( rows_affected > 0 && num_cols < 1 ) {
return true;
}
rows_affected = -1;
if( num_cols > 0 ) {
for( int i = 0; i < num_cols; i++ ) {
a_sqlany_column_info info;
api.sqlany_get_column_info( sqlany_stmt, i, &info );
size_t size = strlen( info.name ) + 1;
char *name = new char[ size ];
memcpy( name, info.name, size );
colNames.push_back( name );
}
int count_string = 0, count_num = 0, count_int = 0;
while( api.sqlany_fetch_next( sqlany_stmt ) ) {
for( int i = 0; i < num_cols; i++ ) {
if( !api.sqlany_get_column( sqlany_stmt, i, &value ) ) {
return false;
break;
}
if( *(value.is_null) ) {
col_types.push_back( A_INVALID_TYPE );
continue;
}
switch( value.type ) {
case A_BINARY:
{
size_t *size = new size_t;
*size = *(value.length);
char *val = new char[ *size ];
memcpy( val, value.buffer, *size );
execData->addString( val, size );
count_string++;
break;
}
case A_STRING:
{
size_t *size = new size_t;
*size = (size_t)( (int)*(value.length) );
char *val = new char[ *size ];
memcpy( val, (char *)value.buffer, *size );
execData->addString( val, size );
count_string++;
break;
}
case A_VAL64:
{
double *val = new double;
*val = (double)*(long long *)value.buffer;
execData->addNum( val );
count_num++;
break;
}
case A_UVAL64:
{
double *val = new double;
*val = (double)*(unsigned long long *)value.buffer;
execData->addNum( val );
count_num++;
break;
}
case A_VAL32:
{
int *val = new int;
*val = *(int*)value.buffer;
execData->addInt( val );
count_int++;
break;
}
case A_UVAL32:
{
double *val = new double;
*val = (double)*(unsigned int*)value.buffer;
execData->addNum( val );
count_num++;
break;
}
case A_VAL16:
{
int *val = new int;
*val = (int)*(short*)value.buffer;
execData->addInt( val );
count_int++;
break;
}
case A_UVAL16:
{
int *val = new int;
*val = (int)*(unsigned short*)value.buffer;
execData->addInt( val );
count_int++;
break;
}
case A_VAL8:
{
int *val = new int;
*val = (int)*(char *)value.buffer;
execData->addInt( val );
count_int++;
break;
}
case A_UVAL8:
{
int *val = new int;
*val = (int)*(unsigned char *)value.buffer;
execData->addInt( val );
count_int++;
break;
}
case A_DOUBLE:
{
double *val = new double;
*val = (double)*(double *)value.buffer;
execData->addNum( val );
count_num++;
break;
}
default:
return false;
}
col_types.push_back( value.type );
}
}
}
return true;
}
bool cleanAPI()
/*************/
{
if( openConnections == 0 ) {
if( api.initialized ) {
api.sqlany_fini();
sqlany_finalize_interface( &api );
return true;
}
}
return false;
}
// Generic Baton and Callback (After) Function
// Use this if the function does not have any return values and
// Does not take any parameters.
// Create custom Baton and Callback (After) functions otherwise
void Connection::noParamAfter( uv_work_t *req )
/*********************************************/
{
Isolate *isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
noParamBaton *baton = static_cast(req->data);
Local undef = Local::New( isolate, Undefined( isolate ) );
if( baton->err ) {
callBack( &( baton->error_msg ), baton->callback, undef,
baton->callback_required );
return;
}
callBack( NULL, baton->callback, undef, baton->callback_required );
delete baton;
delete req;
}
// Stmt Object Functions
StmtObject::StmtObject()
/**********************/
{
connection = NULL;
sqlany_stmt = NULL;
}
StmtObject::~StmtObject()
/***********************/
{
uv_mutex_t *mutex = NULL;
if( connection != NULL ) {
mutex = &connection->conn_mutex;
uv_mutex_lock( mutex );
}
cleanup();
removeConnection();
if( mutex != NULL ) {
uv_mutex_unlock( mutex );
}
}
Persistent StmtObject::constructor;
void StmtObject::Init( Isolate *isolate )
/***************************************/
{
HandleScope scope(isolate);
// Prepare constructor template
Local tpl = FunctionTemplate::New( isolate, New );
tpl->SetClassName( String::NewFromUtf8( isolate, "StmtObject" ) );
tpl->InstanceTemplate()->SetInternalFieldCount( 1 );
// Prototype
NODE_SET_PROTOTYPE_METHOD( tpl, "exec", exec );
NODE_SET_PROTOTYPE_METHOD( tpl, "drop", drop );
NODE_SET_PROTOTYPE_METHOD( tpl, "getMoreResults", getMoreResults );
constructor.Reset( isolate, tpl->GetFunction() );
}
void StmtObject::New( const FunctionCallbackInfo &args )
/*************************************************************/
{
StmtObject* obj = new StmtObject();
obj->Wrap( args.This() );
args.GetReturnValue().Set( args.This() );
}
void StmtObject::NewInstance( const FunctionCallbackInfo &args )
/*********************************************************************/
{
Persistent obj;
CreateNewInstance( args, obj );
args.GetReturnValue().Set( obj );
}
void StmtObject::CreateNewInstance( const FunctionCallbackInfo & args,
Persistent & obj )
/***************************************************************************/
{
Isolate *isolate = args.GetIsolate();
HandleScope scope(isolate);
const unsigned argc = 1;
Handle argv[argc] = { args[0] };
Localcons = Local::New( isolate, constructor );
#if NODE_MAJOR_VERSION >= 10
Local env = isolate->GetCurrentContext();
MaybeLocal mlObj = cons->NewInstance( env, argc, argv );
Local instance = mlObj.ToLocalChecked();
obj.Reset( isolate, instance );
#else
obj.Reset( isolate, cons->NewInstance( argc, argv ) );
#endif
}
void StmtObject::cleanup( void )
/******************************/
{
if( sqlany_stmt != NULL ) {
api.sqlany_free_stmt( sqlany_stmt );
sqlany_stmt = NULL;
}
}
void StmtObject::removeConnection( void )
/***************************************/
{
if( connection != NULL ) {
connection->removeStmt( this );
connection = NULL;
}
}
// Connection Functions
void HashToString( Local obj, Persistent &ret )
/*************************************************************/
{
Isolate *isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Local props = obj->GetOwnPropertyNames();
int length = props->Length();
std::string params = "";
bool first = true;
for( int i = 0; i < length; i++ ) {
Local key = props->Get(i).As();
Local val = obj->Get(key).As();
String::Utf8Value key_utf8( key );
String::Utf8Value val_utf8( val );
if( !first ) {
params += ";";
}
first = false;
params += std::string(*key_utf8);
params += "=";
params += std::string(*val_utf8);
}
ret.Reset( isolate, String::NewFromUtf8( isolate, params.c_str() ) );
}
#if 0
// Handy function for determining what type an object is.
static void CheckArgType( Local &obj )
/*******************************************/
{
static const char *type = NULL;
if( obj->IsArray() ) {
type = "Array";
} else if( obj->IsBoolean() ) {
type = "Boolean";
} else if( obj->IsBooleanObject() ) {
type = "BooleanObject";
} else if( obj->IsDate() ) {
type = "Date";
} else if( obj->IsExternal() ) {
type = "External";
} else if( obj->IsFunction() ) {
type = "Function";
} else if( obj->IsInt32() ) {
type = "Int32";
} else if( obj->IsNativeError() ) {
type = "NativeError";
} else if( obj->IsNull() ) {
type = "Null";
} else if( obj->IsNumber() ) {
type = "Number";
} else if( obj->IsNumberObject() ) {
type = "NumberObject";
} else if( obj->IsObject() ) {
type = "Object";
} else if( obj->IsRegExp() ) {
type = "RegExp";
} else if( obj->IsString() ) {
type = "String";
} else if( obj->IsStringObject() ) {
type = "StringObject";
} else if( obj->IsUint32() ) {
type = "Uint32";
} else if( obj->IsUndefined() ) {
type = "Undefined";
} else {
type = "Unknown";
}
}
#endif
Connection::Connection( const FunctionCallbackInfo &args )
/***************************************************************/
{
Isolate *isolate = args.GetIsolate();
HandleScope scope( isolate );
uv_mutex_init(&conn_mutex);
conn = NULL;
if( args.Length() > 1 ) {
throwError( JS_ERR_INVALID_ARGUMENTS );
return;
}
if( args.Length() == 1 ) {
//CheckArgType( args[0] );
if( args[0]->IsString() ) {
Local str = args[0]->ToString();
int string_len = str->Utf8Length();
char *buf = new char[string_len+1];
str->WriteUtf8( buf );
_arg.Reset( isolate, String::NewFromUtf8( isolate, buf ) );
delete [] buf;
} else if( args[0]->IsObject() ) {
HashToString( args[0]->ToObject(), _arg );
} else if( !args[0]->IsUndefined() && !args[0]->IsNull() ) {
throwError( JS_ERR_INVALID_ARGUMENTS );
} else {
_arg.Reset( isolate, String::NewFromUtf8( isolate, "" ) );
}
} else {
_arg.Reset( isolate, String::NewFromUtf8( isolate, "" ) );
}
}
Connection::~Connection()
/***********************/
{
scoped_lock api_lock( api_mutex );
scoped_lock lock( conn_mutex );
_arg.Reset();
cleanupStmts();
if( conn != NULL ) {
api.sqlany_disconnect( conn );
api.sqlany_free_connection( conn );
conn = NULL;
openConnections--;
}
cleanAPI();
};
void Connection::cleanupStmts( void )
/***********************************/
{
std::vector::iterator findit;
for( findit = statements.begin(); findit != statements.end(); findit++ ) {
StmtObject *s = reinterpret_cast(*findit);
s->cleanup();
}
}
void Connection::removeStmt( class StmtObject *stmt )
/***************************************************/
{
// caller must get mutex
std::vector::iterator findit;
for( findit = statements.begin(); findit != statements.end(); findit++ ) {
if( *findit == reinterpret_cast(stmt) ) {
statements.erase( findit );
return;
}
}
}
Persistent Connection::constructor;
void Connection::Init( Isolate *isolate )
/***************************************/
{
HandleScope scope( isolate );
// Prepare constructor template
Local tpl = FunctionTemplate::New( isolate, New );
tpl->SetClassName( String::NewFromUtf8( isolate, "Connection" ) );
tpl->InstanceTemplate()->SetInternalFieldCount( 1 );
// Prototype
NODE_SET_PROTOTYPE_METHOD( tpl, "exec", exec );
NODE_SET_PROTOTYPE_METHOD( tpl, "prepare", prepare );
NODE_SET_PROTOTYPE_METHOD( tpl, "connect", connect );
NODE_SET_PROTOTYPE_METHOD( tpl, "disconnect", disconnect );
NODE_SET_PROTOTYPE_METHOD( tpl, "close", disconnect );
NODE_SET_PROTOTYPE_METHOD( tpl, "commit", commit );
NODE_SET_PROTOTYPE_METHOD( tpl, "rollback", rollback );
NODE_SET_PROTOTYPE_METHOD( tpl, "connected", connected );
constructor.Reset( isolate, tpl->GetFunction() );
}
void Connection::New( const FunctionCallbackInfo &args )
/*************************************************************/
{
Isolate *isolate = args.GetIsolate();
HandleScope scope( isolate );
if( args.IsConstructCall() ) {
Connection *obj = new Connection( args );
obj->Wrap( args.This() );
args.GetReturnValue().Set( args.This() );
} else {
const int argc = 1;
Local argv[argc] = { args[0] };
Local cons = Local::New( isolate, constructor );
#if NODE_MAJOR_VERSION >= 10
Local env = isolate->GetCurrentContext();
MaybeLocal mlObj = cons->NewInstance( env, argc, argv );
const Local obj = mlObj.ToLocalChecked();
args.GetReturnValue().Set( obj );
#else
args.GetReturnValue().Set( cons->NewInstance( argc, argv ) );
#endif
}
}
void Connection::NewInstance( const FunctionCallbackInfo &args )
/*********************************************************************/
{
Isolate *isolate = args.GetIsolate();
HandleScope scope( isolate );
const unsigned argc = 1;
Handle argv[argc] = { args[0] };
Local cons = Local::New( isolate, constructor );
#if NODE_MAJOR_VERSION >= 10
Local env = isolate->GetCurrentContext();
MaybeLocal mlObj = cons->NewInstance( env, argc, argv );
Local instance = mlObj.ToLocalChecked();
#else
Local instance = cons->NewInstance( argc, argv );
#endif
args.GetReturnValue().Set( instance );
}
#endif // !v010