diff --git a/lib/database.js b/lib/database.js index 7d9314270..a1cf53353 100644 --- a/lib/database.js +++ b/lib/database.js @@ -20,6 +20,7 @@ function Database(filenameGiven, options) { const readonly = util.getBooleanOption(options, 'readonly'); const fileMustExist = util.getBooleanOption(options, 'fileMustExist'); const timeout = 'timeout' in options ? options.timeout : 5000; + const unsafe = util.getBooleanOption(options, 'unsafe'); const verbose = 'verbose' in options ? options.verbose : null; if (readonly && (memory || anonymous)) throw new TypeError('In-memory databases cannot be readonly'); @@ -43,7 +44,8 @@ function Database(filenameGiven, options) { .replace(/\/\/+/g, '/') + '?mode=memory&cache=shared'; } - return new CPPDatabase(filename, filenameGiven, memory || anonymous, readonly, fileMustExist, timeout, verbose || null); + + return new CPPDatabase(filename, filenameGiven, memory || anonymous, readonly, fileMustExist, timeout, unsafe, verbose || null); } util.wrap(CPPDatabase, 'pragma', require('./pragma')); diff --git a/src/better_sqlite3.cpp b/src/better_sqlite3.cpp index 10ae678f1..abc158a7c 100644 --- a/src/better_sqlite3.cpp +++ b/src/better_sqlite3.cpp @@ -93,34 +93,36 @@ ConstantString CS::readonly; #line 17 "./src/util/constants.lzz" ConstantString CS::name; #line 18 "./src/util/constants.lzz" -ConstantString CS::next; +ConstantString CS::unsafe; #line 19 "./src/util/constants.lzz" -ConstantString CS::length; +ConstantString CS::next; #line 20 "./src/util/constants.lzz" -ConstantString CS::done; +ConstantString CS::length; #line 21 "./src/util/constants.lzz" -ConstantString CS::value; +ConstantString CS::done; #line 22 "./src/util/constants.lzz" -ConstantString CS::changes; +ConstantString CS::value; #line 23 "./src/util/constants.lzz" -ConstantString CS::lastInsertRowid; +ConstantString CS::changes; #line 24 "./src/util/constants.lzz" -ConstantString CS::code; +ConstantString CS::lastInsertRowid; #line 25 "./src/util/constants.lzz" -ConstantString CS::statement; +ConstantString CS::code; #line 26 "./src/util/constants.lzz" -ConstantString CS::column; +ConstantString CS::statement; #line 27 "./src/util/constants.lzz" -ConstantString CS::table; +ConstantString CS::column; #line 28 "./src/util/constants.lzz" -ConstantString CS::type; +ConstantString CS::table; #line 29 "./src/util/constants.lzz" -ConstantString CS::totalPages; +ConstantString CS::type; #line 30 "./src/util/constants.lzz" +ConstantString CS::totalPages; +#line 31 "./src/util/constants.lzz" ConstantString CS::remainingPages; -#line 33 "./src/util/constants.lzz" +#line 34 "./src/util/constants.lzz" void CS::Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8::Local <v8 :: Object> module) -#line 33 "./src/util/constants.lzz" +#line 34 "./src/util/constants.lzz" { AddString(isolate, CS::database, "database"); AddString(isolate, CS::reader, "reader"); @@ -128,6 +130,7 @@ void CS::Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8::Loca AddString(isolate, CS::memory, "memory"); AddString(isolate, CS::readonly, "readonly"); AddString(isolate, CS::name, "name"); + AddString(isolate, CS::unsafe, "unsafe"); AddString(isolate, CS::next, "next"); AddString(isolate, CS::length, "length"); AddString(isolate, CS::done, "done"); @@ -230,30 +233,30 @@ void CS::Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8::Loca AddCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); AddCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); } -#line 143 "./src/util/constants.lzz" +#line 145 "./src/util/constants.lzz" v8::Local <v8::String> CS::InternalizedFromLatin1 (v8::Isolate * isolate, char const * str) -#line 143 "./src/util/constants.lzz" +#line 145 "./src/util/constants.lzz" { return v8::String::NewFromOneByte(isolate, reinterpret_cast<const uint8_t*>(str), v8::NewStringType::kInternalized).ToLocalChecked(); } -#line 146 "./src/util/constants.lzz" +#line 148 "./src/util/constants.lzz" void CS::AddString (v8::Isolate * isolate, ConstantString & constant, char const * str) -#line 146 "./src/util/constants.lzz" +#line 148 "./src/util/constants.lzz" { constant.Reset(isolate, InternalizedFromLatin1(isolate, str)); } -#line 149 "./src/util/constants.lzz" +#line 151 "./src/util/constants.lzz" void CS::AddCode (v8::Isolate * isolate, int code, char const * str) -#line 149 "./src/util/constants.lzz" +#line 151 "./src/util/constants.lzz" { codes.emplace(std::piecewise_construct, std::forward_as_tuple(code), std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); } -#line 152 "./src/util/constants.lzz" +#line 154 "./src/util/constants.lzz" CS::CS (char _) -#line 152 "./src/util/constants.lzz" +#line 154 "./src/util/constants.lzz" { assert(false); } -#line 154 "./src/util/constants.lzz" +#line 156 "./src/util/constants.lzz" std::unordered_map <int, ConstantString> CS::codes; #line 19 "./src/util/bind-map.lzz" BindMap::Pair::Pair (v8::Isolate * isolate, char const * _name, int _index) @@ -400,43 +403,43 @@ bool Database::Log (v8::Isolate * isolate, sqlite3_stmt * handle) if (expanded) sqlite3_free(expanded); return failed; } -#line 67 "./src/objects/database.lzz" +#line 68 "./src/objects/database.lzz" Database::~ Database () -#line 67 "./src/objects/database.lzz" +#line 68 "./src/objects/database.lzz" { if (open) dbs.erase(this); CloseHandles(); } -#line 75 "./src/objects/database.lzz" +#line 76 "./src/objects/database.lzz" bool Database::CompareDatabase::operator () (Database const * const a, Database const * const b) const -#line 75 "./src/objects/database.lzz" +#line 76 "./src/objects/database.lzz" { return a < b; } -#line 81 "./src/objects/database.lzz" +#line 82 "./src/objects/database.lzz" bool Database::CompareStatement::operator () (Statement const * const a, Statement const * const b) const -#line 81 "./src/objects/database.lzz" +#line 82 "./src/objects/database.lzz" { return Statement::Compare(a, b); } -#line 87 "./src/objects/database.lzz" +#line 88 "./src/objects/database.lzz" bool Database::CompareBackup::operator () (Backup const * const a, Backup const * const b) const -#line 87 "./src/objects/database.lzz" +#line 88 "./src/objects/database.lzz" { return Backup::Compare(a, b); } -#line 92 "./src/objects/database.lzz" -Database::Database (sqlite3 * _db_handle, v8::Isolate * isolate, v8::Local <v8::Value> _logger) -#line 92 "./src/objects/database.lzz" - : node::ObjectWrap (), db_handle (_db_handle), open (true), busy (false), pragma_mode (false), safe_ints (false), was_js_error (false), has_logger (_logger->IsFunction()), iterators (0), logger (isolate, _logger), stmts (), backups () -#line 103 "./src/objects/database.lzz" +#line 93 "./src/objects/database.lzz" +Database::Database (sqlite3 * _db_handle, v8::Isolate * isolate, bool _unsafe, v8::Local <v8::Value> _logger) +#line 93 "./src/objects/database.lzz" + : node::ObjectWrap (), db_handle (_db_handle), open (true), busy (false), pragma_mode (false), safe_ints (false), was_js_error (false), has_logger (_logger->IsFunction()), iterators (0), unsafe_mode (_unsafe), logger (isolate, _logger), stmts (), backups () +#line 105 "./src/objects/database.lzz" { assert(_db_handle != NULL); dbs.insert(this); } -#line 108 "./src/objects/database.lzz" +#line 110 "./src/objects/database.lzz" void Database::Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8::Local <v8 :: Object> module) -#line 108 "./src/objects/database.lzz" +#line 110 "./src/objects/database.lzz" { v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate, JS_new); t->InstanceTemplate()->SetInternalFieldCount(1); @@ -460,9 +463,9 @@ void Database::Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8 SqliteError.Reset(isolate, v8::Local<v8::Function>::Cast(Require(module, "../../lib/sqlite-error"))); node::AtExit(Database::AtExit); } -#line 132 "./src/objects/database.lzz" +#line 134 "./src/objects/database.lzz" void Database::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 132 "./src/objects/database.lzz" +#line 134 "./src/objects/database.lzz" { assert(info.IsConstructCall()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > filename = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; @@ -471,7 +474,8 @@ void Database::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info) if ( info . Length ( ) <= ( 3 ) || ! info [ 3 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fourth" " argument to be " "a boolean" ) ; bool readonly = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 3 ] ) -> Value ( ) ; if ( info . Length ( ) <= ( 4 ) || ! info [ 4 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fifth" " argument to be " "a boolean" ) ; bool must_exist = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 4 ] ) -> Value ( ) ; if ( info . Length ( ) <= ( 5 ) || ! info [ 5 ] -> IsInt32 ( ) ) return ThrowTypeError ( "Expected " "sixth" " argument to be " "a 32-bit signed integer" ) ; int timeout = v8 :: Local < v8 :: Int32 > :: Cast ( info [ 5 ] ) -> Value ( ) ; - if ( info . Length ( ) <= ( 6 ) ) return ThrowTypeError ( "Expected a " "seventh" " argument" ) ; v8 :: Local < v8 :: Value > logger = info [ 6 ] ; + if ( info . Length ( ) <= ( 6 ) || ! info [ 6 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "seventh" " argument to be " "a boolean" ) ; bool unsafe = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 6 ] ) -> Value ( ) ; + if ( info . Length ( ) <= ( 7 ) ) return ThrowTypeError ( "Expected a " "eighth" " argument" ) ; v8 :: Local < v8 :: Value > logger = info [ 7 ] ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; sqlite3* db_handle; @@ -498,31 +502,32 @@ void Database::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info) assert(status == SQLITE_OK); v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ; - Database* db = new Database(db_handle, isolate, logger); + Database* db = new Database(db_handle, isolate, unsafe, logger); db->Wrap(info.This()); SetFrozen(isolate, ctx, info.This(), CS::memory, in_memory ? v8::True(isolate) : v8::False(isolate)); SetFrozen(isolate, ctx, info.This(), CS::readonly, readonly ? v8::True(isolate) : v8::False(isolate)); SetFrozen(isolate, ctx, info.This(), CS::name, filenameGiven); + SetFrozen(isolate, ctx, info.This(), CS::unsafe, unsafe ? v8::True(isolate) : v8::False(isolate)); info.GetReturnValue().Set(info.This()); } -#line 176 "./src/objects/database.lzz" +#line 180 "./src/objects/database.lzz" void Database::JS_prepare (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 176 "./src/objects/database.lzz" +#line 180 "./src/objects/database.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > source = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; v8::MaybeLocal<v8::Object> maybe_statement = Statement::New( info . GetIsolate ( ) , info.This(), source); if (!maybe_statement.IsEmpty()) info.GetReturnValue().Set(maybe_statement.ToLocalChecked()); } -#line 182 "./src/objects/database.lzz" +#line 186 "./src/objects/database.lzz" void Database::JS_exec (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 182 "./src/objects/database.lzz" +#line 186 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > source = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + if ( db -> iterators && ! db -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; db->busy = true; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if (db->Log(isolate, source)) { @@ -536,20 +541,20 @@ void Database::JS_exec (v8::FunctionCallbackInfo <v8 :: Value> const & info) if (status == SQLITE_OK) info.GetReturnValue().Set(info.This()); else db->ThrowDatabaseError(); } -#line 202 "./src/objects/database.lzz" +#line 206 "./src/objects/database.lzz" void Database::JS_pragma (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 202 "./src/objects/database.lzz" +#line 206 "./src/objects/database.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; node :: ObjectWrap :: Unwrap < Database > ( info . This ( ) ) -> pragma_mode = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 0 ] ) -> Value ( ) ; } -#line 206 "./src/objects/database.lzz" +#line 210 "./src/objects/database.lzz" void Database::JS_checkpoint (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 206 "./src/objects/database.lzz" +#line 210 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + if ( db -> iterators && ! db -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; sqlite3* db_handle = db->db_handle; if (info.Length()) { @@ -587,9 +592,9 @@ void Database::JS_checkpoint (v8::FunctionCallbackInfo <v8 :: Value> const & inf } if (!threw_error) info.GetReturnValue().Set(info.This()); } -#line 249 "./src/objects/database.lzz" +#line 253 "./src/objects/database.lzz" void Database::JS_backup (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 249 "./src/objects/database.lzz" +#line 253 "./src/objects/database.lzz" { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > attachedName = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; if ( info . Length ( ) <= ( 1 ) || ! info [ 1 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "second" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > destFile = v8 :: Local < v8 :: String > :: Cast ( info [ 1 ] ) ; @@ -598,9 +603,9 @@ void Database::JS_backup (v8::FunctionCallbackInfo <v8 :: Value> const & info) v8::MaybeLocal<v8::Object> maybe_backup = Backup::New(isolate, info.This(), attachedName, destFile, v8::Boolean::New(isolate, unlink)); if (!maybe_backup.IsEmpty()) info.GetReturnValue().Set(maybe_backup.ToLocalChecked()); } -#line 258 "./src/objects/database.lzz" +#line 262 "./src/objects/database.lzz" void Database::JS_function (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 258 "./src/objects/database.lzz" +#line 262 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsFunction ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a function" ) ; v8 :: Local < v8 :: Function > fn = v8 :: Local < v8 :: Function > :: Cast ( info [ 0 ] ) ; @@ -610,7 +615,7 @@ void Database::JS_function (v8::FunctionCallbackInfo <v8 :: Value> const & info) if ( info . Length ( ) <= ( 4 ) || ! info [ 4 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "fifth" " argument to be " "a boolean" ) ; bool deterministic = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 4 ] ) -> Value ( ) ; if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + if ( db -> iterators && ! db -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; v8::String::Utf8Value name(EXTRACT_STRING(isolate, nameString)); @@ -622,9 +627,9 @@ void Database::JS_function (v8::FunctionCallbackInfo <v8 :: Value> const & info) } info.GetReturnValue().Set(info.This()); } -#line 280 "./src/objects/database.lzz" +#line 284 "./src/objects/database.lzz" void Database::JS_aggregate (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 280 "./src/objects/database.lzz" +#line 284 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if ( info . Length ( ) <= ( 0 ) ) return ThrowTypeError ( "Expected a " "first" " argument" ) ; v8 :: Local < v8 :: Value > start = info [ 0 ] ; @@ -637,7 +642,7 @@ void Database::JS_aggregate (v8::FunctionCallbackInfo <v8 :: Value> const & info if ( info . Length ( ) <= ( 7 ) || ! info [ 7 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "eighth" " argument to be " "a boolean" ) ; bool deterministic = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 7 ] ) -> Value ( ) ; if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + if ( db -> iterators && ! db -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; v8::String::Utf8Value name(EXTRACT_STRING(isolate, nameString)); @@ -651,15 +656,15 @@ void Database::JS_aggregate (v8::FunctionCallbackInfo <v8 :: Value> const & info } info.GetReturnValue().Set(info.This()); } -#line 307 "./src/objects/database.lzz" +#line 311 "./src/objects/database.lzz" void Database::JS_loadExtension (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 307 "./src/objects/database.lzz" +#line 311 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsString ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a string" ) ; v8 :: Local < v8 :: String > filenameString = v8 :: Local < v8 :: String > :: Cast ( info [ 0 ] ) ; if ( ! db -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + if ( db -> iterators && ! db -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; v8::String::Utf8Value filename(EXTRACT_STRING( info . GetIsolate ( ) , filenameString)); char* error; int status = sqlite3_load_extension(db->db_handle, *filename, NULL, &error); @@ -667,44 +672,44 @@ void Database::JS_loadExtension (v8::FunctionCallbackInfo <v8 :: Value> const & else ThrowSqliteError(error, status); sqlite3_free(error); } -#line 321 "./src/objects/database.lzz" +#line 325 "./src/objects/database.lzz" void Database::JS_close (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 321 "./src/objects/database.lzz" +#line 325 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if (db->open) { if ( db -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if ( db -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; + if ( db -> iterators && ! db -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; dbs.erase(db); db->CloseHandles(); } info.GetReturnValue().Set(info.This()); } -#line 332 "./src/objects/database.lzz" +#line 336 "./src/objects/database.lzz" void Database::JS_defaultSafeIntegers (v8::FunctionCallbackInfo <v8 :: Value> const & info) -#line 332 "./src/objects/database.lzz" +#line 336 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); if (info.Length() == 0) db->safe_ints = true; else { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; db -> safe_ints = v8 :: Local < v8 :: Boolean > :: Cast ( info [ 0 ] ) -> Value ( ) ; } info.GetReturnValue().Set(info.This()); } -#line 339 "./src/objects/database.lzz" +#line 343 "./src/objects/database.lzz" void Database::JS_open (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info) -#line 339 "./src/objects/database.lzz" +#line 343 "./src/objects/database.lzz" { info.GetReturnValue().Set( node :: ObjectWrap :: Unwrap <Database>(info.This())->open); } -#line 343 "./src/objects/database.lzz" +#line 347 "./src/objects/database.lzz" void Database::JS_inTransaction (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info) -#line 343 "./src/objects/database.lzz" +#line 347 "./src/objects/database.lzz" { Database* db = node :: ObjectWrap :: Unwrap <Database>(info.This()); info.GetReturnValue().Set(db->open && !static_cast<bool>(sqlite3_get_autocommit(db->db_handle))); } -#line 348 "./src/objects/database.lzz" +#line 352 "./src/objects/database.lzz" void Database::CloseHandles () -#line 348 "./src/objects/database.lzz" +#line 352 "./src/objects/database.lzz" { if (open) { open = false; @@ -716,20 +721,20 @@ void Database::CloseHandles () assert(status == SQLITE_OK); ((void)status); } } -#line 360 "./src/objects/database.lzz" +#line 364 "./src/objects/database.lzz" void Database::AtExit (void * _) -#line 360 "./src/objects/database.lzz" +#line 364 "./src/objects/database.lzz" { for (Database* db : dbs) db->CloseHandles(); dbs.clear(); } -#line 365 "./src/objects/database.lzz" +#line 369 "./src/objects/database.lzz" std::set <Database*, Database::CompareDatabase> Database::dbs; -#line 366 "./src/objects/database.lzz" +#line 370 "./src/objects/database.lzz" v8::Persistent <v8::Function> Database::SqliteError; -#line 367 "./src/objects/database.lzz" +#line 371 "./src/objects/database.lzz" int const Database::MAX_BUFFER_SIZE; -#line 368 "./src/objects/database.lzz" +#line 372 "./src/objects/database.lzz" int const Database::MAX_STRING_SIZE; #line 6 "./src/objects/statement.lzz" v8::MaybeLocal <v8::Object> Statement::New (v8::Isolate * isolate, v8::Local <v8::Object> database, v8::Local <v8::String> source) @@ -829,7 +834,7 @@ void Statement::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info) Database* db = node :: ObjectWrap :: Unwrap <Database>(database); if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; - if (db->GetState()->pragma_mode) { if ( db -> GetState ( ) -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; } + if (db->GetState()->pragma_mode) { if ( db -> GetState ( ) -> iterators && ! db -> GetState ( ) -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; } v8 :: Isolate * isolate = info . GetIsolate ( ) ; v8::String::Value sql(EXTRACT_STRING(isolate, source)); @@ -864,7 +869,7 @@ void Statement::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info) void Statement::JS_run (v8::FunctionCallbackInfo <v8 :: Value> const & info) #line 139 "./src/objects/statement.lzz" { - Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( stmt -> returns_data ) return ThrowTypeError ( "This statement returns data. Use get(), all(), or iterate() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( db -> GetState ( ) -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ; + Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( stmt -> returns_data ) return ThrowTypeError ( "This statement returns data. Use get(), all(), or iterate() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( db -> GetState ( ) -> iterators && ! db -> GetState ( ) -> unsafe_mode ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ; sqlite3* db_handle = db->GetHandle(); int total_changes_before = sqlite3_total_changes(db_handle); diff --git a/src/better_sqlite3.hpp b/src/better_sqlite3.hpp index b4ebebebe..a97cceb25 100644 --- a/src/better_sqlite3.hpp +++ b/src/better_sqlite3.hpp @@ -23,7 +23,7 @@ #endif #line 153 "./src/util/macros.lzz" template <class T> using CopyablePersistent = v8::Persistent<T, v8::CopyablePersistentTraits<T>>; -#line 158 "./src/util/constants.lzz" +#line 160 "./src/util/constants.lzz" typedef v8::Persistent<v8::String> ConstantString; #define LZZ_INLINE inline #line 18 "./src/util/macros.lzz" @@ -80,46 +80,48 @@ class CS #line 17 "./src/util/constants.lzz" static ConstantString name; #line 18 "./src/util/constants.lzz" - static ConstantString next; + static ConstantString unsafe; #line 19 "./src/util/constants.lzz" - static ConstantString length; + static ConstantString next; #line 20 "./src/util/constants.lzz" - static ConstantString done; + static ConstantString length; #line 21 "./src/util/constants.lzz" - static ConstantString value; + static ConstantString done; #line 22 "./src/util/constants.lzz" - static ConstantString changes; + static ConstantString value; #line 23 "./src/util/constants.lzz" - static ConstantString lastInsertRowid; + static ConstantString changes; #line 24 "./src/util/constants.lzz" - static ConstantString code; + static ConstantString lastInsertRowid; #line 25 "./src/util/constants.lzz" - static ConstantString statement; + static ConstantString code; #line 26 "./src/util/constants.lzz" - static ConstantString column; + static ConstantString statement; #line 27 "./src/util/constants.lzz" - static ConstantString table; + static ConstantString column; #line 28 "./src/util/constants.lzz" - static ConstantString type; + static ConstantString table; #line 29 "./src/util/constants.lzz" - static ConstantString totalPages; + static ConstantString type; #line 30 "./src/util/constants.lzz" + static ConstantString totalPages; +#line 31 "./src/util/constants.lzz" static ConstantString remainingPages; -#line 32 "./src/util/constants.lzz" -private: #line 33 "./src/util/constants.lzz" +private: +#line 34 "./src/util/constants.lzz" friend void RegisterModule (v8::Local <v8 :: Object> exports, v8::Local <v8 :: Object> module); -#line 33 "./src/util/constants.lzz" +#line 34 "./src/util/constants.lzz" static void Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8::Local <v8 :: Object> module); -#line 143 "./src/util/constants.lzz" +#line 145 "./src/util/constants.lzz" static v8::Local <v8::String> InternalizedFromLatin1 (v8::Isolate * isolate, char const * str); -#line 146 "./src/util/constants.lzz" +#line 148 "./src/util/constants.lzz" static void AddString (v8::Isolate * isolate, ConstantString & constant, char const * str); -#line 149 "./src/util/constants.lzz" +#line 151 "./src/util/constants.lzz" static void AddCode (v8::Isolate * isolate, int code, char const * str); -#line 152 "./src/util/constants.lzz" - explicit CS (char _); #line 154 "./src/util/constants.lzz" + explicit CS (char _); +#line 156 "./src/util/constants.lzz" static std::unordered_map <int, ConstantString> codes; }; #line 1 "./src/util/bind-map.lzz" @@ -248,104 +250,108 @@ class Database : public node::ObjectWrap bool was_js_error; #line 57 "./src/objects/database.lzz" unsigned short int iterators; +#line 58 "./src/objects/database.lzz" + bool const unsafe_mode; }; -#line 60 "./src/objects/database.lzz" +#line 61 "./src/objects/database.lzz" State * GetState (); -#line 63 "./src/objects/database.lzz" +#line 64 "./src/objects/database.lzz" sqlite3 * GetHandle (); -#line 67 "./src/objects/database.lzz" +#line 68 "./src/objects/database.lzz" ~ Database (); -#line 72 "./src/objects/database.lzz" +#line 73 "./src/objects/database.lzz" private: -#line 74 "./src/objects/database.lzz" +#line 75 "./src/objects/database.lzz" class CompareDatabase { -#line 74 "./src/objects/database.lzz" - public: #line 75 "./src/objects/database.lzz" + public: +#line 76 "./src/objects/database.lzz" bool operator () (Database const * const a, Database const * const b) const; }; -#line 80 "./src/objects/database.lzz" +#line 81 "./src/objects/database.lzz" class CompareStatement { -#line 80 "./src/objects/database.lzz" - public: #line 81 "./src/objects/database.lzz" + public: +#line 82 "./src/objects/database.lzz" bool operator () (Statement const * const a, Statement const * const b) const; }; -#line 86 "./src/objects/database.lzz" +#line 87 "./src/objects/database.lzz" class CompareBackup { -#line 86 "./src/objects/database.lzz" - public: #line 87 "./src/objects/database.lzz" + public: +#line 88 "./src/objects/database.lzz" bool operator () (Backup const * const a, Backup const * const b) const; }; -#line 92 "./src/objects/database.lzz" - explicit Database (sqlite3 * _db_handle, v8::Isolate * isolate, v8::Local <v8::Value> _logger); -#line 108 "./src/objects/database.lzz" +#line 93 "./src/objects/database.lzz" + explicit Database (sqlite3 * _db_handle, v8::Isolate * isolate, bool _unsafe, v8::Local <v8::Value> _logger); +#line 110 "./src/objects/database.lzz" friend void RegisterModule (v8::Local <v8 :: Object> exports, v8::Local <v8 :: Object> module); -#line 108 "./src/objects/database.lzz" +#line 110 "./src/objects/database.lzz" static void Init (v8::Isolate * isolate, v8::Local <v8 :: Object> exports, v8::Local <v8 :: Object> module); -#line 132 "./src/objects/database.lzz" +#line 134 "./src/objects/database.lzz" static void JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 176 "./src/objects/database.lzz" +#line 180 "./src/objects/database.lzz" static void JS_prepare (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 182 "./src/objects/database.lzz" +#line 186 "./src/objects/database.lzz" static void JS_exec (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 202 "./src/objects/database.lzz" - static void JS_pragma (v8::FunctionCallbackInfo <v8 :: Value> const & info); #line 206 "./src/objects/database.lzz" + static void JS_pragma (v8::FunctionCallbackInfo <v8 :: Value> const & info); +#line 210 "./src/objects/database.lzz" static void JS_checkpoint (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 249 "./src/objects/database.lzz" +#line 253 "./src/objects/database.lzz" static void JS_backup (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 258 "./src/objects/database.lzz" +#line 262 "./src/objects/database.lzz" static void JS_function (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 280 "./src/objects/database.lzz" +#line 284 "./src/objects/database.lzz" static void JS_aggregate (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 307 "./src/objects/database.lzz" +#line 311 "./src/objects/database.lzz" static void JS_loadExtension (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 321 "./src/objects/database.lzz" +#line 325 "./src/objects/database.lzz" static void JS_close (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 332 "./src/objects/database.lzz" +#line 336 "./src/objects/database.lzz" static void JS_defaultSafeIntegers (v8::FunctionCallbackInfo <v8 :: Value> const & info); -#line 339 "./src/objects/database.lzz" - static void JS_open (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info); #line 343 "./src/objects/database.lzz" + static void JS_open (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info); +#line 347 "./src/objects/database.lzz" static void JS_inTransaction (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info); -#line 348 "./src/objects/database.lzz" +#line 352 "./src/objects/database.lzz" void CloseHandles (); -#line 360 "./src/objects/database.lzz" +#line 364 "./src/objects/database.lzz" static void AtExit (void * _); -#line 365 "./src/objects/database.lzz" +#line 369 "./src/objects/database.lzz" static std::set <Database*, Database::CompareDatabase> dbs; -#line 366 "./src/objects/database.lzz" +#line 370 "./src/objects/database.lzz" static v8::Persistent <v8::Function> SqliteError; -#line 367 "./src/objects/database.lzz" +#line 371 "./src/objects/database.lzz" static int const MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX ? INT_MAX : static_cast<int>(node::Buffer::kMaxLength); -#line 368 "./src/objects/database.lzz" +#line 372 "./src/objects/database.lzz" static int const MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX ? INT_MAX : static_cast<int>(v8::String::kMaxLength); -#line 370 "./src/objects/database.lzz" +#line 374 "./src/objects/database.lzz" sqlite3 * const db_handle; -#line 371 "./src/objects/database.lzz" +#line 375 "./src/objects/database.lzz" bool open; -#line 372 "./src/objects/database.lzz" +#line 376 "./src/objects/database.lzz" bool busy; -#line 373 "./src/objects/database.lzz" +#line 377 "./src/objects/database.lzz" bool pragma_mode; -#line 374 "./src/objects/database.lzz" +#line 378 "./src/objects/database.lzz" bool safe_ints; -#line 375 "./src/objects/database.lzz" +#line 379 "./src/objects/database.lzz" bool was_js_error; -#line 376 "./src/objects/database.lzz" +#line 380 "./src/objects/database.lzz" bool const has_logger; -#line 377 "./src/objects/database.lzz" +#line 381 "./src/objects/database.lzz" unsigned short int iterators; -#line 378 "./src/objects/database.lzz" +#line 382 "./src/objects/database.lzz" + bool const unsafe_mode; +#line 383 "./src/objects/database.lzz" CopyablePersistent <v8::Value> const logger; -#line 379 "./src/objects/database.lzz" +#line 384 "./src/objects/database.lzz" std::set <Statement*, Database::CompareStatement> stmts; -#line 380 "./src/objects/database.lzz" +#line 385 "./src/objects/database.lzz" std::set <Backup*, Database::CompareBackup> backups; }; #line 1 "./src/objects/statement.lzz" @@ -839,15 +845,15 @@ LZZ_INLINE void Database::RemoveBackup (Backup * backup) #line 47 "./src/objects/database.lzz" { backups.erase(backup); } -#line 60 "./src/objects/database.lzz" +#line 61 "./src/objects/database.lzz" LZZ_INLINE Database::State * Database::GetState () -#line 60 "./src/objects/database.lzz" +#line 61 "./src/objects/database.lzz" { return reinterpret_cast<State*>(&open); } -#line 63 "./src/objects/database.lzz" +#line 64 "./src/objects/database.lzz" LZZ_INLINE sqlite3 * Database::GetHandle () -#line 63 "./src/objects/database.lzz" +#line 64 "./src/objects/database.lzz" { return db_handle; } diff --git a/src/objects/database.lzz b/src/objects/database.lzz index 21fe1757b..1984ed23d 100644 --- a/src/objects/database.lzz +++ b/src/objects/database.lzz @@ -55,6 +55,7 @@ public: const bool safe_ints; bool was_js_error; unsigned short iterators; + const bool unsafe_mode; }; inline State* GetState() { @@ -89,7 +90,7 @@ private: } }; - explicit Database(sqlite3* _db_handle, v8::Isolate* isolate, v8::Local<v8::Value> _logger) : node::ObjectWrap(), + explicit Database(sqlite3* _db_handle, v8::Isolate* isolate, bool _unsafe, v8::Local<v8::Value> _logger) : node::ObjectWrap(), db_handle(_db_handle), open(true), busy(false), @@ -98,6 +99,7 @@ private: was_js_error(false), has_logger(_logger->IsFunction()), iterators(0), + unsafe_mode(_unsafe), logger(isolate, _logger), stmts(), backups() { @@ -137,7 +139,8 @@ private: REQUIRE_ARGUMENT_BOOLEAN(fourth, bool readonly); REQUIRE_ARGUMENT_BOOLEAN(fifth, bool must_exist); REQUIRE_ARGUMENT_INT32(sixth, int timeout); - REQUIRE_ARGUMENT_ANY(seventh, v8::Local<v8::Value> logger); + REQUIRE_ARGUMENT_BOOLEAN(seventh, bool unsafe); + REQUIRE_ARGUMENT_ANY(eighth, v8::Local<v8::Value> logger); UseIsolate; sqlite3* db_handle; @@ -164,11 +167,12 @@ private: assert(status == SQLITE_OK); UseContext; - Database* db = new Database(db_handle, isolate, logger); + Database* db = new Database(db_handle, isolate, unsafe, logger); db->Wrap(info.This()); SetFrozen(isolate, ctx, info.This(), CS::memory, in_memory ? v8::True(isolate) : v8::False(isolate)); SetFrozen(isolate, ctx, info.This(), CS::readonly, readonly ? v8::True(isolate) : v8::False(isolate)); SetFrozen(isolate, ctx, info.This(), CS::name, filenameGiven); + SetFrozen(isolate, ctx, info.This(), CS::unsafe, unsafe ? v8::True(isolate) : v8::False(isolate)); info.GetReturnValue().Set(info.This()); } @@ -375,6 +379,7 @@ private: bool was_js_error; const bool has_logger; unsigned short iterators; + const bool unsafe_mode; const CopyablePersistent<v8::Value> logger; std::set<Statement*, Database::CompareStatement> stmts; std::set<Backup*, Database::CompareBackup> backups; diff --git a/src/util/constants.lzz b/src/util/constants.lzz index 4c87f3f26..71e67549d 100644 --- a/src/util/constants.lzz +++ b/src/util/constants.lzz @@ -15,6 +15,7 @@ public: static ConstantString memory; static ConstantString readonly; static ConstantString name; + static ConstantString unsafe; static ConstantString next; static ConstantString length; static ConstantString done; @@ -37,6 +38,7 @@ private: AddString(isolate, CS::memory, "memory"); AddString(isolate, CS::readonly, "readonly"); AddString(isolate, CS::name, "name"); + AddString(isolate, CS::unsafe, "unsafe"); AddString(isolate, CS::next, "next"); AddString(isolate, CS::length, "length"); AddString(isolate, CS::done, "done"); diff --git a/src/util/macros.lzz b/src/util/macros.lzz index 437a87126..da1029ce1 100644 --- a/src/util/macros.lzz +++ b/src/util/macros.lzz @@ -64,7 +64,7 @@ void ThrowRangeError(const char* message) { EasyIsolate; isolate->ThrowException if (db->busy) \ return ThrowTypeError("This database connection is busy executing a query") #define REQUIRE_DATABASE_NO_ITERATORS(db) \ - if (db->iterators) \ + if (db->iterators && !db->unsafe_mode) \ return ThrowTypeError("This database connection is busy executing a query") #define REQUIRE_STATEMENT_NOT_LOCKED(stmt) \ if (stmt->locked) \ diff --git a/test/10.database.open.js b/test/10.database.open.js index ba43aa2d3..845aab181 100644 --- a/test/10.database.open.js +++ b/test/10.database.open.js @@ -51,6 +51,7 @@ describe('new Database()', function () { expect(db.readonly).to.be.false; expect(db.open).to.be.true; expect(db.inTransaction).to.be.false; + expect(db.unsafe).to.be.false; expect(existsSync(':memory:')).to.be.false; }); it('should allow named in-memory databases to be created', function () { @@ -61,6 +62,7 @@ describe('new Database()', function () { expect(db.readonly).to.be.false; expect(db.open).to.be.true; expect(db.inTransaction).to.be.false; + expect(db.unsafe).to.be.false; expect(existsSync(util.current())).to.be.false; }); it('should allow disk-bound databases to be created', function () { @@ -71,6 +73,7 @@ describe('new Database()', function () { expect(db.readonly).to.be.false; expect(db.open).to.be.true; expect(db.inTransaction).to.be.false; + expect(db.unsafe).to.be.false; expect(existsSync(util.current())).to.be.true; }); it('should not allow conflicting in-memory options', function () { @@ -90,6 +93,7 @@ describe('new Database()', function () { expect(db.readonly).to.be.true; expect(db.open).to.be.true; expect(db.inTransaction).to.be.false; + expect(db.unsafe).to.be.false; expect(existsSync(util.current())).to.be.true; }); it('should not allow the "readonly" option for in-memory databases', function () { @@ -110,6 +114,7 @@ describe('new Database()', function () { expect(db.readonly).to.be.false; expect(db.open).to.be.true; expect(db.inTransaction).to.be.false; + expect(db.unsafe).to.be.false; expect(existsSync(util.current())).to.be.true; }); util.itUnix('should accept the "timeout" option', function () { @@ -145,6 +150,16 @@ describe('new Database()', function () { expect(existsSync(filepath)).to.be.false; expect(existsSync(util.current())).to.be.false; }); + it('should accept the "unsafe" option', function () { + const db = this.db = new Database(util.current(), { unsafe: true }); + expect(db.name).to.equal(util.current()); + expect(db.memory).to.be.false; + expect(db.readonly).to.be.false; + expect(db.open).to.be.true; + expect(db.inTransaction).to.be.false; + expect(db.unsafe).to.be.true; + expect(existsSync(util.current())).to.be.true; + }); it('should have a proper prototype chain', function () { const db = this.db = new Database(util.next()); expect(db).to.be.an.instanceof(Database); diff --git a/test/44.unsafe.mode.js b/test/44.unsafe.mode.js new file mode 100644 index 000000000..2ea4e99b3 --- /dev/null +++ b/test/44.unsafe.mode.js @@ -0,0 +1,50 @@ +'use strict' +const Database = require('../.'); + +// writing to a table while reading the same table within an iterator. +function writeWhileIterating(db) { + // create table & prepared statements + db.prepare(`CREATE TABLE test (a TEXT)`).run(); + const stmt = { + insert: db.prepare(`INSERT INTO test VALUES (?)`), + each: db.prepare(`SELECT * FROM test WHERE a == 'FOO'`) + }; + + // populate table with two FOO values + stmt.insert.run('FOO'); + stmt.insert.run('FOO'); + + // iterate and insert from/to the same table at the same time + // note: we insert 'BAR' which is not matched by the iterator. + // warning: this is unsafe, if you insert 'FOO' here then the + // database connection will hang forever, you were warned! + for (let _ of stmt.each.iterate()) { + stmt.insert.run('BAR'); + } +}; + +describe('unsafe mode', () => { + afterEach(() => { + if (this.db) this.db.close(); + }); + + it('disabled: writeWhileIterating', () => { + this.db = new Database(util.next(), { unsafe: false }); + expect(this.db.unsafe).to.be.false; + expect(() => { + writeWhileIterating(this.db); + }).to.throw(TypeError); + }); + it('enabled: writeWhileIterating', () => { + this.db = new Database(util.next(), { unsafe: true }); + expect(this.db.unsafe).to.be.true; + expect(() => { + writeWhileIterating(this.db); + }).not.throw(); + const aggs = this.db.prepare(`SELECT a, COUNT(a) AS total FROM test GROUP BY a`).all(); + expect(aggs).to.eql([ + { a: 'BAR', total: 2 }, // two 'BAR' rows + { a: 'FOO', total: 2 } // two 'FOO' rows + ]); + }); +});