Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: withUndefinedBehavior block #371

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
- [Database#aggregate()](#aggregatename-options---this)
- [Database#loadExtension()](#loadextensionpath---this)
- [Database#exec()](#execstring---this)
- [Database#withUndefinedBehavior()](#withUndefinedBehaviorfunction---this)
- [Database#close()](#close---this)
- [Properties](#properties)

@@ -263,6 +264,18 @@ const migration = fs.readFileSync('migrate-schema.sql', 'utf8');
db.exec(migration);
```

### .withUndefinedBehavior(*function*) -> *this*

Immediately synchronously executes the given function. Inside the function operations with potentially undefined behavior (as defined by the SQLite documentation) are allowed. This means certain sanity checks that better-sqlite3 performs are disabled. For example doing writes inside an iterator or executing statements inside the implementation of a user-defined function.

Only use `withUndefinedBehavior` if you absolutely know what you are doing. Use it as low as possible in your call chain to only cover the absolute minimum number of operations you need to do.

```js
db.withUndefinedBehavior(() => {
// TODO: docs
});
```

### .close() -> *this*

Closes the database connection. After invoking this method, no statements can be created or executed.
20 changes: 18 additions & 2 deletions src/objects/database.lzz
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@ public:
const bool safe_ints;
bool was_js_error;
unsigned short iterators;
unsigned short undefinedBehaviorBlocks;
};

inline State* GetState() {
@@ -98,6 +99,7 @@ private:
was_js_error(false),
has_logger(_logger->IsFunction()),
iterators(0),
undefinedBehaviorBlocks(0),
logger(isolate, _logger),
stmts(),
backups() {
@@ -118,6 +120,7 @@ private:
NODE_SET_PROTOTYPE_METHOD(t, "function", JS_function);
NODE_SET_PROTOTYPE_METHOD(t, "aggregate", JS_aggregate);
NODE_SET_PROTOTYPE_METHOD(t, "loadExtension", JS_loadExtension);
NODE_SET_PROTOTYPE_METHOD(t, "withUndefinedBehavior", JS_withUndefinedBehavior);
NODE_SET_PROTOTYPE_METHOD(t, "close", JS_close);
NODE_SET_PROTOTYPE_METHOD(t, "defaultSafeIntegers", JS_defaultSafeIntegers);
NODE_SET_PROTOTYPE_GETTER(t, "open", JS_open);
@@ -183,8 +186,8 @@ private:
Database* db = Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source);
REQUIRE_DATABASE_OPEN(db);
REQUIRE_DATABASE_NOT_BUSY(db);
REQUIRE_DATABASE_NO_ITERATORS(db);
PREFER_DATABASE_NOT_BUSY(db);
PREFER_DATABASE_NO_ITERATORS(db);
db->busy = true;
UseIsolate;
if (db->Log(isolate, source)) {
@@ -318,6 +321,18 @@ private:
sqlite3_free(error);
}

NODE_METHOD(JS_withUndefinedBehavior) {
Database* db = Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> fn);
assert(db->undefinedBehaviorBlocks < USHRT_MAX);
db->undefinedBehaviorBlocks += 1;
UseIsolate;
v8::Local<v8::Function>::Cast(v8::Local<v8::Value>::New(isolate, fn))
->Call(OnlyContext, v8::Undefined(isolate), 0, NULL);
assert(db->undefinedBehaviorBlocks > 0);
db->undefinedBehaviorBlocks -= 1;
}

NODE_METHOD(JS_close) {
Database* db = Unwrap<Database>(info.This());
if (db->open) {
@@ -375,6 +390,7 @@ private:
bool was_js_error;
const bool has_logger;
unsigned short iterators;
unsigned short undefinedBehaviorBlocks;
const CopyablePersistent<v8::Value> logger;
std::set<Statement*, Database::CompareStatement> stmts;
std::set<Backup*, Database::CompareBackup> backups;
4 changes: 2 additions & 2 deletions src/objects/statement-iterator.lzz
Original file line number Diff line number Diff line change
@@ -68,14 +68,14 @@ private:

NODE_METHOD(JS_next) {
StatementIterator* iter = Unwrap<StatementIterator>(info.This());
REQUIRE_DATABASE_NOT_BUSY(iter->db_state);
PREFER_DATABASE_NOT_BUSY(iter->db_state);
if (iter->alive) iter->Next(info);
else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
}

NODE_METHOD(JS_return) {
StatementIterator* iter = Unwrap<StatementIterator>(info.This());
REQUIRE_DATABASE_NOT_BUSY(iter->db_state);
PREFER_DATABASE_NOT_BUSY(iter->db_state);
if (iter->alive) iter->Return(info);
else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
}
6 changes: 6 additions & 0 deletions src/util/macros.lzz
Original file line number Diff line number Diff line change
@@ -63,9 +63,15 @@ void ThrowRangeError(const char* message) { EasyIsolate; isolate->ThrowException
#define REQUIRE_DATABASE_NOT_BUSY(db) \
if (db->busy) \
return ThrowTypeError("This database connection is busy executing a query")
#define PREFER_DATABASE_NOT_BUSY(db) \
if (db->busy && !db->undefinedBehaviorBlocks) \
return ThrowTypeError("This database connection is busy executing a query")
#define REQUIRE_DATABASE_NO_ITERATORS(db) \
if (db->iterators) \
return ThrowTypeError("This database connection is busy executing a query")
#define PREFER_DATABASE_NO_ITERATORS(db) \
if (db->iterators && !db->undefinedBehaviorBlocks) \
return ThrowTypeError("This database connection is busy executing a query")
#define REQUIRE_STATEMENT_NOT_LOCKED(stmt) \
if (stmt->locked) \
return ThrowTypeError("This statement is busy executing a query")
2 changes: 1 addition & 1 deletion src/util/query-macros.lzz
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
sqlite3_stmt* handle = stmt->handle; \
Database* db = stmt->db; \
REQUIRE_DATABASE_OPEN(db->GetState()); \
REQUIRE_DATABASE_NOT_BUSY(db->GetState()); \
PREFER_DATABASE_NOT_BUSY(db->GetState()); \
MUTATE_CHECK(); \
const bool bound = stmt->bound; \
if (!bound) { \
279 changes: 151 additions & 128 deletions test/42.integrity.js

Large diffs are not rendered by default.