1use crate::ast::{ItemType, JsonOp, SelectItem};
2use crate::error::ParseError;
3
4pub fn parse_select(select_str: &str) -> Result<Vec<SelectItem>, ParseError> {
58 if select_str.is_empty() {
59 return Ok(Vec::new());
60 }
61
62 if select_str.trim() == "*" {
63 return Ok(vec![SelectItem::wildcard()]);
64 }
65
66 tokenize_and_parse(select_str)
67}
68
69fn tokenize_and_parse(select_str: &str) -> Result<Vec<SelectItem>, ParseError> {
70 let tokens = tokenize(select_str)?;
71 parse_items(&tokens)
72}
73
74fn tokenize(select_str: &str) -> Result<Vec<SelectToken>, ParseError> {
75 let mut tokens = Vec::new();
76 let mut current = String::new();
77 let mut depth = 0;
78
79 for c in select_str.chars() {
80 match c {
81 '(' => {
82 if !current.is_empty() {
83 tokens.push(SelectToken::Text(current.clone()));
84 }
85 tokens.push(SelectToken::OpenParen);
86 current.clear();
87 depth += 1;
88 }
89 ')' => {
90 if !current.is_empty() {
91 tokens.push(SelectToken::Text(current.clone()));
92 current.clear();
93 }
94 tokens.push(SelectToken::CloseParen);
95 depth -= 1;
96 }
97 ',' => {
98 if !current.is_empty() {
99 tokens.push(SelectToken::Text(current.clone()));
100 current.clear();
101 }
102 tokens.push(SelectToken::Comma);
103 }
104 _ => {
105 current.push(c);
106 }
107 }
108 }
109
110 if !current.is_empty() {
111 tokens.push(SelectToken::Text(current));
112 }
113
114 if depth != 0 {
115 return Err(ParseError::UnclosedParenthesisInSelect);
116 }
117
118 Ok(tokens)
119}
120
121#[derive(Debug, Clone, PartialEq)]
122enum SelectToken {
123 Text(String),
124 OpenParen,
125 CloseParen,
126 Comma,
127}
128
129fn parse_items(tokens: &[SelectToken]) -> Result<Vec<SelectItem>, ParseError> {
130 let mut items = Vec::new();
131 let mut index = 0;
132
133 while index < tokens.len() {
134 match &tokens[index] {
135 SelectToken::Text(text) => {
136 let has_children =
137 index + 1 < tokens.len() && matches!(tokens[index + 1], SelectToken::OpenParen);
138
139 let item = parse_item_text(text, has_children)?;
140
141 if matches!(item.item_type, ItemType::Relation | ItemType::Spread) {
142 if !has_children {
143 return Err(ParseError::ExpectedParenthesisAfterRelation);
144 }
145
146 let (children, next_index) = parse_nested_children(tokens, index + 2)?;
147 let item_with_children = item.with_children(children);
148 items.push(item_with_children);
149 index = next_index;
150 } else {
151 items.push(item);
152 index += 1;
153 }
154 }
155 SelectToken::OpenParen => {
156 return Err(ParseError::UnexpectedToken("(".to_string()));
157 }
158 SelectToken::CloseParen => {
159 return Err(ParseError::UnexpectedClosingParenthesis);
160 }
161 SelectToken::Comma => {
162 index += 1;
163 }
164 }
165 }
166
167 Ok(items)
168}
169
170fn parse_nested_children(
171 tokens: &[SelectToken],
172 start: usize,
173) -> Result<(Vec<SelectItem>, usize), ParseError> {
174 let mut children = Vec::new();
175 let mut index = start;
176 let mut depth = 1;
177
178 while index < tokens.len() && depth > 0 {
179 match &tokens[index] {
180 SelectToken::Text(text) => {
181 let has_children =
182 index + 1 < tokens.len() && matches!(tokens[index + 1], SelectToken::OpenParen);
183
184 let item = parse_item_text(text, has_children)?;
185
186 if matches!(item.item_type, ItemType::Relation | ItemType::Spread) {
187 if !has_children {
188 return Err(ParseError::ExpectedParenthesisAfterRelation);
189 }
190
191 let (nested_children, next_index) = parse_nested_children(tokens, index + 2)?;
192 let item_with_children = item.with_children(nested_children);
193 children.push(item_with_children);
194 index = next_index;
195 } else {
196 children.push(item);
197 index += 1;
198 }
199 }
200 SelectToken::OpenParen => {
201 depth += 1;
202 index += 1;
203 }
204 SelectToken::CloseParen => {
205 depth -= 1;
206 if depth == 0 {
207 index += 1;
208 break;
209 }
210 index += 1;
211 }
212 SelectToken::Comma => {
213 index += 1;
214 }
215 }
216 }
217
218 Ok((children, index))
219}
220
221fn parse_item_text(text: &str, has_children: bool) -> Result<SelectItem, ParseError> {
222 let trimmed = text.trim();
223
224 if trimmed.is_empty() {
225 return Err(ParseError::EmptyFieldName);
226 }
227
228 let is_spread = trimmed.starts_with("...");
229 let (name_part, alias) = extract_alias(if is_spread { &trimmed[3..] } else { trimmed })?;
230
231 let (name, hint) = extract_hint(&name_part)?;
232
233 if name.is_empty() {
234 return Err(ParseError::EmptyFieldName);
235 }
236
237 let item_type = if is_spread {
238 ItemType::Spread
239 } else if has_children {
240 ItemType::Relation
241 } else {
242 ItemType::Field
243 };
244
245 let mut item = match item_type {
246 ItemType::Field => SelectItem::field(name.clone()),
247 ItemType::Relation => SelectItem::relation(name.clone()),
248 ItemType::Spread => SelectItem::spread(name.clone()),
249 };
250
251 if let Some(alias_name) = alias {
252 item = item.with_alias(alias_name);
253 }
254
255 if let Some(h) = hint {
256 item = item.with_hint(h);
257 }
258
259 Ok(item)
260}
261
262fn extract_alias(text: &str) -> Result<(String, Option<String>), ParseError> {
263 if text.contains(':') {
264 let parts: Vec<&str> = text.splitn(2, ':').collect();
265 if parts.len() == 2 {
266 Ok((
267 parts[1].trim().to_string(),
268 Some(parts[0].trim().to_string()),
269 ))
270 } else {
271 Ok((text.to_string(), None))
272 }
273 } else {
274 Ok((text.to_string(), None))
275 }
276}
277
278fn extract_hint(text: &str) -> Result<(String, Option<crate::ast::ItemHint>), ParseError> {
279 if let Some(pos) = text.find('!') {
280 let name = text[..pos].to_string();
281 let hint_str = text[pos + 1..].to_string();
282
283 let hint = parse_field_for_hint(&name, &hint_str)?;
284 Ok((name, Some(hint)))
285 } else {
286 Ok((text.to_string(), None))
287 }
288}
289
290fn parse_field_for_hint(name: &str, hint_str: &str) -> Result<crate::ast::ItemHint, ParseError> {
291 match crate::parser::common::field(name) {
292 Ok((_, field)) => {
293 let json_path_vec = || {
294 field
295 .json_path
296 .iter()
297 .map(|op| match op {
298 JsonOp::Arrow(s) | JsonOp::DoubleArrow(s) => s.clone(),
299 JsonOp::ArrayIndex(i) => i.to_string(),
300 })
301 .collect()
302 };
303
304 match (field.json_path.is_empty(), field.cast) {
305 (true, None) => Ok(crate::ast::ItemHint::Inner(hint_str.to_string())),
306 (true, Some(cast)) => Ok(crate::ast::ItemHint::Cast(cast.to_string())),
307 (false, None) => Ok(crate::ast::ItemHint::JsonPath(json_path_vec())),
308 (false, Some(cast)) => Ok(crate::ast::ItemHint::JsonPathCast(
309 json_path_vec(),
310 cast.to_string(),
311 )),
312 }
313 }
314 Err(_) => Ok(crate::ast::ItemHint::Inner(hint_str.to_string())),
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_parse_select_simple() {
324 let result = parse_select("id,name,email");
325 assert!(result.is_ok());
326 let items = result.unwrap();
327 assert_eq!(items.len(), 3);
328 assert_eq!(items[0].name, "id");
329 }
330
331 #[test]
332 fn test_parse_select_wildcard() {
333 let result = parse_select("*");
334 assert!(result.is_ok());
335 let items = result.unwrap();
336 assert_eq!(items.len(), 1);
337 assert_eq!(items[0].name, "*");
338 }
339
340 #[test]
341 fn test_parse_select_with_alias() {
342 let result = parse_select("user_name:name,user_email:email");
343 assert!(result.is_ok());
344 let items = result.unwrap();
345 assert_eq!(items[0].alias, Some("user_name".to_string()));
346 }
347
348 #[test]
349 fn test_parse_select_with_relation() {
350 let result = parse_select("id,client(id,name)");
351 assert!(result.is_ok());
352 let items = result.unwrap();
353 assert_eq!(items.len(), 2);
354 assert_eq!(items[1].item_type, ItemType::Relation);
355 assert_eq!(items[1].children.as_ref().unwrap().len(), 2);
356 }
357
358 #[test]
359 fn test_parse_select_with_spread() {
360 let result = parse_select("id,...profile(name)");
361 assert!(result.is_ok());
362 let items = result.unwrap();
363 assert_eq!(items[1].item_type, ItemType::Spread);
364 }
365
366 #[test]
367 fn test_parse_select_with_hint() {
368 let result = parse_select("author!inner,client!left");
369 assert!(result.is_ok());
370 let items = result.unwrap();
371 assert!(items[0].hint.is_some());
372 }
373
374 #[test]
375 fn test_parse_select_nested_relations() {
376 let result = parse_select("id,client(id,orders(id,total))");
377 assert!(result.is_ok());
378 let items = result.unwrap();
379 assert_eq!(items.len(), 2);
380
381 let client_children = items[1].children.as_ref().unwrap();
382 assert_eq!(client_children.len(), 2);
383 assert_eq!(client_children[1].item_type, ItemType::Relation);
384 }
385
386 #[test]
387 fn test_parse_select_empty() {
388 let result = parse_select("");
389 assert!(result.is_ok());
390 assert!(result.unwrap().is_empty());
391 }
392
393 #[test]
394 fn test_parse_select_unclosed_parenthesis() {
395 let result = parse_select("client(id,name");
396 assert!(matches!(
397 result,
398 Err(ParseError::UnclosedParenthesisInSelect)
399 ));
400 }
401
402 #[test]
405 fn test_many_to_one_join_via_fk() {
406 let items = parse_select("*, profiles(username, avatar_url)").unwrap();
408 assert_eq!(items.len(), 2);
409 assert_eq!(items[0].name, "*");
410 assert_eq!(items[0].item_type, ItemType::Field);
411 assert_eq!(items[1].name, "profiles");
412 assert_eq!(items[1].item_type, ItemType::Relation);
413 let children = items[1].children.as_ref().unwrap();
414 assert_eq!(children.len(), 2);
415 assert_eq!(children[0].name, "username");
416 assert_eq!(children[1].name, "avatar_url");
417 }
418
419 #[test]
420 fn test_one_to_many_join() {
421 let items = parse_select("title, comments(id, body)").unwrap();
423 assert_eq!(items.len(), 2);
424 assert_eq!(items[0].name, "title");
425 assert_eq!(items[1].name, "comments");
426 assert_eq!(items[1].item_type, ItemType::Relation);
427 let children = items[1].children.as_ref().unwrap();
428 assert_eq!(children.len(), 2);
429 assert_eq!(children[0].name, "id");
430 assert_eq!(children[1].name, "body");
431 }
432
433 #[test]
434 fn test_aliased_relation() {
435 let items = parse_select("*, author:profiles(name)").unwrap();
437 assert_eq!(items.len(), 2);
438 assert_eq!(items[1].name, "profiles");
439 assert_eq!(items[1].alias, Some("author".to_string()));
440 assert_eq!(items[1].item_type, ItemType::Relation);
441 let children = items[1].children.as_ref().unwrap();
442 assert_eq!(children.len(), 1);
443 assert_eq!(children[0].name, "name");
444 }
445
446 #[test]
447 fn test_nested_embedding_with_alias() {
448 let items = parse_select("*, comments(id, author:profiles(name))").unwrap();
450 assert_eq!(items.len(), 2);
451 assert_eq!(items[1].name, "comments");
452 assert_eq!(items[1].item_type, ItemType::Relation);
453
454 let children = items[1].children.as_ref().unwrap();
455 assert_eq!(children.len(), 2);
456 assert_eq!(children[0].name, "id");
457
458 assert_eq!(children[1].name, "profiles");
460 assert_eq!(children[1].alias, Some("author".to_string()));
461 assert_eq!(children[1].item_type, ItemType::Relation);
462 let nested = children[1].children.as_ref().unwrap();
463 assert_eq!(nested.len(), 1);
464 assert_eq!(nested[0].name, "name");
465 }
466
467 #[test]
468 fn test_fk_hint_on_relation() {
469 let items = parse_select("*, author:profiles!author_id_fkey(name)").unwrap();
471 assert_eq!(items.len(), 2);
472 assert_eq!(items[1].name, "profiles");
473 assert_eq!(items[1].alias, Some("author".to_string()));
474 assert_eq!(items[1].item_type, ItemType::Relation);
475 assert!(items[1].hint.is_some());
476 assert_eq!(
477 items[1].hint,
478 Some(crate::ast::ItemHint::Inner("author_id_fkey".to_string()))
479 );
480 let children = items[1].children.as_ref().unwrap();
481 assert_eq!(children.len(), 1);
482 assert_eq!(children[0].name, "name");
483 }
484
485 #[test]
486 fn test_fk_hint_without_alias() {
487 let items = parse_select("*, profiles!author_id_fkey(name)").unwrap();
489 assert_eq!(items.len(), 2);
490 assert_eq!(items[1].name, "profiles");
491 assert_eq!(items[1].alias, None);
492 assert_eq!(items[1].item_type, ItemType::Relation);
493 assert!(items[1].hint.is_some());
494 assert_eq!(
495 items[1].hint,
496 Some(crate::ast::ItemHint::Inner("author_id_fkey".to_string()))
497 );
498 }
499
500 #[test]
501 fn test_multiple_relations() {
502 let items = parse_select("id, author:profiles(name), comments(id, body)").unwrap();
504 assert_eq!(items.len(), 3);
505 assert_eq!(items[0].name, "id");
506 assert_eq!(items[0].item_type, ItemType::Field);
507
508 assert_eq!(items[1].name, "profiles");
509 assert_eq!(items[1].alias, Some("author".to_string()));
510 assert_eq!(items[1].item_type, ItemType::Relation);
511
512 assert_eq!(items[2].name, "comments");
513 assert_eq!(items[2].item_type, ItemType::Relation);
514 assert_eq!(items[2].children.as_ref().unwrap().len(), 2);
515 }
516
517 #[test]
518 fn test_deeply_nested_relations() {
519 let items =
521 parse_select("*, posts(id, comments(id, author:profiles(name, avatar_url)))").unwrap();
522 assert_eq!(items.len(), 2);
523
524 let posts = &items[1];
525 assert_eq!(posts.name, "posts");
526 let post_children = posts.children.as_ref().unwrap();
527 assert_eq!(post_children.len(), 2);
528
529 let comments = &post_children[1];
530 assert_eq!(comments.name, "comments");
531 let comment_children = comments.children.as_ref().unwrap();
532 assert_eq!(comment_children.len(), 2);
533
534 let author = &comment_children[1];
535 assert_eq!(author.name, "profiles");
536 assert_eq!(author.alias, Some("author".to_string()));
537 let author_children = author.children.as_ref().unwrap();
538 assert_eq!(author_children.len(), 2);
539 assert_eq!(author_children[0].name, "name");
540 assert_eq!(author_children[1].name, "avatar_url");
541 }
542}