diff --git a/unittest/gunit/villagesql/CMakeLists.txt b/unittest/gunit/villagesql/CMakeLists.txt index 4213298ceda..157d5eb570f 100644 --- a/unittest/gunit/villagesql/CMakeLists.txt +++ b/unittest/gunit/villagesql/CMakeLists.txt @@ -20,6 +20,7 @@ SET(VILLAGESQL_UNIT_TESTS abi_v1_check-t abi_v2_check-t custom_indexes-t + extension_uninstall_checks-t semver-t type_context-t type_descriptor-t @@ -63,6 +64,7 @@ ENDMACRO() ADD_VILLAGESQL_TEST(abi_v1_check-t) ADD_VILLAGESQL_TEST(abi_v2_check-t) ADD_VILLAGESQL_TEST(custom_indexes-t) +ADD_VILLAGESQL_TEST(extension_uninstall_checks-t) ADD_VILLAGESQL_TEST(semver-t) ADD_VILLAGESQL_TEST(type_context-t) ADD_VILLAGESQL_TEST(type_descriptor-t) diff --git a/unittest/gunit/villagesql/extension_uninstall_checks-t.cc b/unittest/gunit/villagesql/extension_uninstall_checks-t.cc new file mode 100644 index 00000000000..ef8511a67b5 --- /dev/null +++ b/unittest/gunit/villagesql/extension_uninstall_checks-t.cc @@ -0,0 +1,84 @@ +/* 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 . + */ + +// TODO(villagesql-indexing): When CREATE INDEX ... USING EXTENDED is wired +// end-to-end through VEF, add a functional mysql-test +// (custom_index_prevents_extension_uninstall.test) that exercises this +// gating through the SQL surface. Until then, the unit tests below pin the +// predicate directly. + +#include + +#include +#include + +#include "unittest/gunit/test_utils.h" +#include "villagesql/schema/systable/custom_indexes.h" +#include "villagesql/schema/systable/extensions.h" +#include "villagesql/schema/systable/helpers.h" +#include "villagesql/veb/extension_uninstall_checks.h" + +namespace villagesql_unittest { + +using namespace villagesql; + +class ExtensionUninstallChecksTest : public ::testing::Test { + protected: + void SetUp() override { + villagesql::test_set_lower_case_table_names(0); + system_charset_info = &my_charset_utf8mb4_0900_ai_ci; + } +}; + +TEST_F(ExtensionUninstallChecksTest, CustomIndexPreventsExtensionUninstall) { + ExtensionEntry ext(ExtensionKey("vsql_test"), "1.0.0", /*hash=*/{}); + + IndexEntry idx(IndexKey("mydb", "t", "idx"), /*id=*/1, "vsql_test", "1.0.0", + "test_idx"); + std::vector all_indexes = {&idx}; + + EXPECT_TRUE(check_for_indexes_of_extension(ext, all_indexes)); +} + +TEST_F(ExtensionUninstallChecksTest, + OtherExtensionsIndexDoesNotPreventUninstall) { + ExtensionEntry ext(ExtensionKey("vsql_test"), "1.0.0", /*hash=*/{}); + + IndexEntry idx(IndexKey("mydb", "t", "idx"), /*id=*/1, "other_ext", "1.0.0", + "test_idx"); + std::vector all_indexes = {&idx}; + + EXPECT_FALSE(check_for_indexes_of_extension(ext, all_indexes)); +} + +TEST_F(ExtensionUninstallChecksTest, VersionMismatchDoesNotPreventUninstall) { + ExtensionEntry ext(ExtensionKey("vsql_test"), "2.0.0", /*hash=*/{}); + + IndexEntry idx(IndexKey("mydb", "t", "idx"), /*id=*/1, "vsql_test", "1.0.0", + "test_idx"); + std::vector all_indexes = {&idx}; + + EXPECT_FALSE(check_for_indexes_of_extension(ext, all_indexes)); +} + +TEST_F(ExtensionUninstallChecksTest, NoIndexesIsAllowed) { + ExtensionEntry ext(ExtensionKey("vsql_test"), "1.0.0", /*hash=*/{}); + std::vector all_indexes = {}; + + EXPECT_FALSE(check_for_indexes_of_extension(ext, all_indexes)); +} + +} // namespace villagesql_unittest diff --git a/villagesql/veb/CMakeLists.txt b/villagesql/veb/CMakeLists.txt index 2b2f59f72f4..86d1db9f6e9 100644 --- a/villagesql/veb/CMakeLists.txt +++ b/villagesql/veb/CMakeLists.txt @@ -14,6 +14,7 @@ # along with this program; if not, see . SET(VILLAGESQL_VEB_SOURCES + extension_uninstall_checks.cc register.cc sql_extension.cc validate.cc diff --git a/villagesql/veb/extension_uninstall_checks.cc b/villagesql/veb/extension_uninstall_checks.cc new file mode 100644 index 00000000000..d279d15d513 --- /dev/null +++ b/villagesql/veb/extension_uninstall_checks.cc @@ -0,0 +1,52 @@ +/* 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 . + */ + +#include "villagesql/veb/extension_uninstall_checks.h" + +#include "my_sys.h" + +#include "villagesql/include/error.h" + +namespace villagesql { + +bool check_for_indexes_of_extension( + const ExtensionEntry &ext_entry, + const std::vector &all_indexes) { + const IndexEntry *first = nullptr; + int count = 0; + + for (const auto *entry : all_indexes) { + if (entry->extension_name == ext_entry.extension_name() && + entry->extension_version == ext_entry.extension_version) { + if (count == 0) first = entry; + count++; + } + } + + if (first != nullptr) { + villagesql_error( + "Cannot drop extension `%s` as %d custom index(es) depend on it, " + "e.g. %s.%s.%s uses index type %s", + MYF(0), ext_entry.extension_name().c_str(), count, + first->db_name().c_str(), first->table_name().c_str(), + first->index_name().c_str(), first->index_type_name.c_str()); + return true; + } + + return false; +} + +} // namespace villagesql diff --git a/villagesql/veb/extension_uninstall_checks.h b/villagesql/veb/extension_uninstall_checks.h new file mode 100644 index 00000000000..3b21d884f05 --- /dev/null +++ b/villagesql/veb/extension_uninstall_checks.h @@ -0,0 +1,37 @@ +/* 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 . + */ + +#ifndef VILLAGESQL_VEB_EXTENSION_UNINSTALL_CHECKS_H_ +#define VILLAGESQL_VEB_EXTENSION_UNINSTALL_CHECKS_H_ + +#include + +#include "villagesql/schema/systable/custom_indexes.h" +#include "villagesql/schema/systable/extensions.h" + +namespace villagesql { + +// Returns true and emits villagesql_error if any IndexEntry in +// `all_indexes` belongs to `ext_entry` (matched on extension_name + +// extension_version). RESTRICT semantics: the caller aborts uninstall when +// this returns true. No system-table mutations occur. +bool check_for_indexes_of_extension( + const ExtensionEntry &ext_entry, + const std::vector &all_indexes); + +} // namespace villagesql + +#endif // VILLAGESQL_VEB_EXTENSION_UNINSTALL_CHECKS_H_ diff --git a/villagesql/veb/sql_extension.cc b/villagesql/veb/sql_extension.cc index b03e9441b06..eb16dae6b27 100644 --- a/villagesql/veb/sql_extension.cc +++ b/villagesql/veb/sql_extension.cc @@ -54,6 +54,7 @@ #include "villagesql/services/status_vars.h" #include "villagesql/services/sys_vars.h" #include "villagesql/sql/metadata_modifier.h" +#include "villagesql/veb/extension_uninstall_checks.h" #include "villagesql/veb/register.h" #include "villagesql/veb/validate.h" #include "villagesql/veb/veb_file.h" @@ -412,6 +413,11 @@ bool remove_extension_from_victionary( return true; } + const auto &all_indexes = victionary.custom_indexes().get_all_committed(); + if (villagesql::check_for_indexes_of_extension(*ext_entry, all_indexes)) { + return true; + } + // Check for active references to VDFs, TypeContexts, and TypeDescriptors. // A use_count > 1 means something other than Victionary holds a reference // (e.g., an executing query).