Custom types let you define new column types — likeDocumentation Index
Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
COMPLEX, UUID, or
VECTOR — that work with ORDER BY, indexes, and aggregate functions. This
page is Step 4 of the Creating Extensions
tutorial. Complete Steps 1–3 before continuing here.
Define Type Operations
Every custom type needs encode, decode, and compare operations, and optionally a hash operation. Implement them against these signatures and pass builder objects tovsql::make_type<>():
For a
from_string VDF (which returns the custom type), the server sizes the
output buffer to at least the type’s persisted_length value before invoking
the VDF, so buf.size() >= persisted_length is guaranteed on entry. This
applies to both fixed-width types and parameterized types (where
persisted_length is resolved from the type context at call time). No
separate buffer-size request is needed.vsql::Span<T>, a non-owning view over a
contiguous sequence of T — in.value() returns
vsql::Span<const unsigned char> and out.buffer() returns
vsql::Span<unsigned char>. Using C++20 or later, vsql::Span<T> is an alias
for std::span<T>; under C++17 the SDK provides a minimal source-compatible
fallback with the same data(), size(), empty(), indexing, and iterator
surface. It is available via #include <villagesql/vsql.h>.
Register the Type
Thevsql::make_type<kName>() template embeds encode, decode, compare, and hash
operations directly in the type object. VDF names are auto-generated as
TYPE::from_string, TYPE::to_string, TYPE::compare, and TYPE::hash
at compile time. Separate .func(make_type_encode<>(...)) calls are not needed.
build() fails to compile if from_string, to_string, or compare is missing.
Each template method validates the function pointer signature with static_assert.
The type name is passed as a non-type template parameter (NTTP). Declare it as a
static constexpr const char[] array — the pointer identity is used to key
independent VDF name buffers, so two types sharing a function pointer still get
separate auto-generated names.
Type Operations Reference
The template-based API auto-generates these SQL-callable VDFs:| Builder Method | Auto-Generated VDF Name | VDF SQL Signature |
|---|---|---|
.from_string<&f>() | TYPE::from_string | (STRING) -> CUSTOM(this type) |
.to_string<&f>() | TYPE::to_string | (CUSTOM(this type)) -> STRING |
.compare<&f>() | TYPE::compare | (CUSTOM(this type), CUSTOM(this type)) -> INT |
.hash<&f>() | TYPE::hash | (CUSTOM(this type)) -> INT |
ALTER TABLE and Custom Types
ALTER TABLE ... MODIFY COLUMN and CHANGE COLUMN enforce these rules when custom types are involved:
| From | To | Result |
|---|---|---|
| Non-custom | Custom | Error: Cannot convert column 'col' to custom type 'MYTYPE' |
| Custom | String type | Allowed |
| Custom | Non-string type | Error: Cannot convert custom type column 'col' to non-string type |
| Custom | Different custom type | Error if incompatible: Cannot convert between incompatible custom types 'A' and 'B' |
Type Conversion Functions
With the template-based API, encode and decode VDFs are embedded in the type object and registered automatically — no separate.func() calls are needed. The
auto-generated VDFs are SQL-callable:
INSERT INTO t (val) VALUES ('(1.0,2.0)') works without an explicit call. But
expressions that resolve to STRING type — CASE expressions, CONCAT, and
similar — are not implicitly coerced. Wrap them with TYPE::from_string:
Example: COMPLEX Type
Here’s a complete example implementing a COMPLEX number type:VDFs in Generated Columns
VDFs can be used in generated column expressions. The VDF must be declared.deterministic() in the extension builder — the server blocks non-deterministic functions in this context.
complex_abs must be registered with .deterministic(). Traditional MySQL UDFs are not permitted in generated columns.VDFs in Functional Indexes
VDFs can be used in functional index expressions. The same.deterministic()
requirement from generated columns applies here because MySQL implements
functional indexes as hidden generated columns.
WHERE,
ORDER BY, or GROUP BY. Cast the comparison value to the VDF’s return type
so the optimizer matches the expression:
Next Steps
When your type is defined, continue with Step 5 of the tutorial to build and install your extension.Continue: Build Your Extension
Return to the tutorial to build and install your extension.
Parameterized Types
Types that take parameters like VECTOR(1536) — dimension-aware encode, decode, and storage sizing.
Extension API Reference
VDF API contracts, null handling, buffer sizing, and advanced patterns.
Replication
ROW format requirements, extension install order, and version matching for replicated setups.

