perf(oracle): speed up initial table loading

This commit is contained in:
t8y2 committed 2026-05-10 20:47:54 +08:00
1 parent 61bd7480f4
commit d917842fca
7 files changed
+158 -33

No files matched your search

+68 -1
View File
@@ -295,13 +295,15 @@ pub async fn list_triggers(conn: &OracleClient, schema: &str, table: &str) -> Re
pub async fn execute_query(conn: &OracleClient, sql: &str) -> Result<QueryResult, String> {
let start = Instant::now();
let sql = sql.trim().trim_end_matches(';');
let explicit_limit = explicit_select_row_limit(sql);
// Rewrite FETCH FIRST N ROWS ONLY → ROWNUM for Oracle 11g compatibility.
let sql = rewrite_fetch_first(sql);
if starts_with_executable_sql_keyword(sql.as_ref(), &["SELECT", "WITH", "SHOW", "DESCRIBE", "EXPLAIN"]) {
let capped_sql = cap_select_rows(sql.as_ref());
let result = conn.query_with_limit(capped_sql.as_ref(), &[], ORACLE_QUERY_LIMIT, 500).await.map_err(|e| {
let query_limit = explicit_limit.unwrap_or(ORACLE_QUERY_LIMIT).min(ORACLE_QUERY_LIMIT);
let result = conn.query_with_limit(capped_sql.as_ref(), &[], query_limit, 500).await.map_err(|e| {
log::error!("[oracle] execute_query SELECT failed: {e}");
e.to_string()
})?;
@@ -357,6 +359,55 @@ fn has_for_update_clause(sql: &str) -> bool {
sql.to_uppercase().contains(" FOR UPDATE")
}
fn explicit_select_row_limit(sql: &str) -> Option<usize> {
fetch_first_row_limit(sql).or_else(|| rownum_row_limit(sql))
}
fn fetch_first_row_limit(sql: &str) -> Option<usize> {
let upper = sql.to_uppercase();
let fetch_pos = upper.find("FETCH FIRST").or_else(|| upper.find("FETCH NEXT"))?;
let after_fetch = &upper[fetch_pos..];
let end = after_fetch.find("ROWS ONLY")?;
let keyword_len = if after_fetch.starts_with("FETCH FIRST") { 11 } else { 10 };
sql[fetch_pos + keyword_len..fetch_pos + end].trim().parse::<usize>().ok()
}
fn rownum_row_limit(sql: &str) -> Option<usize> {
let upper = sql.to_uppercase();
let mut rest = upper.as_str();
let mut best: Option<usize> = None;
while let Some(pos) = rest.find("ROWNUM") {
rest = &rest[pos + "ROWNUM".len()..];
let trimmed = rest.trim_start();
let value_start = if let Some(after) = trimmed.strip_prefix("<=") {
after.trim_start()
} else if let Some(after) = trimmed.strip_prefix('<') {
if let Some(n) = parse_leading_usize(after.trim_start()) {
let exclusive = n.saturating_sub(1);
best = Some(best.map_or(exclusive, |current| current.min(exclusive)));
}
continue;
} else {
continue;
};
if let Some(n) = parse_leading_usize(value_start) {
best = Some(best.map_or(n, |current| current.min(n)));
}
}
best
}
fn parse_leading_usize(value: &str) -> Option<usize> {
let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
if digits.is_empty() {
return None;
}
digits.parse().ok()
}
fn rewrite_fetch_first(sql: &str) -> std::borrow::Cow<'_, str> {
let upper = sql.to_uppercase();
// Match: ... [OFFSET M ROWS] FETCH FIRST|NEXT N ROWS ONLY
@@ -420,4 +471,20 @@ mod tests {
"SELECT * FROM (SELECT * FROM users) WHERE ROWNUM <= 20"
);
}
#[test]
fn explicit_select_row_limit_reads_fetch_first() {
assert_eq!(explicit_select_row_limit("SELECT * FROM users FETCH FIRST 100 ROWS ONLY"), Some(100));
assert_eq!(explicit_select_row_limit("SELECT * FROM users OFFSET 20 ROWS FETCH NEXT 50 ROWS ONLY"), Some(50));
}
#[test]
fn explicit_select_row_limit_reads_rownum() {
assert_eq!(explicit_select_row_limit("SELECT * FROM users WHERE ROWNUM <= 100"), Some(100));
assert_eq!(explicit_select_row_limit("SELECT * FROM users WHERE ROWNUM < 101"), Some(100));
assert_eq!(
explicit_select_row_limit("SELECT * FROM (SELECT * FROM users WHERE ROWNUM <= 500) WHERE ROWNUM <= 100"),
Some(100)
);
}
}