Skip to main content

cqlite_core/cql/
schema_integration.rs

1//! Schema parsing integration with new parser abstraction layer
2//!
3//! This module provides updated schema parsing that uses the new parser
4//! abstraction layer while maintaining full backward compatibility.
5
6use super::{
7    ast::CqlStatement,
8    config::{ParserBackend, ParserConfig},
9    factory::ParserFactory,
10    traits::CqlVisitor,
11    visitor::SchemaBuilderVisitor,
12    UseCase,
13};
14use crate::error::{Error, Result};
15use crate::schema::TableSchema;
16
17/// Configuration for schema parsing
18#[derive(Debug, Clone)]
19pub struct SchemaParserConfig {
20    /// Parser backend to use
21    pub backend: ParserBackend,
22    /// Whether to enable strict validation (default: true)
23    pub strict_validation: bool,
24    /// Whether to allow experimental features (default: false)
25    pub allow_experimental: bool,
26    /// Timeout for parsing operations (default: 30s)
27    pub timeout_secs: u64,
28}
29
30impl Default for SchemaParserConfig {
31    fn default() -> Self {
32        Self {
33            backend: ParserBackend::Auto,
34            strict_validation: true,
35            allow_experimental: false,
36            timeout_secs: 30,
37        }
38    }
39}
40
41impl SchemaParserConfig {
42    /// Create a fast configuration optimized for performance
43    pub fn fast() -> Self {
44        Self {
45            backend: ParserBackend::Nom,
46            strict_validation: false,
47            allow_experimental: false,
48            timeout_secs: 10,
49        }
50    }
51
52    /// Create a strict configuration with maximum validation
53    pub fn strict() -> Self {
54        Self {
55            backend: ParserBackend::Antlr,
56            strict_validation: true,
57            allow_experimental: false,
58            timeout_secs: 60,
59        }
60    }
61
62    /// Create configuration for a specific use case
63    pub fn for_use_case(use_case: UseCase) -> Self {
64        let backend = ParserFactory::recommend_backend(use_case.clone());
65        Self {
66            backend,
67            strict_validation: matches!(use_case, UseCase::Production | UseCase::Development),
68            allow_experimental: matches!(use_case, UseCase::Development),
69            timeout_secs: match use_case {
70                UseCase::HighPerformance | UseCase::Embedded => 10,
71                UseCase::Interactive => 5,
72                UseCase::Batch => 300,
73                _ => 30,
74            },
75        }
76    }
77}
78
79/// Parse a CQL CREATE TABLE statement using the parser abstraction layer.
80///
81/// This is the main entry point for schema parsing. Passing `None` for `config`
82/// uses [`SchemaParserConfig::default`].
83///
84/// # Example
85/// ```rust,no_run
86/// # tokio_test::block_on(async {
87/// use cqlite_core::cql::schema_integration::parse_cql_schema_enhanced;
88///
89/// let cql = "CREATE TABLE users (id UUID PRIMARY KEY, name TEXT, age INT)";
90/// let schema = parse_cql_schema_enhanced(cql, None).await.unwrap();
91/// assert_eq!(schema.table, "users");
92/// # });
93/// ```
94pub async fn parse_cql_schema_enhanced(
95    cql: &str,
96    config: Option<SchemaParserConfig>,
97) -> Result<TableSchema> {
98    let config = config.unwrap_or_default();
99
100    let parser_config = ParserConfig::default()
101        .with_backend(config.backend)
102        .with_strict_validation(config.strict_validation)
103        .with_timeout(std::time::Duration::from_secs(config.timeout_secs));
104
105    let parser = ParserFactory::create(parser_config)?;
106    let statement = parser.parse(cql).await?;
107
108    let mut visitor = SchemaBuilderVisitor;
109    visitor.visit_statement(&statement)
110}
111
112/// Parse a CQL CREATE TABLE statement with default configuration.
113pub async fn parse_cql_schema_simple(cql: &str) -> Result<TableSchema> {
114    parse_cql_schema_enhanced(cql, None).await
115}
116
117/// Parse a CQL CREATE TABLE statement using the fast (nom, minimal validation) preset.
118pub async fn parse_cql_schema_fast(cql: &str) -> Result<TableSchema> {
119    parse_cql_schema_enhanced(cql, Some(SchemaParserConfig::fast())).await
120}
121
122/// Parse a CQL CREATE TABLE statement using the strict (ANTLR, full validation) preset.
123pub async fn parse_cql_schema_strict(cql: &str) -> Result<TableSchema> {
124    parse_cql_schema_enhanced(cql, Some(SchemaParserConfig::strict())).await
125}
126
127/// Parse multiple CQL CREATE TABLE statements sequentially using a shared configuration.
128pub async fn parse_cql_schemas_batch(
129    statements: Vec<&str>,
130    config: Option<SchemaParserConfig>,
131) -> Result<Vec<TableSchema>> {
132    let config = config.unwrap_or_default();
133    let mut schemas = Vec::with_capacity(statements.len());
134
135    for statement in statements {
136        schemas.push(parse_cql_schema_enhanced(statement, Some(config.clone())).await?);
137    }
138
139    Ok(schemas)
140}
141
142/// Validate CQL CREATE TABLE syntax without building the full schema.
143///
144/// Uses `ParserBackend::Auto` when `backend` is `None`.
145#[allow(dead_code)]
146pub async fn validate_cql_schema_syntax(cql: &str, backend: Option<ParserBackend>) -> Result<bool> {
147    let backend = backend.unwrap_or(ParserBackend::Auto);
148    let parser = ParserFactory::create(ParserConfig::minimal().with_backend(backend))?;
149    Ok(parser.validate_syntax(cql))
150}
151
152/// Extract just the table name from a CQL CREATE TABLE statement.
153pub async fn extract_table_name_enhanced(cql: &str) -> Result<String> {
154    let parser = ParserFactory::create(ParserConfig::minimal().with_backend(ParserBackend::Nom))?;
155    let statement = parser.parse(cql).await?;
156
157    match statement {
158        CqlStatement::CreateTable(create_table) => Ok(create_table.table.name.name),
159        _ => Err(Error::invalid_input(
160            "Not a CREATE TABLE statement".to_string(),
161        )),
162    }
163}
164
165/// Backward-compatibility wrapper matching the original `nom::IResult` signature.
166///
167/// **DEPRECATED**: Prefer `cqlite_core::schema::parse_cql_schema()` — it is synchronous
168/// and does not require a tokio runtime. Delegates to the nom backend directly to
169/// avoid runtime-creation overhead on every call.
170#[deprecated(
171    since = "0.2.0",
172    note = "Use cqlite_core::schema::parse_cql_schema() instead - it's synchronous and more efficient"
173)]
174pub fn parse_cql_schema_compat(cql: &str) -> nom::IResult<&str, TableSchema> {
175    use super::nom_backend::NomParser;
176
177    fn nom_err(cql: &str) -> nom::Err<nom::error::Error<&str>> {
178        nom::Err::Error(nom::error::Error::new(cql, nom::error::ErrorKind::Fail))
179    }
180
181    let parser = NomParser::new(ParserConfig::minimal()).map_err(|_| nom_err(cql))?;
182    parser
183        .parse_create_table_to_schema(cql)
184        .map(|schema| ("", schema))
185        .map_err(|_| nom_err(cql))
186}
187
188/// Returns `true` if `pattern` matches either the unqualified table name or the
189/// fully qualified `keyspace.table` form.
190pub fn table_name_matches_enhanced(schema: &TableSchema, pattern: &str) -> bool {
191    schema.table == pattern || format!("{}.{}", schema.keyspace, schema.table) == pattern
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[tokio::test]
199    #[ignore = "M2+ feature; gated for M1"]
200    async fn test_parse_cql_schema_enhanced() {
201        let cql = r"
202            CREATE TABLE test_keyspace.users (
203                id UUID PRIMARY KEY,
204                name TEXT,
205                age INT,
206                email TEXT
207            )
208        ";
209
210        let schema = parse_cql_schema_enhanced(cql, None).await.unwrap();
211
212        assert_eq!(schema.keyspace, "test_keyspace");
213        assert_eq!(schema.table, "users");
214        assert_eq!(schema.partition_keys.len(), 1);
215        assert_eq!(schema.partition_keys[0].name, "id");
216        assert_eq!(schema.columns.len(), 4);
217    }
218
219    #[tokio::test]
220    #[ignore = "M2+ feature; gated for M1"]
221    async fn test_parse_cql_schema_simple() {
222        let cql = "CREATE TABLE simple_table (id TEXT PRIMARY KEY, value INT)";
223
224        let schema = parse_cql_schema_simple(cql).await.unwrap();
225
226        assert_eq!(schema.table, "simple_table");
227        assert_eq!(schema.partition_keys.len(), 1);
228        assert_eq!(schema.columns.len(), 2);
229    }
230
231    #[tokio::test]
232    async fn test_parse_cql_schema_fast() {
233        let cql = "CREATE TABLE fast_table (pk UUID PRIMARY KEY, data BLOB)";
234
235        let schema = parse_cql_schema_fast(cql).await.unwrap();
236
237        assert_eq!(schema.table, "fast_table");
238        assert_eq!(schema.partition_keys[0].data_type, "uuid");
239    }
240
241    #[tokio::test]
242    #[ignore = "M2+ feature; gated for M1"]
243    async fn test_parse_cql_schemas_batch() {
244        let statements = vec![
245            "CREATE TABLE table1 (id UUID PRIMARY KEY, name TEXT)",
246            "CREATE TABLE table2 (pk INT PRIMARY KEY, value BIGINT)",
247        ];
248
249        let schemas = parse_cql_schemas_batch(statements, None).await.unwrap();
250
251        assert_eq!(schemas.len(), 2);
252        assert_eq!(schemas[0].table, "table1");
253        assert_eq!(schemas[1].table, "table2");
254    }
255
256    #[tokio::test]
257    #[ignore = "M2+ feature; gated for M1"]
258    async fn test_validate_cql_schema_syntax() {
259        let valid_cql = "CREATE TABLE test (id UUID PRIMARY KEY)";
260        let invalid_cql = "CREATE INVALID SYNTAX";
261
262        let valid_result = validate_cql_schema_syntax(valid_cql, None).await.unwrap();
263        let invalid_result = validate_cql_schema_syntax(invalid_cql, None).await.unwrap();
264
265        assert!(valid_result);
266        assert!(!invalid_result);
267    }
268
269    #[tokio::test]
270    async fn test_extract_table_name() {
271        let cql = "CREATE TABLE my_keyspace.my_table (id UUID PRIMARY KEY)";
272
273        let table_name = extract_table_name_enhanced(cql).await.unwrap();
274
275        assert_eq!(table_name, "my_table");
276    }
277
278    #[test]
279    fn test_schema_parser_config() {
280        let default_config = SchemaParserConfig::default();
281        assert!(matches!(default_config.backend, ParserBackend::Auto));
282        assert!(default_config.strict_validation);
283
284        let fast_config = SchemaParserConfig::fast();
285        assert!(matches!(fast_config.backend, ParserBackend::Nom));
286        assert!(!fast_config.strict_validation);
287
288        let strict_config = SchemaParserConfig::strict();
289        assert!(matches!(strict_config.backend, ParserBackend::Antlr));
290        assert!(strict_config.strict_validation);
291    }
292
293    #[test]
294    fn test_table_name_matches() {
295        let schema = TableSchema {
296            keyspace: "test_ks".to_string(),
297            table: "test_table".to_string(),
298            partition_keys: vec![],
299            clustering_keys: vec![],
300            columns: vec![],
301            comments: std::collections::HashMap::new(),
302        };
303
304        assert!(table_name_matches_enhanced(&schema, "test_table"));
305        assert!(table_name_matches_enhanced(&schema, "test_ks.test_table"));
306        assert!(!table_name_matches_enhanced(&schema, "other_table"));
307    }
308}