Skip to main content

toasty_core/schema/db/
column.rs

1use super::{TableId, Type, table};
2use crate::stmt;
3
4use std::fmt;
5
6/// A column in a database table.
7///
8/// Each column has a logical type ([`stmt::Type`]) used by the query engine and
9/// a storage type ([`Type`]) representing how the value is stored in the database.
10///
11/// # Examples
12///
13/// ```ignore
14/// use toasty_core::schema::db::{Column, ColumnId, TableId, Type};
15/// use toasty_core::stmt;
16///
17/// let column = Column {
18///     id: ColumnId { table: TableId(0), index: 0 },
19///     name: "email".to_string(),
20///     ty: stmt::Type::String,
21///     storage_ty: Type::VarChar(255),
22///     nullable: false,
23///     primary_key: false,
24///     auto_increment: false,
25/// };
26///
27/// assert_eq!(column.name, "email");
28/// assert!(!column.nullable);
29/// ```
30#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Column {
33    /// Uniquely identifies the column in the schema.
34    pub id: ColumnId,
35
36    /// The name of the column in the database.
37    pub name: String,
38
39    /// The column type, from Toasty's point of view.
40    pub ty: stmt::Type,
41
42    /// The database storage type of the column.
43    pub storage_ty: Type,
44
45    /// Whether or not the column is nullable
46    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
47    pub nullable: bool,
48
49    /// True if the column is part of the table's primary key
50    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
51    pub primary_key: bool,
52
53    /// True if the column is an integer that should be auto-incremented
54    /// with each insertion of a new row. This should be false if a `storage_ty`
55    /// of type `Serial` is used.
56    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
57    pub auto_increment: bool,
58
59    /// True if the column tracks an OCC version counter.
60    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
61    pub versionable: bool,
62}
63
64#[cfg(feature = "serde")]
65fn is_false(b: &bool) -> bool {
66    !*b
67}
68
69/// Uniquely identifies a column within a schema.
70///
71/// A `ColumnId` combines the [`TableId`] of the owning table with the column's
72/// positional index within that table's column list.
73///
74/// # Examples
75///
76/// ```ignore
77/// use toasty_core::schema::db::{ColumnId, TableId};
78///
79/// let id = ColumnId { table: TableId(0), index: 2 };
80/// assert_eq!(id.index, 2);
81/// ```
82#[derive(PartialEq, Eq, Clone, Copy, Hash)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84pub struct ColumnId {
85    /// The table this column belongs to.
86    pub table: TableId,
87    /// Zero-based position of this column in the table's column list.
88    pub index: usize,
89}
90
91impl ColumnId {
92    pub(crate) fn placeholder() -> Self {
93        Self {
94            table: table::TableId::placeholder(),
95            index: usize::MAX,
96        }
97    }
98}
99
100impl From<&Column> for ColumnId {
101    fn from(value: &Column) -> Self {
102        value.id
103    }
104}
105
106impl fmt::Debug for ColumnId {
107    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(fmt, "ColumnId({}/{})", self.table.0, self.index)
109    }
110}
111
112#[cfg(all(test, feature = "serde"))]
113mod serde_tests {
114    use crate::schema::db::{Column, ColumnId, TableId, Type};
115    use crate::stmt;
116
117    fn base_column() -> Column {
118        Column {
119            id: ColumnId {
120                table: TableId(0),
121                index: 0,
122            },
123            name: "test".to_string(),
124            ty: stmt::Type::String,
125            storage_ty: Type::Text,
126            nullable: false,
127            primary_key: false,
128            auto_increment: false,
129            versionable: false,
130        }
131    }
132
133    #[test]
134    fn false_booleans_are_omitted() {
135        let toml = toml::to_string(&base_column()).unwrap();
136        assert!(!toml.contains("nullable"), "toml: {toml}");
137        assert!(!toml.contains("primary_key"), "toml: {toml}");
138        assert!(!toml.contains("auto_increment"), "toml: {toml}");
139        assert!(!toml.contains("versionable"), "toml: {toml}");
140    }
141
142    #[test]
143    fn nullable_true_is_included() {
144        let col = Column {
145            nullable: true,
146            ..base_column()
147        };
148        let toml = toml::to_string(&col).unwrap();
149        assert!(toml.contains("nullable = true"), "toml: {toml}");
150    }
151
152    #[test]
153    fn primary_key_true_is_included() {
154        let col = Column {
155            primary_key: true,
156            ..base_column()
157        };
158        let toml = toml::to_string(&col).unwrap();
159        assert!(toml.contains("primary_key = true"), "toml: {toml}");
160    }
161
162    #[test]
163    fn auto_increment_true_is_included() {
164        let col = Column {
165            auto_increment: true,
166            ..base_column()
167        };
168        let toml = toml::to_string(&col).unwrap();
169        assert!(toml.contains("auto_increment = true"), "toml: {toml}");
170    }
171
172    #[test]
173    fn missing_bool_fields_deserialize_as_false() {
174        let toml = "name = \"test\"\nty = \"String\"\nstorage_ty = \"Text\"\n\n[id]\ntable = 0\nindex = 0\n";
175        let col: Column = toml::from_str(toml).unwrap();
176        assert!(!col.nullable);
177        assert!(!col.primary_key);
178        assert!(!col.auto_increment);
179        assert!(!col.versionable);
180    }
181
182    #[test]
183    fn round_trip_all_true() {
184        let original = Column {
185            nullable: true,
186            primary_key: true,
187            auto_increment: true,
188            ..base_column()
189        };
190        let decoded: Column = toml::from_str(&toml::to_string(&original).unwrap()).unwrap();
191        assert_eq!(original, decoded);
192    }
193}