diff --git a/src/components/modals/ExportProgressModal.tsx b/src/components/modals/ExportProgressModal.tsx
index c30ae46e..2713f6f3 100644
--- a/src/components/modals/ExportProgressModal.tsx
+++ b/src/components/modals/ExportProgressModal.tsx
@@ -1,4 +1,11 @@
-import { X, FileText, Loader2, CheckCircle, AlertCircle } from "lucide-react";
+import {
+ X,
+ FileText,
+ Loader2,
+ CheckCircle,
+ AlertCircle,
+ AlertTriangle,
+} from "lucide-react";
import { useTranslation } from "react-i18next";
import { Modal } from "../ui/Modal";
@@ -10,6 +17,7 @@ interface ExportProgressModalProps {
rowsProcessed: number;
fileName: string;
errorMessage?: string;
+ warningMessage?: string;
onCancel: () => void;
onClose: () => void;
}
@@ -22,6 +30,7 @@ export const ExportProgressModal = ({
onClose,
fileName,
errorMessage,
+ warningMessage,
}: ExportProgressModalProps) => {
const { t } = useTranslation();
@@ -62,12 +71,20 @@ export const ExportProgressModal = ({
{errorMessage}
) : (
-
- {t("editor.rowsProcessed")}:{" "}
-
- {rowsProcessed.toLocaleString()}
-
-
+ <>
+
+ {t("editor.rowsProcessed")}:{" "}
+
+ {rowsProcessed.toLocaleString()}
+
+
+ {warningMessage && (
+
+ )}
+ >
)}
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index 198aedfd..d9384d77 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -1182,6 +1182,7 @@
"executingQuery": "Abfrage wird ausgeführt...",
"exporting": "Wird exportiert...",
"rowsProcessed": "Verarbeitete Zeilen",
+ "exportLoadedRowsWarning": "Es wurden nur {{loaded}} geladene Zeilen von insgesamt {{total}} Zeilen exportiert. Blättere durch das Ergebnis oder führe eine eingeschränktere Abfrage aus, wenn du mehr Zeilen exportieren musst.",
"queryParameters": "Abfrageparameter",
"formatSql": "SQL formatieren",
"convertToConsole": "In Konsole umwandeln",
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index f0342854..4968944e 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1230,6 +1230,7 @@
"executingQuery": "Executing query...",
"exporting": "Exporting...",
"rowsProcessed": "Rows Processed",
+ "exportLoadedRowsWarning": "Only {{loaded}} loaded rows were exported out of {{total}} total rows. Page through the result or run a narrower query before exporting if you need more rows.",
"queryParameters": "Query Parameters",
"formatSql": "Format SQL",
"convertToConsole": "Convert to Console",
diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json
index 9d0639f7..c24d9555 100644
--- a/src/i18n/locales/es.json
+++ b/src/i18n/locales/es.json
@@ -1200,6 +1200,7 @@
"executingQuery": "Ejecutando consulta...",
"exporting": "Exportando...",
"rowsProcessed": "Filas Procesadas",
+ "exportLoadedRowsWarning": "Solo se exportaron {{loaded}} filas cargadas de {{total}} filas totales. Recorre las páginas del resultado o ejecuta una consulta más específica antes de exportar si necesitas más filas.",
"queryParameters": "Parámetros de Consulta",
"formatSql": "Formatear SQL",
"convertToConsole": "Convertir a Consola",
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index e62286f1..42957551 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -1220,6 +1220,7 @@
"executingQuery": "Exécution de la requête...",
"exporting": "Exportation...",
"rowsProcessed": "Lignes traitées",
+ "exportLoadedRowsWarning": "Seules {{loaded}} lignes chargées sur {{total}} lignes au total ont été exportées. Parcourez les pages du résultat ou exécutez une requête plus restrictive avant d'exporter si vous avez besoin de plus de lignes.",
"queryParameters": "Paramètres de requête",
"formatSql": "Formater SQL",
"convertToConsole": "Convertir en console",
diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json
index c45f47f8..b93eec2e 100644
--- a/src/i18n/locales/it.json
+++ b/src/i18n/locales/it.json
@@ -1204,6 +1204,7 @@
"executingQuery": "Esecuzione query...",
"exporting": "Esportazione in corso...",
"rowsProcessed": "Righe processate",
+ "exportLoadedRowsWarning": "Sono state esportate solo {{loaded}} righe caricate su {{total}} righe totali. Scorri le pagine del risultato o esegui una query più mirata prima di esportare se ti servono più righe.",
"queryParameters": "Parametri Query",
"formatSql": "Formatta SQL",
"convertToConsole": "Converti in Console",
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index 59b119f0..84e7f018 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -1193,6 +1193,7 @@
"executingQuery": "クエリを実行中...",
"exporting": "エクスポート中...",
"rowsProcessed": "処理行数",
+ "exportLoadedRowsWarning": "合計 {{total}} 行のうち、読み込み済みの {{loaded}} 行のみをエクスポートしました。さらに多くの行が必要な場合は、結果をページ送りするか、より絞り込んだクエリを実行してからエクスポートしてください。",
"queryParameters": "クエリパラメータ",
"formatSql": "SQLをフォーマット",
"convertToConsole": "コンソールに変換",
diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json
index 4f12917d..daaf5560 100644
--- a/src/i18n/locales/ko.json
+++ b/src/i18n/locales/ko.json
@@ -1157,6 +1157,7 @@
"executingQuery": "쿼리를 실행하는 중...",
"exporting": "내보내는 중...",
"rowsProcessed": "처리된 행",
+ "exportLoadedRowsWarning": "총 {{total}}개 행 중 로드된 {{loaded}}개 행만 내보냈습니다. 더 많은 행이 필요하면 결과 페이지를 넘기거나 더 좁은 쿼리를 실행한 뒤 내보내세요.",
"queryParameters": "쿼리 매개변수",
"formatSql": "SQL 포맷",
"convertToConsole": "콘솔로 변환",
diff --git a/src/i18n/locales/pt-BR.json b/src/i18n/locales/pt-BR.json
index 3d8b5c47..90f60922 100644
--- a/src/i18n/locales/pt-BR.json
+++ b/src/i18n/locales/pt-BR.json
@@ -1196,6 +1196,7 @@
"executingQuery": "Executando consulta...",
"exporting": "Exportando...",
"rowsProcessed": "Linhas Processadas",
+ "exportLoadedRowsWarning": "Apenas {{loaded}} linhas carregadas de {{total}} linhas totais foram exportadas. Navegue pelas páginas do resultado ou execute uma consulta mais restrita antes de exportar se precisar de mais linhas.",
"queryParameters": "Parâmetros da Consulta",
"formatSql": "Formatar SQL",
"convertToConsole": "Converter para Console",
diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json
index a17204ef..8e073d26 100644
--- a/src/i18n/locales/ru.json
+++ b/src/i18n/locales/ru.json
@@ -1178,6 +1178,7 @@
"executingQuery": "Выполнение запроса...",
"exporting": "Экспорт...",
"rowsProcessed": "Обработано строк",
+ "exportLoadedRowsWarning": "Экспортированы только {{loaded}} загруженных строк из {{total}} строк. Если нужны дополнительные строки, пролистайте результат или выполните более узкий запрос перед экспортом.",
"queryParameters": "Параметры запроса",
"formatSql": "Форматировать SQL",
"convertToConsole": "Преобразовать в консоль",
diff --git a/src/i18n/locales/tl.json b/src/i18n/locales/tl.json
index 43f76781..05ebbf24 100644
--- a/src/i18n/locales/tl.json
+++ b/src/i18n/locales/tl.json
@@ -1213,6 +1213,7 @@
"executingQuery": "Isinasakatuparan ang query...",
"exporting": "Nag-e-export...",
"rowsProcessed": "Mga Row na Naproseso",
+ "exportLoadedRowsWarning": "{{loaded}} lang na na-load na row ang na-export mula sa kabuuang {{total}} row. Mag-page sa resulta o magpatakbo ng mas makitid na query bago mag-export kung kailangan mo ng mas maraming row.",
"queryParameters": "Mga Parameter ng Query",
"formatSql": "I-format ang SQL",
"convertToConsole": "I-convert sa Console",
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index 7a5f42fe..92ce62b5 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -1148,6 +1148,7 @@
"executingQuery": "执行查询中...",
"exporting": "导出中...",
"rowsProcessed": "已处理行数",
+ "exportLoadedRowsWarning": "仅导出了总计 {{total}} 行中的 {{loaded}} 行已加载数据。如果需要更多行,请先翻页加载结果或运行范围更窄的查询后再导出。",
"queryParameters": "查询参数",
"formatSql": "格式化 SQL",
"convertToConsole": "转换为控制台",
diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx
index c3e87c52..32cdaefd 100644
--- a/src/pages/Editor.tsx
+++ b/src/pages/Editor.tsx
@@ -4,6 +4,10 @@ import { useTranslation } from "react-i18next";
import { reconstructTableQuery } from "../utils/editor";
import { shouldShowStatementSuccess } from "../utils/resultPresentation";
import { formatRowsForCopy, copyTextToClipboard } from "../utils/clipboard";
+import {
+ formatResultForExport,
+ getLoadedRowsExportLimit,
+} from "../utils/resultExport";
import { serializePkKey, buildPkMap } from "../utils/dataGrid";
import {
buildKeylessUpdatePlan,
@@ -70,6 +74,7 @@ import {
} from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import { listen, emit } from "@tauri-apps/api/event";
+import { writeTextFile } from "@tauri-apps/plugin-fs";
import { TableToolbar } from "../components/ui/TableToolbar";
import { DataGrid } from "../components/ui/DataGrid";
import { MultiResultPanel } from "../components/ui/MultiResultPanel";
@@ -98,6 +103,7 @@ import {
removeOtherEntries,
removeEntriesToRight,
removeEntriesToLeft,
+ findActiveEntry,
} from "../utils/multiResult";
import {
extractQueryParams,
@@ -264,6 +270,7 @@ export const Editor = ({ commandScopeId }: EditorProps) => {
rowsProcessed: number;
fileName: string;
errorMessage?: string;
+ warningMessage?: string;
}>({
isOpen: false,
status: "exporting",
@@ -468,6 +475,16 @@ export const Editor = ({ commandScopeId }: EditorProps) => {
const isMultiDb = usesMultiDatabaseLayout(activeCapabilities, selectedDatabases);
const isEditorOpen =
!isTableTab && (activeTab?.isEditorOpen ?? activeTab?.type !== "table");
+ const activeResultEntry = useMemo(
+ () =>
+ activeTab?.results
+ ? findActiveEntry(activeTab.results, activeTab.activeResultId)
+ : undefined,
+ [activeTab?.activeResultId, activeTab?.results],
+ );
+ const activeExportResult = activeResultEntry?.result ?? activeTab?.result;
+ const canExportActiveResult =
+ !!activeExportResult && activeExportResult.rows.length > 0;
const handleCloseTab = useCallback(
(tabId: string) => {
@@ -1346,7 +1363,15 @@ export const Editor = ({ commandScopeId }: EditorProps) => {
applied.add(idx);
applyStatement(idx, item);
});
- updateTab(targetTabId, { isLoading: false });
+ const firstResultEntry = batchResults.findIndex(
+ (item) => (item.result?.rows.length ?? 0) > 0,
+ );
+ updateTab(targetTabId, {
+ isLoading: false,
+ ...(firstResultEntry >= 0
+ ? { activeResultId: entries[firstResultEntry].id }
+ : {}),
+ });
},
[
activeConnectionId,
@@ -3283,6 +3308,59 @@ export const Editor = ({ commandScopeId }: EditorProps) => {
const handleExportCommon = async (format: "csv" | "json" | "markdown") => {
if (!activeTab || !activeConnectionId) return;
+ const extension = format === "markdown" ? "md" : format;
+ const multiResult = activeResultEntry?.result;
+ if (multiResult?.rows.length) {
+ try {
+ const loadedRowsLimit = getLoadedRowsExportLimit(multiResult);
+ const warningMessage = loadedRowsLimit
+ ? t("editor.exportLoadedRowsWarning", {
+ loaded: loadedRowsLimit.loadedRows.toLocaleString(),
+ total: loadedRowsLimit.totalRows.toLocaleString(),
+ })
+ : undefined;
+
+ const filePath = await save({
+ filters: [
+ {
+ name: format === "markdown" ? "Markdown" : format.toUpperCase(),
+ extensions: [extension],
+ },
+ ],
+ defaultPath: `result_${Date.now()}.${extension}`,
+ });
+
+ if (!filePath) return;
+
+ setExportState({
+ isOpen: true,
+ status: "exporting",
+ rowsProcessed: multiResult.rows.length,
+ fileName: filePath.split(/[/\\]/).pop() || filePath,
+ errorMessage: undefined,
+ warningMessage,
+ });
+ setExportMenuOpen(false);
+
+ await writeTextFile(
+ filePath,
+ formatResultForExport(multiResult, format, csvDelimiter),
+ );
+
+ setExportState((prev) => ({
+ ...prev,
+ status: "completed",
+ }));
+ } catch (e) {
+ setExportState((prev) => ({
+ ...prev,
+ status: "error",
+ errorMessage: String(e),
+ }));
+ }
+ return;
+ }
+
const effectiveSchema =
activeCapabilities?.schemas === true ? activeTab.schema : undefined;
const tabForQuery = { ...activeTab, schema: effectiveSchema };
@@ -3294,7 +3372,6 @@ export const Editor = ({ commandScopeId }: EditorProps) => {
if (!query || !query.trim()) return;
try {
- const extension = format === "markdown" ? "md" : format;
const filePath = await save({
filters: [
{
@@ -3312,6 +3389,8 @@ export const Editor = ({ commandScopeId }: EditorProps) => {
status: "exporting",
rowsProcessed: 0,
fileName: filePath.split(/[/\\]/).pop() || filePath, // Show only filename
+ errorMessage: undefined,
+ warningMessage: undefined,
});
setExportMenuOpen(false);
@@ -3823,7 +3902,7 @@ export const Editor = ({ commandScopeId }: EditorProps) => {