Skip to main content

cqlite_core/cql/
nom_backend.rs

1//! Nom-based parser implementation
2//!
3//! This module provides a nom-based implementation of the CqlParser trait,
4//! optimized for performance and streaming parsing.
5
6use 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/// Nom-based parser implementation
18#[derive(Debug)]
19pub struct NomParser {}
20
21impl NomParser {
22    /// Create a new nom parser with the given configuration
23    pub fn new(config: ParserConfig) -> Result<Self> {
24        Self::validate_config(&config)?;
25        Ok(Self {})
26    }
27
28    /// Reject configurations that request features the nom backend does not implement.
29    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    /// Parse CQL CREATE TABLE statement and return TableSchema for backward compatibility
42    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    /// Get backend information
54    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        // Quick syntax validation without full parsing
105        !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    /// Convert TableSchema from cql_parser to AST CreateTable statement
115    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            // TODO: detect IF NOT EXISTS from original CQL
154            if_not_exists: false,
155            table: CqlTable::new(&schema.table),
156            columns,
157            primary_key,
158            options,
159        })
160    }
161
162    /// Convert CQL type string to AST data type
163    #[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        // Extract inner string for a parameterized type like `list<...>`.
169        // Returns the slice between the first `<` and the last `>` of `trimmed`.
170        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        // Handle primitive types
218        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            // Assume unknown types are UDTs or custom types
240            _ => Ok(CqlDataType::Custom(type_str.to_string())),
241        }
242    }
243
244    /// Convert AST CreateTable back to TableSchema for backward compatibility
245    pub fn convert_ast_to_table_schema(&self, ast: &CqlCreateTable) -> TableSchema {
246        use crate::schema::{ClusteringColumn, Column, KeyColumn};
247
248        // Look up a column's declared type in the AST; fall back to "text" if
249        // the primary key references a column we somehow didn't see.
250        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            // TODO: extract from table name if qualified
297            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    /// Convert AST data type back to string representation
312    #[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    /// Parse a complete CQL statement using nom parsers
359    fn parse_statement_impl(&self, input: &str) -> Result<CqlStatement> {
360        let trimmed = input.trim();
361
362        // Case-insensitive keyword-prefix dispatch. We check the longest
363        // prefixes first so two-word keywords (e.g. `CREATE TABLE`) win over
364        // their single-word counterparts.
365        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    /// Parse SELECT statement (placeholder)
391    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    /// Parse INSERT statement
407    #[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    /// Parse INSERT statement (stub when write-support is disabled)
415    #[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    /// Parse UPDATE statement
425    #[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    /// Parse UPDATE statement (stub when write-support is disabled)
433    #[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    /// Parse DELETE statement
443    #[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    /// Parse DELETE statement (stub when write-support is disabled)
451    #[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    /// Parse CREATE TABLE statement using the existing nom parser in `schema::cql_parser`.
461    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    /// Parse DROP TABLE statement (placeholder)
475    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    /// Parse data type (placeholder)
485    #[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    /// Parse expression (placeholder)
512    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            // Assume it's a column reference
531            Ok(CqlExpression::Column(CqlIdentifier::new(trimmed)))
532        }
533    }
534
535    /// Parse identifier (placeholder)
536    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    /// Parse literal (placeholder)
547    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    /// Parse column definitions (placeholder)
572    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    /// Parse table options (placeholder)
588    fn parse_table_options_impl(&self, _input: &str) -> Result<CqlTableOptions> {
589        Ok(CqlTableOptions {
590            options: std::collections::HashMap::new(),
591        })
592    }
593
594    /// Quick syntax validation: non-empty input with balanced parentheses and
595    /// closed single-quoted string literals (with backslash escapes).
596    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        // Test SELECT parsing
650        let result = parser.parse("SELECT * FROM users").await;
651        assert!(result.is_ok());
652        assert!(matches!(result.unwrap(), CqlStatement::Select(_)));
653
654        // Test INSERT parsing
655        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        // INSERT should fail without write-support
778        let result = parser.parse("INSERT INTO users (id) VALUES (?)").await;
779        assert!(result.is_err());
780
781        // UPDATE should fail without write-support
782        let result = parser.parse("UPDATE users SET name = ? WHERE id = ?").await;
783        assert!(result.is_err());
784
785        // DELETE should fail without write-support
786        let result = parser.parse("DELETE FROM users WHERE id = ?").await;
787        assert!(result.is_err());
788    }
789}