src/sydra/compat/catalog.zig
Purpose
Builds an in-memory “catalog snapshot” representing a small subset of PostgreSQL system catalog concepts:
- namespaces (schemas)
- classes (relations: tables/views/indexes/sequences)
- attributes (columns)
- types
The snapshot is intended for Postgres-compatibility queries and metadata surfaces.
Public constants
pub const namespace_oid_base: u32 = 11000pub const relation_oid_base: u32 = 22000
These bases are used to assign deterministic OIDs for namespaces and relations created from specs.
Specs (inputs to snapshot building)
pub const NamespaceSpec
name: []const u8owner: u32 = 10
pub const RelationKind = enum { table, index, view, sequence }
Mapped to catalog relkind chars via an internal helper:
table→rindex→iview→vsequence→S
pub const Persistence = enum { permanent, temporary, unlogged }
Mapped to catalog relpersistence chars:
permanent→ptemporary→tunlogged→u
pub const RelationSpec
namespace: []const u8name: []const u8kind: RelationKindpersistence: Persistence = .permanenthas_primary_key: bool = falserow_estimate: f64 = 0is_partition: bool = falsetoast_relation_oid: ?u32 = null
pub const TypeKind = enum { base, enum_type, domain, pseudo }
Mapped to catalog typtype chars:
base→benum_type→edomain→dpseudo→p
pub const TypeSpec
Defines a type row. Notably:
oid: u32is supplied by the caller (types do not use*_oid_base).name,namespaceidentify the row.length: i16,by_value: bool,category: u8,delimiter: u8align with commonpg_typefields.- Additional fields exist for arrays, element/base types, collation, and in/out regprocs.
pub const IdentityKind = enum { none, always, by_default }
Mapped to attidentity chars:
none→(space)always→aby_default→d
pub const GeneratedKind = enum { none, stored }
Mapped to attgenerated chars:
none→(space)stored→s
pub const ColumnSpec
namespace: []const u8relation: []const u8name: []const u8type_oid: u32position: ?i16 = null(optionalattnumoverride)not_null: bool = falsehas_default: bool = falseis_dropped: bool = falsetype_length: i16 = -1type_modifier: i32 = -1identity: IdentityKind = .nonegenerated: GeneratedKind = .nonedimensions: i32 = 0
Snapshot rows (outputs)
The snapshot exposes “row structs” shaped similarly to Postgres catalog tables.
pub const NamespaceRow
oid: u32nspname: []const u8nspowner: u32
pub const ClassRow
oid: u32relname: []const u8relnamespace: u32relkind: u8relpersistence: u8reltuples: f64relhaspkey: boolrelispartition: boolreltoastrelid: u32
pub const AttributeRow
attrelid: u32attname: []const u8atttypid: u32attnum: i16attnotnull: boolatthasdef: boolattisdropped: boolattlen: i16atttypmod: i32attidentity: u8attgenerated: u8attndims: i32
pub const TypeRow
oid: u32typname: []const u8typnamespace: u32typlen: i16typbyval: booltyptype: u8typcategory: u8typdelim: u8typelem: u32typarray: u32typbasetype: u32typcollation: u32typinput: u32typoutput: u32
pub const Snapshot
Fields:
namespaces: []NamespaceRowclasses: []ClassRowattributes: []AttributeRowtypes: []TypeRowowns_memory: bool
pub fn deinit(self: *Snapshot, alloc) void
If owns_memory is true, frees:
- owned strings (
nspname,relname,attname,typname) - the row slices themselves
Then resets self to Snapshot{}.
Building snapshots
pub fn buildSnapshot(alloc, namespace_specs, relation_specs, type_specs, column_specs) !Snapshot
High-level algorithm:
- Assemble namespaces
- Start with
namespace_specs. - Ensure every
RelationSpec.namespaceexists; missing namespaces are inserted with defaultNamespaceSpec{ .owner = 10 }. - Namespaces are de-duplicated by name.
- Start with
- Assign namespace OIDs
- Namespaces are sorted by name.
- OIDs are assigned as
namespace_oid_base + idx. - A lookup map
name → oidis built for later stages.
- Build classes (relations)
relation_specsare copied and sorted by(namespace, name).- Each relation OID is assigned as
relation_oid_base + rel_index. - Relation name strings are duplicated and owned by the snapshot.
- Build types
type_specsare copied and sorted by(namespace, name).- Each type name string is duplicated and owned by the snapshot.
TypeRow.oidcomes fromTypeSpec.oid(caller provided).
- Build attributes (columns)
column_specsare copied and sorted by(namespace, relation, position?, name).- The relation OID is discovered by scanning the built classes.
attnumis allocated per relation, starting at1, unlessColumnSpec.positionoverrides it.- Column name strings are duplicated and owned by the snapshot.
The returned snapshot sets owns_memory = true.
Internal helpers (important invariants)
findRelationOid(classes, ns_oid, relname) !u32is a linear scan; it fails witherror.MissingRelation.nextAttnum(map, rel_oid, override) !i16tracks per-relationattnumand usesoverridewhen provided.
Practical implication: if callers provide conflicting ColumnSpec.position overrides for the same relation, the resulting attribute list can contain duplicate or non-monotonic attnum values.
Store wrapper
pub const Store
Holds a Snapshot and provides lifecycle helpers.
snapshot: Snapshot = .{}pub fn deinit(self: *Store, alloc) voidpub fn load(self, alloc, namespace_specs, relation_specs, type_specs, column_specs) !void- Builds a new snapshot and replaces the old one (deiniting the previous snapshot).
pub fn namespaces/classes/attributes/types(self) []const ...
Global store
pub fn global() *Storereturns a pointer to a file-scopedglobal_store.
Code excerpts
pub const namespace_oid_base: u32 = 11000;
pub const relation_oid_base: u32 = 22000;
pub const NamespaceSpec = struct {
name: []const u8,
owner: u32 = 10,
};
pub const RelationKind = enum {
table,
index,
view,
sequence,
};
pub const Persistence = enum {
permanent,
temporary,
unlogged,
};
pub const RelationSpec = struct {
namespace: []const u8,
name: []const u8,
kind: RelationKind,
persistence: Persistence = .permanent,
has_primary_key: bool = false,
row_estimate: f64 = 0,
is_partition: bool = false,
toast_relation_oid: ?u32 = null,
};
std.sort.heap(MapEntry, entry_buffer, {}, struct {
pub fn lessThan(_: void, lhs: MapEntry, rhs: MapEntry) bool {
return std.mem.lessThan(u8, lhs.key_ptr.*, rhs.key_ptr.*);
}
}.lessThan);
try namespace_entries.ensureTotalCapacity(entry_buffer.len);
idx = 0;
while (idx < entry_buffer.len) : (idx += 1) {
const entry = entry_buffer[idx];
const offset: u32 = @intCast(idx);
const oid = namespace_oid_base + offset;
const name_ptr = entry.key_ptr.*;
try namespace_entries.append(.{
.oid = oid,
.nspname = name_ptr,
.nspowner = entry.value_ptr.*.owner,
});
try ns_lookup.put(name_ptr, oid);
}