Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,45 @@ FROM test_bytearray t1, test_bytearray t2
WHERE t1.id = 1 AND t2.id = 2;
concatenated
'hello world123'
# Test ba_len function (zero-arity)
SELECT vsql_simple.ba_len() as len;
len
8
# Test ba_len rejects arguments
SELECT vsql_simple.ba_len(1);
ERROR HY000: Cannot initialize function 'ba_len': wrong number of arguments (expected 0, got 1)
# Test ba_concat rejects wrong argument count
SELECT vsql_simple.ba_concat(t1.data) FROM test_bytearray t1 WHERE t1.id = 1;
ERROR HY000: Cannot initialize function 'ba_concat': wrong number of arguments (expected 2, got 1)
# Test ba_concat_all function (varargs, uses prerun escape hatch)
# Single argument
SELECT CONCAT("'", vsql_simple.ba_concat_all(t1.data), "'") as result
FROM test_bytearray t1 WHERE t1.id = 1;
result
'hello '
# Two arguments (same as ba_concat)
SELECT CONCAT("'", vsql_simple.ba_concat_all(t1.data, t2.data), "'") as result
FROM test_bytearray t1, test_bytearray t2
WHERE t1.id = 1 AND t2.id = 2;
result
'hello world123'
# Three arguments
SELECT CONCAT("'", vsql_simple.ba_concat_all(t1.data, t2.data, t3.data), "'") as result
FROM test_bytearray t1, test_bytearray t2, test_bytearray t3
WHERE t1.id = 1 AND t2.id = 2 AND t3.id = 3;
result
'hello world123abcdefgh'
# NULL handling: any NULL argument returns NULL
SELECT vsql_simple.ba_concat_all(t1.data, NULL) as result
FROM test_bytearray t1 WHERE t1.id = 1;
result
NULL
# ba_concat_all rejects zero arguments
SELECT vsql_simple.ba_concat_all();
ERROR HY000: Can't initialize function 'ba_concat_all'; ba_concat_all requires at least one argument
# ba_concat_all rejects non-BYTEARRAY arguments (INT)
SELECT vsql_simple.ba_concat_all(1, 2);
ERROR HY000: Can't initialize function 'ba_concat_all'; ba_concat_all: argument 0 must be BYTEARRAY
# Clean up
DROP TABLE test_bytearray;
# Uninstall the extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,44 @@ SELECT CONCAT("'", vsql_simple.ba_concat(t1.data, t2.data), "'") as concatenated
FROM test_bytearray t1, test_bytearray t2
WHERE t1.id = 1 AND t2.id = 2;

--echo # Test ba_len function (zero-arity)
SELECT vsql_simple.ba_len() as len;

--echo # Test ba_len rejects arguments
--error ER_VILLAGESQL_GENERIC_ERROR
SELECT vsql_simple.ba_len(1);

--echo # Test ba_concat rejects wrong argument count
--error ER_VILLAGESQL_GENERIC_ERROR
SELECT vsql_simple.ba_concat(t1.data) FROM test_bytearray t1 WHERE t1.id = 1;

--echo # Test ba_concat_all function (varargs, uses prerun escape hatch)
--echo # Single argument
SELECT CONCAT("'", vsql_simple.ba_concat_all(t1.data), "'") as result
FROM test_bytearray t1 WHERE t1.id = 1;

--echo # Two arguments (same as ba_concat)
SELECT CONCAT("'", vsql_simple.ba_concat_all(t1.data, t2.data), "'") as result
FROM test_bytearray t1, test_bytearray t2
WHERE t1.id = 1 AND t2.id = 2;

--echo # Three arguments
SELECT CONCAT("'", vsql_simple.ba_concat_all(t1.data, t2.data, t3.data), "'") as result
FROM test_bytearray t1, test_bytearray t2, test_bytearray t3
WHERE t1.id = 1 AND t2.id = 2 AND t3.id = 3;

--echo # NULL handling: any NULL argument returns NULL
SELECT vsql_simple.ba_concat_all(t1.data, NULL) as result
FROM test_bytearray t1 WHERE t1.id = 1;

--echo # ba_concat_all rejects zero arguments
--error ER_CANT_INITIALIZE_UDF
SELECT vsql_simple.ba_concat_all();

--echo # ba_concat_all rejects non-BYTEARRAY arguments (INT)
--error ER_CANT_INITIALIZE_UDF
SELECT vsql_simple.ba_concat_all(1, 2);

--echo # Clean up
DROP TABLE test_bytearray;

Expand Down
6 changes: 5 additions & 1 deletion scripts/villint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,12 @@ if [ -z "$COMMIT_ISH" ]; then
if is_jj_workspace; then
# In jj workspace, use git.push config or default to "origin"
# User can set: jj config set git.push "github"
# We diff against the fork point with main@<remote>, not the tip — so when
# upstream main is ahead of our branch's base, upstream commits do not
# appear as "reverse-modified" files in our changed-file set (which would
# otherwise cause villint to lint unrelated upstream-only files).
VILLINT_REMOTE=$(jj config get git.push 2>/dev/null || echo "origin")
COMMIT_ISH="main@$VILLINT_REMOTE"
COMMIT_ISH="fork_point(@ | main@$VILLINT_REMOTE)"
echo "Using jj workspace, comparing against: $COMMIT_ISH"
else
# Default: compare against merge-base with origin/main
Expand Down
96 changes: 77 additions & 19 deletions villagesql/examples/vsql-simple/src/extension.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,57 @@ void ba_concat(CustomArg a, CustomArg b, StringResult out) {
out.set_length(kBytearrayLen * 2);
}

// BA_LEN: return the fixed length of a BYTEARRAY (zero-arity constant function)
void ba_len(IntResult out) { out.set(static_cast<long long>(kBytearrayLen)); }

// BA_CONCAT_ALL: concatenate any number of bytearrays (returns STRING).
//
// This demonstrates the "Escape Hatch" pattern: ba_concat above uses the
// typed Happy Path API (CustomArg, StringResult), but only supports exactly
// two arguments. ba_concat_all drops down to the raw ABI to accept varargs,
// using prerun to validate argument types and size the result buffer.

// Prerun: validate that all arguments are BYTEARRAY and request a buffer
// large enough to hold them all concatenated.
void ba_concat_all_prerun(vef_context_t *, vef_prerun_args_t *args,
vef_prerun_result_t *result) {
if (args->arg_count == 0) {
result->type = VEF_RESULT_ERROR;
snprintf(result->error_msg, VEF_MAX_ERROR_LEN,
"ba_concat_all requires at least one argument");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please test these errors.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done (extension_simple_type_usage.result)

return;
}
for (unsigned int i = 0; i < args->arg_count; i++) {
// NULL literals appear as VEF_TYPE_STRING in prerun; skip type check for
// those and handle them at runtime.
vef_type_id id = args->arg_types[i].id;
if (id != VEF_TYPE_CUSTOM && id != VEF_TYPE_STRING) {
result->type = VEF_RESULT_ERROR;
snprintf(result->error_msg, VEF_MAX_ERROR_LEN,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (see above)

"ba_concat_all: argument %u must be BYTEARRAY", i);
return;
}
}
result->result_buffer_size = args->arg_count * kBytearrayLen;
}

// Main function: raw ABI signature since we iterate over a variable number
// of arguments.
void ba_concat_all(vef_context_t *ctx, vef_vdf_args_t *args,
vef_vdf_result_t *result) {
size_t total_len = args->value_count * kBytearrayLen;
for (unsigned int i = 0; i < args->value_count; i++) {
vef_invalue_t val = vsql::func_builder::get_invalue(ctx, args, i);
if (val.is_null) {
result->type = VEF_RESULT_NULL;
return;
}
memcpy(result->str_buf + i * kBytearrayLen, val.bin_value, kBytearrayLen);
}
result->type = VEF_RESULT_VALUE;
result->actual_len = total_len;
}

static constexpr const char kBytearrayTypeName[] = "bytearray";

constexpr auto BYTEARRAY = vsql::make_type<kBytearrayTypeName>()
Expand All @@ -136,22 +187,29 @@ constexpr auto BYTEARRAY = vsql::make_type<kBytearrayTypeName>()
.compare<&bytearray_compare>()
.build();

VEF_GENERATE_ENTRY_POINTS(make_extension()
.type(BYTEARRAY)
.func(make_func<&rot13>("rot13")
.returns(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&even_chars>("even_chars")
.returns(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&odd_chars>("odd_chars")
.returns(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&ba_concat>("ba_concat")
.returns(STRING)
.param(BYTEARRAY)
.param(BYTEARRAY)
.build()))
VEF_GENERATE_ENTRY_POINTS(
make_extension()
.type(BYTEARRAY)
.func(make_func<&rot13>("rot13")
.returns(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&even_chars>("even_chars")
.returns(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&odd_chars>("odd_chars")
.returns(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&ba_concat>("ba_concat")
.returns(STRING)
.param(BYTEARRAY)
.param(BYTEARRAY)
.build())
.func(make_func<&ba_len>("ba_len").returns(INT).param().build())
.func(make_func<&ba_concat_all>("ba_concat_all")
.returns(STRING)
.varargs()
.prerun<&ba_concat_all_prerun>()
.build()))
6 changes: 6 additions & 0 deletions villagesql/sdk/include/villagesql/abi/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef VILLAGESQL_ABI_TYPES_H_
#define VILLAGESQL_ABI_TYPES_H_

#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
Expand Down Expand Up @@ -499,6 +500,11 @@ typedef struct {
const char *custom_type;
} vef_type_t;

// Sentinel value for vef_signature_t::param_count indicating that the function
// accepts a variable number of arguments. When set, the framework skips
// argument count validation and delegates to the prerun function instead.
#define VEF_PARAM_VARARGS UINT_MAX

typedef struct {
unsigned int param_count;
const vef_type_t *params;
Expand Down
5 changes: 4 additions & 1 deletion villagesql/sdk/include/villagesql/detail/vef_register.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ __attribute__((visibility("hidden"))) vef_func_desc_t *materialize_func_desc(
static vef_func_desc_t desc;

signature.param_count = static_cast<unsigned int>(func_data.num_params());
signature.params = func_data.num_params() > 0 ? func_data.params() : nullptr;
signature.params = (func_data.num_params() > 0 &&
func_data.num_params() != VEF_PARAM_VARARGS)
? func_data.params()
: nullptr;
signature.return_type = func_data.return_type();

desc.protocol = VEF_PROTOCOL_2;
Expand Down
Loading
Loading