@rebasepro/studio 0.21.2-canary.g1ea48be → 0.23.0

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.
@@ -1,11 +1,11 @@
1
- import { t as MonacoEditor } from "./MonacoEditor-D0_QljcX.js";
1
+ import { t as MonacoEditor } from "./MonacoEditor-CUxy5_k5.js";
2
2
  import { ConfirmationDialog, ErrorView, IconForView, useRebaseContext, useSnackbarController, useStudioCollectionRegistry, useStudioSidePanelController, useTranslation } from "@rebasepro/app";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { Button, Checkbox, CircularProgress, Collapse, CopyIcon, DatabaseIcon, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Menu, MenuIcon, MenuItem, MoreVerticalIcon, PencilIcon, PlayIcon, PlusIcon, RefreshCwIcon, ResizablePanels, Tab, Tabs, TerminalIcon, TextField, TextareaAutosize, Tooltip, Trash2Icon, Typography, VirtualTable, XIcon, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { isArrayValue, isRecordValue, readStoredJson, readStoredString, toSnakeCase, writeStoredJson, writeStoredString } from "@rebasepro/utils";
7
7
  import { createPortal } from "react-dom";
8
- import { parseFirst } from "pgsql-ast-parser";
8
+ import { parse, parseFirst } from "pgsql-ast-parser";
9
9
  //#region src/components/SQLEditor/SchemaBrowser.tsx
10
10
  var SchemaBrowser = ({ onTableClick, schemas, isSchemaLoading, schemaError, onRetrySchema }) => {
11
11
  const [expandedSchemas, setExpandedSchemas] = useState({ public: true });
@@ -393,6 +393,181 @@ var SQLEditorSidebar = ({ onSelectSnippet, onTableClick, snippets, history, onDe
393
393
  };
394
394
  //#endregion
395
395
  //#region src/utils/sql_utils.ts
396
+ /** The statements in `sqlText`, or `null` when the parser cannot read it. */
397
+ function parseStatements(sqlText) {
398
+ try {
399
+ return parse(sqlText);
400
+ } catch {
401
+ return null;
402
+ }
403
+ }
404
+ /**
405
+ * The `EXPLAIN` for one statement, or `null` when `sqlText` is not exactly one.
406
+ *
407
+ * Never `ANALYZE`: that executes the statement to time it, so explaining a
408
+ * `DELETE` deleted the rows — without the confirmation "Run" asks for.
409
+ *
410
+ * And never more than one statement. Sent as one simple query, `EXPLAIN (…)
411
+ * SELECT 1; DELETE …` explains the SELECT and *runs* the DELETE. Text the
412
+ * parser cannot read is still explained, as long as no `;` could be hiding a
413
+ * second statement in it.
414
+ */
415
+ function buildExplainSql(sqlText) {
416
+ const statement = sqlText.trim().replace(/;+$/, "").trim();
417
+ if (!statement) return null;
418
+ const statements = parseStatements(statement);
419
+ return (statements ? statements.length === 1 : !statement.includes(";")) ? `EXPLAIN (FORMAT JSON) ${statement}` : null;
420
+ }
421
+ /**
422
+ * Whether the console's automatic `LIMIT` may be appended to `sqlText`: one
423
+ * top-level SELECT that has no limit of its own.
424
+ *
425
+ * Decided on the parsed statement, not the text. A search for the word SELECT
426
+ * also found it inside `INSERT INTO … SELECT` and `CREATE TABLE … AS SELECT`,
427
+ * which then copied a thousand rows and reported success, and at the start of
428
+ * a script whose last statement the appended `LIMIT` turned into a syntax
429
+ * error. Text the parser cannot read is left as written.
430
+ */
431
+ function acceptsAutoLimit(sqlText) {
432
+ const statements = parseStatements(sqlText);
433
+ if (!statements || statements.length !== 1) return false;
434
+ const [statement] = statements;
435
+ return statement.type === "select" && !statement.limit;
436
+ }
437
+ /** A destructive command named anywhere in a text, comments included. */
438
+ var DESTRUCTIVE_COMMAND = /\b(DELETE|UPDATE|DROP|TRUNCATE)\b/i;
439
+ function isDestructiveStatement(statement) {
440
+ switch (statement.type) {
441
+ case "delete":
442
+ case "update": return !statement.where;
443
+ case "truncate table":
444
+ case "drop table":
445
+ case "drop sequence":
446
+ case "drop index":
447
+ case "drop type":
448
+ case "drop trigger":
449
+ case "drop function": return true;
450
+ case "alter table": return statement.changes.some((change) => change.type === "drop column" || change.type === "drop constraint");
451
+ case "with": return statement.bind.some((binding) => isDestructiveStatement(binding.statement)) || isDestructiveStatement(statement.in);
452
+ case "with recursive": return isDestructiveStatement(statement.in);
453
+ case "do": return DESTRUCTIVE_COMMAND.test(statement.code);
454
+ default: return false;
455
+ }
456
+ }
457
+ /**
458
+ * Whether "Run" asks before running `sqlText`: a statement in it deletes or
459
+ * updates without a WHERE of its own, drops or truncates.
460
+ *
461
+ * Decided per parsed statement, not on the text. A search of the text for the
462
+ * word WHERE found it in another statement or in a comment, so `DELETE FROM
463
+ * posts; SELECT * FROM posts WHERE id = 1` deleted every post without asking.
464
+ * Text the parser cannot read is confirmed whenever it names a destructive
465
+ * command.
466
+ */
467
+ function needsDestructiveConfirmation(sqlText) {
468
+ const statements = parseStatements(sqlText);
469
+ if (!statements) return DESTRUCTIVE_COMMAND.test(sqlText);
470
+ return statements.some(isDestructiveStatement);
471
+ }
472
+ /**
473
+ * A table as SQL: `"schema"."table"` when the query named its schema, and a
474
+ * bare `"table"` when it left it to the search path — which then resolves it
475
+ * the same way it resolved the query.
476
+ */
477
+ function quoteTableName(tableName, schemaName) {
478
+ const quote = (identifier) => `"${identifier.replace(/"/g, "\"\"")}"`;
479
+ return schemaName ? `${quote(schemaName)}.${quote(tableName)}` : quote(tableName);
480
+ }
481
+ /**
482
+ * The introspected columns of a table the query named — in the schema it
483
+ * named, when it named one. Unqualified, the first schema that has a table of
484
+ * that name answers.
485
+ */
486
+ function findTableInfo(schemas, table) {
487
+ if (table.schema) return schemas[table.schema]?.find((t) => t.tableName === table.name);
488
+ for (const schema of Object.values(schemas)) {
489
+ const tableInfo = schema.find((t) => t.tableName === table.name);
490
+ if (tableInfo) return tableInfo;
491
+ }
492
+ }
493
+ /**
494
+ * The name Postgres gives the result column of an unaliased select item, as
495
+ * far as it can be told from the expression: a column keeps its name, a cast
496
+ * the name of what it casts, a function call the function's, and anything
497
+ * else a name no table column has.
498
+ */
499
+ function expressionColumnName(expr) {
500
+ switch (expr.type) {
501
+ case "ref": return expr.name;
502
+ case "cast": {
503
+ const operandName = expressionColumnName(expr.operand);
504
+ if (operandName !== "?column?") return operandName;
505
+ return expr.to.kind === "array" ? operandName : expr.to.name;
506
+ }
507
+ case "call": return expr.function.name;
508
+ case "keyword": return expr.keyword;
509
+ case "case": return "case";
510
+ case "array select": return "array";
511
+ case "select": {
512
+ const [first] = expr.columns ?? [];
513
+ return first ? selectedColumnName(first) : "?column?";
514
+ }
515
+ default: return "?column?";
516
+ }
517
+ }
518
+ function selectedColumnName(column) {
519
+ return column.alias?.name ?? expressionColumnName(column.expr);
520
+ }
521
+ /**
522
+ * How many columns of the query's result may carry `name` — an upper bound:
523
+ * a `*` over a source whose columns are not known counts once for every name.
524
+ *
525
+ * A result row is an object keyed by column name, so where two columns share
526
+ * a name the row holds only the last of them. A key or a value read from such
527
+ * a row cannot be told apart from its namesake: for `SELECT * FROM posts p
528
+ * JOIN authors a …` the row's `id` is the author's.
529
+ */
530
+ function resultColumnCounter(ast, schemas) {
531
+ const sources = (ast.from ?? []).map((item) => {
532
+ if (item.type === "table") {
533
+ const renamed = item.name.columnNames ?? [];
534
+ const columns = findTableInfo(schemas, {
535
+ name: item.name.name,
536
+ schema: item.name.schema
537
+ })?.columns.map((c, i) => renamed[i]?.name ?? c.name);
538
+ return {
539
+ refName: item.name.alias ?? item.name.name,
540
+ columns
541
+ };
542
+ }
543
+ if (item.type === "statement") {
544
+ const { statement } = item;
545
+ const named = statement.type === "select" && (statement.columns ?? []).every((c) => !(c.expr.type === "ref" && c.expr.name === "*"));
546
+ const renamed = item.columnNames ?? [];
547
+ const columns = named && statement.type === "select" ? (statement.columns ?? []).map((c, i) => renamed[i]?.name ?? selectedColumnName(c)) : void 0;
548
+ return {
549
+ refName: item.alias,
550
+ columns
551
+ };
552
+ }
553
+ return {
554
+ refName: item.alias?.name,
555
+ columns: void 0
556
+ };
557
+ });
558
+ return (name) => {
559
+ let count = 0;
560
+ for (const column of ast.columns ?? []) {
561
+ const expr = column.expr;
562
+ if (expr.type === "ref" && expr.name === "*") {
563
+ const covered = expr.table ? sources.filter((source) => source.refName === expr.table?.name) : sources;
564
+ if (covered.length === 0) count += 1;
565
+ for (const source of covered) count += source.columns ? source.columns.filter((c) => c === name).length : 1;
566
+ } else if (selectedColumnName(column) === name) count += 1;
567
+ }
568
+ return count;
569
+ };
570
+ }
396
571
  /**
397
572
  * Extract all tables referenced in a SQL query's FROM and JOIN clauses.
398
573
  * Returns an empty array for non-SELECT queries or parse failures.
@@ -406,6 +581,7 @@ function extractTablesFromQuery(sqlString) {
406
581
  for (const item of fromItems) {
407
582
  if (item.type === "table" && item.name) tables.push({
408
583
  name: item.name.name,
584
+ schema: item.name.schema,
409
585
  alias: item.name.alias
410
586
  });
411
587
  if (item.type === "join") {
@@ -434,10 +610,12 @@ function resolveQueryCollections(sqlString, schemas, collections, resultColumns)
434
610
  const tables = extractTablesFromQuery(sqlString);
435
611
  if (tables.length === 0) return [];
436
612
  const selectColumns = [];
613
+ let countResultColumns;
437
614
  try {
438
615
  const ast = parseFirst(sqlString);
439
- if (ast.type === "select" && ast.columns) {
440
- for (const col of ast.columns) if (col.expr?.type === "ref") selectColumns.push({
616
+ if (ast.type === "select") {
617
+ countResultColumns = resultColumnCounter(ast, schemas);
618
+ for (const col of ast.columns ?? []) if (col.expr?.type === "ref") selectColumns.push({
441
619
  table: col.expr.table?.name,
442
620
  column: col.expr.name,
443
621
  alias: col.alias?.name
@@ -447,17 +625,12 @@ function resolveQueryCollections(sqlString, schemas, collections, resultColumns)
447
625
  const results = [];
448
626
  for (const table of tables) {
449
627
  const matched = collections.find((c) => {
450
- return (("table" in c ? c.table : void 0) || toSnakeCase(c.slug)) === table.name;
628
+ const tableName = ("table" in c ? c.table : void 0) || toSnakeCase(c.slug);
629
+ const schemaName = ("schema" in c ? c.schema : void 0) || "public";
630
+ return tableName === table.name && (!table.schema || table.schema === schemaName);
451
631
  });
452
632
  if (!matched) continue;
453
- const tableColumns = [];
454
- for (const schemaEntries of Object.values(schemas)) {
455
- const tableInfo = schemaEntries.find((t) => t.tableName === table.name);
456
- if (tableInfo) {
457
- tableColumns.push(...tableInfo.columns.map((c) => c.name));
458
- break;
459
- }
460
- }
633
+ const tableColumns = findTableInfo(schemas, table)?.columns.map((c) => c.name) ?? [];
461
634
  let pkColumn;
462
635
  const tableRef = table.alias || table.name;
463
636
  const idSelectCol = selectColumns.find((sc) => sc.column === "id" && (!sc.table || sc.table === tableRef || sc.table === table.name));
@@ -466,6 +639,7 @@ function resolveQueryCollections(sqlString, schemas, collections, resultColumns)
466
639
  if (resultColumns.includes("id")) pkColumn = "id";
467
640
  }
468
641
  if (!pkColumn && tableColumns.includes("id")) pkColumn = "id";
642
+ if (pkColumn && (!countResultColumns || countResultColumns(pkColumn) > 1)) pkColumn = void 0;
469
643
  results.push({
470
644
  tableName: table.name,
471
645
  tableAlias: table.alias,
@@ -493,37 +667,25 @@ function determineTableAndPK(sqlString, columnKey, schemas) {
493
667
  const resolvedColumn = selectColumns.find((sc) => sc.alias === columnKey || !sc.alias && sc.column === columnKey);
494
668
  const actualDbColumnName = resolvedColumn?.column ?? columnKey;
495
669
  const columnTableRef = resolvedColumn?.table;
496
- let resolvedTableName = null;
497
- if (tables.length === 1) resolvedTableName = tables[0].name;
670
+ let resolvedTable = null;
671
+ if (tables.length === 1) resolvedTable = tables[0];
498
672
  else {
499
673
  if (columnTableRef) {
500
674
  const matchedTable = tables.find((t) => t.alias === columnTableRef || t.name === columnTableRef);
501
- if (matchedTable) resolvedTableName = matchedTable.name;
675
+ if (matchedTable) resolvedTable = matchedTable;
502
676
  }
503
- if (!resolvedTableName) {
504
- const matchedTables = tables.filter((t) => {
505
- for (const schema of Object.values(schemas)) {
506
- const tableInfo = schema.find((ti) => ti.tableName === t.name);
507
- if (tableInfo && tableInfo.columns.some((c) => c.name === actualDbColumnName)) return true;
508
- }
509
- return false;
510
- });
511
- if (matchedTables.length === 1) resolvedTableName = matchedTables[0].name;
677
+ if (!resolvedTable) {
678
+ const matchedTables = tables.filter((t) => findTableInfo(schemas, t)?.columns.some((c) => c.name === actualDbColumnName) ?? false);
679
+ if (matchedTables.length === 1) resolvedTable = matchedTables[0];
512
680
  else if (matchedTables.length > 1) return { error: `Ambiguous column "${columnKey}": Found in multiple queried tables.` };
513
681
  else return { error: `Could not find column "${columnKey}" in the queried tables.` };
514
682
  }
515
683
  }
516
- if (!resolvedTableName) return { error: "Could not resolve the target table." };
517
- let pkDbColumns = [];
518
- for (const schema of Object.values(schemas)) {
519
- const tableInfo = schema.find((t) => t.tableName === resolvedTableName);
520
- if (tableInfo) {
521
- pkDbColumns = tableInfo.columns.filter((c) => c.isPrimaryKey).map((c) => c.name);
522
- break;
523
- }
524
- }
684
+ if (!resolvedTable) return { error: "Could not resolve the target table." };
685
+ const resolvedTableName = resolvedTable.name;
686
+ const pkDbColumns = (findTableInfo(schemas, resolvedTable)?.columns ?? []).filter((c) => c.isPrimaryKey).map((c) => c.name);
525
687
  if (pkDbColumns.length === 0) return { error: `Table "${resolvedTableName}" has no primary key defined.` };
526
- const tableAlias = tables.find((t) => t.name === resolvedTableName)?.alias;
688
+ const tableAlias = resolvedTable.alias;
527
689
  const primaryKeys = pkDbColumns.map((dbCol) => {
528
690
  const selectCol = selectColumns.find((sc) => sc.column === dbCol && (!sc.table || sc.table === (tableAlias || resolvedTableName)));
529
691
  return {
@@ -531,8 +693,13 @@ function determineTableAndPK(sqlString, columnKey, schemas) {
531
693
  resultColumn: selectCol?.alias || selectCol?.column || dbCol
532
694
  };
533
695
  });
696
+ const countResultColumns = resultColumnCounter(ast, schemas);
697
+ if (countResultColumns(columnKey) > 1) return { error: `Ambiguous column "${columnKey}": the result has more than one column of that name. Give each one its own alias to edit it.` };
698
+ const sharedKey = primaryKeys.find((pk) => countResultColumns(pk.resultColumn) > 1);
699
+ if (sharedKey) return { error: `Cannot tell which "${sharedKey.resultColumn}" column is the key of "${resolvedTableName}": the result has more than one column of that name. Give it its own alias (${tableAlias || resolvedTableName}.${sharedKey.dbColumn} AS ${resolvedTableName}_${sharedKey.dbColumn}) to edit this table.` };
534
700
  return {
535
701
  tableName: resolvedTableName,
702
+ schemaName: resolvedTable.schema,
536
703
  primaryKeys
537
704
  };
538
705
  } catch (e) {
@@ -805,7 +972,8 @@ var SQLEditor = () => {
805
972
  loading: false,
806
973
  error: null,
807
974
  execTime: null,
808
- lastExecutedSql: null
975
+ lastExecutedSql: null,
976
+ lastExecutedConnection: null
809
977
  }));
810
978
  if (restored.length > 0) return restored;
811
979
  return [{
@@ -818,7 +986,8 @@ var SQLEditor = () => {
818
986
  loading: false,
819
987
  error: null,
820
988
  execTime: null,
821
- lastExecutedSql: null
989
+ lastExecutedSql: null,
990
+ lastExecutedConnection: null
822
991
  }];
823
992
  });
824
993
  const [activeTabId, setActiveTabId] = useState(() => {
@@ -1032,6 +1201,7 @@ var SQLEditor = () => {
1032
1201
  const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
1033
1202
  const [pendingAction, setPendingAction] = useState(null);
1034
1203
  const [editingCell, setEditingCell] = useState(null);
1204
+ const isCurrentConnection = useCallback((connection) => connection !== null && connection.database === selectedDatabase && connection.role === selectedRole, [selectedDatabase, selectedRole]);
1035
1205
  const handleDoubleClick = useCallback((rowIndex, columnKey, initialValue, rowData) => {
1036
1206
  if (!activeTab.lastExecutedSql) {
1037
1207
  snackbarController.open({
@@ -1040,6 +1210,13 @@ var SQLEditor = () => {
1040
1210
  });
1041
1211
  return;
1042
1212
  }
1213
+ if (!isCurrentConnection(activeTab.lastExecutedConnection)) {
1214
+ snackbarController.open({
1215
+ type: "error",
1216
+ message: t("studio_sql_cannot_edit_other_connection")
1217
+ });
1218
+ return;
1219
+ }
1043
1220
  const resolution = determineTableAndPK(activeTab.lastExecutedSql, columnKey, schemas);
1044
1221
  if (resolution.error || !resolution.primaryKeys || resolution.primaryKeys.length === 0) {
1045
1222
  snackbarController.open({
@@ -1063,13 +1240,24 @@ var SQLEditor = () => {
1063
1240
  });
1064
1241
  }, [
1065
1242
  activeTab.lastExecutedSql,
1243
+ activeTab.lastExecutedConnection,
1244
+ isCurrentConnection,
1066
1245
  schemas,
1067
- snackbarController
1246
+ snackbarController,
1247
+ t
1068
1248
  ]);
1069
1249
  const handleCellSave = useCallback(async (newValue, rowData, columnKey, rowIndex) => {
1070
1250
  if (!editingCell || !activeTab.lastExecutedSql) return;
1071
1251
  setEditingCell(null);
1072
1252
  if (newValue === editingCell.initialValue) return;
1253
+ const connection = activeTab.lastExecutedConnection;
1254
+ if (!isCurrentConnection(connection)) {
1255
+ snackbarController.open({
1256
+ type: "error",
1257
+ message: t("studio_sql_cannot_edit_other_connection")
1258
+ });
1259
+ return;
1260
+ }
1073
1261
  const resolution = determineTableAndPK(activeTab.lastExecutedSql, columnKey, schemas);
1074
1262
  if (resolution.error || !resolution.tableName || !resolution.primaryKeys || resolution.primaryKeys.length === 0) {
1075
1263
  snackbarController.open({
@@ -1078,7 +1266,7 @@ var SQLEditor = () => {
1078
1266
  });
1079
1267
  return;
1080
1268
  }
1081
- const tableName = resolution.tableName;
1269
+ const tableName = quoteTableName(resolution.tableName, resolution.schemaName);
1082
1270
  const formatValue = (val) => {
1083
1271
  if (val === null || val === void 0) return "NULL";
1084
1272
  if (typeof val === "number") return val;
@@ -1100,12 +1288,12 @@ var SQLEditor = () => {
1100
1288
  };
1101
1289
  const dbColumnName = resolveDbColumnName(columnKey);
1102
1290
  const whereConditions = resolution.primaryKeys.map((pk) => `"${pk.dbColumn}" = ${formatValue(rowData[pk.resultColumn])}`).join(" AND ");
1103
- const updateSql = `UPDATE "${tableName}" SET "${dbColumnName}" = ${formatValue(newValue)} WHERE ${whereConditions};`;
1291
+ const updateSql = `UPDATE ${tableName} SET "${dbColumnName}" = ${formatValue(newValue)} WHERE ${whereConditions};`;
1104
1292
  try {
1105
1293
  if (databaseAdmin?.executeSql) {
1106
1294
  await databaseAdmin.executeSql(updateSql, {
1107
- database: selectedDatabase,
1108
- role: selectedRole
1295
+ database: connection.database,
1296
+ role: connection.role
1109
1297
  });
1110
1298
  const newResults = [...activeTab.results || []];
1111
1299
  if (newResults[rowIndex]) newResults[rowIndex] = {
@@ -1128,12 +1316,13 @@ var SQLEditor = () => {
1128
1316
  editingCell,
1129
1317
  schemas,
1130
1318
  activeTab.lastExecutedSql,
1319
+ activeTab.lastExecutedConnection,
1131
1320
  activeTab.results,
1321
+ isCurrentConnection,
1132
1322
  databaseAdmin,
1133
1323
  updateActiveTab,
1134
1324
  snackbarController,
1135
- selectedDatabase,
1136
- selectedRole
1325
+ t
1137
1326
  ]);
1138
1327
  const [columnWidths, setColumnWidths] = useState(() => {
1139
1328
  return readStoredJson(`rebase_sql_column_widths_${client?.baseUrl ? client.baseUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_") : "default"}`, {
@@ -1200,7 +1389,8 @@ var SQLEditor = () => {
1200
1389
  loading: false,
1201
1390
  error: null,
1202
1391
  execTime: null,
1203
- lastExecutedSql: null
1392
+ lastExecutedSql: null,
1393
+ lastExecutedConnection: null
1204
1394
  }]);
1205
1395
  setActiveTabId(newId);
1206
1396
  };
@@ -1232,12 +1422,23 @@ var SQLEditor = () => {
1232
1422
  const formatted = activeTab.sql.replace(/\s+/g, " ").replace(/\s?,\s?/g, ", ").replace(/\s?=\s?/g, " = ").trim();
1233
1423
  setSql(formatted);
1234
1424
  };
1425
+ /** The editor, for the selection the toolbar's buttons act on. */
1426
+ const editorHandle = useRef(null);
1235
1427
  const handleExplain = async () => {
1236
- const explainSql = `EXPLAIN (FORMAT JSON, ANALYZE) ${activeTab.sql}`;
1428
+ const explainSql = buildExplainSql(editorHandle.current?.getSelectedText() ?? activeTab.sql);
1429
+ if (!explainSql) {
1430
+ updateActiveTab({
1431
+ results: null,
1432
+ error: t("studio_sql_explain_single_statement")
1433
+ });
1434
+ return;
1435
+ }
1237
1436
  updateActiveTab({
1238
1437
  loading: true,
1239
1438
  error: null,
1240
- results: null
1439
+ results: null,
1440
+ lastExecutedSql: null,
1441
+ lastExecutedConnection: null
1241
1442
  });
1242
1443
  const start = performance.now();
1243
1444
  try {
@@ -1260,10 +1461,8 @@ var SQLEditor = () => {
1260
1461
  };
1261
1462
  const executeRun = useCallback(async (sqlOverride) => {
1262
1463
  let sqlToRun = sqlOverride || activeTab.sql;
1263
- const upperSql = sqlToRun.toUpperCase();
1264
1464
  const isAggregate = /\b(COUNT|SUM|AVG|MIN|MAX)\s*\(/i.test(sqlToRun);
1265
- const isExplain = /\bEXPLAIN\b/i.test(sqlToRun);
1266
- if (autoLimit && upperSql.includes("SELECT") && !upperSql.includes("LIMIT") && !isAggregate && !isExplain) {
1465
+ if (autoLimit && acceptsAutoLimit(sqlToRun) && !isAggregate) {
1267
1466
  sqlToRun = sqlToRun.trim().replace(/;$/, "");
1268
1467
  sqlToRun = `${sqlToRun} LIMIT 1000;`;
1269
1468
  }
@@ -1282,7 +1481,11 @@ var SQLEditor = () => {
1282
1481
  updateActiveTab({
1283
1482
  results: result,
1284
1483
  execTime: Math.round(performance.now() - start),
1285
- lastExecutedSql: sqlToRun
1484
+ lastExecutedSql: sqlToRun,
1485
+ lastExecutedConnection: {
1486
+ database: selectedDatabase,
1487
+ role: selectedRole
1488
+ }
1286
1489
  });
1287
1490
  if (history[history.length - 1] !== activeTab.sql) saveHistory([...history, activeTab.sql]);
1288
1491
  } else updateActiveTab({ error: t("studio_sql_execution_not_supported") });
@@ -1304,14 +1507,7 @@ var SQLEditor = () => {
1304
1507
  const handleRun = useCallback(async (selectedText) => {
1305
1508
  const sqlTarget = selectedText || activeTab.sql;
1306
1509
  if (!sqlTarget.trim()) return;
1307
- const hasDestructive = [
1308
- "DELETE",
1309
- "DROP",
1310
- "TRUNCATE",
1311
- "UPDATE"
1312
- ].some((kw) => sqlTarget.toUpperCase().includes(kw));
1313
- const hasWhere = sqlTarget.toUpperCase().includes("WHERE");
1314
- if (hasDestructive && (!hasWhere || sqlTarget.toUpperCase().includes("DROP") || sqlTarget.toUpperCase().includes("TRUNCATE"))) {
1510
+ if (needsDestructiveConfirmation(sqlTarget)) {
1315
1511
  setPendingAction(() => () => executeRun(selectedText));
1316
1512
  setIsConfirmDialogOpen(true);
1317
1513
  return;
@@ -1325,7 +1521,7 @@ var SQLEditor = () => {
1325
1521
  const isInput = activeElement?.tagName === "INPUT" || activeElement?.tagName === "TEXTAREA";
1326
1522
  if (!activeElement?.className?.includes("monaco-mouse-cursor-text") && !isInput) {
1327
1523
  e.preventDefault();
1328
- handleRun();
1524
+ handleRun(editorHandle.current?.getSelectedText());
1329
1525
  }
1330
1526
  }
1331
1527
  };
@@ -1876,7 +2072,7 @@ var SQLEditor = () => {
1876
2072
  })
1877
2073
  }),
1878
2074
  /* @__PURE__ */ jsxs(Button, {
1879
- onClick: () => handleRun(),
2075
+ onClick: () => handleRun(editorHandle.current?.getSelectedText()),
1880
2076
  disabled: loading,
1881
2077
  size: "small",
1882
2078
  color: "primary",
@@ -1898,6 +2094,7 @@ var SQLEditor = () => {
1898
2094
  firstPanel: /* @__PURE__ */ jsx("div", {
1899
2095
  className: "h-full w-full relative flex flex-col min-h-0",
1900
2096
  children: /* @__PURE__ */ jsx(MonacoEditor, {
2097
+ ref: editorHandle,
1901
2098
  value: sql,
1902
2099
  onChange: (v) => setSql(v || ""),
1903
2100
  onRun: handleRun,
@@ -1974,4 +2171,4 @@ var SQLEditor = () => {
1974
2171
  //#endregion
1975
2172
  export { SQLEditor };
1976
2173
 
1977
- //# sourceMappingURL=SQLEditor-sdtoZv5F.js.map
2174
+ //# sourceMappingURL=SQLEditor-TBw_K6CW.js.map