1use crate::cql::{CqlCreateTable, CqlDataType};
8use crate::error::{Error, Result};
9use crate::parser::types::CqlTypeId;
10use crate::schema::{ClusteringColumn, ClusteringOrder, Column, KeyColumn, TableSchema};
11use nom::{
12 branch::alt,
13 bytes::complete::{tag_no_case, take_while, take_while1},
14 character::complete::char,
15 combinator::{map, opt},
16 multi::{separated_list0, separated_list1},
17 sequence::{delimited, preceded, separated_pair, tuple},
18 IResult,
19};
20use serde_json;
21use std::collections::HashMap;
22
23fn keyword(s: &str) -> impl Fn(&str) -> IResult<&str, &str> + '_ {
25 move |input| tag_no_case(s)(input)
26}
27
28fn ws(input: &str) -> IResult<&str, &str> {
30 take_while(|c: char| c.is_whitespace())(input)
31}
32
33fn ws1(input: &str) -> IResult<&str, &str> {
35 take_while1(|c: char| c.is_whitespace())(input)
36}
37
38fn identifier(input: &str) -> IResult<&str, String> {
40 let (input, name) = alt((
41 delimited(char('"'), take_while1(|c: char| c != '"'), char('"')),
43 take_while1(|c: char| c.is_alphanumeric() || c == '_'),
45 ))(input)?;
46
47 Ok((input, name.to_string()))
48}
49
50fn qualified_table_name(input: &str) -> IResult<&str, (Option<String>, String)> {
52 let (input, first) = identifier(input)?;
53 let (input, second) = opt(preceded(char('.'), identifier))(input)?;
54
55 match second {
56 Some(table) => Ok((input, (Some(first), table))),
57 None => Ok((input, (None, first))),
58 }
59}
60
61const MAX_TYPE_NESTING_DEPTH: usize = 32;
74
75fn cql_type(input: &str) -> IResult<&str, String> {
77 fn parse_type_inner(input: &str, depth: usize) -> IResult<&str, String> {
80 if depth > MAX_TYPE_NESTING_DEPTH {
81 return Err(nom::Err::Failure(nom::error::Error::new(
84 input,
85 nom::error::ErrorKind::TooLarge,
86 )));
87 }
88
89 let (input, base) = alt((
90 map(
92 tuple((
93 alt((keyword("list"), keyword("set"))),
94 char('<'),
95 |i| parse_type_inner(i, depth + 1),
96 char('>'),
97 )),
98 |(collection, _, inner, _)| format!("{}<{}>", collection, inner),
99 ),
100 map(
102 tuple((
103 keyword("map"),
104 char('<'),
105 |i| parse_type_inner(i, depth + 1),
106 char(','),
107 ws,
108 |i| parse_type_inner(i, depth + 1),
109 char('>'),
110 )),
111 |(_, _, key_type, _, _, value_type, _)| {
112 format!("map<{}, {}>", key_type, value_type)
113 },
114 ),
115 map(
117 tuple((
118 keyword("tuple"),
119 char('<'),
120 separated_list1(tuple((ws, char(','), ws)), |i| {
121 parse_type_inner(i, depth + 1)
122 }),
123 char('>'),
124 )),
125 |(_, _, types, _)| format!("tuple<{}>", types.join(", ")),
126 ),
127 map(
129 tuple((
130 keyword("frozen"),
131 char('<'),
132 |i| parse_type_inner(i, depth + 1),
133 char('>'),
134 )),
135 |(_, _, inner, _)| format!("frozen<{}>", inner),
136 ),
137 map(identifier, |name| name),
139 ))(input)?;
140
141 Ok((input, base))
142 }
143
144 let (input, _) = ws(input)?;
145 let (input, type_name) = parse_type_inner(input, 0)?;
146 let (input, _) = ws(input)?;
147
148 Ok((input, type_name))
149}
150
151#[cfg(feature = "fuzz")]
157pub(crate) fn cql_type_fuzz(input: &str) -> IResult<&str, String> {
158 cql_type(input)
159}
160
161fn column_definition(input: &str) -> IResult<&str, (String, String, bool)> {
164 let (input, _) = ws(input)?;
165 let (input, name) = identifier(input)?;
166 let (input, _) = ws1(input)?;
167 let (input, data_type) = cql_type(input)?;
168 let (input, _) = ws(input)?;
169
170 let (input, is_static) = opt(keyword("static"))(input)?;
172 let is_static = is_static.is_some();
173 let (input, _) = ws(input)?;
174
175 let (input, _is_primary) = opt(tuple((keyword("primary"), ws1, keyword("key"))))(input)?;
178
179 Ok((input, (name, data_type, is_static)))
182}
183
184fn primary_key_spec(input: &str) -> IResult<&str, (Vec<String>, Vec<String>)> {
186 let (input, _) = ws(input)?;
187 let (input, _) = keyword("primary")(input)?;
188 let (input, _) = ws1(input)?;
189 let (input, _) = keyword("key")(input)?;
190 let (input, _) = ws(input)?;
191 let (input, _) = char('(')(input)?;
192 let (input, _) = ws(input)?;
193
194 let (input, partition_keys) = alt((
196 map(
198 tuple((
199 char('('),
200 ws,
201 separated_list1(tuple((ws, char(','), ws)), identifier),
202 ws,
203 char(')'),
204 )),
205 |(_, _, keys, _, _)| keys,
206 ),
207 map(identifier, |key| vec![key]),
209 ))(input)?;
210
211 let (input, _) = ws(input)?;
212
213 let (input, clustering_keys) = opt(preceded(
215 tuple((char(','), ws)),
216 separated_list1(tuple((ws, char(','), ws)), identifier),
217 ))(input)?;
218
219 let (input, _) = ws(input)?;
220 let (input, _) = char(')')(input)?;
221
222 Ok((input, (partition_keys, clustering_keys.unwrap_or_default())))
223}
224
225fn bracketed_value(input: &str) -> IResult<&str, String> {
232 let bytes: &[u8] = input.as_bytes();
233 match bytes.first() {
235 Some(b'{') | Some(b'[') => {}
236 _ => {
237 return Err(nom::Err::Error(nom::error::Error::new(
238 input,
239 nom::error::ErrorKind::Char,
240 )))
241 }
242 }
243
244 let mut depth: usize = 0;
248 let mut in_string = false;
249 let mut i = 0usize;
250 while i < bytes.len() {
251 let c = bytes[i];
252 if in_string {
253 if c == b'\'' {
254 if bytes.get(i + 1) == Some(&b'\'') {
256 i += 2;
257 continue;
258 }
259 in_string = false;
260 }
261 i += 1;
262 continue;
263 }
264 match c {
265 b'\'' => in_string = true,
266 b'{' | b'[' => depth += 1,
267 b'}' | b']' => {
268 depth -= 1;
269 if depth == 0 {
270 let end = i + 1;
271 let (value, rest) = input.split_at(end);
272 return Ok((rest, value.to_string()));
273 }
274 }
275 _ => {}
276 }
277 i += 1;
278 }
279
280 Err(nom::Err::Error(nom::error::Error::new(
282 input,
283 nom::error::ErrorKind::Char,
284 )))
285}
286
287fn single_quoted_string(input: &str) -> IResult<&str, String> {
297 let bytes = input.as_bytes();
298 if bytes.first() != Some(&b'\'') {
299 return Err(nom::Err::Error(nom::error::Error::new(
300 input,
301 nom::error::ErrorKind::Char,
302 )));
303 }
304
305 let mut value = String::new();
306 let mut i = 1usize; while i < bytes.len() {
308 if bytes[i] == b'\'' {
309 if bytes.get(i + 1) == Some(&b'\'') {
311 value.push('\'');
312 i += 2;
313 continue;
314 }
315 let rest = &input[i + 1..];
317 return Ok((rest, value));
318 }
319 let ch_str = &input[i..];
323 if let Some(ch) = ch_str.chars().next() {
324 value.push(ch);
325 i += ch.len_utf8();
326 } else {
327 break;
328 }
329 }
330
331 Err(nom::Err::Error(nom::error::Error::new(
333 input,
334 nom::error::ErrorKind::Char,
335 )))
336}
337
338fn clustering_order_item(input: &str) -> IResult<&str, (String, String)> {
347 let (input, _) = keyword("clustering")(input)?;
348 let (input, _) = ws1(input)?;
349 let (input, _) = keyword("order")(input)?;
350 let (input, _) = ws1(input)?;
351 let (input, _) = keyword("by")(input)?;
352 let (input, _) = ws(input)?;
353
354 let (input, _) = char('(')(input)?;
363 let (input, body) = clustering_order_body_scan(input)?;
364 let (input, _) = char(')')(input)?;
365
366 Ok((
367 input,
368 (
369 "clustering order by".to_string(),
370 format!("({})", body.trim()),
371 ),
372 ))
373}
374
375fn clustering_order_body_scan(input: &str) -> IResult<&str, &str> {
383 let bytes = input.as_bytes();
384 let mut i = 0usize;
385 let mut quote: Option<u8> = None;
387 while i < bytes.len() {
388 let c = bytes[i];
389 match quote {
390 Some(q) => {
391 if c == q {
392 if bytes.get(i + 1) == Some(&q) {
394 i += 2;
395 continue;
396 }
397 quote = None;
398 }
399 }
400 None => match c {
401 b'\'' | b'"' => quote = Some(c),
402 b')' => {
403 let (body, rest) = input.split_at(i);
404 return Ok((rest, body));
405 }
406 _ => {}
407 },
408 }
409 i += 1;
410 }
411
412 Err(nom::Err::Error(nom::error::Error::new(
414 input,
415 nom::error::ErrorKind::Char,
416 )))
417}
418
419fn parse_clustering_order_body(body: &str) -> HashMap<String, ClusteringOrder> {
428 let inner = body.trim().trim_start_matches('(').trim_end_matches(')');
430
431 let entries = separated_list0(tuple((ws, char(','), ws)), clustering_order_entry);
440 match delimited(ws, entries, ws)(inner) {
441 Ok((_, items)) => items.into_iter().collect(),
442 Err(_) => HashMap::new(),
443 }
444}
445
446fn clustering_order_entry(input: &str) -> IResult<&str, (String, ClusteringOrder)> {
452 let (input, col) = identifier(input)?;
453 let (input, direction) = opt(preceded(ws1, alt((keyword("asc"), keyword("desc")))))(input)?;
455 let order = direction
456 .map(crate::schema::ClusteringOrder::from)
457 .unwrap_or(crate::schema::ClusteringOrder::Asc);
458 Ok((input, (col, order)))
459}
460
461fn table_options(input: &str) -> IResult<&str, HashMap<String, String>> {
463 let (input, _) = ws(input)?;
464 let (input, _) = keyword("with")(input)?;
465 let (input, _) = ws1(input)?;
466
467 let option_pair = map(
469 separated_pair(
470 identifier,
471 tuple((ws, char('='), ws)),
472 alt((
473 bracketed_value,
476 single_quoted_string,
478 map(
480 take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '.'),
481 |s: &str| s.to_string(),
482 ),
483 )),
484 ),
485 |(key, value)| (key, value),
486 );
487
488 let with_item = alt((clustering_order_item, option_pair));
492
493 let (input, options) = separated_list0(tuple((ws, keyword("and"), ws)), with_item)(input)?;
494
495 Ok((input, options.into_iter().collect()))
496}
497
498pub fn split_cql_statements(input: &str) -> Vec<String> {
501 let mut statements = Vec::new();
502 let mut current_statement = String::new();
503 let mut in_string = false;
504 let mut in_single_line_comment = false;
505 let mut in_multi_line_comment = false;
506 let mut escape_next = false;
507
508 let chars: Vec<char> = input.chars().collect();
509 let mut i = 0;
510
511 while i < chars.len() {
512 let c = chars[i];
513
514 if escape_next {
516 current_statement.push(c);
517 escape_next = false;
518 i += 1;
519 continue;
520 }
521
522 if !in_string
524 && !in_single_line_comment
525 && !in_multi_line_comment
526 && i + 1 < chars.len()
527 && c == '/'
528 && chars[i + 1] == '*'
529 {
530 in_multi_line_comment = true;
531 current_statement.push(c);
532 current_statement.push(chars[i + 1]);
533 i += 2;
534 continue;
535 }
536
537 if in_multi_line_comment && i + 1 < chars.len() && c == '*' && chars[i + 1] == '/' {
539 in_multi_line_comment = false;
540 current_statement.push(c);
541 current_statement.push(chars[i + 1]);
542 i += 2;
543 continue;
544 }
545
546 if !in_string
548 && !in_multi_line_comment
549 && !in_single_line_comment
550 && i + 1 < chars.len()
551 && c == '-'
552 && chars[i + 1] == '-'
553 {
554 in_single_line_comment = true;
555 current_statement.push(c);
556 current_statement.push(chars[i + 1]);
557 i += 2;
558 continue;
559 }
560
561 if c == '\n' {
563 in_single_line_comment = false;
564 current_statement.push(c);
565 i += 1;
566 continue;
567 }
568
569 if in_single_line_comment || in_multi_line_comment {
571 current_statement.push(c);
572 i += 1;
573 continue;
574 }
575
576 if c == '\'' {
578 in_string = !in_string;
579 current_statement.push(c);
580 i += 1;
581 continue;
582 }
583
584 if in_string && c == '\\' {
586 escape_next = true;
587 current_statement.push(c);
588 i += 1;
589 continue;
590 }
591
592 if !in_string && c == ';' {
594 let trimmed = current_statement.trim();
595 if !trimmed.is_empty() {
596 statements.push(trimmed.to_string());
597 }
598 current_statement.clear();
599 i += 1;
600 continue;
601 }
602
603 current_statement.push(c);
604 i += 1;
605 }
606
607 let trimmed = current_statement.trim();
609 if !trimmed.is_empty() {
610 statements.push(trimmed.to_string());
611 }
612
613 statements
615 .into_iter()
616 .map(|stmt| strip_leading_trailing_comments(&stmt))
617 .filter(|s| !s.is_empty())
618 .collect()
619}
620
621fn strip_leading_trailing_comments(stmt: &str) -> String {
623 let lines: Vec<&str> = stmt.lines().collect();
624 let mut start = 0;
625 let mut end = lines.len();
626
627 for (i, line) in lines.iter().enumerate() {
629 let trimmed = line.trim();
630 if !trimmed.is_empty() && !trimmed.starts_with("--") && !trimmed.starts_with("/*") {
631 start = i;
632 break;
633 }
634 }
635
636 for (i, line) in lines.iter().enumerate().rev() {
638 let trimmed = line.trim();
639 if !trimmed.is_empty() && !trimmed.starts_with("--") && !trimmed.ends_with("*/") {
640 end = i + 1;
641 break;
642 }
643 }
644
645 if start >= end {
646 return String::new();
647 }
648
649 lines[start..end].join("\n")
650}
651
652#[cfg(test)]
653mod tests_splitter {
654 use super::*;
655
656 #[test]
657 fn test_split_with_comments() {
658 let cql = r#"
659 -- Comment
660 CREATE TYPE test.udt (field text);
661
662 /* Multi-line
663 comment */
664 CREATE TABLE test.tbl (id int PRIMARY KEY);
665 "#;
666
667 let stmts = split_cql_statements(cql);
668 assert_eq!(stmts.len(), 2);
669 assert!(stmts[0].contains("CREATE TYPE"));
670 assert!(!stmts[0].contains("--"));
671 assert!(stmts[1].contains("CREATE TABLE"));
672 }
673}
674
675#[derive(Debug, Clone, PartialEq)]
677pub enum StatementType {
678 CreateTable,
679 CreateType,
680 Other(String),
681}
682
683pub fn classify_statement(statement: &str) -> StatementType {
685 let normalized = statement.trim().to_lowercase();
686
687 let normalized = normalized
689 .lines()
690 .map(|line| {
691 if let Some(pos) = line.find("--") {
693 &line[..pos]
694 } else {
695 line
696 }
697 })
698 .collect::<Vec<&str>>()
699 .join(" ");
700
701 let normalized = normalized.trim();
702
703 if normalized.starts_with("create table")
704 || normalized.starts_with("create table if not exists")
705 {
706 StatementType::CreateTable
707 } else if normalized.starts_with("create type")
708 || normalized.starts_with("create type if not exists")
709 {
710 StatementType::CreateType
711 } else {
712 StatementType::Other(
713 normalized
714 .split_whitespace()
715 .next()
716 .unwrap_or("unknown")
717 .to_string(),
718 )
719 }
720}
721
722#[allow(clippy::type_complexity)]
724pub fn parse_create_type(
725 input: &str,
726) -> IResult<&str, (String, Option<String>, Vec<(String, String)>)> {
727 let (input, _) = ws(input)?;
728 let (input, _) = keyword("create")(input)?;
729 let (input, _) = ws1(input)?;
730 let (input, _) = keyword("type")(input)?;
731 let (input, _) = ws1(input)?;
732
733 let (input, _) = opt(tuple((
735 keyword("if"),
736 ws1,
737 keyword("not"),
738 ws1,
739 keyword("exists"),
740 ws1,
741 )))(input)?;
742
743 let (input, (keyspace, type_name)) = qualified_table_name(input)?;
745
746 let (input, _) = ws(input)?;
747 let (input, _) = char('(')(input)?;
748 let (input, _) = ws(input)?;
749
750 let (input, fields) = separated_list1(
752 tuple((ws, char(','), ws)),
753 map(
754 tuple((identifier, ws1, cql_type)),
755 |(name, _, field_type)| (name, field_type),
756 ),
757 )(input)?;
758
759 let (input, _) = ws(input)?;
760 let (input, _) = char(')')(input)?;
761
762 Ok((input, (type_name, keyspace, fields)))
763}
764
765pub fn parse_create_table(input: &str) -> IResult<&str, TableSchema> {
767 let (input, _) = ws(input)?;
768 let (input, _) = keyword("create")(input)?;
769 let (input, _) = ws1(input)?;
770 let (input, _) = keyword("table")(input)?;
771 let (input, _) = ws1(input)?;
772
773 let (input, _) = opt(tuple((
775 keyword("if"),
776 ws1,
777 keyword("not"),
778 ws1,
779 keyword("exists"),
780 ws1,
781 )))(input)?;
782
783 let (input, (keyspace, table_name)) = qualified_table_name(input)?;
785
786 let (input, _) = ws(input)?;
787 let (input, _) = char('(')(input)?;
788 let (input, _) = ws(input)?;
789
790 let mut columns: Vec<(String, String, bool)> = Vec::new();
793 let mut partition_keys = Vec::new();
794 let mut clustering_keys = Vec::new();
795 let mut primary_key_found = false;
796
797 let (input, items) = separated_list1(
798 tuple((ws, char(','), ws)),
799 alt((
800 map(primary_key_spec, |keys| {
802 (
803 "PRIMARY_KEY".to_string(),
804 serde_json::to_string(&keys).unwrap_or_default(),
805 false, )
807 }),
808 column_definition,
810 )),
811 )(input)?;
812
813 for (name, value, is_static) in items {
815 if name == "PRIMARY_KEY" {
816 if let Ok(keys_tuple) = serde_json::from_str::<(Vec<String>, Vec<String>)>(&value) {
818 partition_keys = keys_tuple.0;
819 clustering_keys = keys_tuple.1;
820 primary_key_found = true;
821 }
822 continue;
823 }
824 columns.push((name, value, is_static));
825 }
826
827 let (input, _) = ws(input)?;
828 let (input, _) = char(')')(input)?;
829
830 let (input, with_options) = opt(table_options)(input)?;
835
836 let clustering_order_map: HashMap<String, ClusteringOrder> = with_options
840 .as_ref()
841 .and_then(|opts| opts.get("clustering order by"))
842 .map(|body| parse_clustering_order_body(body))
843 .unwrap_or_default();
844
845 if !primary_key_found && !columns.is_empty() {
847 let mut found_inline = false;
849 for (col_name, col_type, _is_static) in &columns {
850 if col_type.to_lowercase().contains("primary key") {
851 partition_keys.push(col_name.clone());
852 found_inline = true;
853 break;
854 }
855 }
856
857 if !found_inline {
859 partition_keys.push(columns[0].0.clone());
860 }
861 }
862
863 let schema = TableSchema {
865 keyspace: keyspace.unwrap_or_else(|| "default".to_string()),
866 table: table_name,
867 partition_keys: partition_keys
868 .into_iter()
869 .enumerate()
870 .map(|(pos, name)| {
871 let data_type = columns
872 .iter()
873 .find(|(col_name, _, _)| col_name == &name)
874 .map(|(_, dt, _)| dt.clone())
875 .unwrap_or_else(|| "text".to_string());
876
877 KeyColumn {
878 name,
879 data_type,
880 position: pos,
881 }
882 })
883 .collect(),
884 clustering_keys: clustering_keys
885 .into_iter()
886 .enumerate()
887 .map(|(pos, name)| {
888 let data_type = columns
889 .iter()
890 .find(|(col_name, _, _)| col_name == &name)
891 .map(|(_, dt, _)| dt.clone())
892 .unwrap_or_else(|| "text".to_string());
893
894 let order = clustering_order_map
895 .get(&name)
896 .cloned()
897 .unwrap_or(crate::schema::ClusteringOrder::Asc);
898
899 ClusteringColumn {
900 name,
901 data_type,
902 position: pos,
903 order,
904 }
905 })
906 .collect(),
907 columns: columns
908 .into_iter()
909 .map(|(name, data_type_with_constraints, is_static)| {
910 let data_type = if data_type_with_constraints
912 .to_lowercase()
913 .contains("primary key")
914 {
915 data_type_with_constraints
916 .to_lowercase()
917 .replace("primary key", "")
918 .trim()
919 .to_string()
920 } else {
921 data_type_with_constraints
922 };
923
924 Column {
925 name,
926 data_type,
927 nullable: true,
928 default: None,
929 is_static,
930 }
931 })
932 .collect(),
933 comments: with_options
934 .unwrap_or_default()
935 .into_iter()
936 .map(|(k, v)| (k.to_lowercase(), v))
937 .collect(),
938 dropped_columns: std::collections::HashMap::new(),
939 };
940
941 Ok((input, schema))
942}
943
944pub fn cql_type_to_type_id(cql_type: &str) -> Result<CqlTypeId> {
946 cql_type_to_type_id_with_depth(cql_type, 0)
947}
948
949fn cql_type_to_type_id_with_depth(cql_type: &str, depth: usize) -> Result<CqlTypeId> {
957 if depth > MAX_TYPE_NESTING_DEPTH {
958 return Err(Error::schema(format!(
959 "type nesting too deep (max {})",
960 MAX_TYPE_NESTING_DEPTH
961 )));
962 }
963
964 let type_lower = cql_type.trim().to_lowercase();
965
966 if type_lower.starts_with("list<") {
968 return Ok(CqlTypeId::List);
969 }
970 if type_lower.starts_with("set<") {
971 return Ok(CqlTypeId::Set);
972 }
973 if type_lower.starts_with("map<") {
974 return Ok(CqlTypeId::Map);
975 }
976 if type_lower.starts_with("tuple<") {
977 return Ok(CqlTypeId::Tuple);
978 }
979 if type_lower.starts_with("frozen<") {
980 if let Some(inner_start) = type_lower.find('<') {
982 if let Some(inner_end) = type_lower.rfind('>') {
983 let inner_type = &type_lower[inner_start + 1..inner_end];
984 return cql_type_to_type_id_with_depth(inner_type, depth + 1);
985 }
986 }
987 }
988
989 match type_lower.as_str() {
991 "ascii" => Ok(CqlTypeId::Ascii),
992 "bigint" | "long" => Ok(CqlTypeId::BigInt),
993 "blob" => Ok(CqlTypeId::Blob),
994 "boolean" | "bool" => Ok(CqlTypeId::Boolean),
995 "counter" => Ok(CqlTypeId::Counter),
996 "decimal" => Ok(CqlTypeId::Decimal),
997 "double" => Ok(CqlTypeId::Double),
998 "float" => Ok(CqlTypeId::Float),
999 "int" | "integer" => Ok(CqlTypeId::Int),
1000 "timestamp" => Ok(CqlTypeId::Timestamp),
1001 "uuid" => Ok(CqlTypeId::Uuid),
1002 "varchar" | "text" => Ok(CqlTypeId::Varchar),
1003 "varint" => Ok(CqlTypeId::Varint),
1004 "timeuuid" => Ok(CqlTypeId::Timeuuid),
1005 "inet" => Ok(CqlTypeId::Inet),
1006 "date" => Ok(CqlTypeId::Date),
1007 "time" => Ok(CqlTypeId::Time),
1008 "smallint" => Ok(CqlTypeId::Smallint),
1009 "tinyint" => Ok(CqlTypeId::Tinyint),
1010 "duration" => Ok(CqlTypeId::Duration),
1011 _ => {
1012 Ok(CqlTypeId::Udt)
1014 }
1015 }
1016}
1017
1018pub fn extract_table_name(cql: &str) -> Result<(Option<String>, String)> {
1020 match parse_create_table(cql) {
1021 Ok((_, schema)) => {
1022 let keyspace = if schema.keyspace == "default" {
1023 None
1024 } else {
1025 Some(schema.keyspace)
1026 };
1027 Ok((keyspace, schema.table))
1028 }
1029 Err(_) => {
1030 let cql_lower = cql.to_lowercase();
1032 if let Some(table_start) = cql_lower.find("create table") {
1033 let after_table = &cql[table_start + 12..];
1034 if let Some(if_not_exists) = after_table.find("if not exists") {
1035 let after_if = &after_table[if_not_exists + 13..];
1036 return extract_simple_table_name(after_if);
1037 }
1038 return extract_simple_table_name(after_table);
1039 }
1040
1041 Err(Error::schema(
1042 "Failed to extract table name from CQL".to_string(),
1043 ))
1044 }
1045 }
1046}
1047
1048fn extract_simple_table_name(input: &str) -> Result<(Option<String>, String)> {
1050 let trimmed = input.trim();
1051 let words: Vec<&str> = trimmed.split_whitespace().collect();
1052
1053 if words.is_empty() {
1054 return Err(Error::schema("No table name found".to_string()));
1055 }
1056
1057 let table_name = words[0];
1058
1059 if let Some(dot_pos) = table_name.find('.') {
1061 let keyspace = &table_name[..dot_pos];
1062 let table = &table_name[dot_pos + 1..];
1063 Ok((Some(keyspace.to_string()), table.to_string()))
1064 } else {
1065 Ok((None, table_name.to_string()))
1066 }
1067}
1068
1069pub fn table_name_matches(
1071 schema_keyspace: &Option<String>,
1072 schema_table: &str,
1073 target_keyspace: &Option<String>,
1074 target_table: &str,
1075) -> bool {
1076 if schema_table != target_table {
1078 return false;
1079 }
1080
1081 if target_keyspace.is_none() {
1083 return true;
1084 }
1085
1086 schema_keyspace == target_keyspace
1088}
1089
1090pub fn parse_cql_schema(cql: &str) -> Result<TableSchema> {
1092 match parse_create_table(cql) {
1093 Ok((_, schema)) => {
1094 schema.validate()?;
1096 Ok(schema)
1097 }
1098 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(Error::schema(format!(
1099 "Failed to parse CQL schema: {:?}",
1100 e
1101 ))),
1102 Err(nom::Err::Incomplete(_)) => Err(Error::schema("Incomplete CQL schema".to_string())),
1103 }
1104}
1105
1106pub fn parse_cql_schema_with_visitor(cql: &str) -> Result<TableSchema> {
1112 use crate::cql::traits::CqlVisitor;
1119 use crate::cql::visitor::SchemaBuilderVisitor;
1120 use crate::cql::CqlStatement;
1121
1122 let schema = parse_cql_schema(cql)?;
1124
1125 let ast = table_schema_to_ast(&schema)?;
1128 let statement = CqlStatement::CreateTable(ast);
1129
1130 let mut visitor = SchemaBuilderVisitor;
1132 visitor.visit_statement(&statement)
1133}
1134
1135fn table_schema_to_ast(schema: &TableSchema) -> Result<CqlCreateTable> {
1138 use crate::cql::{
1139 CqlColumnDef, CqlCreateTable, CqlIdentifier, CqlPrimaryKey, CqlTable, CqlTableOptions,
1140 };
1141
1142 let table = if schema.keyspace == "default" {
1144 CqlTable::new(&schema.table)
1145 } else {
1146 CqlTable::with_keyspace(&schema.keyspace, &schema.table)
1147 };
1148
1149 let columns: Result<Vec<CqlColumnDef>> = schema
1151 .columns
1152 .iter()
1153 .map(|col| {
1154 Ok(CqlColumnDef {
1155 name: CqlIdentifier::new(&col.name),
1156 data_type: string_to_cql_data_type(&col.data_type)?,
1157 is_static: col.is_static,
1158 })
1159 })
1160 .collect();
1161
1162 let columns = columns?;
1163
1164 let partition_key: Vec<CqlIdentifier> = schema
1166 .partition_keys
1167 .iter()
1168 .map(|pk| CqlIdentifier::new(&pk.name))
1169 .collect();
1170
1171 let clustering_key: Vec<CqlIdentifier> = schema
1172 .clustering_keys
1173 .iter()
1174 .map(|ck| CqlIdentifier::new(&ck.name))
1175 .collect();
1176
1177 Ok(CqlCreateTable {
1178 if_not_exists: false,
1179 table,
1180 columns,
1181 primary_key: CqlPrimaryKey {
1182 partition_key,
1183 clustering_key,
1184 },
1185 options: CqlTableOptions {
1186 options: HashMap::new(),
1187 },
1188 })
1189}
1190
1191fn string_to_cql_data_type(type_str: &str) -> Result<CqlDataType> {
1193 use crate::cql::{CqlDataType, CqlIdentifier};
1194
1195 let type_lower = type_str.trim().to_lowercase();
1196
1197 if type_lower.starts_with("list<") && type_lower.ends_with('>') {
1199 let inner_type_str = &type_lower[5..type_lower.len() - 1];
1200 let inner_type = string_to_cql_data_type(inner_type_str)?;
1201 return Ok(CqlDataType::List(Box::new(inner_type)));
1202 }
1203
1204 if type_lower.starts_with("set<") && type_lower.ends_with('>') {
1205 let inner_type_str = &type_lower[4..type_lower.len() - 1];
1206 let inner_type = string_to_cql_data_type(inner_type_str)?;
1207 return Ok(CqlDataType::Set(Box::new(inner_type)));
1208 }
1209
1210 if type_lower.starts_with("map<") && type_lower.ends_with('>') {
1211 let inner = &type_lower[4..type_lower.len() - 1];
1212 if let Some(comma_pos) = inner.find(',') {
1213 let key_type_str = inner[..comma_pos].trim();
1214 let value_type_str = inner[comma_pos + 1..].trim();
1215 let key_type = string_to_cql_data_type(key_type_str)?;
1216 let value_type = string_to_cql_data_type(value_type_str)?;
1217 return Ok(CqlDataType::Map(Box::new(key_type), Box::new(value_type)));
1218 }
1219 }
1220
1221 if type_lower.starts_with("frozen<") && type_lower.ends_with('>') {
1222 let inner_type_str = &type_lower[7..type_lower.len() - 1];
1223 let inner_type = string_to_cql_data_type(inner_type_str)?;
1224 return Ok(CqlDataType::Frozen(Box::new(inner_type)));
1225 }
1226
1227 match type_lower.as_str() {
1229 "boolean" | "bool" => Ok(CqlDataType::Boolean),
1230 "tinyint" => Ok(CqlDataType::TinyInt),
1231 "smallint" => Ok(CqlDataType::SmallInt),
1232 "int" => Ok(CqlDataType::Int),
1233 "bigint" | "long" => Ok(CqlDataType::BigInt),
1234 "varint" => Ok(CqlDataType::Varint),
1235 "decimal" => Ok(CqlDataType::Decimal),
1236 "float" => Ok(CqlDataType::Float),
1237 "double" => Ok(CqlDataType::Double),
1238 "text" | "varchar" => Ok(CqlDataType::Text),
1239 "ascii" => Ok(CqlDataType::Ascii),
1240 "blob" => Ok(CqlDataType::Blob),
1241 "timestamp" => Ok(CqlDataType::Timestamp),
1242 "date" => Ok(CqlDataType::Date),
1243 "time" => Ok(CqlDataType::Time),
1244 "uuid" => Ok(CqlDataType::Uuid),
1245 "timeuuid" => Ok(CqlDataType::TimeUuid),
1246 "inet" => Ok(CqlDataType::Inet),
1247 "duration" => Ok(CqlDataType::Duration),
1248 "counter" => Ok(CqlDataType::Counter),
1249 _ => {
1250 Ok(CqlDataType::Udt(CqlIdentifier::new(type_str)))
1252 }
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use super::*;
1259
1260 #[test]
1261 fn test_simple_table_parsing() {
1262 let cql = r#"
1263 CREATE TABLE users (
1264 id uuid PRIMARY KEY,
1265 name text,
1266 email text
1267 )
1268 "#;
1269
1270 let schema = parse_cql_schema(cql).unwrap();
1271 assert_eq!(schema.table, "users");
1272 assert_eq!(schema.columns.len(), 3);
1273 assert_eq!(schema.partition_keys.len(), 1);
1274 assert_eq!(schema.partition_keys[0].name, "id");
1275 }
1276
1277 #[test]
1278 fn test_qualified_table_name() {
1279 let cql = r#"
1280 CREATE TABLE myapp.users (
1281 id bigint PRIMARY KEY,
1282 name text
1283 )
1284 "#;
1285
1286 let schema = parse_cql_schema(cql).unwrap();
1287 assert_eq!(schema.keyspace, "myapp");
1288 assert_eq!(schema.table, "users");
1289 }
1290
1291 #[test]
1292 fn test_complex_types() {
1293 let cql = r#"
1294 CREATE TABLE complex_table (
1295 id uuid PRIMARY KEY,
1296 tags set<text>,
1297 metadata map<text, text>,
1298 coordinates list<double>
1299 )
1300 "#;
1301
1302 let schema = parse_cql_schema(cql).unwrap();
1303 assert_eq!(schema.columns.len(), 4);
1304
1305 let tags_col = schema.columns.iter().find(|c| c.name == "tags").unwrap();
1306 assert_eq!(tags_col.data_type, "set<text>");
1307
1308 let metadata_col = schema
1309 .columns
1310 .iter()
1311 .find(|c| c.name == "metadata")
1312 .unwrap();
1313 assert_eq!(metadata_col.data_type, "map<text, text>");
1314 }
1315
1316 #[test]
1317 fn test_table_name_extraction() {
1318 let cql = "CREATE TABLE IF NOT EXISTS myapp.users (id uuid PRIMARY KEY)";
1319 let (keyspace, table) = extract_table_name(cql).unwrap();
1320 assert_eq!(keyspace, Some("myapp".to_string()));
1321 assert_eq!(table, "users");
1322 }
1323
1324 #[test]
1325 fn test_cql_type_conversion() {
1326 assert_eq!(cql_type_to_type_id("text").unwrap(), CqlTypeId::Varchar);
1327 assert_eq!(cql_type_to_type_id("bigint").unwrap(), CqlTypeId::BigInt);
1328 assert_eq!(cql_type_to_type_id("list<text>").unwrap(), CqlTypeId::List);
1329 assert_eq!(
1330 cql_type_to_type_id("frozen<set<uuid>>").unwrap(),
1331 CqlTypeId::Set
1332 );
1333 }
1334
1335 #[test]
1336 fn test_table_name_matching() {
1337 assert!(table_name_matches(
1339 &Some("ks".to_string()),
1340 "users",
1341 &Some("ks".to_string()),
1342 "users"
1343 ));
1344
1345 assert!(table_name_matches(
1347 &Some("ks".to_string()),
1348 "users",
1349 &None,
1350 "users"
1351 ));
1352
1353 assert!(!table_name_matches(
1355 &Some("ks".to_string()),
1356 "users",
1357 &Some("ks".to_string()),
1358 "orders"
1359 ));
1360
1361 assert!(!table_name_matches(
1363 &Some("ks1".to_string()),
1364 "users",
1365 &Some("ks2".to_string()),
1366 "users"
1367 ));
1368 }
1369
1370 #[test]
1371 fn test_composite_primary_key() {
1372 let cql = r#"
1373 CREATE TABLE time_series (
1374 partition_key text,
1375 clustering_key timestamp,
1376 value double,
1377 PRIMARY KEY (partition_key, clustering_key)
1378 )
1379 "#;
1380
1381 let schema = parse_cql_schema(cql).unwrap();
1382 assert_eq!(schema.partition_keys.len(), 1);
1383 assert_eq!(schema.clustering_keys.len(), 1);
1384
1385 assert_eq!(schema.partition_keys[0].name, "partition_key");
1386 assert_eq!(schema.clustering_keys[0].name, "clustering_key");
1387 }
1388
1389 #[test]
1390 fn test_frozen_collections() {
1391 let cql = r#"
1392 CREATE TABLE frozen_test (
1393 id uuid PRIMARY KEY,
1394 frozen_set frozen<set<text>>,
1395 frozen_map frozen<map<text, bigint>>,
1396 nested_frozen frozen<list<frozen<set<uuid>>>>
1397 )
1398 "#;
1399
1400 let schema = parse_cql_schema(cql).unwrap();
1401
1402 let frozen_set = schema
1403 .columns
1404 .iter()
1405 .find(|c| c.name == "frozen_set")
1406 .unwrap();
1407 assert_eq!(frozen_set.data_type, "frozen<set<text>>");
1408
1409 let frozen_map = schema
1410 .columns
1411 .iter()
1412 .find(|c| c.name == "frozen_map")
1413 .unwrap();
1414 assert_eq!(frozen_map.data_type, "frozen<map<text, bigint>>");
1415
1416 let nested = schema
1417 .columns
1418 .iter()
1419 .find(|c| c.name == "nested_frozen")
1420 .unwrap();
1421 assert_eq!(nested.data_type, "frozen<list<frozen<set<uuid>>>>");
1422 }
1423
1424 #[test]
1425 fn test_udt_columns() {
1426 let cql = r#"
1427 CREATE TABLE user_profiles (
1428 user_id uuid PRIMARY KEY,
1429 address address_type,
1430 preferences frozen<user_prefs>
1431 )
1432 "#;
1433
1434 let schema = parse_cql_schema(cql).unwrap();
1435
1436 let address_col = schema.columns.iter().find(|c| c.name == "address").unwrap();
1437 assert_eq!(address_col.data_type, "address_type");
1438
1439 let prefs_col = schema
1440 .columns
1441 .iter()
1442 .find(|c| c.name == "preferences")
1443 .unwrap();
1444 assert_eq!(prefs_col.data_type, "frozen<user_prefs>");
1445 }
1446
1447 #[test]
1448 fn test_tuple_types() {
1449 let cql = r#"
1450 CREATE TABLE tuple_test (
1451 id uuid PRIMARY KEY,
1452 coordinates tuple<double, double>,
1453 person_info tuple<text, int, boolean>
1454 )
1455 "#;
1456
1457 let schema = parse_cql_schema(cql).unwrap();
1458
1459 let coords = schema
1460 .columns
1461 .iter()
1462 .find(|c| c.name == "coordinates")
1463 .unwrap();
1464 assert_eq!(coords.data_type, "tuple<double, double>");
1465
1466 let person = schema
1467 .columns
1468 .iter()
1469 .find(|c| c.name == "person_info")
1470 .unwrap();
1471 assert_eq!(person.data_type, "tuple<text, int, boolean>");
1472 }
1473
1474 #[test]
1479 fn test_cql_type_adversarial_deep_nesting_returns_err_not_abort() {
1480 let s = "frozen<".repeat(50_000) + "int" + &">".repeat(50_000);
1481 assert!(
1482 cql_type(&s).is_err(),
1483 "pathological nesting must return Err, not abort"
1484 );
1485 }
1486
1487 #[test]
1492 fn test_cql_type_nesting_depth_boundary_is_exact() {
1493 let depth = MAX_TYPE_NESTING_DEPTH; let ok_str = "frozen<".repeat(depth) + "int" + &">".repeat(depth);
1497 let (rest, parsed) =
1498 cql_type(&ok_str).expect("nesting at the depth bound must still parse");
1499 assert!(rest.trim().is_empty(), "entire input must be consumed");
1500 assert_eq!(parsed, ok_str, "type string must be preserved unchanged");
1501
1502 let bad_str = "frozen<".repeat(depth + 1) + "int" + &">".repeat(depth + 1);
1504 assert!(
1505 cql_type(&bad_str).is_err(),
1506 "one level past the bound must return Err"
1507 );
1508 }
1509
1510 #[test]
1516 fn test_cql_type_to_type_id_deep_frozen_returns_err_not_abort() {
1517 let s = "frozen<".repeat(50_000) + "int" + &">".repeat(50_000);
1518 assert!(
1519 cql_type_to_type_id(&s).is_err(),
1520 "pathological frozen nesting must return Err, not abort"
1521 );
1522
1523 let depth = MAX_TYPE_NESTING_DEPTH;
1524 let ok_str = "frozen<".repeat(depth) + "int" + &">".repeat(depth);
1527 assert_eq!(
1528 cql_type_to_type_id(&ok_str).expect("frozen nesting at the bound must resolve"),
1529 CqlTypeId::Int,
1530 "the leaf type id must be unchanged"
1531 );
1532
1533 let bad_str = "frozen<".repeat(depth + 1) + "int" + &">".repeat(depth + 1);
1534 let err = cql_type_to_type_id(&bad_str).expect_err("one level past the bound must error");
1535 let msg = err.to_string();
1536 assert!(
1537 msg.contains("nesting") || msg.contains("deep"),
1538 "error message must mention nesting/depth, got: {msg}"
1539 );
1540 }
1541
1542 #[test]
1543 fn test_case_insensitive_keywords() {
1544 let cql = r#"
1545 create table Users (
1546 ID UUID primary key,
1547 Name TEXT,
1548 Email VARCHAR
1549 )
1550 "#;
1551
1552 let schema = parse_cql_schema(cql).unwrap();
1553 assert_eq!(schema.table, "Users");
1554 assert_eq!(schema.columns.len(), 3);
1555 }
1556
1557 #[test]
1558 fn test_quoted_identifiers() {
1559 let cql = r#"
1560 CREATE TABLE "CaseSensitive" (
1561 "Id" uuid PRIMARY KEY,
1562 "Name With Spaces" text
1563 )
1564 "#;
1565
1566 let schema = parse_cql_schema(cql).unwrap();
1567 assert_eq!(schema.table, "CaseSensitive");
1568
1569 let space_col = schema.columns.iter().find(|c| c.name == "Name With Spaces");
1570 assert!(space_col.is_some());
1571 }
1572
1573 #[test]
1574 fn test_fallback_table_extraction() {
1575 let cql = "CREATE TABLE myapp.orders (id bigint PRIMARY KEY)";
1577 let (keyspace, table) = extract_table_name(cql).unwrap();
1578 assert_eq!(keyspace, Some("myapp".to_string()));
1579 assert_eq!(table, "orders");
1580 }
1581
1582 #[test]
1583 fn test_all_primitive_types() {
1584 let type_mappings = vec![
1585 ("ascii", CqlTypeId::Ascii),
1586 ("bigint", CqlTypeId::BigInt),
1587 ("blob", CqlTypeId::Blob),
1588 ("boolean", CqlTypeId::Boolean),
1589 ("counter", CqlTypeId::Counter),
1590 ("decimal", CqlTypeId::Decimal),
1591 ("double", CqlTypeId::Double),
1592 ("float", CqlTypeId::Float),
1593 ("int", CqlTypeId::Int),
1594 ("timestamp", CqlTypeId::Timestamp),
1595 ("uuid", CqlTypeId::Uuid),
1596 ("varchar", CqlTypeId::Varchar),
1597 ("text", CqlTypeId::Varchar),
1598 ("varint", CqlTypeId::Varint),
1599 ("timeuuid", CqlTypeId::Timeuuid),
1600 ("inet", CqlTypeId::Inet),
1601 ("date", CqlTypeId::Date),
1602 ("time", CqlTypeId::Time),
1603 ("smallint", CqlTypeId::Smallint),
1604 ("tinyint", CqlTypeId::Tinyint),
1605 ("duration", CqlTypeId::Duration),
1606 ];
1607
1608 for (cql_type, expected_id) in type_mappings {
1609 assert_eq!(
1610 cql_type_to_type_id(cql_type).unwrap(),
1611 expected_id,
1612 "Failed for type: {}",
1613 cql_type
1614 );
1615 }
1616 }
1617
1618 #[test]
1623 fn test_with_bloom_filter_fp_chance_preserved_in_comments() {
1624 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1625 WITH bloom_filter_fp_chance = 1.0";
1626 let schema = parse_cql_schema(cql).expect("schema should parse");
1627 assert_eq!(
1628 schema
1629 .comments
1630 .get("bloom_filter_fp_chance")
1631 .map(String::as_str),
1632 Some("1.0"),
1633 "WITH bloom_filter_fp_chance must be preserved into comments, got: {:?}",
1634 schema.comments
1635 );
1636 }
1637
1638 #[test]
1640 fn test_with_multiple_options_preserved() {
1641 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1642 WITH gc_grace_seconds = 0 AND bloom_filter_fp_chance = 0.01";
1643 let schema = parse_cql_schema(cql).expect("schema should parse");
1644 assert_eq!(
1645 schema
1646 .comments
1647 .get("bloom_filter_fp_chance")
1648 .map(String::as_str),
1649 Some("0.01")
1650 );
1651 assert_eq!(
1652 schema.comments.get("gc_grace_seconds").map(String::as_str),
1653 Some("0")
1654 );
1655 }
1656
1657 #[test]
1663 fn test_with_map_valued_option_before_bloom_preserved() {
1664 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1665 WITH compression = {'class': 'LZ4Compressor'} \
1666 AND bloom_filter_fp_chance = 1.0";
1667 let schema = parse_cql_schema(cql).expect("schema should parse");
1668 assert_eq!(
1669 schema
1670 .comments
1671 .get("bloom_filter_fp_chance")
1672 .map(String::as_str),
1673 Some("1.0"),
1674 "bloom_filter_fp_chance must survive a preceding map-valued option, got: {:?}",
1675 schema.comments
1676 );
1677 }
1678
1679 #[test]
1682 fn test_with_compaction_map_before_bloom_preserved() {
1683 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1684 WITH compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32'} \
1685 AND bloom_filter_fp_chance = 1.0";
1686 let schema = parse_cql_schema(cql).expect("schema should parse");
1687 assert_eq!(
1688 schema
1689 .comments
1690 .get("bloom_filter_fp_chance")
1691 .map(String::as_str),
1692 Some("1.0"),
1693 "bloom_filter_fp_chance must survive a preceding compaction map, got: {:?}",
1694 schema.comments
1695 );
1696 }
1697
1698 #[test]
1700 fn test_with_list_valued_option_before_bloom_preserved() {
1701 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1702 WITH some_list = ['a', 'b'] \
1703 AND bloom_filter_fp_chance = 1.0";
1704 let schema = parse_cql_schema(cql).expect("schema should parse");
1705 assert_eq!(
1706 schema
1707 .comments
1708 .get("bloom_filter_fp_chance")
1709 .map(String::as_str),
1710 Some("1.0"),
1711 "bloom_filter_fp_chance must survive a preceding list-valued option, got: {:?}",
1712 schema.comments
1713 );
1714 }
1715
1716 #[test]
1718 fn test_with_map_valued_option_value_captured() {
1719 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1720 WITH compression = {'class': 'LZ4Compressor'}";
1721 let schema = parse_cql_schema(cql).expect("schema should parse");
1722 assert_eq!(
1723 schema.comments.get("compression").map(String::as_str),
1724 Some("{'class': 'LZ4Compressor'}"),
1725 "map-valued option value should be preserved verbatim, got: {:?}",
1726 schema.comments
1727 );
1728 }
1729
1730 #[test]
1737 fn test_with_clustering_order_before_bloom_preserved() {
1738 let cql = "CREATE TABLE ks.t (id int, ck int, name text, PRIMARY KEY (id, ck)) \
1739 WITH CLUSTERING ORDER BY (ck DESC) AND bloom_filter_fp_chance = 1.0";
1740 let schema = parse_cql_schema(cql).expect("schema should parse");
1741 assert_eq!(
1742 schema
1743 .comments
1744 .get("bloom_filter_fp_chance")
1745 .map(String::as_str),
1746 Some("1.0"),
1747 "bloom_filter_fp_chance must survive a preceding CLUSTERING ORDER BY, got: {:?}",
1748 schema.comments
1749 );
1750 }
1751
1752 #[test]
1755 fn test_with_clustering_order_multi_column_before_bloom_preserved() {
1756 let cql =
1757 "CREATE TABLE ks.t (id int, c1 int, c2 int, name text, PRIMARY KEY (id, c1, c2)) \
1758 WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC) AND bloom_filter_fp_chance = 1.0";
1759 let schema = parse_cql_schema(cql).expect("schema should parse");
1760 assert_eq!(
1761 schema
1762 .comments
1763 .get("bloom_filter_fp_chance")
1764 .map(String::as_str),
1765 Some("1.0"),
1766 "bloom_filter_fp_chance must survive a multi-column CLUSTERING ORDER BY, got: {:?}",
1767 schema.comments
1768 );
1769 }
1770
1771 #[test]
1775 fn test_clustering_order_desc_applied_to_clustering_keys() {
1776 let cql = "CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck)) \
1777 WITH CLUSTERING ORDER BY (ck DESC)";
1778 let schema = parse_cql_schema(cql).expect("schema should parse");
1779 let ck = schema
1780 .clustering_keys
1781 .iter()
1782 .find(|c| c.name == "ck")
1783 .expect("ck clustering column must exist");
1784 assert_eq!(
1785 ck.order,
1786 ClusteringOrder::Desc,
1787 "ck must carry DESC ordering, got: {:?}",
1788 schema.clustering_keys
1789 );
1790 }
1791
1792 #[test]
1795 fn test_clustering_order_mixed_applied_per_column() {
1796 let cql = "CREATE TABLE ks.t (pk int, c1 int, c2 int, v int, PRIMARY KEY (pk, c1, c2)) \
1797 WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)";
1798 let schema = parse_cql_schema(cql).expect("schema should parse");
1799 let c1 = schema
1800 .clustering_keys
1801 .iter()
1802 .find(|c| c.name == "c1")
1803 .expect("c1 must exist");
1804 let c2 = schema
1805 .clustering_keys
1806 .iter()
1807 .find(|c| c.name == "c2")
1808 .expect("c2 must exist");
1809 assert_eq!(c1.order, ClusteringOrder::Asc, "c1 should be ASC");
1810 assert_eq!(c2.order, ClusteringOrder::Desc, "c2 should be DESC");
1811 }
1812
1813 #[test]
1816 fn test_clustering_order_defaults_to_asc_without_clause() {
1817 let cql = "CREATE TABLE ks.t (pk int, ck int, v int, PRIMARY KEY (pk, ck))";
1818 let schema = parse_cql_schema(cql).expect("schema should parse");
1819 let ck = schema
1820 .clustering_keys
1821 .iter()
1822 .find(|c| c.name == "ck")
1823 .expect("ck must exist");
1824 assert_eq!(
1825 ck.order,
1826 ClusteringOrder::Asc,
1827 "ck must default to ASC without a CLUSTERING ORDER BY clause"
1828 );
1829 }
1830
1831 #[test]
1836 fn test_clustering_order_quoted_identifier_applied() {
1837 let cql = "CREATE TABLE ks.t (pk int, \"Ck\" int, v int, PRIMARY KEY (pk, \"Ck\")) \
1838 WITH CLUSTERING ORDER BY (\"Ck\" DESC)";
1839 let schema = parse_cql_schema(cql).expect("schema should parse");
1840 let ck = schema
1841 .clustering_keys
1842 .iter()
1843 .find(|c| c.name == "Ck")
1844 .expect("Ck clustering column must exist");
1845 assert_eq!(
1846 ck.order,
1847 ClusteringOrder::Desc,
1848 "quoted \"Ck\" must carry DESC ordering, got: {:?}",
1849 schema.clustering_keys
1850 );
1851 }
1852
1853 #[test]
1856 fn test_clustering_order_mixed_quoted_and_unquoted() {
1857 let cql =
1858 "CREATE TABLE ks.t (pk int, c1 int, \"Ck\" int, v int, PRIMARY KEY (pk, c1, \"Ck\")) \
1859 WITH CLUSTERING ORDER BY (c1 ASC, \"Ck\" DESC)";
1860 let schema = parse_cql_schema(cql).expect("schema should parse");
1861 let c1 = schema
1862 .clustering_keys
1863 .iter()
1864 .find(|c| c.name == "c1")
1865 .expect("c1 must exist");
1866 let ck = schema
1867 .clustering_keys
1868 .iter()
1869 .find(|c| c.name == "Ck")
1870 .expect("Ck must exist");
1871 assert_eq!(c1.order, ClusteringOrder::Asc, "c1 should be ASC");
1872 assert_eq!(ck.order, ClusteringOrder::Desc, "Ck should be DESC");
1873 }
1874
1875 #[test]
1882 fn test_clustering_order_quoted_identifier_with_comma_applied() {
1883 let cql = "CREATE TABLE ks.t (pk int, \"C,k\" int, v int, PRIMARY KEY (pk, \"C,k\")) \
1884 WITH CLUSTERING ORDER BY (\"C,k\" DESC)";
1885 let schema = parse_cql_schema(cql).expect("schema should parse");
1886 let ck = schema
1887 .clustering_keys
1888 .iter()
1889 .find(|c| c.name == "C,k")
1890 .expect("\"C,k\" clustering column must exist");
1891 assert_eq!(
1892 ck.order,
1893 ClusteringOrder::Desc,
1894 "quoted \"C,k\" (with embedded comma) must carry DESC ordering, got: {:?}",
1895 schema.clustering_keys
1896 );
1897 }
1898
1899 #[test]
1907 fn test_clustering_order_quoted_identifier_with_close_paren_applied() {
1908 let cql = "CREATE TABLE ks.t (pk int, \"C)k\" int, v int, PRIMARY KEY (pk, \"C)k\")) \
1909 WITH CLUSTERING ORDER BY (\"C)k\" DESC)";
1910 let schema = parse_cql_schema(cql).expect("schema should parse");
1911 let ck = schema
1912 .clustering_keys
1913 .iter()
1914 .find(|c| c.name == "C)k")
1915 .expect("\"C)k\" clustering column must exist");
1916 assert_eq!(
1917 ck.order,
1918 ClusteringOrder::Desc,
1919 "quoted \"C)k\" (with embedded close-paren) must carry DESC ordering, got: {:?}",
1920 schema.clustering_keys
1921 );
1922 }
1923
1924 #[test]
1933 fn test_with_quote_escaped_comment_before_bloom_preserved() {
1934 let cql = "CREATE TABLE ks.t (id int PRIMARY KEY, name text) \
1935 WITH comment = 'Bob''s table' AND bloom_filter_fp_chance = 1.0";
1936 let schema = parse_cql_schema(cql).expect("schema should parse");
1937 assert_eq!(
1938 schema
1939 .comments
1940 .get("bloom_filter_fp_chance")
1941 .map(String::as_str),
1942 Some("1.0"),
1943 "bloom_filter_fp_chance must survive a quote-escaped comment, got: {:?}",
1944 schema.comments
1945 );
1946 assert_eq!(
1947 schema.comments.get("comment").map(String::as_str),
1948 Some("Bob's table"),
1949 "doubled single-quote must be un-escaped in the captured comment, got: {:?}",
1950 schema.comments
1951 );
1952 }
1953}