├── .gitignore ├── .gitmodules ├── Doxyfile.in ├── .clang-format ├── cmake └── mariadbclientpp-config.cmake.in ├── test ├── AlterTableTest.h ├── StructureTest.h ├── TimeTest.h ├── SelectTest.h ├── TruncationTest.h ├── test_config.h.in ├── GeneralTest.h ├── ParameterizedQueryTest.h ├── RollbackTest.h ├── TruncationTest.cpp ├── StructureTest.cpp ├── CMakeLists.txt ├── AlterTableTest.cpp ├── SkeletonTest.h ├── GeneralTest.cpp ├── RollbackTest.cpp ├── SelectTest.cpp ├── ParameterizedQueryTest.cpp └── TimeTest.cpp ├── src ├── last_error.cpp ├── exceptions.cpp ├── save_point.cpp ├── worker.hpp ├── transaction.cpp ├── worker.cpp ├── bind.cpp ├── private.hpp ├── account.cpp ├── time_span.cpp ├── statement.cpp ├── concurrency.cpp ├── connection.cpp ├── time.cpp └── result_set.cpp ├── include └── mariadb++ │ ├── last_error.hpp │ ├── decimal.hpp │ ├── save_point.hpp │ ├── types.hpp │ ├── bind.hpp │ ├── exceptions.hpp │ ├── transaction.hpp │ ├── concurrency.hpp │ ├── conversion_helper.hpp │ ├── statement.hpp │ ├── data.hpp │ ├── time_span.hpp │ ├── connection.hpp │ ├── result_set.hpp │ ├── account.hpp │ ├── time.hpp │ └── date_time.hpp ├── LICENSE ├── .gitlab-ci.yml ├── .travis.yml ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-* 2 | .idea 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/cmake-modules"] 2 | path = external/cmake-modules 3 | url = ../cmake-modules.git 4 | -------------------------------------------------------------------------------- /Doxyfile.in: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = @CMAKE_PROJECT_NAME@ 2 | PROJECT_NUMBER = @GIT_REFSPEC@:@GIT_SHA1@ 3 | INPUT = @CMAKE_CURRENT_SOURCE_DIR@/src/ @CMAKE_CURRENT_SOURCE_DIR@/include/ 4 | OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/doc/ 5 | GENERATE_LATEX = NO 6 | JAVADOC_AUTOBRIEF = YES 7 | RECURSIVE = YES 8 | FULL_PATH_NAMES = NO 9 | GENERATE_TREEVIEW = YES 10 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | 5 | AccessModifierOffset: -4 6 | AllowShortFunctionsOnASingleLine: Empty 7 | AllowShortIfStatementsOnASingleLine: false 8 | ColumnLimit: 120 9 | DerivePointerAlignment: false 10 | IndentWidth: 4 11 | PointerAlignment: Right 12 | SortIncludes: false 13 | TabWidth: 4 14 | ... 15 | 16 | -------------------------------------------------------------------------------- /cmake/mariadbclientpp-config.cmake.in: -------------------------------------------------------------------------------- 1 | # Get the directory containing this file. 2 | get_filename_component(@PROJECT_NAME@_CURRENT_CONFIG_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 3 | 4 | include(CMakeFindDependencyMacro) 5 | 6 | find_dependency(Threads REQUIRED) 7 | 8 | list(INSERT CMAKE_MODULE_PATH 0 "${@PROJECT_NAME@_CURRENT_CONFIG_DIR}/") 9 | find_dependency(MariaDBClient REQUIRED) 10 | 11 | # Import targets. 12 | include("${@PROJECT_NAME@_CURRENT_CONFIG_DIR}/@PROJECT_NAME@-targets.cmake") 13 | -------------------------------------------------------------------------------- /test/AlterTableTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef __ALTER_TABLE_TEST_H 10 | #define __ALTER_TABLE_TEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class AlterTableTest : public SkeletonTest { 15 | public: 16 | virtual void CreateTestTable() override; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /test/StructureTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_STRUCTURETEST_H 10 | #define MARIADBCLIENTPP_STRUCTURETEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class StructureTest : public SkeletonTest { 15 | protected: 16 | virtual void CreateTestTable() override {} 17 | }; 18 | 19 | #endif // MARIADBCLIENTPP_STRUCTURETEST_H 20 | -------------------------------------------------------------------------------- /test/TimeTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_TIMETEST_H 10 | #define MARIADBCLIENTPP_TIMETEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class TimeTest : public SkeletonTest { 15 | protected: 16 | virtual void CreateTestTable() override { 17 | // do nothing here 18 | } 19 | }; 20 | 21 | #endif // MARIADBCLIENTPP_TIMETEST_H 22 | -------------------------------------------------------------------------------- /test/SelectTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_SELECTTEST_H 10 | #define MARIADBCLIENTPP_SELECTTEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class SelectTest : public SkeletonTest { 15 | // TODO missing: time-based types, string/byte-based types, statement-based tests 16 | 17 | virtual void CreateTestTable() override {} 18 | }; 19 | 20 | #endif // MARIADBCLIENTPP_SELECTTEST_H 21 | -------------------------------------------------------------------------------- /test/TruncationTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_TRUNCATIONTEST_H 10 | #define MARIADBCLIENTPP_TRUNCATIONTEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class TruncationTest : public SkeletonTest { 15 | protected: 16 | void CreateTestTable() override { 17 | m_con->execute("CREATE TABLE " + m_table_name + " (id INT UNSIGNED);"); 18 | } 19 | }; 20 | 21 | #endif // MARIADBCLIENTPP_TRUNCATIONTEST_H 22 | -------------------------------------------------------------------------------- /test/test_config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef MARIADBCLIENTPP_TEST_CONFIG_H 2 | #define MARIADBCLIENTPP_TEST_CONFIG_H 3 | 4 | namespace mariadb { 5 | namespace testing { 6 | class TestConfig { 7 | public: 8 | static constexpr const char *Hostname = "@TEST_HOSTNAME@"; 9 | static const uint32_t Port = @TEST_PORT@; 10 | static constexpr const char *User = "@TEST_USERNAME@"; 11 | static constexpr const char *Password = "@TEST_PASSWORD@"; 12 | static constexpr const char *UnixSocket = "@TEST_UNIXSOCKET@"; 13 | static constexpr const char *Database = "@TEST_DATABASE@"; 14 | }; 15 | } 16 | } 17 | 18 | #endif //MARIADBCLIENTPP_TEST_CONFIG_H 19 | -------------------------------------------------------------------------------- /test/GeneralTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_GENERALTEST_H 10 | #define MARIADBCLIENTPP_GENERALTEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class GeneralTest : public SkeletonTest { 15 | protected: 16 | virtual void CreateTestTable() override { 17 | m_con->execute("CREATE TABLE " + m_table_name + " (id INT AUTO_INCREMENT, str VARCHAR(50) NULL, PRIMARY KEY(id));"); 18 | } 19 | }; 20 | 21 | #endif // MARIADBCLIENTPP_GENERALTEST_H 22 | -------------------------------------------------------------------------------- /src/last_error.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #include 11 | 12 | using namespace mariadb; 13 | 14 | // 15 | // Constructor 16 | // 17 | last_error::last_error() : m_last_error_no(0) {} 18 | 19 | // 20 | // Get last error 21 | // 22 | u32 last_error::error_no() const { 23 | return m_last_error_no; 24 | } 25 | 26 | const std::string &last_error::error() const { 27 | return m_last_error; 28 | } 29 | -------------------------------------------------------------------------------- /include/mariadb++/last_error.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_LAST_ERROR_HPP_ 11 | #define _MARIADB_LAST_ERROR_HPP_ 12 | 13 | #include 14 | #include "types.hpp" 15 | 16 | namespace mariadb { 17 | class last_error { 18 | public: 19 | // 20 | // Constructor 21 | // 22 | last_error(); 23 | 24 | // 25 | // Get last error 26 | // 27 | u32 error_no() const; 28 | const std::string &error() const; 29 | 30 | protected: 31 | u32 m_last_error_no; 32 | std::string m_last_error; 33 | }; 34 | } // namespace mariadb 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /include/mariadb++/decimal.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef _MARIADB_DECIMAL_HPP_ 10 | #define _MARIADB_DECIMAL_HPP_ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace mariadb { 17 | class decimal { 18 | public: 19 | explicit decimal(std::string str = "") : mStr(std::move(str)) {} 20 | 21 | std::string str() const { 22 | return mStr; 23 | } 24 | 25 | f32 float32() const { 26 | return string_cast(mStr); 27 | } 28 | 29 | f64 double64() const { 30 | return string_cast(mStr); 31 | } 32 | 33 | private: 34 | std::string mStr; 35 | }; 36 | } // namespace mariadb 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /test/ParameterizedQueryTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_PARAMETERIZEDQUERYTEST_H 10 | #define MARIADBCLIENTPP_PARAMETERIZEDQUERYTEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class ParameterizedQueryTest : public SkeletonTest { 15 | virtual void CreateTestTable() override { 16 | m_con->execute("CREATE TABLE " + m_table_name + 17 | " (id INT AUTO_INCREMENT, preis INT, str VARCHAR(30), nnstr VARCHAR(30) NOT NULL DEFAULT ''," 18 | "b BOOL, tim DATETIME(6), tiim TIME(6), d DECIMAL(3,2), dd DOUBLE, nul INT, PRIMARY KEY (id));"); 19 | m_con->execute("INSERT INTO " + m_table_name + " (id, preis) VALUES (1, 150);"); 20 | } 21 | }; 22 | 23 | #endif // MARIADBCLIENTPP_PARAMETERIZEDQUERYTEST_H 24 | -------------------------------------------------------------------------------- /test/RollbackTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_ROLLBACKTEST_H 10 | #define MARIADBCLIENTPP_ROLLBACKTEST_H 11 | 12 | #include "SkeletonTest.h" 13 | 14 | class RollbackTest : public SkeletonTest { 15 | protected: 16 | virtual void CreateTestTable() override { 17 | m_con->execute("CREATE TABLE " + m_table_name + 18 | "(id INT PRIMARY KEY AUTO_INCREMENT, " 19 | "data TINYBLOB NULL, " 20 | "str VARCHAR(50) NULL, " 21 | "dt DATETIME NULL, " 22 | "t TIME NULL, " 23 | "value INT NULL, " 24 | "deci DECIMAL(8,4) DEFAULT 0.0);"); 25 | } 26 | }; 27 | 28 | #endif // MARIADBCLIENTPP_ROLLBACKTEST_H 29 | -------------------------------------------------------------------------------- /test/TruncationTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "TruncationTest.h" 10 | 11 | TEST_P(TruncationTest, testUInt) { 12 | uint32_t max = std::numeric_limits::max(); 13 | 14 | // insert max uint 15 | m_con->execute("INSERT INTO " + m_table_name + " VALUES (" + std::to_string(max) + ");"); 16 | 17 | // query directly 18 | result_set_ref res = m_con->query("SELECT * FROM " + m_table_name + ";"); 19 | 20 | ASSERT_TRUE(!!res); 21 | ASSERT_TRUE(res->next()); 22 | ASSERT_EQ(max, res->get_unsigned32(0)); 23 | res.reset(); 24 | 25 | // query as statement 26 | statement_ref stmt = m_con->create_statement("SELECT * FROM " + m_table_name + ";"); 27 | result_set_ref stmt_res = stmt->query(); 28 | 29 | ASSERT_TRUE(!!stmt_res); 30 | ASSERT_TRUE(stmt_res->next()); 31 | ASSERT_EQ(max, stmt_res->get_unsigned32(0)); 32 | } 33 | 34 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, TruncationTest, ::testing::Values(true, false)); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /include/mariadb++/save_point.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_SAVE_POINT_HPP_ 11 | #define _MARIADB_SAVE_POINT_HPP_ 12 | 13 | #include 14 | 15 | namespace mariadb { 16 | class connection; 17 | class transaction; 18 | 19 | /** 20 | * Class used to represent a MariaDB savepoint having automatic rollback functionality 21 | */ 22 | class save_point { 23 | friend class connection; 24 | friend class transaction; 25 | 26 | public: 27 | /** 28 | * Destructor initiates automatic rollback if changes were not committed 29 | */ 30 | virtual ~save_point(); 31 | 32 | /** 33 | * Commits the changes and releases savepoint 34 | */ 35 | void commit(); 36 | 37 | private: 38 | /** 39 | * Create save_point with given transaction 40 | */ 41 | save_point(transaction *trans); 42 | 43 | // internal transaction pointer 44 | transaction *m_transaction; 45 | // distinct name of the current save_pointS 46 | std::string m_name; 47 | }; 48 | 49 | typedef std::shared_ptr save_point_ref; 50 | } // namespace mariadb 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/exceptions.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace mariadb; 16 | 17 | // 18 | // Constructors 19 | // 20 | exception::date_time::date_time(u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond) throw() 21 | : base() { 22 | std::ostringstream oss; 23 | oss << "Invalid date time: " << std::setfill('0') << std::setw(4) << year << "-" << std::setw(2) << int(month) 24 | << "-" << std::setw(2) << int(day) << "T" << std::setw(2) << int(hour) << ":" << std::setw(2) << int(minute) 25 | << ":" << std::setw(2) << int(second) << "." << std::setw(3) << millisecond; 26 | 27 | m_error = oss.str(); 28 | } 29 | 30 | exception::time::time(u8 hour, u8 minute, u8 second, u16 millisecond) throw() : base() { 31 | std::ostringstream oss; 32 | oss << "Invalid time: " << std::setfill('0') << std::setw(2) << int(hour) << ":" << std::setw(2) << int(minute) 33 | << ":" << std::setw(2) << int(second) << "." << std::setw(3) << millisecond; 34 | 35 | m_error = oss.str(); 36 | } 37 | -------------------------------------------------------------------------------- /src/save_point.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | 14 | using namespace mariadb; 15 | 16 | namespace { 17 | handle g_save_point_id = 0; 18 | 19 | const char *g_save_point_create = "SAVEPOINT "; 20 | const char *g_save_point_rollback = "ROLLBACK TO SAVEPOINT "; 21 | const char *g_save_point_release = "RELEASE SAVEPOINT "; 22 | } // namespace 23 | 24 | // 25 | // Constructor 26 | // 27 | save_point::save_point(transaction *trans) : m_transaction(trans) { 28 | m_name = "SP" + std::to_string(++g_save_point_id); 29 | m_transaction->m_connection->execute(g_save_point_create + m_name); 30 | } 31 | 32 | // 33 | // Destructor 34 | // 35 | save_point::~save_point() { 36 | if (!m_transaction) 37 | return; 38 | 39 | m_transaction->remove_save_point(this); 40 | m_transaction->m_connection->execute(g_save_point_rollback + m_name); 41 | } 42 | 43 | // 44 | // Commit the change 45 | // 46 | void save_point::commit() { 47 | if (!m_transaction) 48 | return; 49 | 50 | m_transaction->remove_save_point(this); 51 | m_transaction->m_connection->execute(g_save_point_release + m_name); 52 | m_transaction = NULL; 53 | } 54 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | GIT_SUBMODULE_STRATEGY: recursive 3 | MYSQL_DATABASE: "mariadbpptest" 4 | MYSQL_USER: "test" 5 | MYSQL_PASSWORD: "test" 6 | MYSQL_RANDOM_ROOT_PASSWORD: "true" # it's not used anyway 7 | 8 | stages: 9 | - build # contains build and test and deploy 10 | 11 | .test: 12 | script: 13 | - mkdir build 14 | - cd build 15 | - cmake ../ -DMARIADBPP_DOC=ON -DMARIADBPP_TEST=ON -DTEST_HOSTNAME=db -DTEST_USERNAME=$MYSQL_USER -DTEST_PASSWORD=$MYSQL_PASSWORD -DTEST_PORT=3306 -DTEST_DATABASE=$MYSQL_DATABASE -DGTEST_SRC_DIR=/usr/src/googletest/ 16 | - make 17 | - test/mariadbpp_test # runs gtest target 18 | - cmake . -DCMAKE_BUILD_TYPE=Debug -DVIADUCK_COVERAGE=ON 19 | - make mariadbpp_test_coverage # generate code coverage report 20 | - make mariadbpp_doc # generate doxygen code documentation 21 | stage: build 22 | artifacts: 23 | paths: 24 | - build/mariadbpp_test_coverage/ # coverage data 25 | - build/doc/html/ # doxygen documentation 26 | 27 | test mariadb: 28 | extends: .test 29 | image: viaduck/ci 30 | services: 31 | - name: mariadb:latest 32 | alias: db 33 | 34 | test mysql: 35 | extends: .test 36 | image: ubuntu:22.04 37 | services: 38 | - name: mysql:latest 39 | alias: db 40 | before_script: 41 | - apt-get -y update && apt-get -y install build-essential cmake mysql-server libmysqlclient-dev libgtest-dev python3 lcov doxygen graphviz 42 | -------------------------------------------------------------------------------- /src/worker.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_WORKER_HPP_ 12 | #define _MARIADB_WORKER_HPP_ 13 | 14 | #include 15 | #include 16 | 17 | namespace mariadb { 18 | using namespace concurrency; 19 | 20 | namespace command { 21 | enum type { execute, insert, query }; 22 | } 23 | 24 | // 25 | // Worker entity used by concurrency namespace 26 | // 27 | class worker { 28 | public: 29 | // 30 | // Constructor 31 | // 32 | worker(); 33 | worker(account_ref &account, handle hnd, bool keep_handle, command::type command, const std::string &query); 34 | worker(account_ref &account, handle hnd, bool keep_handle, command::type command, statement_ref &statement); 35 | 36 | // 37 | // Get informations 38 | // 39 | status::type status() const; 40 | handle get_handle() const; 41 | bool keep_handle() const; 42 | 43 | // 44 | // Get result / result_set 45 | // 46 | u64 result() const; 47 | result_set_ref result_set() const; 48 | 49 | // 50 | // Do the actual job 51 | // 52 | void execute(); 53 | 54 | private: 55 | bool m_keep_handle; 56 | handle m_handle; 57 | status::type m_status; 58 | command::type m_command; 59 | u64 m_result; 60 | std::string m_query; 61 | account_ref m_account; 62 | result_set_ref m_result_set; 63 | statement_ref m_statement; 64 | }; 65 | } // namespace mariadb 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /test/StructureTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "StructureTest.h" 10 | 11 | TEST_P(StructureTest, testDateTime) { 12 | date_time dt("2000-01-02 03:04:05.666"); 13 | date_time dt2(2000, 1, 2, 3, 4, 5, 666); 14 | 15 | EXPECT_EQ(dt, dt2); 16 | EXPECT_FALSE(date_time::valid_date(2000, 0, 32)); 17 | 18 | dt = dt.add_milliseconds(1000 * 60 * 60 * 24 * 10); 19 | dt2 = dt2.add_days(10); 20 | 21 | EXPECT_EQ(dt, dt2); 22 | EXPECT_EQ("2000-01-12 03:04:05", dt2.str(false)); 23 | EXPECT_EQ("2000-01-12 03:04:05.666", dt2.str(true)); 24 | } 25 | 26 | TEST_P(StructureTest, testTime) { 27 | mariadb::time t("03:04:05.666"); 28 | mariadb::time t2(3, 4, 5, 666); 29 | 30 | EXPECT_EQ(t, t2); 31 | 32 | t = t.add_milliseconds(1000 * 60 * 60 * 5); 33 | t2 = t2.add_hours(5); 34 | 35 | EXPECT_EQ(t, t2); 36 | EXPECT_EQ("08:04:05", t2.str_time(false)); 37 | EXPECT_EQ("08:04:05.666", t2.str_time(true)); 38 | } 39 | 40 | TEST_P(StructureTest, testTimeSpan) { 41 | date_time dt(2000, 1, 2, 3, 4, 5, 666); 42 | date_time dt2(2000, 1, 5, 5, 5, 5, 999); 43 | 44 | time_span ts = dt2.time_between(dt); 45 | EXPECT_EQ(3, ts.days()); 46 | EXPECT_EQ(2, ts.hours()); 47 | EXPECT_EQ(1, ts.minutes()); 48 | EXPECT_EQ(333, ts.milliseconds()); 49 | } 50 | 51 | TEST_P(StructureTest, testDecimal) { 52 | decimal d("24.1234"); 53 | 54 | EXPECT_EQ(24.1234, d.double64()); 55 | EXPECT_EQ("24.1234", d.str()); 56 | } 57 | 58 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, StructureTest, ::testing::Values(true, false)); 59 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright The ViaDuck Project 2016 - 2024. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | # variables for running tests 7 | set(TEST_HOSTNAME "localhost" CACHE STRING "Hostname for mariadbpp tests") 8 | set(TEST_PORT 3306 CACHE STRING "Port for mariadbpp tests") 9 | set(TEST_UNIXSOCKET "" CACHE STRING "Unix socket for mariadbpp tests") 10 | set(TEST_USERNAME "testuser" CACHE STRING "Database username for mariadbpp tests") 11 | set(TEST_PASSWORD "bockwurst" CACHE STRING "Database username's password for mariadbpp tests") 12 | set(TEST_DATABASE "mariadbpptest" CACHE STRING "Database name for mariadbpp test") 13 | 14 | # write config to header in binary dir 15 | configure_file(test_config.h.in test_config.h) 16 | 17 | include(FindOrBuildGTest) 18 | if (GTEST_FOUND) 19 | # collect test files 20 | file(GLOB_RECURSE MARIADBPP_TEST_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 21 | 22 | macro(make_viaduck_test_target name) 23 | # create test target as base for the san 24 | add_executable(${name} ${MARIADBPP_FILES} ${MARIADBPP_TEST_FILES}) 25 | # includes 26 | target_include_directories(${name} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 27 | # links 28 | target_link_libraries(${name} PUBLIC mariadbclientpp ${GTEST_TARGET}) 29 | endmacro() 30 | make_viaduck_test_target(mariadbpp_test) 31 | 32 | include(EnableSanitizers) 33 | enable_sanitizers_for_target(mariadbpp_test) 34 | 35 | include(EnableCoverage) 36 | enable_coverage_for_target(mariadbpp_test ${CMAKE_CURRENT_SOURCE_DIR}/../ 37 | "test/*" "external/*" "${CMAKE_BINARY_DIR}/*" "${GTEST_SRC_DIR}/*") 38 | endif() 39 | -------------------------------------------------------------------------------- /include/mariadb++/types.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_TYPES_HPP_ 11 | #define _MARIADB_TYPES_HPP_ 12 | 13 | #include 14 | #include 15 | 16 | namespace mariadb { 17 | // 18 | // Default types 19 | // 20 | typedef unsigned char u8; 21 | typedef unsigned short u16; 22 | typedef unsigned int u32; 23 | typedef signed char s8; 24 | typedef signed short s16; 25 | typedef signed int s32; 26 | typedef float f32; 27 | typedef double f64; 28 | typedef long double f128; 29 | 30 | #if defined(_MSC_VER) || defined(__BORLANDC__) 31 | typedef unsigned __int64 u64; 32 | typedef signed __int64 s64; 33 | #else 34 | typedef unsigned long long u64; 35 | typedef signed long long s64; 36 | #endif 37 | 38 | typedef u64 handle; 39 | 40 | // 41 | // Value type 42 | // 43 | namespace value { 44 | enum type { 45 | null = 0, 46 | blob, 47 | data, 48 | date, 49 | date_time, 50 | time, 51 | string, 52 | boolean, 53 | decimal, 54 | unsigned8, 55 | signed8, 56 | unsigned16, 57 | signed16, 58 | unsigned32, 59 | signed32, 60 | unsigned64, 61 | signed64, 62 | float32, 63 | double64, 64 | enumeration 65 | }; 66 | } 67 | 68 | // 69 | // Isolation level 70 | // 71 | namespace isolation { 72 | enum level { repeatable_read = 0, read_committed, read_uncommitted, serializable }; 73 | } 74 | 75 | // 76 | // Stream 77 | // 78 | typedef std::shared_ptr stream_ref; 79 | } // namespace mariadb 80 | 81 | #if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID > 80000 82 | // Assume MySQL Community 8.0+ 83 | typedef bool my_bool; 84 | #endif 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /include/mariadb++/bind.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_BIND_HPP_ 11 | #define _MARIADB_BIND_HPP_ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace mariadb { 18 | class statement; 19 | class result_set; 20 | class bind { 21 | friend class statement; 22 | friend class result_set; 23 | 24 | public: 25 | /** 26 | * Construct a parameter bind 27 | */ 28 | explicit bind(MYSQL_BIND *mysql_bind); 29 | 30 | /** 31 | * Construct a result bind for given field type 32 | */ 33 | bind(MYSQL_BIND *mysql_bind, MYSQL_FIELD *mysql_field); 34 | 35 | /* 36 | * Disallow copying and moving of a bind: 37 | * As the bind can set its union as the bind "buffer", copying the bind would change the address of the union 38 | */ 39 | bind(const bind &) = delete; 40 | bind(bind &&) = delete; 41 | bind &operator=(const bind &) = delete; 42 | bind &operator=(bind &&) = delete; 43 | 44 | char *buffer() const; 45 | 46 | long unsigned int length() const; 47 | 48 | bool is_null() const; 49 | 50 | bool resize(); 51 | 52 | void set(enum_field_types type, const char *buffer = nullptr, unsigned long length = 0, bool us = false); 53 | 54 | private: 55 | MYSQL_BIND *m_bind; 56 | MYSQL_TIME m_time; 57 | 58 | my_bool m_is_null; 59 | my_bool m_error; 60 | 61 | data_ref m_data; 62 | 63 | union { 64 | u64 m_unsigned64; 65 | s64 m_signed64; 66 | s32 m_signed32[2]; 67 | f64 m_double64; 68 | f32 m_float32[2]; 69 | u8 m_uchar8[4]; 70 | }; 71 | }; 72 | 73 | using bind_ref = std::unique_ptr; 74 | } // namespace mariadb 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /test/AlterTableTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "AlterTableTest.h" 10 | 11 | void AlterTableTest::CreateTestTable() { 12 | m_con->execute("CREATE TABLE " + m_table_name + 13 | " (id INT AUTO_INCREMENT, PRIMARY KEY (`id`));"); 14 | } 15 | 16 | TEST_P(AlterTableTest, AlterAddAndDrop_existing) { 17 | m_con->execute("ALTER TABLE " + m_table_name + " ADD new INT;"); 18 | m_con->execute("ALTER TABLE " + m_table_name + " DROP COLUMN new;"); 19 | } 20 | 21 | TEST_P(AlterTableTest, AlterAddAndModify_existing) { 22 | m_con->execute("ALTER TABLE " + m_table_name + " ADD new INT;"); 23 | m_con->execute("ALTER TABLE " + m_table_name + " MODIFY new DOUBLE NOT NULL;"); 24 | } 25 | 26 | TEST_P(AlterTableTest, AlterAdd_non_existing_table) { 27 | EXPECT_ANY_THROW(m_con->execute("ALTER TABLE doesntexist ADD new INT;")); 28 | } 29 | 30 | TEST_P(AlterTableTest, AlterDrop_non_existing_table) { 31 | EXPECT_ANY_THROW(m_con->execute("ALTER TABLE doesntexist DROP COLUMN new;")); 32 | } 33 | 34 | TEST_P(AlterTableTest, AlterModify_non_existing_table) { 35 | EXPECT_ANY_THROW(m_con->execute("ALTER TABLE doesntexist MODIFY new DOUBLE;")); 36 | } 37 | 38 | TEST_P(AlterTableTest, AlterModify_non_existing_column) { 39 | m_con->execute("ALTER TABLE " + m_table_name + " ADD new INT;"); 40 | EXPECT_ANY_THROW( 41 | m_con->execute("ALTER TABLE " + m_table_name + " MODIFY new_doesntexist DOUBLE;")); 42 | } 43 | 44 | TEST_P(AlterTableTest, AlterAddAndDrop_no_connect) { 45 | account_ref no_acc = account::create("256.256.256.256", "", ""); 46 | connection_ref no_conn = connection::create(no_acc); 47 | 48 | EXPECT_ANY_THROW(no_conn->execute("ALTER TABLE " + m_table_name + " ADD new INT;")); 49 | EXPECT_ANY_THROW(no_conn->execute("ALTER TABLE " + m_table_name + " DROP COLUMN new;")); 50 | } 51 | 52 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, AlterTableTest, ::testing::Values(true, false)); 53 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: trusty 3 | 4 | matrix: 5 | include: 6 | - language: cpp 7 | dist: trusty 8 | sudo: required 9 | addons: 10 | mariadb: '10.0' 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | packages: 15 | - g++-5 16 | - libgtest-dev 17 | 18 | script: 19 | # first build mariadb c connector 20 | - curl -o connector.tar.gz https://downloads.mariadb.com/Connectors/c/connector-c-3.0.4/mariadb-connector-c-3.0.4-src.tar.gz && tar xf connector.tar.gz && cd mariadb-connector-c-3.0.4-src/ 21 | - mkdir build && cd build 22 | - cmake ../ -DCMAKE_INSTALL_PREFIX=/usr/ && make libmariadb && sudo make install 23 | - cd ../../ 24 | # now build mariadbpp 25 | - mkdir build && cd build 26 | - cmake ../ -DMARIADBPP_DOC=ON -DMARIADBPP_TEST=ON -DGTEST_SRC_DIR=/usr/src/gtest/ -DCMAKE_BUILD_TYPE=Debug -DTEST_USERNAME=$MYSQL_USER -DTEST_PASSWORD=$MYSQL_PASSWORD -DTEST_PORT=3306 -DTEST_DATABASE=$MYSQL_DATABASE && cmake --build . 27 | - test/mariadbpp_tests 28 | 29 | env: 30 | - LABEL="Linux MariaDB Connector C" 31 | 32 | - language: cpp 33 | dist: trusty 34 | addons: 35 | mariadb: '10.0' 36 | apt: 37 | sources: 38 | - ubuntu-toolchain-r-test 39 | packages: 40 | - g++-5 41 | - libmariadbclient-dev 42 | - libgtest-dev 43 | 44 | env: 45 | - LABEL="Linux libmariadbclient-dev" 46 | 47 | env: 48 | global: 49 | - MAKEFLAGS="-j 2" 50 | - MYSQL_DATABASE="mariadbpp_tests" 51 | - MYSQL_USER: "travis" 52 | - MYSQL_PASSWORD: "" 53 | 54 | before_install: 55 | - export CXX="g++-5" CC="gcc-5" 56 | - mysql -e 'CREATE DATABASE IF NOT EXISTS mariadbpp_tests;' 57 | 58 | script: 59 | - mkdir build && cd build 60 | - cmake ../ -DMARIADBPP_DOC=ON -DMARIADBPP_TEST=ON -DGTEST_SRC_DIR=/usr/src/gtest/ -DCMAKE_BUILD_TYPE=Debug -DTEST_USERNAME=$MYSQL_USER -DTEST_PASSWORD=$MYSQL_PASSWORD -DTEST_PORT=3306 -DTEST_DATABASE=$MYSQL_DATABASE && cmake --build . 61 | - test/mariadbpp_tests 62 | -------------------------------------------------------------------------------- /src/transaction.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace mariadb; 16 | 17 | namespace { 18 | const char *g_isolation_level[] = { 19 | "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;", 20 | "SET TRANSACTION ISOLATION LEVEL READ COMMITTED;", 21 | "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;", 22 | "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;", 23 | }; 24 | 25 | const char *g_consistent_snapshot[] = { 26 | "START TRANSACTION;", 27 | "START TRANSACTION WITH CONSISTENT SNAPSHOT;", 28 | }; 29 | } // namespace 30 | 31 | transaction::transaction(connection *conn, isolation::level level, bool consistent_snapshot) : m_connection(conn) { 32 | conn->execute(g_isolation_level[level]); 33 | conn->execute(g_consistent_snapshot[consistent_snapshot]); 34 | } 35 | 36 | transaction::~transaction() { 37 | if (!m_connection) 38 | return; 39 | 40 | mysql_rollback(m_connection->m_mysql); 41 | cleanup(); 42 | } 43 | 44 | void transaction::cleanup() { 45 | for (save_point *save_point : m_save_points) save_point->m_transaction = nullptr; 46 | 47 | m_save_points.clear(); 48 | } 49 | 50 | void transaction::commit() { 51 | if (!m_connection) 52 | return; 53 | 54 | mysql_commit(m_connection->m_mysql); 55 | cleanup(); 56 | m_connection = nullptr; 57 | } 58 | 59 | save_point_ref transaction::create_save_point() { 60 | if (!m_connection) 61 | return save_point_ref(); 62 | 63 | save_point *sp = new save_point(this); 64 | m_save_points.push_back(sp); 65 | return save_point_ref(sp); 66 | } 67 | 68 | void transaction::remove_save_point(save_point *sv_point) { 69 | m_save_points.erase(std::remove(m_save_points.begin(), m_save_points.end(), sv_point)); 70 | } 71 | -------------------------------------------------------------------------------- /include/mariadb++/exceptions.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2021. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_EXCEPTIONS_HPP_ 12 | #define _MARIADB_EXCEPTIONS_HPP_ 13 | 14 | #include 15 | #include 16 | 17 | namespace mariadb { 18 | namespace exception { 19 | class base : public std::exception { 20 | public: 21 | // 22 | // Constructor 23 | // 24 | base() throw() : std::exception(), m_error_id(0), m_error("Exception not defined") {} 25 | 26 | base(u32 error_id, const std::string &error) throw() : std::exception(), m_error_id(error_id), m_error(error) {} 27 | 28 | base(const std::string &error) throw() : std::exception(), m_error_id(0), m_error(error) {} 29 | 30 | // 31 | // Destructor 32 | // 33 | virtual ~base() throw() {} 34 | 35 | // 36 | // Methods 37 | // 38 | virtual const char *what() const throw() { 39 | return m_error.c_str(); 40 | } 41 | 42 | u32 error_id() const throw() { 43 | return m_error_id; 44 | } 45 | 46 | protected: 47 | u32 m_error_id; 48 | std::string m_error; 49 | }; 50 | 51 | class date_time : public base { 52 | public: 53 | // 54 | // Constructors 55 | // 56 | date_time(u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond) throw(); 57 | }; 58 | 59 | class time : public base { 60 | public: 61 | // 62 | // Constructors 63 | // 64 | time(u8 hour, u8 minute, u8 second, u16 millisecond) throw(); 65 | }; 66 | 67 | class connection : public base { 68 | public: 69 | // 70 | // Constructor 71 | // 72 | connection(u32 error_id, const std::string &error) throw() : base(error_id, error) {} 73 | }; 74 | 75 | class statement : public base { 76 | public: 77 | // 78 | // Constructor 79 | // 80 | statement(u32 error_id, const std::string &error) throw() : base(error_id, error) {} 81 | }; 82 | } // namespace exception 83 | } // namespace mariadb 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /test/SkeletonTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_SKELETONTEST_H 10 | #define MARIADBCLIENTPP_SKELETONTEST_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "test_config.h" 17 | 18 | using namespace mariadb; 19 | 20 | class SkeletonTest : public ::testing::TestWithParam { 21 | public: 22 | virtual void SetUp() override { 23 | // get test names and concatenate them 24 | const ::testing::TestInfo* const test_info = 25 | ::testing::UnitTest::GetInstance()->current_test_info(); 26 | m_table_name = 27 | std::string(test_info->test_case_name()) + "_" + std::string(test_info->name()); 28 | std::replace(m_table_name.begin(), m_table_name.end(), '/', '_'); 29 | 30 | // create user account 31 | using TestConfig = mariadb::testing::TestConfig; 32 | m_account_setup = account::create(TestConfig::Hostname, TestConfig::User, TestConfig::Password, 33 | TestConfig::Database, TestConfig::Port, TestConfig::UnixSocket); 34 | ASSERT_TRUE(!!m_account_setup); 35 | m_account_setup->set_auto_commit(true); 36 | m_account_setup->set_connect_option(MYSQL_OPT_CONNECT_TIMEOUT, 10); 37 | m_account_setup->set_store_result(GetParam()); 38 | ASSERT_EQ(1u, m_account_setup->connect_options().size()); 39 | 40 | // create database connection 41 | m_con = connection::create(m_account_setup); 42 | ASSERT_TRUE(!!m_con); 43 | ASSERT_TRUE(m_con->connect()); 44 | ASSERT_TRUE(m_con->connected()); 45 | 46 | // drop table and call creation 47 | m_con->execute("DROP TABLE IF EXISTS " + m_table_name); 48 | CreateTestTable(); 49 | } 50 | 51 | virtual void TearDown() override { m_con->disconnect(); } 52 | 53 | protected: 54 | virtual void CreateTestTable() = 0; 55 | 56 | account_ref m_account_setup; 57 | std::string m_table_name; 58 | 59 | connection_ref m_con; 60 | }; 61 | 62 | #endif // MARIADBCLIENTPP_SKELETONTEST_H 63 | -------------------------------------------------------------------------------- /include/mariadb++/transaction.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_TRANSACTION_HPP_ 11 | #define _MARIADB_TRANSACTION_HPP_ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mariadb { 19 | class connection; 20 | class save_point; 21 | 22 | /** 23 | * Class representing a SQL transaction having automatic rollback functionality 24 | */ 25 | class transaction { 26 | friend class connection; 27 | friend class save_point; 28 | 29 | public: 30 | /** 31 | * Destructor initiates automatic rollback if changes were not committed 32 | */ 33 | virtual ~transaction(); 34 | 35 | /** 36 | * Commits the changes, releases all savepoints 37 | */ 38 | void commit(); 39 | 40 | /** 41 | * Create named savepoint 42 | * Note: only valid until the transaction is destroyed or committed 43 | * 44 | * @return Reference to a unique new savepoint 45 | */ 46 | save_point_ref create_save_point(); 47 | 48 | private: 49 | /** 50 | * Create a transaction with given isolation level and snapshot setting 51 | * 52 | * @param conn Connection to start transaction on 53 | * @param level Level of database isolation to use 54 | * @param consistent_snapshot Controls whether the transaction needs a consistent snapshot on 55 | * creation 56 | */ 57 | transaction(connection *conn, isolation::level level, bool consistent_snapshot); 58 | 59 | /** 60 | * Removes a savepoint from the list of savepoints 61 | * 62 | * @param sv_point savepoint to remove 63 | */ 64 | void remove_save_point(save_point *sv_point); 65 | 66 | /** 67 | * Cleans up the transaction, releases all savepoints 68 | */ 69 | void cleanup(); 70 | 71 | // parent connection pointer 72 | connection *m_connection; 73 | // list of created savepoints for this transaction 74 | std::vector m_save_points; 75 | }; 76 | 77 | typedef std::shared_ptr transaction_ref; 78 | } // namespace mariadb 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /test/GeneralTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "GeneralTest.h" 10 | #include "mariadb++/concurrency.hpp" 11 | 12 | TEST_P(GeneralTest, testCreateFail) { 13 | // intended syntax error 14 | ASSERT_ANY_THROW(m_con->execute("CREATE TAVBEL testtest ();")); 15 | ASSERT_ANY_THROW(m_con->execute("CREATE TABLE testtest (\");")); 16 | ASSERT_ANY_THROW(m_con->execute("CREATE TABLE testtest¸ ()")); 17 | ASSERT_ANY_THROW(m_con->execute("CREATE TABLE ()")); 18 | } 19 | 20 | TEST_P(GeneralTest, testMissingConnection) { 21 | // create connection without connecting 22 | account_ref no_acc = account::create("0.0.0.0", "", ""); 23 | connection_ref no_conn = connection::create(no_acc); 24 | 25 | ASSERT_FALSE(no_conn->connected()); 26 | EXPECT_ANY_THROW(no_conn->execute("CREATE TABLE asdf;")); 27 | EXPECT_ANY_THROW(no_conn->query("SELECT * FROM asdf;")); 28 | EXPECT_ANY_THROW(no_conn->insert("INSERT INTO asdf (a) VALUES ('a');")); 29 | 30 | EXPECT_ANY_THROW(no_conn->create_statement("SELECT * FROM asdf;")); 31 | } 32 | 33 | TEST_P(GeneralTest, testDuplicateTable) { 34 | EXPECT_ANY_THROW(m_con->execute("CREATE TABLE " + m_table_name + 35 | " (id INT AUTO_INCREMENT, PRIMARY KEY(id));")); 36 | } 37 | 38 | TEST_P(GeneralTest, testConcurrentInsert) { 39 | constexpr int num_results = 100; 40 | 41 | std::vector handles; 42 | std::set results; 43 | 44 | concurrency::set_account(m_account_setup); 45 | 46 | // launch all queries 47 | for (int i = 0; i < num_results; i++) { 48 | auto hndl = concurrency::insert("INSERT INTO " + m_table_name + "(str) VALUES('teest');", true); 49 | handles.push_back(hndl); 50 | } 51 | 52 | // wait for all queries 53 | for (auto h : handles) EXPECT_TRUE(concurrency::wait_handle(h)); 54 | 55 | // get all results 56 | for (auto h : handles) { 57 | u64 res = concurrency::get_execute_result(h); 58 | auto set_result = results.insert(res); 59 | 60 | // fail if this result already existed (insert returns false as second) 61 | EXPECT_TRUE(set_result.second); 62 | } 63 | 64 | // release all handles 65 | for (auto h : handles) concurrency::release_handle(h); 66 | 67 | EXPECT_EQ(num_results, results.size()); 68 | } 69 | 70 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, GeneralTest, ::testing::Values(true, false)); 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mariadb++ 2 | C++ client library for MariaDB. Uses the C connector. 3 | 4 | ## Features 5 | * Prepared statements 6 | * Transactions and savepoints 7 | * Concurrency allows connection sharing between threads 8 | * Data type support: blob, decimal, datetime, time, timespan, etc. 9 | * Exceptions 10 | 11 | ## Dependencies 12 | Install `mariadbclient` or `mysqlclient` libraries. 13 | 14 | ## Usage (local) 15 | 1. Initialize Git submodules: `git submodule update --init` 16 | 2. Add `mariadbclientpp` as a subdirectory in your own `CMakeLists.txt`: 17 | 18 | ```cmake 19 | add_subdirectory(/path/to/mariadbpp) 20 | ... 21 | target_link_libraries(target mariadbclientpp) 22 | ``` 23 | 24 | ## Usage (global) 25 | ### Build and install instructions 26 | 1. Initialize Git submodules: `git submodule update --init` 27 | 2. `mkdir build; cd build` 28 | 3. `cmake ..` 29 | 4. `make install` 30 | 31 | ### Linking instructions 32 | Add `mariadbclientpp` as a CMake package to your CMake project. Make sure 33 | that the environment variable `CMAKE_PREFIX_PATH` includes the directory 34 | where the `mariadbclientpp-config.cmake` file was installed: 35 | 36 | ```cmake 37 | find_package(mariadbclientpp) 38 | ... 39 | target_link_libraries(target mariadbclientpp::mariadbclientpp) 40 | ``` 41 | 42 | ## Building tests 43 | 1. Create database and user according to the information in 44 | [test/CMakeLists.txt](test/CMakeLists.txt) or adjust these values. 45 | 2. Enable tests with `-DMARIADBPP_TEST=ON` and build the software. 46 | 47 | 48 | ## Example 49 | ```c++ 50 | // set up the account 51 | account_ref acc = account::create(...); 52 | 53 | // create connection 54 | connection_ref con = connection::create(acc); 55 | 56 | // insert, update, select on the connection 57 | u64 id = con->insert("INSERT INTO table VALUES (1, 2, 3)"); 58 | u64 affected = con->execute("UPDATE table SET a=1"); 59 | result_set_ref result = con->query("SELECT * FROM table"); 60 | 61 | // create statement 62 | statement_ref stmt = con->create_statement(...); 63 | 64 | // insert, update, select on the statement 65 | u64 id = stmt->insert(); 66 | u64 affected = stmt->execute(); 67 | result_set_ref result = stmt->query(); 68 | 69 | // reading from a result set 70 | while (result->next()) { 71 | int a = result->get_unsigned32(0); 72 | int b = result->get_unsigned32("b"); 73 | } 74 | 75 | // insert using prepared statement 76 | statement_ref stmt = con->create_statement("INSERT INTO table (?, ?, ?)"); 77 | stmt->set_unsigned32(0, 13); 78 | stmt->set_unsigned32(1, 37); 79 | stmt->set_unsigned32(2, 42); 80 | stmt->insert(); 81 | 82 | ``` 83 | More usage examples can be found in the `test/` directory. 84 | 85 | ## History 86 | This library was originally developed on [Launchpad](https://launchpad.net/mariadb++). 87 | It has since been forked and is now being actively maintained [here](https://github.com/viaduck/mariadbpp). 88 | 89 | ## License 90 | This library is subject to the Boost Software License. See accompanying 91 | [LICENSE](LICENSE) file. 92 | -------------------------------------------------------------------------------- /include/mariadb++/concurrency.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_CONCURRENCY_HPP_ 12 | #define _MARIADB_CONCURRENCY_HPP_ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mariadb { 19 | namespace concurrency { 20 | namespace status { 21 | enum type { waiting, executing, succeed, failed, removed }; 22 | } 23 | 24 | // 25 | // Set account for connection 26 | // 27 | extern void set_account(account_ref &account); 28 | 29 | // 30 | // Query status 31 | // 32 | extern status::type worker_status(handle h); 33 | 34 | // 35 | // Execute a query 36 | // Note: the void overloads are needed because it was too easy to "forget" passing keep_handle 37 | // and getting an invalid handle, instead we now return void when no handle is needed 38 | // 39 | extern handle execute(const std::string &query, bool keep_handle); 40 | inline void execute(const std::string &squery) { 41 | execute(squery, false); 42 | } 43 | 44 | extern handle insert(const std::string &query, bool keep_handle); 45 | inline void insert(const std::string &squery) { 46 | insert(squery, false); 47 | } 48 | 49 | extern handle query(const std::string &query, bool keep_handle); 50 | inline void query(const std::string &squery) { 51 | query(squery, false); 52 | } 53 | 54 | // 55 | // Execute a query using a statement 56 | // Note: the void overloads are needed because it was too easy to "forget" passing keep_handle 57 | // and getting an invalid handle, instead we now return void when no handle is needed 58 | // 59 | extern statement_ref create_statement(const std::string &query); 60 | extern handle execute(statement_ref &statement, bool keep_handle); 61 | inline void execute(statement_ref &statement) { 62 | execute(statement, false); 63 | } 64 | 65 | extern handle insert(statement_ref &statement, bool keep_handle); 66 | inline void insert(statement_ref &statement) { 67 | insert(statement, false); 68 | } 69 | 70 | extern handle query(statement_ref &statement, bool keep_handle); 71 | inline void query(statement_ref &statement) { 72 | query(statement, false); 73 | } 74 | 75 | // 76 | // Query executed, result ready to be used 77 | // 78 | extern u64 get_execute_result(handle h); 79 | extern u64 get_insert_result(handle h); 80 | extern result_set_ref get_query_result(handle h); 81 | 82 | // 83 | // Remove a query 84 | // 85 | // Please note, if a result_set is used, it must be done after the result_set is used... 86 | // 87 | extern void release_handle(handle h); 88 | 89 | // 90 | // Wait for a handle to signal 91 | // 92 | extern bool wait_handle(handle h, u64 wait_time_ms = 100); 93 | } // namespace concurrency 94 | } // namespace mariadb 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /include/mariadb++/conversion_helper.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #ifndef MARIADBCLIENTPP_CONVERSION_HELPER_H 10 | #define MARIADBCLIENTPP_CONVERSION_HELPER_H 11 | 12 | #include 13 | #include 14 | 15 | #ifdef WIN32 16 | #undef max 17 | #undef min 18 | #endif 19 | 20 | template 21 | inline T checked_cast(K value) { 22 | if (value < std::numeric_limits::lowest() || value > std::numeric_limits::max()) 23 | return T(); 24 | 25 | return static_cast(value); 26 | } 27 | 28 | template 29 | inline T string_cast(const std::string &str) { 30 | size_t endPos; 31 | int parsedNumber = std::stoi(str, &endPos); 32 | 33 | if (endPos != str.size()) 34 | return T(); 35 | 36 | return checked_cast(parsedNumber); 37 | } 38 | 39 | template <> 40 | inline unsigned long string_cast(const std::string &str) { 41 | size_t endPos; 42 | unsigned long parsedNumber = std::stoul(str, &endPos); 43 | 44 | if (endPos != str.size()) 45 | return 0; 46 | 47 | return parsedNumber; 48 | } 49 | 50 | template <> 51 | inline unsigned int string_cast(const std::string &str) { 52 | unsigned long parsedNumber = string_cast(str); 53 | 54 | return checked_cast(parsedNumber); 55 | } 56 | 57 | template <> 58 | inline unsigned long long string_cast(const std::string &str) { 59 | size_t endPos; 60 | unsigned long long parsedNumber = std::stoull(str, &endPos); 61 | 62 | if (endPos != str.size()) 63 | return 0; 64 | 65 | return parsedNumber; 66 | } 67 | 68 | template <> 69 | inline long long string_cast(const std::string &str) { 70 | size_t endPos; 71 | long long parsedNumber = std::stoll(str, &endPos); 72 | 73 | if (endPos != str.size()) 74 | return 0; 75 | 76 | return parsedNumber; 77 | } 78 | 79 | template <> 80 | inline double string_cast(const std::string &str) { 81 | size_t endPos; 82 | try { 83 | double parsedNumber = std::stod(str, &endPos); 84 | 85 | if (endPos != str.size()) 86 | return 0; 87 | 88 | return parsedNumber; 89 | } catch (std::out_of_range &) { 90 | // Not a Number double 91 | return std::numeric_limits::quiet_NaN(); 92 | } 93 | } 94 | 95 | template <> 96 | inline float string_cast(const std::string &str) { 97 | size_t endPos; 98 | try { 99 | float parsedNumber = std::stof(str, &endPos); 100 | 101 | if (endPos != str.size()) 102 | return 0; 103 | 104 | return parsedNumber; 105 | } catch (std::out_of_range &) { 106 | // Not a Number float 107 | return std::numeric_limits::quiet_NaN(); 108 | } 109 | } 110 | 111 | #endif // MARIADBCLIENTPP_CONVERSION_HELPER_H 112 | -------------------------------------------------------------------------------- /src/worker.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include "worker.hpp" 12 | #include 13 | 14 | using namespace mariadb; 15 | using namespace mariadb::concurrency; 16 | 17 | // 18 | // Constructors 19 | // 20 | worker::worker() 21 | : m_keep_handle(false), m_handle(0), m_status(status::removed), m_command(command::query), m_result(0) {} 22 | 23 | worker::worker(account_ref &account, handle handle, bool keep_handle, command::type command, const std::string &query) 24 | : m_keep_handle(keep_handle), 25 | m_handle(handle), 26 | m_status(handle > 0 ? status::waiting : status::removed), 27 | m_command(command), 28 | m_result(0), 29 | m_query(query), 30 | m_account(account) {} 31 | 32 | worker::worker(account_ref &account, handle handle, bool keep_handle, command::type command, statement_ref &statement) 33 | : m_keep_handle(keep_handle), 34 | m_handle(handle), 35 | m_status(handle > 0 ? status::waiting : status::removed), 36 | m_command(command), 37 | m_result(0), 38 | m_account(account), 39 | m_statement(statement) {} 40 | 41 | // 42 | // Get informations 43 | // 44 | status::type worker::status() const { 45 | return m_status; 46 | } 47 | 48 | bool worker::keep_handle() const { 49 | return m_keep_handle; 50 | } 51 | 52 | mariadb::handle worker::get_handle() const { 53 | return m_handle; 54 | } 55 | 56 | // 57 | // Get result / result_set 58 | // 59 | u64 worker::result() const { 60 | return m_result; 61 | } 62 | 63 | result_set_ref worker::result_set() const { 64 | return m_result_set; 65 | } 66 | 67 | // 68 | // Do the actual job 69 | // 70 | void worker::execute() { 71 | m_status = status::executing; 72 | 73 | try { 74 | connection_ref connection = m_statement ? m_statement->m_connection : connection::create(m_account); 75 | 76 | // 77 | // Make sure auto commit mode is on before continuing... 78 | // 79 | connection->set_auto_commit(true); 80 | connection->connect(); 81 | 82 | switch (m_command) { 83 | case command::execute: 84 | if (m_statement) 85 | m_result = m_statement->execute(); 86 | else 87 | m_result = connection->execute(m_query.c_str()); 88 | break; 89 | 90 | case command::insert: 91 | if (m_statement) 92 | m_result = m_statement->insert(); 93 | else 94 | m_result = connection->insert(m_query.c_str()); 95 | break; 96 | 97 | case command::query: 98 | if (m_statement) 99 | m_result_set = m_statement->query(); 100 | else 101 | m_result_set = connection->query(m_query.c_str()); 102 | break; 103 | } 104 | 105 | m_status = status::succeed; 106 | } catch (const std::exception &e) { 107 | std::cout << e.what() << std::endl; 108 | m_status = status::failed; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright The ViaDuck Project 2016 - 2024. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | cmake_minimum_required(VERSION 3.8...3.16) 7 | project(mariadbclientpp LANGUAGES CXX) 8 | 9 | include(GNUInstallDirs) 10 | option(MARIADBPP_TEST "Build mariadbpp tests" OFF) 11 | option(MARIADBPP_DOC "Build mariadbpp docs" OFF) 12 | option(MARIADBPP_QUIET "Turn off error logging" OFF) 13 | 14 | # add additional cmake modules 15 | list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/external/cmake-modules") 16 | 17 | # dependencies 18 | find_package(MariaDBClient REQUIRED) 19 | find_package(Threads REQUIRED) 20 | 21 | # check capabilities 22 | include(CheckSymbolExists) 23 | set(CMAKE_REQUIRED_LIBRARIES MariaDBClient::MariaDBClient) 24 | check_symbol_exists(mysql_optionsv mysql.h MARIADBPP_HAS_OPTIONS_V) 25 | 26 | # find files 27 | file(GLOB_RECURSE MARIADBPP_PUBLIC_HEADERS include/mariadb++/*) 28 | file(GLOB_RECURSE MARIADBPP_FILES src/*.hpp src/*.cpp) 29 | 30 | # set up target 31 | add_library(mariadbclientpp ${MARIADBPP_FILES} ${MARIADBPP_PUBLIC_HEADERS}) 32 | # include headers 33 | target_include_directories(mariadbclientpp PUBLIC 34 | "$" 35 | "$") 36 | # link dependencies 37 | target_link_libraries(mariadbclientpp MariaDBClient::MariaDBClient Threads::Threads) 38 | # compile options 39 | target_compile_features(mariadbclientpp PUBLIC cxx_std_11) 40 | target_compile_definitions(mariadbclientpp PUBLIC 41 | MARIADB_QUIET=$ 42 | MARIADB_HAS_OPTIONS_V=$ 43 | ) 44 | 45 | if (MSVC) 46 | target_compile_options(mariadbclientpp PRIVATE /W4) 47 | else() 48 | target_compile_options(mariadbclientpp PRIVATE -Wall -Wextra) 49 | endif() 50 | 51 | # install configuration 52 | install(FILES ${MARIADBPP_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mariadb++) 53 | install(TARGETS mariadbclientpp 54 | EXPORT mariadbclientpp_export 55 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 56 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 57 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 58 | ) 59 | 60 | configure_file("cmake/mariadbclientpp-config.cmake.in" 61 | "${CMAKE_CURRENT_BINARY_DIR}/mariadbclientpp-config.cmake" @ONLY) 62 | 63 | install(EXPORT mariadbclientpp_export 64 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mariadbclientpp" 65 | NAMESPACE "mariadbclientpp::" 66 | # In this CMake config file no dependencies are considered. But since we 67 | # do not use any `find_package` call here this approach is sufficient. 68 | FILE mariadbclientpp-targets.cmake 69 | ) 70 | 71 | install(FILES 72 | "${CMAKE_CURRENT_BINARY_DIR}/mariadbclientpp-config.cmake" 73 | "${PROJECT_SOURCE_DIR}/external/cmake-modules/FindMariaDBClient.cmake" 74 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mariadbclientpp" 75 | ) 76 | 77 | SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MariaDB++") 78 | SET(CPACK_PACKAGE_VENDOR "ViaDuck") 79 | SET(CPACK_PACKAGE_VERSION "1.0.0") 80 | SET(CPACK_GENERATOR "RPM;DEB") 81 | SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Nobody") 82 | INCLUDE(CPack) 83 | 84 | # tests 85 | if (MARIADBPP_TEST) 86 | add_subdirectory(test) 87 | endif() 88 | 89 | if (MARIADBPP_DOC) 90 | # doxygen 91 | include(Doxygen) 92 | if (DOXYGEN_FOUND) 93 | setup_doxygen(mariadbpp_doc ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 94 | endif() 95 | endif() 96 | -------------------------------------------------------------------------------- /src/bind.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace mariadb; 16 | 17 | bind::bind(MYSQL_BIND *b) : m_bind(b), m_is_null(0), m_error(0) { 18 | // clear bind 19 | memset(b, 0, sizeof(MYSQL_BIND)); 20 | 21 | // set default data 22 | m_bind->buffer = &m_unsigned64; 23 | m_bind->buffer_length = 0; 24 | m_bind->buffer_type = MYSQL_TYPE_NULL; 25 | m_bind->is_null = &m_is_null; 26 | m_bind->error = &m_error; 27 | m_bind->length = &m_bind->buffer_length; 28 | } 29 | 30 | bind::bind(MYSQL_BIND *b, MYSQL_FIELD *f) : bind(b) { 31 | set(f->type, nullptr, f->max_length, (f->flags & UNSIGNED_FLAG) == UNSIGNED_FLAG); 32 | } 33 | 34 | char *bind::buffer() const { 35 | if (m_data) 36 | return m_data->get(); 37 | 38 | return const_cast(reinterpret_cast(&m_unsigned64)); 39 | } 40 | 41 | unsigned long bind::length() const { 42 | return m_bind->buffer_length; 43 | } 44 | 45 | bool bind::is_null() const { 46 | return (m_is_null != 0); 47 | } 48 | 49 | bool bind::resize() { 50 | if (m_data && m_data->size() < m_bind->buffer_length) { 51 | if (!m_data->resize(m_bind->buffer_length)) 52 | return false; 53 | m_bind->buffer = m_data->get(); 54 | m_bind->buffer_length = m_data->size(); 55 | } 56 | return true; 57 | } 58 | 59 | void bind::set(enum_field_types type, const char *buffer, unsigned long length, bool us) { 60 | m_bind->buffer_type = type; 61 | m_bind->is_unsigned = us ? 1 : 0; 62 | 63 | switch (type) { 64 | case MYSQL_TYPE_NULL: 65 | m_bind->buffer_length = 1; 66 | break; 67 | 68 | case MYSQL_TYPE_TINY: 69 | case MYSQL_TYPE_BIT: 70 | m_bind->buffer_length = 1; 71 | break; 72 | 73 | case MYSQL_TYPE_YEAR: 74 | case MYSQL_TYPE_SHORT: 75 | m_bind->buffer_length = sizeof(s16); 76 | break; 77 | 78 | case MYSQL_TYPE_INT24: 79 | case MYSQL_TYPE_LONG: 80 | m_bind->buffer_length = sizeof(s32); 81 | break; 82 | 83 | case MYSQL_TYPE_LONGLONG: 84 | m_bind->buffer_length = sizeof(s64); 85 | break; 86 | 87 | case MYSQL_TYPE_FLOAT: 88 | m_bind->buffer_length = sizeof(f32); 89 | break; 90 | 91 | case MYSQL_TYPE_DOUBLE: 92 | m_bind->buffer_length = sizeof(f64); 93 | break; 94 | 95 | case MYSQL_TYPE_NEWDATE: 96 | case MYSQL_TYPE_DATE: 97 | case MYSQL_TYPE_TIME: 98 | case MYSQL_TYPE_TIMESTAMP: 99 | case MYSQL_TYPE_DATETIME: 100 | m_bind->buffer = &m_time; 101 | m_bind->buffer_length = sizeof(MYSQL_TIME); 102 | break; 103 | 104 | default: 105 | case MYSQL_TYPE_DECIMAL: 106 | case MYSQL_TYPE_NEWDECIMAL: 107 | case MYSQL_TYPE_TINY_BLOB: 108 | case MYSQL_TYPE_MEDIUM_BLOB: 109 | case MYSQL_TYPE_LONG_BLOB: 110 | case MYSQL_TYPE_BLOB: 111 | case MYSQL_TYPE_ENUM: 112 | case MYSQL_TYPE_VARCHAR: 113 | case MYSQL_TYPE_VAR_STRING: 114 | case MYSQL_TYPE_STRING: 115 | m_data = data_ref(new data(length)); 116 | m_bind->buffer = m_data->get(); 117 | m_bind->buffer_length = m_data->size(); 118 | 119 | if (buffer) 120 | memcpy(m_bind->buffer, buffer, length); 121 | break; 122 | } 123 | } -------------------------------------------------------------------------------- /include/mariadb++/statement.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_STATEMENT_HPP_ 12 | #define _MARIADB_STATEMENT_HPP_ 13 | 14 | #include 15 | #include 16 | 17 | #define MAKE_SETTER_SIG(nm, type, fq) void fq set_##nm(u32 index, type value) 18 | #define MAKE_SETTER_INT(nm, type, fq) void fq _set_body_##nm(bind &bind, type value) 19 | 20 | #define MAKE_SETTER_DECL(nm, type) \ 21 | MAKE_SETTER_SIG(nm, type, ); \ 22 | MAKE_SETTER_INT(nm, type, ) 23 | 24 | #define MAKE_SETTER(nm, type) \ 25 | MAKE_SETTER_SIG(nm, type, statement::) { \ 26 | if (index >= m_data->m_bind_count) \ 27 | throw std::out_of_range("Field index out of range"); \ 28 | \ 29 | bind &bind = *m_data->m_binds.at(index); \ 30 | _set_body_##nm(bind, value); \ 31 | } \ 32 | MAKE_SETTER_INT(nm, type, statement::) 33 | 34 | namespace mariadb { 35 | class connection; 36 | class worker; 37 | class result_set; 38 | typedef std::shared_ptr connection_ref; 39 | 40 | /** 41 | * Class representing a prepared statement with binding functionality 42 | */ 43 | class statement : public last_error { 44 | friend class connection; 45 | friend class result_set; 46 | friend class worker; 47 | 48 | public: 49 | statement() = delete; 50 | 51 | /** 52 | * Execute the query and return the number of rows affected 53 | * 54 | * @return Number of rows affected or zero on error 55 | */ 56 | u64 execute(); 57 | 58 | /** 59 | * Execute the query and return the last insert id 60 | * 61 | * @return Last insert ID or zero on error 62 | */ 63 | u64 insert(); 64 | 65 | /** 66 | * Execute the query and return a result set 67 | * 68 | * @return Result set containing a result or an empty set on error 69 | */ 70 | result_set_ref query(); 71 | 72 | /** 73 | * Set connection ref, used by concurrency 74 | */ 75 | void set_connection(connection_ref &connection); 76 | 77 | // declare all setters 78 | MAKE_SETTER_DECL(blob, stream_ref); 79 | MAKE_SETTER_DECL(date_time, const date_time &); 80 | MAKE_SETTER_DECL(date, const date_time &); 81 | MAKE_SETTER_DECL(time, const time &); 82 | MAKE_SETTER_DECL(data, const data_ref &); 83 | MAKE_SETTER_DECL(decimal, const decimal &); 84 | MAKE_SETTER_DECL(string, const std::string &); 85 | MAKE_SETTER_DECL(boolean, bool); 86 | MAKE_SETTER_DECL(unsigned8, u8); 87 | MAKE_SETTER_DECL(signed8, s8); 88 | MAKE_SETTER_DECL(unsigned16, u16); 89 | MAKE_SETTER_DECL(signed16, s16); 90 | MAKE_SETTER_DECL(unsigned32, u32); 91 | MAKE_SETTER_DECL(signed32, s32); 92 | MAKE_SETTER_DECL(unsigned64, u64); 93 | MAKE_SETTER_DECL(signed64, s64); 94 | MAKE_SETTER_DECL(float, f32); 95 | MAKE_SETTER_DECL(double, f64); 96 | void set_null(u32 index); 97 | 98 | private: 99 | /** 100 | * Private constructor used by connection 101 | */ 102 | statement(connection *conn, const std::string &query); 103 | 104 | // reference to parent connection 105 | connection_ref m_connection; 106 | // non-owning pointer to parent connection 107 | connection *m_parent; 108 | // reference to internal data, shared with all results 109 | statement_data_ref m_data; 110 | }; 111 | 112 | typedef std::shared_ptr statement_ref; 113 | } // namespace mariadb 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /src/private.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2021. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_PRIVATE_HPP_ 12 | #define _MARIADB_PRIVATE_HPP_ 13 | 14 | #include 15 | #include 16 | 17 | namespace mariadb { 18 | #if _WIN32 19 | inline int localtime_safe(struct tm *_tm, const time_t *_time) { 20 | return localtime_s(_tm, _time); 21 | } 22 | 23 | inline int gmtime_safe(struct tm *_tm, const time_t *_time) { 24 | return gmtime_s(_tm, _time); 25 | } 26 | #else 27 | inline int localtime_safe(struct tm *_tm, const time_t *_time) { 28 | return localtime_r(_time, _tm) ? 0 : -1; 29 | } 30 | 31 | inline int gmtime_safe(struct tm *_tm, const time_t *_time) { 32 | return gmtime_r(_time, _tm) ? 0 : -1; 33 | } 34 | #endif 35 | } // namespace mariadb 36 | #if _WIN32 37 | 38 | #if __MINGW32__ 39 | // this is needed because on mingw sprintf_s is broken (due to msvcrt not following c99 standard 40 | // with sprintf...) 41 | #define snprintf(buffer, szbuffer, format, ...) __mingw_sprintf(buffer, format, ##__VA_ARGS__) 42 | #else 43 | #define snprintf sprintf_s 44 | #endif 45 | 46 | #endif 47 | 48 | inline int mysql_option_safe(MYSQL *mysql, const mysql_option option, const char *value) { 49 | #if MARIADB_HAS_OPTIONS_V 50 | return mysql_optionsv(mysql, option, value); 51 | #else 52 | return mysql_options(mysql, option, value); 53 | #endif 54 | } 55 | 56 | #define MARIADB_THROW(error, ...) throw error(__VA_ARGS__) 57 | #define MARIADB_THROW_IF(x, error, ...) \ 58 | do { \ 59 | if ((x)) { \ 60 | MARIADB_THROW(error, __VA_ARGS__); \ 61 | } \ 62 | } while (0) 63 | 64 | #define MARIADB_ERROR_QUIET(error, error_id, error_desc) MARIADB_THROW(error, error_id, error_desc) 65 | #define MARIADB_ERROR_VERBOSE(error, error_id, error_desc) \ 66 | do { \ 67 | std::cerr << "MariaDB Error(" << (error_id) << "): " << (error_desc) << "\nIn function: " << __FUNCTION__ \ 68 | << "\nIn file " << __FILE__ << "\nOn line " << __LINE__ << '\n'; \ 69 | MARIADB_ERROR_QUIET(error, error_id, error_desc); \ 70 | } while (0) 71 | 72 | #if MARIADB_QUIET 73 | #define MARIADB_ERROR MARIADB_ERROR_QUIET 74 | #else 75 | #define MARIADB_ERROR MARIADB_ERROR_VERBOSE 76 | #endif 77 | 78 | #define MARIADB_CONN_ERROR(conn) \ 79 | do { \ 80 | m_last_error_no = mysql_errno(conn); \ 81 | m_last_error = mysql_error(conn); \ 82 | MARIADB_ERROR(exception::connection, m_last_error_no, m_last_error); \ 83 | } while (0) 84 | #define MARIADB_CONN_CLOSE_ERROR(conn) \ 85 | do { \ 86 | m_last_error_no = mysql_errno(conn); \ 87 | m_last_error = mysql_error(conn); \ 88 | disconnect(); \ 89 | MARIADB_ERROR(exception::connection, m_last_error_no, m_last_error); \ 90 | } while (0) 91 | 92 | #define MARIADB_STMT_ERROR(stmt) \ 93 | do { \ 94 | m_last_error_no = mysql_stmt_errno(stmt); \ 95 | m_last_error = mysql_stmt_error(stmt); \ 96 | MARIADB_ERROR(exception::statement, m_last_error_no, m_last_error); \ 97 | } while (0) 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/account.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | 14 | using namespace mariadb; 15 | 16 | account::account(const std::string &host_name, const std::string &user_name, const std::string &password, 17 | const std::string &schema, u32 port, const std::string &unix_socket) 18 | : m_auto_commit(true), 19 | m_port(port), 20 | m_host_name(host_name), 21 | m_user_name(user_name), 22 | m_password(password), 23 | m_schema(schema), 24 | m_unix_socket(unix_socket) {} 25 | 26 | const std::string &account::host_name() const { 27 | return m_host_name; 28 | } 29 | 30 | const std::string &account::user_name() const { 31 | return m_user_name; 32 | } 33 | 34 | const std::string &account::password() const { 35 | return m_password; 36 | } 37 | 38 | const std::string &account::unix_socket() const { 39 | return m_unix_socket; 40 | } 41 | 42 | u32 account::port() const { 43 | return m_port; 44 | } 45 | 46 | const std::string &account::ssl_key() const { 47 | return m_ssl_key; 48 | } 49 | 50 | const std::string &account::ssl_certificate() const { 51 | return m_ssl_certificate; 52 | } 53 | 54 | const std::string &account::ssl_ca() const { 55 | return m_ssl_ca; 56 | } 57 | 58 | const std::string &account::ssl_ca_path() const { 59 | return m_ssl_ca_path; 60 | } 61 | 62 | const std::string &account::ssl_cipher() const { 63 | return m_ssl_cipher; 64 | } 65 | 66 | const std::string &account::schema() const { 67 | return m_schema; 68 | } 69 | 70 | void account::set_schema(const std::string &schema) { 71 | m_schema = schema; 72 | } 73 | 74 | void account::set_ssl(const std::string &key, const std::string &certificate, const std::string &ca, 75 | const std::string &ca_path, const std::string &cipher) { 76 | m_ssl_key = key; 77 | m_ssl_certificate = certificate; 78 | m_ssl_ca = ca; 79 | m_ssl_ca_path = ca_path; 80 | m_ssl_cipher = cipher; 81 | } 82 | 83 | bool account::auto_commit() const { 84 | return m_auto_commit; 85 | } 86 | 87 | void account::set_auto_commit(bool auto_commit) { 88 | m_auto_commit = auto_commit; 89 | } 90 | 91 | bool account::store_result() const { 92 | return m_store_result; 93 | } 94 | 95 | void account::set_store_result(bool store_result) { 96 | m_store_result = store_result; 97 | } 98 | 99 | const account::map_options_t &account::options() const { 100 | return m_options; 101 | } 102 | 103 | const std::string account::option(const std::string &name) const { 104 | const map_options_t::const_iterator value = m_options.find(name); 105 | 106 | // return option value if found 107 | return value == m_options.end() ? "" : value->second; 108 | } 109 | 110 | void account::set_option(const std::string &name, const std::string &value) { 111 | m_options[name] = value; 112 | } 113 | 114 | void account::clear_options() { 115 | m_options.clear(); 116 | } 117 | 118 | const account::map_connect_options_t &account::connect_options() const { 119 | return m_connect_options; 120 | } 121 | 122 | void account::clear_connect_options() { 123 | m_connect_options.clear(); 124 | } 125 | 126 | void account::set_connect_option(mysql_option option, bool arg) { 127 | m_connect_options[option] = std::unique_ptr(new option_arg_bool(arg)); 128 | } 129 | 130 | void account::set_connect_option(mysql_option option, int arg) { 131 | m_connect_options[option] = std::unique_ptr(new option_arg_int(arg)); 132 | } 133 | 134 | void account::set_connect_option(mysql_option option, const std::string &arg) { 135 | m_connect_options[option] = std::unique_ptr(new option_arg_string(arg)); 136 | } 137 | 138 | account_ref account::create(const std::string &host_name, const std::string &user_name, const std::string &password, 139 | const std::string &schema, u32 port, const std::string &unix_socket) { 140 | return account_ref(new account(host_name, user_name, password, schema, port, unix_socket)); 141 | } 142 | -------------------------------------------------------------------------------- /test/RollbackTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "RollbackTest.h" 10 | 11 | TEST_P(RollbackTest, testTransactCommit) { 12 | transaction_ref trx = m_con->create_transaction(); 13 | 14 | u64 id = m_con->insert("INSERT INTO " + m_table_name + "(str) VALUES('test');"); 15 | EXPECT_NE(0, id); 16 | trx->commit(); 17 | 18 | result_set_ref rs = m_con->query("SELECT COUNT(*) FROM " + m_table_name + ";"); 19 | EXPECT_TRUE(!!rs); 20 | EXPECT_TRUE(rs->next()); 21 | EXPECT_EQ(1, rs->get_unsigned64(0)); 22 | } 23 | 24 | TEST_P(RollbackTest, testTransactionRollback) { 25 | // force transaction out of scope to destruct it and trigger automatic rollback 26 | { 27 | transaction_ref trx = m_con->create_transaction(); 28 | m_con->insert("INSERT INTO " + m_table_name + "(str) VALUES('test2');"); 29 | } 30 | 31 | result_set_ref rs = m_con->query("SELECT COUNT(*) FROM " + m_table_name + ";"); 32 | EXPECT_TRUE(!!rs); 33 | EXPECT_TRUE(rs->next()); 34 | EXPECT_EQ(0, rs->get_unsigned64(0)); 35 | } 36 | 37 | TEST_P(RollbackTest, testSavePointCommit) { 38 | transaction_ref trx = m_con->create_transaction(); 39 | { 40 | save_point_ref sp = trx->create_save_point(); 41 | u64 id = m_con->insert("INSERT INTO " + m_table_name + "(str) VALUES('test');"); 42 | EXPECT_NE(0, id); 43 | sp->commit(); 44 | } 45 | trx->commit(); 46 | 47 | result_set_ref rs = m_con->query("SELECT COUNT(*) FROM " + m_table_name + ";"); 48 | EXPECT_TRUE(!!rs); 49 | EXPECT_TRUE(rs->next()); 50 | EXPECT_EQ(1, rs->get_unsigned64(0)); 51 | } 52 | 53 | TEST_P(RollbackTest, testSavePointNoCommit) { 54 | transaction_ref trx = m_con->create_transaction(); 55 | { 56 | save_point_ref sp = trx->create_save_point(); 57 | u64 id = m_con->insert("INSERT INTO " + m_table_name + "(str) VALUES('test');"); 58 | EXPECT_NE(0, id); 59 | } 60 | trx->commit(); 61 | 62 | result_set_ref rs = m_con->query("SELECT COUNT(*) FROM " + m_table_name + ";"); 63 | EXPECT_TRUE(!!rs); 64 | EXPECT_TRUE(rs->next()); 65 | EXPECT_EQ(0, rs->get_unsigned64(0)); 66 | } 67 | 68 | TEST_P(RollbackTest, testMultiInsertIntegration) { 69 | std::string queries[] = { 70 | "INSERT INTO " + m_table_name + " (data) VALUES(?);", 71 | "INSERT INTO " + m_table_name + " (data, str) VALUES(?, ?);", 72 | "INSERT INTO " + m_table_name + " (data, str, dt) VALUES(?, ?, ?);", 73 | "INSERT INTO " + m_table_name + " (data, str, dt, t) VALUES(?, ?, ?, ?);", 74 | "INSERT INTO " + m_table_name + " (data, str, dt, t, value) VALUES(?, ?, ?, ?, ?);", 75 | "INSERT INTO " + m_table_name + 76 | " (data, str, dt, t, value, deci) VALUES(?, ?, ?, ?, ?, ?);", 77 | ""}; 78 | 79 | const char *content = 80 | "01234567890123456789012345678901234567890123456789" 81 | "01234567890123456789012345678901234567890123456789"; 82 | 83 | u32 index = 0; 84 | 85 | while (queries[index] != "") { 86 | statement_ref sta = m_con->create_statement(queries[index]); 87 | data_ref data = data_ref(new mariadb::data(content, 100)); 88 | 89 | sta->set_data(0, data); 90 | 91 | if (index >= 1) sta->set_string(1, "test"); 92 | if (index >= 2) sta->set_date_time(2, mariadb::date_time(2000, 1, 2, 3, 4, 5)); 93 | if (index >= 3) sta->set_time(3, mariadb::time(11, 22, 33)); 94 | if (index >= 4) sta->set_signed32(4, 666); 95 | if (index >= 5) sta->set_decimal(5, decimal("1.1234")); 96 | EXPECT_NE(0, sta->insert()); 97 | 98 | index++; 99 | result_set_ref rs = m_con->query("SELECT COUNT(*) FROM " + m_table_name + ";"); 100 | EXPECT_TRUE(!!rs); 101 | EXPECT_TRUE(rs->next()); 102 | EXPECT_EQ(index, rs->get_unsigned64(0)); 103 | } 104 | 105 | // 106 | // Validate the inserted value from last test 107 | // 108 | 109 | result_set_ref rs = m_con->query("SELECT data, str, dt, t, value, deci FROM " + m_table_name + 110 | " ORDER BY id DESC LIMIT 1;"); 111 | 112 | EXPECT_TRUE(!!rs); 113 | EXPECT_TRUE(rs->next()); 114 | 115 | data_ref data = rs->get_data("data"); 116 | EXPECT_TRUE(!!data); 117 | EXPECT_EQ(100, data->size()); 118 | 119 | EXPECT_EQ("test", rs->get_string("str")); 120 | EXPECT_EQ(666, rs->get_signed32("value")); 121 | EXPECT_EQ("1.1234", rs->get_decimal("deci").str()); 122 | EXPECT_EQ("2000-01-02 03:04:05", rs->get_date_time("dt").str()); 123 | EXPECT_EQ("11:22:33", rs->get_time("t").str_time()); 124 | } 125 | 126 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, RollbackTest, ::testing::Values(true, false)); 127 | -------------------------------------------------------------------------------- /src/time_span.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #include 11 | 12 | using namespace mariadb; 13 | 14 | time_span::time_span(u32 days, u8 hours, u8 minutes, u8 seconds, u16 milliseconds, bool negative) { 15 | set(days, hours, minutes, seconds, milliseconds, negative); 16 | } 17 | 18 | time_span::time_span(const time_span &dur) { 19 | set(dur.days(), dur.hours(), dur.minutes(), dur.seconds(), dur.milliseconds(), dur.negative()); 20 | } 21 | 22 | int time_span::compare(const time_span &ts) const { 23 | if (negative() && !ts.negative()) 24 | return -1; 25 | 26 | if (!negative() && ts.negative()) 27 | return 1; 28 | 29 | if (zero() && ts.zero()) 30 | return 0; 31 | 32 | if (days() < ts.days()) 33 | return -1; 34 | 35 | if (days() > ts.days()) 36 | return 1; 37 | 38 | if (hours() < ts.hours()) 39 | return -1; 40 | 41 | if (hours() > ts.hours()) 42 | return 1; 43 | 44 | if (minutes() < ts.minutes()) 45 | return -1; 46 | 47 | if (minutes() > ts.minutes()) 48 | return 1; 49 | 50 | if (seconds() < ts.seconds()) 51 | return -1; 52 | 53 | if (seconds() > ts.seconds()) 54 | return 1; 55 | 56 | if (milliseconds() < ts.milliseconds()) 57 | return -1; 58 | 59 | return milliseconds() == ts.milliseconds() ? 0 : 1; 60 | } 61 | 62 | time_span &time_span::operator=(const time_span &ts) { 63 | set(ts.days(), ts.hours(), ts.minutes(), ts.seconds(), ts.milliseconds(), ts.negative()); 64 | 65 | return *this; 66 | } 67 | 68 | bool time_span::operator==(const time_span &ts) const { 69 | return compare(ts) == 0; 70 | } 71 | 72 | bool time_span::operator!=(const time_span &ts) const { 73 | return compare(ts) != 0; 74 | } 75 | 76 | bool time_span::operator<(const time_span &ts) const { 77 | return compare(ts) < 0; 78 | } 79 | 80 | bool time_span::operator<=(const time_span &ts) const { 81 | return compare(ts) <= 0; 82 | } 83 | 84 | bool time_span::operator>(const time_span &ts) const { 85 | return compare(ts) > 0; 86 | } 87 | 88 | bool time_span::operator>=(const time_span &ts) const { 89 | return compare(ts) >= 0; 90 | } 91 | 92 | void time_span::set(u32 _days, u8 _hours, u8 _minutes, u8 _seconds, u16 _milliseconds, bool _negative) { 93 | negative(_negative); 94 | days(_days); 95 | hours(_hours); 96 | minutes(_minutes); 97 | seconds(_seconds); 98 | milliseconds(_milliseconds); 99 | } 100 | 101 | bool time_span::zero() const { 102 | return m_days == 0 && m_hours == 0 && m_minutes == 0 && m_seconds == 0 && m_milliseconds == 0; 103 | } 104 | 105 | u32 time_span::days() const { 106 | return m_days; 107 | } 108 | 109 | u32 time_span::days(u32 days) { 110 | return (m_days = days); 111 | } 112 | 113 | u8 time_span::hours() const { 114 | return m_hours; 115 | } 116 | 117 | u8 time_span::hours(u8 hours) { 118 | if (hours > 23) 119 | throw std::invalid_argument("Hours must be < 24"); 120 | 121 | return (m_hours = hours); 122 | } 123 | 124 | u8 time_span::minutes() const { 125 | return m_minutes; 126 | } 127 | 128 | u8 time_span::minutes(u8 minutes) { 129 | if (minutes > 59) 130 | throw std::invalid_argument("Hours must be < 60"); 131 | 132 | return (m_minutes = minutes); 133 | } 134 | 135 | u8 time_span::seconds() const { 136 | return m_seconds; 137 | } 138 | 139 | u8 time_span::seconds(u8 seconds) { 140 | if (seconds > 60) 141 | throw std::invalid_argument("Hours must be < 61"); 142 | 143 | return (m_seconds = seconds); 144 | } 145 | 146 | u16 time_span::milliseconds() const { 147 | return m_milliseconds; 148 | } 149 | 150 | u16 time_span::milliseconds(u16 milliseconds) { 151 | if (milliseconds > 999) 152 | throw std::invalid_argument("Hours must be < 1000"); 153 | 154 | return (m_milliseconds = milliseconds); 155 | } 156 | 157 | bool time_span::negative() const { 158 | return m_negative; 159 | } 160 | 161 | bool time_span::negative(bool negative) { 162 | return (m_negative = negative); 163 | } 164 | 165 | u64 time_span::total_hours() const { 166 | return m_days * 24u + m_hours; 167 | } 168 | 169 | u64 time_span::total_minutes() const { 170 | return total_hours() * 60u + m_minutes; 171 | } 172 | 173 | u64 time_span::total_seconds() const { 174 | return total_minutes() * 60u + m_seconds; 175 | } 176 | 177 | u64 time_span::total_milliseconds() const { 178 | return total_seconds() * 1000u + m_milliseconds; 179 | } 180 | 181 | std::ostream &mariadb::operator<<(std::ostream &os, const time_span &ts) { 182 | if (ts.negative()) 183 | os << "negative "; 184 | 185 | os << ts.days() << " days, " << +ts.hours() << " hours, " << +ts.minutes() << " minutes, " << +ts.seconds() 186 | << " seconds, " << ts.milliseconds() << " milliseconds"; 187 | 188 | return os; 189 | } 190 | -------------------------------------------------------------------------------- /include/mariadb++/data.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_DATA_HPP_ 12 | #define _MARIADB_DATA_HPP_ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mariadb { 19 | template 20 | class data { 21 | public: 22 | typedef Type char_type; 23 | 24 | // 25 | // Constructor 26 | // 27 | data() : m_count(0), m_size(0), m_data(0) {} 28 | 29 | data(u32 count) : m_count(0), m_size(0), m_data(0) { 30 | create(count); 31 | } 32 | 33 | data(const Type *data, size_t count) : m_count(0), m_size(0), m_data(0) { 34 | create(data, static_cast(count)); 35 | } 36 | 37 | // 38 | // Destructor 39 | // 40 | virtual ~data() { 41 | destroy(); 42 | } 43 | 44 | // 45 | // Create the data 46 | // 47 | bool create(u32 count) { 48 | if (m_data) 49 | destroy(); 50 | 51 | m_data = new Type[count]; 52 | 53 | if (m_data == 0) 54 | return false; 55 | 56 | m_count = count; 57 | m_size = sizeof(Type) * count; 58 | m_position = 0; 59 | 60 | return true; 61 | } 62 | 63 | bool create(const Type *data, u32 count) { 64 | if (create(count)) { 65 | memcpy(m_data, data, m_size); 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | bool resize(u32 count) { 73 | if (count > m_count) { 74 | Type *data = new Type[count]; 75 | 76 | if (data == 0) 77 | return false; 78 | 79 | memcpy(data, m_data, m_size); 80 | delete[] m_data; 81 | m_data = data; 82 | } 83 | 84 | m_count = count; 85 | m_size = sizeof(Type) * count; 86 | 87 | return true; 88 | } 89 | 90 | // 91 | // Destroy 92 | // 93 | void destroy() { 94 | if (m_data) 95 | delete[] m_data; 96 | 97 | m_data = NULL; 98 | m_size = 0; 99 | m_count = 0; 100 | m_position = 0; 101 | } 102 | 103 | // 104 | // Convert data to a valid string 105 | // 106 | std::string string() const { 107 | std::string str; 108 | if (m_size) 109 | str.append(reinterpret_cast(m_data), m_size); 110 | return str; 111 | } 112 | 113 | // 114 | // Get data / size 115 | // 116 | inline u32 size() const { 117 | return m_size; 118 | } 119 | inline Type *get() const { 120 | return m_data; 121 | } 122 | 123 | // 124 | // Operator to access the base directly 125 | // 126 | inline operator Type *() { 127 | return m_data; 128 | } 129 | inline operator Type *() const { 130 | return m_data; 131 | } 132 | 133 | // 134 | // Stream methods 135 | // 136 | std::streamsize read(char_type *buffer, std::streamsize size) { 137 | const std::streamsize amount = static_cast(m_size - m_position); 138 | const std::streamsize result = std::min(size, amount); 139 | 140 | if (result != 0) { 141 | std::copy(m_data + m_position, m_data + m_position + result, buffer); 142 | m_position += result; 143 | return result; 144 | } 145 | 146 | return size ? -1 : 0; 147 | } 148 | 149 | std::streamsize write(char_type *buffer, std::streamsize size) { 150 | const std::streamsize amount = static_cast(m_size - m_position); 151 | const std::streamsize result = std::min(size, amount); 152 | 153 | if (result != 0) { 154 | std::copy(buffer, buffer + result, m_data + m_position); 155 | m_position += result; 156 | return result; 157 | } 158 | 159 | return size ? -1 : 0; 160 | } 161 | 162 | std::streampos seek(std::streampos offset, std::ios_base::seekdir seekdir) { 163 | std::streampos pos; 164 | 165 | if (seekdir == std::ios_base::beg) 166 | pos = offset; 167 | else if (seekdir == std::ios_base::cur) 168 | pos = m_position + offset; 169 | else if (seekdir == std::ios_base::end) 170 | pos = m_size + offset; 171 | else 172 | throw std::ios_base::failure("Bad seek direction"); 173 | 174 | if (pos < 0 || pos > m_size) 175 | throw std::ios_base::failure("Bad seek offset"); 176 | 177 | m_position = static_cast(pos); 178 | return pos; 179 | } 180 | 181 | protected: 182 | u32 m_count; 183 | u32 m_size; 184 | u32 m_position; 185 | Type *m_data; 186 | }; 187 | 188 | typedef std::shared_ptr< ::mariadb::data > data_ref; 189 | typedef std::basic_iostream< ::mariadb::data > data_stream; 190 | } // namespace mariadb 191 | 192 | #endif 193 | -------------------------------------------------------------------------------- /src/statement.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2021. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "private.hpp" 18 | #include 19 | 20 | using namespace mariadb; 21 | 22 | statement::statement(connection *conn, const std::string &query) 23 | : m_parent(conn), m_data(statement_data_ref(new statement_data(mysql_stmt_init(conn->m_mysql)))) { 24 | if (!m_data->m_statement) 25 | MARIADB_CONN_ERROR(conn->m_mysql); 26 | else if (mysql_stmt_prepare(m_data->m_statement, query.c_str(), query.size())) 27 | MARIADB_STMT_ERROR(m_data->m_statement); 28 | else { 29 | m_data->m_bind_count = mysql_stmt_param_count(m_data->m_statement); 30 | 31 | if (m_data->m_bind_count > 0) { 32 | m_data->m_raw_binds = new MYSQL_BIND[m_data->m_bind_count]; 33 | 34 | for (uint32_t i = 0; i < m_data->m_bind_count; i++) 35 | m_data->m_binds.emplace_back(new bind(&m_data->m_raw_binds[i])); 36 | } 37 | } 38 | } 39 | 40 | void statement::set_connection(connection_ref &connection) { 41 | m_connection = connection; 42 | } 43 | 44 | u64 statement::execute() { 45 | if (m_data->m_raw_binds && mysql_stmt_bind_param(m_data->m_statement, m_data->m_raw_binds)) 46 | MARIADB_STMT_ERROR(m_data->m_statement); 47 | 48 | if (mysql_stmt_execute(m_data->m_statement)) 49 | MARIADB_STMT_ERROR(m_data->m_statement); 50 | 51 | return mysql_stmt_affected_rows(m_data->m_statement); 52 | } 53 | 54 | u64 statement::insert() { 55 | if (m_data->m_raw_binds && mysql_stmt_bind_param(m_data->m_statement, m_data->m_raw_binds)) 56 | MARIADB_STMT_ERROR(m_data->m_statement); 57 | 58 | if (mysql_stmt_execute(m_data->m_statement)) 59 | MARIADB_STMT_ERROR(m_data->m_statement); 60 | 61 | return mysql_stmt_insert_id(m_data->m_statement); 62 | } 63 | 64 | result_set_ref statement::query() { 65 | result_set_ref rs; 66 | 67 | if (m_data->m_raw_binds && mysql_stmt_bind_param(m_data->m_statement, m_data->m_raw_binds)) 68 | MARIADB_STMT_ERROR(m_data->m_statement); 69 | 70 | if (mysql_stmt_execute(m_data->m_statement)) 71 | MARIADB_STMT_ERROR(m_data->m_statement); 72 | 73 | rs.reset(new result_set(m_parent, m_data)); 74 | return rs; 75 | } 76 | 77 | MAKE_SETTER(blob, stream_ref) { 78 | if (!value) 79 | return; 80 | 81 | value->seekg(0, std::ios_base::end); 82 | u64 size = value->tellg(); 83 | value->seekg(0); 84 | 85 | // allocate empty buffer 86 | bind.set(MYSQL_TYPE_BLOB, nullptr, static_cast(size)); 87 | // copy stream over 88 | value->read(bind.buffer(), bind.length()); 89 | } 90 | 91 | MAKE_SETTER(data, const data_ref &) { 92 | if (!value) 93 | return; 94 | 95 | bind.set(MYSQL_TYPE_BLOB, value->get(), value->size()); 96 | } 97 | 98 | MAKE_SETTER(date_time, const date_time &) { 99 | bind.m_time = value.mysql_time(); 100 | bind.set(MYSQL_TYPE_DATETIME); 101 | } 102 | 103 | MAKE_SETTER(date, const date_time &) { 104 | bind.m_time = value.date().mysql_time(); 105 | bind.set(MYSQL_TYPE_DATE); 106 | } 107 | 108 | MAKE_SETTER(time, const mariadb::time &) { 109 | bind.m_time = value.mysql_time(); 110 | bind.set(MYSQL_TYPE_TIME); 111 | } 112 | 113 | MAKE_SETTER(decimal, const decimal &) { 114 | std::string str = value.str(); 115 | bind.set(MYSQL_TYPE_STRING, str.c_str(), str.size()); 116 | } 117 | 118 | MAKE_SETTER(string, const std::string &) { 119 | bind.set(MYSQL_TYPE_STRING, value.c_str(), value.size()); 120 | } 121 | 122 | MAKE_SETTER(boolean, bool) { 123 | bind.m_unsigned64 = value; 124 | bind.set(MYSQL_TYPE_TINY); 125 | } 126 | 127 | MAKE_SETTER(unsigned8, u8) { 128 | bind.m_unsigned64 = value; 129 | bind.set(MYSQL_TYPE_TINY, nullptr, 0, true); 130 | } 131 | 132 | MAKE_SETTER(signed8, s8) { 133 | bind.m_signed64 = value; 134 | bind.set(MYSQL_TYPE_TINY); 135 | } 136 | 137 | MAKE_SETTER(unsigned16, u16) { 138 | bind.m_unsigned64 = value; 139 | bind.set(MYSQL_TYPE_SHORT, nullptr, 0, true); 140 | } 141 | 142 | MAKE_SETTER(signed16, s16) { 143 | bind.m_signed64 = value; 144 | bind.set(MYSQL_TYPE_SHORT); 145 | } 146 | 147 | MAKE_SETTER(unsigned32, u32) { 148 | bind.m_unsigned64 = value; 149 | bind.set(MYSQL_TYPE_LONG, nullptr, 0, true); 150 | } 151 | 152 | MAKE_SETTER(signed32, s32) { 153 | bind.m_signed64 = value; 154 | bind.set(MYSQL_TYPE_LONG); 155 | } 156 | 157 | MAKE_SETTER(unsigned64, u64) { 158 | bind.m_unsigned64 = value; 159 | bind.set(MYSQL_TYPE_LONGLONG, nullptr, 0, true); 160 | } 161 | 162 | MAKE_SETTER(signed64, s64) { 163 | bind.m_signed64 = value; 164 | bind.set(MYSQL_TYPE_LONGLONG); 165 | } 166 | 167 | MAKE_SETTER(float, f32) { 168 | bind.m_float32[0] = value; 169 | bind.set(MYSQL_TYPE_FLOAT); 170 | } 171 | 172 | MAKE_SETTER(double, f64) { 173 | bind.m_double64 = value; 174 | bind.set(MYSQL_TYPE_DOUBLE); 175 | } 176 | 177 | void statement::set_null(u32 index) { 178 | if (index >= m_data->m_bind_count) 179 | throw std::out_of_range("Field index out of range"); 180 | 181 | bind &bind = *m_data->m_binds.at(index); 182 | bind.set(MYSQL_TYPE_NULL); 183 | } 184 | -------------------------------------------------------------------------------- /include/mariadb++/time_span.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_TIME_SPAN_HPP_ 11 | #define _MARIADB_TIME_SPAN_HPP_ 12 | 13 | #include 14 | #include 15 | 16 | namespace mariadb { 17 | class time_span { 18 | public: 19 | /** 20 | * Construct time_span from given data. Note that a time_span can span one day at most. 21 | * 22 | * @param hours Number of hours 0-23 23 | * @param minutes Number of minutes 0-59 24 | * @param seconds Number of seconds 0-59 25 | * @param milliseconds Number of milliseconds 0-999 26 | * @param negative Indicates negative timespan 27 | */ 28 | time_span(u32 days = 0, u8 hours = 0, u8 minutes = 0, u8 seconds = 0, u16 milliseconds = 0, bool negative = false); 29 | 30 | /** 31 | * Copy constructor 32 | * 33 | * @param dur Timespan to copy from 34 | */ 35 | time_span(const time_span &dur); 36 | 37 | /** 38 | * Compares this timespan with the given timespan. 39 | * 40 | * @param rhs Timespan to compare this to 41 | * @return 1 if this is greater, 0 if equal, -1 if this is smaller 42 | */ 43 | int compare(const time_span &rhs) const; 44 | 45 | time_span &operator=(const time_span &rhs); 46 | bool operator==(const time_span &rhs) const; 47 | bool operator!=(const time_span &rhs) const; 48 | bool operator<(const time_span &rhs) const; 49 | bool operator<=(const time_span &rhs) const; 50 | bool operator>(const time_span &rhs) const; 51 | bool operator>=(const time_span &rhs) const; 52 | 53 | /** 54 | * Set the value of this time_span to given values 55 | * 56 | * @param days Any number of days 57 | * @param hours Number of hours 0-23 58 | * @param minutes Number of minutes 0-59 59 | * @param seconds Number of seconds 0-59 60 | * @param milliseconds Number of milliseconds 0-999 61 | * @param negative Indicates negative timespan 62 | */ 63 | void set(u32 days = 0, u8 hours = 0, u8 minutes = 0, u8 seconds = 0, u16 milliseconds = 0, bool negative = false); 64 | 65 | /** 66 | * Indicates whether this time_span is zero. That is only true if all components are zero 67 | * 68 | * @return True if zero 69 | */ 70 | bool zero() const; 71 | 72 | /** 73 | * Indicates whether this time_span is negative. 74 | * 75 | * @return True if negative 76 | */ 77 | bool negative() const; 78 | 79 | /** 80 | * Sets negative flag on this time_span 81 | * 82 | * @param negative Value to set 83 | * @return Newly set value 84 | */ 85 | bool negative(bool negative); 86 | 87 | /** 88 | * Get number of days 89 | * 90 | * @return Number of days 91 | */ 92 | u32 days() const; 93 | 94 | /** 95 | * Sets the number of days 96 | * 97 | * @param hour Number of days to set 98 | * @return Newly set value 99 | */ 100 | u32 days(u32 day); 101 | 102 | /** 103 | * Get number of hours 104 | * 105 | * @return Number of hours 106 | */ 107 | u8 hours() const; 108 | 109 | /** 110 | * Sets the number of hours 111 | * 112 | * @param hour Number of hours to set 113 | * @return Newly set value 114 | */ 115 | u8 hours(u8 hour); 116 | 117 | /** 118 | * Get number of hours 119 | * 120 | * @return Number of hours 121 | */ 122 | u8 minutes() const; 123 | 124 | /** 125 | * Sets the number of minutes 126 | * 127 | * @param minute Number of minutes to set 128 | * @return Newly set value 129 | */ 130 | u8 minutes(u8 minute); 131 | 132 | /** 133 | * Get number of seconds 134 | * 135 | * @return Number of seconds 136 | */ 137 | u8 seconds() const; 138 | 139 | /** 140 | * Sets the number of seconds 141 | * 142 | * @param second Number of seconds to set 143 | * @return Newly set value 144 | */ 145 | u8 seconds(u8 second); 146 | 147 | /** 148 | * Get number of milliseconds 149 | * 150 | * @return Number of milliseconds 151 | */ 152 | u16 milliseconds() const; 153 | 154 | /** 155 | * Sets the number of milliseconds 156 | * 157 | * @param millisecond Number of milliseconds to set 158 | * @return Newly set value 159 | */ 160 | u16 milliseconds(u16 millisecond); 161 | 162 | /** 163 | * Converts the time_span to minutes 164 | * 165 | * @return Total number of minutes in this time_span 166 | */ 167 | u64 total_hours() const; 168 | 169 | /** 170 | * Converts the time_span to minutes 171 | * 172 | * @return Total number of minutes in this time_span 173 | */ 174 | u64 total_minutes() const; 175 | 176 | /** 177 | * Converts the time_span to seconds 178 | * 179 | * @return Total number of seconds in this time_span 180 | */ 181 | u64 total_seconds() const; 182 | 183 | /** 184 | * Converts the time_span to milliseconds 185 | * 186 | * @return Total number of milliseconds in this time_span 187 | */ 188 | u64 total_milliseconds() const; 189 | 190 | private: 191 | bool m_negative = false; 192 | u32 m_days = 0; 193 | u8 m_hours = 0; 194 | u8 m_minutes = 0; 195 | u8 m_seconds = 0; 196 | u16 m_milliseconds = 0; 197 | }; 198 | 199 | std::ostream &operator<<(std::ostream &os, const time_span &ts); 200 | } // namespace mariadb 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /test/SelectTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2018. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "SelectTest.h" 10 | #include 11 | 12 | TEST_P(SelectTest, SelectEmptyTable) { 13 | m_con->execute("CREATE TABLE " + m_table_name + 14 | " (id INT AUTO_INCREMENT, PRIMARY KEY (`id`));"); 15 | result_set_ref res = m_con->query("SELECT * FROM " + m_table_name); 16 | 17 | ASSERT_TRUE(!!res); 18 | ASSERT_FALSE(res->next()); 19 | } 20 | 21 | TEST_P(SelectTest, IntegerLimits) { 22 | m_con->execute("CREATE TABLE `" + m_table_name + 23 | "` (\n" 24 | "\t`seq` INT(11) NULL DEFAULT NULL,\n" 25 | "\t`signed_tiny` TINYINT(4) NULL DEFAULT NULL,\n" 26 | "\t`signed_small` SMALLINT(6) NULL DEFAULT NULL,\n" 27 | "\t`signed_medium` MEDIUMINT(9) NULL DEFAULT NULL,\n" 28 | "\t`signed_int` INT(11) NULL DEFAULT NULL,\n" 29 | "\t`signed_big` BIGINT(20) NULL DEFAULT NULL,\n" 30 | "\t`unsigned_tiny` TINYINT(3) UNSIGNED NULL DEFAULT NULL,\n" 31 | "\t`unsigned_small` SMALLINT(5) UNSIGNED NULL DEFAULT NULL,\n" 32 | "\t`unsigned_medium` MEDIUMINT(8) UNSIGNED NULL DEFAULT NULL,\n" 33 | "\t`unsigned_int` INT(10) UNSIGNED NULL DEFAULT NULL,\n" 34 | "\t`unsigned_big` BIGINT(20) UNSIGNED NULL DEFAULT NULL\n" 35 | ");"); 36 | // min 37 | m_con->query( 38 | "INSERT INTO " + m_table_name + 39 | " VALUES (0, -128, -32768, -8388608, -2147483648, -9223372036854775808, 0, 0, 0, 0, 0);"); 40 | // max 41 | m_con->query("INSERT INTO " + m_table_name + 42 | " VALUES (1, 127, 32767, 8388607, 2147483647, 9223372036854775807, 255, 65535, " 43 | "16777215, 4294967295, 18446744073709551615);"); 44 | 45 | // 46 | result_set_ref res = m_con->query("SELECT * FROM " + m_table_name + " ORDER BY seq ASC;"); 47 | ASSERT_TRUE(!!res); 48 | ASSERT_TRUE(res->next()); 49 | 50 | // min 51 | EXPECT_EQ(-128, res->get_signed8(1)); 52 | EXPECT_EQ(-32768, res->get_signed16(2)); 53 | EXPECT_EQ(-8388608, res->get_signed32(3)); 54 | EXPECT_EQ(-2147483648, res->get_signed32(4)); 55 | EXPECT_EQ(-9223372036854775807 - 1, 56 | res->get_signed64(5)); // can't use -9223372036854775808 here (parser limitation), 57 | // see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52661 58 | EXPECT_EQ(0, res->get_unsigned8(6)); 59 | EXPECT_EQ(0, res->get_unsigned16(7)); 60 | EXPECT_EQ(0, res->get_unsigned32(8)); 61 | EXPECT_EQ(0, res->get_unsigned32(9)); 62 | EXPECT_EQ(0, res->get_unsigned64(10)); 63 | 64 | // next result set 65 | ASSERT_TRUE(res->next()); 66 | 67 | // max 68 | EXPECT_EQ(127, res->get_signed8(1)); 69 | EXPECT_EQ(32767, res->get_signed16(2)); 70 | EXPECT_EQ(8388607, res->get_signed32(3)); 71 | EXPECT_EQ(2147483647, res->get_signed32(4)); 72 | EXPECT_EQ(9223372036854775807, res->get_signed64(5)); 73 | EXPECT_EQ(255, res->get_unsigned8(6)); 74 | EXPECT_EQ(65535, res->get_unsigned16(7)); 75 | EXPECT_EQ(16777215, res->get_unsigned32(8)); 76 | EXPECT_EQ(4294967295, res->get_unsigned32(9)); 77 | EXPECT_EQ(18446744073709551615ULL, res->get_unsigned64(10)); 78 | } 79 | 80 | TEST_P(SelectTest, RealLimits) { 81 | m_con->execute("CREATE TABLE `" + m_table_name + 82 | "` (\n" 83 | "\t`seq` INT(11) NULL DEFAULT NULL,\n" 84 | "\t`negative_float` FLOAT NULL DEFAULT NULL,\n" 85 | "\t`negative_double` DOUBLE NULL DEFAULT NULL,\n" 86 | "\t`positive_float` FLOAT UNSIGNED NULL DEFAULT NULL,\n" 87 | "\t`positive_double` DOUBLE UNSIGNED NULL DEFAULT NULL\n" 88 | ");"); 89 | 90 | m_con->query("INSERT INTO " + m_table_name + 91 | " VALUES (0, -3.402823466e+38, -1.7976931348623157e+308, 1.175494351e-38, " 92 | "2.2250738585072014e-308);"); 93 | m_con->query("INSERT INTO " + m_table_name + 94 | " VALUES (1, -1.175494351e-38, -2.2250738585072014e-308, 3.402823466e+38, " 95 | "1.7976931348623157e+308);"); 96 | m_con->query("INSERT INTO " + m_table_name + " VALUES (2, 0, 0, 0, 0);"); 97 | 98 | // 99 | result_set_ref res = m_con->query("SELECT * FROM " + m_table_name + " ORDER BY seq ASC;"); 100 | ASSERT_TRUE(!!res); 101 | ASSERT_TRUE(res->next()); 102 | 103 | // 1st row 104 | EXPECT_FLOAT_EQ(-3.40282e+38, 105 | res->get_float(1)); // MariaDB cannot store full single precision -> rounded 106 | EXPECT_FLOAT_EQ(-1.7976931348623157e+308, res->get_double(2)); 107 | EXPECT_TRUE(std::isnan(res->get_float(3))); 108 | EXPECT_FLOAT_EQ(2.2250738585072014e-308, res->get_double(4)); 109 | 110 | // 2nd row 111 | ASSERT_TRUE(res->next()); 112 | EXPECT_TRUE(std::isnan(res->get_float(1))); 113 | EXPECT_FLOAT_EQ(-2.2250738585072014e-308, res->get_double(2)); 114 | EXPECT_FLOAT_EQ(3.40282e+38, 115 | res->get_float(3)); // MariaDB cannot store full single precision -> rounded 116 | EXPECT_FLOAT_EQ(1.7976931348623157e+308, res->get_double(4)); 117 | 118 | // 3rd row 119 | ASSERT_TRUE(res->next()); 120 | EXPECT_FLOAT_EQ(0, res->get_float(1)); 121 | EXPECT_FLOAT_EQ(0, res->get_double(2)); 122 | EXPECT_FLOAT_EQ(0, res->get_float(3)); 123 | EXPECT_FLOAT_EQ(0, res->get_double(4)); 124 | } 125 | 126 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, SelectTest, ::testing::Values(true, false)); 127 | -------------------------------------------------------------------------------- /src/concurrency.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #define LOCK_MUTEX() std::lock_guard lock(g_mutex) 19 | 20 | #include "worker.hpp" 21 | #include "private.hpp" 22 | 23 | using namespace mariadb; 24 | using namespace mariadb::concurrency; 25 | 26 | namespace { 27 | handle g_next_handle(0); 28 | account_ref g_account; 29 | std::mutex g_mutex; 30 | std::vector g_querys_in; 31 | std::map g_querys_out; 32 | bool g_thread_running(false); 33 | 34 | typedef std::map map_t; 35 | 36 | // 37 | // Worker thread 38 | // 39 | void worker_thread() { 40 | g_thread_running = true; 41 | 42 | mysql_thread_init(); 43 | 44 | // breaking in infinite loop because checking g_querys_in would result in a race condition 45 | while (true) { 46 | worker *w = NULL; 47 | { 48 | LOCK_MUTEX(); 49 | auto possible_w = g_querys_in.begin(); 50 | if (possible_w == g_querys_in.end()) 51 | break; 52 | 53 | w = *possible_w; 54 | g_querys_in.erase(possible_w); 55 | } 56 | 57 | w->execute(); 58 | 59 | if (!w->keep_handle()) 60 | delete w; 61 | } 62 | 63 | g_thread_running = false; 64 | mysql_thread_end(); 65 | } 66 | 67 | // 68 | // Start thread if no thread is running 69 | // 70 | void start_thread() { 71 | if (!g_thread_running) { 72 | std::thread t(worker_thread); 73 | t.detach(); 74 | } 75 | } 76 | 77 | // 78 | // Get worker from handle 79 | // 80 | const worker &get_worker(handle h) { 81 | LOCK_MUTEX(); 82 | const map_t::const_iterator w = g_querys_out.find(h); 83 | 84 | if (w != g_querys_out.end()) 85 | return *w->second; 86 | 87 | static worker removed; 88 | return removed; 89 | } 90 | 91 | // 92 | // Add / remove a new query / command to the thread 93 | // 94 | handle add(const std::string &query, command::type command, bool keep_handle) { 95 | LOCK_MUTEX(); 96 | worker *w = new worker(g_account, ++g_next_handle, keep_handle, command, query); 97 | g_querys_in.push_back(w); 98 | 99 | if (keep_handle) 100 | g_querys_out[g_next_handle] = w; 101 | 102 | start_thread(); 103 | 104 | return g_next_handle; 105 | } 106 | 107 | handle add(statement_ref &statement, command::type command, bool keep_handle) { 108 | LOCK_MUTEX(); 109 | worker *w = new worker(g_account, ++g_next_handle, keep_handle, command, statement); 110 | g_querys_in.push_back(w); 111 | 112 | if (keep_handle) 113 | g_querys_out[g_next_handle] = w; 114 | 115 | start_thread(); 116 | 117 | return g_next_handle; 118 | } 119 | } // namespace 120 | 121 | // 122 | // Set account for connection 123 | // 124 | void concurrency::set_account(account_ref &account) { 125 | g_account = account; 126 | } 127 | 128 | // 129 | // Query status 130 | // 131 | status::type concurrency::worker_status(handle h) { 132 | const worker &w = get_worker(h); 133 | return w.status(); 134 | } 135 | 136 | // 137 | // Query executed, result ready to be used 138 | // 139 | u64 concurrency::get_execute_result(handle h) { 140 | const worker &w = get_worker(h); 141 | return w.status() == status::removed ? 0 : w.result(); 142 | } 143 | 144 | u64 concurrency::get_insert_result(handle h) { 145 | const worker &w = get_worker(h); 146 | return w.status() == status::removed ? 0 : w.result(); 147 | } 148 | 149 | result_set_ref concurrency::get_query_result(handle h) { 150 | const worker &w = get_worker(h); 151 | return w.status() == status::removed ? result_set_ref() : w.result_set(); 152 | } 153 | 154 | // 155 | // Execute a query 156 | // 157 | handle concurrency::execute(const std::string &query, bool keep_handle) { 158 | return add(query, command::execute, keep_handle); 159 | } 160 | 161 | handle concurrency::insert(const std::string &query, bool keep_handle) { 162 | return add(query, command::insert, keep_handle); 163 | } 164 | 165 | handle concurrency::query(const std::string &query, bool keep_handle) { 166 | return add(query, command::query, keep_handle); 167 | } 168 | 169 | // 170 | // Execute a query using a statement 171 | // 172 | statement_ref concurrency::create_statement(const std::string &query) { 173 | connection_ref connection = connection::create(g_account); 174 | statement_ref statement = connection->create_statement(query); 175 | statement->set_connection(connection); 176 | return statement; 177 | } 178 | 179 | handle concurrency::execute(statement_ref &statement, bool keep_handle) { 180 | return add(statement, command::execute, keep_handle); 181 | } 182 | 183 | handle concurrency::insert(statement_ref &statement, bool keep_handle) { 184 | return add(statement, command::insert, keep_handle); 185 | } 186 | 187 | handle concurrency::query(statement_ref &statement, bool keep_handle) { 188 | return add(statement, command::query, keep_handle); 189 | } 190 | 191 | // 192 | // Remove a query 193 | // 194 | // Please note, if a result_set is used, it must be done after the result_set is used... 195 | // 196 | void concurrency::release_handle(handle h) { 197 | LOCK_MUTEX(); 198 | map_t::iterator w = g_querys_out.find(h); 199 | 200 | if (w == g_querys_out.end()) 201 | return; 202 | 203 | delete w->second; 204 | g_querys_out.erase(w); 205 | } 206 | 207 | bool concurrency::wait_handle(handle h, u64 wait_time_ms) { 208 | while (worker_status(h) < status::succeed) { 209 | std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); 210 | } 211 | 212 | return worker_status(h) == status::succeed; 213 | } 214 | -------------------------------------------------------------------------------- /src/connection.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2021. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include "private.hpp" 14 | 15 | using namespace mariadb; 16 | 17 | connection::connection(const account_ref &account) : m_mysql(NULL), m_auto_commit(true), m_account(account) {} 18 | 19 | connection_ref connection::create(const account_ref &account) { 20 | return connection_ref(new connection(account)); 21 | } 22 | 23 | connection::~connection() { 24 | disconnect(); 25 | } 26 | 27 | const std::string &connection::schema() const { 28 | return m_schema; 29 | } 30 | 31 | bool connection::set_schema(const std::string &schema) { 32 | if (!connect()) 33 | return false; 34 | 35 | if (mysql_select_db(m_mysql, schema.c_str())) 36 | MARIADB_CONN_ERROR(m_mysql); 37 | 38 | m_schema = schema; 39 | return true; 40 | } 41 | 42 | const std::string &connection::charset() const { 43 | return m_charset; 44 | } 45 | 46 | bool connection::set_charset(const std::string &value) { 47 | if (!connect()) 48 | return false; 49 | 50 | if (mysql_set_character_set(m_mysql, value.c_str())) 51 | MARIADB_CONN_ERROR(m_mysql); 52 | 53 | m_charset = value; 54 | return true; 55 | } 56 | 57 | bool connection::connected() const { 58 | if (m_mysql == nullptr) 59 | return false; 60 | else 61 | return !mysql_ping(m_mysql); 62 | } 63 | 64 | account_ref connection::account() const { 65 | return m_account; 66 | } 67 | 68 | bool connection::auto_commit() const { 69 | return m_auto_commit; 70 | } 71 | 72 | bool connection::set_auto_commit(bool auto_commit) { 73 | if (m_auto_commit == auto_commit) 74 | return true; 75 | 76 | if (!connect()) 77 | return false; 78 | 79 | if (mysql_autocommit(m_mysql, auto_commit)) 80 | MARIADB_CONN_ERROR(m_mysql); 81 | 82 | m_auto_commit = auto_commit; 83 | return true; 84 | } 85 | 86 | bool connection::connect() { 87 | if (connected()) 88 | return true; 89 | 90 | if (m_mysql == nullptr) { 91 | m_mysql = mysql_init(nullptr); 92 | 93 | if (!m_mysql) 94 | MARIADB_ERROR(exception::connection, 0, "Cannot create MYSQL object."); 95 | } 96 | 97 | if (!m_account->ssl_key().empty()) { 98 | if (mysql_option_safe(m_mysql, MYSQL_OPT_SSL_KEY, m_account->ssl_key().c_str()) || 99 | mysql_option_safe(m_mysql, MYSQL_OPT_SSL_CERT, m_account->ssl_certificate().c_str()) || 100 | mysql_option_safe(m_mysql, MYSQL_OPT_SSL_CA, m_account->ssl_ca().c_str()) || 101 | mysql_option_safe(m_mysql, MYSQL_OPT_SSL_CAPATH, m_account->ssl_ca_path().c_str()) || 102 | mysql_option_safe(m_mysql, MYSQL_OPT_SSL_CIPHER, m_account->ssl_cipher().c_str())) 103 | MARIADB_CONN_ERROR(m_mysql); 104 | } 105 | 106 | // 107 | // set connect options 108 | // 109 | for (auto &pair : m_account->connect_options()) { 110 | if (0 != mysql_option_safe(m_mysql, pair.first, static_cast(pair.second->value()))) 111 | MARIADB_CONN_CLOSE_ERROR(m_mysql); 112 | } 113 | 114 | if (!mysql_real_connect(m_mysql, m_account->unix_socket().empty() ? m_account->host_name().c_str() : nullptr, 115 | m_account->user_name().c_str(), m_account->password().c_str(), nullptr, m_account->port(), 116 | m_account->unix_socket().empty() ? nullptr : m_account->unix_socket().c_str(), 117 | CLIENT_MULTI_STATEMENTS)) 118 | MARIADB_CONN_ERROR(m_mysql); 119 | 120 | if (!set_auto_commit(m_account->auto_commit())) 121 | MARIADB_CONN_CLOSE_ERROR(m_mysql); 122 | 123 | if (!m_account->schema().empty()) { 124 | if (!set_schema(m_account->schema().c_str())) 125 | MARIADB_CONN_CLOSE_ERROR(m_mysql); 126 | } 127 | 128 | // 129 | // Set options 130 | // 131 | for (auto &pair : m_account->options()) { 132 | if (1 != execute("SET OPTION " + pair.first + "=" + pair.second)) 133 | MARIADB_CONN_CLOSE_ERROR(m_mysql); 134 | } 135 | 136 | return true; 137 | } 138 | 139 | void connection::disconnect() { 140 | if (!m_mysql) 141 | return; 142 | 143 | mysql_close(m_mysql); 144 | mysql_thread_end(); // mysql_init() call mysql_thread_init therefor it needed to clear memory 145 | // when closed msql handle 146 | m_mysql = nullptr; 147 | } 148 | 149 | result_set_ref connection::query(const std::string &query) { 150 | result_set_ref rs; 151 | 152 | if (!connect()) 153 | return rs; 154 | 155 | if (mysql_real_query(m_mysql, query.c_str(), query.size())) 156 | MARIADB_CONN_ERROR(m_mysql); 157 | 158 | rs.reset(new result_set(this)); 159 | return rs; 160 | } 161 | 162 | u64 connection::execute(const std::string &query) { 163 | if (!connect()) 164 | return 0; 165 | 166 | u64 affected_rows = 0; 167 | 168 | if (mysql_real_query(m_mysql, query.c_str(), query.size())) 169 | MARIADB_CONN_ERROR(m_mysql); 170 | 171 | int status; 172 | do { 173 | MYSQL_RES *result = mysql_store_result(m_mysql); 174 | 175 | if (result) 176 | mysql_free_result(result); 177 | else if (mysql_field_count(m_mysql) == 0) 178 | affected_rows += mysql_affected_rows(m_mysql); 179 | else 180 | MARIADB_CONN_ERROR(m_mysql); 181 | 182 | status = mysql_next_result(m_mysql); 183 | if (status > 0) 184 | MARIADB_CONN_ERROR(m_mysql); 185 | } while (status == 0); 186 | 187 | return affected_rows; 188 | } 189 | 190 | u64 connection::insert(const std::string &query) { 191 | if (!connect()) 192 | return 0; 193 | 194 | if (mysql_real_query(m_mysql, query.c_str(), query.size())) 195 | MARIADB_CONN_ERROR(m_mysql); 196 | 197 | return mysql_insert_id(m_mysql); 198 | } 199 | 200 | statement_ref connection::create_statement(const std::string &query) { 201 | if (!connect()) 202 | return statement_ref(); 203 | 204 | return statement_ref(new statement(this, query)); 205 | } 206 | 207 | transaction_ref connection::create_transaction(isolation::level level, bool consistent_snapshot) { 208 | if (!connect()) 209 | return transaction_ref(); 210 | 211 | return transaction_ref(new transaction(this, level, consistent_snapshot)); 212 | } 213 | -------------------------------------------------------------------------------- /include/mariadb++/connection.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_CONNECTION_HPP_ 12 | #define _MARIADB_CONNECTION_HPP_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | namespace mariadb { 24 | /** 25 | * Wraps a Database connection. 26 | */ 27 | class connection : public last_error { 28 | friend class result_set; 29 | friend class statement; 30 | friend class transaction; 31 | friend class save_point; 32 | 33 | public: 34 | /** 35 | * Destroys connection and automatically disconnects 36 | */ 37 | virtual ~connection(); 38 | 39 | /** 40 | * Actually connects to the database using given account, sets SSL and additional options as 41 | * well as auto commit 42 | * 43 | * @return True on success 44 | */ 45 | bool connect(); 46 | 47 | /** 48 | * Disconnects from the database 49 | */ 50 | void disconnect(); 51 | 52 | /** 53 | * Indicates whether the connection is active. Also detects stale connections 54 | * 55 | * @return True on active connection 56 | */ 57 | bool connected() const; 58 | 59 | /** 60 | * Gets the account associated with this connection 61 | * 62 | * @return Reference to the account 63 | */ 64 | account_ref account() const; 65 | 66 | /** 67 | * Gets the schema (database name) 68 | * 69 | * @return String containing the schema 70 | */ 71 | const std::string &schema() const; 72 | 73 | /** 74 | * Sets the schema (database name). 75 | * The connection needs to be already established 76 | * 77 | * @param schema The new schema name 78 | * @return True on success 79 | */ 80 | bool set_schema(const std::string &schema); 81 | 82 | /** 83 | * Gets the charset associated with this connection 84 | * 85 | * @return String containg the charset (see documentation of MariaDB for possible values) 86 | */ 87 | const std::string &charset() const; 88 | 89 | /** 90 | * Sets the charset. 91 | * The connection needs to be already established 92 | * 93 | * @param value The new charset 94 | * @return True on success 95 | */ 96 | bool set_charset(const std::string &value); 97 | 98 | /** 99 | * Execute a query without interest in a result. 100 | * The connection needs to be established. 101 | * 102 | * @param query SQL query to execute 103 | * @return The number of actually affected rows. 0 on error 104 | */ 105 | u64 execute(const std::string &query); 106 | 107 | /** 108 | * Execute a query (usually, but not limited to INSERT) with interest for the last row id. 109 | * The connection needs to be established. 110 | * 111 | * @param query SQL query to execute 112 | * @return Last row id of the inserted row. 0 on error 113 | */ 114 | u64 insert(const std::string &query); 115 | 116 | /** 117 | * Execute a query with an result (if no result is returned, the result_set will be empty). 118 | * The connection needs to be established. 119 | * Note: The result is only valid as long as the connection is valid. 120 | * 121 | * @param query SQL query to execute 122 | * @return Result of the query as result_set. 123 | */ 124 | result_set_ref query(const std::string &query); 125 | 126 | /** 127 | * Gets the status of the auto_commit setting. 128 | * 129 | * @return True if auto_commit is enabled 130 | */ 131 | bool auto_commit() const; 132 | 133 | /** 134 | * Sets the auto_commit setting. Default MariaDB setting is TRUE. 135 | * The connection needs to be established. 136 | * This setting controls the default behavior of holding all changes back until a COMMIT is 137 | * issued. 138 | * See MariaDB documentation for further information on this setting. 139 | * 140 | * @return True on success 141 | */ 142 | bool set_auto_commit(bool auto_commit); 143 | 144 | /** 145 | * Create a prepared statement. Use "?" to issue bindings in the query which can then be filled 146 | * in. 147 | * For information on how to properly use prepared statements please refer to the MariaDB 148 | * manual. 149 | * The connection needs to be established. 150 | * Note that "?" bindings can only be established at certain locations in a SQL statement. 151 | * A misplaced "?" will result in an error when executing the statement. 152 | * The statement is only valid as long as the connection is valid. 153 | * 154 | * @return Reference to the created statement. 155 | */ 156 | statement_ref create_statement(const std::string &query); 157 | 158 | /** 159 | * Create a transaction. Any change to the database will be held back until you COMMIT the 160 | * transaction. 161 | * A not committed transaction automatically rolls all changes back on destruction. 162 | * Note: the transaction is only valid as long as this connection is valid. 163 | * 164 | * @param level The level of isolation to set while using this transaction. Defaults to 165 | * repeatable read 166 | * @param consistent_snapshot Indicates whether to require a consistent snapshot before entering 167 | * the transaction. 168 | * Note: refer to MariaDB manual for further information 169 | * 170 | * @return Reference to the created transaction. 171 | */ 172 | transaction_ref create_transaction(isolation::level level = isolation::repeatable_read, 173 | bool consistent_snapshot = true); 174 | 175 | /** 176 | * Creates a new connection using the given account 177 | * 178 | * @param account The account used to provide the connection information 179 | * @return Reference to the newly created connection 180 | */ 181 | static connection_ref create(const account_ref &account); 182 | 183 | private: 184 | /** 185 | * Private constructor used to create a connection with the given account 186 | */ 187 | connection(const account_ref &account); 188 | 189 | private: 190 | // internal database connection pointer 191 | MYSQL *m_mysql; 192 | 193 | // state of auto_commit setting 194 | bool m_auto_commit; 195 | // name of current schema 196 | std::string m_schema; 197 | // current charset 198 | std::string m_charset; 199 | // currently used account 200 | account_ref m_account; 201 | }; 202 | } // namespace mariadb 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /include/mariadb++/result_set.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2018. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_RESULT_SET_HPP_ 12 | #define _MARIADB_RESULT_SET_HPP_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define MAKE_GETTER_SIG_STR(nm, rtype, fq) rtype fq get_##nm(const std::string &name) const 23 | #define MAKE_GETTER_SIG_NUM(nm, rtype, fq) rtype fq get_##nm(u32 index) const 24 | #define MAKE_GETTER_SIG_INT(nm, rtype, fq) rtype fq _get_body_##nm(u32 index) const 25 | 26 | #define MAKE_GETTER_DECL(nm, rtype) \ 27 | MAKE_GETTER_SIG_STR(nm, rtype, ); \ 28 | MAKE_GETTER_SIG_NUM(nm, rtype, ); \ 29 | MAKE_GETTER_SIG_INT(nm, rtype, ) 30 | 31 | #define MAKE_GETTER(nm, rtype, vtype) \ 32 | MAKE_GETTER_SIG_STR(nm, rtype, result_set::) { \ 33 | return get_##nm(column_index(name)); \ 34 | } \ 35 | MAKE_GETTER_SIG_NUM(nm, rtype, result_set::) { \ 36 | check_row_fetched(); \ 37 | check_type(index, vtype); \ 38 | \ 39 | if (index >= m_field_count) \ 40 | throw std::out_of_range("Column index out of range"); \ 41 | \ 42 | return _get_body_##nm(index); \ 43 | } \ 44 | MAKE_GETTER_SIG_INT(nm, rtype, result_set::) 45 | 46 | namespace mariadb { 47 | class connection; 48 | class statement; 49 | 50 | /* 51 | * This data is shared between a statement and its result_set, 52 | * therefore it needs to be destroyed only when 53 | * - the statement is freed 54 | * - all of the result_sets are freed 55 | * 56 | * A shared_ptr is used to keep the data alive in the statement and the result_set. 57 | */ 58 | struct statement_data { 59 | explicit statement_data(MYSQL_STMT *stmt) : m_statement(stmt) {} 60 | 61 | ~statement_data(); 62 | 63 | // number of binds in this query 64 | unsigned long m_bind_count = 0; 65 | // pointer to underlying statement 66 | MYSQL_STMT *m_statement; 67 | // pointer to raw binds 68 | MYSQL_BIND *m_raw_binds = nullptr; 69 | // pointer to managed binds 70 | std::vector m_binds; 71 | }; 72 | 73 | typedef std::shared_ptr statement_data_ref; 74 | 75 | /** 76 | * Class used to store query and statement results 77 | */ 78 | class result_set : public last_error { 79 | friend class connection; 80 | friend class statement; 81 | 82 | typedef std::map map_indexes_t; 83 | 84 | public: 85 | /** 86 | * Destructs the result_set and frees all result data 87 | */ 88 | virtual ~result_set(); 89 | 90 | /** 91 | * Get the count of columns contained in this result_set 92 | * 93 | * @return Count of columns 94 | */ 95 | u32 column_count() const; 96 | 97 | /** 98 | * Get the index of a column by column-name (case sensitive) 99 | * 100 | * @param name Name of column to look up 101 | * @return Index of column if found, maximum uint32 if not found 102 | */ 103 | u32 column_index(const std::string &name) const; 104 | 105 | /** 106 | * Gets the type of a column by index 107 | * 108 | * @param index Index of the column to examine 109 | * @return Type enum indicating column type 110 | */ 111 | value::type column_type(u32 index) const; 112 | 113 | /** 114 | * Gets the name of a column by index 115 | * 116 | * @param index Index of the column to get the name for 117 | * @return String representing column name 118 | */ 119 | const std::string column_name(u32 index); 120 | 121 | /** 122 | * Gets the size of the data contained in the column at index 123 | * 124 | * @param index Index of the column to get the size for 125 | * @return Size of the column contents for the currently active row 126 | */ 127 | unsigned long column_size(u32 index) const; 128 | 129 | /** 130 | * Gets the row index of the currently selected row 131 | * 132 | * @return Index of current row 133 | */ 134 | u64 row_index() const; 135 | 136 | /** 137 | * Gets the number of rows in this result 138 | * 139 | * @return Number of rows in result_set 140 | */ 141 | u64 row_count() const; 142 | 143 | /** 144 | * Fetch next row from result_set 145 | * 146 | * @return True if next row exists 147 | */ 148 | bool next(); 149 | 150 | /** 151 | * Set the current row index in result_set (seek to result). 152 | * Also immediately fetches the selected row. 153 | * 154 | * @param index Index of row to select 155 | * @return True if row could be seeked to and fetched. 156 | */ 157 | bool set_row_index(u64 index); 158 | 159 | // declare all getters 160 | MAKE_GETTER_DECL(blob, stream_ref); 161 | MAKE_GETTER_DECL(data, data_ref); 162 | MAKE_GETTER_DECL(date, date_time); 163 | MAKE_GETTER_DECL(date_time, date_time); 164 | MAKE_GETTER_DECL(time, time); 165 | MAKE_GETTER_DECL(decimal, decimal); 166 | MAKE_GETTER_DECL(string, std::string); 167 | MAKE_GETTER_DECL(boolean, bool); 168 | MAKE_GETTER_DECL(unsigned8, u8); 169 | MAKE_GETTER_DECL(signed8, s8); 170 | MAKE_GETTER_DECL(unsigned16, u16); 171 | MAKE_GETTER_DECL(signed16, s16); 172 | MAKE_GETTER_DECL(unsigned32, u32); 173 | MAKE_GETTER_DECL(signed32, s32); 174 | MAKE_GETTER_DECL(unsigned64, u64); 175 | MAKE_GETTER_DECL(signed64, s64); 176 | MAKE_GETTER_DECL(float, f32); 177 | MAKE_GETTER_DECL(double, f64); 178 | MAKE_GETTER_DECL(is_null, bool); 179 | 180 | private: 181 | /** 182 | * Create result_set from connection 183 | */ 184 | explicit result_set(connection *conn); 185 | 186 | /** 187 | * Create result_set from statement 188 | */ 189 | explicit result_set(connection *conn, const statement_data_ref &stmt); 190 | 191 | /** 192 | * Resizes bind buffers for truncated columns after a failed fetch and fetches them again 193 | */ 194 | bool fetch_truncated(); 195 | 196 | /** 197 | * Throws if the result set was created, but no row was ever fetched (using next()) 198 | */ 199 | void check_row_fetched() const; 200 | 201 | /** 202 | * Throws if the actual type of column at index cannot be converted to the requested type 203 | * 204 | * @param index Index of column to check 205 | * @param requested Requested type 206 | */ 207 | void check_type(u32 index, value::type requested) const; 208 | 209 | // pointer to result set 210 | MYSQL_RES *m_result_set; 211 | // pointer to array of fields 212 | MYSQL_FIELD *m_fields; 213 | // pointer to current row 214 | MYSQL_ROW m_row; 215 | // pointer to raw binds 216 | MYSQL_BIND *m_raw_binds; 217 | 218 | // vector of managed binds 219 | std::vector m_binds; 220 | // optional pointer to statement 221 | statement_data_ref m_stmt_data; 222 | // map caching column name by index 223 | map_indexes_t m_indexes; 224 | // array of content lengths for the columns of current row 225 | long unsigned int *m_lengths; 226 | 227 | // count of fields per row 228 | u32 m_field_count; 229 | // indicates if a row was fetched using next() 230 | bool m_was_fetched; 231 | }; 232 | 233 | typedef std::shared_ptr result_set_ref; 234 | } // namespace mariadb 235 | 236 | #endif 237 | -------------------------------------------------------------------------------- /include/mariadb++/account.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2018. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_ACCOUNT_HPP_ 11 | #define _MARIADB_ACCOUNT_HPP_ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mariadb { 19 | class account; 20 | typedef std::shared_ptr account_ref; 21 | 22 | class option_arg { 23 | public: 24 | virtual ~option_arg() = default; 25 | virtual const void *value() = 0; 26 | }; 27 | 28 | #define MAKE_OPTION_ARG(name, type, return_value) \ 29 | class option_arg_##name : public option_arg { \ 30 | public: \ 31 | explicit option_arg_##name(const type &arg) : m_value(arg) {} \ 32 | const void *value() override { \ 33 | return return_value; \ 34 | } \ 35 | \ 36 | protected: \ 37 | type m_value; \ 38 | } 39 | 40 | MAKE_OPTION_ARG(bool, bool, &m_value); 41 | MAKE_OPTION_ARG(int, int, &m_value); 42 | MAKE_OPTION_ARG(string, std::string, m_value.c_str()); 43 | 44 | /** 45 | * Class used to store account and connection information used by mariadb::connection when 46 | * connecting. 47 | * Note that modifying an account after the connection was established is useless. 48 | */ 49 | class account { 50 | public: 51 | typedef std::map map_options_t; 52 | typedef std::map> map_connect_options_t; 53 | 54 | /** 55 | * Destructs the account 56 | */ 57 | virtual ~account() {} 58 | 59 | /** 60 | * Gets the name of the host to connect to 61 | */ 62 | const std::string &host_name() const; 63 | 64 | /** 65 | * Gets the username to log in with 66 | */ 67 | const std::string &user_name() const; 68 | 69 | /** 70 | * Gets the password of the user to log in with 71 | */ 72 | const std::string &password() const; 73 | 74 | /** 75 | * Gets the unix socket path to connect to. 76 | * If this option is set, host and port will be ignored 77 | */ 78 | const std::string &unix_socket() const; 79 | 80 | /** 81 | * Gets the path to the key file 82 | */ 83 | const std::string &ssl_key() const; 84 | 85 | /** 86 | * Gets the path to the certificate file 87 | */ 88 | const std::string &ssl_certificate() const; 89 | 90 | /** 91 | * Gets the path to the certificate authority file 92 | */ 93 | const std::string &ssl_ca() const; 94 | 95 | /** 96 | * Gets the path to the directory containing CA files 97 | */ 98 | const std::string &ssl_ca_path() const; 99 | 100 | /** 101 | * Gets the list of allowed SSL ciphers 102 | */ 103 | const std::string &ssl_cipher() const; 104 | 105 | /** 106 | * Gets the port to connect to 107 | */ 108 | u32 port() const; 109 | 110 | /** 111 | * Gets the name of the database to open on connect 112 | */ 113 | const std::string &schema() const; 114 | 115 | /** 116 | * Sets the name of the database to open on connect 117 | */ 118 | void set_schema(const std::string &schema); 119 | 120 | /** 121 | * Set SSL options. All files should be PEM format 122 | * 123 | * @param key Path to the key file 124 | * @param certificate Path to the certificate file 125 | * @param ca Path to the certificate authority file 126 | * @param ca_path Path to a directory containing CA files 127 | * @param cipher List of allowed SSL ciphers. See MariaDB manual for possible values 128 | */ 129 | void set_ssl(const std::string &key, const std::string &certificate, const std::string &ca, 130 | const std::string &ca_path, const std::string &cipher); 131 | 132 | /** 133 | * Gets the current state of the auto_commit option. This option is turned on by default. 134 | */ 135 | bool auto_commit() const; 136 | 137 | /** 138 | * Sets the state of the auto_commit option. 139 | */ 140 | void set_auto_commit(bool auto_commit); 141 | 142 | /** 143 | * Gets the current state of the store_result option. When set, the connection uses buffered store 144 | * (mysql_store_result) instead of unbuffered store (mysql_use_result). This option is turned on by default. 145 | * 146 | * Note: Unbuffered store has some restrictions and might lead to unexpected behavior. See the documentation 147 | * (https://mariadb.com/kb/en/library/mysql_use_result/) for more information. 148 | */ 149 | bool store_result() const; 150 | 151 | /** 152 | * Sets the state of the store_result option. 153 | */ 154 | void set_store_result(bool store_result); 155 | 156 | /** 157 | * Gets the current value of any named option that was previously set 158 | * 159 | * @return Value of the found option or empty string if not found 160 | */ 161 | const std::string option(const std::string &name) const; 162 | 163 | /** 164 | * Gets a map of all option key/value pairs previously set 165 | */ 166 | const map_options_t &options() const; 167 | 168 | /** 169 | * Sets a named option key/value pair 170 | */ 171 | void set_option(const std::string &name, const std::string &value); 172 | 173 | /** 174 | * Deletes all stored key/value pairs of named options 175 | */ 176 | void clear_options(); 177 | 178 | /** 179 | * Gets a map of all connect option key/value pairs previously set 180 | */ 181 | const map_connect_options_t &connect_options() const; 182 | 183 | /** 184 | * Sets a connect option key/value pair with bool argument 185 | */ 186 | void set_connect_option(mysql_option option, bool arg); 187 | /** 188 | * Sets a connect option key/value pair with int argument 189 | */ 190 | void set_connect_option(mysql_option option, int arg); 191 | /** 192 | * Sets a connect option key/value pair with string argument 193 | */ 194 | void set_connect_option(mysql_option option, const std::string &arg); 195 | 196 | /** 197 | * Deletes all stored key/value pairs of named options 198 | */ 199 | void clear_connect_options(); 200 | 201 | /** 202 | * Create an account 203 | * 204 | * @param host_name Hostname to connect to 205 | * @param user_name Username to log in with 206 | * @param password Password for the user to log in with (may be empty) 207 | * @param schema Database name to select on connect. Can also be set after connecting 208 | * @param port Port of host to connect to (defaults to 3306) 209 | * @param unix_sock Path of unix socket to connect to. If specified, host and port will be 210 | * ignored 211 | */ 212 | static account_ref create(const std::string &host_name, const std::string &user_name, const std::string &password, 213 | const std::string &schema = "", u32 port = 3306, const std::string &unix_socket = ""); 214 | 215 | private: 216 | /** 217 | * Private account constructor 218 | */ 219 | account(const std::string &host_name, const std::string &user_name, const std::string &password, 220 | const std::string &schema, u32 port, const std::string &unix_sock); 221 | 222 | bool m_auto_commit = true; 223 | bool m_store_result = true; 224 | u32 m_port; 225 | std::string m_host_name; 226 | std::string m_user_name; 227 | std::string m_password; 228 | std::string m_schema; 229 | std::string m_unix_socket; 230 | std::string m_ssl_key; 231 | std::string m_ssl_certificate; 232 | std::string m_ssl_ca; 233 | std::string m_ssl_ca_path; 234 | std::string m_ssl_cipher; 235 | map_options_t m_options; 236 | map_connect_options_t m_connect_options; 237 | }; 238 | } // namespace mariadb 239 | 240 | #endif 241 | -------------------------------------------------------------------------------- /include/mariadb++/time.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2020. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #ifndef _MARIADB_TIME_HPP_ 12 | #define _MARIADB_TIME_HPP_ 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace mariadb { 20 | /** 21 | * Class representing SQL time 22 | */ 23 | class time { 24 | public: 25 | /** 26 | * Construct time using given values 27 | * 28 | * @param hour Hours 0-23 29 | * @param minute Minutes 0-59 30 | * @param second Seconds 0-61 (for leap seconds) 31 | * @param millisecond Milliseconds 0-999 32 | */ 33 | time(u8 hour = 0, u8 minute = 0, u8 second = 0, u16 millisecond = 0); 34 | 35 | /** 36 | * Copy constructor 37 | * 38 | * @param t Time to copy from 39 | */ 40 | time(const time &t); 41 | 42 | /** 43 | * Construct time from time.h functions 44 | * 45 | * @param time_struct Timestruct to copy from 46 | */ 47 | time(const tm &time_struct); 48 | 49 | /** 50 | * Construct time from time.h functions 51 | * 52 | * @param time Timestruct to copy from 53 | */ 54 | time(const time_t &time); 55 | 56 | /** 57 | * Construct time from SQL time 58 | * 59 | * @param time SQL time to copy from 60 | */ 61 | time(const MYSQL_TIME &time); 62 | 63 | /** 64 | * Construct time from string 65 | * The format needs to be hh[:mm][:ss][:nnn] 66 | * where less digits are possible and the delimiter may be any non digit 67 | * 68 | * @param t String containing time representation 69 | */ 70 | time(const std::string &t); 71 | 72 | /** 73 | * Allow proper destruction in derived classes 74 | */ 75 | virtual ~time() = default; 76 | 77 | /** 78 | * Compare this instance to given instance 79 | * 80 | * @param t Time to compare to 81 | * @return 1 if this instance is greater, -1 if t is greater, 0 on equality 82 | */ 83 | int compare(const time &t) const; 84 | 85 | /** 86 | * Assigns a value to this instance 87 | */ 88 | time &operator=(const time &t); 89 | 90 | /** 91 | * Checks for equality 92 | */ 93 | bool operator==(const time &t) const; 94 | 95 | /** 96 | * Checks for unequality 97 | */ 98 | bool operator!=(const time &t) const; 99 | 100 | /** 101 | * Checks if this instance is lesser than t 102 | */ 103 | bool operator<(const time &t) const; 104 | 105 | /** 106 | * Checks if this instance is lesser or equal to t 107 | */ 108 | bool operator<=(const time &t) const; 109 | 110 | /** 111 | * Checks if this instance is greater than t 112 | */ 113 | bool operator>(const time &t) const; 114 | 115 | /** 116 | * Checks if this instance is greater or equal to t 117 | */ 118 | bool operator>=(const time &t) const; 119 | 120 | /** 121 | * Get the current hour 0-23 122 | */ 123 | u8 hour() const; 124 | 125 | /** 126 | * Set the current hour 0-23 127 | */ 128 | u8 hour(u8 hour); 129 | 130 | /** 131 | * Get the current minute 0-59 132 | */ 133 | u8 minute() const; 134 | 135 | /** 136 | * Set the current minute 0-59 137 | */ 138 | u8 minute(u8 minute); 139 | 140 | /** 141 | * Get the current second 0-61 (leap second possible) 142 | */ 143 | u8 second() const; 144 | 145 | /** 146 | * Set the current second 0-61 (leap second possible) 147 | */ 148 | u8 second(u8 second); 149 | 150 | /** 151 | * Get the current millisecond 0-999 152 | */ 153 | u16 millisecond() const; 154 | 155 | /** 156 | * Set the current millisecond 0-999 157 | */ 158 | u16 millisecond(u16 millisecond); 159 | 160 | /** 161 | * Set the time from string 162 | * The format needs to be hh[:mm][:ss][.nnn] 163 | * where less digits are possible and the delimiter may be any non digit 164 | * 165 | * Examples: 166 | * h 167 | * h:mm.s?n 168 | * hh,mm!ss-n 169 | * 170 | * @return True on success 171 | */ 172 | virtual bool set(const std::string &t); 173 | 174 | /** 175 | * Set the time from given values 176 | * 177 | * @param hour Hour to set 178 | * @param minute Minute to set 179 | * @param second Second to set 180 | * @param millisecond Milliseconds to set 181 | * @return True on success 182 | */ 183 | bool set(u8 hour, u8 minute, u8 second, u16 millisecond); 184 | 185 | /** 186 | * Adds a certain amount of hours to the current time. Negative values subtract hours 187 | * 188 | * @param hours Number of hours to add 189 | * @return Time containing sum 190 | */ 191 | time add_hours(s32 hours) const; 192 | 193 | /** 194 | * Adds a certain amount of minutes to the current time. Negative values subtract minutes 195 | * 196 | * @param minutes Number of minutes to add 197 | * @return Time containing sum 198 | */ 199 | time add_minutes(s32 minutes) const; 200 | 201 | /** 202 | * Adds a certain amount of seconds to the current time. Negative values subtract seconds 203 | * 204 | * @param minutes Number of seconds to add 205 | * @return Time containing sum 206 | */ 207 | time add_seconds(s32 seconds) const; 208 | 209 | /** 210 | * Adds a certain amount of milliseconds to the current time. Negative values subtract 211 | * milliseconds 212 | * 213 | * @param minutes Number of milliseconds to add 214 | * @return Time containing sum 215 | */ 216 | time add_milliseconds(s32 milliseconds) const; 217 | 218 | /** 219 | * Subtracts the given timespan from the current time 220 | * 221 | * @param dur A duration to subtract 222 | * @return Time containing result 223 | */ 224 | time subtract(const time_span &dur) const; 225 | 226 | /** 227 | * Adds the given timespan to the current time 228 | * 229 | * @param dur A duration to add 230 | * @return Time containing sum 231 | */ 232 | time add(const time_span &dur) const; 233 | 234 | /** 235 | * Calculates the timespan between the current time instance and given instance t 236 | * 237 | * @param t Time to calculate the duration between 238 | * @return Timespan containing the duration 239 | */ 240 | time_span time_between(const time &t) const; 241 | 242 | /** 243 | * Converts the time to time_t (time.h representation) 244 | * 245 | * @return Time as time.h time_t 246 | */ 247 | time_t mktime() const; 248 | 249 | /** 250 | * Calculates the time difference using ::difftime. 251 | * Calculates (this - t) 252 | * 253 | * @return Difference in seconds as double 254 | */ 255 | double diff_time(const time &t) const; 256 | 257 | /** 258 | * Indicates whether this time is considered valid 259 | * 260 | * @return True if time is valid 261 | */ 262 | virtual bool is_valid() const; 263 | 264 | /** 265 | * Indicates whether a given time is valid in terms of limits. Accounts for leap seconds 266 | * 267 | * @param hour Hour to check 268 | * @param minute Minute to check 269 | * @param second Second to check 270 | * @param millisecond Milliseconds to check 271 | * @return True if valid 272 | */ 273 | static bool valid_time(u8 hour, u8 minute, u8 second, u16 millisecond); 274 | 275 | /** 276 | * Converts the time to MySQL time representation. Note that MySQL time does not support 277 | * milliseconds 278 | * 279 | * @return Time as MYSQL_TIME with no milliseconds 280 | */ 281 | MYSQL_TIME mysql_time() const; 282 | 283 | /** 284 | * Converts the time to a string with the format hh:mm:ss[.nnn] 285 | * 286 | * @param with_millisecond Indicates if milliseconds should be printed or not. 287 | * 288 | * @return String representing time with optional milliseconds 289 | */ 290 | const std::string str_time(bool with_millisecond = false) const; 291 | 292 | /** 293 | * Uses time.h to determine the current time in the local timezone. 294 | * 295 | * @return Time representing now 296 | */ 297 | static time now(); 298 | 299 | /** 300 | * Uses time.h to determine the current time in UTC timezone. 301 | * 302 | * @return Time representing now in UTC 303 | */ 304 | static time now_utc(); 305 | 306 | protected: 307 | u8 m_hour; 308 | u8 m_minute; 309 | u8 m_second; 310 | u16 m_millisecond; 311 | }; 312 | 313 | std::ostream &operator<<(std::ostream &os, const time &t); 314 | } // namespace mariadb 315 | 316 | #endif 317 | -------------------------------------------------------------------------------- /test/ParameterizedQueryTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2020. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include "ParameterizedQueryTest.h" 10 | 11 | TEST_P(ParameterizedQueryTest, bindNormal) { 12 | mariadb::statement_ref selectQuery = 13 | m_con->create_statement("SELECT * FROM " + m_table_name + " WHERE 1=?;"); 14 | selectQuery->set_unsigned32(0, 1); 15 | 16 | mariadb::result_set_ref queryResult = selectQuery->query(); 17 | 18 | ASSERT_TRUE(!!queryResult); 19 | ASSERT_TRUE(queryResult->next()); 20 | } 21 | 22 | TEST_P(ParameterizedQueryTest, emptyBind) { 23 | mariadb::statement_ref emptyQuery = 24 | m_con->create_statement("SELECT * FROM " + m_table_name + " WHERE 1=?;"); 25 | 26 | mariadb::result_set_ref queryResult = emptyQuery->query(); 27 | ASSERT_TRUE(!!emptyQuery); 28 | ASSERT_TRUE(!!queryResult); 29 | ASSERT_FALSE(queryResult->next()); 30 | } 31 | 32 | TEST_P(ParameterizedQueryTest, emptyBindQuery) { 33 | // fixme: Invalid query object instead of exception maybe? 34 | EXPECT_ANY_THROW(mariadb::statement_ref emptyQuery = m_con->create_statement("")); 35 | } 36 | 37 | TEST_P(ParameterizedQueryTest, bindAfterQuery) { 38 | mariadb::statement_ref errorQuery = 39 | m_con->create_statement("SELECT * FROM " + m_table_name + " WHERE id = ?;"); 40 | mariadb::result_set_ref queryResult = errorQuery->query(); 41 | 42 | EXPECT_NO_THROW(errorQuery->set_unsigned32(0, 1)); 43 | } 44 | 45 | TEST_P(ParameterizedQueryTest, bindAnyDataType) { 46 | mariadb::statement_ref errorQuery; 47 | mariadb::statement_ref testQuery; 48 | mariadb::result_set_ref queryResult; 49 | 50 | m_con->create_statement("SET sql_mode = 'STRICT_TRANS_TABLES';")->execute(); 51 | 52 | #define ParamTest_TEST(call, call2, name, value) \ 53 | errorQuery = m_con->create_statement("UPDATE " + m_table_name + " SET " + name + "= ?;"); \ 54 | call; \ 55 | errorQuery->execute(); \ 56 | testQuery = m_con->create_statement("SELECT " + std::string(name) + " FROM " + m_table_name + \ 57 | " WHERE id = 1;"); \ 58 | queryResult = testQuery->query(); \ 59 | ASSERT_TRUE(queryResult->next()); \ 60 | ASSERT_EQ(call2, value); \ 61 | queryResult.reset(); 62 | 63 | decimal d = decimal("0.02"); 64 | mariadb::date_time t(mariadb::date_time::now()), z(0, 0, 0); 65 | mariadb::time ti(mariadb::time::now()); 66 | 67 | ParamTest_TEST(errorQuery->set_unsigned32(0, (unsigned int)299), queryResult->get_unsigned32(0), 68 | "preis", 299); 69 | ParamTest_TEST(errorQuery->set_string(0, "TESTSTRING"), queryResult->get_string(0), "str", 70 | "TESTSTRING"); 71 | ParamTest_TEST(errorQuery->set_null(0), queryResult->get_string(0), "str", ""); 72 | ParamTest_TEST(errorQuery->set_null(0), queryResult->get_is_null(0), "str", true); 73 | EXPECT_ANY_THROW(ParamTest_TEST(errorQuery->set_null(0), queryResult->get_string(0), "nnstr", "")); 74 | ParamTest_TEST(errorQuery->set_string(0, ""), queryResult->get_string(0), "nnstr", ""); 75 | ParamTest_TEST(errorQuery->set_boolean(0, true), queryResult->get_boolean(0), "b", true); 76 | ParamTest_TEST(errorQuery->set_double(0, 0.03), queryResult->get_double(0), "dd", 0.03); 77 | ParamTest_TEST(errorQuery->set_double(0, -0.03), queryResult->get_double(0), "dd", -0.03); 78 | ParamTest_TEST(errorQuery->set_signed32(0, 100), queryResult->get_signed32(0), "preis", 100); 79 | ParamTest_TEST(errorQuery->set_date_time(0, t), queryResult->get_date_time(0), "tim", t); 80 | ParamTest_TEST(errorQuery->set_date_time(0, z), queryResult->get_date_time(0), "tim", z); 81 | ParamTest_TEST(errorQuery->set_time(0, ti), queryResult->get_time(0), "tiim", ti); 82 | ParamTest_TEST(errorQuery->set_decimal(0, d), queryResult->get_decimal(0).str(), "d", "0.02"); 83 | ParamTest_TEST(errorQuery->set_null(0), queryResult->get_is_null(0), "nul", true); 84 | } 85 | 86 | TEST_P(ParameterizedQueryTest, bindExecute) { 87 | mariadb::statement_ref crashQuery = 88 | m_con->create_statement("INSERT INTO " + m_table_name + " (id, preis) VALUES (2, ?);"); 89 | crashQuery->set_unsigned32(0, 1); 90 | 91 | crashQuery->query(); 92 | } 93 | 94 | TEST_P(ParameterizedQueryTest, bindDataBlob) { 95 | mariadb::statement_ref errorQuery = 96 | m_con->create_statement("SELECT * FROM " + m_table_name + " WHERE id = ?;"); 97 | 98 | const char* c = 99 | "0123456789012345678901234567890123456789" 100 | "0123456789012345678901234567890123456789" 101 | "0123456789012345678901234567890123456789" 102 | "0123456789012345678901234567890123456789" 103 | "0123456789012345678901234567890123456789" 104 | "0123456789012345678901234567890123456789" 105 | "0123456789012345678901234567890123456789" 106 | "0123456789012345678901234567890123456789" 107 | "0123456789012345678901234567890123456789" 108 | "0123456789012345678901234567890123456789"; 109 | 110 | errorQuery->set_data(0, mariadb::data_ref(new mariadb::data(c, 400))); 111 | 112 | mariadb::result_set_ref queryResult = errorQuery->query(); 113 | 114 | ASSERT_TRUE(!!queryResult); 115 | ASSERT_FALSE(queryResult->next()); 116 | } 117 | 118 | TEST_P(ParameterizedQueryTest, bindDataBlobNullPtr) { 119 | mariadb::statement_ref errorQuery = 120 | m_con->create_statement("SELECT * FROM " + m_table_name + " WHERE id = ?;"); 121 | errorQuery->set_data(0, nullptr); 122 | } 123 | 124 | TEST_P(ParameterizedQueryTest, bindWithoutParameters) { 125 | mariadb::statement_ref errorQuery = m_con->create_statement("SELECT 1;"); 126 | 127 | EXPECT_ANY_THROW(errorQuery->set_unsigned32(1, 100)); 128 | } 129 | 130 | TEST_P(ParameterizedQueryTest, bindReuseSimple) { 131 | mariadb::statement_ref insertQuery = m_con->create_statement("INSERT INTO " + m_table_name + "(preis) VALUES(?);"); 132 | 133 | // bind 1 134 | insertQuery->set_unsigned32(0, 177); 135 | u64 row1 = insertQuery->insert(); 136 | 137 | // bind 2 138 | insertQuery->set_unsigned32(0, 1337); 139 | u64 row2 = insertQuery->insert(); 140 | 141 | // bind 3 - reuse 142 | u64 row3 = insertQuery->insert(); 143 | 144 | mariadb::statement_ref selectQuery = m_con->create_statement("SELECT preis FROM " + m_table_name + " WHERE id = ?"); 145 | 146 | // test 1 147 | selectQuery->set_unsigned64(0, row1); 148 | mariadb::result_set_ref result = selectQuery->query(); 149 | ASSERT_TRUE(!!result); 150 | ASSERT_TRUE(result->next()); 151 | EXPECT_EQ(177u, result->get_unsigned32(0)); 152 | 153 | // test2 154 | selectQuery->set_unsigned64(0, row2); 155 | mariadb::result_set_ref result2 = selectQuery->query(); 156 | ASSERT_TRUE(!!result2); 157 | ASSERT_TRUE(result2->next()); 158 | EXPECT_EQ(1337u, result2->get_unsigned32(0)); 159 | 160 | // test3 161 | selectQuery->set_unsigned64(0, row3); 162 | mariadb::result_set_ref result3 = selectQuery->query(); 163 | ASSERT_TRUE(!!result3); 164 | ASSERT_TRUE(result3->next()); 165 | EXPECT_EQ(1337u, result3->get_unsigned32(0)); 166 | } 167 | 168 | TEST_P(ParameterizedQueryTest, bindReuseString) { 169 | mariadb::statement_ref insertQuery = m_con->create_statement("INSERT INTO " + m_table_name + "(str) VALUES(?);"); 170 | 171 | // bind 1 172 | insertQuery->set_string(0, "asdf"); 173 | u64 row1 = insertQuery->insert(); 174 | 175 | // bind 2 176 | insertQuery->set_string(0, "qwertz"); 177 | u64 row2 = insertQuery->insert(); 178 | 179 | // bind 3 180 | insertQuery->set_string(0, ""); 181 | u64 row3 = insertQuery->insert(); 182 | 183 | mariadb::statement_ref selectQuery = m_con->create_statement("SELECT str FROM " + m_table_name + " WHERE id = ?;"); 184 | 185 | // test 1 186 | selectQuery->set_unsigned64(0, row1); 187 | mariadb::result_set_ref result = selectQuery->query(); 188 | ASSERT_TRUE(!!result); 189 | ASSERT_TRUE(result->next()); 190 | EXPECT_EQ("asdf", result->get_string(0)); 191 | 192 | // test2 193 | selectQuery->set_unsigned64(0, row2); 194 | mariadb::result_set_ref result2 = selectQuery->query(); 195 | ASSERT_TRUE(!!result2); 196 | ASSERT_TRUE(result2->next()); 197 | EXPECT_EQ("qwertz", result2->get_string(0)); 198 | 199 | // test3 200 | selectQuery->set_unsigned64(0, row3); 201 | mariadb::result_set_ref result3 = selectQuery->query(); 202 | ASSERT_TRUE(!!result3); 203 | ASSERT_TRUE(result3->next()); 204 | EXPECT_EQ("", result3->get_string(0)); 205 | } 206 | 207 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, ParameterizedQueryTest, ::testing::Values(true, false)); 208 | -------------------------------------------------------------------------------- /test/TimeTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright The ViaDuck Project 2016 - 2020. 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE or copy at 7 | // http://www.boost.org/LICENSE_1_0.txt) 8 | 9 | #include 10 | #include 11 | 12 | #include "TimeTest.h" 13 | 14 | #define EXPECT_INVALID(t, ...) do { \ 15 | t d(__VA_ARGS__); \ 16 | EXPECT_FALSE(d.is_valid()); \ 17 | } while(false) 18 | 19 | TEST_P(TimeTest, testConstructors) { 20 | // valid times 21 | mariadb::time a; 22 | mariadb::time b(13, 37, 42, 000); 23 | mariadb::time c(b); 24 | 25 | // invalid times 26 | EXPECT_INVALID(mariadb::time, static_cast(24)); 27 | EXPECT_INVALID(mariadb::time, static_cast(24)); 28 | EXPECT_INVALID(mariadb::time, 23u, 60u); 29 | EXPECT_INVALID(mariadb::time, 23u, 59u, 62u); 30 | EXPECT_INVALID(mariadb::time, 23u, 59u, 59u, 1000u); 31 | 32 | // test time.h constructors 33 | tm sometime; 34 | sometime.tm_sec = 42; 35 | sometime.tm_min = 37; 36 | sometime.tm_hour = 13; 37 | 38 | tm invtime; 39 | invtime.tm_hour = 41; 40 | 41 | time_t someothertime = ::time(nullptr); 42 | 43 | mariadb::time e(sometime); 44 | EXPECT_INVALID(mariadb::time, invtime); 45 | 46 | mariadb::time f(someothertime); 47 | 48 | // test string conversion 49 | mariadb::time g("00:00:00.000"); 50 | mariadb::time h("23:59:59.999"); 51 | mariadb::time i("13:37:42.000"); 52 | mariadb::time j("18-59.59?0"); 53 | mariadb::time k("8:9:5-01"); 54 | mariadb::time l("8:59:59.0"); 55 | EXPECT_ANY_THROW(mariadb::time d("24")); 56 | EXPECT_ANY_THROW(mariadb::time d("23:60")); 57 | EXPECT_ANY_THROW(mariadb::time d("23:59:62")); 58 | EXPECT_ANY_THROW(mariadb::time d("23:59:a59.1000")); 59 | EXPECT_ANY_THROW(mariadb::time m("859")); 60 | EXPECT_INVALID(mariadb::time, "23:59:59.1000"); 61 | 62 | EXPECT_NE(a, b); 63 | EXPECT_EQ(b, c); 64 | EXPECT_EQ(c, e); 65 | EXPECT_NE(e, f); 66 | EXPECT_EQ(a, g); 67 | EXPECT_NE(g, h); 68 | EXPECT_EQ(e, i); 69 | 70 | EXPECT_EQ("00:00:00.000", g.str_time(true)); 71 | EXPECT_EQ("23:59:59.999", h.str_time(true)); 72 | EXPECT_EQ("13:37:42.000", i.str_time(true)); 73 | EXPECT_EQ("18:59:59.000", j.str_time(true)); 74 | EXPECT_EQ("08:09:05.001", k.str_time(true)); 75 | EXPECT_EQ("08:59:59", l.str_time()); 76 | } 77 | 78 | TEST_P(TimeTest, testArithm) { 79 | // test ms 80 | mariadb::time a(13, 37); 81 | mariadb::time b(13, 36, 59, 999); 82 | mariadb::time c = b.add_milliseconds(1); 83 | 84 | ASSERT_EQ(a, c); 85 | ASSERT_NE(a, b); 86 | 87 | // test day overflow with ms 88 | mariadb::time d(23, 59, 59, 999); 89 | mariadb::time e = d.add_milliseconds(1); 90 | ASSERT_EQ(mariadb::time(), e); 91 | 92 | // test add second 93 | mariadb::time f(13, 36, 59); 94 | mariadb::time g = f.add_seconds(1); 95 | ASSERT_EQ(a, g); 96 | 97 | // test day overflow with second 98 | mariadb::time h = d.add_seconds(1); 99 | ASSERT_EQ(mariadb::time(0, 0, 0, 999), h); 100 | 101 | // test add minute 102 | mariadb::time i(13, 36); 103 | mariadb::time j = i.add_minutes(1); 104 | ASSERT_EQ(a, j); 105 | 106 | // test day overflow with minute 107 | mariadb::time k = d.add_minutes(1); 108 | ASSERT_EQ(mariadb::time(0, 0, 59, 999), k); 109 | 110 | // test add hour 111 | mariadb::time l(12, 37); 112 | mariadb::time m = l.add_hours(1); 113 | ASSERT_EQ(a, m); 114 | 115 | // test day overflow with hour 116 | mariadb::time n = d.add_hours(1); 117 | ASSERT_EQ(mariadb::time(0, 59, 59, 999), n); 118 | 119 | // test subtraction 120 | ASSERT_EQ(d, n.add_hours(-1)); 121 | ASSERT_EQ(d, k.add_minutes(-1)); 122 | ASSERT_EQ(d, h.add_seconds(-1)); 123 | ASSERT_EQ(d, e.add_milliseconds(-1)); 124 | } 125 | 126 | TEST_P(TimeTest, testDiff) { 127 | mariadb::time a(13, 37, 42, 007); 128 | mariadb::time b(12, 37, 42, 007); 129 | mariadb::time c(23, 37, 42, 007); 130 | 131 | mariadb::time_span s12 = a.time_between(b); 132 | mariadb::time_span s21 = b.time_between(a); 133 | 134 | mariadb::time_span s23 = b.time_between(c); 135 | mariadb::time_span s32 = c.time_between(b); 136 | 137 | mariadb::time_span s31 = c.time_between(a); 138 | mariadb::time_span s13 = a.time_between(c); 139 | 140 | ASSERT_EQ(1, s12.hours()); 141 | ASSERT_EQ(s12.hours(), s21.hours()); 142 | ASSERT_NE(s12.negative(), s21.negative()); 143 | 144 | ASSERT_EQ(11, s23.hours()); 145 | ASSERT_EQ(s23.hours(), s32.hours()); 146 | ASSERT_NE(s23.negative(), s32.negative()); 147 | 148 | ASSERT_EQ(10, s31.hours()); 149 | ASSERT_EQ(s31.hours(), s13.hours()); 150 | ASSERT_NE(s31.negative(), s13.negative()); 151 | } 152 | 153 | TEST_P(TimeTest, testNow) { 154 | mariadb::time n1 = time::now(); 155 | mariadb::time n2 = time::now(); 156 | mariadb::time n3 = time::now(); 157 | 158 | mariadb::time un1 = time::now_utc(); 159 | mariadb::time un2 = time::now_utc(); 160 | mariadb::time un3 = time::now_utc(); 161 | 162 | // make sure n4 and un4 are actually different 163 | std::this_thread::sleep_for(std::chrono::seconds(1)); 164 | mariadb::time n4 = time::now(); 165 | mariadb::time un4 = time::now_utc(); 166 | 167 | ASSERT_GE(n4, n3); 168 | ASSERT_GE(n3, n2); 169 | ASSERT_GE(n2, n1); 170 | 171 | ASSERT_GE(un4, un3); 172 | ASSERT_GE(un3, un2); 173 | ASSERT_GE(un2, un1); 174 | 175 | ASSERT_NE(n1, n4); 176 | ASSERT_NE(un1, un4); 177 | } 178 | 179 | TEST_P(TimeTest, testSpan) { 180 | time_span a; 181 | time_span b(1, 3, 37, 42, 007); 182 | time_span c(0, 3, 37, 42, 007, true); 183 | EXPECT_ANY_THROW(time_span d(0, 33, 37, 42, 007, true)); 184 | EXPECT_ANY_THROW(time_span d(0, 33, 37, 42, 007, true)); 185 | EXPECT_ANY_THROW(time_span d(0, 3, 66, 42, 007)); 186 | EXPECT_ANY_THROW(time_span d(0, 3, 37, 100, 007)); 187 | EXPECT_ANY_THROW(time_span d(0, 3, 37, 42, 1001)); 188 | time_span e(b); 189 | 190 | EXPECT_EQ(b, e); 191 | EXPECT_TRUE(a.zero()); 192 | EXPECT_FALSE(a.negative()); 193 | 194 | EXPECT_NE(a, b); 195 | EXPECT_NE(b, c); 196 | 197 | // equal days 198 | c.days(1); 199 | EXPECT_NE(b, c); 200 | 201 | // all equal 202 | c.negative(false); 203 | EXPECT_EQ(b, c); 204 | 205 | // original das 206 | c.days(0); 207 | ASSERT_EQ(b.total_hours(), c.total_hours() + 24); 208 | ASSERT_EQ(b.total_minutes(), c.total_minutes() + 24 * 60); 209 | ASSERT_EQ(b.total_seconds(), c.total_seconds() + 24 * 60 * 60); 210 | ASSERT_EQ(b.total_milliseconds(), c.total_milliseconds() + 24 * 60 * 60 * 1000); 211 | } 212 | 213 | void readdTest(date_time smaller, date_time bigger) { 214 | ASSERT_EQ(bigger, smaller.add(bigger.time_between(smaller))); 215 | } 216 | 217 | TEST_P(TimeTest, testDateTime) { 218 | date_time a; 219 | date_time b(2012, 12, 21, 13, 37, 42, 007); 220 | date_time before(2012, 12, 19, 13, 37, 42, 007); 221 | date_time c(2008, 2, 29); 222 | EXPECT_INVALID(date_time, 2009, 2, 29); 223 | EXPECT_INVALID(date_time, 2009, 1, 0); 224 | EXPECT_INVALID(date_time, 2009, 4, 31); 225 | date_time e(c); 226 | 227 | date_time f(1900, 1, 1, 13, 37, 42); 228 | mariadb::time ta(13, 37, 42); 229 | date_time g(ta); 230 | 231 | EXPECT_EQ(b, before.add_days(2)); 232 | EXPECT_EQ(e, c); 233 | EXPECT_NE(a, b); 234 | EXPECT_EQ(f, g); 235 | 236 | // test time.h constructors 237 | tm sometime; 238 | sometime.tm_year = 112; 239 | sometime.tm_mon = 11; 240 | sometime.tm_mday = 21; 241 | sometime.tm_hour = 13; 242 | sometime.tm_min = 37; 243 | sometime.tm_sec = 42; 244 | 245 | tm invtime; 246 | invtime.tm_year = 110; 247 | invtime.tm_mon = 12; 248 | invtime.tm_mday = 1; 249 | 250 | time_t someothertime = ::time(nullptr); 251 | 252 | date_time h(sometime); 253 | EXPECT_INVALID(date_time, invtime); 254 | 255 | date_time i(someothertime); 256 | 257 | EXPECT_EQ(h.add_milliseconds(007), b); 258 | EXPECT_NE(a, i); 259 | 260 | date_time aa("2012-12-21 13:37:42.007"); 261 | date_time ba("2008-02-29 13:37:42.007"); 262 | EXPECT_ANY_THROW(date_time d("2007-02-29 a13:37:42.007")); 263 | EXPECT_ANY_THROW(date_time d("2007-a02-29 13:37:42.007")); 264 | EXPECT_ANY_THROW(date_time d("2007-02-29 13:37:a42.007")); 265 | EXPECT_ANY_THROW(date_time d("2007-02-29 113:37:42.007")); 266 | EXPECT_ANY_THROW(date_time d("2007-02-29 13:37:42.a007")); 267 | EXPECT_INVALID(date_time, "2007-02-29 13:37:42.007"); 268 | EXPECT_INVALID(date_time, "2007-02-29 13:37:42.007"); 269 | 270 | EXPECT_EQ(b, aa); 271 | 272 | u16 doy = aa.day_of_year(); 273 | date_time da = date_time::reverse_day_of_year(aa.year(), doy); 274 | EXPECT_EQ(da, aa.date()); 275 | 276 | readdTest(before, b); 277 | 278 | // leap year 279 | readdTest(date_time(2000, 1, 1), date_time(2000, 2, 1)); 280 | 281 | readdTest(date_time(2000, 9, 1), date_time(2010, 2, 1)); 282 | readdTest(date_time(2000, 2, 1), date_time(2010, 9, 1)); 283 | 284 | // non-leap 285 | readdTest(date_time(2001, 1, 1), date_time(2001, 2, 1)); 286 | 287 | readdTest(date_time(2001, 9, 1), date_time(2010, 2, 1)); 288 | readdTest(date_time(2001, 2, 1), date_time(2010, 9, 1)); 289 | 290 | EXPECT_EQ("2012-12-21 13:37:42.007", aa.str(true)); 291 | EXPECT_EQ("2012-12-21 13:37:42", aa.str(false)); 292 | EXPECT_EQ("2012-12-21", aa.str_date()); 293 | 294 | EXPECT_EQ("2008-02-29 13:37:42.007", ba.str(true)); 295 | EXPECT_EQ("2008-02-29 13:37:42", ba.str(false)); 296 | EXPECT_EQ("2008-02-29", ba.str_date()); 297 | } 298 | 299 | INSTANTIATE_TEST_SUITE_P(BufUnbuf, TimeTest, ::testing::Values(true, false)); 300 | -------------------------------------------------------------------------------- /src/time.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2021. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "private.hpp" 20 | 21 | #define MS_PER_SEC 1000 22 | #define MS_PER_MIN (MS_PER_SEC * 60) 23 | #define MS_PER_HOUR (MS_PER_MIN * 60) 24 | #define MS_PER_DAY (MS_PER_HOUR * 24) 25 | 26 | mariadb::time::time(u8 hour, u8 minute, u8 second, u16 millisecond) { 27 | set(hour, minute, second, millisecond); 28 | } 29 | 30 | mariadb::time::time(const time &t) { 31 | set(t.hour(), t.minute(), t.second(), t.millisecond()); 32 | } 33 | 34 | mariadb::time::time(const tm &t) { 35 | set(t.tm_hour, t.tm_min, t.tm_sec, 0); 36 | } 37 | 38 | mariadb::time::time(const time_t &t) { 39 | tm ts; 40 | localtime_safe(&ts, &t); 41 | 42 | set(ts.tm_hour, ts.tm_min, ts.tm_sec, 0); 43 | } 44 | 45 | mariadb::time::time(const MYSQL_TIME &t) { 46 | set(t.hour, t.minute, t.second, t.second_part / 1000); 47 | } 48 | 49 | mariadb::time::time(const std::string &t) { 50 | set(t); 51 | } 52 | 53 | int mariadb::time::compare(const time &t) const { 54 | if (hour() < t.hour()) 55 | return -1; 56 | 57 | if (hour() > t.hour()) 58 | return 1; 59 | 60 | if (minute() < t.minute()) 61 | return -1; 62 | 63 | if (minute() > t.minute()) 64 | return 1; 65 | 66 | if (second() < t.second()) 67 | return -1; 68 | 69 | if (second() > t.second()) 70 | return 1; 71 | 72 | if (millisecond() < t.millisecond()) 73 | return -1; 74 | 75 | return millisecond() == t.millisecond() ? 0 : 1; 76 | } 77 | 78 | mariadb::time &mariadb::time::operator=(const time &t) { 79 | set(t.hour(), t.minute(), t.second(), t.millisecond()); 80 | return *this; 81 | } 82 | 83 | bool mariadb::time::operator==(const time &t) const { 84 | return compare(t) == 0; 85 | } 86 | 87 | bool mariadb::time::operator!=(const time &t) const { 88 | return compare(t) != 0; 89 | } 90 | 91 | bool mariadb::time::operator<(const time &t) const { 92 | return compare(t) < 0; 93 | } 94 | 95 | bool mariadb::time::operator<=(const time &t) const { 96 | return compare(t) <= 0; 97 | } 98 | 99 | bool mariadb::time::operator>(const time &t) const { 100 | return compare(t) > 0; 101 | } 102 | 103 | bool mariadb::time::operator>=(const time &t) const { 104 | return compare(t) >= 0; 105 | } 106 | 107 | bool mariadb::time::set(u8 hour, u8 minute, u8 second, u16 millisecond) { 108 | m_hour = hour; 109 | m_minute = minute; 110 | m_second = second; 111 | m_millisecond = millisecond; 112 | return true; 113 | } 114 | 115 | mariadb::u8 mariadb::time::hour() const { 116 | return m_hour; 117 | } 118 | 119 | mariadb::u8 mariadb::time::hour(u8 hour) { 120 | MARIADB_THROW_IF(hour > 23, exception::time, hour, minute(), second(), millisecond()); 121 | 122 | m_hour = hour; 123 | 124 | return m_hour; 125 | } 126 | 127 | mariadb::u8 mariadb::time::minute() const { 128 | return m_minute; 129 | } 130 | 131 | mariadb::u8 mariadb::time::minute(u8 minute) { 132 | MARIADB_THROW_IF(minute > 59, exception::time, hour(), minute, second(), millisecond()); 133 | 134 | m_minute = minute; 135 | 136 | return m_minute; 137 | } 138 | 139 | mariadb::u8 mariadb::time::second() const { 140 | return m_second; 141 | } 142 | 143 | mariadb::u8 mariadb::time::second(u8 second) { 144 | // Allow seconds to go up to 61 to allow MySQL < 5.0.74 (2008) leap seconds 145 | // See https://docs.oracle.com/cd/E17952_01/mysql-5.0-en/time-zone-leap-seconds.html 146 | MARIADB_THROW_IF(second > 61, exception::time, hour(), minute(), second, millisecond()); 147 | 148 | m_second = second; 149 | 150 | return m_second; 151 | } 152 | 153 | mariadb::u16 mariadb::time::millisecond() const { 154 | return m_millisecond; 155 | } 156 | 157 | mariadb::u16 mariadb::time::millisecond(u16 millisecond) { 158 | MARIADB_THROW_IF(millisecond > 999, exception::time, hour(), minute(), second(), millisecond); 159 | 160 | m_millisecond = millisecond; 161 | 162 | return m_millisecond; 163 | } 164 | 165 | mariadb::time mariadb::time::add_hours(s32 hours) const { 166 | mariadb::time tmp = *this; 167 | 168 | if (hours == 0) 169 | return tmp; 170 | 171 | // hour overflow does not matter, as we dont care about days 172 | hours = (hours + hour() + 24) % 24; 173 | tmp.hour(hours); 174 | return tmp; 175 | } 176 | 177 | mariadb::time mariadb::time::add_minutes(s32 minutes) const { 178 | mariadb::time tmp = *this; 179 | 180 | if (minutes == 0) 181 | return tmp; 182 | 183 | s32 hours = minutes / 60; 184 | minutes = (minutes % 60) + minute(); 185 | 186 | if (minutes >= 60) 187 | ++hours; 188 | else if (minutes < 0) 189 | --hours; 190 | 191 | if (hours != 0) 192 | tmp = tmp.add_hours(hours); 193 | 194 | tmp.minute((minutes + 60) % 60); 195 | return tmp; 196 | } 197 | 198 | mariadb::time mariadb::time::add_seconds(s32 seconds) const { 199 | mariadb::time tmp = *this; 200 | 201 | if (seconds == 0) 202 | return tmp; 203 | 204 | s32 minutes = seconds / 60; 205 | seconds = (seconds % 60) + second(); 206 | 207 | if (seconds >= 60) 208 | ++minutes; 209 | else if (seconds < 0) 210 | --minutes; 211 | 212 | if (minutes != 0) 213 | tmp = tmp.add_minutes(minutes); 214 | 215 | tmp.second((seconds + 60) % 60); 216 | return tmp; 217 | } 218 | 219 | mariadb::time mariadb::time::add_milliseconds(s32 milliseconds) const { 220 | mariadb::time tmp = *this; 221 | 222 | if (milliseconds == 0) 223 | return tmp; 224 | 225 | s32 seconds = milliseconds / 1000; 226 | milliseconds = (milliseconds % 1000) + millisecond(); 227 | 228 | if (milliseconds > 999) 229 | ++seconds; 230 | else if (milliseconds < 0) 231 | --seconds; 232 | 233 | if (seconds != 0) 234 | tmp = tmp.add_seconds(seconds); 235 | 236 | tmp.millisecond((milliseconds + 1000) % 1000); 237 | return tmp; 238 | } 239 | 240 | mariadb::time mariadb::time::subtract(const time_span &dur) const { 241 | // copy and negate timespan 242 | time_span tmp = dur; 243 | tmp.negative(!dur.negative()); 244 | 245 | // add negated 246 | return add(tmp); 247 | } 248 | 249 | mariadb::time mariadb::time::add(const time_span &dur) const { 250 | // negate if needed 251 | s32 negative = dur.negative() ? -1 : 1; 252 | time tmp = *this; 253 | 254 | tmp.add_hours(negative * dur.hours()); 255 | tmp.add_minutes(negative * dur.minutes()); 256 | tmp.add_seconds(negative * dur.seconds()); 257 | tmp.add_milliseconds(negative * dur.milliseconds()); 258 | return tmp; 259 | } 260 | 261 | mariadb::time_span mariadb::time::time_between(const time &t) const { 262 | // equal 263 | if (t == *this) 264 | return time_span(0, 0, 0, 0, 0); 265 | 266 | // recursive call with negation 267 | if (t > *this) { 268 | time_span dur = t.time_between(*this); 269 | dur.negative(true); 270 | return dur; 271 | } 272 | 273 | // calculate the ms representation of both 274 | s64 ms = (hour() * MS_PER_HOUR) + (minute() * MS_PER_MIN) + (second() * MS_PER_SEC) + millisecond(); 275 | s64 t_ms = (t.hour() * MS_PER_HOUR) + (t.minute() * MS_PER_MIN) + (t.second() * MS_PER_SEC) + t.millisecond(); 276 | s64 total_ms = 0; 277 | 278 | // subtract the lesser from greater // TODO: is this case ever reached since we have recusive 279 | // call on t > this? 280 | if (t_ms > ms) 281 | total_ms = MS_PER_DAY - (t_ms - ms); 282 | else 283 | total_ms = ms - t_ms; 284 | 285 | u32 hours = static_cast(total_ms / MS_PER_HOUR); 286 | total_ms = total_ms % MS_PER_HOUR; 287 | 288 | u32 minutes = static_cast(total_ms / MS_PER_MIN); 289 | total_ms = total_ms % MS_PER_MIN; 290 | 291 | u32 seconds = static_cast(total_ms / MS_PER_SEC); 292 | total_ms = total_ms % MS_PER_SEC; 293 | 294 | return time_span(0, hours, minutes, seconds, static_cast(total_ms), false); 295 | } 296 | 297 | time_t mariadb::time::mktime() const { 298 | tm time_struct; 299 | 300 | time_struct.tm_year = 1900; 301 | time_struct.tm_mon = 0; 302 | time_struct.tm_mday = 1; 303 | time_struct.tm_hour = hour(); 304 | time_struct.tm_min = minute(); 305 | time_struct.tm_sec = second(); 306 | 307 | return ::mktime(&time_struct); 308 | } 309 | 310 | MYSQL_TIME mariadb::time::mysql_time() const { 311 | MYSQL_TIME t; 312 | 313 | t.year = 0; 314 | t.month = 0; 315 | t.day = 0; 316 | t.hour = hour(); 317 | t.minute = minute(); 318 | t.second = second(); 319 | t.second_part = millisecond() * 1000u; 320 | t.neg = false; 321 | t.time_type = MYSQL_TIMESTAMP_TIME; 322 | return t; 323 | } 324 | 325 | double mariadb::time::diff_time(const time &t) const { 326 | time_t time_val = mktime(); 327 | time_t t_time_val = t.mktime(); 328 | 329 | return ::difftime(time_val, t_time_val); 330 | } 331 | 332 | bool mariadb::time::is_valid() const { 333 | return time::valid_time(hour(), minute(), second(), millisecond()); 334 | } 335 | 336 | bool mariadb::time::valid_time(u8 hour, u8 minute, u8 second, u16 millisecond) { 337 | // seconds go up to 61 to allow for leap seconds (see time::second) 338 | return hour < 24 && minute < 60 && second <= 61 && millisecond < 1000; 339 | } 340 | 341 | mariadb::time mariadb::time::now() { 342 | using namespace std::chrono; 343 | auto now = system_clock::now(); 344 | 345 | time_t local_time = system_clock::to_time_t(now); 346 | tm ts; 347 | localtime_safe(&ts, &local_time); 348 | 349 | auto millis = duration_cast(now.time_since_epoch()).count() % 1000; 350 | return mariadb::time(ts).add_milliseconds(static_cast(millis)); 351 | } 352 | 353 | mariadb::time mariadb::time::now_utc() { 354 | using namespace std::chrono; 355 | auto now = system_clock::now(); 356 | 357 | time_t utc_time = system_clock::to_time_t(now); 358 | tm ts; 359 | gmtime_safe(&ts, &utc_time); 360 | 361 | auto millis = duration_cast(now.time_since_epoch()).count() % 1000; 362 | return mariadb::time(ts).add_milliseconds(static_cast(millis)); 363 | } 364 | 365 | bool mariadb::time::set(const std::string &t) { 366 | std::stringstream stream(t); 367 | 368 | u8 h, m, s; 369 | u16 s_h = 0, s_m = 0, s_s = 0, s_ms = 0; 370 | char delim; 371 | 372 | // read formatted hours, check overflow before cast 373 | if (stream >> s_h && s_h < 24) { 374 | h = static_cast(s_h); 375 | if (stream.eof()) 376 | return set(h, 0, 0, 0); 377 | 378 | // read formatted minutes, check overflow before cast 379 | if (stream >> delim && stream >> s_m && s_m < 60) { 380 | m = static_cast(s_m); 381 | if (stream.eof()) 382 | return set(h, m, 0, 0); 383 | 384 | // read formatted seconds, check overflow before cast 385 | if (stream >> delim && stream >> s_s && s_s < 62) { 386 | s = static_cast(s_s); 387 | if (stream.eof()) 388 | return set(h, m, s, 0); 389 | 390 | // read formatted millis 391 | if (stream >> delim && stream >> s_ms) 392 | return set(h, m, s, s_ms); 393 | } 394 | } 395 | } 396 | 397 | throw std::invalid_argument("invalid time format"); 398 | } 399 | 400 | const std::string mariadb::time::str_time(bool with_millisecond) const { 401 | std::stringstream stream; 402 | stream.fill('0'); 403 | 404 | /* 405 | * Note: the magic unary + forces conversion of unsigned char (u8) to a numeric printing type, 406 | * otherwise it will 407 | * be printed as char 408 | */ 409 | stream << std::setw(2) << +hour() << ":" << std::setw(2) << +minute() << ":" << std::setw(2) << +second(); 410 | 411 | if (with_millisecond) 412 | stream << "." << std::setw(3) << millisecond(); 413 | 414 | return stream.str(); 415 | } 416 | 417 | std::ostream &mariadb::operator<<(std::ostream &os, const time &t) { 418 | os << t.str_time(true); 419 | return os; 420 | } 421 | -------------------------------------------------------------------------------- /include/mariadb++/date_time.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // The ViaDuck Project 2016 - 2020. 6 | // Distributed under the Boost Software License, Version 1.0. 7 | // (See accompanying file LICENSE or copy at 8 | // http://www.boost.org/LICENSE_1_0.txt) 9 | 10 | #ifndef _MARIADB_DATE_TIME_HPP_ 11 | #define _MARIADB_DATE_TIME_HPP_ 12 | 13 | #include 14 | #include 15 | 16 | namespace mariadb { 17 | /** 18 | * Class used to represent SQL date_time 19 | */ 20 | class date_time : public time { 21 | public: 22 | /** 23 | * Construct date_time from given data. An exception will be thrown on invalid data. 24 | * 25 | * @param year The year to set. 26 | * @param month The month to set. [1-12] 27 | * @param day The day to set. Make sure it exists in the month. [1-28/39/30/31] 28 | * @param hour The hour to set. [0-23} 29 | * @param minute The minute to set. [0-59] 30 | * @param second The second to set. [0-60] 31 | * @param millisecond The millisecond to set. [0-999] 32 | */ 33 | date_time(u16 year = 1970, u8 month = 1, u8 day = 1, u8 hour = 0, u8 minute = 0, u8 second = 0, 34 | u16 millisecond = 0); 35 | 36 | /** 37 | * Copy constructor 38 | * 39 | * @param dt Datetime to copy from 40 | */ 41 | date_time(const date_time &dt); 42 | 43 | /** 44 | * Construct date_time with given time. Sets the date to Jan, 1st of 1900 45 | * 46 | * @param t Time to copy from 47 | */ 48 | date_time(const time &t); 49 | 50 | /** 51 | * Construct date_time from tm struct. Since tm cannot represent milliseconds, none will be set. 52 | * 53 | * @param time_struct Struct to copy from 54 | */ 55 | date_time(const tm &time_struct); 56 | 57 | /** 58 | * Construct date_time from given time_t. Converts the time to local timezone before setting it. 59 | * Note that no milliseconds will be set. 60 | * 61 | * @param time Timetype to set. 62 | */ 63 | date_time(const time_t &time); 64 | 65 | /** 66 | * Construct date_time from MYSQL_TIME. No milliseconds will be set. 67 | * 68 | * @param time MYSQL_TIME to copy from. 69 | */ 70 | date_time(const MYSQL_TIME &time); 71 | 72 | /** 73 | * Construct date_time from ISO yyyy-mm-dd hh:mm:ss.nnn date format. Throws an exception on 74 | * invalid input 75 | * 76 | * @param dt String containing ISO date 77 | */ 78 | date_time(const std::string &dt); 79 | 80 | // 81 | // Operators 82 | // 83 | int compare(const date_time &dt) const; 84 | date_time &operator=(const date_time &dt); 85 | bool operator==(const date_time &dt) const; 86 | bool operator!=(const date_time &dt) const; 87 | bool operator<(const date_time &dt) const; 88 | bool operator<=(const date_time &dt) const; 89 | bool operator>(const date_time &dt) const; 90 | bool operator>=(const date_time &dt) const; 91 | 92 | /** 93 | * Get currently set year 94 | * 95 | * @return Current year 96 | */ 97 | u16 year() const; 98 | 99 | /** 100 | * Set current year. If date becomes invalid, month and day will be reset to 1-1 101 | * 102 | * @param year Year to set 103 | * @return Newly set year 104 | */ 105 | u16 year(u16 year); 106 | 107 | /** 108 | * Get currently set month 109 | * 110 | * @return Current month 111 | */ 112 | u8 month() const; 113 | 114 | /** 115 | * Set current month. If date becomes invalid, day will be reset to 1 116 | * 117 | * @param month Month to set 118 | * @return Newly set month 119 | */ 120 | u8 month(u8 month); 121 | 122 | /** 123 | * Get currently set day of month 124 | * 125 | * @return Current day 126 | */ 127 | u8 day() const; 128 | 129 | /** 130 | * Set current day. If date becomes invalid, an exception will be thrown 131 | * 132 | * @param day Day to set 133 | * @return Newly set day 134 | */ 135 | u8 day(u8 day); 136 | 137 | /** 138 | * Calculates the day of year from current date. 139 | * 140 | * @return Day of year 141 | */ 142 | u16 day_of_year() const; 143 | 144 | /** 145 | * Sets the date by calculating which date the day_of_year corresponds to. 146 | * 147 | * @return Day of year that was set 148 | */ 149 | u16 day_of_year(u16 day_of_year); 150 | 151 | /** 152 | * Set only date part. Invalid dates will throw an exception 153 | * 154 | * @param year Year to set. 155 | * @param month Month to set. [1-12] 156 | * @param day Day to set. [1-28/29/30/31] 157 | * @return True on success 158 | */ 159 | bool set(u16 year, u8 month, u8 day); 160 | 161 | /** 162 | * Set date and time part. Invalid dates will throw an exception 163 | * 164 | * @param year Year to set. 165 | * @param month Month to set. [1-12] 166 | * @param day Day to set. [1-28/29/30/31] 167 | * @param hour The hour to set. [0-23} 168 | * @param minute The minute to set. [0-59] 169 | * @param second The second to set. [0-60] 170 | * @param millisecond The millisecond to set. [0-999] 171 | * @return True on success 172 | */ 173 | bool set(u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond); 174 | 175 | /** 176 | * Set date and time to ISO yyyy-mm-dd hh:mm:ss.nnn date format. 177 | * Throws an exception on invalid input 178 | * 179 | * @param dt String containing ISO date 180 | * @return True on success 181 | */ 182 | bool set(const std::string &dt) override; 183 | 184 | /** 185 | * Add years to current date. 186 | * 187 | * @param years Number of years to add 188 | * @return Newly created date_time containing result 189 | */ 190 | date_time add_years(s32 years) const; 191 | 192 | /** 193 | * Add months to current date with year wrapping. 194 | * 195 | * @param months Number of months to add 196 | * @return Newly created date_time containing result 197 | */ 198 | date_time add_months(s32 months) const; 199 | 200 | /** 201 | * Add days to current date with month wrapping. 202 | * 203 | * @param days Number of days to add 204 | * @return Newly created date_time containing result 205 | */ 206 | date_time add_days(s32 days) const; 207 | 208 | /** 209 | * Add hours to current date with day wrapping. 210 | * 211 | * @param hours Number of hours to add 212 | * @return Newly created date_time containing result 213 | */ 214 | date_time add_hours(s32 hours) const; 215 | 216 | /** 217 | * Add minutes to current date with hour wrapping. 218 | * 219 | * @param minutes Number of minutes to add 220 | * @return Newly created date_time containing result 221 | */ 222 | date_time add_minutes(s32 minutes) const; 223 | 224 | /** 225 | * Add seconds to current date with minute wrapping. 226 | * 227 | * @param seconds Number of seconds to add 228 | * @return Newly created date_time containing result 229 | */ 230 | date_time add_seconds(s32 seconds) const; 231 | 232 | /** 233 | * Add milliseconds to current date with second wrapping. 234 | * 235 | * @param milliseconds Number of milliseconds to add 236 | * @return Newly created date_time containing result 237 | */ 238 | date_time add_milliseconds(s32 milliseconds) const; 239 | 240 | /** 241 | * Adds a timespan to the date_time. 242 | * 243 | * @param dur The duration to add 244 | * @return Newly created date_time containing result 245 | */ 246 | date_time add(const time_span &dur) const; 247 | 248 | /** 249 | * Subtracts a timespan from the date_time. 250 | * 251 | * @param dur The duration to subtract 252 | * @return Newly created date_time containing result 253 | */ 254 | date_time subtract(const time_span &dur) const; 255 | 256 | /** 257 | * Adds a mariadb::time to the date_time. 258 | * 259 | * @param t Time to add 260 | * @return Newly created date_time containing result 261 | */ 262 | date_time add(const time &t) const; 263 | 264 | /** 265 | * Substract a mariadb::time from the date_time. 266 | * 267 | * @param t Time to subtract 268 | * @return Newly created date_time containing result 269 | */ 270 | date_time substract(const time &t) const; 271 | 272 | /** 273 | * Calculates the time_span between this date_time and dt. If dt > this the time_span will be 274 | * negative 275 | * 276 | * @param dt Datetime to calculate the timespan to 277 | * @return Timespan representing the time between this and dt 278 | */ 279 | time_span time_between(const date_time &dt) const; 280 | 281 | /** 282 | * Indicates whether this date is considered valid 283 | * 284 | * @return True if date is valid 285 | */ 286 | bool is_valid() const override; 287 | 288 | /** 289 | * Indicates whether a given year is leap according to gregorian calendar 290 | * 291 | * @param year The year to check 292 | * @return True if year is leap 293 | */ 294 | static bool is_leap_year(u16 year); 295 | 296 | /** 297 | * Indicates whether a given date is valid in terms of existing month and day in month. Accounts 298 | * for leap years 299 | * 300 | * @param year Year to check 301 | * @param month Month to check 302 | * @param day Day to check 303 | * @return True if date is valid 304 | */ 305 | static bool valid_date(u16 year, u8 month, u8 day); 306 | 307 | /** 308 | * Gets the number of days in a given year. Accounts for leap years 309 | * 310 | * @param year Year to check 311 | * @return Number of days in year 312 | */ 313 | static u16 days_in_year(u16 year); 314 | 315 | /** 316 | * Gets the number of days in a given month of a given year. Accounts for leap years 317 | * 318 | * @param year Year to check 319 | * @param month Month to check 320 | * @return Number of days in month of year 321 | */ 322 | static u8 days_in_month(u16 year, u8 month); 323 | 324 | /** 325 | * Gets the day of year for given date. Accounts for leap years 326 | * 327 | * @param year Year of date 328 | * @param month Month of date 329 | * @param day Day of date 330 | * @return Day of year of given date 331 | */ 332 | static u16 day_of_year(u16 year, u8 month, u8 day); 333 | 334 | /** 335 | * Gets the date from given year and day of year 336 | * 337 | * @param year Year in which to position day_of_year 338 | * @param day_of_year Position of day within the year 339 | * @return Datetime representing the exact date 340 | */ 341 | static date_time reverse_day_of_year(u16 year, u16 day_of_year); 342 | 343 | /** 344 | * Convert the date_time to a time_t. Precision is limited to seconds 345 | * 346 | * @return Timetype representing the date_time 347 | */ 348 | time_t mktime() const; 349 | 350 | /** 351 | * Uses ::difftime to calculate number of seconds between two dates 352 | * 353 | * @param dt Datetime to calculate difference to 354 | * @return Number of seconds with fractions between the two dates 355 | */ 356 | double diff_time(const date_time &dt) const; 357 | 358 | /** 359 | * Gets only the date part of this date_time 360 | * 361 | * @return Datetime representing only the date part 362 | */ 363 | date_time date() const; 364 | 365 | /** 366 | * Converts the date_time to a MYSQL_TIME. Precision is limited to seconds 367 | * 368 | * @return MYSQL_TIME representing this date_time 369 | */ 370 | MYSQL_TIME mysql_time() const; 371 | 372 | /** 373 | * Gets the current date and time as date_time. Does not set milliseconds 374 | * 375 | * @return Current date and time in local timezone 376 | */ 377 | static date_time now(); 378 | 379 | /** 380 | * Gets the current date and time as date_time. Does not set milliseconds 381 | * 382 | * @return Current date and time in UTC 383 | */ 384 | static date_time now_utc(); 385 | 386 | /** 387 | * Converts the date and time to ISO 8601 string yyyy-mm-dd hh:mm:ss[.nnn] 388 | * 389 | * @param with_millisecond Controls whether or not to print optional .nnn part 390 | * @return String containing ISO date and time 391 | */ 392 | const std::string str(bool with_millisecond = false) const; 393 | 394 | /** 395 | * Converts only the date part of this date_time to ISO 8601 date string yyyy-mm-dd 396 | * 397 | * @return String containing ISO date 398 | */ 399 | const std::string str_date() const; 400 | 401 | private: 402 | u16 m_year; 403 | u8 m_month; 404 | u8 m_day; 405 | }; 406 | 407 | std::ostream &operator<<(std::ostream &os, const date_time &ddt); 408 | } // namespace mariadb 409 | 410 | #endif 411 | -------------------------------------------------------------------------------- /src/result_set.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // M A R I A D B + + 3 | // 4 | // Copyright Sylvain Rochette Langlois 2013, 5 | // Frantisek Boranek 2015, 6 | // The ViaDuck Project 2016 - 2021. 7 | // Distributed under the Boost Software License, Version 1.0. 8 | // (See accompanying file LICENSE or copy at 9 | // http://www.boost.org/LICENSE_1_0.txt) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "private.hpp" 18 | 19 | using namespace mariadb; 20 | 21 | result_set::result_set(connection *conn) 22 | : m_result_set((conn->account()->store_result() ? mysql_store_result : mysql_use_result)(conn->m_mysql)), 23 | m_fields(nullptr), 24 | m_row(nullptr), 25 | m_raw_binds(nullptr), 26 | m_stmt_data(nullptr), 27 | m_lengths(nullptr), 28 | m_field_count(0), 29 | m_was_fetched(false) { 30 | if (m_result_set) { 31 | m_field_count = mysql_num_fields(m_result_set); 32 | m_fields = mysql_fetch_fields(m_result_set); 33 | 34 | for (u32 i = 0; i < m_field_count; ++i) m_indexes[m_fields[i].name] = i; 35 | } 36 | } 37 | 38 | result_set::result_set(connection *conn, const statement_data_ref &stmt_data) 39 | : m_result_set(nullptr), 40 | m_fields(nullptr), 41 | m_row(nullptr), 42 | m_raw_binds(nullptr), 43 | m_stmt_data(stmt_data), 44 | m_lengths(nullptr), 45 | m_field_count(0), 46 | m_was_fetched(false) { 47 | int max_length = 1; 48 | mysql_stmt_attr_set(stmt_data->m_statement, STMT_ATTR_UPDATE_MAX_LENGTH, &max_length); 49 | 50 | if (conn->account()->store_result() && mysql_stmt_store_result(stmt_data->m_statement)) 51 | MARIADB_STMT_ERROR(stmt_data->m_statement); 52 | else { 53 | m_field_count = mysql_stmt_field_count(stmt_data->m_statement); 54 | m_result_set = mysql_stmt_result_metadata(stmt_data->m_statement); 55 | 56 | if (m_field_count > 0) { 57 | m_fields = mysql_fetch_fields(m_result_set); 58 | m_raw_binds = new MYSQL_BIND[m_field_count]; 59 | m_row = new char *[m_field_count]; 60 | 61 | for (u32 i = 0; i < m_field_count; ++i) { 62 | m_indexes[m_fields[i].name] = i; 63 | m_binds.emplace_back(new bind(&m_raw_binds[i], &m_fields[i])); 64 | m_row[i] = m_binds[i]->buffer(); 65 | } 66 | 67 | mysql_stmt_bind_result(stmt_data->m_statement, m_raw_binds); 68 | } 69 | } 70 | } 71 | 72 | statement_data::~statement_data() { 73 | delete[] m_raw_binds; 74 | 75 | if (m_statement) 76 | mysql_stmt_close(m_statement); 77 | } 78 | 79 | result_set::~result_set() { 80 | if (m_result_set) 81 | mysql_free_result(m_result_set); 82 | 83 | delete[] m_raw_binds; 84 | 85 | if (m_stmt_data) { 86 | delete[] m_row; 87 | 88 | mysql_stmt_free_result(m_stmt_data->m_statement); 89 | } 90 | } 91 | 92 | bool result_set::fetch_truncated() { 93 | for (u32 i = 0; i < m_field_count; ++i) { 94 | if (m_binds[i]->m_error) { 95 | if (!m_binds[i]->resize()) 96 | return false; 97 | m_row[i] = m_binds[i]->buffer(); 98 | if (mysql_stmt_fetch_column(m_stmt_data->m_statement, m_binds[i]->m_bind, i, 0)) 99 | return false; 100 | } 101 | } 102 | return true; 103 | } 104 | 105 | u32 result_set::column_count() const { 106 | return m_field_count; 107 | } 108 | 109 | value::type result_set::column_type(u32 index) const { 110 | if (index >= m_field_count) 111 | throw std::out_of_range("Column index out of range"); 112 | 113 | bool is_unsigned = (m_fields[index].flags & UNSIGNED_FLAG) == UNSIGNED_FLAG; 114 | 115 | switch (m_fields[index].type) { 116 | case MYSQL_TYPE_NULL: 117 | return value::null; 118 | 119 | case MYSQL_TYPE_BIT: 120 | return value::boolean; 121 | 122 | case MYSQL_TYPE_FLOAT: 123 | return value::float32; 124 | 125 | case MYSQL_TYPE_DECIMAL: 126 | case MYSQL_TYPE_NEWDECIMAL: 127 | return value::decimal; 128 | 129 | case MYSQL_TYPE_DOUBLE: 130 | return value::double64; 131 | 132 | case MYSQL_TYPE_NEWDATE: 133 | case MYSQL_TYPE_DATE: 134 | return value::date; 135 | 136 | case MYSQL_TYPE_TIME: 137 | return value::time; 138 | 139 | case MYSQL_TYPE_TIMESTAMP: 140 | case MYSQL_TYPE_DATETIME: 141 | return value::date_time; 142 | 143 | case MYSQL_TYPE_TINY: 144 | return (is_unsigned ? value::unsigned8 : value::signed8); 145 | 146 | case MYSQL_TYPE_YEAR: 147 | case MYSQL_TYPE_SHORT: 148 | return (is_unsigned ? value::unsigned16 : value::signed16); 149 | 150 | case MYSQL_TYPE_INT24: 151 | case MYSQL_TYPE_LONG: 152 | return (is_unsigned ? value::unsigned32 : value::signed32); 153 | 154 | case MYSQL_TYPE_LONGLONG: 155 | return (is_unsigned ? value::unsigned64 : value::signed64); 156 | 157 | case MYSQL_TYPE_TINY_BLOB: 158 | case MYSQL_TYPE_MEDIUM_BLOB: 159 | case MYSQL_TYPE_LONG_BLOB: 160 | case MYSQL_TYPE_BLOB: 161 | return value::blob; 162 | 163 | case MYSQL_TYPE_ENUM: 164 | return value::enumeration; 165 | 166 | default: 167 | case MYSQL_TYPE_VARCHAR: 168 | case MYSQL_TYPE_VAR_STRING: 169 | case MYSQL_TYPE_STRING: 170 | return value::string; 171 | } 172 | } 173 | 174 | const std::string result_set::column_name(u32 index) { 175 | if (index >= m_field_count) 176 | throw std::out_of_range("Column index out of range"); 177 | 178 | return m_fields[index].name; 179 | } 180 | 181 | u32 result_set::column_index(const std::string &name) const { 182 | const map_indexes_t::const_iterator i = m_indexes.find(name); 183 | 184 | if (i == m_indexes.end()) 185 | return 0xffffffff; 186 | 187 | return i->second; 188 | } 189 | 190 | unsigned long result_set::column_size(u32 index) const { 191 | if (index >= m_field_count) 192 | throw std::out_of_range("Column index out of range"); 193 | 194 | return m_stmt_data ? m_binds.at(index)->length() : m_lengths[index]; 195 | } 196 | 197 | bool result_set::set_row_index(u64 index) { 198 | if (m_stmt_data) 199 | mysql_stmt_data_seek(m_stmt_data->m_statement, index); 200 | else 201 | mysql_data_seek(m_result_set, index); 202 | return next(); 203 | } 204 | 205 | bool result_set::next() { 206 | if (!m_result_set) 207 | return (m_was_fetched = false); 208 | 209 | if (m_stmt_data) { 210 | int ret = mysql_stmt_fetch(m_stmt_data->m_statement); 211 | if (ret == MYSQL_DATA_TRUNCATED) 212 | return (m_was_fetched = fetch_truncated()); 213 | return (m_was_fetched = !ret); 214 | } 215 | 216 | m_row = mysql_fetch_row(m_result_set); 217 | m_lengths = mysql_fetch_lengths(m_result_set); 218 | 219 | // make sure no access to results is possible until a result is successfully fetched 220 | return (m_was_fetched = m_row != nullptr); 221 | } 222 | 223 | u64 result_set::row_index() const { 224 | if (m_stmt_data) 225 | return reinterpret_cast(mysql_stmt_row_tell(m_stmt_data->m_statement)); 226 | 227 | return reinterpret_cast(mysql_row_tell(m_result_set)); 228 | } 229 | 230 | u64 result_set::row_count() const { 231 | if (m_stmt_data) 232 | return mysql_stmt_num_rows(m_stmt_data->m_statement); 233 | 234 | return mysql_num_rows(m_result_set); 235 | } 236 | 237 | void result_set::check_row_fetched() const { 238 | if (!m_was_fetched) 239 | throw std::out_of_range("No row was fetched"); 240 | } 241 | 242 | void result_set::check_type(u32 index, value::type requested) const { 243 | value::type actual = column_type(index); 244 | bool type_error; 245 | 246 | // check requested type vs actual type. 247 | switch (requested) { 248 | // these types require exact type match 249 | case value::type::float32: 250 | case value::type::double64: 251 | case value::type::decimal: 252 | 253 | case value::type::time: 254 | case value::type::date_time: 255 | case value::type::date: 256 | 257 | case value::type::enumeration: 258 | type_error = actual != requested; 259 | break; 260 | 261 | // these types require size match 262 | case value::type::unsigned8: 263 | case value::type::signed8: 264 | type_error = actual != value::type::signed8 && actual != value::type::unsigned8; 265 | break; 266 | case value::type::unsigned16: 267 | case value::type::signed16: 268 | type_error = actual != value::type::signed16 && actual != value::type::unsigned16; 269 | break; 270 | case value::type::unsigned32: 271 | case value::type::signed32: 272 | type_error = actual != value::type::signed32 && actual != value::type::unsigned32; 273 | break; 274 | case value::type::unsigned64: 275 | case value::type::signed64: 276 | type_error = actual != value::type::signed64 && actual != value::type::unsigned64; 277 | break; 278 | 279 | // bool can also be a signed8 280 | case value::type::boolean: 281 | type_error = actual != requested && actual != value::type::signed8; 282 | break; 283 | 284 | // only string, blob, data and null are interchangeable 285 | case value::type::string: 286 | case value::type::blob: 287 | case value::type::data: 288 | type_error = actual != value::type::string && actual != value::type::blob && actual != value::type::data && 289 | actual != value::type::null; 290 | break; 291 | 292 | default: 293 | type_error = false; 294 | } 295 | 296 | if (type_error) 297 | MARIADB_ERROR(exception::connection, 12, "type error: requested type " + std::to_string(requested) + " does not match actual type " + std::to_string(actual)); 298 | } 299 | 300 | MAKE_GETTER(blob, stream_ref, value::type::blob) { 301 | size_t len = column_size(index); 302 | 303 | if (len == 0) 304 | return stream_ref(); 305 | 306 | auto *ss = new std::istringstream(); 307 | ss->rdbuf()->pubsetbuf(m_row[index], len); 308 | return stream_ref(ss); 309 | } 310 | 311 | MAKE_GETTER(data, data_ref, value::type::data) { 312 | size_t len = column_size(index); 313 | 314 | return len == 0 ? data_ref() : data_ref(new data(m_row[index], len)); 315 | } 316 | 317 | MAKE_GETTER(string, std::string, value::type::string) { 318 | return std::string(m_row[index], column_size(index)); 319 | } 320 | 321 | MAKE_GETTER(date, date_time, value::type::date) { 322 | if (m_stmt_data) 323 | return mariadb::date_time(m_binds[index]->m_time); 324 | 325 | return date_time(std::string(m_row[index], column_size(index))).date(); 326 | } 327 | 328 | MAKE_GETTER(date_time, date_time, value::type::date_time) { 329 | if (m_stmt_data) 330 | return mariadb::date_time(m_binds[index]->m_time); 331 | 332 | return date_time(std::string(m_row[index], column_size(index))); 333 | } 334 | 335 | MAKE_GETTER(time, mariadb::time, value::type::time) { 336 | if (m_stmt_data) 337 | return mariadb::time(m_binds[index]->m_time); 338 | 339 | return mariadb::time(std::string(m_row[index], column_size(index))); 340 | } 341 | 342 | MAKE_GETTER(decimal, decimal, value::type::decimal) { 343 | return decimal(std::string(m_row[index], column_size(index))); 344 | } 345 | 346 | MAKE_GETTER(boolean, bool, value::type::boolean) { 347 | if (m_stmt_data) 348 | return (m_binds[index]->m_uchar8[0] != 0); 349 | 350 | return string_cast(std::string(m_row[index], column_size(index))); 351 | } 352 | 353 | MAKE_GETTER(unsigned8, u8, value::type::unsigned8) { 354 | if (m_stmt_data) 355 | return checked_cast(0x00000000000000ff & m_binds[index]->m_unsigned64); 356 | 357 | return string_cast(std::string(m_row[index], column_size(index))); 358 | } 359 | 360 | MAKE_GETTER(signed8, s8, value::type::signed8) { 361 | if (m_stmt_data) 362 | return checked_cast(0x00000000000000ff & m_binds[index]->m_signed64); 363 | 364 | return string_cast(std::string(m_row[index], column_size(index))); 365 | } 366 | 367 | MAKE_GETTER(unsigned16, u16, value::type::unsigned16) { 368 | if (m_stmt_data) 369 | return checked_cast(0x000000000000ffff & m_binds[index]->m_unsigned64); 370 | 371 | return string_cast(std::string(m_row[index], column_size(index))); 372 | } 373 | 374 | MAKE_GETTER(signed16, s16, value::type::signed16) { 375 | if (m_stmt_data) 376 | return checked_cast(0x000000000000ffff & m_binds[index]->m_signed64); 377 | 378 | return string_cast(std::string(m_row[index], column_size(index))); 379 | } 380 | 381 | MAKE_GETTER(unsigned32, u32, value::type::unsigned32) { 382 | if (m_stmt_data) 383 | return checked_cast(0x00000000ffffffff & m_binds[index]->m_unsigned64); 384 | 385 | return string_cast(std::string(m_row[index], column_size(index))); 386 | } 387 | 388 | MAKE_GETTER(signed32, s32, value::type::signed32) { 389 | if (m_stmt_data) 390 | return m_binds[index]->m_signed32[0]; 391 | 392 | return string_cast(std::string(m_row[index], column_size(index))); 393 | } 394 | 395 | MAKE_GETTER(unsigned64, u64, value::type::unsigned64) { 396 | if (m_stmt_data) 397 | return m_binds[index]->m_unsigned64; 398 | 399 | return string_cast(std::string(m_row[index], column_size(index))); 400 | } 401 | 402 | MAKE_GETTER(signed64, s64, value::type::signed64) { 403 | if (m_stmt_data) 404 | return m_binds[index]->m_signed64; 405 | 406 | return string_cast(std::string(m_row[index], column_size(index))); 407 | } 408 | 409 | MAKE_GETTER(float, f32, value::type::float32) { 410 | if (m_stmt_data) 411 | return m_binds[index]->m_float32[0]; 412 | 413 | return string_cast(std::string(m_row[index], column_size(index))); 414 | } 415 | 416 | MAKE_GETTER(double, f64, value::type::double64) { 417 | if (m_stmt_data) 418 | return checked_cast(m_binds[index]->m_double64); 419 | 420 | return string_cast(std::string(m_row[index], column_size(index))); 421 | } 422 | 423 | MAKE_GETTER(is_null, bool, value::type::null) { 424 | if (m_stmt_data) 425 | return m_binds[index]->is_null(); 426 | 427 | return !m_row[index]; 428 | } 429 | --------------------------------------------------------------------------------