Skip to content

Commit 58e4eee

Browse files
authored
demo: include custom table renderer (#9)
* include custom table renderer * naming * rename file
1 parent 3d834c4 commit 58e4eee

3 files changed

Lines changed: 353 additions & 6 deletions

File tree

demo/custom-renderers.ts

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import type { NamespaceTooltipData } from "../src/sql/hover.js";
2+
3+
interface ColumnMetadata {
4+
type: string;
5+
primaryKey?: boolean;
6+
foreignKey?: boolean;
7+
unique?: boolean;
8+
default?: string;
9+
notNull?: boolean;
10+
comment?: string;
11+
}
12+
13+
interface ForeignKeyMetadata {
14+
column: string;
15+
referencedTable: string;
16+
referencedColumn: string;
17+
}
18+
19+
interface IndexMetadata {
20+
name: string;
21+
columns: string[];
22+
unique: boolean;
23+
}
24+
25+
interface TableMetadata {
26+
description: string;
27+
rowCount: string;
28+
columns: Record<string, ColumnMetadata>;
29+
foreignKeys: ForeignKeyMetadata[];
30+
indexes: IndexMetadata[];
31+
}
32+
33+
export type Schema = "users" | "posts" | "orders" | "customers" | "categories";
34+
35+
export const tableTooltipRenderer = (data: NamespaceTooltipData) => {
36+
// Show table name, columns, description, primary key, foreign key, index, unique, check, default, comment
37+
const table = data.item.path.join(".");
38+
const columns = data.item.namespace?.[table] ?? [];
39+
40+
// Enhanced table metadata (simulated for demo purposes)
41+
const tableMetadata = getTableMetadata(table);
42+
43+
let tooltip = `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 13px; line-height: 1.4;">`;
44+
45+
// Table header
46+
tooltip += `<div style="font-weight: 600; font-size: 14px; color: #1f2937; margin-bottom: 8px; border-bottom: 1px solid #e5e7eb; padding-bottom: 4px;">📋 Table: ${table}</div>`;
47+
48+
// Description
49+
if (tableMetadata.description) {
50+
tooltip += `<div style="color: #6b7280; font-style: italic; margin-bottom: 12px;">${tableMetadata.description}</div>`;
51+
}
52+
53+
// Column details in a table
54+
if (columns.length > 0) {
55+
tooltip += `<div style="margin-bottom: 12px;">`;
56+
tooltip += `<div style="font-weight: 600; color: #374151; margin-bottom: 6px;">📊 Columns</div>`;
57+
tooltip += `<table style="width: 100%; border-collapse: collapse; font-size: 12px;">`;
58+
tooltip += `<thead><tr style="background-color: #f9fafb;">`;
59+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Column</th>`;
60+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Type</th>`;
61+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Constraints</th>`;
62+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Description</th>`;
63+
tooltip += `</tr></thead><tbody>`;
64+
65+
columns.forEach((column) => {
66+
const columnInfo = tableMetadata.columns[column];
67+
const constraints: string[] = [];
68+
69+
if (columnInfo) {
70+
if (columnInfo.primaryKey) constraints.push("🔑 PK");
71+
if (columnInfo.foreignKey) constraints.push("🔗 FK");
72+
if (columnInfo.unique) constraints.push("✨ UNIQUE");
73+
if (columnInfo.notNull) constraints.push("❌ NOT NULL");
74+
if (columnInfo.default) constraints.push(`💡 DEFAULT ${columnInfo.default}`);
75+
76+
tooltip += `<tr style="border-bottom: 1px solid #f3f4f6;">`;
77+
tooltip += `<td style="padding: 4px 8px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; color: #059669;">${column}</td>`;
78+
tooltip += `<td style="padding: 4px 8px; color: #7c3aed;">${columnInfo.type}</td>`;
79+
tooltip += `<td style="padding: 4px 8px; color: #dc2626;">${constraints.join(" ")}</td>`;
80+
tooltip += `<td style="padding: 4px 8px; color: #6b7280; font-size: 11px;">${columnInfo.comment || ""}</td>`;
81+
tooltip += `</tr>`;
82+
} else {
83+
tooltip += `<tr style="border-bottom: 1px solid #f3f4f6;">`;
84+
tooltip += `<td style="padding: 4px 8px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; color: #059669;">${column}</td>`;
85+
tooltip += `<td style="padding: 4px 8px; color: #7c3aed;">-</td>`;
86+
tooltip += `<td style="padding: 4px 8px; color: #dc2626;">-</td>`;
87+
tooltip += `<td style="padding: 4px 8px; color: #6b7280; font-size: 11px;">-</td>`;
88+
tooltip += `</tr>`;
89+
}
90+
});
91+
92+
tooltip += `</tbody></table></div>`;
93+
}
94+
95+
// Foreign key relationships
96+
if (tableMetadata.foreignKeys.length > 0) {
97+
tooltip += `<div style="margin-bottom: 12px;">`;
98+
tooltip += `<div style="font-weight: 600; color: #374151; margin-bottom: 6px;">🔗 Foreign Keys</div>`;
99+
tooltip += `<table style="width: 100%; border-collapse: collapse; font-size: 12px;">`;
100+
tooltip += `<thead><tr style="background-color: #f9fafb;">`;
101+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Column</th>`;
102+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">References</th>`;
103+
tooltip += `</tr></thead><tbody>`;
104+
105+
tableMetadata.foreignKeys.forEach((fk) => {
106+
tooltip += `<tr style="border-bottom: 1px solid #f3f4f6;">`;
107+
tooltip += `<td style="padding: 4px 8px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; color: #059669;">${fk.column}</td>`;
108+
tooltip += `<td style="padding: 4px 8px; color: #7c3aed;">${fk.referencedTable}.${fk.referencedColumn}</td>`;
109+
tooltip += `</tr>`;
110+
});
111+
112+
tooltip += `</tbody></table></div>`;
113+
}
114+
115+
// Indexes
116+
if (tableMetadata.indexes.length > 0) {
117+
tooltip += `<div style="margin-bottom: 12px;">`;
118+
tooltip += `<div style="font-weight: 600; color: #374151; margin-bottom: 6px;">📈 Indexes</div>`;
119+
tooltip += `<table style="width: 100%; border-collapse: collapse; font-size: 12px;">`;
120+
tooltip += `<thead><tr style="background-color: #f9fafb;">`;
121+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Name</th>`;
122+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Columns</th>`;
123+
tooltip += `<th style="text-align: left; padding: 4px 8px; border: 1px solid #e5e7eb; font-weight: 600;">Type</th>`;
124+
tooltip += `</tr></thead><tbody>`;
125+
126+
tableMetadata.indexes.forEach((index) => {
127+
tooltip += `<tr style="border-bottom: 1px solid #f3f4f6;">`;
128+
tooltip += `<td style="padding: 4px 8px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; color: #059669;">${index.name}</td>`;
129+
tooltip += `<td style="padding: 4px 8px; color: #7c3aed;">${index.columns.join(", ")}</td>`;
130+
tooltip += `<td style="padding: 4px 8px; color: #dc2626;">${index.unique ? "UNIQUE" : "NORMAL"}</td>`;
131+
tooltip += `</tr>`;
132+
});
133+
134+
tooltip += `</tbody></table></div>`;
135+
}
136+
137+
// Table statistics
138+
tooltip += `<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 11px;">`;
139+
tooltip += `📊 ${tableMetadata.rowCount} rows`;
140+
tooltip += `</div>`;
141+
142+
tooltip += `</div>`;
143+
144+
return tooltip;
145+
};
146+
147+
// Helper function to get enhanced table metadata
148+
function getTableMetadata(tableName: string): TableMetadata {
149+
const metadata: Record<Schema, TableMetadata> = {
150+
users: {
151+
description: "User accounts and profile information",
152+
rowCount: "1,234",
153+
columns: {
154+
id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique user identifier" },
155+
name: { type: "VARCHAR(255)", notNull: true, comment: "User's full name" },
156+
email: {
157+
type: "VARCHAR(255)",
158+
unique: true,
159+
notNull: true,
160+
comment: "User's email address",
161+
},
162+
active: { type: "BOOLEAN", default: "true", comment: "Whether the user account is active" },
163+
status: {
164+
type: "ENUM('active','inactive','suspended')",
165+
default: "'active'",
166+
comment: "User account status",
167+
},
168+
created_at: {
169+
type: "TIMESTAMP",
170+
default: "CURRENT_TIMESTAMP",
171+
comment: "Account creation date",
172+
},
173+
updated_at: {
174+
type: "TIMESTAMP",
175+
default: "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
176+
comment: "Last update timestamp",
177+
},
178+
profile_id: { type: "INT", foreignKey: true, comment: "Reference to user profile" },
179+
},
180+
foreignKeys: [{ column: "profile_id", referencedTable: "profiles", referencedColumn: "id" }],
181+
indexes: [
182+
{ name: "idx_users_email", columns: ["email"], unique: true },
183+
{ name: "idx_users_status", columns: ["status"], unique: false },
184+
{ name: "idx_users_created", columns: ["created_at"], unique: false },
185+
],
186+
},
187+
posts: {
188+
description: "Blog posts and articles",
189+
rowCount: "5,678",
190+
columns: {
191+
id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique post identifier" },
192+
title: { type: "VARCHAR(255)", notNull: true, comment: "Post title" },
193+
content: { type: "TEXT", comment: "Post content" },
194+
user_id: { type: "INT", notNull: true, foreignKey: true, comment: "Author of the post" },
195+
published: { type: "BOOLEAN", default: "false", comment: "Publication status" },
196+
created_at: {
197+
type: "TIMESTAMP",
198+
default: "CURRENT_TIMESTAMP",
199+
comment: "Post creation date",
200+
},
201+
updated_at: {
202+
type: "TIMESTAMP",
203+
default: "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
204+
comment: "Last update timestamp",
205+
},
206+
category_id: { type: "INT", foreignKey: true, comment: "Post category" },
207+
},
208+
foreignKeys: [
209+
{ column: "user_id", referencedTable: "users", referencedColumn: "id" },
210+
{ column: "category_id", referencedTable: "categories", referencedColumn: "id" },
211+
],
212+
indexes: [
213+
{ name: "idx_posts_user", columns: ["user_id"], unique: false },
214+
{ name: "idx_posts_published", columns: ["published"], unique: false },
215+
{ name: "idx_posts_created", columns: ["created_at"], unique: false },
216+
],
217+
},
218+
orders: {
219+
description: "Customer orders and transactions",
220+
rowCount: "12,345",
221+
columns: {
222+
id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique order identifier" },
223+
customer_id: {
224+
type: "INT",
225+
notNull: true,
226+
foreignKey: true,
227+
comment: "Customer who placed the order",
228+
},
229+
order_date: { type: "DATE", notNull: true, comment: "Date when order was placed" },
230+
total_amount: { type: "DECIMAL(10,2)", notNull: true, comment: "Total order amount" },
231+
status: {
232+
type: "ENUM('pending','processing','shipped','delivered','cancelled')",
233+
default: "'pending'",
234+
comment: "Order status",
235+
},
236+
shipping_address: { type: "TEXT", comment: "Shipping address" },
237+
created_at: {
238+
type: "TIMESTAMP",
239+
default: "CURRENT_TIMESTAMP",
240+
comment: "Order creation timestamp",
241+
},
242+
},
243+
foreignKeys: [
244+
{ column: "customer_id", referencedTable: "customers", referencedColumn: "id" },
245+
],
246+
indexes: [
247+
{ name: "idx_orders_customer", columns: ["customer_id"], unique: false },
248+
{ name: "idx_orders_date", columns: ["order_date"], unique: false },
249+
{ name: "idx_orders_status", columns: ["status"], unique: false },
250+
],
251+
},
252+
customers: {
253+
description: "Customer information and profiles",
254+
rowCount: "2,345",
255+
columns: {
256+
id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique customer identifier" },
257+
first_name: { type: "VARCHAR(100)", notNull: true, comment: "Customer's first name" },
258+
last_name: { type: "VARCHAR(100)", notNull: true, comment: "Customer's last name" },
259+
email: {
260+
type: "VARCHAR(255)",
261+
unique: true,
262+
notNull: true,
263+
comment: "Customer's email address",
264+
},
265+
phone: { type: "VARCHAR(20)", comment: "Customer's phone number" },
266+
address: { type: "TEXT", comment: "Customer's address" },
267+
city: { type: "VARCHAR(100)", comment: "Customer's city" },
268+
country: { type: "VARCHAR(100)", comment: "Customer's country" },
269+
},
270+
foreignKeys: [],
271+
indexes: [
272+
{ name: "idx_customers_email", columns: ["email"], unique: true },
273+
{ name: "idx_customers_name", columns: ["last_name", "first_name"], unique: false },
274+
],
275+
},
276+
categories: {
277+
description: "Product and post categories",
278+
rowCount: "89",
279+
columns: {
280+
id: { type: "INT", primaryKey: true, notNull: true, comment: "Unique category identifier" },
281+
name: { type: "VARCHAR(100)", notNull: true, comment: "Category name" },
282+
description: { type: "TEXT", comment: "Category description" },
283+
parent_id: {
284+
type: "INT",
285+
foreignKey: true,
286+
comment: "Parent category for hierarchical structure",
287+
},
288+
},
289+
foreignKeys: [{ column: "parent_id", referencedTable: "categories", referencedColumn: "id" }],
290+
indexes: [
291+
{ name: "idx_categories_name", columns: ["name"], unique: false },
292+
{ name: "idx_categories_parent", columns: ["parent_id"], unique: false },
293+
],
294+
},
295+
};
296+
297+
return (
298+
metadata[tableName] || {
299+
description: "Table information",
300+
rowCount: "Unknown",
301+
columns: {},
302+
foreignKeys: [],
303+
indexes: [],
304+
}
305+
);
306+
}

demo/index.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@
1313
<body>
1414
<main class="max-w-4xl mx-auto px-4 py-8">
1515
<h1 class="text-4xl font-bold mb-4">
16-
<a href="https://github.com/marimo-team/codemirror-sql" class="text-blue-600 hover:text-blue-800"
16+
<a href="https://github.com/marimo-team/codemirror-sql" class="text-blue-600 hover:text-blue-800" target="_blank"
1717
>codemirror-sql</a
1818
>
1919
</h1>
20-
<p class="mb-2">by <a href="https://marimo.io/" class="text-blue-600 hover:text-blue-800">marimo</a></p>
20+
<p class="mb-2">by <a href="https://marimo.io/" class="text-blue-600 hover:text-blue-800" target="_blank">marimo</a></p>
2121
<p class="mb-6 text-gray-700">
2222
A CodeMirror extension for SQL with real-time syntax validation and error diagnostics using
23-
<a href="https://www.npmjs.com/package/node-sql-parser" class="text-blue-600 hover:text-blue-800">node-sql-parser</a>.
23+
<a href="https://www.npmjs.com/package/node-sql-parser" class="text-blue-600 hover:text-blue-800" target="_blank"
24+
>node-sql-parser</a
25+
>.
2426
</p>
2527

2628
<div class="grid gap-8">

0 commit comments

Comments
 (0)