use super::{Column, ColumnId, Schema, TableId};
use crate::stmt;
use std::fmt;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Index {
pub id: IndexId,
pub name: String,
pub on: TableId,
pub columns: Vec<IndexColumn>,
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
pub unique: bool,
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
pub primary_key: bool,
}
#[cfg(feature = "serde")]
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IndexId {
pub table: TableId,
pub index: usize,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IndexColumn {
pub column: ColumnId,
pub op: IndexOp,
pub scope: IndexScope,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum IndexOp {
Eq,
Sort(stmt::Direction),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum IndexScope {
Partition,
Local,
}
impl IndexColumn {
pub fn table_column<'a>(&self, schema: &'a Schema) -> &'a Column {
schema.column(self.column)
}
}
impl IndexScope {
pub fn is_partition(self) -> bool {
matches!(self, Self::Partition)
}
pub fn is_local(self) -> bool {
matches!(self, Self::Local)
}
}
impl IndexId {
pub(crate) fn placeholder() -> Self {
Self {
table: TableId::placeholder(),
index: usize::MAX,
}
}
}
impl fmt::Debug for IndexId {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "IndexId({}/{})", self.table.0, self.index)
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use crate::schema::db::{ColumnId, Index, IndexColumn, IndexId, IndexOp, IndexScope, TableId};
fn base_index() -> Index {
Index {
id: IndexId {
table: TableId(0),
index: 0,
},
name: "idx".to_string(),
on: TableId(0),
columns: vec![IndexColumn {
column: ColumnId {
table: TableId(0),
index: 0,
},
op: IndexOp::Eq,
scope: IndexScope::Local,
}],
unique: false,
primary_key: false,
}
}
#[test]
fn false_booleans_are_omitted() {
let toml = toml::to_string(&base_index()).unwrap();
assert!(!toml.contains("unique"), "toml: {toml}");
assert!(!toml.contains("primary_key"), "toml: {toml}");
}
#[test]
fn unique_true_is_included() {
let idx = Index {
unique: true,
..base_index()
};
let toml = toml::to_string(&idx).unwrap();
assert!(toml.contains("unique = true"), "toml: {toml}");
}
#[test]
fn primary_key_true_is_included() {
let idx = Index {
primary_key: true,
..base_index()
};
let toml = toml::to_string(&idx).unwrap();
assert!(toml.contains("primary_key = true"), "toml: {toml}");
}
#[test]
fn missing_bool_fields_deserialize_as_false() {
let toml = "name = \"idx\"\non = 0\n\n[id]\ntable = 0\nindex = 0\n\n[[columns]]\nop = \"Eq\"\nscope = \"Local\"\n\n[columns.column]\ntable = 0\nindex = 0\n";
let idx: Index = toml::from_str(toml).unwrap();
assert!(!idx.unique);
assert!(!idx.primary_key);
}
#[test]
fn round_trip_all_true() {
let original = Index {
unique: true,
primary_key: true,
..base_index()
};
let decoded: Index = toml::from_str(&toml::to_string(&original).unwrap()).unwrap();
assert_eq!(decoded.unique, original.unique);
assert_eq!(decoded.primary_key, original.primary_key);
assert_eq!(decoded.name, original.name);
}
}