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 });
+  });
+});