The ABI boundary
VillageSQL extensions interact with the server through a binary interface (ABI) with a clear separation of ownership between the extension and server. The server owns:- Storage: InnoDB reads and writes bytes; it doesn’t interpret them
- Schema: column type metadata and which extension registered each type are stored in the VillageSQL system tables and persist across restarts
- Recovery, replication, and backups: these operate on raw bytes, the same as any built-in column type
- Extension registration: names, versions, and function/type registrations are reloaded from the VillageSQL system tables at startup
- Binary format: the
from_stringencode function defines what gets written to disk;to_stringreads it back during output - Business rules: validation, comparison, hashing, and error handling
- State: extensions don’t persist anything outside the bytes the server stores for them
villagesql/sdk/include/villagesql/abi/types.h in the server repository.
How custom types are stored
When you declare a column asUUID (as an example from vsql-uuid), the server stores a fixed-length block of raw bytes per row. The human-readable form — like d7d665f3-bb13-4c2f-b10f-d2126eb40cba — exists only at the boundaries: from_string encodes it to binary on write, and to_string decodes it back on read. Everything in between — storage, redo log, crash recovery, binary log — operates on those bytes without interpreting them.
Crash recovery
Extensions reload automatically on server restart — no manual intervention is required. You can verify this viaINFORMATION_SCHEMA.EXTENSION_REGISTRATION, which shows the same registration entry after restart as before.
Row data survives through InnoDB’s normal crash recovery. The extension’s binary format is never special-cased — InnoDB handles a UUID column no differently from a VARBINARY column.
Binary log format
In row-format replication (the default), custom type values travel as raw binary in the binary log. There is no re-encoding step: the extension’sfrom_string is not called when writing the binlog, and to_string is called only when a client reads the value back.
A row-level binary log entry for a UUID column looks like this:
to_string; one without it — an external CDC pipeline or binlog reader — sees only the raw bytes.
Restore
Restoring a VillageSQL server works the same way as crash recovery: the server starts up, reads its extension registrations, and loads each extension automatically. No special handling is needed for extension-typed columns — the raw bytes are in the backup, and the extensions decode them on demand. Extensions are not stored inside the database — they live as.veb files in veb_dir on disk. If the VEB file is missing when the server starts on the restored instance, startup aborts with VEB file not found. The .veb_expansion_cache is not a substitute for the .veb itself. When distributing your extension, make sure your users know to include veb_dir in any backup or server migration alongside the data directory.
Schema operations
ALTER TABLE on tables with extension-typed columns behaves exactly as it does for built-in types. The server tracks the column’s extension binding in its schema metadata, and that binding survives the table rebuild. Adding or modifying other columns on a table with a vsql-uuid id column leaves the UUID data intact.
