Skip to main content

toasty_core/schema/app/
index.rs

1use super::{FieldId, ModelId};
2use crate::schema::db::{IndexOp, IndexScope};
3
4/// An index defined on a model's fields.
5///
6/// Indices speed up queries by letting the database locate rows without a full
7/// table scan. An index can cover one or more fields, may enforce uniqueness,
8/// and may serve as the primary key.
9///
10/// Fields are split into *partition* fields (used for distribution in NoSQL
11/// backends like DynamoDB) and *local* fields (sort keys or secondary columns).
12///
13/// # Examples
14///
15/// ```
16/// use toasty_core::schema::app::{Index, IndexId, IndexField, ModelId};
17/// use toasty_core::schema::db::{IndexOp, IndexScope};
18///
19/// let index = Index {
20///     id: IndexId { model: ModelId(0), index: 0 },
21///     name: None,
22///     fields: vec![IndexField {
23///         field: ModelId(0).field(0),
24///         op: IndexOp::Eq,
25///         scope: IndexScope::Local,
26///     }],
27///     unique: true,
28///     primary_key: true,
29/// };
30/// assert!(index.unique);
31/// assert!(index.primary_key);
32/// ```
33#[derive(Debug, Clone)]
34pub struct Index {
35    /// Uniquely identifies this index within the schema.
36    pub id: IndexId,
37
38    /// User-provided index name from `#[index(name = "...", ...)]` or
39    /// `#[key(name = "...", ...)]`. When `None`, the schema builder generates
40    /// a name automatically.
41    pub name: Option<String>,
42
43    /// Fields included in the index, in order.
44    pub fields: Vec<IndexField>,
45
46    /// When `true`, the index enforces uniqueness across indexed entries.
47    pub unique: bool,
48
49    /// When `true`, this index represents the model's primary key.
50    pub primary_key: bool,
51}
52
53/// Uniquely identifies an [`Index`] within a schema.
54///
55/// Composed of the owning model's [`ModelId`] and a positional index into that
56/// model's index list.
57///
58/// # Examples
59///
60/// ```
61/// use toasty_core::schema::app::{IndexId, ModelId};
62///
63/// let id = IndexId { model: ModelId(0), index: 0 };
64/// assert_eq!(id.model, ModelId(0));
65/// ```
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub struct IndexId {
68    /// The model this index belongs to.
69    pub model: ModelId,
70    /// Positional index within the model's index list.
71    pub index: usize,
72}
73
74/// A single field entry within an [`Index`].
75///
76/// Describes which field is indexed, the comparison operation used for lookups,
77/// and whether this field is a partition key or a local (sort) key.
78///
79/// # Examples
80///
81/// ```
82/// use toasty_core::schema::app::{IndexField, ModelId};
83/// use toasty_core::schema::db::{IndexOp, IndexScope};
84///
85/// let field = IndexField {
86///     field: ModelId(0).field(1),
87///     op: IndexOp::Eq,
88///     scope: IndexScope::Local,
89/// };
90/// ```
91#[derive(Debug, Copy, Clone)]
92pub struct IndexField {
93    /// The field being indexed.
94    pub field: FieldId,
95
96    /// The comparison operation used when querying this index field.
97    pub op: IndexOp,
98
99    /// Whether this field is a partition key or a local (sort) key.
100    pub scope: IndexScope,
101}
102
103impl Index {
104    /// Returns the partition-scoped fields of this index.
105    ///
106    /// Partition fields come before local fields and determine data
107    /// distribution in NoSQL backends.
108    pub fn partition_fields(&self) -> &[IndexField] {
109        let i = self.index_of_first_local_field();
110        &self.fields[0..i]
111    }
112
113    /// Returns the local (sort-key) fields of this index.
114    ///
115    /// Local fields follow partition fields and determine ordering within a
116    /// partition.
117    pub fn local_fields(&self) -> &[IndexField] {
118        let i = self.index_of_first_local_field();
119        &self.fields[i..]
120    }
121
122    fn index_of_first_local_field(&self) -> usize {
123        self.fields
124            .iter()
125            .position(|field| field.scope.is_local())
126            .unwrap_or(self.fields.len())
127    }
128}