Skip to content

Commit 8790202

Browse files
malone-at-workdbentley-vsql
authored andcommitted
sdk: C++ wrappers for type op VDFs (#103)
Adds builders for idiomatic C++ type operations: * make_type_encode * make_type_decode * make_type_compare * make_type_hash Updates extension.h docs and examples to reflect the new signatures.
1 parent 8c9e6f1 commit 8790202

3 files changed

Lines changed: 262 additions & 47 deletions

File tree

villagesql/sdk/include/villagesql/extension.h

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -126,30 +126,48 @@
126126
// you find that you need to use prerun or postrun functions, please come talk
127127
// to us so we can understand your use case.
128128
//
129-
// For type conversion functions (from_string / to_string):
130-
//
131-
// make_func("mytype_from_string")
132-
// .from_string<&encode_func>("mytype") // STRING -> mytype
133-
//
134-
// make_func("mytype_to_string")
135-
// .to_string<&decode_func>("mytype") // mytype -> STRING
136-
//
137-
//
138129
// DEFINING TYPES
139130
// --------------
140131
//
141132
// Custom types are defined using make_type("name") and chained builder methods,
142-
// ending with .build():
133+
// ending with .build(). The type operations (encode, decode, compare, hash) are
134+
// implemented as VDFs registered separately using make_type_encode,
135+
// make_type_decode, make_type_compare, and make_type_hash, then referenced
136+
// by name in the type descriptor:
143137
//
144138
// make_type("mytype")
145-
// .persisted_length(8) // Fixed storage size in bytes
146-
// .max_decode_buffer_length(64) // Max bytes for string representation
147-
// .encode(&mytype_encode) // String -> binary
148-
// .decode(&mytype_decode) // Binary -> string
149-
// .compare(&mytype_compare) // For ORDER BY, indexes
150-
// .hash(&mytype_hash) // Optional: for hash joins
139+
// .persisted_length(8) // Fixed storage size in bytes
140+
// .max_decode_buffer_length(64) // Max bytes for string representation
141+
// .encode("mytype_encode") // Name of the encode VDF
142+
// .decode("mytype_decode") // Name of the decode VDF
143+
// .compare("mytype_compare") // Name of the compare VDF
144+
// .hash("mytype_hash") // Optional: name of the hash VDF
151145
// .build()
152146
//
147+
// The VDFs are created using the ergonomic make_type_* entry points:
148+
//
149+
// make_type_encode<&my_encode>("mytype_encode", MYTYPE)
150+
// make_type_decode<&my_decode>("mytype_decode", MYTYPE)
151+
// make_type_compare<&my_compare>("mytype_compare", MYTYPE)
152+
// make_type_hash<&my_hash>("mytype_hash", MYTYPE) // optional
153+
//
154+
// Extension authors write against these ergonomic C++ signatures:
155+
//
156+
// // Encode: string -> binary. false=success, true=error.
157+
// // Set *length = SIZE_MAX to produce SQL NULL.
158+
// bool my_encode(std::string_view from, Span<unsigned char> buf,
159+
// size_t *length);
160+
//
161+
// // Decode: binary -> string. false=success, true=error.
162+
// bool my_decode(Span<const unsigned char> data, Span<char> out,
163+
// size_t *out_len);
164+
//
165+
// // Compare: returns <0, 0, or >0.
166+
// int my_compare(Span<const unsigned char> a, Span<const unsigned char> b);
167+
//
168+
// // Hash: returns hash code.
169+
// size_t my_hash(Span<const unsigned char> data);
170+
//
153171
//
154172
// REGISTERING THE EXTENSION
155173
// -------------------------
@@ -158,10 +176,17 @@
158176
//
159177
// VEF_GENERATE_ENTRY_POINTS(
160178
// make_extension("my_ext", "1.0.0")
161-
// .func(make_func<&func1_impl>("func1").returns(INT).build())
162-
// .func(make_func<&func2_impl>("func2").returns(STRING).build())
163-
// .type(make_type("mytype").persisted_length(8)
164-
// .encode(&enc).decode(&dec).compare(&cmp).build()))
179+
// .type(make_type(MYTYPE)
180+
// .persisted_length(8)
181+
// .max_decode_buffer_length(64)
182+
// .encode("mytype_encode")
183+
// .decode("mytype_decode")
184+
// .compare("mytype_compare")
185+
// .build())
186+
// .func(make_type_encode<&my_encode>("mytype_encode", MYTYPE))
187+
// .func(make_type_decode<&my_decode>("mytype_decode", MYTYPE))
188+
// .func(make_type_compare<&my_compare>("mytype_compare", MYTYPE))
189+
// .func(make_func<&func1_impl>("func1").returns(INT).build()))
165190
//
166191
// This generates the extern "C" vef_register() and vef_unregister() functions
167192
// that mysqld calls to load the extension.
@@ -180,35 +205,35 @@
180205
// // BYTEARRAY type: fixed 8-byte value stored as raw bytes
181206
//
182207
// // Encode: string -> binary (copy up to 8 bytes, zero-pad)
183-
// bool bytearray_encode(unsigned char* buf, size_t buf_size,
184-
// const char* from, size_t from_len, size_t* length) {
185-
// if (buf_size < kBytearrayLen) return true; // error
186-
// memset(buf, 0, kBytearrayLen);
187-
// size_t copy_len = from_len < kBytearrayLen ? from_len : kBytearrayLen;
188-
// if (from && copy_len > 0) memcpy(buf, from, copy_len);
208+
// bool bytearray_encode(std::string_view from, Span<unsigned char> buf,
209+
// size_t* length) {
210+
// if (buf.size() < kBytearrayLen) return true; // error
211+
// memset(buf.data(), 0, kBytearrayLen);
212+
// size_t n = from.size() < kBytearrayLen ? from.size() : kBytearrayLen;
213+
// if (n > 0) memcpy(buf.data(), from.data(), n);
189214
// *length = kBytearrayLen;
190215
// return false; // success
191216
// }
192217
//
193218
// // Decode: binary -> string (copy 8 bytes)
194-
// bool bytearray_decode(const unsigned char* buf, size_t buf_size,
195-
// char* to, size_t to_size, size_t* to_length) {
196-
// if (to_size < kBytearrayLen) return true; // error
197-
// memcpy(to, buf, kBytearrayLen);
198-
// *to_length = kBytearrayLen;
219+
// bool bytearray_decode(Span<const unsigned char> data, Span<char> out,
220+
// size_t* out_len) {
221+
// if (out.size() < kBytearrayLen) return true; // error
222+
// memcpy(out.data(), data.data(), kBytearrayLen);
223+
// *out_len = kBytearrayLen;
199224
// return false; // success
200225
// }
201226
//
202227
// // Compare: lexicographic byte comparison
203-
// int bytearray_compare(const unsigned char* a, size_t a_len,
204-
// const unsigned char* b, size_t b_len) {
205-
// return memcmp(a, b, kBytearrayLen);
228+
// int bytearray_compare(Span<const unsigned char> a,
229+
// Span<const unsigned char> b) {
230+
// return memcmp(a.data(), b.data(), kBytearrayLen);
206231
// }
207232
//
208233
// // ROT13: apply ROT13 cipher to ASCII letters in a bytearray
209-
// void rot13_impl(BinaryArg input, BinaryResult out) {
210-
// if (input.is_null()) { out.set_null(); return; }
211-
// auto src = input.value(); // villagesql::Span<const unsigned char>
234+
// void rot13_impl(BinaryArg in, BinaryResult out) {
235+
// if (in.is_null()) { out.set_null(); return; }
236+
// auto src = in.value(); // villagesql::Span<const unsigned char>
212237
// auto dst = out.buffer(); // villagesql::Span<unsigned char>
213238
// for (size_t i = 0; i < kBytearrayLen; i++) {
214239
// unsigned char c = src[i];
@@ -219,16 +244,22 @@
219244
// out.set_length(kBytearrayLen);
220245
// }
221246
//
222-
// // Register everything inline
247+
// // Register everything
223248
// VEF_GENERATE_ENTRY_POINTS(
224249
// make_extension("bytearray_ext", "1.0.0")
225250
// .type(make_type(BYTEARRAY)
226251
// .persisted_length(kBytearrayLen)
227252
// .max_decode_buffer_length(kBytearrayLen)
228-
// .encode(&bytearray_encode)
229-
// .decode(&bytearray_decode)
230-
// .compare(&bytearray_compare)
253+
// .encode("bytearray_encode")
254+
// .decode("bytearray_decode")
255+
// .compare("bytearray_compare")
231256
// .build())
257+
// .func(make_type_encode<&bytearray_encode>(
258+
// "bytearray_encode", BYTEARRAY))
259+
// .func(make_type_decode<&bytearray_decode>(
260+
// "bytearray_decode", BYTEARRAY))
261+
// .func(make_type_compare<&bytearray_compare>(
262+
// "bytearray_compare", BYTEARRAY))
232263
// .func(make_func<&rot13_impl>("rot13")
233264
// .returns(BYTEARRAY)
234265
// .param(BYTEARRAY)

villagesql/sdk/include/villagesql/func_builder.h

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,35 @@ struct ToStringWrapper {
320320
}
321321
};
322322

323+
// =============================================================================
324+
// C++ Type Operation Builders
325+
// =============================================================================
326+
//
327+
// Extension authors must implement functions with the following signatures
328+
// to enable a new type to be used in VillageSQL, and use the builders
329+
// make_type_encode, make_type_decode, make_type_compare, and make_type_hash to
330+
// and register the names with the type builder.
331+
// TypeEncodeFunc -> VDF: (STRING) -> CUSTOM(type)
332+
// TypeDecodeFunc -> VDF: (CUSTOM(type)) -> STRING
333+
// TypeCompareFunc -> VDF: (CUSTOM(type), CUSTOM(type)) -> INT
334+
// TypeHashFunc -> VDF: (CUSTOM(type)) -> INT
335+
336+
// Encode: string -> binary. Returns false on success, true on error.
337+
// Set *length = SIZE_MAX to produce SQL NULL.
338+
using TypeEncodeFunc = bool (*)(std::string_view from, Span<unsigned char> buf,
339+
size_t *length);
340+
341+
// Decode: binary -> string. Returns false on success, true on error.
342+
using TypeDecodeFunc = bool (*)(Span<const unsigned char> data, Span<char> out,
343+
size_t *out_len);
344+
345+
// Compare: two binary values. Returns <0, 0, or >0.
346+
using TypeCompareFunc = int (*)(Span<const unsigned char> a,
347+
Span<const unsigned char> b);
348+
349+
// Hash: binary value -> hash code.
350+
using TypeHashFunc = size_t (*)(Span<const unsigned char> data);
351+
323352
// IntrinsicDefaultWrapper: wraps a vef_intrinsic_default_func_t into a VDF.
324353
// VDF signature: (INT) -> STRING (binary), where INT is the resolved
325354
// persisted_length (buffer_size). Returns the encoded default value as binary.
@@ -342,6 +371,102 @@ struct IntrinsicDefaultWrapper {
342371
}
343372
};
344373

374+
// TypeEncodeVdfWrapper: wraps a TypeEncodeFunc into a VDF.
375+
// VDF signature: (STRING) -> CUSTOM(type).
376+
template <TypeEncodeFunc Func>
377+
struct TypeEncodeVdfWrapper {
378+
static void invoke(vef_context_t *ctx, vef_vdf_args_t *args,
379+
vef_vdf_result_t *result) {
380+
vef_invalue_t arg = get_invalue(ctx, args, 0);
381+
if (arg.is_null) {
382+
result->type = VEF_RESULT_NULL;
383+
return;
384+
}
385+
size_t length;
386+
bool failed = Func({arg.str_value, arg.str_len},
387+
{result->bin_buf, result->max_bin_len}, &length);
388+
if (failed) {
389+
result->type = VEF_RESULT_ERROR;
390+
constexpr size_t kMaxInputDisplay = 64;
391+
size_t display_len = arg.str_len;
392+
const char *ellipsis = "";
393+
if (display_len > kMaxInputDisplay) {
394+
display_len = kMaxInputDisplay;
395+
ellipsis = "...";
396+
}
397+
snprintf(result->error_msg, VEF_MAX_ERROR_LEN,
398+
"failed to encode '%.*s%s'", static_cast<int>(display_len),
399+
arg.str_value, ellipsis);
400+
return;
401+
}
402+
if (length == SIZE_MAX) {
403+
result->type = VEF_RESULT_NULL;
404+
return;
405+
}
406+
result->type = VEF_RESULT_VALUE;
407+
result->actual_len = length;
408+
}
409+
};
410+
411+
// TypeDecodeVdfWrapper: wraps a TypeDecodeFunc into a VDF.
412+
// VDF signature: (CUSTOM(type)) -> STRING.
413+
template <TypeDecodeFunc Func>
414+
struct TypeDecodeVdfWrapper {
415+
static void invoke(vef_context_t *ctx, vef_vdf_args_t *args,
416+
vef_vdf_result_t *result) {
417+
vef_invalue_t arg = get_invalue(ctx, args, 0);
418+
if (arg.is_null) {
419+
result->type = VEF_RESULT_NULL;
420+
return;
421+
}
422+
size_t out_len;
423+
bool failed = Func({arg.bin_value, arg.bin_len},
424+
{result->str_buf, result->max_str_len}, &out_len);
425+
if (failed) {
426+
result->type = VEF_RESULT_ERROR;
427+
snprintf(result->error_msg, VEF_MAX_ERROR_LEN, "failed to decode value");
428+
return;
429+
}
430+
result->type = VEF_RESULT_VALUE;
431+
result->actual_len = out_len;
432+
}
433+
};
434+
435+
// TypeCompareVdfWrapper: wraps a TypeCompareFunc into a VDF.
436+
// VDF signature: (CUSTOM(type), CUSTOM(type)) -> INT.
437+
template <TypeCompareFunc Func>
438+
struct TypeCompareVdfWrapper {
439+
static void invoke(vef_context_t *ctx, vef_vdf_args_t *args,
440+
vef_vdf_result_t *result) {
441+
vef_invalue_t a = get_invalue(ctx, args, 0);
442+
vef_invalue_t b = get_invalue(ctx, args, 1);
443+
if (a.is_null || b.is_null) {
444+
result->type = VEF_RESULT_NULL;
445+
return;
446+
}
447+
result->int_value =
448+
Func({a.bin_value, a.bin_len}, {b.bin_value, b.bin_len});
449+
result->type = VEF_RESULT_VALUE;
450+
}
451+
};
452+
453+
// TypeHashVdfWrapper: wraps a TypeHashFunc into a VDF.
454+
// VDF signature: (CUSTOM(type)) -> INT.
455+
template <TypeHashFunc Func>
456+
struct TypeHashVdfWrapper {
457+
static void invoke(vef_context_t *ctx, vef_vdf_args_t *args,
458+
vef_vdf_result_t *result) {
459+
vef_invalue_t arg = get_invalue(ctx, args, 0);
460+
if (arg.is_null) {
461+
result->type = VEF_RESULT_NULL;
462+
return;
463+
}
464+
result->int_value =
465+
static_cast<long long>(Func({arg.bin_value, arg.bin_len}));
466+
result->type = VEF_RESULT_VALUE;
467+
}
468+
};
469+
345470
// =============================================================================
346471
// Extension Author Function Signatures (for parameterized types)
347472
// =============================================================================
@@ -754,6 +879,67 @@ constexpr StaticFuncDesc<1> make_intrinsic_default(const char *name) {
754879
return StaticFuncDesc<1>(name, meta);
755880
}
756881

882+
// Entry point for encode VDFs: (STRING) -> CUSTOM(type_name).
883+
// make_type_encode<&my_func>("func_name", TYPE)
884+
// Register with .func() and reference in type via .encode("func_name").
885+
template <TypeEncodeFunc Func>
886+
constexpr StaticFuncDesc<1> make_type_encode(const char *name,
887+
const char *type_name) {
888+
FuncWithMetadata meta{};
889+
meta.f = &TypeEncodeVdfWrapper<Func>::invoke;
890+
meta.return_type = to_vef_type(type_name);
891+
meta.param_types[0] = to_vef_type(STRING);
892+
meta.num_params = 1;
893+
meta.buffer_size = 0; // server provides bin_buf sized to persisted_length
894+
return StaticFuncDesc<1>(name, meta);
895+
}
896+
897+
// Entry point for decode VDFs: (CUSTOM(type_name)) -> STRING.
898+
// make_type_decode<&my_func>("func_name", TYPE)
899+
// Register with .func() and reference in type via .decode("func_name").
900+
template <TypeDecodeFunc Func>
901+
constexpr StaticFuncDesc<1> make_type_decode(const char *name,
902+
const char *type_name) {
903+
FuncWithMetadata meta{};
904+
meta.f = &TypeDecodeVdfWrapper<Func>::invoke;
905+
meta.return_type = to_vef_type(STRING);
906+
meta.param_types[0] = to_vef_type(type_name);
907+
meta.num_params = 1;
908+
meta.buffer_size = 0;
909+
return StaticFuncDesc<1>(name, meta);
910+
}
911+
912+
// Entry point for compare VDFs: (CUSTOM(type_name), CUSTOM(type_name)) -> INT.
913+
// make_type_compare<&my_func>("func_name", TYPE)
914+
// Register with .func() and reference in type via .compare("func_name").
915+
template <TypeCompareFunc Func>
916+
constexpr StaticFuncDesc<2> make_type_compare(const char *name,
917+
const char *type_name) {
918+
FuncWithMetadata meta{};
919+
meta.f = &TypeCompareVdfWrapper<Func>::invoke;
920+
meta.return_type = to_vef_type(INT);
921+
meta.param_types[0] = to_vef_type(type_name);
922+
meta.param_types[1] = to_vef_type(type_name);
923+
meta.num_params = 2;
924+
meta.buffer_size = 0;
925+
return StaticFuncDesc<2>(name, meta);
926+
}
927+
928+
// Entry point for hash VDFs: (CUSTOM(type_name)) -> INT.
929+
// make_type_hash<&my_func>("func_name", TYPE)
930+
// Register with .func() and reference in type via .hash("func_name").
931+
template <TypeHashFunc Func>
932+
constexpr StaticFuncDesc<1> make_type_hash(const char *name,
933+
const char *type_name) {
934+
FuncWithMetadata meta{};
935+
meta.f = &TypeHashVdfWrapper<Func>::invoke;
936+
meta.return_type = to_vef_type(INT);
937+
meta.param_types[0] = to_vef_type(type_name);
938+
meta.num_params = 1;
939+
meta.buffer_size = 0;
940+
return StaticFuncDesc<1>(name, meta);
941+
}
942+
757943
// =============================================================================
758944
// Internal Implementation
759945
// =============================================================================

villagesql/sdk/include/villagesql/type_builder.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ namespace type_builder {
3434
// make_type("mytype")
3535
// .persisted_length(8)
3636
// .max_decode_buffer_length(64)
37-
// .encode(&my_encode)
38-
// .decode(&my_decode)
39-
// .compare(&my_compare)
40-
// .hash(&my_hash) // optional
37+
// .encode("mytype_encode")
38+
// .decode("mytype_decode")
39+
// .compare("mytype_compare")
40+
// .hash("mytype_hash") // optional
4141
// .build()
4242
//
43-
// TODO(villagesql-beta): add type safe registration methods for the type
44-
// operations.
4543
// TODO(villagesql-beta): allow VDFs that aren't directly callable from SQL.
4644

4745
class TypeBuilder {

0 commit comments

Comments
 (0)