384 lines
13 KiB
C++
384 lines
13 KiB
C++
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);
|
|
}
|