Handle a SQL statement: transaction control, SET/SHOW, DDL, or DataFusion. `sql_params`, when present, carries the caller's bound values for `$1`, `$2`, … placeholders in `sql`. The handler renders each value as a SQL literal via `value_to_sql_literal` and substitutes the placeholders before any other dispatch — DDL routing, planner, transaction buffer — so every downstream sees one canonical SQL
(
ctx: &DispatchCtx<'_>,
seq: u64,
sql: &str,
sql_params: Option<&[Value]>,
)
| 27 | /// string with literal values in place of placeholders. `None` (the |
| 28 | /// common case) routes the SQL through unmodified. |
| 29 | pub(crate) async fn handle_sql( |
| 30 | ctx: &DispatchCtx<'_>, |
| 31 | seq: u64, |
| 32 | sql: &str, |
| 33 | sql_params: Option<&[Value]>, |
| 34 | ) -> NativeResponse { |
| 35 | // Inline bound parameters before any dispatch — keeps the |
| 36 | // substitution invariant in one place so the DDL router, planner, |
| 37 | // and transaction buffer all see the same SQL shape regardless of |
| 38 | // whether the caller sent params or inlined values directly. |
| 39 | let substituted: Option<String> = match sql_params { |
| 40 | Some(params) if !params.is_empty() => match inline_params(sql, params) { |
| 41 | Ok(s) => Some(s), |
| 42 | Err(msg) => return NativeResponse::error(seq, "42P02", msg), |
| 43 | }, |
| 44 | _ => None, |
| 45 | }; |
| 46 | let sql = substituted.as_deref().unwrap_or(sql); |
| 47 | let sql_trimmed = sql.trim(); |
| 48 | let upper = sql_trimmed.to_uppercase(); |
| 49 | |
| 50 | ctx.sessions.ensure_session(*ctx.peer_addr); |
| 51 | |
| 52 | if sql_trimmed.is_empty() || sql_trimmed == ";" { |
| 53 | return NativeResponse::ok(seq); |
| 54 | } |
| 55 | |
| 56 | // Transaction control. |
| 57 | if upper == "BEGIN" || upper == "BEGIN TRANSACTION" || upper == "START TRANSACTION" { |
| 58 | return handle_begin(ctx, seq); |
| 59 | } |
| 60 | if upper == "COMMIT" || upper == "END" || upper == "END TRANSACTION" { |
| 61 | return handle_commit(ctx, seq).await; |
| 62 | } |
| 63 | if upper == "ROLLBACK" || upper == "ABORT" { |
| 64 | return handle_rollback(ctx, seq); |
| 65 | } |
| 66 | if upper.starts_with("SAVEPOINT ") { |
| 67 | return NativeResponse::status_row(seq, "SAVEPOINT"); |
| 68 | } |
| 69 | if upper.starts_with("RELEASE SAVEPOINT ") || upper.starts_with("RELEASE ") { |
| 70 | return NativeResponse::status_row(seq, "RELEASE"); |
| 71 | } |
| 72 | if upper.starts_with("ROLLBACK TO ") { |
| 73 | return NativeResponse::status_row(seq, "ROLLBACK"); |
| 74 | } |
| 75 | |
| 76 | if ctx.sessions.transaction_state(ctx.peer_addr) == TransactionState::Failed { |
| 77 | return NativeResponse::error( |
| 78 | seq, |
| 79 | "25P02", |
| 80 | "current transaction is aborted, commands ignored until end of transaction block", |
| 81 | ); |
| 82 | } |
| 83 | |
| 84 | // SET / SHOW / RESET. |
| 85 | if upper.starts_with("SET ") { |
| 86 | return handle_set_sql(ctx, seq, sql_trimmed); |
no test coverage detected