@marimo-team/frontend 0.16.1 → 0.16.2
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/{ConnectedDataExplorerComponent-2wVcyvDj.js → ConnectedDataExplorerComponent-B5cPvWoQ.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-D2j6i0hv.js → ImageComparisonComponent-CqR26LSv.js} +1 -1
- package/dist/assets/{VegaLite-BckFaf2D.js → VegaLite-DvQDATwI.js} +1 -1
- package/dist/assets/_baseEach--KDTwKbG.js +1 -0
- package/dist/assets/_baseMap-Cu3o-eyO.js +1 -0
- package/dist/assets/{_baseUniq-BKktIGQ1.js → _baseUniq-y7ZXnMo1.js} +1 -1
- package/dist/assets/{_createAggregator-C5CVY-0t.js → _createAggregator-ZcHkHPNJ.js} +1 -1
- package/dist/assets/{agent-panel-RGLNjkYe.js → agent-panel-B91RoLct.js} +6 -6
- package/dist/assets/{any-language-editor-DjuXwGCA.js → any-language-editor-CxfHcm5h.js} +1 -1
- package/dist/assets/{architectureDiagram-W76B3OCA-Dyj4ds_R.js → architectureDiagram-W76B3OCA-BQsvK8uR.js} +1 -1
- package/dist/assets/{between-horizontal-start-Dt2aKpPf.js → between-horizontal-start-BmYToIaM.js} +1 -1
- package/dist/assets/{blockDiagram-QIGZ2CNN-o-i7DDvN.js → blockDiagram-QIGZ2CNN-r3HgCj4w.js} +1 -1
- package/dist/assets/{c4Diagram-FPNF74CW-DGHEwWrx.js → c4Diagram-FPNF74CW-BJbPNt41.js} +1 -1
- package/dist/assets/channel-DFaEx1fu.js +1 -0
- package/dist/assets/{chat-panel-9alr8FS4.js → chat-panel-IoPMv8e2.js} +3 -3
- package/dist/assets/{chunk-4BX2VUAB-BJecb-Ri.js → chunk-4BX2VUAB-Dv4MZ9Hj.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CAATkc4w.js → chunk-55IACEB6-CM4AHquB.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DPuNbQ-f.js → chunk-FMBD7UC4-C_Zz0ENB.js} +1 -1
- package/dist/assets/{chunk-K7UQS3LO-C8TWVLiH.js → chunk-K7UQS3LO-DYSmiXYq.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-DiZZ09q4.js → chunk-QN33PNHL-QM4OPuQP.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BIUM7usu.js → chunk-QZHKN3VN-CfAsGyeB.js} +1 -1
- package/dist/assets/{chunk-TVAH2DTR-vGTArPBG.js → chunk-TVAH2DTR-6j_Cpjsi.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-D2KRqp_x.js → chunk-TZMSLE5B-BHslFJQE.js} +1 -1
- package/dist/assets/{circle-play-cjeNez0N.js → circle-play-CK3UZRYQ.js} +1 -1
- package/dist/assets/classDiagram-KNZD7YFC-BsZtvV5O.js +1 -0
- package/dist/assets/classDiagram-v2-RKCZMP56-BsZtvV5O.js +1 -0
- package/dist/assets/{clear-button-C97JtAez.js → clear-button-C4fDVSv8.js} +1 -1
- package/dist/assets/clone-YBEvPE-s.js +1 -0
- package/dist/assets/command-palette-D7hOfvf6.js +1 -0
- package/dist/assets/{common-Du9rSOwD.js → common-D-lbuUwz.js} +1 -1
- package/dist/assets/{compile-CZXqyOxa.js → compile-DVQe1Mzk.js} +1 -1
- package/dist/assets/{cose-bilkent-S5V4N54A-CqUN5Y9b.js → cose-bilkent-S5V4N54A-D-IS7WC8.js} +1 -1
- package/dist/assets/{dagre-5GWH7T2D-RJqTI9DM.js → dagre-5GWH7T2D-lYu-tEWT.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-DZN0q1LV.js → data-grid-overlay-editor-C5peOCit.js} +1 -1
- package/dist/assets/datasources-panel-D3NA20uZ.js +1 -0
- package/dist/assets/{dependency-graph-panel-CEog_O7V.js → dependency-graph-panel-BGVYOfkV.js} +1 -1
- package/dist/assets/{diagram-N5W7TBWH-D-l4zZ9d.js → diagram-N5W7TBWH-BnvIuYUp.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-CCOmBUt-.js → diagram-QEK2KX5R-DemedRK3.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-C_I_9jnZ.js → diagram-S2PKOQOG-iiY7AuyH.js} +1 -1
- package/dist/assets/{documentation-panel-C1BtMZ3M.js → documentation-panel-C3dSwOSQ.js} +1 -1
- package/dist/assets/{edit-page-B-oevUZ9.js → edit-page-C5TsEeSo.js} +17 -17
- package/dist/assets/{ellipsis-vertical-BEb-J8z6.js → ellipsis-vertical-CazJl8M7.js} +1 -1
- package/dist/assets/{empty-state-C99UyDE3.js → empty-state-DW308mFO.js} +1 -1
- package/dist/assets/{erDiagram-AWTI2OKA-BePOLi5M.js → erDiagram-AWTI2OKA-6wQ8Ugg0.js} +1 -1
- package/dist/assets/{error-panel-Bs34jXFh.js → error-panel-D1VnJ1yP.js} +1 -1
- package/dist/assets/{file-explorer-panel-Ck6UL861.js → file-explorer-panel-0oVd4t-D.js} +1 -1
- package/dist/assets/{flowDiagram-PVAE7QVJ-BgjFu5l7.js → flowDiagram-PVAE7QVJ-C55IUWjm.js} +1 -1
- package/dist/assets/{ganttDiagram-OWAHRB6G-YOPb3XSV.js → ganttDiagram-OWAHRB6G-DmqCM6ME.js} +1 -1
- package/dist/assets/{gitGraphDiagram-NY62KEGX-CGhqaDTy.js → gitGraphDiagram-NY62KEGX-DBvhAeM_.js} +1 -1
- package/dist/assets/{glide-data-editor-9QUH6iso.js → glide-data-editor-CHNuHidQ.js} +11 -11
- package/dist/assets/{graph-DQQFGrho.js → graph-CG6BgUWQ.js} +1 -1
- package/dist/assets/{home-page-DRKpPCrF.js → home-page-dgivXuSR.js} +3 -3
- package/dist/assets/{index-SGLNXrGP.js → index-BTGpssVX.js} +1 -1
- package/dist/assets/{index-Aeo6WiK7.js → index-BYVZlBF8.js} +1 -1
- package/dist/assets/{index-CUFv_thQ.js → index-BelfnXwL.js} +1 -1
- package/dist/assets/{index-DdnKZNxM.js → index-BneyUujp.js} +1 -1
- package/dist/assets/{index-BJNCMUmG.js → index-C02SqeRj.js} +1 -1
- package/dist/assets/{index-aE43R74q.js → index-C7dtgr9A.js} +1 -1
- package/dist/assets/{index-C2MD0vgD.js → index-CAQvMTzM.js} +1 -1
- package/dist/assets/index-CGDMlQfO.css +1 -0
- package/dist/assets/index-CelXfcd8.js +580 -0
- package/dist/assets/{index-C_tkBKNO.js → index-Csd6QrCV.js} +1 -1
- package/dist/assets/{index-BAbIIxHU.js → index-CtPksxf0.js} +1 -1
- package/dist/assets/{index-2252nrk6.js → index-Cxyk7pt-.js} +1 -1
- package/dist/assets/{index-BW3k9Gss.js → index-DAZ-9ri2.js} +1 -1
- package/dist/assets/{index-ClzeQrN7.js → index-DONRrmA2.js} +1 -1
- package/dist/assets/{index-B8jXZ12t.js → index-Db36XTG_.js} +1 -1
- package/dist/assets/{index-BprjMYH5.js → index-DdIhdEVw.js} +1 -1
- package/dist/assets/{index-CFKO7WXI.js → index-M_pBKDSe.js} +1 -1
- package/dist/assets/{index-CfaDbEdi.js → index-_luCZMLM.js} +1 -1
- package/dist/assets/{index-BjgnbONl.js → index-mkubqy9-.js} +1 -1
- package/dist/assets/{index-C1ez98sk.js → index-sbO9UaUU.js} +1 -1
- package/dist/assets/{index-G5QZppK2.js → index-z4krxQ4j.js} +1 -1
- package/dist/assets/infoDiagram-STP46IZ2-wTALjfPc.js +2 -0
- package/dist/assets/{isEmpty-D-4c7sMv.js → isEmpty-CqX_YTIf.js} +1 -1
- package/dist/assets/{journeyDiagram-BIP6EPQ6-C94u3Mv3.js → journeyDiagram-BIP6EPQ6-Y5w_Tqe_.js} +1 -1
- package/dist/assets/{kanban-definition-6OIFK2YF-BEXYFzz7.js → kanban-definition-6OIFK2YF-DbXs5Rxi.js} +1 -1
- package/dist/assets/{layout-Bz2BJ2ru.js → layout-BCNPDACj.js} +1 -1
- package/dist/assets/{linear-D8s7K76e.js → linear-uO6UVhXt.js} +1 -1
- package/dist/assets/{links-BpXlz1GG.js → links-Drv7cJgN.js} +3 -3
- package/dist/assets/{logs-panel-DC7wpmPz.js → logs-panel-BEQ1eRUp.js} +1 -1
- package/dist/assets/{markdown-renderer-DRdSWR9X.js → markdown-renderer-Dmzbb00W.js} +3 -3
- package/dist/assets/{mermaid-Y3x4hmD0.js → mermaid-qRc4MXIj.js} +1 -1
- package/dist/assets/{mermaid.core-DzthE35Y.js → mermaid.core-CvvJtCRj.js} +4 -4
- package/dist/assets/min-DYUOb1RR.js +1 -0
- package/dist/assets/{mindmap-definition-Q6HEUPPD-DktvuLe1.js → mindmap-definition-Q6HEUPPD-G5NognM-.js} +1 -1
- package/dist/assets/{number-overlay-editor-BEfwI1IT.js → number-overlay-editor-DPr5sHFu.js} +1 -1
- package/dist/assets/{outline-panel-CdsnAy2w.js → outline-panel-gxQXvVi4.js} +1 -1
- package/dist/assets/{packages-panel-DiTA-d_D.js → packages-panel-B1T0VPlg.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-DQDNQ-de.js → pieDiagram-ADFJNKIX-DK9SHkfc.js} +1 -1
- package/dist/assets/{quadrantDiagram-LMRXKWRM-0kgIXc2-.js → quadrantDiagram-LMRXKWRM-D1DdWF8C.js} +1 -1
- package/dist/assets/{react-plotly-DJqqfM7c.js → react-plotly-CTwajqCb.js} +1 -1
- package/dist/assets/{requirementDiagram-4UW4RH46-B5rb0ypd.js → requirementDiagram-4UW4RH46-DnjDAypr.js} +1 -1
- package/dist/assets/{run-page-CFmLrv1R.js → run-page-CQY9im22.js} +1 -1
- package/dist/assets/{sankeyDiagram-GR3RE2ED-Dom7IlnF.js → sankeyDiagram-GR3RE2ED-B67Va-ER.js} +1 -1
- package/dist/assets/{scratchpad-panel-CuHWpHO8.js → scratchpad-panel-DlDfcDtW.js} +1 -1
- package/dist/assets/{secrets-panel-CfHc5YD0.js → secrets-panel-BDGyuGZA.js} +1 -1
- package/dist/assets/{sequenceDiagram-C3RYC4MD-PNJWXQbw.js → sequenceDiagram-C3RYC4MD-DiWgZPtN.js} +1 -1
- package/dist/assets/{slides-component-CJgaTRZ0.js → slides-component-DhpPRtQp.js} +1 -1
- package/dist/assets/{snippets-panel-B2EC1txM.js → snippets-panel-CLkBXhJ2.js} +1 -1
- package/dist/assets/{sortBy-DZnlX29-.js → sortBy-D4OG7w4O.js} +1 -1
- package/dist/assets/{state-CWict9RU.js → state-Dz_3JyED.js} +1 -1
- package/dist/assets/{stateDiagram-KXAO66HF-BE58aJnr.js → stateDiagram-KXAO66HF-ByF2AULw.js} +1 -1
- package/dist/assets/stateDiagram-v2-UMBNRL4Z-CtBJqosP.js +1 -0
- package/dist/assets/{storage-DRaR04wR.js → storage-Dr0CC44z.js} +6 -6
- package/dist/assets/{terminal-BX3Su5q7.js → terminal-BtdissBf.js} +1 -1
- package/dist/assets/{time-hUzZfpNE.js → time-DKdOTnQg.js} +1 -1
- package/dist/assets/{timeline-definition-XQNQX7LJ-CqQP9t51.js → timeline-definition-XQNQX7LJ-DzER9bf6.js} +1 -1
- package/dist/assets/{tracing-B10Q1n-L.js → tracing-Dpx5M-u3.js} +2 -2
- package/dist/assets/{tracing-panel-Du8WCnno.js → tracing-panel-hCjBkSER.js} +2 -2
- package/dist/assets/{trash-B81GTiv6.js → trash-C6Ko-g5q.js} +1 -1
- package/dist/assets/{tree-6vW2ogkh.js → tree-BHN2gcCF.js} +6 -6
- package/dist/assets/{treemap-75Q7IDZK-CdwDwwsz.js → treemap-75Q7IDZK-DR79Mhzt.js} +1 -1
- package/dist/assets/{variable-panel-D5qgJI7k.js → variable-panel-PFBCFz36.js} +1 -1
- package/dist/assets/{vega-component-DJaJWMJM.js → vega-component-Db6-uY4C.js} +1 -1
- package/dist/assets/{xychartDiagram-6GGTOJPD-WFtXqaM9.js → xychartDiagram-6GGTOJPD-DWzBP3tZ.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +3 -3
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/data-table/__tests__/columns.test.tsx +38 -0
- package/src/components/data-table/cell-hover-template/feature.ts +1 -1
- package/src/components/data-table/cell-hover-template/types.ts +1 -1
- package/src/components/data-table/columns.tsx +21 -2
- package/src/components/data-table/renderers.tsx +16 -8
- package/src/components/data-table/schemas.ts +16 -0
- package/src/components/editor/Cell.tsx +2 -0
- package/src/components/editor/errors/sql-validation-errors.tsx +34 -0
- package/src/components/editor/output/ConsoleOutput.tsx +13 -1
- package/src/components/editor/output/MarimoErrorOutput.tsx +60 -1
- package/src/core/ai/context/providers/cell-output.ts +1 -18
- package/src/core/codemirror/language/__tests__/extension.test.ts +24 -0
- package/src/core/codemirror/language/__tests__/sql-validation.test.ts +133 -0
- package/src/core/codemirror/language/languages/sql/sql-mode.ts +20 -0
- package/src/core/codemirror/language/languages/sql/sql.ts +90 -3
- package/src/core/codemirror/language/languages/sql/validation-errors.ts +79 -0
- package/src/core/codemirror/language/panel/panel.tsx +8 -2
- package/src/core/codemirror/language/panel/sql.tsx +81 -4
- package/src/core/config/feature-flag.tsx +3 -1
- package/src/core/datasets/request-registry.ts +17 -1
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +1 -0
- package/src/core/network/requests-network.ts +7 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.ts +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/wasm/bridge.ts +5 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +4 -0
- package/src/plugins/core/registerReactComponent.tsx +23 -19
- package/src/plugins/impl/DataTablePlugin.tsx +11 -4
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +17 -5
- package/src/stories/dataframe.stories.tsx +2 -0
- package/src/utils/__tests__/dom.test.ts +167 -0
- package/src/utils/dom.ts +55 -0
- package/dist/assets/_baseEach-CvTX9w0Y.js +0 -1
- package/dist/assets/_baseMap-CtlwA90f.js +0 -1
- package/dist/assets/channel-Co6iMgWq.js +0 -1
- package/dist/assets/classDiagram-KNZD7YFC-BbJ0rY3y.js +0 -1
- package/dist/assets/classDiagram-v2-RKCZMP56-BbJ0rY3y.js +0 -1
- package/dist/assets/clone-BMP0PsTa.js +0 -1
- package/dist/assets/command-palette-B93Pjcky.js +0 -1
- package/dist/assets/datasources-panel-v7H3cR0p.js +0 -1
- package/dist/assets/index-C7CoaNFb.js +0 -578
- package/dist/assets/index-DadI618h.css +0 -1
- package/dist/assets/infoDiagram-STP46IZ2-CJLOpSAf.js +0 -2
- package/dist/assets/min-BBO3-1Hg.js +0 -1
- package/dist/assets/stateDiagram-v2-UMBNRL4Z-CdThjimL.js +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
InfoIcon,
|
|
5
|
+
NotebookPenIcon,
|
|
6
|
+
SquareArrowOutUpRightIcon,
|
|
7
|
+
} from "lucide-react";
|
|
4
8
|
import { Fragment, type JSX } from "react";
|
|
5
9
|
import {
|
|
6
10
|
Accordion,
|
|
@@ -72,6 +76,8 @@ export const MarimoErrorOutput = ({
|
|
|
72
76
|
titleContents = "Ancestor stopped";
|
|
73
77
|
alertVariant = "default";
|
|
74
78
|
titleColor = "text-secondary-foreground";
|
|
79
|
+
} else if (errors.some((e) => e.type === "sql-error")) {
|
|
80
|
+
titleContents = "SQL Error in statement";
|
|
75
81
|
} else {
|
|
76
82
|
// Check for exception type
|
|
77
83
|
const exceptionError = errors.find((e) => e.type === "exception");
|
|
@@ -126,6 +132,10 @@ export const MarimoErrorOutput = ({
|
|
|
126
132
|
const unknownErrors = errors.filter(
|
|
127
133
|
(e): e is Extract<MarimoError, { type: "unknown" }> => e.type === "unknown",
|
|
128
134
|
);
|
|
135
|
+
const sqlErrors = errors.filter(
|
|
136
|
+
(e): e is Extract<MarimoError, { type: "sql-error" }> =>
|
|
137
|
+
e.type === "sql-error",
|
|
138
|
+
);
|
|
129
139
|
|
|
130
140
|
const openScratchpad = () => {
|
|
131
141
|
chromeActions.openApplication("scratchpad");
|
|
@@ -485,6 +495,55 @@ export const MarimoErrorOutput = ({
|
|
|
485
495
|
);
|
|
486
496
|
}
|
|
487
497
|
|
|
498
|
+
if (sqlErrors.length > 0) {
|
|
499
|
+
messages.push(
|
|
500
|
+
<div key="sql-errors">
|
|
501
|
+
{sqlErrors.map((error, idx) => {
|
|
502
|
+
const line =
|
|
503
|
+
error.sql_line != null ? (error?.sql_line | 0) + 1 : null;
|
|
504
|
+
const col = error.sql_col != null ? (error?.sql_col | 0) + 1 : null;
|
|
505
|
+
return (
|
|
506
|
+
<div key={`sql-error-${idx}`} className="space-y-2">
|
|
507
|
+
<p className="text-muted-foreground">{error.msg}</p>
|
|
508
|
+
{error.hint && (
|
|
509
|
+
<div className="flex items-start gap-2">
|
|
510
|
+
<InfoIcon className="h-4 w-4 text-muted-foreground mt-0.5 flex-shrink-0" />
|
|
511
|
+
<pre className="whitespace-pre-wrap text-sm text-muted-foreground">
|
|
512
|
+
{error.hint}
|
|
513
|
+
</pre>
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
516
|
+
{error.sql_statement && (
|
|
517
|
+
<div className="bg-muted/50 p-2 rounded text-xs font-mono">
|
|
518
|
+
<pre className="whitespace-pre-wrap">
|
|
519
|
+
{error.sql_statement}
|
|
520
|
+
</pre>
|
|
521
|
+
</div>
|
|
522
|
+
)}
|
|
523
|
+
{line !== null && col !== null && (
|
|
524
|
+
<p className="text-xs text-muted-foreground">
|
|
525
|
+
Error at line {line}, column {col}
|
|
526
|
+
</p>
|
|
527
|
+
)}
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
531
|
+
{cellId && <AutoFixButton errors={sqlErrors} cellId={cellId} />}
|
|
532
|
+
<Tip title="How to fix SQL errors">
|
|
533
|
+
<p className="pb-2">
|
|
534
|
+
SQL parsing errors often occur due to invalid syntax, missing
|
|
535
|
+
keywords, or unsupported SQL features.
|
|
536
|
+
</p>
|
|
537
|
+
<p className="py-2">
|
|
538
|
+
Check your SQL syntax and ensure you're using supported SQL
|
|
539
|
+
dialect features. The error location can help you identify the
|
|
540
|
+
problematic part of your query.
|
|
541
|
+
</p>
|
|
542
|
+
</Tip>
|
|
543
|
+
</div>,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
488
547
|
return messages;
|
|
489
548
|
};
|
|
490
549
|
|
|
@@ -9,6 +9,7 @@ import { displayCellName } from "@/core/cells/names";
|
|
|
9
9
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
10
10
|
import type { OutputMessage } from "@/core/kernel/messages";
|
|
11
11
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
12
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
12
13
|
import { Logger } from "@/utils/Logger";
|
|
13
14
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
14
15
|
import { contextToXml } from "../utils";
|
|
@@ -64,24 +65,6 @@ function isMediaMimetype(
|
|
|
64
65
|
return false;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function parseHtmlContent(htmlString: string): string {
|
|
68
|
-
try {
|
|
69
|
-
// Create a temporary DOM element to parse HTML
|
|
70
|
-
const tempDiv = document.createElement("div");
|
|
71
|
-
tempDiv.innerHTML = htmlString;
|
|
72
|
-
|
|
73
|
-
// Extract text content, removing HTML tags
|
|
74
|
-
const textContent = tempDiv.textContent || tempDiv.innerText || "";
|
|
75
|
-
|
|
76
|
-
// Clean up extra whitespace
|
|
77
|
-
return textContent.replaceAll(/\s+/g, " ").trim();
|
|
78
|
-
} catch (error) {
|
|
79
|
-
Logger.error("Error parsing HTML content:", error);
|
|
80
|
-
// If parsing fails, return the original string
|
|
81
|
-
return htmlString;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
68
|
export class CellOutputContextProvider extends AIContextProvider<CellOutputContextItem> {
|
|
86
69
|
readonly title = "Cell Outputs";
|
|
87
70
|
readonly mentionPrefix = "@";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
languageAdapterState,
|
|
15
15
|
switchLanguage,
|
|
16
16
|
} from "../extension";
|
|
17
|
+
import { exportedForTesting as sqlValidationErrorsForTesting } from "../languages/sql/validation-errors";
|
|
17
18
|
import { languageMetadataField } from "../metadata";
|
|
18
19
|
|
|
19
20
|
let view: EditorView | null = null;
|
|
@@ -258,3 +259,26 @@ describe("switchLanguage", () => {
|
|
|
258
259
|
});
|
|
259
260
|
});
|
|
260
261
|
});
|
|
262
|
+
|
|
263
|
+
describe("sqlValidationErrors", () => {
|
|
264
|
+
const { splitErrorMessage } = sqlValidationErrorsForTesting;
|
|
265
|
+
|
|
266
|
+
describe("split error message", () => {
|
|
267
|
+
it("should split the error message into error type and error message", () => {
|
|
268
|
+
const error = "SyntaxError: SELECT * FROM df";
|
|
269
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
270
|
+
expect(errorType).toBe("SyntaxError");
|
|
271
|
+
expect(errorMessage).toBe("SELECT * FROM df");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should handle multiple colons", () => {
|
|
275
|
+
const error =
|
|
276
|
+
"SyntaxError: SELECT * FROM df:SyntaxError: SELECT * FROM df";
|
|
277
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
278
|
+
expect(errorType).toBe("SyntaxError");
|
|
279
|
+
expect(errorMessage).toBe(
|
|
280
|
+
"SELECT * FROM df:SyntaxError: SELECT * FROM df",
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { exportedForTesting } from "../languages/sql/validation-errors";
|
|
5
|
+
|
|
6
|
+
describe("Error Message Splitting", () => {
|
|
7
|
+
it("should handle error message splitting correctly", () => {
|
|
8
|
+
const { splitErrorMessage } = exportedForTesting;
|
|
9
|
+
|
|
10
|
+
const result1 = splitErrorMessage("Syntax error: unexpected token");
|
|
11
|
+
expect(result1.errorType).toBe("Syntax error");
|
|
12
|
+
expect(result1.errorMessage).toBe("unexpected token");
|
|
13
|
+
|
|
14
|
+
const result2 = splitErrorMessage("Multiple: colons: in error");
|
|
15
|
+
expect(result2.errorType).toBe("Multiple");
|
|
16
|
+
expect(result2.errorMessage).toBe("colons: in error");
|
|
17
|
+
|
|
18
|
+
const result3 = splitErrorMessage("No colon error");
|
|
19
|
+
expect(result3.errorType).toBe("No colon error");
|
|
20
|
+
expect(result3.errorMessage).toBe("");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("DuckDB Error Handling", () => {
|
|
25
|
+
it("should extract codeblock from error with LINE information", () => {
|
|
26
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
27
|
+
|
|
28
|
+
const error =
|
|
29
|
+
'Binder Error: Referenced column "attacks" not found in FROM clause! Candidate bindings: "Attack", "Total" LINE 1:... from pokemon WHERE \'type_2\' = 32 and attack = 32 and not attacks = \'hi\' ^';
|
|
30
|
+
|
|
31
|
+
const result = handleDuckdbError(error);
|
|
32
|
+
|
|
33
|
+
expect(result.errorType).toBe("Binder Error");
|
|
34
|
+
expect(result.errorMessage).toBe(
|
|
35
|
+
'Referenced column "attacks" not found in FROM clause! Candidate bindings: "Attack", "Total"',
|
|
36
|
+
);
|
|
37
|
+
expect(result.codeblock).toBe(
|
|
38
|
+
"LINE 1:... from pokemon WHERE 'type_2' = 32 and attack = 32 and not attacks = 'hi' ^",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should handle error without LINE information", () => {
|
|
43
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
44
|
+
|
|
45
|
+
const error = "Syntax Error: Invalid syntax near WHERE";
|
|
46
|
+
|
|
47
|
+
const result = handleDuckdbError(error);
|
|
48
|
+
|
|
49
|
+
expect(result.errorType).toBe("Syntax Error");
|
|
50
|
+
expect(result.errorMessage).toBe("Invalid syntax near WHERE");
|
|
51
|
+
expect(result.codeblock).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle error with LINE at the beginning", () => {
|
|
55
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
56
|
+
|
|
57
|
+
const error = "LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^";
|
|
58
|
+
|
|
59
|
+
const result = handleDuckdbError(error);
|
|
60
|
+
|
|
61
|
+
expect(result.errorType).toBe("LINE 1");
|
|
62
|
+
expect(result.errorMessage).toBe(
|
|
63
|
+
"SELECT * FROM table WHERE invalid_column = 1 ^",
|
|
64
|
+
);
|
|
65
|
+
expect(result.codeblock).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should handle error with multiple LINE occurrences", () => {
|
|
69
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
70
|
+
|
|
71
|
+
const error =
|
|
72
|
+
"Error: Something went wrong LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^";
|
|
73
|
+
|
|
74
|
+
const result = handleDuckdbError(error);
|
|
75
|
+
|
|
76
|
+
expect(result.errorType).toBe("Error");
|
|
77
|
+
expect(result.errorMessage).toBe("Something went wrong");
|
|
78
|
+
expect(result.codeblock).toBe(
|
|
79
|
+
"LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle complex error with nested quotes", () => {
|
|
84
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
85
|
+
|
|
86
|
+
const error =
|
|
87
|
+
"Binder Error: Column \"name\" not found! LINE 1: SELECT * FROM users WHERE name = 'John' AND age > 25 ^";
|
|
88
|
+
|
|
89
|
+
const result = handleDuckdbError(error);
|
|
90
|
+
|
|
91
|
+
expect(result.errorType).toBe("Binder Error");
|
|
92
|
+
expect(result.errorMessage).toBe('Column "name" not found!');
|
|
93
|
+
expect(result.codeblock).toBe(
|
|
94
|
+
"LINE 1: SELECT * FROM users WHERE name = 'John' AND age > 25 ^",
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should handle error with LINE but no caret", () => {
|
|
99
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
100
|
+
|
|
101
|
+
const error = "Error: Invalid query LINE 1: SELECT * FROM table";
|
|
102
|
+
|
|
103
|
+
const result = handleDuckdbError(error);
|
|
104
|
+
|
|
105
|
+
expect(result.errorType).toBe("Error");
|
|
106
|
+
expect(result.errorMessage).toBe("Invalid query");
|
|
107
|
+
expect(result.codeblock).toBe("LINE 1: SELECT * FROM table");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should trim whitespace from codeblock", () => {
|
|
111
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
112
|
+
|
|
113
|
+
const error = "Error: Something wrong LINE 1: SELECT * FROM table ^ ";
|
|
114
|
+
|
|
115
|
+
const result = handleDuckdbError(error);
|
|
116
|
+
|
|
117
|
+
expect(result.errorType).toBe("Error");
|
|
118
|
+
expect(result.errorMessage).toBe("Something wrong");
|
|
119
|
+
expect(result.codeblock).toBe("LINE 1: SELECT * FROM table ^");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should handle empty error message", () => {
|
|
123
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
124
|
+
|
|
125
|
+
const error = "";
|
|
126
|
+
|
|
127
|
+
const result = handleDuckdbError(error);
|
|
128
|
+
|
|
129
|
+
expect(result.errorType).toBe("");
|
|
130
|
+
expect(result.errorMessage).toBe("");
|
|
131
|
+
expect(result.codeblock).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtom } from "jotai";
|
|
4
|
+
import { atomWithStorage } from "jotai/utils";
|
|
5
|
+
import { store } from "@/core/state/jotai";
|
|
6
|
+
|
|
7
|
+
const BASE_KEY = "marimo:notebook-sql-mode";
|
|
8
|
+
|
|
9
|
+
export type SQLMode = "validate" | "default";
|
|
10
|
+
|
|
11
|
+
const sqlModeAtom = atomWithStorage<SQLMode>(BASE_KEY, "default");
|
|
12
|
+
|
|
13
|
+
export function useSQLMode() {
|
|
14
|
+
const [sqlMode, setSQLMode] = useAtom(sqlModeAtom);
|
|
15
|
+
return { sqlMode, setSQLMode };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getSQLMode() {
|
|
19
|
+
return store.get(sqlModeAtom);
|
|
20
|
+
}
|
|
@@ -5,7 +5,7 @@ import { insertTab } from "@codemirror/commands";
|
|
|
5
5
|
import { type SQLDialect, type SQLNamespace, sql } from "@codemirror/lang-sql";
|
|
6
6
|
import type { EditorState, Extension } from "@codemirror/state";
|
|
7
7
|
import { Compartment } from "@codemirror/state";
|
|
8
|
-
import {
|
|
8
|
+
import { EditorView, keymap } from "@codemirror/view";
|
|
9
9
|
import type { SyntaxNode, TreeCursor } from "@lezer/common";
|
|
10
10
|
import { parser } from "@lezer/python";
|
|
11
11
|
import {
|
|
@@ -16,12 +16,19 @@ import {
|
|
|
16
16
|
} from "@marimo-team/codemirror-sql";
|
|
17
17
|
import { DuckDBDialect } from "@marimo-team/codemirror-sql/dialects";
|
|
18
18
|
import dedent from "string-dedent";
|
|
19
|
+
import { cellIdState } from "@/core/codemirror/cells/state";
|
|
19
20
|
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
20
21
|
import {
|
|
21
22
|
dataSourceConnectionsAtom,
|
|
22
23
|
setLatestEngineSelected,
|
|
23
24
|
} from "@/core/datasets/data-source-connections";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
type ConnectionName,
|
|
27
|
+
DUCKDB_ENGINE,
|
|
28
|
+
INTERNAL_SQL_ENGINES,
|
|
29
|
+
} from "@/core/datasets/engines";
|
|
30
|
+
import { ValidateSQL } from "@/core/datasets/request-registry";
|
|
31
|
+
import type { ValidateSQLResult } from "@/core/kernel/messages";
|
|
25
32
|
import { store } from "@/core/state/jotai";
|
|
26
33
|
import { resolvedThemeAtom } from "@/theme/useTheme";
|
|
27
34
|
import { Logger } from "@/utils/Logger";
|
|
@@ -37,6 +44,11 @@ import {
|
|
|
37
44
|
tablesCompletionSource,
|
|
38
45
|
} from "./completion-sources";
|
|
39
46
|
import { SCHEMA_CACHE } from "./completion-store";
|
|
47
|
+
import { getSQLMode } from "./sql-mode";
|
|
48
|
+
import {
|
|
49
|
+
clearSqlValidationError,
|
|
50
|
+
setSqlValidationError,
|
|
51
|
+
} from "./validation-errors";
|
|
40
52
|
|
|
41
53
|
const DEFAULT_DIALECT = DuckDBDialect;
|
|
42
54
|
const DEFAULT_PARSER_DIALECT = "DuckDB";
|
|
@@ -64,12 +76,15 @@ export class SQLLanguageAdapter
|
|
|
64
76
|
{
|
|
65
77
|
readonly type = "sql";
|
|
66
78
|
sqlLinterEnabled: boolean;
|
|
79
|
+
sqlModeEnabled: boolean;
|
|
67
80
|
|
|
68
81
|
constructor() {
|
|
69
82
|
try {
|
|
70
83
|
this.sqlLinterEnabled = getFeatureFlag("sql_linter");
|
|
84
|
+
this.sqlModeEnabled = getFeatureFlag("sql_mode");
|
|
71
85
|
} catch {
|
|
72
86
|
this.sqlLinterEnabled = false;
|
|
87
|
+
this.sqlModeEnabled = false;
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
90
|
|
|
@@ -265,6 +280,10 @@ export class SQLLanguageAdapter
|
|
|
265
280
|
);
|
|
266
281
|
}
|
|
267
282
|
|
|
283
|
+
if (this.sqlModeEnabled) {
|
|
284
|
+
extensions.push(sqlValidationExtension());
|
|
285
|
+
}
|
|
286
|
+
|
|
268
287
|
return extensions;
|
|
269
288
|
}
|
|
270
289
|
}
|
|
@@ -315,9 +334,14 @@ function getSchema(view: EditorView): SQLNamespace {
|
|
|
315
334
|
function guessParserDialect(state: EditorState): ParserDialects | null {
|
|
316
335
|
const metadata = getSQLMetadata(state);
|
|
317
336
|
const connectionName = metadata.engine;
|
|
337
|
+
return connectionNameToParserDialect(connectionName);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function connectionNameToParserDialect(
|
|
341
|
+
connectionName: ConnectionName,
|
|
342
|
+
): ParserDialects | null {
|
|
318
343
|
const dialect =
|
|
319
344
|
SCHEMA_CACHE.getInternalDialect(connectionName)?.toLowerCase();
|
|
320
|
-
|
|
321
345
|
switch (dialect) {
|
|
322
346
|
case "postgresql":
|
|
323
347
|
case "postgres":
|
|
@@ -543,3 +567,66 @@ function safeDedent(code: string): string {
|
|
|
543
567
|
return code;
|
|
544
568
|
}
|
|
545
569
|
}
|
|
570
|
+
|
|
571
|
+
function sqlValidationExtension(): Extension {
|
|
572
|
+
let debounceTimeout: NodeJS.Timeout | null = null;
|
|
573
|
+
let lastValidationRequest: string | null = null;
|
|
574
|
+
|
|
575
|
+
return EditorView.updateListener.of((update) => {
|
|
576
|
+
const sqlMode = getSQLMode();
|
|
577
|
+
if (sqlMode !== "validate") {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const metadata = getSQLMetadata(update.state);
|
|
582
|
+
const connectionName = metadata.engine;
|
|
583
|
+
if (!INTERNAL_SQL_ENGINES.has(connectionName)) {
|
|
584
|
+
// Currently only internal engines are supported
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!update.docChanged) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const doc = update.state.doc;
|
|
593
|
+
const sqlContent = doc.toString();
|
|
594
|
+
|
|
595
|
+
// Clear existing timeout
|
|
596
|
+
if (debounceTimeout) {
|
|
597
|
+
clearTimeout(debounceTimeout);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Debounce the validation call
|
|
601
|
+
debounceTimeout = setTimeout(async () => {
|
|
602
|
+
// Skip if content hasn't changed since last validation
|
|
603
|
+
if (lastValidationRequest === sqlContent) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
lastValidationRequest = sqlContent;
|
|
608
|
+
const cellId = update.view.state.facet(cellIdState);
|
|
609
|
+
|
|
610
|
+
if (sqlContent === "") {
|
|
611
|
+
clearSqlValidationError(cellId);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
const result: ValidateSQLResult = await ValidateSQL.request({
|
|
617
|
+
engine: connectionName,
|
|
618
|
+
query: sqlContent,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (result.error) {
|
|
622
|
+
const dialect = connectionNameToParserDialect(connectionName);
|
|
623
|
+
setSqlValidationError({ cellId, error: result.error, dialect });
|
|
624
|
+
} else {
|
|
625
|
+
clearSqlValidationError(cellId);
|
|
626
|
+
}
|
|
627
|
+
} catch (error) {
|
|
628
|
+
Logger.warn("Failed to validate SQL", { error });
|
|
629
|
+
}
|
|
630
|
+
}, 300);
|
|
631
|
+
});
|
|
632
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { SupportedDialects } from "@marimo-team/codemirror-sql";
|
|
4
|
+
import { atom, useAtomValue } from "jotai";
|
|
5
|
+
import type { CellId } from "@/core/cells/ids";
|
|
6
|
+
import { store } from "@/core/state/jotai";
|
|
7
|
+
|
|
8
|
+
export interface SQLValidationError {
|
|
9
|
+
errorType: string;
|
|
10
|
+
errorMessage: string;
|
|
11
|
+
codeblock?: string; // Code block that caused the error
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type CellToSQLErrors = Map<CellId, SQLValidationError>;
|
|
15
|
+
|
|
16
|
+
export const sqlValidationErrorsAtom = atom<CellToSQLErrors>(
|
|
17
|
+
new Map<CellId, SQLValidationError>(),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const useSqlValidationErrorsForCell = (cellId: CellId) => {
|
|
21
|
+
const sqlValidationErrors = useAtomValue(sqlValidationErrorsAtom);
|
|
22
|
+
return sqlValidationErrors.get(cellId);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function clearSqlValidationError(cellId: CellId) {
|
|
26
|
+
const sqlValidationErrors = store.get(sqlValidationErrorsAtom);
|
|
27
|
+
const newErrors = new Map(sqlValidationErrors);
|
|
28
|
+
newErrors.delete(cellId);
|
|
29
|
+
store.set(sqlValidationErrorsAtom, newErrors);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function setSqlValidationError({
|
|
33
|
+
cellId,
|
|
34
|
+
error,
|
|
35
|
+
dialect,
|
|
36
|
+
}: {
|
|
37
|
+
cellId: CellId;
|
|
38
|
+
error: string;
|
|
39
|
+
dialect: SupportedDialects | null;
|
|
40
|
+
}) {
|
|
41
|
+
const sqlValidationErrors = store.get(sqlValidationErrorsAtom);
|
|
42
|
+
const newErrors = new Map(sqlValidationErrors);
|
|
43
|
+
|
|
44
|
+
const errorResult: SQLValidationError =
|
|
45
|
+
dialect === "DuckDB" ? handleDuckdbError(error) : splitErrorMessage(error);
|
|
46
|
+
|
|
47
|
+
newErrors.set(cellId, errorResult);
|
|
48
|
+
store.set(sqlValidationErrorsAtom, newErrors);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleDuckdbError(error: string): SQLValidationError {
|
|
52
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
53
|
+
let newErrorMessage = errorMessage;
|
|
54
|
+
|
|
55
|
+
// Extract the LINE and the rest of the message as codeblock, keep errorMessage as whatever is before
|
|
56
|
+
let codeblock: string | undefined;
|
|
57
|
+
const lineIndex = errorMessage.indexOf("LINE ");
|
|
58
|
+
if (lineIndex !== -1) {
|
|
59
|
+
codeblock = errorMessage.slice(Math.max(0, lineIndex)).trim();
|
|
60
|
+
newErrorMessage = errorMessage.slice(0, Math.max(0, lineIndex)).trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
errorType,
|
|
65
|
+
errorMessage: newErrorMessage,
|
|
66
|
+
codeblock,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function splitErrorMessage(error: string) {
|
|
71
|
+
const errorType = error.split(":")[0].trim();
|
|
72
|
+
const errorMessage = error.split(":").slice(1).join(":").trim();
|
|
73
|
+
return { errorType, errorMessage };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const exportedForTesting = {
|
|
77
|
+
splitErrorMessage,
|
|
78
|
+
handleDuckdbError,
|
|
79
|
+
};
|
|
@@ -5,7 +5,8 @@ import { Button } from "@/components/ui/button";
|
|
|
5
5
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
6
6
|
import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
|
|
7
7
|
import { normalizeName } from "@/core/cells/names";
|
|
8
|
-
import
|
|
8
|
+
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
9
|
+
import { type ConnectionName, DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
9
10
|
import { useAutoGrowInputProps } from "@/hooks/useAutoGrowInputProps";
|
|
10
11
|
import { formatSQL } from "../../format";
|
|
11
12
|
import { languageAdapterState } from "../extension";
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
import type { LanguageMetadataOf } from "../types";
|
|
23
24
|
import type { QuotePrefixKind } from "../utils/quotes";
|
|
24
25
|
import { getQuotePrefix, MarkdownQuotePrefixTooltip } from "./markdown";
|
|
25
|
-
import { SQLEngineSelect } from "./sql";
|
|
26
|
+
import { SQLEngineSelect, SQLModeSelect } from "./sql";
|
|
26
27
|
|
|
27
28
|
const Divider = () => <div className="h-4 border-r border-border" />;
|
|
28
29
|
|
|
@@ -70,6 +71,8 @@ export const LanguagePanelComponent: React.FC<{
|
|
|
70
71
|
updateSQLDialectFromConnection(view, engine);
|
|
71
72
|
};
|
|
72
73
|
|
|
74
|
+
const sqlModeEnabled = getFeatureFlag("sql_mode");
|
|
75
|
+
|
|
73
76
|
actions = (
|
|
74
77
|
<div className="flex flex-1 gap-2 items-center">
|
|
75
78
|
<label className="flex gap-2 items-center">
|
|
@@ -95,6 +98,9 @@ export const LanguagePanelComponent: React.FC<{
|
|
|
95
98
|
onChange={switchEngine}
|
|
96
99
|
/>
|
|
97
100
|
<div className="flex items-center gap-2 ml-auto">
|
|
101
|
+
{sqlModeEnabled && metadata.engine === DUCKDB_ENGINE && (
|
|
102
|
+
<SQLModeSelect />
|
|
103
|
+
)}
|
|
98
104
|
<Tooltip content="Format SQL">
|
|
99
105
|
<Button
|
|
100
106
|
variant="text"
|