@marimo-team/islands 0.25.3-dev134 → 0.25.3-dev136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ErrorBoundary-CImYa86e.js → ErrorBoundary-BuBLWxuk.js} +1 -1
- package/dist/{chat-ui-DqcauBr6.js → chat-ui-CvB8Map3.js} +3 -3
- package/dist/{code-visibility-BZoWT5f1.js → code-visibility-CwJEaaN5.js} +1 -1
- package/dist/{common-CGmIoGK3.js → common-lvg18mZi.js} +386 -386
- package/dist/{data-grid-overlay-editor-CtOna148.js → data-grid-overlay-editor-BGhLvQMo.js} +1 -1
- package/dist/{dropdown-menu-CgL3fm8P.js → dropdown-menu-GWcGCcxE.js} +71 -56
- package/dist/{feedback-button-7ZbDdjfz.js → feedback-button-BgwhkoQn.js} +2 -2
- package/dist/{glide-data-editor-D72zmCan.js → glide-data-editor-CWLd9Tfd.js} +4 -4
- package/dist/{globals-DE2S2io9.js → globals-CvsmUhAp.js} +1 -1
- package/dist/{html-to-image-CfgsWObF.js → html-to-image-BrC8OJcj.js} +3 -3
- package/dist/main.js +5171 -5178
- package/dist/{number-overlay-editor-Br0lPnFE.js → number-overlay-editor-DAYzjL3A.js} +1 -1
- package/dist/{process-output-B64ZYWuI.js → process-output-Bw-vzyoy.js} +1 -1
- package/dist/{reveal-component-DnbPU885.js → reveal-component-De2-S3OE.js} +3 -3
- package/package.json +1 -1
- package/src/components/datasources/__tests__/column-preview.test.tsx +35 -3
- package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
- package/src/components/datasources/__tests__/utils.test.ts +57 -6
- package/src/components/datasources/column-preview.tsx +5 -2
- package/src/components/datasources/datasources.tsx +3 -4
- package/src/components/datasources/utils.ts +32 -23
- package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
- package/src/components/ui/range-slider.tsx +22 -9
- package/src/plugins/impl/RangeSliderPlugin.tsx +17 -2
- package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
- package/src/utils/__tests__/numbers.test.ts +57 -0
- package/src/utils/numbers.ts +59 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
2
|
import { t } from "./react-sHtZdjeP.js";
|
|
3
|
-
import { a as n } from "./glide-data-editor-
|
|
3
|
+
import { a as n } from "./glide-data-editor-CWLd9Tfd.js";
|
|
4
4
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.3.0_27cffe7f1bc30287d59ac27db3240c5b/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
|
|
5
5
|
var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
|
|
6
6
|
name: "NumberOverlayEditorStyle",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as e } from "./html-to-image-
|
|
1
|
+
import { O as e } from "./html-to-image-BrC8OJcj.js";
|
|
2
2
|
import { m as t } from "./tooltip-CJ4Rqasp.js";
|
|
3
3
|
import { T as n, w as r } from "./error-entries-CmFDQ9CA.js";
|
|
4
4
|
import { b as i } from "./select-CkF_GBxO.js";
|
|
@@ -4,14 +4,14 @@ import { t as o } from "./react-sHtZdjeP.js";
|
|
|
4
4
|
import { t as s } from "./compiler-runtime-CldRdSsv.js";
|
|
5
5
|
import { m as c, t as l } from "./tooltip-CJ4Rqasp.js";
|
|
6
6
|
import { k as u } from "./error-entries-CmFDQ9CA.js";
|
|
7
|
-
import { $ as d, Q as f, Z as p, an as m, i as h, l as g, ln as _, n as v, on as y, r as b, t as x, u as S } from "./common-
|
|
7
|
+
import { $ as d, Q as f, Z as p, an as m, i as h, l as g, ln as _, n as v, on as y, r as b, t as x, u as S } from "./common-lvg18mZi.js";
|
|
8
8
|
import { M as C } from "./select-CkF_GBxO.js";
|
|
9
|
-
import {
|
|
9
|
+
import { J as ee } from "./dropdown-menu-GWcGCcxE.js";
|
|
10
10
|
import { _ as w } from "./toDate-DGvRHwTk.js";
|
|
11
11
|
import { t as T } from "./jsx-runtime-3BD1kT0s.js";
|
|
12
12
|
import { q as E } from "./use-restore-focus-CmGwRNId.js";
|
|
13
13
|
import { A as D, C as O, b as k } from "./useTheme-Bq9pi_vz.js";
|
|
14
|
-
import { a as A, n as te, t as ne } from "./code-visibility-
|
|
14
|
+
import { a as A, n as te, t as ne } from "./code-visibility-CwJEaaN5.js";
|
|
15
15
|
/* empty css */
|
|
16
16
|
var j = c("sticky-note", [["path", {
|
|
17
17
|
d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
|
package/package.json
CHANGED
|
@@ -79,7 +79,9 @@ describe("DatasetColumnPreview fullyQualifiedTableName", () => {
|
|
|
79
79
|
it("qualifies with database and schema for flat engines", () => {
|
|
80
80
|
const client = renderPreview(ctx({ database: "db", schema: "public" }));
|
|
81
81
|
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
82
|
-
expect.objectContaining({
|
|
82
|
+
expect.objectContaining({
|
|
83
|
+
fullyQualifiedTableName: '"db"."public"."users"',
|
|
84
|
+
}),
|
|
83
85
|
);
|
|
84
86
|
});
|
|
85
87
|
|
|
@@ -87,7 +89,7 @@ describe("DatasetColumnPreview fullyQualifiedTableName", () => {
|
|
|
87
89
|
// Regression: previously produced "db..users".
|
|
88
90
|
const client = renderPreview(ctx({ database: "db", schema: "" }));
|
|
89
91
|
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
90
|
-
expect.objectContaining({ fullyQualifiedTableName: "db.users" }),
|
|
92
|
+
expect.objectContaining({ fullyQualifiedTableName: '"db"."users"' }),
|
|
91
93
|
);
|
|
92
94
|
});
|
|
93
95
|
|
|
@@ -98,7 +100,37 @@ describe("DatasetColumnPreview fullyQualifiedTableName", () => {
|
|
|
98
100
|
);
|
|
99
101
|
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
100
102
|
expect.objectContaining({
|
|
101
|
-
fullyQualifiedTableName: "top.nested.deep.users",
|
|
103
|
+
fullyQualifiedTableName: '"top"."nested"."deep"."users"',
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("quotes a literal dot as one schema segment", () => {
|
|
109
|
+
const client = renderPreview(
|
|
110
|
+
ctx({
|
|
111
|
+
database: "memory",
|
|
112
|
+
schema: "finance.reports",
|
|
113
|
+
schemaPath: ["finance.reports"],
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
fullyQualifiedTableName: '"memory"."finance.reports"."users"',
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("escapes quotes inside schema identifiers", () => {
|
|
124
|
+
const client = renderPreview(
|
|
125
|
+
ctx({
|
|
126
|
+
database: "memory",
|
|
127
|
+
schema: 'fi"nance',
|
|
128
|
+
schemaPath: ['fi"nance', "reports"],
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
fullyQualifiedTableName: '"memory"."fi""nance"."reports"."users"',
|
|
102
134
|
}),
|
|
103
135
|
);
|
|
104
136
|
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from "@testing-library/react";
|
|
10
|
+
import { createStore, Provider } from "jotai";
|
|
11
|
+
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
12
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
13
|
+
import { Command, CommandList } from "@/components/ui/command";
|
|
14
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
15
|
+
import {
|
|
16
|
+
dataSourceConnectionsAtom,
|
|
17
|
+
type SQLTableContext,
|
|
18
|
+
} from "@/core/datasets/data-source-connections";
|
|
19
|
+
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
20
|
+
import { datasetsAtom, exportedForTesting } from "@/core/datasets/state";
|
|
21
|
+
import type { DataTable } from "@/core/kernel/messages";
|
|
22
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
23
|
+
import {
|
|
24
|
+
DataSources,
|
|
25
|
+
hideEmptyDatasourcesAtom,
|
|
26
|
+
TableList,
|
|
27
|
+
} from "../datasources";
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const table: DataTable = {
|
|
34
|
+
name: "reports",
|
|
35
|
+
columns: [
|
|
36
|
+
{
|
|
37
|
+
name: "amount",
|
|
38
|
+
type: "integer",
|
|
39
|
+
external_type: "INTEGER",
|
|
40
|
+
sample_values: [],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
source: "memory",
|
|
44
|
+
source_type: "duckdb",
|
|
45
|
+
type: "table",
|
|
46
|
+
engine: null,
|
|
47
|
+
indexes: null,
|
|
48
|
+
num_columns: 1,
|
|
49
|
+
num_rows: null,
|
|
50
|
+
variable_name: null,
|
|
51
|
+
primary_keys: null,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe("Datasource column preview responses", () => {
|
|
55
|
+
it.each([
|
|
56
|
+
{ path: ["main"], qualifiedName: '"memory"."main"."reports"' },
|
|
57
|
+
{
|
|
58
|
+
path: ["finance", "reports"],
|
|
59
|
+
qualifiedName: '"memory"."finance"."reports"."reports"',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
path: ["finance.reports"],
|
|
63
|
+
qualifiedName: '"memory"."finance.reports"."reports"',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
path: ['fi"nance', "reports"],
|
|
67
|
+
qualifiedName: '"memory"."fi""nance"."reports"."reports"',
|
|
68
|
+
},
|
|
69
|
+
])(
|
|
70
|
+
"renders the returned preview for $path",
|
|
71
|
+
async ({ path, qualifiedName }) => {
|
|
72
|
+
const store = createStore();
|
|
73
|
+
const client = MockRequestClient.create();
|
|
74
|
+
store.set(requestClientAtom, client);
|
|
75
|
+
const context: SQLTableContext = {
|
|
76
|
+
engine: DUCKDB_ENGINE,
|
|
77
|
+
database: "memory",
|
|
78
|
+
schema: path.at(-1) ?? "",
|
|
79
|
+
schemaPath: path,
|
|
80
|
+
dialect: "duckdb",
|
|
81
|
+
};
|
|
82
|
+
render(
|
|
83
|
+
<Provider store={store}>
|
|
84
|
+
<TooltipProvider>
|
|
85
|
+
<Command shouldFilter={false}>
|
|
86
|
+
<CommandList>
|
|
87
|
+
<TableList tables={[table]} sqlTableContext={context} />
|
|
88
|
+
</CommandList>
|
|
89
|
+
</Command>
|
|
90
|
+
</TooltipProvider>
|
|
91
|
+
</Provider>,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
fireEvent.click(await screen.findByText("reports"));
|
|
95
|
+
fireEvent.click(await screen.findByText("amount"));
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
expect(client.previewDatasetColumn).toHaveBeenCalledWith(
|
|
98
|
+
expect.objectContaining({ fullyQualifiedTableName: qualifiedName }),
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
expect(screen.getByText("Loading...")).toBeInTheDocument();
|
|
102
|
+
|
|
103
|
+
const actions = exportedForTesting.createActions((action) => {
|
|
104
|
+
store.set(datasetsAtom, (state) =>
|
|
105
|
+
exportedForTesting.reducer(state, action),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
act(() => {
|
|
109
|
+
actions.addColumnPreview({
|
|
110
|
+
table_name: qualifiedName,
|
|
111
|
+
column_name: "amount",
|
|
112
|
+
stats: { min: 42 },
|
|
113
|
+
error: null,
|
|
114
|
+
missing_packages: null,
|
|
115
|
+
chart_spec: null,
|
|
116
|
+
chart_code: null,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(await screen.findByText("42")).toBeInTheDocument();
|
|
121
|
+
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("activates a schema and a table with the same path independently using the keyboard", async () => {
|
|
127
|
+
const store = createStore();
|
|
128
|
+
store.set(requestClientAtom, MockRequestClient.create());
|
|
129
|
+
store.set(hideEmptyDatasourcesAtom, false);
|
|
130
|
+
store.set(dataSourceConnectionsAtom, {
|
|
131
|
+
latestEngineSelected: DUCKDB_ENGINE,
|
|
132
|
+
connectionsMap: new Map([
|
|
133
|
+
[
|
|
134
|
+
DUCKDB_ENGINE,
|
|
135
|
+
{
|
|
136
|
+
name: DUCKDB_ENGINE,
|
|
137
|
+
dialect: "duckdb",
|
|
138
|
+
source: "duckdb",
|
|
139
|
+
display_name: "DuckDB (In-Memory)",
|
|
140
|
+
databases: [
|
|
141
|
+
{
|
|
142
|
+
name: "memory",
|
|
143
|
+
dialect: "duckdb",
|
|
144
|
+
schemas: [
|
|
145
|
+
{
|
|
146
|
+
name: "finance",
|
|
147
|
+
tables: [table],
|
|
148
|
+
child_schemas: [{ name: "reports", tables: [] }],
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
]),
|
|
156
|
+
});
|
|
157
|
+
render(
|
|
158
|
+
<Provider store={store}>
|
|
159
|
+
<TooltipProvider>
|
|
160
|
+
<DataSources />
|
|
161
|
+
</TooltipProvider>
|
|
162
|
+
</Provider>,
|
|
163
|
+
);
|
|
164
|
+
fireEvent.click(await screen.findByText("memory"));
|
|
165
|
+
fireEvent.click(await screen.findByText("finance"));
|
|
166
|
+
await waitFor(() => {
|
|
167
|
+
expect(screen.getAllByText("reports")).toHaveLength(2);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const input = screen.getByRole("combobox");
|
|
171
|
+
input.focus();
|
|
172
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
173
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
174
|
+
expect(await screen.findByText("No tables found")).toBeInTheDocument();
|
|
175
|
+
expect(screen.queryByText("amount")).not.toBeInTheDocument();
|
|
176
|
+
|
|
177
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
178
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
179
|
+
expect(await screen.findByText("amount")).toBeInTheDocument();
|
|
180
|
+
expect(screen.getByText("No tables found")).toBeInTheDocument();
|
|
181
|
+
});
|
|
@@ -34,6 +34,37 @@ describe("sqlCode", () => {
|
|
|
34
34
|
name: "email" as const,
|
|
35
35
|
} as DataTableColumn;
|
|
36
36
|
|
|
37
|
+
it.each([
|
|
38
|
+
{
|
|
39
|
+
path: ["finance", "reports"],
|
|
40
|
+
expected: '"memory"."finance"."reports"."users"',
|
|
41
|
+
},
|
|
42
|
+
{ path: ["finance.reports"], expected: '"finance.reports"."users"' },
|
|
43
|
+
{
|
|
44
|
+
path: ['fi"nance', "reports"],
|
|
45
|
+
expected: '"memory"."fi""nance"."reports"."users"',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: ["main", "reports"],
|
|
49
|
+
expected: '"memory"."main"."reports"."users"',
|
|
50
|
+
},
|
|
51
|
+
])("preserves schema path $path in generated SQL", ({ path, expected }) => {
|
|
52
|
+
const result = sqlCode({
|
|
53
|
+
table: mockTable,
|
|
54
|
+
columnName: "*",
|
|
55
|
+
sqlTableContext: {
|
|
56
|
+
engine: DUCKDB_ENGINE,
|
|
57
|
+
dialect: "duckdb",
|
|
58
|
+
database: "memory",
|
|
59
|
+
defaultDatabase: "memory",
|
|
60
|
+
defaultSchema: "main",
|
|
61
|
+
schema: path.at(-1)!,
|
|
62
|
+
schemaPath: path,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
expect(result).toContain(`FROM ${expected}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
37
68
|
describe("basic SQL generation", () => {
|
|
38
69
|
it("should generate basic SQL without sqlTableContext", () => {
|
|
39
70
|
const result = sqlCode({ table: mockTable, columnName: mockColumn.name });
|
|
@@ -484,7 +515,7 @@ describe("tableUniqueId", () => {
|
|
|
484
515
|
|
|
485
516
|
it("uses database + schema for flat engines", () => {
|
|
486
517
|
expect(tableUniqueId(ctx({ database: "db", schema: "public" }), "t")).toBe(
|
|
487
|
-
"db
|
|
518
|
+
'["db","public","t"]',
|
|
488
519
|
);
|
|
489
520
|
});
|
|
490
521
|
|
|
@@ -495,7 +526,7 @@ describe("tableUniqueId", () => {
|
|
|
495
526
|
ctx({ database: "top", schema: "nested", schemaPath: ["nested"] }),
|
|
496
527
|
"t",
|
|
497
528
|
),
|
|
498
|
-
).toBe("top
|
|
529
|
+
).toBe('["top","nested","t"]');
|
|
499
530
|
});
|
|
500
531
|
|
|
501
532
|
it("includes the full schema path for deeply nested namespaces", () => {
|
|
@@ -508,7 +539,7 @@ describe("tableUniqueId", () => {
|
|
|
508
539
|
}),
|
|
509
540
|
"t",
|
|
510
541
|
),
|
|
511
|
-
).toBe("top
|
|
542
|
+
).toBe('["top","nested","deep","t"]');
|
|
512
543
|
});
|
|
513
544
|
|
|
514
545
|
it("falls back to the flat schema when schemaPath is empty", () => {
|
|
@@ -517,16 +548,36 @@ describe("tableUniqueId", () => {
|
|
|
517
548
|
ctx({ database: "db", schema: "public", schemaPath: [] }),
|
|
518
549
|
"t",
|
|
519
550
|
),
|
|
520
|
-
).toBe("db
|
|
551
|
+
).toBe('["db","public","t"]');
|
|
521
552
|
});
|
|
522
553
|
|
|
523
554
|
it("does not emit a double dot for schemaless tables", () => {
|
|
524
555
|
expect(
|
|
525
556
|
tableUniqueId(ctx({ database: "db", schema: "", schemaPath: [] }), "t"),
|
|
526
|
-
).toBe("db
|
|
557
|
+
).toBe('["db","t"]');
|
|
527
558
|
expect(tableUniqueId(ctx({ database: "db", schema: "" }), "t")).toBe(
|
|
528
|
-
"db
|
|
559
|
+
'["db","t"]',
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("keeps literal-dot and nested schemas distinct", () => {
|
|
564
|
+
const nested = tableUniqueId(
|
|
565
|
+
ctx({
|
|
566
|
+
database: "memory",
|
|
567
|
+
schema: "reports",
|
|
568
|
+
schemaPath: ["finance", "reports"],
|
|
569
|
+
}),
|
|
570
|
+
"sales",
|
|
571
|
+
);
|
|
572
|
+
const literal = tableUniqueId(
|
|
573
|
+
ctx({
|
|
574
|
+
database: "memory",
|
|
575
|
+
schema: "finance.reports",
|
|
576
|
+
schemaPath: ["finance.reports"],
|
|
577
|
+
}),
|
|
578
|
+
"sales",
|
|
529
579
|
);
|
|
580
|
+
expect(nested).not.toBe(literal);
|
|
530
581
|
});
|
|
531
582
|
});
|
|
532
583
|
|
|
@@ -29,7 +29,7 @@ import { Button } from "../ui/button";
|
|
|
29
29
|
import { Tooltip } from "../ui/tooltip";
|
|
30
30
|
import { ColumnPreviewContainer } from "./components";
|
|
31
31
|
import { InstallPackageButton } from "./install-package-button";
|
|
32
|
-
import { convertStatsName,
|
|
32
|
+
import { convertStatsName, fullyQualifiedTableName, sqlCode } from "./utils";
|
|
33
33
|
|
|
34
34
|
export const DatasetColumnPreview: React.FC<{
|
|
35
35
|
table: DataTable;
|
|
@@ -48,7 +48,10 @@ export const DatasetColumnPreview: React.FC<{
|
|
|
48
48
|
tableName: table.name,
|
|
49
49
|
columnName: column.name,
|
|
50
50
|
sourceType: table.source_type,
|
|
51
|
-
fullyQualifiedTableName:
|
|
51
|
+
fullyQualifiedTableName: fullyQualifiedTableName(
|
|
52
|
+
sqlTableContext,
|
|
53
|
+
table.name,
|
|
54
|
+
),
|
|
52
55
|
});
|
|
53
56
|
};
|
|
54
57
|
|
|
@@ -78,6 +78,7 @@ import {
|
|
|
78
78
|
areChildSchemasResolved,
|
|
79
79
|
areSchemasResolved,
|
|
80
80
|
areTablesResolved,
|
|
81
|
+
fullyQualifiedTableName,
|
|
81
82
|
isSchemaless,
|
|
82
83
|
schemaSubtreeMatchesSearch,
|
|
83
84
|
shouldExpandDatabaseForSearch,
|
|
@@ -637,7 +638,7 @@ const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
|
|
|
637
638
|
const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
|
|
638
639
|
const [prevExpandForSearch, setPrevExpandForSearch] =
|
|
639
640
|
React.useState(expandForSearch);
|
|
640
|
-
const uniqueValue =
|
|
641
|
+
const uniqueValue = JSON.stringify(["schema", databaseName, ...schemaPath]);
|
|
641
642
|
const childSchemas = schema.child_schemas ?? [];
|
|
642
643
|
|
|
643
644
|
// Re-sync when the query changes or when newly resolved data turns this
|
|
@@ -1095,9 +1096,7 @@ const DatasetColumnItem: React.FC<{
|
|
|
1095
1096
|
column={column}
|
|
1096
1097
|
onAddColumnChart={handleAddColumn}
|
|
1097
1098
|
preview={columnsPreviews.get(
|
|
1098
|
-
sqlTableContext
|
|
1099
|
-
? `${sqlTableContext.database}.${sqlTableContext.schema}.${table.name}:${column.name}`
|
|
1100
|
-
: `${table.name}:${column.name}`,
|
|
1099
|
+
`${fullyQualifiedTableName(sqlTableContext, table.name)}:${column.name}`,
|
|
1101
1100
|
)}
|
|
1102
1101
|
sqlTableContext={sqlTableContext}
|
|
1103
1102
|
/>
|
|
@@ -13,26 +13,29 @@ import type {
|
|
|
13
13
|
import { logNever } from "@/utils/assertNever";
|
|
14
14
|
import type { ColumnHeaderStatsKey } from "../data-table/types";
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
function tablePath(context: SQLTableContext, tableName: string): string[] {
|
|
17
|
+
const segments = context.schemaPath?.length
|
|
18
|
+
? context.schemaPath
|
|
19
|
+
: [context.schema];
|
|
20
|
+
return [context.database, ...segments.filter(Boolean), tableName];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
23
|
export function tableUniqueId(
|
|
24
|
-
|
|
24
|
+
context: SQLTableContext | undefined,
|
|
25
25
|
tableName: string,
|
|
26
26
|
): string {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
return context ? JSON.stringify(tablePath(context, tableName)) : tableName;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function fullyQualifiedTableName(
|
|
31
|
+
context: SQLTableContext | undefined,
|
|
32
|
+
tableName: string,
|
|
33
|
+
): string {
|
|
34
|
+
return context
|
|
35
|
+
? getFormatter(context.dialect).formatTablePath(
|
|
36
|
+
tablePath(context, tableName),
|
|
37
|
+
)
|
|
38
|
+
: tableName;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
// Some databases have no schemas, so we don't show it (eg. Clickhouse)
|
|
@@ -114,6 +117,10 @@ const defaultFormatter: SqlCodeFormatter = {
|
|
|
114
117
|
`SELECT ${columnName} FROM ${tableName} LIMIT 100`,
|
|
115
118
|
};
|
|
116
119
|
|
|
120
|
+
function quoteIdentifier(identifier: string): string {
|
|
121
|
+
return `"${identifier.replaceAll('"', '""')}"`;
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
function getFormatter(dialect: string): SqlCodeFormatter {
|
|
118
125
|
dialect = dialect.toLowerCase();
|
|
119
126
|
if (!isKnownDialect(dialect)) {
|
|
@@ -146,9 +153,9 @@ function getFormatter(dialect: string): SqlCodeFormatter {
|
|
|
146
153
|
// Quote column and table names to avoid raising errors on weird characters
|
|
147
154
|
return {
|
|
148
155
|
formatTablePath: (tablePath: string[]) =>
|
|
149
|
-
tablePath.map(
|
|
156
|
+
tablePath.map(quoteIdentifier).join("."),
|
|
150
157
|
formatSelectClause: (columnName: string, tableName: string) =>
|
|
151
|
-
`SELECT ${columnName === "*" ? "*" :
|
|
158
|
+
`SELECT ${columnName === "*" ? "*" : quoteIdentifier(columnName)} FROM ${tableName} LIMIT 100`,
|
|
152
159
|
};
|
|
153
160
|
case "db2":
|
|
154
161
|
case "db2i":
|
|
@@ -194,6 +201,7 @@ export function sqlCode({
|
|
|
194
201
|
engine,
|
|
195
202
|
schema,
|
|
196
203
|
defaultSchema,
|
|
204
|
+
schemaPath,
|
|
197
205
|
defaultDatabase,
|
|
198
206
|
database,
|
|
199
207
|
dialect,
|
|
@@ -207,12 +215,13 @@ export function sqlCode({
|
|
|
207
215
|
}
|
|
208
216
|
} else {
|
|
209
217
|
// Include schema if it's not the default schema
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
const path = schemaPath?.length ? schemaPath : [schema];
|
|
219
|
+
if (path.length > 1 || path[0] !== defaultSchema) {
|
|
220
|
+
tablePath.unshift(...path);
|
|
212
221
|
}
|
|
213
222
|
|
|
214
|
-
//
|
|
215
|
-
if (database !== defaultDatabase) {
|
|
223
|
+
// Nested schema names can also be attached catalog names.
|
|
224
|
+
if (path.length > 1 || database !== defaultDatabase) {
|
|
216
225
|
tablePath.unshift(database);
|
|
217
226
|
}
|
|
218
227
|
}
|