diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 3234d9881b5..e73bbf12cd5 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -73,6 +73,13 @@ using v8::Value; inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, std::string_view input) { + // SQLITE_MAX_LENGTH exceeds String::kMaxLength, and V8 returns an empty + // handle without throwing. Raise the error here or the value is dropped. + if (input.size() > static_cast(String::kMaxLength)) [[unlikely]] { + isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate)); + return MaybeLocal(); + } + const int len = static_cast(input.size()); if (simdutf::validate_ascii(input.data(), input.size())) { return String::NewFromOneByte( @@ -351,7 +358,11 @@ class Database; inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, Database* db) { if (db->ShouldIgnoreSQLiteError()) { db->SetIgnoreNextSQLiteError(false); - return; + // Suppression that swallows no pending exception would also swallow the + // SQLite error, reporting a failed statement as a success. + if (isolate->HasPendingException()) { + return; + } } Local e; diff --git a/test/parallel/test-sqlite-statement.js b/test/parallel/test-sqlite-statement.js index 8fac6f4ff50..d06b9018bf8 100644 --- a/test/parallel/test-sqlite-statement.js +++ b/test/parallel/test-sqlite-statement.js @@ -1,8 +1,9 @@ // Flags: --expose-gc 'use strict'; -const { skipIfSQLiteMissing } = require('../common'); +const { enoughTestMem, skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); const { Database, Statement } = require('node:sqlite'); +const { constants } = require('node:buffer'); const { suite, test } = require('node:test'); suite('Statement() constructor', () => { @@ -1442,3 +1443,30 @@ suite('options.persistent', () => { t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42n }); }); }); + +suite('values larger than the maximum string length', { skip: !enoughTestMem }, () => { + // hex() doubles its input, so this is the smallest blob whose text form + // exceeds what V8 can hold in a string. + const blobSize = (constants.MAX_STRING_LENGTH >>> 1) + 1; + const tooLong = { code: 'ERR_STRING_TOO_LONG', name: 'Error' }; + + test('get() throws instead of returning undefined', (t) => { + using db = new Database(':memory:'); + using stmt = db.prepare('SELECT hex(zeroblob(?))'); + t.assert.throws(() => { + stmt.get(blobSize); + }, tooLong); + }); + + test('exec() surfaces the error from a user-defined function', (t) => { + using db = new Database(':memory:'); + db.exec('CREATE TABLE data(val TEXT)'); + db.function('identity', (val) => val); + + t.assert.throws(() => { + db.exec(`INSERT INTO data (val) VALUES (identity(hex(zeroblob(${blobSize}))))`); + }, tooLong); + using stmt = db.prepare('SELECT count(*) AS count FROM data'); + t.assert.deepStrictEqual(stmt.get(), { __proto__: null, count: 0 }); + }); +});