Skip to main content
Glama
statement.cpp13.2 kB
Statement::Statement( Database* db, sqlite3_stmt* handle, sqlite3_uint64 id, bool returns_data ) : node::ObjectWrap(), db(db), handle(handle), extras(new Extras(id)), alive(true), locked(false), bound(false), has_bind_map(false), safe_ints(db->GetState()->safe_ints), mode(Data::FLAT), returns_data(returns_data) { assert(db != NULL); assert(handle != NULL); assert(db->GetState()->open); assert(!db->GetState()->busy); db->AddStatement(this); } Statement::~Statement() { if (alive) db->RemoveStatement(this); CloseHandles(); delete extras; } // Whenever this is used, db->RemoveStatement must be invoked beforehand. void Statement::CloseHandles() { if (alive) { alive = false; sqlite3_finalize(handle); } } // Returns the Statement's bind map (creates it upon first execution). BindMap* Statement::GetBindMap(v8::Isolate* isolate) { if (has_bind_map) return &extras->bind_map; BindMap* bind_map = &extras->bind_map; int param_count = sqlite3_bind_parameter_count(handle); for (int i = 1; i <= param_count; ++i) { const char* name = sqlite3_bind_parameter_name(handle, i); if (name != NULL) bind_map->Add(isolate, name + 1, i); } has_bind_map = true; return bind_map; } Statement::Extras::Extras(sqlite3_uint64 id) : bind_map(0), id(id) {} INIT(Statement::Init) { v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Statement"); SetPrototypeMethod(isolate, data, t, "run", JS_run); SetPrototypeMethod(isolate, data, t, "get", JS_get); SetPrototypeMethod(isolate, data, t, "all", JS_all); SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate); SetPrototypeMethod(isolate, data, t, "bind", JS_bind); SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck); SetPrototypeMethod(isolate, data, t, "expand", JS_expand); SetPrototypeMethod(isolate, data, t, "raw", JS_raw); SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers); SetPrototypeMethod(isolate, data, t, "columns", JS_columns); SetPrototypeGetter(isolate, data, t, "busy", JS_busy); return t->GetFunction(OnlyContext).ToLocalChecked(); } NODE_METHOD(Statement::JS_new) { UseAddon; if (!addon->privileged_info) { return ThrowTypeError("Statements can only be constructed by the db.prepare() method"); } assert(info.IsConstructCall()); Database* db = Unwrap<Database>(addon->privileged_info->This()); REQUIRE_DATABASE_OPEN(db->GetState()); REQUIRE_DATABASE_NOT_BUSY(db->GetState()); v8::Local<v8::String> source = (*addon->privileged_info)[0].As<v8::String>(); v8::Local<v8::Object> database = (*addon->privileged_info)[1].As<v8::Object>(); bool pragmaMode = (*addon->privileged_info)[2].As<v8::Boolean>()->Value(); int flags = SQLITE_PREPARE_PERSISTENT; if (pragmaMode) { REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState()); flags = 0; } UseIsolate; v8::String::Utf8Value utf8(isolate, source); sqlite3_stmt* handle; const char* tail; if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) { return db->ThrowDatabaseError(); } if (handle == NULL) { return ThrowRangeError("The supplied SQL string contains no statements"); } // https://github.com/WiseLibs/better-sqlite3/issues/975#issuecomment-1520934678 for (char c; (c = *tail); ) { if (IS_SKIPPED(c)) { ++tail; continue; } if (c == '/' && tail[1] == '*') { tail += 2; for (char c; (c = *tail); ++tail) { if (c == '*' && tail[1] == '/') { tail += 2; break; } } } else if (c == '-' && tail[1] == '-') { tail += 2; for (char c; (c = *tail); ++tail) { if (c == '\n') { ++tail; break; } } } else { sqlite3_finalize(handle); return ThrowRangeError("The supplied SQL string contains more than one statement"); } } UseContext; bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode; Statement* stmt = new Statement(db, handle, addon->NextId(), returns_data); stmt->Wrap(info.This()); SetFrozen(isolate, ctx, info.This(), addon->cs.reader, v8::Boolean::New(isolate, returns_data)); SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0)); SetFrozen(isolate, ctx, info.This(), addon->cs.source, source); SetFrozen(isolate, ctx, info.This(), addon->cs.database, database); info.GetReturnValue().Set(info.This()); } NODE_METHOD(Statement::JS_run) { STATEMENT_START(ALLOW_ANY_STATEMENT, DOES_MUTATE); sqlite3* db_handle = db->GetHandle(); int total_changes_before = sqlite3_total_changes(db_handle); sqlite3_step(handle); if (sqlite3_reset(handle) == SQLITE_OK) { int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); Addon* addon = db->GetAddon(); UseContext; v8::Local<v8::Object> result = v8::Object::New(isolate); result->Set(ctx, addon->cs.changes.Get(isolate), v8::Int32::New(isolate, changes)).FromJust(); result->Set(ctx, addon->cs.lastInsertRowid.Get(isolate), stmt->safe_ints ? v8::BigInt::New(isolate, id).As<v8::Value>() : v8::Number::New(isolate, (double)id).As<v8::Value>() ).FromJust(); STATEMENT_RETURN(result); } STATEMENT_THROW(); } NODE_METHOD(Statement::JS_get) { STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); int status = sqlite3_step(handle); if (status == SQLITE_ROW) { v8::Local<v8::Value> result = Data::GetRowJS(isolate, OnlyContext, handle, stmt->safe_ints, stmt->mode); sqlite3_reset(handle); STATEMENT_RETURN(result); } else if (status == SQLITE_DONE) { sqlite3_reset(handle); STATEMENT_RETURN(v8::Undefined(isolate)); } sqlite3_reset(handle); STATEMENT_THROW(); } NODE_METHOD(Statement::JS_all) { STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); UseContext; const bool safe_ints = stmt->safe_ints; const char mode = stmt->mode; #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 bool js_error = false; uint32_t row_count = 0; v8::Local<v8::Array> result = v8::Array::New(isolate, 0); while (sqlite3_step(handle) == SQLITE_ROW) { if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; } result->Set(ctx, row_count++, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)).FromJust(); } if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { STATEMENT_RETURN(result); } if (js_error) db->GetState()->was_js_error = true; STATEMENT_THROW(); #else v8::LocalVector<v8::Value> rows(isolate); rows.reserve(8); if (mode == Data::FLAT) { RowBuilder rowBuilder(isolate, handle, safe_ints); while (sqlite3_step(handle) == SQLITE_ROW) { rows.emplace_back(rowBuilder.GetRowJS()); } } else { while (sqlite3_step(handle) == SQLITE_ROW) { rows.emplace_back(Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)); } } if (sqlite3_reset(handle) == SQLITE_OK) { if (rows.size() > 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); db->GetState()->was_js_error = true; } else { STATEMENT_RETURN(v8::Array::New(isolate, rows.data(), rows.size())); } } STATEMENT_THROW(); #endif } NODE_METHOD(Statement::JS_iterate) { UseAddon; UseIsolate; v8::Local<v8::Function> c = addon->StatementIterator.Get(isolate); addon->privileged_info = &info; v8::MaybeLocal<v8::Object> maybeIterator = c->NewInstance(OnlyContext, 0, NULL); addon->privileged_info = NULL; if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked()); } NODE_METHOD(Statement::JS_bind) { Statement* stmt = Unwrap<Statement>(info.This()); if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object"); REQUIRE_DATABASE_OPEN(stmt->db->GetState()); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); REQUIRE_STATEMENT_NOT_LOCKED(stmt); STATEMENT_BIND(stmt->handle); stmt->bound = true; info.GetReturnValue().Set(info.This()); } NODE_METHOD(Statement::JS_pluck) { Statement* stmt = Unwrap<Statement>(info.This()); if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data"); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); REQUIRE_STATEMENT_NOT_LOCKED(stmt); bool use = true; if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode; info.GetReturnValue().Set(info.This()); } NODE_METHOD(Statement::JS_expand) { Statement* stmt = Unwrap<Statement>(info.This()); if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data"); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); REQUIRE_STATEMENT_NOT_LOCKED(stmt); bool use = true; if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode; info.GetReturnValue().Set(info.This()); } NODE_METHOD(Statement::JS_raw) { Statement* stmt = Unwrap<Statement>(info.This()); if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data"); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); REQUIRE_STATEMENT_NOT_LOCKED(stmt); bool use = true; if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); } stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode; info.GetReturnValue().Set(info.This()); } NODE_METHOD(Statement::JS_safeIntegers) { Statement* stmt = Unwrap<Statement>(info.This()); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); REQUIRE_STATEMENT_NOT_LOCKED(stmt); if (info.Length() == 0) stmt->safe_ints = true; else { REQUIRE_ARGUMENT_BOOLEAN(first, stmt->safe_ints); } info.GetReturnValue().Set(info.This()); } NODE_METHOD(Statement::JS_columns) { Statement* stmt = Unwrap<Statement>(info.This()); if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data"); REQUIRE_DATABASE_OPEN(stmt->db->GetState()); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); Addon* addon = stmt->db->GetAddon(); UseIsolate; #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 UseContext; int column_count = sqlite3_column_count(stmt->handle); v8::Local<v8::Array> columns = v8::Array::New(isolate); v8::Local<v8::String> name = addon->cs.name.Get(isolate); v8::Local<v8::String> columnName = addon->cs.column.Get(isolate); v8::Local<v8::String> tableName = addon->cs.table.Get(isolate); v8::Local<v8::String> databaseName = addon->cs.database.Get(isolate); v8::Local<v8::String> typeName = addon->cs.type.Get(isolate); for (int i = 0; i < column_count; ++i) { v8::Local<v8::Object> column = v8::Object::New(isolate); column->Set(ctx, name, InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) ).FromJust(); column->Set(ctx, columnName, InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) ).FromJust(); column->Set(ctx, tableName, InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) ).FromJust(); column->Set(ctx, databaseName, InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) ).FromJust(); column->Set(ctx, typeName, InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) ).FromJust(); columns->Set(ctx, i, column).FromJust(); } info.GetReturnValue().Set(columns); #else v8::LocalVector<v8::Name> keys(isolate); keys.reserve(5); keys.emplace_back(addon->cs.name.Get(isolate).As<v8::Name>()); keys.emplace_back(addon->cs.column.Get(isolate).As<v8::Name>()); keys.emplace_back(addon->cs.table.Get(isolate).As<v8::Name>()); keys.emplace_back(addon->cs.database.Get(isolate).As<v8::Name>()); keys.emplace_back(addon->cs.type.Get(isolate).As<v8::Name>()); int column_count = sqlite3_column_count(stmt->handle); v8::LocalVector<v8::Value> columns(isolate); columns.reserve(column_count); for (int i = 0; i < column_count; ++i) { v8::LocalVector<v8::Value> values(isolate); keys.reserve(5); values.emplace_back( InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) ); values.emplace_back( InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) ); values.emplace_back( InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) ); values.emplace_back( InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) ); values.emplace_back( InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) ); columns.emplace_back( v8::Object::New(isolate, GET_PROTOTYPE(v8::Object::New(isolate)), keys.data(), values.data(), keys.size() ) ); } info.GetReturnValue().Set( v8::Array::New(isolate, columns.data(), columns.size()) ); #endif } NODE_GETTER(Statement::JS_busy) { Statement* stmt = Unwrap<Statement>(info.This()); info.GetReturnValue().Set(stmt->alive && stmt->locked); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/mnehmos.synch.mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server