omnist/ops/signature.rs
1//! Field-signature helpers for schema minimization (and isomorphism).
2//! Ported from `~/dev/omnist/omnist/ops/signature.py`.
3//!
4//! [`local_signature`] is the target-blind structural key used as the
5//! *initial* partition for `minimize`'s partition refinement: a key
6//! including ref target names would be too strong a starting point --
7//! records that turn out to be equivalent because their ref targets are
8//! themselves equivalent-but-differently-named would never even land in
9//! the same starting block. It captures a field's label, cardinality, and
10//! scalar-or-ref *shape*, but excludes ref target names (those are compared
11//! by evolving block id during `minimize`'s refinement instead).
12
13use crate::schema::{FieldType, Record, ScalarKind};
14
15/// A field's target-blind shape: `Scalar(kind, nullable)`, `Ref` (the
16/// target record's name is deliberately excluded -- see the module doc
17/// comment), or `Any`.
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum ShapeKey {
20 /// A scalar shape with kind and nullability.
21 Scalar(ScalarKind, bool),
22 /// A reference to a record in the environment.
23 Ref,
24 /// An `any` type slot.
25 Any,
26}
27
28/// One field's signature entry: `(label, min, max, shape)`.
29pub type FieldKey = (String, usize, Option<usize>, ShapeKey);
30
31/// A record's target-blind structural key: every field's [`FieldKey`],
32/// sorted by label.
33pub type LocalSignature = Vec<FieldKey>;
34
35/// Target-blind structural key for a record: fields sorted by label, each
36/// keyed by `(label, min, max, shape)`.
37///
38/// Fields are sorted by label rather than kept in declaration order:
39/// validation ignores field order (a `Record` is a *set* of labeled fields),
40/// so two records that declare the same fields in a different order accept
41/// exactly the same documents and MUST land in the same initial partition
42/// block -- keying by declaration order would incorrectly split them.
43pub fn local_signature(rec: &Record) -> LocalSignature {
44 let mut fields: LocalSignature = rec
45 .fields()
46 .iter()
47 .map(|f| {
48 let shape = match &f.ty {
49 FieldType::Scalar(s) => ShapeKey::Scalar(s.kind(), s.is_nullable()),
50 FieldType::Ref(_) => ShapeKey::Ref,
51 FieldType::Any => ShapeKey::Any,
52 };
53 (f.label.clone(), f.min, f.max, shape)
54 })
55 .collect();
56 // `Record::new` already rejects duplicate labels, so this sort can never
57 // need a tie-breaker -- codepoint order via `String`'s default `Ord`
58 // (byte-wise on valid UTF-8, which coincides with codepoint order),
59 // never a locale-aware comparison (see the omnist-ts#56 regression test
60 // in `tests.rs`).
61 fields.sort_by(|a, b| a.0.cmp(&b.0));
62 fields
63}