Skip to main content

hematite/catalog/
object.rs

1//! Schema-level relational objects beyond base tables.
2
3use crate::error::{HematiteError, Result};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct View {
7    pub name: String,
8    pub query_sql: String,
9    pub column_names: Vec<String>,
10    pub dependencies: Vec<String>,
11}
12
13impl View {
14    pub fn validate(&self) -> Result<()> {
15        if self.name.is_empty() {
16            return Err(HematiteError::StorageError(
17                "View name cannot be empty".to_string(),
18            ));
19        }
20        if self.query_sql.trim().is_empty() {
21            return Err(HematiteError::StorageError(format!(
22                "View '{}' must store a query",
23                self.name
24            )));
25        }
26        if self.column_names.is_empty() {
27            return Err(HematiteError::StorageError(format!(
28                "View '{}' must expose at least one column",
29                self.name
30            )));
31        }
32        Ok(())
33    }
34
35    pub fn serialize(&self, buffer: &mut Vec<u8>) {
36        write_string(buffer, &self.name);
37        write_string(buffer, &self.query_sql);
38        buffer.extend_from_slice(&(self.column_names.len() as u32).to_le_bytes());
39        for column_name in &self.column_names {
40            write_string(buffer, column_name);
41        }
42        buffer.extend_from_slice(&(self.dependencies.len() as u32).to_le_bytes());
43        for dependency in &self.dependencies {
44            write_string(buffer, dependency);
45        }
46    }
47
48    pub fn deserialize(buffer: &[u8], offset: &mut usize) -> Result<Self> {
49        let name = read_string_with_len(buffer, offset, "view name")?;
50        let query_sql = read_string_with_len(buffer, offset, "view query")?;
51        let column_count = read_len(buffer, offset, "view column count")?;
52        let mut column_names = Vec::with_capacity(column_count);
53        for _ in 0..column_count {
54            column_names.push(read_string_with_len(buffer, offset, "view column name")?);
55        }
56        let dependency_count = read_len(buffer, offset, "view dependency count")?;
57        let mut dependencies = Vec::with_capacity(dependency_count);
58        for _ in 0..dependency_count {
59            dependencies.push(read_string_with_len(buffer, offset, "view dependency")?);
60        }
61        let view = Self {
62            name,
63            query_sql,
64            column_names,
65            dependencies,
66        };
67        view.validate()?;
68        Ok(view)
69    }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum TriggerEvent {
74    Insert,
75    Update,
76    Delete,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Trigger {
81    pub name: String,
82    pub table_name: String,
83    pub event: TriggerEvent,
84    pub body_sql: String,
85    pub old_alias: Option<String>,
86    pub new_alias: Option<String>,
87}
88
89impl Trigger {
90    pub fn validate(&self) -> Result<()> {
91        if self.name.is_empty() {
92            return Err(HematiteError::StorageError(
93                "Trigger name cannot be empty".to_string(),
94            ));
95        }
96        if self.table_name.is_empty() {
97            return Err(HematiteError::StorageError(format!(
98                "Trigger '{}' must reference a table",
99                self.name
100            )));
101        }
102        if self.body_sql.trim().is_empty() {
103            return Err(HematiteError::StorageError(format!(
104                "Trigger '{}' must store a body statement",
105                self.name
106            )));
107        }
108        Ok(())
109    }
110
111    pub fn serialize(&self, buffer: &mut Vec<u8>) {
112        write_string(buffer, &self.name);
113        write_string(buffer, &self.table_name);
114        buffer.push(trigger_event_to_byte(self.event));
115        write_string(buffer, &self.body_sql);
116        write_optional_string(buffer, self.old_alias.as_deref());
117        write_optional_string(buffer, self.new_alias.as_deref());
118    }
119
120    pub fn deserialize(buffer: &[u8], offset: &mut usize) -> Result<Self> {
121        let name = read_string_with_len(buffer, offset, "trigger name")?;
122        let table_name = read_string_with_len(buffer, offset, "trigger table")?;
123        let event = read_trigger_event(buffer, offset)?;
124        let body_sql = read_string_with_len(buffer, offset, "trigger body")?;
125        let old_alias = read_optional_string(buffer, offset, "trigger OLD alias")?;
126        let new_alias = read_optional_string(buffer, offset, "trigger NEW alias")?;
127        let trigger = Self {
128            name,
129            table_name,
130            event,
131            body_sql,
132            old_alias,
133            new_alias,
134        };
135        trigger.validate()?;
136        Ok(trigger)
137    }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum NamedConstraintKind {
142    Check,
143    ForeignKey,
144    Unique,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct NamedConstraint {
149    pub table_name: String,
150    pub name: String,
151    pub kind: NamedConstraintKind,
152}
153
154fn write_string(buffer: &mut Vec<u8>, value: &str) {
155    buffer.extend_from_slice(&(value.len() as u32).to_le_bytes());
156    buffer.extend_from_slice(value.as_bytes());
157}
158
159fn write_optional_string(buffer: &mut Vec<u8>, value: Option<&str>) {
160    match value {
161        Some(value) => {
162            buffer.push(1);
163            write_string(buffer, value);
164        }
165        None => buffer.push(0),
166    }
167}
168
169fn read_len(buffer: &[u8], offset: &mut usize, label: &str) -> Result<usize> {
170    if *offset + 4 > buffer.len() {
171        return Err(HematiteError::CorruptedData(format!("Invalid {}", label)));
172    }
173    let len = u32::from_le_bytes(
174        buffer[*offset..*offset + 4]
175            .try_into()
176            .map_err(|_| HematiteError::CorruptedData(format!("Invalid {}", label)))?,
177    ) as usize;
178    *offset += 4;
179    Ok(len)
180}
181
182fn read_string_with_len(buffer: &[u8], offset: &mut usize, label: &str) -> Result<String> {
183    let len = read_len(buffer, offset, &format!("{} length", label))?;
184    if *offset + len > buffer.len() {
185        return Err(HematiteError::CorruptedData(format!("Invalid {}", label)));
186    }
187    let value = String::from_utf8(buffer[*offset..*offset + len].to_vec())
188        .map_err(|_| HematiteError::CorruptedData(format!("Invalid UTF-8 in {}", label)))?;
189    *offset += len;
190    Ok(value)
191}
192
193fn read_optional_string(buffer: &[u8], offset: &mut usize, label: &str) -> Result<Option<String>> {
194    if *offset >= buffer.len() {
195        return Err(HematiteError::CorruptedData(format!("Invalid {}", label)));
196    }
197    let flag = buffer[*offset];
198    *offset += 1;
199    match flag {
200        0 => Ok(None),
201        1 => Ok(Some(read_string_with_len(buffer, offset, label)?)),
202        _ => Err(HematiteError::CorruptedData(format!("Invalid {}", label))),
203    }
204}
205
206fn trigger_event_to_byte(event: TriggerEvent) -> u8 {
207    match event {
208        TriggerEvent::Insert => 0,
209        TriggerEvent::Update => 1,
210        TriggerEvent::Delete => 2,
211    }
212}
213
214fn read_trigger_event(buffer: &[u8], offset: &mut usize) -> Result<TriggerEvent> {
215    if *offset >= buffer.len() {
216        return Err(HematiteError::CorruptedData(
217            "Invalid trigger event".to_string(),
218        ));
219    }
220    let event = match buffer[*offset] {
221        0 => TriggerEvent::Insert,
222        1 => TriggerEvent::Update,
223        2 => TriggerEvent::Delete,
224        _ => {
225            return Err(HematiteError::CorruptedData(
226                "Invalid trigger event".to_string(),
227            ))
228        }
229    };
230    *offset += 1;
231    Ok(event)
232}