t8y2/dbx · info

valid ES SQL pagination regex

Error message

valid ES SQL pagination regex

What it means

Panic from Regex::new(...).expect when compiling the hard-coded ES SQL pagination pattern `(?i)^(.*?)\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$`. The pattern is a compile-time constant known to be valid, so the expect documents a build-time invariant; a panic means the constant itself was corrupted in an edit. Users cannot trigger it via query input since the regex is never built from user data.

Solutions

  1. Keep the pattern as a validated constant; never interpolate user input into it
  2. Use once_cell::sync::Lazy<Regex> (or std OnceLock) to compile once and fail fast at first use with a clear message
  3. If the pattern ever becomes dynamic, replace expect with match Regex::new(...) returning a proper error
  4. Add a unit test that es_sql_pagination parses sample LIMIT/OFFSET queries to catch accidental corruption

Example fix

// before
let re =
    Regex::new(r"(?i)^(.*?)\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$").expect("valid ES SQL pagination regex");
// after
static ES_SQL_PAGINATION: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?i)^(.*?)\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$")
        .expect("valid ES SQL pagination regex")
});
let re = &*ES_SQL_PAGINATION;
Defensive patterns

Strategy: try-catch

Validate before calling

let q = query.trim().trim_end_matches(';');
let has_limit = q.to_lowercase().contains(" limit ");
// no caller-side action needed; the regex is a library constant
let _ = has_limit;

Try / catch

let (sql, limit) = es_sql_pagination(query); // panics only on corrupted constant; catch_unwind is unnecessary — pin with a unit test
assert!(!sql.to_lowercase().contains("offset 0"));

Prevention

When it happens

Trigger: Only fires if the literal regex string is edited into an invalid pattern (typo'd group, unbalanced paren), or if the code is changed to interpolate user/runtime input into the pattern.

Common situations: Hand-edits to the regex literal; refactors parameterizing the pattern with dynamic content; not reachable from es_sql() query input.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/e225f50fe93440d8. Report an issue: GitHub.

Appendix: source

Thrown at crates/dbx-core/src/db/elasticsearch_driver.rs:2468

) -> Result<QueryResult, String> {
    let search = EsIndexedSearch {
        index: translated.index,
        body: translated.body,
        from_plan_pagination: translated.from_plan_pagination,
        // 用户自己写了 LIMIT 时不覆盖行数,否则会把「取 10 条」显示成索引总量。
        report_index_total: !translated.user_limited,
    };
    execute_indexed_search(client, search, start, sql_response_parser, cursor).await
}

/// Split a trailing `LIMIT n OFFSET m` from an ES SQL statement. The OFFSET
/// form is produced by the DBX pagination plan; it must be removed before
/// sending the query to `_sql` so ES SQL cursor pagination can drive paging.
/// A bare user `LIMIT n` (no OFFSET) is preserved as an explicit row cap.
fn es_sql_pagination(query: &str) -> (String, Option<usize>) {
    let trimmed = query.trim().trim_end_matches(';').trim();
    let re =
        Regex::new(r"(?i)^(.*?)\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$").expect("valid ES SQL pagination regex");
    if let Some(caps) = re.captures(trimmed) {
        let limit = caps.get(2).and_then(|value| value.as_str().parse::<usize>().ok());
        let offset = caps.get(3).and_then(|value| value.as_str().parse::<usize>().ok());
        // Only the plan's `OFFSET 0` first page is safe to strip. A
        // user-written `OFFSET > 0` must keep its explicit offset semantics.
        if offset == Some(0) {
            let base = caps.get(1).map(|value| value.as_str().trim().to_string()).unwrap_or_default();
            (base, limit)
        } else {
            (trimmed.to_string(), limit)
        }
    } else {
        (trimmed.to_string(), None)
    }
}

async fn execute_sql_query(
    client: &EsClient,

View on GitHub (pinned to c0390bff16)