use crate::{
db::{
identity::{EntityName, IndexName},
schema::commit_schema_fingerprint_for_model,
},
error::InternalError,
model::EntityModel,
traits::EntityKind,
};
#[derive(Clone, Copy, Debug)]
pub struct SchemaMigrationEntityTarget {
name: EntityName,
model: &'static EntityModel,
}
impl SchemaMigrationEntityTarget {
pub fn for_entity<E>() -> Result<Self, InternalError>
where
E: EntityKind + 'static,
{
Self::from_model(E::MODEL)
}
pub fn from_model(model: &'static EntityModel) -> Result<Self, InternalError> {
let name = EntityName::try_from_str(model.name()).map_err(|err| {
InternalError::schema_evolution_invalid_identity(format!(
"invalid entity name '{}': {err}",
model.name()
))
})?;
Ok(Self { name, model })
}
#[must_use]
pub const fn name(self) -> EntityName {
self.name
}
#[must_use]
pub const fn model(self) -> &'static EntityModel {
self.model
}
#[must_use]
pub const fn runtime_path(self) -> &'static str {
self.model.path()
}
#[must_use]
pub fn schema_fingerprint(self) -> [u8; 16] {
commit_schema_fingerprint_for_model(self.model.path(), self.model)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SchemaMigrationStepIntent {
AddIndex { index: IndexName },
}
impl SchemaMigrationStepIntent {
#[must_use]
pub const fn add_index(index: IndexName) -> Self {
Self::AddIndex { index }
}
}
#[derive(Clone, Debug)]
pub struct SchemaMigrationRowOp {
target: SchemaMigrationEntityTarget,
key: Vec<u8>,
before: Option<Vec<u8>>,
after: Option<Vec<u8>>,
}
impl SchemaMigrationRowOp {
#[must_use]
pub const fn new(
target: SchemaMigrationEntityTarget,
key: Vec<u8>,
before: Option<Vec<u8>>,
after: Option<Vec<u8>>,
) -> Self {
Self {
target,
key,
before,
after,
}
}
#[must_use]
pub const fn insert(target: SchemaMigrationEntityTarget, key: Vec<u8>, after: Vec<u8>) -> Self {
Self::new(target, key, None, Some(after))
}
#[must_use]
pub const fn target(&self) -> SchemaMigrationEntityTarget {
self.target
}
#[must_use]
pub const fn key(&self) -> &[u8] {
self.key.as_slice()
}
#[must_use]
pub fn before(&self) -> Option<&[u8]> {
self.before.as_deref()
}
#[must_use]
pub fn after(&self) -> Option<&[u8]> {
self.after.as_deref()
}
pub(in crate::db) fn into_migration_row_op(
self,
) -> Result<crate::db::migration::MigrationRowOp, InternalError> {
crate::db::migration::MigrationRowOp::new(
self.target.runtime_path(),
self.key,
self.before,
self.after,
self.target.schema_fingerprint(),
)
}
}
#[derive(Clone, Debug)]
pub enum SchemaDataTransformation {
ExplicitRowOps(Vec<SchemaMigrationRowOp>),
}
impl SchemaDataTransformation {
#[must_use]
pub const fn explicit_row_ops(row_ops: Vec<SchemaMigrationRowOp>) -> Self {
Self::ExplicitRowOps(row_ops)
}
#[must_use]
pub const fn row_ops(&self) -> &[SchemaMigrationRowOp] {
match self {
Self::ExplicitRowOps(row_ops) => row_ops.as_slice(),
}
}
pub(in crate::db) fn into_row_ops(self) -> Vec<SchemaMigrationRowOp> {
match self {
Self::ExplicitRowOps(row_ops) => row_ops,
}
}
}
#[derive(Clone, Debug)]
pub struct SchemaMigrationDescriptor {
migration_id: EntityName,
version: u64,
description: String,
intent: SchemaMigrationStepIntent,
data_transformation: Option<SchemaDataTransformation>,
}
impl SchemaMigrationDescriptor {
pub fn new(
migration_id: EntityName,
version: u64,
description: impl Into<String>,
intent: SchemaMigrationStepIntent,
data_transformation: Option<SchemaDataTransformation>,
) -> Result<Self, InternalError> {
let description = description.into();
if version == 0 {
return Err(InternalError::schema_evolution_version_required(
migration_id.as_str(),
));
}
if description.trim().is_empty() {
return Err(InternalError::schema_evolution_description_required(
migration_id.as_str(),
));
}
Ok(Self {
migration_id,
version,
description,
intent,
data_transformation,
})
}
#[must_use]
pub const fn migration_id(&self) -> EntityName {
self.migration_id
}
#[must_use]
pub const fn version(&self) -> u64 {
self.version
}
#[must_use]
pub const fn description(&self) -> &str {
self.description.as_str()
}
#[must_use]
pub const fn intent(&self) -> &SchemaMigrationStepIntent {
&self.intent
}
#[must_use]
pub const fn data_transformation(&self) -> Option<&SchemaDataTransformation> {
self.data_transformation.as_ref()
}
pub(in crate::db) fn into_data_transformation(self) -> Option<SchemaDataTransformation> {
self.data_transformation
}
}