@marimo-team/frontend 0.23.11-dev1 → 0.23.11-dev2
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/assets/{CellStatus-e0ex7Iei.js → CellStatus-Xx_7tl6i.js} +1 -1
- package/dist/assets/{JsonOutput-DRNPZOvX.js → JsonOutput-CbiJ_QRH.js} +5 -5
- package/dist/assets/{MarimoErrorOutput-BH6hs0Ir.js → MarimoErrorOutput-D9gYNYr6.js} +2 -2
- package/dist/assets/{RenderHTML-BQ1PO4Wd.js → RenderHTML-YHkbXfrr.js} +1 -1
- package/dist/assets/{RunButton-F8pLIvFp.js → RunButton-BSiMfpSC.js} +1 -1
- package/dist/assets/{add-cell-with-ai-C2OtCghK.js → add-cell-with-ai-CMER9awn.js} +8 -8
- package/dist/assets/{add-connection-dialog-AhwxOztN.js → add-connection-dialog-DKp7ZBWA.js} +1 -1
- package/dist/assets/{agent-panel-D6ryE8xb.js → agent-panel-DN7RxKMN.js} +3 -3
- package/dist/assets/{ai-model-dropdown-CG4B4rqH.js → ai-model-dropdown-iEXaZ6YH.js} +3 -3
- package/dist/assets/{app-config-button-D_sFrSql.js → app-config-button-x5lccIL4.js} +1 -1
- package/dist/assets/{cell-editor-XNa3wJ3P.js → cell-editor-G1jWTDlE.js} +5 -5
- package/dist/assets/{cell-link-Bj4-yOIx.js → cell-link-kkkjfd8C.js} +1 -1
- package/dist/assets/{cells-DOA0Gew8.js → cells-DGi-ohLl.js} +67 -67
- package/dist/assets/{chat-display-CezkHLLR.js → chat-display-D3J4Wxxq.js} +1 -1
- package/dist/assets/{chat-panel-DEBTPtwE.js → chat-panel-Cswz1fGO.js} +1 -1
- package/dist/assets/{chat-ui-CIr1o1hq.js → chat-ui-Cl5CdH_w.js} +1 -1
- package/dist/assets/{column-preview-nu3Qo2OW.js → column-preview-DypE7iks.js} +1 -1
- package/dist/assets/{command-palette-_VEEjmkI.js → command-palette-BtXkFFyo.js} +1 -1
- package/dist/assets/{common-fDFYY_sv.js → common-ByASZK7c.js} +1 -1
- package/dist/assets/{components--C6N-DXq.js → components-D8MC-pIV.js} +1 -1
- package/dist/assets/{datasource-CR6RRpTi.js → datasource-CpHnS4tt.js} +2 -2
- package/dist/assets/{dependency-graph-panel-BEgkvdX0.js → dependency-graph-panel-BpSpgk5-.js} +1 -1
- package/dist/assets/{documentation-panel-HvbKykbI.js → documentation-panel-iZ0hlSyN.js} +1 -1
- package/dist/assets/{download-DhxnAw14.js → download-YWP1Hdr2.js} +3 -3
- package/dist/assets/{edit-page-D-02q6iz.js → edit-page-Bq0PlZWO.js} +6 -6
- package/dist/assets/{error-panel-WLBQ3q9p.js → error-panel-Cz8n3tJR.js} +1 -1
- package/dist/assets/{file-explorer-panel-DtzGlnzT.js → file-explorer-panel-D9WR_f2x.js} +3 -3
- package/dist/assets/{file-icons-D2f3nfbq.js → file-icons-PPuQLn_Y.js} +1 -1
- package/dist/assets/{file-name-input-BNYf9WWM.js → file-name-input-BTXHtcG6.js} +1 -1
- package/dist/assets/{floating-outline-BvHFWRoz.js → floating-outline-C3q3X-Lc.js} +1 -1
- package/dist/assets/{focus-Ldqh99xE.js → focus-cLz6vpFk.js} +1 -1
- package/dist/assets/{form-CxBAInfg.js → form-CMwVIRZb.js} +1 -1
- package/dist/assets/{home-page-DwFpLpXK.js → home-page-DZO0YSAN.js} +2 -2
- package/dist/assets/{hooks-5gNQitDd.js → hooks-BZ1DWowv.js} +1 -1
- package/dist/assets/{html-to-image-CyAtzePO.js → html-to-image-Cso6uxBX.js} +2 -2
- package/dist/assets/index-BNWrEIlp.css +2 -0
- package/dist/assets/{index-CGCBsDqs.js → index-Cc7y-hhH.js} +14 -14
- package/dist/assets/{kiosk-mode-CTWHjzXs.js → kiosk-mode-U7OyKG2t.js} +1 -1
- package/dist/assets/{layout-D3pd3R21.js → layout-Bx16aCt5.js} +5 -5
- package/dist/assets/{logs-panel-BVLYycQb.js → logs-panel-DvVodKOA.js} +1 -1
- package/dist/assets/{markdown-renderer-C9Ujvj0b.js → markdown-renderer-iZsApCjt.js} +1 -1
- package/dist/assets/{name-cell-input-DabvuruX.js → name-cell-input-BMWQdztR.js} +1 -1
- package/dist/assets/{outline-panel-B-ncbNWI.js → outline-panel-CLBPCXIO.js} +1 -1
- package/dist/assets/{packages-panel-CESeW_3T.js → packages-panel-JkGrPw8-.js} +1 -1
- package/dist/assets/{panels-CTy3hq0L.js → panels-BzShZ7de.js} +1 -1
- package/dist/assets/{process-output-pPgH0ANl.js → process-output-B90zX9Wb.js} +1 -1
- package/dist/assets/{radio-group-D7rh6Zek.js → radio-group-NLdY8xke.js} +1 -1
- package/dist/assets/{readonly-python-code-CvPx4CU_.js → readonly-python-code-7GLqujXI.js} +1 -1
- package/dist/assets/{reveal-component-lTtEZQh1.js → reveal-component-Br0POw5W.js} +12 -12
- package/dist/assets/{run-page-BB6ghGfO.js → run-page-Cl_FUfH2.js} +1 -1
- package/dist/assets/{scratchpad-panel-Bvb8RxUv.js → scratchpad-panel-BMEpUyr3.js} +1 -1
- package/dist/assets/session-panel-BbcFNEli.js +1 -0
- package/dist/assets/{snippets-panel-BSgcoo-M.js → snippets-panel-hdutxjmA.js} +1 -1
- package/dist/assets/{state-BWM3qRkR.js → state-BshcA8O2.js} +1 -1
- package/dist/assets/{state-C8yHPSLN.js → state-C-rzpByZ.js} +2 -2
- package/dist/assets/{textarea-bBuxbFm7.js → textarea-CTAL0lla.js} +1 -1
- package/dist/assets/{tracing-pt7eDHWc.js → tracing-BF5hbD3V.js} +1 -1
- package/dist/assets/{tracing-panel-BveZ5DZw.js → tracing-panel-5AsyMp7H.js} +2 -2
- package/dist/assets/{tree-actions-1KQDSu1H.js → tree-actions-DupCr05h.js} +1 -1
- package/dist/assets/{useCellActionButton-C4V7J6PS.js → useCellActionButton-BAo_hBug.js} +1 -1
- package/dist/assets/{useDeleteCell-BvnurAZ9.js → useDeleteCell-B6dMWmEN.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-DNlXaxbt.js → useDependencyPanelTab-BZB0AnlV.js} +1 -1
- package/dist/assets/{useNotebookActions-BIQkawja.js → useNotebookActions-DWcbGJ23.js} +1 -1
- package/dist/assets/{useRunCells-cSZpNqnR.js → useRunCells-mJYEegIM.js} +1 -1
- package/dist/assets/{useSplitCell-s7dSiBUa.js → useSplitCell-LSbcCg0_.js} +1 -1
- package/dist/index.html +23 -23
- package/package.json +1 -1
- package/src/components/datasources/__tests__/filter-empty.test.ts +81 -0
- package/src/components/datasources/__tests__/utils.test.ts +62 -1
- package/src/components/datasources/components.tsx +15 -7
- package/src/components/datasources/datasources.tsx +311 -178
- package/src/components/datasources/utils.ts +40 -1
- package/src/core/datasets/__tests__/data-source.test.ts +226 -0
- package/src/core/datasets/data-source-connections.ts +88 -24
- package/dist/assets/index-BAYF7dcV.css +0 -2
- package/dist/assets/session-panel-CTJPYbAa.js +0 -1
|
@@ -4,15 +4,54 @@ import { BigQueryDialect } from "@marimo-team/codemirror-sql/dialects";
|
|
|
4
4
|
import { isKnownDialect } from "@/core/codemirror/language/languages/sql/utils";
|
|
5
5
|
import type { SQLTableContext } from "@/core/datasets/data-source-connections";
|
|
6
6
|
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
Database,
|
|
9
|
+
DatabaseSchema,
|
|
10
|
+
DataTable,
|
|
11
|
+
DataType,
|
|
12
|
+
} from "@/core/kernel/messages";
|
|
8
13
|
import { logNever } from "@/utils/assertNever";
|
|
9
14
|
import type { ColumnHeaderStatsKey } from "../data-table/types";
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Stable id for a table node in the datasources tree.
|
|
18
|
+
*
|
|
19
|
+
* schemaPath already includes the leaf schema for nested namespaces, so use it
|
|
20
|
+
* when present and fall back to the flat schema name otherwise (avoids
|
|
21
|
+
* duplicating the leaf, e.g. `top.nested.nested.table`).
|
|
22
|
+
*/
|
|
23
|
+
export function tableUniqueId(
|
|
24
|
+
sqlTableContext: SQLTableContext | undefined,
|
|
25
|
+
tableName: string,
|
|
26
|
+
): string {
|
|
27
|
+
if (!sqlTableContext) {
|
|
28
|
+
return tableName;
|
|
29
|
+
}
|
|
30
|
+
const segments = (
|
|
31
|
+
sqlTableContext.schemaPath?.length
|
|
32
|
+
? sqlTableContext.schemaPath
|
|
33
|
+
: [sqlTableContext.schema]
|
|
34
|
+
).filter(Boolean);
|
|
35
|
+
return [sqlTableContext.database, ...segments, tableName].join(".");
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
// Some databases have no schemas, so we don't show it (eg. Clickhouse)
|
|
12
39
|
export function isSchemaless(schemaName: string) {
|
|
13
40
|
return schemaName === "";
|
|
14
41
|
}
|
|
15
42
|
|
|
43
|
+
// Lazy discovery: the `*_resolved` flags default to `true` and are only `false`
|
|
44
|
+
// when enumeration was deferred. Helper functions to centralize logic
|
|
45
|
+
export function areSchemasResolved(database: Database): boolean {
|
|
46
|
+
return database.schemas_resolved !== false;
|
|
47
|
+
}
|
|
48
|
+
export function areTablesResolved(schema: DatabaseSchema): boolean {
|
|
49
|
+
return schema.tables_resolved !== false;
|
|
50
|
+
}
|
|
51
|
+
export function areChildSchemasResolved(schema: DatabaseSchema): boolean {
|
|
52
|
+
return schema.child_schemas_resolved !== false;
|
|
53
|
+
}
|
|
54
|
+
|
|
16
55
|
interface SqlCodeFormatter {
|
|
17
56
|
/**
|
|
18
57
|
* Format the table path based on dialect-specific rules
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import { variableName } from "@/__tests__/branded";
|
|
4
4
|
import type { DatabaseSchema, DataTable } from "@/core/kernel/messages";
|
|
5
|
+
import { store } from "@/core/state/jotai";
|
|
5
6
|
import type { VariableName } from "@/core/variables/types";
|
|
6
7
|
import {
|
|
8
|
+
allTablesAtom,
|
|
7
9
|
type DataSourceConnection,
|
|
10
|
+
dataSourceConnectionsAtom,
|
|
8
11
|
type DataSourceState,
|
|
9
12
|
exportedForTesting,
|
|
10
13
|
type SQLTableContext,
|
|
@@ -575,3 +578,226 @@ describe("add table", () => {
|
|
|
575
578
|
expect(db1?.schemas.length).toBe(1);
|
|
576
579
|
});
|
|
577
580
|
});
|
|
581
|
+
|
|
582
|
+
describe("nested namespaces", () => {
|
|
583
|
+
// Iceberg-style: database "top" with a schemaless schema and a nested
|
|
584
|
+
// namespace "nested" that has a deferred child namespace "deep".
|
|
585
|
+
const nestedConnections: DataSourceConnection[] = [
|
|
586
|
+
{
|
|
587
|
+
name: "ice" as ConnectionName,
|
|
588
|
+
source: "iceberg",
|
|
589
|
+
display_name: "Iceberg",
|
|
590
|
+
dialect: "iceberg",
|
|
591
|
+
databases: [
|
|
592
|
+
{
|
|
593
|
+
name: "top",
|
|
594
|
+
dialect: "iceberg",
|
|
595
|
+
schemas_resolved: true,
|
|
596
|
+
schemas: [
|
|
597
|
+
{ name: "", tables: [], tables_resolved: true },
|
|
598
|
+
{
|
|
599
|
+
name: "nested",
|
|
600
|
+
tables: [],
|
|
601
|
+
tables_resolved: false,
|
|
602
|
+
child_schemas: [],
|
|
603
|
+
child_schemas_resolved: false,
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
},
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
let baseState: DataSourceState;
|
|
612
|
+
|
|
613
|
+
beforeEach(() => {
|
|
614
|
+
baseState = addConnection(nestedConnections, initialState());
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const findSchema = (
|
|
618
|
+
state: DataSourceState,
|
|
619
|
+
path: string[],
|
|
620
|
+
): DatabaseSchema | undefined => {
|
|
621
|
+
const conn = state.connectionsMap.get("ice" as ConnectionName);
|
|
622
|
+
const db = conn?.databases.find((d) => d.name === "top");
|
|
623
|
+
let schemas = db?.schemas ?? [];
|
|
624
|
+
let found: DatabaseSchema | undefined;
|
|
625
|
+
for (const segment of path) {
|
|
626
|
+
found = schemas.find((s) => s.name === segment);
|
|
627
|
+
schemas = found?.child_schemas ?? [];
|
|
628
|
+
}
|
|
629
|
+
return found;
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
it("sets child namespaces at a nested path", () => {
|
|
633
|
+
const children: DatabaseSchema[] = [
|
|
634
|
+
{ name: "deep", tables: [], tables_resolved: false },
|
|
635
|
+
];
|
|
636
|
+
const newState = reducer(baseState, {
|
|
637
|
+
type: "addSchemaList",
|
|
638
|
+
payload: {
|
|
639
|
+
schemas: children,
|
|
640
|
+
sqlSchemaContext: {
|
|
641
|
+
engine: "ice",
|
|
642
|
+
database: "top",
|
|
643
|
+
schemaPath: ["nested"],
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const nested = findSchema(newState, ["nested"]);
|
|
649
|
+
expect(nested?.child_schemas_resolved).toBe(true);
|
|
650
|
+
expect(nested?.child_schemas?.map((s) => s.name)).toEqual(["deep"]);
|
|
651
|
+
// The schemaless sibling is untouched.
|
|
652
|
+
expect(findSchema(newState, [""])?.name).toBe("");
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("sets tables at a nested path", () => {
|
|
656
|
+
const tables: DataTable[] = [
|
|
657
|
+
{
|
|
658
|
+
name: "table4",
|
|
659
|
+
columns: [],
|
|
660
|
+
num_columns: 0,
|
|
661
|
+
num_rows: 0,
|
|
662
|
+
variable_name: null,
|
|
663
|
+
source: "iceberg",
|
|
664
|
+
source_type: "catalog",
|
|
665
|
+
type: "table",
|
|
666
|
+
},
|
|
667
|
+
];
|
|
668
|
+
const newState = reducer(baseState, {
|
|
669
|
+
type: "addTableList",
|
|
670
|
+
payload: {
|
|
671
|
+
tables,
|
|
672
|
+
sqlTableContext: {
|
|
673
|
+
engine: "ice",
|
|
674
|
+
database: "top",
|
|
675
|
+
schema: "nested",
|
|
676
|
+
dialect: "iceberg",
|
|
677
|
+
schemaPath: ["nested"],
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const nested = findSchema(newState, ["nested"]);
|
|
683
|
+
expect(nested?.tables_resolved).toBe(true);
|
|
684
|
+
expect(nested?.tables.map((t) => t.name)).toEqual(["table4"]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("replaces a single table at a nested path", () => {
|
|
688
|
+
const makeTable = (numRows: number): DataTable => ({
|
|
689
|
+
name: "table4",
|
|
690
|
+
columns: [],
|
|
691
|
+
num_columns: 0,
|
|
692
|
+
num_rows: numRows,
|
|
693
|
+
variable_name: null,
|
|
694
|
+
source: "iceberg",
|
|
695
|
+
source_type: "catalog",
|
|
696
|
+
type: "table",
|
|
697
|
+
});
|
|
698
|
+
const context = {
|
|
699
|
+
engine: "ice",
|
|
700
|
+
database: "top",
|
|
701
|
+
schema: "nested",
|
|
702
|
+
dialect: "iceberg",
|
|
703
|
+
schemaPath: ["nested"],
|
|
704
|
+
};
|
|
705
|
+
let state = reducer(baseState, {
|
|
706
|
+
type: "addTableList",
|
|
707
|
+
payload: { tables: [makeTable(1)], sqlTableContext: context },
|
|
708
|
+
});
|
|
709
|
+
state = reducer(state, {
|
|
710
|
+
type: "addTable",
|
|
711
|
+
payload: { table: makeTable(42), sqlTableContext: context },
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const nested = findSchema(state, ["nested"]);
|
|
715
|
+
expect(nested?.tables).toHaveLength(1);
|
|
716
|
+
expect(nested?.tables[0].num_rows).toBe(42);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("does not change anything for a missing nested path", () => {
|
|
720
|
+
const newState = reducer(baseState, {
|
|
721
|
+
type: "addSchemaList",
|
|
722
|
+
payload: {
|
|
723
|
+
schemas: [{ name: "deep", tables: [] }],
|
|
724
|
+
sqlSchemaContext: {
|
|
725
|
+
engine: "ice",
|
|
726
|
+
database: "top",
|
|
727
|
+
schemaPath: ["does_not_exist"],
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
// The result is unchanged: nested namespace stays unresolved and the
|
|
732
|
+
// database keeps its two schemas (schemaless + nested).
|
|
733
|
+
const newDb = newState.connectionsMap
|
|
734
|
+
.get("ice" as ConnectionName)
|
|
735
|
+
?.databases.find((d) => d.name === "top");
|
|
736
|
+
expect(findSchema(newState, ["nested"])?.child_schemas_resolved).toBe(
|
|
737
|
+
false,
|
|
738
|
+
);
|
|
739
|
+
expect(newDb?.schemas.length).toBe(2);
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
describe("allTablesAtom with nested namespaces", () => {
|
|
744
|
+
it("enumerates tables from nested namespaces", () => {
|
|
745
|
+
const table = (name: string): DataTable => ({
|
|
746
|
+
name,
|
|
747
|
+
columns: [],
|
|
748
|
+
num_columns: 0,
|
|
749
|
+
num_rows: 0,
|
|
750
|
+
variable_name: null,
|
|
751
|
+
source: "iceberg",
|
|
752
|
+
source_type: "catalog",
|
|
753
|
+
type: "table",
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const state = addConnection(
|
|
757
|
+
[
|
|
758
|
+
{
|
|
759
|
+
name: "ice" as ConnectionName,
|
|
760
|
+
source: "iceberg",
|
|
761
|
+
display_name: "Iceberg",
|
|
762
|
+
dialect: "iceberg",
|
|
763
|
+
databases: [
|
|
764
|
+
{
|
|
765
|
+
name: "top",
|
|
766
|
+
dialect: "iceberg",
|
|
767
|
+
schemas_resolved: true,
|
|
768
|
+
schemas: [
|
|
769
|
+
{
|
|
770
|
+
name: "",
|
|
771
|
+
tables: [table("toptable")],
|
|
772
|
+
tables_resolved: true,
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: "nested",
|
|
776
|
+
tables: [table("nestedtable")],
|
|
777
|
+
tables_resolved: true,
|
|
778
|
+
child_schemas_resolved: true,
|
|
779
|
+
child_schemas: [
|
|
780
|
+
{
|
|
781
|
+
name: "deep",
|
|
782
|
+
tables: [table("deeptable")],
|
|
783
|
+
tables_resolved: true,
|
|
784
|
+
child_schemas: [],
|
|
785
|
+
child_schemas_resolved: true,
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
},
|
|
793
|
+
],
|
|
794
|
+
initialState(),
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
store.set(dataSourceConnectionsAtom, state);
|
|
798
|
+
const names = [...store.get(allTablesAtom).values()].map((t) => t.name);
|
|
799
|
+
expect(names).toContain("toptable");
|
|
800
|
+
expect(names).toContain("nestedtable");
|
|
801
|
+
expect(names).toContain("deeptable");
|
|
802
|
+
});
|
|
803
|
+
});
|
|
@@ -49,6 +49,9 @@ export interface DataSourceState {
|
|
|
49
49
|
export interface SQLSchemaContext {
|
|
50
50
|
engine: string;
|
|
51
51
|
database: string;
|
|
52
|
+
// Parent schema path (relative to `database`) for nested schemas.
|
|
53
|
+
// Empty/undefined for the database's top level.
|
|
54
|
+
schemaPath?: string[];
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export interface SQLTableContext {
|
|
@@ -58,6 +61,49 @@ export interface SQLTableContext {
|
|
|
58
61
|
dialect: string;
|
|
59
62
|
defaultSchema?: string | null;
|
|
60
63
|
defaultDatabase?: string | null;
|
|
64
|
+
// Nested schema path (relative to `database`). Empty/undefined at top level.
|
|
65
|
+
schemaPath?: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Immutably descend `path` (schema segment names) into a nested schema list
|
|
70
|
+
* and apply `update` to the matching schema. Unmatched branches are unchanged.
|
|
71
|
+
*/
|
|
72
|
+
function updateSchemaAtPath(
|
|
73
|
+
schemas: DatabaseSchema[],
|
|
74
|
+
path: string[],
|
|
75
|
+
update: (schema: DatabaseSchema) => DatabaseSchema,
|
|
76
|
+
): DatabaseSchema[] {
|
|
77
|
+
if (path.length === 0) {
|
|
78
|
+
return schemas;
|
|
79
|
+
}
|
|
80
|
+
const [head, ...rest] = path;
|
|
81
|
+
return schemas.map((schema) => {
|
|
82
|
+
if (schema.name !== head) {
|
|
83
|
+
return schema;
|
|
84
|
+
}
|
|
85
|
+
if (rest.length === 0) {
|
|
86
|
+
return update(schema);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...schema,
|
|
90
|
+
child_schemas: updateSchemaAtPath(
|
|
91
|
+
schema.child_schemas ?? [],
|
|
92
|
+
rest,
|
|
93
|
+
update,
|
|
94
|
+
),
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The path (schema/namespace segment names within a database) that locates the
|
|
101
|
+
* schema holding a set of tables. For nested namespaces this is the
|
|
102
|
+
* `schemaPath`; otherwise it is the single (possibly schemaless) schema name.
|
|
103
|
+
*/
|
|
104
|
+
function tableSchemaPath(sqlTableContext: SQLTableContext): string[] {
|
|
105
|
+
const { schemaPath, schema } = sqlTableContext;
|
|
106
|
+
return schemaPath && schemaPath.length > 0 ? schemaPath : [schema];
|
|
61
107
|
}
|
|
62
108
|
|
|
63
109
|
function initialState(): DataSourceState {
|
|
@@ -159,6 +205,7 @@ const {
|
|
|
159
205
|
return state;
|
|
160
206
|
}
|
|
161
207
|
|
|
208
|
+
const schemaPath = sqlSchemaContext.schemaPath ?? [];
|
|
162
209
|
const newMap = new Map(connectionsMap);
|
|
163
210
|
const newConn: DataSourceConnection = {
|
|
164
211
|
...conn,
|
|
@@ -166,10 +213,22 @@ const {
|
|
|
166
213
|
if (db.name !== sqlSchemaContext.database) {
|
|
167
214
|
return db;
|
|
168
215
|
}
|
|
216
|
+
// Top level: replace the database's schemas.
|
|
217
|
+
if (schemaPath.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
...db,
|
|
220
|
+
schemas: schemas,
|
|
221
|
+
schemas_resolved: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// Nested namespace: replace the child schemas of the namespace at path.
|
|
169
225
|
return {
|
|
170
226
|
...db,
|
|
171
|
-
schemas: schemas,
|
|
172
|
-
|
|
227
|
+
schemas: updateSchemaAtPath(db.schemas, schemaPath, (schema) => ({
|
|
228
|
+
...schema,
|
|
229
|
+
child_schemas: schemas,
|
|
230
|
+
child_schemas_resolved: true,
|
|
231
|
+
})),
|
|
173
232
|
};
|
|
174
233
|
}),
|
|
175
234
|
};
|
|
@@ -198,6 +257,7 @@ const {
|
|
|
198
257
|
return state;
|
|
199
258
|
}
|
|
200
259
|
|
|
260
|
+
const path = tableSchemaPath(sqlTableContext);
|
|
201
261
|
const newMap = new Map(connectionsMap);
|
|
202
262
|
const newConn: DataSourceConnection = {
|
|
203
263
|
...conn,
|
|
@@ -207,16 +267,11 @@ const {
|
|
|
207
267
|
}
|
|
208
268
|
return {
|
|
209
269
|
...db,
|
|
210
|
-
schemas: db.schemas
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
...schema,
|
|
216
|
-
tables: tables,
|
|
217
|
-
tables_resolved: true,
|
|
218
|
-
};
|
|
219
|
-
}),
|
|
270
|
+
schemas: updateSchemaAtPath(db.schemas, path, (schema) => ({
|
|
271
|
+
...schema,
|
|
272
|
+
tables: tables,
|
|
273
|
+
tables_resolved: true,
|
|
274
|
+
})),
|
|
220
275
|
};
|
|
221
276
|
}),
|
|
222
277
|
};
|
|
@@ -246,6 +301,7 @@ const {
|
|
|
246
301
|
return state;
|
|
247
302
|
}
|
|
248
303
|
|
|
304
|
+
const path = tableSchemaPath(sqlTableContext);
|
|
249
305
|
const newMap = new Map(connectionsMap);
|
|
250
306
|
const newConn: DataSourceConnection = {
|
|
251
307
|
...conn,
|
|
@@ -253,21 +309,15 @@ const {
|
|
|
253
309
|
if (db.name !== sqlTableContext.database) {
|
|
254
310
|
return db;
|
|
255
311
|
}
|
|
256
|
-
|
|
257
312
|
return {
|
|
258
313
|
...db,
|
|
259
|
-
schemas: db.schemas
|
|
260
|
-
if (schema.name !== sqlTableContext.schema) {
|
|
261
|
-
return schema;
|
|
262
|
-
}
|
|
263
|
-
|
|
314
|
+
schemas: updateSchemaAtPath(db.schemas, path, (schema) => {
|
|
264
315
|
// If tables array is empty, add the table
|
|
265
316
|
// Otherwise, replace existing table or keep unchanged tables
|
|
266
317
|
const tables =
|
|
267
318
|
schema.tables.length === 0
|
|
268
319
|
? [table]
|
|
269
320
|
: schema.tables.map((t) => (t.name === tableName ? table : t));
|
|
270
|
-
|
|
271
321
|
return {
|
|
272
322
|
...schema,
|
|
273
323
|
tables,
|
|
@@ -327,9 +377,14 @@ export const allTablesAtom = atom((get) => {
|
|
|
327
377
|
const isDefaultDb =
|
|
328
378
|
database.name === conn.default_database || conn.databases.length === 1;
|
|
329
379
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
380
|
+
// Walk schemas recursively so nested namespaces (e.g. Iceberg
|
|
381
|
+
// `top.nested`) are enumerated. `segments` is the path of named
|
|
382
|
+
// (non-schemaless) namespace segments down to this schema.
|
|
383
|
+
const walkSchema = (schema: DatabaseSchema, segments: string[]): void => {
|
|
384
|
+
const schemalessDb = segments.length === 0;
|
|
385
|
+
const isDefaultSchema =
|
|
386
|
+
segments.length === 1 && segments[0] === conn.default_schema;
|
|
387
|
+
const schemaQualifier = segments.join(".");
|
|
333
388
|
|
|
334
389
|
for (const table of schema.tables) {
|
|
335
390
|
let nameToSave: string;
|
|
@@ -358,14 +413,14 @@ export const allTablesAtom = atom((get) => {
|
|
|
358
413
|
continue;
|
|
359
414
|
}
|
|
360
415
|
|
|
361
|
-
nameToSave = `${
|
|
416
|
+
nameToSave = `${schemaQualifier}.${table.name}`;
|
|
362
417
|
|
|
363
418
|
if (isDefaultDb && !tableNames.has(nameToSave)) {
|
|
364
419
|
tableNames.set(nameToSave, table);
|
|
365
420
|
continue;
|
|
366
421
|
}
|
|
367
422
|
|
|
368
|
-
nameToSave = `${database.name}.${
|
|
423
|
+
nameToSave = `${database.name}.${schemaQualifier}.${table.name}`;
|
|
369
424
|
|
|
370
425
|
if (tableNames.has(nameToSave)) {
|
|
371
426
|
Logger.warn(`Table name collision for ${nameToSave}. Skipping.`);
|
|
@@ -373,6 +428,15 @@ export const allTablesAtom = atom((get) => {
|
|
|
373
428
|
tableNames.set(nameToSave, table);
|
|
374
429
|
}
|
|
375
430
|
}
|
|
431
|
+
|
|
432
|
+
// Recurse into nested child namespaces. Children are always named.
|
|
433
|
+
for (const child of schema.child_schemas ?? []) {
|
|
434
|
+
walkSchema(child, [...segments, child.name]);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
for (const schema of database.schemas) {
|
|
439
|
+
walkSchema(schema, isSchemaless(schema.name) ? [] : [schema.name]);
|
|
376
440
|
}
|
|
377
441
|
}
|
|
378
442
|
}
|