🐛 fix(sql-editor): 修复存储过程定义执行截断

This commit is contained in:
Syngnat
2026-06-04 15:46:09 +08:00
parent 4cef232271
commit 8f7c790700
8 changed files with 328 additions and 229 deletions

View File

@@ -18,7 +18,7 @@ import { applyQueryAutoLimit } from '../utils/queryAutoLimit';
import { extractQueryResultTableRef, type QueryResultTableRef } from '../utils/queryResultTable';
import { quoteIdentPart } from '../utils/sql';
import { formatSqlExecutionError } from '../utils/sqlErrorSemantics';
import { resolveCurrentSqlStatementRange, resolveExecutableSql } from '../utils/sqlStatementSelection';
import { findSqlStatementRanges, resolveCurrentSqlStatementRange, resolveExecutableSql } from '../utils/sqlStatementSelection';
import { isMacLikePlatform } from '../utils/appearance';
import { splitSidebarQualifiedName } from '../utils/sidebarLocate';
import { normalizeSidebarViewName } from '../utils/sidebarMetadata';
@@ -741,65 +741,6 @@ const areSqlStatementListsEqual = (left: string[], right: string[]): boolean =>
&& left.every((statement, index) => normalizeExecutedSqlKey(statement) === normalizeExecutedSqlKey(right[index]))
);
const isSqlIdentifierStart = (ch: string): boolean => /^[A-Za-z_]$/.test(ch);
const isSqlIdentifierPart = (ch: string): boolean => /^[A-Za-z0-9_$#]$/.test(ch);
const skipSqlWhitespaceAndComments = (text: string, position: number): number => {
let index = position;
while (index < text.length) {
const ch = text[index];
const next = index + 1 < text.length ? text[index + 1] : '';
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\f') {
index += 1;
continue;
}
if (ch === '-' && next === '-') {
index += 2;
while (index < text.length && text[index] !== '\n') index += 1;
continue;
}
if (ch === '/' && next === '*') {
index += 2;
while (index + 1 < text.length && !(text[index] === '*' && text[index + 1] === '/')) {
index += 1;
}
if (index + 1 < text.length) index += 2;
continue;
}
break;
}
return index;
};
const nextSqlSignificantToken = (text: string, position: number): string => {
const index = skipSqlWhitespaceAndComments(text, position);
if (index >= text.length || !isSqlIdentifierStart(text[index])) return '';
let end = index + 1;
while (end < text.length && isSqlIdentifierPart(text[end])) end += 1;
return text.slice(index, end).toLowerCase();
};
const nextSqlSignificantChar = (text: string, position: number): string => {
const index = skipSqlWhitespaceAndComments(text, position);
return index >= text.length ? '' : text[index];
};
const shouldEnterPlsqlBeginBlock = (text: string, tokenEnd: number): boolean => {
const nextChar = nextSqlSignificantChar(text, tokenEnd);
if (!nextChar || nextChar === ';') return false;
return !['transaction', 'work', 'isolation', 'read', 'write'].includes(nextSqlSignificantToken(text, tokenEnd));
};
const shouldEnterPlsqlDeclareBlock = (text: string, tokenEnd: number): boolean => {
const nextToken = nextSqlSignificantToken(text, tokenEnd);
return Boolean(nextToken);
};
const isPlsqlControlEnd = (text: string, tokenEnd: number): boolean => (
['if', 'loop', 'case'].includes(nextSqlSignificantToken(text, tokenEnd))
);
const normalizeEditorPosition = (position: any): { lineNumber: number; column: number } | null => {
if (!position) return null;
const lineNumber = Number(position.positionLineNumber ?? position.lineNumber ?? position.endLineNumber ?? position.startLineNumber ?? position.selectionStartLineNumber);
@@ -3834,169 +3775,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
];
const splitSQLStatements = (sql: string): string[] => {
const text = (sql || '').replace(/\r\n/g, '\n');
const statements: string[] = [];
let cur = '';
let inSingle = false;
let inDouble = false;
let inBacktick = false;
let escaped = false;
let inLineComment = false;
let inBlockComment = false;
let dollarTag: string | null = null; // postgres/kingbase: $$...$$ or $tag$...$tag$
let plsqlDepth = 0;
let plsqlDeclareBeginSkips = 0;
let justClosedPLSQLBlock = false;
const push = () => {
const s = cur.trim();
if (s) statements.push(s);
cur = '';
};
const isWS = (ch: string) => ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r';
for (let i = 0; i < text.length; i++) {
const ch = text[i];
const next = i + 1 < text.length ? text[i + 1] : '';
const prev = i > 0 ? text[i - 1] : '';
const next2 = i + 2 < text.length ? text[i + 2] : '';
if (!inSingle && !inDouble && !inBacktick) {
if (inLineComment) {
cur += ch;
if (ch === '\n') inLineComment = false;
continue;
}
if (inBlockComment) {
cur += ch;
if (ch === '*' && next === '/') {
cur += next;
i++;
inBlockComment = false;
}
continue;
}
// Start comments
if (ch === '/' && next === '*') {
cur += ch + next;
i++;
inBlockComment = true;
continue;
}
if (ch === '#') {
cur += ch;
inLineComment = true;
continue;
}
if (ch === '-' && next === '-' && (i === 0 || isWS(prev)) && (next2 === '' || isWS(next2))) {
cur += ch + next;
i++;
inLineComment = true;
continue;
}
// Dollar-quoted strings (PG/Kingbase)
if (dollarTag) {
if (text.startsWith(dollarTag, i)) {
cur += dollarTag;
i += dollarTag.length - 1;
dollarTag = null;
} else {
cur += ch;
}
continue;
}
if (ch === '$') {
const m = text.slice(i).match(/^\$[A-Za-z0-9_]*\$/);
if (m && m[0]) {
dollarTag = m[0];
cur += dollarTag;
i += dollarTag.length - 1;
continue;
}
}
}
if (escaped) {
cur += ch;
escaped = false;
continue;
}
if ((inSingle || inDouble) && ch === '\\') {
cur += ch;
escaped = true;
continue;
}
if (!inDouble && !inBacktick && ch === '\'') {
inSingle = !inSingle;
cur += ch;
continue;
}
if (!inSingle && !inBacktick && ch === '"') {
inDouble = !inDouble;
cur += ch;
continue;
}
if (!inSingle && !inDouble && ch === '`') {
inBacktick = !inBacktick;
cur += ch;
continue;
}
if (!inSingle && !inDouble && !inBacktick && !dollarTag && isSqlIdentifierStart(ch)) {
let end = i + 1;
while (end < text.length && isSqlIdentifierPart(text[end])) {
end += 1;
}
const token = text.slice(i, end).toLowerCase();
if (token === 'begin' && plsqlDeclareBeginSkips > 0) {
plsqlDeclareBeginSkips -= 1;
justClosedPLSQLBlock = false;
} else if (token === 'begin' && shouldEnterPlsqlBeginBlock(text, end)) {
plsqlDepth += 1;
justClosedPLSQLBlock = false;
} else if (token === 'declare' && shouldEnterPlsqlDeclareBlock(text, end)) {
plsqlDepth += 1;
plsqlDeclareBeginSkips += 1;
justClosedPLSQLBlock = false;
} else if (token === 'end' && plsqlDepth > 0 && !isPlsqlControlEnd(text, end)) {
plsqlDepth -= 1;
if (plsqlDeclareBeginSkips > plsqlDepth) {
plsqlDeclareBeginSkips = plsqlDepth;
}
justClosedPLSQLBlock = plsqlDepth === 0;
}
cur += text.slice(i, end);
i = end - 1;
continue;
}
if (!inSingle && !inDouble && !inBacktick && !dollarTag && (ch === ';' || ch === '')) {
if (plsqlDepth > 0) {
cur += ch;
continue;
}
if (justClosedPLSQLBlock) {
cur += ch;
push();
justClosedPLSQLBlock = false;
continue;
}
push();
continue;
}
cur += ch;
}
push();
return statements;
return findSqlStatementRanges(sql).map((range) => range.text);
};
const getSelectedSQL = (): string => {