fix(grid): reuse local result rows for csv/xlsx export instead of re-running query
* fix(grid): reuse local result rows for csv/xlsx export instead of re-running query
Exporting the full result set ("导出当前结果集全部数据 CSV") previously
re-executed the source SQL on the database via the backend streaming path
(exportQueryResultViaBackend -> start_query_result_export), and the frontend
fallback (fullExportResult/fetchTabResultForExport) did the same. For a slow
query this means running it a second time just to export rows already on
screen, which can hang or crash (e.g. a 2-minute paimon query returning 30
rows).
When the in-memory result already holds the complete set (no server-side
pagination, not truncated, no further pages), skip both re-executing paths and
write the displayed rows directly. Large/truncated/paginated results keep the
original streaming behavior unchanged.
* fix(grid): export raw in-memory result for complete local result set
---------
Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
1 parent
15114f58dd
commit
5db0fc4fb1
2 files changed
+39
-3
No files matched your search
@@ -3278,6 +3278,10 @@ const hasKnownTotalRowCount = computed(() => typeof serverKnownTotalRowCount.val
|
|||||||
// rowCount IS the total. Without this hint, the "page is full → assume more"
|
// rowCount IS the total. Without this hint, the "page is full → assume more"
|
||||||
// fallback in canGoNextDataGridPage lets the user keep clicking next forever.
|
// fallback in canGoNextDataGridPage lets the user keep clicking next forever.
|
||||||
const allRowsLoaded = computed(() => isResultsContext.value && props.pageLimit === undefined);
|
const allRowsLoaded = computed(() => isResultsContext.value && props.pageLimit === undefined);
|
||||||
|
// True when the in-memory result already holds the complete result set (results
|
||||||
|
// context, no server-side pagination, not truncated, no further pages). Used to
|
||||||
|
// skip re-executing the query on export and instead write the local rows.
|
||||||
|
const hasCompleteLocalResult = computed(() => !!props.result && allRowsLoaded.value && props.result.truncated !== true && props.result.has_more !== true);
|
||||||
const canGoNextPage = computed(() => {
|
const canGoNextPage = computed(() => {
|
||||||
return canGoNextDataGridPage({
|
return canGoNextDataGridPage({
|
||||||
hasMore: props.result.has_more,
|
hasMore: props.result.has_more,
|
||||||
@@ -5827,6 +5831,8 @@ const {
|
|||||||
hasRowSelection,
|
hasRowSelection,
|
||||||
fullExportResult: props.fullExportResult,
|
fullExportResult: props.fullExportResult,
|
||||||
queryResultExportRequest: props.queryResultExportRequest,
|
queryResultExportRequest: props.queryResultExportRequest,
|
||||||
|
hasCompleteLocalResult,
|
||||||
|
completeLocalResult: computed(() => (hasCompleteLocalResult.value ? props.result : undefined)),
|
||||||
allExportResults: computed(() => props.allExportResults),
|
allExportResults: computed(() => props.allExportResults),
|
||||||
currentResultLabel: computed(() => props.result.sourceLabel),
|
currentResultLabel: computed(() => props.result.sourceLabel),
|
||||||
exportFileBaseName: computed(() => props.exportFileBaseName),
|
exportFileBaseName: computed(() => props.exportFileBaseName),
|
||||||
|
|||||||
@@ -52,6 +52,23 @@ export interface UseDataGridExportOptions {
|
|||||||
hasRowSelection: ComputedRef<boolean>;
|
hasRowSelection: ComputedRef<boolean>;
|
||||||
fullExportResult?: (onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void) => Promise<QueryResult | undefined>;
|
fullExportResult?: (onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void) => Promise<QueryResult | undefined>;
|
||||||
queryResultExportRequest?: (options: { exportId: string; filePath: string; format: "csv" | "xlsx" }) => Promise<QueryResultExportRequest | undefined>;
|
queryResultExportRequest?: (options: { exportId: string; filePath: string; format: "csv" | "xlsx" }) => Promise<QueryResultExportRequest | undefined>;
|
||||||
|
/**
|
||||||
|
* True when the in-memory result already holds the complete result set —
|
||||||
|
* i.e. the query ran without server-side pagination, was not truncated, and
|
||||||
|
* has no further pages. When true, full-result exports skip the re-executing
|
||||||
|
* backend/frontend streaming paths and write the local rows directly, so a
|
||||||
|
* slow query is never re-run just to export rows that are already on screen.
|
||||||
|
*/
|
||||||
|
hasCompleteLocalResult?: ComputedRef<boolean>;
|
||||||
|
/**
|
||||||
|
* The raw in-memory QueryResult to use for "export all" when
|
||||||
|
* hasCompleteLocalResult is true. Exports the original query result (all
|
||||||
|
* rows, all columns, committed values) so the output matches the original
|
||||||
|
* re-run-SQL semantics — displayItems only covers visible columns and
|
||||||
|
* reflects client-side filters/search and unsaved edits, which would
|
||||||
|
* silently change what "export all data" produces.
|
||||||
|
*/
|
||||||
|
completeLocalResult?: ComputedRef<QueryResult | undefined>;
|
||||||
allExportResults?: ComputedRef<Array<{ sheetName: string; result: QueryResult }> | undefined>;
|
allExportResults?: ComputedRef<Array<{ sheetName: string; result: QueryResult }> | undefined>;
|
||||||
currentResultLabel?: ComputedRef<string | undefined>;
|
currentResultLabel?: ComputedRef<string | undefined>;
|
||||||
exportFileBaseName?: ComputedRef<string | undefined>;
|
exportFileBaseName?: ComputedRef<string | undefined>;
|
||||||
@@ -122,6 +139,8 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||||||
hasRowSelection,
|
hasRowSelection,
|
||||||
fullExportResult,
|
fullExportResult,
|
||||||
queryResultExportRequest,
|
queryResultExportRequest,
|
||||||
|
hasCompleteLocalResult,
|
||||||
|
completeLocalResult,
|
||||||
allExportResults,
|
allExportResults,
|
||||||
currentResultLabel,
|
currentResultLabel,
|
||||||
exportFileBaseName,
|
exportFileBaseName,
|
||||||
@@ -146,10 +165,18 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resultToExport(rowIds?: number[], onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void, useFullExport = true): Promise<{ columns: string[]; rows: CellValue[][] }> {
|
async function resultToExport(rowIds?: number[], onProgress?: (info: { rowsExported: number; totalRows: number | null }) => void, useFullExport = true): Promise<{ columns: string[]; rows: CellValue[][] }> {
|
||||||
if (useFullExport && rowIds === undefined && fullExportResult) {
|
if (useFullExport && rowIds === undefined && fullExportResult && !hasCompleteLocalResult?.value) {
|
||||||
const result = await fullExportResult(onProgress);
|
const result = await fullExportResult(onProgress);
|
||||||
if (result) return { columns: result.columns, rows: result.rows };
|
if (result) return { columns: result.columns, rows: result.rows };
|
||||||
}
|
}
|
||||||
|
// The full result is already in memory — export the raw QueryResult (all
|
||||||
|
// rows, all columns, committed values) so "export all data" matches the
|
||||||
|
// original re-run-SQL semantics. displayItems only covers visible columns
|
||||||
|
// and reflects client-side filters/search and unsaved edits, which would
|
||||||
|
// silently change what the export contains.
|
||||||
|
if (useFullExport && rowIds === undefined && hasCompleteLocalResult?.value && completeLocalResult?.value) {
|
||||||
|
return { columns: completeLocalResult.value.columns, rows: completeLocalResult.value.rows };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
columns: columns.value,
|
columns: columns.value,
|
||||||
rows: rowsToExport(rowIds).map((item) => item.data),
|
rows: rowsToExport(rowIds).map((item) => item.data),
|
||||||
@@ -512,7 +539,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||||||
if (await exportQueryResultViaBackend("csv", rowIds)) return;
|
if (await exportQueryResultViaBackend("csv", rowIds)) return;
|
||||||
if (await exportFullTableDataViaBackend("csv", rowIds)) return;
|
if (await exportFullTableDataViaBackend("csv", rowIds)) return;
|
||||||
|
|
||||||
const needsFullExport = rowIds === undefined && !!fullExportResult;
|
const needsFullExport = rowIds === undefined && !!fullExportResult && !hasCompleteLocalResult?.value;
|
||||||
if (needsFullExport && exportProgressDialog && exportProgressState) {
|
if (needsFullExport && exportProgressDialog && exportProgressState) {
|
||||||
exportProgressState.value = {
|
exportProgressState.value = {
|
||||||
title: t("exportProgress.title"),
|
title: t("exportProgress.title"),
|
||||||
@@ -716,7 +743,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||||||
if (!path) return;
|
if (!path) return;
|
||||||
outputPath = path as string;
|
outputPath = path as string;
|
||||||
}
|
}
|
||||||
const needsFullExport = rowIds === undefined && !!fullExportResult;
|
const needsFullExport = rowIds === undefined && !!fullExportResult && !hasCompleteLocalResult?.value;
|
||||||
if (needsFullExport && exportProgressDialog && exportProgressState) {
|
if (needsFullExport && exportProgressDialog && exportProgressState) {
|
||||||
exportProgressState.value = {
|
exportProgressState.value = {
|
||||||
title: t("exportProgress.title"),
|
title: t("exportProgress.title"),
|
||||||
@@ -908,6 +935,9 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||||||
if (rowIds !== undefined || context.value !== "results" || !queryResultExportRequest) {
|
if (rowIds !== undefined || context.value !== "results" || !queryResultExportRequest) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// The full result is already in memory — don't re-execute the query on the
|
||||||
|
// backend just to stream the same rows back to a file.
|
||||||
|
if (hasCompleteLocalResult?.value) return false;
|
||||||
|
|
||||||
const extension = format;
|
const extension = format;
|
||||||
const filterName = format === "csv" ? "CSV" : "Excel";
|
const filterName = format === "csv" ? "CSV" : "Excel";
|
||||||
|
|||||||
Reference in new issue
Block a user