Compare commits
No commits in common. "feature/matador-ng" and "main" have entirely different histories.
feature/ma
...
main
|
|
@ -1,59 +1,50 @@
|
||||||
cmake_minimum_required(VERSION 3.30)
|
cmake_minimum_required(VERSION 3.26)
|
||||||
|
|
||||||
project(
|
project(
|
||||||
matador
|
query
|
||||||
VERSION 0.9.8
|
VERSION 1.0.0
|
||||||
LANGUAGES C CXX
|
DESCRIPTION "SQL query fluent prototype for PostgreSQL, SQLite, MySQL and MSSQL"
|
||||||
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
include(cmake/CPM.cmake)
|
|
||||||
include(CTest)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
SET(GCC_CLANG_COMMON_FLAGS "-Wall -Wextra -pedantic -ftemplate-backtrace-limit=0")
|
set(GCC_CLANG_COMMON_FLAGS "-Wall -Wconversion -Wextra -pedantic -ftemplate-backtrace-limit=0")
|
||||||
SET(GCC_CLANG_COMMON_FLAGS_DEBUG "-O0 -g -DDEBUG")
|
|
||||||
SET(GCC_CLANG_COMMON_FLAGS_RELEASE "-O1 -DNDEBUG")
|
|
||||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
message(STATUS "C++ Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
|
if (WIN32)
|
||||||
|
|
||||||
find_package(PostgreSQL REQUIRED)
|
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
||||||
message(STATUS "GCC detected - Adding compiler flags")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_CLANG_COMMON_FLAGS}")
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${GCC_CLANG_COMMON_FLAGS_DEBUG}")
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${GCC_CLANG_COMMON_FLAGS_RELEASE}")
|
|
||||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
|
||||||
message(STATUS "MSVC detected - Adding compiler flags")
|
|
||||||
# set(CMAKE_CXX_FLAGS "/W3 /EHsc /bigobj")
|
|
||||||
# set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Od /Zi /D_DEBUG /DDEBUG")
|
|
||||||
# set(CMAKE_CXX_FLAGS_RELEASE "/O1 /DNDEBUG")
|
|
||||||
add_compile_options(/Zc:preprocessor)
|
add_compile_options(/Zc:preprocessor)
|
||||||
endif ()
|
endif()
|
||||||
#if(ENABLE_COVERAGE)
|
|
||||||
# # set compiler flags
|
|
||||||
# set(CMAKE_CXX_FLAGS "-O0 -coverage")
|
|
||||||
#
|
|
||||||
# # find required tools
|
|
||||||
# find_program(LCOV lcov REQUIRED)
|
|
||||||
# find_program(GENHTML genhtml REQUIRED)
|
|
||||||
#
|
|
||||||
# # add coverage target
|
|
||||||
# add_custom_target(coverage
|
|
||||||
# # gather data
|
|
||||||
# COMMAND ${LCOV} --directory . --exclude catch2 --exclude /usr/include --exclude test --capture --base-directory ${CMAKE_SOURCE_DIR} --output-file coverage.info
|
|
||||||
# # generate report
|
|
||||||
# COMMAND ${GENHTML} --demangle-cpp -o coverage coverage.info
|
|
||||||
# WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
||||||
#endif()
|
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
add_subdirectory(source)
|
find_package(ODBC REQUIRED)
|
||||||
add_subdirectory(demo)
|
find_package(SQLite3 REQUIRED)
|
||||||
add_subdirectory(backends)
|
find_package(PostgreSQL REQUIRED)
|
||||||
|
find_package(MySQL REQUIRED)
|
||||||
|
|
||||||
|
message(STATUS "Found ODBC config ${ODBC_CONFIG}")
|
||||||
|
message(STATUS "Adding ODBC include directory: ${ODBC_INCLUDE_DIRS}")
|
||||||
|
message(STATUS "Adding ODBC libs: ${ODBC_LIBRARIES}")
|
||||||
|
|
||||||
|
message(STATUS "Found SQLite3 ${SQLite3_VERSION}")
|
||||||
|
message(STATUS "Adding SQLite3 include directory: ${SQLite3_INCLUDE_DIRS}")
|
||||||
|
message(STATUS "Adding SQLite3 libs: ${SQLite3_LIBRARIES}")
|
||||||
|
|
||||||
|
message(STATUS "Adding MySQL include directory: ${MYSQL_INCLUDE_DIR}")
|
||||||
|
message(STATUS "Adding MySQL libs: ${MYSQL_LIBRARY}")
|
||||||
|
|
||||||
|
message(STATUS "Adding PostgreSQL include directory: ${PostgreSQL_INCLUDE_DIR}")
|
||||||
|
message(STATUS "Adding PostgreSQL libs: ${PostgreSQL_LIBRARY}")
|
||||||
|
|
||||||
|
message(STATUS "Common flags ${CMAKE_CXX_FLAGS}")
|
||||||
|
message(STATUS "Debug flags ${CMAKE_CXX_FLAGS_DEBUG}")
|
||||||
|
message(STATUS "Relase flags ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
|
||||||
|
message(STATUS "Linker flags ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
add_subdirectory(backends)
|
||||||
|
add_subdirectory(demo)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Todo
|
||||||
|
|
||||||
|
- Add is_valid() method to connection & connection_impl
|
||||||
|
- Read in entity fields
|
||||||
|
- Add special handling for update in backends
|
||||||
|
- Add ODBC/SQL Server backend
|
||||||
|
|
||||||
|
|
||||||
|
Fetch eager strategies
|
||||||
|
======================
|
||||||
|
|
||||||
|
ONE TO ONE/MANY
|
||||||
|
*person* *address*
|
||||||
|
- has one address - belongs to person
|
||||||
|
|
||||||
|
=> join "address" on "person.id" == "address.person_id"
|
||||||
|
|
||||||
|
*address* *person*
|
||||||
|
- belongs to person - has one address
|
||||||
|
|
||||||
|
=> join "person" on "address.person_id" == "person.id"
|
||||||
|
|
||||||
|
*book* *author*
|
||||||
|
- belongs to author - has many books
|
||||||
|
|
||||||
|
- => join "author" on "book.author_id" == "author.id"
|
||||||
|
|
||||||
|
HAS MANY TO ONE (WITHOUT RELATION TABLE)
|
||||||
|
|
||||||
|
*author* *book*
|
||||||
|
- has many books - belongs to author
|
||||||
|
|
||||||
|
- => join "book" on "author.id" == "book.author_id"
|
||||||
|
|
||||||
|
if "has many" type has primary key & field "author_id"
|
||||||
|
if table name belongs to entity template type?
|
||||||
|
|
||||||
|
HAS MANY TO MANY (WITHOUT RELATION TABLE)
|
||||||
|
|
||||||
|
*student* *student_course* *course*
|
||||||
|
- has many courses - belongs to student
|
||||||
|
- belongs to course - has many students
|
||||||
|
|
||||||
|
=> join "student_course" on "student.id" == "student_course.student_id"
|
||||||
|
join "student_course" on "course.id" == "student_course.course_id"
|
||||||
|
|
||||||
|
if has many type hasn't primary key (is relation table)
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
#add_subdirectory(sqlite)
|
add_subdirectory(sqlite)
|
||||||
add_subdirectory(postgres)
|
add_subdirectory(postgres)
|
||||||
#add_subdirectory(mysql)
|
add_subdirectory(mysql)
|
||||||
#add_subdirectory(odbc)
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
set(HEADER
|
||||||
|
include/mysql_connection.hpp
|
||||||
|
include/mysql_error.hpp
|
||||||
|
include/mysql_result_reader.hpp
|
||||||
|
include/mysql_dialect.hpp
|
||||||
|
include/mysql_statement.hpp
|
||||||
|
include/mysql_parameter_binder.hpp
|
||||||
|
include/mysql_prepared_result_reader.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/mysql_connection.cpp
|
||||||
|
src/mysql_error.cpp
|
||||||
|
src/mysql_result_reader.cpp
|
||||||
|
src/mysql_dialect.cpp
|
||||||
|
src/mysql_statement.cpp
|
||||||
|
src/mysql_parameter_binder.cpp
|
||||||
|
src/mysql_prepared_result_reader.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBRARY_TARGET matador-mysql)
|
||||||
|
|
||||||
|
add_subdirectory(test)
|
||||||
|
|
||||||
|
add_library(${LIBRARY_TARGET} MODULE ${SOURCES} ${HEADER})
|
||||||
|
|
||||||
|
set_target_properties(${LIBRARY_TARGET}
|
||||||
|
PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/backends"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${LIBRARY_TARGET} PRIVATE
|
||||||
|
${PROJECT_SOURCE_DIR}/include
|
||||||
|
${PROJECT_SOURCE_DIR}/backends/mysql/include
|
||||||
|
${MYSQL_INCLUDE_DIR})
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_TARGET} matador ${MYSQL_LIBRARY})
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
#ifndef QUERY_POSTGRES_CONNECTION_HPP
|
||||||
|
#define QUERY_POSTGRES_CONNECTION_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#ifdef matador_mysql_EXPORTS
|
||||||
|
#define MATADOR_MYSQL_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define MATADOR_MYSQL_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#pragma warning(disable: 4355)
|
||||||
|
#else
|
||||||
|
#define MATADOR_MYSQL_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "matador/sql/connection_impl.hpp"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <mysql.h>
|
||||||
|
#else
|
||||||
|
#include <mysql/mysql.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
class mysql_connection : public matador::sql::connection_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit mysql_connection(const sql::connection_info &info);
|
||||||
|
void open() override;
|
||||||
|
void close() override;
|
||||||
|
bool is_open() override;
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> fetch(const std::string &stmt) override;
|
||||||
|
std::unique_ptr<sql::statement_impl> prepare(sql::query_context context) override;
|
||||||
|
|
||||||
|
size_t execute(const std::string &stmt) override;
|
||||||
|
|
||||||
|
std::vector<sql::column_definition> describe(const std::string& table) override;
|
||||||
|
|
||||||
|
bool exists(const std::string &schema_name, const std::string &table_name) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::unique_ptr<MYSQL> mysql_;
|
||||||
|
|
||||||
|
using string_to_int_map = std::unordered_map<std::string, unsigned long>;
|
||||||
|
|
||||||
|
static string_to_int_map statement_name_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
MATADOR_MYSQL_API matador::sql::connection_impl* create_database(const matador::sql::connection_info &info);
|
||||||
|
|
||||||
|
MATADOR_MYSQL_API void destroy_database(matador::sql::connection_impl *db);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_CONNECTION_HPP
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef QUERY_POSTGRES_DIALECT_HPP
|
||||||
|
#define QUERY_POSTGRES_DIALECT_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#ifdef matador_mysql_EXPORTS
|
||||||
|
#define MATADOR_MYSQL_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define MATADOR_MYSQL_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#pragma warning(disable: 4355)
|
||||||
|
#else
|
||||||
|
#define MATADOR_MYSQL_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "matador/sql/dialect.hpp"
|
||||||
|
|
||||||
|
extern "C" [[maybe_unused]] MATADOR_MYSQL_API const matador::sql::dialect* get_dialect();
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_DIALECT_HPP
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef QUERY_POSTGRES_ERROR_HPP
|
||||||
|
#define QUERY_POSTGRES_ERROR_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <mysql.h>
|
||||||
|
#else
|
||||||
|
#include <mysql/mysql.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
void throw_mysql_error(const char *what, const std::string &source);
|
||||||
|
void throw_mysql_error(MYSQL *db, const std::string &source);
|
||||||
|
void throw_mysql_error(MYSQL_STMT *stmt, const std::string &source, const std::string &sql);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_ERROR_HPP
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
|
#define QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
|
|
||||||
|
#include "matador/sql/parameter_binder.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <mysql.h>
|
||||||
|
#else
|
||||||
|
#include <mysql/mysql.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
struct mysql_result_info
|
||||||
|
{
|
||||||
|
unsigned long length = 0;
|
||||||
|
my_bool is_null = false;
|
||||||
|
my_bool error = false;
|
||||||
|
// std::unique_ptr<char[]> buffer;
|
||||||
|
char *buffer = nullptr;
|
||||||
|
unsigned long buffer_length = 0;
|
||||||
|
bool is_allocated = false;
|
||||||
|
|
||||||
|
~mysql_result_info()
|
||||||
|
{
|
||||||
|
if (is_allocated) {
|
||||||
|
delete [] buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class mysql_parameter_binder final : public sql::parameter_binder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit mysql_parameter_binder(size_t size);
|
||||||
|
|
||||||
|
void bind(size_t pos, char i) override;
|
||||||
|
void bind(size_t pos, short i) override;
|
||||||
|
void bind(size_t pos, int i) override;
|
||||||
|
void bind(size_t pos, long i) override;
|
||||||
|
void bind(size_t pos, long long int i) override;
|
||||||
|
void bind(size_t pos, unsigned char i) override;
|
||||||
|
void bind(size_t pos, unsigned short i) override;
|
||||||
|
void bind(size_t pos, unsigned int i) override;
|
||||||
|
void bind(size_t pos, unsigned long i) override;
|
||||||
|
void bind(size_t pos, unsigned long long int i) override;
|
||||||
|
void bind(size_t pos, bool b) override;
|
||||||
|
void bind(size_t pos, float d) override;
|
||||||
|
void bind(size_t pos, double d) override;
|
||||||
|
void bind(size_t pos, const char *string) override;
|
||||||
|
void bind(size_t pos, const char *string, size_t size) override;
|
||||||
|
void bind(size_t pos, const std::string &string) override;
|
||||||
|
void bind(size_t pos, const std::string &x, size_t size) override;
|
||||||
|
|
||||||
|
void bind(size_t pos, const utils::blob &blob) override;
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<MYSQL_BIND>& bind_params();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct is_null_t
|
||||||
|
{
|
||||||
|
my_bool is_null = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<MYSQL_BIND> bind_params_;
|
||||||
|
std::vector<is_null_t> is_null_vector;
|
||||||
|
std::vector<mysql_result_info> info_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef QUERY_MYSQL_PREPARED_RESULT_READER_HPP
|
||||||
|
#define QUERY_MYSQL_PREPARED_RESULT_READER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_result_reader.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <mysql.h>
|
||||||
|
#else
|
||||||
|
#include <mysql/mysql.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
class mysql_prepared_result_reader : public sql::query_result_reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit mysql_prepared_result_reader(MYSQL_STMT *stmt);
|
||||||
|
~mysql_prepared_result_reader() override;
|
||||||
|
|
||||||
|
[[nodiscard]] size_t column_count() const override;
|
||||||
|
[[nodiscard]] const char *column(size_t index) const override;
|
||||||
|
bool fetch() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MYSQL_STMT *stmt_{};
|
||||||
|
|
||||||
|
MYSQL_ROW current_row_{};
|
||||||
|
|
||||||
|
size_t row_count_{};
|
||||||
|
size_t column_count_{};
|
||||||
|
int row_index_{-1};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_MYSQL_PREPARED_RESULT_READER_HPP
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
|
#define QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_result_reader.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <mysql.h>
|
||||||
|
#else
|
||||||
|
#include <mysql/mysql.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
class mysql_result_reader : public sql::query_result_reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit mysql_result_reader(MYSQL_RES *result, unsigned int column_count);
|
||||||
|
~mysql_result_reader() override;
|
||||||
|
|
||||||
|
[[nodiscard]] size_t column_count() const override;
|
||||||
|
[[nodiscard]] const char *column(size_t index) const override;
|
||||||
|
bool fetch() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MYSQL_RES *result_{};
|
||||||
|
|
||||||
|
MYSQL_ROW current_row_{};
|
||||||
|
|
||||||
|
size_t row_count_{};
|
||||||
|
size_t column_count_{};
|
||||||
|
int row_index_{-1};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef QUERY_POSTGRES_STATEMENT_HPP
|
||||||
|
#define QUERY_POSTGRES_STATEMENT_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/statement_impl.hpp"
|
||||||
|
|
||||||
|
#include "mysql_parameter_binder.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <mysql.h>
|
||||||
|
#else
|
||||||
|
#include <mysql/mysql.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
class mysql_statement final : public sql::statement_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
mysql_statement(MYSQL_STMT *stmt, const sql::query_context &query);
|
||||||
|
|
||||||
|
size_t execute() override;
|
||||||
|
std::unique_ptr<sql::query_result_impl> fetch() override;
|
||||||
|
void reset() override;
|
||||||
|
protected:
|
||||||
|
sql::parameter_binder& binder() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MYSQL_STMT *stmt_{nullptr};
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
|
||||||
|
mysql_parameter_binder binder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_STATEMENT_HPP
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
#include "mysql_connection.hpp"
|
||||||
|
#include "mysql_error.hpp"
|
||||||
|
#include "mysql_result_reader.hpp"
|
||||||
|
#include "mysql_statement.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/record.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
mysql_connection::string_to_int_map mysql_connection::statement_name_map_{};
|
||||||
|
|
||||||
|
mysql_connection::mysql_connection(const sql::connection_info &info)
|
||||||
|
: connection_impl(info) {}
|
||||||
|
|
||||||
|
void mysql_connection::open()
|
||||||
|
{
|
||||||
|
if (is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_ = std::make_unique<MYSQL>();
|
||||||
|
|
||||||
|
if (!mysql_init(mysql_.get())) {
|
||||||
|
throw_mysql_error(mysql_.get(), "mysql_init");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mysql_real_connect(mysql_.get(),
|
||||||
|
info().hostname.c_str(),
|
||||||
|
info().user.c_str(),
|
||||||
|
!info().password.empty() ? info().password.c_str() : nullptr,
|
||||||
|
info().database.c_str(),
|
||||||
|
info().port,
|
||||||
|
nullptr,
|
||||||
|
0)) {
|
||||||
|
// disconnect all handles
|
||||||
|
const std::string error_message = mysql_error(mysql_.get());
|
||||||
|
mysql_close(mysql_.get());
|
||||||
|
|
||||||
|
mysql_.reset();
|
||||||
|
// throw exception
|
||||||
|
throw_mysql_error(error_message.c_str(), "mysql_real_connect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_connection::close()
|
||||||
|
{
|
||||||
|
if (mysql_) {
|
||||||
|
mysql_close(mysql_.get());
|
||||||
|
mysql_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mysql_connection::is_open()
|
||||||
|
{
|
||||||
|
return mysql_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql::data_type_t to_type(enum_field_types type, unsigned int flags)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case MYSQL_TYPE_TINY:
|
||||||
|
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_char : sql::data_type_t::type_char;
|
||||||
|
case MYSQL_TYPE_SHORT:
|
||||||
|
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_short : sql::data_type_t::type_short;
|
||||||
|
case MYSQL_TYPE_LONG:
|
||||||
|
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_int : sql::data_type_t::type_int;
|
||||||
|
case MYSQL_TYPE_LONGLONG:
|
||||||
|
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_long_long : sql::data_type_t::type_long_long;
|
||||||
|
case MYSQL_TYPE_FLOAT:
|
||||||
|
return sql::data_type_t::type_float;
|
||||||
|
case MYSQL_TYPE_DOUBLE:
|
||||||
|
return sql::data_type_t::type_double;
|
||||||
|
case MYSQL_TYPE_VARCHAR:
|
||||||
|
case MYSQL_TYPE_VAR_STRING:
|
||||||
|
return sql::data_type_t::type_varchar;
|
||||||
|
case MYSQL_TYPE_BLOB:
|
||||||
|
return sql::data_type_t::type_blob;
|
||||||
|
case MYSQL_TYPE_STRING:
|
||||||
|
return sql::data_type_t::type_text;
|
||||||
|
case MYSQL_TYPE_DATE:
|
||||||
|
return sql::data_type_t::type_date;
|
||||||
|
case MYSQL_TYPE_DATETIME:
|
||||||
|
case MYSQL_TYPE_TIMESTAMP:
|
||||||
|
return sql::data_type_t::type_time;
|
||||||
|
default:
|
||||||
|
return sql::data_type_t::type_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::constraints to_constraints(unsigned int flags)
|
||||||
|
{
|
||||||
|
utils::constraints options{utils::constraints::NONE};
|
||||||
|
if (flags & PRI_KEY_FLAG) {
|
||||||
|
options |= utils::constraints::PRIMARY_KEY;
|
||||||
|
}
|
||||||
|
if (flags & UNIQUE_KEY_FLAG) {
|
||||||
|
options |= utils::constraints::UNIQUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql::null_option to_null_option(unsigned int flags)
|
||||||
|
{
|
||||||
|
return flags & NOT_NULL_FLAG ? sql::null_option::NOT_NULL : sql::null_option::NULLABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql::data_type_t string2type(const std::string &type_string)
|
||||||
|
{
|
||||||
|
if (strncmp(type_string.c_str(), "tinyint", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_char;
|
||||||
|
} else if (strncmp(type_string.c_str(), "smallint", 8) == 0) {
|
||||||
|
if (strstr(type_string.c_str(), "unsigned") != nullptr) {
|
||||||
|
return sql::data_type_t::type_unsigned_short;
|
||||||
|
} else {
|
||||||
|
return sql::data_type_t::type_short;
|
||||||
|
}
|
||||||
|
} else if (strncmp(type_string.c_str(), "int", 3) == 0) {
|
||||||
|
if (strstr(type_string.c_str(), "unsigned") != nullptr) {
|
||||||
|
return sql::data_type_t::type_unsigned_int;
|
||||||
|
} else {
|
||||||
|
return sql::data_type_t::type_int;
|
||||||
|
}
|
||||||
|
} else if (strncmp(type_string.c_str(), "bigint", 6) == 0) {
|
||||||
|
if (strstr(type_string.c_str(), "unsigned") != nullptr) {
|
||||||
|
return sql::data_type_t::type_unsigned_long_long;
|
||||||
|
} else {
|
||||||
|
return sql::data_type_t::type_long_long;
|
||||||
|
}
|
||||||
|
} else if (strcmp(type_string.c_str(), "date") == 0) {
|
||||||
|
return sql::data_type_t::type_date;
|
||||||
|
} else if (strncmp(type_string.c_str(), "datetime", 8) == 0) {
|
||||||
|
return sql::data_type_t::type_time;
|
||||||
|
} else if (strcmp(type_string.c_str(), "float") == 0) {
|
||||||
|
return sql::data_type_t::type_float;
|
||||||
|
} else if (strcmp(type_string.c_str(), "double") == 0) {
|
||||||
|
return sql::data_type_t::type_double;
|
||||||
|
} else if (strncmp(type_string.c_str(), "varchar", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_varchar;
|
||||||
|
} else if (strncmp(type_string.c_str(), "text", 4) == 0) {
|
||||||
|
return sql::data_type_t::type_text;
|
||||||
|
} else {
|
||||||
|
return sql::data_type_t::type_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct type_info
|
||||||
|
{
|
||||||
|
sql::data_type_t type{sql::data_type_t::type_unknown};
|
||||||
|
size_t size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
type_info determine_type_info(const std::string &type_string)
|
||||||
|
{
|
||||||
|
static const std::regex TYPE_REGEX(R"(^(\w+)(\((\d+)(,(\d+))?\))?$)");
|
||||||
|
std::smatch matcher;
|
||||||
|
|
||||||
|
type_info result;
|
||||||
|
if (std::regex_match(type_string, matcher, TYPE_REGEX)) {
|
||||||
|
result.type = string2type(matcher[1].str());
|
||||||
|
if (matcher[3].matched) {
|
||||||
|
result.size = std::stoi(matcher[3].str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> mysql_connection::fetch(const std::string &stmt)
|
||||||
|
{
|
||||||
|
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = mysql_store_result(mysql_.get());
|
||||||
|
if (result == nullptr) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto field_count = mysql_num_fields(result);
|
||||||
|
auto fields = mysql_fetch_fields(result);
|
||||||
|
std::vector<sql::column_definition> prototype;
|
||||||
|
for (unsigned i = 0; i < field_count; ++i) {
|
||||||
|
auto type = to_type(fields[i].type, fields[i].flags);
|
||||||
|
auto options = to_constraints(fields[i].flags);
|
||||||
|
auto null_opt = to_null_option(fields[i].flags);
|
||||||
|
|
||||||
|
prototype.emplace_back(fields[i].name, type, options, null_opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<mysql_result_reader>(result, field_count), std::move(prototype)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::statement_impl> mysql_connection::prepare(sql::query_context context)
|
||||||
|
{
|
||||||
|
MYSQL_STMT *stmt = mysql_stmt_init(mysql_.get());
|
||||||
|
if (stmt == nullptr) {
|
||||||
|
throw_mysql_error(mysql_.get(), "mysql_stmt_init");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysql_stmt_prepare(stmt, context.sql.c_str(), static_cast<unsigned long>(context.sql.size())) != 0) {
|
||||||
|
throw_mysql_error(stmt, "mysql_stmt_prepare", context.sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<mysql_statement>(stmt, std::move(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mysql_connection::execute(const std::string &stmt)
|
||||||
|
{
|
||||||
|
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mysql_affected_rows(mysql_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sql::column_definition> mysql_connection::describe(const std::string &table)
|
||||||
|
{
|
||||||
|
std::string stmt("SHOW COLUMNS FROM " + table);
|
||||||
|
|
||||||
|
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = mysql_store_result(mysql_.get());
|
||||||
|
if (result == nullptr) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_result_reader reader(result, mysql_num_fields(result));
|
||||||
|
std::vector<sql::column_definition> prototype;
|
||||||
|
while (reader.fetch()) {
|
||||||
|
|
||||||
|
char *end = nullptr;
|
||||||
|
std::string name = reader.column(0);
|
||||||
|
|
||||||
|
auto typeinfo = determine_type_info(reader.column(1));
|
||||||
|
end = nullptr;
|
||||||
|
sql::null_option null_opt{sql::null_option::NULLABLE};
|
||||||
|
if (strtoul(reader.column(2), &end, 10) == 0) {
|
||||||
|
null_opt = sql::null_option::NOT_NULL;
|
||||||
|
}
|
||||||
|
prototype.push_back({name, typeinfo.type, {typeinfo.size}, null_opt, prototype.size()});
|
||||||
|
}
|
||||||
|
|
||||||
|
return prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mysql_connection::exists(const std::string &/*schema_name*/, const std::string &table_name)
|
||||||
|
{
|
||||||
|
std::string stmt("SELECT 1 FROM information_schema.tables WHERE table_schema = '" + info().database + "' AND table_name = '" + table_name + "'");
|
||||||
|
|
||||||
|
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = mysql_store_result(mysql_.get());
|
||||||
|
if (result == nullptr) {
|
||||||
|
throw_mysql_error(mysql_.get(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result->row_count == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
MATADOR_MYSQL_API matador::sql::connection_impl *create_database(const matador::sql::connection_info &info)
|
||||||
|
{
|
||||||
|
return new matador::backends::mysql::mysql_connection(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
MATADOR_MYSQL_API void destroy_database(matador::sql::connection_impl *db)
|
||||||
|
{
|
||||||
|
delete db;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#include "mysql_dialect.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/dialect_builder.hpp"
|
||||||
|
|
||||||
|
[[maybe_unused]] const matador::sql::dialect *get_dialect()
|
||||||
|
{
|
||||||
|
using namespace matador::sql;
|
||||||
|
const static dialect d = dialect_builder::builder()
|
||||||
|
.create()
|
||||||
|
.with_token_replace_map({
|
||||||
|
{dialect::token_t::START_QUOTE, "`"},
|
||||||
|
{dialect::token_t::END_QUOTE, "`"},
|
||||||
|
})
|
||||||
|
.with_default_schema_name("")
|
||||||
|
.build();
|
||||||
|
return &d;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include "mysql_error.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
void throw_mysql_error(const char *what, const std::string &source)
|
||||||
|
{
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "mysql error (" << source << "): " << what;
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_mysql_error(MYSQL *db, const std::string &source)
|
||||||
|
{
|
||||||
|
if (mysql_errno(db) != 0) {
|
||||||
|
throw_mysql_error(mysql_error(db), source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_mysql_error(MYSQL_STMT *stmt, const std::string &source, const std::string &sql)
|
||||||
|
{
|
||||||
|
if (mysql_stmt_errno(stmt) != 0) {
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "mysql error (" << source << ") " << mysql_stmt_error(stmt) << ": " << sql;
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
#include "mysql_parameter_binder.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template < class T >
|
||||||
|
void bind_value(enum_field_types type, T value, MYSQL_BIND &bind, my_bool &is_null)
|
||||||
|
{
|
||||||
|
if (bind.buffer == nullptr) {
|
||||||
|
// allocating memory
|
||||||
|
bind.buffer = new char[sizeof(T)];
|
||||||
|
bind.buffer_type = type;
|
||||||
|
bind.buffer_length = sizeof(T);
|
||||||
|
bind.is_null = &is_null;
|
||||||
|
bind.is_unsigned = std::is_unsigned<T>::value;
|
||||||
|
}
|
||||||
|
*static_cast<T*>(bind.buffer) = value;
|
||||||
|
is_null = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind_value(enum_field_types type, const char *value, size_t, MYSQL_BIND &bind, my_bool &is_null)
|
||||||
|
{
|
||||||
|
std::size_t len(strlen(value) + 1);
|
||||||
|
if (bind.buffer_length < len) {
|
||||||
|
// reallocate memory
|
||||||
|
delete [] static_cast<char*>(bind.buffer);
|
||||||
|
bind.buffer = nullptr;
|
||||||
|
bind.buffer_length = 0;
|
||||||
|
bind.buffer_type = type;
|
||||||
|
bind.is_null = &is_null;
|
||||||
|
}
|
||||||
|
if (bind.buffer == nullptr) {
|
||||||
|
// allocating memory
|
||||||
|
bind.buffer = new char[len];
|
||||||
|
memset(bind.buffer, 0, len);
|
||||||
|
}
|
||||||
|
bind.buffer_length = (unsigned long)(len - 1);
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
strncpy_s(static_cast<char*>(bind.buffer), len, value, _TRUNCATE);
|
||||||
|
#else
|
||||||
|
strncpy(static_cast<char*>(bind.buffer), value, len);
|
||||||
|
#endif
|
||||||
|
is_null = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//void bind_value(enum_field_types type, const matador::date &x, MYSQL_BIND &bind, my_bool &is_null)
|
||||||
|
//{
|
||||||
|
// if (bind.buffer == nullptr) {
|
||||||
|
// size_t s = sizeof(MYSQL_TIME);
|
||||||
|
// bind.buffer = new char[s];
|
||||||
|
// bind.buffer_length = (unsigned long)s;
|
||||||
|
// bind.is_null = &is_null;
|
||||||
|
// bind.buffer_type = type;
|
||||||
|
// bind.length = nullptr;
|
||||||
|
// }
|
||||||
|
// memset(bind.buffer, 0, sizeof(MYSQL_TIME));
|
||||||
|
// is_null = false;
|
||||||
|
// auto *mt = static_cast<MYSQL_TIME*>(bind.buffer);
|
||||||
|
// mt->day = (unsigned int)x.day();
|
||||||
|
// mt->month = (unsigned int)x.month();
|
||||||
|
// mt->year = (unsigned int)x.year();
|
||||||
|
// mt->time_type = MYSQL_TIMESTAMP_DATE;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//void bind_value(enum_field_types type, const matador::time &x, MYSQL_BIND &bind, my_bool &is_null)
|
||||||
|
//{
|
||||||
|
// if (bind.buffer == nullptr) {
|
||||||
|
// size_t s = sizeof(MYSQL_TIME);
|
||||||
|
// bind.buffer = new char[s];
|
||||||
|
// bind.buffer_length = (unsigned long)s;
|
||||||
|
// bind.buffer_type = type;
|
||||||
|
// bind.length = nullptr;
|
||||||
|
// bind.is_null = &is_null;
|
||||||
|
// }
|
||||||
|
// memset(bind.buffer, 0, sizeof(MYSQL_TIME));
|
||||||
|
// is_null = false;
|
||||||
|
// auto *mt = static_cast<MYSQL_TIME*>(bind.buffer);
|
||||||
|
// mt->day = (unsigned int)x.day();
|
||||||
|
// mt->month = (unsigned int)x.month();
|
||||||
|
// mt->year = (unsigned int)x.year();
|
||||||
|
// mt->hour = (unsigned int)x.hour();
|
||||||
|
// mt->minute = (unsigned int)x.minute();
|
||||||
|
// mt->second = (unsigned int)x.second();
|
||||||
|
// mt->second_part = (unsigned long)x.milli_second() * 1000;
|
||||||
|
// mt->time_type = MYSQL_TIMESTAMP_DATETIME;
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_parameter_binder::mysql_parameter_binder(size_t size)
|
||||||
|
: bind_params_(size)
|
||||||
|
, is_null_vector(size)
|
||||||
|
, info_(size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, char i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_TINY, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, short i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_SHORT, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, long i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, long long int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_LONGLONG, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, unsigned char i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_TINY, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, unsigned short i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_SHORT, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, unsigned int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, unsigned long i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, unsigned long long int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_LONGLONG, i, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, bool b)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_TINY, b, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, float d)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_FLOAT, d, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, double d)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_DOUBLE, d, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, const char *str)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_STRING, str, strlen(str), bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, const char *str, size_t size)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_VAR_STRING, str, size, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, const std::string &str)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_STRING, str.data(), str.size(), bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, const std::string &str, size_t size)
|
||||||
|
{
|
||||||
|
detail::bind_value(MYSQL_TYPE_VAR_STRING, str.data(), size, bind_params_[pos], is_null_vector[pos].is_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_parameter_binder::bind(size_t pos, const utils::blob &blob)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<MYSQL_BIND> &mysql_parameter_binder::bind_params()
|
||||||
|
{
|
||||||
|
return bind_params_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include "mysql_prepared_result_reader.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
mysql_prepared_result_reader::mysql_prepared_result_reader(MYSQL_STMT *stmt)
|
||||||
|
: stmt_(stmt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
mysql_prepared_result_reader::~mysql_prepared_result_reader()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mysql_prepared_result_reader::column_count() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mysql_prepared_result_reader::column(size_t index) const
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mysql_prepared_result_reader::fetch()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "mysql_result_reader.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
mysql_result_reader::mysql_result_reader(MYSQL_RES *result, unsigned int column_count)
|
||||||
|
: result_(result)
|
||||||
|
, row_count_(mysql_num_rows(result_))
|
||||||
|
, column_count_(column_count)
|
||||||
|
{}
|
||||||
|
|
||||||
|
mysql_result_reader::~mysql_result_reader()
|
||||||
|
{
|
||||||
|
if (result_) {
|
||||||
|
mysql_free_result(result_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mysql_result_reader::column_count() const
|
||||||
|
{
|
||||||
|
return column_count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mysql_result_reader::column(size_t index) const
|
||||||
|
{
|
||||||
|
return current_row_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mysql_result_reader::fetch()
|
||||||
|
{
|
||||||
|
current_row_ = mysql_fetch_row(result_);
|
||||||
|
|
||||||
|
return current_row_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include "mysql_statement.hpp"
|
||||||
|
#include "mysql_error.hpp"
|
||||||
|
#include "mysql_prepared_result_reader.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::mysql {
|
||||||
|
|
||||||
|
mysql_statement::mysql_statement(MYSQL_STMT *stmt, const sql::query_context &query)
|
||||||
|
: statement_impl(query)
|
||||||
|
, stmt_(stmt)
|
||||||
|
, binder_(query_.bind_vars.size())
|
||||||
|
{}
|
||||||
|
|
||||||
|
size_t mysql_statement::execute()
|
||||||
|
{
|
||||||
|
if (!binder_.bind_params().empty()) {
|
||||||
|
if (mysql_stmt_bind_param(stmt_, binder_.bind_params().data()) != 0) {
|
||||||
|
throw_mysql_error(stmt_, "mysql", query_.sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysql_stmt_execute(stmt_) != 0) {
|
||||||
|
throw_mysql_error(stmt_, "mysql", query_.sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mysql_stmt_affected_rows(stmt_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> mysql_statement::fetch()
|
||||||
|
{
|
||||||
|
if (!binder_.bind_params().empty()) {
|
||||||
|
if (mysql_stmt_bind_param(stmt_, binder_.bind_params().data()) != 0) {
|
||||||
|
throw_mysql_error(stmt_, "mysql", query_.sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysql_stmt_execute(stmt_) != 0) {
|
||||||
|
throw_mysql_error(stmt_, "mysql", query_.sql);
|
||||||
|
}
|
||||||
|
if (mysql_stmt_store_result(stmt_) != 0) {
|
||||||
|
throw_mysql_error(stmt_, "mysql", query_.sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<mysql_prepared_result_reader>(stmt_), std::move(query_.prototype)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mysql_statement::reset() {}
|
||||||
|
|
||||||
|
sql::parameter_binder& mysql_statement::binder()
|
||||||
|
{
|
||||||
|
return binder_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
Include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.5.4 # or a later release
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||||
|
include(CTest)
|
||||||
|
include(Catch)
|
||||||
|
|
||||||
|
set(MYSQL_CONNECTION_STRING "mysql://test:test123!@127.0.0.1:3306/matador_test")
|
||||||
|
|
||||||
|
configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/mysql/test/connection.hpp @ONLY IMMEDIATE)
|
||||||
|
|
||||||
|
message(STATUS "mysql connection string: ${MYSQL_CONNECTION_STRING}")
|
||||||
|
|
||||||
|
set(TEST_SOURCES
|
||||||
|
../../tests/QueryTest.cpp
|
||||||
|
../../tests/QueryTest.cpp
|
||||||
|
../../tests/ConnectionTest.cpp
|
||||||
|
../../tests/QueryRecordTest.cpp
|
||||||
|
../../tests/StatementTest.cpp
|
||||||
|
../../tests/TypeTraitsTest.cpp
|
||||||
|
../../tests/StatementCacheTest.cpp
|
||||||
|
../../tests/SessionTest.cpp)
|
||||||
|
|
||||||
|
set(LIBRARY_TEST_TARGET mysql_tests)
|
||||||
|
|
||||||
|
add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES})
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
matador
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
${MySQL_LIBRARY})
|
||||||
|
|
||||||
|
target_include_directories(${LIBRARY_TEST_TARGET}
|
||||||
|
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/include
|
||||||
|
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/test
|
||||||
|
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (MySQL)")
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef MYSQL_CONNECTION_HPP
|
||||||
|
#define MYSQL_CONNECTION_HPP
|
||||||
|
|
||||||
|
namespace matador::test::connection {
|
||||||
|
const char* const dns = "@MYSQL_CONNECTION_STRING@";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*SQLITE_CONNECTION_HPP*/
|
||||||
|
|
@ -32,10 +32,4 @@ target_include_directories(${LIBRARY_TARGET} PRIVATE
|
||||||
${PROJECT_SOURCE_DIR}/backends/postgres/include
|
${PROJECT_SOURCE_DIR}/backends/postgres/include
|
||||||
${PostgreSQL_INCLUDE_DIRS})
|
${PostgreSQL_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_libraries(${LIBRARY_TARGET}
|
target_link_libraries(${LIBRARY_TARGET} matador ${PostgreSQL_LIBRARIES})
|
||||||
matador-core
|
|
||||||
matador-orm
|
|
||||||
${CMAKE_DL_LIBS}
|
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
|
||||||
${PostgreSQL_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
#define MATADOR_POSTGRES_API
|
#define MATADOR_POSTGRES_API
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "matador/sql/interface/connection_impl.hpp"
|
#include "matador/sql/connection_impl.hpp"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
@ -20,25 +20,22 @@
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
class postgres_connection : public sql::connection_impl
|
class postgres_connection : public matador::sql::connection_impl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit postgres_connection(const sql::connection_info &info);
|
explicit postgres_connection(const sql::connection_info &info);
|
||||||
utils::result<void, utils::error> open() override;
|
void open() override;
|
||||||
utils::result<void, utils::error> close() override;
|
void close() override;
|
||||||
[[nodiscard]] utils::result<bool, utils::error> is_open() const override;
|
bool is_open() override;
|
||||||
[[nodiscard]] utils::result<bool, utils::error> is_valid() const override;
|
|
||||||
[[nodiscard]] utils::result<utils::version, utils::error> client_version() const override;
|
|
||||||
[[nodiscard]] utils::result<utils::version, utils::error> server_version() const override;
|
|
||||||
|
|
||||||
utils::result<size_t, utils::error> execute(const std::string &stmt) override;
|
std::unique_ptr<sql::query_result_impl> fetch(const std::string &stmt) override;
|
||||||
utils::result<std::unique_ptr<sql::statement_impl>, utils::error> prepare(const sql::query_context &context) override;
|
std::unique_ptr<sql::statement_impl> prepare(sql::query_context context) override;
|
||||||
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> fetch(const sql::query_context &context) override;
|
|
||||||
|
|
||||||
utils::result<std::vector<object::attribute>, utils::error> describe(const std::string& table) override;
|
size_t execute(const std::string &stmt) override;
|
||||||
utils::result<bool, utils::error> exists(const std::string &schema_name, const std::string &table_name) override;
|
|
||||||
|
|
||||||
[[nodiscard]] std::string to_escaped_string( const utils::blob& value ) const override;
|
std::vector<sql::column_definition> describe(const std::string& table) override;
|
||||||
|
|
||||||
|
bool exists(const std::string &schema_name, const std::string &table_name) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ;
|
[[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ;
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,15 @@
|
||||||
#ifndef QUERY_POSTGRES_ERROR_HPP
|
#ifndef QUERY_POSTGRES_ERROR_HPP
|
||||||
#define QUERY_POSTGRES_ERROR_HPP
|
#define QUERY_POSTGRES_ERROR_HPP
|
||||||
|
|
||||||
#include "matador/utils/error.hpp"
|
|
||||||
|
|
||||||
#include "matador/sql/error_code.hpp"
|
|
||||||
|
|
||||||
#include <libpq-fe.h>
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
utils::error make_error(sql::error_code ec,
|
void throw_postgres_error(const char *what, const std::string &source);
|
||||||
const PGresult *res,
|
void throw_postgres_error(PGconn *db, const std::string &source);
|
||||||
const PGconn *db,
|
void throw_postgres_error(PGresult *res, PGconn *db, const std::string &source, const std::string &sql);
|
||||||
const std::string &msg,
|
|
||||||
const std::string &sql = {});
|
|
||||||
|
|
||||||
bool is_result_error(const PGresult *res);
|
|
||||||
|
|
||||||
// void throw_postgres_error(const char *what, const std::string &source);
|
|
||||||
// void throw_postgres_error(PGconn *db, const std::string &source);
|
|
||||||
// void throw_postgres_error(PGresult *res, PGconn *db, const std::string &source, const std::string &sql);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,42 @@
|
||||||
#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H
|
#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
#define QUERY_POSTGRES_PARAMETER_BINDER_H
|
#define QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
|
|
||||||
#include "matador/sql/interface/parameter_binder.hpp"
|
#include "matador/sql/parameter_binder.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
class postgres_parameter_binder final : public sql::parameter_binder {
|
class postgres_parameter_binder final : public sql::parameter_binder
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
struct bind_data {
|
|
||||||
explicit bind_data(size_t size);
|
|
||||||
std::vector<std::string> strings;
|
|
||||||
std::vector<std::vector<unsigned char>> bytes;
|
|
||||||
std::vector<const char*> values;
|
|
||||||
std::vector<int> lengths;
|
|
||||||
std::vector<int> formats;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit postgres_parameter_binder(size_t size);
|
explicit postgres_parameter_binder(size_t size);
|
||||||
|
|
||||||
void write_value(size_t pos, const uint8_t &x) override;
|
void bind(size_t pos, char i) override;
|
||||||
void write_value(size_t pos, const uint16_t &x) override;
|
void bind(size_t pos, short i) override;
|
||||||
void write_value(size_t pos, const uint32_t &x) override;
|
void bind(size_t pos, int i) override;
|
||||||
void write_value(size_t pos, const uint64_t &x) override;
|
void bind(size_t pos, long i) override;
|
||||||
void write_value(size_t pos, const int8_t &x) override;
|
void bind(size_t pos, long long int i) override;
|
||||||
void write_value(size_t pos, const int16_t &x) override;
|
void bind(size_t pos, unsigned char i) override;
|
||||||
void write_value(size_t pos, const int32_t &x) override;
|
void bind(size_t pos, unsigned short i) override;
|
||||||
void write_value(size_t pos, const int64_t &x) override;
|
void bind(size_t pos, unsigned int i) override;
|
||||||
void write_value(size_t pos, const bool &x) override;
|
void bind(size_t pos, unsigned long i) override;
|
||||||
void write_value(size_t pos, const float &x) override;
|
void bind(size_t pos, unsigned long long int i) override;
|
||||||
void write_value(size_t pos, const double &x) override;
|
void bind(size_t pos, bool b) override;
|
||||||
void write_value(size_t pos, const time &x ) override;
|
void bind(size_t pos, float d) override;
|
||||||
void write_value(size_t pos, const date &x ) override;
|
void bind(size_t pos, double d) override;
|
||||||
void write_value(size_t pos, const char *x) override;
|
void bind(size_t pos, const char *string) override;
|
||||||
void write_value(size_t pos, const char *x, size_t size) override;
|
void bind(size_t pos, const char *string, size_t size) override;
|
||||||
void write_value(size_t pos, const std::string &x) override;
|
void bind(size_t pos, const std::string &string) override;
|
||||||
void write_value(size_t pos, const std::string &x, size_t size) override;
|
void bind(size_t pos, const std::string &x, size_t size) override;
|
||||||
void write_value(size_t pos, const utils::blob &x) override;
|
|
||||||
void write_value(size_t pos, const utils::value &x, size_t size) override;
|
|
||||||
|
|
||||||
[[nodiscard]] const bind_data& params() const;
|
void bind(size_t pos, const utils::blob &blob) override;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<const char*>& params() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bind_data bind_data_;
|
std::vector<std::string> strings_;
|
||||||
// std::vector<std::string> strings_;
|
std::vector<const char*> params_;
|
||||||
// std::vector<const char*> params_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,12 @@
|
||||||
#ifndef QUERY_POSTGRES_RESULT_READER_HPP
|
#ifndef QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
#define QUERY_POSTGRES_RESULT_READER_HPP
|
#define QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
|
|
||||||
#include "matador/sql/interface/query_result_reader.hpp"
|
|
||||||
|
|
||||||
#include <libpq-fe.h>
|
#include <libpq-fe.h>
|
||||||
|
#include "matador/sql/query_result_reader.hpp"
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
namespace detail {
|
class postgres_result_reader : public sql::query_result_reader
|
||||||
|
|
||||||
class empty_binder final : public utils::attribute_reader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void read_value(const char *, size_t, int8_t &) override {}
|
|
||||||
void read_value(const char *, size_t, int16_t &) override {}
|
|
||||||
void read_value(const char *, size_t, int32_t &) override {}
|
|
||||||
void read_value(const char *, size_t, int64_t &) override {}
|
|
||||||
void read_value(const char *, size_t, uint8_t &) override {}
|
|
||||||
void read_value(const char *, size_t, uint16_t &) override {}
|
|
||||||
void read_value(const char *, size_t, uint32_t &) override {}
|
|
||||||
void read_value(const char *, size_t, uint64_t &) override {}
|
|
||||||
void read_value(const char *, size_t, bool &) override {}
|
|
||||||
void read_value(const char *, size_t, float &) override {}
|
|
||||||
void read_value(const char *, size_t, double &) override {}
|
|
||||||
void read_value(const char *, size_t, time &) override {}
|
|
||||||
void read_value(const char *, size_t, date &) override {}
|
|
||||||
void read_value(const char *, size_t, char *, size_t) override {}
|
|
||||||
void read_value(const char *, size_t, std::string &) override {}
|
|
||||||
void read_value(const char *, size_t, std::string &, size_t) override {}
|
|
||||||
void read_value(const char *, size_t, utils::blob &) override {}
|
|
||||||
void read_value(const char *, size_t, utils::value &, size_t) override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class postgres_result_reader final : public sql::query_result_reader
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit postgres_result_reader(PGresult *result);
|
explicit postgres_result_reader(PGresult *result);
|
||||||
|
|
@ -42,31 +14,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] size_t column_count() const override;
|
[[nodiscard]] size_t column_count() const override;
|
||||||
[[nodiscard]] const char *column(size_t index) const override;
|
[[nodiscard]] const char *column(size_t index) const override;
|
||||||
utils::result<bool, utils::error> fetch() override;
|
bool fetch() override;
|
||||||
[[nodiscard]] size_t start_column_index() const override;
|
|
||||||
void unshift() override;
|
|
||||||
|
|
||||||
void read_value(const char *id, size_t index, int8_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, int16_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, int32_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, int64_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, uint8_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, uint16_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, uint32_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, uint64_t &value) override;
|
|
||||||
void read_value(const char *id, size_t index, bool &value) override;
|
|
||||||
void read_value(const char *id, size_t index, float &value) override;
|
|
||||||
void read_value(const char *id, size_t index, double &value) override;
|
|
||||||
void read_value(const char *id, size_t index, matador::time &value) override;
|
|
||||||
void read_value(const char *id, size_t index, matador::date &value) override;
|
|
||||||
void read_value(const char *id, size_t index, char *value, size_t size) override;
|
|
||||||
void read_value(const char *id, size_t index, std::string &value) override;
|
|
||||||
void read_value(const char *id, size_t index, std::string &value, size_t size) override;
|
|
||||||
void read_value(const char *id, size_t index, utils::blob &value) override;
|
|
||||||
void read_value(const char *id, size_t index, utils::value &val, size_t size) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
attribute_reader &result_binder() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PGresult *result_{};
|
PGresult *result_{};
|
||||||
|
|
@ -74,8 +22,6 @@ private:
|
||||||
size_t row_count_{};
|
size_t row_count_{};
|
||||||
size_t column_count_{};
|
size_t column_count_{};
|
||||||
int row_index_{-1};
|
int row_index_{-1};
|
||||||
|
|
||||||
detail::empty_binder empty_binder_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
#ifndef QUERY_POSTGRES_STATEMENT_HPP
|
#ifndef QUERY_POSTGRES_STATEMENT_HPP
|
||||||
#define QUERY_POSTGRES_STATEMENT_HPP
|
#define QUERY_POSTGRES_STATEMENT_HPP
|
||||||
|
|
||||||
#include "matador/sql/interface/statement_impl.hpp"
|
#include "matador/sql/statement_impl.hpp"
|
||||||
#include "matador/sql/interface/parameter_binder.hpp"
|
|
||||||
|
#include "postgres_parameter_binder.h"
|
||||||
|
|
||||||
#include <libpq-fe.h>
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
|
|
@ -11,18 +12,21 @@ namespace matador::backends::postgres {
|
||||||
class postgres_statement final : public sql::statement_impl
|
class postgres_statement final : public sql::statement_impl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
postgres_statement(PGconn *db, std::string name, const sql::query_context &query);
|
postgres_statement(PGconn *db, PGresult *result, std::string name, const sql::query_context &query);
|
||||||
|
|
||||||
utils::result<size_t, utils::error> execute(const sql::parameter_binder& bindings) override;
|
|
||||||
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> fetch(const sql::parameter_binder& bindings) override;
|
|
||||||
|
|
||||||
|
size_t execute() override;
|
||||||
|
std::unique_ptr<sql::query_result_impl> fetch() override;
|
||||||
|
void reset() override;
|
||||||
protected:
|
protected:
|
||||||
[[nodiscard]] std::unique_ptr<utils::attribute_writer> create_binder() const override;
|
sql::parameter_binder& binder() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PGconn *db_{nullptr};
|
PGconn *db_{nullptr};
|
||||||
|
PGresult *result_{nullptr};
|
||||||
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
|
||||||
|
postgres_parameter_binder binder_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,118 +3,73 @@
|
||||||
#include "postgres_result_reader.hpp"
|
#include "postgres_result_reader.hpp"
|
||||||
#include "postgres_statement.hpp"
|
#include "postgres_statement.hpp"
|
||||||
|
|
||||||
#include "matador/object/attribute.hpp"
|
|
||||||
|
|
||||||
#include "matador/sql/error_code.hpp"
|
|
||||||
#include "matador/sql/record.hpp"
|
#include "matador/sql/record.hpp"
|
||||||
|
|
||||||
#include "matador/sql/internal/query_result_impl.hpp"
|
#include <iostream>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
postgres_connection::string_to_int_map postgres_connection::statement_name_map_{};
|
postgres_connection::string_to_int_map postgres_connection::statement_name_map_{};
|
||||||
|
|
||||||
postgres_connection::postgres_connection(const sql::connection_info &info)
|
postgres_connection::postgres_connection(const sql::connection_info &info)
|
||||||
: connection_impl(info) {
|
: connection_impl(info) {}
|
||||||
}
|
|
||||||
|
|
||||||
utils::result<void, utils::error> postgres_connection::open() {
|
void postgres_connection::open()
|
||||||
if (conn_ != nullptr) {
|
{
|
||||||
return utils::ok<void>();
|
if (is_open()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string connection(
|
std::string connection("user=" + info().user + " password=" + info().password + " host=" + info().hostname + " dbname=" + info().database + " port=" + std::to_string(info().port));
|
||||||
"user=" + info().user + " password=" + info().password + " host=" + info().hostname + " dbname=" + info().database +
|
|
||||||
" port=" + std::to_string(info().port));
|
|
||||||
|
|
||||||
conn_ = PQconnectdb(connection.c_str());
|
conn_ = PQconnectdb(connection.c_str());
|
||||||
if (PQstatus(conn_) == CONNECTION_BAD) {
|
if (PQstatus(conn_) == CONNECTION_BAD) {
|
||||||
const std::string msg = PQerrorMessage(conn_);
|
const std::string msg = PQerrorMessage(conn_);
|
||||||
PQfinish(conn_);
|
PQfinish(conn_);
|
||||||
conn_ = nullptr;
|
conn_ = nullptr;
|
||||||
return utils::failure(make_error(sql::error_code::OPEN_ERROR, nullptr, conn_, "Failed to connect"));
|
throw_postgres_error(msg.c_str(), "postgres");
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils::ok<void>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<void, utils::error> postgres_connection::close() {
|
void postgres_connection::close()
|
||||||
|
{
|
||||||
if (conn_) {
|
if (conn_) {
|
||||||
PQfinish(conn_);
|
PQfinish(conn_);
|
||||||
conn_ = nullptr;
|
conn_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils::ok<void>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<bool, utils::error> postgres_connection::is_open() const {
|
bool postgres_connection::is_open()
|
||||||
return utils::ok(conn_ != nullptr);
|
{
|
||||||
|
return conn_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<bool, utils::error> postgres_connection::is_valid() const {
|
std::unique_ptr<sql::query_result_impl> postgres_connection::fetch(const std::string &stmt)
|
||||||
return utils::ok(PQstatus(conn_) == CONNECTION_OK);
|
{
|
||||||
}
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
utils::result<utils::version, utils::error> postgres_connection::client_version() const {
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
const auto client_version = PQlibVersion();
|
|
||||||
return utils::ok(utils::version{
|
|
||||||
static_cast<unsigned int>(client_version / 10000),
|
|
||||||
static_cast<unsigned int>((client_version % 10000) / 100),
|
|
||||||
static_cast<unsigned int>(client_version % 100)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::result<utils::version, utils::error> postgres_connection::server_version() const {
|
std::vector<sql::column_definition> prototype;
|
||||||
const auto server_version = PQserverVersion(conn_);
|
auto num_col = PQnfields(res);
|
||||||
|
|
||||||
if (server_version == 0) {
|
|
||||||
return utils::failure(make_error(sql::error_code::FAILURE, nullptr, conn_, "Failed to get server version"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils::ok(utils::version{
|
|
||||||
static_cast<unsigned int>(server_version / 10000),
|
|
||||||
static_cast<unsigned int>((server_version % 10000) / 100),
|
|
||||||
static_cast<unsigned int>(server_version % 100)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::basic_type oid2type(Oid oid);
|
|
||||||
|
|
||||||
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> postgres_connection::fetch(const sql::query_context &context) {
|
|
||||||
PGresult *res = PQexec(conn_, context.sql.c_str());
|
|
||||||
|
|
||||||
if (is_result_error(res)) {
|
|
||||||
return utils::failure(make_error(sql::error_code::FETCH_FAILED, res, conn_, "Failed to fetch", context.sql));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<object::attribute> prototype = context.prototype;
|
|
||||||
|
|
||||||
const int num_col = PQnfields(res);
|
|
||||||
if (prototype.size() != static_cast<size_t>(num_col)) {
|
|
||||||
return utils::failure(make_error(sql::error_code::FETCH_FAILED, res, conn_, "Number of received columns doesn't match expected columns.", context.sql));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < num_col; ++i) {
|
for (int i = 0; i < num_col; ++i) {
|
||||||
if (!prototype.at(i).is_null()) {
|
const char *col_name = PQfname(res, i);
|
||||||
continue;
|
auto type = PQftype(res, i);
|
||||||
|
auto size = PQfmod(res, i);
|
||||||
|
prototype.emplace_back(col_name);
|
||||||
}
|
}
|
||||||
const auto type = oid2type(PQftype(res, i));
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), std::move(prototype)));
|
||||||
// const char *col_name = PQfname(res, i);
|
|
||||||
// const auto size = PQfmod(res, i);
|
|
||||||
prototype.at(i).change_type(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils::ok(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), prototype));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string postgres_connection::generate_statement_name(const sql::query_context &query) {
|
std::string postgres_connection::generate_statement_name(const sql::query_context &query)
|
||||||
|
{
|
||||||
std::stringstream name;
|
std::stringstream name;
|
||||||
name << query.table_name << "_" << query.command_name;
|
name << query.table.name << "_" << query.command_name;
|
||||||
auto result = statement_name_map_.find(name.str());
|
auto result = postgres_connection::statement_name_map_.find(name.str());
|
||||||
|
|
||||||
if (result == statement_name_map_.end()) {
|
if (result == postgres_connection::statement_name_map_.end()) {
|
||||||
result = statement_name_map_.insert(std::make_pair(name.str(), 0)).first;
|
result = postgres_connection::statement_name_map_.insert(std::make_pair(name.str(), 0)).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
name << "_" << ++result->second;
|
name << "_" << ++result->second;
|
||||||
|
|
@ -122,169 +77,112 @@ std::string postgres_connection::generate_statement_name(const sql::query_contex
|
||||||
return name.str();
|
return name.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<std::unique_ptr<sql::statement_impl>, utils::error> postgres_connection::prepare(const sql::query_context &context) {
|
std::unique_ptr<sql::statement_impl> postgres_connection::prepare(sql::query_context context)
|
||||||
auto statement_name = generate_statement_name(context);
|
{
|
||||||
|
auto statement_name = postgres_connection::generate_statement_name(context);
|
||||||
|
|
||||||
const PGresult *result = PQprepare(conn_, statement_name.c_str(), context.sql.c_str(),
|
PGresult *result = PQprepare(conn_, statement_name.c_str(), context.sql.c_str(), static_cast<int>(context.bind_vars.size()), nullptr);
|
||||||
static_cast<int>(context.bind_vars.size()), nullptr);
|
|
||||||
|
|
||||||
if (is_result_error(result)) {
|
throw_postgres_error(result, conn_, "postgres", context.sql);
|
||||||
return utils::failure(make_error(sql::error_code::PREPARE_FAILED, result, conn_, "Failed to prepare", context.sql));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<sql::statement_impl> s(std::make_unique<postgres_statement>(conn_, statement_name, context));
|
return std::make_unique<postgres_statement>(conn_, result, statement_name, std::move(context));
|
||||||
return utils::ok(std::move(s));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<size_t, utils::error> postgres_connection::execute(const std::string &stmt) {
|
size_t postgres_connection::execute(const std::string &stmt)
|
||||||
|
{
|
||||||
PGresult *res = PQexec(conn_, stmt.c_str());
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK &&
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
status != PGRES_TUPLES_OK) {
|
|
||||||
return utils::failure(make_error(sql::error_code::FAILURE, res, conn_, "Failed to execute", stmt));
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto affected_rows = utils::to<size_t>(PQcmdTuples(res));
|
const auto affected_rows = sql::to_long_long(PQcmdTuples(res));
|
||||||
|
|
||||||
PQclear(res);
|
PQclear(res);
|
||||||
|
|
||||||
return utils::ok(static_cast<size_t>(affected_rows));
|
return affected_rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::basic_type oid2type(const Oid oid) {
|
sql::data_type_t string2type(const char *type)
|
||||||
switch (oid) {
|
{
|
||||||
case 16:
|
|
||||||
return utils::basic_type::type_bool;
|
|
||||||
case 17:
|
|
||||||
return utils::basic_type::type_blob;
|
|
||||||
case 18:
|
|
||||||
return utils::basic_type::type_int8;
|
|
||||||
case 21:
|
|
||||||
return utils::basic_type::type_int16;
|
|
||||||
case 23:
|
|
||||||
return utils::basic_type::type_int32;
|
|
||||||
case 20:
|
|
||||||
return utils::basic_type::type_int64;
|
|
||||||
case 25:
|
|
||||||
return utils::basic_type::type_text;
|
|
||||||
case 1043:
|
|
||||||
return utils::basic_type::type_varchar;
|
|
||||||
case 700:
|
|
||||||
return utils::basic_type::type_float;
|
|
||||||
case 701:
|
|
||||||
return utils::basic_type::type_double;
|
|
||||||
case 1082:
|
|
||||||
return utils::basic_type::type_date;
|
|
||||||
case 1114:
|
|
||||||
return utils::basic_type::type_time;
|
|
||||||
default:
|
|
||||||
return utils::basic_type::type_null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::basic_type string2type(const char *type) {
|
|
||||||
if (strcmp(type, "int2") == 0) {
|
if (strcmp(type, "int2") == 0) {
|
||||||
return utils::basic_type::type_int16;
|
return sql::data_type_t::type_short;
|
||||||
} else if (strcmp(type, "int4") == 0) {
|
} else if (strcmp(type, "int4") == 0) {
|
||||||
return utils::basic_type::type_int32;
|
return sql::data_type_t::type_int;
|
||||||
} else if (strcmp(type, "int8") == 0) {
|
} else if (strcmp(type, "int8") == 0) {
|
||||||
return utils::basic_type::type_int64;
|
return sql::data_type_t::type_long_long;
|
||||||
} else if (strcmp(type, "bool") == 0) {
|
} else if (strncmp(type, "int8", 6) == 0) {
|
||||||
return utils::basic_type::type_bool;
|
return sql::data_type_t::type_long_long;
|
||||||
} else if (strcmp(type, "date") == 0) {
|
} else if (strcmp(type, "date") == 0) {
|
||||||
return utils::basic_type::type_date;
|
return sql::data_type_t::type_date;
|
||||||
} else if (strcmp(type, "timestamp") == 0) {
|
} else if (strncmp(type, "timestamp", 8) == 0) {
|
||||||
return utils::basic_type::type_time;
|
return sql::data_type_t::type_time;
|
||||||
} else if (strcmp(type, "float4") == 0) {
|
} else if (strcmp(type, "float4") == 0) {
|
||||||
return utils::basic_type::type_float;
|
return sql::data_type_t::type_float;
|
||||||
} else if (strcmp(type, "float8") == 0) {
|
} else if (strcmp(type, "float8") == 0) {
|
||||||
return utils::basic_type::type_double;
|
return sql::data_type_t::type_double;
|
||||||
} else if (strncmp(type, "varchar", 7) == 0) {
|
} else if (strncmp(type, "varchar", 7) == 0) {
|
||||||
return utils::basic_type::type_varchar;
|
return sql::data_type_t::type_varchar;
|
||||||
} else if (strcmp(type, "character varying") == 0) {
|
} else if (strncmp(type, "character varying", 7) == 0) {
|
||||||
return utils::basic_type::type_varchar;
|
return sql::data_type_t::type_varchar;
|
||||||
} else if (strcmp(type, "text") == 0) {
|
} else if (strncmp(type, "text", 0) == 0) {
|
||||||
return utils::basic_type::type_text;
|
return sql::data_type_t::type_text;
|
||||||
} else if (strcmp(type, "bytea") == 0) {
|
|
||||||
return utils::basic_type::type_blob;
|
|
||||||
} else {
|
} else {
|
||||||
return utils::basic_type::type_null;
|
return sql::data_type_t::type_unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<std::vector<object::attribute>, utils::error> postgres_connection::describe(const std::string &table) {
|
std::vector<sql::column_definition> postgres_connection::describe(const std::string &table)
|
||||||
const std::string stmt(
|
{
|
||||||
"SELECT ordinal_position, column_name, udt_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema='public' AND table_name='"
|
std::string stmt(
|
||||||
+ table + "'");
|
"SELECT ordinal_position, column_name, udt_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema='public' AND table_name='" + table + "'");
|
||||||
|
|
||||||
PGresult *res = PQexec(conn_, stmt.c_str());
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
if (is_result_error(res)) {
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
return utils::failure(make_error(sql::error_code::DESCRIBE_FAILED, res, conn_, "Failed to describe", stmt));
|
|
||||||
}
|
|
||||||
|
|
||||||
postgres_result_reader reader(res);
|
postgres_result_reader reader(res);
|
||||||
std::vector<object::attribute> prototype;
|
std::vector<sql::column_definition> prototype;
|
||||||
while (auto fetched = reader.fetch()) {
|
while (reader.fetch()) {
|
||||||
if (!fetched.is_ok()) {
|
|
||||||
return utils::failure(fetched.release_error());
|
|
||||||
}
|
|
||||||
if (!*fetched) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
char *end = nullptr;
|
char *end = nullptr;
|
||||||
// Todo: Handle error
|
// Todo: Handle error
|
||||||
// auto index = strtoul(reader.column(0), &end, 10) - 1;
|
auto index = strtoul(reader.column(0), &end, 10) - 1;
|
||||||
std::string name = reader.column(1);
|
std::string name = reader.column(1);
|
||||||
|
|
||||||
// Todo: extract size
|
// Todo: extract size
|
||||||
auto type = (string2type(reader.column(2)));
|
auto type = (string2type(reader.column(2)));
|
||||||
end = nullptr;
|
end = nullptr;
|
||||||
object::null_option_type null_opt{object::null_option_type::Nullable};
|
sql::null_option null_opt{sql::null_option::NULLABLE};
|
||||||
if (strtoul(reader.column(4), &end, 10) == 0) {
|
if (strtoul(reader.column(4), &end, 10) == 0) {
|
||||||
null_opt = object::null_option_type::NotNull;
|
null_opt = sql::null_option::NOT_NULL;
|
||||||
}
|
}
|
||||||
// f.default_value(res->column(4));
|
// f.default_value(res->column(4));
|
||||||
prototype.emplace_back(name, type, utils::null_attributes, null_opt);
|
prototype.emplace_back(name, type, utils::null_attributes, null_opt, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils::ok(prototype);
|
return std::move(prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<bool, utils::error> postgres_connection::exists(const std::string &schema_name, const std::string &table_name) {
|
bool postgres_connection::exists(const std::string &schema_name, const std::string &table_name)
|
||||||
const std::string stmt(
|
{
|
||||||
"SELECT 1 FROM information_schema.tables WHERE table_schema = '" + schema_name + "' AND table_name = '" + table_name
|
std::string stmt("SELECT 1 FROM information_schema.tables WHERE table_schema = '" + schema_name + "' AND table_name = '" + table_name + "'");
|
||||||
+ "'");
|
|
||||||
|
|
||||||
PGresult *res = PQexec(conn_, stmt.c_str());
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
if (is_result_error(res)) {
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
return utils::failure(make_error(sql::error_code::TABLE_EXISTS_FAILED, res, conn_, "Failed check if table exists", stmt));
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto result = utils::to<size_t>(PQcmdTuples(res));
|
return sql::to_long_long(PQcmdTuples(res)) == 1;
|
||||||
if (!result) {
|
|
||||||
return utils::failure(make_error(sql::error_code::FAILURE, res, conn_, "Failed to convert result value", stmt));
|
|
||||||
}
|
|
||||||
return utils::ok(*result == 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string postgres_connection::to_escaped_string(const utils::blob& value) const
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
MATADOR_POSTGRES_API matador::sql::connection_impl *create_database(const matador::sql::connection_info &info)
|
||||||
{
|
{
|
||||||
size_t escapedDataLength;
|
|
||||||
unsigned char *escapedData = PQescapeByteaConn(conn_, value.data(), value.size(), &escapedDataLength);
|
|
||||||
|
|
||||||
return {reinterpret_cast<char*>(escapedData), escapedDataLength-1};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
MATADOR_POSTGRES_API matador::sql::connection_impl *create_database(const matador::sql::connection_info &info) {
|
|
||||||
return new matador::backends::postgres::postgres_connection(info);
|
return new matador::backends::postgres::postgres_connection(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
MATADOR_POSTGRES_API void destroy_database(matador::sql::connection_impl *db) {
|
MATADOR_POSTGRES_API void destroy_database(matador::sql::connection_impl *db)
|
||||||
|
{
|
||||||
delete db;
|
delete db;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,20 @@
|
||||||
|
|
||||||
#include "matador/sql/dialect_builder.hpp"
|
#include "matador/sql/dialect_builder.hpp"
|
||||||
|
|
||||||
#include "matador/utils/basic_types.hpp"
|
|
||||||
|
|
||||||
[[maybe_unused]] const matador::sql::dialect *get_dialect()
|
[[maybe_unused]] const matador::sql::dialect *get_dialect()
|
||||||
{
|
{
|
||||||
using namespace matador::sql;
|
using namespace matador::sql;
|
||||||
const static dialect d = dialect_builder::builder()
|
const static dialect d = dialect_builder::builder()
|
||||||
.create()
|
.create()
|
||||||
.with_placeholder_func([](const size_t index) {
|
.with_placeholder_func([](size_t index) {
|
||||||
return "$" + std::to_string(index);
|
return "$" + std::to_string(index);
|
||||||
})
|
})
|
||||||
.with_token_replace_map({
|
.with_token_replace_map({
|
||||||
{dialect_token::BeginBinaryData, "'\\x"}
|
{dialect::token_t::BEGIN_BINARY_DATA, "E'\\"}
|
||||||
})
|
})
|
||||||
.with_data_type_replace_map({
|
.with_data_type_replace_map({
|
||||||
{matador::utils::basic_type::type_int8, "SMALLINT"},
|
{data_type_t::type_blob, "BYTEA"}
|
||||||
{matador::utils::basic_type::type_uint8, "SMALLINT"},
|
|
||||||
{matador::utils::basic_type::type_float, "REAL"},
|
|
||||||
{matador::utils::basic_type::type_double, "DOUBLE PRECISION"},
|
|
||||||
{matador::utils::basic_type::type_time, "TIMESTAMP"},
|
|
||||||
{matador::utils::basic_type::type_blob, "BYTEA"}
|
|
||||||
})
|
})
|
||||||
.with_bool_strings("TRUE", "FALSE")
|
|
||||||
.with_default_schema_name("public")
|
.with_default_schema_name("public")
|
||||||
.build();
|
.build();
|
||||||
return &d;
|
return &d;
|
||||||
|
|
|
||||||
|
|
@ -6,30 +6,6 @@
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
utils::error make_error(const sql::error_code ec, const PGresult *res, const PGconn *db, const std::string &msg,
|
|
||||||
const std::string &sql) {
|
|
||||||
utils::error err(ec, msg);
|
|
||||||
err.add_error_info("dbms", "postgres");
|
|
||||||
if (!sql.empty()) {
|
|
||||||
err.add_error_info("sql", sql);
|
|
||||||
}
|
|
||||||
if (res == nullptr) {
|
|
||||||
err.add_error_info("message", PQerrorMessage(db));
|
|
||||||
} else if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
|
|
||||||
err.add_error_info("message", PQresultErrorField(res, PG_DIAG_SQLSTATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_result_error(const PGresult *res) {
|
|
||||||
if (res == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const auto status = PQresultStatus(res);
|
|
||||||
return status != PGRES_TUPLES_OK && status != PGRES_COMMAND_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void throw_postgres_error(const char *what, const std::string &source)
|
void throw_postgres_error(const char *what, const std::string &source)
|
||||||
{
|
{
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
|
|
@ -50,9 +26,8 @@ void throw_postgres_error(PGresult *res, PGconn *db, const std::string &source,
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "postgres error (" << source << ", " << PQerrorMessage(db) << ": " << sql;
|
msg << "postgres error (" << source << ", " << PQerrorMessage(db) << ": " << sql;
|
||||||
throw std::logic_error(msg.str());
|
throw std::logic_error(msg.str());
|
||||||
}
|
} else if ((PQresultStatus(res) != PGRES_COMMAND_OK &&
|
||||||
if (const auto status = PQresultStatus(res); status != PGRES_COMMAND_OK &&
|
PQresultStatus(res) != PGRES_TUPLES_OK)) {
|
||||||
status != PGRES_TUPLES_OK) {
|
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "postgres error (" << source << ", " << PQresultErrorField(res, PG_DIAG_SQLSTATE) << ") " << PQerrorMessage(db) << ": " << sql;
|
msg << "postgres error (" << source << ", " << PQresultErrorField(res, PG_DIAG_SQLSTATE) << ") " << PQerrorMessage(db) << ": " << sql;
|
||||||
throw std::logic_error(msg.str());
|
throw std::logic_error(msg.str());
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,29 @@
|
||||||
#include "postgres_parameter_binder.h"
|
#include "postgres_parameter_binder.h"
|
||||||
|
|
||||||
#include "matador/utils/string.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template<class T>
|
|
||||||
void bind_value(postgres_parameter_binder::bind_data &data, const size_t index, const T &x) {
|
template < class T >
|
||||||
data.strings[index] = std::to_string(x);
|
void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t index, T &x)
|
||||||
data.values[index] = data.strings[index].c_str();
|
{
|
||||||
data.lengths[index] = static_cast<int>(data.strings[index].size());
|
strings[index] = std::to_string(x);
|
||||||
data.formats[index] = 0;
|
params[index] = strings[index].c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// template<>
|
template <>
|
||||||
// void bind_value(postgres_parameter_binder::bind_data &data, size_t index, const char &x) {
|
void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t index, char &x)
|
||||||
// data.strings[index] = std::to_string(x);
|
{
|
||||||
// data.values[index] = data.strings[index].data();
|
strings[index] = std::to_string(x);
|
||||||
// data.formats[index] = 0;
|
params[index] = strings[index].data();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// template<>
|
template <>
|
||||||
// void bind_value(postgres_parameter_binder::bind_data &data, size_t index, const unsigned char &x) {
|
void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t index, unsigned char &x)
|
||||||
// data.strings[index] = std::to_string(x);
|
{
|
||||||
// data.values[index] = data.strings[index].data();
|
strings[index] = std::to_string(x);
|
||||||
// data.formats[index] = 0;
|
params[index] = strings[index].data();
|
||||||
// }
|
}
|
||||||
|
|
||||||
//template <>
|
//template <>
|
||||||
//void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t &index, const matador::date &x)
|
//void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t &index, const matador::date &x)
|
||||||
|
|
@ -43,111 +40,108 @@ void bind_value(postgres_parameter_binder::bind_data &data, const size_t index,
|
||||||
// params[index] = strings[index].c_str();
|
// params[index] = strings[index].c_str();
|
||||||
// ++index;
|
// ++index;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
postgres_parameter_binder::bind_data::bind_data(const size_t size)
|
postgres_parameter_binder::postgres_parameter_binder(size_t size)
|
||||||
: strings(size)
|
: strings_(size)
|
||||||
, bytes(size)
|
, params_(size)
|
||||||
, values(size)
|
|
||||||
, lengths(size)
|
|
||||||
, formats(size)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
postgres_parameter_binder::postgres_parameter_binder(const size_t size)
|
void postgres_parameter_binder::bind(size_t pos, char i)
|
||||||
: bind_data_(size)
|
{
|
||||||
{}
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const int8_t &x) {
|
|
||||||
detail::bind_value(bind_data_, pos, x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const int16_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, short i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const int32_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, int i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const int64_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, long i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const uint8_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, long long int i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const uint16_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, unsigned char i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const uint32_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, unsigned short i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const uint64_t &x) {
|
void postgres_parameter_binder::bind(size_t pos, unsigned int i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const bool &x) {
|
void postgres_parameter_binder::bind(size_t pos, unsigned long i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const float &x) {
|
void postgres_parameter_binder::bind(size_t pos, unsigned long long int i)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const double &x) {
|
void postgres_parameter_binder::bind(size_t pos, bool b)
|
||||||
detail::bind_value(bind_data_, pos, x);
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const char *x) {
|
void postgres_parameter_binder::bind(size_t pos, float d)
|
||||||
bind_data_.values[pos] = x;
|
{
|
||||||
bind_data_.lengths[pos] = static_cast<int>(std::strlen(x));
|
detail::bind_value(strings_, params_, pos, d);
|
||||||
bind_data_.formats[pos] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const char *x, size_t /*size*/) {
|
void postgres_parameter_binder::bind(size_t pos, double d)
|
||||||
bind_data_.values[pos] = x;
|
{
|
||||||
bind_data_.lengths[pos] = static_cast<int>(std::strlen(x));
|
detail::bind_value(strings_, params_, pos, d);
|
||||||
bind_data_.formats[pos] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const std::string &x) {
|
void postgres_parameter_binder::bind(size_t pos, const char *str)
|
||||||
bind_data_.values[pos] = x.data();
|
{
|
||||||
bind_data_.lengths[pos] = static_cast<int>(x.size());
|
params_[pos] = str;
|
||||||
bind_data_.formats[pos] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const std::string &x, size_t /*size*/) {
|
void postgres_parameter_binder::bind(size_t pos, const char *str, size_t size)
|
||||||
write_value(pos, x);
|
{
|
||||||
|
params_[pos] = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const time &/*x*/) {
|
void postgres_parameter_binder::bind(size_t pos, const std::string &str)
|
||||||
// bind_data_.strings[pos] = utils::to_string(x, "%Y-%m-%d %T.%f");
|
{
|
||||||
bind_data_.values[pos] = bind_data_.strings[pos].data();
|
strings_[pos] = str;
|
||||||
bind_data_.lengths[pos] = static_cast<int>(bind_data_.strings[pos].size());
|
params_[pos] = strings_[pos].c_str();
|
||||||
bind_data_.formats[pos] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const date &/*x*/) {
|
void postgres_parameter_binder::bind(size_t pos, const std::string &str, size_t size)
|
||||||
// bind_data_.strings[pos] = utils::to_string(x, utils::date_format::ISO8601);
|
{
|
||||||
bind_data_.values[pos] = bind_data_.strings[pos].data();
|
bind(pos, str);
|
||||||
bind_data_.lengths[pos] = static_cast<int>(bind_data_.strings[pos].size());
|
|
||||||
bind_data_.formats[pos] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t pos, const utils::blob &x) {
|
void postgres_parameter_binder::bind(size_t pos, const utils::blob &blob)
|
||||||
bind_data_.bytes[pos] = x;
|
{
|
||||||
bind_data_.values[pos] = reinterpret_cast<char*>(bind_data_.bytes[pos].data());
|
params_[pos] = "";
|
||||||
bind_data_.lengths[pos] = static_cast<int>(bind_data_.bytes[pos].size());
|
|
||||||
bind_data_.formats[pos] = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void postgres_parameter_binder::write_value(const size_t /*pos*/, const utils::value &/*x*/, size_t /*size*/) {}
|
const std::vector<const char *> &postgres_parameter_binder::params() const
|
||||||
|
{
|
||||||
const postgres_parameter_binder::bind_data &postgres_parameter_binder::params() const {
|
return params_;
|
||||||
return bind_data_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include "postgres_result_reader.hpp"
|
#include "postgres_result_reader.hpp"
|
||||||
|
|
||||||
#include "matador/utils/convert.hpp"
|
#include "matador/sql/to_value.hpp"
|
||||||
#include "matador/utils/value.hpp"
|
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
|
@ -23,215 +22,14 @@ size_t postgres_result_reader::column_count() const
|
||||||
return column_count_;
|
return column_count_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *postgres_result_reader::column( const size_t index) const
|
const char *postgres_result_reader::column(size_t index) const
|
||||||
{
|
{
|
||||||
return PQgetvalue(result_, static_cast<int>(row_index_), static_cast<int>(index));
|
return PQgetvalue(result_, static_cast<int>(row_index_), static_cast<int>(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<bool, utils::error> postgres_result_reader::fetch() { return utils::ok(++row_index_ < row_count_); }
|
bool postgres_result_reader::fetch()
|
||||||
|
|
||||||
size_t postgres_result_reader::start_column_index() const {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::unshift() {
|
|
||||||
if (row_index_ > 0) {
|
|
||||||
--row_index_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, int8_t &value) {
|
|
||||||
if (auto res = utils::to<int8_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, int16_t &value) {
|
|
||||||
if (auto res = utils::to<int16_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, int32_t &value) {
|
|
||||||
if (auto res = utils::to<int32_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, int64_t &value) {
|
|
||||||
if (auto res = utils::to<int64_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, uint8_t &value) {
|
|
||||||
if (auto res = utils::to<uint8_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, uint16_t &value) {
|
|
||||||
if (auto res = utils::to<uint16_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, uint32_t &value) {
|
|
||||||
if (auto res = utils::to<uint32_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, uint64_t &value) {
|
|
||||||
if (auto res = utils::to<uint64_t>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, bool &value) {
|
|
||||||
if (auto res = utils::to<bool>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, float &value) {
|
|
||||||
if (auto res = utils::to<float>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, double &value) {
|
|
||||||
if (auto res = utils::to<double>(column(index)); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t /*index*/, time &/*value*/) {
|
|
||||||
// if (const auto val = column(index); strlen(val) > 0) {
|
|
||||||
// value = time::parse(val, "%Y-%m-%d %T.%f");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t /*index*/, date &/*value*/) {
|
|
||||||
// if (const auto val = column(index); strlen(val) > 0) {
|
|
||||||
// value.set(val, matador::utils::date_format::ISO8601);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, char *value, size_t size) {
|
|
||||||
auto val = column(index);
|
|
||||||
if (const size_t len = strlen(val); len > size) {
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
strncpy_s(value, size, val, len);
|
|
||||||
#else
|
|
||||||
strncpy(value, val, size);
|
|
||||||
#endif
|
|
||||||
value[size-1] = '\n';
|
|
||||||
} else {
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
strcpy_s(value, size, val);
|
|
||||||
#else
|
|
||||||
strcpy(value, val);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, std::string &value) {
|
|
||||||
value.assign(column(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, std::string &value, size_t /*s*/) {
|
|
||||||
value.assign(column(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value( const char* /*id*/, const size_t index, utils::blob& value )
|
|
||||||
{
|
{
|
||||||
const auto *data = reinterpret_cast<const unsigned char*>(column(index));
|
return ++row_index_ < row_count_;
|
||||||
// auto length = PQgetlength(result_, row_index_, static_cast<int>(index));
|
|
||||||
|
|
||||||
size_t length;
|
|
||||||
unsigned char* unescaped = PQunescapeBytea(data, &length);
|
|
||||||
|
|
||||||
value.assign(unescaped, unescaped+length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
|
||||||
void set_value(const char* str, utils::value& value) {
|
|
||||||
if (const auto res = utils::to<Type>(str); res.is_ok()) {
|
|
||||||
value = res.value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
|
||||||
void set_value<utils::blob>(const char* str, utils::value& value) {
|
|
||||||
size_t length;
|
|
||||||
unsigned char* unescaped = PQunescapeBytea(reinterpret_cast<const unsigned char*>(str), &length);
|
|
||||||
|
|
||||||
value = utils::blob(unescaped, unescaped+length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void postgres_result_reader::read_value(const char * /*id*/, const size_t index, utils::value &val, size_t) {
|
|
||||||
switch (val.type()) {
|
|
||||||
case utils::basic_type::type_int8:
|
|
||||||
set_value<int8_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_int16:
|
|
||||||
set_value<int16_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_int32:
|
|
||||||
set_value<int32_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_int64:
|
|
||||||
set_value<int64_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_uint8:
|
|
||||||
set_value<uint8_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_uint16:
|
|
||||||
set_value<uint16_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_uint32:
|
|
||||||
set_value<uint32_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_uint64:
|
|
||||||
set_value<uint64_t>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_float:
|
|
||||||
set_value<float>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_double:
|
|
||||||
set_value<double>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_bool:
|
|
||||||
set_value<bool>(column(index), val);
|
|
||||||
break;
|
|
||||||
case utils::basic_type::type_text:
|
|
||||||
case utils::basic_type::type_varchar: {
|
|
||||||
if (const auto *column_value = column(index); column_value == nullptr) {
|
|
||||||
val = std::string{};
|
|
||||||
} else {
|
|
||||||
val = std::string{column_value};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case utils::basic_type::type_time:
|
|
||||||
case utils::basic_type::type_date: {
|
|
||||||
val = std::string{column(index)};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case utils::basic_type::type_null: {
|
|
||||||
val = nullptr_t{};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case utils::basic_type::type_blob: {
|
|
||||||
set_value<utils::blob>(column(index), val);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::attribute_reader &postgres_result_reader::result_binder() {
|
|
||||||
return empty_binder_;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace matador::backends::postgres
|
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,40 @@
|
||||||
#include "postgres_statement.hpp"
|
#include "postgres_statement.hpp"
|
||||||
#include "postgres_error.hpp"
|
#include "postgres_error.hpp"
|
||||||
#include "postgres_parameter_binder.h"
|
|
||||||
#include "postgres_result_reader.hpp"
|
#include "postgres_result_reader.hpp"
|
||||||
|
|
||||||
namespace matador::backends::postgres {
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
postgres_statement::postgres_statement(PGconn *db, std::string name, const sql::query_context &query)
|
postgres_statement::postgres_statement(PGconn *db, PGresult *result, std::string name, const sql::query_context &query)
|
||||||
: statement_impl(query, 0)
|
: statement_impl(query)
|
||||||
, db_(db)
|
, db_(db)
|
||||||
|
, result_(result)
|
||||||
, name_(std::move(name))
|
, name_(std::move(name))
|
||||||
|
, binder_(query_.bind_vars.size())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
utils::result<size_t, utils::error> postgres_statement::execute(const sql::parameter_binder& bindings) {
|
size_t postgres_statement::execute()
|
||||||
const auto* postgres_bindings = dynamic_cast<const postgres_parameter_binder*>(&bindings);
|
{
|
||||||
if (!postgres_bindings) {
|
PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast<int>(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0);
|
||||||
return utils::failure(utils::error(sql::error_code::EXECUTE_FAILED, "Failed to cast bindings to postgres bindings"));
|
|
||||||
}
|
|
||||||
PGresult *res = PQexecPrepared(db_,
|
|
||||||
name_.c_str(),
|
|
||||||
static_cast<int>(postgres_bindings->params().values.size()),
|
|
||||||
postgres_bindings->params().values.data(),
|
|
||||||
postgres_bindings->params().lengths.data(),
|
|
||||||
postgres_bindings->params().formats.data(),
|
|
||||||
0);
|
|
||||||
|
|
||||||
if (is_result_error(res)) {
|
throw_postgres_error(res, db_, "postgres", query_.sql);
|
||||||
return utils::failure(make_error(sql::error_code::EXECUTE_FAILED, res, db_, "Failed to execute statement", query_.sql));
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto *tuples = PQcmdTuples(res);
|
return std::stoul(PQcmdTuples(res));
|
||||||
if (strlen(tuples) == 0) {
|
|
||||||
return utils::ok(static_cast<size_t>(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils::ok(static_cast<size_t>(std::stoul(tuples)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::result<std::unique_ptr<sql::query_result_impl>, utils::error> postgres_statement::fetch(const sql::parameter_binder& bindings) {
|
std::unique_ptr<sql::query_result_impl> postgres_statement::fetch()
|
||||||
const auto* postgres_bindings = dynamic_cast<const postgres_parameter_binder*>(&bindings);
|
{
|
||||||
if (!postgres_bindings) {
|
PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast<int>(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0);
|
||||||
return utils::failure(utils::error(sql::error_code::EXECUTE_FAILED, "Failed to cast bindings to postgres bindings"));
|
|
||||||
}
|
|
||||||
PGresult *res = PQexecPrepared(db_,
|
|
||||||
name_.c_str(),
|
|
||||||
static_cast<int>(postgres_bindings->params().values.size()),
|
|
||||||
postgres_bindings->params().values.data(),
|
|
||||||
postgres_bindings->params().lengths.data(),
|
|
||||||
postgres_bindings->params().formats.data(),
|
|
||||||
0);
|
|
||||||
|
|
||||||
if (is_result_error(res)) {
|
throw_postgres_error(res, db_, "postgres", query_.sql);
|
||||||
return utils::failure(make_error(sql::error_code::FETCH_FAILED, res, db_, "Failed to fetch statement", query_.sql));
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils::ok(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), query_.prototype));
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), std::move(query_.prototype)));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<utils::attribute_writer> postgres_statement::create_binder() const {
|
void postgres_statement::reset() {}
|
||||||
return std::make_unique<postgres_parameter_binder>(query_.bind_vars.size());
|
|
||||||
|
sql::parameter_binder& postgres_statement::binder()
|
||||||
|
{
|
||||||
|
return binder_;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,54 +1,45 @@
|
||||||
CPMAddPackage("gh:catchorg/Catch2@3.7.1")
|
Include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.5.4 # or a later release
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||||
|
include(CTest)
|
||||||
|
include(Catch)
|
||||||
|
|
||||||
set(POSTGRES_CONNECTION_STRING "postgres://test:test123!@127.0.0.1:15442/matador")
|
set(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:15432/test")
|
||||||
#set(POSTGRES_CONNECTION_STRING "postgres://test:test123!@127.0.0.1:5432/matador")
|
|
||||||
|
|
||||||
configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE)
|
configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE)
|
||||||
|
|
||||||
message(STATUS "postgresql connection string: ${POSTGRES_CONNECTION_STRING}")
|
message(STATUS "postgresql connection string: ${POSTGRES_CONNECTION_STRING}")
|
||||||
|
|
||||||
set(TEST_SOURCES
|
set(TEST_SOURCES
|
||||||
../../../test/models/coordinate.hpp
|
../../tests/QueryTest.cpp
|
||||||
../../../test/models/location.hpp
|
../../tests/ConnectionTest.cpp
|
||||||
../../../test/models/types.hpp
|
../../tests/QueryRecordTest.cpp
|
||||||
../../../test/backends/ColorEnumTraits.cpp
|
../../tests/StatementTest.cpp
|
||||||
../../../test/backends/ColorEnumTraits.hpp
|
../../tests/TypeTraitsTest.cpp
|
||||||
../../../test/backends/ConnectionTest.cpp
|
../../tests/StatementCacheTest.cpp
|
||||||
../../../test/backends/QueryBasicTest.cpp
|
../../tests/SessionTest.cpp)
|
||||||
../../../test/backends/QueryFixture.cpp
|
|
||||||
../../../test/backends/QueryFixture.hpp
|
|
||||||
../../../test/backends/QueryRecordTest.cpp
|
|
||||||
../../../test/backends/QueryStatementTests.cpp
|
|
||||||
../../../test/backends/QueryTest.cpp
|
|
||||||
../../../test/backends/SchemaFixture.cpp
|
|
||||||
../../../test/backends/SchemaFixture.hpp
|
|
||||||
../../../test/backends/SchemaTest.cpp
|
|
||||||
../../../test/backends/SessionFixture.cpp
|
|
||||||
../../../test/backends/SessionFixture.hpp
|
|
||||||
../../../test/backends/SessionTest.cpp
|
|
||||||
../../../test/backends/StatementCacheTest.cpp
|
|
||||||
../../../test/backends/StatementTest.cpp
|
|
||||||
../../../test/backends/TypeTraitsTest.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set(LIBRARY_TEST_TARGET PostgresTests)
|
set(LIBRARY_TEST_TARGET postgres_tests)
|
||||||
|
|
||||||
add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES})
|
add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES})
|
||||||
|
|
||||||
target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE
|
target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE
|
||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
matador-core
|
matador
|
||||||
matador-orm
|
|
||||||
${CMAKE_DL_LIBS}
|
${CMAKE_DL_LIBS}
|
||||||
${PostgreSQL_LIBRARY})
|
${PostgreSQL_LIBRARY})
|
||||||
|
|
||||||
add_dependencies(${LIBRARY_TEST_TARGET} matador-postgres)
|
|
||||||
|
|
||||||
target_include_directories(${LIBRARY_TEST_TARGET}
|
target_include_directories(${LIBRARY_TEST_TARGET}
|
||||||
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/include
|
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/include
|
||||||
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/test
|
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/test
|
||||||
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
#catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (PostgreSQL)")
|
catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (PostgreSQL)")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
set(HEADER
|
||||||
|
include/sqlite_connection.hpp
|
||||||
|
include/sqlite_error.hpp
|
||||||
|
include/sqlite_dialect.hpp
|
||||||
|
include/sqlite_result_reader.hpp
|
||||||
|
include/sqlite_statement.hpp
|
||||||
|
include/sqlite_parameter_binder.h
|
||||||
|
include/sqlite_prepared_result_reader.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/sqlite_connection.cpp
|
||||||
|
src/sqlite_error.cpp
|
||||||
|
src/sqlite_dialect.cpp
|
||||||
|
src/sqlite_result_reader.cpp
|
||||||
|
src/sqlite_statement.cpp
|
||||||
|
src/sqlite_parameter_binder.cpp
|
||||||
|
src/sqlite_prepared_result_reader.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBRARY_TARGET matador-sqlite)
|
||||||
|
|
||||||
|
add_subdirectory(test)
|
||||||
|
|
||||||
|
add_library(${LIBRARY_TARGET} MODULE ${SOURCES} ${HEADER})
|
||||||
|
|
||||||
|
set_target_properties(${LIBRARY_TARGET}
|
||||||
|
PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/backends"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${LIBRARY_TARGET} PRIVATE
|
||||||
|
${PROJECT_SOURCE_DIR}/include
|
||||||
|
${PROJECT_SOURCE_DIR}/backends/sqlite/include
|
||||||
|
${SQLite3_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_TARGET} matador ${SQLite3_LIBRARIES})
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#ifndef QUERY_SQLITE_CONNECTION_HPP
|
||||||
|
#define QUERY_SQLITE_CONNECTION_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#ifdef matador_sqlite_EXPORTS
|
||||||
|
#define MATADOR_SQLITE_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define MATADOR_SQLITE_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#pragma warning(disable: 4355)
|
||||||
|
#else
|
||||||
|
#define MATADOR_SQLITE_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "matador/sql/connection_impl.hpp"
|
||||||
|
|
||||||
|
#include "sqlite_result_reader.hpp"
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
class sqlite_connection : public matador::sql::connection_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit sqlite_connection(const sql::connection_info &info);
|
||||||
|
void open() override;
|
||||||
|
void close() override;
|
||||||
|
bool is_open() override;
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> fetch(const std::string &stmt) override;
|
||||||
|
std::unique_ptr<sql::statement_impl> prepare(sql::query_context query) override;
|
||||||
|
|
||||||
|
size_t execute(const std::string &stmt) override;
|
||||||
|
|
||||||
|
std::vector<sql::column_definition> describe(const std::string& table) override;
|
||||||
|
|
||||||
|
bool exists(const std::string &schema_name, const std::string &table_name) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct fetch_context
|
||||||
|
{
|
||||||
|
std::vector<sql::column_definition> prototype;
|
||||||
|
sqlite_result_reader::rows rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int parse_result(void* param, int column_count, char** values, char** columns);
|
||||||
|
|
||||||
|
fetch_context fetch_internal(const std::string &stmt);
|
||||||
|
|
||||||
|
private:
|
||||||
|
sqlite3 *db_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
MATADOR_SQLITE_API matador::sql::connection_impl* create_database(const matador::sql::connection_info &info);
|
||||||
|
|
||||||
|
MATADOR_SQLITE_API void destroy_database(matador::sql::connection_impl *db);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_SQLITE_CONNECTION_HPP
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef QUERY_SQLITE_DIALECT_HPP
|
||||||
|
#define QUERY_SQLITE_DIALECT_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#ifdef matador_sqlite_EXPORTS
|
||||||
|
#define MATADOR_SQLITE_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define MATADOR_SQLITE_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#pragma warning(disable: 4355)
|
||||||
|
#else
|
||||||
|
#define MATADOR_SQLITE_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "matador/sql/dialect.hpp"
|
||||||
|
|
||||||
|
extern "C" [[maybe_unused]] MATADOR_SQLITE_API const matador::sql::dialect* get_dialect();
|
||||||
|
|
||||||
|
#endif //QUERY_SQLITE_DIALECT_HPP
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef QUERY_SQLITE_ERROR_HPP
|
||||||
|
#define QUERY_SQLITE_ERROR_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct sqlite3;
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source);
|
||||||
|
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source, const std::string &sql);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_SQLITE_ERROR_HPP
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef QUERY_SQLITE_PARAMETER_BINDER_H
|
||||||
|
#define QUERY_SQLITE_PARAMETER_BINDER_H
|
||||||
|
|
||||||
|
#include "matador/sql/parameter_binder.hpp"
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
class sqlite_parameter_binder final : public sql::parameter_binder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit sqlite_parameter_binder(sqlite3 *db, sqlite3_stmt *stmt);
|
||||||
|
|
||||||
|
void bind(size_t pos, char i) override;
|
||||||
|
void bind(size_t pos, short i) override;
|
||||||
|
void bind(size_t pos, int i) override;
|
||||||
|
void bind(size_t pos, long i) override;
|
||||||
|
void bind(size_t pos, long long int i) override;
|
||||||
|
void bind(size_t pos, unsigned char i) override;
|
||||||
|
void bind(size_t pos, unsigned short i) override;
|
||||||
|
void bind(size_t pos, unsigned int i) override;
|
||||||
|
void bind(size_t pos, unsigned long i) override;
|
||||||
|
void bind(size_t pos, unsigned long long int i) override;
|
||||||
|
void bind(size_t pos, bool b) override;
|
||||||
|
void bind(size_t pos, float d) override;
|
||||||
|
void bind(size_t pos, double d) override;
|
||||||
|
void bind(size_t pos, const char *string) override;
|
||||||
|
void bind(size_t pos, const char *str, size_t size) override;
|
||||||
|
void bind(size_t pos, const std::string &str) override;
|
||||||
|
void bind(size_t pos, const std::string &str, size_t size) override;
|
||||||
|
void bind(size_t pos, const utils::blob &blob) override;
|
||||||
|
private:
|
||||||
|
sqlite3 *db_{nullptr};
|
||||||
|
sqlite3_stmt *stmt_{nullptr};
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<std::string> > host_strings_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_SQLITE_PARAMETER_BINDER_H
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef QUERY_SQLITE_PREPARED_RESULT_READER_HPP
|
||||||
|
#define QUERY_SQLITE_PREPARED_RESULT_READER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_result_reader.hpp"
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
class sqlite_prepared_result_reader final : public sql::query_result_reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
sqlite_prepared_result_reader(sqlite3 *db, sqlite3_stmt *stmt);
|
||||||
|
|
||||||
|
[[nodiscard]] size_t column_count() const override;
|
||||||
|
[[nodiscard]] const char *column(size_t index) const override;
|
||||||
|
bool fetch() override;
|
||||||
|
|
||||||
|
void read_value(const char *id, size_t index, char &value) override;
|
||||||
|
void read_value(const char *id, size_t index, short &value) override;
|
||||||
|
void read_value(const char *id, size_t index, int &value) override;
|
||||||
|
void read_value(const char *id, size_t index, long &value) override;
|
||||||
|
void read_value(const char *id, size_t index, long long int &value) override;
|
||||||
|
void read_value(const char *id, size_t index, unsigned char &value) override;
|
||||||
|
void read_value(const char *id, size_t index, unsigned short &value) override;
|
||||||
|
void read_value(const char *id, size_t index, unsigned int &value) override;
|
||||||
|
void read_value(const char *id, size_t index, unsigned long &value) override;
|
||||||
|
void read_value(const char *id, size_t index, unsigned long long int &value) override;
|
||||||
|
void read_value(const char *id, size_t index, bool &value) override;
|
||||||
|
void read_value(const char *id, size_t index, float &value) override;
|
||||||
|
void read_value(const char *id, size_t index, double &value) override;
|
||||||
|
void read_value(const char *id, size_t index, char *value, size_t s) override;
|
||||||
|
void read_value(const char *id, size_t index, std::string &value) override;
|
||||||
|
void read_value(const char *id, size_t index, std::string &value, size_t s) override;
|
||||||
|
void read_value(const char *id, size_t index, sql::value &val, size_t size) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
sqlite3 *db_{nullptr};
|
||||||
|
sqlite3_stmt *stmt_{nullptr};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif //QUERY_SQLITE_PREPARED_RESULT_READER_HPP
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef QUERY_SQLITE_RESULT_READER_HPP
|
||||||
|
#define QUERY_SQLITE_RESULT_READER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_result_reader.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
class sqlite_result_reader final : public sql::query_result_reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using columns = std::vector<char*>;
|
||||||
|
using rows = std::vector<columns>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
sqlite_result_reader(rows result, size_t column_count);
|
||||||
|
~sqlite_result_reader() override;
|
||||||
|
|
||||||
|
[[nodiscard]] size_t column_count() const override;
|
||||||
|
|
||||||
|
[[nodiscard]] const char* column(size_t index) const override;
|
||||||
|
[[nodiscard]] bool fetch() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
rows result_;
|
||||||
|
long long row_index_ = -1;
|
||||||
|
size_t column_count_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_SQLITE_RESULT_READER_HPP
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef QUERY_SQLITE_STATEMENT_HPP
|
||||||
|
#define QUERY_SQLITE_STATEMENT_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/statement_impl.hpp"
|
||||||
|
|
||||||
|
#include "sqlite_parameter_binder.h"
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
class sqlite_statement final : public sql::statement_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
sqlite_statement(sqlite3 *db, sqlite3_stmt *stmt, const sql::query_context &query);
|
||||||
|
~sqlite_statement();
|
||||||
|
|
||||||
|
size_t execute() override;
|
||||||
|
std::unique_ptr<sql::query_result_impl> fetch() override;
|
||||||
|
void reset() override;
|
||||||
|
protected:
|
||||||
|
sql::parameter_binder& binder() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
sqlite3 *db_{nullptr};
|
||||||
|
sqlite3_stmt *stmt_{nullptr};
|
||||||
|
|
||||||
|
sqlite_parameter_binder binder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_SQLITE_STATEMENT_HPP
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
#include "sqlite_connection.hpp"
|
||||||
|
#include "sqlite_error.hpp"
|
||||||
|
#include "sqlite_result_reader.hpp"
|
||||||
|
#include "sqlite_statement.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/record.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
sqlite_connection::sqlite_connection(const sql::connection_info &info)
|
||||||
|
: connection_impl(info) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_connection::open()
|
||||||
|
{
|
||||||
|
if (is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = sqlite3_open(info().database.c_str(), &db_);
|
||||||
|
|
||||||
|
if (ret != SQLITE_OK) {
|
||||||
|
throw_sqlite_error(ret, db_, "open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_connection::close()
|
||||||
|
{
|
||||||
|
int ret = sqlite3_close(db_);
|
||||||
|
|
||||||
|
throw_sqlite_error(ret, db_, "close");
|
||||||
|
|
||||||
|
db_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sqlite_connection::is_open()
|
||||||
|
{
|
||||||
|
return db_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sqlite_connection::parse_result(void* param, int column_count, char** values, char** columns)
|
||||||
|
{
|
||||||
|
auto *context = static_cast<fetch_context*>(param);
|
||||||
|
|
||||||
|
sqlite_result_reader::columns column;
|
||||||
|
for(int i = 0; i < column_count; ++i) {
|
||||||
|
// copy and store column data;
|
||||||
|
if (values[i] == nullptr) {
|
||||||
|
auto val = new char[1];
|
||||||
|
val[0] = '\0';
|
||||||
|
column.push_back(val);
|
||||||
|
} else {
|
||||||
|
size_t size = strlen(values[i]);
|
||||||
|
auto val = new char[size + 1];
|
||||||
|
std::memcpy(val, values[i], size);
|
||||||
|
val[size] = '\0';
|
||||||
|
column.push_back(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context->rows.emplace_back(column);
|
||||||
|
|
||||||
|
if (context->prototype.empty()) {
|
||||||
|
for(int i = 0; i < column_count; ++i) {
|
||||||
|
context->prototype.emplace_back(columns[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite_connection::fetch_context sqlite_connection::fetch_internal(const std::string &stmt)
|
||||||
|
{
|
||||||
|
fetch_context context;
|
||||||
|
char *errmsg = nullptr;
|
||||||
|
const int ret = sqlite3_exec(db_, stmt.c_str(), parse_result, &context, &errmsg);
|
||||||
|
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite", stmt);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sqlite_connection::execute(const std::string &stmt)
|
||||||
|
{
|
||||||
|
char *errmsg = nullptr;
|
||||||
|
int ret = sqlite3_exec(db_, stmt.c_str(), nullptr, nullptr, &errmsg);
|
||||||
|
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite", stmt);
|
||||||
|
|
||||||
|
return sqlite3_changes(db_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> sqlite_connection::fetch(const std::string &stmt)
|
||||||
|
{
|
||||||
|
auto context = fetch_internal(stmt);
|
||||||
|
|
||||||
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<sqlite_result_reader>(std::move(context.rows), context.prototype.size()), std::move(context.prototype)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::statement_impl> sqlite_connection::prepare(sql::query_context query)
|
||||||
|
{
|
||||||
|
sqlite3_stmt *stmt{};
|
||||||
|
int ret = sqlite3_prepare_v2(db_, query.sql.c_str(), static_cast<int>(query.sql.size()), &stmt, nullptr);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_prepare_v2", query.sql);
|
||||||
|
|
||||||
|
return std::make_unique<sqlite_statement>(db_, stmt, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql::data_type_t string2type(const char *type)
|
||||||
|
{
|
||||||
|
if (strncmp(type, "INTEGER", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_int;
|
||||||
|
} else if (strncmp(type, "TINYINT", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_char;
|
||||||
|
} else if (strncmp(type, "SMALLINT", 8) == 0) {
|
||||||
|
return sql::data_type_t::type_short;
|
||||||
|
} else if (strncmp(type, "BIGINT", 6) == 0) {
|
||||||
|
return sql::data_type_t::type_long_long;
|
||||||
|
} else if (strcmp(type, "BOOLEAN") == 0) {
|
||||||
|
return sql::data_type_t::type_bool;
|
||||||
|
} else if (strcmp(type, "REAL") == 0) {
|
||||||
|
return sql::data_type_t::type_double;
|
||||||
|
} else if (strcmp(type, "FLOAT") == 0) {
|
||||||
|
return sql::data_type_t::type_float;
|
||||||
|
} else if (strcmp(type, "DOUBLE") == 0) {
|
||||||
|
return sql::data_type_t::type_double;
|
||||||
|
} else if (strcmp(type, "BLOB") == 0) {
|
||||||
|
return sql::data_type_t::type_blob;
|
||||||
|
} else if (strcmp(type, "NULL") == 0) {
|
||||||
|
return sql::data_type_t::type_null;
|
||||||
|
} else if (strncmp(type, "VARCHAR", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_varchar;
|
||||||
|
} else if (strcmp(type, "DATE") == 0) {
|
||||||
|
return sql::data_type_t::type_date;
|
||||||
|
} else if (strcmp(type, "DATETIME") == 0) {
|
||||||
|
return sql::data_type_t::type_time;
|
||||||
|
} else if (strcmp(type, "TEXT") == 0) {
|
||||||
|
return sql::data_type_t::type_text;
|
||||||
|
} else {
|
||||||
|
return sql::data_type_t::type_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sql::column_definition> sqlite_connection::describe(const std::string& table)
|
||||||
|
{
|
||||||
|
const auto result = fetch_internal("PRAGMA table_info(" + table + ")");
|
||||||
|
|
||||||
|
sqlite_result_reader reader(result.rows, result.prototype.size());
|
||||||
|
std::vector<sql::column_definition> prototype;
|
||||||
|
while (reader.fetch()) {
|
||||||
|
char *end = nullptr;
|
||||||
|
// Todo: add index to column
|
||||||
|
auto index = strtoul(reader.column(0), &end, 10);
|
||||||
|
std::string name = reader.column(1);
|
||||||
|
|
||||||
|
auto type = (string2type(reader.column(2)));
|
||||||
|
end = nullptr;
|
||||||
|
sql::null_option null_opt{sql::null_option::NULLABLE};
|
||||||
|
if (strtoul(reader.column(3), &end, 10) == 0) {
|
||||||
|
null_opt = sql::null_option::NOT_NULL;
|
||||||
|
}
|
||||||
|
// f.default_value(res->column(4));
|
||||||
|
prototype.emplace_back(name, type, utils::null_attributes, null_opt, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sqlite_connection::exists(const std::string &schema_name, const std::string &table_name)
|
||||||
|
{
|
||||||
|
const auto result = fetch_internal("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name='" + table_name + "' LIMIT 1");
|
||||||
|
sqlite_result_reader reader(result.rows, result.prototype.size());
|
||||||
|
|
||||||
|
if (!reader.fetch()) {
|
||||||
|
// Todo: throw an exception?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int v{};
|
||||||
|
reader.read_value(nullptr, 0, v);
|
||||||
|
|
||||||
|
return v == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
MATADOR_SQLITE_API matador::sql::connection_impl *create_database(const matador::sql::connection_info &info)
|
||||||
|
{
|
||||||
|
return new matador::backends::sqlite::sqlite_connection(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
MATADOR_SQLITE_API void destroy_database(matador::sql::connection_impl *db)
|
||||||
|
{
|
||||||
|
delete db;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "sqlite_dialect.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/dialect_builder.hpp"
|
||||||
|
|
||||||
|
[[maybe_unused]] const matador::sql::dialect *get_dialect()
|
||||||
|
{
|
||||||
|
using namespace matador::sql;
|
||||||
|
const static dialect d = dialect_builder::builder()
|
||||||
|
.create()
|
||||||
|
.with_token_replace_map({
|
||||||
|
{dialect::token_t::BEGIN, "BEGIN TRANSACTION"},
|
||||||
|
{dialect::token_t::COMMIT, "COMMIT TRANSACTION"},
|
||||||
|
{dialect::token_t::ROLLBACK, "ROLLBACK TRANSACTION"}
|
||||||
|
})
|
||||||
|
.with_default_schema_name("main")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return &d;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include "sqlite_error.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source)
|
||||||
|
{
|
||||||
|
if (ec != SQLITE_OK && ec != SQLITE_DONE) {
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "sqlite error (" << source << "): " << sqlite3_errmsg(db);
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source, const std::string &sql)
|
||||||
|
{
|
||||||
|
if (ec != SQLITE_OK&& ec != SQLITE_DONE) {
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "sqlite error (" << source << ", sql: " << sql << "): " << sqlite3_errmsg(db);
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
#include "sqlite_parameter_binder.h"
|
||||||
|
#include "sqlite_error.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
sqlite_parameter_binder::sqlite_parameter_binder(sqlite3 *db, sqlite3_stmt *stmt)
|
||||||
|
: db_(db)
|
||||||
|
, stmt_(stmt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, char i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, short i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, int i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, long i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, long long int i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int64(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, unsigned char i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, unsigned short i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, unsigned int i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int64(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, unsigned long i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int64(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, unsigned long long int i)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int64(stmt_, static_cast<int>(++pos), i);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, bool b)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_int(stmt_, static_cast<int>(++pos), b);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, float d)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_double(stmt_, static_cast<int>(++pos), d);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, double d)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_double(stmt_, static_cast<int>(++pos), d);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_int");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, const char *str)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_text(stmt_, static_cast<int>(++pos), str, static_cast<int>(strlen(str)), nullptr);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_text");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, const char *x, size_t size)
|
||||||
|
{
|
||||||
|
auto len = strlen(x);
|
||||||
|
size = (len > size) ? size : len;
|
||||||
|
int ret = sqlite3_bind_text(stmt_, static_cast<int>(++pos), x, static_cast<int>(size), nullptr);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_text");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, const std::string &str)
|
||||||
|
{
|
||||||
|
int ret = sqlite3_bind_text(stmt_, static_cast<int>(++pos), str.c_str(), static_cast<int>(str.size()), nullptr);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_text");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, const std::string &x, size_t size)
|
||||||
|
{
|
||||||
|
auto len = x.size();
|
||||||
|
if (size == 0) {
|
||||||
|
size = len;
|
||||||
|
} else {
|
||||||
|
size = (len > size) ? size : len;
|
||||||
|
}
|
||||||
|
int ret = sqlite3_bind_text(stmt_, static_cast<int>(++pos), x.data(), static_cast<int>(size), nullptr);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_bind_text");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_parameter_binder::bind(size_t pos, const utils::blob &blob)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
#include "sqlite_prepared_result_reader.hpp"
|
||||||
|
#include "sqlite_error.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
sqlite_prepared_result_reader::sqlite_prepared_result_reader(sqlite3 *db, sqlite3_stmt *stmt)
|
||||||
|
: db_(db)
|
||||||
|
, stmt_(stmt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
size_t sqlite_prepared_result_reader::column_count() const
|
||||||
|
{
|
||||||
|
return sqlite3_column_count(stmt_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *sqlite_prepared_result_reader::column(size_t index) const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<const char*>(sqlite3_column_text(stmt_, static_cast<int>(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sqlite_prepared_result_reader::fetch()
|
||||||
|
{
|
||||||
|
int ret = sqlite3_step(stmt_);
|
||||||
|
if (ret != SQLITE_ROW) {
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_step");
|
||||||
|
}
|
||||||
|
return ret != SQLITE_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, char &value)
|
||||||
|
{
|
||||||
|
value = static_cast<char>(sqlite3_column_int(stmt_, static_cast<int>(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, short &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, int &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, long &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, long long int &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned char &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned short &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned int &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned long &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned long long int &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, bool &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, float &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, double &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, char *value, size_t s)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, std::string &value)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, std::string &value, size_t s)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, value, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_prepared_result_reader::read_value(const char *id, size_t index, sql::value &val, size_t size)
|
||||||
|
{
|
||||||
|
query_result_reader::read_value(id, index, val, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "sqlite_result_reader.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
|
||||||
|
sqlite_result_reader::sqlite_result_reader(sqlite_result_reader::rows result, size_t column_count)
|
||||||
|
: result_(std::move(result))
|
||||||
|
, column_count_(column_count) {}
|
||||||
|
|
||||||
|
sqlite_result_reader::~sqlite_result_reader()
|
||||||
|
{
|
||||||
|
std::for_each(result_.begin(), result_.end(), [](rows ::value_type& row) {
|
||||||
|
std::for_each(row.begin(), row.end(), [](const char *val) {
|
||||||
|
delete [] val;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sqlite_result_reader::column_count() const
|
||||||
|
{
|
||||||
|
return column_count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sqlite_result_reader::column(size_t index) const
|
||||||
|
{
|
||||||
|
return result_[row_index_][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sqlite_result_reader::fetch()
|
||||||
|
{
|
||||||
|
return ++row_index_ < result_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#include "sqlite_statement.hpp"
|
||||||
|
#include "sqlite_prepared_result_reader.hpp"
|
||||||
|
#include "sqlite_error.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::sqlite {
|
||||||
|
sqlite_statement::sqlite_statement(sqlite3 *db, sqlite3_stmt *stmt, const sql::query_context &query)
|
||||||
|
: statement_impl(query)
|
||||||
|
, db_(db)
|
||||||
|
, stmt_(stmt)
|
||||||
|
, binder_(db, stmt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sqlite_statement::~sqlite_statement()
|
||||||
|
{
|
||||||
|
sqlite3_finalize(stmt_);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sqlite_statement::execute()
|
||||||
|
{
|
||||||
|
// get next row
|
||||||
|
int ret = sqlite3_reset(stmt_);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_reset");
|
||||||
|
if (ret = sqlite3_step(stmt_); ret != SQLITE_DONE) {
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_step");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlite3_changes(db_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> sqlite_statement::fetch()
|
||||||
|
{
|
||||||
|
int ret = sqlite3_reset(stmt_);
|
||||||
|
throw_sqlite_error(ret, db_, "sqlite3_reset");
|
||||||
|
|
||||||
|
auto reader = std::make_unique<sqlite_prepared_result_reader>(db_, stmt_);
|
||||||
|
return std::move(std::make_unique<sql::query_result_impl>(std::move(reader), query_.prototype));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_statement::reset()
|
||||||
|
{
|
||||||
|
if (stmt_) {
|
||||||
|
sqlite3_reset(stmt_);
|
||||||
|
sqlite3_clear_bindings(stmt_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql::parameter_binder& sqlite_statement::binder()
|
||||||
|
{
|
||||||
|
return binder_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
Include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.5.4 # or a later release
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||||
|
include(CTest)
|
||||||
|
include(Catch)
|
||||||
|
|
||||||
|
set(SQLITE_CONNECTION_STRING "sqlite://test.db")
|
||||||
|
|
||||||
|
configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/sqlite/test/connection.hpp @ONLY IMMEDIATE)
|
||||||
|
|
||||||
|
message(STATUS "sqlite connection string: ${SQLITE_CONNECTION_STRING}")
|
||||||
|
|
||||||
|
set(TEST_SOURCES
|
||||||
|
../../tests/QueryTest.cpp
|
||||||
|
../../tests/QueryTest.cpp
|
||||||
|
../../tests/ConnectionTest.cpp
|
||||||
|
../../tests/QueryRecordTest.cpp
|
||||||
|
../../tests/StatementTest.cpp
|
||||||
|
../../tests/TypeTraitsTest.cpp
|
||||||
|
../../tests/StatementCacheTest.cpp
|
||||||
|
../../tests/SessionTest.cpp)
|
||||||
|
|
||||||
|
set(LIBRARY_TEST_TARGET sqlite_tests)
|
||||||
|
|
||||||
|
add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES})
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
matador
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
${SQLite3_LIBRARY})
|
||||||
|
|
||||||
|
target_include_directories(${LIBRARY_TEST_TARGET}
|
||||||
|
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/include
|
||||||
|
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>/test
|
||||||
|
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
catch_discover_tests(${LIBRARY_TEST_TARGET} TEST_SUFFIX " (SQLite3)")
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef SQLITE_CONNECTION_HPP
|
||||||
|
#define SQLITE_CONNECTION_HPP
|
||||||
|
|
||||||
|
namespace matador::test::connection {
|
||||||
|
const char* const dns = "@SQLITE_CONNECTION_STRING@";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*SQLITE_CONNECTION_HPP*/
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "catch2/catch_test_macros.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/connection.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
using namespace matador::sql;
|
||||||
|
|
||||||
|
TEST_CASE("Create connection test", "[connection]") {
|
||||||
|
|
||||||
|
connection c(matador::test::connection::dns);
|
||||||
|
REQUIRE(!c.is_open());
|
||||||
|
|
||||||
|
c.open();
|
||||||
|
REQUIRE(c.is_open());
|
||||||
|
|
||||||
|
c.close();
|
||||||
|
REQUIRE(!c.is_open());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,402 @@
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include "matador/sql/column.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
|
#include "matador/sql/connection.hpp"
|
||||||
|
#include "matador/sql/query_builder.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
class QueryRecordFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QueryRecordFixture()
|
||||||
|
: db(matador::test::connection::dns)
|
||||||
|
, schema(db.dialect().default_schema_name())
|
||||||
|
{
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
~QueryRecordFixture() {
|
||||||
|
drop_table_if_exists("flight");
|
||||||
|
drop_table_if_exists("airplane");
|
||||||
|
drop_table_if_exists("person");
|
||||||
|
drop_table_if_exists("quotes");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
matador::sql::connection db;
|
||||||
|
matador::sql::schema schema;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drop_table_if_exists(const std::string &table_name) {
|
||||||
|
if (db.exists(table_name)) {
|
||||||
|
db.query(schema).drop().table(table_name).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using namespace matador::sql;
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Create and drop table statement", "[session][record]")
|
||||||
|
{
|
||||||
|
REQUIRE(!db.exists("person"));
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(db.exists("person"));
|
||||||
|
|
||||||
|
db.query(schema).drop()
|
||||||
|
.table("person")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(!db.exists("person"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Create and drop table statement with foreign key", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("airplane", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("brand", 255),
|
||||||
|
make_column<std::string>("model", 255),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(db.exists("airplane"));
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("flight", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_fk_column<unsigned long>("airplane_id", "airplane", "id"),
|
||||||
|
make_column<std::string>("pilot_name", 255),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(db.exists("flight"));
|
||||||
|
|
||||||
|
db.query(schema).drop()
|
||||||
|
.table("flight")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(!db.exists("flight"));
|
||||||
|
|
||||||
|
db.query(schema).drop()
|
||||||
|
.table("airplane")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(!db.exists("airplane"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute insert record statement", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert()
|
||||||
|
.into("person", {"id", "name", "age"})
|
||||||
|
.values({7, "george", 45})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db.query(schema).select({"id", "name", "age"})
|
||||||
|
.from("person")
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
for (const auto &i: result) {
|
||||||
|
REQUIRE(i.size() == 3);
|
||||||
|
REQUIRE(i.at(0).name() == "id");
|
||||||
|
// REQUIRE(i.at(0).type() == data_type_t::type_long_long);
|
||||||
|
REQUIRE(i.at(0).template as<long long>() == 7);
|
||||||
|
REQUIRE(i.at(1).name() == "name");
|
||||||
|
// REQUIRE(i.at(1).type() == data_type_t::type_varchar);
|
||||||
|
REQUIRE(i.at(1).template as<std::string>() == "george");
|
||||||
|
REQUIRE(i.at(2).name() == "age");
|
||||||
|
// REQUIRE(i.at(2).type() == matador::sql::data_type_t::type_int);
|
||||||
|
REQUIRE(i.at(2).template as<int>() == 45);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop()
|
||||||
|
.table("person")
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute insert record statement with foreign key", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("airplane", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("brand", 255),
|
||||||
|
make_column<std::string>("model", 255),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("flight", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_fk_column<unsigned long>("airplane_id", "airplane", "id"),
|
||||||
|
make_column<std::string>("pilot_name", 255),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert().into("airplane", {"id", "brand", "model"}).values({1, "Airbus", "A380"}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
res = db.query(schema).insert().into("airplane", {"id", "brand", "model"}).values({2, "Boeing", "707"}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
res = db.query(schema).insert().into("airplane", {"id", "brand", "model"}).values({3, "Boeing", "747"}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto count = db.query(schema).select({count_all()}).from("airplane").fetch_value<int>();
|
||||||
|
REQUIRE(count == 3);
|
||||||
|
|
||||||
|
res = db.query(schema).insert().into("flight", {"id", "airplane_id", "pilot_name"}).values({4, 1, "George"}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
db.query(schema).drop().table("flight").execute();
|
||||||
|
db.query(schema).drop().table("airplane").execute();
|
||||||
|
|
||||||
|
REQUIRE(!db.exists("flight"));
|
||||||
|
REQUIRE(!db.exists("airplane"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute update record statement", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert()
|
||||||
|
.into("person", {"id", "name", "age"})
|
||||||
|
.values({7, "george", 45})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
res = db.query(schema).update("person")
|
||||||
|
.set({{"id", 7},
|
||||||
|
{"name", "jane"},
|
||||||
|
{"age", 35}})
|
||||||
|
.where("id"_col == 7)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db.query(schema).select({"id", "name", "age"})
|
||||||
|
.from("person")
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
for (const auto &i: result) {
|
||||||
|
REQUIRE(i.size() == 3);
|
||||||
|
REQUIRE(i.at(0).name() == "id");
|
||||||
|
REQUIRE(i.at(0).is_integer());
|
||||||
|
REQUIRE(i.at(0).as<long long>() == 7);
|
||||||
|
REQUIRE(i.at(1).name() == "name");
|
||||||
|
REQUIRE(i.at(1).is_varchar());
|
||||||
|
REQUIRE(i.at(1).as<std::string>() == "jane");
|
||||||
|
REQUIRE(i.at(2).name() == "age");
|
||||||
|
REQUIRE(i.at(2).is_integer());
|
||||||
|
REQUIRE(i.at(2).as<int>() == 35);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute select statement", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({1, "george", 45}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({2, "jane", 32}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({3, "michael", 67}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({4, "bob", 13}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db.query(schema).select({"id", "name", "age"})
|
||||||
|
.from("person")
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
std::list<std::string> expected_names{"george", "jane", "michael", "bob"};
|
||||||
|
for (const auto &p: result) {
|
||||||
|
REQUIRE(p.at(1).str() == expected_names.front());
|
||||||
|
expected_names.pop_front();
|
||||||
|
}
|
||||||
|
REQUIRE(expected_names.empty());
|
||||||
|
|
||||||
|
auto rec = db.query(schema).select({"id", "name", "age"})
|
||||||
|
.from("person")
|
||||||
|
.fetch_one();
|
||||||
|
REQUIRE(rec.has_value());
|
||||||
|
REQUIRE(rec->at(1).str() == "george");
|
||||||
|
|
||||||
|
auto name = db.query(schema).select({"name"})
|
||||||
|
.from("person")
|
||||||
|
.fetch_value<std::string>();
|
||||||
|
REQUIRE(name == "george");
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute select statement with order by", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({1, "george", 45}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({2, "jane", 32}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({3, "michael", 67}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({4, "bob", 13}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db.query(schema).select({"id", "name", "age"})
|
||||||
|
.from("person")
|
||||||
|
.order_by("name").asc()
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
std::list<std::string> expected_names{"bob", "george", "jane", "michael"};
|
||||||
|
for (const auto &p: result) {
|
||||||
|
REQUIRE(p.at(1).str() == expected_names.front());
|
||||||
|
expected_names.pop_front();
|
||||||
|
}
|
||||||
|
REQUIRE(expected_names.empty());
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute select statement with group by and order by", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({1, "george", 45}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({2, "jane", 45}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({3, "michael", 13}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({4, "bob", 13}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({5, "charlie", 67}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db.query(schema).select({count("age").as("age_count"), "age"})
|
||||||
|
.from("person")
|
||||||
|
.group_by("age")
|
||||||
|
.order_by("age_count").desc()
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
std::list<std::pair<int, int>> expected_values{{2, 45},
|
||||||
|
{2, 13},
|
||||||
|
{1, 67}};
|
||||||
|
for (const auto &r: result) {
|
||||||
|
REQUIRE(r.at(0).as<int>() == expected_values.front().first);
|
||||||
|
REQUIRE(r.at(1).as<int>() == expected_values.front().second);
|
||||||
|
expected_values.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Execute delete statement", "[session][record]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<unsigned short>("age")
|
||||||
|
}).execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({1, "george", 45}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
res = db.query(schema).insert().into("person", {"id", "name", "age"}).values({2, "jane", 45}).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto count = db.query(schema).select({count_all()}).from("person").fetch_value<int>();
|
||||||
|
REQUIRE(count == 2);
|
||||||
|
|
||||||
|
res = db.query(schema).remove()
|
||||||
|
.from("person")
|
||||||
|
.where("id"_col == 1)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
count = db.query(schema).select({count_all()}).from("person").fetch_value<int>();
|
||||||
|
REQUIRE(count == 1);
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryRecordFixture, "Test quoted identifier", "[session][record]") {
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("quotes", {
|
||||||
|
make_column<std::string>("from", 255),
|
||||||
|
make_column<std::string>("to", 255)
|
||||||
|
}).execute();
|
||||||
|
|
||||||
|
// check table description
|
||||||
|
std::vector<std::string> columns = { "from", "to"};
|
||||||
|
std::vector<data_type_t> types = {data_type_t::type_varchar, data_type_t::type_varchar};
|
||||||
|
auto fields = db.describe("quotes");
|
||||||
|
|
||||||
|
for (const auto &field : fields) {
|
||||||
|
REQUIRE(field.name() == columns[field.index()]);
|
||||||
|
REQUIRE(field.type() == types[field.index()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).insert().into("quotes", {"from", "to"}).values({"Berlin", "London"}).execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).select({"from", "to"}).from("quotes").fetch_one();
|
||||||
|
|
||||||
|
REQUIRE(res.has_value());
|
||||||
|
REQUIRE("Berlin" == res->at("from").str());
|
||||||
|
REQUIRE("London" == res->at("to").str());
|
||||||
|
|
||||||
|
db.query(schema).update("quotes").set({{"from", "Hamburg"}, {"to", "New York"}}).where("from"_col == "Berlin").execute();
|
||||||
|
|
||||||
|
res = db.query(schema).select({"from", "to"}).from("quotes").fetch_one();
|
||||||
|
|
||||||
|
REQUIRE("Hamburg" == res->at("from").str());
|
||||||
|
REQUIRE("New York" == res->at("to").str());
|
||||||
|
|
||||||
|
db.query(schema).drop().table("quotes").execute();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,535 @@
|
||||||
|
#include "catch2/catch_test_macros.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/column_definition.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
|
#include "matador/sql/query_builder.hpp"
|
||||||
|
#include "matador/sql/session.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
#include "models/airplane.hpp"
|
||||||
|
#include "models/flight.hpp"
|
||||||
|
#include "models/person.hpp"
|
||||||
|
#include "models/recipe.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace matador::sql;
|
||||||
|
using namespace matador::test;
|
||||||
|
|
||||||
|
class QueryFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QueryFixture()
|
||||||
|
: db(matador::test::connection::dns)
|
||||||
|
, schema(db.dialect().default_schema_name())
|
||||||
|
{
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
~QueryFixture()
|
||||||
|
{
|
||||||
|
drop_table_if_exists("flight");
|
||||||
|
drop_table_if_exists("airplane");
|
||||||
|
drop_table_if_exists("person");
|
||||||
|
drop_table_if_exists("recipe_ingredients");
|
||||||
|
drop_table_if_exists("recipes");
|
||||||
|
drop_table_if_exists("ingredients");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
matador::sql::connection db;
|
||||||
|
matador::sql::schema schema;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drop_table_if_exists(const std::string &table_name)
|
||||||
|
{
|
||||||
|
if (db.exists(table_name)) {
|
||||||
|
db.query(schema).drop().table(table_name).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Create table with foreign key relation", "[session]")
|
||||||
|
{
|
||||||
|
schema.attach<airplane>("airplane");
|
||||||
|
schema.attach<flight>("flight");
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<airplane>("airplane")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(db.exists("airplane"));
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<flight>("flight")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(db.exists("flight"));
|
||||||
|
|
||||||
|
db.query(schema).drop().table("flight").execute();
|
||||||
|
db.query(schema).drop().table("airplane").execute();
|
||||||
|
|
||||||
|
REQUIRE(!db.exists("flight"));
|
||||||
|
REQUIRE(!db.exists("airplane"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Execute select statement with where clause", "[session]")
|
||||||
|
{
|
||||||
|
schema.attach<person>("person");
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<person>("person")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
person george{7, "george", 45};
|
||||||
|
george.image.push_back(37);
|
||||||
|
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("person", column_generator::generate<person>(schema, true))
|
||||||
|
.values(george)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
// fetch person as record
|
||||||
|
auto result_record = db.query(schema)
|
||||||
|
.select(column_generator::generate<person>(schema, true))
|
||||||
|
.from("person")
|
||||||
|
.where("id"_col == 7)
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
for (const auto &i: result_record) {
|
||||||
|
REQUIRE(i.size() == 4);
|
||||||
|
REQUIRE(i.at(0).name() == "id");
|
||||||
|
REQUIRE(i.at(0).is_integer());
|
||||||
|
REQUIRE(i.at(0).as<long long>() == george.id);
|
||||||
|
REQUIRE(i.at(1).name() == "name");
|
||||||
|
REQUIRE(i.at(1).is_varchar());
|
||||||
|
REQUIRE(i.at(1).as<std::string>() == george.name);
|
||||||
|
REQUIRE(i.at(2).name() == "age");
|
||||||
|
REQUIRE(i.at(2).is_integer());
|
||||||
|
REQUIRE(i.at(2).as<long long>() == george.age);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch person as person
|
||||||
|
auto result_person = db.query(schema)
|
||||||
|
.select(column_generator::generate<person>(schema, true))
|
||||||
|
.from("person")
|
||||||
|
.where("id"_col == 7)
|
||||||
|
.fetch_all<person>();
|
||||||
|
|
||||||
|
for (const auto &i: result_person) {
|
||||||
|
REQUIRE(i.id == 7);
|
||||||
|
REQUIRE(i.name == "george");
|
||||||
|
REQUIRE(i.age == 45);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Execute insert statement", "[session]")
|
||||||
|
{
|
||||||
|
db.query(schema).create()
|
||||||
|
.table("person", {
|
||||||
|
make_pk_column<unsigned long>("id"),
|
||||||
|
make_column<std::string>("name", 255),
|
||||||
|
make_column<std::string>("color", 63)
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto res = db.query(schema).insert()
|
||||||
|
.into("person", {{"", "id", ""}, {"", "name", ""}, {"", "color", ""}})
|
||||||
|
.values({7, "george", "green"})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
// fetch person as record
|
||||||
|
auto result_record = db.query(schema).select({"id", "name", "color"})
|
||||||
|
.from("person")
|
||||||
|
.where("id"_col == 7)
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
for (const auto &i: result_record) {
|
||||||
|
REQUIRE(i.size() == 3);
|
||||||
|
REQUIRE(i.at(0).name() == "id");
|
||||||
|
REQUIRE(i.at(0).is_integer());
|
||||||
|
REQUIRE(i.at(0).as<unsigned long>() == 7);
|
||||||
|
REQUIRE(i.at(1).name() == "name");
|
||||||
|
REQUIRE(i.at(1).is_varchar());
|
||||||
|
REQUIRE(i.at(1).as<std::string>() == "george");
|
||||||
|
REQUIRE(i.at(2).name() == "color");
|
||||||
|
REQUIRE(i.at(2).is_varchar());
|
||||||
|
REQUIRE(i.at(2).as<std::string>() == "green");
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop().table("person").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key", "[session]")
|
||||||
|
{
|
||||||
|
schema.attach<airplane>("airplane");
|
||||||
|
schema.attach<flight>("flight");
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<airplane>("airplane")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<flight>("flight")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::vector<entity<airplane>> planes{
|
||||||
|
make_entity<airplane>(1, "Airbus", "A380"),
|
||||||
|
make_entity<airplane>(2, "Boeing", "707"),
|
||||||
|
make_entity<airplane>(3, "Boeing", "747")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &plane: planes) {
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("airplane", column_generator::generate<airplane>(schema, true))
|
||||||
|
.values(*plane)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = db.query(schema)
|
||||||
|
.select({count_all()})
|
||||||
|
.from("airplane")
|
||||||
|
.fetch_value<int>().value();
|
||||||
|
REQUIRE(count == 3);
|
||||||
|
|
||||||
|
flight f4711{4, planes.at(1), "hans"};
|
||||||
|
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("flight", column_generator::generate<flight>(schema, true))
|
||||||
|
.values(f4711)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto f = *db.query(schema)
|
||||||
|
.select(column_generator::generate<flight>(schema, true))
|
||||||
|
.from("flight")
|
||||||
|
.fetch_all().begin();
|
||||||
|
REQUIRE(f.at(0).as<unsigned long>() == 4);
|
||||||
|
REQUIRE(f.at(1).as<unsigned long>() == 2);
|
||||||
|
REQUIRE(f.at(2).as<std::string>() == "hans");
|
||||||
|
|
||||||
|
db.query(schema).drop().table("flight").execute();
|
||||||
|
db.query(schema).drop().table("airplane").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and join_left", "[session][join_left]")
|
||||||
|
{
|
||||||
|
schema.attach<airplane>("airplane");
|
||||||
|
schema.attach<flight>("flight");
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<airplane>("airplane")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<flight>("flight")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::vector<entity<airplane>> planes{
|
||||||
|
make_entity<airplane>(1, "Airbus", "A380"),
|
||||||
|
make_entity<airplane>(2, "Boeing", "707"),
|
||||||
|
make_entity<airplane>(3, "Boeing", "747")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &plane: planes) {
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("airplane", column_generator::generate<airplane>(schema, true))
|
||||||
|
.values(*plane)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = db.query(schema)
|
||||||
|
.select({count_all()})
|
||||||
|
.from("airplane")
|
||||||
|
.fetch_value<int>().value();
|
||||||
|
REQUIRE(count == 3);
|
||||||
|
|
||||||
|
std::vector<entity<flight>> flights{
|
||||||
|
make_entity<flight>(4, planes.at(0), "hans"),
|
||||||
|
make_entity<flight>(5, planes.at(0), "otto"),
|
||||||
|
make_entity<flight>(6, planes.at(1), "george"),
|
||||||
|
make_entity<flight>(7, planes.at(2), "paul")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &f: flights) {
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("flight", {"id", "airplane_id", "pilot_name"})
|
||||||
|
.values(*f)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto f = *db.query(schema)
|
||||||
|
.select(column_generator::generate<flight>(schema, true))
|
||||||
|
.from("flight")
|
||||||
|
.fetch_all().begin();
|
||||||
|
REQUIRE(f.at(0).as<unsigned long>() == 4);
|
||||||
|
REQUIRE(f.at(1).as<unsigned long>() == 1);
|
||||||
|
REQUIRE(f.at(2).as<std::string>() == "hans");
|
||||||
|
|
||||||
|
auto result = db.query(schema).select({"f.id", "ap.brand", "ap.model", "f.pilot_name"})
|
||||||
|
.from({"flight", "f"})
|
||||||
|
.join_left({"airplane", "ap"})
|
||||||
|
.on("f.airplane_id"_col == "ap.id"_col)
|
||||||
|
.order_by("f.id").asc()
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
std::vector<std::pair<unsigned long, std::string>> expected_result {
|
||||||
|
{4, "hans"},
|
||||||
|
{5, "otto"},
|
||||||
|
{6, "george"},
|
||||||
|
{7, "paul"}
|
||||||
|
};
|
||||||
|
size_t index{0};
|
||||||
|
for (const auto &r: result) {
|
||||||
|
REQUIRE(r.size() == 4);
|
||||||
|
REQUIRE(r.at(0).as<unsigned long>() == expected_result[index].first);
|
||||||
|
REQUIRE(r.at(3).as<std::string>() == expected_result[index++].second);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop().table("flight").execute();
|
||||||
|
db.query(schema).drop().table("airplane").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Select statement with foreign key and for single entity", "[session][join_left][find]") {
|
||||||
|
schema.attach<airplane>("airplane");
|
||||||
|
schema.attach<flight>("flight");
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<airplane>("airplane")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<flight>("flight")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::vector<entity<airplane>> planes{
|
||||||
|
make_entity<airplane>(1, "Airbus", "A380"),
|
||||||
|
make_entity<airplane>(2, "Boeing", "707"),
|
||||||
|
make_entity<airplane>(3, "Boeing", "747")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &plane: planes) {
|
||||||
|
auto res = db
|
||||||
|
.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("airplane", column_generator::generate<airplane>(schema, true))
|
||||||
|
.values(*plane)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = db
|
||||||
|
.query(schema)
|
||||||
|
.select({count_all()})
|
||||||
|
.from("airplane")
|
||||||
|
.fetch_value<int>().value();
|
||||||
|
REQUIRE(count == 3);
|
||||||
|
|
||||||
|
std::vector<entity<flight>> flights{
|
||||||
|
make_entity<flight>(4, planes.at(0), "hans"),
|
||||||
|
make_entity<flight>(5, planes.at(0), "otto"),
|
||||||
|
make_entity<flight>(6, planes.at(1), "george"),
|
||||||
|
make_entity<flight>(7, planes.at(2), "paul")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &f: flights) {
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("flight", column_generator::generate<flight>(schema, true))
|
||||||
|
.values(*f)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto f = db.query(schema)
|
||||||
|
.select(column_generator::generate<flight>(schema, true))
|
||||||
|
.from("flight")
|
||||||
|
.fetch_one();
|
||||||
|
REQUIRE(f.has_value());
|
||||||
|
REQUIRE(f->at(0).as<unsigned long>() == 4);
|
||||||
|
REQUIRE(f->at(1).as<unsigned long>() == 1);
|
||||||
|
REQUIRE(f->at(2).as<std::string>() == "hans");
|
||||||
|
|
||||||
|
auto result = db
|
||||||
|
.query(schema)
|
||||||
|
.select({"f.id", "f.airplane_id", "ap.brand", "ap.model", "f.pilot_name"})
|
||||||
|
.from({"flight", "f"})
|
||||||
|
.join_left({"airplane", "ap"})
|
||||||
|
.on("f.airplane_id"_col == "ap.id"_col)
|
||||||
|
.where("f.id"_col == 4)
|
||||||
|
.fetch_one<flight>();
|
||||||
|
|
||||||
|
auto expected_flight = flights[0];
|
||||||
|
|
||||||
|
REQUIRE(result);
|
||||||
|
REQUIRE(result->id == expected_flight->id);
|
||||||
|
REQUIRE(result->pilot_name == expected_flight->pilot_name);
|
||||||
|
REQUIRE(result->airplane.get());
|
||||||
|
REQUIRE(result->airplane->id == 1);
|
||||||
|
REQUIRE(result->airplane->model == "A380");
|
||||||
|
REQUIRE(result->airplane->brand == "Airbus");
|
||||||
|
|
||||||
|
db.query(schema).drop().table("flight").execute();
|
||||||
|
db.query(schema).drop().table("airplane").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(QueryFixture, "Select statement with many to many relationship", "[session][join][many_to_many]") {
|
||||||
|
schema.attach<recipe>("recipes");
|
||||||
|
schema.attach<ingredient>("ingredients");
|
||||||
|
schema.attach<recipe_ingredient>("recipe_ingredients");
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<recipe>("recipes")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<ingredient>("ingredients")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<recipe_ingredient>("recipe_ingredients")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::vector<ingredient> ingredients {
|
||||||
|
{1, "Apple"},
|
||||||
|
{2, "Strawberry"},
|
||||||
|
{3, "Pineapple"},
|
||||||
|
{4, "Sugar"},
|
||||||
|
{5, "Flour"},
|
||||||
|
{6, "Butter"},
|
||||||
|
{7, "Beans"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &i: ingredients) {
|
||||||
|
auto res = db
|
||||||
|
.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("ingredients", column_generator::generate<ingredient>(schema, true))
|
||||||
|
.values(i)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<recipe> recipes{
|
||||||
|
{7, "Apple Crumble"},
|
||||||
|
{8, "Beans Chili"},
|
||||||
|
{9, "Fruit Salad"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &r: recipes) {
|
||||||
|
auto res = db
|
||||||
|
.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("recipes", column_generator::generate<recipe>(schema, true))
|
||||||
|
.values(r)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<int, int>> recipe_ingredients {
|
||||||
|
{ 7, 1 },
|
||||||
|
{ 7, 4 },
|
||||||
|
{ 7, 5 },
|
||||||
|
{ 8, 6 },
|
||||||
|
{ 8, 7 },
|
||||||
|
{ 9, 1 },
|
||||||
|
{ 9, 2 },
|
||||||
|
{ 9, 3 }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &ri: recipe_ingredients) {
|
||||||
|
auto res = db
|
||||||
|
.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("recipe_ingredients", column_generator::generate<recipe_ingredient>(schema, true))
|
||||||
|
.values({ri.first, ri.second})
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = db
|
||||||
|
.query(schema)
|
||||||
|
.select({"r.id", "r.name", "ri.ingredient_id"})
|
||||||
|
.from({"recipes", "r"})
|
||||||
|
.join_left({"recipe_ingredients", "ri"})
|
||||||
|
.on("r.id"_col == "ri.recipe_id"_col)
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
std::vector<std::tuple<unsigned long, std::string, unsigned long>> expected_result_one_join {
|
||||||
|
{7, "Apple Crumble", 1},
|
||||||
|
{7, "Apple Crumble", 4},
|
||||||
|
{7, "Apple Crumble", 5},
|
||||||
|
{8, "Beans Chili", 6},
|
||||||
|
{8, "Beans Chili", 7},
|
||||||
|
{9, "Fruit Salad", 1},
|
||||||
|
{9, "Fruit Salad", 2},
|
||||||
|
{9, "Fruit Salad", 3}
|
||||||
|
};
|
||||||
|
size_t index{0};
|
||||||
|
for (const auto &r: result) {
|
||||||
|
REQUIRE(r.size() == 3);
|
||||||
|
REQUIRE(r.at(0).as<unsigned long>().value() == std::get<0>(expected_result_one_join[index]));
|
||||||
|
REQUIRE(r.at(1).as<std::string>().value() == std::get<1>(expected_result_one_join[index]));
|
||||||
|
REQUIRE(r.at(2).as<unsigned long>().value() == std::get<2>(expected_result_one_join[index]));
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = db
|
||||||
|
.query(schema)
|
||||||
|
.select({"r.id", "r.name", "ri.ingredient_id", "i.name"})
|
||||||
|
.from({"recipes", "r"})
|
||||||
|
.join_left({"recipe_ingredients", "ri"}).on("r.id"_col == "ri.recipe_id"_col)
|
||||||
|
.join_left({"ingredients", "i"}).on("ri.ingredient_id"_col == "i.id"_col)
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
std::vector<std::tuple<unsigned long, std::string, unsigned long, std::string>> expected_result_two_joins {
|
||||||
|
{7, "Apple Crumble", 1, "Apple"},
|
||||||
|
{7, "Apple Crumble", 4, "Sugar"},
|
||||||
|
{7, "Apple Crumble", 5, "Flour"},
|
||||||
|
{8, "Beans Chili", 6, "Butter"},
|
||||||
|
{8, "Beans Chili", 7, "Beans"},
|
||||||
|
{9, "Fruit Salad", 1, "Apple"},
|
||||||
|
{9, "Fruit Salad", 2, "Strawberry"},
|
||||||
|
{9, "Fruit Salad", 3, "Pineapple"}
|
||||||
|
};
|
||||||
|
index = 0;
|
||||||
|
for (const auto &r: result) {
|
||||||
|
REQUIRE(r.size() == 4);
|
||||||
|
REQUIRE(r.at(0).as<unsigned long>().value() == std::get<0>(expected_result_two_joins[index]));
|
||||||
|
REQUIRE(r.at(1).as<std::string>().value() == std::get<1>(expected_result_two_joins[index]));
|
||||||
|
REQUIRE(r.at(2).as<unsigned long>().value() == std::get<2>(expected_result_two_joins[index]));
|
||||||
|
REQUIRE(r.at(3).as<std::string>().value() == std::get<3>(expected_result_two_joins[index]));
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = db
|
||||||
|
.query(schema)
|
||||||
|
.select({"r.id", "r.name", "ri.ingredient_id", "i.name"})
|
||||||
|
.from({"recipes", "r"})
|
||||||
|
.join_left({"recipe_ingredients", "ri"}).on("r.id"_col == "ri.recipe_id"_col)
|
||||||
|
.join_left({"ingredients", "i"}).on("ri.ingredient_id"_col == "i.id"_col)
|
||||||
|
.where("r.id"_col == 8)
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
index = 3;
|
||||||
|
for (const auto &r: result) {
|
||||||
|
REQUIRE(r.size() == 4);
|
||||||
|
REQUIRE(r.at(0).as<unsigned long>().value() == std::get<0>(expected_result_two_joins[index]));
|
||||||
|
REQUIRE(r.at(1).as<std::string>().value() == std::get<1>(expected_result_two_joins[index]));
|
||||||
|
REQUIRE(r.at(2).as<unsigned long>().value() == std::get<2>(expected_result_two_joins[index]));
|
||||||
|
REQUIRE(r.at(3).as<std::string>().value() == std::get<3>(expected_result_two_joins[index]));
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.query(schema).drop().table("recipe_ingredients").execute();
|
||||||
|
db.query(schema).drop().table("recipes").execute();
|
||||||
|
db.query(schema).drop().table("ingredients").execute();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#include "catch2/catch_test_macros.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/session.hpp"
|
||||||
|
|
||||||
|
#include "models/airplane.hpp"
|
||||||
|
#include "models/flight.hpp"
|
||||||
|
|
||||||
|
class SessionFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SessionFixture()
|
||||||
|
: pool(matador::test::connection::dns, 4)
|
||||||
|
, ses(pool)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~SessionFixture()
|
||||||
|
{
|
||||||
|
drop_table_if_exists("flights");
|
||||||
|
drop_table_if_exists("airplanes");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
matador::sql::connection_pool<matador::sql::connection> pool;
|
||||||
|
matador::sql::session ses;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drop_table_if_exists(const std::string &table_name)
|
||||||
|
{
|
||||||
|
if (ses.table_exists(table_name)) {
|
||||||
|
ses.drop_table(table_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using namespace matador;
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(SessionFixture, "Session relation test", "[session][relation]") {
|
||||||
|
using namespace matador;
|
||||||
|
ses.attach<test::airplane>("airplanes");
|
||||||
|
ses.attach<test::flight>("flights");
|
||||||
|
ses.create_schema();
|
||||||
|
auto plane = ses.insert<test::airplane>(1, "Boeing", "A380");
|
||||||
|
auto f = ses.insert<test::flight>(2, plane, "sully");
|
||||||
|
|
||||||
|
auto result = ses.find<test::flight>(2);
|
||||||
|
REQUIRE(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(SessionFixture, "Use session to find object with id", "[session][find]") {
|
||||||
|
using namespace matador::test;
|
||||||
|
ses.attach<airplane>("airplanes");
|
||||||
|
ses.create_schema();
|
||||||
|
auto a380 = ses.insert<airplane>(1, "Boeing", "A380");
|
||||||
|
|
||||||
|
auto result = ses.find<airplane>(2);
|
||||||
|
REQUIRE(!result.is_ok());
|
||||||
|
REQUIRE((result.err() == sql::session_error::FailedToFindObject));
|
||||||
|
|
||||||
|
result = ses.find<airplane>(1);
|
||||||
|
|
||||||
|
REQUIRE(result);
|
||||||
|
auto read_a380 = result.value();
|
||||||
|
REQUIRE(a380->id == read_a380->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(SessionFixture, "Use session to find all objects", "[session][find]") {
|
||||||
|
using namespace matador::test;
|
||||||
|
ses.attach<airplane>("airplanes");
|
||||||
|
ses.create_schema();
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<airplane>> planes;
|
||||||
|
planes.emplace_back(new airplane(1, "Airbus", "A380"));
|
||||||
|
planes.emplace_back(new airplane(2, "Boeing", "707"));
|
||||||
|
planes.emplace_back(new airplane(3, "Boeing", "747"));
|
||||||
|
|
||||||
|
for (auto &&plane: planes) {
|
||||||
|
ses.insert(plane.release());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = ses.find<airplane>();
|
||||||
|
|
||||||
|
std::vector<std::tuple<int, std::string, std::string>> expected_result {
|
||||||
|
{1, "Airbus", "A380"},
|
||||||
|
{2, "Boeing", "707"},
|
||||||
|
{3, "Boeing", "747"}
|
||||||
|
};
|
||||||
|
REQUIRE(result);
|
||||||
|
auto all_planes = result.release();
|
||||||
|
size_t index {0};
|
||||||
|
for (const auto &i: all_planes) {
|
||||||
|
REQUIRE(i.id == std::get<0>(expected_result[index]));
|
||||||
|
REQUIRE(i.brand == std::get<1>(expected_result[index]));
|
||||||
|
REQUIRE(i.model == std::get<2>(expected_result[index]));
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
#include "matador/sql/connection_info.hpp"
|
#include "matador/sql/connection_info.hpp"
|
||||||
#include "matador/sql/connection_pool.hpp"
|
#include "matador/sql/connection_pool.hpp"
|
||||||
#include "matador/orm/session.hpp"
|
#include "matador/sql/session.hpp"
|
||||||
|
#include "matador/sql/statement_cache.hpp"
|
||||||
// #include "matador/sql/statement_cache.hpp"
|
|
||||||
|
|
||||||
#include "connection.hpp"
|
#include "connection.hpp"
|
||||||
#include "matador/sql/dialect_builder.hpp"
|
|
||||||
|
|
||||||
using namespace matador;
|
using namespace matador;
|
||||||
|
|
||||||
|
|
@ -15,20 +13,19 @@ class StatementCacheFixture
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StatementCacheFixture()
|
StatementCacheFixture()
|
||||||
: pool(test::connection::dns, 4)//, ses(pool)
|
: pool(matador::test::connection::dns, 4), ses(pool)
|
||||||
{}
|
{}
|
||||||
~StatementCacheFixture() = default;
|
~StatementCacheFixture() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
sql::connection_pool pool;
|
matador::sql::connection_pool<matador::sql::connection> pool;
|
||||||
// orm::session ses;
|
matador::sql::session ses;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_CASE_METHOD(StatementCacheFixture, "Acquire prepared statement", "[statement cache]") {
|
TEST_CASE_METHOD(StatementCacheFixture, "Acquire prepared statement", "[statement cache]") {
|
||||||
// const auto d = sql::dialect_builder::builder().create().build();
|
sql::statement_cache cache;
|
||||||
// sql::statement_cache cache(pool, d);
|
|
||||||
|
|
||||||
// auto conn = pool.acquire();
|
auto conn = pool.acquire();
|
||||||
|
|
||||||
std::string sql = R"(SELECT * FROM person WHERE name = 'george')";
|
std::string sql = R"(SELECT * FROM person WHERE name = 'george')";
|
||||||
// auto &stmt = cache.acquire(sql, *conn);
|
// auto &stmt = cache.acquire(sql, *conn);
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include "matador/sql/column_definition.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
|
#include "matador/sql/connection.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
#include "models/airplane.hpp"
|
||||||
|
|
||||||
|
using namespace matador::sql;
|
||||||
|
using namespace matador::test;
|
||||||
|
|
||||||
|
class StatementTestFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StatementTestFixture()
|
||||||
|
: db(matador::test::connection::dns)
|
||||||
|
, schema(db.dialect().default_schema_name())
|
||||||
|
{
|
||||||
|
db.open();
|
||||||
|
db.query(schema).create().table<airplane>("airplane").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
~StatementTestFixture()
|
||||||
|
{
|
||||||
|
drop_table_if_exists("airplane");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
matador::sql::connection db;
|
||||||
|
matador::sql::schema schema;
|
||||||
|
|
||||||
|
std::vector<entity<airplane>> planes{
|
||||||
|
make_entity<airplane>(1, "Airbus", "A380"),
|
||||||
|
make_entity<airplane>(2, "Boeing", "707"),
|
||||||
|
make_entity<airplane>(3, "Boeing", "747")
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drop_table_if_exists(const std::string &table_name) {
|
||||||
|
if (db.exists(table_name)) {
|
||||||
|
db.query(schema).drop().table(table_name).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(StatementTestFixture, "Create prepared statement", "[statement]")
|
||||||
|
{
|
||||||
|
schema.attach<airplane>("airplane");
|
||||||
|
table ap{"airplane"};
|
||||||
|
SECTION("Insert with prepared statement and placeholder") {
|
||||||
|
auto stmt = db.query(schema).insert()
|
||||||
|
.into("airplane", column_generator::generate<airplane>(schema, true))
|
||||||
|
.values<airplane>()
|
||||||
|
.prepare();
|
||||||
|
|
||||||
|
for (const auto &plane: planes) {
|
||||||
|
auto res = stmt.bind(*plane).execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
stmt.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = db.query(schema)
|
||||||
|
.select(column_generator::generate<airplane>(schema, true))
|
||||||
|
.from(ap)
|
||||||
|
.fetch_all<airplane>();
|
||||||
|
|
||||||
|
size_t index{0};
|
||||||
|
for (const auto &i: result) {
|
||||||
|
REQUIRE(i.id == planes[index]->id);
|
||||||
|
REQUIRE(i.brand == planes[index]->brand);
|
||||||
|
REQUIRE(i.model == planes[index++]->model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Select with prepared statement") {
|
||||||
|
for (const auto &plane: planes) {
|
||||||
|
auto res = db.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("airplane", column_generator::generate<airplane>(schema, true))
|
||||||
|
.values(*plane)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stmt = db.query(schema)
|
||||||
|
.select(column_generator::generate<airplane>(schema, true))
|
||||||
|
.from(ap)
|
||||||
|
.where("brand"_col == _)
|
||||||
|
.prepare();
|
||||||
|
|
||||||
|
stmt.bind(0, "Airbus");
|
||||||
|
|
||||||
|
auto result = stmt.fetch<airplane>();
|
||||||
|
|
||||||
|
for (const auto &i: result) {
|
||||||
|
REQUIRE(i.id == planes[0]->id);
|
||||||
|
REQUIRE(i.brand == planes[0]->brand);
|
||||||
|
REQUIRE(i.model == planes[0]->model);
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.reset();
|
||||||
|
|
||||||
|
stmt.bind(0, "Boeing");
|
||||||
|
|
||||||
|
result = stmt.fetch<airplane>();
|
||||||
|
|
||||||
|
size_t index{1};
|
||||||
|
for (const auto &i: result) {
|
||||||
|
REQUIRE(i.id == planes[index]->id);
|
||||||
|
REQUIRE(i.brand == planes[index]->brand);
|
||||||
|
REQUIRE(i.model == planes[index++]->model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include "matador/sql/connection.hpp"
|
||||||
|
#include "matador/sql/column_generator.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/enum_mapper.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
#include "models/location.hpp"
|
||||||
|
|
||||||
|
using namespace matador::sql;
|
||||||
|
using namespace matador::test;
|
||||||
|
|
||||||
|
class TypeTraitsTestFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TypeTraitsTestFixture()
|
||||||
|
: db(matador::test::connection::dns)
|
||||||
|
, schema(db.dialect().default_schema_name())
|
||||||
|
{
|
||||||
|
db.open();
|
||||||
|
db.query(schema).create()
|
||||||
|
.table<location>("location")
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
~TypeTraitsTestFixture()
|
||||||
|
{
|
||||||
|
db.query(schema).drop().table("location").execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
matador::sql::connection db;
|
||||||
|
matador::sql::schema schema;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drop_table_if_exists(const std::string &table_name) {
|
||||||
|
if (db.exists(table_name)) {
|
||||||
|
db.query(schema).drop().table(table_name).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const matador::utils::enum_mapper<Color> color_enum({
|
||||||
|
{Color::Green, "green"},
|
||||||
|
{Color::Red, "red"},
|
||||||
|
{Color::Blue, "blue"},
|
||||||
|
{Color::Yellow, "yellow"},
|
||||||
|
{Color::Black, "black"},
|
||||||
|
{Color::White, "white"},
|
||||||
|
{Color::Brown, "brown"}
|
||||||
|
});
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct matador::sql::data_type_traits<Color, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t size)
|
||||||
|
{ return data_type_traits<std::string>::builtin_type(size); }
|
||||||
|
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, Color &value)
|
||||||
|
{
|
||||||
|
std::string enum_string;
|
||||||
|
reader.read_value(id, index, enum_string, 64);
|
||||||
|
if (const auto enum_opt = color_enum.to_enum(enum_string)) {
|
||||||
|
value = enum_opt.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static any_type create_value(const Color &value)
|
||||||
|
{
|
||||||
|
return color_enum.to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, Color &value)
|
||||||
|
{
|
||||||
|
binder.bind(index, color_enum.to_string(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(TypeTraitsTestFixture, "Special handling of attributes with type traits", "[typetraits]")
|
||||||
|
{
|
||||||
|
schema.attach<location>("location");
|
||||||
|
SECTION("Insert and select with direct execution") {
|
||||||
|
location loc{1, "center", {1, 2, 3}, Color::Black};
|
||||||
|
|
||||||
|
auto res = db
|
||||||
|
.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("location", column_generator::generate<location>(schema, true))
|
||||||
|
.values(loc)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db
|
||||||
|
.query(schema)
|
||||||
|
.select(column_generator::generate<location>(schema, true))
|
||||||
|
.from("location")
|
||||||
|
.fetch_all<location>();
|
||||||
|
|
||||||
|
for (const auto &l: result) {
|
||||||
|
REQUIRE(l.name == "center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Insert and select with prepared statement") {
|
||||||
|
location loc{1, "center", {1, 2, 3}, Color::Black};
|
||||||
|
|
||||||
|
auto stmt = db
|
||||||
|
.query(schema)
|
||||||
|
.insert()
|
||||||
|
.into("location", column_generator::generate<location>(schema, true))
|
||||||
|
.values<location>()
|
||||||
|
.prepare();
|
||||||
|
|
||||||
|
auto res = stmt
|
||||||
|
.bind(loc)
|
||||||
|
.execute();
|
||||||
|
REQUIRE(res == 1);
|
||||||
|
|
||||||
|
auto result = db
|
||||||
|
.query(schema)
|
||||||
|
.select(column_generator::generate<location>(schema, true))
|
||||||
|
.from("location")
|
||||||
|
.fetch_all<location>();
|
||||||
|
|
||||||
|
for (const auto &l: result) {
|
||||||
|
REQUIRE(l.name == "center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1269
cmake/CPM.cmake
1269
cmake/CPM.cmake
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,129 @@
|
||||||
|
# - Find mysqlclient
|
||||||
|
# Find the native MySQL includes and library
|
||||||
|
#
|
||||||
|
# MYSQL_INCLUDE_DIR - where to find mysql.h, etc.
|
||||||
|
# MYSQL_LIBRARY - List of libraries when using MySQL.
|
||||||
|
# MYSQL_FOUND - True if MySQL found.
|
||||||
|
|
||||||
|
IF (MYSQL_INCLUDE_DIR)
|
||||||
|
# Already in cache, be silent
|
||||||
|
SET(MYSQL_FIND_QUIETLY TRUE)
|
||||||
|
ENDIF (MYSQL_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
find_path(MYSQL_INCLUDE_DIR mysql.h
|
||||||
|
PATHS
|
||||||
|
$ENV{MYSQL_INCLUDE_DIR}
|
||||||
|
$ENV{MYSQL_DIR}/include
|
||||||
|
$ENV{ProgramFiles}/MySQL/*/include
|
||||||
|
$ENV{SystemDrive}/MySQL/*/include
|
||||||
|
$ENV{ProgramW6432}/MySQL/*/include
|
||||||
|
$ENV{ProgramFiles}/MariaDB/*/include
|
||||||
|
$ENV{SystemDrive}/MariaDB/*/include
|
||||||
|
$ENV{ProgramW6432}/MariaDB/*/include
|
||||||
|
)
|
||||||
|
else(WIN32)
|
||||||
|
find_path(MYSQL_INCLUDE_DIR mysql.h
|
||||||
|
PATHS
|
||||||
|
$ENV{MYSQL_DIR}/include
|
||||||
|
$ENV{MYSQL_INCLUDE_DIR}
|
||||||
|
/usr/local/mysql/include
|
||||||
|
/opt/mysql/mysql/include
|
||||||
|
PATH_SUFFIXES
|
||||||
|
mysql
|
||||||
|
)
|
||||||
|
endif(WIN32)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
if (${CMAKE_BUILD_TYPE})
|
||||||
|
STRING(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# path suffix for debug/release mode
|
||||||
|
# binary_dist: mysql binary distribution
|
||||||
|
# build_dist: custom build
|
||||||
|
if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug")
|
||||||
|
SET(binary_dist debug)
|
||||||
|
SET(build_dist Debug)
|
||||||
|
else(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug")
|
||||||
|
ADD_DEFINITIONS(-DDBUG_OFF)
|
||||||
|
SET(binary_dist opt)
|
||||||
|
SET(build_dist Release)
|
||||||
|
endif(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug")
|
||||||
|
|
||||||
|
FIND_LIBRARY(MYSQL_LIBRARY NAMES libmysql libmariadb
|
||||||
|
PATHS
|
||||||
|
$ENV{MYSQL_DIR}/lib
|
||||||
|
# $ENV{MYSQL_DIR}/lib/${binary_dist}
|
||||||
|
# $ENV{MYSQL_DIR}/libmysql/${build_dist}
|
||||||
|
# $ENV{MYSQL_DIR}/client/${build_dist}
|
||||||
|
# $ENV{ProgramFiles}/MySQL/*/lib/${binary_dist}
|
||||||
|
$ENV{ProgramFiles}/MySQL/*/lib
|
||||||
|
$ENV{SystemDrive}/MySQL/*/lib
|
||||||
|
$ENV{ProgramW6432}/MySQL/*/lib
|
||||||
|
$ENV{ProgramFiles}/MariaDB/*/lib
|
||||||
|
$ENV{SystemDrive}/MariaDB/*/lib
|
||||||
|
$ENV{ProgramW6432}/MariaDB/*/lib
|
||||||
|
# $ENV{SystemDrive}/MySQL/*/lib/${binary_dist}
|
||||||
|
# $ENV{ProgramFiles}/MariaDB/*/lib/${binary_dist}
|
||||||
|
# $ENV{SystemDrive}/MariaDB/*/lib/${binary_dist}
|
||||||
|
# $ENV{MYSQL_DIR}/lib/opt
|
||||||
|
# $ENV{MYSQL_DIR}/client/release
|
||||||
|
# $ENV{ProgramFiles}/MySQL/*/lib/opt
|
||||||
|
# $ENV{SystemDrive}/MySQL/*/lib/opt
|
||||||
|
# $ENV{ProgramW6432}/MySQL/*/lib
|
||||||
|
# $ENV{ProgramFiles}/MariaDB/*/lib/opt
|
||||||
|
# $ENV{SystemDrive}/MariaDB/*/lib/opt
|
||||||
|
# $ENV{ProgramW6432}/MariaDB/*/lib
|
||||||
|
)
|
||||||
|
else(WIN32)
|
||||||
|
find_library(MYSQL_LIBRARY NAMES libmysql
|
||||||
|
PATHS
|
||||||
|
$ENV{MYSQL_DIR}/libmysql_r/.libs
|
||||||
|
$ENV{MYSQL_DIR}/lib
|
||||||
|
$ENV{MYSQL_DIR}/lib/mysql
|
||||||
|
/usr/local/mysql/lib
|
||||||
|
/opt/mysql/mysql/lib
|
||||||
|
PATH_SUFFIXES
|
||||||
|
mysql
|
||||||
|
)
|
||||||
|
endif(WIN32)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
else(WIN32)
|
||||||
|
set(MYSQL_LIB_PATHS
|
||||||
|
$ENV{MYSQL_DIR}/libmysql_r/.libs
|
||||||
|
$ENV{MYSQL_DIR}/lib
|
||||||
|
$ENV{MYSQL_DIR}/lib/mysql
|
||||||
|
/usr/local/mysql/lib
|
||||||
|
/opt/mysql/mysql/lib
|
||||||
|
PATH_SUFFIXES
|
||||||
|
mysql
|
||||||
|
)
|
||||||
|
find_library(MYSQL_LIBRARY NAMES mysqlclient
|
||||||
|
PATHS
|
||||||
|
${MYSQL_LIB_PATHS}
|
||||||
|
)
|
||||||
|
endif(WIN32)
|
||||||
|
|
||||||
|
IF (MYSQL_INCLUDE_DIR)
|
||||||
|
MESSAGE(STATUS "MariaDB include ${MYSQL_INCLUDE_DIR}")
|
||||||
|
ELSE (MYSQL_INCLUDE_DIR)
|
||||||
|
MESSAGE(STATUS "MariaDB include dir not found")
|
||||||
|
ENDIF (MYSQL_INCLUDE_DIR)
|
||||||
|
|
||||||
|
IF (MYSQL_LIBRARY)
|
||||||
|
MESSAGE(STATUS "MariaDB libs ${MYSQL_LIBRARY}")
|
||||||
|
ELSE (MYSQL_LIBRARY)
|
||||||
|
MESSAGE(STATUS "MariaDB libs dir not found")
|
||||||
|
ENDIF (MYSQL_LIBRARY)
|
||||||
|
|
||||||
|
|
||||||
|
IF (MYSQL_INCLUDE_DIR AND MYSQL_LIBRARY)
|
||||||
|
SET(MYSQL_FOUND TRUE)
|
||||||
|
ELSE (MYSQL_INCLUDE_DIR AND MYSQL_LIBRARY)
|
||||||
|
SET(MYSQL_FOUND FALSE)
|
||||||
|
SET(MYSQL_LIBRARY)
|
||||||
|
ENDIF (MYSQL_INCLUDE_DIR AND MYSQL_LIBRARY)
|
||||||
|
|
||||||
|
MARK_AS_ADVANCED(MYSQL_LIBRARY MYSQL_INCLUDE_DIR)
|
||||||
|
|
@ -1,32 +1,8 @@
|
||||||
add_executable(demo main.cpp
|
|
||||||
object.cpp)
|
|
||||||
|
add_executable(demo main.cpp)
|
||||||
target_link_libraries(demo PRIVATE
|
target_link_libraries(demo PRIVATE
|
||||||
matador-core
|
matador
|
||||||
matador-orm
|
|
||||||
${CMAKE_DL_LIBS}
|
${CMAKE_DL_LIBS}
|
||||||
${SQLite3_LIBRARIES}
|
${SQLite3_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(sandbox sandbox.cpp)
|
|
||||||
target_link_libraries(sandbox PRIVATE
|
|
||||||
matador-core
|
|
||||||
matador-orm
|
|
||||||
${CMAKE_DL_LIBS}
|
|
||||||
${SQLite3_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(work work.cpp)
|
|
||||||
target_link_libraries(work PRIVATE
|
|
||||||
matador-core
|
|
||||||
matador-orm
|
|
||||||
${CMAKE_DL_LIBS}
|
|
||||||
${PostgreSQL_LIBRARY}
|
|
||||||
)
|
|
||||||
|
|
||||||
add_dependencies(work matador-postgres)
|
|
||||||
|
|
||||||
add_executable(object object.cpp)
|
|
||||||
target_link_libraries(object PRIVATE
|
|
||||||
matador-core
|
|
||||||
${CMAKE_DL_LIBS}
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#ifndef AUTHOR_HPP
|
|
||||||
#define AUTHOR_HPP
|
|
||||||
|
|
||||||
#include "matador/object/collection.hpp"
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/access.hpp"
|
|
||||||
|
|
||||||
namespace demo {
|
|
||||||
struct book;
|
|
||||||
|
|
||||||
struct author {
|
|
||||||
unsigned int id{};
|
|
||||||
std::string first_name;
|
|
||||||
std::string last_name;
|
|
||||||
std::string date_of_birth;
|
|
||||||
unsigned short year_of_birth{};
|
|
||||||
bool distinguished{false};
|
|
||||||
matador::object::collection<matador::object::object_ptr<book>> books;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::attribute( op, "first_name", first_name, 63 );
|
|
||||||
field::attribute( op, "last_name", last_name, 63 );
|
|
||||||
field::attribute( op, "date_of_birth", date_of_birth, 31 );
|
|
||||||
field::attribute( op, "year_of_birth", year_of_birth );
|
|
||||||
field::attribute( op, "distinguished", distinguished );
|
|
||||||
field::has_many( op, "books", books, "author_id", matador::utils::CascadeNoneFetchLazy );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //AUTHOR_HPP
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
#ifndef BOOK_HPP
|
|
||||||
#define BOOK_HPP
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
#include "matador/utils/access.hpp"
|
|
||||||
|
|
||||||
namespace demo {
|
|
||||||
struct author;
|
|
||||||
|
|
||||||
struct book {
|
|
||||||
unsigned int id{};
|
|
||||||
matador::object::object_ptr<author> book_author;
|
|
||||||
std::string title;
|
|
||||||
unsigned short published_in{};
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::attribute( op, "title", title, 511 );
|
|
||||||
field::belongs_to( op, "author_id", book_author, matador::utils::CascadeNoneFetchLazy );
|
|
||||||
field::attribute( op, "published_in", published_in );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif //BOOK_HPP
|
|
||||||
324
demo/main.cpp
324
demo/main.cpp
|
|
@ -1,25 +1,19 @@
|
||||||
#include "matador/query/column.hpp"
|
#include "matador/sql/column.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
|
#include "matador/sql/schema.hpp"
|
||||||
#include "matador/sql/connection.hpp"
|
#include "matador/sql/connection.hpp"
|
||||||
#include "matador/sql/query_macro.hpp"
|
#include "matador/sql/entity.hpp"
|
||||||
|
|
||||||
#include "matador/query/criteria.hpp"
|
|
||||||
#include "matador/query/query.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
#include "matador/object/repository.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/access.hpp"
|
#include "matador/utils/access.hpp"
|
||||||
#include "matador/utils/default_type_traits.hpp"
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
#include "matador/utils/enum_mapper.hpp"
|
||||||
#include "matador/utils/types.hpp"
|
|
||||||
|
|
||||||
#include "matador/sql/query_macro.hpp"
|
#include "matador/sql/query_helper.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct author {
|
struct author {
|
||||||
unsigned int id{};
|
unsigned long id{};
|
||||||
std::string first_name;
|
std::string first_name;
|
||||||
std::string last_name;
|
std::string last_name;
|
||||||
std::string date_of_birth;
|
std::string date_of_birth;
|
||||||
|
|
@ -28,7 +22,7 @@ struct author {
|
||||||
|
|
||||||
template<typename Operator>
|
template<typename Operator>
|
||||||
void process( Operator& op ) {
|
void process( Operator& op ) {
|
||||||
namespace field = matador::access;
|
namespace field = matador::utils::access;
|
||||||
field::primary_key( op, "id", id );
|
field::primary_key( op, "id", id );
|
||||||
field::attribute( op, "first_name", first_name, 63 );
|
field::attribute( op, "first_name", first_name, 63 );
|
||||||
field::attribute( op, "last_name", last_name, 63 );
|
field::attribute( op, "last_name", last_name, 63 );
|
||||||
|
|
@ -39,27 +33,27 @@ struct author {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct book {
|
struct book {
|
||||||
unsigned int id{};
|
unsigned long id{};
|
||||||
matador::object::object_ptr<author> book_author;
|
matador::sql::entity<author> book_author;
|
||||||
std::string title;
|
std::string title;
|
||||||
unsigned short published_in{};
|
unsigned short published_in{};
|
||||||
|
|
||||||
template<typename Operator>
|
template<typename Operator>
|
||||||
void process( Operator& op ) {
|
void process( Operator& op ) {
|
||||||
namespace field = matador::access;
|
namespace field = matador::utils::access;
|
||||||
field::primary_key( op, "id", id );
|
field::primary_key( op, "id", id );
|
||||||
field::attribute( op, "title", title, 511 );
|
field::attribute( op, "title", title, 511 );
|
||||||
field::has_one( op, "author_id", book_author, matador::utils::CascadeNoneFetchLazy );
|
field::has_one( op, "author_id", book_author, matador::utils::default_foreign_attributes );
|
||||||
field::attribute( op, "published_in", published_in );
|
field::attribute( op, "published_in", published_in );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct payload {
|
struct payload {
|
||||||
unsigned int id{};
|
unsigned long id{};
|
||||||
|
|
||||||
template<typename Operator>
|
template<typename Operator>
|
||||||
void process( Operator& op ) {
|
void process( Operator& op ) {
|
||||||
namespace field = matador::access;
|
namespace field = matador::utils::access;
|
||||||
field::primary_key( op, "id", id );
|
field::primary_key( op, "id", id );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -78,8 +72,8 @@ struct job {
|
||||||
Background
|
Background
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned int id{};
|
unsigned long id{};
|
||||||
matador::object::object_ptr<payload> data;
|
matador::sql::entity<payload> payload;
|
||||||
std::string type;
|
std::string type;
|
||||||
std::string description;
|
std::string description;
|
||||||
job_state state;
|
job_state state;
|
||||||
|
|
@ -87,9 +81,9 @@ struct job {
|
||||||
|
|
||||||
template<typename Operator>
|
template<typename Operator>
|
||||||
void process( Operator& op ) {
|
void process( Operator& op ) {
|
||||||
namespace field = matador::access;
|
namespace field = matador::utils::access;
|
||||||
field::primary_key( op, "id", id );
|
field::primary_key( op, "id", id );
|
||||||
field::belongs_to( op, "payload", data, matador::utils::CascadeNoneFetchLazy );
|
field::belongs_to( op, "payload", payload, matador::utils::default_foreign_attributes );
|
||||||
field::attribute( op, "type", type, 511 );
|
field::attribute( op, "type", type, 511 );
|
||||||
field::attribute( op, "description", description, 511 );
|
field::attribute( op, "description", description, 511 );
|
||||||
field::attribute( op, "state", state );
|
field::attribute( op, "state", state );
|
||||||
|
|
@ -111,10 +105,13 @@ static const matador::utils::enum_mapper<job::job_mode> job_mode_enum({
|
||||||
});
|
});
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct matador::utils::data_type_traits<job::job_state, void> {
|
struct matador::sql::data_type_traits<job::job_state, void>
|
||||||
static basic_type type(const std::size_t size) { return data_type_traits<std::string>::type(size); }
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t size)
|
||||||
|
{ return data_type_traits<std::string>::builtin_type(size); }
|
||||||
|
|
||||||
static void read_value(attribute_reader &reader, const char *id, const size_t index, job::job_state &value) {
|
static void read_value(query_result_reader &reader, const char *id, size_t index, job::job_state &value)
|
||||||
|
{
|
||||||
std::string enum_string;
|
std::string enum_string;
|
||||||
reader.read_value(id, index, enum_string, 64);
|
reader.read_value(id, index, enum_string, 64);
|
||||||
if (const auto enum_opt = job_state_enum.to_enum(enum_string)) {
|
if (const auto enum_opt = job_state_enum.to_enum(enum_string)) {
|
||||||
|
|
@ -122,16 +119,25 @@ struct matador::utils::data_type_traits<job::job_state, void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void write_value(attribute_writer &binder, const size_t index, const job::job_state &value) {
|
static any_type create_value(const job::job_state &value)
|
||||||
binder.write_value(index, job_state_enum.to_string(value));
|
{
|
||||||
|
return job_state_enum.to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, job::job_state &value)
|
||||||
|
{
|
||||||
|
binder.bind(index, job_state_enum.to_string(value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct matador::utils::data_type_traits<job::job_mode, void> {
|
struct matador::sql::data_type_traits<job::job_mode, void>
|
||||||
static basic_type type( const std::size_t size) { return data_type_traits<std::string>::type(size); }
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t size)
|
||||||
|
{ return data_type_traits<std::string>::builtin_type(size); }
|
||||||
|
|
||||||
static void read_value(attribute_reader &reader, const char *id, const size_t index, job::job_mode &value) {
|
static void read_value(query_result_reader &reader, const char *id, size_t index, job::job_mode &value)
|
||||||
|
{
|
||||||
std::string enum_string;
|
std::string enum_string;
|
||||||
reader.read_value(id, index, enum_string, 64);
|
reader.read_value(id, index, enum_string, 64);
|
||||||
if (const auto enum_opt = job_mode_enum.to_enum(enum_string)) {
|
if (const auto enum_opt = job_mode_enum.to_enum(enum_string)) {
|
||||||
|
|
@ -139,155 +145,151 @@ struct matador::utils::data_type_traits<job::job_mode, void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bind_value(attribute_writer &binder, const size_t index, const job::job_mode &value) {
|
static any_type create_value(const job::job_mode &value)
|
||||||
binder.write_value(index, job_mode_enum.to_string(value));
|
{
|
||||||
|
return job_mode_enum.to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, job::job_mode &value)
|
||||||
|
{
|
||||||
|
binder.bind(index, job_mode_enum.to_string(value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
QUERY_HELPER( authors, AUTHOR, id, first_name, last_name, date_of_birth, year_of_birth, distinguished )
|
QUERY_HELPER( authors, id, first_name, last_name, date_of_birth, year_of_birth, distinguished )
|
||||||
|
|
||||||
QUERY_HELPER( books, BOOK, id, author_id, title, published_in )
|
QUERY_HELPER( books, id, author_id, title, published_in )
|
||||||
|
|
||||||
QUERY_HELPER( job, JOB, id, payload, type, description, state, mode )
|
QUERY_HELPER( job, id, payload, type, description, state, mode )
|
||||||
|
|
||||||
QUERY_HELPER( payload, PAYLOAD, id )
|
QUERY_HELPER( payload, id )
|
||||||
|
|
||||||
QUERY_HELPER( temporary_table, TEMPORARY_TABLE, id );
|
QUERY_HELPER( temporary_table, id );
|
||||||
|
|
||||||
QUERY_HELPER(customer, CUSTOMER, id, name, email, address)
|
|
||||||
QUERY_HELPER(product, PRODUCT, id, title, description, price, category)
|
|
||||||
QUERY_HELPER(category, CATEGORY, id, title, description)
|
|
||||||
QUERY_HELPER(cart, CART, id, items, owner)
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
using namespace matador::sql;
|
using namespace matador::sql;
|
||||||
using namespace matador::object;
|
using namespace matador;
|
||||||
using namespace matador::utils;
|
|
||||||
using namespace matador::query::meta;
|
|
||||||
|
|
||||||
const std::string env_var{"MATADOR_BACKENDS_PATH"};
|
const std::string env_var{"MATADOR_BACKENDS_PATH"};
|
||||||
|
|
||||||
std::string dns{"sqlite://demo.db"};
|
std::string dns{"sqlite://demo.db"};
|
||||||
repository s( "main" );
|
schema s( "main" );
|
||||||
auto result = s.attach<author>( "authors" ).and_then( [&s] {
|
s.attach<author>( "authors" );
|
||||||
return s.attach<book>( "books" );
|
s.attach<book>( "books" );
|
||||||
} );
|
|
||||||
// s.attach<book>( "books" );
|
|
||||||
|
|
||||||
connection c( dns );
|
connection c( dns );
|
||||||
result = c.open();
|
c.open();
|
||||||
// s.create( c );
|
s.create( c );
|
||||||
//
|
|
||||||
// auto create_authors_sql = c.query( s )
|
auto create_authors_sql = c.query( s )
|
||||||
// .create()
|
.create()
|
||||||
// .table<author>( qh::authors )
|
.table<author>( qh::authors )
|
||||||
// .execute();
|
.execute();
|
||||||
//
|
|
||||||
// c.query( s )
|
c.query( s )
|
||||||
// .create()
|
.create()
|
||||||
// .table<book>( qh::books )
|
.table<book>( qh::books )
|
||||||
// .execute();
|
.execute();
|
||||||
//
|
|
||||||
// std::cout << "SQL: " << create_authors_sql << "\n";
|
std::cout << "SQL: " << create_authors_sql << "\n";
|
||||||
//
|
|
||||||
// author mc;
|
author mc;
|
||||||
// mc.id = 1;
|
mc.id = 1;
|
||||||
// mc.first_name = "Michael";
|
mc.first_name = "Michael";
|
||||||
// mc.last_name = "Crichton";
|
mc.last_name = "Crichton";
|
||||||
// mc.date_of_birth = "19.8.1954";
|
mc.date_of_birth = "19.8.1954";
|
||||||
// mc.year_of_birth = 1954;
|
mc.year_of_birth = 1954;
|
||||||
// mc.distinguished = true;
|
mc.distinguished = true;
|
||||||
// auto insert_authors_sql = c.query( s )
|
auto insert_authors_sql = c.query( s )
|
||||||
// .insert()
|
.insert()
|
||||||
// .into( qh::authors )
|
.into( qh::authors )
|
||||||
// .values( mc )
|
.values( mc )
|
||||||
// .execute();
|
.execute();
|
||||||
//
|
|
||||||
// std::cout << "SQL: " << insert_authors_sql << "\n";
|
std::cout << "SQL: " << insert_authors_sql << "\n";
|
||||||
//
|
|
||||||
// auto result = c.query( s )
|
auto result = c.query( s )
|
||||||
// .select( qh::authors.columns )
|
.select( qh::authors.columns )
|
||||||
// .from( qh::authors )
|
.from( qh::authors )
|
||||||
// .fetch_all();
|
.fetch_all();
|
||||||
//
|
|
||||||
// for (const auto& row: result) { std::cout << "Author " << row.at( qh::authors.first_name ) << "\n"; }
|
for (const auto& row: result) { std::cout << "Author " << row.at( qh::authors.first_name ) << "\n"; }
|
||||||
//
|
|
||||||
// auto update_authors_sql = c.query( s )
|
auto update_authors_sql = c.query( s )
|
||||||
// .update( qh::authors )
|
.update( qh::authors )
|
||||||
// .set( {{qh::authors.first_name, "Stephen"},
|
.set( {{qh::authors.first_name, "Stephen"},
|
||||||
// {qh::authors.last_name, "King"}} )
|
{qh::authors.last_name, "King"}} )
|
||||||
// .where( qh::authors.last_name == "Crichton" )
|
.where( qh::authors.last_name == "Crichton" )
|
||||||
// .execute();
|
.execute();
|
||||||
//
|
|
||||||
// std::cout << "SQL: " << update_authors_sql << "\n";
|
std::cout << "SQL: " << update_authors_sql << "\n";
|
||||||
//
|
|
||||||
// auto authors = c.query( s )
|
auto authors = c.query( s )
|
||||||
// .select( qh::authors.columns )
|
.select( qh::authors.columns )
|
||||||
// .from( qh::authors )
|
.from( qh::authors )
|
||||||
// .fetch_all<author>();
|
.fetch_all<author>();
|
||||||
//
|
|
||||||
// for (const auto& a: authors) { std::cout << "Author " << a.first_name << "\n"; }
|
for (const auto& a: authors) { std::cout << "Author " << a.first_name << "\n"; }
|
||||||
//
|
|
||||||
// c.query( s )
|
c.query( s )
|
||||||
// .insert()
|
.insert()
|
||||||
// .into( qh::books )
|
.into( qh::books )
|
||||||
// .values( {2, "It", mc.id, 1980} )
|
.values( {2, "It", mc.id, 1980} )
|
||||||
// .execute();
|
.execute();
|
||||||
//
|
|
||||||
// c.query( s )
|
c.query( s )
|
||||||
// .insert()
|
.insert()
|
||||||
// .into( qh::books )
|
.into( qh::books )
|
||||||
// .values( {3, "Misery", mc.id, 1984} )
|
.values( {3, "Misery", mc.id, 1984} )
|
||||||
// .execute();
|
.execute();
|
||||||
//
|
|
||||||
auto select_books_sql = matador::query::query::select( BOOK, {AUTHOR.last_name} )
|
auto select_books_sql = c.query( s )
|
||||||
.from( BOOK )
|
.select( qh::books.columns, {qh::authors.last_name} )
|
||||||
.join_left( AUTHOR )
|
.from( qh::books )
|
||||||
.on( BOOK.author_id == AUTHOR.id )
|
.join_left( qh::authors )
|
||||||
.where( BOOK.published_in < 2008 && AUTHOR.last_name == "King" )
|
.on( qh::books.author_id == qh::authors.id )
|
||||||
.group_by( BOOK.published_in )
|
.where( qh::books.published_in < 2008 && qh::authors.last_name == "King" )
|
||||||
.order_by( BOOK.title ).asc()
|
.group_by( qh::books.published_in )
|
||||||
|
.order_by( qh::books.title ).asc()
|
||||||
.limit( 5 )
|
.limit( 5 )
|
||||||
.offset( 2 )
|
.offset( 2 )
|
||||||
.fetch_all(c);
|
.fetch_all();
|
||||||
|
|
||||||
for (const auto& r: *select_books_sql) {
|
for (const auto& r: select_books_sql) { std::cout << "R: " << r.at( qh::books.title ) << ", " << r.at( qh::authors.last_name ) << "\n"; }
|
||||||
std::cout << "R: " << r.at( BOOK.title ) << ", " << r.at( AUTHOR.last_name ) << "\n";
|
// SELECT book.title, book.id, book.author_id, book.published_in, author.name
|
||||||
}
|
// FROM book
|
||||||
// // SELECT book.title, book.id, book.author_id, book.published_in, author.name
|
// INNER JOIN author ON book.author_id = author.id
|
||||||
// // FROM book
|
// WHERE book.published_in < 2008 AND author.name = "Michael Crichton"
|
||||||
// // INNER JOIN author ON book.author_id = author.id
|
// ORDER BY "book.title" ASC
|
||||||
// // WHERE book.published_in < 2008 AND author.name = "Michael Crichton"
|
// OFFSET 2 LIMIT 5
|
||||||
// // ORDER BY "book.title" ASC
|
|
||||||
// // OFFSET 2 LIMIT 5
|
c.query( s ).drop().table( qh::books ).execute();
|
||||||
//
|
|
||||||
// c.query( s ).drop().table( qh::books ).execute();
|
auto drop_authors_sql = c.query( s )
|
||||||
//
|
.drop()
|
||||||
// auto drop_authors_sql = c.query( s )
|
.table( qh::authors )
|
||||||
// .drop()
|
.execute();
|
||||||
// .table( qh::authors )
|
|
||||||
// .execute();
|
std::cout << "SQL: " << drop_authors_sql << "\n";
|
||||||
//
|
|
||||||
// std::cout << "SQL: " << drop_authors_sql << "\n";
|
auto res = c.query( s )
|
||||||
//
|
.select( {qh::payload.id} )
|
||||||
// auto res = c.query( s )
|
.from( qh::payload )
|
||||||
// .select( {qh::payload.id} )
|
.join_left( qh::job )
|
||||||
// .from( qh::payload )
|
.on( qh::job.payload == qh::payload.id )
|
||||||
// .join_left( qh::job )
|
.where(
|
||||||
// .on( qh::job.payload == qh::payload.id )
|
in( qh::payload.id, c.query( s )
|
||||||
// .where(
|
.select( {qh::job.state} )
|
||||||
// in( qh::payload.id, c.query( s )
|
.from( qh::job )
|
||||||
// .select( {qh::job.state} )
|
.where( qh::job.state == job::job_state::Running )
|
||||||
// .from( qh::job )
|
) &&
|
||||||
// .where( qh::job.state == job::job_state::Running )
|
in( qh::payload.id, c.query( s )
|
||||||
// ) &&
|
.select( {qh::temporary_table.id} )
|
||||||
// in( qh::payload.id, c.query( s )
|
.from( qh::temporary_table ) )
|
||||||
// .select( {qh::temporary_table.id} )
|
)
|
||||||
// .from( qh::temporary_table ) )
|
.build();
|
||||||
// )
|
// .fetch_value<unsigned long>();
|
||||||
// .build();
|
std::cout << "SQL: " << res.sql << "\n";
|
||||||
// // .fetch_value<unsigned long>();
|
|
||||||
// std::cout << "SQL: " << res.sql << "\n";
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
233
demo/object.cpp
233
demo/object.cpp
|
|
@ -1,233 +0,0 @@
|
||||||
#include "matador/utils/basic_types.hpp"
|
|
||||||
#include "matador/utils/constraints.hpp"
|
|
||||||
#include "matador/utils/field_attributes.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
|
|
||||||
namespace object {
|
|
||||||
|
|
||||||
namespace utils = matador::utils;
|
|
||||||
|
|
||||||
class object;
|
|
||||||
|
|
||||||
enum class null_option_type : uint8_t {
|
|
||||||
Nullable,
|
|
||||||
NotNull
|
|
||||||
};
|
|
||||||
|
|
||||||
class attribute {
|
|
||||||
public:
|
|
||||||
explicit attribute(std::string name); // NOLINT(*-explicit-constructor)
|
|
||||||
|
|
||||||
attribute(const attribute&) = default;
|
|
||||||
attribute& operator=(const attribute&) = default;
|
|
||||||
attribute(attribute&&) noexcept = default;
|
|
||||||
attribute& operator=(attribute&&) noexcept = default;
|
|
||||||
|
|
||||||
attribute() = default;
|
|
||||||
attribute(std::string name,
|
|
||||||
utils::basic_type type,
|
|
||||||
const utils::field_attributes& opts = utils::null_attributes,
|
|
||||||
null_option_type null_opt = null_option_type::NotNull)
|
|
||||||
: name_(std::move(name)), type_(type), options_(opts), null_option_type_(null_opt) {}
|
|
||||||
|
|
||||||
[[nodiscard]] const std::string& name() const { return name_; }
|
|
||||||
void name(const std::string& n) { name_ = n; }
|
|
||||||
[[nodiscard]] const utils::field_attributes& options() const { return options_; }
|
|
||||||
[[nodiscard]] utils::field_attributes& options();
|
|
||||||
|
|
||||||
[[nodiscard]] bool is_nullable() const { return null_option_type_ == null_option_type::Nullable; }
|
|
||||||
[[nodiscard]] utils::basic_type type() const { return type_; }
|
|
||||||
|
|
||||||
[[nodiscard]] bool is_integer() const { return type_ >= utils::basic_type::type_int8 && type_ <= utils::basic_type::type_uint64; }
|
|
||||||
[[nodiscard]] bool is_floating_point() const { return type_ == utils::basic_type::type_float || type_ == utils::basic_type::type_double; }
|
|
||||||
[[nodiscard]] bool is_bool() const { return type_ == utils::basic_type::type_bool; }
|
|
||||||
[[nodiscard]] bool is_string() const { return type_ == utils::basic_type::type_text; }
|
|
||||||
[[nodiscard]] bool is_varchar() const { return type_ == utils::basic_type::type_varchar; }
|
|
||||||
[[nodiscard]] bool is_date() const { return type_ == utils::basic_type::type_date; }
|
|
||||||
[[nodiscard]] bool is_time() const { return type_ == utils::basic_type::type_time; }
|
|
||||||
[[nodiscard]] bool is_blob() const { return type_ == utils::basic_type::type_blob; }
|
|
||||||
[[nodiscard]] bool is_null() const { return type_ == utils::basic_type::type_null; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class object;
|
|
||||||
|
|
||||||
std::string name_;
|
|
||||||
std::string alias_;
|
|
||||||
utils::basic_type type_{utils::basic_type::type_null};
|
|
||||||
utils::field_attributes options_;
|
|
||||||
null_option_type null_option_type_{null_option_type::NotNull};
|
|
||||||
|
|
||||||
object* parent_{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
class constraint {
|
|
||||||
public:
|
|
||||||
constraint() = default;
|
|
||||||
|
|
||||||
explicit constraint(std::string name) : name_(std::move(name)) {}
|
|
||||||
[[nodiscard]] const std::string& name() const { return name_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class object;
|
|
||||||
|
|
||||||
std::string name_;
|
|
||||||
std::string attribute_name_;
|
|
||||||
utils::constraints options_{utils::constraints::None};
|
|
||||||
|
|
||||||
object* parent_{nullptr};
|
|
||||||
attribute* attribute_{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
class object {
|
|
||||||
public:
|
|
||||||
explicit object(std::string name, std::string alias = "");
|
|
||||||
|
|
||||||
void add_attribute(attribute attr) {
|
|
||||||
auto &ref = attributes_.emplace_back(std::move(attr));
|
|
||||||
ref.parent_ = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const attribute& create_attribute(std::string name, object& obj) {
|
|
||||||
attribute attr{std::move(name)};
|
|
||||||
attr.parent_ = &obj;
|
|
||||||
return obj.attributes_.emplace_back(std::move(attr));
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_constraint(constraint c) {
|
|
||||||
auto &ref = constraints_.emplace_back(std::move(c));
|
|
||||||
ref.parent_ = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] const std::string& name() const { return name_; }
|
|
||||||
[[nodiscard]] const std::string& alias() const { return alias_; }
|
|
||||||
|
|
||||||
[[nodiscard]] bool has_attributes() const { return attributes_.empty(); }
|
|
||||||
[[nodiscard]] size_t attribute_count() const { return attributes_.size(); }
|
|
||||||
[[nodiscard]] const std::vector<attribute>& attributes() const { return attributes_; }
|
|
||||||
|
|
||||||
[[nodiscard]] bool has_constraints() const { return constraints_.empty(); }
|
|
||||||
[[nodiscard]] size_t constraint_count() const { return constraints_.size(); }
|
|
||||||
[[nodiscard]] const std::vector<constraint>& constraints() const { return constraints_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string name_;
|
|
||||||
std::string alias_;
|
|
||||||
|
|
||||||
std::vector<attribute> attributes_;
|
|
||||||
std::vector<constraint> constraints_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Type>
|
|
||||||
class typed_object : public object {
|
|
||||||
public:
|
|
||||||
using object::object;
|
|
||||||
|
|
||||||
Type as(std::string alias) { return Type{std::move(alias)}; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct column_builder {
|
|
||||||
explicit column_builder(std::string column_name)
|
|
||||||
: column_name( std::move(column_name) ) {
|
|
||||||
}
|
|
||||||
|
|
||||||
column_builder& not_null() {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
column_builder& primary_key() {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator attribute() const {
|
|
||||||
return attribute{column_name};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string column_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
column_builder column(std::string name) {
|
|
||||||
return column_builder(std::move(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace matador::sql {
|
|
||||||
struct constraint {
|
|
||||||
std::string name;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct constraint_builder {
|
|
||||||
constraint_builder& constraint(std::string name) {
|
|
||||||
constraint_name = std::move(name);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
constraint_builder& primary_key(std::string name) {
|
|
||||||
pk_column_name = std::move(name);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
constraint_builder& foreign_key(std::string name) {
|
|
||||||
fk_column_name = std::move(name);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
constraint_builder& references(std::string table, std::string column) {
|
|
||||||
this->table_name = std::move(table);
|
|
||||||
this->column_name = std::move(column);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator matador::sql::constraint() const {
|
|
||||||
return matador::sql::constraint{constraint_name};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string constraint_name;
|
|
||||||
std::string pk_column_name;
|
|
||||||
std::string fk_column_name;
|
|
||||||
std::string table_name;
|
|
||||||
std::string column_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
constraint_builder constraint(std::string name) {
|
|
||||||
constraint_builder builder;
|
|
||||||
return builder.constraint(std::move(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Type>
|
|
||||||
std::string column_prefix(typed_object<Type> *tab) {
|
|
||||||
if (!tab || tab->empty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (!tab->alias().empty()) {
|
|
||||||
return tab->alias();
|
|
||||||
}
|
|
||||||
return tab->name();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct table_builder {
|
|
||||||
explicit table_builder(std::string name)
|
|
||||||
: table_name( std::move(name) ) {}
|
|
||||||
|
|
||||||
table_builder& as(std::string table_alias) {
|
|
||||||
this->alias = std::move(table_alias);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator object() const {
|
|
||||||
return object{table_name, alias};
|
|
||||||
}
|
|
||||||
std::string table_name;
|
|
||||||
std::string alias;
|
|
||||||
};
|
|
||||||
|
|
||||||
table_builder table(std::string name) {
|
|
||||||
return table_builder(std::move(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
#ifndef QUERY_RECIPE_HPP
|
|
||||||
#define QUERY_RECIPE_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/access.hpp"
|
|
||||||
#include "matador/utils/foreign_attributes.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/collection.hpp"
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
#include "matador/object/many_to_many_relation.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace demo {
|
|
||||||
|
|
||||||
struct recipe;
|
|
||||||
struct ingredient
|
|
||||||
{
|
|
||||||
unsigned int id{};
|
|
||||||
std::string name;
|
|
||||||
matador::object::collection<matador::object::object_ptr<demo::recipe>> recipes{};
|
|
||||||
|
|
||||||
ingredient()= default;
|
|
||||||
ingredient(const unsigned int id, std::string name)
|
|
||||||
: id(id), name(std::move(name)) {}
|
|
||||||
|
|
||||||
template<class Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key(op, "id", id);
|
|
||||||
field::attribute(op, "name", name, 255);
|
|
||||||
field::has_many_to_many(op, "recipe_ingredients", recipes, "ingredient_id", "recipe_id", matador::utils::fetch_type::EAGER);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct recipe
|
|
||||||
{
|
|
||||||
unsigned int id{};
|
|
||||||
std::string name;
|
|
||||||
matador::object::collection<matador::object::object_ptr<demo::ingredient>> ingredients{};
|
|
||||||
|
|
||||||
recipe()= default;
|
|
||||||
recipe(const unsigned int id, std::string name)
|
|
||||||
: id(id), name(std::move(name)) {}
|
|
||||||
|
|
||||||
template<class Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key(op, "id", id);
|
|
||||||
field::attribute(op, "name", name, 255);
|
|
||||||
field::has_many_to_many(op, "recipe_ingredients", ingredients, matador::utils::fetch_type::LAZY);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class recipe_ingredient : public matador::object::many_to_many_relation<recipe, ingredient> {
|
|
||||||
public:
|
|
||||||
recipe_ingredient() : many_to_many_relation("recipe_id", "ingredient_id") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //QUERY_RECIPE_HPP
|
|
||||||
214
demo/sandbox.cpp
214
demo/sandbox.cpp
|
|
@ -1,214 +0,0 @@
|
||||||
#include "matador/object/repository.hpp"
|
|
||||||
|
|
||||||
#include "matador/logger/log_manager.hpp"
|
|
||||||
|
|
||||||
#include "author.hpp"
|
|
||||||
#include "book.hpp"
|
|
||||||
#include "recipe.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* node author
|
|
||||||
*
|
|
||||||
* relation_endpoints
|
|
||||||
* 1. - field_name "books"
|
|
||||||
* - type "has_many"
|
|
||||||
* - node <author>
|
|
||||||
* - foreign endpoint (1) "author"
|
|
||||||
*
|
|
||||||
* node book
|
|
||||||
* 2. - field_name "author_id"
|
|
||||||
* - type "belongs_to"
|
|
||||||
* - node <book>
|
|
||||||
* - foreign endpoint (2) "books"
|
|
||||||
*
|
|
||||||
* Attach process:
|
|
||||||
*
|
|
||||||
* attach<author>
|
|
||||||
* - relation completer detects "has_many<book>"
|
|
||||||
* - has_many<book> doesn't find <book> (not yet attached)
|
|
||||||
* - create endpoint (1) without foreign endpoint
|
|
||||||
* - create node many_to_many_relation<author, book>("author_id", "id")
|
|
||||||
* - 3. create endpoint
|
|
||||||
* - field name "author_id"
|
|
||||||
* - type "belongs_to"
|
|
||||||
* - node <many_to_many_relation<author, book>>
|
|
||||||
* - foreign endpoint (1) "author
|
|
||||||
* - set foreign endpoint of (1) to endpoint (3)
|
|
||||||
* - register endpoint in authors node by type book
|
|
||||||
* - attach (internal) node<many_to_many_relation<author, book>>
|
|
||||||
*
|
|
||||||
* attach<book>
|
|
||||||
* - relation completer detects "belongs_to<author>"
|
|
||||||
* - belongs_to<author> finds <book>
|
|
||||||
* - try to find relation endpoint of type book in authors node
|
|
||||||
* - if endpoint was found
|
|
||||||
* - validate node in endpoint
|
|
||||||
* - if node is of type many_to_many_relation
|
|
||||||
* - detach node of endpoint
|
|
||||||
* - update node in endpoint to <book>
|
|
||||||
* - else
|
|
||||||
* - create endpoint <book> (1)
|
|
||||||
* - create endpoint <author> (2)
|
|
||||||
* - set foreign endpoint of (1) to endpoint (2)
|
|
||||||
* - set foreign endpoint of (2) to endpoint (1)
|
|
||||||
* - check relation endpoints...
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace demo {
|
|
||||||
struct names {
|
|
||||||
unsigned int id{};
|
|
||||||
std::vector<std::string> names_list;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::has_many(op, "name_list", names_list, "names_id", matador::utils::fetch_type::EAGER);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct user;
|
|
||||||
struct profile {
|
|
||||||
unsigned int id{};
|
|
||||||
std::string first_name;
|
|
||||||
std::string last_name;
|
|
||||||
matador::object::object_ptr<demo::user> user;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::attribute( op, "first_name", first_name, 255 );
|
|
||||||
field::attribute( op, "last_name", last_name, 255 );
|
|
||||||
field::belongs_to( op, "user_id", user, matador::utils::CascadeNoneFetchLazy );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
struct user {
|
|
||||||
unsigned int id{};
|
|
||||||
std::string username;
|
|
||||||
matador::object::object_ptr<demo::profile> profile;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::attribute( op, "username", username, 255 );
|
|
||||||
field::has_one(op, "profile_id", profile, matador::utils::CascadeNoneFetchLazy );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct person {
|
|
||||||
unsigned int id{};
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::attribute( op, "name", name, 255 );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct person_repo {
|
|
||||||
unsigned int id{};
|
|
||||||
matador::object::collection<matador::object::object_ptr<person>> person_list;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::has_many( op, "person_list", person_list, "person_id", matador::utils::CascadeNoneFetchLazy );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace demo;
|
|
||||||
using namespace matador;
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
// logger::default_min_log_level(logger::log_level::LVL_DEBUG);
|
|
||||||
// logger::add_log_sink(logger::create_stdout_sink());
|
|
||||||
|
|
||||||
{
|
|
||||||
// has_many with builtin-type
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<names>("names");
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// has_many with foreign without belongs_to
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<person_repo>("person_repo")
|
|
||||||
.and_then([&repo] { return repo.attach<person>("persons"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// has_many with foreign without belongs_to
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<person>("persons")
|
|
||||||
.and_then([&repo] { return repo.attach<person_repo>("person_repo"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// has_many to belongs_to
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<author>("authors")
|
|
||||||
.and_then([&repo] { return repo.attach<book>("books"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// belongs_to to has_many
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<book>("books")
|
|
||||||
.and_then([&repo] { return repo.attach<author>("authors"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// has_many_to_many (with join columns first)
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<demo::ingredient>("ingredients")
|
|
||||||
.and_then([&repo] { return repo.attach<recipe>("recipes"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// has_many_to_many (with join columns last)
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<demo::recipe>("recipes")
|
|
||||||
.and_then([&repo] { return repo.attach<ingredient>("ingredients"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// belongs_to to has_one
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<profile>("profiles")
|
|
||||||
.and_then([&repo] { return repo.attach<user>("users"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// has_one to belongs_to
|
|
||||||
object::repository repo;
|
|
||||||
|
|
||||||
auto result = repo.attach<user>("users")
|
|
||||||
.and_then([&repo] { return repo.attach<profile>("profiles"); });
|
|
||||||
|
|
||||||
repo.dump(std::cout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
129
demo/work.cpp
129
demo/work.cpp
|
|
@ -1,129 +0,0 @@
|
||||||
#include "work/admin/CollectionCenter.hpp"
|
|
||||||
#include "work/admin/InternalUserDirectory.hpp"
|
|
||||||
#include "work/admin/LdapGroupSchemaSettings.hpp"
|
|
||||||
#include "work/admin/LdapImportSettings.hpp"
|
|
||||||
#include "work/admin/LdapUserDirectory.hpp"
|
|
||||||
#include "work/admin/LdapUserSchemaSettings.hpp"
|
|
||||||
#include "work/admin/LoginHistory.hpp"
|
|
||||||
#include "work/admin/Scenario.hpp"
|
|
||||||
#include "work/admin/User.hpp"
|
|
||||||
#include "work/admin/UserDirectory.hpp"
|
|
||||||
#include "work/admin/UserSession.hpp"
|
|
||||||
|
|
||||||
#include "work/jobs/Job.hpp"
|
|
||||||
#include "work/jobs/Task.hpp"
|
|
||||||
#include "work/jobs/Payload.hpp"
|
|
||||||
#include "work/jobs/IdPayload.hpp"
|
|
||||||
#include "work/jobs/IdListPayload.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/default_type_traits.hpp"
|
|
||||||
#include "matador/object/repository.hpp"
|
|
||||||
|
|
||||||
#include "matador/sql/connection.hpp"
|
|
||||||
|
|
||||||
#include "matador/orm/session.hpp"
|
|
||||||
|
|
||||||
template <> struct matador::utils::data_type_traits<work::core::timestamp, void> {
|
|
||||||
static basic_type type(std::size_t /*size*/) { return basic_type::type_uint64; }
|
|
||||||
static void read_value(attribute_reader &/*reader*/, const char * /*id*/, size_t /*index*/, nullptr_t &/*value*/) {
|
|
||||||
|
|
||||||
}
|
|
||||||
static void bind_value(attribute_writer &/*binder*/, size_t /*index*/, nullptr_t &/*value*/) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <> struct matador::utils::data_type_traits<work::core::UserInfo, void> {
|
|
||||||
static basic_type type(std::size_t /*size*/) { return basic_type::type_uint64; }
|
|
||||||
static void read_value(attribute_reader &/*reader*/, const char * /*id*/, size_t /*index*/, nullptr_t &/*value*/) {
|
|
||||||
|
|
||||||
}
|
|
||||||
static void bind_value(attribute_writer &/*binder*/, size_t /*index*/, nullptr_t &/*value*/) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using namespace matador;
|
|
||||||
using namespace work::models;
|
|
||||||
|
|
||||||
// Proposal for polymorphic classes:
|
|
||||||
// object_ptr::as<Type> does the following checks;
|
|
||||||
//
|
|
||||||
// 1. The requested type has super class
|
|
||||||
// 2. Super class has discriminator column defined
|
|
||||||
// 3. Super class has discriminator value defined
|
|
||||||
// 4. Discriminator value is mapped to the requested type
|
|
||||||
//
|
|
||||||
// If all checks succeed, the requested is fetched from
|
|
||||||
// the database.
|
|
||||||
// schema.attach<jobs::Payload>("payloads", make_polymorph("type"));
|
|
||||||
// schema.attach<jobs::Payload, jobs::IdPayload>("id_list_payloads", make_polymorph_type("IdPayload"));
|
|
||||||
// schema.attach<jobs::Payload, jobs::IdListPayload>("id_payloads", make_polymorph_type("IdListPayload"));
|
|
||||||
// object::object_ptr<jobs::Payload> payload;
|
|
||||||
// auto result = payload.as<jobs::IdPayload>();
|
|
||||||
// if (result.is_ok()) {
|
|
||||||
// const auto& is_payload = result.value();
|
|
||||||
// // Use requested type
|
|
||||||
// id_payload->id = 1;
|
|
||||||
// }
|
|
||||||
// payload.is_polymorphic();
|
|
||||||
// payload.is_polymorphic_type<jobs::IdPayload>();
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
// logger::default_min_log_level(logger::log_level::LVL_DEBUG);
|
|
||||||
// logger::add_log_sink(logger::create_stdout_sink());
|
|
||||||
|
|
||||||
// sql::connection_pool<sql::connection> pool("postgres://news:news@127.0.0.1:15432/matador", 4);
|
|
||||||
sql::connection_pool pool("postgres://test:test123!@127.0.0.1:5432/matador", 4);
|
|
||||||
|
|
||||||
object::repository repo("Administration");
|
|
||||||
|
|
||||||
auto result = repo.attach<admin::CollectionCenter>("collection_centers");
|
|
||||||
// repo.create(pool);
|
|
||||||
// repo.drop(pool);
|
|
||||||
|
|
||||||
utils::message_bus bus;
|
|
||||||
orm::session ses({bus, pool});
|
|
||||||
|
|
||||||
result = ses.attach<admin::CollectionCenter>("collection_centers")
|
|
||||||
.and_then([&ses] { return ses.attach<admin::UserDirectory>("user_directories"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::LdapGroupSchemaSettings>("ldap_group_schema_settings"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::LdapImportSettings>("ldap_import_settings"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::LdapUserSchemaSettings>("ldap_user_schema_settings"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::InternalUserDirectory, admin::UserDirectory>("internal_user_directories"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::LdapUserDirectory, admin::UserDirectory>("ldap_user_directories"); } )
|
|
||||||
.and_then([&ses] { return ses.attach<admin::LoginHistory>("login_histories"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::Scenario>("scenarios"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::User>("users"); })
|
|
||||||
.and_then([&ses] { return ses.attach<admin::UserSession>("user_sessions"); })
|
|
||||||
.and_then([&ses] { return ses.attach<jobs::Job>("jobs"); })
|
|
||||||
.and_then([&ses] { return ses.attach<jobs::Payload>("payloads"); })
|
|
||||||
.and_then([&ses] { return ses.attach<jobs::IdPayload, jobs::Payload>("id_list_payloads"); })
|
|
||||||
.and_then([&ses] { return ses.attach<jobs::IdListPayload, jobs::Payload>("id_payloads"); })
|
|
||||||
.and_then([&ses] { return ses.attach<jobs::Task>("tasks"); })
|
|
||||||
;
|
|
||||||
ses.dump_schema(std::cout);
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
std::cout << "error: " << result.err().message() << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = ses.create_schema();
|
|
||||||
if (!result) {
|
|
||||||
std::cout << "error: " << result.err() << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const std::string dns{"sqlite://demo.db"};
|
|
||||||
// sql::connection c(dns);
|
|
||||||
//
|
|
||||||
// result = c.open();
|
|
||||||
//
|
|
||||||
// // orm::session s()
|
|
||||||
// if (!result.is_ok()) {
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
#ifndef COLLECTION_CENTER_HPP
|
|
||||||
#define COLLECTION_CENTER_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/collection.hpp"
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
#include "matador/utils/fetch_type.hpp"
|
|
||||||
#include "matador/utils/types.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct User;
|
|
||||||
|
|
||||||
enum class CollectionCenterType : uint8_t {
|
|
||||||
NoType,
|
|
||||||
Core,
|
|
||||||
Unit
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<CollectionCenterType> CollectionCenterTypeEnum({
|
|
||||||
{CollectionCenterType::NoType, "NoType"},
|
|
||||||
{CollectionCenterType::Core, "Core"},
|
|
||||||
{CollectionCenterType::Unit, "Unit"}
|
|
||||||
});
|
|
||||||
|
|
||||||
struct CollectionCenter : core::Model {
|
|
||||||
std::string name;
|
|
||||||
matador::utils::blob symbol;
|
|
||||||
CollectionCenterType type{CollectionCenterType::NoType};
|
|
||||||
matador::object::collection<matador::object::object_ptr<User>> users;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "name", name, 511 );
|
|
||||||
field::attribute( op, "symbol", symbol );
|
|
||||||
field::attribute( op, "type", type );
|
|
||||||
field::has_many( op, "collection_center_users", users, "users_id", matador::utils::fetch_type::LAZY );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //COLLECTION_CENTER_HPP
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#ifndef INTERNAL_USER_DIRECTORY_HPP
|
|
||||||
#define INTERNAL_USER_DIRECTORY_HPP
|
|
||||||
|
|
||||||
#include "UserDirectory.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct InternalUserDirectory : UserDirectory {
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<UserDirectory>( this ) );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //INTERNALUSERDIRECTORY_HPP
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#ifndef LDAP_GROUP_SCHEMA_SETTINGS_HPP
|
|
||||||
#define LDAP_GROUP_SCHEMA_SETTINGS_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct LdapUserDirectory;
|
|
||||||
|
|
||||||
struct LdapGroupSchemaSettings : core::Model {
|
|
||||||
std::string group_object_filter;
|
|
||||||
std::string user_member_attribute;
|
|
||||||
matador::object::object_ptr<LdapUserDirectory> ldap_user_directory;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::has_one(op, "ldap_user_directory", ldap_user_directory, matador::utils::default_foreign_attributes);
|
|
||||||
field::attribute( op, "group_object_filter", group_object_filter, 511 );
|
|
||||||
field::attribute( op, "user_member_attribute", user_member_attribute, 511 );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LDAP_GROUP_SCHEMA_SETTINGS_HPP
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#ifndef LDAP_IMPORT_SETTINGS_HPP
|
|
||||||
#define LDAP_IMPORT_SETTINGS_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct LdapUserDirectory;
|
|
||||||
|
|
||||||
struct LdapImportSettings : core::Model {
|
|
||||||
std::string default_role;
|
|
||||||
unsigned int sync_interval;
|
|
||||||
unsigned int network_timeout;
|
|
||||||
matador::object::object_ptr<LdapUserDirectory> ldap_user_directory;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::has_one(op, "ldap_user_directory", ldap_user_directory, matador::utils::default_foreign_attributes);
|
|
||||||
field::attribute( op, "default_role", default_role, 511 );
|
|
||||||
field::attribute( op, "sync_interval", sync_interval );
|
|
||||||
field::attribute( op, "network_timeout", network_timeout );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LDAP_IMPORT_SETTINGS_HPP
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#ifndef LDAP_USER_DIRECTORY_HPP
|
|
||||||
#define LDAP_USER_DIRECTORY_HPP
|
|
||||||
|
|
||||||
#include "LdapUserSchemaSettings.hpp"
|
|
||||||
#include "LdapGroupSchemaSettings.hpp"
|
|
||||||
#include "LdapImportSettings.hpp"
|
|
||||||
#include "UserDirectory.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/collection.hpp"
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct LdapUserDirectory : UserDirectory {
|
|
||||||
std::string schema_base_dn;
|
|
||||||
std::string additional_user_base_dn;
|
|
||||||
std::string additional_group_base_dn;
|
|
||||||
matador::object::object_ptr<LdapUserSchemaSettings> user_schema_settings;
|
|
||||||
matador::object::object_ptr<LdapGroupSchemaSettings> group_schema_settings;
|
|
||||||
matador::object::object_ptr<LdapImportSettings> import_settings;
|
|
||||||
matador::object::collection<std::string> users;
|
|
||||||
matador::object::collection<std::string> groups;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<UserDirectory>( this ) );
|
|
||||||
field::attribute( op, "schema_base_dn", schema_base_dn, 511 );
|
|
||||||
field::attribute( op, "additional_user_base_dn", additional_user_base_dn, 511 );
|
|
||||||
field::attribute( op, "additional_group_base_dn", additional_group_base_dn, 511 );
|
|
||||||
field::belongs_to( op, "user_schema_settings", user_schema_settings, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "group_schema_settings", group_schema_settings, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "import_settings", import_settings, matador::utils::default_foreign_attributes );
|
|
||||||
field::has_many( op, "ldap_users", users, "users_id", matador::utils::default_foreign_attributes );
|
|
||||||
field::has_many( op, "ldap_groups", groups, "groups_id", matador::utils::default_foreign_attributes );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LDAP_USER_DIRECTORY_HPP
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
#ifndef LDAP_USER_SCHEMA_SETTINGS_HPP
|
|
||||||
#define LDAP_USER_SCHEMA_SETTINGS_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct LdapUserDirectory;
|
|
||||||
|
|
||||||
struct LdapUserSchemaSettings : core::Model {
|
|
||||||
std::string user_object_filter;
|
|
||||||
std::string user_unique_id_attribute;
|
|
||||||
std::string user_member_of_attribute;
|
|
||||||
std::string user_name_attribute;
|
|
||||||
matador::object::object_ptr<LdapUserDirectory> ldap_user_directory;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process(op, *matador::base_class<Model>( this ));
|
|
||||||
field::has_one(op, "ldap_user_directory", ldap_user_directory, matador::utils::default_foreign_attributes);
|
|
||||||
field::attribute(op, "user_object_filter", user_object_filter, 511);
|
|
||||||
field::attribute(op, "user_unique_id_attribute", user_unique_id_attribute, 511);
|
|
||||||
field::attribute(op, "user_member_of_attribute", user_member_of_attribute, 511);
|
|
||||||
field::attribute(op, "user_name_attribute", user_name_attribute, 511);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LDAP_USER_SCHEMA_SETTINGS_HPP
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#ifndef LOCKTYPE_HPP
|
|
||||||
#define LOCKTYPE_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
enum class LockType : uint8_t {
|
|
||||||
NoLock,
|
|
||||||
AdminLock,
|
|
||||||
SystemLock
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<LockType> LockTypeEnum({
|
|
||||||
{LockType::NoLock, "NoLock"},
|
|
||||||
{LockType::AdminLock, "AdminLock"},
|
|
||||||
{LockType::SystemLock, "SystemLock"}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LOCKTYPE_HPP
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#ifndef LOGIN_HISTORY_HPP
|
|
||||||
#define LOGIN_HISTORY_HPP
|
|
||||||
|
|
||||||
#include "CollectionCenter.hpp"
|
|
||||||
#include "Scenario.hpp"
|
|
||||||
#include "User.hpp"
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
|
|
||||||
enum class FailReason : uint8_t {
|
|
||||||
NoFailReason,
|
|
||||||
InternalError,
|
|
||||||
NoPermissionToLogin,
|
|
||||||
NoScenarioForUser,
|
|
||||||
UnknownUserName,
|
|
||||||
UserLockedByAdmin,
|
|
||||||
UserLockedBySystem,
|
|
||||||
UserLockedByAdminAndSystem,
|
|
||||||
WrongPassword
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<FailReason> FailReasonEnum({
|
|
||||||
{FailReason::NoFailReason, "NoFailReason"},
|
|
||||||
{FailReason::InternalError, "InternalError"},
|
|
||||||
{FailReason::NoPermissionToLogin, "NoPermissionToLogin"},
|
|
||||||
{FailReason::NoScenarioForUser, "NoScenarioForUser"},
|
|
||||||
{FailReason::UnknownUserName, "UnknownUserName"},
|
|
||||||
{FailReason::UserLockedByAdmin, "UserLockedByAdmin"},
|
|
||||||
{FailReason::UserLockedBySystem, "UserLockedBySystem"},
|
|
||||||
{FailReason::UserLockedByAdminAndSystem, "UserLockedByAdminAndSystem"},
|
|
||||||
{FailReason::WrongPassword, "WrongPassword"}
|
|
||||||
});
|
|
||||||
|
|
||||||
struct LoginHistory : core::Model {
|
|
||||||
matador::object::object_ptr<User> user;
|
|
||||||
matador::object::object_ptr<Scenario> scenario;
|
|
||||||
matador::object::object_ptr<CollectionCenter> collection_center;
|
|
||||||
std::string login_name;
|
|
||||||
FailReason fail_reason{FailReason::NoFailReason};
|
|
||||||
core::timestamp login_time;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::belongs_to( op, "user", user, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "scenario", scenario, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "collection_center", collection_center, matador::utils::default_foreign_attributes );
|
|
||||||
field::attribute( op, "login_name", login_name, 511 );
|
|
||||||
field::attribute( op, "fail_reason", fail_reason );
|
|
||||||
field::attribute( op, "login_time", login_time );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //LOGIN_HISTORY_HPP
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
#ifndef SCENARIO_HPP
|
|
||||||
#define SCENARIO_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/collection.hpp"
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct CollectionCenter;
|
|
||||||
|
|
||||||
enum class ScenarioMode : uint8_t {
|
|
||||||
InvalidMode,
|
|
||||||
Exercise,
|
|
||||||
Operation,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<ScenarioMode> ScenarioModeEnum({
|
|
||||||
{ScenarioMode::InvalidMode, "InvalidMode"},
|
|
||||||
{ScenarioMode::Exercise, "Exercise"},
|
|
||||||
{ScenarioMode::Operation, "Operation"}
|
|
||||||
});
|
|
||||||
|
|
||||||
enum class ScenarioState : uint8_t {
|
|
||||||
InvalidState,
|
|
||||||
Active,
|
|
||||||
Inactive
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<ScenarioState> ScenarioStateEnum({
|
|
||||||
{ScenarioState::InvalidState, "InvalidState"},
|
|
||||||
{ScenarioState::Active, "Active"},
|
|
||||||
{ScenarioState::Inactive, "Inactive"}
|
|
||||||
});
|
|
||||||
|
|
||||||
struct Scenario : core::Model {
|
|
||||||
std::string name;
|
|
||||||
ScenarioMode mode{ScenarioMode::InvalidMode};
|
|
||||||
std::string description;
|
|
||||||
matador::utils::blob symbol;
|
|
||||||
ScenarioState state{ScenarioState::InvalidState};
|
|
||||||
matador::object::collection<matador::object::object_ptr<CollectionCenter>> collection_center;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "name", name, 511 );
|
|
||||||
field::attribute( op, "mode", mode );
|
|
||||||
field::attribute( op, "description", description, 511 );
|
|
||||||
field::attribute( op, "symbol", symbol );
|
|
||||||
field::attribute( op, "state", state );
|
|
||||||
field::has_many( op, "collection_center", collection_center, "scenario_id", matador::utils::fetch_type::LAZY );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //SCENARIO_HPP
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
#ifndef USER_HPP
|
|
||||||
#define USER_HPP
|
|
||||||
|
|
||||||
#include "LockType.hpp"
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
#include "../core/Types.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
#include "matador/utils/types.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct UserDirectory;
|
|
||||||
|
|
||||||
struct User : core::Model {
|
|
||||||
std::string name;
|
|
||||||
matador::utils::blob symbol;
|
|
||||||
std::string salt;
|
|
||||||
std::string password;
|
|
||||||
LockType lock_type{LockType::NoLock};
|
|
||||||
core::timestamp locked_at;
|
|
||||||
std::string lock_reason;
|
|
||||||
std::string role;
|
|
||||||
matador::object::object_ptr<UserDirectory> user_directory;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "name", name, 511 );
|
|
||||||
field::attribute( op, "symbol", symbol );
|
|
||||||
field::attribute( op, "salt", salt, 511 );
|
|
||||||
field::attribute( op, "password", password, 511 );
|
|
||||||
field::attribute( op, "lock_type", lock_type );
|
|
||||||
field::attribute( op, "locked_at", locked_at );
|
|
||||||
field::attribute( op, "lock_reason", lock_reason, 511 );
|
|
||||||
field::attribute( op, "role", role, 63 );
|
|
||||||
field::belongs_to( op, "user_directory", user_directory, matador::utils::default_foreign_attributes );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //USER_HPP
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#ifndef USER_DIRECTORY_HPP
|
|
||||||
#define USER_DIRECTORY_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct UserDirectory : core::Model {
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "name", name, 511 );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //USER_DIRECTORY_HPP
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#ifndef USER_SESSION_HPP
|
|
||||||
#define USER_SESSION_HPP
|
|
||||||
|
|
||||||
#include "CollectionCenter.hpp"
|
|
||||||
#include "Scenario.hpp"
|
|
||||||
#include "User.hpp"
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
#include "../core/Types.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::admin {
|
|
||||||
struct UserSession : core::Model {
|
|
||||||
matador::object::object_ptr<User> user;
|
|
||||||
matador::object::object_ptr<Scenario> scenario;
|
|
||||||
matador::object::object_ptr<CollectionCenter> collection_center;
|
|
||||||
core::timestamp offline_since;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::belongs_to( op, "user", user, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "scenario", scenario, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "collection_center", collection_center, matador::utils::default_foreign_attributes );
|
|
||||||
field::attribute( op, "offline_since", offline_since );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //USER_SESSION_HPP
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#ifndef MODEL_HPP
|
|
||||||
#define MODEL_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/access.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace work::core {
|
|
||||||
struct Model {
|
|
||||||
uint64_t id;
|
|
||||||
uint64_t version;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::primary_key( op, "id", id );
|
|
||||||
field::revision( op, "version", version );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif //MODEL_HPP
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#ifndef TYPES_HPP
|
|
||||||
#define TYPES_HPP
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace work::core {
|
|
||||||
using timestamp = std::chrono::system_clock::time_point;
|
|
||||||
}
|
|
||||||
#endif //TYPES_HPP
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#ifndef USERINFO_HPP
|
|
||||||
#define USERINFO_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/access.hpp"
|
|
||||||
|
|
||||||
namespace work::core {
|
|
||||||
struct UserInfo {
|
|
||||||
unsigned long long user_session_id;
|
|
||||||
std::string user_role;
|
|
||||||
std::string database_name;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::attribute( op, "user_session_id", user_session_id );
|
|
||||||
field::attribute( op, "user_role", user_role, 255 );
|
|
||||||
field::attribute( op, "database_name", database_name, 255 );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //USERINFO_HPP
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#ifndef ID_LIST_PAYLOAD_HPP
|
|
||||||
#define ID_LIST_PAYLOAD_HPP
|
|
||||||
|
|
||||||
#include "Payload.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/collection.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/foreign_attributes.hpp"
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
struct IdListPayload : Payload {
|
|
||||||
matador::object::collection<uint64_t> payload_ids;
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Payload>( this ) );
|
|
||||||
field::has_many( op, "payload_ids", payload_ids, "payload_id", matador::utils::CascadeNoneFetchLazy );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //ID_LIST_PAYLOAD_HPP
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#ifndef ID_PAYLOAD_HPP
|
|
||||||
#define ID_PAYLOAD_HPP
|
|
||||||
|
|
||||||
#include "Payload.hpp"
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
struct IdPayload : Payload {
|
|
||||||
uint64_t payload_id{};
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Payload>( this ) );
|
|
||||||
field::attribute( op, "payload_id", payload_id );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //ID_PAYLOAD_HPP
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
#ifndef JOB_HPP
|
|
||||||
#define JOB_HPP
|
|
||||||
|
|
||||||
#include "JobMode.hpp"
|
|
||||||
#include "JobState.hpp"
|
|
||||||
#include "Payload.hpp"
|
|
||||||
#include "Task.hpp"
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
#include "../core/Types.hpp"
|
|
||||||
#include "../core/UserInfo.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
|
|
||||||
struct Job : core::Model {
|
|
||||||
std::string name;
|
|
||||||
std::string description;
|
|
||||||
JobState state;
|
|
||||||
JobMode mode;
|
|
||||||
core::timestamp created_at;
|
|
||||||
matador::object::object_ptr<Payload> payload;
|
|
||||||
matador::object::object_ptr<Task> task;
|
|
||||||
core::UserInfo user_info;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "name", name, 511 );
|
|
||||||
field::attribute( op, "description", description, 511 );
|
|
||||||
field::attribute( op, "state", state );
|
|
||||||
field::attribute( op, "mode", mode );
|
|
||||||
field::attribute( op, "created_at", created_at );
|
|
||||||
field::has_one(op, "payload", payload, matador::utils::default_foreign_attributes );
|
|
||||||
field::belongs_to( op, "task", task, matador::utils::default_foreign_attributes );
|
|
||||||
field::attribute( op, "user_info", user_info );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //JOB_HPP
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
#ifndef JOB_MODE_HPP
|
|
||||||
#define JOB_MODE_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
enum class JobMode : uint8_t {
|
|
||||||
Background,
|
|
||||||
Foreground
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<JobMode> JobModeEnum({
|
|
||||||
{JobMode::Background, "Background"},
|
|
||||||
{JobMode::Foreground, "Foreground"}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //JOB_MODE_HPP
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#ifndef JOB_STATE_HPP
|
|
||||||
#define JOB_STATE_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
enum class JobState : uint8_t {
|
|
||||||
Pending,
|
|
||||||
Running,
|
|
||||||
Succeeded,
|
|
||||||
Failed,
|
|
||||||
Cancelled
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<JobState> JobStateEnum({
|
|
||||||
{JobState::Pending, "Pending"},
|
|
||||||
{JobState::Running, "Running"},
|
|
||||||
{JobState::Succeeded, "Succeeded"},
|
|
||||||
{JobState::Failed, "Failed"},
|
|
||||||
{JobState::Cancelled, "Cancelled"}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif //JOB_STATE_HPP
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#ifndef PAYLOAD_HPP
|
|
||||||
#define PAYLOAD_HPP
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
struct Job;
|
|
||||||
struct Payload : core::Model {
|
|
||||||
std::string type;
|
|
||||||
matador::object::object_ptr<Job> job;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "type", type, 255 );
|
|
||||||
field::belongs_to( op, "job", job, matador::utils::default_foreign_attributes );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //PAYLOAD_HPP
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#ifndef TASK_HPP
|
|
||||||
#define TASK_HPP
|
|
||||||
|
|
||||||
#include "TaskState.hpp"
|
|
||||||
|
|
||||||
#include "../core/Model.hpp"
|
|
||||||
#include "../core/Types.hpp"
|
|
||||||
|
|
||||||
#include "matador/object/object_ptr.hpp"
|
|
||||||
|
|
||||||
#include "matador/utils/base_class.hpp"
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
struct Task : core::Model {
|
|
||||||
std::string name;
|
|
||||||
std::string description;
|
|
||||||
std::string job_name;
|
|
||||||
TaskState state;
|
|
||||||
matador::object::object_ptr<Payload> payload;
|
|
||||||
JobMode job_mode;
|
|
||||||
core::timestamp start_delay;
|
|
||||||
core::timestamp interval;
|
|
||||||
uint64_t user_session_id;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process( Operator& op ) {
|
|
||||||
namespace field = matador::access;
|
|
||||||
field::process( op, *matador::base_class<Model>( this ) );
|
|
||||||
field::attribute( op, "name", name, 511 );
|
|
||||||
field::attribute( op, "description", description, 511 );
|
|
||||||
field::attribute( op, "job_name", job_name, 511 );
|
|
||||||
field::attribute( op, "state", state );
|
|
||||||
field::belongs_to( op, "payload", payload, matador::utils::default_foreign_attributes );
|
|
||||||
field::attribute( op, "job_mode", job_mode );
|
|
||||||
field::attribute( op, "start_delay", start_delay );
|
|
||||||
field::attribute( op, "interval", interval );
|
|
||||||
field::attribute( op, "user_session_id", user_session_id );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //TASK_HPP
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#ifndef TASK_STATE_HPP
|
|
||||||
#define TASK_STATE_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/enum_mapper.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace work::models::jobs {
|
|
||||||
enum class TaskState : uint8_t {
|
|
||||||
Active,
|
|
||||||
Inactive
|
|
||||||
};
|
|
||||||
|
|
||||||
static const matador::utils::enum_mapper<TaskState> TaskStateEnum({
|
|
||||||
{TaskState::Active, "Active"},
|
|
||||||
{TaskState::Inactive, "Inactive"}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif //TASK_STATE_HPP
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#ifndef MATADOR_BASIC_FILE_SINK_HPP
|
|
||||||
#define MATADOR_BASIC_FILE_SINK_HPP
|
|
||||||
|
|
||||||
#include "matador/logger/log_sink.hpp"
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A base class for the file-stream-based sinks
|
|
||||||
*
|
|
||||||
* This class acts like a base class for all
|
|
||||||
* concrete sinks working with a file stream to write
|
|
||||||
* the log message.
|
|
||||||
*/
|
|
||||||
class basic_file_sink : public log_sink
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
basic_file_sink() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a basic_file_sink with a given file stream
|
|
||||||
*
|
|
||||||
* @param f File stream to write on
|
|
||||||
*/
|
|
||||||
explicit basic_file_sink(FILE *f);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Writes the log message to the internal
|
|
||||||
* file stream.
|
|
||||||
*
|
|
||||||
* @param message The message to write
|
|
||||||
* @param size The size of the message
|
|
||||||
*/
|
|
||||||
void write(const char *message, size_t size) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the internal file stream.
|
|
||||||
*/
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/// @cond MATADOR_DEV
|
|
||||||
FILE *stream = nullptr;
|
|
||||||
/// @endcond
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //MATADOR_BASIC_FILE_SINK_HPP
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
#ifndef MATADOR_FILE_SINK_HPP
|
|
||||||
#define MATADOR_FILE_SINK_HPP
|
|
||||||
|
|
||||||
#include "matador/logger/basic_file_sink.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A file sink writing the log message to one file
|
|
||||||
*
|
|
||||||
* The log sink writes all log messages to one single
|
|
||||||
* file identified by a given path.
|
|
||||||
*
|
|
||||||
* Note because there is no limit, the file grows infinitely.
|
|
||||||
*/
|
|
||||||
class file_sink final : public basic_file_sink
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates a file_sink with the given path.
|
|
||||||
* If the path doesn't exist, it is created.
|
|
||||||
*
|
|
||||||
* @param path The log file to write to
|
|
||||||
*/
|
|
||||||
explicit file_sink(const std::string &path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a file_sink with the given path.
|
|
||||||
* If the path doesn't exist, it is created.
|
|
||||||
*
|
|
||||||
* @param path The log file to write to
|
|
||||||
*/
|
|
||||||
explicit file_sink(const char *path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the file_sink
|
|
||||||
*/
|
|
||||||
~file_sink() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the path to the log file.
|
|
||||||
*
|
|
||||||
* @return The path to the log file
|
|
||||||
*/
|
|
||||||
std::string path() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string path_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Log sink writing to stdout
|
|
||||||
*
|
|
||||||
* This log sink writes all messages to stdout.
|
|
||||||
*/
|
|
||||||
class stdout_sink final : public basic_file_sink
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
stdout_sink();
|
|
||||||
~stdout_sink() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do nothing on close
|
|
||||||
*/
|
|
||||||
void close() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Log sink writing to stderr
|
|
||||||
*
|
|
||||||
* This log sink writes all messages to stderr.
|
|
||||||
*/
|
|
||||||
class stderr_sink final : public basic_file_sink
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
stderr_sink();
|
|
||||||
~stderr_sink() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do nothing on close
|
|
||||||
*/
|
|
||||||
void close() override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //MATADOR_FILE_SINK_HPP
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
#ifndef MATADOR_LOG_DOMAIN_HPP
|
|
||||||
#define MATADOR_LOG_DOMAIN_HPP
|
|
||||||
|
|
||||||
#include "matador/logger/log_level.hpp"
|
|
||||||
#include "matador/logger/log_sink.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <list>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connection to a set of log sinks
|
|
||||||
*
|
|
||||||
* A log domain is the connection point between
|
|
||||||
* a set of log sinks and the logger objects
|
|
||||||
* in the user code.
|
|
||||||
*
|
|
||||||
* A domain consists of a unique name and a
|
|
||||||
* list of sinks
|
|
||||||
*/
|
|
||||||
class log_domain final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* The time format for each log line
|
|
||||||
*/
|
|
||||||
static constexpr auto TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%f";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a log_domain with the given name
|
|
||||||
* and the given log range
|
|
||||||
*
|
|
||||||
* @param name The name of the log domain
|
|
||||||
* @param log_range The log range of this domain
|
|
||||||
*/
|
|
||||||
log_domain(std::string name, log_level_range log_range);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the domain
|
|
||||||
*
|
|
||||||
* @return The name of the domain
|
|
||||||
*/
|
|
||||||
[[nodiscard]] std::string name() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the max log level. The default
|
|
||||||
* max level is LVL_FATAL
|
|
||||||
*
|
|
||||||
* @param max_level max log level
|
|
||||||
*/
|
|
||||||
void max_log_level(log_level max_level);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the max log level
|
|
||||||
*
|
|
||||||
* @return The max log level
|
|
||||||
*/
|
|
||||||
[[nodiscard]] log_level max_log_level() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the min log level. Default
|
|
||||||
* min leven is LVL_INFO
|
|
||||||
*
|
|
||||||
* @param min_level min log level
|
|
||||||
*/
|
|
||||||
void min_log_level(log_level min_level);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the min log level
|
|
||||||
*
|
|
||||||
* @return The min log level
|
|
||||||
*/
|
|
||||||
[[nodiscard]] log_level min_log_level() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a sink to the domain.
|
|
||||||
*
|
|
||||||
* The sink must be packed into a std::shared_ptr
|
|
||||||
* because it can be shared among other domains
|
|
||||||
*
|
|
||||||
* @param sink The sink to add
|
|
||||||
*/
|
|
||||||
void add_sink(sink_ptr sink);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the given message for the given source and log level
|
|
||||||
* to this log domain.
|
|
||||||
*
|
|
||||||
* @param lvl Log level
|
|
||||||
* @param source Source of the log message
|
|
||||||
* @param message Message to log
|
|
||||||
*/
|
|
||||||
void log(log_level lvl, const std::string &source, const char *message) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the list of log sinks
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void get_time_stamp(char* timestamp_buffer) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::map<log_level, std::string> level_strings;
|
|
||||||
|
|
||||||
std::string name_;
|
|
||||||
std::list<sink_ptr> sinks{};
|
|
||||||
|
|
||||||
log_level_range log_level_range_;
|
|
||||||
|
|
||||||
mutable std::mutex mutex_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //MATADOR_LOG_DOMAIN_HPP
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
#ifndef MATADOR_LOG_LEVEL_HPP
|
|
||||||
#define MATADOR_LOG_LEVEL_HPP
|
|
||||||
|
|
||||||
#include <iosfwd>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents all available log levels
|
|
||||||
*/
|
|
||||||
enum class log_level
|
|
||||||
{
|
|
||||||
LVL_FATAL, /**< If a serious error occurred, use FATAL level */
|
|
||||||
LVL_ERROR, /**< On error use ERROR level */
|
|
||||||
LVL_WARN, /**< Warnings should use WARN level */
|
|
||||||
LVL_INFO, /**< Information should go with INFO level */
|
|
||||||
LVL_DEBUG, /**< Debug output should use DEBUG level */
|
|
||||||
LVL_TRACE, /**< Trace information should use TRACE level */
|
|
||||||
LVL_ALL /**< This level represents all log levels and should be used for logging */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write log level in a human-readable string
|
|
||||||
* to a given std::ostream.
|
|
||||||
*
|
|
||||||
* @param os std::stream to write to
|
|
||||||
* @param lvl Log level to write
|
|
||||||
* @return The std::ostream
|
|
||||||
*/
|
|
||||||
std::ostream& operator<<(std::ostream &os, log_level lvl);
|
|
||||||
|
|
||||||
/// @cond MATADOR_DEV
|
|
||||||
|
|
||||||
struct log_level_range
|
|
||||||
{
|
|
||||||
log_level min_level = log_level::LVL_INFO;
|
|
||||||
log_level max_level = log_level::LVL_FATAL;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @endcond
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //MATADOR_LOG_LEVEL_HPP
|
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
#ifndef MATADOR_LOG_MANAGER_HPP
|
|
||||||
#define MATADOR_LOG_MANAGER_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/singleton.hpp"
|
|
||||||
|
|
||||||
#include "matador/logger/logger.hpp"
|
|
||||||
#include "matador/logger/log_sink.hpp"
|
|
||||||
#include "matador/logger/file_sink.hpp"
|
|
||||||
#include "matador/logger/rotating_file_sink.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Manages all log domains
|
|
||||||
*
|
|
||||||
* The log_manager class is a singleton and
|
|
||||||
* manages all available log_domains
|
|
||||||
*
|
|
||||||
* There ist always a default log domain
|
|
||||||
* with the name "default"
|
|
||||||
* available for which sinks can be added
|
|
||||||
* and loggers can be created.
|
|
||||||
*/
|
|
||||||
class log_manager final : public utils::singleton<log_manager>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates a logger with the given source name
|
|
||||||
* for the default log domain
|
|
||||||
*
|
|
||||||
* @param source Name of the source
|
|
||||||
* @return The created logger
|
|
||||||
*/
|
|
||||||
[[nodiscard]] logger create_logger(std::string source) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a logger with the given source name
|
|
||||||
* for the log domain identified by the given
|
|
||||||
* log domain name.
|
|
||||||
*
|
|
||||||
* If the log domain with the given name doesn't exist,
|
|
||||||
* the domain is created
|
|
||||||
*
|
|
||||||
* @param source Name of the source
|
|
||||||
* @param domain_name The name of the log domain to execute to
|
|
||||||
* @return The created logger
|
|
||||||
*/
|
|
||||||
logger create_logger(std::string source, const std::string &domain_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a log sink to the default log_domain
|
|
||||||
*
|
|
||||||
* @param sink Sink to add to the default log_domain
|
|
||||||
*/
|
|
||||||
void add_sink(sink_ptr sink) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a log sink to the log_domain with the given name.
|
|
||||||
* If the log domain doesn't exist, it is automatically created.
|
|
||||||
*
|
|
||||||
* @param sink Sink to add
|
|
||||||
* @param domain_name Name of the log domain
|
|
||||||
*/
|
|
||||||
void add_sink(sink_ptr sink, const std::string &domain_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all sinks from the default log domain
|
|
||||||
*/
|
|
||||||
void clear_all_sinks() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all sinks from the log domain
|
|
||||||
* with the given name
|
|
||||||
*
|
|
||||||
* @param domain_name Domain name to clear all sinks from
|
|
||||||
*/
|
|
||||||
void clear_all_sinks(const std::string &domain_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all log domains but the default log domain.
|
|
||||||
* Clears all sinks from the default log domain.
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the max default log level. The default
|
|
||||||
* max leven is LVL_FATAL. All log domains
|
|
||||||
* will start with this default max log range
|
|
||||||
*
|
|
||||||
* @param max_level max log level
|
|
||||||
*/
|
|
||||||
static void max_default_log_level(log_level max_level);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default max log level
|
|
||||||
*
|
|
||||||
* @return The max log level
|
|
||||||
*/
|
|
||||||
static log_level max_default_log_level();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default min log level. The default
|
|
||||||
* min leven is LVL_INFO. All log domains
|
|
||||||
* will start with this default max log range
|
|
||||||
*
|
|
||||||
* @param min_level min log level
|
|
||||||
*/
|
|
||||||
static void min_default_log_level(log_level min_level);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default min log level
|
|
||||||
*
|
|
||||||
* @return The min log level
|
|
||||||
*/
|
|
||||||
static log_level min_default_log_level();
|
|
||||||
|
|
||||||
/// @cond MATADOR_DEV
|
|
||||||
std::shared_ptr<log_domain> find_domain(const std::string &name);
|
|
||||||
void log_default(log_level lvl, const std::string &source, const char *message) const;
|
|
||||||
/// @endcond
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/// @cond MATADOR_DEV
|
|
||||||
log_manager()
|
|
||||||
{
|
|
||||||
default_log_domain_ = log_domain_map_.insert(std::make_pair("default", std::make_shared<log_domain>("default", default_log_level_range_))).first->second;
|
|
||||||
}
|
|
||||||
/// @endcond
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<log_domain> acquire_domain(const std::string &name);
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class utils::singleton<log_manager>;
|
|
||||||
|
|
||||||
std::shared_ptr<log_domain> default_log_domain_;
|
|
||||||
|
|
||||||
std::map<std::string, std::shared_ptr<log_domain>> log_domain_map_;
|
|
||||||
|
|
||||||
static log_level_range default_log_level_range_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut to create a file log sink
|
|
||||||
* with the given path. If the path doesn't
|
|
||||||
* exist, it is created.
|
|
||||||
*
|
|
||||||
* @param logfile Path to the logfile
|
|
||||||
* @return A shared_ptr to the file_sink
|
|
||||||
*/
|
|
||||||
std::shared_ptr<file_sink> create_file_sink(const std::string &logfile);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut to create a stderr log sink.
|
|
||||||
*
|
|
||||||
* @return A shared_ptr to the stderr_sink
|
|
||||||
*/
|
|
||||||
std::shared_ptr<stderr_sink> create_stderr_sink();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut to create a stdout log sink.
|
|
||||||
*
|
|
||||||
* @return A shared_ptr to the stdout_sink
|
|
||||||
*/
|
|
||||||
std::shared_ptr<stdout_sink> create_stdout_sink();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut to create a rotating file log sink
|
|
||||||
* with the given path, max log files and max
|
|
||||||
* log file size. If the path doesn't
|
|
||||||
* exist, it is created.
|
|
||||||
*
|
|
||||||
* @param logfile Path to the log file
|
|
||||||
* @param max_size Max log file size
|
|
||||||
* @param file_count Max number of log files
|
|
||||||
* @return A shared_ptr to the rotating_file_sink
|
|
||||||
*/
|
|
||||||
std::shared_ptr<rotating_file_sink> create_rotating_file_sink(const std::string &logfile, size_t max_size, size_t file_count);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default min log level.
|
|
||||||
*
|
|
||||||
* @param min_lvl Default min log level
|
|
||||||
*/
|
|
||||||
void default_min_log_level(log_level min_lvl);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default max log level.
|
|
||||||
*
|
|
||||||
* @param max_lvl Default max log level
|
|
||||||
*/
|
|
||||||
void default_max_log_level(log_level max_lvl);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the domain min log level for the
|
|
||||||
* domain with the given name.
|
|
||||||
*
|
|
||||||
* @param name Log domain name
|
|
||||||
* @param min_lvl Default min log level
|
|
||||||
*/
|
|
||||||
void domain_min_log_level(const std::string &name, log_level min_lvl);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default max log level for the
|
|
||||||
* domain with the given name.
|
|
||||||
*
|
|
||||||
* @param name Log domain name
|
|
||||||
* @param max_lvl Default max log level
|
|
||||||
*/
|
|
||||||
void domain_max_log_level(const std::string &name, log_level max_lvl);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a log sink to the default log domain
|
|
||||||
*
|
|
||||||
* @param sink The log sink to add
|
|
||||||
*/
|
|
||||||
void add_log_sink(sink_ptr sink);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a log sink to the log domain
|
|
||||||
* with the given name. If the domain
|
|
||||||
* doesn't exist, it is created.
|
|
||||||
*
|
|
||||||
* @param sink The log sink to add
|
|
||||||
* @param domain The log domain name to add
|
|
||||||
*/
|
|
||||||
void add_log_sink(sink_ptr sink, const std::string &domain);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all sinks from the
|
|
||||||
* default domain
|
|
||||||
*/
|
|
||||||
void clear_all_log_sinks();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all sinks from the log domain
|
|
||||||
* with the given domain name
|
|
||||||
*
|
|
||||||
* @param domain Domain name to clear all sinks
|
|
||||||
*/
|
|
||||||
void clear_all_log_sinks(const std::string &domain);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a logger with the given source name
|
|
||||||
* connected to the default log domain.
|
|
||||||
*
|
|
||||||
* @param source The name of the source
|
|
||||||
* @return The logger instance
|
|
||||||
*/
|
|
||||||
logger create_logger(std::string source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a logger with the given source name
|
|
||||||
* connected to the log domain with the given
|
|
||||||
* name. If the domain doesn't exist, it is created
|
|
||||||
*
|
|
||||||
* @param source The name of the source
|
|
||||||
* @param domain The name of the log domain
|
|
||||||
* @return The logger instance
|
|
||||||
*/
|
|
||||||
logger create_logger(std::string source, const std::string &domain);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the given message for the given source and log level
|
|
||||||
* to the default log domain.
|
|
||||||
*
|
|
||||||
* @param lvl Log level
|
|
||||||
* @param source Source of the log message
|
|
||||||
* @param message Message to log
|
|
||||||
*/
|
|
||||||
void log_default(log_level lvl, const std::string &source, const char *message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log the given message with source and log level
|
|
||||||
* to the default domain. The message will be created
|
|
||||||
* from the what-argument and the args while the preprocessed
|
|
||||||
* message uses the printf style to add the arguments.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments
|
|
||||||
* @param lvl Log level
|
|
||||||
* @param source Source of the log message
|
|
||||||
* @param what The printf style message
|
|
||||||
* @param args The arguments for the message
|
|
||||||
*/
|
|
||||||
template<typename... ARGS>
|
|
||||||
void log(const log_level lvl, const std::string &source, const char *what, ARGS const &... args)
|
|
||||||
{
|
|
||||||
char message_buffer[16384];
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
sprintf_s(message_buffer, 912, what, args...);
|
|
||||||
#else
|
|
||||||
sprintf(message_buffer, what, args...);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
log_default(lvl, source, message_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //MATADOR_LOG_MANAGER_HPP
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#ifndef MATADOR_LOG_SINK_HPP
|
|
||||||
#define MATADOR_LOG_SINK_HPP
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Base class for all log sinks
|
|
||||||
*
|
|
||||||
* This class must be the base class for all
|
|
||||||
* log sinks and provides their interface
|
|
||||||
*
|
|
||||||
* The main interface is the write() interface
|
|
||||||
* defining how the log message is written.
|
|
||||||
*
|
|
||||||
* The close() interface defines a way to close
|
|
||||||
* the concrete log sink
|
|
||||||
*/
|
|
||||||
class log_sink
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Destroys the log sink
|
|
||||||
*/
|
|
||||||
virtual ~log_sink() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the given log message with the given size
|
|
||||||
* to the concrete sink
|
|
||||||
*
|
|
||||||
* @param message The message to log
|
|
||||||
* @param size The size of the message
|
|
||||||
*/
|
|
||||||
virtual void write(const char *message, std::size_t size) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the log sink if necessary.
|
|
||||||
*/
|
|
||||||
virtual void close() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
using sink_ptr = std::shared_ptr<log_sink>; /**< Shortcut to the log sink shared pointer */
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //MATADOR_LOG_SINK_HPP
|
|
||||||
|
|
@ -1,266 +0,0 @@
|
||||||
#ifndef MATADOR_LOGGER_HPP
|
|
||||||
#define MATADOR_LOGGER_HPP
|
|
||||||
|
|
||||||
#include "matador/logger/log_level.hpp"
|
|
||||||
#include "matador/logger/log_domain.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief logger to write log messages to log domains
|
|
||||||
*
|
|
||||||
* This class is used to write log messages to a connected
|
|
||||||
* log domain (@sa log_domain).
|
|
||||||
* Everywhere a logger is needed, it can be instantiated with
|
|
||||||
* @code
|
|
||||||
* matador::create_logger(source name)
|
|
||||||
* @endcode
|
|
||||||
*
|
|
||||||
* The interface provides methods to log to each relevant
|
|
||||||
* log level (@sa log_level)
|
|
||||||
*
|
|
||||||
* The message format syntax is like the printf syntax.
|
|
||||||
* If the message string contains placeholder (beginning with %)
|
|
||||||
* an argument is expected to be part of the argument list of the
|
|
||||||
* calling method.
|
|
||||||
*
|
|
||||||
* All log messages are written through the internal
|
|
||||||
* log_domain object to the sinks.
|
|
||||||
*/
|
|
||||||
class logger final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a logger with a given source name connected
|
|
||||||
* to the given log_domain
|
|
||||||
*
|
|
||||||
* @param source The name of the source
|
|
||||||
* @param log_domain The log_domain containing the log sinks
|
|
||||||
*/
|
|
||||||
logger(std::string source, std::shared_ptr<log_domain> log_domain);
|
|
||||||
|
|
||||||
logger(const logger& l) = delete;
|
|
||||||
logger(logger&& l) noexcept;
|
|
||||||
logger& operator=(const logger& l) = delete;
|
|
||||||
logger& operator=(logger&& l) noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message string with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void fatal(const std::string &what, ARGS const &... args) const { fatal(what.c_str(), args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void fatal(const char *what, ARGS const &... args) const { log(log_level::LVL_FATAL, what, args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message string with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void error(const std::string &what, ARGS const &... args) const { error(what.c_str(), args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void error(const char *what, ARGS const &... args) const { log(log_level::LVL_ERROR, what, args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message string with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void warn(const std::string &what, ARGS const &... args) const { warn(what.c_str(), args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void warn(const char *what, ARGS const &... args) const { log(log_level::LVL_WARN, what, args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message string with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void info(const std::string &what, ARGS const &... args) const { info(what.c_str(), args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void info(const char *what, ARGS const &... args) const { log(log_level::LVL_INFO, what, args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message string with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void debug(const std::string &what, ARGS const &... args) const { debug(what.c_str(), args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void debug(const char *what, ARGS const &... args) const { log(log_level::LVL_DEBUG, what, args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message string with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void trace(const std::string &what, ARGS const &... args) const { trace(what.c_str(), args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer with log level LVL_FATAL
|
|
||||||
* to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void trace(const char *what, ARGS const &... args) const { log(log_level::LVL_TRACE, what, args...); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer
|
|
||||||
* with the given log level to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @tparam ARGS Type of the arguments to be replaced for the message placeholder
|
|
||||||
* @param lvl The log level
|
|
||||||
* @param what The message to log
|
|
||||||
* @param args The arguments to be replaced in the message
|
|
||||||
*/
|
|
||||||
template<typename ... ARGS>
|
|
||||||
void log(log_level lvl, const char *what, ARGS const &... args) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a log message represented by a char pointer
|
|
||||||
* with the given log level to the connected log_domain.
|
|
||||||
*
|
|
||||||
* @param lvl The log level
|
|
||||||
* @param what The message to log
|
|
||||||
*/
|
|
||||||
void log(log_level lvl, const char *what) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the source the logger represents
|
|
||||||
*
|
|
||||||
* @return Represented log source name
|
|
||||||
*/
|
|
||||||
[[nodiscard]] const std::string& source() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the connected log domain
|
|
||||||
*
|
|
||||||
* @return The name of the log domain
|
|
||||||
*/
|
|
||||||
[[nodiscard]] std::string domain() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string source_;
|
|
||||||
std::shared_ptr<log_domain> logger_domain_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... ARGS>
|
|
||||||
void logger::log(log_level lvl, const char *what, ARGS const &... args) const {
|
|
||||||
char message_buffer[16384];
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
sprintf_s(message_buffer, 16384, what, args...);
|
|
||||||
#else
|
|
||||||
sprintf(message_buffer, what, args...);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
logger_domain_->log(lvl, source_, message_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
decltype(auto) myForward(T&& t)
|
|
||||||
{
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
decltype(auto) myForward(std::string& t)
|
|
||||||
{
|
|
||||||
return t.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
decltype(auto) myForward(std::string&& t)
|
|
||||||
{
|
|
||||||
return t.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
static void log(const char* pszFmt, Args&&... args)
|
|
||||||
{
|
|
||||||
doSomething(pszFmt, myForward<Args>(std::forward<Args>(args))...);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //MATADOR_LOGGER_HPP
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
#ifndef MATADOR_ROTATING_FILE_SINK_HPP
|
|
||||||
#define MATADOR_ROTATING_FILE_SINK_HPP
|
|
||||||
|
|
||||||
#include "matador/logger/log_sink.hpp"
|
|
||||||
#include "matador/utils/file.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace matador::logger {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A rotating log file sink
|
|
||||||
*
|
|
||||||
* This log sink provides a possibility to
|
|
||||||
* rotate several log files if the current
|
|
||||||
* log file reaches the maximum size.
|
|
||||||
*
|
|
||||||
* The user can define the maximum number of log files
|
|
||||||
* and the maximum size of the current log file
|
|
||||||
*
|
|
||||||
* The name of the current log file is defined within the
|
|
||||||
* given logfile path. Each rotated (moved) log file gets
|
|
||||||
* an incremented number extension right before the
|
|
||||||
* file extension, e.g.:
|
|
||||||
*
|
|
||||||
* Log file name is 'log.txt' the first rotated log file
|
|
||||||
* is named 'log-1.txt' and so on until the maximum
|
|
||||||
* number of log files is reached. Then it starts from
|
|
||||||
* the beginning.
|
|
||||||
* Keep in mind that the log file to which is currently
|
|
||||||
* written to is always named like the file name
|
|
||||||
* given within the path.
|
|
||||||
*/
|
|
||||||
class rotating_file_sink final : public log_sink
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates a rotating log file sink within the given path
|
|
||||||
* with the given maximum number of rotating log files where
|
|
||||||
* each file size is never greater than the given max file
|
|
||||||
* size.
|
|
||||||
*
|
|
||||||
* @param path Path of the log file
|
|
||||||
* @param max_size Max log file size
|
|
||||||
* @param file_count Max log file count
|
|
||||||
*/
|
|
||||||
rotating_file_sink(const std::string& path, size_t max_size, size_t file_count);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the message to the current log file. If the
|
|
||||||
* actual size exceeds the file size limit, the log files
|
|
||||||
* are rotated.
|
|
||||||
*
|
|
||||||
* @param message Message to write
|
|
||||||
* @param size The size of the log message
|
|
||||||
*/
|
|
||||||
void write(const char *message, size_t size) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close all open log files
|
|
||||||
*/
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string calculate_filename(size_t fileno);
|
|
||||||
|
|
||||||
void rotate();
|
|
||||||
void prepare(const std::string &path);
|
|
||||||
|
|
||||||
private:
|
|
||||||
file logfile_;
|
|
||||||
std::string path_;
|
|
||||||
std::string base_path_;
|
|
||||||
std::string extension_;
|
|
||||||
size_t max_size_ = 0;
|
|
||||||
size_t current_size_ = 0;
|
|
||||||
size_t current_file_no_ = 0;
|
|
||||||
size_t file_count_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //MATADOR_ROTATING_FILE_SINK_HPP
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
#ifndef MATADOR_ACCEPTOR_HPP
|
|
||||||
#define MATADOR_ACCEPTOR_HPP
|
|
||||||
|
|
||||||
#include "matador/net/export.hpp"
|
|
||||||
#include "matador/net/handler.hpp"
|
|
||||||
#include "matador/net/handler_creator.hpp"
|
|
||||||
#include "matador/net/ip.hpp"
|
|
||||||
|
|
||||||
#include "matador/logger/logger.hpp"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace matador {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The acceptor class is used to accept new connection
|
|
||||||
* within the reactor dispatcher.
|
|
||||||
*
|
|
||||||
* Once a new connection was accepted by the acceptor a
|
|
||||||
* new handler is created and registered within the reactor
|
|
||||||
* to handle the established connection
|
|
||||||
*/
|
|
||||||
class OOS_NET_API acceptor : public handler, public handler_creator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef std::function<std::shared_ptr<handler>(tcp::socket sock, tcp::peer endpoint, acceptor *accptr)> t_accept_handler; /**< Shortcut to a function creating a handler on successfully accepted a new connection */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor
|
|
||||||
*/
|
|
||||||
acceptor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an acceptor with the given endpoint. The endpoint
|
|
||||||
* represents the address on which the acceptor listens for new
|
|
||||||
* connections
|
|
||||||
*
|
|
||||||
* @param endpoint Endpoint to listen for new connections
|
|
||||||
*/
|
|
||||||
explicit acceptor(tcp::peer endpoint);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an acceptor with the given endpoint. The endpoint
|
|
||||||
* represents the address on which the acceptor listens for new
|
|
||||||
* connections. The given function is called when a new
|
|
||||||
* connection was accepted and returns a new handler for
|
|
||||||
* the new connection.
|
|
||||||
*
|
|
||||||
* @param endpoint Endpoint to listen for new connections
|
|
||||||
* @param on_new_connection Function creating a new handler for each accepted new connection
|
|
||||||
*/
|
|
||||||
acceptor(tcp::peer endpoint, t_accept_handler on_new_connection);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor
|
|
||||||
*/
|
|
||||||
~acceptor() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a new connection is accepted the given function
|
|
||||||
* is called to create a new handler for the connection
|
|
||||||
*
|
|
||||||
* @param on_new_connection Function creating a new handler for the new connection
|
|
||||||
*/
|
|
||||||
void accecpt(t_accept_handler on_new_connection);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accepts new connection at the given endpoint.
|
|
||||||
* When a new connection is accepted the given function
|
|
||||||
* is called to create a new handler for the connection.
|
|
||||||
*
|
|
||||||
* @param endpoint Endpoint to listen for new connections
|
|
||||||
* @param on_new_connection Function creating a new handler for the new connection
|
|
||||||
*/
|
|
||||||
void accecpt(const tcp::peer& endpoint, t_accept_handler on_new_connection);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the acceptor means the socket address of the
|
|
||||||
* endpoint is bound to the created listing socket
|
|
||||||
* Then the socket is used for listening for new
|
|
||||||
* connections.
|
|
||||||
*/
|
|
||||||
void open() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current listening socket fd
|
|
||||||
*
|
|
||||||
* @return Listening socket fd
|
|
||||||
*/
|
|
||||||
socket_type handle() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is called when a new connection wants to
|
|
||||||
* execute to the endpoint. Once the connection was accepted
|
|
||||||
* a new connection handler is created and the socket is
|
|
||||||
* passed to the handler. The handler is then registered
|
|
||||||
* to the reactor to disptach its read and write events.
|
|
||||||
*/
|
|
||||||
void on_input() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does actually nothing
|
|
||||||
*/
|
|
||||||
void on_output() override {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does actually nothing
|
|
||||||
*/
|
|
||||||
void on_except() override {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does actually nothing
|
|
||||||
*/
|
|
||||||
void on_timeout() override {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does actually nothing
|
|
||||||
*/
|
|
||||||
void on_close() override {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the listen fd of the acceptor
|
|
||||||
*/
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns always false because new connections
|
|
||||||
* are indicated as read events.
|
|
||||||
*
|
|
||||||
* @return Always false
|
|
||||||
*/
|
|
||||||
bool is_ready_write() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the acceptor was opened
|
|
||||||
* and a listen fd was created.
|
|
||||||
*
|
|
||||||
* @return True If a listen socket was created
|
|
||||||
*/
|
|
||||||
bool is_ready_read() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the acceptor that
|
|
||||||
* this handler was closed.
|
|
||||||
*
|
|
||||||
* @param hndlr Closed handler.
|
|
||||||
*/
|
|
||||||
void notify_close(handler *hndlr) override;
|
|
||||||
|
|
||||||
std::string name() const override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Returns the current endpoint accepting new connection.
|
|
||||||
*
|
|
||||||
* @return Current listening endpoint
|
|
||||||
*/
|
|
||||||
const tcp::peer& endpoint() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
tcp::acceptor acceptor_;
|
|
||||||
tcp::peer endpoint_;
|
|
||||||
|
|
||||||
std::string name_ { "acceptor" };
|
|
||||||
|
|
||||||
t_accept_handler accept_handler_;
|
|
||||||
|
|
||||||
logger log_;
|
|
||||||
|
|
||||||
tcp::peer create_client_endpoint() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //MATADOR_ACCEPTOR_HPP
|
|
||||||
|
|
@ -1,453 +0,0 @@
|
||||||
#ifndef MATADOR_ADDRESS_HPP
|
|
||||||
#define MATADOR_ADDRESS_HPP
|
|
||||||
|
|
||||||
#include "matador/utils/os.hpp"
|
|
||||||
|
|
||||||
#include "matador/net/export.hpp"
|
|
||||||
#include "matador/net/os.hpp"
|
|
||||||
#include "matador/net/error.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#else
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace matador {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum representing the protocol
|
|
||||||
* family IPv4 and IPv6
|
|
||||||
*/
|
|
||||||
enum protocol_family {
|
|
||||||
V4, /**< IPv4 enum value */
|
|
||||||
V6 /**< IPv6 enum value */
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @cond MATADOR_DEV
|
|
||||||
template < protocol_family PF >
|
|
||||||
class address_router;
|
|
||||||
/// @endcond
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The address class represents a IPv4 or
|
|
||||||
* IPv6 address.
|
|
||||||
*/
|
|
||||||
class OOS_NET_API address
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Default constructor
|
|
||||||
*/
|
|
||||||
address() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an address from the given
|
|
||||||
* addr representing a IPv4 socket address
|
|
||||||
* structure
|
|
||||||
*
|
|
||||||
* @param addr Initial IPv4 Socket address
|
|
||||||
*/
|
|
||||||
explicit address(const sockaddr_in &addr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an address from the given
|
|
||||||
* addr representing a IPv6 socket address
|
|
||||||
* structure
|
|
||||||
*
|
|
||||||
* @param addr Initial IPv6 Socket address
|
|
||||||
*/
|
|
||||||
explicit address(const sockaddr_in6 &addr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy constructs an address from the
|
|
||||||
* given address x
|
|
||||||
*
|
|
||||||
* @param x Address to copy from
|
|
||||||
*/
|
|
||||||
address(const address &x) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy assign an address from the
|
|
||||||
* given address x
|
|
||||||
*
|
|
||||||
* @param x Address to assign from
|
|
||||||
* @return The assigned address
|
|
||||||
*/
|
|
||||||
address& operator=(const address &x);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move copy constructs an address from the
|
|
||||||
* given address x
|
|
||||||
*
|
|
||||||
* @param x Address to move copy from
|
|
||||||
*/
|
|
||||||
address(address &&x) noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move assign an address from the
|
|
||||||
* given address x
|
|
||||||
*
|
|
||||||
* @param x Address to move assign from
|
|
||||||
* @return The moved address
|
|
||||||
*/
|
|
||||||
address& operator=(address &&x) noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructs the address
|
|
||||||
*/
|
|
||||||
~address();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the address as unsigned long value
|
|
||||||
*
|
|
||||||
* @return The address as unsigned long value
|
|
||||||
*/
|
|
||||||
unsigned int to_ulong() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the address in string format either
|
|
||||||
* in IPv4 dotted format or in IPv6 colon based
|
|
||||||
* format
|
|
||||||
*
|
|
||||||
* @return The address as string
|
|
||||||
*/
|
|
||||||
std::string to_string() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the port number
|
|
||||||
*
|
|
||||||
* @param pn Port number to set
|
|
||||||
*/
|
|
||||||
void port(unsigned short pn);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current port number of the
|
|
||||||
* address.
|
|
||||||
*
|
|
||||||
* @return The current port number
|
|
||||||
*/
|
|
||||||
unsigned short port() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if address is IPv4
|
|
||||||
*
|
|
||||||
* @return True if IPv4 address
|
|
||||||
*/
|
|
||||||
bool is_v4() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if address is IPv4
|
|
||||||
*
|
|
||||||
* @return True if IPv4 address
|
|
||||||
*/
|
|
||||||
bool is_v6() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the raw sockaddr structure
|
|
||||||
*
|
|
||||||
* @return The raw sockaddr structure
|
|
||||||
*/
|
|
||||||
sockaddr* addr();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the raw sockaddr structure
|
|
||||||
*
|
|
||||||
* @return The raw sockaddr structure
|
|
||||||
*/
|
|
||||||
const sockaddr* addr() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IPv4 sockaddr_in structure
|
|
||||||
*
|
|
||||||
* @return The IPv4 sockaddr_in structure
|
|
||||||
*/
|
|
||||||
sockaddr_in* addr_v4();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IPv4 sockaddr_in structure
|
|
||||||
*
|
|
||||||
* @return The IPv4 sockaddr_in structure
|
|
||||||
*/
|
|
||||||
const sockaddr_in* addr_v4() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IPv6 sockaddr_in6 structure
|
|
||||||
*
|
|
||||||
* @return The IPv6 sockaddr_in6 structure
|
|
||||||
*/
|
|
||||||
sockaddr_in6* addr_v6();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IPv6 sockaddr_in6 structure
|
|
||||||
*
|
|
||||||
* @return The IPv6 sockaddr_in6 structure
|
|
||||||
*/
|
|
||||||
const sockaddr_in6* addr_v6() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the size of the underlying
|
|
||||||
* address structure.
|
|
||||||
*
|
|
||||||
* @return Size of the underlying address structure
|
|
||||||
*/
|
|
||||||
socklen_t size() const;
|
|
||||||
|
|
||||||
typedef address_router<V4> v4; /**< Shortcut to the internal IPv4 address router */
|
|
||||||
typedef address_router<V6> v6; /**< Shortcut to the internal IPv6 address router */
|
|
||||||
|
|
||||||
private:
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
template < protocol_family PF >
|
|
||||||
friend class address_router;
|
|
||||||
|
|
||||||
union socket_address {
|
|
||||||
sockaddr sa_raw;
|
|
||||||
sockaddr_in sa_in;
|
|
||||||
sockaddr_in6 sa_in6;
|
|
||||||
};
|
|
||||||
|
|
||||||
socket_address socket_address_ {};
|
|
||||||
|
|
||||||
socklen_t size_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @cond MATADOR_DEV
|
|
||||||
template <>
|
|
||||||
class address_router<V4>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
address_router() = delete;
|
|
||||||
address_router& operator=(const address_router&) = delete;
|
|
||||||
address_router(const address_router&) = delete;
|
|
||||||
address_router& operator=(address_router&&) = delete;
|
|
||||||
address_router(address_router&&) = delete;
|
|
||||||
|
|
||||||
static address empty() { return mk_address(INADDR_ANY); };
|
|
||||||
static address any() { return mk_address(INADDR_ANY); }
|
|
||||||
static address loopback() { return mk_address(INADDR_LOOPBACK); }
|
|
||||||
static address broadcast() {return mk_address(INADDR_BROADCAST); }
|
|
||||||
static address from_ip(const std::string &str) { return from_ip(str.c_str()); }
|
|
||||||
static address from_ip(const char *str)
|
|
||||||
{
|
|
||||||
// now fill in the address info struct
|
|
||||||
// create and fill the hints struct
|
|
||||||
if (str == nullptr) {
|
|
||||||
return address();
|
|
||||||
}
|
|
||||||
// get address from string
|
|
||||||
|
|
||||||
sockaddr_in addr{};
|
|
||||||
int ret = os::inet_pton(PF_INET, str, &(addr.sin_addr));
|
|
||||||
if (ret == 1) {
|
|
||||||
addr.sin_family = PF_INET;
|
|
||||||
return address(addr);
|
|
||||||
} else if (ret == 0) {
|
|
||||||
detail::throw_logic_error("invalid ip address");
|
|
||||||
} else {
|
|
||||||
detail::throw_logic_error_with_errno("invalid ip address: %s", errno);
|
|
||||||
}
|
|
||||||
return address();
|
|
||||||
}
|
|
||||||
|
|
||||||
static address from_hostname(const std::string &str) { return from_hostname(str.c_str()); }
|
|
||||||
static address from_hostname(const char *str)
|
|
||||||
{
|
|
||||||
// now fill in the address info struct
|
|
||||||
// create and fill the hints struct
|
|
||||||
if (str == nullptr) {
|
|
||||||
return address();
|
|
||||||
}
|
|
||||||
// get address from string
|
|
||||||
sockaddr_in addr{};
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if ((ret = os::inet_pton(AF_INET, str, &addr.sin_addr)) == 1) {
|
|
||||||
addr.sin_family = PF_INET;
|
|
||||||
} else if (ret == -1) {
|
|
||||||
detail::throw_logic_error_with_errno("invalid address: %s", errno);
|
|
||||||
} else { /* 0 == try name */
|
|
||||||
struct addrinfo hints{};
|
|
||||||
struct addrinfo *result = nullptr;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(struct addrinfo));
|
|
||||||
hints.ai_family = AF_INET; /* Allow IPv4 or IPv6 */
|
|
||||||
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
|
|
||||||
hints.ai_flags = AI_PASSIVE; /* Numeric or net network hostname */
|
|
||||||
hints.ai_protocol = 0; /* Any protocol */
|
|
||||||
hints.ai_canonname = nullptr;
|
|
||||||
hints.ai_addr = nullptr;
|
|
||||||
hints.ai_next = nullptr;
|
|
||||||
|
|
||||||
int s = getaddrinfo(str, nullptr, &hints, &result);
|
|
||||||
if (s != 0) {
|
|
||||||
detail::throw_logic_error_with_gai_errno("invalid ip address (getaddrinfo): %s", s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* getaddrinfo() returns a list of address structures.
|
|
||||||
Try each address until we successfully bind(2).
|
|
||||||
If socket(2) (or bind(2)) fails, we (close the socket
|
|
||||||
and) try the next address. */
|
|
||||||
|
|
||||||
// take first address
|
|
||||||
memcpy(&addr, result->ai_addr, sizeof(&result->ai_addr));
|
|
||||||
|
|
||||||
freeaddrinfo(result); /* No longer needed */
|
|
||||||
}
|
|
||||||
addr.sin_family = PF_INET;
|
|
||||||
memset(addr.sin_zero, '\0', sizeof(addr.sin_zero));
|
|
||||||
return address(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static address mk_address(unsigned int inaddr)
|
|
||||||
{
|
|
||||||
sockaddr_in addr{};
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sin_family = PF_INET;
|
|
||||||
addr.sin_addr.s_addr = htonl(inaddr);
|
|
||||||
return address(addr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
class address_router<V6>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
address_router() = delete;
|
|
||||||
address_router& operator=(const address_router&) = delete;
|
|
||||||
address_router(const address_router&) = delete;
|
|
||||||
address_router& operator=(address_router&&) = delete;
|
|
||||||
address_router(address_router&&) = delete;
|
|
||||||
|
|
||||||
static address empty() { return mk_address(in6addr_any); }
|
|
||||||
static address any() { return mk_address(in6addr_any); }
|
|
||||||
static address loopback() { return mk_address(in6addr_loopback); }
|
|
||||||
static address broadcast() {return mk_multicast_address(); }
|
|
||||||
|
|
||||||
static address from_ip(const std::string &str)
|
|
||||||
{
|
|
||||||
return from_ip(str.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
static address from_ip(const char *str)
|
|
||||||
{
|
|
||||||
// now fill in the address info struct
|
|
||||||
// create and fill the hints struct
|
|
||||||
if (str == nullptr) {
|
|
||||||
return address();
|
|
||||||
}
|
|
||||||
// get address from string
|
|
||||||
|
|
||||||
sockaddr_in6 addr{};
|
|
||||||
int ret = os::inet_pton(PF_INET6, str, &(addr.sin6_addr));
|
|
||||||
if (ret == 1) {
|
|
||||||
addr.sin6_family = PF_INET6;
|
|
||||||
return address(addr);
|
|
||||||
} else if (ret == 0) {
|
|
||||||
char message_buffer[1024];
|
|
||||||
os::sprintf(message_buffer, 1024, "INET_PTON (ip): invalid ip address [%s]", str);
|
|
||||||
detail::throw_logic_error(message_buffer);
|
|
||||||
} else {
|
|
||||||
char message_buffer[1024];
|
|
||||||
int err = errno;
|
|
||||||
os::sprintf(message_buffer, 1024, "INET_PTON (ip): invalid ip address [%s]: %%s", str);
|
|
||||||
detail::throw_logic_error_with_errno(message_buffer, err);
|
|
||||||
}
|
|
||||||
return address();
|
|
||||||
}
|
|
||||||
|
|
||||||
static address from_hostname(const std::string &str)
|
|
||||||
{
|
|
||||||
return from_hostname(str.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
static address from_hostname(const char *str)
|
|
||||||
{
|
|
||||||
// now fill in the address info struct
|
|
||||||
// create and fill the hints struct
|
|
||||||
if (str == nullptr) {
|
|
||||||
return address();
|
|
||||||
}
|
|
||||||
// get address from string
|
|
||||||
sockaddr_in6 addr{};
|
|
||||||
|
|
||||||
int ret = os::inet_pton(PF_INET6, str, &addr.sin6_addr);
|
|
||||||
|
|
||||||
if (ret == 1) {
|
|
||||||
addr.sin6_family = PF_INET6;
|
|
||||||
} else if (ret == -1) {
|
|
||||||
char message_buffer[1024];
|
|
||||||
int err = errno;
|
|
||||||
os::sprintf(message_buffer, 1024, "INET_PTON (host): invalid ip address [%s] (errno: %d): %%s", str, err);
|
|
||||||
detail::throw_logic_error_with_errno(message_buffer, err);
|
|
||||||
} else { /* 0 == try name */
|
|
||||||
struct addrinfo hints{};
|
|
||||||
struct addrinfo *result = nullptr;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(struct addrinfo));
|
|
||||||
hints.ai_family = AF_INET6; /* Allow IPv4 or IPv6 */
|
|
||||||
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
|
|
||||||
hints.ai_flags = AI_PASSIVE; /* Numeric or net network hostname */
|
|
||||||
hints.ai_protocol = 0; /* Any protocol */
|
|
||||||
hints.ai_canonname = nullptr;
|
|
||||||
hints.ai_addr = nullptr;
|
|
||||||
hints.ai_next = nullptr;
|
|
||||||
|
|
||||||
int s = getaddrinfo(str, nullptr, &hints, &result);
|
|
||||||
if (s != 0) {
|
|
||||||
char message_buffer[1024];
|
|
||||||
os::sprintf(message_buffer, 1024, "GETADDRINFO (host): invalid ip address [%s] (errno: %d): %%s", str, s);
|
|
||||||
detail::throw_logic_error_with_errno(message_buffer, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* getaddrinfo() returns a list of address structures.
|
|
||||||
Try each address until we successfully bind(2).
|
|
||||||
If socket(2) (or bind(2)) fails, we (close the socket
|
|
||||||
and) try the next address. */
|
|
||||||
|
|
||||||
// take first address
|
|
||||||
memcpy(&addr, result->ai_addr, result->ai_addrlen);
|
|
||||||
|
|
||||||
freeaddrinfo(result); /* No longer needed */
|
|
||||||
}
|
|
||||||
addr.sin6_family = PF_INET6;
|
|
||||||
return address(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static OOS_NET_API const char *IP6ADDR_MULTICAST_ALLNODES;
|
|
||||||
|
|
||||||
static address mk_address(in6_addr in6addr)
|
|
||||||
{
|
|
||||||
sockaddr_in6 addr{};
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sin6_family = PF_INET6;
|
|
||||||
addr.sin6_addr = in6addr;
|
|
||||||
return address(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static address mk_multicast_address()
|
|
||||||
{
|
|
||||||
sockaddr_in6 addr{};
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sin6_family = PF_INET6;
|
|
||||||
os::inet_pton(AF_INET6, IP6ADDR_MULTICAST_ALLNODES, &addr.sin6_addr);
|
|
||||||
return address(addr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @endcond
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif //MATADOR_ADDRESS_HPP
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue