const isDollarQuoteTagCharacter = (character: string): boolean => { return /^[A-Za-z0-9_]$/u.test(character) } const getDollarQuoteDelimiter = (sql: string, startIndex: number): string | undefined => { if (sql[startIndex] !== "$") { return undefined } const firstTagCharacter = sql[startIndex + 1] if (firstTagCharacter === "$") { return "$$" } if (firstTagCharacter === undefined || !/^[A-Za-z_]$/u.test(firstTagCharacter)) { return undefined } for (let endIndex = startIndex + 2; endIndex < sql.length; endIndex = endIndex + 1) { const character = sql[endIndex]! if (character === "$") { return sql.slice(startIndex, endIndex + 1) } if (!isDollarQuoteTagCharacter(character)) { return undefined } } return undefined } const hasExecutableSql = (sqlFragment: string): boolean => { let lineComment = false let blockCommentDepth = 0 for (let i = 0; i < sqlFragment.length; i = i + 1) { const char = sqlFragment[i]! const next = sqlFragment[i + 1] if (lineComment) { if (char === "\n" || char === "\r") { lineComment = false } continue } if (blockCommentDepth > 0) { if (char === "/" && next === "*") { blockCommentDepth = blockCommentDepth + 1 i = i + 1 continue } if (char === "*" && next === "/") { blockCommentDepth = blockCommentDepth - 1 i = i + 1 } continue } if (char.trim() === "") { continue } if (char === "-" && next === "-") { lineComment = true i = i + 1 continue } if (char === "/" && next === "*") { blockCommentDepth = 1 i = i + 1 continue } return true } return false } /** * @description 将包含多条 SQL 语句的字符串切分成单条 SQL 语句的数组。 */ export const splitMultiStatementsSql = (multiStatementsSql: string): string[] => { const statements: string[] = [] let current = "" let quote: "'" | '"' | "`" | null = null let dollarQuoteDelimiter: string | null = null let escapeNext = false let lineComment = false let blockCommentDepth = 0 const appendToCurrent = (fragment: string): void => { current = `${current}${fragment}` } const pushStatement = (): void => { const trimmed = current.trim() if (trimmed.length !== 0 && hasExecutableSql(trimmed)) { statements.push(trimmed) } current = "" } for (let i = 0; i < multiStatementsSql.length; i = i + 1) { const char = multiStatementsSql[i]! const next = multiStatementsSql[i + 1] if (lineComment) { appendToCurrent(char) if (char === "\n" || char === "\r") { lineComment = false } continue } if (blockCommentDepth > 0) { if (char === "/" && next === "*") { blockCommentDepth = blockCommentDepth + 1 appendToCurrent("/*") i = i + 1 continue } if (char === "*" && next === "/") { blockCommentDepth = blockCommentDepth - 1 appendToCurrent("*/") i = i + 1 continue } appendToCurrent(char) continue } if (dollarQuoteDelimiter !== null) { const delimiter = dollarQuoteDelimiter if (multiStatementsSql.startsWith(delimiter, i)) { appendToCurrent(delimiter) i = i + delimiter.length - 1 dollarQuoteDelimiter = null continue } appendToCurrent(char) continue } if (quote !== null) { appendToCurrent(char) if (escapeNext) { escapeNext = false continue } if (char === "\\") { escapeNext = true continue } if (char === quote) { if (next === quote) { appendToCurrent(quote) i = i + 1 continue } quote = null } continue } if (char === "-" && next === "-") { lineComment = true appendToCurrent("--") i = i + 1 continue } if (char === "/" && next === "*") { blockCommentDepth = 1 appendToCurrent("/*") i = i + 1 continue } const delimiter = getDollarQuoteDelimiter(multiStatementsSql, i) if (delimiter !== undefined) { dollarQuoteDelimiter = delimiter appendToCurrent(delimiter) i = i + delimiter.length - 1 continue } if (char === "'" || char === '"' || char === "`") { quote = char escapeNext = false appendToCurrent(char) continue } if (char === ";") { pushStatement() continue } appendToCurrent(char) } pushStatement() return statements }