feat(viewer): erDiagram shows real column types when present; ArtifactView accepts a {content} sql wrapper
erDiagram reads data_type/type from the candidate (top-level or signals),
falling back to the generic 'col' token Mermaid needs — real types render as
soon as schema_linking.json carries them. ArtifactView gains a defensive
{content} fallback for sql-kind artifacts, verified against real session shapes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,11 @@ test("sql artifact renders the SQL text", () => {
|
||||
expect(screen.getByText("SELECT 1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("sql artifact accepts a { content } wrapper", () => {
|
||||
render(<ArtifactView artifact={{ kind: "sql", data: { content: "SELECT 7" } }} />);
|
||||
expect(screen.getByText("SELECT 7")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("cte_plan renders an ordered list of names", () => {
|
||||
render(<ArtifactView artifact={{ kind: "cte_plan", data: { names: ["a_cte", "b_cte"] } }} />);
|
||||
expect(screen.getByText("a_cte")).toBeInTheDocument();
|
||||
|
||||
@@ -38,6 +38,7 @@ function toSqlBlocks(raw: unknown): SqlBlock[] | null {
|
||||
const rec = asRecord(raw);
|
||||
if (!rec) return null;
|
||||
if (typeof rec.sql === "string") return [{ name: "SQL", sql: rec.sql }];
|
||||
if (typeof rec.content === "string") return [{ name: "SQL", sql: rec.content }];
|
||||
if (Array.isArray(rec.ctes)) {
|
||||
const blocks = rec.ctes
|
||||
.map((c) => asRecord(c))
|
||||
|
||||
@@ -97,3 +97,16 @@ test("(e) buildErDiagram emits entities, attributes and a relation", () => {
|
||||
expect(def).toContain("col id");
|
||||
expect(def).toContain("orders }o--o{ customers : join");
|
||||
});
|
||||
|
||||
test("(f) buildErDiagram uses a column's real type when the data carries one", () => {
|
||||
const def = buildErDiagram(
|
||||
[
|
||||
{ kind: "table", name: "orders", decision: "promoted" },
|
||||
{ kind: "column", name: "orders.total", decision: "promoted", signals: { data_type: "numeric" } },
|
||||
{ kind: "column", name: "orders.id", decision: "promoted" }, // no type -> generic "col"
|
||||
],
|
||||
[],
|
||||
);
|
||||
expect(def).toContain("numeric total");
|
||||
expect(def).toContain("col id");
|
||||
});
|
||||
|
||||
@@ -33,6 +33,19 @@ function reasonFor(c: Candidate): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
// The real DB type for a promoted column, when the schema-linking data carries it
|
||||
// (`data_type`/`type`, at top level or under `signals`). Today's schema_linking.json
|
||||
// has no column types, so this falls back to the generic `col` token that Mermaid
|
||||
// still needs as the attribute type; a real type shows as soon as the pipeline
|
||||
// provides one. The token is sanitized to keep the erDiagram valid.
|
||||
function attrType(c: Candidate): string {
|
||||
const sig = (c.signals ?? {}) as Record<string, unknown>;
|
||||
const rec = c as unknown as Record<string, unknown>;
|
||||
const raw = sig["data_type"] ?? sig["type"] ?? rec["data_type"] ?? rec["type"];
|
||||
const t = typeof raw === "string" && raw.trim() ? raw.trim() : "col";
|
||||
return t.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
}
|
||||
|
||||
export function buildErDiagram(promoted: Candidate[], joins: Join[]): string {
|
||||
const sanitize = (name: string) => name.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
const tables = promoted.filter((c) => c.kind === "table");
|
||||
@@ -46,7 +59,7 @@ export function buildErDiagram(promoted: Candidate[], joins: Join[]): string {
|
||||
const cols = columns.filter((col) => col.name.startsWith(t.name + "."));
|
||||
lines.push(` ${id} {`);
|
||||
for (const col of cols) {
|
||||
lines.push(` col ${sanitize(col.name.slice(t.name.length + 1))}`);
|
||||
lines.push(` ${attrType(col)} ${sanitize(col.name.slice(t.name.length + 1))}`);
|
||||
}
|
||||
lines.push(` }`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user