fix(oracle): highlight execution error position in editor

This commit is contained in:
zipg authored and GitHub committed 2026-08-06 23:16:35 +08:00
1 parent 69f4450aee
commit a1dfc81d4c
3 files changed
+103 -7

No files matched your search

@@ -79,7 +79,7 @@ import {
type SqlObjectNavigationTarget,
} from "@/lib/sql/sqlNavigation";
import { buildHoverTableSql, ddlForHoverPreview, hoverTableMatchesScope, quoteQualifiedName, reformatHoverDdl, scopeHoverTables, type HoverTableScope } from "@/lib/editor/hoverTableSql";
import { lineColumnToOffset, parseSqlErrorLocation } from "@/lib/sql/sqlDiagnostics";
import { lineColumnToOffset, sqlErrorDecorationRange as resolveSqlErrorDecorationRange } from "@/lib/sql/sqlDiagnostics";
import {
DBX_TABLE_REFERENCE_MIME,
DBX_TABLE_REFERENCE_DROP_EVENT,
@@ -2208,14 +2208,11 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number)
function sqlErrorDecorationRange(currentState: import("@codemirror/state").EditorState) {
if (!props.executionError) return [];
if (!props.executionErrorSql || props.executionErrorSql !== currentState.doc.toString()) return [];
const location = parseSqlErrorLocation(props.executionError);
if (!location) return [];
const offset = lineColumnToOffset(currentState.doc.toString(), location);
if (offset == null) return [];
const range = resolveSqlErrorDecorationRange(currentState.doc.toString(), props.executionError);
if (!range) return [];
return [
{
from: offset,
to: Math.min(offset + 1, currentState.doc.length),
...range,
message: props.executionError,
},
];
@@ -3,6 +3,11 @@ export interface SqlErrorLocation {
column: number;
}
export interface SqlErrorRange {
from: number;
to: number;
}
function toZeroBased(value: string | undefined): number | null {
if (!value) return null;
const parsed = Number.parseInt(value, 10);
@@ -42,3 +47,48 @@ export function lineColumnToOffset(sql: string, location: SqlErrorLocation): num
return Math.min(offset + location.column, offset + lines[location.line].length);
}
function oracleInvalidIdentifierRange(sql: string, message: string, position: number): SqlErrorRange | null {
const identifierMatch = /\bORA-00904:\s*"((?:""|[^"])*)"\s*:\s*invalid identifier\b/i.exec(message);
if (!identifierMatch?.[1]) return null;
const reportedIdentifier = identifierMatch[1].replace(/""/g, '"');
const identifierPattern = /"(?:""|[^"])*"|[\p{L}_][\p{L}\p{N}_$#]*/gu;
let bestRange: SqlErrorRange | null = null;
let bestDistance = Number.POSITIVE_INFINITY;
for (const match of sql.matchAll(identifierPattern)) {
const token = match[0];
const tokenStart = match.index;
const quoted = token.startsWith('"');
const identifier = quoted ? token.slice(1, -1).replace(/""/g, '"') : token;
if (quoted ? identifier !== reportedIdentifier : identifier.toUpperCase() !== reportedIdentifier.toUpperCase()) continue;
const from = tokenStart + (quoted ? 1 : 0);
const to = tokenStart + token.length - (quoted ? 1 : 0);
const distance = position < from ? from - position : position > to ? position - to : 0;
if (distance >= bestDistance) continue;
bestDistance = distance;
bestRange = { from, to };
}
return bestRange;
}
export function sqlErrorDecorationRange(sql: string, message: string): SqlErrorRange | null {
const location = parseSqlErrorLocation(message);
if (location) {
const offset = lineColumnToOffset(sql, location);
if (offset == null || offset >= sql.length) return null;
return { from: offset, to: offset + 1 };
}
// Oracle Agent reports a zero-based absolute offset. For qualified invalid
// identifiers it can point later in the selector, so prefer the named token.
const positionMatch = /\berror\s+occur(?:red)?\s+at\s+position\s*:\s*(\d+)\b/i.exec(message);
if (!positionMatch?.[1]) return null;
const position = Number.parseInt(positionMatch[1], 10);
if (!Number.isSafeInteger(position) || position < 0 || position >= sql.length) return null;
return oracleInvalidIdentifierRange(sql, message, position) ?? { from: position, to: position + 1 };
}