From 6c40b4070bfd7ad336ae7b150b71984fe504e6f8 Mon Sep 17 00:00:00 2001 From: sunlei Date: Fri, 24 Jul 2026 04:23:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3QQBot=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=8E=A8=E9=80=81SQL=E5=BC=95=E5=8F=B7=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqbot-message-push-sql.spec.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/modules/qqbot/message-push/qqbot-message-push-sql.spec.ts b/test/modules/qqbot/message-push/qqbot-message-push-sql.spec.ts index 785ad7f..8fd046d 100644 --- a/test/modules/qqbot/message-push/qqbot-message-push-sql.spec.ts +++ b/test/modules/qqbot/message-push/qqbot-message-push-sql.spec.ts @@ -129,12 +129,13 @@ const menuEntries: readonly MenuEntry[] = [ */ const advanceSqlQuoteState = (sql: string, index: number, quoted: boolean) => { if (sql[index] !== "'") return { quoted, skipNext: false }; - if (quoted && sql[index + 1] === "'") return { quoted, skipNext: true }; let precedingBackslashes = 0; for (let cursor = index - 1; sql[cursor] === '\\'; cursor -= 1) precedingBackslashes += 1; + if (precedingBackslashes % 2 === 1) return { quoted, skipNext: false }; + if (quoted && sql[index + 1] === "'") return { quoted, skipNext: true }; return { - quoted: precedingBackslashes % 2 === 1 ? quoted : !quoted, + quoted: !quoted, skipNext: false, }; }; @@ -373,6 +374,21 @@ describe('QQBot message-push SQL contract', () => { expect(splitSqlTuple("'closed', next")).toEqual(["'closed'", 'next']); expect(splitSqlTuple("'it''s closed', next")).toEqual(["'it's closed'", 'next']); expect(splitSqlTuple(String.raw`'odd\' quote', next`)).toEqual([String.raw`'odd\' quote'`, 'next']); + const oddEscapedQuoteThenEnd = String.raw`'odd\''`; + expect(splitSqlTuple(`${oddEscapedQuoteThenEnd}, next`)).toEqual([oddEscapedQuoteThenEnd, 'next']); + expect(extractSqlTuples(`(${oddEscapedQuoteThenEnd}), ('next')`)).toEqual([ + oddEscapedQuoteThenEnd, + "'next'", + ]); + expect(splitSqlStatements(`select ${oddEscapedQuoteThenEnd}; select 'next';`)).toEqual([ + `select ${oddEscapedQuoteThenEnd};`, + "select 'next';", + ]); + const cteStatements = splitSqlStatements( + `with expected_menu as (select ${oddEscapedQuoteThenEnd} as id); select 'next';`, + ); + expect(cteStatements).toHaveLength(2); + expect(extractExpectedMenuCte(cteStatements[0])).toBe(`select ${oddEscapedQuoteThenEnd} as id`); expect(splitSqlTuple(String.raw`'even\\', next`)).toEqual([String.raw`'even\\'`, 'next']); expect(extractSqlTuples(String.raw`('even\\'), ('next')`)).toEqual([String.raw`'even\\'`, "'next'"]); expect(splitSqlStatements(String.raw`select 'even\\'; select 'next';`)).toEqual([