diff --git a/include/sqlpp11/mysql/connection.h b/include/sqlpp11/mysql/connection.h index 1c711c0e..c44957a0 100644 --- a/include/sqlpp11/mysql/connection.h +++ b/include/sqlpp11/mysql/connection.h @@ -386,10 +386,15 @@ namespace sqlpp return run_prepared_remove_impl(r._prepared_statement); } - //! execute arbitrary command (e.g. create a table) - void execute(const std::string& command) + //! Execute arbitrary statement (e.g. create a table). + //! Essentially this calls mysql_query, see https://dev.mysql.com/doc/c-api/8.0/en/mysql-query.html + //! Note: + //! * This usually only allows a single statement (unless configured otherwise for the connection). + //! * If you are passing a statement with results, like a SELECT, you will need to fetch results before issuing + //! the next statement on the same connection. + void execute(const std::string& statement) { - execute_statement(_handle, command); + execute_statement(_handle, statement); } //! escape given string (does not quote, though) diff --git a/include/sqlpp11/postgresql/connection.h b/include/sqlpp11/postgresql/connection.h index 60faf6a4..590cd1f5 100644 --- a/include/sqlpp11/postgresql/connection.h +++ b/include/sqlpp11/postgresql/connection.h @@ -331,7 +331,9 @@ namespace sqlpp return run_prepared_remove_impl(r._prepared_statement); } - // Execute + //! Execute a single statement (like creating a table). + //! Note that technically, this supports executing multiple statements today, but this is likely to change to + //! align with other connectors. std::shared_ptr execute(const std::string& stmt) { validate_connection_handle(); diff --git a/include/sqlpp11/sqlite3/connection.h b/include/sqlpp11/sqlite3/connection.h index aa9cabd8..63aac63c 100644 --- a/include/sqlpp11/sqlite3/connection.h +++ b/include/sqlpp11/sqlite3/connection.h @@ -74,14 +74,20 @@ namespace sqlpp detail::prepared_statement_handle_t result{nullptr, handle->config->debug}; - auto rc = sqlite3_prepare_v2(handle->native_handle(), statement.c_str(), static_cast(statement.size()), - &result.sqlite_statement, nullptr); + const char* uncompiledTail = nullptr; + const auto rc = sqlite3_prepare_v2(handle->native_handle(), statement.c_str(), + static_cast(statement.size()), &result.sqlite_statement, &uncompiledTail); if (rc != SQLITE_OK) { throw sqlpp::exception{ "Sqlite3 error: Could not prepare statement: " + std::string(sqlite3_errmsg(handle->native_handle())) + - " (statement was >>" + (rc == SQLITE_TOOBIG ? statement.substr(0, 128) + "..." : statement) + "<<\n"}; + " ,statement was >>" + (rc == SQLITE_TOOBIG ? statement.substr(0, 128) + "..." : statement) + "<<\n"}; + } + + if (uncompiledTail != statement.c_str() + statement.size()) + { + throw sqlpp::exception{"Sqlite3 connector: Cannot execute multi-statements: >>" + statement + "<<\n"}; } return result; @@ -352,7 +358,8 @@ namespace sqlpp return run_prepared_remove_impl(r._prepared_statement); } - //! execute arbitrary command (e.g. create a table) + //! Execute a single arbitrary statement (e.g. create a table) + //! Throws an exception if multiple statements are passed (e.g. separated by semicolon). size_t execute(const std::string& statement) { auto prepared = prepare_statement(_handle, statement); diff --git a/tests/sqlite3/usage/CMakeLists.txt b/tests/sqlite3/usage/CMakeLists.txt index fc8f3b66..55d2ca02 100644 --- a/tests/sqlite3/usage/CMakeLists.txt +++ b/tests/sqlite3/usage/CMakeLists.txt @@ -40,6 +40,7 @@ set(test_files Blob.cpp Connection.cpp ConnectionPool.cpp + Execute.cpp ) create_test_sourcelist(test_sources test_main.cpp ${test_files}) diff --git a/tests/sqlite3/usage/Execute.cpp b/tests/sqlite3/usage/Execute.cpp new file mode 100644 index 00000000..7c3a11f9 --- /dev/null +++ b/tests/sqlite3/usage/Execute.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, Roland Bock + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +namespace sql = sqlpp::sqlite3; + +int Execute(int, char*[]) +{ + sql::connection db({":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "", true}); + + // execute supports single statements. + db.execute(R"(SELECT 1)"); + + // execute throws an exception if multiple statements are passed in the string. + try + { + db.execute(R"(SELECT 1; SELECT 2)"); + } + catch (const sqlpp::exception& e) + { + const auto message = std::string(e.what()); + if (message.find("Cannot execute multi-statements") == message.npos) + { + std::cerr << "Unexpected exception for multi-statement: " << message; + return 1; + } + } + + return 0; +}