fix(sqlserver): avoid false unknown column diagnostics
This commit is contained in:
parent
9b702b86bf
commit
ac4313afee
|
|
@ -393,6 +393,7 @@ async function ensureColumnsForTable(table: { name: string; schema?: string | nu
|
|||
table.name,
|
||||
table.schema ?? undefined,
|
||||
);
|
||||
if (columns.length === 0) return;
|
||||
cachedColumnsByTable.set(cacheKey, columns);
|
||||
}
|
||||
|
||||
|
|
@ -754,10 +755,12 @@ async function provideSqlCompletions(
|
|||
completionContext.insertSchema,
|
||||
);
|
||||
if (epoch !== completionEpoch) return null;
|
||||
const insertKey = completionContext.insertSchema
|
||||
? `${completionContext.insertSchema}.${completionContext.insertTable}`
|
||||
: completionContext.insertTable;
|
||||
insertColumnsByTable.set(insertKey, insertCols);
|
||||
if (insertCols.length > 0) {
|
||||
const insertKey = completionContext.insertSchema
|
||||
? `${completionContext.insertSchema}.${completionContext.insertTable}`
|
||||
: completionContext.insertTable;
|
||||
insertColumnsByTable.set(insertKey, insertCols);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
@ -865,6 +868,7 @@ async function provideSqlCompletions(
|
|||
refTable.schema,
|
||||
);
|
||||
if (epoch !== completionEpoch) return;
|
||||
if (columns.length === 0) return;
|
||||
cachedColumnsByTable.set(cacheKey, columns);
|
||||
} catch (e) {
|
||||
console.error(`[DBX] Failed to load columns for ${cacheKey}:`, e);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,9 @@ function columnsForTable(
|
|||
const keys = table.schema ? [`${table.schema}.${table.name}`, table.name] : [table.name];
|
||||
for (const key of keys) {
|
||||
const columns = columnsByTable.get(key) ?? columnsByTable.get(normalizeName(key));
|
||||
if (columns) return columns;
|
||||
// Empty metadata usually means the upstream schema lookup was inconclusive,
|
||||
// so avoid surfacing a false "unknown column" warning.
|
||||
if (columns && columns.length > 0) return columns;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1419,6 +1419,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
table: string,
|
||||
schema?: string,
|
||||
): Promise<SqlCompletionColumn[]> {
|
||||
if (isSchemaAwareDatabase(connectionId) && !schema) {
|
||||
return [];
|
||||
}
|
||||
const cacheKey = `${connectionId}:${database}:${schema || ""}:${table}`;
|
||||
if (!completionColumnsCache.value[cacheKey]) {
|
||||
await ensureConnected(connectionId);
|
||||
|
|
|
|||
|
|
@ -21,3 +21,17 @@ fn extracts_unqualified_columns_from_single_table_select() {
|
|||
analysis.columns.iter().map(|column| (column.qualifier.as_deref(), column.name.as_str())).collect();
|
||||
assert_eq!(columns, vec![(None, "missing"), (None, "id")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_unqualified_order_by_columns_for_sqlserver_queries() {
|
||||
let analysis =
|
||||
analyze_sql_references("SELECT * FROM Evt_GCM_Qop_Info ORDER BY PDReceiveDatePartInfo DESC", Some("sqlserver"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(analysis.tables.len(), 1);
|
||||
assert_eq!(analysis.tables[0].name, "Evt_GCM_Qop_Info");
|
||||
|
||||
let columns: Vec<_> =
|
||||
analysis.columns.iter().map(|column| (column.qualifier.as_deref(), column.name.as_str())).collect();
|
||||
assert_eq!(columns, vec![(None, "PDReceiveDatePartInfo")]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { useConnectionStore } from "../../apps/desktop/src/stores/connectionStore.ts";
|
||||
import { buildSqlSemanticDiagnostics } from "../../apps/desktop/src/lib/sqlSemanticDiagnostics.ts";
|
||||
import type { ConnectionConfig, SqlReferenceAnalysis } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
const span = (startColumn: number, endColumn: number) => ({
|
||||
start_line: 1,
|
||||
start_column: startColumn,
|
||||
end_line: 1,
|
||||
end_column: endColumn,
|
||||
});
|
||||
|
||||
function installMemoryStorage(initial: Record<string, string> = {}) {
|
||||
const values = new Map(Object.entries(initial));
|
||||
const original = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
||||
Object.defineProperty(globalThis, "localStorage", {
|
||||
configurable: true,
|
||||
value: {
|
||||
getItem: (key: string) => values.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => values.set(key, value),
|
||||
removeItem: (key: string) => values.delete(key),
|
||||
clear: () => values.clear(),
|
||||
},
|
||||
});
|
||||
return {
|
||||
restore() {
|
||||
if (original) Object.defineProperty(globalThis, "localStorage", original);
|
||||
else Reflect.deleteProperty(globalThis, "localStorage");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sqlServerConn(): ConnectionConfig {
|
||||
return {
|
||||
id: "sqlserver-1",
|
||||
name: "SQL Server",
|
||||
db_type: "sqlserver",
|
||||
host: "localhost",
|
||||
port: 1433,
|
||||
username: "sa",
|
||||
password: "",
|
||||
database: "appdb",
|
||||
};
|
||||
}
|
||||
|
||||
test("semantic diagnostics skip warnings when column metadata is inconclusive", () => {
|
||||
const analysis: SqlReferenceAnalysis = {
|
||||
tables: [{ name: "Evt_GCM_Qop_Info", span: span(15, 30) }],
|
||||
columns: [{ name: "PDReceiveDatePartInfo", span: span(40, 60) }],
|
||||
};
|
||||
|
||||
const diagnostics = buildSqlSemanticDiagnostics(analysis, {
|
||||
tables: [{ name: "Evt_GCM_Qop_Info", type: "table" }],
|
||||
columnsByTable: new Map([["Evt_GCM_Qop_Info", []]]),
|
||||
});
|
||||
|
||||
assert.deepEqual(diagnostics, []);
|
||||
});
|
||||
|
||||
test("sqlserver completion columns do not query using database name as schema fallback", async () => {
|
||||
const storage = installMemoryStorage();
|
||||
const originalFetch = globalThis.fetch;
|
||||
const requests: string[] = [];
|
||||
|
||||
globalThis.fetch = (async (input) => {
|
||||
const url = String(input);
|
||||
requests.push(url);
|
||||
if (url === "/api/connection/list") {
|
||||
return new Response(JSON.stringify([sqlServerConn()]), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (url === "/api/layout/sidebar") {
|
||||
return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
}
|
||||
if (url === "/api/connection/connect") {
|
||||
return new Response(JSON.stringify("ok"), { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
}
|
||||
if (url.startsWith("/api/schema/columns?")) {
|
||||
throw new Error(`unexpected columns lookup: ${url}`);
|
||||
}
|
||||
return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
setActivePinia(createPinia());
|
||||
const store = useConnectionStore();
|
||||
await store.initFromDisk();
|
||||
|
||||
const columns = await store.listCompletionColumns("sqlserver-1", "appdb", "Evt_GCM_Qop_Info");
|
||||
|
||||
assert.deepEqual(columns, []);
|
||||
assert.equal(
|
||||
requests.some((url) => url.startsWith("/api/schema/columns?")),
|
||||
false,
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
storage.restore();
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue