diff --git a/.github/workflows/validation-rust.yaml b/.github/workflows/validation-rust.yaml index c814d362962f3..de831680c779e 100644 --- a/.github/workflows/validation-rust.yaml +++ b/.github/workflows/validation-rust.yaml @@ -157,7 +157,7 @@ jobs: run: | rm -rf target/doc/ rust/target/doc/ cargo doc --manifest-path rust/mariadb/Cargo.toml --no-deps - cargo doc --manifest-path rust/bindings/Cargo.toml --no-deps + cargo doc --manifest-path rust/mariadb-sys/Cargo.toml --no-deps - run: | echo `pwd`/rust/target/doc >> $GITHUB_PATH # fake index.html so github likes us diff --git a/.gitignore b/.gitignore index a60f2264bc6a0..7e4b5ffb6c160 100644 --- a/.gitignore +++ b/.gitignore @@ -629,4 +629,5 @@ versioninfo_exe.rc win/packaging/ca/symlinks.cc # rust output -rust/target +target/ +rust_target diff --git a/CMakeLists.txt b/CMakeLists.txt index df8e7cac6fd84..107f1c54dbe6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,7 +164,6 @@ INCLUDE(pcre) INCLUDE(libfmt) INCLUDE(ctest) INCLUDE(plugin) -INCLUDE(rust) INCLUDE(install_macros) INCLUDE(systemd) INCLUDE(mysql_add_executable) @@ -448,7 +447,6 @@ MARK_AS_ADVANCED(PYTHON_SHEBANG) # Add storage engines and plugins. CONFIGURE_PLUGINS() -CONFIGURE_RUST_PLUGINS() ADD_SUBDIRECTORY(include) ADD_SUBDIRECTORY(dbug) @@ -460,6 +458,7 @@ ADD_SUBDIRECTORY(client) ADD_SUBDIRECTORY(extra) ADD_SUBDIRECTORY(libservices) ADD_SUBDIRECTORY(sql/share) +ADD_SUBDIRECTORY(rust) IF(NOT WITHOUT_SERVER) ADD_SUBDIRECTORY(tests) diff --git a/rust/Cargo.lock b/Cargo.lock similarity index 97% rename from rust/Cargo.lock rename to Cargo.lock index 59b5106a79bba..9ecc9182fba02 100644 --- a/rust/Cargo.lock +++ b/Cargo.lock @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.68.1" +version = "0.69.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" dependencies = [ "bitflags", "cexpr", @@ -443,15 +443,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" -[[package]] -name = "doxygen-rs" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" -dependencies = [ - "phf", -] - [[package]] name = "ecdsa" version = "0.16.9" @@ -866,6 +857,7 @@ dependencies = [ "log", "mariadb-macros", "mariadb-sys", + "strum", "time", ] @@ -886,7 +878,8 @@ version = "0.1.0" dependencies = [ "bindgen", "cmake", - "doxygen-rs", + "regex", + "walkdir", ] [[package]] @@ -1029,48 +1022,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -1283,12 +1234,27 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "sct" version = "0.7.1" @@ -1394,12 +1360,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "spin" version = "0.9.8" @@ -1416,12 +1376,48 @@ dependencies = [ "der", ] +[[package]] +name = "storage-csv" +version = "0.1.0" +dependencies = [ + "mariadb", +] + +[[package]] +name = "storage-simple" +version = "0.1.0" +dependencies = [ + "mariadb", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.5.0" @@ -1622,6 +1618,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000000..00badefc469b2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +# Define our crates within a workspace + +[workspace] +resolver = "2" +members = [ + "rust/mariadb-sys", + "rust/mariadb", + "rust/macros", + "rust/common/encryption-common", + "rust/examples/keymgt-basic-rs", + "rust/examples/keymgt-debug-rs", + "rust/examples/encryption-simple-rs", + "rust/examples/encryption-aes-rs", + "rust/examples/ftparser-simple", + "rust/examples/storage-simple", + "rust/examples/storage-csv", + "rust/plugins/keymgt-clevis", + "rust/plugins/encryption-file-chacha", + "rust/test-runner", +] diff --git a/cmake/rust.cmake b/cmake/rust.cmake deleted file mode 100644 index 4056a7855e19e..0000000000000 --- a/cmake/rust.cmake +++ /dev/null @@ -1,240 +0,0 @@ -# TODO: just parse any needed configuration from cargo.toml -# CMAKE_PARSE_ARGUMENTS(ARG -# "STORAGE_ENGINE;STATIC_ONLY;MODULE_ONLY;MANDATORY;DEFAULT;DISABLED;NOT_EMBEDDED;RECOMPILE_FOR_EMBEDDED;CLIENT" -# "MODULE_OUTPUT_NAME;STATIC_OUTPUT_NAME;COMPONENT;CONFIG;VERSION" -# "LINK_LIBRARIES;DEPENDS" -# ${ARGN} -# ) - -# BUG: for some reason this doesn't always invoke cargo to recompile - -macro(CONFIGURE_RUST_PLUGINS) - set(rust_dir "${CMAKE_SOURCE_DIR}/rust") - set(cargo_target_dir "${CMAKE_CURRENT_BINARY_DIR}/rust_target") - message(STATUS "rust dir ${rust_dir}") - - execute_process(COMMAND python3 "${rust_dir}/cmake_helper.py" OUTPUT_VARIABLE plugins) - - # Add common include directories - INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/sql - ${PCRE_INCLUDES} - ${SSL_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIR}) - - # find_library(servlib NAMES "services") - # message("LIBPATH ${CMAKE_LIBRARY_PATH} FINDLIBS ${servlib}") - - # See cmake_helper.py for the output that we get here. We loop through each - # plugin - foreach(entry IN LISTS plugins) - string(REPLACE "|" ";" entry "${entry}") - list(GET entry 0 cache_name) - list(GET entry 1 target_name) - list(GET entry 2 cargo_name) - list(GET entry 3 staticlib_name) - list(GET entry 4 dylib_name_out) - list(GET entry 5 dylib_name_final) - set(output_path "${cargo_target_dir}/release") - - # Copied from plugin.cmake, set default `howtobuild` - if(ARG_DISABLED) - set(howtobuild NO) - elseif(compat STREQUAL ".") - set(howtobuild DYNAMIC) - elseif(compat STREQUAL "with.") - if(NOT ARG_MODULE_ONLY) - set(howtobuild STATIC) - ELSE() - set(howtobuild DYNAMIC) - endif() - elseif(compat STREQUAL ".without") - set(howtobuild NO) - elseif(compat STREQUAL "with.without") - set(howtobuild STATIC) - else() - set(howtobuild DYNAMIC) - endif() - - - # NO - not at all - # YES - static if possible, otherwise dynamic if possible, otherwise abort - # AUTO - static if possible, otherwise dynamic, if possible - # STATIC - static if possible, otherwise not at all - # DYNAMIC - dynamic if possible, otherwise not at all - set(${cache_name} ${howtobuild} CACHE STRING - "How to build plugin ${cargo_name}. Options are: NO STATIC DYNAMIC YES AUTO.") - - if(NOT ${${cache_name}} MATCHES "^(NO|YES|AUTO|STATIC|DYNAMIC)$") - message(FATAL_ERROR "Invalid value t${cache_name} for ${cache_name}") - endif() - - set(cargo_cmd - cargo rustc - --target-dir=${cargo_target_dir} - --package=${cargo_name} - --locked - --quiet - ) - - set(rustc_extra_args -L "native=${CMAKE_CURRENT_BINARY_DIR}/libservices") - - # Configure debug/release options - if(CMAKE_BUILD_TYPE MATCHES "Debug") - set(cargo_cmd ${cargo_cmd} --profile=dev) - set(output_path "${cargo_target_dir}/debug") - elseif(CMAKE_BUILD_TYPE MATCHES "Release") - set(cargo_cmd ${cargo_cmd} --profile=release) - elseif(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") - set(cargo_cmd ${cargo_cmd} --profile=release) - elseif(CMAKE_BUILD_TYPE MATCHES "MinSizeRel") - set(cargo_cmd ${cargo_cmd} --profile=release) - set(rustc_extra_args ${rustc_extra_args} -C strip=debuginfo) - endif() - - set(dylib_path "${output_path}/${dylib_name_out}") - - # Used by build.rs - set(env_args -E env CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} - CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR} - ) - - if(NOT ARG_MODULE_OUTPUT_NAME) - if(ARG_STORAGE_ENGINE) - set(ARG_MODULE_OUTPUT_NAME "ha_${target_name}") - else() - set(ARG_MODULE_OUTPUT_NAME "${target_name}") - endif() - endif() - - if( - ${${cache_name}} MATCHES "(STATIC|AUTO|YES)" AND NOT ARG_MODULE_ONLY - AND NOT ARG_CLIENT - ) - message(STATUS "configuring rust plugin ${target_name} as static") - - # Build a staticlib - if(CMAKE_GENERATOR MATCHES "Makefiles|Ninja") - # If there is a shared library from previous shared build, - # remove it. This is done just for mysql-test-run.pl - # so it does not try to use stale shared lib as plugin - # in test. - file(REMOVE - ${CMAKE_CURRENT_BINARY_DIR}/${ARG_MODULE_OUTPUT_NAME}${CMAKE_SHARED_MODULE_SUFFIX}) - endif() - - - add_custom_target(${target_name} - # We set make_static_lib to generate the correct symbols - # equivalent of `COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN$...` for C plugins - # Todos: - # TARGET_LINK_LIBRARIES (${target} mysqlservices ${ARG_LINK_LIBRARIES}) - COMMAND ${CMAKE_COMMAND} ${env_args} - ${cargo_cmd} --crate-type=staticlib - -- ${rustc_extra_args} --cfg=make_static_lib - WORKING_DIRECTORY ${rust_dir} - COMMENT "start cargo for ${target_name} with '${cargo_cmd}' static" - VERBATIM - ) - - # add_custom_target(${target_name} ALL - # COMMAND echo "invoking cargo for ${target_name}" - # DEPENDS ${staticlib_name} - # ) - - # Update mysqld dependencies - SET (MYSQLD_STATIC_PLUGIN_LIBS ${MYSQLD_STATIC_PLUGIN_LIBS} - ${target_name} ${ARG_LINK_LIBRARIES} CACHE INTERNAL "" FORCE) - - message("more to do here...") - - elseif( - ${${cache_name}} MATCHES "(DYNAMIC|AUTO|YES)" - AND NOT ARG_STATIC_ONLY AND NOT WITHOUT_DYNAMIC_PLUGINS - ) - # Build a dynamiclib - message(STATUS "configuring rust plugin ${target_name} as dynamic") - - add_version_info(${target_name} MODULE SOURCES) - - # if(ARG_RECOMPILE_FOR_EMBEDDED OR ARG_STORAGE_ENGINE) - # if(MSVC OR CMAKE_SYSTEM_NAME MATCHES AIX) - # target_link_libraries(${target} server) - # elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") - # target_link_libraries(${target} mariadbd) - # endif() - # elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT WITH_ASAN AND NOT WITH_TSAN AND NOT WITH_UBSAN AND NOT WITH_MSAN) - # target_link_libraries(${target} "-Wl,--no-undefined") - # endif() - - add_custom_target(${target_name} ALL - COMMAND ${CMAKE_COMMAND} - ${env_args} - ${cargo_cmd} - --crate-type=cdylib - -- - ${rustc_extra_args} - WORKING_DIRECTORY ${rust_dir} - COMMENT "start cargo for ${target_name} with '${cargo_cmd}' dynamic" - VERBATIM - ) - - add_dependencies(${target_name} mysqlservices) - - # IF(CMAKE_SYSTEM_NAME MATCHES AIX) - # TARGET_LINK_OPTIONS(${target} PRIVATE "-Wl,-bE:${CMAKE_SOURCE_DIR}/libservices/mysqlservices_aix.def") - # ENDIF() - - - set_target_properties(${target} PROPERTIES PREFIX "") - if(NOT ARG_CLIENT) - set_target_properties(${target} PROPERTIES - COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN${version_string}") - endif() - - IF (NOT ARG_CLIENT) - SET_TARGET_PROPERTIES (${target} PROPERTIES - COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN${version_string}") - ENDIF() - - # add_custom_target(${target_name} ALL - # COMMAND echo "invoking cargo for ${target_name}" - # DEPENDS ${dylib_path} - # ) - - add_dependencies(${target_name} GenError) - # add_dependencies(mariadb-plugin ${target_name}) - set_target_properties(${target_name} PROPERTIES OUTPUT_NAME "${target_name}") - # mysql_install_targets(${target_name} DESTINATION ${INSTALL_PLUGINDIR} COMPONENT ${ARG_COMPONENT}) - install(FILES ${dylib_path} DESTINATION ${INSTALL_PLUGINDIR} RENAME ${dylib_name_final} COMPONENT ${ARG_COMPONENT}) - - if(ARG_CONFIG AND INSTALL_SYSCONF2DIR) - install(FILES ${ARG_CONFIG} COMPONENT ${ARG_COMPONENT} DESTINATION ${INSTALL_SYSCONF2DIR}) - endif() - else() - message(STATUS "skipping rust plugin ${target_name}") - endif() - - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mysql-test") - INSTALL_MYSQL_TEST("${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/" "plugin/${subpath}") - endif() - - # if(TARGET ${target_name}) - # get_target_property(plugin_type ${target_name} TYPE) - # string(REPLACE "_LIBRARY" "" plugin_type ${plugin_type}) - # set(have_target 1) - # else() - # set(plugin_type) - # set(have_target 0) - # endif() - - # if(ARG_STORAGE_ENGINE) - # ADD_FEATURE_INFO(${plugin} ${have_target} "Storage Engine ${plugin_type}") - # elseif(ARG_CLIENT) - # ADD_FEATURE_INFO(${plugin} ${have_target} "Client plugin ${plugin_type}") - # else() - # ADD_FEATURE_INFO(${plugin} ${have_target} "Server plugin ${plugin_type}") - # endif() - # endif(NOT WITHOUT_SERVER OR ARG_CLIENT) - endforeach() -endmacro() diff --git a/rust/CMakeLists.txt b/rust/CMakeLists.txt new file mode 100644 index 0000000000000..955ec91edd3bd --- /dev/null +++ b/rust/CMakeLists.txt @@ -0,0 +1,384 @@ +# Rust CMake configuration +# +# This file is in charge of creating bridge libraries that Rust can link to, as +# well as invoking Cargo to build Rust plugins. + +##### General configuration ##### + +set(rust_dir "${CMAKE_CURRENT_SOURCE_DIR}") +set(cargo_target_dir "${CMAKE_BINARY_DIR}/rust_target") +message(STATUS "rust dir ${rust_dir}") +message(STATUS "cargo target ${cargo_target_dir}") + + +##### Configure bridge libraries ##### +# Some plugin types only have a C++ interface but not a C interface. It is +# easier to just expose a pure C interface that Rust can use, rather than +# replicating a C++ class virtual instance in Rust. +# +# This is referred to as the "bridge" + +add_definitions(-DMYSQL_SERVER=1) + +# Add common include directories +include_directories( + ../include + ../sql + ../libmariadb/include + bridge + ${PCRE_INCLUDES} + ${SSL_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIR} +) + +# Bridge library for storage engine pluigns +add_convenience_library(rust_bridge_storage + bridge/handler_bridge.cc + ../sql/handler.cc +) + +set_target_properties( + rust_bridge_storage PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/rust/bridge +) + +# target_include_directories( +# rust_bridge_storage PUBLIC +# ${CMAKE_SOURCE_DIR}/rust/bridge +# ) + +# # Dummy target used to aggregate linker args. I can't get target_link_libraries +# # to work for some reason. +# # add_executable(rustbar bridge/m.cc) +# add_library(test_dummy_target SHARED bridge/handler_bridge.cc) +# # target_link_libraries(test_dummy_target rust_bridge_storage) +# target_include_directories( +# test_dummy_target PUBLIC +# ../include +# ../sql +# ../libmariadb/include +# bridge +# ${PCRE_INCLUDES} +# ${SSL_INCLUDE_DIRS} +# ${ZLIB_INCLUDE_DIR} +# ) +# if(MSVC OR CMAKE_SYSTEM_NAME MATCHES AIX) +# target_link_libraries(test_dummy_target server) +# elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") +# target_link_libraries(test_dummy_target mariadbd) +# endif() +# set_target_properties(test_dummy_target PROPERTIES C_COMPILER_LAUNCHER "${CMAKE_SOURCE_DIR}/rust/test.py;c") +# set_target_properties(test_dummy_target PROPERTIES CXX_COMPILER_LAUNCHER "${CMAKE_SOURCE_DIR}/rust/test.py;cxx") +# set_target_properties(test_dummy_target PROPERTIES C_LINKER_LAUNCHER "${CMAKE_SOURCE_DIR}/rust/test.py;ld") +# set_target_properties(test_dummy_target PROPERTIES CXX_LINKER_LAUNCHER "${CMAKE_SOURCE_DIR}/rust/test.py;ld") + +set(rustc_args ${rustc_args} "-Lnative=${CMAKE_CURRENT_BINARY_DIR}/sql") + +# Linker args specific to storage engine plugins +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") + +# FIXME: I'm sure we don't actually need all of these (would be nice if the +# linker could ignore unused+undefined symbols) and I'm sure they aren't all +# compiled as PIC. Maybe we can clean this up somehow? +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=$") + +# System libraries +# set(rustc_args_storage ${rustc_args_storage} "-lstatic=openssl") +set(rustc_args_storage ${rustc_args_storage} "-lstatic=c++") +set(rustc_args_storage ${rustc_args_storage} "-lstatic=z") +set(rustc_args_storage ${rustc_args_storage} "-lstatic=pcre2-8") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=${OPENSSL_SSL_LIBRARY}") +set(rustc_args_storage ${rustc_args_storage} "-Clink-arg=${OPENSSL_CRYPTO_LIBRARY}") + + +##### Configure Rust plugins ##### + +# TODO: just parse any needed configuration from cargo.toml + +# There are some things that are just easier to not do in CMake +execute_process( + COMMAND python3 "${rust_dir}/cmake-helper.py" + OUTPUT_VARIABLE helper_output + COMMAND_ERROR_IS_FATAL ANY +) +message(STATUS "plugin: ${helper_output}") + +# Load a dictionary-like-thing in the only way cmake can +# All set variables start with "from_helper" +foreach(key_value_string ${helper_output}) + # Find the key then the value + string(STRIP ${key_value_string} key_value_string) + string(REGEX MATCH "^[^=]+" key ${key_value_string}) + string(REPLACE "${key}=" "" value ${key_value_string}) + # We use `|` in place of `;` for values that are lists + string(REPLACE "|" ";" value ${value}) + # Set the variable. I wish there were a better way to handle this... + set(${key} "${value}") +endforeach() + +set(rust_shared_suffix ${CMAKE_SHARED_LIBRARY_SUFFIX}) + +if(APPLE) + set(rust_shared_suffix ".dylib") +endif() + +# See cmake-helper.py for the output that we get here. We loop through each +# plugin +foreach(plugin_name IN LISTS from_helper_all_plugins) + message(STATUS "configuring rust plugin ${plugin_name}") + set(cache_name ${from_helper_plugin_${plugin_name}_cache_name}) + set(cargo_name ${from_helper_plugin_${plugin_name}_cargo_name}) + set(target_name ${from_helper_plugin_${plugin_name}_cmake_target_name}) + set(staticlib_name "${CMAKE_STATIC_LIBRARY_PREFIX}${plugin_name}${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(dylib_name "${CMAKE_SHARED_LIBRARY_PREFIX}${plugin_name}${rust_shared_suffix}") + + # Copied from plugin.cmake, set default `howtobuild` + if(ARG_DISABLED) + set(howtobuild NO) + elseif(compat STREQUAL ".") + set(howtobuild DYNAMIC) + elseif(compat STREQUAL "with.") + if(NOT ARG_MODULE_ONLY) + set(howtobuild STATIC) + ELSE() + set(howtobuild DYNAMIC) + endif() + elseif(compat STREQUAL ".without") + set(howtobuild NO) + elseif(compat STREQUAL "with.without") + set(howtobuild STATIC) + else() + set(howtobuild DYNAMIC) + endif() + + + # NO - not at all + # YES - static if possible, otherwise dynamic if possible, otherwise abort + # AUTO - static if possible, otherwise dynamic, if possible + # STATIC - static if possible, otherwise not at all + # DYNAMIC - dynamic if possible, otherwise not at all + set(${cache_name} ${howtobuild} CACHE STRING + "How to build plugin ${plugin_name}. Options are: NO STATIC DYNAMIC YES AUTO.") + + if(NOT ${cache_name} MATCHES "^(NO|YES|AUTO|STATIC|DYNAMIC)$") + message(FATAL_ERROR "Invalid value '${${cache_name}}' for ${cache_name}") + endif() + + set(cargo_cmd + cargo rustc + --target-dir=${cargo_target_dir} + --package=${cargo_name} + --locked + --quiet + ) + + set(output_path "${cargo_target_dir}/release") + + # Configure debug/release options + if(CMAKE_BUILD_TYPE MATCHES "Debug") + set(cargo_cmd ${cargo_cmd} --profile=dev) + set(output_path "${cargo_target_dir}/debug") + elseif(CMAKE_BUILD_TYPE MATCHES "Release") + set(cargo_cmd ${cargo_cmd} --profile=release) + elseif(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") + set(cargo_cmd ${cargo_cmd} --profile=release) + elseif(CMAKE_BUILD_TYPE MATCHES "MinSizeRel") + set(cargo_cmd ${cargo_cmd} --profile=release) + set(rustc_args ${rustc_args} -C strip=debuginfo) + endif() + + + set(dylib_path "${output_path}/${dylib_name}") + + # Used by build.rs + set(env_args -E env + CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}" + CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}" + ) + + if(NOT ARG_MODULE_OUTPUT_NAME) + if(ARG_STORAGE_ENGINE) + set(ARG_MODULE_OUTPUT_NAME "ha_${target_name}") + else() + set(ARG_MODULE_OUTPUT_NAME "${target_name}") + endif() + endif() + + if( + ${cache_name} MATCHES "(STATIC|AUTO|YES)" AND NOT ARG_MODULE_ONLY + AND NOT ARG_CLIENT + ) + + # Build a staticlib + if(CMAKE_GENERATOR MATCHES "Makefiles|Ninja") + # If there is a shared library from previous shared build, + # remove it. This is done just for mysql-test-run.pl + # so it does not try to use stale shared lib as plugin + # in test. + file(REMOVE + ${CMAKE_CURRENT_BINARY_DIR}/${ARG_MODULE_OUTPUT_NAME}${CMAKE_SHARED_MODULE_SUFFIX}) + endif() + + set(rustc_args_this_plugin "") + + if(${from_helper_plugin_${plugin_name}_needs_storage}) + message(STATUS "linking in storage module") + set(rustc_args_this_plugin ${rustc_args_this_plugin} ${rustc_args_storage}) + endif() + + add_custom_target(${target_name} + # We set make_static_lib to generate the correct symbols + # equivalent of `COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN$...` for C plugin_config + # Todos: + # TARGET_LINK_LIBRARIES (${target} mysqlservices ${ARG_LINK_LIBRARIES}) + COMMAND ${CMAKE_COMMAND} + ${env_args} + ${cargo_cmd} + --crate-type=staticlib + -- + ${rustc_args} + ${rustc_args_this_plugin} + --cfg=make_static_lib + WORKING_DIRECTORY ${rust_dir} + COMMENT "start cargo for ${target_name} with '${cargo_cmd}' static" + VERBATIM + ) + + # Update mysqld dependencies + set(MYSQLD_STATIC_PLUGIN_LIBS ${MYSQLD_STATIC_PLUGIN_LIBS} + ${target_name} ${ARG_LINK_LIBRARIES} CACHE INTERNAL "" FORCE) + + message("more to do here...") + + elseif( + ${cache_name} MATCHES "(DYNAMIC|AUTO|YES)" + AND NOT ARG_STATIC_ONLY AND NOT WITHOUT_DYNAMIC_PLUGINS + ) + # Build a dynamiclib + add_version_info(${target_name} MODULE SOURCES) + + # if(ARG_RECOMPILE_FOR_EMBEDDED OR ARG_STORAGE_ENGINE) + # if(MSVC OR CMAKE_SYSTEM_NAME MATCHES AIX) + # target_link_libraries(${target} server) + # elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + # target_link_libraries(${target} mariadbd) + # endif() + # elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT WITH_ASAN AND NOT WITH_TSAN AND NOT WITH_UBSAN AND NOT WITH_MSAN) + # target_link_libraries(${target} "-Wl,--no-undefined") + # endif() + + add_custom_target(${target_name} ALL + COMMAND ${CMAKE_COMMAND} + ${env_args} + ${cargo_cmd} + --crate-type=cdylib + -- + ${rustc_args} + # FIXME: only provide storage arguments if we are buildling a storage engine + ${rustc_args_storage} + WORKING_DIRECTORY ${rust_dir} + COMMENT "start cargo for ${target_name} with '${cargo_cmd}' dynamic" + VERBATIM + ) + + # IF(CMAKE_SYSTEM_NAME MATCHES AIX) + # TARGET_LINK_OPTIONS(${target} PRIVATE "-Wl,-bE:${CMAKE_SOURCE_DIR}/libservices/mysqlservices_aix.def") + # ENDIF() + + set_target_properties(${target} PROPERTIES PREFIX "") + if(NOT ARG_CLIENT) + set_target_properties(${target} PROPERTIES + COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN${version_string}") + endif() + + IF (NOT ARG_CLIENT) + SET_TARGET_PROPERTIES (${target} PROPERTIES + COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN${version_string}") + + endif() + + # add_custom_target(${target_name} ALL + # COMMAND echo "invoking cargo for ${target_name}" + # DEPENDS ${dylib_path} + # ) + + add_dependencies(${target_name} GenError) + # add_dependencies(mariadb-plugin ${target_name}) + set_target_properties(${target_name} PROPERTIES OUTPUT_NAME "${target_name}") + # mysql_install_targets(${target_name} DESTINATION ${INSTALL_PLUGINDIR} COMPONENT ${ARG_COMPONENT}) + + + if(${from_helper_plugin_${plugin_name}_is_example}) + set(plugin_pfx "example_") + endif() + + set(dylib_name_installed "${plugin_pfx}${plugin_name}${CMAKE_SHARED_MODULE_SUFFIX}") + + install(FILES ${dylib_path} DESTINATION ${INSTALL_PLUGINDIR} + RENAME ${dylib_name_installed} COMPONENT ${ARG_COMPONENT}) + + if(ARG_CONFIG AND INSTALL_SYSCONF2DIR) + install(FILES ${ARG_CONFIG} COMPONENT ${ARG_COMPONENT} DESTINATION ${INSTALL_SYSCONF2DIR}) + endif() + else() + message(STATUS "skipping rust plugin ${target_name}") + endif() + + if(${from_helper_plugin_${plugin_name}_needs_any_services}) + add_dependencies(${target_name} mysqlservices) + endif() + + if(${from_helper_plugin_${plugin_name}_needs_storage}) + add_dependencies(${target_name} rust_bridge_storage) + endif() + + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mysql-test") + INSTALL_MYSQL_TEST("${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/" "plugin/${subpath}") + endif() + + # if(TARGET ${target_name}) + # get_target_property(plugin_type ${target_name} TYPE) + # string(REPLACE "_LIBRARY" "" plugin_type ${plugin_type}) + # set(have_target 1) + # else() + # set(plugin_type) + # set(have_target 0) + # endif() + + # if(ARG_STORAGE_ENGINE) + # ADD_FEATURE_INFO(${plugin} ${have_target} "Storage Engine ${plugin_type}") + # elseif(ARG_CLIENT) + # ADD_FEATURE_INFO(${plugin} ${have_target} "Client plugin ${plugin_type}") + # else() + # ADD_FEATURE_INFO(${plugin} ${have_target} "Server plugin ${plugin_type}") + # endif() + # endif(NOT WITHOUT_SERVER OR ARG_CLIENT) +endforeach() diff --git a/rust/Cargo.toml b/rust/Cargo.toml deleted file mode 100644 index b0179391ba773..0000000000000 --- a/rust/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -# Define our crates within a workspace - -[workspace] -resolver = "2" -members = [ - "bindings", - "mariadb", - "macros", - "common/encryption-common", - "examples/keymgt-basic-rs", - "examples/keymgt-debug-rs", - "examples/encryption-simple-rs", - "examples/encryption-aes-rs", - "examples/ftparser-simple", - "plugins/keymgt-clevis", - "plugins/encryption-file-chacha", - "test-runner", -] diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs deleted file mode 100644 index 275962d0b1a9d..0000000000000 --- a/rust/bindings/build.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! This file runs `cmake` as needed, then `bindgen` to produce the rust -//! bindings -//! -//! Since we want to avoid configuring if possible, we try a few things in -//! order: -//! -//! - Check if cmake args are present, if so use that built output -//! - Check if the source directory root can be used, use that if so -//! - Configure it outselves, output in a temp directory - -use std::collections::HashSet; -use std::env; -use std::path::PathBuf; -use std::process::Command; - -use bindgen::callbacks::{DeriveInfo, MacroParsingBehavior, ParseCallbacks}; -use bindgen::{BindgenError, Bindings, EnumVariation}; - -// `math.h` seems to double define some things, To avoid this, we ignore them. -const IGNORE_MACROS: [&str; 20] = [ - "FE_DIVBYZERO", - "FE_DOWNWARD", - "FE_INEXACT", - "FE_INVALID", - "FE_OVERFLOW", - "FE_TONEAREST", - "FE_TOWARDZERO", - "FE_UNDERFLOW", - "FE_UPWARD", - "FP_INFINITE", - "FP_INT_DOWNWARD", - "FP_INT_TONEAREST", - "FP_INT_TONEARESTFROMZERO", - "FP_INT_TOWARDZERO", - "FP_INT_UPWARD", - "FP_NAN", - "FP_NORMAL", - "FP_SUBNORMAL", - "FP_ZERO", - "IPPORT_RESERVED", -]; - -const DERIVE_COPY_NAMES: [&str; 1] = ["enum_field_types"]; - -#[derive(Debug)] -struct BuildCallbacks(HashSet); - -impl ParseCallbacks for BuildCallbacks { - /// Ignore macros that are in the ignored list - fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { - if self.0.contains(name) { - MacroParsingBehavior::Ignore - } else { - MacroParsingBehavior::Default - } - } - - /// Use a converter to turn doxygen comments into rustdoc - fn process_comment(&self, comment: &str) -> Option { - Some(doxygen_rs::transform(comment)) - } - - fn add_derives(&self, _info: &DeriveInfo<'_>) -> Vec { - if DERIVE_COPY_NAMES.contains(&_info.name) { - vec!["Copy".to_owned()] - } else { - vec![] - } - } -} - -impl BuildCallbacks { - fn new() -> Self { - Self(IGNORE_MACROS.into_iter().map(|s| s.to_owned()).collect()) - } -} - -/// Get the root of our mariadb project -fn mariadb_root() -> String { - format!("{}/../../", env::var("CARGO_MANIFEST_DIR").unwrap()) -} - -/// Find paths provided by CMake environment variables -fn include_paths_from_cmake() -> Option<[String; 2]> { - if let Ok(src_dir) = env::var("CMAKE_SOURCE_DIR") { - let Ok(dst_dir) = env::var("CMAKE_BINARY_DIR") else { - panic!("CMAKE_SOURCE_DIR set but CMAKE_BINARY_DIR unset"); - }; - Some([src_dir, dst_dir]) - } else { - None - } -} - -/// Run cmake in our temp directory -fn configure_returning_incl_paths() -> [String; 2] { - let root = mariadb_root(); - let output_dir = format!("{}/cmake", env::var("OUT_DIR").unwrap()); - - // Run cmake to configure only - Command::new("cmake") - .arg(format!("-S{root}")) - .arg(format!("-B{output_dir}")) - .output() - .expect("failed to invoke cmake"); - - [root, output_dir] -} - -/// Given some include directories, see if bindgen works -fn run_bindgen_with_includes(search_paths: &[String]) -> Result { - let incl_args = search_paths - .iter() - .flat_map(|path| [format!("-I{path}/sql"), format!("-I{path}/include")]); - - bindgen::Builder::default() - // The input header we would like to generate - // bindings for. - .header("src/wrapper.h") - // Fix math.h double defines - .parse_callbacks(Box::new(BuildCallbacks::new())) - .clang_args(incl_args) - .clang_arg("-xc++") - .clang_arg("-std=c++17") - // Don't derive copy for structs - .derive_copy(false) - // Use rust-style enums labeled with non_exhaustive to represent C enums - .default_enum_style(EnumVariation::Rust { - non_exhaustive: true, - }) - // LLVM has some issues with long dobule and ABI compatibility - // disabling the only relevant function here to suppress errors - .blocklist_function("strfroml") - .blocklist_function("strfromf64x") - .blocklist_function("strtof64x_l") - .blocklist_function("strtof64x") - .blocklist_function("strtold") - .blocklist_function("strtold_l") - // qvct, evct, qfcvt_r, ... - .blocklist_function("[a-z]{1,2}cvt(?:_r)?") - // c++ things that aren't supported - .blocklist_item("List_iterator") - .blocklist_type("std::char_traits") - .opaque_type("std_.*") - .blocklist_item("std_basic_string") - .blocklist_item("std_collate.*") - .blocklist_item("__gnu_cxx.*") - // We redefine this to use the variables from `st_service_ref` since they don't seem to - // import with the expected values (a static vs. dynamic thing) - .blocklist_item("sql_service") - // Finish the builder and generate the bindings. - .generate() -} - -/// Tell cargo how to find libmysqlclient.so -fn specify_link() { - // FIXME: this is a bit sloppy - // println!("cargo:rustc-link-lib=dylib=mariadbclient"); - // println!("cargo:rustc-link-lib=static=mysqlservices"); - - // // todo: change to cmake_link_dirs, split by `;` - // if let Ok(cmake_dir) = env::var("CMAKE_BINARY_DIR") { - // println!("cargo:rustc-link-search=native={cmake_dir}/libservices"); - // } - // println!("cargo:rustc-link-lib=static=libmysqlservices"); - - // println!("cargo:rustc-link-lib=static=mysqlservices"); -} - -fn main() { - // Tell cargo to invalidate the built crate whenever the wrapper changes - println!("cargo:rerun-if-changed=src/wrapper.h"); - - let bindings = include_paths_from_cmake() - .and_then(|paths| run_bindgen_with_includes(&paths).ok()) - .or_else(|| run_bindgen_with_includes(&[mariadb_root()]).ok()) - .or_else(|| run_bindgen_with_includes(&configure_returning_incl_paths()).ok()) - .expect("Unable to generate bindings"); - - // Write the bindings to the $OUT_DIR/bindings.rs file. - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("couldn't write bindings"); - - specify_link(); -} diff --git a/rust/bridge/handler_bridge.cc b/rust/bridge/handler_bridge.cc new file mode 100644 index 0000000000000..4dd0e77e9856c --- /dev/null +++ b/rust/bridge/handler_bridge.cc @@ -0,0 +1,13 @@ + +#include "handler_bridge.h" + +extern "C" handler* +ha_bridge_construct(handlerton *hton, TABLE_SHARE *table_args, + MEM_ROOT *mem_root, const handler_bridge_vt* vt) { + return new (mem_root) handler_bridge(hton, table_args, mem_root, vt); +} + +// TODO: does this get called automatically? +extern "C" void ha_bridge_destroy(handler*bridge) { + delete (handler_bridge*)bridge; +} diff --git a/rust/bridge/handler_bridge.h b/rust/bridge/handler_bridge.h new file mode 100644 index 0000000000000..39839817d7890 --- /dev/null +++ b/rust/bridge/handler_bridge.h @@ -0,0 +1,156 @@ +/** @file handler_bridge.h + + @brief + This file provides a bridge class that allows using a storage engine via a C + API, rather than C++. Essentially this is needed to construct the needed + vtables. +*/ + +#pragma once + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + +#include "my_global.h" +#include "handler.h" + +class handler_bridge; + +/** + A C representation of the handler class. + + For now all function pointers must be nonnull. We could change this to check + for null then call the parent function if so at some point. +*/ +typedef struct handler_bridge_vt { + void (*constructor)(handler_bridge*, handlerton*, MEM_ROOT*, TABLE_SHARE*); + void (*destructor)(handler_bridge*); + const char* (*index_type)(handler_bridge*, uint); + ulonglong (*table_flags)(const handler_bridge*); + ulong (*index_flags)(const handler_bridge*, uint, uint, bool); + uint (*max_supported_record_length)(const handler_bridge*); + uint (*max_supported_keys)(const handler_bridge*); + uint (*max_supported_key_parts)(const handler_bridge*); + uint (*max_supported_key_length)(const handler_bridge*); + IO_AND_CPU_COST (*scan_time)(handler_bridge*); + IO_AND_CPU_COST (*keyread_time)(handler_bridge*, uint, ulong, ha_rows, ulonglong); + IO_AND_CPU_COST (*rnd_pos_time)(handler_bridge*, ha_rows); + int (*open)(handler_bridge*, const char*, int, uint); + int (*close)(handler_bridge*); + int (*write_row)(handler_bridge*, const uchar*); + int (*update_row)(handler_bridge*, const uchar*, const uchar*); + int (*delete_row)(handler_bridge*, const uchar*); + int (*index_read_map)(handler_bridge*, uchar*, const uchar*, key_part_map, + enum ha_rkey_function); + int (*index_next)(handler_bridge*, uchar*); + int (*index_prev)(handler_bridge*, uchar*); + int (*index_first)(handler_bridge*, uchar*); + int (*index_last)(handler_bridge*, uchar*); + int (*rnd_init)(handler_bridge*, bool ); + int (*rnd_end)(handler_bridge*); + int (*rnd_next)(handler_bridge*, uchar*); + int (*rnd_pos)(handler_bridge*, uchar*, uchar*); + void (*position)(handler_bridge*, const uchar*); + int (*info)(handler_bridge*, uint); + int (*extra)(handler_bridge*, enum ha_extra_function); + int (*external_lock)(handler_bridge*, THD*, int); + int (*delete_all_rows)(handler_bridge*); + ha_rows (*records_in_range)(handler_bridge*, uint, const key_range*, + const key_range*, page_range*); + int (*delete_table)(handler_bridge*, const char*); + int (*create)(handler_bridge*, const char*, TABLE*, HA_CREATE_INFO*); + enum_alter_inplace_result + (*check_if_supported_inplace_alter)(handler_bridge*, TABLE*, Alter_inplace_info*); + THR_LOCK_DATA** (*store_lock)(handler_bridge*, THD*, THR_LOCK_DATA**, enum thr_lock_type); +} handler_bridge_vt; + + +/** Wrapper that can expose a C vtable as a C++ class */ +class handler_bridge: public handler { +public: + /** The vtable that we defer to for all method calls */ + const handler_bridge_vt *const vt; + /** Storage for anything needed. Should only be touched by the C API, not this class. */ + void *data; + /** Just a convenience point for a Rust type ID */ + uint8_t type_id[16]; + + handler_bridge(handlerton *hton, TABLE_SHARE *table_arg, + MEM_ROOT *mem_root, const handler_bridge_vt *const vt) + :handler(hton, table_arg), + vt(vt) + { + vt->constructor(this, hton, mem_root, table_arg); + } + + virtual ~handler_bridge() { + vt->destructor(this); + } + + const char *index_type(uint inx) { return vt->index_type(this, inx); } + ulonglong table_flags() const { return vt->table_flags(this); } + ulong index_flags(uint inx, uint part, bool all_parts) const { + return vt->index_flags(this, inx, part, all_parts); + } + uint max_supported_record_length() const { return vt->max_supported_record_length(this); } + uint max_supported_keys() const { return vt->max_supported_keys(this); } + uint max_supported_key_parts() const { return vt->max_supported_key_parts(this); } + uint max_supported_key_length() const { return vt->max_supported_key_length(this); } + virtual IO_AND_CPU_COST scan_time() { return vt->scan_time(this); } + virtual IO_AND_CPU_COST keyread_time(uint index, ulong ranges, ha_rows rows, + ulonglong blocks) { + return vt->keyread_time(this, index, ranges, rows, blocks); + } + virtual IO_AND_CPU_COST rnd_pos_time(ha_rows rows) { return vt->rnd_pos_time(this, rows); } + int open(const char *name, int mode, uint test_if_locked) { + return vt->open(this, name, mode, test_if_locked); + } + int close(void) { return vt->close(this); } + int write_row(const uchar *buf) { return vt->write_row(this, buf); } + int update_row(const uchar *old_data, const uchar *new_data) { return vt->update_row(this, old_data, new_data); } + int delete_row(const uchar *buf) { return vt->delete_row(this, buf); } + int index_read_map(uchar *buf, const uchar *key, + key_part_map keypart_map, enum ha_rkey_function find_flag) { + return vt->index_read_map(this, buf, key, keypart_map, find_flag); + } + int index_next(uchar *buf) { return vt->index_next(this, buf); } + int index_prev(uchar *buf) { return vt->index_prev(this, buf); } + int index_first(uchar *buf) { return vt->index_first(this, buf); } + int index_last(uchar *buf) { return vt->index_last(this, buf); } + int rnd_init(bool scan) { return vt->rnd_init(this, scan); } + int rnd_end() { return vt->rnd_end(this); } + int rnd_next(uchar *buf) { return vt->rnd_next(this, buf); } + int rnd_pos(uchar *buf, uchar *pos) { return vt->rnd_pos(this, buf, pos); } + void position(const uchar *record) { return vt->position(this, record); } + int info(uint flag) { return vt->info(this, flag); } + int extra(enum ha_extra_function operation) { return vt->extra(this, operation); } + int external_lock(THD *thd, int lock_type) { return vt->external_lock(this, thd, lock_type); } + int delete_all_rows(void) { return vt->delete_all_rows(this); } + ha_rows records_in_range(uint inx, const key_range *min_key, const key_range *max_key, + page_range *pages) { + return vt->records_in_range(this, inx, min_key, max_key, pages); + } + int delete_table(const char *from) { return vt->delete_table(this, from); } + int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info) { + return vt->create(this, name, form, create_info); + } + enum_alter_inplace_result + check_if_supported_inplace_alter(TABLE* altered_table, Alter_inplace_info* ha_alter_info) { + return vt->check_if_supported_inplace_alter(this, altered_table, ha_alter_info); + } + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type) { + return vt->store_lock(this, thd, to, lock_type); + } +}; + +/** + A builder that will create the C++ class from a C vtable. This is used to create a + `handler` from a `handlerton`. + */ +extern "C" handler* +ha_bridge_construct(handlerton*, TABLE_SHARE*, MEM_ROOT*, const handler_bridge_vt*); + +/** Destroy a `handler_bridge` */ +extern "C" void ha_bridge_destroy(handler*); diff --git a/rust/bridge/testlinkme.cc b/rust/bridge/testlinkme.cc new file mode 100644 index 0000000000000..33c14ce1d76c0 --- /dev/null +++ b/rust/bridge/testlinkme.cc @@ -0,0 +1,3 @@ +int main() { + return 0; +} diff --git a/rust/cmake-helper.py b/rust/cmake-helper.py new file mode 100755 index 0000000000000..5ca4830edb3b6 --- /dev/null +++ b/rust/cmake-helper.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""`cargo metadata` provides us with a JSON-formatted version of all workspace member +manifests. This tool parses + +See more at . + +We return a key-value list separated by `;`. We use `|` for a list separator within +values. All keys start with `from_helper` so they can be set directly in CMake. +""" + +import json +import subprocess as sp +import sys +from dataclasses import dataclass +from pathlib import Path + +SOURCE_DIR = Path(__file__).parent.parent +PLUGIN_PATH = SOURCE_DIR.joinpath("rust").joinpath("plugins") +EXAMPLE_PLUGIN_PATH = SOURCE_DIR.joinpath("rust").joinpath("examples") +HELPER_PFX = "from_helper_" + + +@dataclass +class Plugin: + # Name of the package to cargo + cargo_name: str + is_example: bool + manifest_path: Path + meta: dict + + def name(self) -> str: + """Cargo name in snake case""" + return self.cargo_name.lower().replace("-", "_").strip() + + def cmake_var_prefix(self) -> str: + """The prefix we use for all CMake variables""" + return f"{HELPER_PFX}plugin_{self.name()}" + + def mdb_features(self) -> list[str]: + """List any specific features from the `mariadb` crate that affect linkage""" + mdb_dep = next( + (dep for dep in self.meta["dependencies"] if dep["name"] == "mariadb"), None + ) + if mdb_dep is None: + return [] + return mdb_dep["features"] + + def create_env(self) -> dict: + """Create the variables we want to have in cmake""" + ex_pfx_upper = "EXAMPLE_" if self.is_example else "" + + features = self.mdb_features() + needs_storage = "storage" in features + needs_service_sql = "service-sql" in features + needs_any_services = any(f.startswith("service-") for f in features) + + return { + "cargo_name": self.cargo_name, + "cache_name": f"PLUGIN_{ex_pfx_upper}{self.name().upper()}", + "cmake_target_name": f"plugin_{self.name()}", + "mariadb_features": features, + "is_example": "TRUE" if self.is_example else "FALSE", + "needs_storage": needs_storage, + "needs_service_sql": needs_service_sql, + "needs_any_services": needs_any_services, + # "target_basename": self.meta + # "staticlib_fname": + } + + +def main(): + ws_manifest = SOURCE_DIR.joinpath("Cargo.toml") + print(f"reading workspace manifest from {ws_manifest}", file=sys.stderr) + + # Use `cargo metadata` to load workspace members + ws_meta = json.loads( + sp.check_output( + [ + "cargo", + "metadata", + "--manifest-path", + ws_manifest, + "--format-version=1", + "--no-deps", + ] + ) + ) + + plugins: list[Plugin] = [] + + for pkg_meta in ws_meta["packages"]: + manifest_path = Path(pkg_meta["manifest_path"]) + + # Keep normal plugins and examples, ignore any other crates + if PLUGIN_PATH in manifest_path.parents: + plugins.append(Plugin(pkg_meta["name"], False, manifest_path, pkg_meta)) + elif EXAMPLE_PLUGIN_PATH in manifest_path.parents: + plugins.append(Plugin(pkg_meta["name"], True, manifest_path, pkg_meta)) + + ret = f"{HELPER_PFX}all_plugins=" + ret += "|".join(p.name() for p in plugins) + ret += ";" + + for plugin in plugins: + pfx = plugin.cmake_var_prefix() + ret += ";".join(f"{pfx}_{k}={v}" for (k, v) in plugin.create_env().items()) + ret += ";" + + ret = ret.rstrip(";") + + print(ret) + + +if __name__ == "__main__": + main() diff --git a/rust/cmake_helper.py b/rust/cmake_helper.py deleted file mode 100644 index acc738442f497..0000000000000 --- a/rust/cmake_helper.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -"""CMake's regex engine can't seem to handle multiline, so this extracts our -rust plugins. - -Returns a combination of var names and crate names: - -``` -PLUGIN_CACHE_NAME|target_name|cargo-name|staticlib_name.a|libdylib_name.so|dylib_name.so;PLUGIN_BAR|bar... -``` -""" - -import re -import os - - -def main(): - this_path = os.path.dirname(__file__) - - with open(f"{this_path}/Cargo.toml") as f: - cargo = f.read() - - members = re.search(r"members\s+=\s+\[(.*)\]", cargo, re.DOTALL).group(1) - paths = [m.strip().split('"')[1] for m in members.strip().split(",") if len(m) > 0] - - ret = [] - - for path in paths: - if not (path.startswith("examples") or path.startswith("plugins")): - continue - - with open(f"{this_path}/{path}/Cargo.toml") as f: - data = f.read() - - cargo_name = re.search(r"name\s+=\s+\"(\S+)\"", data, re.MULTILINE).group(1) - name_var = cargo_name.upper().replace("-", "_") - is_example = path.startswith("example") - ex_pfx_upper = "EXAMPLE_" if is_example else "" - ex_pfx_lower = ex_pfx_upper.lower() - - # Cmake config name - cache_name = f"PLUGIN_{ex_pfx_upper}{name_var}" - # Name of the target - target_name = cache_name.lower() - # Name of the staticlib - static_name = f"lib{name_var.lower()}.a" - # Name we want in the plugins dir, no lib prefix - dyn_name_final = f"{ex_pfx_lower}{name_var.lower()}.so" - # Name that is output by rust - dyn_name_out = f"lib{name_var.lower()}.so" - - ret.append( - f"{cache_name}|{target_name}|{cargo_name}|{static_name}|{dyn_name_out}|{dyn_name_final}" - ) - - print(";".join(ret), end="") - - -if __name__ == "__main__": - main() diff --git a/rust/examples/ftparser-simple/src/lib.rs b/rust/examples/ftparser-simple/src/lib.rs index bff5c70da07bf..3a2bcea327f09 100644 --- a/rust/examples/ftparser-simple/src/lib.rs +++ b/rust/examples/ftparser-simple/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use mariadb::plugin::ftparser::{FullTextParser, Parameters}; use mariadb::EmptyResult; diff --git a/rust/examples/storage-csv/Cargo.toml b/rust/examples/storage-csv/Cargo.toml new file mode 100644 index 0000000000000..269d1b2f9d34c --- /dev/null +++ b/rust/examples/storage-csv/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "storage-csv" +version = "0.1.0" +edition = "2021" +license = "GPL-2.0-only" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +mariadb = { path = "../../mariadb", features = ["storage"] } diff --git a/rust/examples/storage-csv/src/lib.rs b/rust/examples/storage-csv/src/lib.rs new file mode 100644 index 0000000000000..217160475d014 --- /dev/null +++ b/rust/examples/storage-csv/src/lib.rs @@ -0,0 +1,46 @@ +#![allow(unused)] + +use std::ffi::{c_int, CStr}; +use std::path::Path; + +use mariadb::log::debug; +use mariadb::plugin::{License, Maturity}; +use mariadb::storage::{Handler, Handlerton, Mode, OpenOp, StorageResult}; +use mariadb::{register_plugin_storage, MemRoot, TableShare}; + +register_plugin_storage! { + name: "EXAMPLE_RUST_CSV", + author: "Trevor Gross", + description: "Reimplementation of ha_tina for debugging", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + handlerton: ExampleHton, +} + +struct ExampleHton; +struct ExampleHandler {} + +impl Handlerton for ExampleHton { + type Handler = ExampleHandler; + + type SavePoint = (); +} + +impl Handler for ExampleHandler { + type Handlerton = ExampleHton; + + // #[mariadb::dbug::instrument] + fn new(table: &TableShare, mem_root: MemRoot) -> Self { + Self {} + } + + fn open(name: &Path, mode: Mode, open_options: OpenOp) -> StorageResult { + // init share + todo!() + } + + fn write_row(buf: &CStr) { + // Nothing to do here + } +} diff --git a/rust/examples/storage-simple/Cargo.toml b/rust/examples/storage-simple/Cargo.toml new file mode 100644 index 0000000000000..1ff9507303bd6 --- /dev/null +++ b/rust/examples/storage-simple/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "storage-simple" +version = "0.1.0" +edition = "2021" +license = "GPL-2.0-only" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +mariadb = { path = "../../mariadb", features = ["storage"] } diff --git a/rust/examples/storage-simple/src/lib.rs b/rust/examples/storage-simple/src/lib.rs new file mode 100644 index 0000000000000..b0a22f29a9c26 --- /dev/null +++ b/rust/examples/storage-simple/src/lib.rs @@ -0,0 +1,46 @@ +#![allow(unused)] + +use std::ffi::{c_int, CStr}; +use std::path::Path; + +use mariadb::log::debug; +use mariadb::plugin::{License, Maturity}; +use mariadb::storage::{Handler, Handlerton, Mode, OpenOp, StorageResult}; +use mariadb::{register_plugin_storage, MemRoot, TableShare}; + +register_plugin_storage! { + name: "EXAMPLE_RUST", + author: "Trevor Gross", + description: "Sample storage engine plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + handlerton: ExampleHton, +} + +struct ExampleHton; +struct ExampleHandler {} + +impl Handlerton for ExampleHton { + type Handler = ExampleHandler; + + type SavePoint = (); +} + +impl Handler for ExampleHandler { + type Handlerton = ExampleHton; + + // #[mariadb::dbug::instrument] + fn new(table: &TableShare, mem_root: MemRoot) -> Self { + Self {} + } + + fn open(name: &Path, mode: Mode, open_options: OpenOp) -> StorageResult { + // init share + todo!() + } + + fn write_row(buf: &CStr) { + // Nothing to do here + } +} diff --git a/rust/macros/src/dbug.rs b/rust/macros/src/dbug.rs new file mode 100644 index 0000000000000..874caa71a8d8c --- /dev/null +++ b/rust/macros/src/dbug.rs @@ -0,0 +1,61 @@ +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +/// This moves the function body to an inner function, then wraps a call to the +/// inner function with equivalent to MariaDB's `DBUG_ENTER` and `DBUG_RETURN`. +/// +/// This only happens if the rust flag `--cfg dbug_trace` is passed. +pub fn instrument( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as ItemFn); + if let Some(tt) = attr.into_iter().next() { + return syn::Error::new(tt.span().into(), "No attribute arguments expected") + .into_compile_error() + .into(); + } + + let ItemFn { + ref attrs, + ref vis, + ref sig, + ref block, + } = input; + + let inner_ident = Ident::new("inner", Span::call_site()); + let mut inner_sig = sig.clone(); + inner_sig.ident = inner_ident; + + let _crate_name = if std::env::var("CARGO_CRATE_NAME").unwrap() == "mariadb" { + "crate" + } else { + "::mariadb" + }; + + quote! { + // #[cfg(not(dbug_trace))] + // #input + + // #[cfg(dbug_trace)] + #(#attrs)* + #vis #sig { + #inner_sig #block + + // let name = #crate_name :: internal :: cstr!(module_path!()); + let x = std :: file!(); + // let file = #crate_name::internal::cstr!(file!()); + // let line = line!(); + // let frame = std::mem::MaybeUninit<::mariadb::bindings::_db_stack_frame_>::zeroed(); + // ::mariadb::bindings::_db_enter_(name, file.as_ptr(), line frame.as_mut_ptr()); + + let ret = inner(); + + // ::mariadb::bindings::_db_return_(frame.as_mut_ptr()); + + ret + } + } + .into() +} diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index 0eb97cf11c3f3..ce3329c655845 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -6,6 +6,7 @@ #![allow(clippy::must_use_candidate)] #![allow(clippy::option_if_let_else)] +mod dbug; mod fields; mod helpers; mod parse_vars; @@ -19,3 +20,9 @@ use proc_macro::TokenStream; pub fn register_plugin(item: TokenStream) -> TokenStream { register_plugin::entry(item) } + +/// Instrument a function call with `dbug`. +#[proc_macro_attribute] +pub fn dbug_instrument(attr: TokenStream, item: TokenStream) -> TokenStream { + dbug::instrument(attr, item) +} diff --git a/rust/macros/tests/fail/plugin-extra-sysargs.stderr b/rust/macros/tests/fail/plugin-extra-sysargs.stderr index d5e0ef2c74052..846f9c7cbaf70 100644 --- a/rust/macros/tests/fail/plugin-extra-sysargs.stderr +++ b/rust/macros/tests/fail/plugin-extra-sysargs.stderr @@ -4,4 +4,4 @@ error[E0560]: struct `sysvar_str_t` has no field named `blk_sz` 22 | interval: "50" | ^^^^ `sysvar_str_t` does not have this field | - = note: available fields are: `check`, `update` + = note: all struct fields are already assigned diff --git a/rust/bindings/Cargo.toml b/rust/mariadb-sys/Cargo.toml similarity index 70% rename from rust/bindings/Cargo.toml rename to rust/mariadb-sys/Cargo.toml index 29533403b9135..dda03854d53db 100644 --- a/rust/bindings/Cargo.toml +++ b/rust/mariadb-sys/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = '2021' license = "GPL-2.0-only" - [build-dependencies] -bindgen = "0.68.1" +bindgen = "0.69.1" cmake = "0.1.50" -doxygen-rs = "0.4.2" +regex = "1.10.2" +walkdir = "2.4.0" diff --git a/rust/mariadb-sys/build.rs b/rust/mariadb-sys/build.rs new file mode 100644 index 0000000000000..3e56e6aa1891e --- /dev/null +++ b/rust/mariadb-sys/build.rs @@ -0,0 +1,293 @@ +//! This file runs `cmake` as needed, then `bindgen` to produce the rust +//! bindings +//! +//! Since we want to avoid configuring if possible, we try a few things in +//! order: +//! +//! - Check if cmake args are present, if so use that built output +//! - Check if the source directory root can be used, use that if so +//! - Configure it outselves, output in a temp directory + +use std::env; +use std::path::PathBuf; +use std::process::Command; + +use bindgen::callbacks::{DeriveInfo, ParseCallbacks}; +use bindgen::{Bindings, EnumVariation}; +use regex::Regex; + +const DERIVE_COPY_NAMES: [&str; 1] = ["enum_field_types"]; + +type Error = Box; + +fn main() { + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=src/wrapper.h"); + + make_bindings(); +} + +fn make_bindings() { + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + + // We try each of these methods to generate paths, closures so we can call + // them lazily (since they may have side effects). + let try_make_incl_paths = [ + || include_paths_from_cmake(), + || Some(vec![mariadb_root()]), + || Some(configure_returning_incl_paths()), + ]; + + let mut last_error = None; + let mut success = false; + + #[derive(Debug)] + #[allow(dead_code)] + struct LoggedError { + location: &'static str, + e: Error, + loop_count: usize, + paths: Vec, + } + + for (loop_count, make_pathset) in try_make_incl_paths.iter().enumerate() { + let Some(paths) = make_pathset() else { + continue; + }; + + // For source and output directories, add the include paths for `sql/`, + // `include/`, and `rust/bridge/` + let include_paths: Vec<_> = paths + .iter() + .flat_map(|path| { + [ + path.join("sql"), + path.join("include"), + path.join("rust").join("bridge"), + ] + }) + .collect(); + + let bindings = match make_bindings_with_includes(&include_paths) { + Ok(v) => v, + Err(e) => { + let le = LoggedError { + location: "bindgen", + e, + loop_count, + paths, + }; + last_error = Some(le); + continue; // just move to the next source paths if we fail here + } + }; + + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("couldn't write bindings"); + + // Tell cargo to rerun if header files change. We walkdir to find only headers + // because using every file was too slow. + for path in include_paths.iter() { + for header_file in walkdir::WalkDir::new(path) + .into_iter() + .filter_map(Result::ok) + .map(walkdir::DirEntry::into_path) + .filter(|p| p.extension() == Some("h".as_ref())) + { + println!("cargo:rerun-if-changed={}", header_file.display()); + } + } + + success = true; + break; + } + + if !success { + panic!("failed to generate bindings. errors: {last_error:#?}"); + } +} + +/// Get the root of our mariadb project +fn mariadb_root() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .parent() + .unwrap() + .parent() + .unwrap() + .to_owned() +} + +/// Find paths provided by CMake environment variables +fn include_paths_from_cmake() -> Option> { + if let Ok(src_dir) = env::var("CMAKE_SOURCE_DIR") { + let Ok(dst_dir) = env::var("CMAKE_BINARY_DIR") else { + panic!("CMAKE_SOURCE_DIR set but CMAKE_BINARY_DIR unset"); + }; + eprintln!("using paths from cmake"); + + Some(vec![PathBuf::from(src_dir), PathBuf::from(dst_dir)]) + } else { + eprintln!("cmake environment not set, skipping"); + None + } +} + +/// Run cmake in our temp directory +fn configure_returning_incl_paths() -> Vec { + eprintln!("no preconfigured source found, running cmake configure"); + + let root = mariadb_root(); + let output_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("cmake"); + + // Run cmake to configure only + Command::new("cmake") + .arg(format!("-S{}", root.display())) + .arg(format!("-B{}", output_dir.display())) + .output() + .expect("failed to invoke cmake"); + + vec![output_dir, root] +} + +/// Given some include directories, see if bindgen works +fn make_bindings_with_includes(include_paths: &[PathBuf]) -> Result { + let incl_args: Vec<_> = include_paths + .iter() + .map(|path| format!("-I{}", path.display())) + .collect(); + + bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("src/wrapper.h") + .header("src/handler_helper.h") + // Fix math.h double defines + .parse_callbacks(Box::new(BuildCallbacks)) + .clang_args(incl_args) + .clang_arg("-xc++") + .clang_arg("-std=c++17") + // Don't derive copy for structs + .derive_copy(false) + // Use `core::ffi` instead of `std::os::raw` + .use_core() + // Will be required in a future version of `rustc` + .wrap_unsafe_ops(true) + // Use rust-style enums labeled with non_exhaustive to represent C enums + .default_enum_style(EnumVariation::ModuleConsts) + // The cpp classes need vtables + .vtable_generation(true) + // We allow only specific types to avoid generating too many unneeded bindings + // + // Bindings for dbug instrumentation + .allowlist_item("_db_.*") + // Bindings for all plugins + .allowlist_item(".*PLUGIN.*") + .allowlist_item(".*INTERFACE_VERSION.*") + .allowlist_item("st_(maria|mysql)_plugin") + // Items for variables + .allowlist_item("mysql_var_.*") + .allowlist_item("TYPELIB") + // Items for for encryption plugins + .allowlist_var("MY_AES.*") + .allowlist_var("ENCRYPTION_.*.*") + .allowlist_item("PLUGIN_.*") + .allowlist_type("st_mariadb_encryption") + // Items for ft parsers + .allowlist_item("enum_ftparser_mode") + .allowlist_item("enum_field_types") + // Items for storage engines + .allowlist_item("handlerton") + .allowlist_item("handler") + .allowlist_item(".*(ha|handler)_bridge.*") + .allowlist_item("st_mysql_storage.*") + .allowlist_type("TABLE(_SHARE)?") + .allowlist_type("MYSQL_HANDLERTON.*") + .allowlist_var("HA_.*") + .allowlist_var("IO_SIZE") + .allowlist_var("MAX_REF_PARTS") + .allowlist_var("MAX_DATA_LENGTH_FOR_KEY") + // Items for the SQL service. Note that `sql_service` (from `st_service_ref`) needs to + // be handwritten because it doesn't seem to import with the expected values (a static vs. + // dynamic thing). + .allowlist_item("MYSQL_.*") + .allowlist_type("sql_service_st") + // Finish the builder and generate the bindings. + .generate() + .map_err(Into::into) +} + +// /// Tell cargo how to find needed libraries +// fn configure_linkage() { +// // Set up link libraries and directories as provided by cmake +// if let Ok(cmake_link_libs) = env::var("CMAKE_LINK_LIBRARIES") { +// eprintln!("link libs: {cmake_link_libs}"); +// for lib in cmake_link_libs.split(';') { +// // Remove the extension +// let libname = lib.split_once('.').map_or(lib, |x| x.0); +// println!("cargo:rustc-link-lib=static={libname}"); +// } +// } + +// if let Ok(cmake_link_dirs) = env::var("CMAKE_LINK_DIRECTORIES") { +// eprintln!("link dirs: {cmake_link_dirs}"); +// for dir in cmake_link_dirs.split(';') { +// println!("cargo:rustc-link-search=native={dir}"); +// } +// } +// } + +#[derive(Debug)] +struct BuildCallbacks; + +impl ParseCallbacks for BuildCallbacks { + /// Simple converter to turn doxygen comments into rustdoc + fn process_comment(&self, comment: &str) -> Option { + let brief_re = Regex::new(r"[\\@]brief ?(.*)").unwrap(); + let param_re = Regex::new(r"[\\@]param(\[(\S+)\])? (\S+)").unwrap(); + let retval_re = Regex::new(r"[\\@]retval (\S+)").unwrap(); + let brackets_re = Regex::new(r"\[(.*)\]").unwrap(); + let doxy_pos_re = Regex::new(r"^< ?(.*)").unwrap(); + let url_re = Regex::new( + r"(?x) + https?:// # scheme + ([-a-zA-Z0-9@:%._\+~\#=]{2,256}\.)+ # subdomain + [a-zA-Z]{2,63} # TLD + \b([-a-zA-Z0-9@:%_\+.~\#?&/=]*) # query parameters + ", + ) + .unwrap(); + + // Add `<...>` brackets to URLs + let comment = url_re.replace_all(comment, "<$0>"); + let comment = brief_re.replace_all(&comment, "$1\n"); + let comment = param_re.replace_all(&comment, "\n* `$3` ($2)"); + let comment = retval_re.replace_all(&comment, "\n**Returns**: $1"); + let comment = brackets_re.replace_all(&comment, r"\[$1\]"); + let comment = doxy_pos_re.replace_all(&comment, "$1"); + + Some(comment.to_string()) + } + + fn add_derives(&self, _info: &DeriveInfo<'_>) -> Vec { + if DERIVE_COPY_NAMES.contains(&_info.name) { + vec!["Copy".to_owned()] + } else { + vec![] + } + } + + fn int_macro(&self, name: &str, _value: i64) -> Option { + let signed_vals = [ + "MARIA_PLUGIN_INTERFACE_VERSION", + "MYSQL_HANDLERTON_INTERFACE_VERSION", + ]; + + if signed_vals.contains(&name) { + Some(bindgen::callbacks::IntKind::Int) + } else { + None + } + } +} diff --git a/rust/bindings/src/hand_impls.rs b/rust/mariadb-sys/src/hand_impls.rs similarity index 100% rename from rust/bindings/src/hand_impls.rs rename to rust/mariadb-sys/src/hand_impls.rs diff --git a/rust/mariadb-sys/src/handler_helper.h b/rust/mariadb-sys/src/handler_helper.h new file mode 100644 index 0000000000000..719947128df78 --- /dev/null +++ b/rust/mariadb-sys/src/handler_helper.h @@ -0,0 +1,13 @@ +// #pragma once + +// #include "my_global.h" +// #include "handler.h" + + +// class ha_helper: public handler { + +// } + +// void build_vtable() { + +// } diff --git a/rust/bindings/src/lib.rs b/rust/mariadb-sys/src/lib.rs similarity index 82% rename from rust/bindings/src/lib.rs rename to rust/mariadb-sys/src/lib.rs index 402b4d3501ea8..6005220f6aece 100644 --- a/rust/bindings/src/lib.rs +++ b/rust/mariadb-sys/src/lib.rs @@ -7,9 +7,12 @@ #![allow(clippy::useless_transmute)] #![allow(clippy::too_many_arguments)] #![allow(clippy::missing_safety_doc)] +// FIXME: +#![allow(improper_ctypes)] // Bindings are autogenerated at build time using build.rs include!(concat!(env!("OUT_DIR"), "/bindings.rs")); mod hand_impls; + pub use hand_impls::*; diff --git a/rust/bindings/src/wrapper.h b/rust/mariadb-sys/src/wrapper.h similarity index 63% rename from rust/bindings/src/wrapper.h rename to rust/mariadb-sys/src/wrapper.h index fb7ab092654d2..cf69fb61de4ee 100644 --- a/rust/bindings/src/wrapper.h +++ b/rust/mariadb-sys/src/wrapper.h @@ -1,7 +1,12 @@ // Directives here indicate what to include in bindings // #include +#include +#include +#include +#include #include #include #include #include +#include diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index d94e8c3d363ee..c49dc7382d99d 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -6,10 +6,19 @@ license = "GPL-2.0-only" [dependencies] -mariadb-sys = { path = "../bindings" } +mariadb-sys = { path = "../mariadb-sys" } mariadb-macros = { path = "../macros" } cstr = "0.2.11" time = { version = "0.3.29", features = ["formatting"]} log = "0.4.20" env_logger = "0.10.0" encoding_rs = "0.8.33" +strum = { version = "0.25.0", features = ["derive"] } + +# We use cargo features to segment off different areas of the MariaDB source that +# require specific linkage. +[features] +# Storage interfaces +storage = [] +# Interfaces to use the sql service +service-sql = [] diff --git a/rust/mariadb/src/helpers.rs b/rust/mariadb/src/helpers.rs deleted file mode 100644 index 0198058c0b31a..0000000000000 --- a/rust/mariadb/src/helpers.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::cell::UnsafeCell; - -/// Used for plugin registrations, which are in global scope. -#[doc(hidden)] -#[derive(Debug)] -#[repr(transparent)] -pub struct UnsafeSyncCell(UnsafeCell); - -impl UnsafeSyncCell { - /// # Safety - /// - /// This inner value be used in a Sync/Send way - pub const unsafe fn new(value: T) -> Self { - Self(UnsafeCell::new(value)) - } - - pub const fn as_ptr(&self) -> *const T { - self.0.get() - } - - pub const fn get(&self) -> *mut T { - self.0.get() - } - - pub fn get_mut(&mut self) -> &mut T { - self.0.get_mut() - } -} - -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for UnsafeSyncCell {} -unsafe impl Sync for UnsafeSyncCell {} - -#[allow(dead_code)] -pub fn str2bool(s: &str) -> Option { - const TRUE_VALS: [&str; 3] = ["on", "true", "1"]; - const FALSE_VALS: [&str; 3] = ["off", "false", "0"]; - let lower = s.to_lowercase(); - if TRUE_VALS.contains(&lower.as_str()) { - Some(true) - } else if FALSE_VALS.contains(&lower.as_str()) { - Some(false) - } else { - None - } -} diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index dfbcc8aa12c3e..c1077faa31b82 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -17,23 +17,44 @@ use std::io::Write; -mod common; -mod helpers; +mod my_alloc; pub mod plugin; +#[cfg(feature = "service-sql")] pub mod service_sql; +pub mod sql; +#[cfg(feature = "storage")] +pub mod storage; +mod table; +mod thd; mod util; +#[cfg(feature = "service-sql")] +mod value; + +#[cfg(test)] +mod tests; -#[doc(inline)] -pub use common::*; pub use log; #[doc(hidden)] pub use mariadb_sys as bindings; +pub use my_alloc::MemRoot; +pub use table::TableShare; +#[cfg(test)] +use tests::assert_layouts_eq; +pub use thd::Thd; +#[cfg(feature = "service-sql")] +#[doc(inline)] +pub use value::*; #[doc(hidden)] pub mod internals { pub use cstr::cstr; - pub use super::helpers::UnsafeSyncCell; + pub use super::util::{parse_version_str, UnsafeSyncCell}; +} + +pub mod dbug { + //! Instrumentation for MariaDB's `dbug` log and backtrace module. + pub use mariadb_macros::dbug_instrument as instrument; } /// Just a more intuitive type alias for a pass/fail `Result` with no contents. diff --git a/rust/mariadb/src/my_alloc.rs b/rust/mariadb/src/my_alloc.rs new file mode 100644 index 0000000000000..1adf8acff1b2a --- /dev/null +++ b/rust/mariadb/src/my_alloc.rs @@ -0,0 +1,15 @@ +#![allow(dead_code)] + +use crate::bindings; + +pub struct MemRoot<'a> { + root: &'a bindings::MEM_ROOT, +} + +impl<'a> MemRoot<'a> { + pub(crate) unsafe fn from_raw(root: *mut bindings::MEM_ROOT) -> Self { + Self { + root: unsafe { &*root }, + } + } +} diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 96c2bf3939e28..fc038423375bc 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -70,9 +70,15 @@ use std::ffi::{c_int, c_uint}; use std::str::FromStr; use mariadb_sys as bindings; + pub mod encryption; + mod encryption_wrapper; pub mod ftparser; +#[cfg(feature = "storage")] +pub mod storage; +#[cfg(feature = "storage")] +mod storage_wrapper; mod variables; mod variables_parse; mod wrapper; @@ -91,6 +97,10 @@ pub mod internals { wrap_crypt_ctx_finish, wrap_crypt_ctx_init, wrap_crypt_ctx_size, wrap_crypt_ctx_update, wrap_encrypted_length, WrapKeyMgr, }; + #[cfg(feature = "storage")] + pub use super::storage_wrapper::{ + build_handler_vtable, wrap_storage_deinit_fn, wrap_storage_init_fn, HandlertonMeta, + }; pub use super::variables::SysVarInterface; pub use super::wrapper::{ default_deinit_notype, default_init_notype, new_null_plugin_st, wrap_deinit_fn, diff --git a/rust/mariadb/src/plugin/storage.rs b/rust/mariadb/src/plugin/storage.rs new file mode 100644 index 0000000000000..c9cce57258bfa --- /dev/null +++ b/rust/mariadb/src/plugin/storage.rs @@ -0,0 +1,70 @@ +#[macro_export] +macro_rules! register_plugin_storage { + ( + name: + $name:literal,author: + $author:literal,description: + $description:literal,license: + $license:expr,maturity: + $maturity:expr,version: + $version:literal,handlerton: + $hton:ty $(,)? + ) => { + static STORAGE_ENGINE: $crate::bindings::st_mysql_storage_engine = + $crate::bindings::st_mysql_storage_engine { + interface_version: $crate::bindings::MYSQL_HANDLERTON_INTERFACE_VERSION, + }; + + #[no_mangle] + #[cfg(not(make_static_lib))] + #[allow(non_upper_case_globals)] + static _maria_plugin_interface_version_: ::std::ffi::c_int = + $crate::bindings::MARIA_PLUGIN_INTERFACE_VERSION; + + #[no_mangle] + #[cfg(not(make_static_lib))] + #[allow(non_upper_case_globals)] + static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = + std::mem::size_of::<$crate::bindings::st_maria_plugin>() as ::std::ffi::c_int; + + #[no_mangle] + #[cfg(not(make_static_lib))] + #[allow(non_upper_case_globals)] + static _maria_plugin_declarations_: [$crate::internals::UnsafeSyncCell< + $crate::bindings::st_maria_plugin, + >; 2] = unsafe { + [ + $crate::internals::UnsafeSyncCell::new($crate::bindings::st_maria_plugin { + type_: $crate::plugin::PluginType::MyStorageEngine.to_ptype_registration(), + info: std::ptr::addr_of!(STORAGE_ENGINE).cast_mut().cast(), + name: $crate::internals::cstr!($name).as_ptr(), + author: $crate::internals::cstr!($author).as_ptr(), + descr: $crate::internals::cstr!($description).as_ptr(), + license: $license.to_license_registration(), + init: Some($crate::plugin::internals::wrap_storage_init_fn::<$hton>), + deinit: Some($crate::plugin::internals::wrap_storage_deinit_fn::<$hton>), + version: $crate::internals::parse_version_str($version), + status_vars: ::std::ptr::null_mut(), + system_vars: ::std::ptr::null_mut(), + version_info: $crate::internals::cstr!($version).as_ptr(), + maturity: $maturity.to_maturity_registration(), + }), + $crate::internals::UnsafeSyncCell::new( + $crate::plugin::internals::new_null_plugin_st(), + ), + ] + }; + + impl $crate::plugin::internals::PluginMeta for $hton { + const NAME: &'static str = $name; + } + + impl $crate::plugin::internals::HandlertonMeta for $hton { + fn get_vtable() -> &'static $crate::bindings::handler_bridge_vt { + static VTABLE: $crate::bindings::handler_bridge_vt = + $crate::plugin::internals::build_handler_vtable::<$hton>(); + &VTABLE + } + } + }; +} diff --git a/rust/mariadb/src/plugin/storage_wrapper.rs b/rust/mariadb/src/plugin/storage_wrapper.rs new file mode 100644 index 0000000000000..13679468c5c37 --- /dev/null +++ b/rust/mariadb/src/plugin/storage_wrapper.rs @@ -0,0 +1,365 @@ +#![allow(unused)] + +use std::any::TypeId; +use std::ffi::{c_char, c_int, c_uchar, c_uint, c_ulong, c_ulonglong, c_void, CStr, CString}; +use std::sync::Mutex; +use std::{mem, ptr}; + +use log::info; + +use super::wrapper::{init_common, PluginMeta}; +use crate::storage::{Handler, Handlerton}; +use crate::{bindings, MemRoot, TableShare}; + +/// Trait implemented by the macro for an easy +pub trait HandlertonMeta: Handlerton + PluginMeta { + /// This function should return the vtable, which should be located in statics. + fn get_vtable() -> &'static bindings::handler_bridge_vt; +} + +// fn build_cstring(s: &str) -> &'static CStr { +// static ALL: Mutex> = Mutex::new(Vec::new()); +// let cs = CString::try_from(s).unwrap(); +// ALL.lock().unwrap().push(cs); +// todo!() +// } + +/// Initialize the handlerton +pub extern "C" fn wrap_storage_init_fn(hton: *mut c_void) -> c_int { + /// Wrapper to create a handler from this handlerton + #[allow(improper_ctypes_definitions)] + unsafe extern "C" fn create_handler( + hton: *mut bindings::handlerton, + table: *mut bindings::TABLE_SHARE, + mem_root: *mut bindings::MEM_ROOT, + ) -> *mut bindings::handler { + let vt = P::get_vtable(); + unsafe { bindings::ha_bridge_construct(hton, table, mem_root, vt) } + } + + init_common(); + + let hton = hton.cast::(); + unsafe { + (*hton).create = Some(create_handler::

); + (*hton).flags = P::FLAGS; + // (*hton).tablefile_extensions = + } + + log::info!("loaded storage engine {}", P::NAME); + 0 +} + +/// Deinitialize the handlerton. Nothing to do here for now. +pub extern "C" fn wrap_storage_deinit_fn(hton: *mut c_void) -> c_int { + 0 +} + +pub const fn build_handler_vtable() -> bindings::handler_bridge_vt { + bindings::handler_bridge_vt { + constructor: Some(wrap_constructor::), + destructor: Some(wrap_destructor::), + index_type: Some(wrap_index_type::), + table_flags: Some(wrap_table_flags::), + index_flags: Some(wrap_index_flags::), + max_supported_record_length: Some(wrap_max_supported_record_length::), + max_supported_keys: Some(wrap_max_supported_keys::), + max_supported_key_parts: Some(wrap_max_supported_key_parts::), + max_supported_key_length: Some(wrap_max_supported_key_length::), + scan_time: Some(wrap_scan_time::), + keyread_time: Some(wrap_keyread_time::), + rnd_pos_time: Some(wrap_rnd_pos_time::), + open: Some(wrap_open::), + close: Some(wrap_close::), + write_row: Some(wrap_write_row::), + update_row: Some(wrap_update_row::), + delete_row: Some(wrap_delete_row::), + index_read_map: Some(wrap_index_read_map::), + index_next: Some(wrap_index_next::), + index_prev: Some(wrap_index_prev::), + index_first: Some(wrap_index_first::), + index_last: Some(wrap_index_last::), + rnd_init: Some(wrap_rnd_init::), + rnd_end: Some(wrap_rnd_end::), + rnd_next: Some(wrap_rnd_next::), + rnd_pos: Some(wrap_rnd_pos::), + position: Some(wrap_position::), + info: Some(wrap_info::), + extra: Some(wrap_extra::), + external_lock: Some(wrap_external_lock::), + delete_all_rows: Some(wrap_delete_all_rows::), + records_in_range: Some(wrap_records_in_range::), + delete_table: Some(wrap_delete_table::), + create: Some(wrap_create::), + check_if_supported_inplace_alter: Some(wrap_check_if_supported_inplace_alter::), + store_lock: Some(wrap_store_lock::), + } +} + +unsafe extern "C" fn wrap_constructor( + this: *mut bindings::handler_bridge, + _hton: *mut bindings::handlerton, + mem_root: *mut bindings::MEM_ROOT, + table: *mut bindings::TABLE_SHARE, +) { + let ha_rs = unsafe { H::new(TableShare::from_raw(table), MemRoot::from_raw(mem_root)) }; + unsafe { (*this).data = Box::into_raw(Box::new(ha_rs)).cast() }; + + (*this).type_id = mem::transmute(TypeId::of::()); +} + +unsafe extern "C" fn wrap_destructor(this: *mut bindings::handler_bridge) { + // Sanity check we have the expected type + let tid: TypeId = mem::transmute((*this).type_id); + debug_assert_eq!(tid, TypeId::of::()); + + let ha_rs = unsafe { Box::from_raw((*this).data.cast::()) }; + (*this).data = ptr::null_mut(); + + drop(ha_rs) +} + +unsafe extern "C" fn wrap_index_type( + this: *mut bindings::handler_bridge, + arg1: c_uint, +) -> *const c_char { + todo!() +} + +unsafe extern "C" fn wrap_table_flags( + this: *const bindings::handler_bridge, +) -> c_ulonglong { + todo!() +} + +unsafe extern "C" fn wrap_index_flags( + this: *const bindings::handler_bridge, + arg1: c_uint, + arg2: c_uint, + arg3: bool, +) -> c_ulong { + todo!() +} + +unsafe extern "C" fn wrap_max_supported_record_length( + this: *const bindings::handler_bridge, +) -> c_uint { + todo!() +} + +unsafe extern "C" fn wrap_max_supported_keys( + this: *const bindings::handler_bridge, +) -> c_uint { + todo!() +} + +unsafe extern "C" fn wrap_max_supported_key_parts( + this: *const bindings::handler_bridge, +) -> c_uint { + todo!() +} + +unsafe extern "C" fn wrap_max_supported_key_length( + this: *const bindings::handler_bridge, +) -> c_uint { + todo!() +} + +unsafe extern "C" fn wrap_scan_time( + arg1: *mut bindings::handler_bridge, +) -> bindings::IO_AND_CPU_COST { + todo!() +} + +unsafe extern "C" fn wrap_keyread_time( + this: *mut bindings::handler_bridge, + arg2: c_uint, + arg3: c_ulong, + arg4: bindings::ha_rows, + arg5: c_ulonglong, +) -> bindings::IO_AND_CPU_COST { + todo!() +} + +unsafe extern "C" fn wrap_rnd_pos_time( + this: *mut bindings::handler_bridge, + arg2: bindings::ha_rows, +) -> bindings::IO_AND_CPU_COST { + todo!() +} + +unsafe extern "C" fn wrap_open( + this: *mut bindings::handler_bridge, + arg2: *const c_char, + arg3: c_int, + arg4: c_uint, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_close(arg1: *mut bindings::handler_bridge) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_write_row( + arg1: *mut bindings::handler_bridge, + arg2: *const c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_update_row( + arg1: *mut bindings::handler_bridge, + arg2: *const c_uchar, + arg3: *const c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_delete_row( + arg1: *mut bindings::handler_bridge, + arg2: *const c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_index_read_map( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, + arg3: *const c_uchar, + arg4: bindings::key_part_map, + arg5: bindings::ha_rkey_function::Type, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_index_next( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_index_prev( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_index_first( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_index_last( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_rnd_init( + arg1: *mut bindings::handler_bridge, + arg2: bool, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_rnd_end(arg1: *mut bindings::handler_bridge) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_rnd_next( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_rnd_pos( + arg1: *mut bindings::handler_bridge, + arg2: *mut c_uchar, + arg3: *mut c_uchar, +) -> c_int { + todo!() +} +unsafe extern "C" fn wrap_position( + arg1: *mut bindings::handler_bridge, + arg2: *const c_uchar, +) { + todo!() +} + +unsafe extern "C" fn wrap_info( + arg1: *mut bindings::handler_bridge, + arg2: c_uint, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_extra( + arg1: *mut bindings::handler_bridge, + arg2: bindings::ha_extra_function::Type, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_external_lock( + arg1: *mut bindings::handler_bridge, + arg2: *mut bindings::THD, + arg3: c_int, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_delete_all_rows( + arg1: *mut bindings::handler_bridge, +) -> c_int { + todo!() +} + +unsafe extern "C" fn wrap_records_in_range( + arg1: *mut bindings::handler_bridge, + arg2: c_uint, + arg3: *const bindings::key_range, + arg4: *const bindings::key_range, + arg5: *mut bindings::page_range, +) -> bindings::ha_rows { + todo!() +} + +unsafe extern "C" fn wrap_delete_table( + arg1: *mut bindings::handler_bridge, + arg2: *const c_char, +) -> c_int { + todo!() +} + +pub unsafe extern "C" fn wrap_create( + arg1: *mut bindings::handler_bridge, + arg2: *const c_char, + arg3: *mut bindings::TABLE, + arg4: *mut bindings::HA_CREATE_INFO, +) -> c_int { + todo!() +} + +pub unsafe extern "C" fn wrap_check_if_supported_inplace_alter( + arg1: *mut bindings::handler_bridge, + arg2: *mut bindings::TABLE, + arg3: *mut bindings::Alter_inplace_info, +) -> bindings::enum_alter_inplace_result::Type { + todo!() +} + +pub unsafe extern "C" fn wrap_store_lock( + arg1: *mut bindings::handler_bridge, + arg2: *mut bindings::THD, + arg3: *mut *mut bindings::THR_LOCK_DATA, + arg4: bindings::thr_lock_type::Type, +) -> *mut *mut bindings::THR_LOCK_DATA { + todo!() +} diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs index 47089e1e7728e..0a721eafb8c13 100644 --- a/rust/mariadb/src/plugin/wrapper.rs +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -56,7 +56,7 @@ pub unsafe extern "C" fn default_deinit_notype(_: *mut c_void) -> } /// What to run when every plugin is loaded. -fn init_common() { +pub(super) fn init_common() { configure_logger!(); env::set_var("RUST_BACKTRACE", "1"); } diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs index 155df66148be7..1cc10ba77d01a 100644 --- a/rust/mariadb/src/service_sql.rs +++ b/rust/mariadb/src/service_sql.rs @@ -14,7 +14,7 @@ use std::{fmt, mem, slice, str}; use log::{debug, error, trace}; pub use self::error::ClientError; -use crate::helpers::UnsafeSyncCell; +use crate::util::UnsafeSyncCell; use crate::{bindings, Value}; /// Type wrapper for `Result` with a `ClientError` error variant @@ -443,7 +443,7 @@ impl FieldMeta<'_> { str::from_utf8(name_slice).expect("non-utf8 identifier") } - fn ftype(&self) -> bindings::enum_field_types { + fn ftype(&self) -> bindings::enum_field_types::Type { self.inner.type_ } } diff --git a/rust/mariadb/src/sql.rs b/rust/mariadb/src/sql.rs new file mode 100644 index 0000000000000..e5f5864f1567d --- /dev/null +++ b/rust/mariadb/src/sql.rs @@ -0,0 +1,7 @@ +use crate::bindings; + +/// Max parts used as ref +pub const MAX_REFERENCE_PARTS: usize = bindings::MAX_REF_PARTS as usize; + +/// Maximum length of the data portion of an index lookup key +pub const MAX_DATA_LENGTH_FOR_KEY: usize = bindings::MAX_DATA_LENGTH_FOR_KEY as usize; diff --git a/rust/mariadb/src/storage.rs b/rust/mariadb/src/storage.rs new file mode 100644 index 0000000000000..cec8f00d183e1 --- /dev/null +++ b/rust/mariadb/src/storage.rs @@ -0,0 +1,17 @@ +//! Interfaces for storage engines +//! +//! Most of this is for plugin use + +#![allow(unused)] + +mod error; +mod handler; +mod handlerton; + +pub use error::{StorageError, StorageResult}; +pub use handler::{Handler, Mode, OpenOp}; +pub use handlerton::{Handlerton, HandlertonCtx}; + +use crate::bindings; + +pub const MAX_RECORD_LENGTH: usize = bindings::HA_MAX_REC_LENGTH as usize; diff --git a/rust/mariadb/src/storage/error.rs b/rust/mariadb/src/storage/error.rs new file mode 100644 index 0000000000000..3f466d415602b --- /dev/null +++ b/rust/mariadb/src/storage/error.rs @@ -0,0 +1,172 @@ +use strum::{EnumIter, IntoEnumIterator}; + +use crate::bindings; + +pub type StorageResult = Result; + +/// Storage handler error types, as defined in `my_base.h` +#[derive(Debug, EnumIter)] +pub enum StorageError { + /// Didn't find key on read or update + KeyNotFound = bindings::HA_ERR_KEY_NOT_FOUND as isize, + /// Duplicate key on write + FoundDuppKey = bindings::HA_ERR_FOUND_DUPP_KEY as isize, + /// Internal error + InternalError = bindings::HA_ERR_INTERNAL_ERROR as isize, + /// Update with is recoverable + RecordChanged = bindings::HA_ERR_RECORD_CHANGED as isize, + /// Wrong index given to function + WrongIndex = bindings::HA_ERR_WRONG_INDEX as isize, + /// Indexfile is crashed + Crashed = bindings::HA_ERR_CRASHED as isize, + /// Record-file is crashed or table is corrupt + WrongInRecord = bindings::HA_ERR_WRONG_IN_RECORD as isize, + /// Out of memory + OutOfMem = bindings::HA_ERR_OUT_OF_MEM as isize, + /// Initialization failed and should be retried + RetryInit = bindings::HA_ERR_RETRY_INIT as isize, + /// not a MYI file - no signature + NotAtAble = bindings::HA_ERR_NOT_A_TABLE as isize, + /// Command not supported + WrongCommand = bindings::HA_ERR_WRONG_COMMAND as isize, + /// old databasfile + OldFile = bindings::HA_ERR_OLD_FILE as isize, + /// No record read in update() + NoActiveRecord = bindings::HA_ERR_NO_ACTIVE_RECORD as isize, + /// A record is not there + RecordDeleted = bindings::HA_ERR_RECORD_DELETED as isize, + /// No more room in file + RecordFileFull = bindings::HA_ERR_RECORD_FILE_FULL as isize, + /// No more room in file + IndexFileFull = bindings::HA_ERR_INDEX_FILE_FULL as isize, + /// end in next/prev/first/last + EndOfFile = bindings::HA_ERR_END_OF_FILE as isize, + /// unsupported extension used + Unsupported = bindings::HA_ERR_UNSUPPORTED as isize, + /// Too big row + ToBigRow = bindings::HA_ERR_TO_BIG_ROW as isize, + /// Wrong create option + GCreateOption = bindings::HA_WRONG_CREATE_OPTION as isize, + /// Duplicate unique on write + FoundDuppUnique = bindings::HA_ERR_FOUND_DUPP_UNIQUE as isize, + /// Can't open charset + UnknownCharset = bindings::HA_ERR_UNKNOWN_CHARSET as isize, + /// conflicting tables in MERGE + WrongMrgTableDef = bindings::HA_ERR_WRONG_MRG_TABLE_DEF as isize, + /// Last (automatic?) repair failed + CrashedOnRepair = bindings::HA_ERR_CRASHED_ON_REPAIR as isize, + /// Table must be repaired + CrashedOnUsage = bindings::HA_ERR_CRASHED_ON_USAGE as isize, + LockWaitTimeout = bindings::HA_ERR_LOCK_WAIT_TIMEOUT as isize, + LockTableFull = bindings::HA_ERR_LOCK_TABLE_FULL as isize, + /// Updates not allowed + ReadOnlyTransaction = bindings::HA_ERR_READ_ONLY_TRANSACTION as isize, + LockDeadlock = bindings::HA_ERR_LOCK_DEADLOCK as isize, + /// Cannot add a foreign key constr. + CannotAddForeign = bindings::HA_ERR_CANNOT_ADD_FOREIGN as isize, + /// Cannot add a child row + NoReferencedRow = bindings::HA_ERR_NO_REFERENCED_ROW as isize, + /// Cannot delete a parent row + RowIsReferenced = bindings::HA_ERR_ROW_IS_REFERENCED as isize, + /// No savepoint with that name + NoSavepoint = bindings::HA_ERR_NO_SAVEPOINT as isize, + /// Non unique key block size + NonUniqueBlockSize = bindings::HA_ERR_NON_UNIQUE_BLOCK_SIZE as isize, + /// The table does not exist in engine + NoSuchTable = bindings::HA_ERR_NO_SUCH_TABLE as isize, + /// The table existed in storage engine + TableExist = bindings::HA_ERR_TABLE_EXIST as isize, + /// Could not connect to storage engine + NoConnection = bindings::HA_ERR_NO_CONNECTION as isize, + /// NULLs are not supported in spatial index + NullInSpatial = bindings::HA_ERR_NULL_IN_SPATIAL as isize, + /// The table changed in storage engine + TableDefChanged = bindings::HA_ERR_TABLE_DEF_CHANGED as isize, + /// There's no partition in table for given value + NoPartitionFound = bindings::HA_ERR_NO_PARTITION_FOUND as isize, + /// Row-based binlogging of row failed + RbrLoggingFailed = bindings::HA_ERR_RBR_LOGGING_FAILED as isize, + /// Index needed in foreign key constr + DropIndexFk = bindings::HA_ERR_DROP_INDEX_FK as isize, + /// Upholding foreign key constraints would lead to a duplicate key error in e other table. + ForeignDuplicateKey = bindings::HA_ERR_FOREIGN_DUPLICATE_KEY as isize, + /// The table changed in storage engine + TableNeedsUpgrade = bindings::HA_ERR_TABLE_NEEDS_UPGRADE as isize, + /// The table is not writable + TableReadonly = bindings::HA_ERR_TABLE_READONLY as isize, + /// Failed to get next autoinc value + AutoincReadFailed = bindings::HA_ERR_AUTOINC_READ_FAILED as isize, + /// Failed to set row autoinc value + AutoincErange = bindings::HA_ERR_AUTOINC_ERANGE as isize, + /// Generic error + Generic = bindings::HA_ERR_GENERIC as isize, + /// row not actually updated: new values same as the old values + RecordIsTheSame = bindings::HA_ERR_RECORD_IS_THE_SAME as isize, + /// It is not possible to log this statement + LoggingImpossible = bindings::HA_ERR_LOGGING_IMPOSSIBLE as isize, + /// The event was corrupt, leading to illegal data being read + CorruptEvent = bindings::HA_ERR_CORRUPT_EVENT as isize, + /// New file format + NewFile = bindings::HA_ERR_NEW_FILE as isize, + /// The event could not be processed. No other handler error happene. + RowsEventApply = bindings::HA_ERR_ROWS_EVENT_APPLY as isize, + /// Error during initialization + Initialization = bindings::HA_ERR_INITIALIZATION as isize, + /// File too short + FileTooShort = bindings::HA_ERR_FILE_TOO_SHORT as isize, + /// Wrong CRC on page + WrongCrc = bindings::HA_ERR_WRONG_CRC as isize, + /// oo many active concurrent transactions + TooManyConcurrentTrxs = bindings::HA_ERR_TOO_MANY_CONCURRENT_TRXS as isize, + /// There's no explicitly listed partition in table for the given value + NotInLockPartitions = bindings::HA_ERR_NOT_IN_LOCK_PARTITIONS as isize, + /// Index column length exceeds limit + IndexColTooLong = bindings::HA_ERR_INDEX_COL_TOO_LONG as isize, + /// Index corrupted + IndexCorrupt = bindings::HA_ERR_INDEX_CORRUPT as isize, + /// Undo log record too big + UndoRecTooBig = bindings::HA_ERR_UNDO_REC_TOO_BIG as isize, + /// Invalid InnoDB Doc ID + InvalidDocid = bindings::HA_FTS_INVALID_DOCID as isize, + // /// Table being used in foreign key check (disabled in `my_base.h`) + // TableInFkCheck = bindings::HA_ERR_TABLE_IN_FK_CHECK as isize, + /// The tablespace existed in storage engine + TablespaceExists = bindings::HA_ERR_TABLESPACE_EXISTS as isize, + /// Table has too many columns + TooManyFields = bindings::HA_ERR_TOO_MANY_FIELDS as isize, + /// Row in wrong partition + RowInWrongPartition = bindings::HA_ERR_ROW_IN_WRONG_PARTITION as isize, + RowNotVisible = bindings::HA_ERR_ROW_NOT_VISIBLE as isize, + AbortedByUser = bindings::HA_ERR_ABORTED_BY_USER as isize, + DiskFull = bindings::HA_ERR_DISK_FULL as isize, + IncompatibleDefinition = bindings::HA_ERR_INCOMPATIBLE_DEFINITION as isize, + /// Too many words in a phrase + FtsTooManyWordsInPhrase = bindings::HA_ERR_FTS_TOO_MANY_WORDS_IN_PHRASE as isize, + /// Table encrypted but decrypt failed + DecryptionFailed = bindings::HA_ERR_DECRYPTION_FAILED as isize, + /// FK cascade depth exceeded + FkDepthExceeded = bindings::HA_ERR_FK_DEPTH_EXCEEDED as isize, + /// Missing Tablespace + TablespaceMissing = bindings::HA_ERR_TABLESPACE_MISSING as isize, + SequenceInvalidData = bindings::HA_ERR_SEQUENCE_INVALID_DATA as isize, + SequenceRunOut = bindings::HA_ERR_SEQUENCE_RUN_OUT as isize, + CommitError = bindings::HA_ERR_COMMIT_ERROR as isize, + PartitionList = bindings::HA_ERR_PARTITION_LIST as isize, + NoEncryption = bindings::HA_ERR_NO_ENCRYPTION as isize, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_error_counts() { + // Verify errors are synced between here and MDB + // numbers 125 (unnamed) and 183 (`HA_ERR_TABLE_IN_FK_CHECK`) are missing + const SKIPPED_ERR_COUNT: usize = 2; + assert_eq!( + StorageError::iter().count() + SKIPPED_ERR_COUNT, + bindings::HA_ERR_ERRORS.try_into().unwrap() + ); + } +} diff --git a/rust/mariadb/src/storage/handler.rs b/rust/mariadb/src/storage/handler.rs new file mode 100644 index 0000000000000..ae1c905dba70c --- /dev/null +++ b/rust/mariadb/src/storage/handler.rs @@ -0,0 +1,270 @@ +use std::cmp::max; +use std::ffi::{c_int, c_uint, c_ulong, CStr}; +use std::marker::PhantomData; +use std::mem; +use std::path::Path; + +use super::{Handlerton, StorageError, StorageResult, MAX_RECORD_LENGTH}; +use crate::sql::{MAX_DATA_LENGTH_FOR_KEY, MAX_REFERENCE_PARTS}; +use crate::{bindings, MemRoot, TableShare}; + +pub const IO_SIZE: usize = bindings::IO_SIZE as usize; + +#[derive(Clone, Default)] +pub struct TableFlags(bindings::handler_Table_flags); +#[derive(Clone, Default)] +pub struct IndexFlags(c_ulong); +#[derive(Debug)] +pub struct IoAndCpuCost(bindings::IO_AND_CPU_COST); + +impl IoAndCpuCost { + pub fn new(io: f64, cpu: f64) -> Self { + Self(bindings::IO_AND_CPU_COST { io, cpu }) + } + + pub fn io(&self) -> f64 { + self.0.io + } + + pub fn cpu(&self) -> f64 { + self.0.io + } +} + +#[repr(transparent)] +pub struct CreateInfo<'a> { + inner: bindings::HA_CREATE_INFO, + phantom: PhantomData<&'a ()>, +} + +impl<'a> CreateInfo<'a> { + pub(crate) unsafe fn from_raw(ptr: *const bindings::HA_CREATE_INFO) -> &'a Self { + unsafe { &*ptr.cast() } + } +} + +#[repr(transparent)] +pub struct HandlerCtx<'a> { + inner: bindings::handler, + phantom: PhantomData<&'a ()>, +} + +impl<'a> HandlerCtx<'a> { + fn stats(&self) -> &'a Statistics { + unsafe { mem::transmute(&self.inner.stats) } + } + fn costs(&self) -> &'a OptimizerCosts { + unsafe { mem::transmute(&self.inner.costs) } + } +} + +#[repr(transparent)] +pub struct Statistics(bindings::ha_statistics); + +impl Statistics { + pub fn data_file_length(&self) -> usize { + self.0.data_file_length.try_into().unwrap() + } + pub fn max_data_file_length(&self) -> usize { + self.0.max_data_file_length.try_into().unwrap() + } + pub fn index_file_length(&self) -> usize { + self.0.index_file_length.try_into().unwrap() + } + pub fn max_index_file_length(&self) -> usize { + self.0.max_index_file_length.try_into().unwrap() + } + pub fn delete_length(&self) -> usize { + self.0.delete_length.try_into().unwrap() + } + pub fn auto_increment_value(&self) -> usize { + self.0.auto_increment_value.try_into().unwrap() + } + pub fn records(&self) -> usize { + self.0.records.try_into().unwrap() + } + pub fn deleted(&self) -> usize { + self.0.deleted.try_into().unwrap() + } + pub fn mean_rec_length(&self) -> usize { + self.0.mean_rec_length.try_into().unwrap() + } + pub fn block_size(&self) -> usize { + self.0.block_size.try_into().unwrap() + } + pub fn checksum(&self) -> u32 { + self.0.checksum + } +} + +#[repr(transparent)] +pub struct OptimizerCosts(bindings::OPTIMIZER_COSTS); + +impl OptimizerCosts { + fn index_block_copy_cost(&self) -> f64 { + self.0.index_block_copy_cost + } +} + +#[repr(transparent)] +pub struct Mode(c_int); +#[repr(transparent)] +pub struct OpenOp(c_uint); + +pub trait Handler: 'static { + type Handlerton: Handlerton; + + /// Set this to true if index support is available. If so, this type must + /// also implement `IndexHandler`. + const SUPPORTS_INDEX: bool = false; + + // const TABLE_FLAGS: TableFlags = TableFlags(0); + const MAX_SUPPORTED_RECORD_LENGTH: usize = bindings::HA_MAX_REC_LENGTH as usize; + + /// Create a new handler + /// + /// # When is this called? + /// + /// - Every time there is a new connection + fn new(table: &TableShare, mem_root: MemRoot) -> Self; + + /// Open a table, the name will be the name of the file. + /// + /// Closing happens by dropping this item. + /// + /// # When is this called? + /// + /// Not for every request, more to come... + // TODO: figure out the interaction with ::extra in the C docs, maybe refactor + fn open(name: &Path, mode: Mode, open_options: OpenOp) -> StorageResult; + + /// Create a new table and exit + /// + /// This should + /// + /// # When is this called? + /// + /// - SQL `CREATE TABLE` statements + fn create(&self, name: &CStr, form: TableShare, create_info: &CreateInfo) {} + + fn table_flags(&self) -> TableFlags { + TableFlags::default() + } + + /// This is an INSERT statement + fn write_row(buf: &CStr) {} + + fn update_row() {} + + fn delete_row() {} + + /// Delete all rows + /// + /// ## When is this called? + /// + /// - SQL `DELETE` with no `WHERE` clause + fn delete_all_rows() {} + + fn store_lock(&self) {} + + /// Time for a full table data scan + fn scan_time(&self, ctx: HandlerCtx) -> IoAndCpuCost { + // Duplicated from handler.h + let length = ctx.stats().data_file_length() as f64; + let io = length / (IO_SIZE as f64); + let bsize = ctx.stats().block_size() as f64; + let cpu = ((length + bsize - 1.0) / bsize) + ctx.costs().index_block_copy_cost(); + let cpu = cpu.clamp(0.0, 1e200); + IoAndCpuCost::new(io, cpu) + } + + /// Cost of fetching `rows` records through `rnd_pos`. + fn rnd_pos_time(&self, ctx: HandlerCtx, rows: usize) -> IoAndCpuCost { + let r = rows as f64; + let io = ((ctx.stats().block_size() + IO_SIZE).saturating_sub(1) as f64) / (IO_SIZE as f64); + let cpu = r + ctx.costs().index_block_copy_cost(); + + IoAndCpuCost::new(io, cpu) + } +} + +pub trait RepairingHandler: Handler { + fn pre_calculate_checksum(&self) -> u32 { + 0 + } + + fn calculate_checksum(&self) -> u32 { + 0 + } + + fn is_crashed(&self) -> bool { + false + } + + fn auto_repair(error: i32) -> bool { + false + } +} + +pub trait IndexableHandler: Handler { + fn max_supported_record_length(&self) -> usize { + MAX_RECORD_LENGTH + } + fn max_supported_keys(&self) -> usize { + 0 + } + + fn max_supported_key_parts(&self) -> usize { + MAX_REFERENCE_PARTS + } + + fn max_supported_key_length(&self) -> usize { + MAX_DATA_LENGTH_FOR_KEY + } + + /// Return the display name of the index, if any + fn index_type(&self, index_number: usize) -> Option<&'static CStr> { + None + } + + fn index_flags(&self, index: usize, part: usize, all_parts: bool) -> IndexFlags { + IndexFlags::default() + } + + /// Calculate the cost of `index_only` scan for a given index, a number of ranges, + /// and a number of records. + /// + /// - `index`: the index to read + /// - `rows`: number of records to read + /// - `blocks`: number of IO blocks that need to be accessed, or 0 if not known. + fn keyread_time( + &self, + ctx: &HandlerCtx, + index: usize, + ranges: usize, + rows: usize, + blocks: usize, + ) -> IoAndCpuCost { + todo!() + } + + /// Time for a full table index scan without copy or compare cost. + fn key_scan_time(&self, ctx: &HandlerCtx, index: usize, rows: usize) -> IoAndCpuCost { + Self::keyread_time(self, ctx, index, 1, max(rows, 1), 0) + } + + fn index_read_map() {} + fn index_next() {} + fn index_prev() {} + fn index_first() {} + fn index_last() {} +} + +pub trait InplaceAlterTable {} + +// fn foo(a: Box>) { +// todo!() +// } +// fn bar(a: Box>) { +// todo!() +// } diff --git a/rust/mariadb/src/storage/handlerton.rs b/rust/mariadb/src/storage/handlerton.rs new file mode 100644 index 0000000000000..ba97155cdb867 --- /dev/null +++ b/rust/mariadb/src/storage/handlerton.rs @@ -0,0 +1,99 @@ +use super::Handler; +use crate::thd::ThdKillLevel; +use crate::{bindings, MemRoot, TableShare, Thd}; + +pub enum Error {} +pub type Result = std::result::Result; + +pub struct HandlertonCtx<'a> { + hton: &'a mut bindings::handlerton, + thd: &'a mut bindings::THD, +} + +impl<'a> HandlertonCtx<'a> { + unsafe fn new(hton: *mut bindings::handlerton, thd: *mut bindings::THD) -> Self { + debug_assert!(!hton.is_null()); + debug_assert!(!thd.is_null()); + Self { + hton: unsafe { &mut *hton }, + thd: unsafe { &mut *thd }, + } + } +} + +pub struct HandlertonThd<'a> { + thd: &'a mut Thd<'a>, + slot: usize, +} + +impl<'a> HandlertonThd<'a> { + unsafe fn new(hton: *mut bindings::handlerton, thd: *mut bindings::THD) -> Self { + debug_assert!(!hton.is_null()); + debug_assert!(!thd.is_null()); + Self { + thd: unsafe { Thd::new_mut(thd) }, + slot: unsafe { (*hton).slot }.try_into().unwrap(), + } + } + + fn data(&self) { + // let x = self.thd.0.ha_data[self.slot]; + } +} + +// TODO: do we really have a `self`? I.e., can a `handlerton` contain arbitrary data? + +/// A "handlerton" ("handler singleton") is the entrypoint for a storage engine handler. +/// +/// This defines registration and creation information. +pub trait Handlerton { + type Handler: Handler; + /// A type of data that is stored during a savepoint. + type SavePoint; + const FLAGS: u32 = 0; + + /// Extensions of files created for a single table in the database directory + /// (`datadir/db_name/`). + const TABLEFILE_EXTENSIONS: &'static [&'static str] = &[]; + + // fn close_connection(thd: &HandlertonThd) -> Result; + // fn kill_query(thd: &HandlertonThd, level: ThdKillLevel); + + // TODO: should Savepoint be its own trait? + + // /// Create a new savepoint + // fn savepoint_set(thd: &HandlertonThd) -> Result; + // /// Restore to a previous savepoint + // fn savepoint_rollback(thd: &HandlertonThd, sv: &mut Self::SavePoint) -> Result; + // fn savepoint_rollback_can_release_mdl(thd: &HandlertonThd) -> bool { + // false + // } + // fn savepoint_release(thd: &HandlertonThd, sv: &mut Self::SavePoint) -> Result; + + // /// Perform the commit. + // /// + // /// If `is_true_commit` is false, we are in an end of statement within a transaction + // fn commit(thd: &HandlertonThd, is_true_commit: bool) -> Result; + // fn commit_ordered(thd: &HandlertonThd, is_true_commit: bool) -> Result; + // fn rollback(thd: &HandlertonThd, is_true_commit: bool) -> Result; + // fn prepare(thd: &HandlertonThd, is_true_commit: bool) -> Result; + // fn prepare_ordered(thd: &HandlertonThd, is_true_commit: bool) -> Result; + + //... more to do +} + +// TODO: also take table_options, field_options, and index_options. Maybe we can put these +// into traits? +pub fn initialize_handlerton(hton: &mut bindings::handlerton) { + hton.kill_query = Some(wrap_kill_query::); +} + +#[allow(improper_ctypes_definitions)] // level is not FFI-safe +unsafe extern "C" fn wrap_kill_query( + hton: *mut bindings::handlerton, + thd: *mut bindings::THD, + level: bindings::thd_kill_levels::Type, +) { + let ctx = unsafe { HandlertonCtx::new(hton, thd) }; + todo!() +} diff --git a/rust/mariadb/src/table.rs b/rust/mariadb/src/table.rs new file mode 100644 index 0000000000000..17d1b1f75a489 --- /dev/null +++ b/rust/mariadb/src/table.rs @@ -0,0 +1,42 @@ +#![allow(dead_code)] + +use std::cell::UnsafeCell; +use std::marker::PhantomData; + +use crate::bindings; + +#[repr(transparent)] +pub struct Table<'a> { + share: UnsafeCell, + phantom: PhantomData<&'a ()>, +} + +impl<'a> Table<'a> { + pub(crate) unsafe fn from_raw(tab: *const bindings::TABLE) -> &'a Self { + unsafe { &*tab.cast() } + } +} + +/// A structure shared among all table objects. Has lots of internal locking. +#[repr(transparent)] +pub struct TableShare<'a> { + share: UnsafeCell, + phantom: PhantomData<&'a ()>, +} + +impl<'a> TableShare<'a> { + pub(crate) unsafe fn from_raw(tab: *const bindings::TABLE_SHARE) -> &'a Self { + unsafe { &*tab.cast() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_layout() { + crate::assert_layouts_eq!(bindings::TABLE, Table); + crate::assert_layouts_eq!(bindings::TABLE_SHARE, TableShare); + } +} diff --git a/rust/mariadb/src/tests.rs b/rust/mariadb/src/tests.rs new file mode 100644 index 0000000000000..0bc29335b54cf --- /dev/null +++ b/rust/mariadb/src/tests.rs @@ -0,0 +1,10 @@ +macro_rules! assert_layouts_eq { + ($a:ty, $b:ty) => { + assert_eq!( + core::alloc::Layout::new::<$a>(), + core::alloc::Layout::new::<$b>(), + ); + }; +} + +pub(crate) use assert_layouts_eq; diff --git a/rust/mariadb/src/thd.rs b/rust/mariadb/src/thd.rs new file mode 100644 index 0000000000000..717cd24be2d59 --- /dev/null +++ b/rust/mariadb/src/thd.rs @@ -0,0 +1,24 @@ +#![allow(dead_code)] + +use std::marker::PhantomData; + +use crate::bindings; + +#[repr(transparent)] +pub struct Thd<'a>(pub(crate) bindings::THD, PhantomData<&'a ()>); + +impl<'a> Thd<'a> { + pub(crate) unsafe fn new_mut(ptr: *mut bindings::THD) -> &'a mut Self { + unsafe { &mut *ptr.cast::() } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ThdKillLevel { + /// No action needed + NotKilled = bindings::thd_kill_levels::THD_IS_NOT_KILLED as isize, + /// Abort when possible, don't corrupt any data + AbortSoftly = bindings::thd_kill_levels::THD_ABORT_SOFTLY as isize, + /// Abort as soon as possible + AbortAsap = bindings::thd_kill_levels::THD_ABORT_ASAP as isize, +} diff --git a/rust/mariadb/src/util.rs b/rust/mariadb/src/util.rs index a8f1f6abb1ec8..4d25054ee8e2c 100644 --- a/rust/mariadb/src/util.rs +++ b/rust/mariadb/src/util.rs @@ -1,7 +1,41 @@ -use core::ffi::c_int; +use std::cell::UnsafeCell; +use std::ffi::{c_int, c_uint}; use crate::EmptyResult; +/// Used for plugin registrations, which are in global scope. +#[doc(hidden)] +#[derive(Debug)] +#[repr(transparent)] +pub struct UnsafeSyncCell(UnsafeCell); + +impl UnsafeSyncCell { + /// # Safety + /// + /// This inner value be used in a Sync/Send way + pub const unsafe fn new(value: T) -> Self { + Self(UnsafeCell::new(value)) + } + + pub const fn as_ptr(&self) -> *const T { + self.0.get() + } + + pub const fn get(&self) -> *mut T { + self.0.get() + } + + pub fn get_mut(&mut self) -> &mut T { + self.0.get_mut() + } +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for UnsafeSyncCell {} +unsafe impl Sync for UnsafeSyncCell {} + +/// Turn an integer into a `Result<(), ()>`. 0 is success, anything else is an error. +#[allow(dead_code)] pub(crate) fn to_result(val: c_int) -> EmptyResult { if val == 0 { EmptyResult::Ok(()) @@ -9,3 +43,81 @@ pub(crate) fn to_result(val: c_int) -> EmptyResult { EmptyResult::Err(()) } } + +#[allow(dead_code)] +pub fn str2bool(s: &str) -> Option { + const TRUE_VALS: [&str; 3] = ["on", "true", "1"]; + const FALSE_VALS: [&str; 3] = ["off", "false", "0"]; + let lower = s.to_lowercase(); + if TRUE_VALS.contains(&lower.as_str()) { + Some(true) + } else if FALSE_VALS.contains(&lower.as_str()) { + Some(false) + } else { + None + } +} + +/// Turn a "a.b" version string into a C integer +/// +/// This is ugly and panicky since we need it to be const +pub const fn parse_version_str(s: &str) -> c_uint { + const fn get_single_val(buf: &[u8], start: usize, end: usize) -> u16 { + let mut i = start; + let mut ret: u16 = 0; + + loop { + if i >= end { + break; + } + + let ch = buf[i]; + assert!(ch >= b'0' && ch <= b'9'); + ret *= 10; + ret += (ch - b'0') as u16; + + i += 1; + } + + ret + } + + let buf = s.as_bytes(); + + let mut i = 0; + let mut dot_pos = None; + loop { + if i > buf.len() { + break; + } + if buf[i] == b'.' { + dot_pos = Some(i); + break; + } + i += 1; + } + + let Some(dot_pos) = dot_pos else { + panic!("expected a version string in the form 'a.b'"); + }; + + let major = get_single_val(buf, 0, dot_pos); + let minor = get_single_val(buf, dot_pos + 1, buf.len()); + + assert!(major <= 0xff); + assert!(minor <= 0xff); + + ((major << 8) | minor) as c_uint +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_version_str() { + assert_eq!(parse_version_str("1.2"), 0x0102); + assert_eq!(parse_version_str("0.1"), 0x0001); + assert_eq!(parse_version_str("100.255"), 0x64ff); + } +} diff --git a/rust/mariadb/src/common.rs b/rust/mariadb/src/value.rs similarity index 99% rename from rust/mariadb/src/common.rs rename to rust/mariadb/src/value.rs index 41ddeaad1add7..769d6b20c8922 100644 --- a/rust/mariadb/src/common.rs +++ b/rust/mariadb/src/value.rs @@ -29,7 +29,7 @@ impl<'a> Value<'a> { /// Don't ask me why but our responses from the server API seem to all be strings /// So: take a pointer to the string then parse it as whatever value we expect pub(crate) unsafe fn from_str_ptr( - ty: bindings::enum_field_types, + ty: bindings::enum_field_types::Type, ptr: *const c_char, len: usize, ) -> Self { diff --git a/rust/plugins/keymgt-clevis/Cargo.toml b/rust/plugins/keymgt-clevis/Cargo.toml index 7b8ecc9013dbe..d2c40a2361743 100644 --- a/rust/plugins/keymgt-clevis/Cargo.toml +++ b/rust/plugins/keymgt-clevis/Cargo.toml @@ -10,5 +10,5 @@ crate-type = ["cdylib"] [dependencies] clevis = ">= 0.4.1" josekit = "0.8.3" -mariadb = { path = "../../mariadb" } +mariadb = { path = "../../mariadb", features = ["service-sql"] } ureq = "2.7.1" diff --git a/rust/test.py b/rust/test.py new file mode 100755 index 0000000000000..be2fbfed7798f --- /dev/null +++ b/rust/test.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +import subprocess +import sys + +print("LOOK HERE PY SCRIPT", sys.argv, file=sys.stderr) +print("LOOK HERE PY SCRIPT E", sys.argv) + +subprocess.call(sys.argv[2:])