| 9 | use crate::error::SqlError; |
| 10 | |
| 11 | pub(super) fn parse_mirror_database(parts: &[&str]) -> Result<NodedbStatement, SqlError> { |
| 12 | // The source is specified as `<cluster_id>.<database_name>`, allowing the |
| 13 | // handler to set up a cross-cluster QUIC link to the correct source cluster. |
| 14 | let local_name = parts |
| 15 | .get(2) |
| 16 | .copied() |
| 17 | .ok_or_else(|| SqlError::Parse { |
| 18 | detail: "MIRROR DATABASE requires a local name".into(), |
| 19 | })? |
| 20 | .trim_matches('"') |
| 21 | .to_string(); |
| 22 | let from_idx = parts |
| 23 | .iter() |
| 24 | .position(|w| w.to_uppercase() == "FROM") |
| 25 | .ok_or_else(|| SqlError::Parse { |
| 26 | detail: "MIRROR DATABASE requires FROM <source_cluster>.<source_database>".into(), |
| 27 | })?; |
| 28 | let source_token = parts |
| 29 | .get(from_idx + 1) |
| 30 | .copied() |
| 31 | .ok_or_else(|| SqlError::Parse { |
| 32 | detail: "MIRROR DATABASE FROM requires <source_cluster>.<source_database>".into(), |
| 33 | })? |
| 34 | .trim_matches('"'); |
| 35 | |
| 36 | // Split on the first '.' to extract cluster.database. If there is no dot |
| 37 | // the whole token is treated as the source_cluster and source_database |
| 38 | // defaults to the same identifier (same-name convention for local testing). |
| 39 | let (source_cluster, source_database) = match source_token.find('.') { |
| 40 | Some(dot_pos) => { |
| 41 | let cluster = source_token[..dot_pos].trim_matches('"').to_string(); |
| 42 | let database = source_token[dot_pos + 1..].trim_matches('"').to_string(); |
| 43 | if cluster.is_empty() || database.is_empty() { |
| 44 | return Err(SqlError::Parse { |
| 45 | detail: format!( |
| 46 | "MIRROR DATABASE FROM: invalid source '{source_token}'; \ |
| 47 | expected <source_cluster>.<source_database>" |
| 48 | ), |
| 49 | }); |
| 50 | } |
| 51 | (cluster, database) |
| 52 | } |
| 53 | None => { |
| 54 | let name = source_token.to_string(); |
| 55 | (name.clone(), name) |
| 56 | } |
| 57 | }; |
| 58 | |
| 59 | // MODE = sync | async (optional; default async) |
| 60 | let mode = parts |
| 61 | .windows(3) |
| 62 | .find(|w| w[0].to_uppercase() == "MODE" && w[1] == "=") |
| 63 | .map(|w| match w[2].to_uppercase().as_str() { |
| 64 | "SYNC" => Ok(MirrorMode::Sync), |
| 65 | "ASYNC" => Ok(MirrorMode::Async), |
| 66 | other => Err(SqlError::Parse { |
| 67 | detail: format!("MIRROR DATABASE MODE: expected 'sync' or 'async', got '{other}'"), |
| 68 | }), |