(toks: &[Tok<'_>])
| 87 | } |
| 88 | |
| 89 | pub(super) fn parse_algo(toks: &[Tok<'_>]) -> Option<NodedbStatement> { |
| 90 | let algorithm = |
| 91 | super::helpers::find_keyword(toks, "ALGO").and_then(|i| match toks.get(i + 1)? { |
| 92 | Tok::Word(w) => Some(w.to_ascii_uppercase()), |
| 93 | _ => None, |
| 94 | })?; |
| 95 | |
| 96 | // Accept either a bare word (`ON users`) or a quoted literal (`ON 'users'`) |
| 97 | // so clients can escape collection names safely. |
| 98 | // |
| 99 | // Reject the `ON (subquery)` form early: the tokenizer strips `(` and `)`, |
| 100 | // so `ON (SELECT …)` becomes the token sequence `[ON, SELECT, …]`. |
| 101 | // `quoted_after("ON")` would return `"SELECT"` which would be silently |
| 102 | // stored as the collection name and then ignored — producing tenant-wide |
| 103 | // results. Returning None here causes the statement to be treated as |
| 104 | // unparseable, surfacing a structured error rather than silent wrong data. |
| 105 | let collection_raw = quoted_after(toks, "ON")?; |
| 106 | const SUBQUERY_KEYWORDS: &[&str] = &["SELECT", "WITH", "VALUES", "TABLE"]; |
| 107 | if SUBQUERY_KEYWORDS |
| 108 | .iter() |
| 109 | .any(|kw| collection_raw.eq_ignore_ascii_case(kw)) |
| 110 | { |
| 111 | return None; |
| 112 | } |
| 113 | let collection = collection_raw.to_lowercase(); |
| 114 | |
| 115 | Some(NodedbStatement::Graph(GraphStmt::GraphAlgo { |
| 116 | algorithm, |
| 117 | collection, |
| 118 | edge_label: quoted_after(toks, "EDGE_LABEL"), |
| 119 | damping: super::helpers::float_after(toks, "DAMPING"), |
| 120 | tolerance: super::helpers::float_after(toks, "TOLERANCE"), |
| 121 | resolution: super::helpers::float_after(toks, "RESOLUTION"), |
| 122 | max_iterations: usize_after(toks, "ITERATIONS"), |
| 123 | sample_size: usize_after(toks, "SAMPLE"), |
| 124 | source_node: quoted_after(toks, "FROM").or_else(|| quoted_after(toks, "SOURCE")), |
| 125 | direction: word_after(toks, "DIRECTION"), |
| 126 | mode: word_after(toks, "MODE"), |
| 127 | personalization: super::helpers::object_after(toks, "PERSONALIZATION"), |
| 128 | })) |
| 129 | } |
| 130 | |
| 131 | /// Parse `GRAPH RAG FUSION ON <collection> QUERY ARRAY[…] [options…]`. |
| 132 | /// |
no test coverage detected