mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(editor): 修复 MySQL DUAL、SYSDATE 提示和额外间距
This commit is contained in:
@@ -72,7 +72,17 @@ import { metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { usesLocalOnlyEditorCompletionMetadata, usesOnDemandOnlyEditorColumnMetadata } from "@/lib/metadata/completionMetadataPolicy";
|
||||
import { queryContextObjectActions, queryContextObjectRoute, queryTableCandidateAtSqlPosition, resolveQueryContextCandidateDatabase, resolveQueryContextObjectTarget, type QueryContextObjectAction } from "@/lib/sql/queryCursorTableTarget";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { areSqlSemanticDiagnosticsEqual, buildSqlParserErrorDiagnostic, buildSqlSemanticDiagnostics, isSqlSemanticDiagnosticInputContext, shouldRunSqlSemanticDiagnostics, sqlSemanticDiagnosticRangesForViewport, tableReferenceKey, type SqlSemanticDiagnostic } from "@/lib/sql/semantic/diagnostics";
|
||||
import {
|
||||
areSqlSemanticDiagnosticsEqual,
|
||||
buildSqlParserErrorDiagnostic,
|
||||
buildSqlSemanticDiagnostics,
|
||||
isSqlSemanticDiagnosticInputContext,
|
||||
isSqlVirtualTableReference,
|
||||
shouldRunSqlSemanticDiagnostics,
|
||||
sqlSemanticDiagnosticRangesForViewport,
|
||||
tableReferenceKey,
|
||||
type SqlSemanticDiagnostic,
|
||||
} from "@/lib/sql/semantic/diagnostics";
|
||||
import { sqlReferenceAnalysisDialectFor } from "@/lib/sql/semantic/dialect";
|
||||
import { buildRedisSyntaxDiagnostics, shouldRunRedisDiagnostics } from "@/lib/redis/redisSyntaxDiagnostics";
|
||||
import { buildRedisCompletionItemsFromContext, getRedisCompletionContext, getRedisCompletionResultValidFor, shouldAutoOpenRedisCompletion, takesKeyArgument, type RedisCompletionItem } from "@/lib/redis/redisCompletion";
|
||||
@@ -301,6 +311,7 @@ let previewRangeComp: import("@codemirror/state").Compartment | null = null;
|
||||
let buildPreviewRangeExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildResultSourceRangeExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildStatementExecutionMarkersExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let statementExecutionGutterMounted = false;
|
||||
let buildRunStatementGutterExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let indentComp: import("@codemirror/state").Compartment | null = null;
|
||||
let codeMirrorIndentUnit: typeof import("@codemirror/language").indentUnit | null = null;
|
||||
@@ -1400,6 +1411,10 @@ function completionMetadataTarget(table: { name: string; schema?: string | null
|
||||
return { database: props.database, schema: table.schema ?? props.schema };
|
||||
}
|
||||
|
||||
function isVirtualCompletionTableReference(table: { name: string; schema?: string | null }): boolean {
|
||||
return isSqlVirtualTableReference(table, props.databaseType);
|
||||
}
|
||||
|
||||
function completionQualifiedTableTarget(completionContext: ReturnType<typeof getSqlCompletionContext>): { name: string; schema: string } | null {
|
||||
if (!completionContext.suggestColumns) return null;
|
||||
const parts = completionContext.qualifierParts ?? completionContext.qualifier?.split(".").filter(Boolean) ?? [];
|
||||
@@ -1430,6 +1445,7 @@ async function findExactSemanticDiagnosticTable(table: SqlTableReference): Promi
|
||||
}
|
||||
|
||||
async function ensureColumnsForTable(table: { name: string; schema?: string | null }): Promise<boolean> {
|
||||
if (isVirtualCompletionTableReference(table)) return false;
|
||||
const cacheKey = completionCacheKey(table);
|
||||
if (cachedColumnsByTable.has(cacheKey)) return true;
|
||||
if (!props.connectionId || props.database == null) return false;
|
||||
@@ -1447,6 +1463,7 @@ function isMissingTableMetadataError(error: unknown) {
|
||||
}
|
||||
|
||||
async function ensureForeignKeysForTable(table: { name: string; schema?: string | null }) {
|
||||
if (isVirtualCompletionTableReference(table)) return;
|
||||
const cacheKey = completionCacheKey(table);
|
||||
if (cachedForeignKeysByTable.has(cacheKey) || !props.connectionId || props.database == null) return;
|
||||
const target = completionMetadataTarget(table);
|
||||
@@ -1754,7 +1771,7 @@ async function enrichSemanticDiagnosticTables(tables: SqlTableReference[]): Prom
|
||||
const enriched: SqlTableReference[] = [];
|
||||
const missingTables = new Set<string>();
|
||||
for (const table of tables) {
|
||||
if (isStatementLocalSemanticTable(table)) {
|
||||
if (isStatementLocalSemanticTable(table) || isSqlVirtualTableReference(table, props.databaseType)) {
|
||||
enriched.push(table);
|
||||
continue;
|
||||
}
|
||||
@@ -1774,7 +1791,7 @@ async function ensureColumnsForSemanticDiagnostics(tables: SqlTableReference[]):
|
||||
const seen = new Set<string>();
|
||||
const targets: SqlTableReference[] = [];
|
||||
for (const table of tables) {
|
||||
if (isStatementLocalSemanticTable(table)) continue;
|
||||
if (isStatementLocalSemanticTable(table) || isSqlVirtualTableReference(table, props.databaseType)) continue;
|
||||
const tableWithInlineColumns = table as SqlTableReference & {
|
||||
columns?: string[];
|
||||
};
|
||||
@@ -1896,6 +1913,7 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
|
||||
missingTables,
|
||||
loadedColumnTables: loadedColumnsByTable,
|
||||
sql: range.sql,
|
||||
databaseType: props.databaseType,
|
||||
}),
|
||||
range,
|
||||
sql,
|
||||
@@ -2437,6 +2455,7 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getS
|
||||
|
||||
const cteDefs = extractCteDefinitions(fullDoc);
|
||||
for (const refTable of completionContext.referencedTables) {
|
||||
if (isVirtualCompletionTableReference(refTable)) continue;
|
||||
const cteDef = cteDefs.find((c) => c.name.toLowerCase() === refTable.name.toLowerCase());
|
||||
if (cteDef) {
|
||||
columnsByTable.set(
|
||||
@@ -2555,6 +2574,7 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
|
||||
}
|
||||
if (!onDemandOnlyColumns && !tableNameCompletion) {
|
||||
for (const refTable of completionContext.referencedTables) {
|
||||
if (isVirtualCompletionTableReference(refTable)) continue;
|
||||
if (refTable.columns && refTable.columns.length > 0) continue;
|
||||
const cacheKey = refTable.schema ? `${refTable.schema}.${refTable.name}` : refTable.name;
|
||||
if (cacheKey === qualifiedColumnCacheKey) continue;
|
||||
@@ -2757,7 +2777,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
|
||||
}
|
||||
return rt;
|
||||
});
|
||||
const unresolvedRefs = refs.filter((rt) => !rt.schema && !rt.columns);
|
||||
const unresolvedRefs = refs.filter((rt) => !rt.schema && !rt.columns && !isVirtualCompletionTableReference(rt));
|
||||
if (!localOnlyMetadata && unresolvedRefs.length > 0) {
|
||||
const lookupGroups = await Promise.all(unresolvedRefs.map((rt) => connectionStore.listCompletionTables(props.connectionId!, props.database!, rt.name, 20, props.schema, false, props.schema)));
|
||||
if (epoch !== completionEpoch) return null;
|
||||
@@ -2796,6 +2816,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
|
||||
if (shouldFetchColumnsForCompletion) {
|
||||
await Promise.all(
|
||||
refs.map(async (refTable) => {
|
||||
if (isVirtualCompletionTableReference(refTable)) return;
|
||||
if (refTable.columns && refTable.columns.length > 0) return;
|
||||
const cacheKey = refTable.schema ? `${refTable.schema}.${refTable.name}` : refTable.name;
|
||||
if (cachedColumnsByTable.has(cacheKey)) return;
|
||||
@@ -3196,6 +3217,7 @@ onMounted(async () => {
|
||||
});
|
||||
return field;
|
||||
};
|
||||
statementExecutionGutterMounted = (props.statementExecutionMarkers?.length ?? 0) > 0;
|
||||
|
||||
buildSqlSignatureExtension = () =>
|
||||
showTooltip.compute(["doc", "selection"], (currentState) => {
|
||||
@@ -3463,7 +3485,7 @@ onMounted(async () => {
|
||||
}),
|
||||
previewRangeComp.of(buildPreviewRangeExtension()),
|
||||
buildResultSourceRangeExtension(),
|
||||
statementExecutionGutterComp.of(buildStatementExecutionMarkersExtension()),
|
||||
statementExecutionGutterComp.of(statementExecutionGutterMounted ? buildStatementExecutionMarkersExtension() : []),
|
||||
Prec.highest(
|
||||
keymap.of([
|
||||
{ key: "'", run: handleSqlSingleQuote },
|
||||
@@ -3770,7 +3792,22 @@ watch(
|
||||
watch(
|
||||
() => props.statementExecutionMarkers ?? [],
|
||||
(markers) => {
|
||||
if (!view.value || !setStatementExecutionMarkersEffect) return;
|
||||
if (!view.value || !statementExecutionGutterComp || !buildStatementExecutionMarkersExtension || !setStatementExecutionMarkersEffect) return;
|
||||
if (markers.length === 0) {
|
||||
if (!statementExecutionGutterMounted) return;
|
||||
view.value.dispatch({
|
||||
effects: statementExecutionGutterComp.reconfigure([]),
|
||||
});
|
||||
statementExecutionGutterMounted = false;
|
||||
return;
|
||||
}
|
||||
if (!statementExecutionGutterMounted) {
|
||||
view.value.dispatch({
|
||||
effects: statementExecutionGutterComp.reconfigure(buildStatementExecutionMarkersExtension()),
|
||||
});
|
||||
statementExecutionGutterMounted = true;
|
||||
return;
|
||||
}
|
||||
view.value.dispatch({
|
||||
effects: setStatementExecutionMarkersEffect.of(markers),
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface SqlSemanticDiagnosticSchema {
|
||||
missingTables?: Set<string>;
|
||||
loadedColumnTables?: Set<string>;
|
||||
sql?: string;
|
||||
databaseType?: DatabaseType;
|
||||
}
|
||||
|
||||
export interface SqlSemanticDiagnosticVisibleRange {
|
||||
@@ -52,6 +53,7 @@ export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, sche
|
||||
}
|
||||
|
||||
for (const table of tables) {
|
||||
if (isSqlVirtualTableReference(table, schema.databaseType)) continue;
|
||||
if (!schema.missingTables?.has(tableReferenceKey(table))) continue;
|
||||
diagnostics.push({
|
||||
span: trimSqlTextSpanWhitespace(schema.sql, table.span),
|
||||
@@ -82,6 +84,10 @@ export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, sche
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
export function isSqlVirtualTableReference(table: { name: string; schema?: string | null }, databaseType?: DatabaseType): boolean {
|
||||
return databaseType === "mysql" && !table.schema && normalizeName(table.name) === "dual";
|
||||
}
|
||||
|
||||
function trimSqlTextSpanWhitespace(sql: string | undefined, span: SqlTextSpan): SqlTextSpan {
|
||||
if (!sql) return span;
|
||||
const range = sqlTextSpanToOffsetRange(sql, span);
|
||||
|
||||
@@ -958,6 +958,7 @@ const MYSQL_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["DATE_FORMAT", ["date", "format"]],
|
||||
["FROM_UNIXTIME", ["unix_timestamp"]],
|
||||
["UNIX_TIMESTAMP", []],
|
||||
["SYSDATE", []],
|
||||
["JSON_EXTRACT", ["json", "path"]],
|
||||
["JSON_UNQUOTE", ["json"]],
|
||||
["GROUP_CONCAT", ["expression"]],
|
||||
|
||||
+7
-1
@@ -107,7 +107,7 @@ test("suggests lower-case SQL keywords when configured", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("suggests PostgreSQL-specific data types and functions", () => {
|
||||
test("suggests database-specific data types and functions", () => {
|
||||
const typeItems = buildSqlCompletionItems("create table events (payload js", "create table events (payload js".length, {
|
||||
tables: [],
|
||||
columnsByTable: new Map(),
|
||||
@@ -128,6 +128,11 @@ test("suggests PostgreSQL-specific data types and functions", () => {
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "mysql",
|
||||
});
|
||||
const mysqlSysdateItems = buildSqlCompletionItems("select sysd", "select sysd".length, {
|
||||
tables: [],
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "mysql",
|
||||
});
|
||||
const postgresDateItems = buildSqlCompletionItems("select date_f", "select date_f".length, {
|
||||
tables: [],
|
||||
columnsByTable: new Map(),
|
||||
@@ -138,6 +143,7 @@ test("suggests PostgreSQL-specific data types and functions", () => {
|
||||
assert.ok(serialItems.some((item) => item.type === "keyword" && item.label === "SERIAL"));
|
||||
assert.ok(functionItems.some((item) => item.type === "function" && item.label === "JSONB_BUILD_OBJECT"));
|
||||
assert.ok(mysqlFunctionItems.some((item) => item.type === "function" && item.label === "DATE_FORMAT"));
|
||||
assert.ok(mysqlSysdateItems.some((item) => item.type === "function" && item.label === "SYSDATE"));
|
||||
assert.equal(
|
||||
postgresDateItems.some((item) => item.type === "function" && item.label === "DATE_FORMAT"),
|
||||
false,
|
||||
|
||||
@@ -51,6 +51,22 @@ test("flags confirmed missing tables", () => {
|
||||
assert.equal(diagnostics[0]?.severity, "error");
|
||||
});
|
||||
|
||||
test("does not flag MySQL DUAL as a missing physical table", () => {
|
||||
const analysis: SqlReferenceAnalysis = {
|
||||
tables: [{ name: "DUAL", span: span(24, 27) }],
|
||||
columns: [],
|
||||
};
|
||||
|
||||
const diagnostics = buildSqlSemanticDiagnostics(analysis, {
|
||||
tables: [],
|
||||
columnsByTable: new Map(),
|
||||
missingTables: new Set(["dual"]),
|
||||
databaseType: "mysql",
|
||||
});
|
||||
|
||||
assert.deepEqual(diagnostics, []);
|
||||
});
|
||||
|
||||
test("trims whitespace from missing table diagnostic spans", () => {
|
||||
const analysis: SqlReferenceAnalysis = {
|
||||
tables: [{ name: "t_00011", span: span(32, 39) }],
|
||||
|
||||
Reference in New Issue
Block a user