| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -88,6 +88,17 @@ inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate, | |||
| 88 | 88 | } \ | |
| 89 | 89 | } while (0) | |
| 90 | 90 | ||
| 91 | + #define RESET_OR_THROW(isolate, db, stmt, ret) \ | ||
| 92 | + CHECK_ERROR_OR_THROW((isolate), (db), sqlite3_reset((stmt)), SQLITE_OK, (ret)) | ||
| 93 | + | ||
| 94 | + // Surface deferred SQLite errors that sqlite3_reset() returns from the prior | ||
| 95 | + // sqlite3_step(). Disables the safety-net reset guard via |needs_reset|. | ||
| 96 | + #define RESET_AND_CHECK(isolate, db, stmt, needs_reset, ret) \ | ||
| 97 | + do { \ | ||
| 98 | + (needs_reset) = false; \ | ||
| 99 | + RESET_OR_THROW((isolate), (db), (stmt), (ret)); \ | ||
| 100 | + } while (0) | ||
| 101 | + | ||
| 91 | 102 | #define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \ | |
| 92 | 103 | do { \ | |
| 93 | 104 | if ((condition)) { \ | |
@@ -3020,9 +3031,20 @@ MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env, | |||
| 3020 | 3031 | bool use_big_ints) { | |
| 3021 | 3032 | Isolate* isolate = env->isolate(); | |
| 3022 | 3033 | EscapableHandleScope scope(isolate); | |
| 3023 | - sqlite3_step(stmt); | ||
| 3024 | - int r = sqlite3_reset(stmt); | ||
| 3025 | - CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, MaybeLocal<Object>()); | ||
| 3034 | + bool needs_reset = true; | ||
| 3035 | + auto reset = OnScopeLeave([&]() { | ||
| 3036 | + if (needs_reset) sqlite3_reset(stmt); | ||
| 3037 | + }); | ||
| 3038 | + | ||
| 3039 | + int step_r = sqlite3_step(stmt); | ||
| 3040 | + // SQLITE_ROW is accepted here (and discarded) so that run() can still be | ||
| 3041 | + // used on RETURNING/SELECT statements, matching prior behavior of | ||
| 3042 | + // ignoring the step result entirely. | ||
| 3043 | + if (step_r != SQLITE_DONE && step_r != SQLITE_ROW) { | ||
| 3044 | + THROW_ERR_SQLITE_ERROR(isolate, db); | ||
| 3045 | + return MaybeLocal<Object>(); | ||
| 3046 | + } | ||
| 3047 | + RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Object>()); | ||
| 3026 | 3048 | ||
| 3027 | 3049 | sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection()); | |
| 3028 | 3050 | sqlite3_int64 changes = sqlite3_changes64(db->Connection()); | |
@@ -3096,18 +3118,25 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, | |||
| 3096 | 3118 | bool use_big_ints) { | |
| 3097 | 3119 | Isolate* isolate = env->isolate(); | |
| 3098 | 3120 | EscapableHandleScope scope(isolate); | |
| 3099 | - auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt); }); | ||
| 3121 | + bool needs_reset = true; | ||
| 3122 | + auto reset = OnScopeLeave([&]() { | ||
| 3123 | + if (needs_reset) sqlite3_reset(stmt); | ||
| 3124 | + }); | ||
| 3100 | 3125 | ||
| 3101 | 3126 | int r = sqlite3_step(stmt); | |
| 3102 | - if (r == SQLITE_DONE) return scope.Escape(Undefined(isolate)); | ||
| 3127 | + if (r == SQLITE_DONE) { | ||
| 3128 | + RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>()); | ||
| 3129 | + return scope.Escape(Undefined(isolate)); | ||
| 3130 | + } | ||
| 3103 | 3131 | if (r != SQLITE_ROW) { | |
| 3104 | 3132 | THROW_ERR_SQLITE_ERROR(isolate, db); | |
| 3105 | 3133 | return MaybeLocal<Value>(); | |
| 3106 | 3134 | } | |
| 3107 | 3135 | ||
| 3108 | 3136 | int num_cols = sqlite3_column_count(stmt); | |
| 3109 | 3137 | if (num_cols == 0) { | |
| 3110 | - return Undefined(isolate); | ||
| 3138 | + RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>()); | ||
| 3139 | + return scope.Escape(Undefined(isolate)); | ||
| 3111 | 3140 | } | |
| 3112 | 3141 | ||
| 3113 | 3142 | LocalVector<Value> row_values(isolate); | |
@@ -3116,9 +3145,9 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, | |||
| 3116 | 3145 | return MaybeLocal<Value>(); | |
| 3117 | 3146 | } | |
| 3118 | 3147 | ||
| 3148 | + Local<Value> result; | ||
| 3119 | 3149 | if (return_arrays) { | |
| 3120 | - return scope.Escape( | ||
| 3121 | - Array::New(isolate, row_values.data(), row_values.size())); | ||
| 3150 | + result = Array::New(isolate, row_values.data(), row_values.size()); | ||
| 3122 | 3151 | } else { | |
| 3123 | 3152 | LocalVector<Name> keys(isolate); | |
| 3124 | 3153 | keys.reserve(num_cols); | |
@@ -3131,9 +3160,12 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, | |||
| 3131 | 3160 | } | |
| 3132 | 3161 | ||
| 3133 | 3162 | DCHECK_EQ(keys.size(), row_values.size()); | |
| 3134 | - return scope.Escape(Object::New( | ||
| 3135 | - isolate, Null(isolate), keys.data(), row_values.data(), num_cols)); | ||
| 3163 | + result = Object::New( | ||
| 3164 | + isolate, Null(isolate), keys.data(), row_values.data(), num_cols); | ||
| 3136 | 3165 | } | |
| 3166 | + | ||
| 3167 | + RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>()); | ||
| 3168 | + return scope.Escape(result); | ||
| 3137 | 3169 | } | |
| 3138 | 3170 | ||
| 3139 | 3171 | void StatementSync::All(const FunctionCallbackInfo<Value>& args) { | |
@@ -3150,15 +3182,19 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) { | |||
| 3150 | 3182 | return; | |
| 3151 | 3183 | } | |
| 3152 | 3184 | ||
| 3153 | - auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); | ||
| 3154 | - | ||
| 3185 | + bool needs_reset = true; | ||
| 3186 | + auto reset = OnScopeLeave([&]() { | ||
| 3187 | + if (needs_reset) sqlite3_reset(stmt->statement_); | ||
| 3188 | + }); | ||
| 3155 | 3189 | Local<Value> result; | |
| 3156 | 3190 | if (StatementExecutionHelper::All(env, | |
| 3157 | 3191 | stmt->db_.get(), | |
| 3158 | 3192 | stmt->statement_, | |
| 3159 | 3193 | stmt->return_arrays_, | |
| 3160 | 3194 | stmt->use_big_ints_) | |
| 3161 | 3195 | .ToLocal(&result)) { | |
| 3196 | + RESET_AND_CHECK( | ||
| 3197 | + isolate, stmt->db_.get(), stmt->statement_, needs_reset, void()); | ||
| 3162 | 3198 | args.GetReturnValue().Set(result); | |
| 3163 | 3199 | } | |
| 3164 | 3200 | } | |
@@ -3592,14 +3628,20 @@ void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) { | |||
| 3592 | 3628 | return; | |
| 3593 | 3629 | } | |
| 3594 | 3630 | ||
| 3595 | - auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); | ||
| 3631 | + Isolate* isolate = env->isolate(); | ||
| 3632 | + bool needs_reset = true; | ||
| 3633 | + auto reset = OnScopeLeave([&]() { | ||
| 3634 | + if (needs_reset) sqlite3_reset(stmt->statement_); | ||
| 3635 | + }); | ||
| 3596 | 3636 | Local<Value> result; | |
| 3597 | 3637 | if (StatementExecutionHelper::All(env, | |
| 3598 | 3638 | stmt->db_.get(), | |
| 3599 | 3639 | stmt->statement_, | |
| 3600 | 3640 | stmt->return_arrays_, | |
| 3601 | 3641 | stmt->use_big_ints_) | |
| 3602 | 3642 | .ToLocal(&result)) { | |
| 3643 | + RESET_AND_CHECK( | ||
| 3644 | + isolate, stmt->db_.get(), stmt->statement_, needs_reset, void()); | ||
| 3603 | 3645 | args.GetReturnValue().Set(result); | |
| 3604 | 3646 | } | |
| 3605 | 3647 | } | |
@@ -3833,8 +3875,11 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) { | |||
| 3833 | 3875 | if (r != SQLITE_ROW) { | |
| 3834 | 3876 | CHECK_ERROR_OR_THROW( | |
| 3835 | 3877 | env->isolate(), iter->stmt_->db_.get(), r, SQLITE_DONE, void()); | |
| 3836 | - sqlite3_reset(iter->stmt_->statement_); | ||
| 3837 | 3878 | iter->done_ = true; | |
| 3879 | + RESET_OR_THROW(env->isolate(), | ||
| 3880 | + iter->stmt_->db_.get(), | ||
| 3881 | + iter->stmt_->statement_, | ||
| 3882 | + void()); | ||
| 3838 | 3883 | MaybeLocal<Value> values[] = {Boolean::New(isolate, true), Null(isolate)}; | |
| 3839 | 3884 | Local<Object> result; | |
| 3840 | 3885 | if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) | |
@@ -3886,6 +3931,10 @@ void StatementSyncIterator::Return(const FunctionCallbackInfo<Value>& args) { | |||
| 3886 | 3931 | env, iter->stmt_->IsFinalized(), "statement has been finalized"); | |
| 3887 | 3932 | Isolate* isolate = env->isolate(); | |
| 3888 | 3933 | ||
| 3934 | + // Unlike Next(), the reset result is intentionally ignored here: Return() | ||
| 3935 | + // is invoked by the language during abrupt completion (e.g. a `throw` | ||
| 3936 | + // inside a `for...of` body), and throwing on a deferred SQLite error | ||
| 3937 | + // would discard the caller's already-pending exception. | ||
| 3889 | 3938 | sqlite3_reset(iter->stmt_->statement_); | |
| 3890 | 3939 | iter->done_ = true; | |
| 3891 | 3940 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -79,6 +79,28 @@ suite('StatementSync.prototype.get()', () => { | |||
| 79 | 79 | message: /statement has been finalized/, | |
| 80 | 80 | }); | |
| 81 | 81 | }); | |
| 82 | + | ||
| 83 | + test('surfaces a deferred SQLite error from reset() even though a row was already built', (t) => { | ||
| 84 | + using db = new DatabaseSync(':memory:'); | ||
| 85 | + db.exec(` | ||
| 86 | + PRAGMA foreign_keys = ON; | ||
| 87 | + PRAGMA defer_foreign_keys = ON; | ||
| 88 | + CREATE TABLE parent(id INTEGER PRIMARY KEY); | ||
| 89 | + CREATE TABLE child(id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id)); | ||
| 90 | + `); | ||
| 91 | + // The FK check is deferred until the implicit transaction commits, which | ||
| 92 | + // happens inside reset() here because RETURNING leaves the statement's | ||
| 93 | + // VDBE running after the row is produced. | ||
| 94 | + const stmt = db.prepare( | ||
| 95 | + 'INSERT INTO child (parent_id) VALUES (999) RETURNING id' | ||
| 96 | + ); | ||
| 97 | + t.assert.throws(() => { | ||
| 98 | + stmt.get(); | ||
| 99 | + }, { | ||
| 100 | + code: 'ERR_SQLITE_ERROR', | ||
| 101 | + message: /FOREIGN KEY constraint failed/, | ||
| 102 | + }); | ||
| 103 | + }); | ||
| 82 | 104 | }); | |
| 83 | 105 | ||
| 84 | 106 | suite('StatementSync.prototype.all()', () => { | |
@@ -144,6 +166,25 @@ suite('StatementSync.prototype.all()', () => { | |||
| 144 | 166 | message: /statement has been finalized/, | |
| 145 | 167 | }); | |
| 146 | 168 | }); | |
| 169 | + | ||
| 170 | + test('surfaces a deferred SQLite error from reset() even though the array was already built', (t) => { | ||
| 171 | + using db = new DatabaseSync(':memory:'); | ||
| 172 | + db.exec(` | ||
| 173 | + PRAGMA foreign_keys = ON; | ||
| 174 | + PRAGMA defer_foreign_keys = ON; | ||
| 175 | + CREATE TABLE parent(id INTEGER PRIMARY KEY); | ||
| 176 | + CREATE TABLE child(id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id)); | ||
| 177 | + `); | ||
| 178 | + const stmt = db.prepare( | ||
| 179 | + 'INSERT INTO child (parent_id) VALUES (999) RETURNING id' | ||
| 180 | + ); | ||
| 181 | + t.assert.throws(() => { | ||
| 182 | + stmt.all(); | ||
| 183 | + }, { | ||
| 184 | + code: 'ERR_SQLITE_ERROR', | ||
| 185 | + message: /FOREIGN KEY constraint failed/, | ||
| 186 | + }); | ||
| 187 | + }); | ||
| 147 | 188 | }); | |
| 148 | 189 | ||
| 149 | 190 | suite('StatementSync.prototype.iterate()', () => { | |
@@ -322,6 +363,41 @@ suite('StatementSync.prototype.iterate()', () => { | |||
| 322 | 363 | message: /statement has been finalized/, | |
| 323 | 364 | }); | |
| 324 | 365 | }); | |
| 366 | + | ||
| 367 | + test('does not replay results after the iterator is naturally exhausted', (t) => { | ||
| 368 | + using db = new DatabaseSync(':memory:'); | ||
| 369 | + db.exec(` | ||
| 370 | + CREATE TABLE test(key TEXT); | ||
| 371 | + INSERT INTO test (key) VALUES ('key1'); | ||
| 372 | + `); | ||
| 373 | + const it = db.prepare('SELECT * FROM test').iterate(); | ||
| 374 | + t.assert.deepStrictEqual(it.next(), { | ||
| 375 | + __proto__: null, done: false, value: { __proto__: null, key: 'key1' }, | ||
| 376 | + }); | ||
| 377 | + t.assert.deepStrictEqual( | ||
| 378 | + it.next(), { __proto__: null, done: true, value: null }); | ||
| 379 | + // Calling next() again on an exhausted iterator must keep reporting | ||
| 380 | + // done, not silently reset the statement and replay from row 1. | ||
| 381 | + t.assert.deepStrictEqual( | ||
| 382 | + it.next(), { __proto__: null, done: true, value: null }); | ||
| 383 | + }); | ||
| 384 | + | ||
| 385 | + test('propagates a pending exception when the loop body throws mid-iteration', (t) => { | ||
| 386 | + using db = new DatabaseSync(':memory:'); | ||
| 387 | + db.exec(` | ||
| 388 | + CREATE TABLE test(key TEXT); | ||
| 389 | + INSERT INTO test (key) VALUES ('key1'); | ||
| 390 | + INSERT INTO test (key) VALUES ('key2'); | ||
| 391 | + `); | ||
| 392 | + const stmt = db.prepare('SELECT * FROM test'); | ||
| 393 | + const userError = new Error('boom'); | ||
| 394 | + t.assert.throws(() => { | ||
| 395 | + // eslint-disable-next-line no-unused-vars | ||
| 396 | + for (const row of stmt.iterate()) { | ||
| 397 | + throw userError; | ||
| 398 | + } | ||
| 399 | + }, (err) => err === userError); | ||
| 400 | + }); | ||
| 325 | 401 | }); | |
| 326 | 402 | ||
| 327 | 403 | suite('StatementSync.prototype.run()', () => { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments