fix(sql): centralize table pagination strategies

This commit is contained in:
t8y2 committed 2026-06-19 10:23:14 +08:00
1 parent e27d34a34a
commit 535d247ae1
7 files changed
+355 -159

No files matched your search

@@ -1,5 +1,26 @@
use crate::models::connection::DatabaseType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TablePaginationStrategy {
LimitOffset,
FetchFirst,
Db2FetchFirst,
SqlServerTop,
IrisTop,
InformixFirst,
Rownum,
QuestDbLimit,
Unbounded,
AgentMaxRows,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaginationContext {
TablePreview,
BoundedRead,
UserQuery,
}
pub fn is_schema_aware(database_type: DatabaseType) -> bool {
matches!(
database_type,
@@ -40,6 +61,30 @@ pub fn uses_fetch_first(database_type: DatabaseType) -> bool {
matches!(database_type, DatabaseType::Oracle | DatabaseType::Dameng | DatabaseType::Db2)
}
pub fn pagination_strategy(database_type: Option<DatabaseType>, context: PaginationContext) -> TablePaginationStrategy {
match database_type {
Some(DatabaseType::Jdbc) => TablePaginationStrategy::AgentMaxRows,
Some(DatabaseType::Oracle)
if matches!(context, PaginationContext::TablePreview | PaginationContext::UserQuery) =>
{
TablePaginationStrategy::Unbounded
}
Some(DatabaseType::Oracle) => TablePaginationStrategy::FetchFirst,
Some(DatabaseType::Dameng) => TablePaginationStrategy::FetchFirst,
Some(DatabaseType::Db2) => TablePaginationStrategy::Db2FetchFirst,
Some(DatabaseType::SqlServer) => TablePaginationStrategy::SqlServerTop,
Some(DatabaseType::Iris) => TablePaginationStrategy::IrisTop,
Some(DatabaseType::Informix) => TablePaginationStrategy::InformixFirst,
Some(DatabaseType::OceanbaseOracle) => TablePaginationStrategy::Rownum,
Some(DatabaseType::Questdb) => TablePaginationStrategy::QuestDbLimit,
_ => TablePaginationStrategy::LimitOffset,
}
}
pub fn table_pagination_strategy(database_type: Option<DatabaseType>) -> TablePaginationStrategy {
pagination_strategy(database_type, PaginationContext::TablePreview)
}
pub(super) fn is_simple_informix_identifier(name: &str) -> bool {
let mut chars = name.chars();
let Some(first) = chars.next() else {
+68 -84
View File
@@ -1,6 +1,6 @@
use crate::models::connection::DatabaseType;
use super::capabilities::uses_fetch_first;
use super::capabilities::{table_pagination_strategy, uses_fetch_first, TablePaginationStrategy};
use super::identifiers::{normalize_where_input, qualified_table_name, quote_table_identifier};
use super::types::{
TableDataSelectSqlOptions, TableSelectSqlOptions, DBX_NEO4J_ELEMENT_ID_COLUMN, DBX_ROWID_COLUMN,
@@ -62,75 +62,68 @@ pub fn build_table_data_select_sql(options: TableDataSelectSqlOptions) -> String
table
};
if database_type == Some(DatabaseType::Iris) {
return format!("SELECT TOP {limit} {select_columns} FROM {table_alias}{where_clause}{order}");
}
if database_type == Some(DatabaseType::Informix) {
let row_limit = informix_row_limit_clause(limit, options.offset.unwrap_or(0));
return format!("SELECT {row_limit} {select_columns} FROM {table_alias}{where_clause}{order}");
}
if database_type == Some(DatabaseType::Db2) && options.offset.is_some_and(|offset| offset > 0) {
return build_db2_table_select_page_sql(
&table_alias,
&where_clause,
order_by,
&options.columns,
limit,
options.offset.unwrap_or(0),
);
}
if database_type == Some(DatabaseType::OceanbaseOracle) {
return build_oceanbase_oracle_table_select_sql(&table_alias, &where_clause, &order, &select_columns, limit);
}
if database_type == Some(DatabaseType::Oracle) {
return format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order}");
}
if database_type.is_some_and(uses_fetch_first) {
let offset = options
.offset
.filter(|offset| *offset > 0)
.map(|offset| format!(" OFFSET {offset} ROWS"))
.unwrap_or_default();
return format!(
"SELECT {select_columns} FROM {table_alias}{where_clause}{order}{offset} FETCH FIRST {limit} ROWS ONLY"
);
}
if database_type == Some(DatabaseType::SqlServer) {
return build_sqlserver_table_select_sql(
match table_pagination_strategy(database_type) {
TablePaginationStrategy::IrisTop => {
format!("SELECT TOP {limit} {select_columns} FROM {table_alias}{where_clause}{order}")
}
TablePaginationStrategy::InformixFirst => {
let row_limit = informix_row_limit_clause(limit, options.offset.unwrap_or(0));
format!("SELECT {row_limit} {select_columns} FROM {table_alias}{where_clause}{order}")
}
TablePaginationStrategy::Db2FetchFirst if options.offset.is_some_and(|offset| offset > 0) => {
build_db2_table_select_page_sql(
&table_alias,
&where_clause,
order_by,
&options.columns,
limit,
options.offset.unwrap_or(0),
)
}
TablePaginationStrategy::Db2FetchFirst | TablePaginationStrategy::FetchFirst => {
let offset = options
.offset
.filter(|offset| *offset > 0)
.map(|offset| format!(" OFFSET {offset} ROWS"))
.unwrap_or_default();
format!(
"SELECT {select_columns} FROM {table_alias}{where_clause}{order}{offset} FETCH FIRST {limit} ROWS ONLY"
)
}
TablePaginationStrategy::Rownum => {
build_rownum_table_select_sql(&table_alias, &where_clause, &order, &select_columns, limit)
}
TablePaginationStrategy::Unbounded => {
format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order}")
}
TablePaginationStrategy::SqlServerTop => build_sqlserver_table_select_sql(
&table_alias,
&where_clause,
order_by.unwrap_or("(SELECT NULL)"),
&options.columns,
limit,
options.offset.unwrap_or(0),
);
}
if database_type == Some(DatabaseType::Questdb) {
return build_questdb_table_select_sql(
),
TablePaginationStrategy::QuestDbLimit => build_questdb_table_select_sql(
&table_alias,
&where_clause,
&order,
&options.columns,
limit,
options.offset.unwrap_or(0),
);
),
TablePaginationStrategy::AgentMaxRows => {
format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order};")
}
TablePaginationStrategy::LimitOffset => {
let offset = options
.offset
.filter(|offset| *offset > 0)
.map(|offset| format!(" OFFSET {offset}"))
.unwrap_or_default();
format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order} LIMIT {limit}{offset};")
}
}
let offset =
options.offset.filter(|offset| *offset > 0).map(|offset| format!(" OFFSET {offset}")).unwrap_or_default();
// JDBC connections avoid SQL-level LIMIT because it is not universally
// supported across all JDBC drivers; the agent truncates rows while reading.
if database_type == Some(DatabaseType::Jdbc) {
return format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order};");
}
format!("SELECT {select_columns} FROM {table_alias}{where_clause}{order} LIMIT {limit}{offset};")
}
pub fn build_table_select_sql(options: TableSelectSqlOptions<'_>) -> String {
@@ -161,33 +154,24 @@ pub fn build_table_select_sql(options: TableSelectSqlOptions<'_>) -> String {
};
let limit = options.limit;
if database_type == Some(DatabaseType::Iris) {
return format!("SELECT TOP {limit} {select_columns} FROM {table}{order_by}");
match table_pagination_strategy(database_type) {
TablePaginationStrategy::IrisTop => format!("SELECT TOP {limit} {select_columns} FROM {table}{order_by}"),
TablePaginationStrategy::InformixFirst => {
format!("SELECT FIRST {limit} {select_columns} FROM {table}{order_by}")
}
TablePaginationStrategy::Rownum => build_rownum_table_select_sql(&table, "", &order_by, &select_columns, limit),
TablePaginationStrategy::Db2FetchFirst | TablePaginationStrategy::FetchFirst => {
format!("SELECT {select_columns} FROM {table}{order_by} FETCH FIRST {limit} ROWS ONLY")
}
TablePaginationStrategy::SqlServerTop => {
format!("SELECT TOP ({limit}) {select_columns} FROM {table}{order_by}")
}
TablePaginationStrategy::AgentMaxRows => format!("SELECT {select_columns} FROM {table}{order_by};"),
TablePaginationStrategy::Unbounded => format!("SELECT {select_columns} FROM {table}{order_by}"),
TablePaginationStrategy::QuestDbLimit | TablePaginationStrategy::LimitOffset => {
format!("SELECT {select_columns} FROM {table}{order_by} LIMIT {limit};")
}
}
if database_type == Some(DatabaseType::Informix) {
return format!("SELECT FIRST {limit} {select_columns} FROM {table}{order_by}");
}
if database_type == Some(DatabaseType::OceanbaseOracle) {
let inner_select = format!("SELECT {select_columns} FROM {table}{order_by}");
return format!("SELECT {select_columns} FROM ({inner_select}) WHERE ROWNUM <= {limit}");
}
if database_type.is_some_and(uses_fetch_first) {
return format!("SELECT {select_columns} FROM {table}{order_by} FETCH FIRST {limit} ROWS ONLY");
}
if database_type == Some(DatabaseType::SqlServer) {
return format!("SELECT TOP ({limit}) {select_columns} FROM {table}{order_by}");
}
// JDBC connections avoid SQL-level LIMIT; the agent truncates rows while reading.
if database_type == Some(DatabaseType::Jdbc) {
return format!("SELECT {select_columns} FROM {table}{order_by};");
}
format!("SELECT {select_columns} FROM {table}{order_by} LIMIT {limit};")
}
fn informix_row_limit_clause(limit: usize, offset: usize) -> String {
@@ -198,7 +182,7 @@ fn informix_row_limit_clause(limit: usize, offset: usize) -> String {
}
}
fn build_oceanbase_oracle_table_select_sql(
fn build_rownum_table_select_sql(
table: &str,
where_clause: &str,
order: &str,
+23
View File
@@ -52,6 +52,29 @@ fn qualifies_schema_only_for_schema_aware_databases() {
);
}
#[test]
fn maps_table_pagination_strategy_by_database_type() {
assert_eq!(table_pagination_strategy(Some(DatabaseType::Mysql)), TablePaginationStrategy::LimitOffset);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Dameng)), TablePaginationStrategy::FetchFirst);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Db2)), TablePaginationStrategy::Db2FetchFirst);
assert_eq!(table_pagination_strategy(Some(DatabaseType::SqlServer)), TablePaginationStrategy::SqlServerTop);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Iris)), TablePaginationStrategy::IrisTop);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Informix)), TablePaginationStrategy::InformixFirst);
assert_eq!(table_pagination_strategy(Some(DatabaseType::OceanbaseOracle)), TablePaginationStrategy::Rownum);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Questdb)), TablePaginationStrategy::QuestDbLimit);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Oracle)), TablePaginationStrategy::Unbounded);
assert_eq!(
pagination_strategy(Some(DatabaseType::Oracle), PaginationContext::BoundedRead),
TablePaginationStrategy::FetchFirst
);
assert_eq!(
pagination_strategy(Some(DatabaseType::Oracle), PaginationContext::UserQuery),
TablePaginationStrategy::Unbounded
);
assert_eq!(table_pagination_strategy(Some(DatabaseType::Jdbc)), TablePaginationStrategy::AgentMaxRows);
assert_eq!(table_pagination_strategy(None), TablePaginationStrategy::LimitOffset);
}
#[test]
fn builds_select_sql_with_limit_syntax_for_database_type() {
let columns = vec!["id".to_string(), "name".to_string()];