Skip to main content

reifydb_core/interface/catalog/
migration.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_runtime::hash::{Hash128, xxh3_128};
5use serde::{Deserialize, Serialize};
6
7use crate::interface::catalog::id::{MigrationEventId, MigrationId};
8
9/// A migration definition stored in the catalog.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct Migration {
12	pub id: MigrationId,
13	pub name: String,
14	/// RQL source text for the migration body
15	pub body: String,
16	/// Optional RQL source text for the rollback body
17	pub rollback_body: Option<String>,
18	/// Content hash of `body || 0x00 || rollback_body.unwrap_or("")`.
19	/// Used to detect post-registration tampering: re-registering a migration
20	/// whose name is already known but whose hash differs is rejected.
21	pub hash: Hash128,
22}
23
24/// Compute the content hash for a migration body and optional rollback body.
25///
26/// The two strings are joined with a NUL byte (which is not a valid character
27/// in RQL source) so that `body="A", rollback="B"` and `body="", rollback="AB"`
28/// produce different hashes.
29pub fn migration_hash(body: &str, rollback_body: Option<&str>) -> Hash128 {
30	let mut buf = Vec::with_capacity(body.len() + 1 + rollback_body.map(|r| r.len()).unwrap_or(0));
31	buf.extend_from_slice(body.as_bytes());
32	buf.push(0);
33	if let Some(rb) = rollback_body {
34		buf.extend_from_slice(rb.as_bytes());
35	}
36	xxh3_128(&buf)
37}
38
39/// An audit trail entry for a migration apply or rollback.
40/// The CommitVersion is NOT a field - it's the MVCC version key.
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct MigrationEvent {
43	pub id: MigrationEventId,
44	pub migration_id: MigrationId,
45	pub action: MigrationAction,
46}
47
48/// The type of migration action recorded in the audit trail.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum MigrationAction {
51	Applied,
52	Rollback,
53}