fix(jdbc): send rowOffset for generic JDBC table pagination

This commit is contained in:
zipg authored and GitHub committed 2026-09-21 11:38:18 +08:00
1 parent 79d6ab64e9
commit 90ad995803
7 files changed
+119 -3

No files matched your search

@@ -179,6 +179,75 @@ describe("queryStore table data refresh", () => {
expect(mocks.executeMulti).toHaveBeenCalledWith("cache-1", "USER", 'SELECT * FROM "SS"."SS_User" ORDER BY "ID" ASC', undefined, expect.any(String), expect.objectContaining({ maxRows: 100, fetchSize: 100, rowOffset: 100 }));
});
it("uses JDBC ResultSet offset pagination for dialects that cannot paginate in SQL", async () => {
mocks.getConnectionConfig.mockReturnValue({
id: "sybase-1",
name: "Sybase ASE 16",
db_type: "jdbc",
database: "app",
connection_string: "jdbc:sybase:Tds:db.example.com:5000/app",
query_timeout_secs: 30,
});
mocks.buildTableSelectSql.mockResolvedValue("SELECT * FROM dbo.users ORDER BY id ASC");
const { useQueryStore } = await import("@/stores/queryStore");
const store = useQueryStore();
const tabId = store.createTab("sybase-1", "app", "users", "data", "dbo");
store.setTableMeta(tabId, {
schema: "dbo",
tableName: "users",
tableType: "TABLE",
columns: [{ name: "id", data_type: "int", is_nullable: false, column_default: null, is_primary_key: true, extra: null }],
primaryKeys: ["id"],
});
const tab = store.tabs.find((candidate) => candidate.id === tabId)!;
tab.orderByInput = "id ASC";
tab.resultPageLimit = 100;
tab.resultPageOffset = 100;
await store.refreshDataTab(tabId);
// Generic JDBC emits a bare SELECT, so the agent has to skip the offset;
// otherwise page 2 re-renders page 1 (#9015).
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(
expect.objectContaining({
databaseType: "jdbc",
limit: 100,
offset: 100,
useDriverRowOffset: true,
}),
);
expect(mocks.executeMulti).toHaveBeenCalledWith("sybase-1", "app", "SELECT * FROM dbo.users ORDER BY id ASC", undefined, expect.any(String), expect.objectContaining({ maxRows: 100, fetchSize: 100, rowOffset: 100 }));
});
it("keeps YashanDB JDBC tabs on server-side pagination", async () => {
mocks.getConnectionConfig.mockReturnValue({
id: "yasdb-1",
name: "YashanDB",
db_type: "jdbc",
database: "app",
connection_string: "jdbc:yasdb://localhost:1688/app",
query_timeout_secs: 30,
});
mocks.buildTableSelectSql.mockResolvedValue("SELECT * FROM app.users");
const { useQueryStore } = await import("@/stores/queryStore");
const store = useQueryStore();
const tabId = store.createTab("yasdb-1", "app", "users", "data");
store.setTableMeta(tabId, {
tableName: "users",
tableType: "TABLE",
columns: [{ name: "id", data_type: "int", is_nullable: false, column_default: null, is_primary_key: true, extra: null }],
primaryKeys: ["id"],
});
const tab = store.tabs.find((candidate) => candidate.id === tabId)!;
tab.resultPageLimit = 100;
tab.resultPageOffset = 100;
await store.refreshDataTab(tabId);
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.not.objectContaining({ useDriverRowOffset: true }));
expect(mocks.executeMulti).toHaveBeenCalledWith("yasdb-1", "app", "SELECT * FROM app.users", undefined, expect.any(String), expect.not.objectContaining({ rowOffset: expect.anything() }));
});
it("executes Doris external catalog data tabs against the catalog database, not the connection default database", async () => {
mocks.getConnectionConfig.mockReturnValue({
id: "doris-1",
+3 -3
View File
@@ -55,7 +55,7 @@ import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
import { getCachedTableMetadata, loadTableColumns, loadTableIndexes, loadTableMetadata, tableMetadataToDataTabMeta, updateCachedTableMetadataType, type TableMetadataRequest } from "@/lib/metadata/tableMetadataCache";
import { MetadataTaskLimiter } from "@/lib/metadata/metadataTaskLimiter";
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
import { connectionObjectTreeNodeSchema, connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
import { connectionObjectTreeNodeSchema, connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint, jdbcConnectionUsesDriverRowOffset, 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";
@@ -4546,7 +4546,7 @@ export const useQueryStore = defineStore("query", () => {
const orderBy = tab.orderByInput?.trim() || sortOrder;
const limit = tab.resultPageLimit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);
const offset = tab.resultPageOffset ?? 0;
const useDriverRowOffset = conn?.db_type === "jdbc" && effectiveDbType === "iris";
const useDriverRowOffset = jdbcConnectionUsesDriverRowOffset(conn, effectiveDbType);
const sql = await buildTableSelectSql({
databaseType: effectiveDbType,
@@ -7263,7 +7263,7 @@ export const useQueryStore = defineStore("query", () => {
// connection-local state and avoid MySQL pool resets on every refresh.
const dataTabMeta = tab.mode === "data" ? tableMetaForDataTab(tab) : undefined;
const useTableDataPreview = canUseTableDataLargeValuePreview(effectiveDbType, dataTabMeta?.columns ?? [], dataTabMeta?.primaryKeys ?? []);
const useJdbcDriverRowOffset = tab.mode === "data" && conn?.db_type === "jdbc" && effectiveDbType === "iris";
const useJdbcDriverRowOffset = tab.mode === "data" && jdbcConnectionUsesDriverRowOffset(conn, effectiveDbType);
const executionOptions = {
...(typeof pageLimit === "number"
? useAgentResultSession