Skip to main content

cqlite_core/cql/
factory.rs

1//! Parser factory for creating parser instances
2//!
3//! This module provides factory functions for creating parser instances
4//! with different backends and configurations.
5
6use crate::error::{Error, Result};
7use std::sync::{Arc, Mutex, OnceLock};
8
9use super::{
10    config::{ParserBackend, ParserConfig},
11    nom_backend::NomParser,
12    traits::{CqlParser, CqlParserFactory, FactoryInfo, ParserBackendInfo},
13};
14
15/// Main parser factory
16#[derive(Debug, Default)]
17pub struct ParserFactory;
18
19impl ParserFactory {
20    /// Create a parser with the default configuration.
21    pub fn create_default() -> Result<Arc<dyn CqlParser + Send + Sync>> {
22        Self::create(ParserConfig::default())
23    }
24
25    /// Create a parser with the specified configuration.
26    pub fn create(mut config: ParserConfig) -> Result<Arc<dyn CqlParser + Send + Sync>> {
27        config.validate().map_err(Error::configuration)?;
28
29        if matches!(config.backend, ParserBackend::Auto) {
30            config.backend = Self::select_optimal_backend(&config);
31        }
32
33        match config.backend.clone() {
34            ParserBackend::Nom => Ok(Arc::new(NomParser::new(config)?)),
35            ParserBackend::Auto => unreachable!("Auto resolved above"),
36            ParserBackend::Custom(name) => Err(Error::configuration(format!(
37                "Custom parser '{}' not available",
38                name
39            ))),
40        }
41    }
42
43    /// Select the backend for an `Auto` configuration.
44    ///
45    /// nom is the only built-in backend, so `Auto` always resolves to it.
46    fn select_optimal_backend(_config: &ParserConfig) -> ParserBackend {
47        ParserBackend::Nom
48    }
49
50    /// Get information about available backends.
51    pub fn get_available_backends() -> Vec<ParserBackendInfo> {
52        vec![NomParser::backend_info()]
53    }
54
55    /// Check whether the given backend can be instantiated.
56    ///
57    /// nom is the only built-in backend; `Auto` resolves to it. Custom backends
58    /// are never available through this factory.
59    pub fn is_backend_available(backend: &ParserBackend) -> bool {
60        match backend {
61            ParserBackend::Nom | ParserBackend::Auto => true,
62            ParserBackend::Custom(_) => false,
63        }
64    }
65
66    /// Recommend a backend for a high-level use case.
67    ///
68    /// Every use case maps to the single built-in nom backend.
69    pub fn recommend_backend(use_case: UseCase) -> ParserBackend {
70        match use_case {
71            UseCase::HighPerformance
72            | UseCase::Embedded
73            | UseCase::Batch
74            | UseCase::Development
75            | UseCase::Interactive
76            | UseCase::Production => ParserBackend::Nom,
77        }
78    }
79
80    /// Create a parser optimized for a specific use case.
81    pub fn create_for_use_case(use_case: UseCase) -> Result<Arc<dyn CqlParser + Send + Sync>> {
82        let backend = Self::recommend_backend(use_case.clone());
83        Self::create(Self::create_config_for_use_case(use_case, backend))
84    }
85
86    fn create_config_for_use_case(use_case: UseCase, backend: ParserBackend) -> ParserConfig {
87        use super::config::ParserFeature;
88        use std::time::Duration;
89
90        match use_case {
91            UseCase::HighPerformance => ParserConfig::fast().with_backend(backend),
92            UseCase::Development => ParserConfig::development().with_backend(backend),
93            UseCase::Production => ParserConfig::default().with_backend(backend),
94            UseCase::Embedded => ParserConfig::minimal().with_backend(backend),
95            UseCase::Interactive => ParserConfig::development()
96                .with_backend(backend)
97                .with_timeout(Duration::from_millis(100)),
98            UseCase::Batch => ParserConfig::fast()
99                .with_backend(backend)
100                .with_feature(ParserFeature::Parallel)
101                .with_timeout(Duration::from_secs(300)),
102        }
103    }
104}
105
106impl CqlParserFactory for ParserFactory {
107    fn create_parser(&self) -> Result<Box<dyn CqlParser>> {
108        self.create_parser_with_config(ParserConfig::default())
109    }
110
111    fn create_parser_with_config(&self, config: ParserConfig) -> Result<Box<dyn CqlParser>> {
112        Ok(Box::new(ParserWrapper {
113            inner: Self::create(config)?,
114        }))
115    }
116
117    fn factory_info(&self) -> FactoryInfo {
118        FactoryInfo {
119            name: "DefaultParserFactory".to_string(),
120            supported_backends: vec!["nom".to_string()],
121            default_backend: "auto".to_string(),
122        }
123    }
124}
125
126/// Adapter turning the shared `Arc<dyn CqlParser + Send + Sync>` returned by
127/// [`ParserFactory::create`] into the `Box<dyn CqlParser>` required by
128/// [`CqlParserFactory`].
129#[derive(Debug)]
130struct ParserWrapper {
131    inner: Arc<dyn CqlParser + Send + Sync>,
132}
133
134#[async_trait::async_trait]
135impl CqlParser for ParserWrapper {
136    async fn parse(&self, input: &str) -> Result<super::ast::CqlStatement> {
137        self.inner.parse(input).await
138    }
139
140    async fn parse_type(&self, input: &str) -> Result<super::ast::CqlDataType> {
141        self.inner.parse_type(input).await
142    }
143
144    async fn parse_expression(&self, input: &str) -> Result<super::ast::CqlExpression> {
145        self.inner.parse_expression(input).await
146    }
147
148    async fn parse_identifier(&self, input: &str) -> Result<super::ast::CqlIdentifier> {
149        self.inner.parse_identifier(input).await
150    }
151
152    async fn parse_literal(&self, input: &str) -> Result<super::ast::CqlLiteral> {
153        self.inner.parse_literal(input).await
154    }
155
156    async fn parse_column_definitions(&self, input: &str) -> Result<Vec<super::ast::CqlColumnDef>> {
157        self.inner.parse_column_definitions(input).await
158    }
159
160    async fn parse_table_options(&self, input: &str) -> Result<super::ast::CqlTableOptions> {
161        self.inner.parse_table_options(input).await
162    }
163
164    fn validate_syntax(&self, input: &str) -> bool {
165        self.inner.validate_syntax(input)
166    }
167
168    fn backend_info(&self) -> super::traits::ParserBackendInfo {
169        self.inner.backend_info()
170    }
171}
172
173/// Use case categories for parser optimization.
174#[derive(Debug, Clone, PartialEq)]
175pub enum UseCase {
176    /// High-performance parsing with minimal overhead.
177    HighPerformance,
178    /// Development environment with rich error messages and debugging.
179    Development,
180    /// Production environment with balanced performance and reliability.
181    Production,
182    /// Embedded systems with strict resource constraints.
183    Embedded,
184    /// Interactive environments requiring fast response times.
185    Interactive,
186    /// Batch processing with large volumes of queries.
187    Batch,
188}
189
190/// Registry for custom parser backends.
191#[derive(Debug, Default)]
192pub struct ParserRegistry {
193    custom_factories: std::collections::HashMap<String, Box<dyn CqlParserFactory + Send + Sync>>,
194}
195
196impl ParserRegistry {
197    pub fn new() -> Self {
198        Self::default()
199    }
200
201    pub fn register_factory(
202        &mut self,
203        name: String,
204        factory: Box<dyn CqlParserFactory + Send + Sync>,
205    ) {
206        self.custom_factories.insert(name, factory);
207    }
208
209    pub fn get_factory(&self, name: &str) -> Option<&(dyn CqlParserFactory + Send + Sync)> {
210        self.custom_factories.get(name).map(|f| f.as_ref())
211    }
212
213    pub fn list_factories(&self) -> Vec<&str> {
214        self.custom_factories.keys().map(|s| s.as_str()).collect()
215    }
216
217    /// Create a parser using a registered factory.
218    pub fn create_with_factory(
219        &self,
220        factory_name: &str,
221        config: ParserConfig,
222    ) -> Result<Box<dyn CqlParser>> {
223        self.get_factory(factory_name)
224            .ok_or_else(|| Error::configuration(format!("Factory '{}' not found", factory_name)))?
225            .create_parser_with_config(config)
226    }
227}
228
229static GLOBAL_REGISTRY: OnceLock<Mutex<ParserRegistry>> = OnceLock::new();
230
231/// Register a factory in the process-wide registry.
232pub fn register_global_factory(name: String, factory: Box<dyn CqlParserFactory + Send + Sync>) {
233    let registry = GLOBAL_REGISTRY.get_or_init(|| Mutex::new(ParserRegistry::new()));
234    let mut guard = registry.lock().unwrap_or_else(|e| e.into_inner());
235    guard.register_factory(name, factory);
236}
237
238/// Benchmark helpers for comparing parser backends.
239pub mod benchmarks {
240    use super::*;
241    use std::time::{Duration, Instant};
242
243    #[derive(Debug, Clone)]
244    pub struct BenchmarkResult {
245        pub backend: String,
246        pub avg_parse_time: Duration,
247        pub min_parse_time: Duration,
248        pub max_parse_time: Duration,
249        pub success_rate: f64,
250        pub errors: Vec<String>,
251    }
252
253    impl BenchmarkResult {
254        fn failed(backend: &ParserBackend, error: String) -> Self {
255            Self {
256                backend: format!("{:?}", backend),
257                avg_parse_time: Duration::ZERO,
258                min_parse_time: Duration::ZERO,
259                max_parse_time: Duration::ZERO,
260                success_rate: 0.0,
261                errors: vec![error],
262            }
263        }
264    }
265
266    #[derive(Debug, Clone)]
267    pub struct BenchmarkConfig {
268        pub iterations: u32,
269        pub timeout: Duration,
270        pub test_cases: Vec<String>,
271    }
272
273    impl Default for BenchmarkConfig {
274        fn default() -> Self {
275            Self {
276                iterations: 100,
277                timeout: Duration::from_secs(1),
278                test_cases: vec![
279                    "SELECT * FROM users".to_string(),
280                    "INSERT INTO users (id, name) VALUES (?, ?)".to_string(),
281                    "UPDATE users SET name = ? WHERE id = ?".to_string(),
282                    "DELETE FROM users WHERE id = ?".to_string(),
283                    "CREATE TABLE test (id UUID PRIMARY KEY, data TEXT)".to_string(),
284                ],
285            }
286        }
287    }
288
289    /// Run benchmarks on all available parser backends.
290    pub async fn benchmark_parsers(config: BenchmarkConfig) -> Vec<BenchmarkResult> {
291        let mut results = Vec::new();
292        for backend in [ParserBackend::Nom] {
293            if ParserFactory::is_backend_available(&backend) {
294                results.push(benchmark_backend(backend, &config).await);
295            }
296        }
297        results
298    }
299
300    async fn benchmark_backend(
301        backend: ParserBackend,
302        config: &BenchmarkConfig,
303    ) -> BenchmarkResult {
304        let parser_config = ParserConfig::default().with_backend(backend.clone());
305        let parser = match ParserFactory::create(parser_config) {
306            Ok(p) => p,
307            Err(e) => {
308                return BenchmarkResult::failed(&backend, format!("Failed to create parser: {}", e))
309            }
310        };
311
312        let mut times = Vec::new();
313        let mut errors = Vec::new();
314        let mut successes: u32 = 0;
315
316        for _ in 0..config.iterations {
317            for test_case in &config.test_cases {
318                let start = Instant::now();
319                match tokio::time::timeout(config.timeout, parser.parse(test_case)).await {
320                    Ok(Ok(_)) => {
321                        successes += 1;
322                        times.push(start.elapsed());
323                    }
324                    Ok(Err(e)) => errors.push(format!("Parse error: {}", e)),
325                    Err(_) => errors.push("Timeout".to_string()),
326                }
327            }
328        }
329
330        let total_attempts = config.iterations * config.test_cases.len() as u32;
331        let success_rate = f64::from(successes) / f64::from(total_attempts);
332        let avg_parse_time = if times.is_empty() {
333            Duration::ZERO
334        } else {
335            times.iter().sum::<Duration>() / times.len() as u32
336        };
337
338        BenchmarkResult {
339            backend: format!("{:?}", backend),
340            avg_parse_time,
341            min_parse_time: times.iter().min().copied().unwrap_or_default(),
342            max_parse_time: times.iter().max().copied().unwrap_or_default(),
343            success_rate,
344            errors,
345        }
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn test_factory_creation() {
355        let factory = ParserFactory;
356        let info = factory.factory_info();
357        assert_eq!(info.name, "DefaultParserFactory");
358        assert!(!info.supported_backends.is_empty());
359    }
360
361    #[test]
362    fn test_backend_availability() {
363        assert!(ParserFactory::is_backend_available(&ParserBackend::Nom));
364        assert!(ParserFactory::is_backend_available(&ParserBackend::Auto));
365        assert!(!ParserFactory::is_backend_available(
366            &ParserBackend::Custom("unknown".to_string())
367        ));
368    }
369
370    #[test]
371    fn test_backend_recommendation() {
372        // Every use case now maps to the single built-in nom backend.
373        for use_case in [
374            UseCase::HighPerformance,
375            UseCase::Development,
376            UseCase::Production,
377            UseCase::Embedded,
378            UseCase::Interactive,
379            UseCase::Batch,
380        ] {
381            assert_eq!(
382                ParserFactory::recommend_backend(use_case),
383                ParserBackend::Nom
384            );
385        }
386    }
387
388    #[test]
389    fn test_auto_backend_selection() {
390        // `Auto` always resolves to nom, regardless of features or strictness.
391        let config = ParserConfig::fast();
392        assert_eq!(
393            ParserFactory::select_optimal_backend(&config),
394            ParserBackend::Nom
395        );
396
397        let config = ParserConfig::strict();
398        assert_eq!(
399            ParserFactory::select_optimal_backend(&config),
400            ParserBackend::Nom
401        );
402    }
403
404    #[test]
405    fn test_create_for_every_use_case_succeeds() {
406        // Regression for issue #1639: a non-functional stub backend used to be
407        // handed out for Development/Interactive and whenever strict_validation
408        // was set, making every parse fail. Every use case must now yield a
409        // working nom parser.
410        for use_case in [
411            UseCase::HighPerformance,
412            UseCase::Development,
413            UseCase::Production,
414            UseCase::Embedded,
415            UseCase::Interactive,
416            UseCase::Batch,
417        ] {
418            let parser = ParserFactory::create_for_use_case(use_case.clone())
419                .unwrap_or_else(|e| panic!("create failed for {:?}: {}", use_case, e));
420            assert_eq!(parser.backend_info().name, "nom", "use case {:?}", use_case);
421        }
422    }
423
424    #[test]
425    fn test_parser_registry() {
426        let registry = ParserRegistry::new();
427        assert!(registry.list_factories().is_empty());
428    }
429}