Use this file to discover all available pages before exploring further.
The vsql_complex extension is VillageSQL’s reference implementation for custom types using the VEF SDK—demonstrating production-ready extension patterns.Source:villagesql/examples/vsql-complex/ in VillageSQL repository
COMPLEX type for complex numbers (a + bi) with arithmetic, utilities, and aggregation.Usage Example:
INSTALL EXTENSION vsql_complex;CREATE TABLE signals ( id INT PRIMARY KEY, impedance COMPLEX);INSERT INTO signals VALUES (1, '(50.0,10.0)');SELECT complex_real(impedance) as resistance, complex_imag(impedance) as reactance, complex_abs(impedance) as magnitudeFROM signals;
File: src/complex.ccvsql_complex uses the VEF SDK with VEF_GENERATE_ENTRY_POINTS():
#include <villagesql/vsql.h>// Type name constants as char arrays — required as non-type template parameters// (NTTPs) so that make_type can auto-generate VDF names like "COMPLEX::from_string".static constexpr const char kComplexTypeName[] = "COMPLEX";static constexpr const char kComplex2TypeName[] = "COMPLEX2";// Type objects: encode/decode/compare VDFs are embedded via template parameters.// No separate .func() registration is needed for type operations.constexpr auto COMPLEX = vsql::make_type<kComplexTypeName>() .persisted_length(kComplexSize) .max_decode_buffer_length(64) .from_string<&complex_from_string>() .to_string<&complex_to_string>() .compare<&complex_compare>() .intrinsic_default_str("(0,0)") .build();constexpr auto COMPLEX2 = vsql::make_type<kComplex2TypeName>() .persisted_length(kComplexSize) .max_decode_buffer_length(64) .from_string<&complex2_from_string>() .to_string<&complex_to_string>() .compare<&complex_compare>() .hash<&complex2_hash>() .intrinsic_default_str("(0,0)") .build();using namespace vsql;VEF_GENERATE_ENTRY_POINTS( make_extension() .type(COMPLEX) .type(COMPLEX2) // Arithmetic operations .func(make_func<&complex_add_impl>("complex_add") .returns(COMPLEX) .param(COMPLEX) .param(COMPLEX) .deterministic() .build()) .func(make_func<&complex_subtract_impl>("complex_subtract") .returns(COMPLEX) .param(COMPLEX) .param(COMPLEX) .deterministic() .build()) .func(make_func<&complex_multiply_impl>("complex_multiply") .returns(COMPLEX) .param(COMPLEX) .param(COMPLEX) .deterministic() .build()) .func(make_func<&complex_divide_impl>("complex_divide") .returns(COMPLEX) .param(COMPLEX) .param(COMPLEX) .deterministic() .build()) // Utility functions .func(make_func<&complex_real_impl>("complex_real") .returns(REAL) .param(COMPLEX) .build()) .func(make_func<&complex_imag_impl>("complex_imag") .returns(REAL) .param(COMPLEX) .build()) .func(make_func<&complex_abs_impl>("complex_abs") .returns(REAL) .param(COMPLEX) .build()) .func(make_func<&complex_conjugate_impl>("complex_conjugate") .returns(COMPLEX) .param(COMPLEX) .build()) // Aggregate functions .func(make_aggregate_func<ComplexSumState, &complex_sum_result>( "complex_sum") .returns(COMPLEX) .param(COMPLEX) .clear<&complex_sum_clear>() .accumulate<&complex_sum_accumulate>() .build()))
Key patterns:
Single macro call registers everything — no manual SQL needed
Type objects are constexpr variables built with vsql::make_type<kName>(), where kName is a static constexpr const char[] used as a non-type template parameter
Type operations (.from_string<>(), .to_string<>(), .compare<>(), .hash<>()) are embedded in the type object — no separate .func() calls needed for them
.compare() enables ORDER BY and indexing; .hash() is optional
.intrinsic_default_str("(0,0)") sets the default value written when INSERT IGNORE or UPDATE IGNORE assigns NULL to a NOT NULL column of this type
Function registration uses make_func<&impl>("name") with .build()
Aggregate registration uses make_aggregate_func<State, &result_fn>("name") with .clear<&fn>() and .accumulate<&fn>(); the result function has signature void(const State&, ResultWrapper)
{ "name": "vsql_complex", "version": "0.0.1", "description": "Complex number data type for VillageSQL", "author": "VillageSQL Contributors", "license": "GPL-2.0"}
VillageSQL supports SELECT INTO OUTFILE and LOAD DATA INFILE for custom types, allowing you to export and import data while preserving custom type values.
Custom types serialize to their string representation when exported:
INSTALL EXTENSION vsql_complex;-- Create table with custom typeCREATE TABLE signals ( id INT PRIMARY KEY, reading COMPLEX);INSERT INTO signals VALUES (1, '(3.0,4.0)'), (2, '(5.0,12.0)'), (3, '(-1.0,2.0)');-- Export to fileSELECT * FROM signals INTO OUTFILE '/tmp/signals_export.txt';
-- Create new table with same schemaCREATE TABLE signals_imported ( id INT PRIMARY KEY, reading COMPLEX);-- Import dataLOAD DATA INFILE '/tmp/signals_export.txt' INTO TABLE signals_imported;-- VerifySELECT * FROM signals_imported;
SELECT id, reading, complex_abs(reading) AS magnitude, complex_real(reading) AS real_part, complex_imag(reading) AS imag_partINTO OUTFILE '/tmp/signals_computed.txt'FROM signals;
Custom types are exported in their string representation format. Binary export with SELECT INTO DUMPFILE is not currently supported for custom types.