use super::{
SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmission,
SchemaDdlMutationAdmissionError, SchemaDdlMutationTarget,
};
use crate::db::schema::{
AcceptedSchemaSnapshot, FieldId, PersistedFieldSnapshot, PersistedSchemaSnapshot,
SchemaFieldDefault, SchemaFieldSlot, SchemaRowLayout,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SchemaFieldAdditionTarget {
field_id: FieldId,
name: String,
slot: SchemaFieldSlot,
}
impl SchemaFieldAdditionTarget {
#[must_use]
fn from_field(field: &PersistedFieldSnapshot) -> Self {
Self {
field_id: field.id(),
name: field.name().to_string(),
slot: field.slot(),
}
}
#[must_use]
pub(in crate::db) const fn field_id(&self) -> FieldId {
self.field_id
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub(in crate::db) const fn slot(&self) -> SchemaFieldSlot {
self.slot
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SchemaFieldDropTarget {
field_id: FieldId,
name: String,
slot: SchemaFieldSlot,
}
impl SchemaFieldDropTarget {
#[must_use]
fn from_field(field: &PersistedFieldSnapshot) -> Self {
Self {
field_id: field.id(),
name: field.name().to_string(),
slot: field.slot(),
}
}
#[must_use]
pub(in crate::db) const fn field_id(&self) -> FieldId {
self.field_id
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub(in crate::db) const fn slot(&self) -> SchemaFieldSlot {
self.slot
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SchemaFieldDefaultTarget {
field_id: FieldId,
name: String,
}
impl SchemaFieldDefaultTarget {
#[must_use]
fn from_field(field: &PersistedFieldSnapshot) -> Self {
Self {
field_id: field.id(),
name: field.name().to_string(),
}
}
#[must_use]
pub(in crate::db) const fn field_id(&self) -> FieldId {
self.field_id
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SchemaFieldNullabilityTarget {
field_id: FieldId,
name: String,
}
impl SchemaFieldNullabilityTarget {
#[must_use]
fn from_field(field: &PersistedFieldSnapshot) -> Self {
Self {
field_id: field.id(),
name: field.name().to_string(),
}
}
#[must_use]
pub(in crate::db) const fn field_id(&self) -> FieldId {
self.field_id
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct SchemaFieldRenameTarget {
field_id: FieldId,
old_name: String,
new_name: String,
}
impl SchemaFieldRenameTarget {
#[must_use]
fn from_field_name(before: &PersistedFieldSnapshot, new_name: &str) -> Self {
Self {
field_id: before.id(),
old_name: before.name().to_string(),
new_name: new_name.to_string(),
}
}
#[must_use]
pub(in crate::db) const fn field_id(&self) -> FieldId {
self.field_id
}
#[must_use]
pub(in crate::db) const fn old_name(&self) -> &str {
self.old_name.as_str()
}
#[must_use]
pub(in crate::db) const fn new_name(&self) -> &str {
self.new_name.as_str()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum SchemaDdlFieldDropCandidateError {
Unknown,
PrimaryKey,
Generated,
Indexed(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum SchemaDdlFieldDefaultCandidateError {
Unknown,
Generated,
Required,
Indexed(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum SchemaDdlFieldNullabilityCandidateError {
Unknown,
Generated,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum SchemaDdlFieldRenameCandidateError {
Unknown,
Duplicate,
Generated,
}
pub(in crate::db) fn admit_sql_ddl_field_addition_candidate(
field: &PersistedFieldSnapshot,
) -> SchemaDdlMutationAdmission {
SchemaDdlMutationAdmission {
target: SchemaDdlMutationTarget::FieldAddition(SchemaFieldAdditionTarget::from_field(
field,
)),
}
}
#[must_use]
pub(in crate::db) fn admit_sql_ddl_field_drop_candidate(
field: &PersistedFieldSnapshot,
) -> SchemaDdlMutationAdmission {
SchemaDdlMutationAdmission {
target: SchemaDdlMutationTarget::FieldDrop(SchemaFieldDropTarget::from_field(field)),
}
}
pub(in crate::db) fn admit_sql_ddl_field_default_candidate(
field: &PersistedFieldSnapshot,
) -> SchemaDdlMutationAdmission {
SchemaDdlMutationAdmission {
target: SchemaDdlMutationTarget::FieldDefaultChange(SchemaFieldDefaultTarget::from_field(
field,
)),
}
}
#[must_use]
pub(in crate::db) fn admit_sql_ddl_field_nullability_candidate(
field: &PersistedFieldSnapshot,
) -> SchemaDdlMutationAdmission {
SchemaDdlMutationAdmission {
target: SchemaDdlMutationTarget::FieldNullabilityChange(
SchemaFieldNullabilityTarget::from_field(field),
),
}
}
#[must_use]
pub(in crate::db) fn admit_sql_ddl_field_rename_candidate(
before: &PersistedFieldSnapshot,
new_name: &str,
) -> SchemaDdlMutationAdmission {
SchemaDdlMutationAdmission {
target: SchemaDdlMutationTarget::FieldRename(SchemaFieldRenameTarget::from_field_name(
before, new_name,
)),
}
}
fn resolve_sql_ddl_field_dependent_index(
accepted_before: &AcceptedSchemaSnapshot,
field: &PersistedFieldSnapshot,
) -> Option<String> {
accepted_before
.persisted_snapshot()
.indexes()
.iter()
.find(|index| index.references_field(field.id(), field.name()))
.map(|index| index.name().to_string())
}
pub(in crate::db) fn resolve_sql_ddl_field_drop_candidate(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
) -> Result<PersistedFieldSnapshot, SchemaDdlFieldDropCandidateError> {
let accepted = accepted_before.persisted_snapshot();
let field = accepted
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlFieldDropCandidateError::Unknown)?;
if accepted.primary_key_field_ids().contains(&field.id()) {
return Err(SchemaDdlFieldDropCandidateError::PrimaryKey);
}
if field.generated() {
return Err(SchemaDdlFieldDropCandidateError::Generated);
}
if let Some(index_name) = resolve_sql_ddl_field_dependent_index(accepted_before, field) {
return Err(SchemaDdlFieldDropCandidateError::Indexed(index_name));
}
Ok(field.clone())
}
pub(in crate::db) fn resolve_sql_ddl_field_set_default_candidate(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
) -> Result<PersistedFieldSnapshot, SchemaDdlFieldDefaultCandidateError> {
let field = accepted_before
.persisted_snapshot()
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlFieldDefaultCandidateError::Unknown)?;
Ok(field.clone())
}
pub(in crate::db) fn validate_sql_ddl_field_default_change_candidate(
accepted_before: &AcceptedSchemaSnapshot,
field: &PersistedFieldSnapshot,
default: &SchemaFieldDefault,
) -> Result<(), SchemaDdlFieldDefaultCandidateError> {
if field.default() == default {
return Ok(());
}
if field.generated() {
return Err(SchemaDdlFieldDefaultCandidateError::Generated);
}
if default.is_none() && !field.nullable() {
return Err(SchemaDdlFieldDefaultCandidateError::Required);
}
if let Some(index_name) = resolve_sql_ddl_field_dependent_index(accepted_before, field) {
return Err(SchemaDdlFieldDefaultCandidateError::Indexed(index_name));
}
Ok(())
}
pub(in crate::db) fn resolve_sql_ddl_field_drop_default_candidate(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
) -> Result<PersistedFieldSnapshot, SchemaDdlFieldDefaultCandidateError> {
let field = accepted_before
.persisted_snapshot()
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlFieldDefaultCandidateError::Unknown)?;
validate_sql_ddl_field_default_change_candidate(
accepted_before,
field,
&SchemaFieldDefault::None,
)?;
Ok(field.clone())
}
pub(in crate::db) fn resolve_sql_ddl_field_nullability_candidate(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
nullable: bool,
) -> Result<PersistedFieldSnapshot, SchemaDdlFieldNullabilityCandidateError> {
let field = accepted_before
.persisted_snapshot()
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlFieldNullabilityCandidateError::Unknown)?;
if field.nullable() != nullable && field.generated() {
return Err(SchemaDdlFieldNullabilityCandidateError::Generated);
}
Ok(field.clone())
}
pub(in crate::db) fn resolve_sql_ddl_field_rename_candidate(
accepted_before: &AcceptedSchemaSnapshot,
old_name: &str,
new_name: &str,
) -> Result<PersistedFieldSnapshot, SchemaDdlFieldRenameCandidateError> {
let accepted = accepted_before.persisted_snapshot();
let field = accepted
.fields()
.iter()
.find(|field| field.name() == old_name)
.ok_or(SchemaDdlFieldRenameCandidateError::Unknown)?;
if old_name == new_name {
return Ok(field.clone());
}
if accepted
.fields()
.iter()
.any(|field| field.name() == new_name)
{
return Err(SchemaDdlFieldRenameCandidateError::Duplicate);
}
if field.generated() {
return Err(SchemaDdlFieldRenameCandidateError::Generated);
}
Ok(field.clone())
}
pub(in crate::db) fn derive_sql_ddl_field_addition_accepted_after(
accepted_before: &AcceptedSchemaSnapshot,
field: PersistedFieldSnapshot,
) -> Result<SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmissionError> {
let before = accepted_before.persisted_snapshot();
let mut fields = before.fields().to_vec();
fields.push(field.clone());
let mut field_to_slot = before.row_layout().field_to_slot().to_vec();
field_to_slot.push((field.id(), field.slot()));
let persisted_after = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
before.version(),
before.entity_path().to_string(),
before.entity_name().to_string(),
before.primary_key_field_ids().to_vec(),
SchemaRowLayout::new(before.row_layout().version(), field_to_slot),
fields,
before.indexes().to_vec(),
)
.with_relations(before.relations().to_vec());
let accepted_after = AcceptedSchemaSnapshot::try_new(persisted_after)
.map_err(|_| SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let admission = admit_sql_ddl_field_addition_candidate(&field);
Ok(SchemaDdlAcceptedSnapshotDerivation {
accepted_after,
admission,
})
}
pub(in crate::db) fn derive_sql_ddl_field_drop_accepted_after(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
) -> Result<SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmissionError> {
let before = accepted_before.persisted_snapshot();
let before_field = before
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let retained_fields = before
.fields()
.iter()
.filter(|field| field.id() != before_field.id())
.collect::<Vec<_>>();
let dense_identities = retained_fields
.iter()
.enumerate()
.map(|(index, field)| {
let ordinal = u32::try_from(index)
.ok()
.and_then(|index| index.checked_add(1))
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
Ok((
field.id(),
FieldId::new(ordinal),
SchemaFieldSlot::from_generated_index(index),
))
})
.collect::<Result<Vec<_>, _>>()?;
let map_field = |field_id: FieldId, _slot: SchemaFieldSlot| {
dense_identities
.iter()
.find(|(before_id, _, _)| *before_id == field_id)
.map(|(_, after_id, after_slot)| (*after_id, *after_slot))
};
let fields = retained_fields
.iter()
.zip(&dense_identities)
.map(|(field, (_, id, slot))| field.clone_with_identity(*id, *slot))
.collect::<Vec<_>>();
let row_layout = SchemaRowLayout::new(
before.row_layout().version(),
dense_identities
.iter()
.map(|(_, id, slot)| (*id, *slot))
.collect(),
);
let primary_key_field_ids = before
.primary_key_field_ids()
.iter()
.map(|field_id| map_field(*field_id, SchemaFieldSlot::new(0)).map(|(id, _)| id))
.collect::<Option<Vec<_>>>()
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let indexes = before
.indexes()
.iter()
.map(|index| index.clone_with_dense_identities(index.ordinal(), map_field))
.collect::<Option<Vec<_>>>()
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let relations = before
.relations()
.iter()
.map(|relation| {
relation.clone_with_mapped_field_ids(|field_id| {
map_field(field_id, SchemaFieldSlot::new(0)).map(|(id, _)| id)
})
})
.collect::<Option<Vec<_>>>()
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let persisted_after = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
before.version(),
before.entity_path().to_string(),
before.entity_name().to_string(),
primary_key_field_ids,
row_layout,
fields,
indexes,
)
.with_relations(relations);
let accepted_after = AcceptedSchemaSnapshot::try_new(persisted_after)
.map_err(|_| SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let admission = admit_sql_ddl_field_drop_candidate(before_field);
Ok(SchemaDdlAcceptedSnapshotDerivation {
accepted_after,
admission,
})
}
pub(in crate::db) fn derive_sql_ddl_field_default_accepted_after(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
default: SchemaFieldDefault,
) -> Result<SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmissionError> {
let before = accepted_before.persisted_snapshot();
let before_field = before
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
validate_sql_ddl_field_default_change_candidate(accepted_before, before_field, &default)
.map_err(|_| SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let fields = before
.fields()
.iter()
.map(|field| {
if field.id() == before_field.id() {
field.clone_with_default(default.clone())
} else {
field.clone()
}
})
.collect();
let persisted_after = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
before.version(),
before.entity_path().to_string(),
before.entity_name().to_string(),
before.primary_key_field_ids().to_vec(),
before.row_layout().clone(),
fields,
before.indexes().to_vec(),
)
.with_relations(before.relations().to_vec());
let accepted_after = AcceptedSchemaSnapshot::try_new(persisted_after)
.map_err(|_| SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let after_field = accepted_after
.persisted_snapshot()
.fields()
.iter()
.find(|field| field.id() == before_field.id())
.ok_or(SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let admission = admit_sql_ddl_field_default_candidate(after_field);
Ok(SchemaDdlAcceptedSnapshotDerivation {
accepted_after,
admission,
})
}
pub(in crate::db) fn derive_sql_ddl_field_nullability_accepted_after(
accepted_before: &AcceptedSchemaSnapshot,
field_name: &str,
nullable: bool,
) -> Result<SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmissionError> {
let before = accepted_before.persisted_snapshot();
let before_field = before
.fields()
.iter()
.find(|field| field.name() == field_name)
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let fields = before
.fields()
.iter()
.map(|field| {
if field.id() == before_field.id() {
field.clone_with_nullable(nullable)
} else {
field.clone()
}
})
.collect();
let persisted_after = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
before.version(),
before.entity_path().to_string(),
before.entity_name().to_string(),
before.primary_key_field_ids().to_vec(),
before.row_layout().clone(),
fields,
before.indexes().to_vec(),
)
.with_relations(before.relations().to_vec());
let accepted_after = AcceptedSchemaSnapshot::try_new(persisted_after)
.map_err(|_| SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let after_field = accepted_after
.persisted_snapshot()
.fields()
.iter()
.find(|field| field.id() == before_field.id())
.ok_or(SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let admission = admit_sql_ddl_field_nullability_candidate(after_field);
Ok(SchemaDdlAcceptedSnapshotDerivation {
accepted_after,
admission,
})
}
pub(in crate::db) fn derive_sql_ddl_field_rename_accepted_after(
accepted_before: &AcceptedSchemaSnapshot,
old_name: &str,
new_name: &str,
) -> Result<SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmissionError> {
let before = accepted_before.persisted_snapshot();
let before_field = before
.fields()
.iter()
.find(|field| field.name() == old_name)
.ok_or(SchemaDdlMutationAdmissionError::UnsupportedExecutionPath)?;
let fields = before
.fields()
.iter()
.map(|field| {
if field.id() == before_field.id() {
field.clone_with_name(new_name.to_string())
} else {
field.clone()
}
})
.collect();
let indexes = before
.indexes()
.iter()
.map(|index| {
index.clone_with_renamed_field_path_root(
before_field.id(),
before_field.name(),
new_name,
)
})
.collect();
let persisted_after = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
before.version(),
before.entity_path().to_string(),
before.entity_name().to_string(),
before.primary_key_field_ids().to_vec(),
before.row_layout().clone(),
fields,
indexes,
)
.with_relations(before.relations().to_vec());
let accepted_after = AcceptedSchemaSnapshot::try_new(persisted_after)
.map_err(|_| SchemaDdlMutationAdmissionError::AcceptedAfterRejected)?;
let admission = admit_sql_ddl_field_rename_candidate(before_field, new_name);
Ok(SchemaDdlAcceptedSnapshotDerivation {
accepted_after,
admission,
})
}