fix(clickhouse): handle system.settings pagination

This commit is contained in:
lewis 2026-07-24 00:07:38 +08:00 committed by GitHub
parent 2bdab48d78
commit 74559d6eec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 69 additions and 3 deletions

View File

@ -8,7 +8,7 @@ use crate::sql_dialect::{
firebird_rows_clause, pagination_strategy, quote_table_identifier, PaginationContext, TablePaginationStrategy,
};
use sqlparser::ast::{Expr, GroupByExpr, SelectItem, SetExpr, Statement};
use sqlparser::dialect::{GenericDialect, MsSqlDialect};
use sqlparser::dialect::{ClickHouseDialect, GenericDialect, MsSqlDialect};
use sqlparser::parser::Parser;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -1015,8 +1015,7 @@ fn add_outer_standard_limit(
fn add_clickhouse_limit(statement: &str, limit_sql: &str) -> String {
let limit_sql = limit_sql.trim();
let settings_index =
top_level_sql_tokens(statement).iter().find(|token| token.text == "SETTINGS").map(|token| token.start);
let settings_index = clickhouse_settings_clause_index(statement);
if let Some(index) = settings_index {
let statement_before_settings = statement[..index].trim_end();
@ -1027,6 +1026,31 @@ fn add_clickhouse_limit(statement: &str, limit_sql: &str) -> String {
append_sql_suffix(statement, &format!("{limit_sql};"))
}
fn clickhouse_settings_clause_index(statement: &str) -> Option<usize> {
let parsed_settings = Parser::parse_sql(&ClickHouseDialect {}, statement).ok().and_then(|statements| {
let [Statement::Query(query)] = statements.as_slice() else {
return None;
};
Some(query.settings.is_some())
});
if parsed_settings == Some(false) {
return None;
}
// Keep the lexical fallback for valid ClickHouse syntax that sqlparser does not yet support.
top_level_sql_tokens(statement)
.iter()
.rev()
.find(|token| token.text == "SETTINGS" && !is_qualified_identifier_part(statement, token))
.map(|token| token.start)
}
fn is_qualified_identifier_part(sql: &str, token: &SqlToken) -> bool {
let token_end = token.start + token.text.len();
sql[..token.start].chars().rev().find(|ch| !ch.is_whitespace()) == Some('.')
|| sql[token_end..].chars().find(|ch| !ch.is_whitespace()) == Some('.')
}
fn derived_table_sql(prefix: &str, statement: &str, suffix: &str) -> String {
format!("{prefix} ({}) {suffix}", statement_for_sql_suffix(statement))
}
@ -1818,6 +1842,48 @@ WHERE u.id = picked.id;
);
}
#[test]
fn clickhouse_settings_table_is_not_treated_as_settings_clause() {
let result = build_paginated_query_sql(PaginatedQuerySqlOptions {
original_sql: "SELECT * FROM system.settings".to_string(),
database_type: Some(DatabaseType::ClickHouse),
limit: 100,
offset: 0,
});
assert!(result.ok);
assert_eq!(result.sql.unwrap(), "SELECT * FROM system.settings LIMIT 100;");
}
#[test]
fn clickhouse_settings_identifier_is_not_treated_as_settings_clause() {
let result = build_paginated_query_sql(PaginatedQuerySqlOptions {
original_sql: "SELECT settings FROM (SELECT 1 AS settings)".to_string(),
database_type: Some(DatabaseType::ClickHouse),
limit: 100,
offset: 0,
});
assert!(result.ok);
assert_eq!(result.sql.unwrap(), "SELECT settings FROM (SELECT 1 AS settings) LIMIT 100;");
}
#[test]
fn clickhouse_settings_identifier_keeps_trailing_settings_clause() {
let result = build_paginated_query_sql(PaginatedQuerySqlOptions {
original_sql: "SELECT settings FROM (SELECT 1 AS settings) SETTINGS max_threads = 1".to_string(),
database_type: Some(DatabaseType::ClickHouse),
limit: 100,
offset: 0,
});
assert!(result.ok);
assert_eq!(
result.sql.unwrap(),
"SELECT settings FROM (SELECT 1 AS settings) LIMIT 100 SETTINGS max_threads = 1;"
);
}
#[test]
fn clickhouse_query_plan_places_limit_before_settings() {
let sql = "SELECT * FROM system.clusters SETTINGS max_execution_time = 0";