Files
dbx/apps/desktop/src/lib/__tests__/sql/multiSourceColumnMapping.spec.ts
T

198 lines
7.9 KiB
TypeScript
Vendored

import { describe, expect, it } from "vitest";
import { analyzeEditableQueryEditability, analyzeSelectStructureForDisplay, resolveSourceColumnsByOrdinal } from "@/lib/sql/sqlAnalysis";
/**
* Result columns resolve by projection ordinal, each carrying its source
* identity (sourceKey + canonical source column), so joined results keep
* per-source comments instead of first-source-wins on name clashes.
*/
describe("multi-source result column mapping", () => {
it("expands a bare star in a joined result for display metadata", () => {
const analysis = analyzeSelectStructureForDisplay("SELECT * FROM orders a JOIN users b ON a.user_id = b.id");
expect(analysis?.selectStar).toBe(true);
const resolved = resolveSourceColumnsByOrdinal(
"oracle",
analysis!,
[
{ source: analysis!.sources![0]!, columns: [{ name: "ID" }, { name: "USER_ID" }] },
{ source: analysis!.sources![1]!, columns: [{ name: "ID" }, { name: "NAME" }] },
],
4,
);
expect(resolved).toEqual([
{ sourceKey: "a:0", sourceColumn: "ID" },
{ sourceKey: "a:0", sourceColumn: "USER_ID" },
{ sourceKey: "b:1", sourceColumn: "ID" },
{ sourceKey: "b:1", sourceColumn: "NAME" },
]);
});
it("parses a JOIN as multi-source with per-source columns", () => {
const result = analyzeEditableQueryEditability("SELECT a.id, a.user_id, b.name FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const sources = result.analysis.sources!;
expect(sources.map((source) => source.tableName)).toEqual(["orders", "users"]);
expect(result.analysis.columns.map((column) => column.resultName)).toEqual(["id", "user_id", "name"]);
expect(result.analysis.columns.map((column) => column.sourceKey)).toEqual(["a:0", "a:0", "b:1"]);
});
it("maps each JOIN result column back to (source, column) by ordinal", () => {
const result = analyzeEditableQueryEditability("SELECT a.id, a.user_id, b.name FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"mysql",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }, { name: "amount" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "name" }] },
],
3,
);
expect(resolved).toEqual([
{ sourceKey: "a:0", sourceColumn: "id" },
{ sourceKey: "a:0", sourceColumn: "user_id" },
{ sourceKey: "b:1", sourceColumn: "name" },
]);
});
it("keeps duplicate result column names resolved per source (both tables have id)", () => {
const result = analyzeEditableQueryEditability("SELECT a.id, b.id FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"mysql",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "name" }] },
],
2,
);
// Result column #0 is orders.id, #1 is users.id — no first-source-wins.
expect(resolved).toEqual([
{ sourceKey: "a:0", sourceColumn: "id" },
{ sourceKey: "b:1", sourceColumn: "id" },
]);
});
it("resolves a uniquely qualified unqualified alias (name AS username) back to its physical column", () => {
const result = analyzeEditableQueryEditability("SELECT name AS username FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"mysql",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }, { name: "amount" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "name" }] },
],
1,
);
expect(resolved).toEqual([{ sourceKey: "b:1", sourceColumn: "name" }]);
});
it("returns undefined for an ambiguous unqualified column shared by several sources", () => {
const result = analyzeEditableQueryEditability("SELECT id FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"mysql",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "name" }] },
],
1,
);
expect(resolved).toEqual([undefined]);
});
it("resolves quoted mixed-case identifiers exactly (case preserved)", () => {
const result = analyzeEditableQueryEditability('SELECT a."ID", b."Name" FROM orders a JOIN users b ON a.user_id = b.id');
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"postgres",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "ID" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "Name" }] },
],
2,
);
expect(resolved).toEqual([
{ sourceKey: "a:0", sourceColumn: "ID" },
{ sourceKey: "b:1", sourceColumn: "Name" },
]);
});
it("does not collapse an unquoted lower-case name onto a distinct quoted mixed-case column", () => {
const result = analyzeEditableQueryEditability('SELECT a.id, b."ID" FROM orders a JOIN users b ON a.user_id = b.id');
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"postgres",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "ID" }, { name: "name" }] },
],
2,
);
// a.id folds to postgres `id`; b."ID" matches the quoted column exactly.
expect(resolved).toEqual([
{ sourceKey: "a:0", sourceColumn: "id" },
{ sourceKey: "b:1", sourceColumn: "ID" },
]);
});
it("expands a qualified star projection in projection order", () => {
const result = analyzeEditableQueryEditability("SELECT a.*, b.name FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"mysql",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }, { name: "amount" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "name" }] },
],
4,
);
expect(resolved).toEqual([
{ sourceKey: "a:0", sourceColumn: "id" },
{ sourceKey: "a:0", sourceColumn: "user_id" },
{ sourceKey: "a:0", sourceColumn: "amount" },
{ sourceKey: "b:1", sourceColumn: "name" },
]);
});
it("returns undefined for computed columns and extra result columns", () => {
const result = analyzeEditableQueryEditability("SELECT a.id, a.amount + b.id AS total, b.name FROM orders a JOIN users b ON a.user_id = b.id");
expect(result.editable).toBe(true);
if (!result.editable) return;
const resolved = resolveSourceColumnsByOrdinal(
"mysql",
result.analysis,
[
{ source: result.analysis.sources![0]!, columns: [{ name: "id" }, { name: "user_id" }, { name: "amount" }] },
{ source: result.analysis.sources![1]!, columns: [{ name: "id" }, { name: "name" }] },
],
3,
);
expect(resolved).toEqual([{ sourceKey: "a:0", sourceColumn: "id" }, undefined, { sourceKey: "b:1", sourceColumn: "name" }]);
});
});