icydb_core/db/schema_evolution/
descriptor.rs1use crate::{
7 db::{
8 identity::{EntityName, IndexName},
9 schema::commit_schema_fingerprint_for_model,
10 },
11 error::InternalError,
12 model::EntityModel,
13 traits::EntityKind,
14};
15
16#[derive(Clone, Copy, Debug)]
25pub struct SchemaMigrationEntityTarget {
26 name: EntityName,
27 model: &'static EntityModel,
28}
29
30impl SchemaMigrationEntityTarget {
31 pub fn for_entity<E>() -> Result<Self, InternalError>
33 where
34 E: EntityKind + 'static,
35 {
36 Self::from_model(E::MODEL)
37 }
38
39 pub fn from_model(model: &'static EntityModel) -> Result<Self, InternalError> {
41 let name = EntityName::try_from_str(model.name()).map_err(|err| {
42 InternalError::schema_evolution_invalid_identity(format!(
43 "invalid entity name '{}': {err}",
44 model.name()
45 ))
46 })?;
47
48 Ok(Self { name, model })
49 }
50
51 #[must_use]
53 pub const fn name(self) -> EntityName {
54 self.name
55 }
56
57 #[must_use]
59 pub const fn model(self) -> &'static EntityModel {
60 self.model
61 }
62
63 #[must_use]
65 pub const fn runtime_path(self) -> &'static str {
66 self.model.path()
67 }
68
69 #[must_use]
71 pub fn schema_fingerprint(self) -> [u8; 16] {
72 commit_schema_fingerprint_for_model(self.model.path(), self.model)
73 }
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum SchemaMigrationStepIntent {
87 AddIndex { index: IndexName },
88}
89
90impl SchemaMigrationStepIntent {
91 #[must_use]
93 pub const fn add_index(index: IndexName) -> Self {
94 Self::AddIndex { index }
95 }
96}
97
98#[derive(Clone, Debug)]
107pub struct SchemaMigrationRowOp {
108 target: SchemaMigrationEntityTarget,
109 key: Vec<u8>,
110 before: Option<Vec<u8>>,
111 after: Option<Vec<u8>>,
112}
113
114impl SchemaMigrationRowOp {
115 #[must_use]
117 pub const fn new(
118 target: SchemaMigrationEntityTarget,
119 key: Vec<u8>,
120 before: Option<Vec<u8>>,
121 after: Option<Vec<u8>>,
122 ) -> Self {
123 Self {
124 target,
125 key,
126 before,
127 after,
128 }
129 }
130
131 #[must_use]
133 pub const fn insert(target: SchemaMigrationEntityTarget, key: Vec<u8>, after: Vec<u8>) -> Self {
134 Self::new(target, key, None, Some(after))
135 }
136
137 #[must_use]
139 pub const fn target(&self) -> SchemaMigrationEntityTarget {
140 self.target
141 }
142
143 #[must_use]
145 pub const fn key(&self) -> &[u8] {
146 self.key.as_slice()
147 }
148
149 #[must_use]
151 pub fn before(&self) -> Option<&[u8]> {
152 self.before.as_deref()
153 }
154
155 #[must_use]
157 pub fn after(&self) -> Option<&[u8]> {
158 self.after.as_deref()
159 }
160
161 pub(in crate::db) fn into_migration_row_op(
162 self,
163 ) -> Result<crate::db::migration::MigrationRowOp, InternalError> {
164 crate::db::migration::MigrationRowOp::new(
165 self.target.runtime_path(),
166 self.key,
167 self.before,
168 self.after,
169 self.target.schema_fingerprint(),
170 )
171 }
172}
173
174#[derive(Clone, Debug)]
184pub enum SchemaDataTransformation {
185 ExplicitRowOps(Vec<SchemaMigrationRowOp>),
186}
187
188impl SchemaDataTransformation {
189 #[must_use]
191 pub const fn explicit_row_ops(row_ops: Vec<SchemaMigrationRowOp>) -> Self {
192 Self::ExplicitRowOps(row_ops)
193 }
194
195 #[must_use]
197 pub const fn row_ops(&self) -> &[SchemaMigrationRowOp] {
198 match self {
199 Self::ExplicitRowOps(row_ops) => row_ops.as_slice(),
200 }
201 }
202
203 pub(in crate::db) fn into_row_ops(self) -> Vec<SchemaMigrationRowOp> {
204 match self {
205 Self::ExplicitRowOps(row_ops) => row_ops,
206 }
207 }
208}
209
210#[derive(Clone, Debug)]
220pub struct SchemaMigrationDescriptor {
221 migration_id: EntityName,
222 version: u64,
223 description: String,
224 intent: SchemaMigrationStepIntent,
225 data_transformation: Option<SchemaDataTransformation>,
226}
227
228impl SchemaMigrationDescriptor {
229 pub fn new(
231 migration_id: EntityName,
232 version: u64,
233 description: impl Into<String>,
234 intent: SchemaMigrationStepIntent,
235 data_transformation: Option<SchemaDataTransformation>,
236 ) -> Result<Self, InternalError> {
237 let description = description.into();
238 if version == 0 {
239 return Err(InternalError::schema_evolution_version_required(
240 migration_id.as_str(),
241 ));
242 }
243 if description.trim().is_empty() {
244 return Err(InternalError::schema_evolution_description_required(
245 migration_id.as_str(),
246 ));
247 }
248
249 Ok(Self {
250 migration_id,
251 version,
252 description,
253 intent,
254 data_transformation,
255 })
256 }
257
258 #[must_use]
260 pub const fn migration_id(&self) -> EntityName {
261 self.migration_id
262 }
263
264 #[must_use]
266 pub const fn version(&self) -> u64 {
267 self.version
268 }
269
270 #[must_use]
272 pub const fn description(&self) -> &str {
273 self.description.as_str()
274 }
275
276 #[must_use]
278 pub const fn intent(&self) -> &SchemaMigrationStepIntent {
279 &self.intent
280 }
281
282 #[must_use]
284 pub const fn data_transformation(&self) -> Option<&SchemaDataTransformation> {
285 self.data_transformation.as_ref()
286 }
287
288 pub(in crate::db) fn into_data_transformation(self) -> Option<SchemaDataTransformation> {
289 self.data_transformation
290 }
291}