diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
index 5923fbca5f..d0af3bdf82 100644
--- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue
+++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
@@ -351,6 +351,7 @@ const editActiveDataGridTypeColorSchemeId = ref(settingsStore.editorSettings.act
const showThemeCustomizer = ref(false);
const showDataGridTypeColorScheme = ref(false);
const editExecuteMode = ref(settingsStore.editorSettings.executeMode);
+const editDefaultTransactionMode = ref(settingsStore.editorSettings.defaultTransactionMode);
const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts));
function translateWithExecuteShortcut(key: string): string {
return t(key, { shortcut: formatShortcutDisplay(editShortcuts.value.executeSql) });
@@ -573,6 +574,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
customThemes: editCustomThemes.value,
activeCustomThemeId: editActiveCustomThemeId.value,
executeMode: editExecuteMode.value,
+ defaultTransactionMode: editDefaultTransactionMode.value,
executeAllOnBlankLine: editExecuteAllOnBlankLine.value,
showExecutionTargetPicker: editShowExecutionTargetPicker.value,
showStatementRunButtons: editShowStatementRunButtons.value,
@@ -837,6 +839,7 @@ function syncEditorSettingsDraftFromStore() {
editCustomThemes.value = [...settingsStore.editorSettings.customThemes];
editActiveCustomThemeId.value = settingsStore.editorSettings.activeCustomThemeId;
editExecuteMode.value = settingsStore.editorSettings.executeMode;
+ editDefaultTransactionMode.value = settingsStore.editorSettings.defaultTransactionMode;
editExecuteAllOnBlankLine.value = settingsStore.editorSettings.executeAllOnBlankLine;
editShowExecutionTargetPicker.value = settingsStore.editorSettings.showExecutionTargetPicker;
editShowStatementRunButtons.value = settingsStore.editorSettings.showStatementRunButtons;
@@ -1094,6 +1097,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily;
editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize;
editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode;
+ editDefaultTransactionMode.value = DEFAULT_EDITOR_SETTINGS.defaultTransactionMode;
editExecuteAllOnBlankLine.value = DEFAULT_EDITOR_SETTINGS.executeAllOnBlankLine;
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editShowStatementRunButtons.value = DEFAULT_EDITOR_SETTINGS.showStatementRunButtons;
@@ -1212,6 +1216,7 @@ function resetAllDefaults() {
editCustomThemes.value = [...DEFAULT_EDITOR_SETTINGS.customThemes];
editActiveCustomThemeId.value = DEFAULT_EDITOR_SETTINGS.activeCustomThemeId;
editExecuteMode.value = DEFAULT_EDITOR_SETTINGS.executeMode;
+ editDefaultTransactionMode.value = DEFAULT_EDITOR_SETTINGS.defaultTransactionMode;
editExecuteAllOnBlankLine.value = DEFAULT_EDITOR_SETTINGS.executeAllOnBlankLine;
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editShowStatementRunButtons.value = DEFAULT_EDITOR_SETTINGS.showStatementRunButtons;
@@ -1429,6 +1434,10 @@ function onExecuteModeChange(v: any) {
if (v === "all" || v === "current") editExecuteMode.value = v;
}
+function onDefaultTransactionModeChange(v: any) {
+ if (v === "auto" || v === "manual") editDefaultTransactionMode.value = v;
+}
+
function onCompletionTriggerModeChange(v: any) {
if (v === "manual" || v === "require-prefix" || v === "positional") {
editCompletionTriggerMode.value = v;
@@ -4031,6 +4040,24 @@ onUnmounted(() => {
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 94899fd9ba..4e6327d9c6 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -6515,6 +6515,11 @@ export default {
executeModeDescription: "Run SQL shortcut: {shortcut}. Controls whether it executes all SQL or the statement at the cursor by default.",
executeModeAll: "Execute all SQL",
executeModeCurrent: "Execute statement at cursor",
+ defaultTransactionMode: "Default transaction commit mode",
+ defaultTransactionModeDescription:
+ 'The transaction mode used by default when a new SQL editor is opened. With "Manual transaction", new editors start in manual transaction mode, requiring manual commit or rollback; with "Auto commit", each statement is committed automatically. Only applies to databases that support transactions (PostgreSQL, MySQL, Oracle, JDBC).',
+ defaultTransactionModeAuto: "Auto commit",
+ defaultTransactionModeManual: "Manual transaction",
executeAllOnBlankLine: "Execute all SQL from blank lines",
executeAllOnBlankLineDescription: "In current-statement mode, execute all SQL when there is no statement at the cursor. Disabled by default.",
showExecutionTargetPicker: "Show execution target picker",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index 483cb25ff2..905d91f728 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -6183,6 +6183,11 @@ export default withEnglishFallback({
executeModeDescription: "Atajo para ejecutar SQL: {shortcut}. Controla si se ejecuta todo el SQL o la sentencia en el cursor de forma predeterminada.",
executeModeAll: "Ejecutar todo el SQL",
executeModeCurrent: "Ejecutar sentencia en el cursor",
+ defaultTransactionMode: "Modo de confirmación de transacción predeterminado",
+ defaultTransactionModeDescription:
+ 'El modo de transacción que se usa de forma predeterminada al abrir un nuevo editor SQL. Con "Transacción manual", los nuevos editores inician en modo de transacción manual y requieren confirmación o reversión manual; con "Confirmación automática", cada sentencia se confirma automáticamente. Solo se aplica a bases de datos que admiten transacciones (PostgreSQL, MySQL, Oracle, JDBC).',
+ defaultTransactionModeAuto: "Confirmación automática",
+ defaultTransactionModeManual: "Transacción manual",
executeAllOnBlankLine: "Ejecutar todo el SQL desde líneas en blanco",
executeAllOnBlankLineDescription: "En el modo de sentencia actual, ejecuta todo el SQL cuando no hay una sentencia en el cursor. Desactivado de forma predeterminada.",
showExecutionTargetPicker: "Mostrar selector de objetivo",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index d5b4984e97..f391430dcc 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -6183,6 +6183,11 @@ export default withEnglishFallback({
executeModeDescription: "Scorciatoia Esegui SQL: {shortcut}. Stabilisce se eseguire tutto l'SQL o l'istruzione al cursore per impostazione predefinita.",
executeModeAll: "Esegui tutto l'SQL",
executeModeCurrent: "Esegui istruzione al cursore",
+ defaultTransactionMode: "Modalità di commit transazione predefinita",
+ defaultTransactionModeDescription:
+ 'La modalità di transazione utilizzata per impostazione predefinita quando si apre un nuovo editor SQL. Con "Transazione manuale", i nuovi editori iniziano in modalità di transazione manuale, richiedendo commit o rollback manuale; con "Commit automatico", ogni istruzione viene committata automaticamente. Si applica solo ai database che supportano le transazioni (PostgreSQL, MySQL, Oracle, JDBC).',
+ defaultTransactionModeAuto: "Commit automatico",
+ defaultTransactionModeManual: "Transazione manuale",
executeAllOnBlankLine: "Esegui tutto l'SQL dalle righe vuote",
executeAllOnBlankLineDescription: "In modalità istruzione corrente, esegue tutto l'SQL quando non è presente un'istruzione al cursore. Disattivato per impostazione predefinita.",
showExecutionTargetPicker: "Mostra selettore destinazione",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index d2b810dede..53a13100bd 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -6209,6 +6209,11 @@ export default withEnglishFallback({
executeModeDescription: "SQL実行ショートカット: {shortcut}。既定でSQL全体またはカーソル位置のステートメントのどちらを実行するかを設定します。",
executeModeAll: "すべてのSQLを実行",
executeModeCurrent: "カーソル位置の文を実行",
+ defaultTransactionMode: "既定のトランザクションコミットモード",
+ defaultTransactionModeDescription:
+ "新しい SQL エディタを開いたときに既定で使用するトランザクションモード。「手動トランザクション」を選択すると、新しいエディタは手動トランザクションモードで開始され、手動でコミットまたはロールバックします。「自動コミット」を選択すると、各ステートメントが自動的にコミットされます。トランザクションをサポートするデータベース(PostgreSQL、MySQL、Oracle、JDBC)のみに適用されます。",
+ defaultTransactionModeAuto: "自動コミット",
+ defaultTransactionModeManual: "手動トランザクション",
executeAllOnBlankLine: "空行ではすべてのSQLを実行",
executeAllOnBlankLineDescription: "現在の文を実行するモードで、カーソル位置に文がない場合はすべてのSQLを実行します。既定では無効です。",
showExecutionTargetPicker: "実行対象ピッカーを表示",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index 64ebd65ef3..1dab66391d 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -5896,6 +5896,11 @@ export default withEnglishFallback({
executeModeDescription: "SQL 실행 단축키: {shortcut}. 기본적으로 전체 SQL 또는 커서가 있는 구문 중 무엇을 실행할지 설정합니다.",
executeModeAll: "모든 SQL 실행",
executeModeCurrent: "커서 위치의 구문 실행",
+ defaultTransactionMode: "기본 트랜잭션 커밋 모드",
+ defaultTransactionModeDescription:
+ '새 SQL 편집기를 열 때 기본적으로 사용되는 트랜잭션 모드입니다. "수동 트랜잭션"을 선택하면 새 편집기가 수동 트랜잭션 모드로 시작되어 수동으로 커밋 또는 롤백해야 합니다. "자동 커밋"을 선택하면 각 문이 자동으로 커밋됩니다. 트랜잭션을 지원하는 데이터베이스(PostgreSQL, MySQL, Oracle, JDBC)에만 적용됩니다.',
+ defaultTransactionModeAuto: "자동 커밋",
+ defaultTransactionModeManual: "수동 트랜잭션",
executeAllOnBlankLine: "빈 줄에서 모든 SQL 실행",
executeAllOnBlankLineDescription: "현재 구문 실행 모드에서 커서 위치에 구문이 없으면 모든 SQL을 실행합니다. 기본적으로 비활성화됩니다.",
showExecutionTargetPicker: "실행 대상 선택기 표시",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index edb1bc46bf..4e221b1bed 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -6185,6 +6185,11 @@ export default withEnglishFallback({
executeModeDescription: "Atalho Executar SQL: {shortcut}. Controla se todo o SQL ou a instrução no cursor é executada por padrão.",
executeModeAll: "Executar todo o SQL",
executeModeCurrent: "Executar instrução no cursor",
+ defaultTransactionMode: "Modo de confirmação de transação padrão",
+ defaultTransactionModeDescription:
+ 'O modo de transação usado por padrão ao abrir um novo editor SQL. Com "Transação manual", novos editores iniciam no modo de transação manual, exigindo confirmação ou reversão manual; com "Confirmação automática", cada instrução é confirmada automaticamente. Aplica-se apenas a bancos de dados que oferecem suporte a transações (PostgreSQL, MySQL, Oracle, JDBC).',
+ defaultTransactionModeAuto: "Confirmação automática",
+ defaultTransactionModeManual: "Transação manual",
executeAllOnBlankLine: "Executar todo o SQL em linhas em branco",
executeAllOnBlankLineDescription: "No modo de instrução atual, executa todo o SQL quando não há instrução no cursor. Desativado por padrão.",
showExecutionTargetPicker: "Mostrar seletor de destino",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 7ded26586b..3d66fabc17 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -6498,6 +6498,10 @@ export default withEnglishFallback({
executeModeDescription: "执行 SQL 快捷键:{shortcut}。用于控制默认执行全部 SQL 或光标所在语句。",
executeModeAll: "执行全部 SQL",
executeModeCurrent: "执行光标所在语句",
+ defaultTransactionMode: "默认事务提交方式",
+ defaultTransactionModeDescription: "新建 SQL 编辑器时默认采用的事务提交方式。选择“手动事务”后,新打开的编辑器将以手动事务模式开始,需手动提交或回滚;选择“自动提交”则每条语句自动提交。仅支持事务的数据库(PostgreSQL、MySQL、Oracle、JDBC)生效。",
+ defaultTransactionModeAuto: "自动提交",
+ defaultTransactionModeManual: "手动事务",
executeAllOnBlankLine: "空白位置执行全部 SQL",
executeAllOnBlankLineDescription: "在“执行光标所在语句”模式下,光标没有对应语句时执行全部 SQL。默认关闭。",
showExecutionTargetPicker: "显示执行目标选择器",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index a7e27e79a8..440808d429 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -5501,6 +5501,10 @@ export default withEnglishFallback({
executeModeDescription: "執行 SQL 快捷鍵:{shortcut}。用於控制預設執行全部 SQL 或游標所在語句。",
executeModeAll: "執行全部 SQL",
executeModeCurrent: "執行指標所在語句",
+ defaultTransactionMode: "預設交易提交方式",
+ defaultTransactionModeDescription: "新增 SQL 編輯器時預設採用的交易提交方式。選擇「手動交易」後,新開啟的編輯器將以手動交易模式開始,需手動提交或回滾;選擇「自動提交」則每條語句自動提交。僅支援交易的資料庫(PostgreSQL、MySQL、Oracle、JDBC)生效。",
+ defaultTransactionModeAuto: "自動提交",
+ defaultTransactionModeManual: "手動交易",
executeAllOnBlankLine: "空白位置執行全部 SQL",
executeAllOnBlankLineDescription: "在「執行游標所在語句」模式下,游標沒有對應語句時執行全部 SQL。預設關閉。",
showExecutionTargetPicker: "顯示執行目標選擇器",
diff --git a/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts b/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts
index d070182b3d..0cb0df991a 100644
--- a/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts
+++ b/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts
@@ -129,7 +129,7 @@ describe("supportsTransaction", () => {
});
describe("defaultAutoCommitForDbType", () => {
- it("defaults query tabs to auto-commit", () => {
+ it("defaults query tabs to auto-commit unless the user configured manual", () => {
expect(defaultAutoCommitForDbType("oceanbase-oracle")).toBe(true);
expect(defaultAutoCommitForDbType("oracle")).toBe(true);
expect(defaultAutoCommitForDbType("mysql")).toBe(true);
@@ -137,6 +137,25 @@ describe("defaultAutoCommitForDbType", () => {
expect(defaultAutoCommitForDbType("dameng")).toBe(true);
expect(defaultAutoCommitForDbType(undefined)).toBe(true);
});
+
+ it("honors the configured default transaction mode", () => {
+ expect(defaultAutoCommitForDbType("mysql", "manual")).toBe(false);
+ expect(defaultAutoCommitForDbType("postgres", "manual")).toBe(false);
+ expect(defaultAutoCommitForDbType("oracle", "manual")).toBe(false);
+ expect(defaultAutoCommitForDbType("jdbc", "manual")).toBe(false);
+ expect(defaultAutoCommitForDbType("mysql", "auto")).toBe(true);
+ expect(defaultAutoCommitForDbType(undefined, "auto")).toBe(true);
+ });
+
+ it("keeps non-transaction databases auto-commit even when manual is configured", () => {
+ expect(defaultAutoCommitForDbType("redis", "manual")).toBe(true);
+ expect(defaultAutoCommitForDbType("mongodb", "manual")).toBe(true);
+ expect(defaultAutoCommitForDbType("sqlite", "manual")).toBe(true);
+ expect(defaultAutoCommitForDbType("dameng", "manual")).toBe(true);
+ expect(defaultAutoCommitForDbType("clickhouse", "manual")).toBe(true);
+ expect(defaultAutoCommitForDbType("oceanbase-oracle", "manual")).toBe(true);
+ expect(defaultAutoCommitForDbType(undefined, "manual")).toBe(true);
+ });
});
describe("supportsSqlInListPaste", () => {
diff --git a/apps/desktop/src/lib/database/databaseFeatureSupport.ts b/apps/desktop/src/lib/database/databaseFeatureSupport.ts
index 385a84a8d0..5218ce16b8 100644
--- a/apps/desktop/src/lib/database/databaseFeatureSupport.ts
+++ b/apps/desktop/src/lib/database/databaseFeatureSupport.ts
@@ -253,7 +253,18 @@ export function supportsTransaction(dbType?: string): boolean {
/**
* Default auto-commit mode when opening a query tab for the given database type.
* Query tabs default to auto-commit; users can explicitly switch to manual transactions.
+ *
+ * When the user has configured `manual` as their default, manual mode only applies to
+ * databases that actually support explicit transaction control — otherwise the tab is
+ * forced back to auto-commit so it does not start in an unsupported state. (For
+ * non-transaction databases, `queryStore` also forces auto-commit on first execution,
+ * but that would leave the new tab's initial state misleading; gating here avoids that.)
+ *
+ * The `defaultMode` selector is the user-configured default (Settings > Editor);
+ * it is supplied by the caller rather than read here so this module stays free of
+ * any Pinia store dependency.
*/
-export function defaultAutoCommitForDbType(_dbType?: string): boolean {
- return true;
+export function defaultAutoCommitForDbType(dbType: string | undefined, defaultMode: "auto" | "manual" = "auto"): boolean {
+ if (defaultMode !== "manual") return true;
+ return !supportsTransaction(dbType);
}
diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts
index d0a7a1570f..c91ba25ce9 100644
--- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts
+++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts
@@ -94,6 +94,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"continueOnErrorOnBatch",
"clickTableNavigationTarget",
"completionTriggerMode",
+ "defaultTransactionMode",
] as const satisfies readonly (keyof EditorSettings)[];
export type EditorSettingsDraftKey = (typeof EDITOR_SETTINGS_DRAFT_KEYS)[number];
diff --git a/apps/desktop/src/lib/settings/settingsSearch.ts b/apps/desktop/src/lib/settings/settingsSearch.ts
index 554ba8d072..19de12fa62 100644
--- a/apps/desktop/src/lib/settings/settingsSearch.ts
+++ b/apps/desktop/src/lib/settings/settingsSearch.ts
@@ -106,6 +106,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "editor-theme", category: "editor", titleKey: "settings.theme", targetId: "editor" },
{ id: "editor-font-size", category: "editor", titleKey: "settings.fontSize", targetId: "editor" },
{ id: "editor-execute-mode", category: "editor", titleKey: "settings.executeMode", targetId: "editor" },
+ { id: "editor-default-transaction-mode", category: "editor", titleKey: "settings.defaultTransactionMode", descriptionKey: "settings.defaultTransactionModeDescription", targetId: "editor" },
{ id: "editor-execute-all-on-blank-line", category: "editor", titleKey: "settings.executeAllOnBlankLine", descriptionKey: "settings.executeAllOnBlankLineDescription", targetId: "editor" },
{ id: "editor-execution-target", category: "editor", titleKey: "settings.showExecutionTargetPicker", descriptionKey: "settings.showExecutionTargetPickerDescription", targetId: "editor" },
{ id: "editor-run-buttons", category: "editor", titleKey: "settings.showStatementRunButtons", descriptionKey: "settings.showStatementRunButtonsDescription", targetId: "editor" },
diff --git a/apps/desktop/src/stores/__tests__/queryStore.defaultTransactionMode.spec.ts b/apps/desktop/src/stores/__tests__/queryStore.defaultTransactionMode.spec.ts
new file mode 100644
index 0000000000..a073ae1a8a
--- /dev/null
+++ b/apps/desktop/src/stores/__tests__/queryStore.defaultTransactionMode.spec.ts
@@ -0,0 +1,105 @@
+import { createPinia, setActivePinia } from "pinia";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ analyzeEditableQueryEditability: vi.fn(),
+ beginManualTransaction: vi.fn(),
+ closeClientConnectionSession: vi.fn(),
+ closeQuerySession: vi.fn(),
+ executeInManualTransaction: vi.fn(),
+ executeMulti: vi.fn(),
+ getConnectionConfig: vi.fn(),
+ prepareQueryPaginationExecutionPlan: vi.fn(),
+ saveOpenTabsState: vi.fn(),
+}));
+
+vi.mock("@/lib/backend/api", () => ({
+ analyzeEditableQueryEditability: mocks.analyzeEditableQueryEditability,
+ beginManualTransaction: mocks.beginManualTransaction,
+ closeClientConnectionSession: mocks.closeClientConnectionSession,
+ closeQuerySession: mocks.closeQuerySession,
+ executeInManualTransaction: mocks.executeInManualTransaction,
+ executeMulti: mocks.executeMulti,
+ prepareQueryPaginationExecutionPlan: mocks.prepareQueryPaginationExecutionPlan,
+ saveOpenTabsState: mocks.saveOpenTabsState,
+}));
+
+vi.mock("@/stores/connectionStore", () => ({
+ useConnectionStore: () => ({
+ ensureConnected: vi.fn().mockResolvedValue(undefined),
+ getConfig: mocks.getConnectionConfig,
+ recordConnectionLostError: vi.fn(),
+ }),
+}));
+
+// The in-memory editor settings are mutated per test so a fresh tab reflects the
+// configured default transaction mode. Missing key falls back to auto-commit.
+const editorSettings: Record = {
+ autoCalculateTotalRows: false,
+ continueOnErrorOnBatch: false,
+ pageSize: 100,
+ queryResultMaxRowsEnabled: false,
+ queryResultMaxRows: 1000,
+ openTabsRestoreMode: "all",
+ confirmUnsavedSqlClose: false,
+};
+
+vi.mock("@/stores/settingsStore", () => ({
+ useSettingsStore: () => ({ editorSettings }),
+}));
+
+describe("queryStore default transaction mode", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ setActivePinia(createPinia());
+ mocks.getConnectionConfig.mockReturnValue({
+ id: "oracle-1",
+ name: "Oracle",
+ db_type: "oracle",
+ database: "ORCL",
+ query_timeout_secs: 30,
+ });
+ mocks.prepareQueryPaginationExecutionPlan.mockImplementation(async (options) => ({
+ sqlToExecute: options.sql,
+ pageSql: undefined,
+ pageLimit: undefined,
+ pageOffset: undefined,
+ countSql: undefined,
+ useAgentResultSession: false,
+ }));
+ mocks.analyzeEditableQueryEditability.mockResolvedValue({ editable: false, reason: "not-select" });
+ mocks.saveOpenTabsState.mockResolvedValue(undefined);
+ delete editorSettings.defaultTransactionMode;
+ });
+
+ it("creates a query tab in manual transaction mode when configured", async () => {
+ editorSettings.defaultTransactionMode = "manual";
+
+ const { useQueryStore } = await import("@/stores/queryStore");
+ const store = useQueryStore();
+ const tabId = store.createTab("oracle-1", "ORCL", "Query", "query", "APP");
+
+ const tab = store.tabs.find((item) => item.id === tabId)!;
+ expect(tab.autoCommit).toBe(false);
+ });
+
+ it("creates a query tab in auto-commit mode by default", async () => {
+ const { useQueryStore } = await import("@/stores/queryStore");
+ const store = useQueryStore();
+ const tabId = store.createTab("oracle-1", "ORCL", "Query", "query", "APP");
+
+ const tab = store.tabs.find((item) => item.id === tabId)!;
+ expect(tab.autoCommit).toBe(true);
+ });
+
+ it("creates a query tab in auto-commit mode when explicitly set to auto", async () => {
+ editorSettings.defaultTransactionMode = "auto";
+
+ const { useQueryStore } = await import("@/stores/queryStore");
+ const store = useQueryStore();
+ const tabId = store.createTab("oracle-1", "ORCL", "Query", "query", "APP");
+
+ const tab = store.tabs.find((item) => item.id === tabId)!;
+ expect(tab.autoCommit).toBe(true);
+ });
+});
diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
index eed299e8d3..3fa584dd83 100644
--- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
+++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
@@ -184,6 +184,12 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ sqlSemanticDiagnosticsEnabled: false } as any).sqlSemanticDiagnosticsMode).toBe("disabled");
});
+ it("defaults the transaction commit mode to auto and preserves manual", () => {
+ expect(normalizeEditorSettings({}).defaultTransactionMode).toBe("auto");
+ expect(normalizeEditorSettings({ defaultTransactionMode: "manual" }).defaultTransactionMode).toBe("manual");
+ expect(normalizeEditorSettings({ defaultTransactionMode: "bogus" } as any).defaultTransactionMode).toBe("auto");
+ });
+
it("defaults update downloads to the official source", () => {
expect(normalizeEditorSettings({}).updateDownloadSource).toBe("official");
});
diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts
index e925692dac..f0338a7b1e 100644
--- a/apps/desktop/src/stores/queryStore.ts
+++ b/apps/desktop/src/stores/queryStore.ts
@@ -733,6 +733,9 @@ export const useQueryStore = defineStore("query", () => {
const t = getI18nT();
const settingsStore = useSettingsStore();
const tabs = ref([]);
+ // Default auto-commit for a fresh query tab follows the user's global
+ // "默认事务提交方式" setting (Settings > Editor), not a hard-coded constant.
+ const defaultAutoCommitForDbTypeWithSetting = (dbType?: string) => defaultAutoCommitForDbType(dbType, settingsStore.editorSettings.defaultTransactionMode);
// A stable Set of "connectionId\x00database" keys. Computed only from the
// minimal tab identity fields so that it does NOT invalidate when other
// properties change (isExecuting, result, sql, tableMeta...). Previously
@@ -1655,7 +1658,7 @@ export const useQueryStore = defineStore("query", () => {
for (const tab of restored.tabs) {
const connection = connectionStore.getConfig(tab.connectionId);
if (tab.mode === "query" && tab.autoCommit === undefined) {
- tab.autoCommit = defaultAutoCommitForDbType(connection?.db_type);
+ tab.autoCommit = defaultAutoCommitForDbTypeWithSetting(connection?.db_type);
} else if (tab.mode === "data" && connection) {
tab.schema = connectionObjectTreeNodeSchema(connection, tab.database, tab.schema);
}
@@ -1837,7 +1840,7 @@ export const useQueryStore = defineStore("query", () => {
isCancelling: false,
isExplaining: false,
mode,
- ...(mode === "query" ? { autoCommit: defaultAutoCommitForDbType(dbType) } : {}),
+ ...(mode === "query" ? { autoCommit: defaultAutoCommitForDbTypeWithSetting(dbType) } : {}),
};
if (mode === "query") tab.originalSql = initialSql ?? "";
const activeIndex = options.insertAfterActive ? tabs.value.findIndex((item) => item.id === activeTabId.value) : -1;
@@ -1931,7 +1934,7 @@ export const useQueryStore = defineStore("query", () => {
isCancelling: false,
isExplaining: false,
mode: "query",
- autoCommit: defaultAutoCommitForDbType(dbType),
+ autoCommit: defaultAutoCommitForDbTypeWithSetting(dbType),
};
tabs.value.push(tab);
activeTabId.value = id;
@@ -3286,7 +3289,7 @@ export const useQueryStore = defineStore("query", () => {
if (tab.txnSessionId) void rollbackTransaction(tab.id);
if (options?.resetAutoCommit) {
const dbType = useConnectionStore().getConfig(tab.connectionId)?.db_type;
- tab.autoCommit = defaultAutoCommitForDbType(dbType);
+ tab.autoCommit = defaultAutoCommitForDbTypeWithSetting(dbType);
}
clearOracleTxnPossiblyDirty(tab);
tab.txnAutoRolledBack = false;
@@ -3441,7 +3444,7 @@ export const useQueryStore = defineStore("query", () => {
isCancelling: false,
isExplaining: false,
mode: "query",
- autoCommit: defaultAutoCommitForDbType(dbType),
+ autoCommit: defaultAutoCommitForDbTypeWithSetting(dbType),
editorSelection: restoredPosition.selection,
editorViewport: restoredPosition.viewport,
};
@@ -6361,7 +6364,7 @@ export const useQueryStore = defineStore("query", () => {
isCancelling: false,
isExplaining: false,
mode: "query",
- autoCommit: defaultAutoCommitForDbType(dbType),
+ autoCommit: defaultAutoCommitForDbTypeWithSetting(dbType),
};
if (!restoreCachedResultPayload(tab, archive.snapshot)) return undefined;
const activeRun = tab.resultRuns?.find((run) => run.id === tab.activeResultRunId) ?? tab.resultRuns?.[0];
diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts
index d9763426df..8a7c90d135 100644
--- a/apps/desktop/src/stores/settingsStore.ts
+++ b/apps/desktop/src/stores/settingsStore.ts
@@ -61,6 +61,7 @@ export type UpdateDownloadSource = "official" | "cnb";
export type SqlSemanticDiagnosticsMode = "auto" | "enabled" | "disabled";
export type OpenTabsRestoreMode = "all" | "pinned" | "none";
export type AppCloseUnsavedTabsMode = "prompt" | "keep-drafts";
+export type DefaultTransactionMode = "auto" | "manual";
export const DEFAULT_SIDEBAR_TABLE_PAGE_SIZE = 1000;
export const DUCKDB_WORKER_MAX_PROCESSES_MIN = 1;
@@ -644,6 +645,7 @@ export interface EditorSettings {
continueOnErrorOnBatch: boolean;
clickTableNavigationTarget: ClickTableNavigationTarget;
completionTriggerMode: SqlCompletionTriggerMode;
+ defaultTransactionMode: DefaultTransactionMode;
}
export interface ToolbarItems {
@@ -857,6 +859,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
continueOnErrorOnBatch: false,
clickTableNavigationTarget: "data",
completionTriggerMode: "positional",
+ defaultTransactionMode: "auto",
};
export const STORAGE_KEY = "dbx-editor-settings";
@@ -970,6 +973,10 @@ function normalizeClickTableNavigationTarget(value: unknown): ClickTableNavigati
return value === "data" || value === "ddl" ? value : DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
}
+function normalizeDefaultTransactionMode(value: unknown): DefaultTransactionMode {
+ return value === "manual" ? value : DEFAULT_EDITOR_SETTINGS.defaultTransactionMode;
+}
+
function normalizeOpenTabsRestoreMode(value: unknown, legacyRestoreOpenTabsOnLaunch?: unknown): OpenTabsRestoreMode {
if (value === "all" || value === "pinned" || value === "none") return value;
if (typeof legacyRestoreOpenTabsOnLaunch === "boolean") return legacyRestoreOpenTabsOnLaunch ? "all" : "none";
@@ -1273,6 +1280,7 @@ export function normalizeEditorSettings(settings: Partial, exist
continueOnErrorOnBatch: settings.continueOnErrorOnBatch === true,
clickTableNavigationTarget: normalizeClickTableNavigationTarget(settings.clickTableNavigationTarget),
completionTriggerMode: normalizeCompletionTriggerMode(settings.completionTriggerMode),
+ defaultTransactionMode: normalizeDefaultTransactionMode(settings.defaultTransactionMode),
};
}
@@ -1852,6 +1860,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.continueOnErrorOnBatch !== undefined) editorSettings.value.continueOnErrorOnBatch = partial.continueOnErrorOnBatch === true;
if (partial.clickTableNavigationTarget !== undefined) editorSettings.value.clickTableNavigationTarget = normalizeClickTableNavigationTarget(partial.clickTableNavigationTarget);
if (partial.completionTriggerMode !== undefined) editorSettings.value.completionTriggerMode = normalizeCompletionTriggerMode(partial.completionTriggerMode);
+ if (partial.defaultTransactionMode !== undefined) editorSettings.value.defaultTransactionMode = normalizeDefaultTransactionMode(partial.defaultTransactionMode);
if (partial.flatteningMultiLineText !== undefined) editorSettings.value.flatteningMultiLineText = partial.flatteningMultiLineText;
}