use crate::{
db::{
data::decode_runtime_value_from_accepted_field_contract,
schema::{
AcceptedFieldDecodeContract, PersistedFieldSnapshot, PersistedIndexSnapshot,
PersistedSchemaSnapshot, SchemaMutationRequest,
},
},
value::Value,
};
pub(super) fn generated_index_names_only_changed(
actual: &PersistedSchemaSnapshot,
expected: &PersistedSchemaSnapshot,
) -> bool {
if actual == expected {
return false;
}
if actual.version() != expected.version()
|| actual.entity_path() != expected.entity_path()
|| actual.entity_name() != expected.entity_name()
|| actual.primary_key_field_ids() != expected.primary_key_field_ids()
|| !active_row_layout_matches(actual, expected)
|| actual.fields() != expected.fields()
{
return false;
}
let mut renamed = false;
for expected_index in expected.indexes() {
let Some(actual_index) = actual
.indexes()
.iter()
.find(|index| index.ordinal() == expected_index.ordinal())
else {
return false;
};
if !index_contract_matches_ignoring_name(actual_index, expected_index) {
return false;
}
renamed |= actual_index.name() != expected_index.name();
}
renamed
&& actual
.indexes()
.iter()
.filter(|index| {
!expected
.indexes()
.iter()
.any(|expected_index| expected_index.ordinal() == index.ordinal())
})
.all(is_supported_extra_accepted_index)
}
fn index_contract_matches_ignoring_name(
actual: &PersistedIndexSnapshot,
expected: &PersistedIndexSnapshot,
) -> bool {
actual.ordinal() == expected.ordinal()
&& actual.store() == expected.store()
&& actual.unique() == expected.unique()
&& actual.key() == expected.key()
&& actual.predicate_sql() == expected.predicate_sql()
}
pub(super) fn accepted_snapshot_extends_generated_indexes(
actual: &PersistedSchemaSnapshot,
expected: &PersistedSchemaSnapshot,
) -> bool {
if actual == expected {
return false;
}
if actual.version() < expected.version()
|| actual.entity_path() != expected.entity_path()
|| actual.entity_name() != expected.entity_name()
|| actual.primary_key_field_ids() != expected.primary_key_field_ids()
|| actual.row_layout().field_to_slot() != expected.row_layout().field_to_slot()
|| actual.row_layout().retired_field_slots() != expected.row_layout().retired_field_slots()
|| actual.fields() != expected.fields()
{
return false;
}
if !expected
.indexes()
.iter()
.all(|index| actual.indexes().contains(index))
{
return false;
}
let has_ddl_index_extension = actual
.indexes()
.iter()
.any(|index| !expected.indexes().contains(index));
has_ddl_index_extension
&& actual
.indexes()
.iter()
.filter(|index| !expected.indexes().contains(index))
.all(is_supported_extra_accepted_index)
}
pub(super) fn accepted_snapshot_extends_generated_with_ddl_fields(
actual: &PersistedSchemaSnapshot,
expected: &PersistedSchemaSnapshot,
) -> bool {
if actual == expected {
return false;
}
if actual.version() < expected.version()
|| actual.entity_path() != expected.entity_path()
|| actual.entity_name() != expected.entity_name()
|| actual.primary_key_field_ids() != expected.primary_key_field_ids()
|| actual.fields().len() < expected.fields().len()
|| actual.row_layout().field_to_slot().len() < expected.row_layout().field_to_slot().len()
{
return false;
}
if !actual
.fields()
.iter()
.zip(expected.fields())
.all(|(actual_field, expected_field)| actual_field == expected_field)
{
return false;
}
if !actual
.row_layout()
.field_to_slot()
.iter()
.zip(expected.row_layout().field_to_slot())
.all(|(actual_pair, expected_pair)| actual_pair == expected_pair)
{
return false;
}
if actual.fields()[expected.fields().len()..]
.iter()
.any(PersistedFieldSnapshot::generated)
{
return false;
}
if !expected
.row_layout()
.retired_field_slots()
.iter()
.all(|retired| actual.row_layout().retired_field_slots().contains(retired))
{
return false;
}
if actual
.row_layout()
.retired_field_slots()
.iter()
.filter(|retired| {
!expected
.row_layout()
.retired_field_slots()
.contains(retired)
})
.any(|(field_id, _)| {
expected
.fields()
.iter()
.any(|field| field.id() == *field_id)
})
{
return false;
}
if !expected
.indexes()
.iter()
.all(|index| actual.indexes().contains(index))
{
return false;
}
let has_ddl_field_extension = actual.fields().len() > expected.fields().len()
|| actual.row_layout().field_to_slot().len() > expected.row_layout().field_to_slot().len()
|| actual.row_layout().retired_field_slots() != expected.row_layout().retired_field_slots();
has_ddl_field_extension
&& actual
.indexes()
.iter()
.filter(|index| !expected.indexes().contains(index))
.all(is_supported_extra_accepted_index)
}
fn active_row_layout_matches(
actual: &PersistedSchemaSnapshot,
expected: &PersistedSchemaSnapshot,
) -> bool {
actual.row_layout().version() == expected.row_layout().version()
&& actual.row_layout().field_to_slot() == expected.row_layout().field_to_slot()
}
fn is_supported_extra_accepted_index(index: &PersistedIndexSnapshot) -> bool {
SchemaMutationRequest::from_accepted_field_path_index(index).is_ok()
|| SchemaMutationRequest::from_accepted_expression_index(index).is_ok()
}
pub(super) fn field_has_supported_missing_absence_policy(field: &PersistedFieldSnapshot) -> bool {
(field.nullable() && field.default().is_none()) || field_default_payload_is_valid(field)
}
fn field_default_payload_is_valid(field: &PersistedFieldSnapshot) -> bool {
let Some(payload) = field.default().slot_payload() else {
return false;
};
let contract = AcceptedFieldDecodeContract::new(
field.name(),
field.kind(),
field.nullable(),
field.storage_decode(),
field.leaf_codec(),
);
decode_runtime_value_from_accepted_field_contract(contract, payload)
.is_ok_and(|value| !matches!(value, Value::Null))
}