1use crate::error::Result;
7use async_trait::async_trait;
8
9use super::{
10 ast::*,
11 config::ParserConfig,
12 error::ParserError,
13 traits::{CqlParser, ParserBackendInfo, ParserFeature, PerformanceCharacteristics},
14};
15use crate::schema::TableSchema;
16
17#[derive(Debug)]
19pub struct NomParser {}
20
21impl NomParser {
22 pub fn new(config: ParserConfig) -> Result<Self> {
24 Self::validate_config(&config)?;
25 Ok(Self {})
26 }
27
28 fn validate_config(config: &ParserConfig) -> Result<()> {
30 use super::config::ParserFeature;
31
32 if config.has_feature(&ParserFeature::CodeCompletion) {
33 return Err(ParserError::unsupported_feature("nom", "code completion").into());
34 }
35 if config.has_feature(&ParserFeature::SyntaxHighlighting) {
36 return Err(ParserError::unsupported_feature("nom", "syntax highlighting").into());
37 }
38 Ok(())
39 }
40
41 pub fn parse_create_table_to_schema(&self, input: &str) -> Result<TableSchema> {
43 match self.parse_create_table_statement(input)? {
44 CqlStatement::CreateTable(ast) => Ok(self.convert_ast_to_table_schema(&ast)),
45 _ => Err(ParserError::syntax(
46 "Expected CREATE TABLE statement",
47 super::traits::SourcePosition::start(),
48 )
49 .into()),
50 }
51 }
52
53 pub fn backend_info() -> ParserBackendInfo {
55 ParserBackendInfo {
56 name: "nom".to_string(),
57 version: "7.1".to_string(),
58 features: vec![
59 ParserFeature::Streaming,
60 ParserFeature::Parallel,
61 ParserFeature::Caching,
62 ],
63 performance: PerformanceCharacteristics {
64 statements_per_second: 10_000,
65 memory_per_statement: 1024,
66 startup_time_ms: 1,
67 async_support: true,
68 },
69 }
70 }
71}
72
73#[async_trait]
74impl CqlParser for NomParser {
75 async fn parse(&self, input: &str) -> Result<CqlStatement> {
76 self.parse_statement_impl(input)
77 }
78
79 async fn parse_type(&self, input: &str) -> Result<CqlDataType> {
80 self.parse_type_impl(input)
81 }
82
83 async fn parse_expression(&self, input: &str) -> Result<CqlExpression> {
84 self.parse_expression_impl(input)
85 }
86
87 async fn parse_identifier(&self, input: &str) -> Result<CqlIdentifier> {
88 self.parse_identifier_impl(input)
89 }
90
91 async fn parse_literal(&self, input: &str) -> Result<CqlLiteral> {
92 self.parse_literal_impl(input)
93 }
94
95 async fn parse_column_definitions(&self, input: &str) -> Result<Vec<CqlColumnDef>> {
96 self.parse_column_definitions_impl(input)
97 }
98
99 async fn parse_table_options(&self, input: &str) -> Result<CqlTableOptions> {
100 self.parse_table_options_impl(input)
101 }
102
103 fn validate_syntax(&self, input: &str) -> bool {
104 !input.trim().is_empty() && self.quick_syntax_check(input)
106 }
107
108 fn backend_info(&self) -> ParserBackendInfo {
109 Self::backend_info()
110 }
111}
112
113impl NomParser {
114 fn convert_table_schema_to_ast(
116 &self,
117 schema: crate::schema::TableSchema,
118 ) -> Result<CqlCreateTable> {
119 let columns = schema
120 .columns
121 .iter()
122 .map(|column| {
123 Ok(CqlColumnDef {
124 name: CqlIdentifier::new(&column.name),
125 data_type: self.convert_cql_type_string_to_ast(&column.data_type)?,
126 is_static: column.is_static,
127 })
128 })
129 .collect::<Result<Vec<_>>>()?;
130
131 let primary_key = CqlPrimaryKey {
132 partition_key: schema
133 .partition_keys
134 .iter()
135 .map(|k| CqlIdentifier::new(&k.name))
136 .collect(),
137 clustering_key: schema
138 .clustering_keys
139 .iter()
140 .map(|k| CqlIdentifier::new(&k.name))
141 .collect(),
142 };
143
144 let options = CqlTableOptions {
145 options: schema
146 .comments
147 .into_iter()
148 .map(|(k, v)| (k, CqlLiteral::String(v)))
149 .collect(),
150 };
151
152 Ok(CqlCreateTable {
153 if_not_exists: false,
155 table: CqlTable::new(&schema.table),
156 columns,
157 primary_key,
158 options,
159 })
160 }
161
162 #[allow(clippy::only_used_in_recursion)]
164 fn convert_cql_type_string_to_ast(&self, type_str: &str) -> Result<CqlDataType> {
165 let trimmed = type_str.trim();
166 let type_lower = trimmed.to_lowercase();
167
168 let inner_of = |prefix_len: usize| -> Option<&str> {
171 trimmed[prefix_len..]
172 .rfind('>')
173 .map(|end| trimmed[prefix_len..prefix_len + end].trim())
174 };
175
176 if type_lower.starts_with("list<") {
177 if let Some(inner) = inner_of("list<".len()) {
178 return Ok(CqlDataType::List(Box::new(
179 self.convert_cql_type_string_to_ast(inner)?,
180 )));
181 }
182 }
183 if type_lower.starts_with("set<") {
184 if let Some(inner) = inner_of("set<".len()) {
185 return Ok(CqlDataType::Set(Box::new(
186 self.convert_cql_type_string_to_ast(inner)?,
187 )));
188 }
189 }
190 if type_lower.starts_with("map<") {
191 if let Some(inner) = inner_of("map<".len()) {
192 let parts: Vec<&str> = inner.splitn(2, ',').collect();
193 if parts.len() == 2 {
194 let key_type = self.convert_cql_type_string_to_ast(parts[0].trim())?;
195 let value_type = self.convert_cql_type_string_to_ast(parts[1].trim())?;
196 return Ok(CqlDataType::Map(Box::new(key_type), Box::new(value_type)));
197 }
198 }
199 }
200 if type_lower.starts_with("tuple<") {
201 if let Some(inner) = inner_of("tuple<".len()) {
202 let types = inner
203 .split(',')
204 .map(|part| self.convert_cql_type_string_to_ast(part.trim()))
205 .collect::<Result<Vec<_>>>()?;
206 return Ok(CqlDataType::Tuple(types));
207 }
208 }
209 if type_lower.starts_with("frozen<") {
210 if let Some(inner) = inner_of("frozen<".len()) {
211 return Ok(CqlDataType::Frozen(Box::new(
212 self.convert_cql_type_string_to_ast(inner)?,
213 )));
214 }
215 }
216
217 match type_lower.as_str() {
219 "text" | "varchar" => Ok(CqlDataType::Text),
220 "ascii" => Ok(CqlDataType::Ascii),
221 "int" | "integer" => Ok(CqlDataType::Int),
222 "bigint" | "long" => Ok(CqlDataType::BigInt),
223 "smallint" => Ok(CqlDataType::SmallInt),
224 "tinyint" => Ok(CqlDataType::TinyInt),
225 "boolean" | "bool" => Ok(CqlDataType::Boolean),
226 "float" => Ok(CqlDataType::Float),
227 "double" => Ok(CqlDataType::Double),
228 "decimal" => Ok(CqlDataType::Decimal),
229 "uuid" => Ok(CqlDataType::Uuid),
230 "timeuuid" => Ok(CqlDataType::TimeUuid),
231 "timestamp" => Ok(CqlDataType::Timestamp),
232 "date" => Ok(CqlDataType::Date),
233 "time" => Ok(CqlDataType::Time),
234 "blob" => Ok(CqlDataType::Blob),
235 "inet" => Ok(CqlDataType::Inet),
236 "duration" => Ok(CqlDataType::Duration),
237 "varint" => Ok(CqlDataType::Varint),
238 "counter" => Ok(CqlDataType::Counter),
239 _ => Ok(CqlDataType::Custom(type_str.to_string())),
241 }
242 }
243
244 pub fn convert_ast_to_table_schema(&self, ast: &CqlCreateTable) -> TableSchema {
246 use crate::schema::{ClusteringColumn, Column, KeyColumn};
247
248 let type_of = |name: &str| -> String {
251 ast.columns
252 .iter()
253 .find(|col| col.name.name == name)
254 .map(|col| self.convert_ast_type_to_string(&col.data_type))
255 .unwrap_or_else(|| "text".to_string())
256 };
257
258 let partition_keys = ast
259 .primary_key
260 .partition_key
261 .iter()
262 .enumerate()
263 .map(|(pos, key)| KeyColumn {
264 name: key.name.clone(),
265 data_type: type_of(&key.name),
266 position: pos,
267 })
268 .collect();
269
270 let clustering_keys = ast
271 .primary_key
272 .clustering_key
273 .iter()
274 .enumerate()
275 .map(|(pos, key)| ClusteringColumn {
276 name: key.name.clone(),
277 data_type: type_of(&key.name),
278 position: pos,
279 order: crate::schema::ClusteringOrder::Asc,
280 })
281 .collect();
282
283 let columns = ast
284 .columns
285 .iter()
286 .map(|col| Column {
287 name: col.name.name.clone(),
288 data_type: self.convert_ast_type_to_string(&col.data_type),
289 nullable: true,
290 default: None,
291 is_static: col.is_static,
292 })
293 .collect();
294
295 TableSchema {
296 keyspace: "default".to_string(),
298 table: ast.table.name.name.clone(),
299 partition_keys,
300 clustering_keys,
301 columns,
302 comments: ast
303 .options
304 .options
305 .iter()
306 .map(|(k, v)| (k.clone(), format!("{:?}", v)))
307 .collect(),
308 }
309 }
310
311 #[allow(clippy::only_used_in_recursion)]
313 fn convert_ast_type_to_string(&self, ast_type: &CqlDataType) -> String {
314 match ast_type {
315 CqlDataType::Text => "text".to_string(),
316 CqlDataType::Ascii => "ascii".to_string(),
317 CqlDataType::Int => "int".to_string(),
318 CqlDataType::BigInt => "bigint".to_string(),
319 CqlDataType::SmallInt => "smallint".to_string(),
320 CqlDataType::TinyInt => "tinyint".to_string(),
321 CqlDataType::Boolean => "boolean".to_string(),
322 CqlDataType::Float => "float".to_string(),
323 CqlDataType::Double => "double".to_string(),
324 CqlDataType::Decimal => "decimal".to_string(),
325 CqlDataType::Uuid => "uuid".to_string(),
326 CqlDataType::TimeUuid => "timeuuid".to_string(),
327 CqlDataType::Timestamp => "timestamp".to_string(),
328 CqlDataType::Date => "date".to_string(),
329 CqlDataType::Time => "time".to_string(),
330 CqlDataType::Blob => "blob".to_string(),
331 CqlDataType::Inet => "inet".to_string(),
332 CqlDataType::Duration => "duration".to_string(),
333 CqlDataType::Varint => "varint".to_string(),
334 CqlDataType::Counter => "counter".to_string(),
335 CqlDataType::List(inner) => format!("list<{}>", self.convert_ast_type_to_string(inner)),
336 CqlDataType::Set(inner) => format!("set<{}>", self.convert_ast_type_to_string(inner)),
337 CqlDataType::Map(key, value) => format!(
338 "map<{}, {}>",
339 self.convert_ast_type_to_string(key),
340 self.convert_ast_type_to_string(value)
341 ),
342 CqlDataType::Tuple(types) => {
343 let type_strs: Vec<String> = types
344 .iter()
345 .map(|t| self.convert_ast_type_to_string(t))
346 .collect();
347 format!("tuple<{}>", type_strs.join(", "))
348 }
349 CqlDataType::Frozen(inner) => {
350 format!("frozen<{}>", self.convert_ast_type_to_string(inner))
351 }
352 CqlDataType::Custom(name) => name.clone(),
353 CqlDataType::Varchar => "varchar".to_string(),
354 CqlDataType::Udt(name) => name.name.clone(),
355 }
356 }
357
358 fn parse_statement_impl(&self, input: &str) -> Result<CqlStatement> {
360 let trimmed = input.trim();
361
362 fn starts_with_ci(haystack: &str, needle: &str) -> bool {
366 haystack.len() >= needle.len() && haystack[..needle.len()].eq_ignore_ascii_case(needle)
367 }
368
369 if starts_with_ci(trimmed, "create table") {
370 self.parse_create_table_statement(input)
371 } else if starts_with_ci(trimmed, "drop table") {
372 self.parse_drop_table_statement(input)
373 } else if starts_with_ci(trimmed, "select") {
374 self.parse_select_statement(input)
375 } else if starts_with_ci(trimmed, "insert") {
376 self.parse_insert_statement(input)
377 } else if starts_with_ci(trimmed, "update") {
378 self.parse_update_statement(input)
379 } else if starts_with_ci(trimmed, "delete") {
380 self.parse_delete_statement(input)
381 } else {
382 Err(ParserError::syntax(
383 format!("Unsupported statement type: {}", input),
384 super::traits::SourcePosition::start(),
385 )
386 .into())
387 }
388 }
389
390 fn parse_select_statement(&self, input: &str) -> Result<CqlStatement> {
392 let lower = input.to_lowercase();
393 let select = CqlSelect {
394 distinct: lower.contains("distinct"),
395 select_list: vec![CqlSelectItem::Wildcard],
396 from: CqlTable::new("placeholder_table"),
397 where_clause: None,
398 order_by: None,
399 limit: None,
400 allow_filtering: lower.contains("allow filtering"),
401 };
402
403 Ok(CqlStatement::Select(select))
404 }
405
406 #[cfg(feature = "write-support")]
408 fn parse_insert_statement(&self, input: &str) -> Result<CqlStatement> {
409 use super::mutation_parser::parse_insert_statement;
410 let insert = parse_insert_statement(input)?;
411 Ok(CqlStatement::Insert(insert))
412 }
413
414 #[cfg(not(feature = "write-support"))]
416 fn parse_insert_statement(&self, _input: &str) -> Result<CqlStatement> {
417 Err(ParserError::unsupported_feature(
418 "nom",
419 "INSERT statement parsing requires 'write-support' feature",
420 )
421 .into())
422 }
423
424 #[cfg(feature = "write-support")]
426 fn parse_update_statement(&self, input: &str) -> Result<CqlStatement> {
427 use super::mutation_parser::parse_update_statement;
428 let update = parse_update_statement(input)?;
429 Ok(CqlStatement::Update(update))
430 }
431
432 #[cfg(not(feature = "write-support"))]
434 fn parse_update_statement(&self, _input: &str) -> Result<CqlStatement> {
435 Err(ParserError::unsupported_feature(
436 "nom",
437 "UPDATE statement parsing requires 'write-support' feature",
438 )
439 .into())
440 }
441
442 #[cfg(feature = "write-support")]
444 fn parse_delete_statement(&self, input: &str) -> Result<CqlStatement> {
445 use super::mutation_parser::parse_delete_statement;
446 let delete = parse_delete_statement(input)?;
447 Ok(CqlStatement::Delete(delete))
448 }
449
450 #[cfg(not(feature = "write-support"))]
452 fn parse_delete_statement(&self, _input: &str) -> Result<CqlStatement> {
453 Err(ParserError::unsupported_feature(
454 "nom",
455 "DELETE statement parsing requires 'write-support' feature",
456 )
457 .into())
458 }
459
460 fn parse_create_table_statement(&self, input: &str) -> Result<CqlStatement> {
462 let (_, table_schema) =
463 crate::schema::cql_parser::parse_create_table(input).map_err(|e| {
464 ParserError::syntax(
465 format!("Failed to parse CREATE TABLE: {:?}", e),
466 super::traits::SourcePosition::start(),
467 )
468 })?;
469
470 let ast = self.convert_table_schema_to_ast(table_schema)?;
471 Ok(CqlStatement::CreateTable(ast))
472 }
473
474 fn parse_drop_table_statement(&self, _input: &str) -> Result<CqlStatement> {
476 let drop_table = CqlDropTable {
477 if_exists: false,
478 table: CqlTable::new("placeholder_table"),
479 };
480
481 Ok(CqlStatement::DropTable(drop_table))
482 }
483
484 #[allow(clippy::only_used_in_recursion)]
486 fn parse_type_impl(&self, input: &str) -> Result<CqlDataType> {
487 let trimmed = input.trim().to_lowercase();
488
489 match trimmed.as_str() {
490 "text" | "varchar" => return Ok(CqlDataType::Text),
491 "int" | "integer" => return Ok(CqlDataType::Int),
492 "bigint" => return Ok(CqlDataType::BigInt),
493 "uuid" => return Ok(CqlDataType::Uuid),
494 "boolean" | "bool" => return Ok(CqlDataType::Boolean),
495 "timestamp" => return Ok(CqlDataType::Timestamp),
496 "blob" => return Ok(CqlDataType::Blob),
497 _ => {}
498 }
499
500 if trimmed.starts_with("list<") && trimmed.ends_with('>') {
501 let inner_type = self.parse_type_impl(&trimmed[5..trimmed.len() - 1])?;
502 Ok(CqlDataType::List(Box::new(inner_type)))
503 } else if trimmed.starts_with("set<") && trimmed.ends_with('>') {
504 let inner_type = self.parse_type_impl(&trimmed[4..trimmed.len() - 1])?;
505 Ok(CqlDataType::Set(Box::new(inner_type)))
506 } else {
507 Ok(CqlDataType::Custom(input.to_string()))
508 }
509 }
510
511 fn parse_expression_impl(&self, input: &str) -> Result<CqlExpression> {
513 let trimmed = input.trim();
514
515 if trimmed == "?" {
516 Ok(CqlExpression::Parameter(1))
517 } else if let Some(stripped) = trimmed.strip_prefix(':') {
518 Ok(CqlExpression::NamedParameter(stripped.to_string()))
519 } else if trimmed.starts_with('\'') && trimmed.ends_with('\'') {
520 Ok(CqlExpression::Literal(CqlLiteral::String(
521 trimmed[1..trimmed.len() - 1].to_string(),
522 )))
523 } else if let Ok(num) = trimmed.parse::<i64>() {
524 Ok(CqlExpression::Literal(CqlLiteral::Integer(num)))
525 } else if trimmed == "true" || trimmed == "false" {
526 Ok(CqlExpression::Literal(CqlLiteral::Boolean(
527 trimmed == "true",
528 )))
529 } else {
530 Ok(CqlExpression::Column(CqlIdentifier::new(trimmed)))
532 }
533 }
534
535 fn parse_identifier_impl(&self, input: &str) -> Result<CqlIdentifier> {
537 let trimmed = input.trim();
538
539 if trimmed.starts_with('"') && trimmed.ends_with('"') {
540 Ok(CqlIdentifier::quoted(&trimmed[1..trimmed.len() - 1]))
541 } else {
542 Ok(CqlIdentifier::new(trimmed))
543 }
544 }
545
546 fn parse_literal_impl(&self, input: &str) -> Result<CqlLiteral> {
548 let trimmed = input.trim();
549
550 if trimmed == "null" {
551 Ok(CqlLiteral::Null)
552 } else if trimmed == "true" || trimmed == "false" {
553 Ok(CqlLiteral::Boolean(trimmed == "true"))
554 } else if trimmed.starts_with('\'') && trimmed.ends_with('\'') {
555 Ok(CqlLiteral::String(
556 trimmed[1..trimmed.len() - 1].to_string(),
557 ))
558 } else if let Ok(num) = trimmed.parse::<i64>() {
559 Ok(CqlLiteral::Integer(num))
560 } else if let Ok(num) = trimmed.parse::<f64>() {
561 Ok(CqlLiteral::Float(num))
562 } else {
563 Err(ParserError::syntax(
564 format!("Invalid literal: {}", input),
565 super::traits::SourcePosition::start(),
566 )
567 .into())
568 }
569 }
570
571 fn parse_column_definitions_impl(&self, _input: &str) -> Result<Vec<CqlColumnDef>> {
573 Ok(vec![
574 CqlColumnDef {
575 name: CqlIdentifier::new("id"),
576 data_type: CqlDataType::Uuid,
577 is_static: false,
578 },
579 CqlColumnDef {
580 name: CqlIdentifier::new("name"),
581 data_type: CqlDataType::Text,
582 is_static: false,
583 },
584 ])
585 }
586
587 fn parse_table_options_impl(&self, _input: &str) -> Result<CqlTableOptions> {
589 Ok(CqlTableOptions {
590 options: std::collections::HashMap::new(),
591 })
592 }
593
594 fn quick_syntax_check(&self, input: &str) -> bool {
597 let trimmed = input.trim();
598 if trimmed.is_empty() {
599 return false;
600 }
601
602 let mut paren_count: i32 = 0;
603 let mut in_string = false;
604 let mut escape_next = false;
605
606 for ch in trimmed.chars() {
607 if escape_next {
608 escape_next = false;
609 continue;
610 }
611
612 match ch {
613 '\\' if in_string => escape_next = true,
614 '\'' => in_string = !in_string,
615 '(' if !in_string => paren_count += 1,
616 ')' if !in_string => paren_count -= 1,
617 _ => {}
618 }
619
620 if paren_count < 0 {
621 return false;
622 }
623 }
624
625 paren_count == 0 && !in_string
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::super::config::ParserConfig;
632 use super::*;
633
634 #[tokio::test]
635 async fn test_nom_parser_creation() {
636 let config = ParserConfig::default().with_backend(super::super::config::ParserBackend::Nom);
637 let parser = NomParser::new(config).unwrap();
638
639 let info = parser.backend_info();
640 assert_eq!(info.name, "nom");
641 }
642
643 #[cfg(feature = "write-support")]
644 #[tokio::test]
645 async fn test_basic_parsing() {
646 let config = ParserConfig::default();
647 let parser = NomParser::new(config).unwrap();
648
649 let result = parser.parse("SELECT * FROM users").await;
651 assert!(result.is_ok());
652 assert!(matches!(result.unwrap(), CqlStatement::Select(_)));
653
654 let result = parser
656 .parse("INSERT INTO users (id, name) VALUES (?, ?)")
657 .await;
658 assert!(result.is_ok());
659 assert!(matches!(result.unwrap(), CqlStatement::Insert(_)));
660 }
661
662 #[tokio::test]
663 async fn test_type_parsing() {
664 let config = ParserConfig::default();
665 let parser = NomParser::new(config).unwrap();
666
667 let result = parser.parse_type("text").await;
668 assert!(result.is_ok());
669 assert_eq!(result.unwrap(), CqlDataType::Text);
670
671 let result = parser.parse_type("list<int>").await;
672 assert!(result.is_ok());
673 assert!(matches!(result.unwrap(), CqlDataType::List(_)));
674 }
675
676 #[tokio::test]
677 async fn test_expression_parsing() {
678 let config = ParserConfig::default();
679 let parser = NomParser::new(config).unwrap();
680
681 let result = parser.parse_expression("?").await;
682 assert!(result.is_ok());
683 assert!(matches!(result.unwrap(), CqlExpression::Parameter(_)));
684
685 let result = parser.parse_expression("'hello'").await;
686 assert!(result.is_ok());
687 assert!(matches!(
688 result.unwrap(),
689 CqlExpression::Literal(CqlLiteral::String(_))
690 ));
691 }
692
693 #[test]
694 fn test_syntax_validation() {
695 let config = ParserConfig::default();
696 let parser = NomParser::new(config).unwrap();
697
698 assert!(parser.validate_syntax("SELECT * FROM users"));
699 assert!(!parser.validate_syntax(""));
700 assert!(!parser.validate_syntax("SELECT * FROM users ("));
701 assert!(!parser.validate_syntax("SELECT * FROM 'unclosed string"));
702 }
703
704 #[test]
705 fn test_unsupported_features() {
706 use super::super::config::{ParserConfig, ParserFeature};
707
708 let config = ParserConfig::default().with_feature(ParserFeature::CodeCompletion);
709
710 let result = NomParser::new(config);
711 assert!(result.is_err());
712 }
713
714 #[cfg(feature = "write-support")]
715 #[tokio::test]
716 async fn test_parse_insert_through_parser() {
717 let config = ParserConfig::default();
718 let parser = NomParser::new(config).unwrap();
719
720 let cql = "INSERT INTO users (id, name) VALUES (?, ?)";
721 let result = parser.parse(cql).await;
722 assert!(result.is_ok());
723
724 match result.unwrap() {
725 CqlStatement::Insert(insert) => {
726 assert_eq!(insert.table.name.name, "users");
727 assert_eq!(insert.columns.len(), 2);
728 }
729 _ => panic!("Expected INSERT statement"),
730 }
731 }
732
733 #[cfg(feature = "write-support")]
734 #[tokio::test]
735 async fn test_parse_update_through_parser() {
736 let config = ParserConfig::default();
737 let parser = NomParser::new(config).unwrap();
738
739 let cql = "UPDATE users SET name = ? WHERE id = ?";
740 let result = parser.parse(cql).await;
741 assert!(result.is_ok());
742
743 match result.unwrap() {
744 CqlStatement::Update(update) => {
745 assert_eq!(update.table.name.name, "users");
746 assert_eq!(update.assignments.len(), 1);
747 }
748 _ => panic!("Expected UPDATE statement"),
749 }
750 }
751
752 #[cfg(feature = "write-support")]
753 #[tokio::test]
754 async fn test_parse_delete_through_parser() {
755 let config = ParserConfig::default();
756 let parser = NomParser::new(config).unwrap();
757
758 let cql = "DELETE FROM users WHERE id = ?";
759 let result = parser.parse(cql).await;
760 assert!(result.is_ok());
761
762 match result.unwrap() {
763 CqlStatement::Delete(delete) => {
764 assert_eq!(delete.table.name.name, "users");
765 assert!(delete.columns.is_empty());
766 }
767 _ => panic!("Expected DELETE statement"),
768 }
769 }
770
771 #[cfg(not(feature = "write-support"))]
772 #[tokio::test]
773 async fn test_mutation_statements_require_feature() {
774 let config = ParserConfig::default();
775 let parser = NomParser::new(config).unwrap();
776
777 let result = parser.parse("INSERT INTO users (id) VALUES (?)").await;
779 assert!(result.is_err());
780
781 let result = parser.parse("UPDATE users SET name = ? WHERE id = ?").await;
783 assert!(result.is_err());
784
785 let result = parser.parse("DELETE FROM users WHERE id = ?").await;
787 assert!(result.is_err());
788 }
789}