pgdesign v0.26.0 /internal/enc
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

Go go
const CodecVersion = 1

CodecVersion 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

Go go
const KindSchemaMeta   Kind = "schema"    // the schema-global header (name, extensions, pg_version, groups)

#KindTable

Go go
const KindTable        Kind = "table"     //

#KindView

Go go
const KindView         Kind = "view"      //

#KindMatView

Go go
const KindMatView      Kind = "matview"   //

#KindSequence

Go go
const KindSequence     Kind = "sequence"  //

#KindFunction

Go go
const KindFunction     Kind = "function"  //

#KindEnum

Go go
const KindEnum         Kind = "enum"      //

#KindDomain

Go go
const KindDomain       Kind = "domain"    //

#KindComposite

Go go
const KindComposite    Kind = "composite" //

#KindSMType

Go go
const KindSMType       Kind = "sm_type"   // a state-machine type definition (identity carrier)

#KindRegistrySnap

Go go
const KindRegistrySnap Kind = "registry"  // the semtype registry snapshot (import-surface residue channel; empty for identity)

#KindDML

Go go
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:""} -> "dml:" Key{Kind:"raw", Name:""} -> "raw:"

where is the op's zero-based position within its edge. The label is per-edge-unique and cross-edge-meaningless (op 0 of any edge renders "dml:0"), which is exactly what edge identity needs: it keeps identical data edges byte-identical without pretending a data op names a schema object. Pseudo-target keys are MANIFEST NO-OPS: they never appear in a revision manifest and are never resolved by the consistency checker.

#KindRaw

Go go
const KindRaw Kind = "raw" // pseudo-target for opaque RawSQL bodies (SM triggers, partman config)

#SchemaMeta

Go go
type SchemaMeta struct

SchemaMeta 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

Go go
type Kind string

Kind 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

Go go
type Key struct

Key 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

Go go
func DecodeTable(data []byte) (model.Table, error)

DecodeTable decodes canonical bytes into a table.

#DecodeView

Go go
func DecodeView(data []byte) (model.View, error)

DecodeView decodes canonical bytes into a view.

#DecodeMaterializedView

Go go
func DecodeMaterializedView(data []byte) (model.MaterializedView, error)

DecodeMaterializedView decodes canonical bytes into a materialized view.

#DecodeSequence

Go go
func DecodeSequence(data []byte) (model.Sequence, error)

DecodeSequence decodes canonical bytes into a sequence.

#DecodeFunction

Go go
func DecodeFunction(data []byte) (model.Function, error)

DecodeFunction decodes canonical bytes into a function.

#DecodeEnum

Go go
func DecodeEnum(data []byte) (model.Enum, error)

DecodeEnum decodes canonical bytes into an enum type.

#DecodeDomain

Go go
func DecodeDomain(data []byte) (model.Domain, error)

DecodeDomain decodes canonical bytes into a domain type.

#DecodeCompositeType

Go go
func DecodeCompositeType(data []byte) (model.CompositeType, error)

DecodeCompositeType decodes canonical bytes into a composite type.

#DecodeStateMachine

Go go
func DecodeStateMachine(data []byte) (model.StateMachine, error)

DecodeStateMachine decodes canonical bytes into a state-machine type definition.

#DecodeSchemaMeta

Go go
func DecodeSchemaMeta(data []byte) (SchemaMeta, error)

DecodeSchemaMeta decodes canonical bytes into the schema-global header.

#EncodeTable

Go go
func EncodeTable(t model.Table) ([]byte, error) { return canonicalJSON(tableToForm(t)) }

EncodeTable returns the canonical bytes for a single table.

#EncodeView

Go go
func EncodeView(v model.View) ([]byte, error) { return canonicalJSON(viewToForm(v)) }

EncodeView returns the canonical bytes for a view.

#EncodeMaterializedView

Go go
func EncodeMaterializedView(mv model.MaterializedView) ([]byte, error)

EncodeMaterializedView returns the canonical bytes for a materialized view.

#EncodeSequence

Go go
func EncodeSequence(s model.Sequence) ([]byte, error) { return canonicalJSON(sequenceToForm(s)) }

EncodeSequence returns the canonical bytes for a standalone sequence.

#EncodeFunction

Go go
func EncodeFunction(fn model.Function) ([]byte, error) { return canonicalJSON(functionToForm(fn)) }

EncodeFunction returns the canonical bytes for a function.

#EncodeEnum

Go go
func EncodeEnum(e model.Enum) ([]byte, error) { return canonicalJSON(enumToForm(e)) }

EncodeEnum returns the canonical bytes for an enum type.

#EncodeDomain

Go go
func EncodeDomain(d model.Domain) ([]byte, error) { return canonicalJSON(domainToForm(d)) }

EncodeDomain returns the canonical bytes for a domain type.

#EncodeCompositeType

Go go
func EncodeCompositeType(c model.CompositeType) ([]byte, error)

EncodeCompositeType returns the canonical bytes for a composite type.

#EncodeStateMachine

Go go
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

Go go
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

Go go
func FunctionArgSig(args []model.FunctionArg) string

FunctionArgSig 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

Go go
func KeyForTable(t model.Table) Key

KeyForTable returns the manifest key for a table.

#KeyForView

Go go
func KeyForView(v model.View) Key

KeyForView returns the manifest key for a view.

#KeyForMatView

Go go
func KeyForMatView(mv model.MaterializedView) Key

KeyForMatView returns the manifest key for a materialized view.

#KeyForSequence

Go go
func KeyForSequence(s model.Sequence) Key

KeyForSequence returns the manifest key for a standalone sequence.

#KeyForFunction

Go go
func KeyForFunction(f model.Function) Key

KeyForFunction returns the manifest key for a function, including its argument signature so overloads are distinct.

#KeyForEnum

Go go
func KeyForEnum(e model.Enum) Key

KeyForEnum returns the manifest key for an enum type.

#KeyForDomain

Go go
func KeyForDomain(d model.Domain) Key

KeyForDomain returns the manifest key for a domain type.

#KeyForComposite

Go go
func KeyForComposite(c model.CompositeType) Key

KeyForComposite returns the manifest key for a composite type.

#KeyForStateMachine

Go go
func KeyForStateMachine(sm model.StateMachine) Key

KeyForStateMachine returns the manifest key for a state-machine type.

#ParseKey

Go go
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

Go go
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:" and never resolves in a manifest (edge_format.md TENSION 2).

#KeyForRaw

Go go
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:" and never resolves in a manifest (edge_format.md TENSION 2).

#EncodeObjects

Go go
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

Go go
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

Go go
func DecodeObject(s *model.Schema, b []byte) error

DecodeObject 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

Go go
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

Go go
func EncodedModelFields() map[string][]string

EncodedModelFields 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

Go go
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

Go go
func RegistrySnapshotEmpty(reg *semtype.Registry) bool

RegistrySnapshotEmpty 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

Go go
func (k Key) String() string

String 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.

Search