diff --git a/mysql-test/suite/villagesql/extension/r/type_method_name_validation.result b/mysql-test/suite/villagesql/extension/r/type_method_name_validation.result
deleted file mode 100644
index 9d045fc9270..00000000000
--- a/mysql-test/suite/villagesql/extension/r/type_method_name_validation.result
+++ /dev/null
@@ -1,21 +0,0 @@
-call mtr.add_suppression("Orphaned expansion directory found but not removed");
-call mtr.add_suppression("uses '::' but prefix");
-call mtr.add_suppression("is not a valid type method");
-call mtr.add_suppression("Failed to install extension");
-# restart: --veb-dir=MYSQLTEST_VARDIR/veb
-# Test 1: bad prefix — WRONGTYPE::encode for type MYTYPE
-Creating extension bad_colon_prefix using SDK...
-Created bad_colon_prefix.veb
-INSTALL EXTENSION bad_colon_prefix;
-ERROR HY000: Failed to install extension 'bad_colon_prefix': type 'MYTYPE' failed validation
-# Verify extension was NOT installed
-SELECT * FROM INFORMATION_SCHEMA.EXTENSIONS;
-EXTENSION_NAME EXTENSION_VERSION
-# Test 2: bad suffix — MYTYPE::transform for type MYTYPE
-Creating extension bad_colon_suffix using SDK...
-Created bad_colon_suffix.veb
-INSTALL EXTENSION bad_colon_suffix;
-ERROR HY000: Failed to install extension 'bad_colon_suffix': type 'MYTYPE' failed validation
-# Verify extension was NOT installed
-SELECT * FROM INFORMATION_SCHEMA.EXTENSIONS;
-EXTENSION_NAME EXTENSION_VERSION
diff --git a/mysql-test/suite/villagesql/extension/t/type_method_name_validation.test b/mysql-test/suite/villagesql/extension/t/type_method_name_validation.test
deleted file mode 100644
index 6a8951e0733..00000000000
--- a/mysql-test/suite/villagesql/extension/t/type_method_name_validation.test
+++ /dev/null
@@ -1,38 +0,0 @@
-# Test install-time validation of :: names in type method VDFs.
-# Verifies that INSTALL EXTENSION fails when a type's VDF name uses :: but
-# the prefix doesn't match the type name or the suffix is not a valid method.
-
---let $veb_dir = $MYSQLTEST_VARDIR/veb
---exec mkdir -p $veb_dir
-
-call mtr.add_suppression("Orphaned expansion directory found but not removed");
-call mtr.add_suppression("uses '::' but prefix");
-call mtr.add_suppression("is not a valid type method");
-call mtr.add_suppression("Failed to install extension");
---replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
---let $restart_parameters = restart: --veb-dir=$veb_dir
---source include/restart_mysqld.inc
-
---echo # Test 1: bad prefix — WRONGTYPE::encode for type MYTYPE
---let $extension_name = bad_colon_prefix
---let $extension_version = 0.0.1-devtest
---let $extension_source = $MYSQL_TEST_DIR/suite/villagesql/std_data/bad_colon_prefix.cc
---source include/villagesql/create_extension_sdk.inc
-
---error ER_VILLAGESQL_GENERIC_ERROR
-INSTALL EXTENSION bad_colon_prefix;
-
---echo # Verify extension was NOT installed
-SELECT * FROM INFORMATION_SCHEMA.EXTENSIONS;
-
---echo # Test 2: bad suffix — MYTYPE::transform for type MYTYPE
---let $extension_name = bad_colon_suffix
---let $extension_version = 0.0.1-devtest
---let $extension_source = $MYSQL_TEST_DIR/suite/villagesql/std_data/bad_colon_suffix.cc
---source include/villagesql/create_extension_sdk.inc
-
---error ER_VILLAGESQL_GENERIC_ERROR
-INSTALL EXTENSION bad_colon_suffix;
-
---echo # Verify extension was NOT installed
-SELECT * FROM INFORMATION_SCHEMA.EXTENSIONS;
diff --git a/mysql-test/suite/villagesql/std_data/bad_colon_prefix.cc b/mysql-test/suite/villagesql/std_data/bad_colon_prefix.cc
deleted file mode 100644
index 63cb8bfe631..00000000000
--- a/mysql-test/suite/villagesql/std_data/bad_colon_prefix.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Copyright (c) 2026 VillageSQL Contributors
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see .
- */
-
-// Bad extension: registers type MYTYPE but names its encode VDF
-// "WRONGTYPE::from_string". The :: prefix does not match the type name, so
-// INSTALL EXTENSION must fail.
-
-#include
-
-#include
-
-bool mytype_encode(std::string_view from, vsql::Span buf,
- size_t *length) {
- if (buf.size() < 4) return true;
- size_t to_copy = from.size() < buf.size() ? from.size() : buf.size();
- memcpy(buf.data(), from.data(), to_copy);
- *length = to_copy;
- return false;
-}
-
-bool mytype_decode(vsql::Span data,
- vsql::Span out, size_t *out_len) {
- if (data.size() == 0) return true;
- size_t to_copy = data.size() < out.size() ? data.size() : out.size();
- memcpy(out.data(), data.data(), to_copy);
- *out_len = to_copy;
- return false;
-}
-
-int mytype_compare(vsql::Span a,
- vsql::Span b) {
- size_t min_len = a.size() < b.size() ? a.size() : b.size();
- int r = memcmp(a.data(), b.data(), min_len);
- if (r != 0) return r;
- if (a.size() < b.size()) return -1;
- if (a.size() > b.size()) return 1;
- return 0;
-}
-
-using villagesql::vsql::make_extension;
-using namespace villagesql::func_builder;
-using namespace villagesql::type_builder;
-
-// MYTYPE is the type name, but the encode VDF is named
-// "WRONGTYPE::from_string". The prefix "WRONGTYPE" does not match "MYTYPE", so
-// installation must fail.
-VEF_GENERATE_ENTRY_POINTS(
- make_extension()
- .type(make_type("MYTYPE")
- .persisted_length(16)
- .max_decode_buffer_length(64)
- .encode("WRONGTYPE::from_string")
- .decode("MYTYPE::decode")
- .compare("MYTYPE::compare")
- .build())
- .func(make_type_encode<&mytype_encode>("WRONGTYPE::from_string",
- "MYTYPE"))
- .func(make_type_decode<&mytype_decode>("MYTYPE::decode", "MYTYPE"))
- .func(make_type_compare<&mytype_compare>("MYTYPE::compare", "MYTYPE")))
diff --git a/mysql-test/suite/villagesql/std_data/bad_colon_suffix.cc b/mysql-test/suite/villagesql/std_data/bad_colon_suffix.cc
deleted file mode 100644
index bfacf18e158..00000000000
--- a/mysql-test/suite/villagesql/std_data/bad_colon_suffix.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-/* Copyright (c) 2026 VillageSQL Contributors
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see .
- */
-
-// Bad extension: registers type MYTYPE but names its encode VDF
-// "MYTYPE::transform". The :: suffix "transform" is not a valid type method
-// name, so INSTALL EXTENSION must fail.
-
-#include
-
-#include
-
-bool mytype_encode(std::string_view from, vsql::Span buf,
- size_t *length) {
- if (buf.size() < 4) return true;
- size_t to_copy = from.size() < buf.size() ? from.size() : buf.size();
- memcpy(buf.data(), from.data(), to_copy);
- *length = to_copy;
- return false;
-}
-
-bool mytype_decode(vsql::Span data,
- vsql::Span out, size_t *out_len) {
- if (data.size() == 0) return true;
- size_t to_copy = data.size() < out.size() ? data.size() : out.size();
- memcpy(out.data(), data.data(), to_copy);
- *out_len = to_copy;
- return false;
-}
-
-int mytype_compare(vsql::Span a,
- vsql::Span b) {
- size_t min_len = a.size() < b.size() ? a.size() : b.size();
- int r = memcmp(a.data(), b.data(), min_len);
- if (r != 0) return r;
- if (a.size() < b.size()) return -1;
- if (a.size() > b.size()) return 1;
- return 0;
-}
-
-using villagesql::vsql::make_extension;
-using namespace villagesql::func_builder;
-using namespace villagesql::type_builder;
-
-// MYTYPE is the type name, but the encode VDF is named "MYTYPE::transform".
-// The suffix "transform" is not a valid type method (must be encode, decode,
-// compare, or hash), so installation must fail.
-VEF_GENERATE_ENTRY_POINTS(
- make_extension()
- .type(make_type("MYTYPE")
- .persisted_length(16)
- .max_decode_buffer_length(64)
- .encode("MYTYPE::transform")
- .decode("MYTYPE::decode")
- .compare("MYTYPE::compare")
- .build())
- .func(make_type_encode<&mytype_encode>("MYTYPE::transform", "MYTYPE"))
- .func(make_type_decode<&mytype_decode>("MYTYPE::decode", "MYTYPE"))
- .func(make_type_compare<&mytype_compare>("MYTYPE::compare", "MYTYPE")))
diff --git a/unittest/gunit/villagesql/validate-t.cc b/unittest/gunit/villagesql/validate-t.cc
index cf2ccdb86ca..8966ace52a7 100644
--- a/unittest/gunit/villagesql/validate-t.cc
+++ b/unittest/gunit/villagesql/validate-t.cc
@@ -287,6 +287,108 @@ TEST_F(ValidateExtensionRegistrationTest,
EXPECT_EQ(result->funcs.size(), 1u);
}
+// A v2 type whose encode_vdf_name is malformed fails validation.
+TEST_F(ValidateExtensionRegistrationTest, V2TypeBadVdfName) {
+ struct TestCase {
+ const char *bad_name;
+ const char *expected_error;
+ };
+
+ const TestCase cases[] = {
+ {"MYTYPE::transform",
+ "type 'MYTYPE' failed validation"}, // unrecognised method suffix
+ {"WRONGTYPE::from_string",
+ "type 'MYTYPE' failed validation"}, // prefix does not match type name
+ {"::MYTPE::transform", "type 'MYTYPE' failed validation"},
+ {"MYTPE::::transform", "type 'MYTYPE' failed validation"},
+ };
+
+ for (const auto &tc : cases) {
+ SCOPED_TRACE(std::string("bad_name=") + tc.bad_name);
+
+ vef_type_desc_t td = {};
+ td.protocol = VEF_PROTOCOL_2;
+ td.name = "MYTYPE";
+ td.persisted_length = 16;
+ td.max_decode_buffer_length = 256;
+ td.encode_vdf_name = tc.bad_name;
+
+ vef_type_desc_t *types[] = {&td};
+ vef_registration_t reg = {};
+ reg.protocol = VEF_PROTOCOL_2;
+ reg.deprecated_extension_name = "my_ext";
+ reg.type_count = 1;
+ reg.types = types;
+
+ std::string error;
+ auto result = villagesql::veb::validate_extension_registration(
+ make_ext_reg(®, VEF_PROTOCOL_2), "my_ext", "1.0.0", error);
+
+ EXPECT_FALSE(result.has_value());
+ EXPECT_EQ(error, tc.expected_error);
+ }
+}
+
+// A v2 type registration using valid VDF names succeeds end-to-end.
+TEST_F(ValidateExtensionRegistrationTest, V2TypeValidVdfNames) {
+ vef_type_desc_t td = {};
+ td.protocol = VEF_PROTOCOL_2;
+ td.name = "MYTYPE";
+ td.persisted_length = 16;
+ td.max_decode_buffer_length = 256;
+ td.encode_vdf_name = "MYTYPE::from_string";
+ td.decode_vdf_name = "MYTYPE::to_string";
+ td.compare_vdf_name = "MYTYPE::compare";
+
+ vef_type_t str_type = {VEF_TYPE_STRING, nullptr};
+ vef_type_t int_type = {VEF_TYPE_INT, nullptr};
+ vef_type_t custom_mytype = {VEF_TYPE_CUSTOM, "MYTYPE"};
+
+ vef_type_t encode_params[] = {str_type};
+ vef_signature_t encode_sig = {1, encode_params, custom_mytype};
+ vef_func_desc_t encode_fd = {};
+ encode_fd.protocol = VEF_PROTOCOL_2;
+ encode_fd.name = "MYTYPE::from_string";
+ encode_fd.signature = &encode_sig;
+ encode_fd.vdf = stub_vdf;
+
+ vef_type_t decode_params[] = {custom_mytype};
+ vef_signature_t decode_sig = {1, decode_params, str_type};
+ vef_func_desc_t decode_fd = {};
+ decode_fd.protocol = VEF_PROTOCOL_2;
+ decode_fd.name = "MYTYPE::to_string";
+ decode_fd.signature = &decode_sig;
+ decode_fd.vdf = stub_vdf;
+
+ vef_type_t compare_params[] = {custom_mytype, custom_mytype};
+ vef_signature_t compare_sig = {2, compare_params, int_type};
+ vef_func_desc_t compare_fd = {};
+ compare_fd.protocol = VEF_PROTOCOL_2;
+ compare_fd.name = "MYTYPE::compare";
+ compare_fd.signature = &compare_sig;
+ compare_fd.vdf = stub_vdf;
+
+ vef_type_desc_t *types[] = {&td};
+ vef_func_desc_t *funcs[] = {&encode_fd, &decode_fd, &compare_fd};
+
+ vef_registration_t reg = {};
+ reg.protocol = VEF_PROTOCOL_2;
+ reg.deprecated_extension_name = "my_ext";
+ reg.type_count = 1;
+ reg.types = types;
+ reg.func_count = 3;
+ reg.funcs = funcs;
+
+ std::string error;
+ auto result = villagesql::veb::validate_extension_registration(
+ make_ext_reg(®, VEF_PROTOCOL_2), "my_ext", "1.0.0", error);
+
+ ASSERT_TRUE(result.has_value());
+ EXPECT_TRUE(error.empty());
+ ASSERT_EQ(result->types.size(), 1u);
+ EXPECT_EQ(result->types[0].type_name(), "MYTYPE");
+}
+
// Multiple types and funcs are all validated and returned.
TEST_F(ValidateExtensionRegistrationTest, MultipleTypesAndFuncs) {
vef_type_desc_t td1 = make_v1_type("TYPE_A");