fix(sqlserver): preserve local temp table count sessions

This commit is contained in:
zipg authored and GitHub committed 2026-09-01 16:20:56 +08:00
1 parent a7922b9cbf
commit 440c41f5b9
6 files changed
+158 -8

No files matched your search

@@ -1807,6 +1807,7 @@ defineExpose({
:page-offset="activeTab.resultPageOffset"
:page-limit="activeTab.resultPageLimit"
:count-sql="activeTab.resultCountSql"
:count-total-rows="activeTab.resultCountSql ? () => queryStore.countTabResultRows(activeTab.id) : undefined"
:total-row-count="activeTab.resultTotalRowCount"
:total-row-count-is-exact="activeTab.resultTotalRowCount !== undefined || activeTab.result.total_is_exact !== false"
:total-row-count-loading="activeTab.resultTotalRowCountLoading"
@@ -30,6 +30,7 @@ describe("ContentArea query result grid", () => {
expect(queryGridBlock).toContain("<DataGrid");
expect(queryGridBlock).toContain(':connection-id="activeResultConnectionId"');
expect(queryGridBlock).toContain(':count-sql="activeTab.resultCountSql"');
expect(queryGridBlock).toContain(':count-total-rows="activeTab.resultCountSql ? () => queryStore.countTabResultRows(activeTab.id) : undefined"');
expect(queryGridBlock).toContain(':total-row-count="activeTab.resultTotalRowCount"');
expect(queryGridBlock).toContain(':total-row-count-loading="activeTab.resultTotalRowCountLoading"');
});
@@ -0,0 +1,12 @@
import { tokenizeSqlSemantic, unquoteSqlSemanticIdentifier } from "@/lib/sql/semantic/tokens";
import type { DatabaseType } from "@/types/database";
export function sqlServerCountUsesLocalTempTable(databaseType: DatabaseType | undefined, sql: string): boolean {
if (databaseType !== "sqlserver" || !sql) return false;
return tokenizeSqlSemantic(sql, "sqlserver").some((token) => {
if (token.kind !== "word" && token.kind !== "quoted_identifier") return false;
const identifier = token.kind === "quoted_identifier" ? unquoteSqlSemanticIdentifier(token) : token.text;
return identifier.startsWith("#") && !identifier.startsWith("##");
});
}
+60 -8
View File
@@ -48,6 +48,7 @@ import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/table
import { connectionObjectTreeNodeSchema, connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
import { frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
import { queryResultNameFromPreamble, queryResultSourceLabel } from "@/lib/sql/queryResultSource";
import { sqlServerCountUsesLocalTempTable } from "@/lib/query/queryResultCountSession";
import { beginDataGridNativeSelectionBlock, finishDataGridNativeSelectionBlock } from "@/lib/dataGrid/dataGridNativeSelection";
import { appendLargeValueCells, canUseTableDataLargeValuePreview, remapLargeValueCells, tableDataLargeValuePreviewOptions, TABLE_DATA_RESULT_MAX_BYTES } from "@/lib/dataGrid/dataGridLargeValues";
import { simpleDataGridOrderByReferencesMissingColumn, sortDataGridRowIndexes, type DataGridSortDirection } from "@/lib/dataGrid/dataGridSort";
@@ -4497,6 +4498,7 @@ export const useQueryStore = defineStore("query", () => {
traceId: string;
elapsed: () => string;
timeoutSecs: number;
txnSessionId?: string;
}) {
const resultRowCount = options.result.rows.length;
if (resultRowCount <= 0) {
@@ -4519,9 +4521,9 @@ export const useQueryStore = defineStore("query", () => {
return;
}
const clientSessionId = tabClientSessionId({ id: options.tabId }, "count");
const countExecutionId = `${options.executionId}:count`;
void (async () => {
let isolatedClientSessionId: string | undefined;
try {
const countTarget = options.countSql ? { sql: options.countSql, schema: options.schema } : await options.countSqlTarget?.();
if (!countTarget?.sql) {
@@ -4529,12 +4531,23 @@ export const useQueryStore = defineStore("query", () => {
return;
}
queryExecutionLog("info", "count:start", { traceId: options.traceId, elapsed: options.elapsed() });
const countResult = await api.executeQuery(options.connectionId, options.database, countTarget.sql, countTarget.schema, countExecutionId, {
clientSessionId,
catalog: options.catalog,
timeoutSecs: options.timeoutSecs,
});
const total = Number(countResult.rows?.[0]?.[0] ?? 0);
let countResult: QueryResult | undefined;
if (options.txnSessionId) {
const txnResults = await api.executeInManualTransaction(options.txnSessionId, countTarget.sql, options.database, countTarget.schema, 1);
countResult = txnResults.find((result) => result.columns.length > 0) ?? txnResults[txnResults.length - 1];
} else {
const connection = useConnectionStore().getConfig(options.connectionId);
const databaseType = effectiveDatabaseTypeForConnection(connection);
const usesQuerySession = sqlServerCountUsesLocalTempTable(databaseType, countTarget.sql);
const clientSessionId = usesQuerySession ? tabClientSessionId({ id: options.tabId }) : tabClientSessionId({ id: options.tabId }, "count");
if (!usesQuerySession) isolatedClientSessionId = clientSessionId;
countResult = await api.executeQuery(options.connectionId, options.database, countTarget.sql, countTarget.schema, countExecutionId, {
clientSessionId,
catalog: options.catalog,
timeoutSecs: options.timeoutSecs,
});
}
const total = Number(countResult?.rows?.[0]?.[0] ?? 0);
if (!Number.isFinite(total) || total < 0) {
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, undefined);
return;
@@ -4555,11 +4568,48 @@ export const useQueryStore = defineStore("query", () => {
error,
});
} finally {
void closeClientSessionId(options.connectionId, options.database, clientSessionId, options.catalog, { tabId: options.tabId });
if (isolatedClientSessionId) {
void closeClientSessionId(options.connectionId, options.database, isolatedClientSessionId, options.catalog, { tabId: options.tabId });
}
}
})();
}
async function countTabResultRows(id: string): Promise<number | undefined> {
const tab = tabs.value.find((candidate) => candidate.id === id);
if (!tab?.resultCountSql) return undefined;
const target = queryResultExecutionLocation(tab);
const connection = useConnectionStore().getConfig(target.connectionId);
const databaseType = effectiveDatabaseTypeForConnection(connection);
let countResult: QueryResult | undefined;
let isolatedClientSessionId: string | undefined;
try {
if (tab.autoCommit === false && tab.txnSessionId) {
const txnResults = await api.executeInManualTransaction(tab.txnSessionId, tab.resultCountSql, target.database, target.schema, 1);
countResult = txnResults.find((result) => result.columns.length > 0) ?? txnResults[txnResults.length - 1];
} else {
const usesQuerySession = sqlServerCountUsesLocalTempTable(databaseType, tab.resultCountSql);
const clientSessionId = usesQuerySession ? tabClientSessionId(tab) : tabClientSessionId(tab, "count");
if (!usesQuerySession) isolatedClientSessionId = clientSessionId;
countResult = await api.executeQuery(target.connectionId, target.database, tab.resultCountSql, target.schema, undefined, {
clientSessionId,
catalog: target.catalog,
maxRows: 1,
timeoutSecs: queryTimeoutSecsForConnection(connection, useSettingsStore().editorSettings.globalQueryTimeoutSecs),
});
}
const total = Number(countResult?.rows?.[0]?.[0]);
return Number.isFinite(total) && total >= 0 ? total : undefined;
} finally {
if (isolatedClientSessionId) {
void closeClientSessionId(target.connectionId, target.database, isolatedClientSessionId, target.catalog, { tabId: tab.id });
}
}
}
async function executeTabSql(
id: string,
sql: string,
@@ -5647,6 +5697,7 @@ export const useQueryStore = defineStore("query", () => {
traceId,
elapsed,
timeoutSecs: queryTimeoutSecs,
txnSessionId: current.autoCommit === false ? current.txnSessionId : undefined,
});
}
queryExecutionLog("info", "result:assigned", {
@@ -6969,6 +7020,7 @@ export const useQueryStore = defineStore("query", () => {
exportResultArchive,
importResultArchive,
fetchTabResultForExport,
countTabResultRows,
buildQueryResultExportRequest,
exportQuerySqlDirect,
getResourceLifecycleDiagnostics: () => resourceLifecycleDiagnostics(tabs.value),