quill_sql/storage/
tuple.rs

1use crate::catalog::{Schema, SchemaRef, EMPTY_SCHEMA_REF};
2use crate::error::{QuillSQLError, QuillSQLResult};
3use crate::storage::page::RecordId;
4use crate::utils::scalar::ScalarValue;
5use crate::utils::table_ref::TableReference;
6use std::cmp::Ordering;
7use std::fmt::{Display, Formatter};
8use std::sync::{Arc, LazyLock};
9
10pub static EMPTY_TUPLE: LazyLock<Tuple> = LazyLock::new(|| Tuple::empty(EMPTY_SCHEMA_REF.clone()));
11
12#[derive(Debug, Clone, Eq, PartialEq)]
13pub struct Tuple {
14    pub schema: SchemaRef,
15    pub data: Vec<ScalarValue>,
16}
17
18impl Tuple {
19    pub fn new(schema: SchemaRef, data: Vec<ScalarValue>) -> Self {
20        debug_assert_eq!(schema.columns.len(), data.len());
21        Self { schema, data }
22    }
23
24    pub fn project_with_schema(&self, projected_schema: SchemaRef) -> QuillSQLResult<Self> {
25        let indices = projected_schema
26            .columns
27            .iter()
28            .map(|col| {
29                self.schema
30                    .index_of(col.relation.as_ref(), col.name.as_str())
31            })
32            .collect::<QuillSQLResult<Vec<usize>>>()?;
33        let projected_data = indices
34            .iter()
35            .map(|idx| self.data[*idx].clone())
36            .collect::<Vec<ScalarValue>>();
37        Ok(Self::new(projected_schema, projected_data))
38    }
39
40    pub fn empty(schema: SchemaRef) -> Self {
41        let mut data = vec![];
42        for col in schema.columns.iter() {
43            data.push(ScalarValue::new_empty(col.data_type));
44        }
45        Self::new(schema, data)
46    }
47
48    pub fn try_merge(tuples: impl IntoIterator<Item = Self>) -> QuillSQLResult<Self> {
49        let mut data = vec![];
50        let mut merged_schema = Schema::empty();
51        for tuple in tuples {
52            data.extend(tuple.data);
53            merged_schema = Schema::try_merge(vec![merged_schema, tuple.schema.as_ref().clone()])?;
54        }
55        Ok(Self::new(Arc::new(merged_schema), data))
56    }
57
58    pub fn is_null(&self) -> bool {
59        self.data.iter().all(|x| x.is_null())
60    }
61
62    pub fn value(&self, index: usize) -> QuillSQLResult<&ScalarValue> {
63        self.data.get(index).ok_or(QuillSQLError::Internal(format!(
64            "Not found column data at {} in tuple: {:?}",
65            index, self
66        )))
67    }
68    pub fn value_by_name(
69        &self,
70        relation: Option<&TableReference>,
71        name: &str,
72    ) -> QuillSQLResult<&ScalarValue> {
73        let idx = self.schema.index_of(relation, name)?;
74        self.value(idx)
75    }
76
77    pub fn as_rid(&self) -> QuillSQLResult<RecordId> {
78        if self.data.len() < 2 {
79            return Err(QuillSQLError::Execution(
80                "RID tuple must have at least two columns".to_string(),
81            ));
82        }
83        let page_id = value_as_u32(&self.data[0])?;
84        let slot_num = value_as_u32(&self.data[1])?;
85        Ok(RecordId::new(page_id, slot_num))
86    }
87}
88
89impl Ord for Tuple {
90    fn cmp(&self, other: &Self) -> Ordering {
91        self.partial_cmp(other).unwrap_or(Ordering::Equal)
92    }
93}
94
95impl PartialOrd for Tuple {
96    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97        let column_count = self.schema.column_count();
98        for idx in 0..column_count {
99            let order = self.value(idx).ok()?.partial_cmp(other.value(idx).ok()?)?;
100            if order == Ordering::Equal {
101                continue;
102            } else {
103                return Some(order);
104            }
105        }
106        Some(Ordering::Equal)
107    }
108}
109
110impl Display for Tuple {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        let values = self
113            .data
114            .iter()
115            .map(|v| v.to_string())
116            .collect::<Vec<String>>()
117            .join(", ");
118        write!(f, "({})", values)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use crate::catalog::{Column, DataType, Schema};
125    use std::cmp::Ordering;
126    use std::sync::Arc;
127
128    #[test]
129    pub fn tuple_compare() {
130        let schema = Arc::new(Schema::new(vec![
131            Column::new("a", DataType::Int8, false),
132            Column::new("b", DataType::Int16, false),
133        ]));
134        let tuple1 = super::Tuple::new(schema.clone(), vec![1i8.into(), 2i16.into()]);
135        let tuple2 = super::Tuple::new(schema.clone(), vec![1i8.into(), 2i16.into()]);
136        let tuple3 = super::Tuple::new(schema.clone(), vec![1i8.into(), 3i16.into()]);
137        let tuple4 = super::Tuple::new(schema.clone(), vec![2i8.into(), 2i16.into()]);
138        let tuple5 = super::Tuple::new(schema.clone(), vec![1i8.into(), 1i16.into()]);
139
140        assert_eq!(tuple1.partial_cmp(&tuple2).unwrap(), Ordering::Equal);
141        assert_eq!(tuple1.partial_cmp(&tuple3).unwrap(), Ordering::Less);
142        assert_eq!(tuple1.partial_cmp(&tuple4).unwrap(), Ordering::Less);
143        assert_eq!(tuple1.partial_cmp(&tuple5).unwrap(), Ordering::Greater);
144    }
145}
146
147fn value_as_u32(value: &ScalarValue) -> QuillSQLResult<u32> {
148    let num = match value {
149        ScalarValue::Int16(Some(v)) => *v as i32,
150        ScalarValue::Int32(Some(v)) => *v,
151        ScalarValue::Int64(Some(v)) => *v as i32,
152        ScalarValue::UInt16(Some(v)) => *v as i32,
153        ScalarValue::UInt32(Some(v)) => *v as i32,
154        ScalarValue::UInt64(Some(v)) => *v as i32,
155        ScalarValue::Int8(Some(v)) => *v as i32,
156        ScalarValue::UInt8(Some(v)) => *v as i32,
157        _ => {
158            return Err(QuillSQLError::Execution(
159                "RID column must be integer".to_string(),
160            ))
161        }
162    };
163    if num < 0 {
164        return Err(QuillSQLError::Execution(
165            "RID column must be positive".to_string(),
166        ));
167    }
168    Ok(num as u32)
169}