Commit c5b7a06d9b7 for nodejs
commit c5b7a06d9b7c6ddd4567ca4019be7dae3a48fada
Author: Guilherme Araújo <arauujogui@gmail.com>
Date: Tue Sep 29 13:58:35 2026 -0300
sqlite: throw on oversized string values
SQLite serves TEXT up to SQLITE_MAX_LENGTH, past what V8 can represent
as a string. V8 returns an empty handle without throwing, and the
user-defined function path then suppressed the SQLite error as if a
JavaScript exception were pending. exec() reported success for a
statement that never ran, and get() returned undefined.
Throw ERR_STRING_TOO_LONG when the value cannot be converted, and
suppress a SQLite error only when an exception is actually pending.
Assisted-by: Claude Code
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66209
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
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<String> 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<size_t>(String::kMaxLength)) [[unlikely]] {
+ isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
+ return MaybeLocal<String>();
+ }
+
const int len = static_cast<int>(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<Object> 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 });
+ });
+});