On this page
Package enc is pgdesign's canonical per-object encoder, mapping each resolved model object to canonical JSON bytes so that object identity equals its hash.
#internal/enc
#internal/enc
Package enc is pgdesign's canonical per-object encoder: it maps each resolved model object to canonical JSON bytes, and decodes those bytes back to the object. It is the code form of law L1 (one canonical form): two models that are ≈_syn (equal under the structural sublanguage defined by the order-semantics table) encode to identical bytes, so id = hash(enc(x)) is a content identity.
Scope (roadmap kernel 1.1): this package delivers the PER-OBJECT encoder and the manifest-key machinery groundwork. The whole-model form, the envelope, and the single serializer are roadmap 1.5 and deliberately NOT built here.
Design:
- Every top-level encoded form carries a CODEC VERSION (epoch) field and a self-describing "kind" field. Ids are epoch-relative (L2): a change to enc or N re-keys the world, so the codec version travels with the bytes. - Encoding goes through DEDICATED form structs (the *Form types), never the model structs directly. This makes the canonical byte order a property of the encoder, independent of the model struct field order: a field-order refactor of a model struct cannot shift identity. - Per-field presence semantics distinguish unset from zero. Model fields that are already pointers (defaults, statistics, cost/rows, sequence bounds) stay pointers in the form; nil is omitted, a non-nil pointer to a zero value is preserved. - Map-typed fields (index opclasses/collations/with, schema groups, transition Requires, state-machine transition maps) are emitted as JSON objects. encoding/json sorts object keys, which is the deliberate, stable key-ordering mechanism; set-valued leaf slices are sorted by the encoder before emission. - The exclusion allowlist (see policy.go) records, for every DDL-reaching model struct and every registry-snapshot struct, which exported fields are encoded and which are excluded WITH A REASON. The reflection-based totality guard (policy_test.go) turns red the moment a new field is added without being classified.
The order-semantics table — exhaustive over the Model, classifying each collection's collection-order and intra-object order as SEMANTIC or CANONICAL-ONLY — is committed alongside this package in ORDER_SEMANTICS.md. That table IS the definition of ≈_syn on the structural sublanguage.
enc is pure kernel: it imports only model, semtype, typeinfo, fd, and the standard library. It never imports migrate, introspect, serve, or cmd.
#CodecVersion
const CodecVersion = 1CodecVersion is the codec epoch stamped into every encoded form. Ids are epoch-relative: a deliberate change to enc's field policy, ordering, or the normalizer it will eventually consume (roadmap 1.2) bumps this constant and re-keys the store. Such bumps are rare, deliberate breaking-major events (L2).
#KindSchemaMeta
const KindSchemaMeta Kind = "schema" // the schema-global header (name, extensions, pg_version, groups)#KindTable
const KindTable Kind = "table" //#KindView
const KindView Kind = "view" //#KindMatView
const KindMatView Kind = "matview" //#KindSequence
const KindSequence Kind = "sequence" //#KindFunction
const KindFunction Kind = "function" //#KindEnum
const KindEnum Kind = "enum" //#KindDomain
const KindDomain Kind = "domain" //#KindComposite
const KindComposite Kind = "composite" //#KindSMType
const KindSMType Kind = "sm_type" // a state-machine type definition (identity carrier)#KindRegistrySnap
const KindRegistrySnap Kind = "registry" // the semtype registry snapshot (import-surface residue channel; empty for identity)#KindDML
const KindDML Kind = "dml" // pseudo-target for data-manipulation ops (backfill/transform)KindDML and KindRaw are PSEUDO-TARGET kinds for data/opaque-SQL migration ops (roadmap 5.1, edge_format.md TENSION 2). They never name a schema object: a DML op changes rows and a RawSQL op is opaque, so neither resolves in a manifest. The grammar is PINNED by the edge format:
Key{Kind:"dml", Name:"
where
#KindRaw
const KindRaw Kind = "raw" // pseudo-target for opaque RawSQL bodies (SM triggers, partman config)#SchemaMeta
type SchemaMeta structSchemaMeta is the decoded schema-global header. It is a distinct type from model.Schema because a header carries only the schema-global fields, not the per-object collections.
#Kind
type Kind stringKind is the object kind of a manifest key. Kind-qualification is what keeps a table named x and a function named x from ever colliding: their keys differ in Kind even when schema and name coincide.
#Key
type Key structKey is a kind-qualified manifest key: (kind, schema, name) plus, for functions, the argument signature so overloads are distinct entries. A manifest (roadmap 1.4) is a sorted map of Key -> object-id; this type and its construction and collision behavior live here, in the encoder kernel, so 1.4 can build the manifest on top without re-deriving key identity.
#DecodeTable
func DecodeTable(data []byte) (model.Table, error)DecodeTable decodes canonical bytes into a table.
#DecodeView
func DecodeView(data []byte) (model.View, error)DecodeView decodes canonical bytes into a view.
#DecodeMaterializedView
func DecodeMaterializedView(data []byte) (model.MaterializedView, error)DecodeMaterializedView decodes canonical bytes into a materialized view.
#DecodeSequence
func DecodeSequence(data []byte) (model.Sequence, error)DecodeSequence decodes canonical bytes into a sequence.
#DecodeFunction
func DecodeFunction(data []byte) (model.Function, error)DecodeFunction decodes canonical bytes into a function.
#DecodeEnum
func DecodeEnum(data []byte) (model.Enum, error)DecodeEnum decodes canonical bytes into an enum type.
#DecodeDomain
func DecodeDomain(data []byte) (model.Domain, error)DecodeDomain decodes canonical bytes into a domain type.
#DecodeCompositeType
func DecodeCompositeType(data []byte) (model.CompositeType, error)DecodeCompositeType decodes canonical bytes into a composite type.
#DecodeStateMachine
func DecodeStateMachine(data []byte) (model.StateMachine, error)DecodeStateMachine decodes canonical bytes into a state-machine type definition.
#DecodeSchemaMeta
func DecodeSchemaMeta(data []byte) (SchemaMeta, error)DecodeSchemaMeta decodes canonical bytes into the schema-global header.
#EncodeTable
func EncodeTable(t model.Table) ([]byte, error) { return canonicalJSON(tableToForm(t)) }EncodeTable returns the canonical bytes for a single table.
#EncodeView
func EncodeView(v model.View) ([]byte, error) { return canonicalJSON(viewToForm(v)) }EncodeView returns the canonical bytes for a view.
#EncodeMaterializedView
func EncodeMaterializedView(mv model.MaterializedView) ([]byte, error)EncodeMaterializedView returns the canonical bytes for a materialized view.
#EncodeSequence
func EncodeSequence(s model.Sequence) ([]byte, error) { return canonicalJSON(sequenceToForm(s)) }EncodeSequence returns the canonical bytes for a standalone sequence.
#EncodeFunction
func EncodeFunction(fn model.Function) ([]byte, error) { return canonicalJSON(functionToForm(fn)) }EncodeFunction returns the canonical bytes for a function.
#EncodeEnum
func EncodeEnum(e model.Enum) ([]byte, error) { return canonicalJSON(enumToForm(e)) }EncodeEnum returns the canonical bytes for an enum type.
#EncodeDomain
func EncodeDomain(d model.Domain) ([]byte, error) { return canonicalJSON(domainToForm(d)) }EncodeDomain returns the canonical bytes for a domain type.
#EncodeCompositeType
func EncodeCompositeType(c model.CompositeType) ([]byte, error)EncodeCompositeType returns the canonical bytes for a composite type.
#EncodeStateMachine
func EncodeStateMachine(sm model.StateMachine) ([]byte, error)EncodeStateMachine returns the canonical bytes for a state-machine type definition (the identity carrier for SM types).
#EncodeSchemaMeta
func EncodeSchemaMeta(s *model.Schema) ([]byte, error)EncodeSchemaMeta returns the canonical bytes for the schema-global header: name, extensions (canonical order), groups, and pg_version. The per-object collections (tables, views, types, ...) are encoded separately.
#FunctionArgSig
func FunctionArgSig(args []model.FunctionArg) stringFunctionArgSig builds the canonical argument-type signature for a function from its resolved args, in positional order. Argument names and defaults are NOT part of the signature — PostgreSQL overload resolution keys on argument TYPES alone, so the signature must too.
#KeyForTable
func KeyForTable(t model.Table) KeyKeyForTable returns the manifest key for a table.
#KeyForView
func KeyForView(v model.View) KeyKeyForView returns the manifest key for a view.
#KeyForMatView
func KeyForMatView(mv model.MaterializedView) KeyKeyForMatView returns the manifest key for a materialized view.
#KeyForSequence
func KeyForSequence(s model.Sequence) KeyKeyForSequence returns the manifest key for a standalone sequence.
#KeyForFunction
func KeyForFunction(f model.Function) KeyKeyForFunction returns the manifest key for a function, including its argument signature so overloads are distinct.
#KeyForEnum
func KeyForEnum(e model.Enum) KeyKeyForEnum returns the manifest key for an enum type.
#KeyForDomain
func KeyForDomain(d model.Domain) KeyKeyForDomain returns the manifest key for a domain type.
#KeyForComposite
func KeyForComposite(c model.CompositeType) KeyKeyForComposite returns the manifest key for a composite type.
#KeyForStateMachine
func KeyForStateMachine(sm model.StateMachine) KeyKeyForStateMachine returns the manifest key for a state-machine type.
#ParseKey
func ParseKey(s string) (Key, error)ParseKey reconstructs a Key from its String() form — the inverse used by the on-disk revision manifest (roadmap 5.2, store_layout.md), whose entries are a sorted map keyed by Key.String(). It handles every OBJECT kind (the kinds that appear in a manifest); the pseudo-target kinds dml/raw NEVER appear in a manifest, so ParseKey rejects them as a hard error rather than silently admitting a data op into the schema-object namespace.
Grammar (mirrors String):
schema:name -> KindSchemaMeta registry: -> KindRegistrySnap function:schema.name(args) -> KindFunction (ArgSig = "(args)") kind:schema.name -> other kinds, schema-qualified kind:name -> other kinds, no schema
Object names are unquoted identifiers with no '.', so the qualifier splits on the sole '.' when present. ParseKey round-trips every KeyFor* construction: ParseKey(k.String()) == k for object kinds.
#KeyForDML
func KeyForDML(seq int) Key { return Key{Kind: KindDML, Name: strconv.Itoa(seq)} }KeyForDML mints the PINNED pseudo-target key for a data-manipulation op at the given zero-based edge sequence. It renders "dml:
#KeyForRaw
func KeyForRaw(seq int) Key { return Key{Kind: KindRaw, Name: strconv.Itoa(seq)} }KeyForRaw mints the PINNED pseudo-target key for an opaque RawSQL op at the given zero-based edge sequence. It renders "raw:
#EncodeObjects
func EncodeObjects(s *model.Schema) (map[Key][]byte, error)EncodeObjects encodes every object of a schema to its canonical bytes, keyed by kind-qualified manifest key. This is the per-object encoder surface a manifest (roadmap 1.4) is built on: a manifest is a sorted map Key -> hash(bytes). It is NOT the whole-model serializer — there is no preamble, envelope, or concatenation here; that is roadmap 1.5.
The schema-global header is included under a KindSchemaMeta key so that EncodeObjects / DecodeObjects round-trips the whole model. State-machine type definitions are first-class objects here (KindSMType) — they carry the full transition graph with comments and are the identity carrier for SM types. The registry snapshot is NOT an identity input (see snapshot.go): all identity-bearing registry state now has a model home, so the snapshot is empty for identity.
#PeekKind
func PeekKind(b []byte) (Kind, error)PeekKind reads the self-describing "kind" field from a per-object canonical form without fully decoding it. It is what lets a whole-model form (roadmap 1.5) — an ordered concatenation of per-object forms carrying no external keys — route each form to the right decoder: the manifest key is not needed for decoding, only the kind, and the kind travels inside the bytes.
#DecodeObject
func DecodeObject(s *model.Schema, b []byte) errorDecodeObject decodes a single per-object canonical form and merges it into s. The schema-global header (KindSchemaMeta) sets the schema-level fields; every other kind appends to its collection. It is the shared per-object decode dispatch used by both DecodeObjects (keyed map) and the whole-model form decoder in roadmap 1.5 (keyless ordered concatenation). DecodeObject does NOT Canonicalize — the caller does that once after all objects are merged.
#DecodeObjects
func DecodeObjects(objs map[Key][]byte) (*model.Schema, error)DecodeObjects reconstructs a schema from a per-object encoding produced by EncodeObjects, then Canonicalizes it (rebuilding the derived caches and canonical ordering the encoding deliberately omits). Together with EncodeObjects it realizes decode∘enc = id on canonicalized models: encoding a canonical schema, decoding, and re-encoding yields byte-identical objects.
#EncodedModelFields
func EncodedModelFields() map[string][]stringEncodedModelFields returns, for every DDL-reaching model struct (keyed by its unqualified struct name, matching reflect.Type.Name()), the list of exported field names the encoder serializes into canonical bytes. It is a copy of the encoder's own field policy — the SAME registry the totality guard (policy_test.go) checks for completeness — exposed so downstream kernel verification can be DRIVEN BY the encoder's notion of identity rather than a hand-maintained parallel list. The chief consumer is roadmap 1.4's diff-totality mutation guard: it perturbs each encoded field and asserts diff is non-empty, retiring the diff-under-reporting defect class by construction.
The returned map is freshly allocated; mutating it does not affect the policy.
#EncodeRegistrySnapshot
func EncodeRegistrySnapshot(reg *semtype.Registry) ([]byte, error)EncodeRegistrySnapshot returns the canonical bytes for the registry residue channel (see the package comment above). It is empty for every registry and is NOT part of schema identity.
#RegistrySnapshotEmpty
func RegistrySnapshotEmpty(reg *semtype.Registry) boolRegistrySnapshotEmpty reports whether the registry snapshot contributes nothing to identity. It is unconditionally true: all identity-bearing registry state has a model home, so there is no residue for ANY registry — including state-machine-bearing ones. This is the escape-hatch invariant the verification tests assert.
#Key.String
func (k Key) String() stringString renders the key in a stable, collision-free textual form:
kind:schema.name (most kinds) function:schema.name(args) (functions carry their signature) schema:name (the schema-global header; no schema qualifier) registry: (the singleton registry snapshot)
The kind prefix is always present, so keys of different kinds never collide even with identical schema and name.