Skip to main content

cqlite_core/cql/
config.rs

1//! Parser configuration and settings
2//!
3//! This module defines configuration options for the parser subsystem,
4//! allowing fine-tuning of parser behavior and backend selection.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::time::Duration;
9
10/// Parser configuration
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct ParserConfig {
13    /// Backend to use for parsing
14    pub backend: ParserBackend,
15
16    /// Timeout for parsing operations
17    pub timeout: Duration,
18
19    /// Maximum depth for nested expressions
20    pub max_expression_depth: u32,
21
22    /// Maximum number of items in collections
23    pub max_collection_size: u32,
24
25    /// Maximum length for string literals
26    pub max_string_length: u32,
27
28    /// Maximum number of parameters in a statement
29    pub max_parameters: u32,
30
31    /// Whether to enable strict validation
32    pub strict_validation: bool,
33
34    /// Whether to allow experimental features
35    pub allow_experimental: bool,
36
37    /// Backend-specific options
38    pub backend_options: HashMap<String, serde_json::Value>,
39
40    /// Features to enable
41    pub features: Vec<ParserFeature>,
42
43    /// Memory limits
44    pub memory_limits: MemoryLimits,
45
46    /// Performance settings
47    pub performance: PerformanceSettings,
48
49    /// Error handling settings
50    pub error_handling: ErrorHandlingSettings,
51
52    /// Memory settings
53    pub memory_settings: MemorySettings,
54
55    /// Security settings
56    pub security_settings: SecuritySettings,
57}
58
59/// Parser backend selection
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub enum ParserBackend {
62    /// Use nom parser (fast, streaming)
63    Nom,
64
65    /// Use ANTLR parser (full-featured, better error recovery)
66    Antlr,
67
68    /// Auto-select best backend based on input characteristics
69    Auto,
70
71    /// Custom backend (for extensions)
72    Custom(String),
73}
74
75/// Parser features that can be enabled/disabled
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub enum ParserFeature {
78    /// Support for streaming/incremental parsing
79    Streaming,
80
81    /// Enhanced error recovery
82    ErrorRecovery,
83
84    /// Syntax highlighting support
85    SyntaxHighlighting,
86
87    /// Code completion support
88    CodeCompletion,
89
90    /// AST transformation support
91    AstTransformation,
92
93    /// Custom operator support
94    CustomOperators,
95
96    /// Parallel parsing support
97    Parallel,
98
99    /// Caching of parse results
100    Caching,
101
102    /// Validation during parsing
103    OnlineValidation,
104
105    /// Performance profiling
106    Profiling,
107}
108
109/// Memory limit settings
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct MemoryLimits {
112    /// Maximum memory usage for AST construction (bytes)
113    pub max_ast_size: u64,
114
115    /// Maximum memory usage for temporary parsing data (bytes)
116    pub max_temp_memory: u64,
117
118    /// Maximum call stack depth
119    pub max_stack_depth: u32,
120
121    /// Maximum number of cached parse results
122    pub max_cache_entries: u32,
123}
124
125/// Performance-related settings
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub struct PerformanceSettings {
128    /// Number of worker threads for parallel parsing
129    pub worker_threads: u32,
130
131    /// Buffer size for streaming parsing (bytes)
132    pub stream_buffer_size: u32,
133
134    /// Whether to enable parse result caching
135    pub enable_caching: bool,
136
137    /// Cache TTL for parse results
138    pub cache_ttl: Duration,
139
140    /// Whether to enable JIT compilation (if supported)
141    pub enable_jit: bool,
142
143    /// Optimization level (0-3)
144    pub optimization_level: u8,
145}
146
147/// Error handling settings
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149pub struct ErrorHandlingSettings {
150    /// Maximum number of errors to collect before stopping
151    pub max_errors: u32,
152
153    /// Whether to continue parsing after recoverable errors
154    pub continue_on_error: bool,
155
156    /// Number of context lines to include in error messages
157    pub error_context_lines: u32,
158
159    /// Whether to include suggestions in error messages
160    pub include_suggestions: bool,
161
162    /// Whether to collect detailed error statistics
163    pub collect_error_stats: bool,
164}
165
166/// Memory-specific settings for parser
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub struct MemorySettings {
169    /// Maximum memory usage for parser operations (bytes)
170    pub max_parser_memory: u64,
171
172    /// Memory allocation strategy
173    pub allocation_strategy: MemoryAllocationStrategy,
174
175    /// Whether to enable memory pooling
176    pub enable_memory_pooling: bool,
177
178    /// Pool size for memory allocations
179    pub memory_pool_size: usize,
180}
181
182/// Memory allocation strategies
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub enum MemoryAllocationStrategy {
185    /// Standard allocation
186    Standard,
187    /// Pool-based allocation
188    Pooled,
189    /// Arena-based allocation
190    Arena,
191}
192
193/// Security settings for parser
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195pub struct SecuritySettings {
196    /// Maximum query depth to prevent stack overflow
197    pub max_query_depth: u32,
198
199    /// Maximum number of tokens in a query
200    pub max_token_count: u32,
201
202    /// Whether to enable input sanitization
203    pub enable_input_sanitization: bool,
204
205    /// Whether to restrict dangerous operations
206    pub restrict_dangerous_operations: bool,
207
208    /// List of blocked keywords
209    pub blocked_keywords: Vec<String>,
210}
211
212impl Default for ParserConfig {
213    fn default() -> Self {
214        Self {
215            backend: ParserBackend::Auto,
216            timeout: Duration::from_secs(30),
217            max_expression_depth: 100,
218            max_collection_size: 10_000,
219            max_string_length: 1_000_000,
220            max_parameters: 1000,
221            strict_validation: true,
222            allow_experimental: false,
223            backend_options: HashMap::new(),
224            features: vec![
225                ParserFeature::ErrorRecovery,
226                ParserFeature::OnlineValidation,
227            ],
228            memory_limits: MemoryLimits::default(),
229            performance: PerformanceSettings::default(),
230            error_handling: ErrorHandlingSettings::default(),
231            memory_settings: MemorySettings::default(),
232            security_settings: SecuritySettings::default(),
233        }
234    }
235}
236
237impl Default for MemoryLimits {
238    fn default() -> Self {
239        Self {
240            max_ast_size: 100 * 1024 * 1024,   // 100 MB
241            max_temp_memory: 50 * 1024 * 1024, // 50 MB
242            max_stack_depth: 1000,
243            max_cache_entries: 10_000,
244        }
245    }
246}
247
248impl Default for PerformanceSettings {
249    fn default() -> Self {
250        Self {
251            worker_threads: num_cpus::get() as u32,
252            stream_buffer_size: 64 * 1024, // 64 KB
253            enable_caching: true,
254            cache_ttl: Duration::from_secs(300), // 5 minutes
255            enable_jit: false,
256            optimization_level: 2,
257        }
258    }
259}
260
261impl Default for ErrorHandlingSettings {
262    fn default() -> Self {
263        Self {
264            max_errors: 100,
265            continue_on_error: true,
266            error_context_lines: 3,
267            include_suggestions: true,
268            collect_error_stats: false,
269        }
270    }
271}
272
273impl Default for MemorySettings {
274    fn default() -> Self {
275        Self {
276            max_parser_memory: 50 * 1024 * 1024, // 50 MB
277            allocation_strategy: MemoryAllocationStrategy::Standard,
278            enable_memory_pooling: false,
279            memory_pool_size: 1024,
280        }
281    }
282}
283
284impl Default for SecuritySettings {
285    fn default() -> Self {
286        Self {
287            max_query_depth: 100,
288            max_token_count: 10_000,
289            enable_input_sanitization: true,
290            restrict_dangerous_operations: true,
291            blocked_keywords: vec![],
292        }
293    }
294}
295
296impl ParserConfig {
297    /// Create a new configuration with default values
298    pub fn new() -> Self {
299        Self::default()
300    }
301
302    /// Create a fast configuration optimized for performance
303    ///
304    /// Parallel parsing requires >=2 worker threads, so the thread count is
305    /// set to `max(num_cpus, 2)` to stay valid even on single-core CI runners.
306    pub fn fast() -> Self {
307        Self {
308            backend: ParserBackend::Nom,
309            timeout: Duration::from_secs(10),
310            strict_validation: false,
311            features: vec![
312                ParserFeature::Parallel,
313                ParserFeature::Caching,
314                ParserFeature::Streaming,
315            ],
316            performance: PerformanceSettings {
317                optimization_level: 3,
318                enable_jit: true,
319                worker_threads: (num_cpus::get() as u32).max(2),
320                ..Default::default()
321            },
322            ..Default::default()
323        }
324    }
325
326    /// Create a strict configuration with maximum validation
327    pub fn strict() -> Self {
328        Self {
329            backend: ParserBackend::Antlr,
330            strict_validation: true,
331            allow_experimental: false,
332            features: vec![
333                ParserFeature::ErrorRecovery,
334                ParserFeature::OnlineValidation,
335                ParserFeature::Profiling,
336                ParserFeature::SyntaxHighlighting,
337            ],
338            error_handling: ErrorHandlingSettings {
339                max_errors: 1,
340                continue_on_error: false,
341                include_suggestions: true,
342                collect_error_stats: true,
343                ..Default::default()
344            },
345            ..Default::default()
346        }
347    }
348
349    /// Create a development configuration with debugging features
350    pub fn development() -> Self {
351        Self {
352            backend: ParserBackend::Auto,
353            allow_experimental: true,
354            features: vec![
355                ParserFeature::ErrorRecovery,
356                ParserFeature::SyntaxHighlighting,
357                ParserFeature::CodeCompletion,
358                ParserFeature::AstTransformation,
359                ParserFeature::OnlineValidation,
360                ParserFeature::Profiling,
361            ],
362            error_handling: ErrorHandlingSettings {
363                continue_on_error: true,
364                include_suggestions: true,
365                collect_error_stats: true,
366                ..Default::default()
367            },
368            ..Default::default()
369        }
370    }
371
372    /// Create a minimal configuration for embedded use
373    pub fn minimal() -> Self {
374        Self {
375            backend: ParserBackend::Nom,
376            timeout: Duration::from_secs(5),
377            max_expression_depth: 50,
378            max_collection_size: 1000,
379            max_string_length: 10_000,
380            max_parameters: 100,
381            strict_validation: false,
382            features: vec![],
383            memory_limits: MemoryLimits {
384                max_ast_size: 10 * 1024 * 1024,   // 10 MB
385                max_temp_memory: 5 * 1024 * 1024, // 5 MB
386                max_stack_depth: 100,
387                max_cache_entries: 100,
388            },
389            performance: PerformanceSettings {
390                worker_threads: 1,
391                enable_caching: false,
392                optimization_level: 1,
393                ..Default::default()
394            },
395            error_handling: ErrorHandlingSettings {
396                max_errors: 10,
397                error_context_lines: 1,
398                include_suggestions: false,
399                collect_error_stats: false,
400                ..Default::default()
401            },
402            ..Default::default()
403        }
404    }
405
406    /// Set the parser backend
407    pub fn with_backend(mut self, backend: ParserBackend) -> Self {
408        self.backend = backend;
409        self
410    }
411
412    /// Set the timeout
413    pub fn with_timeout(mut self, timeout: Duration) -> Self {
414        self.timeout = timeout;
415        self
416    }
417
418    /// Enable strict validation
419    pub fn with_strict_validation(mut self, strict: bool) -> Self {
420        self.strict_validation = strict;
421        self
422    }
423
424    /// Add a feature (no-op if already present)
425    pub fn with_feature(mut self, feature: ParserFeature) -> Self {
426        if !self.features.contains(&feature) {
427            self.features.push(feature);
428        }
429        self
430    }
431
432    /// Check if a feature is enabled
433    pub fn has_feature(&self, feature: &ParserFeature) -> bool {
434        self.features.contains(feature)
435    }
436
437    /// Validate the configuration
438    pub fn validate(&self) -> Result<(), String> {
439        if self.timeout.as_secs() == 0 {
440            return Err("Timeout must be greater than 0".to_string());
441        }
442        if self.max_expression_depth == 0 {
443            return Err("Max expression depth must be greater than 0".to_string());
444        }
445        if self.max_collection_size == 0 {
446            return Err("Max collection size must be greater than 0".to_string());
447        }
448        if self.memory_limits.max_ast_size == 0 {
449            return Err("Max AST size must be greater than 0".to_string());
450        }
451        if self.memory_limits.max_stack_depth == 0 {
452            return Err("Max stack depth must be greater than 0".to_string());
453        }
454        if self.performance.worker_threads == 0 {
455            return Err("Worker threads must be greater than 0".to_string());
456        }
457        if self.performance.stream_buffer_size < 1024 {
458            return Err("Stream buffer size should be at least 1KB for efficiency".to_string());
459        }
460        if self.performance.optimization_level > 3 {
461            return Err("Optimization level must be 0-3".to_string());
462        }
463        if self.error_handling.max_errors == 0 {
464            return Err("Max errors must be greater than 0".to_string());
465        }
466
467        if self.has_feature(&ParserFeature::Parallel) && self.performance.worker_threads == 1 {
468            return Err("Parallel parsing requires at least 2 worker threads. Use ParserConfig::fast() for automatic thread count adjustment.".to_string());
469        }
470        if self.has_feature(&ParserFeature::Streaming)
471            && matches!(self.backend, ParserBackend::Antlr)
472        {
473            return Err("Streaming is not supported with ANTLR backend".to_string());
474        }
475
476        Ok(())
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    #[test]
485    fn test_default_config() {
486        let config = ParserConfig::default();
487        assert!(matches!(config.backend, ParserBackend::Auto));
488        assert!(config.strict_validation);
489        assert!(!config.allow_experimental);
490        assert!(config.validate().is_ok());
491    }
492
493    #[test]
494    fn test_preset_configs() {
495        let fast = ParserConfig::fast();
496        assert!(matches!(fast.backend, ParserBackend::Nom));
497        assert!(!fast.strict_validation);
498        assert!(fast.validate().is_ok());
499
500        let strict = ParserConfig::strict();
501        assert!(matches!(strict.backend, ParserBackend::Antlr));
502        assert!(strict.strict_validation);
503        assert!(strict.validate().is_ok());
504
505        let minimal = ParserConfig::minimal();
506        assert_eq!(minimal.memory_limits.max_ast_size, 10 * 1024 * 1024);
507        assert!(minimal.validate().is_ok());
508    }
509
510    #[test]
511    fn test_config_validation() {
512        let mut config = ParserConfig::default();
513        assert!(config.validate().is_ok());
514
515        config.timeout = Duration::from_secs(0);
516        assert!(config.validate().is_err());
517
518        config = ParserConfig::default();
519        config.performance.optimization_level = 5;
520        assert!(config.validate().is_err());
521    }
522
523    #[test]
524    fn test_feature_management() {
525        let mut config = ParserConfig::default();
526        assert!(!config.has_feature(&ParserFeature::Streaming));
527
528        config = config.with_feature(ParserFeature::Streaming);
529        assert!(config.has_feature(&ParserFeature::Streaming));
530    }
531
532    #[test]
533    fn test_parallel_requires_multiple_workers() {
534        let mut config = ParserConfig::minimal().with_feature(ParserFeature::Parallel);
535        config.performance.worker_threads = 1;
536        let err = config
537            .validate()
538            .expect_err("parallel parsing should require >1 worker");
539        assert!(err.contains("Parallel parsing requires at least 2 worker threads"));
540    }
541
542    #[test]
543    fn test_streaming_not_allowed_with_antlr() {
544        let config = ParserConfig::strict().with_feature(ParserFeature::Streaming);
545        let err = config
546            .validate()
547            .expect_err("streaming should be incompatible with ANTLR backend");
548        assert!(err.contains("Streaming is not supported with ANTLR backend"));
549    }
550}