#include #include #include #include "macros.h" #include "database.h" #include "statement.h" using namespace node_sqlite3; Napi::FunctionReference Statement::constructor; Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); Napi::Function t = DefineClass(env, "Statement", { InstanceMethod("bind", &Statement::Bind), InstanceMethod("get", &Statement::Get), InstanceMethod("run", &Statement::Run), InstanceMethod("all", &Statement::All), InstanceMethod("each", &Statement::Each), InstanceMethod("reset", &Statement::Reset), InstanceMethod("finalize", &Statement::Finalize_), }); constructor = Napi::Persistent(t); constructor.SuppressDestruct(); exports.Set("Statement", t); return exports; } // A Napi InstanceOf for Javascript Objects "Date" and "RegExp" bool OtherInstanceOf(Napi::Object source, char* object_type) { if (object_type == "Date") { return source.InstanceOf(source.Env().Global().Get("Date").As()); } else if (object_type == "RegExp") { return source.InstanceOf(source.Env().Global().Get("RegExp").As()); } return false; } void Statement::Process() { if (finalized && !queue.empty()) { return CleanQueue(); } while (prepared && !locked && !queue.empty()) { Call* call = queue.front(); queue.pop(); call->callback(call->baton); delete call; } } void Statement::Schedule(Work_Callback callback, Baton* baton) { if (finalized) { queue.push(new Call(callback, baton)); CleanQueue(); } else if (!prepared || locked) { queue.push(new Call(callback, baton)); } else { callback(baton); } } template void Statement::Error(T* baton) { Statement* stmt = baton->stmt; Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); // Fail hard on logic errors. assert(stmt->status != 0); EXCEPTION(Napi::String::New(env, stmt->message.c_str()), stmt->status, exception); Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { Napi::Value argv[] = { exception }; TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } else { Napi::Value argv[] = { Napi::String::New(env, "error"), exception }; EMIT_EVENT(stmt->Value(), 2, argv); } } // { Database db, String sql, Array params, Function callback } Statement::Statement(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { Napi::Env env = info.Env(); int length = info.Length(); if (length <= 0 || !Database::HasInstance(info[0])) { Napi::TypeError::New(env, "Database object expected").ThrowAsJavaScriptException(); return; } else if (length <= 1 || !info[1].IsString()) { Napi::TypeError::New(env, "SQL query expected").ThrowAsJavaScriptException(); return; } else if (length > 2 && !info[2].IsUndefined() && !info[2].IsFunction()) { Napi::TypeError::New(env, "Callback expected").ThrowAsJavaScriptException(); return; } Database* db = Napi::ObjectWrap::Unwrap(info[0].As()); Napi::String sql = info[1].As(); info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("sql", sql, napi_default)); init(db); Statement* stmt = this; PrepareBaton* baton = new PrepareBaton(db, info[2].As(), stmt); baton->sql = std::string(sql.As().Utf8Value().c_str()); db->Schedule(Work_BeginPrepare, baton); } void Statement::Work_BeginPrepare(Database::Baton* baton) { assert(baton->db->open); baton->db->pending++; Napi::Env env = baton->db->Env(); int status = napi_create_async_work( env, NULL, Napi::String::New(env, "sqlite3.Statement.Prepare"), Work_Prepare, Work_AfterPrepare, baton, &baton->request ); assert(status == 0); napi_queue_async_work(env, baton->request); } void Statement::Work_Prepare(napi_env e, void* data) { STATEMENT_INIT(PrepareBaton); // In case preparing fails, we use a mutex to make sure we get the associated // error message. sqlite3_mutex* mtx = sqlite3_db_mutex(baton->db->_handle); sqlite3_mutex_enter(mtx); stmt->status = sqlite3_prepare_v2( baton->db->_handle, baton->sql.c_str(), baton->sql.size(), &stmt->_handle, NULL ); if (stmt->status != SQLITE_OK) { stmt->message = std::string(sqlite3_errmsg(baton->db->_handle)); stmt->_handle = NULL; } sqlite3_mutex_leave(mtx); } void Statement::Work_AfterPrepare(napi_env e, napi_status status, void* data) { STATEMENT_INIT(PrepareBaton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); if (stmt->status != SQLITE_OK) { Error(baton); stmt->Finalize_(); } else { stmt->prepared = true; if (!baton->callback.IsEmpty() && baton->callback.Value().IsFunction()) { Napi::Function cb = baton->callback.Value(); Napi::Value argv[] = { env.Null() }; TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } STATEMENT_END(); } template Values::Field* Statement::BindParameter(const Napi::Value source, T pos) { if (source.IsString()) { std::string val = source.As().Utf8Value(); return new Values::Text(pos, val.length(), val.c_str()); } else if (OtherInstanceOf(source.As(), "RegExp")) { std::string val = source.ToString().Utf8Value(); return new Values::Text(pos, val.length(), val.c_str()); } else if (source.IsNumber()) { if (OtherIsInt(source.As())) { return new Values::Integer(pos, source.As().Int32Value()); } else { return new Values::Float(pos, source.As().DoubleValue()); } } else if (source.IsBoolean()) { return new Values::Integer(pos, source.As().Value() ? 1 : 0); } else if (source.IsNull()) { return new Values::Null(pos); } else if (source.IsBuffer()) { Napi::Buffer buffer = source.As>(); return new Values::Blob(pos, buffer.Length(), buffer.Data()); } else if (OtherInstanceOf(source.As(), "Date")) { return new Values::Float(pos, source.ToNumber().DoubleValue()); } else if (source.IsObject()) { std::string val = source.ToString().Utf8Value(); return new Values::Text(pos, val.length(), val.c_str()); } else { return NULL; } } template T* Statement::Bind(const Napi::CallbackInfo& info, int start, int last) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); if (last < 0) last = info.Length(); Napi::Function callback; if (last > start && info[last - 1].IsFunction()) { callback = info[last - 1].As(); last--; } T* baton = new T(this, callback); if (start < last) { if (info[start].IsArray()) { Napi::Array array = info[start].As(); int length = array.Length(); // Note: bind parameters start with 1. for (int i = 0, pos = 1; i < length; i++, pos++) { baton->parameters.push_back(BindParameter((array).Get(i), pos)); } } else if (!info[start].IsObject() || OtherInstanceOf(info[start].As(), "RegExp") || OtherInstanceOf(info[start].As(), "Date") || info[start].IsBuffer()) { // Parameters directly in array. // Note: bind parameters start with 1. for (int i = start, pos = 1; i < last; i++, pos++) { baton->parameters.push_back(BindParameter(info[i], pos)); } } else if (info[start].IsObject()) { Napi::Object object = info[start].As(); Napi::Array array = object.GetPropertyNames(); int length = array.Length(); for (int i = 0; i < length; i++) { Napi::Value name = (array).Get(i); Napi::Number num = name.ToNumber(); if (num.Int32Value() == num.DoubleValue()) { baton->parameters.push_back( BindParameter((object).Get(name), num.Int32Value())); } else { baton->parameters.push_back(BindParameter((object).Get(name), name.As().Utf8Value().c_str())); } } } else { return NULL; } } return baton; } bool Statement::Bind(const Parameters & parameters) { if (parameters.size() == 0) { return true; } sqlite3_reset(_handle); sqlite3_clear_bindings(_handle); Parameters::const_iterator it = parameters.begin(); Parameters::const_iterator end = parameters.end(); for (; it < end; ++it) { Values::Field* field = *it; if (field != NULL) { unsigned int pos; if (field->index > 0) { pos = field->index; } else { pos = sqlite3_bind_parameter_index(_handle, field->name.c_str()); } switch (field->type) { case SQLITE_INTEGER: { status = sqlite3_bind_int(_handle, pos, ((Values::Integer*)field)->value); } break; case SQLITE_FLOAT: { status = sqlite3_bind_double(_handle, pos, ((Values::Float*)field)->value); } break; case SQLITE_TEXT: { status = sqlite3_bind_text(_handle, pos, ((Values::Text*)field)->value.c_str(), ((Values::Text*)field)->value.size(), SQLITE_TRANSIENT); } break; case SQLITE_BLOB: { status = sqlite3_bind_blob(_handle, pos, ((Values::Blob*)field)->value, ((Values::Blob*)field)->length, SQLITE_TRANSIENT); } break; case SQLITE_NULL: { status = sqlite3_bind_null(_handle, pos); } break; } if (status != SQLITE_OK) { message = std::string(sqlite3_errmsg(db->_handle)); return false; } } } return true; } Napi::Value Statement::Bind(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { Napi::TypeError::New(env, "Data type is not supported").ThrowAsJavaScriptException(); return env.Null(); } else { stmt->Schedule(Work_BeginBind, baton); return info.This(); } } void Statement::Work_BeginBind(Baton* baton) { STATEMENT_BEGIN(Bind); } void Statement::Work_Bind(napi_env e, void* data) { STATEMENT_INIT(Baton); sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); sqlite3_mutex_enter(mtx); stmt->Bind(baton->parameters); sqlite3_mutex_leave(mtx); } void Statement::Work_AfterBind(napi_env e, napi_status status, void* data) { STATEMENT_INIT(Baton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); if (stmt->status != SQLITE_OK) { Error(baton); } else { // Fire callbacks. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { Napi::Value argv[] = { env.Null() }; TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } STATEMENT_END(); } Napi::Value Statement::Get(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); return env.Null(); } else { stmt->Schedule(Work_BeginGet, baton); return info.This(); } } void Statement::Work_BeginGet(Baton* baton) { STATEMENT_BEGIN(Get); } void Statement::Work_Get(napi_env e, void* data) { STATEMENT_INIT(RowBaton); if (stmt->status != SQLITE_DONE || baton->parameters.size()) { sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); sqlite3_mutex_enter(mtx); if (stmt->Bind(baton->parameters)) { stmt->status = sqlite3_step(stmt->_handle); if (!(stmt->status == SQLITE_ROW || stmt->status == SQLITE_DONE)) { stmt->message = std::string(sqlite3_errmsg(stmt->db->_handle)); } } sqlite3_mutex_leave(mtx); if (stmt->status == SQLITE_ROW) { // Acquire one result row before returning. GetRow(&baton->row, stmt->_handle); } } } void Statement::Work_AfterGet(napi_env e, napi_status status, void* data) { STATEMENT_INIT(RowBaton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); if (stmt->status != SQLITE_ROW && stmt->status != SQLITE_DONE) { Error(baton); } else { // Fire callbacks. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { if (stmt->status == SQLITE_ROW) { // Create the result array from the data we acquired. Napi::Value argv[] = { env.Null(), RowToJS(env, &baton->row) }; TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } else { Napi::Value argv[] = { env.Null() }; TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } } STATEMENT_END(); } Napi::Value Statement::Run(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); return env.Null(); } else { stmt->Schedule(Work_BeginRun, baton); return info.This(); } } void Statement::Work_BeginRun(Baton* baton) { STATEMENT_BEGIN(Run); } void Statement::Work_Run(napi_env e, void* data) { STATEMENT_INIT(RunBaton); sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); sqlite3_mutex_enter(mtx); // Make sure that we also reset when there are no parameters. if (!baton->parameters.size()) { sqlite3_reset(stmt->_handle); } if (stmt->Bind(baton->parameters)) { stmt->status = sqlite3_step(stmt->_handle); if (!(stmt->status == SQLITE_ROW || stmt->status == SQLITE_DONE)) { stmt->message = std::string(sqlite3_errmsg(stmt->db->_handle)); } else { baton->inserted_id = sqlite3_last_insert_rowid(stmt->db->_handle); baton->changes = sqlite3_changes(stmt->db->_handle); } } sqlite3_mutex_leave(mtx); } void Statement::Work_AfterRun(napi_env e, napi_status status, void* data) { STATEMENT_INIT(RunBaton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); if (stmt->status != SQLITE_ROW && stmt->status != SQLITE_DONE) { Error(baton); } else { // Fire callbacks. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { (stmt->Value()).Set(Napi::String::New(env, "lastID"), Napi::Number::New(env, baton->inserted_id)); (stmt->Value()).Set( Napi::String::New(env, "changes"), Napi::Number::New(env, baton->changes)); Napi::Value argv[] = { env.Null() }; TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } STATEMENT_END(); } Napi::Value Statement::All(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); return env.Null(); } else { stmt->Schedule(Work_BeginAll, baton); return info.This(); } } void Statement::Work_BeginAll(Baton* baton) { STATEMENT_BEGIN(All); } void Statement::Work_All(napi_env e, void* data) { STATEMENT_INIT(RowsBaton); sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); sqlite3_mutex_enter(mtx); // Make sure that we also reset when there are no parameters. if (!baton->parameters.size()) { sqlite3_reset(stmt->_handle); } if (stmt->Bind(baton->parameters)) { while ((stmt->status = sqlite3_step(stmt->_handle)) == SQLITE_ROW) { Row* row = new Row(); GetRow(row, stmt->_handle); baton->rows.push_back(row); } if (stmt->status != SQLITE_DONE) { stmt->message = std::string(sqlite3_errmsg(stmt->db->_handle)); } } sqlite3_mutex_leave(mtx); } void Statement::Work_AfterAll(napi_env e, napi_status status, void* data) { STATEMENT_INIT(RowsBaton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); if (stmt->status != SQLITE_DONE) { Error(baton); } else { // Fire callbacks. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { if (baton->rows.size()) { // Create the result array from the data we acquired. Napi::Array result(Napi::Array::New(env, baton->rows.size())); Rows::const_iterator it = baton->rows.begin(); Rows::const_iterator end = baton->rows.end(); for (int i = 0; it < end; ++it, i++) { (result).Set(i, RowToJS(env,*it)); delete *it; } Napi::Value argv[] = { env.Null(), result }; TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } else { // There were no result rows. Napi::Value argv[] = { env.Null(), Napi::Array::New(env, 0) }; TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } } } STATEMENT_END(); } Napi::Value Statement::Each(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; int last = info.Length(); Napi::Function completed; if (last >= 2 && info[last - 1].IsFunction() && info[last - 2].IsFunction()) { completed = info[--last].As(); } EachBaton* baton = stmt->Bind(info, 0, last); if (baton == NULL) { Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); return env.Null(); } else { baton->completed.Reset(completed, 1); stmt->Schedule(Work_BeginEach, baton); return info.This(); } } void Statement::Work_BeginEach(Baton* baton) { // Only create the Async object when we're actually going into // the event loop. This prevents dangling events. EachBaton* each_baton = static_cast(baton); each_baton->async = new Async(each_baton->stmt, reinterpret_cast(AsyncEach)); each_baton->async->item_cb.Reset(each_baton->callback.Value(), 1); each_baton->async->completed_cb.Reset(each_baton->completed.Value(), 1); STATEMENT_BEGIN(Each); } void Statement::Work_Each(napi_env e, void* data) { STATEMENT_INIT(EachBaton); Async* async = baton->async; sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); int retrieved = 0; // Make sure that we also reset when there are no parameters. if (!baton->parameters.size()) { sqlite3_reset(stmt->_handle); } if (stmt->Bind(baton->parameters)) { while (true) { sqlite3_mutex_enter(mtx); stmt->status = sqlite3_step(stmt->_handle); if (stmt->status == SQLITE_ROW) { sqlite3_mutex_leave(mtx); Row* row = new Row(); GetRow(row, stmt->_handle); NODE_SQLITE3_MUTEX_LOCK(&async->mutex) async->data.push_back(row); retrieved++; NODE_SQLITE3_MUTEX_UNLOCK(&async->mutex) uv_async_send(&async->watcher); } else { if (stmt->status != SQLITE_DONE) { stmt->message = std::string(sqlite3_errmsg(stmt->db->_handle)); } sqlite3_mutex_leave(mtx); break; } } } async->completed = true; uv_async_send(&async->watcher); } void Statement::CloseCallback(uv_handle_t* handle) { assert(handle != NULL); assert(handle->data != NULL); Async* async = static_cast(handle->data); delete async; } void Statement::AsyncEach(uv_async_t* handle) { Async* async = static_cast(handle->data); Napi::Env env = async->stmt->Env(); Napi::HandleScope scope(env); while (true) { // Get the contents out of the data cache for us to process in the JS callback. Rows rows; NODE_SQLITE3_MUTEX_LOCK(&async->mutex) rows.swap(async->data); NODE_SQLITE3_MUTEX_UNLOCK(&async->mutex) if (rows.empty()) { break; } Napi::Function cb = async->item_cb.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { Napi::Value argv[2]; argv[0] = env.Null(); Rows::const_iterator it = rows.begin(); Rows::const_iterator end = rows.end(); for (int i = 0; it < end; ++it, i++) { argv[1] = RowToJS(env,*it); async->retrieved++; TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv); delete *it; } } } Napi::Function cb = async->completed_cb.Value(); if (async->completed) { if (!cb.IsEmpty() && cb.IsFunction()) { Napi::Value argv[] = { env.Null(), Napi::Number::New(env, async->retrieved) }; TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv); } uv_close(reinterpret_cast(handle), CloseCallback); } } void Statement::Work_AfterEach(napi_env e, napi_status status, void* data) { STATEMENT_INIT(EachBaton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); if (stmt->status != SQLITE_DONE) { Error(baton); } STATEMENT_END(); } Napi::Value Statement::Reset(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(stmt, callback); stmt->Schedule(Work_BeginReset, baton); return info.This(); } void Statement::Work_BeginReset(Baton* baton) { STATEMENT_BEGIN(Reset); } void Statement::Work_Reset(napi_env e, void* data) { STATEMENT_INIT(Baton); sqlite3_reset(stmt->_handle); stmt->status = SQLITE_OK; } void Statement::Work_AfterReset(napi_env e, napi_status status, void* data) { STATEMENT_INIT(Baton); Napi::Env env = stmt->Env(); Napi::HandleScope scope(env); // Fire callbacks. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { Napi::Value argv[] = { env.Null() }; TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } STATEMENT_END(); } Napi::Value Statement::RowToJS(Napi::Env env, Row* row) { Napi::EscapableHandleScope scope(env); Napi::Object result = Napi::Object::New(env); Row::const_iterator it = row->begin(); Row::const_iterator end = row->end(); for (int i = 0; it < end; ++it, i++) { Values::Field* field = *it; Napi::Value value; switch (field->type) { case SQLITE_INTEGER: { value = Napi::Number::New(env, ((Values::Integer*)field)->value); } break; case SQLITE_FLOAT: { value = Napi::Number::New(env, ((Values::Float*)field)->value); } break; case SQLITE_TEXT: { value = Napi::String::New(env, ((Values::Text*)field)->value.c_str(), ((Values::Text*)field)->value.size()); } break; case SQLITE_BLOB: { value = Napi::Buffer::Copy(env, ((Values::Blob*)field)->value, ((Values::Blob*)field)->length); } break; case SQLITE_NULL: { value = env.Null(); } break; } (result).Set(Napi::String::New(env, field->name.c_str()), value); DELETE_FIELD(field); } return scope.Escape(result); } void Statement::GetRow(Row* row, sqlite3_stmt* stmt) { int rows = sqlite3_column_count(stmt); for (int i = 0; i < rows; i++) { int type = sqlite3_column_type(stmt, i); const char* name = sqlite3_column_name(stmt, i); switch (type) { case SQLITE_INTEGER: { row->push_back(new Values::Integer(name, sqlite3_column_int64(stmt, i))); } break; case SQLITE_FLOAT: { row->push_back(new Values::Float(name, sqlite3_column_double(stmt, i))); } break; case SQLITE_TEXT: { const char* text = (const char*)sqlite3_column_text(stmt, i); int length = sqlite3_column_bytes(stmt, i); row->push_back(new Values::Text(name, length, text)); } break; case SQLITE_BLOB: { const void* blob = sqlite3_column_blob(stmt, i); int length = sqlite3_column_bytes(stmt, i); row->push_back(new Values::Blob(name, length, blob)); } break; case SQLITE_NULL: { row->push_back(new Values::Null(name)); } break; default: assert(false); } } } Napi::Value Statement::Finalize_(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Statement* stmt = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(stmt, callback); stmt->Schedule(Finalize_, baton); return stmt->db->Value(); } void Statement::Finalize_(Baton* baton) { Napi::Env env = baton->stmt->Env(); Napi::HandleScope scope(env); baton->stmt->Finalize_(); // Fire callback in case there was one. Napi::Function cb = baton->callback.Value(); if (!cb.IsUndefined() && cb.IsFunction()) { TRY_CATCH_CALL(baton->stmt->Value(), cb, 0, NULL); } delete baton; } void Statement::Finalize_() { assert(!finalized); finalized = true; CleanQueue(); // Finalize returns the status code of the last operation. We already fired // error events in case those failed. sqlite3_finalize(_handle); _handle = NULL; db->Unref(); } void Statement::CleanQueue() { Napi::Env env = this->Env(); Napi::HandleScope scope(env); if (prepared && !queue.empty()) { // This statement has already been prepared and is now finalized. // Fire error for all remaining items in the queue. EXCEPTION(Napi::String::New(env, "Statement is already finalized"), SQLITE_MISUSE, exception); Napi::Value argv[] = { exception }; bool called = false; // Clear out the queue so that this object can get GC'ed. while (!queue.empty()) { Call* call = queue.front(); queue.pop(); Napi::Function cb = call->baton->callback.Value(); if (prepared && !cb.IsEmpty() && cb.IsFunction()) { TRY_CATCH_CALL(Value(), cb, 1, argv); called = true; } // We don't call the actual callback, so we have to make sure that // the baton gets destroyed. delete call->baton; delete call; } // When we couldn't call a callback function, emit an error on the // Statement object. if (!called) { Napi::Value info[] = { Napi::String::New(env, "error"), exception }; EMIT_EVENT(Value(), 2, info); } } else while (!queue.empty()) { // Just delete all items in the queue; we already fired an event when // preparing the statement failed. Call* call = queue.front(); queue.pop(); // We don't call the actual callback, so we have to make sure that // the baton gets destroyed. delete call->baton; delete call; } }