1use 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#[derive(Debug, Clone)]
19pub struct SchemaParserConfig {
20 pub backend: ParserBackend,
22 pub strict_validation: bool,
24 pub allow_experimental: bool,
26 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 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 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 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
79pub 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
112pub async fn parse_cql_schema_simple(cql: &str) -> Result<TableSchema> {
114 parse_cql_schema_enhanced(cql, None).await
115}
116
117pub async fn parse_cql_schema_fast(cql: &str) -> Result<TableSchema> {
119 parse_cql_schema_enhanced(cql, Some(SchemaParserConfig::fast())).await
120}
121
122pub async fn parse_cql_schema_strict(cql: &str) -> Result<TableSchema> {
124 parse_cql_schema_enhanced(cql, Some(SchemaParserConfig::strict())).await
125}
126
127pub 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#[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
152pub 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#[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
188pub 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}