Skip to main content

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}