Giter VIP home page Giter VIP logo

superiormysqlpp's Introduction

SuperiorMySQL++

  • Build Status
  • License: LGPL v3

Modern C++ wrapper for MySQL C API

Author: Tomas Nozicka, Seznam.cz

Note: This library currently doesn't support MySQL library version 8 and newer.

Features

  • Modern C++ (currently written in C++14)
  • Minimal overhead
  • Header only
  • Multi-statement queries
  • High performance conversions
  • Advanced prepared statements support with type checks and automatic, typesafe bindings
  • Configurable logging
  • Integrated connection pool
    • Connection management
    • Health checks
    • DNS change checking
  • Extensive and fully automated multi-platform tests (using Docker)

Status

Currently, it is already used at Seznam.cz in production code with great results.

The library is thoroughly tested and all tests are fully automated.

In future we are going to add more examples and documentation.

Please help us out by reporting bugs. (https://github.com/seznam/SuperiorMySqlpp/issues)

We appreciate your feedback!

Installation

Requirements

  • C++14 compatible compiler (tested GCC>=4.9)
  • MySQL C API dev (libmysqlclient-dev)
  • Boost Asio library [optional, for DNS-aware connection pool only] (on Debian, package libboost-system-dev covers required dependencies)

Additional requirements for testing

  • socat

Bootstrap

We use git submodules for our dependencies and git requires you to initialize them manually.

Submodules are currently used only for testing, so initializing is optional.

git submodule update --init --recursive

Test

Tests require docker(>=1.5.0) for running mysql instances with testing data.

Tests are disabled by default, you can enable them by adding -DTEST_ENABLED=TRUE to cmake comamnd.

Aside from basic tests, there is also

  • test_odr: test of One Definition Rule. Allowed, but not build by default. Nonexecutable, fails by failing to build succesfully.
  • test_extended: tests of additional, optional facilities depending on Boost
mkdir build && cd build
cmake .. -DTEST_ENABLED=TRUE
cmake --build . -j <number of concurrent jobs>

Preview

Until we create proper examples, you can see all functionality in action by looking at our tests (https://github.com/seznam/SuperiorMySqlpp/tree/devel/tests).

Please be aware that tests must validate all possible cases and syntax and should not be taken as reference in these matters.

You can look at some basic examples below:

Connection

Connection can be constructed either by passing all necessary arguments directly to Connection constructor or by passing ConnectionConfiguration object.

Connection connection{"<database>", "<user>", "<password>", "<host>", "<port>"};

ConnectionConfiguration contains static factory methods used to build proper connection configuration for desired purpose, e.g. encrypted connection over TCP or connection over Unix socket.

auto tcpConfig = ConnectionConfiguration::getTcpConnectionConfiguration("<database>", "<user>", "<password>", "<host>", "<port>");
auto tcpSslConfig = ConnectionConfiguration::getSslTcpConnectionConfiguration(SslConfiguration{}, "<database>", "<user>", "<password>", "<host>", "<port>");
auto socketConfig = ConnectionConfiguration::getSocketConnectionConfiguration("<database>", "<user>", "<password>", "<socket>");
auto socketSslConfig = ConnectionConfiguration::getSslSocketConnectionConfiguration(SslConfiguration{}, "<database>", "<user>", "<password>", "<socket>");

Connection connection{tcpConfig};

Connections are not thread-safe => use one connection per thread. If you intend to have multiple connection to one server consider using connection pool.

Connection pool

auto connectionPool = SuperiorMySqlpp::makeConnectionPool([](){
    return std::async(std::launch::async,
        [&]() {
            uint16_t port = 3306;
            return std::make_shared<SuperiorMySqlpp::Connection>(
                "<database>", "<user>", "<password>", "<host>", port);
        }
    );
});
connectionPool.setMinSpare(10);  // optional
connectionPool.setMaxSpare(20);  // optional

// possibly set interval times and other things

// Starts the parallel job to keep the number of connections in given range
connectionPool.startResourceCountKeeper();  // optional
// Starts the parallel job to auto-restart broken connections
connectionPool.startHealthCareJob();  // optional

std::shared_ptr<SuperiorMySqlpp::Connection> connection = connectionPool.get();

Queries

Simple result

auto query = connection.makeQuery("INSERT INTO ..");
query.execute();

Store result

auto query = connection.makeQuery("SELECT * FROM ...");
query.execute();

auto result = query.store();
while (auto row = result.fetchRow())
{
    // process row
}

Use result

auto query = connection.makeQuery("SELECT * FROM ...");
query.execute();

auto result = query.use();
while (auto row = result.fetchRow())
{
    // process row
}

Escaping

To escape variable manually you may use method connection.escapeString. Preferred way is using query stream manipulators:

auto query = connection.makeQuery();
query << escape << "ab'cd";  // escape - next argument will be escaped

Multi-statement queries

auto query = connection.makeQuery(
    "INSERT INTO ...;"
    "INSERT INTO ...;"
    "INSERT INTO ...;"
);
query.execute();
do {} while (query.nextResult());

Prepared statement

Prepared statements by default automatically check bound types and query metadata and issue warnings or exceptions if you bound any incompatible types. All C API prepared statements variables types are supported and bindings are set using C++ type system.

These are in fact relatively simple examples. There are a lot of configurations for prepared statement including how strictly do you want to check metadata, allowing some types of implicit conversion and so on.

Param bindings

// type of prepared statements parameters is deduced automatically from arguments
auto preparedStatement = connection.makePreparedStatement(
    "INSERT INTO ... VALUES (?)", 0
);
preparedStatement.execute();

// or if you want multiple inserts
for (auto i=1; i<10; ++i)
{
    std::get<0>(preparedStatement.getParams()) = i
    preparedStatement.execute();
}

Result bindings

auto preparedStatement = connection.makePreparedStatement<ResultBindings<Sql::Int, Sql::Int>>(
    "SELECT `id`, `money` FROM ..."
);
preparedStatement.execute();
while (preparedStatement.fetch())
{
    // you can use std::tie
    Sql::Int id, money;
    std::tie(id, money) = preparedStatement.getResult();

    // or directly use e.g. id as:
    preparedStatement.getResult().get<0>()
}

Dynamic prepared statement

This type is for situations when you do not know which columns you are going to need at compile time.

Param bindings

auto preparedStatement = connection.makeDynamicPreparedStatement(
        "INSERT INTO ... VALUES (?)"
);
for (auto id=0; id<10; ++id)
{
    preparedStatement.bindParam(0, id);
    preparedStatement.updateParamBindings();
    preparedStatement.execute();
}

Result bindings

auto preparedStatement = connection.makeDynamicPreparedStatement(
        "SELECT `id` FROM ..."
);
preparedStatement.execute();
int id = -1;
preparedStatement.bindResult(0, id);
preparedStatement.updateResultBindings();
while (preparedStatement.fetch())
{
    // do something with id
}

Convenience read functions

psParamQuery

Invokes psQuery with param setter only.

psParamQuery(connection, "INSERT INTO ... (col1, col2, ...) VALUES (?, ?, ...)", [&](T1 &col1, T2& col2, ...) -> bool {
    col1 = ...;
    col2 = ...;
    return true; // Or false, if we want to stop
});
psResultQuery
psResultQuery(connection, "SELECT ... FROM ...", <Callable>);

Where callable can be C function, lambda, or member function, however in the last case you need to use wrapper, for example wrapMember function (located in superior_mysqlpp/extras/member_wrapper.hpp).

psResultQuery(connection, "SELECT ... FROM ...", [&](int arg1, int arg2){});
void processRow(int arg1, int arg2) {}
psResultQuery(connection, "SELECT ... FROM ...", &processRow);
class ProcessingClass {
public:
    void processRow(int arg1, int arg2) {}
};

ProcessingClass pc;
psResultQuery(connection, "SELECT ... FROM ...", wrapMember(&pc, &ProcessingClass::processRow));

This method doesn't throw exceptions, however query execution and row fetching can still fail, resulting in exception.

psReadValues
auto preparedStatement = connection.makePreparedStatement<ResultBindings<Sql::Int, Sql::Int>>("SELECT ... FROM ...");
int arg1, arg2;

psReadValues(preparedStatement, arg1, arg2);

or

int arg1, arg2;
psReadValues("SELECT ... FROM ...", connection, arg1, arg2);

Note: This function is made only for reading single row. In case you are reading more than one row, an UnexpectedRowCountError exception is thrown.

sQuery
int myData = 0;

psQuery(
    connection,
    "SELECT ?",
    [&](int &value) -> bool {
        value = myData;
        return (myData++) < 5; // Return true, if data are set, false otherwise (no more input data available)
    },
    [&](int value) {
        printf("Got value: %d\n", value);
    }
)

RowStreamAdapter

Syntactic sugar is provided for extracting values from Row using a familiar stream operator. When a NULL value is encountered, value is default-constructed. Extracting non-existent values is undefined behaviour.

auto row = ...
std::string s;
int i = 0;
Extras::RowStreamAdapter {row}
    >> s
    >> i
    ;

Transactions

Library automatically detects exceptions and does commit or rollback as appropriate.

(This is actually quite sophisticated since you must detect if the exception occurred between transaction ctor and dtor (even if there is already active exception). C++17 helps us by introducing std::uncaught_exceptions (instead of std::uncaught_exception), but today we are forced to use internal compiler structures.)

{
    Transaction transaction{connection};
    connection.makeQuery("INSERT INTO ...;").execute();
}
// or you can specify transaction characteristics or isolation level
{
    Transaction transaction{connection, TransactionCharacteristics::ReadOnly, IsolationLevel::RepeatableRead};
    connection.makeQuery("INSERT INTO ...;").execute();
}

Logging

The library has build-in support for custom logging. In default configuration it logs only warnings and errors to std::cerr.

You may choose some of the library predefined loggers:

// Log all event to std::cout and std::cerr
auto&& logger = std::make_shared<Loggers::Full>();
DefaultLogger::setLoggerPtr(std::move(logger));

Or define your own:

class MyLogger final : public Loggers::Base
{
    using Base::Base;
    virtual ~MyLogger() override
    {
        // do something
    }

    virtual void logWarning(const std::string& message) const override
    {
        // do something
    }

    // a lot of logging methods like: logMySqlConnecting, logMySqlConnected, logMySqlClose, logMySqlCommit, ...
}

auto&& logger = std::make_shared<MyLogger>();
DefaultLogger::setLoggerPtr(std::move(logger));

Current issues

There are problems caused by MySQL C API's bad design which are solved by https://github.com/seznam/SuperiorMySqlpp/blob/master/include/superior_mysqlpp/low_level/mysql_hacks.hpp. This is causing problems with MariaDB which stripped down some symbols from their shared object that we use to fix this bug. (#2)

MariaDB compatibility

MariaDB connector/C 10.2 upwards can be used instead of MySQL connector/C.

Older versions are not supported due to some issues, for instance:

  • memory leaks when ConnectionPool is used and cannot be handled by mysql_hacks.hpp because missing symbols
  • missing MARIADB_VERSION_ID -- we are not able detect whether MariaDB is used
  • failing truncation detection test (depending on used version)

Known bugs

MariaDB 10.2.8 has a broken MySQL compatibility symlink (libmysqlclient.so), therefore you need to link directly with MariaDB client lib (-lmariadb) instead of using usual symlink (-lmysqlclient).

Contribution notes

Firstly, thank for your interest!

We've using semantic commit messages (format of category: message), notably for generating changelogs. Once your work is ready for merge, please rebase to latest version, as we strongly prefer to merge through fast-forward and keep the history clean.

superiormysqlpp's People

Contributors

adamstepan avatar byronhe avatar danopernis avatar erbureth avatar jirislav avatar kadel avatar majcl avatar mrwarlockx avatar opatrilpeter avatar pnov avatar predatorcz avatar smradcz avatar tnozicka avatar tomasprochazka2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

superiormysqlpp's Issues

Incorrect size passed to memset while clearing params/resultsBinding vectors

In dynamic_prepared_statement.hpp clearing memory of param/result binding vector is not called properly because sizeof(vector) doesn't return size of allocated data but only size of vector instance (24 bytes ~ 3 pointers).

Moreover it seems that all these memset callings could be removed at all because the new values are zero-initialized when vector of PODs (plain old data) is resized (please correct me if I'm wrong).

makeHexString suitable for std::string

Method makeHexString is usable only for char[] (and maybe custom types supporting std::begin, std::end and default conversion to char[]) but not for C-string represented as const char *, for std::string c_str() method neither.

Also using std::distance for getting item count "Iterable" type should be better than getting it by difference of end and begin iterator.

DBDriver::Result::freeResult() is useless

I think the method DBDriver::Result::freeResult() should be removed. It calls mysql_free_result but it will be done automatically in class destructor.

In case the method is really wanted, then some things should be updated:

  • it should return void instead of magic auto
  • it should set resultPtr to nullptr after calling mysql_free_result method to ensure destructor won't freeing memory on same pointer again.

pkg-config support

It would be nice to add support for pkg-config, i.e. install file libsuperiormysqlpp.pc to /usr/lib/pkgconfig with following contents:

prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: SuperiorMySqlpp
Description: SuperiorMySqlpp
Version: 0.1.1
Libs: -lmysqlclient
Cflags: -I${includedir}

Wrong initialization of Nullable<String> in dynamic result binding

When a Nullable<String> value is bound using dynamic result binding, it's not initialized correctly. I have table with nullable string column, which is filled by a data. Calling Nullable::isValid() returns false, but using operator*() I get correct data from db. The problem might be missing initialization of "engaged" value.

Here's failing testcase for this issue from @tnozicka :

diff --git a/tests/db_access/dynamic_prepared_statements.cpp b/tests/db_access/dynamic_prepared_statements.cpp
index e32750b..612761f 100644
--- a/tests/db_access/dynamic_prepared_statements.cpp
+++ b/tests/db_access/dynamic_prepared_statements.cpp
@@ -148,6 +148,7 @@ go_bandit([](){
             int count = 0;
             while (preparedStatement.fetch())
             {
+                AssertThat(sname.isValid(), IsTrue());
                 auto name = sname->getString();

                 AssertThat(id, Equals(12));

Strange error while incorrect dynamic prepared statement result binding.

Example:

  1. We have two unsigned int columns in dynamic prepared statement result.
  2. We incorrectly bind only first column, but twice, to different places.
  3. Strange error occurs on updateResultBinding that decimal cannot be binded to unsigned Long
    (the second type is ok - unsigned int(10) is actually unsigned Long, but this decimal is wrong, and also the error message misleading.

dbuild - allow setting concurrency

Currently dbuild does not allow you to set parallelism level and invoke 'make -j' which may overload some low end machines and cause compilation to fail.

Noexcept function should not throw an exception

G++ compiler since version 6.1 contains warning in case the exception is thrown from function with noexcept specifier
(see patch https://gcc.gnu.org/ml/gcc-patches/2015-05/msg00381.html).

Current code compilation fails on Statement::close().

include/superior_mysqlpp/low_level/dbdriver.hpp: In member function ‘void SuperiorMySqlpp::LowLevel::DBDriver::Statement::close()’:
include/superior_mysqlpp/low_level/dbdriver.hpp:637:91: warning: throw will always call terminate() [-Wterminate]
                             mysql_stmt_error(statementPtr), mysql_stmt_errno(statementPtr));

The warning can be suppressed by passing -Wno-terminate to the compiler or better we should avoid throwing exception here.

DBDriver - many methods can be noexcept

Many methods in DBDriver class can be declared with noexcept specifier.
They call underlying C API functions directly with no other logic thus exceptions shouldn't arise there in most cases.

Max string length is 1024 (in result binding)

SuperiorMySqlpp::Sql::String is by default initialized to the length of 1024B. When binding such value to a varchar(1000) I got "Data truncated while fetching statement!" error. Would be nice to have String initialized to the lenght which is always big enough (even for UTF-8 values), ideally derived from actual data column length.

QueryError should have error code.

QueryError should have separate member containing error code to be able to programatically decide what action to take when exception occurs.

Other option is to have separate exception class for each error code, but the first option is definitely easier and it should be enough.

an cmake issue

....
-- Found Threads: TRUE
-- Found Boost: /usr/lib/x86_64-linux-gnu/cmake/Boost-1.71.0/BoostConfig.cmake (found version "1.71.0") found components: system
-- Configuring done
CMake Error at tests/odr/CMakeLists.txt:15 (add_library):
No SOURCES given to target: test_odr

CMake Generate step failed. Build files cannot be regenerated correctly.

Linker error with gcc 10 and multiple translation units including superior_mysqlpp.hpp

With recent compilers (e.g., gcc 10) the linking of multiple translation units including the header superior_mysqlpp.hpp results in an error. The linker complains about multiple definitions of SuperiorMySqlpp::mapSecondGetter.

This is because of this definition:

auto mapSecondGetter = [](auto&& item) -> auto& { return std::forward<decltype(item)>(item).second; };
)

I provided a solution to this issue in the PR #131

mysql_sqlstate

DBDriver class contains wrapper method for mysql_stmt_sqlstate but not for mysql_sqlstate.
This functions return NULL terminated string with SQLSTATE error code which may be useful for debugging purposes.

NOTE: The DBDriver::Statement::sqlState which calls underlying mysql_stmt_sqlstate is not used in upper layers.

Problem with `cout << row;`

Hi, i found problem, when i've try fetching data from database with simple query like this:

while (Row row = result.fetchRow()) {
    std::cout << row << std::endl;
}

This code fetch just part of first row. I assume that problem is in row.hpp on line 107, when are pushing results into stream. Problem may be solved calling instead os << item; use this os << item.getStringView().

MSVC14 - string_view

The following header;

#include <experimental/string_view>

is not existent in CMAKE + MSVC14 environment as shown in picture; http://i.imgur.com/AvrejUx.png

The following options have been appended to CMakeLists.txt;

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm500")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")

Although, the header non existence still persists.

'Commited' should be 'commiTTed', at least in SQL queries

There are typos in

SuperiorMySqlpp::IsolationLevel    {
        ReadUncommited,
        ReadCommited,
..

Committed should have double 't'. It's not only about correct spelling. It results in errors when creating transactions:

SuperiorMySqlpp::detail::toQueryString(IsolationLevel isolationLevel)
        {
            switch (isolationLevel)
            {
                case IsolationLevel::ReadUncommited:
                    return "READ UNCOMMITED";

                case IsolationLevel::ReadCommited:
                    return "READ COMMITED";
...

An error:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'COMMITED; START TRANSACTION READ WRITE

clang v. 3.5.0 doesn't compile if including superior_mysql.hpp

Can't compile with clang v. 3.5.0 a simple cpp file including the header file superior_mysql.hpp:

#include <superior_mysqlpp.hpp>
int main()
{
    return 0;
}

I've tried both with latest commits (c57030c and 4428775).

The errors produced are the following:

In file included from superior_mysqlpp/logging.hpp:18:
superior_mysqlpp/prepared_statements/validate_metadata_modes.hpp:120:17: error: inline declaration of 'isCompatible<0>' follows
      non-inline definition
    inline bool isCompatible<ValidateMetadataMode::Disabled>(FieldTypes, bool, FieldTypes, bool)
                ^
superior_mysqlpp/prepared_statements/validate_metadata_modes.hpp:120:17: note: previous definition is here
superior_mysqlpp/prepared_statements/validate_metadata_modes.hpp:126:17: error: inline declaration of 'isCompatible<1>' follows
      non-inline definition
    inline bool isCompatible<ValidateMetadataMode::Strict>(FieldTypes from, bool from_is_unsigned, FieldTypes to, bool to_is_unsigned)
                ^

Figure out how to be compatible with MariaDB

MariaDB has striped down some symbols that we use to fix design flaws in C API. Particularly THR_LOCK_threads, THR_KEY_mysys, THR_COND_threads, THR_thread_count.

Temporary fix migth be some macro to disable these fixes resulting in small memory leaks in C API TSS (thread specific storage).

Make user-defined converters include-order independent

Currently the users can specify converters for rowstream by template-specializing the SupericorMySqlpp::Converters::to(const char* str, unsigned int length) function.

However, this specialization has to be defined before superior_mysqlpp includes, otherwise it is not picked up.

Using templated converter struct instead would not have that problem.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.