Pre-process a SQL string, rewriting NodeDB-specific syntax. Returns `Ok(None)` if no rewriting was needed. Temporal clause parse errors bubble up as `SqlError::Parse` so they surface to the caller identically to sqlparser errors.
(sql: &str)
| 40 | /// errors bubble up as `SqlError::Parse` so they surface to the caller |
| 41 | /// identically to sqlparser errors. |
| 42 | pub fn preprocess(sql: &str) -> Result<Option<PreprocessedSql>, SqlError> { |
| 43 | let trimmed = sql.trim(); |
| 44 | |
| 45 | // Extract temporal clauses first — they can appear in both SELECT and |
| 46 | // INSERT...SELECT, and stripping them before the UPSERT/object-literal |
| 47 | // rewrites keeps those rewriters pattern-free of NodeDB extensions. |
| 48 | let (temporal_sql, temporal) = |
| 49 | match extract_temporal(trimmed).map_err(|e| SqlError::Parse { detail: e.0 })? { |
| 50 | Some(ex) => (ex.sql, ex.temporal), |
| 51 | None => (trimmed.to_string(), TemporalScope::default()), |
| 52 | }; |
| 53 | let any_temporal = temporal != TemporalScope::default(); |
| 54 | |
| 55 | // Rewrite `SEARCH <coll> USING VECTOR(...)` to canonical |
| 56 | // `SELECT * FROM <coll> ORDER BY vector_distance(...) LIMIT k` before any |
| 57 | // first-word dispatch — the rewritten form re-enters the rest of the |
| 58 | // pipeline as a plain SELECT. |
| 59 | let (temporal_sql, search_vector_rewritten) = |
| 60 | match try_rewrite_search_using_vector(&temporal_sql) { |
| 61 | Some(rewritten) => (rewritten, true), |
| 62 | None => (temporal_sql, false), |
| 63 | }; |
| 64 | |
| 65 | let first_word = first_sql_word(&temporal_sql) |
| 66 | .map(|w| w.to_uppercase()) |
| 67 | .unwrap_or_default(); |
| 68 | let is_upsert = first_word == "UPSERT"; |
| 69 | |
| 70 | if is_upsert { |
| 71 | let rewritten = format!("INSERT INTO {}", &temporal_sql["UPSERT INTO ".len()..]); |
| 72 | if let Some(result) = try_rewrite_object_literal(&rewritten) { |
| 73 | return Ok(Some(PreprocessedSql { |
| 74 | sql: result, |
| 75 | is_upsert: true, |
| 76 | temporal, |
| 77 | })); |
| 78 | } |
| 79 | return Ok(Some(PreprocessedSql { |
| 80 | sql: rewritten, |
| 81 | is_upsert: true, |
| 82 | temporal, |
| 83 | })); |
| 84 | } |
| 85 | |
| 86 | if first_word == "INSERT" |
| 87 | && let Some(result) = try_rewrite_object_literal(&temporal_sql) |
| 88 | { |
| 89 | return Ok(Some(PreprocessedSql { |
| 90 | sql: result, |
| 91 | is_upsert: false, |
| 92 | temporal, |
| 93 | })); |
| 94 | } |
| 95 | |
| 96 | let mut sql_buf = temporal_sql; |
| 97 | let mut any_rewrite = any_temporal || search_vector_rewritten; |
| 98 | |
| 99 | if has_operator_outside_literals(&sql_buf, "<->") |