From bf7a8e54bd50d0810dd36a41e7902456e2c43d19 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 13:01:57 +0000 Subject: [PATCH] fix: leave MongoDB query statements alone when normalizing SQL The Node and Java agents record MongoDB collection operations as sql_query events with database_type "mongodb". Their "sql" is a statement whose argument values are already replaced with placeholders: db.users.updateOne({"_id": ?}, {"$set": {"name": ?}, "$inc": {"n": ?}}) normalizeSQL had no case for that database type, so the fallback obfuscation applied. It treats the quoted keys as string literals and a pair of $operators as a dollar-quoted string, which turned the statement above into `db.users.updateOne({?: ?}, {?: {?: ?}, ?: {?: ?}})` in the sqlNormalized index and in change reports. Return the statement as it is for that database type. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01LntddoBsqjRDRBepBx7oLZ --- packages/models/src/sql/normalize.js | 7 +++++ .../models/tests/unit/sql.normalize.spec.js | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/models/tests/unit/sql.normalize.spec.js diff --git a/packages/models/src/sql/normalize.js b/packages/models/src/sql/normalize.js index 357096a5d3..e63f32a43a 100644 --- a/packages/models/src/sql/normalize.js +++ b/packages/models/src/sql/normalize.js @@ -92,6 +92,13 @@ function detectUnmatchedPairs(obfuscated, adapter) { * @returns {string} */ export default function normalize(sql, adapter) { + // The MongoDB agents record each operation as a statement whose argument + // values are already replaced with placeholders, for example + // db.users.updateOne({"_id": ?}, {"$set": {"name": ?}}). The generic + // obfuscation below would treat the quoted keys as string literals and a + // pair of $operators as a dollar-quoted string, so leave it as it is. + if (adapter === 'mongodb') return sql; + /** @type {RegExp[]} */ let regexp; switch (adapter) { case 'mysql': diff --git a/packages/models/tests/unit/sql.normalize.spec.js b/packages/models/tests/unit/sql.normalize.spec.js new file mode 100644 index 0000000000..17f4b4c6c9 --- /dev/null +++ b/packages/models/tests/unit/sql.normalize.spec.js @@ -0,0 +1,28 @@ +import normalize from '../../src/sql/normalize'; + +describe('normalize SQL', () => { + test('replaces literals for a known dialect', () => { + expect(normalize("SELECT * FROM users WHERE id = 1 AND name = 'bob'", 'postgres')).toEqual( + 'SELECT * FROM users WHERE id = ? AND name = ?' + ); + }); + + test('replaces literals with the fallback for an unknown dialect', () => { + expect(normalize('SELECT * FROM users WHERE id = 1', 'something')).toEqual( + 'SELECT * FROM users WHERE id = ?' + ); + }); + + test('leaves MongoDB statements alone, since the agent already normalized them', () => { + const statements = [ + 'db.users.find({"a": ?})', + 'db.users.updateOne({"_id": ?}, {"$set": {"name": ?}, "$inc": {"n": ?}}, {"upsert": ?})', + 'db.orders.aggregate([{"$match": {"status": ?}}, {"$group": {"_id": ?, "total": {"$sum": ?}}}])', + 'db.getCollection("with-dash").insertOne({"a": ?})', + 'db.users.distinct("email", {"deleted": ?})', + ]; + statements.forEach((statement) => { + expect(normalize(statement, 'mongodb')).toEqual(statement); + }); + }); +});