Skip to main content

cqlc_core/
schema.rs

1//! The resolved schema model.
2
3use crate::span::Span;
4use crate::types::{CqlType, Order};
5
6/// The role of a column in the primary key.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum ColumnRole {
10    /// Part of the partition key.
11    PartitionKey,
12    /// A clustering column.
13    ClusteringKey,
14    /// Not part of the primary key.
15    Regular,
16}
17
18/// A table column.
19#[derive(Clone, Debug, PartialEq)]
20#[non_exhaustive]
21pub struct Column {
22    /// Normalized column name.
23    pub name: String,
24    /// The declared type.
25    pub cql_type: CqlType,
26    /// Position in the primary key.
27    pub role: ColumnRole,
28    /// Declared `STATIC`.
29    pub is_static: bool,
30    /// Where the column is defined.
31    pub span: Span,
32}
33
34impl Column {
35    /// Whether the column is part of the primary key.
36    pub fn is_key(&self) -> bool {
37        self.role != ColumnRole::Regular
38    }
39
40    /// Primary key columns are never null; everything else may be absent.
41    pub fn nullable(&self) -> bool {
42        !self.is_key()
43    }
44}
45
46/// A clustering column with its sort order.
47#[derive(Clone, Debug, PartialEq)]
48#[non_exhaustive]
49pub struct ClusteringColumn {
50    /// Column name.
51    pub name: String,
52    /// Declared order (`CLUSTERING ORDER BY`), ascending by default.
53    pub order: Order,
54}
55
56/// A table definition.
57#[derive(Clone, Debug, PartialEq)]
58#[non_exhaustive]
59pub struct Table {
60    /// Keyspace, if the `CREATE TABLE` qualified the name.
61    pub keyspace: Option<String>,
62    /// Table name.
63    pub name: String,
64    /// In declaration order.
65    pub columns: Vec<Column>,
66    /// Names of the partition key columns, in key order.
67    pub partition_key: Vec<String>,
68    /// Clustering columns, in key order.
69    pub clustering: Vec<ClusteringColumn>,
70    /// Where the table is defined.
71    pub span: Span,
72}
73
74impl Table {
75    /// Finds a column by its normalized name.
76    pub fn column(&self, name: &str) -> Option<&Column> {
77        self.columns.iter().find(|c| c.name == name)
78    }
79
80    /// Whether the table has a counter column.
81    pub fn is_counter_table(&self) -> bool {
82        self.columns
83            .iter()
84            .any(|c| matches!(c.cql_type, CqlType::Counter))
85    }
86
87    /// `keyspace.table`, or just the table name if unqualified.
88    pub fn qualified_name(&self) -> String {
89        match &self.keyspace {
90            Some(ks) => format!("{ks}.{}", self.name),
91            None => self.name.clone(),
92        }
93    }
94}
95
96/// A user-defined type.
97#[derive(Clone, Debug, PartialEq)]
98#[non_exhaustive]
99pub struct Udt {
100    /// Keyspace, if qualified.
101    pub keyspace: Option<String>,
102    /// Type name.
103    pub name: String,
104    /// Fields in declaration order.
105    pub fields: Vec<(String, CqlType)>,
106    /// Where the type is defined.
107    pub span: Span,
108}
109
110/// All tables and user-defined types.
111#[derive(Clone, Debug, Default, PartialEq)]
112#[non_exhaustive]
113pub struct Schema {
114    /// Tables in definition order.
115    pub tables: Vec<Table>,
116    /// User-defined types in definition order.
117    pub udts: Vec<Udt>,
118}
119
120impl Schema {
121    /// Finds a user-defined type by name.
122    pub fn udt(&self, name: &str) -> Option<&Udt> {
123        self.udts.iter().find(|u| u.name == name)
124    }
125}
126
127/// Result of [`Schema::lookup_table`].
128#[derive(Debug)]
129#[non_exhaustive]
130pub enum Lookup<'a> {
131    /// Exactly one table matches.
132    Found(&'a Table),
133    /// No table matches.
134    NotFound,
135    /// Unqualified name that exists in several keyspaces.
136    Ambiguous(Vec<&'a Table>),
137}
138
139impl Schema {
140    /// Qualified names must match a table declared in that keyspace;
141    /// unqualified names match by table name alone.
142    pub fn lookup_table(&self, keyspace: Option<&str>, name: &str) -> Lookup<'_> {
143        let matches: Vec<&Table> = self
144            .tables
145            .iter()
146            .filter(|t| {
147                t.name == name && keyspace.is_none_or(|ks| t.keyspace.as_deref() == Some(ks))
148            })
149            .collect();
150        match matches.len() {
151            0 => Lookup::NotFound,
152            1 => Lookup::Found(matches[0]),
153            _ => Lookup::Ambiguous(matches),
154        }
155    }
156}