Skip to main content

icydb_core/db/schema/
describe.rs

1//! Module: db::schema::describe
2//! Responsibility: deterministic entity-schema introspection DTOs for runtime consumers.
3//! Does not own: query planning, execution routing, or relation enforcement semantics.
4//! Boundary: projects accepted schema metadata into stable describe surfaces.
5
6use crate::{
7    db::schema::CompositeCodec,
8    db::{
9        data::decode_admitted_value_from_accepted_field_contract,
10        schema::{
11            AcceptedCheckExprV1, AcceptedConstraintKind, AcceptedFieldKind,
12            AcceptedFieldPersistenceContract, AcceptedIdentityInspection,
13            AcceptedInsertOmissionPolicy, AcceptedRowLayoutRuntimeContract, AcceptedRuleOperation,
14            AcceptedRuleTarget, AcceptedSchemaSnapshot, AcceptedValueCatalogHandle,
15            ConstraintActivationKind, ConstraintActivationSnapshot, ConstraintActivationState,
16            ConstraintOrigin, ConstraintValidationJob, FieldId, FieldInsertGeneration,
17            MAX_ACCEPTED_RECURSIVE_DEPTH, MAX_SCHEMA_SNAPSHOT_BYTES, PersistedIndexKeyItemSnapshot,
18            PersistedIndexKeySnapshot, PersistedIndexSnapshot, PersistedNestedLeafSnapshot,
19            PersistedRelationEdgeSnapshot, PersistedRelationPathStepSnapshot,
20            PersistedRelationSourceSnapshot, PersistedSchemaSnapshot, SchemaHistoricalFill,
21            composite_catalog::{AcceptedCompositeElement, AcceptedCompositeShape},
22            identity_kind_maximum, output_value_from_runtime, query_field_is_queryable,
23            render_accepted_check_expr_sql,
24            runtime::AcceptedRowLayoutRuntimeField,
25        },
26    },
27    error::InternalError,
28    value::{OutputValue, render_output_value_text},
29};
30use std::fmt::Write;
31
32use serde::Deserialize;
33use sha2::{Digest, Sha256};
34
35const ENTITY_FIELD_DESCRIPTION_NO_SLOT: u16 = u16::MAX;
36const MAX_SCHEMA_VALUE_RENDER_CHARS: usize = 128;
37const MAX_SQL_COLUMN_EXTRA_FLAGS: usize = 3;
38const MAX_SQL_COMPACT_COLUMN_ROWS: usize =
39    icydb_schema::MAX_FRAGMENT_FIELDS * (1 + icydb_schema::MAX_FRAGMENT_FIELDS);
40
41/// Compact accepted index-membership hint for one SQL column row.
42#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
43#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
44pub enum SqlColumnKey {
45    /// Accepted primary-key field.
46    Primary,
47    /// Sole field path in one unconditional accepted unique secondary index.
48    Unique,
49    /// Member of a compound, filtered or non-unique accepted secondary index.
50    Multiple,
51    /// No accepted primary or secondary index membership.
52    None,
53}
54
55/// Compact accepted insert-default policy for one SQL column row.
56#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
57#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
58pub enum SqlColumnDefault {
59    /// Database-owned insert synthesis.
60    Auto,
61    /// Missing inserts produce `NULL`.
62    Null,
63    /// Bounded canonical accepted literal.
64    Literal {
65        /// Canonical rendered literal text.
66        text: String,
67    },
68    /// A value is required and no accepted default exists.
69    Required,
70    /// Nested paths own no independent insert slot.
71    NotApplicable,
72}
73
74impl SqlColumnDefault {
75    /// Borrow canonical literal text when this is a literal default.
76    #[must_use]
77    pub const fn literal_text(&self) -> Option<&str> {
78        match self {
79            Self::Literal { text } => Some(text.as_str()),
80            Self::Auto | Self::Null | Self::Required | Self::NotApplicable => None,
81        }
82    }
83}
84
85/// Closed compact extra-fact vocabulary for one SQL column row.
86#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
87#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
88pub enum SqlColumnExtra {
89    /// Accepted Identity generation owns this field.
90    Identity,
91    /// Accepted write policy synthesizes this field on insert.
92    Generated,
93    /// This field participates in an accepted relation edge.
94    Relation,
95}
96
97/// Compact accepted-schema column projection.
98#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
99#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
100pub struct SqlColumnSummary {
101    name: String,
102    field_type: String,
103    nullable: bool,
104    key: SqlColumnKey,
105    default: SqlColumnDefault,
106    extra: Vec<SqlColumnExtra>,
107}
108
109impl SqlColumnSummary {
110    fn new(
111        name: String,
112        field_type: String,
113        nullable: bool,
114        key: SqlColumnKey,
115        default: SqlColumnDefault,
116        extra: Vec<SqlColumnExtra>,
117    ) -> Result<Self, InternalError> {
118        if extra.len() > MAX_SQL_COLUMN_EXTRA_FLAGS
119            || default
120                .literal_text()
121                .is_some_and(|text| text.len() > MAX_SCHEMA_VALUE_RENDER_CHARS)
122        {
123            return Err(InternalError::store_invariant());
124        }
125        Ok(Self {
126            name,
127            field_type,
128            nullable,
129            key,
130            default,
131            extra,
132        })
133    }
134
135    /// Borrow the canonical accepted query path.
136    #[must_use]
137    pub const fn name(&self) -> &str {
138        self.name.as_str()
139    }
140
141    /// Borrow the accepted field-kind rendering.
142    #[must_use]
143    pub const fn field_type(&self) -> &str {
144        self.field_type.as_str()
145    }
146
147    /// Return effective accepted explicit-nullability.
148    #[must_use]
149    pub const fn nullable(&self) -> bool {
150        self.nullable
151    }
152
153    /// Return the compact accepted index hint.
154    #[must_use]
155    pub const fn key(&self) -> SqlColumnKey {
156        self.key
157    }
158
159    /// Borrow the compact accepted insert-default policy.
160    #[must_use]
161    pub const fn default(&self) -> &SqlColumnDefault {
162        &self.default
163    }
164
165    /// Borrow ordered accepted extra facts.
166    #[must_use]
167    pub const fn extra(&self) -> &[SqlColumnExtra] {
168        self.extra.as_slice()
169    }
170}
171
172/// Discriminated public `DESCRIBE` result.
173#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
174#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
175pub enum SqlDescribeOutput {
176    /// Conventional compact column table.
177    Compact {
178        /// Accepted entity display name.
179        entity: String,
180        /// Canonical compact column rows.
181        columns: Vec<SqlColumnSummary>,
182    },
183    /// Complete maintained operational dossier.
184    Verbose {
185        /// Complete accepted entity description.
186        description: EntitySchemaDescription,
187    },
188}
189
190/// Discriminated public `SHOW COLUMNS` result.
191#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
192#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
193pub enum SqlShowColumnsOutput {
194    /// Compact column projection shared with `DESCRIBE`.
195    Compact {
196        /// Accepted entity display name.
197        entity: String,
198        /// Canonical compact column rows.
199        columns: Vec<SqlColumnSummary>,
200    },
201    /// Detailed accepted field/layout rows only.
202    Verbose {
203        /// Accepted entity display name.
204        entity: String,
205        /// Maintained verbose field descriptions.
206        columns: Vec<EntityFieldDescription>,
207    },
208}
209
210/// Public `SHOW RELATIONS` result.
211#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
212#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
213pub struct SqlShowRelationsOutput {
214    entity: String,
215    relations: Vec<EntityRelationDescription>,
216}
217
218impl SqlShowRelationsOutput {
219    /// Build one bounded relation-only result.
220    pub(in crate::db) fn new(
221        entity: String,
222        relations: Vec<EntityRelationDescription>,
223    ) -> Result<Self, InternalError> {
224        if relations.len() > icydb_schema::MAX_FRAGMENT_RELATIONS {
225            return Err(InternalError::store_invariant());
226        }
227        Ok(Self { entity, relations })
228    }
229
230    /// Borrow the accepted entity display name.
231    #[must_use]
232    pub const fn entity(&self) -> &str {
233        self.entity.as_str()
234    }
235
236    /// Borrow accepted relation rows in stable relation-ID order.
237    #[must_use]
238    pub const fn relations(&self) -> &[EntityRelationDescription] {
239        self.relations.as_slice()
240    }
241}
242
243#[cfg_attr(
244    doc,
245    doc = "EntitySchemaDescription\n\nStable describe payload for one entity model."
246)]
247#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
248#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
249pub struct EntitySchemaDescription {
250    pub(crate) entity_path: String,
251    pub(crate) entity_name: String,
252    pub(crate) entity_tag: u64,
253    pub(crate) accepted_schema_fingerprint_method: u8,
254    pub(crate) accepted_schema_fingerprint: [u8; 16],
255    pub(crate) primary_key: String,
256    pub(crate) primary_key_fields: Vec<String>,
257    pub(crate) identity: Option<Box<EntityIdentityDescription>>,
258    pub(crate) fields: Vec<EntityFieldDescription>,
259    pub(crate) indexes: Vec<EntityIndexDescription>,
260    pub(crate) relations: Vec<EntityRelationDescription>,
261    pub(crate) constraints: Vec<EntityConstraintDescription>,
262    pub(crate) row_layout_current: u32,
263    pub(crate) row_layout_history_floor: u32,
264}
265
266impl EntitySchemaDescription {
267    /// Construct one entity schema description payload.
268    #[expect(
269        clippy::too_many_arguments,
270        reason = "schema description construction keeps identity, collections, and layout explicit"
271    )]
272    #[must_use]
273    pub const fn new(
274        entity_path: String,
275        entity_name: String,
276        entity_tag: u64,
277        accepted_schema_fingerprint_method: u8,
278        accepted_schema_fingerprint: [u8; 16],
279        primary_key: String,
280        primary_key_fields: Vec<String>,
281        fields: Vec<EntityFieldDescription>,
282        indexes: Vec<EntityIndexDescription>,
283        relations: Vec<EntityRelationDescription>,
284        constraints: Vec<EntityConstraintDescription>,
285        row_layout_current: u32,
286        row_layout_history_floor: u32,
287    ) -> Self {
288        Self {
289            entity_path,
290            entity_name,
291            entity_tag,
292            accepted_schema_fingerprint_method,
293            accepted_schema_fingerprint,
294            primary_key,
295            primary_key_fields,
296            identity: None,
297            fields,
298            indexes,
299            relations,
300            constraints,
301            row_layout_current,
302            row_layout_history_floor,
303        }
304    }
305
306    /// Borrow the entity module path.
307    #[must_use]
308    pub const fn entity_path(&self) -> &str {
309        self.entity_path.as_str()
310    }
311
312    /// Borrow the entity display name.
313    #[must_use]
314    pub const fn entity_name(&self) -> &str {
315        self.entity_name.as_str()
316    }
317
318    /// Return the accepted durable entity identity used by diagnostic facts.
319    #[must_use]
320    pub const fn entity_tag(&self) -> u64 {
321        self.entity_tag
322    }
323
324    /// Return the accepted schema-fingerprint method used by diagnostic facts.
325    #[must_use]
326    pub const fn accepted_schema_fingerprint_method(&self) -> u8 {
327        self.accepted_schema_fingerprint_method
328    }
329
330    /// Return the exact accepted entity-schema fingerprint.
331    #[must_use]
332    pub const fn accepted_schema_fingerprint(&self) -> [u8; 16] {
333        self.accepted_schema_fingerprint
334    }
335
336    /// Borrow the rendered primary-key field list.
337    #[must_use]
338    pub const fn primary_key(&self) -> &str {
339        self.primary_key.as_str()
340    }
341
342    /// Borrow ordered primary-key field names.
343    #[must_use]
344    pub const fn primary_key_fields(&self) -> &[String] {
345        self.primary_key_fields.as_slice()
346    }
347
348    /// Borrow the accepted Identity policy and lifetime allocation state.
349    #[must_use]
350    pub fn identity(&self) -> Option<&EntityIdentityDescription> {
351        self.identity.as_deref()
352    }
353
354    /// Borrow field description entries.
355    #[must_use]
356    pub const fn fields(&self) -> &[EntityFieldDescription] {
357        self.fields.as_slice()
358    }
359
360    /// Borrow index description entries.
361    #[must_use]
362    pub const fn indexes(&self) -> &[EntityIndexDescription] {
363        self.indexes.as_slice()
364    }
365
366    /// Borrow relation description entries.
367    #[must_use]
368    pub const fn relations(&self) -> &[EntityRelationDescription] {
369        self.relations.as_slice()
370    }
371
372    /// Borrow accepted or generated structural constraint descriptions.
373    #[must_use]
374    pub const fn constraints(&self) -> &[EntityConstraintDescription] {
375        self.constraints.as_slice()
376    }
377
378    /// Return the current accepted physical row-layout identity.
379    #[must_use]
380    pub const fn row_layout_current(&self) -> u32 {
381        self.row_layout_current
382    }
383
384    /// Return the oldest admitted physical row-layout identity.
385    #[must_use]
386    pub const fn row_layout_history_floor(&self) -> u32 {
387        self.row_layout_history_floor
388    }
389
390    fn with_identity(mut self, identity: Option<EntityIdentityDescription>) -> Self {
391        self.identity = identity.map(Box::new);
392        self
393    }
394}
395
396/// Accepted Identity generator policy, exact unsigned domain, and current
397/// lifetime allocation state for one entity.
398#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
399#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
400pub struct EntityIdentityDescription {
401    field: String,
402    generator: String,
403    accepted_kind: String,
404    minimum: u128,
405    maximum: u128,
406    high_water: u128,
407    remaining: u128,
408    exhausted: bool,
409}
410
411impl EntityIdentityDescription {
412    pub(in crate::db) fn new(
413        field: String,
414        accepted_kind: String,
415        maximum: u128,
416        high_water: u128,
417    ) -> Result<Self, InternalError> {
418        let remaining = maximum
419            .checked_sub(high_water)
420            .ok_or_else(InternalError::identity_state_corruption)?;
421        Ok(Self {
422            field,
423            generator: "Identity::next".to_string(),
424            accepted_kind,
425            minimum: 1,
426            maximum,
427            high_water,
428            remaining,
429            exhausted: high_water == maximum,
430        })
431    }
432
433    /// Borrow the accepted Identity field name.
434    #[must_use]
435    pub const fn field(&self) -> &str {
436        self.field.as_str()
437    }
438
439    /// Borrow the fixed accepted generator spelling.
440    #[must_use]
441    pub const fn generator(&self) -> &str {
442        self.generator.as_str()
443    }
444
445    /// Borrow the exact accepted unsigned field kind.
446    #[must_use]
447    pub const fn accepted_kind(&self) -> &str {
448        self.accepted_kind.as_str()
449    }
450
451    /// Return the first generated value.
452    #[must_use]
453    pub const fn minimum(&self) -> u128 {
454        self.minimum
455    }
456
457    /// Return the exact accepted lifetime allocation maximum.
458    #[must_use]
459    pub const fn maximum(&self) -> u128 {
460        self.maximum
461    }
462
463    /// Return the greatest committed value, or zero before the first commit.
464    #[must_use]
465    pub const fn high_water(&self) -> u128 {
466        self.high_water
467    }
468
469    /// Return the remaining lifetime allocation capacity.
470    #[must_use]
471    pub const fn remaining(&self) -> u128 {
472        self.remaining
473    }
474
475    /// Return whether the exact accepted unsigned domain is exhausted.
476    #[must_use]
477    pub const fn exhausted(&self) -> bool {
478        self.exhausted
479    }
480}
481
482pub(in crate::db) fn describe_accepted_identity(
483    identity: &AcceptedIdentityInspection,
484    high_water: u128,
485) -> Result<EntityIdentityDescription, InternalError> {
486    let accepted_kind = describe_kind_name(identity.accepted_kind())
487        .ok_or_else(InternalError::identity_state_corruption)?;
488    let maximum = identity_kind_maximum(identity.accepted_kind())
489        .ok_or_else(InternalError::identity_state_corruption)?;
490    EntityIdentityDescription::new(
491        identity.field_name().to_string(),
492        accepted_kind.to_string(),
493        maximum,
494        high_water,
495    )
496}
497
498#[cfg_attr(
499    doc,
500    doc = "EntityConstraintDescription\n\nOne accepted structural constraint entry in a describe payload."
501)]
502#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
503#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
504pub struct EntityConstraintDescription {
505    pub(crate) id: u32,
506    pub(crate) name: String,
507    pub(crate) kind: String,
508    pub(crate) origin: String,
509    pub(crate) validation_state: String,
510    pub(crate) validation_progress: Option<ConstraintValidationProgressDescription>,
511    pub(crate) field_id: Option<u32>,
512    pub(crate) index_id: Option<u32>,
513    pub(crate) relation_id: Option<u32>,
514    pub(crate) fields: Vec<String>,
515    pub(crate) index: Option<String>,
516    pub(crate) predicate_sql: Option<String>,
517    pub(crate) relation: Option<String>,
518    pub(crate) target_entity: Option<String>,
519    pub(crate) action: Option<String>,
520    pub(crate) semantics: String,
521    pub(crate) check_sql: Option<String>,
522}
523
524/// Current bounded validation-job counters for one activating constraint.
525#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
526#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
527pub struct ConstraintValidationProgressDescription {
528    phase: String,
529    rows_scanned: u64,
530    findings_seen: u64,
531    restarts: u64,
532}
533
534impl ConstraintValidationProgressDescription {
535    fn from_job(job: &ConstraintValidationJob) -> Self {
536        Self {
537            phase: job.phase().as_str().to_string(),
538            rows_scanned: job.rows_scanned(),
539            findings_seen: job.findings_seen(),
540            restarts: job.restarts(),
541        }
542    }
543
544    /// Borrow the current bounded proof phase.
545    #[must_use]
546    pub const fn phase(&self) -> &str {
547        self.phase.as_str()
548    }
549
550    /// Return the cumulative classified-row count.
551    #[must_use]
552    pub const fn rows_scanned(&self) -> u64 {
553        self.rows_scanned
554    }
555
556    /// Return the cumulative finding count.
557    #[must_use]
558    pub const fn findings_seen(&self) -> u64 {
559        self.findings_seen
560    }
561
562    /// Return the cumulative proof-restart count.
563    #[must_use]
564    pub const fn restarts(&self) -> u64 {
565        self.restarts
566    }
567}
568
569impl EntityConstraintDescription {
570    /// Return the stable entity-local constraint identity.
571    #[must_use]
572    pub const fn id(&self) -> u32 {
573        self.id
574    }
575
576    /// Borrow the stable accepted constraint name.
577    #[must_use]
578    pub const fn name(&self) -> &str {
579        self.name.as_str()
580    }
581
582    /// Borrow the structural constraint kind label.
583    #[must_use]
584    pub const fn kind(&self) -> &str {
585        self.kind.as_str()
586    }
587
588    /// Borrow the constraint origin label.
589    #[must_use]
590    pub const fn origin(&self) -> &str {
591        self.origin.as_str()
592    }
593
594    /// Borrow the validation-state label.
595    #[must_use]
596    pub const fn validation_state(&self) -> &str {
597        self.validation_state.as_str()
598    }
599
600    /// Borrow current bounded validation progress, when activation has begun.
601    #[must_use]
602    pub const fn validation_progress(&self) -> Option<&ConstraintValidationProgressDescription> {
603        self.validation_progress.as_ref()
604    }
605
606    /// Return the referenced field identity for a not-null constraint.
607    #[must_use]
608    pub const fn field_id(&self) -> Option<u32> {
609        self.field_id
610    }
611
612    /// Return the referenced logical index identity for a unique constraint.
613    #[must_use]
614    pub const fn index_id(&self) -> Option<u32> {
615        self.index_id
616    }
617
618    /// Return the referenced logical relation identity.
619    #[must_use]
620    pub const fn relation_id(&self) -> Option<u32> {
621        self.relation_id
622    }
623
624    /// Borrow current accepted field names participating in the constraint.
625    #[must_use]
626    pub const fn fields(&self) -> &[String] {
627        self.fields.as_slice()
628    }
629
630    /// Borrow the current accepted index display name, when applicable.
631    #[must_use]
632    pub fn index(&self) -> Option<&str> {
633        self.index.as_deref()
634    }
635
636    /// Borrow the accepted backing-index predicate, when the unique
637    /// constraint describes partial uniqueness.
638    #[must_use]
639    pub fn predicate_sql(&self) -> Option<&str> {
640        self.predicate_sql.as_deref()
641    }
642
643    /// Borrow the current accepted relation display name, when applicable.
644    #[must_use]
645    pub fn relation(&self) -> Option<&str> {
646        self.relation.as_deref()
647    }
648
649    /// Borrow the current relation target entity path, when applicable.
650    #[must_use]
651    pub fn target_entity(&self) -> Option<&str> {
652        self.target_entity.as_deref()
653    }
654
655    /// Borrow the derived referential action, when applicable.
656    #[must_use]
657    pub fn action(&self) -> Option<&str> {
658        self.action.as_deref()
659    }
660
661    /// Borrow the derived structural semantics label.
662    #[must_use]
663    pub const fn semantics(&self) -> &str {
664        self.semantics.as_str()
665    }
666
667    /// Borrow the canonical accepted check expression, when applicable.
668    #[must_use]
669    pub fn check_sql(&self) -> Option<&str> {
670        self.check_sql.as_deref()
671    }
672}
673
674#[cfg_attr(
675    doc,
676    doc = "EntityFieldDescription\n\nOne field entry in a describe payload."
677)]
678#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
679#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
680pub struct EntityFieldDescription {
681    pub(crate) name: String,
682    pub(crate) slot: u16,
683    pub(crate) kind: String,
684    pub(crate) nullable: bool,
685    pub(crate) primary_key: bool,
686    pub(crate) queryable: bool,
687    pub(crate) origin: String,
688    pub(crate) insert_omission: Option<String>,
689    pub(crate) insert_default: Option<String>,
690    pub(crate) insert_default_bytes: Option<u32>,
691    pub(crate) insert_default_hash: Option<String>,
692    pub(crate) introduced_in_layout: Option<u32>,
693    pub(crate) historical_fill: Option<String>,
694    pub(crate) historical_fill_bytes: Option<u32>,
695    pub(crate) historical_fill_hash: Option<String>,
696}
697
698///
699/// EntityFieldTemporalFacts
700///
701/// One internally assembled projection of the independent accepted insert and
702/// historical-absence contracts. Nested rows carry an explicitly empty bundle.
703///
704
705struct EntityFieldTemporalFacts {
706    insert_omission: Option<String>,
707    insert_default: Option<String>,
708    insert_default_bytes: Option<u32>,
709    insert_default_hash: Option<String>,
710    introduced_in_layout: Option<u32>,
711    historical_fill: Option<String>,
712    historical_fill_bytes: Option<u32>,
713    historical_fill_hash: Option<String>,
714}
715
716impl EntityFieldTemporalFacts {
717    const fn nested() -> Self {
718        Self {
719            insert_omission: None,
720            insert_default: None,
721            insert_default_bytes: None,
722            insert_default_hash: None,
723            introduced_in_layout: None,
724            historical_fill: None,
725            historical_fill_bytes: None,
726            historical_fill_hash: None,
727        }
728    }
729}
730
731impl EntityFieldDescription {
732    /// Construct one field description entry.
733    #[expect(
734        clippy::too_many_arguments,
735        reason = "schema description construction keeps every temporal field fact explicit"
736    )]
737    #[must_use]
738    pub fn new(
739        name: String,
740        slot: Option<u16>,
741        kind: String,
742        nullable: bool,
743        primary_key: bool,
744        queryable: bool,
745        origin: String,
746        insert_omission: Option<String>,
747        insert_default: Option<String>,
748        insert_default_bytes: Option<u32>,
749        insert_default_hash: Option<String>,
750        introduced_in_layout: Option<u32>,
751        historical_fill: Option<String>,
752        historical_fill_bytes: Option<u32>,
753        historical_fill_hash: Option<String>,
754    ) -> Self {
755        Self::new_with_temporal_facts(
756            name,
757            slot,
758            primary_key,
759            DescribeFieldMetadata::new(kind, nullable, queryable, origin),
760            EntityFieldTemporalFacts {
761                insert_omission,
762                insert_default,
763                insert_default_bytes,
764                insert_default_hash,
765                introduced_in_layout,
766                historical_fill,
767                historical_fill_bytes,
768                historical_fill_hash,
769            },
770        )
771    }
772
773    fn new_with_temporal_facts(
774        name: String,
775        slot: Option<u16>,
776        primary_key: bool,
777        metadata: DescribeFieldMetadata,
778        temporal: EntityFieldTemporalFacts,
779    ) -> Self {
780        let slot = match slot {
781            Some(slot) => slot,
782            None => ENTITY_FIELD_DESCRIPTION_NO_SLOT,
783        };
784
785        Self {
786            name,
787            slot,
788            kind: metadata.kind,
789            nullable: metadata.nullable,
790            primary_key,
791            queryable: metadata.queryable,
792            origin: metadata.origin,
793            insert_omission: temporal.insert_omission,
794            insert_default: temporal.insert_default,
795            insert_default_bytes: temporal.insert_default_bytes,
796            insert_default_hash: temporal.insert_default_hash,
797            introduced_in_layout: temporal.introduced_in_layout,
798            historical_fill: temporal.historical_fill,
799            historical_fill_bytes: temporal.historical_fill_bytes,
800            historical_fill_hash: temporal.historical_fill_hash,
801        }
802    }
803
804    /// Borrow the field name.
805    #[must_use]
806    pub const fn name(&self) -> &str {
807        self.name.as_str()
808    }
809
810    /// Return the physical row slot for top-level fields.
811    #[must_use]
812    pub const fn slot(&self) -> Option<u16> {
813        if self.slot == ENTITY_FIELD_DESCRIPTION_NO_SLOT {
814            None
815        } else {
816            Some(self.slot)
817        }
818    }
819
820    /// Borrow the rendered field kind label.
821    #[must_use]
822    pub const fn kind(&self) -> &str {
823        self.kind.as_str()
824    }
825
826    /// Return whether this field permits explicit `NULL`.
827    #[must_use]
828    pub const fn nullable(&self) -> bool {
829        self.nullable
830    }
831
832    /// Return whether this field is the primary key.
833    #[must_use]
834    pub const fn primary_key(&self) -> bool {
835        self.primary_key
836    }
837
838    /// Return whether this field is queryable.
839    #[must_use]
840    pub const fn queryable(&self) -> bool {
841        self.queryable
842    }
843
844    /// Borrow the accepted/generated field origin label.
845    #[must_use]
846    pub const fn origin(&self) -> &str {
847        self.origin.as_str()
848    }
849
850    /// Borrow the accepted insert-omission policy label for a top-level field.
851    #[must_use]
852    pub fn insert_omission(&self) -> Option<&str> {
853        self.insert_omission.as_deref()
854    }
855
856    /// Borrow the bounded canonical accepted insert-default rendering.
857    #[must_use]
858    pub fn insert_default(&self) -> Option<&str> {
859        self.insert_default.as_deref()
860    }
861
862    /// Return the accepted insert-default payload byte count.
863    #[must_use]
864    pub const fn insert_default_bytes(&self) -> Option<u32> {
865        self.insert_default_bytes
866    }
867
868    /// Borrow the stable accepted insert-default payload hash.
869    #[must_use]
870    pub fn insert_default_hash(&self) -> Option<&str> {
871        self.insert_default_hash.as_deref()
872    }
873
874    /// Return the row layout that first physically contained this field.
875    #[must_use]
876    pub const fn introduced_in_layout(&self) -> Option<u32> {
877        self.introduced_in_layout
878    }
879
880    /// Borrow the accepted frozen historical-absence rendering.
881    #[must_use]
882    pub fn historical_fill(&self) -> Option<&str> {
883        self.historical_fill.as_deref()
884    }
885
886    /// Return the historical-fill payload byte count when one is stored.
887    #[must_use]
888    pub const fn historical_fill_bytes(&self) -> Option<u32> {
889        self.historical_fill_bytes
890    }
891
892    /// Borrow the stable historical-fill payload hash.
893    #[must_use]
894    pub fn historical_fill_hash(&self) -> Option<&str> {
895        self.historical_fill_hash.as_deref()
896    }
897}
898
899#[cfg_attr(
900    doc,
901    doc = "EntityIndexDescription\n\nOne index entry in a describe payload."
902)]
903#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
904#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
905pub struct EntityIndexDescription {
906    pub(crate) name: String,
907    pub(crate) unique: bool,
908    pub(crate) fields: Vec<String>,
909    pub(crate) origin: String,
910}
911
912impl EntityIndexDescription {
913    /// Construct one index description entry.
914    #[must_use]
915    pub const fn new(name: String, unique: bool, fields: Vec<String>, origin: String) -> Self {
916        Self {
917            name,
918            unique,
919            fields,
920            origin,
921        }
922    }
923
924    /// Borrow the index name.
925    #[must_use]
926    pub const fn name(&self) -> &str {
927        self.name.as_str()
928    }
929
930    /// Return whether the index enforces uniqueness.
931    #[must_use]
932    pub const fn unique(&self) -> bool {
933        self.unique
934    }
935
936    /// Borrow ordered index field names.
937    #[must_use]
938    pub const fn fields(&self) -> &[String] {
939        self.fields.as_slice()
940    }
941
942    /// Borrow the accepted index origin label.
943    #[must_use]
944    pub const fn origin(&self) -> &str {
945        self.origin.as_str()
946    }
947}
948
949#[cfg_attr(
950    doc,
951    doc = "EntityRelationDescription\n\nOne relation entry in a describe payload."
952)]
953#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
954#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
955pub struct EntityRelationDescription {
956    pub(crate) field: String,
957    pub(crate) target_path: String,
958    pub(crate) target_entity_name: String,
959    pub(crate) target_store_path: String,
960    pub(crate) cardinality: EntityRelationCardinality,
961}
962
963impl EntityRelationDescription {
964    /// Construct one relation description entry.
965    #[must_use]
966    pub const fn new(
967        field: String,
968        target_path: String,
969        target_entity_name: String,
970        target_store_path: String,
971        cardinality: EntityRelationCardinality,
972    ) -> Self {
973        Self {
974            field,
975            target_path,
976            target_entity_name,
977            target_store_path,
978            cardinality,
979        }
980    }
981
982    /// Borrow the source relation field name.
983    #[must_use]
984    pub const fn field(&self) -> &str {
985        self.field.as_str()
986    }
987
988    /// Borrow the relation target path.
989    #[must_use]
990    pub const fn target_path(&self) -> &str {
991        self.target_path.as_str()
992    }
993
994    /// Borrow the relation target entity name.
995    #[must_use]
996    pub const fn target_entity_name(&self) -> &str {
997        self.target_entity_name.as_str()
998    }
999
1000    /// Borrow the relation target store path.
1001    #[must_use]
1002    pub const fn target_store_path(&self) -> &str {
1003        self.target_store_path.as_str()
1004    }
1005
1006    /// Return relation cardinality.
1007    #[must_use]
1008    pub const fn cardinality(&self) -> EntityRelationCardinality {
1009        self.cardinality
1010    }
1011}
1012
1013#[cfg_attr(
1014    doc,
1015    doc = "EntityRelationCardinality\n\nDescribe source multiplicity: List includes list/map-value traversal; Set denotes set-only traversal."
1016)]
1017#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
1018#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
1019pub enum EntityRelationCardinality {
1020    Single,
1021    List,
1022    Set,
1023}
1024
1025/// Accepted identity and fingerprint metadata projected into one entity description.
1026pub(in crate::db) struct AcceptedEntityDescriptionMetadata {
1027    identity: Option<EntityIdentityDescription>,
1028    entity_tag: u64,
1029    accepted_schema_fingerprint_method: u8,
1030    accepted_schema_fingerprint: [u8; 16],
1031}
1032
1033impl AcceptedEntityDescriptionMetadata {
1034    /// Capture the accepted metadata that accompanies persisted schema authority.
1035    pub(in crate::db) const fn new(
1036        identity: Option<EntityIdentityDescription>,
1037        entity_tag: u64,
1038        accepted_schema_fingerprint_method: u8,
1039        accepted_schema_fingerprint: [u8; 16],
1040    ) -> Self {
1041        Self {
1042            identity,
1043            entity_tag,
1044            accepted_schema_fingerprint_method,
1045            accepted_schema_fingerprint,
1046        }
1047    }
1048}
1049
1050/// Build one entity-schema description solely from accepted persisted authority.
1051pub(in crate::db) fn describe_accepted_entity_with_persisted_schema(
1052    schema: &AcceptedSchemaSnapshot,
1053    value_catalog: &AcceptedValueCatalogHandle,
1054    validation_jobs: &[ConstraintValidationJob],
1055    metadata: AcceptedEntityDescriptionMetadata,
1056    resolve_relation_target: impl Fn(&str) -> Result<(String, String), InternalError>,
1057) -> Result<EntitySchemaDescription, InternalError> {
1058    describe_entity_with_persisted_schema(
1059        schema,
1060        value_catalog,
1061        validation_jobs,
1062        metadata,
1063        &resolve_relation_target,
1064    )
1065}
1066
1067fn describe_entity_with_persisted_schema(
1068    schema: &AcceptedSchemaSnapshot,
1069    value_catalog: &AcceptedValueCatalogHandle,
1070    validation_jobs: &[ConstraintValidationJob],
1071    metadata: AcceptedEntityDescriptionMetadata,
1072    resolve_relation_target: &impl Fn(&str) -> Result<(String, String), InternalError>,
1073) -> Result<EntitySchemaDescription, InternalError> {
1074    let row_layout = AcceptedRowLayoutRuntimeContract::from_accepted_schema(schema)?;
1075    let fields = describe_entity_fields_with_runtime_contract(schema, &row_layout, value_catalog)?;
1076    let primary_key_fields = schema.primary_key_field_names();
1077    if primary_key_fields.is_empty() {
1078        return Err(InternalError::store_invariant());
1079    }
1080    let primary_key_fields = primary_key_fields
1081        .into_iter()
1082        .map(str::to_string)
1083        .collect::<Vec<_>>();
1084    let primary_key = render_primary_key_fields(primary_key_fields.as_slice());
1085
1086    Ok(describe_entity_model_from_description_rows(
1087        schema.entity_path(),
1088        schema.entity_name(),
1089        metadata.entity_tag,
1090        metadata.accepted_schema_fingerprint_method,
1091        metadata.accepted_schema_fingerprint,
1092        primary_key.as_str(),
1093        primary_key_fields,
1094        fields,
1095        describe_entity_indexes_with_persisted_schema(schema),
1096        describe_entity_relations_with_persisted_schema(schema, resolve_relation_target)?,
1097        describe_entity_constraints_with_persisted_schema(schema, value_catalog, validation_jobs)?,
1098        row_layout.current_layout_version().get(),
1099        row_layout.history_floor().get(),
1100    )
1101    .with_identity(metadata.identity))
1102}
1103
1104// Assemble the common DESCRIBE payload once field rows have already been built.
1105// Callers project relation descriptions from the same authority as their field
1106// and index rows, so accepted DESCRIBE output does not fall back to generated
1107// relation metadata.
1108#[expect(
1109    clippy::too_many_arguments,
1110    reason = "one final schema DTO assembly keeps every already-owned section explicit"
1111)]
1112fn describe_entity_model_from_description_rows(
1113    entity_path: &str,
1114    entity_name: &str,
1115    entity_tag: u64,
1116    accepted_schema_fingerprint_method: u8,
1117    accepted_schema_fingerprint: [u8; 16],
1118    primary_key: &str,
1119    primary_key_fields: Vec<String>,
1120    fields: Vec<EntityFieldDescription>,
1121    indexes: Vec<EntityIndexDescription>,
1122    relations: Vec<EntityRelationDescription>,
1123    constraints: Vec<EntityConstraintDescription>,
1124    row_layout_current: u32,
1125    row_layout_history_floor: u32,
1126) -> EntitySchemaDescription {
1127    EntitySchemaDescription::new(
1128        entity_path.to_string(),
1129        entity_name.to_string(),
1130        entity_tag,
1131        accepted_schema_fingerprint_method,
1132        accepted_schema_fingerprint,
1133        primary_key.to_string(),
1134        primary_key_fields,
1135        fields,
1136        indexes,
1137        relations,
1138        constraints,
1139        row_layout_current,
1140        row_layout_history_floor,
1141    )
1142}
1143
1144/// Project ordered accepted and activating constraints, validating their live progress.
1145pub(in crate::db) fn describe_entity_constraints_with_persisted_schema(
1146    schema: &AcceptedSchemaSnapshot,
1147    value_catalog: &AcceptedValueCatalogHandle,
1148    validation_jobs: &[ConstraintValidationJob],
1149) -> Result<Vec<EntityConstraintDescription>, InternalError> {
1150    let snapshot = schema.persisted_snapshot();
1151    let mut descriptions = snapshot
1152        .constraints()
1153        .iter()
1154        .map(|constraint| describe_accepted_constraint(snapshot, value_catalog, constraint))
1155        .collect::<Result<Vec<_>, InternalError>>()?;
1156    descriptions.extend(
1157        snapshot
1158            .constraint_activations()
1159            .iter()
1160            .map(|activation| {
1161                let job = validation_jobs
1162                    .iter()
1163                    .find(|job| job.constraint_id() == activation.id());
1164                describe_constraint_activation(snapshot, value_catalog, activation, job)
1165            })
1166            .collect::<Result<Vec<_>, InternalError>>()?,
1167    );
1168    if validation_jobs.iter().any(|job| {
1169        !snapshot
1170            .constraint_activations()
1171            .iter()
1172            .any(|activation| activation.id() == job.constraint_id())
1173    }) {
1174        return Err(InternalError::store_invariant());
1175    }
1176    icydb_schema::compact_sort_unstable_by(&mut descriptions, |left, right| {
1177        (left.id(), left.validation_state() != "validated")
1178            .cmp(&(right.id(), right.validation_state() != "validated"))
1179    });
1180    Ok(descriptions)
1181}
1182
1183fn describe_accepted_constraint(
1184    snapshot: &PersistedSchemaSnapshot,
1185    value_catalog: &AcceptedValueCatalogHandle,
1186    constraint: &crate::db::schema::AcceptedConstraintSnapshot,
1187) -> Result<EntityConstraintDescription, InternalError> {
1188    let mut description = accepted_constraint_description(
1189        constraint.id().get(),
1190        constraint.name(),
1191        constraint.origin(),
1192    );
1193    match constraint.kind() {
1194        AcceptedConstraintKind::PrimaryKey => {
1195            description.kind = "primary_key".to_string();
1196            description.fields = snapshot
1197                .primary_key_field_ids()
1198                .iter()
1199                .map(|field_id| accepted_field_name(snapshot, *field_id))
1200                .collect::<Result<Vec<_>, _>>()?;
1201            description.semantics = "primary_key_v1".to_string();
1202        }
1203        AcceptedConstraintKind::NotNull { field_id } => {
1204            apply_not_null_description(&mut description, snapshot, *field_id)?;
1205        }
1206        AcceptedConstraintKind::Unique { index_id } => {
1207            let index = snapshot
1208                .indexes()
1209                .iter()
1210                .find(|index| index.schema_id() == *index_id)
1211                .ok_or_else(InternalError::store_invariant)?;
1212            apply_unique_index_description(&mut description, index, snapshot, value_catalog)?;
1213        }
1214        AcceptedConstraintKind::Relation { relation_id } => {
1215            let relation = snapshot
1216                .relations()
1217                .iter()
1218                .find(|relation| relation.id() == *relation_id)
1219                .ok_or_else(InternalError::store_invariant)?;
1220            apply_relation_description(&mut description, snapshot, relation)?;
1221        }
1222        AcceptedConstraintKind::Check { expression } => {
1223            apply_check_description(&mut description, snapshot, value_catalog, expression)?;
1224        }
1225        AcceptedConstraintKind::TargetedRule { target, operation } => {
1226            apply_targeted_rule_description(&mut description, snapshot, target, operation)?;
1227        }
1228    }
1229    Ok(description)
1230}
1231
1232fn describe_constraint_activation(
1233    snapshot: &PersistedSchemaSnapshot,
1234    value_catalog: &AcceptedValueCatalogHandle,
1235    activation: &ConstraintActivationSnapshot,
1236    validation_job: Option<&ConstraintValidationJob>,
1237) -> Result<EntityConstraintDescription, InternalError> {
1238    let mut description = accepted_constraint_description(
1239        activation.id().get(),
1240        activation.name(),
1241        activation.origin(),
1242    );
1243    match activation.state() {
1244        ConstraintActivationState::EnforcingNewWrites if validation_job.is_none() => {
1245            description.validation_state = "enforcing_new_writes".to_string();
1246        }
1247        ConstraintActivationState::Validating => {
1248            let job = validation_job.ok_or_else(InternalError::store_invariant)?;
1249            job.validate(Some(activation))?;
1250            description.validation_state = "validating".to_string();
1251            description.validation_progress =
1252                Some(ConstraintValidationProgressDescription::from_job(job));
1253        }
1254        ConstraintActivationState::EnforcingNewWrites => {
1255            return Err(InternalError::store_invariant());
1256        }
1257    }
1258    match activation.kind() {
1259        ConstraintActivationKind::NotNull { field_id } => {
1260            apply_not_null_description(&mut description, snapshot, *field_id)?;
1261        }
1262        ConstraintActivationKind::Unique { index_id } => {
1263            let index = snapshot
1264                .candidate_indexes()
1265                .iter()
1266                .find(|index| index.schema_id() == *index_id)
1267                .ok_or_else(InternalError::store_invariant)?;
1268            apply_unique_index_description(&mut description, index, snapshot, value_catalog)?;
1269        }
1270        ConstraintActivationKind::Relation { relation_id } => {
1271            let relation = snapshot
1272                .candidate_relations()
1273                .iter()
1274                .find(|relation| relation.id() == *relation_id)
1275                .ok_or_else(InternalError::store_invariant)?;
1276            apply_relation_description(&mut description, snapshot, relation)?;
1277        }
1278        ConstraintActivationKind::Check { expression } => {
1279            apply_check_description(&mut description, snapshot, value_catalog, expression)?;
1280        }
1281        ConstraintActivationKind::TargetedRule { target, operation } => {
1282            apply_targeted_rule_description(&mut description, snapshot, target, operation)?;
1283        }
1284    }
1285    Ok(description)
1286}
1287
1288fn accepted_constraint_description(
1289    id: u32,
1290    name: &str,
1291    origin: ConstraintOrigin,
1292) -> EntityConstraintDescription {
1293    EntityConstraintDescription {
1294        id,
1295        name: name.to_string(),
1296        kind: String::new(),
1297        origin: accepted_constraint_origin_label(origin).to_string(),
1298        validation_state: "validated".to_string(),
1299        validation_progress: None,
1300        field_id: None,
1301        index_id: None,
1302        relation_id: None,
1303        fields: Vec::new(),
1304        index: None,
1305        predicate_sql: None,
1306        relation: None,
1307        target_entity: None,
1308        action: None,
1309        semantics: String::new(),
1310        check_sql: None,
1311    }
1312}
1313
1314// Render constraint meaning once; callers retain accepted/candidate lookup and
1315// validation-state ownership.
1316fn apply_not_null_description(
1317    description: &mut EntityConstraintDescription,
1318    snapshot: &PersistedSchemaSnapshot,
1319    field_id: FieldId,
1320) -> Result<(), InternalError> {
1321    description.kind = "not_null".to_string();
1322    description.field_id = Some(field_id.get());
1323    description.fields = vec![accepted_field_name(snapshot, field_id)?];
1324    description.semantics = "not_null_v1".to_string();
1325    Ok(())
1326}
1327
1328fn apply_relation_description(
1329    description: &mut EntityConstraintDescription,
1330    snapshot: &PersistedSchemaSnapshot,
1331    relation: &PersistedRelationEdgeSnapshot,
1332) -> Result<(), InternalError> {
1333    description.kind = "relation".to_string();
1334    description.relation_id = Some(relation.id().get());
1335    description.fields = relation
1336        .source()
1337        .root_field_ids()
1338        .iter()
1339        .map(|field_id| accepted_field_name(snapshot, *field_id))
1340        .collect::<Result<Vec<_>, _>>()?;
1341    description.relation = Some(relation.name().to_string());
1342    description.target_entity = Some(relation.target_path().to_string());
1343    description.action = Some("restrict".to_string());
1344    description.semantics = "relation_pk_restrict_v1".to_string();
1345    Ok(())
1346}
1347
1348fn apply_check_description(
1349    description: &mut EntityConstraintDescription,
1350    snapshot: &PersistedSchemaSnapshot,
1351    value_catalog: &AcceptedValueCatalogHandle,
1352    expression: &AcceptedCheckExprV1,
1353) -> Result<(), InternalError> {
1354    description.kind = "check".to_string();
1355    description.fields = expression
1356        .dependencies()
1357        .into_iter()
1358        .map(|field_id| accepted_field_name(snapshot, field_id))
1359        .collect::<Result<Vec<_>, _>>()?;
1360    description.semantics = "check_expr_v1".to_string();
1361    description.check_sql = Some(render_accepted_check_expr_sql(
1362        expression,
1363        snapshot,
1364        value_catalog,
1365    )?);
1366    Ok(())
1367}
1368
1369fn apply_targeted_rule_description(
1370    description: &mut EntityConstraintDescription,
1371    snapshot: &PersistedSchemaSnapshot,
1372    target: &AcceptedRuleTarget,
1373    operation: &AcceptedRuleOperation,
1374) -> Result<(), InternalError> {
1375    description.kind = "targeted_rule".to_string();
1376    description.field_id = Some(target.root_field_id().get());
1377    description.fields = vec![accepted_field_name(snapshot, target.root_field_id())?];
1378    description.semantics = match operation {
1379        AcceptedRuleOperation::LengthRangeInclusive { .. } => "targeted_length_range_v1",
1380        AcceptedRuleOperation::MultipleOf { .. } => "targeted_multiple_of_v1",
1381        AcceptedRuleOperation::NumericMaximumInclusive { .. } => "targeted_numeric_maximum_v1",
1382        AcceptedRuleOperation::NumericMinimumInclusive { .. } => "targeted_numeric_minimum_v1",
1383        AcceptedRuleOperation::NumericRangeInclusive { .. } => "targeted_numeric_range_v1",
1384    }
1385    .to_string();
1386    Ok(())
1387}
1388
1389fn apply_unique_index_description(
1390    description: &mut EntityConstraintDescription,
1391    index: &PersistedIndexSnapshot,
1392    snapshot: &PersistedSchemaSnapshot,
1393    value_catalog: &AcceptedValueCatalogHandle,
1394) -> Result<(), InternalError> {
1395    description.kind = "unique".to_string();
1396    description.index_id = Some(index.schema_id().get());
1397    description.fields = describe_persisted_index_fields(index.key());
1398    description.index = Some(index.name().to_string());
1399    description.predicate_sql = index
1400        .predicate()
1401        .map(|predicate| predicate.render_sql(snapshot.fields(), value_catalog))
1402        .transpose()?;
1403    description.semantics = if index.predicate().is_some() {
1404        "partial_unique_index_v1"
1405    } else {
1406        "unique_index_v1"
1407    }
1408    .to_string();
1409    Ok(())
1410}
1411
1412const fn accepted_constraint_origin_label(origin: ConstraintOrigin) -> &'static str {
1413    match origin {
1414        ConstraintOrigin::Generated => "generated",
1415        ConstraintOrigin::SqlDdl => "sql_ddl",
1416    }
1417}
1418
1419fn accepted_field_name(
1420    snapshot: &crate::db::schema::PersistedSchemaSnapshot,
1421    field_id: FieldId,
1422) -> Result<String, InternalError> {
1423    snapshot
1424        .fields()
1425        .iter()
1426        .find(|field| field.id() == field_id)
1427        .map(|field| field.name().to_string())
1428        .ok_or_else(InternalError::store_invariant)
1429}
1430
1431fn render_primary_key_fields(fields: &[String]) -> String {
1432    fields.join(", ")
1433}
1434
1435fn describe_entity_indexes_with_persisted_schema(
1436    schema: &AcceptedSchemaSnapshot,
1437) -> Vec<EntityIndexDescription> {
1438    schema
1439        .persisted_snapshot()
1440        .indexes()
1441        .iter()
1442        .map(|index| {
1443            EntityIndexDescription::new(
1444                index.name().to_string(),
1445                index.unique(),
1446                describe_persisted_index_fields(index.key()),
1447                if index.generated() {
1448                    "generated".to_string()
1449                } else {
1450                    "ddl".to_string()
1451                },
1452            )
1453        })
1454        .collect()
1455}
1456
1457fn describe_persisted_index_fields(key: &PersistedIndexKeySnapshot) -> Vec<String> {
1458    key.items()
1459        .iter()
1460        .map(|item| match item {
1461            PersistedIndexKeyItemSnapshot::FieldPath(path) => path.path().join("."),
1462            PersistedIndexKeyItemSnapshot::Expression(expression) => {
1463                expression.canonical_text().to_string()
1464            }
1465        })
1466        .collect()
1467}
1468
1469/// Build the canonical compact SQL column projection from accepted authority.
1470pub(in crate::db) fn describe_compact_columns_with_persisted_schema(
1471    schema: &AcceptedSchemaSnapshot,
1472    value_catalog: &AcceptedValueCatalogHandle,
1473) -> Result<Vec<SqlColumnSummary>, InternalError> {
1474    let row_layout = AcceptedRowLayoutRuntimeContract::from_accepted_schema(schema)?;
1475    let snapshot = schema.persisted_snapshot();
1476    if snapshot.fields().len() != row_layout.fields().len()
1477        || snapshot.fields().len() > icydb_schema::MAX_FRAGMENT_FIELDS
1478    {
1479        return Err(InternalError::store_invariant());
1480    }
1481
1482    let capacity = compact_column_capacity(snapshot.fields())?;
1483    let mut accepted_fields = snapshot
1484        .fields()
1485        .iter()
1486        .zip(row_layout.fields())
1487        .collect::<Vec<_>>();
1488    icydb_schema::compact_sort_unstable_by(&mut accepted_fields, |left, right| {
1489        left.0.id().cmp(&right.0.id())
1490    });
1491    let mut columns = Vec::with_capacity(capacity);
1492    for (field, runtime_field) in accepted_fields {
1493        let matching_identity = field.id() == runtime_field.field_id();
1494        let matching_name = field.name() == runtime_field.name();
1495        if !matching_identity || !matching_name {
1496            return Err(InternalError::store_invariant());
1497        }
1498
1499        let generated = accepted_write_policy_generates(runtime_field);
1500        let relation = snapshot
1501            .relations()
1502            .iter()
1503            .any(|relation| relation.source().uses_root_field(field.id()));
1504        let extra = compact_column_extras(
1505            runtime_field.write_policy().insert_generation()
1506                == Some(FieldInsertGeneration::Identity),
1507            generated,
1508            relation,
1509        );
1510
1511        columns.push(SqlColumnSummary::new(
1512            field.name().to_string(),
1513            summarize_persisted_field_kind(field.kind(), value_catalog)?,
1514            field.nullable(),
1515            compact_column_key(snapshot, field.name()),
1516            compact_column_default(runtime_field, value_catalog)?,
1517            extra,
1518        )?);
1519
1520        let mut nested = field.nested_leaves().iter().collect::<Vec<_>>();
1521        icydb_schema::compact_sort_unstable_by(&mut nested, |left, right| {
1522            left.path().cmp(right.path())
1523        });
1524        for leaf in nested {
1525            let mut canonical_path = Vec::with_capacity(leaf.path().len().saturating_add(1));
1526            canonical_path.push(field.name());
1527            canonical_path.extend(leaf.path().iter().map(String::as_str));
1528            let canonical_name = canonical_path.join(".");
1529            columns.push(SqlColumnSummary::new(
1530                canonical_name.clone(),
1531                summarize_persisted_field_kind(leaf.kind(), value_catalog)?,
1532                nested_path_nullable(field.nullable(), field.nested_leaves(), leaf.path()),
1533                compact_column_key(snapshot, canonical_name.as_str()),
1534                SqlColumnDefault::NotApplicable,
1535                compact_column_extras(false, generated, false),
1536            )?);
1537        }
1538    }
1539
1540    if columns.len() != capacity {
1541        return Err(InternalError::store_invariant());
1542    }
1543    Ok(columns)
1544}
1545
1546fn compact_column_capacity(
1547    fields: &[crate::db::schema::PersistedFieldSnapshot],
1548) -> Result<usize, InternalError> {
1549    compact_column_capacity_from_counts(
1550        fields.len(),
1551        fields.iter().map(|field| field.nested_leaves().len()),
1552    )
1553}
1554
1555fn compact_column_capacity_from_counts(
1556    field_count: usize,
1557    nested_counts: impl IntoIterator<Item = usize>,
1558) -> Result<usize, InternalError> {
1559    if field_count > icydb_schema::MAX_FRAGMENT_FIELDS {
1560        return Err(InternalError::store_invariant());
1561    }
1562    let mut seen_fields = 0usize;
1563    let mut total = field_count;
1564    for nested_count in nested_counts {
1565        seen_fields = seen_fields
1566            .checked_add(1)
1567            .ok_or_else(InternalError::store_invariant)?;
1568        if nested_count > icydb_schema::MAX_FRAGMENT_FIELDS {
1569            return Err(InternalError::store_invariant());
1570        }
1571        total = total
1572            .checked_add(nested_count)
1573            .ok_or_else(InternalError::store_invariant)?;
1574    }
1575    if seen_fields != field_count {
1576        return Err(InternalError::store_invariant());
1577    }
1578    if total > MAX_SQL_COMPACT_COLUMN_ROWS {
1579        return Err(InternalError::store_invariant());
1580    }
1581    Ok(total)
1582}
1583
1584const fn accepted_write_policy_generates(field: &AcceptedRowLayoutRuntimeField<'_>) -> bool {
1585    let policy = field.write_policy();
1586    policy.insert_generation().is_some() || policy.write_management().is_some()
1587}
1588
1589fn compact_column_extras(identity: bool, generated: bool, relation: bool) -> Vec<SqlColumnExtra> {
1590    let mut extra = Vec::with_capacity(MAX_SQL_COLUMN_EXTRA_FLAGS);
1591    if identity {
1592        extra.push(SqlColumnExtra::Identity);
1593    }
1594    if generated {
1595        extra.push(SqlColumnExtra::Generated);
1596    }
1597    if relation {
1598        extra.push(SqlColumnExtra::Relation);
1599    }
1600    extra
1601}
1602
1603fn compact_column_default(
1604    field: &AcceptedRowLayoutRuntimeField<'_>,
1605    value_catalog: &AcceptedValueCatalogHandle,
1606) -> Result<SqlColumnDefault, InternalError> {
1607    if accepted_write_policy_generates(field) {
1608        return Ok(SqlColumnDefault::Auto);
1609    }
1610    match field.insert_omission_policy() {
1611        AcceptedInsertOmissionPolicy::NullIfMissing => Ok(SqlColumnDefault::Null),
1612        AcceptedInsertOmissionPolicy::DefaultIfMissing => {
1613            let payload = field
1614                .insert_default()
1615                .slot_payload()
1616                .ok_or_else(InternalError::store_invariant)?;
1617            let rendered = accepted_payload_facts(field, value_catalog, payload)?;
1618            Ok(SqlColumnDefault::Literal {
1619                text: rendered.value,
1620            })
1621        }
1622        AcceptedInsertOmissionPolicy::Required => Ok(SqlColumnDefault::Required),
1623    }
1624}
1625
1626fn nested_path_nullable(
1627    top_level_nullable: bool,
1628    leaves: &[PersistedNestedLeafSnapshot],
1629    path: &[String],
1630) -> bool {
1631    top_level_nullable
1632        || leaves.iter().any(|candidate| {
1633            candidate.path().len() <= path.len()
1634                && path.starts_with(candidate.path())
1635                && candidate.nullable()
1636        })
1637}
1638
1639fn compact_column_key(snapshot: &PersistedSchemaSnapshot, path: &str) -> SqlColumnKey {
1640    let top_level_field = snapshot.fields().iter().find(|field| field.name() == path);
1641    let primary =
1642        top_level_field.is_some_and(|field| snapshot.primary_key_field_ids().contains(&field.id()));
1643    let memberships = snapshot.indexes().iter().filter_map(|index| {
1644        let key_items = index.key().component_count();
1645        let exact_path_member = index
1646            .key()
1647            .field_paths()
1648            .any(|item| item.path().join(".") == path);
1649        if !exact_path_member {
1650            return None;
1651        }
1652        // A predicate limits uniqueness to its matching rows; it cannot supply
1653        // a whole-column UNI hint, even for a single-component key.
1654        Some((index.unique() && index.predicate().is_none(), key_items))
1655    });
1656    classify_compact_column_key(primary, memberships)
1657}
1658
1659fn classify_compact_column_key(
1660    primary: bool,
1661    memberships: impl IntoIterator<Item = (bool, usize)>,
1662) -> SqlColumnKey {
1663    if primary {
1664        return SqlColumnKey::Primary;
1665    }
1666    let mut multiple = false;
1667    for (unique, key_items) in memberships {
1668        if unique && key_items == 1 {
1669            return SqlColumnKey::Unique;
1670        }
1671        multiple = true;
1672    }
1673    if multiple {
1674        SqlColumnKey::Multiple
1675    } else {
1676        SqlColumnKey::None
1677    }
1678}
1679
1680#[cfg_attr(
1681    doc,
1682    doc = "Build field descriptors using accepted persisted schema slot metadata."
1683)]
1684#[cfg(any(test, feature = "sql"))]
1685pub(in crate::db) fn describe_entity_fields_with_persisted_schema(
1686    schema: &AcceptedSchemaSnapshot,
1687    value_catalog: &AcceptedValueCatalogHandle,
1688) -> Result<Vec<EntityFieldDescription>, InternalError> {
1689    let row_layout = AcceptedRowLayoutRuntimeContract::from_accepted_schema(schema)?;
1690    describe_entity_fields_with_runtime_contract(schema, &row_layout, value_catalog)
1691}
1692
1693fn describe_entity_fields_with_runtime_contract(
1694    schema: &AcceptedSchemaSnapshot,
1695    row_layout: &AcceptedRowLayoutRuntimeContract<'_>,
1696    value_catalog: &AcceptedValueCatalogHandle,
1697) -> Result<Vec<EntityFieldDescription>, InternalError> {
1698    let snapshot = schema.persisted_snapshot();
1699    if snapshot.fields().len() != row_layout.fields().len() {
1700        return Err(InternalError::store_invariant());
1701    }
1702    let mut fields = Vec::with_capacity(snapshot.fields().len());
1703
1704    // Accepted-schema describe surfaces must follow the stored schema payload,
1705    // not the generated model's current field order.
1706    for (field, runtime_field) in snapshot.fields().iter().zip(row_layout.fields()) {
1707        if field.id() != runtime_field.field_id() {
1708            return Err(InternalError::store_invariant());
1709        }
1710        let primary_key = snapshot.primary_key_field_ids().contains(&field.id());
1711        let slot = Some(runtime_field.slot().get());
1712        let metadata = DescribeFieldMetadata::new(
1713            summarize_persisted_field_kind(field.kind(), value_catalog)?,
1714            field.nullable(),
1715            query_field_is_queryable(field.kind(), value_catalog.composite_catalog()),
1716            field_origin_label(field.generated()),
1717        );
1718        let temporal = accepted_field_temporal_facts(runtime_field, value_catalog)?;
1719
1720        push_described_field_row(
1721            &mut fields,
1722            field.name(),
1723            slot,
1724            primary_key,
1725            None,
1726            metadata,
1727            temporal,
1728        );
1729
1730        if !field.nested_leaves().is_empty() {
1731            describe_persisted_nested_leaves(
1732                &mut fields,
1733                field.nested_leaves(),
1734                field_origin_label(field.generated()),
1735                value_catalog,
1736            )?;
1737        }
1738    }
1739
1740    Ok(fields)
1741}
1742
1743///
1744/// DescribeFieldMetadata
1745///
1746/// Field-description metadata selected before one field row is rendered.
1747///
1748
1749struct DescribeFieldMetadata {
1750    kind: String,
1751    nullable: bool,
1752    queryable: bool,
1753    origin: String,
1754}
1755
1756impl DescribeFieldMetadata {
1757    // Build one metadata bundle from already-rendered field facts.
1758    const fn new(kind: String, nullable: bool, queryable: bool, origin: String) -> Self {
1759        Self {
1760            kind,
1761            nullable,
1762            queryable,
1763            origin,
1764        }
1765    }
1766}
1767
1768// Add one already-resolved field row to the stable describe DTO list. The
1769// caller owns where metadata came from: generated model or accepted schema.
1770fn push_described_field_row(
1771    fields: &mut Vec<EntityFieldDescription>,
1772    name: &str,
1773    slot: Option<u16>,
1774    primary_key: bool,
1775    tree_prefix: Option<&'static str>,
1776    metadata: DescribeFieldMetadata,
1777    temporal: EntityFieldTemporalFacts,
1778) {
1779    // Nested field rows keep a compact tree marker so table-oriented describe
1780    // output scans as a hierarchy without assigning nested leaves row slots.
1781    let display_name = if let Some(prefix) = tree_prefix {
1782        format!("{prefix}{name}")
1783    } else {
1784        name.to_string()
1785    };
1786
1787    fields.push(EntityFieldDescription::new_with_temporal_facts(
1788        display_name,
1789        slot,
1790        primary_key,
1791        metadata,
1792        temporal,
1793    ));
1794}
1795
1796// Render accepted nested leaf descriptors. Nested leaves do not own physical
1797// row slots, so they always appear with the no-slot sentinel in the Wire DTO.
1798fn describe_persisted_nested_leaves(
1799    fields: &mut Vec<EntityFieldDescription>,
1800    nested_leaves: &[PersistedNestedLeafSnapshot],
1801    origin: String,
1802    value_catalog: &AcceptedValueCatalogHandle,
1803) -> Result<(), InternalError> {
1804    for (index, leaf) in nested_leaves.iter().enumerate() {
1805        let prefix = if index + 1 == nested_leaves.len() {
1806            "└─ "
1807        } else {
1808            "├─ "
1809        };
1810        let name = leaf.path().join(".");
1811        let metadata = DescribeFieldMetadata::new(
1812            summarize_persisted_field_kind(leaf.kind(), value_catalog)?,
1813            leaf.nullable(),
1814            query_field_is_queryable(leaf.kind(), value_catalog.composite_catalog()),
1815            origin.clone(),
1816        );
1817
1818        push_described_field_row(
1819            fields,
1820            &name,
1821            None,
1822            false,
1823            Some(prefix),
1824            metadata,
1825            EntityFieldTemporalFacts::nested(),
1826        );
1827    }
1828
1829    Ok(())
1830}
1831
1832fn field_origin_label(generated: bool) -> String {
1833    if generated {
1834        "generated".to_string()
1835    } else {
1836        "ddl".to_string()
1837    }
1838}
1839
1840pub(in crate::db) fn describe_entity_relations_with_persisted_schema(
1841    schema: &AcceptedSchemaSnapshot,
1842    resolve_target: &impl Fn(&str) -> Result<(String, String), InternalError>,
1843) -> Result<Vec<EntityRelationDescription>, InternalError> {
1844    let snapshot = schema.persisted_snapshot();
1845    if snapshot.relations().len() > icydb_schema::MAX_FRAGMENT_RELATIONS {
1846        return Err(InternalError::store_invariant());
1847    }
1848    snapshot
1849        .relations()
1850        .iter()
1851        .map(|relation| {
1852            let local_fields = relation
1853                .source()
1854                .root_field_ids()
1855                .iter()
1856                .map(|field_id| accepted_field_name(snapshot, *field_id))
1857                .collect::<Result<Vec<_>, _>>()?;
1858            let (target_entity_name, target_store_path) = resolve_target(relation.target_path())?;
1859
1860            Ok(EntityRelationDescription::new(
1861                render_primary_key_fields(local_fields.as_slice()),
1862                relation.target_path().to_string(),
1863                target_entity_name,
1864                target_store_path,
1865                persisted_relation_cardinality(snapshot, relation)?,
1866            ))
1867        })
1868        .collect()
1869}
1870
1871fn persisted_relation_cardinality(
1872    snapshot: &PersistedSchemaSnapshot,
1873    relation: &PersistedRelationEdgeSnapshot,
1874) -> Result<EntityRelationCardinality, InternalError> {
1875    let field_ids = match relation.source() {
1876        PersistedRelationSourceSnapshot::Direct { field_ids } => field_ids,
1877        PersistedRelationSourceSnapshot::Nested { steps, .. } => {
1878            // Flattened list/map values can repeat targets even inside a set.
1879            // Retain Set only when every collection step is a set traversal.
1880            let mut cardinality = EntityRelationCardinality::Single;
1881            for step in steps {
1882                match step {
1883                    PersistedRelationPathStepSnapshot::ListItems
1884                    | PersistedRelationPathStepSnapshot::MapValues => {
1885                        return Ok(EntityRelationCardinality::List);
1886                    }
1887                    PersistedRelationPathStepSnapshot::SetItems => {
1888                        cardinality = EntityRelationCardinality::Set;
1889                    }
1890                    _ => {}
1891                }
1892            }
1893            return Ok(cardinality);
1894        }
1895    };
1896    let [field_id] = field_ids.as_slice() else {
1897        return Ok(EntityRelationCardinality::Single);
1898    };
1899    let field = snapshot
1900        .fields()
1901        .iter()
1902        .find(|field| field.id() == *field_id)
1903        .ok_or_else(InternalError::store_invariant)?;
1904
1905    Ok(match field.kind() {
1906        AcceptedFieldKind::List(_) => EntityRelationCardinality::List,
1907        AcceptedFieldKind::Set(_) => EntityRelationCardinality::Set,
1908        _ => EntityRelationCardinality::Single,
1909    })
1910}
1911
1912fn write_accepted_composite_shape_summary(
1913    out: &mut String,
1914    shape: &AcceptedCompositeShape,
1915    value_catalog: &AcceptedValueCatalogHandle,
1916    depth: usize,
1917) -> Result<(), InternalError> {
1918    match shape {
1919        AcceptedCompositeShape::Record(fields) => {
1920            out.push_str("record{");
1921            for (index, field) in fields.iter().enumerate() {
1922                if index > 0 {
1923                    out.push_str(", ");
1924                }
1925                out.push_str(field.name());
1926                out.push(':');
1927                write_accepted_composite_element_summary(
1928                    out,
1929                    field.contract(),
1930                    value_catalog,
1931                    depth,
1932                )?;
1933            }
1934            out.push('}');
1935        }
1936        AcceptedCompositeShape::Tuple(elements) => {
1937            out.push_str("tuple<");
1938            for (index, element) in elements.iter().enumerate() {
1939                if index > 0 {
1940                    out.push_str(", ");
1941                }
1942                write_accepted_composite_element_summary(out, element, value_catalog, depth)?;
1943            }
1944            out.push('>');
1945        }
1946        AcceptedCompositeShape::Newtype(inner) => {
1947            out.push_str("newtype<");
1948            write_accepted_composite_element_summary(out, inner, value_catalog, depth)?;
1949            out.push('>');
1950        }
1951    }
1952
1953    Ok(())
1954}
1955
1956fn write_accepted_composite_element_summary(
1957    out: &mut String,
1958    element: &AcceptedCompositeElement,
1959    value_catalog: &AcceptedValueCatalogHandle,
1960    depth: usize,
1961) -> Result<(), InternalError> {
1962    write_persisted_field_kind_summary(out, element.kind(), value_catalog, depth)?;
1963    write_composite_nullability_summary(out, element.nullable());
1964    Ok(())
1965}
1966
1967fn write_composite_codec_summary(out: &mut String, codec: CompositeCodec) {
1968    match codec {
1969        CompositeCodec::StructuralV1 => out.push_str("structural_v1"),
1970    }
1971}
1972
1973fn write_composite_nullability_summary(out: &mut String, nullable: bool) {
1974    if nullable {
1975        out.push('?');
1976    }
1977}
1978
1979// Write the common text/blob describe label. Both generated and accepted schema
1980// summaries use this path so bounded and explicitly unbounded contracts stay
1981// visibly identical across `DESCRIBE` and `SHOW COLUMNS`.
1982fn write_length_bounded_field_kind_summary(
1983    out: &mut String,
1984    kind_name: &str,
1985    max_len: Option<u32>,
1986) {
1987    out.push_str(kind_name);
1988    if let Some(max_len) = max_len {
1989        out.push_str("(max_len=");
1990        out.push_str(&max_len.to_string());
1991        out.push(')');
1992    } else {
1993        out.push_str("(unbounded)");
1994    }
1995}
1996
1997///
1998/// RenderedTemporalPayload
1999///
2000/// One accepted temporal payload projected as an inseparable bounded value,
2001/// byte count, and stable diagnostic hash.
2002///
2003
2004struct RenderedTemporalPayload {
2005    value: String,
2006    bytes: u32,
2007    hash: String,
2008}
2009
2010fn accepted_field_temporal_facts(
2011    field: &AcceptedRowLayoutRuntimeField<'_>,
2012    value_catalog: &AcceptedValueCatalogHandle,
2013) -> Result<EntityFieldTemporalFacts, InternalError> {
2014    let write_policy = field.write_policy();
2015    let insert_omission = if write_policy.insert_generation().is_some() {
2016        "generated"
2017    } else if write_policy.write_management().is_some() {
2018        "managed"
2019    } else {
2020        match field.insert_omission_policy() {
2021            AcceptedInsertOmissionPolicy::NullIfMissing => "null",
2022            AcceptedInsertOmissionPolicy::DefaultIfMissing => "default",
2023            AcceptedInsertOmissionPolicy::Required => "required",
2024        }
2025    };
2026    let insert_default = field
2027        .insert_default()
2028        .slot_payload()
2029        .map(|payload| accepted_payload_facts(field, value_catalog, payload))
2030        .transpose()?;
2031    let (insert_default, insert_default_bytes, insert_default_hash) = match insert_default {
2032        Some(payload) => (Some(payload.value), Some(payload.bytes), Some(payload.hash)),
2033        None => (None, None, None),
2034    };
2035    let (historical_fill, historical_fill_bytes, historical_fill_hash) =
2036        match field.historical_fill() {
2037            SchemaHistoricalFill::Reject => (Some("reject".to_string()), None, None),
2038            SchemaHistoricalFill::Null => (Some("null".to_string()), None, None),
2039            SchemaHistoricalFill::SlotPayload(payload) => {
2040                let rendered = accepted_payload_facts(field, value_catalog, payload.as_slice())?;
2041                (
2042                    Some(rendered.value),
2043                    Some(rendered.bytes),
2044                    Some(rendered.hash),
2045                )
2046            }
2047        };
2048
2049    Ok(EntityFieldTemporalFacts {
2050        insert_omission: Some(insert_omission.to_string()),
2051        insert_default,
2052        insert_default_bytes,
2053        insert_default_hash,
2054        introduced_in_layout: Some(field.introduced_in_layout().get()),
2055        historical_fill,
2056        historical_fill_bytes,
2057        historical_fill_hash,
2058    })
2059}
2060
2061fn accepted_payload_facts(
2062    field: &AcceptedRowLayoutRuntimeField<'_>,
2063    value_catalog: &AcceptedValueCatalogHandle,
2064    payload: &[u8],
2065) -> Result<RenderedTemporalPayload, InternalError> {
2066    let persistence = AcceptedFieldPersistenceContract::new(value_catalog, field.decode_contract())
2067        .map_err(|_| InternalError::store_invariant())?;
2068    let admitted = decode_admitted_value_from_accepted_field_contract(persistence, payload)?;
2069    let output = output_value_from_runtime(value_catalog.enum_catalog(), admitted.into_value())
2070        .map_err(|_| InternalError::store_invariant())?;
2071    let hash = short_default_payload_fingerprint(payload);
2072    let rendered = bounded_schema_value_rendering(&output, payload, hash.as_str());
2073    let bytes = u32::try_from(payload.len()).map_err(|_| InternalError::store_invariant())?;
2074
2075    Ok(RenderedTemporalPayload {
2076        value: rendered,
2077        bytes,
2078        hash,
2079    })
2080}
2081
2082fn bounded_schema_value_rendering(value: &OutputValue, payload: &[u8], hash: &str) -> String {
2083    let rendered = match value.as_public() {
2084        crate::value::PublicValue::Text(value) => format!("'{}'", value.escape_default()),
2085        _ => render_output_value_text(value),
2086    };
2087    if rendered.len() <= MAX_SCHEMA_VALUE_RENDER_CHARS {
2088        return rendered;
2089    }
2090
2091    format!(
2092        "{}(bytes={}, sha256={})",
2093        output_value_kind_label(value),
2094        payload.len(),
2095        hash,
2096    )
2097}
2098
2099const fn output_value_kind_label(value: &OutputValue) -> &'static str {
2100    match value.as_public() {
2101        crate::value::PublicValue::Account(_) => "account",
2102        crate::value::PublicValue::Blob(_) => "blob",
2103        crate::value::PublicValue::Bool(_) => "bool",
2104        crate::value::PublicValue::Date(_) => "date",
2105        crate::value::PublicValue::Decimal(_) => "decimal",
2106        crate::value::PublicValue::Duration(_) => "duration",
2107        crate::value::PublicValue::Enum(_) => "enum",
2108        crate::value::PublicValue::Float32(_) => "float32",
2109        crate::value::PublicValue::Float64(_) => "float64",
2110        crate::value::PublicValue::Int64(_) => "int64",
2111        crate::value::PublicValue::Int128(_) => "int128",
2112        crate::value::PublicValue::List(_) => "list",
2113        crate::value::PublicValue::Map(_) => "map",
2114        crate::value::PublicValue::Null => "null",
2115        crate::value::PublicValue::Principal(_) => "principal",
2116        crate::value::PublicValue::Subaccount(_) => "subaccount",
2117        crate::value::PublicValue::Text(_) => "text",
2118        crate::value::PublicValue::Timestamp(_) => "timestamp",
2119        crate::value::PublicValue::Nat64(_) => "nat64",
2120        crate::value::PublicValue::Nat128(_) => "nat128",
2121        crate::value::PublicValue::Ulid(_) => "ulid",
2122        crate::value::PublicValue::Unit => "unit",
2123        crate::value::PublicValue::U256(_) => "u256",
2124    }
2125}
2126
2127fn short_default_payload_fingerprint(payload: &[u8]) -> String {
2128    let digest = Sha256::digest(payload);
2129    let mut out = String::with_capacity(16);
2130    for byte in &digest[..8] {
2131        let _ = write!(out, "{byte:02x}");
2132    }
2133    out
2134}
2135
2136#[cfg_attr(
2137    doc,
2138    doc = "Render one stable field-kind label from accepted persisted schema metadata."
2139)]
2140fn summarize_persisted_field_kind(
2141    kind: &AcceptedFieldKind,
2142    value_catalog: &AcceptedValueCatalogHandle,
2143) -> Result<String, InternalError> {
2144    let mut out = String::new();
2145    write_persisted_field_kind_summary(&mut out, kind, value_catalog, 0)?;
2146    if out.len() > MAX_SCHEMA_SNAPSHOT_BYTES as usize {
2147        return Err(InternalError::store_unsupported());
2148    }
2149
2150    Ok(out)
2151}
2152
2153// Stream the accepted persisted field-kind label in the stable public
2154// `DESCRIBE` format directly from live schema metadata.
2155fn write_persisted_field_kind_summary(
2156    out: &mut String,
2157    kind: &AcceptedFieldKind,
2158    value_catalog: &AcceptedValueCatalogHandle,
2159    depth: usize,
2160) -> Result<(), InternalError> {
2161    // Accepted type graphs may be cyclic or reuse subgraphs exponentially.
2162    // Bound descent and the shared output across sibling expansions.
2163    if depth >= MAX_ACCEPTED_RECURSIVE_DEPTH || out.len() > MAX_SCHEMA_SNAPSHOT_BYTES as usize {
2164        return Err(InternalError::store_unsupported());
2165    }
2166    if let Some(name) = describe_kind_name(kind) {
2167        out.push_str(name);
2168        return Ok(());
2169    }
2170
2171    match kind {
2172        AcceptedFieldKind::Blob { max_len } => {
2173            write_length_bounded_field_kind_summary(out, "blob", *max_len);
2174        }
2175        AcceptedFieldKind::Decimal { scale } => {
2176            let _ = write!(out, "decimal(scale={scale})");
2177        }
2178        AcceptedFieldKind::Enum { type_id } => {
2179            let definition = value_catalog
2180                .enum_catalog()
2181                .enum_type(*type_id)
2182                .ok_or_else(InternalError::store_invariant)?;
2183            out.push_str("enum(");
2184            out.push_str(definition.path());
2185            out.push(')');
2186        }
2187        AcceptedFieldKind::Text { max_len } => {
2188            write_length_bounded_field_kind_summary(out, "text", *max_len);
2189        }
2190        AcceptedFieldKind::Relation {
2191            target_entity_name,
2192            key_kind,
2193            ..
2194        } => {
2195            out.push_str("relation(target=");
2196            out.push_str(target_entity_name);
2197            out.push_str(", key=");
2198            write_persisted_field_kind_summary(out, key_kind, value_catalog, depth + 1)?;
2199            out.push(')');
2200        }
2201        AcceptedFieldKind::List(inner) => {
2202            out.push_str("list<");
2203            write_persisted_field_kind_summary(out, inner, value_catalog, depth + 1)?;
2204            out.push('>');
2205        }
2206        AcceptedFieldKind::Set(inner) => {
2207            out.push_str("set<");
2208            write_persisted_field_kind_summary(out, inner, value_catalog, depth + 1)?;
2209            out.push('>');
2210        }
2211        AcceptedFieldKind::Map { key, value } => {
2212            out.push_str("map<");
2213            write_persisted_field_kind_summary(out, key, value_catalog, depth + 1)?;
2214            out.push_str(", ");
2215            write_persisted_field_kind_summary(out, value, value_catalog, depth + 1)?;
2216            out.push('>');
2217        }
2218        AcceptedFieldKind::Composite { type_id } => {
2219            let composite_catalog = value_catalog.composite_catalog();
2220            let definition = composite_catalog
2221                .composite_type(*type_id)
2222                .ok_or_else(InternalError::store_invariant)?;
2223            out.push_str("composite(path=");
2224            out.push_str(definition.path());
2225            out.push_str(", codec=");
2226            write_composite_codec_summary(out, definition.codec());
2227            out.push_str(", shape=");
2228            write_accepted_composite_shape_summary(
2229                out,
2230                definition.shape(),
2231                value_catalog,
2232                depth + 1,
2233            )?;
2234            out.push(')');
2235        }
2236        AcceptedFieldKind::Account
2237        | AcceptedFieldKind::Bool
2238        | AcceptedFieldKind::Date
2239        | AcceptedFieldKind::Duration
2240        | AcceptedFieldKind::Float32
2241        | AcceptedFieldKind::Float64
2242        | AcceptedFieldKind::Int8
2243        | AcceptedFieldKind::Int16
2244        | AcceptedFieldKind::Int32
2245        | AcceptedFieldKind::Int64
2246        | AcceptedFieldKind::Int128
2247        | AcceptedFieldKind::Principal
2248        | AcceptedFieldKind::Subaccount
2249        | AcceptedFieldKind::Timestamp
2250        | AcceptedFieldKind::Nat8
2251        | AcceptedFieldKind::Nat16
2252        | AcceptedFieldKind::Nat32
2253        | AcceptedFieldKind::Nat64
2254        | AcceptedFieldKind::Nat128
2255        | AcceptedFieldKind::Ulid
2256        | AcceptedFieldKind::Unit
2257        | AcceptedFieldKind::U256 => return Err(InternalError::store_invariant()),
2258    }
2259
2260    Ok(())
2261}
2262
2263const fn describe_kind_name(kind: &AcceptedFieldKind) -> Option<&'static str> {
2264    Some(match kind {
2265        AcceptedFieldKind::Account => "account",
2266        AcceptedFieldKind::Bool => "bool",
2267        AcceptedFieldKind::Date => "date",
2268        AcceptedFieldKind::Duration => "duration",
2269        AcceptedFieldKind::Float32 => "float32",
2270        AcceptedFieldKind::Float64 => "float64",
2271        AcceptedFieldKind::Int8 => "int8",
2272        AcceptedFieldKind::Int16 => "int16",
2273        AcceptedFieldKind::Int32 => "int32",
2274        AcceptedFieldKind::Int64 => "int64",
2275        AcceptedFieldKind::Int128 => "int128",
2276        AcceptedFieldKind::Principal => "principal",
2277        AcceptedFieldKind::Subaccount => "subaccount",
2278        AcceptedFieldKind::Timestamp => "timestamp",
2279        AcceptedFieldKind::Nat8 => "nat8",
2280        AcceptedFieldKind::Nat16 => "nat16",
2281        AcceptedFieldKind::Nat32 => "nat32",
2282        AcceptedFieldKind::Nat64 => "nat64",
2283        AcceptedFieldKind::Nat128 => "nat128",
2284        AcceptedFieldKind::Ulid => "ulid",
2285        AcceptedFieldKind::Unit => "unit",
2286        AcceptedFieldKind::U256 => "u256",
2287        AcceptedFieldKind::Blob { .. }
2288        | AcceptedFieldKind::Decimal { .. }
2289        | AcceptedFieldKind::Enum { .. }
2290        | AcceptedFieldKind::Text { .. }
2291        | AcceptedFieldKind::Relation { .. }
2292        | AcceptedFieldKind::List(_)
2293        | AcceptedFieldKind::Set(_)
2294        | AcceptedFieldKind::Map { .. }
2295        | AcceptedFieldKind::Composite { .. } => return None,
2296    })
2297}
2298
2299//
2300// TESTS
2301//
2302
2303#[cfg(test)]
2304mod tests {
2305    use std::collections::BTreeMap;
2306
2307    use super::{
2308        EntityFieldDescription, EntityIdentityDescription, EntityRelationCardinality,
2309        EntityRelationDescription, MAX_SCHEMA_VALUE_RENDER_CHARS, SqlColumnDefault, SqlColumnExtra,
2310        SqlColumnKey, SqlColumnSummary, SqlDescribeOutput, SqlShowRelationsOutput,
2311        classify_compact_column_key, compact_column_capacity_from_counts, compact_column_extras,
2312        describe_accepted_constraint, describe_compact_columns_with_persisted_schema,
2313        describe_constraint_activation, describe_entity_constraints_with_persisted_schema,
2314        describe_entity_fields_with_persisted_schema,
2315        describe_entity_relations_with_persisted_schema, nested_path_nullable,
2316        persisted_relation_cardinality,
2317    };
2318    use crate::db::schema::{
2319        AcceptedCheckExprV1, AcceptedCheckValueExprV1, AcceptedCompositeCatalog,
2320        AcceptedConstraintCatalog, AcceptedConstraintKind, AcceptedConstraintSnapshot,
2321        AcceptedFieldKind, AcceptedIndexPredicate, AcceptedNamedTypeIdentity,
2322        AcceptedRuleOperation, AcceptedRuleTarget, AcceptedSchemaFingerprint,
2323        AcceptedSchemaRevision, AcceptedSchemaSnapshot, AcceptedValueCatalogHandle,
2324        CompositeFieldId, CompositeTypeId, ConstraintActivationKind, ConstraintActivationSnapshot,
2325        ConstraintActivationState, ConstraintId, ConstraintOrigin, ConstraintValidationJob,
2326        FieldId, FieldStorageDecode, LeafCodec, MAX_SCHEMA_SNAPSHOT_BYTES, PersistedFieldSnapshot,
2327        PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
2328        PersistedNestedLeafSnapshot, PersistedRelationEdgeSnapshot,
2329        PersistedRelationPathStepSnapshot, PersistedSchemaSnapshot, RelationId, ScalarCodec,
2330        SchemaFieldSlot, SchemaIndexId, SchemaInsertDefault, SchemaRowLayout, SchemaVersion,
2331        composite_catalog::{
2332            AcceptedCompositeElement, AcceptedCompositeField, AcceptedCompositeShape,
2333            decode_accepted_composite_catalog, encode_accepted_composite_catalog,
2334        },
2335        decode_persisted_schema_snapshot, empty_accepted_enum_catalog_for_tests,
2336        encode_persisted_schema_snapshot,
2337    };
2338
2339    const REACHABLE_COMPACT_REPLY_COMPOSITE_FIELDS: usize = icydb_schema::MAX_FRAGMENT_FIELDS;
2340    const REACHABLE_COMPACT_REPLY_TOP_LEVEL_COMPOSITES: usize = 95;
2341    const IC_QUERY_REPLY_BYTES: usize = 3 * 1024 * 1024;
2342
2343    #[test]
2344    fn field_kind_summaries_bound_cycles_and_reused_type_expansion() {
2345        for (levels, cycle) in [(1, false), (1, true), (20, false)] {
2346            let definitions = (1..=levels)
2347                .map(|level| {
2348                    let kind = if cycle {
2349                        AcceptedFieldKind::Composite {
2350                            type_id: CompositeTypeId::new(1).unwrap(),
2351                        }
2352                    } else if level == levels {
2353                        AcceptedFieldKind::Nat64
2354                    } else {
2355                        AcceptedFieldKind::Composite {
2356                            type_id: CompositeTypeId::new(level + 1).unwrap(),
2357                        }
2358                    };
2359                    (
2360                        CompositeTypeId::new(level).unwrap(),
2361                        (
2362                            format!("tests::T{level}"),
2363                            AcceptedCompositeShape::Tuple(vec![
2364                                AcceptedCompositeElement::new(kind.clone(), false),
2365                                AcceptedCompositeElement::new(kind, false),
2366                            ]),
2367                        ),
2368                    )
2369                })
2370                .collect();
2371            let enums = empty_accepted_enum_catalog_for_tests();
2372            let composites =
2373                AcceptedCompositeCatalog::from_initial_definitions(definitions, &enums).unwrap();
2374            let values = AcceptedValueCatalogHandle::new_for_tests(
2375                enums,
2376                composites,
2377                AcceptedSchemaRevision::INITIAL,
2378            );
2379            let kind = AcceptedFieldKind::Composite {
2380                type_id: CompositeTypeId::new(1).unwrap(),
2381            };
2382            let schema = summary_schema(kind.clone());
2383            for result in [
2384                describe_entity_fields_with_persisted_schema(&schema, &values).map(|_| ()),
2385                describe_compact_columns_with_persisted_schema(&schema, &values).map(|_| ()),
2386            ] {
2387                if levels == 1 && !cycle {
2388                    result.unwrap();
2389                } else {
2390                    assert_eq!(
2391                        result.unwrap_err().class(),
2392                        crate::error::ErrorClass::Unsupported
2393                    );
2394                }
2395            }
2396            let result = super::summarize_persisted_field_kind(&kind, &values);
2397            if levels == 1 && !cycle {
2398                assert_eq!(
2399                    result.unwrap(),
2400                    "composite(path=tests::T1, codec=structural_v1, shape=tuple<nat64, nat64>)"
2401                );
2402            } else {
2403                assert_eq!(
2404                    result.unwrap_err().class(),
2405                    crate::error::ErrorClass::Unsupported
2406                );
2407            }
2408        }
2409    }
2410
2411    fn summary_schema(kind: AcceptedFieldKind) -> AcceptedSchemaSnapshot {
2412        let (snapshot, _) = constraint_description_fixture();
2413        let field = PersistedFieldSnapshot::new_initial(
2414            FieldId::new(2),
2415            "value".into(),
2416            SchemaFieldSlot::new(1),
2417            kind,
2418            Vec::new(),
2419            false,
2420            SchemaInsertDefault::None,
2421            FieldStorageDecode::CatalogValue,
2422            LeafCodec::Structural,
2423        );
2424        let snapshot = snapshot
2425            .clone()
2426            .with_fields(vec![snapshot.fields()[0].clone(), field])
2427            .with_indexes(Vec::new());
2428        let constraints = AcceptedConstraintCatalog::initial(
2429            snapshot.fields(),
2430            snapshot.indexes(),
2431            snapshot.relations(),
2432        )
2433        .unwrap();
2434        AcceptedSchemaSnapshot::try_new(snapshot.with_constraint_catalog(constraints)).unwrap()
2435    }
2436
2437    fn constraint_description_fixture() -> (PersistedSchemaSnapshot, AcceptedValueCatalogHandle) {
2438        let snapshot = PersistedSchemaSnapshot::new_with_indexes(
2439            SchemaVersion::initial(),
2440            "tests::Account".to_string(),
2441            "Account".to_string(),
2442            FieldId::new(1),
2443            SchemaRowLayout::initial(vec![
2444                (FieldId::new(1), SchemaFieldSlot::new(0)),
2445                (FieldId::new(2), SchemaFieldSlot::new(1)),
2446            ]),
2447            vec![
2448                PersistedFieldSnapshot::new_initial(
2449                    FieldId::new(1),
2450                    "id".to_string(),
2451                    SchemaFieldSlot::new(0),
2452                    AcceptedFieldKind::Ulid,
2453                    Vec::new(),
2454                    false,
2455                    SchemaInsertDefault::None,
2456                    FieldStorageDecode::ByKind,
2457                    LeafCodec::Scalar(ScalarCodec::Ulid),
2458                ),
2459                PersistedFieldSnapshot::new_initial(
2460                    FieldId::new(2),
2461                    "email".to_string(),
2462                    SchemaFieldSlot::new(1),
2463                    AcceptedFieldKind::Text { max_len: None },
2464                    Vec::new(),
2465                    true,
2466                    SchemaInsertDefault::None,
2467                    FieldStorageDecode::ByKind,
2468                    LeafCodec::Scalar(ScalarCodec::Text),
2469                ),
2470            ],
2471            vec![PersistedIndexSnapshot::new(
2472                SchemaIndexId::new(1).expect("test index identity should be non-zero"),
2473                1,
2474                "account_email".to_string(),
2475                "tests::Account::account_email".to_string(),
2476                true,
2477                PersistedIndexKeySnapshot::from_field_paths(vec![
2478                    PersistedIndexFieldPathSnapshot::new(
2479                        FieldId::new(2),
2480                        SchemaFieldSlot::new(1),
2481                        vec!["email".to_string()],
2482                        AcceptedFieldKind::Text { max_len: None },
2483                        true,
2484                    ),
2485                ]),
2486                Some(crate::db::schema::AcceptedIndexPredicate::test_non_null(2)),
2487            )],
2488        );
2489        let catalog = AcceptedConstraintCatalog::initial(
2490            snapshot.fields(),
2491            snapshot.indexes(),
2492            snapshot.relations(),
2493        )
2494        .expect("fixture constraints should build");
2495        let snapshot = snapshot.with_constraint_catalog(catalog);
2496        let value_catalog = AcceptedValueCatalogHandle::new_for_tests(
2497            empty_accepted_enum_catalog_for_tests(),
2498            AcceptedCompositeCatalog::empty(),
2499            AcceptedSchemaRevision::INITIAL,
2500        );
2501        (snapshot, value_catalog)
2502    }
2503
2504    #[test]
2505    fn filtered_unique_constraint_description_exposes_partial_backing_contract() {
2506        let (snapshot, value_catalog) = constraint_description_fixture();
2507        let constraint = snapshot
2508            .constraints()
2509            .iter()
2510            .find(|constraint| constraint.name() == "account_email")
2511            .expect("unique constraint should exist");
2512
2513        let description = describe_accepted_constraint(&snapshot, &value_catalog, constraint)
2514            .expect("accepted unique constraint should describe");
2515        assert_eq!(description.index_id(), Some(1));
2516        assert_eq!(description.index(), Some("account_email"));
2517        assert_eq!(description.predicate_sql(), Some("email IS NOT NULL"));
2518        assert_eq!(description.semantics(), "partial_unique_index_v1");
2519    }
2520
2521    #[test]
2522    fn constraint_descriptions_preserve_live_progress_and_reject_unbound_jobs() {
2523        let (snapshot, values) = constraint_description_fixture();
2524        let catalog = snapshot
2525            .constraint_catalog()
2526            .clone()
2527            .with_added_check_activation(
2528                "pending_check".into(),
2529                ConstraintOrigin::SqlDdl,
2530                AcceptedCheckExprV1::True,
2531                AcceptedSchemaFingerprint::new([1; 32]),
2532                7,
2533            )
2534            .unwrap();
2535        let id = catalog.activations()[0].id();
2536        let catalog = catalog.with_validation_started(id).unwrap();
2537        let job = ConstraintValidationJob::start(
2538            crate::types::EntityTag::new(1),
2539            snapshot.entity_path().into(),
2540            &catalog.activations()[0],
2541            None,
2542        )
2543        .unwrap();
2544        let schema = AcceptedSchemaSnapshot::new(snapshot.clone().with_constraint_catalog(catalog));
2545        let descriptions = describe_entity_constraints_with_persisted_schema(
2546            &schema,
2547            &values,
2548            std::slice::from_ref(&job),
2549        )
2550        .unwrap();
2551        assert!(
2552            descriptions
2553                .windows(2)
2554                .all(|pair| pair[0].id() < pair[1].id())
2555        );
2556        let active = descriptions
2557            .iter()
2558            .find(|row| row.id() == id.get())
2559            .unwrap();
2560        assert_eq!(active.validation_state(), "validating");
2561        assert_eq!(active.validation_progress().unwrap().phase(), "forward");
2562        assert_eq!(active.validation_progress().unwrap().rows_scanned(), 0);
2563
2564        let missing_job =
2565            describe_entity_constraints_with_persisted_schema(&schema, &values, &[]).unwrap_err();
2566        assert_eq!(
2567            missing_job.diagnostic_code(),
2568            icydb_diagnostic_code::DiagnosticCode::StoreInvariantViolation,
2569        );
2570        let unbound_job = describe_entity_constraints_with_persisted_schema(
2571            &AcceptedSchemaSnapshot::new(snapshot),
2572            &values,
2573            &[job],
2574        )
2575        .unwrap_err();
2576        assert_eq!(
2577            unbound_job.diagnostic_code(),
2578            icydb_diagnostic_code::DiagnosticCode::StoreInvariantViolation,
2579        );
2580    }
2581
2582    #[test]
2583    fn row_local_constraint_descriptions_preserve_meaning_across_activation() {
2584        let (snapshot, values) = constraint_description_fixture();
2585        let field_id = FieldId::new(2);
2586        let expression = Box::new(AcceptedCheckExprV1::IsNotNull(
2587            AcceptedCheckValueExprV1::Field(field_id),
2588        ));
2589        let target = AcceptedRuleTarget::new(
2590            field_id,
2591            AcceptedNamedTypeIdentity::Composite(CompositeTypeId::new(1).unwrap()),
2592        );
2593        let operation = Box::new(AcceptedRuleOperation::LengthRangeInclusive { min: 1, max: 10 });
2594        for (accepted, activating, semantics) in [
2595            (
2596                AcceptedConstraintKind::NotNull { field_id },
2597                ConstraintActivationKind::NotNull { field_id },
2598                "not_null_v1",
2599            ),
2600            (
2601                AcceptedConstraintKind::Check {
2602                    expression: expression.clone(),
2603                },
2604                ConstraintActivationKind::Check { expression },
2605                "check_expr_v1",
2606            ),
2607            (
2608                AcceptedConstraintKind::TargetedRule {
2609                    target,
2610                    operation: operation.clone(),
2611                },
2612                ConstraintActivationKind::TargetedRule { target, operation },
2613                "targeted_length_range_v1",
2614            ),
2615        ] {
2616            assert_constraint_description_lifecycle(
2617                &snapshot, &values, accepted, activating, semantics,
2618            );
2619        }
2620    }
2621
2622    fn assert_constraint_description_lifecycle(
2623        snapshot: &PersistedSchemaSnapshot,
2624        values: &AcceptedValueCatalogHandle,
2625        accepted: AcceptedConstraintKind,
2626        activating: ConstraintActivationKind,
2627        semantics: &str,
2628    ) {
2629        let id = ConstraintId::new(10).unwrap();
2630        let constraint = AcceptedConstraintSnapshot::new(
2631            id,
2632            "rule".into(),
2633            ConstraintOrigin::Generated,
2634            accepted,
2635        );
2636        let mut expected = describe_accepted_constraint(snapshot, values, &constraint).unwrap();
2637        assert_eq!(expected.semantics(), semantics);
2638        assert_eq!(expected.fields(), &["email"]);
2639        assert_eq!(expected.validation_state(), "validated");
2640        for state in [
2641            ConstraintActivationState::EnforcingNewWrites,
2642            ConstraintActivationState::Validating,
2643        ] {
2644            let activation = ConstraintActivationSnapshot::new(
2645                id,
2646                "rule".into(),
2647                ConstraintOrigin::Generated,
2648                activating.clone(),
2649                state,
2650                AcceptedSchemaFingerprint::new([1; 32]),
2651                1,
2652            );
2653            let job = (state == ConstraintActivationState::Validating).then(|| {
2654                ConstraintValidationJob::start(
2655                    crate::types::EntityTag::new(1),
2656                    snapshot.entity_path().into(),
2657                    &activation,
2658                    None,
2659                )
2660                .unwrap()
2661            });
2662            if job.is_some() {
2663                assert!(
2664                    describe_constraint_activation(snapshot, values, &activation, None).is_err()
2665                );
2666            }
2667            let description =
2668                describe_constraint_activation(snapshot, values, &activation, job.as_ref())
2669                    .unwrap();
2670            assert_eq!(
2671                description.validation_state(),
2672                if job.is_some() {
2673                    "validating"
2674                } else {
2675                    "enforcing_new_writes"
2676                }
2677            );
2678            assert_eq!(description.validation_progress().is_some(), job.is_some());
2679            expected
2680                .validation_state
2681                .clone_from(&description.validation_state);
2682            expected
2683                .validation_progress
2684                .clone_from(&description.validation_progress);
2685            assert_eq!(description, expected);
2686        }
2687    }
2688
2689    #[test]
2690    fn relation_descriptions_use_the_owner_for_their_lifecycle_state() {
2691        let (snapshot, values) = constraint_description_fixture();
2692        let relation_id = RelationId::new(1).unwrap();
2693        let accepted = PersistedRelationEdgeSnapshot::new_direct(
2694            relation_id,
2695            "accepted_relation".into(),
2696            "AcceptedTarget".into(),
2697            vec![FieldId::new(1)],
2698        );
2699        let candidate = PersistedRelationEdgeSnapshot::new_direct(
2700            relation_id,
2701            "candidate_relation".into(),
2702            "CandidateTarget".into(),
2703            vec![FieldId::new(2)],
2704        );
2705        let snapshot = snapshot
2706            .with_relations(vec![accepted])
2707            .with_constraint_candidates(Vec::new(), vec![candidate]);
2708        let id = ConstraintId::new(10).unwrap();
2709        let constraint = AcceptedConstraintSnapshot::new(
2710            id,
2711            "relation".into(),
2712            ConstraintOrigin::SqlDdl,
2713            AcceptedConstraintKind::Relation { relation_id },
2714        );
2715        let activation = ConstraintActivationSnapshot::new(
2716            id,
2717            "relation".into(),
2718            ConstraintOrigin::SqlDdl,
2719            ConstraintActivationKind::Relation { relation_id },
2720            ConstraintActivationState::EnforcingNewWrites,
2721            AcceptedSchemaFingerprint::new([1; 32]),
2722            1,
2723        );
2724        let accepted = describe_accepted_constraint(&snapshot, &values, &constraint).unwrap();
2725        let candidate =
2726            describe_constraint_activation(&snapshot, &values, &activation, None).unwrap();
2727        assert_eq!(accepted.relation(), Some("accepted_relation"));
2728        assert_eq!(accepted.target_entity(), Some("AcceptedTarget"));
2729        assert_eq!(accepted.fields(), &["id"]);
2730        assert_eq!(candidate.relation(), Some("candidate_relation"));
2731        assert_eq!(candidate.target_entity(), Some("CandidateTarget"));
2732        assert_eq!(candidate.fields(), &["email"]);
2733        assert_eq!(candidate.action(), Some("restrict"));
2734        assert_eq!(candidate.semantics(), "relation_pk_restrict_v1");
2735    }
2736
2737    #[test]
2738    fn identity_description_reports_exact_remaining_capacity_and_exhaustion() {
2739        let available =
2740            EntityIdentityDescription::new("id".to_string(), "nat8".to_string(), 255, 254)
2741                .expect("in-domain Identity description should build");
2742        assert_eq!(available.minimum(), 1);
2743        assert_eq!(available.maximum(), 255);
2744        assert_eq!(available.high_water(), 254);
2745        assert_eq!(available.remaining(), 1);
2746        assert!(!available.exhausted());
2747
2748        let exhausted =
2749            EntityIdentityDescription::new("id".to_string(), "nat8".to_string(), 255, 255)
2750                .expect("exact-domain exhaustion should remain describable");
2751        assert_eq!(exhausted.remaining(), 0);
2752        assert!(exhausted.exhausted());
2753
2754        assert!(
2755            EntityIdentityDescription::new("id".to_string(), "nat8".to_string(), 255, 256).is_err(),
2756            "state beyond the accepted domain must not be described",
2757        );
2758    }
2759
2760    #[test]
2761    fn compact_key_contract_distinguishes_single_unique_from_compound_membership() {
2762        assert_eq!(
2763            classify_compact_column_key(true, [(true, 1), (false, 2)]),
2764            SqlColumnKey::Primary
2765        );
2766        assert_eq!(
2767            classify_compact_column_key(false, [(true, 2)]),
2768            SqlColumnKey::Multiple,
2769            "compound unique membership must not imply independent uniqueness",
2770        );
2771        assert_eq!(
2772            classify_compact_column_key(false, [(false, 1), (true, 1)]),
2773            SqlColumnKey::Unique,
2774            "single-field unique membership has precedence over non-unique membership",
2775        );
2776        assert_eq!(
2777            classify_compact_column_key(false, std::iter::empty()),
2778            SqlColumnKey::None
2779        );
2780    }
2781
2782    #[test]
2783    fn descriptive_column_keys_require_unconditional_uniqueness() {
2784        let (snapshot, values) = constraint_description_fixture();
2785        // An unconditional unique control needs a non-null source; nullable
2786        // unique admission deliberately requires an explicit omission guard.
2787        let email = PersistedFieldSnapshot::new_initial(
2788            FieldId::new(2),
2789            "email".into(),
2790            SchemaFieldSlot::new(1),
2791            AcceptedFieldKind::Text { max_len: None },
2792            Vec::new(),
2793            false,
2794            SchemaInsertDefault::None,
2795            FieldStorageDecode::ByKind,
2796            LeafCodec::Scalar(ScalarCodec::Text),
2797        );
2798        let snapshot = snapshot
2799            .clone()
2800            .with_fields(vec![snapshot.fields()[0].clone(), email]);
2801        let partial = snapshot.indexes()[0]
2802            .clone_with_top_level_field_nullability(FieldId::new(2), false)
2803            .clone_with_predicate_for_tests(Some(AcceptedIndexPredicate::bind_test_sql(
2804                "email <> 'excluded'",
2805                snapshot.fields(),
2806            )));
2807        let key = partial.key().clone();
2808        let unconditional = |ordinal| {
2809            PersistedIndexSnapshot::new(
2810                SchemaIndexId::new(2).unwrap(),
2811                ordinal,
2812                "account_all_email".into(),
2813                "tests::Account::account_all_email".into(),
2814                true,
2815                key.clone(),
2816                None,
2817            )
2818        };
2819        for (indexes, expected) in [
2820            (vec![partial.clone()], SqlColumnKey::Multiple),
2821            (vec![unconditional(1)], SqlColumnKey::Unique),
2822            (vec![partial, unconditional(2)], SqlColumnKey::Unique),
2823            (Vec::new(), SqlColumnKey::None),
2824        ] {
2825            let snapshot = snapshot.clone().with_indexes(indexes);
2826            let constraints = AcceptedConstraintCatalog::initial(
2827                snapshot.fields(),
2828                snapshot.indexes(),
2829                snapshot.relations(),
2830            )
2831            .unwrap();
2832            let schema = AcceptedSchemaSnapshot::try_new_with_acceptance(
2833                snapshot.with_constraint_catalog(constraints),
2834            )
2835            .unwrap();
2836            let columns = describe_compact_columns_with_persisted_schema(&schema, &values).unwrap();
2837            assert_eq!(columns[0].key(), SqlColumnKey::Primary);
2838            assert_eq!(columns[1].key(), expected);
2839        }
2840    }
2841
2842    // An accepted record with distinct address.city and city leaves lets the
2843    // compact and verbose projections qualify the same metadata authority.
2844    fn nested_description_fixture() -> (AcceptedSchemaSnapshot, AcceptedValueCatalogHandle) {
2845        let (snapshot, _) = constraint_description_fixture();
2846        let profile_id = CompositeTypeId::new(1).unwrap();
2847        let address_id = CompositeTypeId::new(2).unwrap();
2848        let text = AcceptedFieldKind::Text { max_len: None };
2849        let member = |id, name: &str, kind, nullable| {
2850            AcceptedCompositeField::new(
2851                CompositeFieldId::new(id).unwrap(),
2852                name.into(),
2853                AcceptedCompositeElement::new(kind, nullable),
2854            )
2855        };
2856        let enums = empty_accepted_enum_catalog_for_tests();
2857        let profile_shape = AcceptedCompositeShape::Record(vec![
2858            member(
2859                1,
2860                "address",
2861                AcceptedFieldKind::Composite {
2862                    type_id: address_id,
2863                },
2864                false,
2865            ),
2866            member(2, "city", text.clone(), true),
2867        ]);
2868        let address_shape =
2869            AcceptedCompositeShape::Record(vec![member(1, "city", text.clone(), false)]);
2870        let composites = AcceptedCompositeCatalog::from_initial_definitions(
2871            BTreeMap::from([
2872                (profile_id, ("tests::Profile".into(), profile_shape)),
2873                (address_id, ("tests::Address".into(), address_shape)),
2874            ]),
2875            &enums,
2876        )
2877        .unwrap();
2878        let values = AcceptedValueCatalogHandle::new_for_tests(
2879            enums,
2880            composites,
2881            AcceptedSchemaRevision::INITIAL,
2882        );
2883        let leaves = vec![
2884            PersistedNestedLeafSnapshot::new(
2885                vec!["address".into(), "city".into()],
2886                text.clone(),
2887                false,
2888            ),
2889            PersistedNestedLeafSnapshot::new(vec!["city".into()], text, true),
2890        ];
2891        let profile = PersistedFieldSnapshot::new_initial(
2892            FieldId::new(2),
2893            "profile".into(),
2894            SchemaFieldSlot::new(1),
2895            AcceptedFieldKind::Composite {
2896                type_id: profile_id,
2897            },
2898            leaves,
2899            false,
2900            SchemaInsertDefault::None,
2901            FieldStorageDecode::CatalogValue,
2902            LeafCodec::Structural,
2903        );
2904        let snapshot = snapshot
2905            .clone()
2906            .with_fields(vec![snapshot.fields()[0].clone(), profile])
2907            .with_indexes(Vec::new());
2908        let constraints = AcceptedConstraintCatalog::initial(
2909            snapshot.fields(),
2910            snapshot.indexes(),
2911            snapshot.relations(),
2912        )
2913        .unwrap();
2914        let schema =
2915            AcceptedSchemaSnapshot::try_new(snapshot.with_constraint_catalog(constraints)).unwrap();
2916
2917        (schema, values)
2918    }
2919
2920    #[test]
2921    fn descriptive_nested_names_retain_the_complete_relative_path() {
2922        let (schema, values) = nested_description_fixture();
2923        let fields = describe_entity_fields_with_persisted_schema(&schema, &values).unwrap();
2924        let columns = describe_compact_columns_with_persisted_schema(&schema, &values).unwrap();
2925        assert_eq!(fields[1].name(), "profile");
2926        for (index, display, canonical) in [
2927            (2, "├─ address.city", "profile.address.city"),
2928            (3, "└─ city", "profile.city"),
2929        ] {
2930            assert_eq!(fields[index].name(), display);
2931            assert_eq!(columns[index].name(), canonical);
2932            assert_eq!(fields[index].slot(), None);
2933            assert_eq!(fields[index].nullable(), columns[index].nullable());
2934        }
2935        assert!(!fields[2].nullable());
2936        assert!(fields[3].nullable());
2937    }
2938
2939    #[test]
2940    fn descriptive_nested_relations_report_collection_multiplicity() {
2941        use PersistedRelationPathStepSnapshot::{ListItems, MapValues, OptionalSome, SetItems};
2942        // Optional/scalar traversal stays single. Any sequence/map fanout is
2943        // list-like; a path with only set fanout retains the existing Set hint.
2944        for (steps, expected) in [
2945            (vec![OptionalSome], EntityRelationCardinality::Single),
2946            (
2947                vec![OptionalSome, ListItems],
2948                EntityRelationCardinality::List,
2949            ),
2950            (vec![SetItems], EntityRelationCardinality::Set),
2951            (vec![MapValues], EntityRelationCardinality::List),
2952            (vec![SetItems, ListItems], EntityRelationCardinality::List),
2953            (vec![ListItems, SetItems], EntityRelationCardinality::List),
2954            (vec![SetItems, SetItems], EntityRelationCardinality::Set),
2955            (vec![SetItems, MapValues], EntityRelationCardinality::List),
2956        ] {
2957            let kind = steps
2958                .iter()
2959                .rev()
2960                .fold(AcceptedFieldKind::Ulid, |kind, step| match step {
2961                    ListItems => AcceptedFieldKind::List(Box::new(kind)),
2962                    SetItems => AcceptedFieldKind::Set(Box::new(kind)),
2963                    MapValues => AcceptedFieldKind::Map {
2964                        key: Box::new(AcceptedFieldKind::Nat64),
2965                        value: Box::new(kind),
2966                    },
2967                    OptionalSome => kind,
2968                    _ => unreachable!("fixture contains only collection/optional traversal"),
2969                });
2970            let nullable = steps.first() == Some(&OptionalSome);
2971            let relation = PersistedRelationEdgeSnapshot::new_nested(
2972                RelationId::new(1).unwrap(),
2973                "nested_owner".into(),
2974                "tests::Owner".into(),
2975                FieldId::new(2),
2976                steps,
2977            );
2978            let schema = relation_description_fixture(kind, nullable, relation);
2979            let rows = describe_entity_relations_with_persisted_schema(&schema, &|_| {
2980                Ok(("Owner".into(), "tests::OwnerStore".into()))
2981            })
2982            .unwrap();
2983            assert_eq!(rows.len(), 1);
2984            assert_eq!(rows[0].cardinality(), expected);
2985        }
2986    }
2987
2988    // Keep relation projections rooted in an admitted source shape rather than
2989    // supplying collection steps over an unrelated scalar field.
2990    fn relation_description_fixture(
2991        kind: AcceptedFieldKind,
2992        nullable: bool,
2993        relation: PersistedRelationEdgeSnapshot,
2994    ) -> AcceptedSchemaSnapshot {
2995        let (snapshot, _) = constraint_description_fixture();
2996        let (storage, codec) = if kind == AcceptedFieldKind::Ulid {
2997            (
2998                FieldStorageDecode::ByKind,
2999                LeafCodec::Scalar(ScalarCodec::Ulid),
3000            )
3001        } else {
3002            (FieldStorageDecode::CatalogValue, LeafCodec::Structural)
3003        };
3004        let source = PersistedFieldSnapshot::new_initial(
3005            FieldId::new(2),
3006            "owner".into(),
3007            SchemaFieldSlot::new(1),
3008            kind,
3009            Vec::new(),
3010            nullable,
3011            SchemaInsertDefault::None,
3012            storage,
3013            codec,
3014        );
3015        let snapshot = snapshot
3016            .clone()
3017            .with_fields(vec![snapshot.fields()[0].clone(), source])
3018            .with_indexes(Vec::new())
3019            .with_relations(vec![relation]);
3020        let constraints = AcceptedConstraintCatalog::initial(
3021            snapshot.fields(),
3022            snapshot.indexes(),
3023            snapshot.relations(),
3024        )
3025        .unwrap();
3026        AcceptedSchemaSnapshot::try_new(snapshot.with_constraint_catalog(constraints)).unwrap()
3027    }
3028
3029    #[test]
3030    fn descriptive_direct_relations_preserve_scalar_tuple_list_and_set_hints() {
3031        for (kind, expected) in [
3032            (AcceptedFieldKind::Ulid, EntityRelationCardinality::Single),
3033            (
3034                AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Ulid)),
3035                EntityRelationCardinality::List,
3036            ),
3037            (
3038                AcceptedFieldKind::Set(Box::new(AcceptedFieldKind::Ulid)),
3039                EntityRelationCardinality::Set,
3040            ),
3041        ] {
3042            let relation = PersistedRelationEdgeSnapshot::new_direct(
3043                RelationId::new(1).unwrap(),
3044                "owner".into(),
3045                "tests::Owner".into(),
3046                vec![FieldId::new(2)],
3047            );
3048            let schema = relation_description_fixture(kind, false, relation);
3049            assert_eq!(
3050                persisted_relation_cardinality(
3051                    schema.persisted_snapshot(),
3052                    &schema.persisted_snapshot().relations()[0]
3053                )
3054                .unwrap(),
3055                expected
3056            );
3057        }
3058        let (snapshot, _) = constraint_description_fixture();
3059        let tuple = PersistedRelationEdgeSnapshot::new_direct(
3060            RelationId::new(1).unwrap(),
3061            "tuple_owner".into(),
3062            "tests::Owner".into(),
3063            vec![FieldId::new(1), FieldId::new(2)],
3064        );
3065        assert_eq!(
3066            persisted_relation_cardinality(&snapshot, &tuple).unwrap(),
3067            EntityRelationCardinality::Single
3068        );
3069    }
3070
3071    #[test]
3072    fn compact_extra_contract_is_closed_and_deterministically_ordered() {
3073        assert_eq!(
3074            compact_column_extras(true, true, true),
3075            vec![
3076                SqlColumnExtra::Identity,
3077                SqlColumnExtra::Generated,
3078                SqlColumnExtra::Relation,
3079            ]
3080        );
3081        assert_eq!(
3082            compact_column_extras(false, true, false),
3083            vec![SqlColumnExtra::Generated]
3084        );
3085        assert!(compact_column_extras(false, false, false).is_empty());
3086    }
3087
3088    #[test]
3089    fn compact_projection_bounds_accept_maximum_and_reject_max_plus_one() {
3090        assert_eq!(
3091            compact_column_capacity_from_counts(
3092                icydb_schema::MAX_FRAGMENT_FIELDS,
3093                std::iter::repeat_n(
3094                    icydb_schema::MAX_FRAGMENT_FIELDS,
3095                    icydb_schema::MAX_FRAGMENT_FIELDS,
3096                ),
3097            )
3098            .expect("accepted maximum should remain projectable"),
3099            super::MAX_SQL_COMPACT_COLUMN_ROWS,
3100        );
3101        assert!(
3102            compact_column_capacity_from_counts(
3103                icydb_schema::MAX_FRAGMENT_FIELDS + 1,
3104                std::iter::repeat_n(0, icydb_schema::MAX_FRAGMENT_FIELDS + 1),
3105            )
3106            .is_err()
3107        );
3108        assert!(
3109            compact_column_capacity_from_counts(1, [icydb_schema::MAX_FRAGMENT_FIELDS + 1],)
3110                .is_err()
3111        );
3112
3113        let valid = SqlColumnSummary::new(
3114            "value".to_string(),
3115            "text".to_string(),
3116            false,
3117            SqlColumnKey::None,
3118            SqlColumnDefault::Literal {
3119                text: "x".repeat(MAX_SCHEMA_VALUE_RENDER_CHARS),
3120            },
3121            vec![
3122                SqlColumnExtra::Identity,
3123                SqlColumnExtra::Generated,
3124                SqlColumnExtra::Relation,
3125            ],
3126        );
3127        let valid = valid.expect("the complete admitted compact row should remain valid");
3128        assert!(
3129            SqlColumnSummary::new(
3130                "value".to_string(),
3131                "text".to_string(),
3132                false,
3133                SqlColumnKey::None,
3134                SqlColumnDefault::Literal {
3135                    text: "x".repeat(MAX_SCHEMA_VALUE_RENDER_CHARS + 1),
3136                },
3137                Vec::new(),
3138            )
3139            .is_err()
3140        );
3141        assert!(
3142            SqlColumnSummary::new(
3143                "value".to_string(),
3144                "text".to_string(),
3145                false,
3146                SqlColumnKey::None,
3147                SqlColumnDefault::Required,
3148                vec![SqlColumnExtra::Generated; 4],
3149            )
3150            .is_err()
3151        );
3152
3153        let maximum = SqlDescribeOutput::Compact {
3154            entity: "AcceptedMaximum".to_string(),
3155            columns: vec![valid; super::MAX_SQL_COMPACT_COLUMN_ROWS],
3156        };
3157        assert!(matches!(
3158            icydb_schema::wire::encoded_len(&maximum),
3159            Err(icydb_schema::wire::Error::Limit),
3160        ));
3161        assert!(matches!(
3162            icydb_schema::wire::encode_one(&maximum),
3163            Err(icydb_schema::wire::Error::Limit),
3164        ));
3165
3166        let relation = EntityRelationDescription::new(
3167            "owner_id".to_string(),
3168            "entities::Owner".to_string(),
3169            "Owner".to_string(),
3170            "stores::Owner".to_string(),
3171            EntityRelationCardinality::Single,
3172        );
3173        assert!(
3174            SqlShowRelationsOutput::new(
3175                "Entry".to_string(),
3176                vec![relation.clone(); icydb_schema::MAX_FRAGMENT_RELATIONS],
3177            )
3178            .is_ok()
3179        );
3180        assert!(
3181            SqlShowRelationsOutput::new(
3182                "Entry".to_string(),
3183                vec![relation; icydb_schema::MAX_FRAGMENT_RELATIONS + 1],
3184            )
3185            .is_err()
3186        );
3187    }
3188
3189    #[test]
3190    fn reachable_accepted_compact_projection_exceeds_the_public_query_reply_limit() {
3191        let accepted = reachable_compact_reply_schema();
3192        let value_catalog = reachable_compact_reply_value_catalog();
3193        let columns = describe_compact_columns_with_persisted_schema(&accepted, &value_catalog)
3194            .expect("reachable accepted schema should project compact columns");
3195
3196        assert_eq!(
3197            columns.len(),
3198            1 + REACHABLE_COMPACT_REPLY_TOP_LEVEL_COMPOSITES
3199                * (1 + REACHABLE_COMPACT_REPLY_COMPOSITE_FIELDS),
3200        );
3201        let output = SqlDescribeOutput::Compact {
3202            entity: accepted.entity_name().to_string(),
3203            columns,
3204        };
3205        let encoded_len = icydb_schema::wire::encoded_len(&output)
3206            .expect("reachable accepted compact output should admit allocation-free Wire sizing");
3207        assert!(
3208            encoded_len > IC_QUERY_REPLY_BYTES,
3209            "a valid accepted schema must exercise the generated endpoint reply guard",
3210        );
3211        assert!(matches!(
3212            icydb_schema::wire::encode_one(&output),
3213            Err(icydb_schema::wire::Error::Limit),
3214        ));
3215    }
3216
3217    #[test]
3218    fn field_descriptions_preserve_root_and_nested_queryability() {
3219        let accepted = reachable_compact_reply_schema();
3220        let value_catalog = reachable_compact_reply_value_catalog();
3221        let fields = describe_entity_fields_with_persisted_schema(&accepted, &value_catalog)
3222            .expect("accepted root and leaf metadata should describe");
3223
3224        assert!(fields[0].queryable());
3225        let (groups, remainder) =
3226            fields[1..].as_chunks::<{ 1 + REACHABLE_COMPACT_REPLY_COMPOSITE_FIELDS }>();
3227        assert!(remainder.is_empty());
3228        assert_eq!(groups.len(), REACHABLE_COMPACT_REPLY_TOP_LEVEL_COMPOSITES);
3229        for group in groups {
3230            assert!(!group[0].queryable(), "record roots remain non-queryable");
3231            assert!(group[1..].iter().all(EntityFieldDescription::queryable));
3232        }
3233    }
3234
3235    #[test]
3236    fn nested_nullability_includes_nullable_ancestors() {
3237        let leaves = vec![
3238            PersistedNestedLeafSnapshot::new(
3239                vec!["address".to_string()],
3240                AcceptedFieldKind::Unit,
3241                true,
3242            ),
3243            PersistedNestedLeafSnapshot::new(
3244                vec!["address".to_string(), "city".to_string()],
3245                AcceptedFieldKind::Unit,
3246                false,
3247            ),
3248        ];
3249        assert!(nested_path_nullable(
3250            false,
3251            leaves.as_slice(),
3252            &["address".to_string(), "city".to_string()],
3253        ));
3254        assert!(nested_path_nullable(
3255            true,
3256            leaves.as_slice(),
3257            &["other".to_string()],
3258        ));
3259        assert!(!nested_path_nullable(
3260            false,
3261            leaves.as_slice(),
3262            &["other".to_string()],
3263        ));
3264    }
3265
3266    fn compact_reply_leaf_name(index: usize) -> String {
3267        let first = u8::try_from(index / 26).expect("bounded leaf prefix fits u8") + b'a';
3268        let second = u8::try_from(index % 26).expect("bounded leaf suffix fits u8") + b'a';
3269        String::from_utf8(vec![first, second]).expect("ASCII leaf name should be UTF-8")
3270    }
3271
3272    fn compact_reply_top_level_name(index: usize) -> String {
3273        let prefix = format!("field_{index:03}_");
3274        format!("{prefix}{}", "x".repeat(128 - prefix.len()))
3275    }
3276
3277    fn reachable_compact_reply_schema() -> AcceptedSchemaSnapshot {
3278        let composite_type_id = CompositeTypeId::new(1).expect("one is non-zero");
3279        let nested_leaves = (0..REACHABLE_COMPACT_REPLY_COMPOSITE_FIELDS)
3280            .map(|index| {
3281                PersistedNestedLeafSnapshot::new(
3282                    vec![compact_reply_leaf_name(index)],
3283                    AcceptedFieldKind::Unit,
3284                    false,
3285                )
3286            })
3287            .collect::<Vec<_>>();
3288        let mut fields = vec![PersistedFieldSnapshot::new_initial(
3289            FieldId::new(1),
3290            "id".to_string(),
3291            SchemaFieldSlot::new(0),
3292            AcceptedFieldKind::Nat64,
3293            Vec::new(),
3294            false,
3295            SchemaInsertDefault::None,
3296            FieldStorageDecode::ByKind,
3297            LeafCodec::Scalar(ScalarCodec::Nat64),
3298        )];
3299        let mut layout = vec![(FieldId::new(1), SchemaFieldSlot::new(0))];
3300        for index in 0..REACHABLE_COMPACT_REPLY_TOP_LEVEL_COMPOSITES {
3301            let raw_id = u32::try_from(index)
3302                .expect("bounded top-level index fits u32")
3303                .checked_add(2)
3304                .expect("bounded top-level identity has a successor");
3305            let raw_slot = u16::try_from(index)
3306                .expect("bounded top-level index fits u16")
3307                .checked_add(1)
3308                .expect("bounded top-level slot has a successor");
3309            let id = FieldId::new(raw_id);
3310            let slot = SchemaFieldSlot::new(raw_slot);
3311            fields.push(PersistedFieldSnapshot::new_initial(
3312                id,
3313                compact_reply_top_level_name(index),
3314                slot,
3315                AcceptedFieldKind::Composite {
3316                    type_id: composite_type_id,
3317                },
3318                nested_leaves.clone(),
3319                false,
3320                SchemaInsertDefault::None,
3321                FieldStorageDecode::ByKind,
3322                LeafCodec::Structural,
3323            ));
3324            layout.push((id, slot));
3325        }
3326        let persisted = PersistedSchemaSnapshot::new(
3327            SchemaVersion::initial(),
3328            "tests::ReachableCompactReply".to_string(),
3329            "ReachableCompactReply".to_string(),
3330            FieldId::new(1),
3331            SchemaRowLayout::initial(layout),
3332            fields,
3333        );
3334        let encoded = encode_persisted_schema_snapshot(&persisted)
3335            .expect("reachable compact-reply schema should fit its persisted payload limit");
3336        assert_eq!(encoded.len(), 310_865);
3337        assert!(encoded.len() <= MAX_SCHEMA_SNAPSHOT_BYTES as usize);
3338        AcceptedSchemaSnapshot::try_new(
3339            decode_persisted_schema_snapshot(encoded.as_slice())
3340                .expect("persisted compact-reply schema should decode"),
3341        )
3342        .expect("decoded compact-reply schema should satisfy accepted integrity")
3343    }
3344
3345    fn reachable_compact_reply_value_catalog() -> AcceptedValueCatalogHandle {
3346        let enum_catalog = empty_accepted_enum_catalog_for_tests();
3347        let composite_type_id = CompositeTypeId::new(1).expect("one is non-zero");
3348        let composite_fields = (0..REACHABLE_COMPACT_REPLY_COMPOSITE_FIELDS)
3349            .map(|index| {
3350                let raw_id = u32::try_from(index)
3351                    .expect("bounded composite index fits u32")
3352                    .checked_add(1)
3353                    .expect("bounded composite identity has a successor");
3354                AcceptedCompositeField::new(
3355                    CompositeFieldId::new(raw_id).expect("composite field identity is non-zero"),
3356                    compact_reply_leaf_name(index),
3357                    AcceptedCompositeElement::new(AcceptedFieldKind::Unit, false),
3358                )
3359            })
3360            .collect::<Vec<_>>();
3361        let composite_catalog = AcceptedCompositeCatalog::from_initial_definitions(
3362            BTreeMap::from([(
3363                composite_type_id,
3364                (
3365                    "tests::CompactReplyRecord".to_string(),
3366                    AcceptedCompositeShape::Record(composite_fields),
3367                ),
3368            )]),
3369            &enum_catalog,
3370        )
3371        .expect("bounded reusable record composite should admit");
3372        let encoded = encode_accepted_composite_catalog(&composite_catalog, &enum_catalog)
3373            .expect("reachable composite authority should fit its persisted payload limit");
3374        assert!(encoded.len() <= MAX_SCHEMA_SNAPSHOT_BYTES as usize);
3375        let composite_catalog = decode_accepted_composite_catalog(&encoded, &enum_catalog)
3376            .expect("persisted composite authority should decode");
3377        AcceptedValueCatalogHandle::new_for_tests(
3378            enum_catalog,
3379            composite_catalog,
3380            AcceptedSchemaRevision::INITIAL,
3381        )
3382    }
3383}