14 files changed
+160
-1
No files matched your search
@@ -900,6 +900,7 @@ export async function executeQuery(
|
|||||||
catalog?: string;
|
catalog?: string;
|
||||||
fetchSize?: number;
|
fetchSize?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
rowOffset?: number;
|
||||||
resultSessionId?: string;
|
resultSessionId?: string;
|
||||||
clientSessionId?: string;
|
clientSessionId?: string;
|
||||||
timeoutSecs?: number;
|
timeoutSecs?: number;
|
||||||
@@ -927,6 +928,7 @@ export async function executeMulti(
|
|||||||
catalog?: string;
|
catalog?: string;
|
||||||
fetchSize?: number;
|
fetchSize?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
rowOffset?: number;
|
||||||
maxResultBytes?: number;
|
maxResultBytes?: number;
|
||||||
resultKeyColumns?: string[];
|
resultKeyColumns?: string[];
|
||||||
tableDataPreview?: boolean;
|
tableDataPreview?: boolean;
|
||||||
@@ -970,6 +972,7 @@ export async function executeMultiWithProgress(
|
|||||||
catalog?: string;
|
catalog?: string;
|
||||||
fetchSize?: number;
|
fetchSize?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
rowOffset?: number;
|
||||||
maxResultBytes?: number;
|
maxResultBytes?: number;
|
||||||
resultKeyColumns?: string[];
|
resultKeyColumns?: string[];
|
||||||
tableDataPreview?: boolean;
|
tableDataPreview?: boolean;
|
||||||
|
|||||||
@@ -1115,6 +1115,7 @@ export async function executeQuery(
|
|||||||
catalog?: string;
|
catalog?: string;
|
||||||
fetchSize?: number;
|
fetchSize?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
rowOffset?: number;
|
||||||
resultSessionId?: string;
|
resultSessionId?: string;
|
||||||
clientSessionId?: string;
|
clientSessionId?: string;
|
||||||
timeoutSecs?: number;
|
timeoutSecs?: number;
|
||||||
@@ -1146,6 +1147,7 @@ export async function executeMulti(
|
|||||||
catalog?: string;
|
catalog?: string;
|
||||||
fetchSize?: number;
|
fetchSize?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
rowOffset?: number;
|
||||||
maxResultBytes?: number;
|
maxResultBytes?: number;
|
||||||
resultKeyColumns?: string[];
|
resultKeyColumns?: string[];
|
||||||
tableDataPreview?: boolean;
|
tableDataPreview?: boolean;
|
||||||
@@ -1193,6 +1195,7 @@ export async function executeMultiWithProgress(
|
|||||||
catalog?: string;
|
catalog?: string;
|
||||||
fetchSize?: number;
|
fetchSize?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
rowOffset?: number;
|
||||||
maxResultBytes?: number;
|
maxResultBytes?: number;
|
||||||
resultKeyColumns?: string[];
|
resultKeyColumns?: string[];
|
||||||
tableDataPreview?: boolean;
|
tableDataPreview?: boolean;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface BuildTableSelectSqlOptions {
|
|||||||
orderBy?: string;
|
orderBy?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
useDriverRowOffset?: boolean;
|
||||||
whereInput?: string;
|
whereInput?: string;
|
||||||
includeRowId?: boolean;
|
includeRowId?: boolean;
|
||||||
catalog?: string;
|
catalog?: string;
|
||||||
|
|||||||
@@ -128,6 +128,44 @@ describe("queryStore table data refresh", () => {
|
|||||||
expect(store.tabs.find((tab) => tab.id === archiveTabId)?.result).toBeUndefined();
|
expect(store.tabs.find((tab) => tab.id === archiveTabId)?.result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses JDBC ResultSet offset pagination for Caché data tabs", async () => {
|
||||||
|
mocks.getConnectionConfig.mockReturnValue({
|
||||||
|
id: "cache-1",
|
||||||
|
name: "Caché 2016",
|
||||||
|
db_type: "jdbc",
|
||||||
|
database: "USER",
|
||||||
|
connection_string: "jdbc:Cache://localhost:1972/USER",
|
||||||
|
query_timeout_secs: 30,
|
||||||
|
});
|
||||||
|
mocks.buildTableSelectSql.mockResolvedValue('SELECT * FROM "SS"."SS_User" ORDER BY "ID" ASC');
|
||||||
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
|
const store = useQueryStore();
|
||||||
|
const tabId = store.createTab("cache-1", "USER", "SS_User", "data", "SS");
|
||||||
|
store.setTableMeta(tabId, {
|
||||||
|
schema: "SS",
|
||||||
|
tableName: "SS_User",
|
||||||
|
tableType: "TABLE",
|
||||||
|
columns: [{ name: "ID", data_type: "%Library.Integer", 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);
|
||||||
|
|
||||||
|
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
databaseType: "iris",
|
||||||
|
limit: 100,
|
||||||
|
offset: 100,
|
||||||
|
useDriverRowOffset: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
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("refreshes one targeted tab while preserving its query context", async () => {
|
it("refreshes one targeted tab while preserving its query context", async () => {
|
||||||
const { useQueryStore } = await import("@/stores/queryStore");
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
const store = useQueryStore();
|
const store = useQueryStore();
|
||||||
|
|||||||
@@ -2766,6 +2766,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||||||
const orderBy = tab.orderByInput?.trim() || sortOrder;
|
const orderBy = tab.orderByInput?.trim() || sortOrder;
|
||||||
const limit = tab.resultPageLimit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);
|
const limit = tab.resultPageLimit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize);
|
||||||
const offset = tab.resultPageOffset ?? 0;
|
const offset = tab.resultPageOffset ?? 0;
|
||||||
|
const useDriverRowOffset = conn?.db_type === "jdbc" && effectiveDbType === "iris";
|
||||||
const refreshPreparationId = uuid();
|
const refreshPreparationId = uuid();
|
||||||
|
|
||||||
// Reserve the tab synchronously before SQL construction yields so repeated
|
// Reserve the tab synchronously before SQL construction yields so repeated
|
||||||
@@ -2789,6 +2790,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||||||
orderBy,
|
orderBy,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
|
...(useDriverRowOffset ? { useDriverRowOffset: true } : {}),
|
||||||
});
|
});
|
||||||
if (!sql.trim()) throw new Error("Failed to build table refresh SQL");
|
if (!sql.trim()) throw new Error("Failed to build table refresh SQL");
|
||||||
const current = tabs.value.find((candidate) => candidate.id === id);
|
const current = tabs.value.find((candidate) => candidate.id === id);
|
||||||
@@ -4577,6 +4579,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||||||
// connection-local state and avoid MySQL pool resets on every refresh.
|
// connection-local state and avoid MySQL pool resets on every refresh.
|
||||||
const dataTabMeta = tab.mode === "data" ? tableMetaForDataTab(tab) : undefined;
|
const dataTabMeta = tab.mode === "data" ? tableMetaForDataTab(tab) : undefined;
|
||||||
const useTableDataPreview = canUseTableDataLargeValuePreview(effectiveDbType, dataTabMeta?.columns ?? [], dataTabMeta?.primaryKeys ?? []);
|
const useTableDataPreview = canUseTableDataLargeValuePreview(effectiveDbType, dataTabMeta?.columns ?? [], dataTabMeta?.primaryKeys ?? []);
|
||||||
|
const useJdbcDriverRowOffset = tab.mode === "data" && conn?.db_type === "jdbc" && effectiveDbType === "iris";
|
||||||
const executionOptions = {
|
const executionOptions = {
|
||||||
...(typeof pageLimit === "number"
|
...(typeof pageLimit === "number"
|
||||||
? useAgentResultSession
|
? useAgentResultSession
|
||||||
@@ -4589,6 +4592,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||||||
}
|
}
|
||||||
: { maxRows: pageLimit, fetchSize: pageLimit }
|
: { maxRows: pageLimit, fetchSize: pageLimit }
|
||||||
: { maxRows: agentProtocolQueryResultMaxRows(queryResultMaxRows) }),
|
: { maxRows: agentProtocolQueryResultMaxRows(queryResultMaxRows) }),
|
||||||
|
...(useJdbcDriverRowOffset && typeof pageOffset === "number" && pageOffset > 0 ? { rowOffset: pageOffset } : {}),
|
||||||
...(executionClientSessionId ? { clientSessionId: executionClientSessionId } : {}),
|
...(executionClientSessionId ? { clientSessionId: executionClientSessionId } : {}),
|
||||||
...(tab.mode === "data" && (effectiveDbType === "mysql" || effectiveDbType === "postgres")
|
...(tab.mode === "data" && (effectiveDbType === "mysql" || effectiveDbType === "postgres")
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -3093,6 +3093,22 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iris_data_grid_count_queries_the_table_without_wrapping_top_sql() {
|
||||||
|
assert_eq!(
|
||||||
|
build_data_grid_count_sql(DataGridCountSqlOptions {
|
||||||
|
database_type: Some(DatabaseType::Iris),
|
||||||
|
identifier_quote: Some("\"".to_string()),
|
||||||
|
catalog: None,
|
||||||
|
database: None,
|
||||||
|
schema: Some("SS".to_string()),
|
||||||
|
table_name: "SS_User".to_string(),
|
||||||
|
where_input: Some("SSUSR_IsActive = 'Y'".to_string()),
|
||||||
|
}),
|
||||||
|
"SELECT COUNT(*) AS cnt FROM \"SS\".\"SS_User\" WHERE (SSUSR_IsActive = 'Y')"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn postgres_keyless_update_preserves_jsonb_array_elements() {
|
fn postgres_keyless_update_preserves_jsonb_array_elements() {
|
||||||
let endpoints =
|
let endpoints =
|
||||||
|
|||||||
@@ -885,6 +885,7 @@ pub struct QueryExecutionOptions {
|
|||||||
pub max_rows: Option<usize>,
|
pub max_rows: Option<usize>,
|
||||||
pub fetch_size: Option<usize>,
|
pub fetch_size: Option<usize>,
|
||||||
pub page_size: Option<usize>,
|
pub page_size: Option<usize>,
|
||||||
|
pub row_offset: Option<usize>,
|
||||||
pub max_result_bytes: Option<usize>,
|
pub max_result_bytes: Option<usize>,
|
||||||
/// Result columns that must stay exact because clients use them as stable
|
/// Result columns that must stay exact because clients use them as stable
|
||||||
/// row identifiers when fetching full large-cell values on demand.
|
/// row identifiers when fetching full large-cell values on demand.
|
||||||
@@ -975,6 +976,9 @@ pub fn agent_execute_query_params(
|
|||||||
if let Some(fetch_size) = options.fetch_size {
|
if let Some(fetch_size) = options.fetch_size {
|
||||||
params["fetchSize"] = serde_json::json!(agent_protocol_row_count(fetch_size));
|
params["fetchSize"] = serde_json::json!(agent_protocol_row_count(fetch_size));
|
||||||
}
|
}
|
||||||
|
if let Some(row_offset) = options.row_offset {
|
||||||
|
params["rowOffset"] = serde_json::json!(agent_protocol_row_offset(row_offset));
|
||||||
|
}
|
||||||
if let Some(timeout_secs) = options.timeout_secs {
|
if let Some(timeout_secs) = options.timeout_secs {
|
||||||
params["timeoutSecs"] = serde_json::json!(timeout_secs);
|
params["timeoutSecs"] = serde_json::json!(timeout_secs);
|
||||||
}
|
}
|
||||||
@@ -1001,6 +1005,9 @@ pub fn agent_execute_query_page_params(
|
|||||||
if let Some(fetch_size) = options.fetch_size {
|
if let Some(fetch_size) = options.fetch_size {
|
||||||
params["fetchSize"] = serde_json::json!(agent_protocol_row_count(fetch_size));
|
params["fetchSize"] = serde_json::json!(agent_protocol_row_count(fetch_size));
|
||||||
}
|
}
|
||||||
|
if let Some(row_offset) = options.row_offset {
|
||||||
|
params["rowOffset"] = serde_json::json!(agent_protocol_row_offset(row_offset));
|
||||||
|
}
|
||||||
if let Some(timeout_secs) = options.timeout_secs {
|
if let Some(timeout_secs) = options.timeout_secs {
|
||||||
params["timeoutSecs"] = serde_json::json!(timeout_secs);
|
params["timeoutSecs"] = serde_json::json!(timeout_secs);
|
||||||
}
|
}
|
||||||
@@ -1018,6 +1025,10 @@ fn agent_protocol_row_count(value: usize) -> usize {
|
|||||||
value.clamp(1, AGENT_PROTOCOL_MAX_ROWS)
|
value.clamp(1, AGENT_PROTOCOL_MAX_ROWS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn agent_protocol_row_offset(value: usize) -> usize {
|
||||||
|
value.min(AGENT_PROTOCOL_MAX_ROWS)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn agent_close_query_session_params(session_id: &str) -> serde_json::Value {
|
pub fn agent_close_query_session_params(session_id: &str) -> serde_json::Value {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"sessionId": session_id,
|
"sessionId": session_id,
|
||||||
@@ -2100,6 +2111,9 @@ fn external_driver_query_params(
|
|||||||
if let Some(fetch_size) = options.fetch_size {
|
if let Some(fetch_size) = options.fetch_size {
|
||||||
params["fetchSize"] = serde_json::json!(fetch_size);
|
params["fetchSize"] = serde_json::json!(fetch_size);
|
||||||
}
|
}
|
||||||
|
if let Some(row_offset) = options.row_offset {
|
||||||
|
params["rowOffset"] = serde_json::json!(agent_protocol_row_offset(row_offset));
|
||||||
|
}
|
||||||
if let Some(timeout_secs) = options.timeout_secs {
|
if let Some(timeout_secs) = options.timeout_secs {
|
||||||
params["timeoutSecs"] = serde_json::json!(timeout_secs);
|
params["timeoutSecs"] = serde_json::json!(timeout_secs);
|
||||||
}
|
}
|
||||||
@@ -6800,6 +6814,7 @@ for line in sys.stdin:
|
|||||||
&QueryExecutionOptions {
|
&QueryExecutionOptions {
|
||||||
max_rows: Some(500),
|
max_rows: Some(500),
|
||||||
fetch_size: Some(250),
|
fetch_size: Some(250),
|
||||||
|
row_offset: Some(100),
|
||||||
timeout_secs: Some(600),
|
timeout_secs: Some(600),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -6811,6 +6826,7 @@ for line in sys.stdin:
|
|||||||
assert_eq!(params["schema"], "app");
|
assert_eq!(params["schema"], "app");
|
||||||
assert_eq!(params["maxRows"], 500);
|
assert_eq!(params["maxRows"], 500);
|
||||||
assert_eq!(params["fetchSize"], 250);
|
assert_eq!(params["fetchSize"], 250);
|
||||||
|
assert_eq!(params["rowOffset"], 100);
|
||||||
assert_eq!(params["timeoutSecs"], 600);
|
assert_eq!(params["timeoutSecs"], 600);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6823,6 +6839,7 @@ for line in sys.stdin:
|
|||||||
QueryExecutionOptions {
|
QueryExecutionOptions {
|
||||||
max_rows: Some(500),
|
max_rows: Some(500),
|
||||||
fetch_size: Some(250),
|
fetch_size: Some(250),
|
||||||
|
row_offset: Some(100),
|
||||||
timeout_secs: Some(600),
|
timeout_secs: Some(600),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -6833,6 +6850,7 @@ for line in sys.stdin:
|
|||||||
assert_eq!(params["schema"], "app");
|
assert_eq!(params["schema"], "app");
|
||||||
assert_eq!(params["maxRows"], 500);
|
assert_eq!(params["maxRows"], 500);
|
||||||
assert_eq!(params["fetchSize"], 250);
|
assert_eq!(params["fetchSize"], 250);
|
||||||
|
assert_eq!(params["rowOffset"], 100);
|
||||||
assert_eq!(params["timeoutSecs"], 600);
|
assert_eq!(params["timeoutSecs"], 600);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6988,6 +7006,7 @@ for line in sys.stdin:
|
|||||||
assert!(params.get("schema").is_none());
|
assert!(params.get("schema").is_none());
|
||||||
assert_eq!(params["maxRows"], MAX_ROWS);
|
assert_eq!(params["maxRows"], MAX_ROWS);
|
||||||
assert!(params.get("fetchSize").is_none());
|
assert!(params.get("fetchSize").is_none());
|
||||||
|
assert!(params.get("rowOffset").is_none());
|
||||||
assert!(params.get("timeoutSecs").is_none());
|
assert!(params.get("timeoutSecs").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7000,6 +7019,7 @@ for line in sys.stdin:
|
|||||||
QueryExecutionOptions {
|
QueryExecutionOptions {
|
||||||
page_size: Some(500),
|
page_size: Some(500),
|
||||||
fetch_size: Some(250),
|
fetch_size: Some(250),
|
||||||
|
row_offset: Some(100),
|
||||||
timeout_secs: Some(600),
|
timeout_secs: Some(600),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -7010,6 +7030,7 @@ for line in sys.stdin:
|
|||||||
assert_eq!(params["schema"], "app");
|
assert_eq!(params["schema"], "app");
|
||||||
assert_eq!(params["pageSize"], 500);
|
assert_eq!(params["pageSize"], 500);
|
||||||
assert_eq!(params["fetchSize"], 250);
|
assert_eq!(params["fetchSize"], 250);
|
||||||
|
assert_eq!(params["rowOffset"], 100);
|
||||||
assert_eq!(params["timeoutSecs"], 600);
|
assert_eq!(params["timeoutSecs"], 600);
|
||||||
assert_eq!(params["maxRows"], MAX_ROWS);
|
assert_eq!(params["maxRows"], MAX_ROWS);
|
||||||
}
|
}
|
||||||
@@ -7025,6 +7046,7 @@ for line in sys.stdin:
|
|||||||
page_size: Some(oversized),
|
page_size: Some(oversized),
|
||||||
fetch_size: Some(oversized),
|
fetch_size: Some(oversized),
|
||||||
max_rows: Some(oversized),
|
max_rows: Some(oversized),
|
||||||
|
row_offset: Some(oversized),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -7032,6 +7054,7 @@ for line in sys.stdin:
|
|||||||
assert_eq!(params["pageSize"], AGENT_PROTOCOL_MAX_ROWS);
|
assert_eq!(params["pageSize"], AGENT_PROTOCOL_MAX_ROWS);
|
||||||
assert_eq!(params["fetchSize"], AGENT_PROTOCOL_MAX_ROWS);
|
assert_eq!(params["fetchSize"], AGENT_PROTOCOL_MAX_ROWS);
|
||||||
assert_eq!(params["maxRows"], AGENT_PROTOCOL_MAX_ROWS);
|
assert_eq!(params["maxRows"], AGENT_PROTOCOL_MAX_ROWS);
|
||||||
|
assert_eq!(params["rowOffset"], AGENT_PROTOCOL_MAX_ROWS);
|
||||||
assert_eq!(agent_fetch_query_page_params("session-1", oversized)["pageSize"], AGENT_PROTOCOL_MAX_ROWS);
|
assert_eq!(agent_fetch_query_page_params("session-1", oversized)["pageSize"], AGENT_PROTOCOL_MAX_ROWS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -225,7 +225,11 @@ pub fn build_table_data_select_sql(options: TableDataSelectSqlOptions) -> String
|
|||||||
|
|
||||||
match table_pagination_strategy(database_type) {
|
match table_pagination_strategy(database_type) {
|
||||||
TablePaginationStrategy::IrisTop => {
|
TablePaginationStrategy::IrisTop => {
|
||||||
format!("SELECT TOP {limit} {select_columns} FROM {table_alias}{where_clause}{order}")
|
if options.use_driver_row_offset {
|
||||||
|
format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order}")
|
||||||
|
} else {
|
||||||
|
format!("SELECT TOP {limit} {select_columns} FROM {table_alias}{where_clause}{order}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TablePaginationStrategy::InformixFirst => {
|
TablePaginationStrategy::InformixFirst => {
|
||||||
let row_limit = informix_row_limit_clause(limit, options.offset.unwrap_or(0));
|
let row_limit = informix_row_limit_clause(limit, options.offset.unwrap_or(0));
|
||||||
@@ -654,6 +658,7 @@ mod tests {
|
|||||||
order_by: None,
|
order_by: None,
|
||||||
limit: Some(10),
|
limit: Some(10),
|
||||||
offset: None,
|
offset: None,
|
||||||
|
use_driver_row_offset: false,
|
||||||
where_input: None,
|
where_input: None,
|
||||||
include_row_id: false,
|
include_row_id: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -621,6 +621,25 @@ fn builds_table_data_where_and_schema_queries() {
|
|||||||
}),
|
}),
|
||||||
"SELECT TOP 100 * FROM \"Ens\".\"AlarmResponse\""
|
"SELECT TOP 100 * FROM \"Ens\".\"AlarmResponse\""
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
build_table_data_select_sql(TableDataSelectSqlOptions {
|
||||||
|
database_type: Some(DatabaseType::Iris),
|
||||||
|
schema: Some("Ens".to_string()),
|
||||||
|
table_name: "AlarmResponse".to_string(),
|
||||||
|
table_type: None,
|
||||||
|
primary_keys: Vec::new(),
|
||||||
|
columns: Vec::new(),
|
||||||
|
fallback_order_columns: Vec::new(),
|
||||||
|
order_by: Some("\"ID\" ASC".to_string()),
|
||||||
|
limit: Some(100),
|
||||||
|
offset: Some(100),
|
||||||
|
use_driver_row_offset: true,
|
||||||
|
where_input: Some("Status = 'Open'".to_string()),
|
||||||
|
include_row_id: false,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
"SELECT * FROM \"Ens\".\"AlarmResponse\" WHERE (Status = 'Open') ORDER BY \"ID\" ASC"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
build_table_data_select_sql(TableDataSelectSqlOptions {
|
build_table_data_select_sql(TableDataSelectSqlOptions {
|
||||||
database_type: Some(DatabaseType::Iotdb),
|
database_type: Some(DatabaseType::Iotdb),
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ pub struct TableDataSelectSqlOptions {
|
|||||||
pub limit: Option<usize>,
|
pub limit: Option<usize>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub offset: Option<usize>,
|
pub offset: Option<usize>,
|
||||||
|
/// The JDBC driver advances the ResultSet before collecting the page.
|
||||||
|
/// This keeps database-specific identifier rules while avoiding SQL
|
||||||
|
/// pagination syntax that the selected driver does not support.
|
||||||
|
#[serde(default)]
|
||||||
|
pub use_driver_row_offset: bool,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub where_input: Option<String>,
|
pub where_input: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub struct ExecuteQueryRequest {
|
|||||||
pub max_rows: Option<usize>,
|
pub max_rows: Option<usize>,
|
||||||
pub fetch_size: Option<usize>,
|
pub fetch_size: Option<usize>,
|
||||||
pub page_size: Option<usize>,
|
pub page_size: Option<usize>,
|
||||||
|
pub row_offset: Option<usize>,
|
||||||
pub max_result_bytes: Option<usize>,
|
pub max_result_bytes: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub result_key_columns: Vec<String>,
|
pub result_key_columns: Vec<String>,
|
||||||
@@ -363,6 +364,7 @@ pub async fn execute_query(
|
|||||||
max_rows: req.max_rows,
|
max_rows: req.max_rows,
|
||||||
fetch_size: req.fetch_size,
|
fetch_size: req.fetch_size,
|
||||||
page_size: req.page_size,
|
page_size: req.page_size,
|
||||||
|
row_offset: req.row_offset,
|
||||||
max_result_bytes: req.max_result_bytes,
|
max_result_bytes: req.max_result_bytes,
|
||||||
result_key_columns: req.result_key_columns,
|
result_key_columns: req.result_key_columns,
|
||||||
table_data_preview: req.table_data_preview,
|
table_data_preview: req.table_data_preview,
|
||||||
@@ -412,6 +414,7 @@ pub async fn execute_multi(
|
|||||||
max_rows: req.max_rows,
|
max_rows: req.max_rows,
|
||||||
fetch_size: req.fetch_size,
|
fetch_size: req.fetch_size,
|
||||||
page_size: req.page_size,
|
page_size: req.page_size,
|
||||||
|
row_offset: req.row_offset,
|
||||||
max_result_bytes: req.max_result_bytes,
|
max_result_bytes: req.max_result_bytes,
|
||||||
result_key_columns: req.result_key_columns,
|
result_key_columns: req.result_key_columns,
|
||||||
table_data_preview: req.table_data_preview,
|
table_data_preview: req.table_data_preview,
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ public final class DbxJdbcPlugin {
|
|||||||
optionalText(params, "schema"),
|
optionalText(params, "schema"),
|
||||||
positiveInt(params, "maxRows", MAX_ROWS),
|
positiveInt(params, "maxRows", MAX_ROWS),
|
||||||
nonNegativeInt(params, "fetchSize", 0),
|
nonNegativeInt(params, "fetchSize", 0),
|
||||||
|
nonNegativeInt(params, "rowOffset", 0),
|
||||||
nonNegativeInt(params, "timeoutSecs", -1)
|
nonNegativeInt(params, "timeoutSecs", -1)
|
||||||
);
|
);
|
||||||
case "executeQueryPage", "execute_query_page" -> executeQueryPage(
|
case "executeQueryPage", "execute_query_page" -> executeQueryPage(
|
||||||
@@ -665,6 +666,7 @@ public final class DbxJdbcPlugin {
|
|||||||
String schema,
|
String schema,
|
||||||
int maxRows,
|
int maxRows,
|
||||||
int fetchSize,
|
int fetchSize,
|
||||||
|
int rowOffset,
|
||||||
int timeoutSecs
|
int timeoutSecs
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
@@ -689,6 +691,10 @@ public final class DbxJdbcPlugin {
|
|||||||
String label = meta.getColumnLabel(i);
|
String label = meta.getColumnLabel(i);
|
||||||
columns.add(label == null || label.isBlank() ? meta.getColumnName(i) : label);
|
columns.add(label == null || label.isBlank() ? meta.getColumnName(i) : label);
|
||||||
}
|
}
|
||||||
|
for (int skipped = 0; skipped < rowOffset && rs.next(); skipped++) {
|
||||||
|
// Caché/IRIS does not support SQL offset pagination; advance
|
||||||
|
// the forward-only JDBC cursor before collecting this page.
|
||||||
|
}
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
if (rows.size() >= maxRows) {
|
if (rows.size() >= maxRows) {
|
||||||
truncated = true;
|
truncated = true;
|
||||||
|
|||||||
@@ -71,6 +71,35 @@ final class DbxJdbcPluginTest {
|
|||||||
assertEquals("APP", response.path("result").path("rows").path(0).path(0).asText());
|
assertEquals("APP", response.path("result").path("rows").path(0).path(0).asText());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executeQuerySkipsRowsBeforeCollectingThePage() throws Exception {
|
||||||
|
request("executeQuery", """
|
||||||
|
{
|
||||||
|
"connection": %s,
|
||||||
|
"sql": "CREATE TABLE IF NOT EXISTS page_rows(id INT PRIMARY KEY)"
|
||||||
|
}
|
||||||
|
""".formatted(CONNECTION));
|
||||||
|
request("executeQuery", """
|
||||||
|
{
|
||||||
|
"connection": %s,
|
||||||
|
"sql": "MERGE INTO page_rows KEY(id) VALUES (1), (2), (3)"
|
||||||
|
}
|
||||||
|
""".formatted(CONNECTION));
|
||||||
|
|
||||||
|
JsonNode response = request("executeQuery", """
|
||||||
|
{
|
||||||
|
"connection": %s,
|
||||||
|
"sql": "SELECT id FROM page_rows ORDER BY id",
|
||||||
|
"rowOffset": 1,
|
||||||
|
"maxRows": 1
|
||||||
|
}
|
||||||
|
""".formatted(CONNECTION));
|
||||||
|
|
||||||
|
assertFalse(response.has("error"), response.toString());
|
||||||
|
assertEquals(2, response.path("result").path("rows").path(0).path(0).asInt());
|
||||||
|
assertEquals(1, response.path("result").path("rows").size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reportsDriverLinkageErrorsWithoutTerminatingThePlugin() throws Exception {
|
void reportsDriverLinkageErrorsWithoutTerminatingThePlugin() throws Exception {
|
||||||
JsonNode response = request("testConnection", """
|
JsonNode response = request("testConnection", """
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub async fn execute_query(
|
|||||||
max_rows: Option<usize>,
|
max_rows: Option<usize>,
|
||||||
fetch_size: Option<usize>,
|
fetch_size: Option<usize>,
|
||||||
page_size: Option<usize>,
|
page_size: Option<usize>,
|
||||||
|
row_offset: Option<usize>,
|
||||||
result_session_id: Option<String>,
|
result_session_id: Option<String>,
|
||||||
client_session_id: Option<String>,
|
client_session_id: Option<String>,
|
||||||
timeout_secs: Option<u64>,
|
timeout_secs: Option<u64>,
|
||||||
@@ -61,6 +62,7 @@ pub async fn execute_query(
|
|||||||
max_rows,
|
max_rows,
|
||||||
fetch_size,
|
fetch_size,
|
||||||
page_size,
|
page_size,
|
||||||
|
row_offset,
|
||||||
catalog,
|
catalog,
|
||||||
result_session_id,
|
result_session_id,
|
||||||
client_session_id,
|
client_session_id,
|
||||||
@@ -88,6 +90,7 @@ pub async fn execute_multi(
|
|||||||
max_rows: Option<usize>,
|
max_rows: Option<usize>,
|
||||||
fetch_size: Option<usize>,
|
fetch_size: Option<usize>,
|
||||||
page_size: Option<usize>,
|
page_size: Option<usize>,
|
||||||
|
row_offset: Option<usize>,
|
||||||
max_result_bytes: Option<usize>,
|
max_result_bytes: Option<usize>,
|
||||||
result_key_columns: Option<Vec<String>>,
|
result_key_columns: Option<Vec<String>>,
|
||||||
table_data_preview: Option<bool>,
|
table_data_preview: Option<bool>,
|
||||||
@@ -147,6 +150,7 @@ pub async fn execute_multi(
|
|||||||
max_rows,
|
max_rows,
|
||||||
fetch_size,
|
fetch_size,
|
||||||
page_size,
|
page_size,
|
||||||
|
row_offset,
|
||||||
max_result_bytes,
|
max_result_bytes,
|
||||||
result_key_columns: result_key_columns.unwrap_or_default(),
|
result_key_columns: result_key_columns.unwrap_or_default(),
|
||||||
table_data_preview: table_data_preview.unwrap_or(false),
|
table_data_preview: table_data_preview.unwrap_or(false),
|
||||||
|
|||||||
Reference in new issue
Block a user