use crate::{
db::schema::{
AcceptedSchemaSnapshot, FieldId, PersistedFieldKind, PersistedNestedLeafSnapshot,
PersistedRelationEdgeSnapshot, SchemaFieldDefault, SchemaFieldSlot, SchemaFieldWritePolicy,
SchemaVersion,
},
error::InternalError,
model::{
entity::EntityModel,
field::{FieldModel, FieldStorageDecode, LeafCodec},
},
};
#[cfg(test)]
use std::cell::Cell;
#[cfg(test)]
thread_local! {
static GENERATED_COMPATIBLE_ROW_LAYOUT_PROOFS: Cell<u64> = const { Cell::new(0) };
}
#[cfg(test)]
pub(in crate::db) fn reset_generated_compatible_row_layout_proof_count_for_tests() {
GENERATED_COMPATIBLE_ROW_LAYOUT_PROOFS.with(|proofs| proofs.set(0));
}
#[cfg(test)]
pub(in crate::db) fn generated_compatible_row_layout_proof_count_for_tests() -> u64 {
GENERATED_COMPATIBLE_ROW_LAYOUT_PROOFS.with(Cell::get)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum AcceptedFieldAbsencePolicy {
NullIfMissing,
DefaultIfMissing,
Required,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) struct AcceptedRowLayoutRuntimeField<'a> {
field_id: FieldId,
name: &'a str,
slot: SchemaFieldSlot,
kind: &'a PersistedFieldKind,
nested_leaves: &'a [PersistedNestedLeafSnapshot],
nullable: bool,
default: &'a SchemaFieldDefault,
write_policy: SchemaFieldWritePolicy,
storage_decode: FieldStorageDecode,
leaf_codec: LeafCodec,
absence_policy: AcceptedFieldAbsencePolicy,
generated: bool,
}
impl<'a> AcceptedRowLayoutRuntimeField<'a> {
#[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) -> &'a str {
self.name
}
#[must_use]
pub(in crate::db) const fn slot(&self) -> SchemaFieldSlot {
self.slot
}
#[must_use]
pub(in crate::db) const fn kind(&self) -> &'a PersistedFieldKind {
self.kind
}
#[cfg_attr(
not(test),
allow(
dead_code,
reason = "runtime layout tests and schema handoff audits exercise nested-leaf access before production row-layout callers need it"
)
)]
#[must_use]
pub(in crate::db) const fn nested_leaves(&self) -> &'a [PersistedNestedLeafSnapshot] {
self.nested_leaves
}
#[cfg_attr(
not(test),
allow(
dead_code,
reason = "runtime layout tests and schema handoff audits exercise nullability access before production row-layout callers need it"
)
)]
#[must_use]
pub(in crate::db) const fn nullable(&self) -> bool {
self.nullable
}
#[allow(
dead_code,
reason = "database defaults are part of the accepted runtime boundary before additive write support"
)]
#[must_use]
pub(in crate::db) const fn default(&self) -> &'a SchemaFieldDefault {
self.default
}
#[must_use]
pub(in crate::db) const fn write_policy(&self) -> SchemaFieldWritePolicy {
self.write_policy
}
#[must_use]
pub(in crate::db) const fn absence_policy(&self) -> AcceptedFieldAbsencePolicy {
self.absence_policy
}
#[must_use]
pub(in crate::db) const fn generated(&self) -> bool {
self.generated
}
#[must_use]
pub(in crate::db) const fn decode_contract(&self) -> AcceptedFieldDecodeContract<'a> {
AcceptedFieldDecodeContract::new(
self.name,
self.kind,
self.nullable,
self.storage_decode,
self.leaf_codec,
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) struct AcceptedFieldDecodeContract<'a> {
field_name: &'a str,
kind: &'a PersistedFieldKind,
nullable: bool,
storage_decode: FieldStorageDecode,
leaf_codec: LeafCodec,
}
impl<'a> AcceptedFieldDecodeContract<'a> {
#[must_use]
pub(in crate::db) const fn new(
field_name: &'a str,
kind: &'a PersistedFieldKind,
nullable: bool,
storage_decode: FieldStorageDecode,
leaf_codec: LeafCodec,
) -> Self {
Self {
field_name,
kind,
nullable,
storage_decode,
leaf_codec,
}
}
#[must_use]
pub(in crate::db) const fn field_name(&self) -> &'a str {
self.field_name
}
#[must_use]
pub(in crate::db) const fn kind(&self) -> &'a PersistedFieldKind {
self.kind
}
#[must_use]
pub(in crate::db) const fn nullable(&self) -> bool {
self.nullable
}
#[must_use]
pub(in crate::db) const fn storage_decode(&self) -> FieldStorageDecode {
self.storage_decode
}
#[must_use]
pub(in crate::db) const fn leaf_codec(&self) -> LeafCodec {
self.leaf_codec
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct OwnedAcceptedFieldDecodeContract {
field_name: String,
kind: PersistedFieldKind,
nullable: bool,
storage_decode: FieldStorageDecode,
leaf_codec: LeafCodec,
write_policy: SchemaFieldWritePolicy,
absence_policy: AcceptedFieldAbsencePolicy,
default: SchemaFieldDefault,
generated: bool,
}
impl OwnedAcceptedFieldDecodeContract {
#[must_use]
fn from_runtime_field(field: &AcceptedRowLayoutRuntimeField<'_>) -> Self {
let contract = field.decode_contract();
Self {
field_name: contract.field_name().to_string(),
kind: contract.kind().clone(),
nullable: contract.nullable(),
storage_decode: contract.storage_decode(),
leaf_codec: contract.leaf_codec(),
write_policy: field.write_policy(),
absence_policy: field.absence_policy(),
default: field.default().clone(),
generated: field.generated(),
}
}
#[must_use]
pub(in crate::db) const fn decode_contract(&self) -> AcceptedFieldDecodeContract<'_> {
AcceptedFieldDecodeContract::new(
self.field_name.as_str(),
&self.kind,
self.nullable,
self.storage_decode,
self.leaf_codec,
)
}
#[must_use]
pub(in crate::db) const fn absence_policy(&self) -> AcceptedFieldAbsencePolicy {
self.absence_policy
}
#[must_use]
pub(in crate::db) const fn write_policy(&self) -> SchemaFieldWritePolicy {
self.write_policy
}
#[must_use]
pub(in crate::db) const fn default(&self) -> &SchemaFieldDefault {
&self.default
}
#[must_use]
pub(in crate::db) const fn field_name(&self) -> &str {
self.field_name.as_str()
}
#[must_use]
pub(in crate::db) const fn kind(&self) -> &PersistedFieldKind {
&self.kind
}
#[must_use]
pub(in crate::db) const fn generated(&self) -> bool {
self.generated
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct OwnedAcceptedRelationEdgeContract {
name: String,
target_path: String,
local_field_slots: Vec<usize>,
}
impl OwnedAcceptedRelationEdgeContract {
fn from_runtime_relation_edge(
relation: &PersistedRelationEdgeSnapshot,
fields: &[AcceptedRowLayoutRuntimeField<'_>],
) -> Result<Self, InternalError> {
let mut local_field_slots = Vec::with_capacity(relation.local_field_ids().len());
for field_id in relation.local_field_ids() {
let Some(field) = fields.iter().find(|field| field.field_id() == *field_id) else {
return Err(InternalError::store_invariant(format!(
"accepted row layout runtime contract missing relation local field_id={}",
field_id.get(),
)));
};
local_field_slots.push(usize::from(field.slot().get()));
}
Ok(Self {
name: relation.name().to_string(),
target_path: relation.target_path().to_string(),
local_field_slots,
})
}
#[must_use]
pub(in crate::db) const fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub(in crate::db) const fn target_path(&self) -> &str {
self.target_path.as_str()
}
#[must_use]
pub(in crate::db) const fn local_field_slots(&self) -> &[usize] {
self.local_field_slots.as_slice()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct AcceptedRowDecodeContract {
required_slot_count: usize,
max_physical_slot_count: usize,
primary_key_slot_index: usize,
primary_key_slot_indices: Vec<usize>,
fields_by_slot: Vec<Option<OwnedAcceptedFieldDecodeContract>>,
relation_edges: Vec<OwnedAcceptedRelationEdgeContract>,
}
impl AcceptedRowDecodeContract {
fn from_runtime_contract(descriptor: &AcceptedRowLayoutRuntimeContract<'_>) -> Self {
let mut fields_by_slot = vec![None; descriptor.required_slot_count()];
for field in descriptor.fields() {
fields_by_slot[usize::from(field.slot().get())] =
Some(OwnedAcceptedFieldDecodeContract::from_runtime_field(field));
}
Self {
required_slot_count: descriptor.required_slot_count(),
max_physical_slot_count: descriptor.max_physical_slot_count(),
primary_key_slot_index: descriptor.first_primary_key_slot_index(),
primary_key_slot_indices: descriptor.primary_key_slot_indices().to_vec(),
fields_by_slot,
relation_edges: descriptor.relation_edges().to_vec(),
}
}
#[cfg(test)]
pub(in crate::db) fn from_generated_model_for_tests(model: &'static EntityModel) -> Self {
let proposal = crate::db::schema::compiled_schema_proposal_for_model(model);
let accepted =
AcceptedSchemaSnapshot::try_new(proposal.initial_persisted_schema_snapshot())
.expect("generated model proposal should produce an accepted test schema");
let (descriptor, _) =
AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(&accepted, model)
.expect("generated model accepted test schema should be generated-compatible");
descriptor.row_decode_contract()
}
#[must_use]
pub(in crate::db) const fn required_slot_count(&self) -> usize {
self.required_slot_count
}
#[must_use]
pub(in crate::db) const fn max_physical_slot_count(&self) -> usize {
self.max_physical_slot_count
}
#[must_use]
pub(in crate::db) const fn first_primary_key_slot_index(&self) -> usize {
self.primary_key_slot_index
}
#[allow(
dead_code,
reason = "ordered primary-key slot access becomes live when row decode exposes composite key component slots"
)]
#[must_use]
pub(in crate::db) const fn primary_key_slot_indices(&self) -> &[usize] {
self.primary_key_slot_indices.as_slice()
}
#[must_use]
pub(in crate::db) const fn relation_edges(&self) -> &[OwnedAcceptedRelationEdgeContract] {
self.relation_edges.as_slice()
}
#[must_use]
pub(in crate::db) fn field_for_slot(
&self,
slot: usize,
) -> Option<&OwnedAcceptedFieldDecodeContract> {
self.fields_by_slot.get(slot)?.as_ref()
}
pub(in crate::db) fn required_field_for_slot(
&self,
entity_path: &str,
slot: usize,
) -> Result<&OwnedAcceptedFieldDecodeContract, InternalError> {
self.field_for_slot(slot).ok_or_else(|| {
InternalError::persisted_row_slot_lookup_out_of_bounds(entity_path, slot)
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) struct AcceptedGeneratedRowCompatibilityProof {
required_slot_count: usize,
primary_key_slot_index: usize,
}
impl AcceptedGeneratedRowCompatibilityProof {
#[must_use]
#[cfg(test)]
pub(in crate::db) const fn required_slot_count(self) -> usize {
self.required_slot_count
}
#[must_use]
#[cfg(test)]
pub(in crate::db) const fn first_primary_key_slot_index(self) -> usize {
self.primary_key_slot_index
}
}
#[derive(Debug, Eq, PartialEq)]
pub(in crate::db) struct AcceptedRowLayoutRuntimeContract<'a> {
version: SchemaVersion,
required_slot_count: usize,
max_physical_slot_count: usize,
primary_key_names: Vec<&'a str>,
primary_key_kinds: Vec<&'a PersistedFieldKind>,
primary_key_slot_indices: Vec<usize>,
fields: Vec<AcceptedRowLayoutRuntimeField<'a>>,
relation_edges: Vec<OwnedAcceptedRelationEdgeContract>,
}
impl<'a> AcceptedRowLayoutRuntimeContract<'a> {
pub(in crate::db) fn from_accepted_schema(
accepted: &'a AcceptedSchemaSnapshot,
) -> Result<Self, InternalError> {
let snapshot = accepted.persisted_snapshot();
let row_layout = snapshot.row_layout();
let mut required_slot_count = 0usize;
let mut fields = Vec::with_capacity(snapshot.fields().len());
for field in snapshot.fields() {
let Some(slot) = row_layout.slot_for_field(field.id()) else {
return Err(InternalError::store_invariant(format!(
"accepted row layout runtime contract missing slot for field_id={}",
field.id().get(),
)));
};
let slot_end = usize::from(slot.get()).saturating_add(1);
required_slot_count = required_slot_count.max(slot_end);
fields.push(AcceptedRowLayoutRuntimeField {
field_id: field.id(),
name: field.name(),
slot,
kind: field.kind(),
nested_leaves: field.nested_leaves(),
nullable: field.nullable(),
default: field.default(),
write_policy: field.write_policy(),
storage_decode: field.storage_decode(),
leaf_codec: field.leaf_codec(),
absence_policy: accepted_field_absence_policy(field.nullable(), field.default()),
generated: field.generated(),
});
}
let mut primary_key_names = Vec::with_capacity(snapshot.primary_key_field_ids().len());
let mut primary_key_kinds = Vec::with_capacity(snapshot.primary_key_field_ids().len());
let mut primary_key_slot_indices =
Vec::with_capacity(snapshot.primary_key_field_ids().len());
for primary_key_field_id in snapshot.primary_key_field_ids() {
let Some(primary_key_field) = fields
.iter()
.find(|field| field.field_id() == *primary_key_field_id)
else {
return Err(InternalError::store_invariant(format!(
"accepted row layout runtime contract missing primary-key field_id={}",
primary_key_field_id.get(),
)));
};
primary_key_names.push(primary_key_field.name());
primary_key_kinds.push(primary_key_field.kind());
primary_key_slot_indices.push(usize::from(primary_key_field.slot().get()));
}
let relation_edges = snapshot
.relations()
.iter()
.map(|relation| {
OwnedAcceptedRelationEdgeContract::from_runtime_relation_edge(relation, &fields)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
version: row_layout.version(),
required_slot_count,
max_physical_slot_count: row_layout.allocated_slot_count().max(required_slot_count),
primary_key_names,
primary_key_kinds,
primary_key_slot_indices,
fields,
relation_edges,
})
}
pub(in crate::db) fn from_generated_compatible_schema(
accepted: &'a AcceptedSchemaSnapshot,
model: &'static EntityModel,
) -> Result<(Self, AcceptedGeneratedRowCompatibilityProof), InternalError> {
#[cfg(test)]
GENERATED_COMPATIBLE_ROW_LAYOUT_PROOFS
.with(|proofs| proofs.set(proofs.get().saturating_add(1)));
let descriptor = Self::from_accepted_schema(accepted)?;
let row_proof = descriptor.generated_row_compatibility_proof_for_model(model)?;
Ok((descriptor, row_proof))
}
#[cfg_attr(
not(test),
allow(
dead_code,
reason = "runtime layout tests assert accepted-version handoff before production callers read the descriptor version directly"
)
)]
#[must_use]
pub(in crate::db) const fn version(&self) -> SchemaVersion {
self.version
}
#[must_use]
pub(in crate::db) const fn required_slot_count(&self) -> usize {
self.required_slot_count
}
#[must_use]
pub(in crate::db) const fn max_physical_slot_count(&self) -> usize {
self.max_physical_slot_count
}
#[must_use]
pub(in crate::db) fn first_primary_key_name(&self) -> &'a str {
self.primary_key_names[0]
}
#[must_use]
pub(in crate::db) const fn primary_key_names(&self) -> &[&'a str] {
self.primary_key_names.as_slice()
}
#[must_use]
pub(in crate::db) fn is_primary_key_field_name(&self, field_name: &str) -> bool {
self.primary_key_names.contains(&field_name)
}
#[must_use]
pub(in crate::db) fn first_primary_key_kind(&self) -> &'a PersistedFieldKind {
self.primary_key_kinds[0]
}
#[allow(
dead_code,
reason = "ordered primary-key kind access becomes live when SQL/composite key literal admission leaves the first-component helper"
)]
#[must_use]
pub(in crate::db) const fn primary_key_kinds(&self) -> &[&'a PersistedFieldKind] {
self.primary_key_kinds.as_slice()
}
#[must_use]
pub(in crate::db) fn first_primary_key_slot_index(&self) -> usize {
self.primary_key_slot_indices[0]
}
#[allow(
dead_code,
reason = "ordered primary-key slot access becomes live when row decode exposes composite key component slots"
)]
#[must_use]
pub(in crate::db) const fn primary_key_slot_indices(&self) -> &[usize] {
self.primary_key_slot_indices.as_slice()
}
#[must_use]
pub(in crate::db) const fn relation_edges(&self) -> &[OwnedAcceptedRelationEdgeContract] {
self.relation_edges.as_slice()
}
#[must_use]
pub(in crate::db) const fn fields(&self) -> &[AcceptedRowLayoutRuntimeField<'a>] {
self.fields.as_slice()
}
#[cfg_attr(
not(test),
allow(
dead_code,
reason = "runtime layout tests exercise slot lookup before production callers need the typed slot wrapper"
)
)]
#[must_use]
pub(in crate::db) fn field_for_slot(
&self,
slot: SchemaFieldSlot,
) -> Option<&AcceptedRowLayoutRuntimeField<'a>> {
self.fields.iter().find(|field| field.slot() == slot)
}
#[must_use]
pub(in crate::db) fn field_for_slot_index(
&self,
slot: usize,
) -> Option<&AcceptedRowLayoutRuntimeField<'a>> {
self.fields
.iter()
.find(|field| usize::from(field.slot().get()) == slot)
}
#[cfg_attr(
not(test),
allow(
dead_code,
reason = "runtime layout tests exercise field-id lookup before production callers need direct durable-id access"
)
)]
#[must_use]
pub(in crate::db) fn field_for_id(
&self,
field_id: FieldId,
) -> Option<&AcceptedRowLayoutRuntimeField<'a>> {
self.fields
.iter()
.find(|field| field.field_id() == field_id)
}
#[must_use]
pub(in crate::db) fn field_by_name(
&self,
name: &str,
) -> Option<&AcceptedRowLayoutRuntimeField<'a>> {
self.fields.iter().find(|field| field.name() == name)
}
#[must_use]
pub(in crate::db) fn field_slot_index_by_name(&self, name: &str) -> Option<usize> {
self.field_by_name(name)
.map(|field| usize::from(field.slot().get()))
}
#[must_use]
pub(in crate::db) fn field_kind_by_name(&self, name: &str) -> Option<&PersistedFieldKind> {
self.field_by_name(name)
.map(AcceptedRowLayoutRuntimeField::kind)
}
#[must_use]
pub(in crate::db) fn row_decode_contract(&self) -> AcceptedRowDecodeContract {
AcceptedRowDecodeContract::from_runtime_contract(self)
}
pub(in crate::db) fn generated_row_compatibility_proof_for_model(
&self,
model: &'static EntityModel,
) -> Result<AcceptedGeneratedRowCompatibilityProof, InternalError> {
let generated_primary_key_names = model
.primary_key_model()
.fields()
.iter()
.map(FieldModel::name)
.collect::<Vec<_>>();
if self.primary_key_names() != generated_primary_key_names.as_slice() {
return Err(InternalError::store_invariant(format!(
"accepted row layout primary key is not generated-compatible: accepted_primary_key={:?} generated_primary_key={:?}",
self.primary_key_names(),
generated_primary_key_names,
)));
}
if self.required_slot_count() < model.fields().len() {
return Err(InternalError::store_invariant(format!(
"accepted row layout field count is not generated-compatible: accepted={} generated={}",
self.required_slot_count(),
model.fields().len(),
)));
}
for (generated_slot, field) in model.fields().iter().enumerate() {
let Some(accepted_field) = self.field_by_name(field.name()) else {
return Err(InternalError::store_invariant(format!(
"accepted row layout missing generated field '{}'",
field.name(),
)));
};
let accepted_slot = usize::from(accepted_field.slot().get());
if accepted_slot != generated_slot {
return Err(InternalError::store_invariant(format!(
"accepted row layout slot is not generated-compatible: field='{}' accepted_slot={} generated_slot={}",
field.name(),
accepted_slot,
generated_slot,
)));
}
ensure_generated_field_decode_contract_compatible(accepted_field, field)?;
}
for slot in model.fields().len()..self.required_slot_count() {
let Some(extra_field) = self.field_for_slot_index(slot) else {
continue;
};
if extra_field.generated() {
return Err(InternalError::store_invariant(format!(
"accepted row layout has generated field outside generated model: field='{}' slot={slot}",
extra_field.name(),
)));
}
if matches!(
extra_field.absence_policy(),
AcceptedFieldAbsencePolicy::Required
) {
return Err(InternalError::store_invariant(format!(
"accepted row layout trailing DDL field is required without missing-field policy: field='{}' slot={slot}",
extra_field.name(),
)));
}
}
Ok(AcceptedGeneratedRowCompatibilityProof {
required_slot_count: self.required_slot_count(),
primary_key_slot_index: self.first_primary_key_slot_index(),
})
}
}
fn ensure_generated_field_decode_contract_compatible(
accepted_field: &AcceptedRowLayoutRuntimeField<'_>,
generated_field: &FieldModel,
) -> Result<(), InternalError> {
let accepted_contract = accepted_field.decode_contract();
let generated_kind = PersistedFieldKind::from_model_kind(generated_field.kind());
if accepted_contract.kind() != &generated_kind {
return Err(InternalError::store_invariant(format!(
"accepted row layout kind is not generated-compatible: field='{}' accepted_kind={:?} generated_kind={:?}",
accepted_contract.field_name(),
accepted_contract.kind(),
generated_kind,
)));
}
if accepted_contract.nullable() != generated_field.nullable() {
return Err(InternalError::store_invariant(format!(
"accepted row layout nullability is not generated-compatible: field='{}' accepted_nullable={} generated_nullable={}",
accepted_contract.field_name(),
accepted_contract.nullable(),
generated_field.nullable(),
)));
}
if accepted_contract.storage_decode() != generated_field.storage_decode() {
return Err(InternalError::store_invariant(format!(
"accepted row layout storage decode is not generated-compatible: field='{}' accepted_storage_decode={:?} generated_storage_decode={:?}",
accepted_contract.field_name(),
accepted_contract.storage_decode(),
generated_field.storage_decode(),
)));
}
if accepted_contract.leaf_codec() != generated_field.leaf_codec() {
return Err(InternalError::store_invariant(format!(
"accepted row layout leaf codec is not generated-compatible: field='{}' accepted_leaf_codec={:?} generated_leaf_codec={:?}",
accepted_contract.field_name(),
accepted_contract.leaf_codec(),
generated_field.leaf_codec(),
)));
}
Ok(())
}
const fn accepted_field_absence_policy(
nullable: bool,
default: &SchemaFieldDefault,
) -> AcceptedFieldAbsencePolicy {
match (nullable, default) {
(true, SchemaFieldDefault::None) => AcceptedFieldAbsencePolicy::NullIfMissing,
(false, SchemaFieldDefault::None) => AcceptedFieldAbsencePolicy::Required,
(_, SchemaFieldDefault::SlotPayload(_)) => AcceptedFieldAbsencePolicy::DefaultIfMissing,
}
}
#[cfg(test)]
mod tests;