1use crate::schema::CqlType;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub enum CqlStatement {
14 Select(CqlSelect),
16 Insert(CqlInsert),
18 Update(CqlUpdate),
20 Delete(CqlDelete),
22 CreateTable(CqlCreateTable),
24 DropTable(CqlDropTable),
26 CreateIndex(CqlCreateIndex),
28 AlterTable(CqlAlterTable),
30 CreateType(CqlCreateType),
32 DropType(CqlDropType),
34 Use(CqlUse),
36 Truncate(CqlTruncate),
38 Batch(CqlBatch),
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct CqlSelect {
45 pub distinct: bool,
47 pub select_list: Vec<CqlSelectItem>,
49 pub from: CqlTable,
51 pub where_clause: Option<CqlExpression>,
53 pub order_by: Option<Vec<CqlOrderBy>>,
55 pub limit: Option<u64>,
57 pub allow_filtering: bool,
59}
60
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63pub enum CqlSelectItem {
64 Wildcard,
66 Expression {
68 expression: CqlExpression,
69 alias: Option<CqlIdentifier>,
70 },
71 Function {
73 name: CqlIdentifier,
74 args: Vec<CqlExpression>,
75 alias: Option<CqlIdentifier>,
76 },
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct CqlInsert {
82 pub table: CqlTable,
84 pub columns: Vec<CqlIdentifier>,
86 pub values: CqlInsertValues,
88 pub if_not_exists: bool,
90 pub using: Option<CqlUsing>,
92}
93
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub enum CqlInsertValues {
97 Values(Vec<CqlExpression>),
99 Json(String),
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct CqlUpdate {
106 pub table: CqlTable,
108 pub using: Option<CqlUsing>,
110 pub assignments: Vec<CqlAssignment>,
112 pub where_clause: CqlExpression,
114 pub if_condition: Option<CqlExpression>,
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
120pub struct CqlAssignment {
121 pub column: CqlIdentifier,
123 pub operator: CqlAssignmentOperator,
125 pub value: CqlExpression,
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub enum CqlAssignmentOperator {
132 Assign,
134 AddAssign,
136 SubAssign,
138 ListAppend,
140 ListPrepend,
142 SetAdd,
144 SetRemove,
146 MapUpdate(CqlExpression),
148}
149
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152pub struct CqlDelete {
153 pub columns: Vec<CqlIdentifier>,
155 pub table: CqlTable,
157 pub using: Option<CqlUsing>,
159 pub where_clause: CqlExpression,
161 pub if_condition: Option<CqlExpression>,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct CqlCreateTable {
168 pub if_not_exists: bool,
170 pub table: CqlTable,
172 pub columns: Vec<CqlColumnDef>,
174 pub primary_key: CqlPrimaryKey,
176 pub options: CqlTableOptions,
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct CqlColumnDef {
183 pub name: CqlIdentifier,
185 pub data_type: CqlDataType,
187 pub is_static: bool,
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct CqlPrimaryKey {
194 pub partition_key: Vec<CqlIdentifier>,
196 pub clustering_key: Vec<CqlIdentifier>,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202pub struct CqlDropTable {
203 pub if_exists: bool,
205 pub table: CqlTable,
207}
208
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211pub struct CqlCreateIndex {
212 pub if_not_exists: bool,
214 pub name: Option<CqlIdentifier>,
216 pub table: CqlTable,
218 pub columns: Vec<CqlIndexColumn>,
220 pub using: Option<String>,
222 pub options: HashMap<String, String>,
224}
225
226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
228pub enum CqlIndexColumn {
229 Column(CqlIdentifier),
231 Keys(CqlIdentifier),
233 Values(CqlIdentifier),
235 Entries(CqlIdentifier),
237 Full(CqlIdentifier),
239}
240
241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243pub struct CqlAlterTable {
244 pub table: CqlTable,
246 pub operation: CqlAlterTableOp,
248}
249
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub enum CqlAlterTableOp {
253 AddColumn(CqlColumnDef),
255 DropColumn(CqlIdentifier),
257 AlterColumn {
259 column: CqlIdentifier,
260 new_type: CqlDataType,
261 },
262 RenameColumn {
264 old_name: CqlIdentifier,
265 new_name: CqlIdentifier,
266 },
267 WithOptions(CqlTableOptions),
269}
270
271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
273pub struct CqlCreateType {
274 pub if_not_exists: bool,
276 pub name: CqlIdentifier,
278 pub fields: Vec<CqlUdtField>,
280}
281
282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
284pub struct CqlUdtField {
285 pub name: CqlIdentifier,
287 pub data_type: CqlDataType,
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct CqlDropType {
294 pub if_exists: bool,
296 pub name: CqlIdentifier,
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub struct CqlUse {
303 pub keyspace: CqlIdentifier,
305}
306
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
309pub struct CqlTruncate {
310 pub table: CqlTable,
312}
313
314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct CqlBatch {
321 pub batch_type: CqlBatchType,
323 pub using: Option<CqlUsing>,
325 pub statements: Vec<CqlBatchStatement>,
327}
328
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331pub enum CqlBatchType {
332 Logged,
334 Unlogged,
336 Counter,
338}
339
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
342pub enum CqlBatchStatement {
343 Insert(CqlInsert),
345 Update(CqlUpdate),
347 Delete(CqlDelete),
349}
350
351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353pub enum CqlExpression {
354 Literal(CqlLiteral),
356 Column(CqlIdentifier),
358 Parameter(u32),
360 NamedParameter(String),
362 Binary {
364 left: Box<CqlExpression>,
365 operator: CqlBinaryOperator,
366 right: Box<CqlExpression>,
367 },
368 Unary {
370 operator: CqlUnaryOperator,
371 operand: Box<CqlExpression>,
372 },
373 Function {
375 name: CqlIdentifier,
376 args: Vec<CqlExpression>,
377 },
378 In {
380 expression: Box<CqlExpression>,
381 values: Vec<CqlExpression>,
382 },
383 Contains {
385 column: CqlIdentifier,
386 value: Box<CqlExpression>,
387 },
388 ContainsKey {
390 column: CqlIdentifier,
391 key: Box<CqlExpression>,
392 },
393 CollectionAccess {
395 collection: Box<CqlExpression>,
396 index: Box<CqlExpression>,
397 },
398 FieldAccess {
400 object: Box<CqlExpression>,
401 field: CqlIdentifier,
402 },
403 Case {
405 when_clauses: Vec<CqlWhenClause>,
406 else_clause: Option<Box<CqlExpression>>,
407 },
408 Cast {
410 expression: Box<CqlExpression>,
411 target_type: CqlDataType,
412 },
413}
414
415#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
417pub struct CqlWhenClause {
418 pub condition: CqlExpression,
420 pub result: CqlExpression,
422}
423
424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
426pub enum CqlBinaryOperator {
427 And,
429 Or,
430
431 Eq,
433 Ne,
434 Lt,
435 Le,
436 Gt,
437 Ge,
438
439 Add,
441 Sub,
442 Mul,
443 Div,
444 Mod,
445
446 Like,
448}
449
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
452pub enum CqlUnaryOperator {
453 Not,
455 Minus,
457 Plus,
459}
460
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
463pub enum CqlLiteral {
464 Null,
466 Boolean(bool),
468 Integer(i64),
470 Float(f64),
472 String(String),
474 Uuid(String),
476 Blob(String),
478 Collection(CqlCollectionLiteral),
480 Udt(CqlUdtLiteral),
482 Tuple(Vec<CqlLiteral>),
484}
485
486#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
488pub enum CqlCollectionLiteral {
489 List(Vec<CqlLiteral>),
491 Set(Vec<CqlLiteral>),
493 Map(Vec<(CqlLiteral, CqlLiteral)>),
495}
496
497#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
499pub struct CqlUdtLiteral {
500 pub type_name: Option<CqlIdentifier>,
502 pub fields: Vec<(CqlIdentifier, CqlLiteral)>,
504}
505
506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
508pub enum CqlDataType {
509 Boolean,
511 TinyInt,
512 SmallInt,
513 Int,
514 BigInt,
515 Varint,
516 Decimal,
517 Float,
518 Double,
519 Text,
520 Ascii,
521 Varchar,
522 Blob,
523 Timestamp,
524 Date,
525 Time,
526 Uuid,
527 TimeUuid,
528 Inet,
529 Duration,
530 Counter,
531
532 List(Box<CqlDataType>),
534 Set(Box<CqlDataType>),
535 Map(Box<CqlDataType>, Box<CqlDataType>),
536
537 Tuple(Vec<CqlDataType>),
539 Udt(CqlIdentifier),
540 Frozen(Box<CqlDataType>),
541
542 Custom(String),
544}
545
546#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
548pub struct CqlIdentifier {
549 pub name: String,
551 pub quoted: bool,
553}
554
555#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
557pub struct CqlTable {
558 pub keyspace: Option<CqlIdentifier>,
560 pub name: CqlIdentifier,
562}
563
564#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
566pub struct CqlOrderBy {
567 pub column: CqlIdentifier,
569 pub direction: CqlSortDirection,
571}
572
573pub type CqlOrdering = CqlOrderBy;
575
576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
578pub struct CqlLimit {
579 pub count: u64,
581}
582
583#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
585pub struct CqlTtl {
586 pub seconds: Option<CqlExpression>,
588}
589
590#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
592pub struct CqlTimestamp {
593 pub microseconds: Option<CqlExpression>,
595}
596
597#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
599pub enum CqlSortDirection {
600 Asc,
602 Desc,
604}
605
606#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
608pub struct CqlUsing {
609 pub ttl: Option<CqlExpression>,
611 pub timestamp: Option<CqlExpression>,
613}
614
615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
617pub struct CqlTableOptions {
618 pub options: HashMap<String, CqlLiteral>,
620}
621
622impl CqlIdentifier {
623 pub fn new(name: impl Into<String>) -> Self {
625 Self {
626 name: name.into(),
627 quoted: false,
628 }
629 }
630
631 pub fn quoted(name: impl Into<String>) -> Self {
633 Self {
634 name: name.into(),
635 quoted: true,
636 }
637 }
638
639 pub fn as_str(&self) -> &str {
641 &self.name
642 }
643
644 pub fn name(&self) -> &str {
646 &self.name
647 }
648
649 pub fn is_quoted(&self) -> bool {
651 self.quoted
652 }
653
654 pub fn needs_quoting(&self) -> bool {
656 self.quoted || !self.is_valid_unquoted()
657 }
658
659 fn is_valid_unquoted(&self) -> bool {
661 let mut chars = self.name.chars();
662 let Some(first) = chars.next() else {
663 return false;
664 };
665 (first.is_ascii_alphabetic() || first == '_')
666 && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
667 }
668}
669
670impl CqlTable {
671 pub fn new(name: impl Into<String>) -> Self {
673 Self {
674 keyspace: None,
675 name: CqlIdentifier::new(name),
676 }
677 }
678
679 pub fn with_keyspace(keyspace: impl Into<String>, name: impl Into<String>) -> Self {
681 Self {
682 keyspace: Some(CqlIdentifier::new(keyspace)),
683 name: CqlIdentifier::new(name),
684 }
685 }
686
687 pub fn full_name(&self) -> String {
689 match &self.keyspace {
690 Some(ks) => format!("{}.{}", ks.as_str(), self.name.as_str()),
691 None => self.name.as_str().to_string(),
692 }
693 }
694
695 pub fn name(&self) -> &CqlIdentifier {
697 &self.name
698 }
699
700 pub fn keyspace(&self) -> Option<&CqlIdentifier> {
702 self.keyspace.as_ref()
703 }
704}
705
706impl From<CqlDataType> for CqlType {
707 fn from(data_type: CqlDataType) -> Self {
708 match data_type {
709 CqlDataType::Boolean => CqlType::Boolean,
710 CqlDataType::TinyInt => CqlType::TinyInt,
711 CqlDataType::SmallInt => CqlType::SmallInt,
712 CqlDataType::Int => CqlType::Int,
713 CqlDataType::BigInt => CqlType::BigInt,
714 CqlDataType::Float => CqlType::Float,
715 CqlDataType::Double => CqlType::Double,
716 CqlDataType::Decimal => CqlType::Decimal,
717 CqlDataType::Text | CqlDataType::Varchar => CqlType::Text,
718 CqlDataType::Ascii => CqlType::Ascii,
719 CqlDataType::Blob => CqlType::Blob,
720 CqlDataType::Timestamp => CqlType::Timestamp,
721 CqlDataType::Date => CqlType::Date,
722 CqlDataType::Time => CqlType::Time,
723 CqlDataType::Uuid => CqlType::Uuid,
724 CqlDataType::TimeUuid => CqlType::TimeUuid,
725 CqlDataType::Inet => CqlType::Inet,
726 CqlDataType::Duration => CqlType::Duration,
727 CqlDataType::List(inner) => CqlType::List(Box::new((*inner).into())),
728 CqlDataType::Set(inner) => CqlType::Set(Box::new((*inner).into())),
729 CqlDataType::Map(key, value) => {
730 CqlType::Map(Box::new((*key).into()), Box::new((*value).into()))
731 }
732 CqlDataType::Tuple(types) => {
733 CqlType::Tuple(types.into_iter().map(|t| t.into()).collect())
734 }
735 CqlDataType::Udt(name) => CqlType::Udt(name.as_str().to_string(), vec![]),
736 CqlDataType::Frozen(inner) => CqlType::Frozen(Box::new((*inner).into())),
737 CqlDataType::Custom(name) => CqlType::Custom(name),
738 CqlDataType::Varint => CqlType::BigInt, CqlDataType::Counter => CqlType::Counter,
740 }
741 }
742}
743
744impl From<CqlType> for CqlDataType {
745 fn from(cql_type: CqlType) -> Self {
746 match cql_type {
747 CqlType::Boolean => CqlDataType::Boolean,
748 CqlType::TinyInt => CqlDataType::TinyInt,
749 CqlType::SmallInt => CqlDataType::SmallInt,
750 CqlType::Int => CqlDataType::Int,
751 CqlType::BigInt => CqlDataType::BigInt,
752 CqlType::Counter => CqlDataType::Counter,
753 CqlType::Float => CqlDataType::Float,
754 CqlType::Double => CqlDataType::Double,
755 CqlType::Decimal => CqlDataType::Decimal,
756 CqlType::Text | CqlType::Varchar => CqlDataType::Text,
757 CqlType::Ascii => CqlDataType::Ascii,
758 CqlType::Blob => CqlDataType::Blob,
759 CqlType::Timestamp => CqlDataType::Timestamp,
760 CqlType::Date => CqlDataType::Date,
761 CqlType::Time => CqlDataType::Time,
762 CqlType::Uuid => CqlDataType::Uuid,
763 CqlType::TimeUuid => CqlDataType::TimeUuid,
764 CqlType::Inet => CqlDataType::Inet,
765 CqlType::Duration => CqlDataType::Duration,
766 CqlType::Varint => CqlDataType::Custom("varint".to_string()),
767 CqlType::List(inner) => CqlDataType::List(Box::new((*inner).into())),
768 CqlType::Set(inner) => CqlDataType::Set(Box::new((*inner).into())),
769 CqlType::Map(key, value) => {
770 CqlDataType::Map(Box::new((*key).into()), Box::new((*value).into()))
771 }
772 CqlType::Tuple(types) => {
773 CqlDataType::Tuple(types.into_iter().map(|t| t.into()).collect())
774 }
775 CqlType::Udt(name, _) => CqlDataType::Udt(CqlIdentifier::new(name)),
776 CqlType::Frozen(inner) => CqlDataType::Frozen(Box::new((*inner).into())),
777 CqlType::Custom(name) => CqlDataType::Custom(name),
778 }
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785
786 #[test]
787 fn test_identifier_creation() {
788 let id1 = CqlIdentifier::new("test");
789 assert_eq!(id1.name, "test");
790 assert!(!id1.quoted);
791 assert!(!id1.needs_quoting());
792
793 let id2 = CqlIdentifier::quoted("test");
794 assert_eq!(id2.name, "test");
795 assert!(id2.quoted);
796 assert!(id2.needs_quoting());
797 }
798
799 #[test]
800 fn test_identifier_validation() {
801 assert!(CqlIdentifier::new("valid_name").is_valid_unquoted());
802 assert!(CqlIdentifier::new("_valid").is_valid_unquoted());
803 assert!(CqlIdentifier::new("valid123").is_valid_unquoted());
804
805 assert!(!CqlIdentifier::new("123invalid").is_valid_unquoted());
806 assert!(!CqlIdentifier::new("invalid-name").is_valid_unquoted());
807 assert!(!CqlIdentifier::new("").is_valid_unquoted());
808 }
809
810 #[test]
811 fn test_table_creation() {
812 let table1 = CqlTable::new("users");
813 assert_eq!(table1.name.as_str(), "users");
814 assert!(table1.keyspace.is_none());
815 assert_eq!(table1.full_name(), "users");
816
817 let table2 = CqlTable::with_keyspace("test", "users");
818 assert_eq!(table2.keyspace.as_ref().unwrap().as_str(), "test");
819 assert_eq!(table2.name.as_str(), "users");
820 assert_eq!(table2.full_name(), "test.users");
821 }
822
823 #[test]
824 fn test_data_type_conversion() {
825 let cql_type = CqlType::List(Box::new(CqlType::Text));
826 let data_type: CqlDataType = cql_type.clone().into();
827 let back_to_cql: CqlType = data_type.into();
828 assert_eq!(cql_type, back_to_cql);
829 }
830
831 #[test]
832 fn test_identifier_needs_quoting_rules() {
833 let numeric_start = CqlIdentifier::new("123abc");
834 assert!(numeric_start.needs_quoting());
835
836 let mixed_case = CqlIdentifier::new("CamelCase");
837 assert!(!mixed_case.needs_quoting());
838
839 let quoted = CqlIdentifier::quoted("any value");
840 assert!(quoted.needs_quoting());
841 }
842
843 #[test]
844 fn test_table_options_default_is_empty() {
845 let options = CqlTableOptions::default();
846 assert!(options.options.is_empty());
847 }
848
849 #[test]
850 fn test_varint_and_counter_mapping() {
851 let big_int = CqlType::from(CqlDataType::Varint);
852 assert_eq!(big_int, CqlType::BigInt);
853
854 let counter_type = CqlType::from(CqlDataType::Counter);
855 assert_eq!(counter_type, CqlType::Counter);
856 }
857}