Compare commits
110
Commits
ae236746ad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19de5714f4 | ||
|
|
484c2d8b05 | ||
|
|
43d2f81b95 | ||
|
|
27f6c81da2 | ||
|
|
12e5b27a8c | ||
|
|
ff3a2e4217 | ||
|
|
ac8b2ed4e4 | ||
|
|
38da76bfb3 | ||
|
|
4199c5029c | ||
|
|
fb57545cce | ||
|
|
7bb7afa227 | ||
|
|
2022f6f75b | ||
|
|
b4765cac88 | ||
|
|
25bcc362f2 | ||
|
|
b7c12d8217 | ||
|
|
1fe5c9bac4 | ||
|
|
8f754f3542 | ||
|
|
f977b2afc9 | ||
|
|
5a3cdc8645 | ||
|
|
fa7ff832c9 | ||
|
|
30e2e0c221 | ||
|
|
85f87367c0 | ||
|
|
31e9c7e9ac | ||
|
|
6f3e589e10 | ||
|
|
05c0c0393d | ||
|
|
e14d9a77eb | ||
|
|
4cda0f2664 | ||
|
|
50a0eb580a | ||
|
|
be9f66c427 | ||
|
|
5d06a13775 | ||
|
|
4a04a678f9 | ||
|
|
b15b8da31f | ||
|
|
12919ba372 | ||
|
|
9a02abacea | ||
|
|
6bbc870362 | ||
|
|
dd94ed1020 | ||
|
|
2b552c4383 | ||
|
|
496e94ddcd | ||
|
|
be610ffcad | ||
|
|
830185c3c5 | ||
|
|
34740bf126 | ||
|
|
c9a84e0568 | ||
|
|
7e1713ddd3 | ||
|
|
b9709d14c2 | ||
|
|
2d9a4f3866 | ||
|
|
b1f2d94c7a | ||
|
|
6f5326941e | ||
|
|
0822332669 | ||
|
|
16d7fd7e76 | ||
|
|
b255ae22b4 | ||
|
|
b11993d60b | ||
|
|
a3f467a5a8 | ||
|
|
b966a7a1a7 | ||
|
|
78c5f64b34 | ||
|
|
0f13884f06 | ||
|
|
fa3ea28920 | ||
|
|
aa221aa571 | ||
|
|
e8b0f0e802 | ||
|
|
27353e011a | ||
|
|
f13837e515 | ||
|
|
c025fb282a | ||
|
|
7c02d745c8 | ||
|
|
e171147915 | ||
|
|
47848ff674 | ||
|
|
615143efb0 | ||
|
|
aa5d32e30c | ||
|
|
9c8e402692 | ||
|
|
9509e56f3e | ||
|
|
102a7fc604 | ||
|
|
b158b41556 | ||
|
|
9355f7ea27 | ||
|
|
ed6366fef6 | ||
|
|
5804454ae6 | ||
|
|
2060883d59 | ||
|
|
92eb30767e | ||
|
|
71cb460e4c | ||
|
|
a16d3d7a21 | ||
|
|
4504c783e1 | ||
|
|
94b1c356a5 | ||
|
|
320d06bb20 | ||
|
|
2893f749b6 | ||
|
|
56c65203fc | ||
|
|
81f22d73d9 | ||
|
|
1c92e07173 | ||
|
|
8ad13076c8 | ||
|
|
da423ea8bb | ||
|
|
8ba70cc79e | ||
|
|
5326102801 | ||
|
|
93b868aaaf | ||
|
|
6a4111ba3d | ||
|
|
f3c502a2ce | ||
|
|
a436af5293 | ||
|
|
640dcfadb6 | ||
|
|
825e530a8d | ||
|
|
c2a96f4cbd | ||
|
|
700cbc0652 | ||
|
|
72bc6db6b3 | ||
|
|
1dbe4e6096 | ||
|
|
376beded43 | ||
|
|
44adc81ce1 | ||
|
|
1efc2bdc96 | ||
|
|
72f9d28d0d | ||
|
|
145c4dc0a3 | ||
|
|
171e8d36ce | ||
|
|
ae5777bdb0 | ||
|
|
c801d53e8c | ||
|
|
7c1e940814 | ||
|
|
d76ac60278 | ||
|
|
4ed01a617d | ||
|
|
30f2126225 |
@@ -1,2 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
cmake-build-debug
|
cmake-build-debug
|
||||||
|
Testing
|
||||||
|
debug
|
||||||
|
|||||||
+29
-4
@@ -1,25 +1,50 @@
|
|||||||
cmake_minimum_required(VERSION 3.26)
|
cmake_minimum_required(VERSION 3.26)
|
||||||
project(query)
|
project(
|
||||||
|
query
|
||||||
|
VERSION 1.0.0
|
||||||
|
DESCRIPTION "SQL query fluent prototype for PostgreSQL, SQLite, MySQL and MSSQL"
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
set(GCC_CLANG_COMMON_FLAGS "-Wall -Wconversion -Wextra -pedantic -ftemplate-backtrace-limit=0")
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
add_compile_options(/Zc:preprocessor)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
find_package(ODBC REQUIRED)
|
find_package(ODBC REQUIRED)
|
||||||
find_package(SQLite3 REQUIRED)
|
find_package(SQLite3 REQUIRED)
|
||||||
|
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 "Found SQLite3 ${SQLite3_VERSION}")
|
||||||
message(STATUS "Adding SQLite3 include directory: ${SQLite3_INCLUDE_DIRS}")
|
message(STATUS "Adding SQLite3 include directory: ${SQLite3_INCLUDE_DIRS}")
|
||||||
message(STATUS "Adding SQLite3 libs: ${SQLite3_LIBRARIES}")
|
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 "Common flags ${CMAKE_CXX_FLAGS}")
|
||||||
message(STATUS "Debug flags ${CMAKE_CXX_FLAGS_DEBUG}")
|
message(STATUS "Debug flags ${CMAKE_CXX_FLAGS_DEBUG}")
|
||||||
message(STATUS "Relase flags ${CMAKE_CXX_FLAGS_RELEASE}")
|
message(STATUS "Relase flags ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
|
||||||
message(STATUS "Linker flags ${CMAKE_EXE_LINKER_FLAGS}")
|
message(STATUS "Linker flags ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
add_subdirectory(backends)
|
add_subdirectory(backends)
|
||||||
|
add_subdirectory(demo)
|
||||||
add_executable(query main.cpp)
|
|
||||||
target_link_libraries(query matador)
|
|
||||||
|
|||||||
@@ -1,3 +1,89 @@
|
|||||||
# query
|
# query_context
|
||||||
|
|
||||||
A fluent sql query builder
|
A fluent sql query_context builder
|
||||||
|
|
||||||
|
```MATADOR_BACKENDS_PATH=/home/sascha/Develop/query/cmake-build-debug/backends```
|
||||||
|
|
||||||
|
Object definition
|
||||||
|
```cpp
|
||||||
|
enum class Color {
|
||||||
|
Red, Green, Blue, Yellow, Pink, Black, White
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Coordinate
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int z;
|
||||||
|
};
|
||||||
|
|
||||||
|
// special handling for custom type Coordinate
|
||||||
|
namespace matador::utils::access {
|
||||||
|
|
||||||
|
template<class Operator>
|
||||||
|
void attribute(Operator &op, const char *id, Coordinate &value, const field_attributes &attr = null_attributes) {
|
||||||
|
attribute(op, (std::string(id) + "_x").c_str(), value.x, attr);
|
||||||
|
attribute(op, (std::string(id) + "_y").c_str(), value.y, attr);
|
||||||
|
attribute(op, (std::string(id) + "_z").c_str(), value.z, attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct airplane
|
||||||
|
{
|
||||||
|
unsigned long id;
|
||||||
|
std::string brand;
|
||||||
|
std::string model;
|
||||||
|
Color color;
|
||||||
|
Coordinate position;
|
||||||
|
|
||||||
|
template<class Operator>
|
||||||
|
void process(Operator &op) {
|
||||||
|
namespace field = matador::utils::access;
|
||||||
|
field::primary_key(op, "id", id);
|
||||||
|
field::attribute(op, "brand", brand, 255);
|
||||||
|
field::attribute(op, "model", model, 255);
|
||||||
|
field::attribute(op, "color", color);
|
||||||
|
field::attribute(op, "position", position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
```cpp
|
||||||
|
connection_pool<connection> pool("sqlite://sqlite.db", 4);
|
||||||
|
session s(pool);
|
||||||
|
|
||||||
|
// create all tables
|
||||||
|
s.create().table<airplane>().execute();
|
||||||
|
|
||||||
|
std::vector<airplane> planes {
|
||||||
|
{1, "Airbus", "A380", Color::Green, {1, 2, 3}},
|
||||||
|
{2, "Boeing", "707", Color::White, {4, 5, 6}},
|
||||||
|
{3, "Boeing", "747", Color::Blue, {0, 0, 0}}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto stmt = s.insert()
|
||||||
|
.into<airplane>("airplane")
|
||||||
|
.values<airplane>().prepare();
|
||||||
|
|
||||||
|
|
||||||
|
// insert
|
||||||
|
for (const auto &plane: planes) {
|
||||||
|
auto res = stmt.bind(plane).execute();
|
||||||
|
stmt.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
s.update("airplane")
|
||||||
|
.set({"model", "737"})
|
||||||
|
.where("id"_col == 2 && "color"_col == Color::White)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto result = s.select<airplane>()
|
||||||
|
.from("airplane")
|
||||||
|
.where("brand"_col == "Boeing")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
for (const auto &plane : result) {
|
||||||
|
std::cout << "airplane: " << plane->brand << " - " << plane->model << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
@@ -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 +1,3 @@
|
|||||||
add_subdirectory(sqlite)
|
add_subdirectory(sqlite)
|
||||||
|
add_subdirectory(postgres)
|
||||||
|
add_subdirectory(mysql)
|
||||||
@@ -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*/
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
set(HEADER
|
||||||
|
include/postgres_connection.hpp
|
||||||
|
include/postgres_error.hpp
|
||||||
|
include/postgres_result_reader.hpp
|
||||||
|
include/postgres_dialect.hpp
|
||||||
|
include/postgres_statement.hpp
|
||||||
|
include/postgres_parameter_binder.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/postgres_connection.cpp
|
||||||
|
src/postgres_error.cpp
|
||||||
|
src/postgres_result_reader.cpp
|
||||||
|
src/postgres_dialect.cpp
|
||||||
|
src/postgres_statement.cpp
|
||||||
|
src/postgres_parameter_binder.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBRARY_TARGET matador-postgres)
|
||||||
|
|
||||||
|
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/postgres/include
|
||||||
|
${PostgreSQL_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_TARGET} matador ${PostgreSQL_LIBRARIES})
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#ifndef QUERY_POSTGRES_CONNECTION_HPP
|
||||||
|
#define QUERY_POSTGRES_CONNECTION_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#ifdef matador_postgres_EXPORTS
|
||||||
|
#define MATADOR_POSTGRES_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define MATADOR_POSTGRES_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#pragma warning(disable: 4355)
|
||||||
|
#else
|
||||||
|
#define MATADOR_POSTGRES_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "matador/sql/connection_impl.hpp"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
class postgres_connection : public matador::sql::connection_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit postgres_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:
|
||||||
|
[[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PGconn *conn_{nullptr};
|
||||||
|
|
||||||
|
using string_to_int_map = std::unordered_map<std::string, unsigned long>;
|
||||||
|
|
||||||
|
static string_to_int_map statement_name_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
MATADOR_POSTGRES_API matador::sql::connection_impl* create_database(const matador::sql::connection_info &info);
|
||||||
|
|
||||||
|
MATADOR_POSTGRES_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_postgres_EXPORTS
|
||||||
|
#define MATADOR_POSTGRES_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define MATADOR_POSTGRES_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#pragma warning(disable: 4355)
|
||||||
|
#else
|
||||||
|
#define MATADOR_POSTGRES_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "matador/sql/dialect.hpp"
|
||||||
|
|
||||||
|
extern "C" [[maybe_unused]] MATADOR_POSTGRES_API const matador::sql::dialect* get_dialect();
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_DIALECT_HPP
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef QUERY_POSTGRES_ERROR_HPP
|
||||||
|
#define QUERY_POSTGRES_ERROR_HPP
|
||||||
|
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_ERROR_HPP
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
|
#define QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
|
|
||||||
|
#include "matador/sql/parameter_binder.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
class postgres_parameter_binder final : public sql::parameter_binder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit postgres_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]] const std::vector<const char*>& params() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> strings_;
|
||||||
|
std::vector<const char*> params_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_PARAMETER_BINDER_H
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
|
#define QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
|
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
#include "matador/sql/query_result_reader.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
class postgres_result_reader : public sql::query_result_reader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit postgres_result_reader(PGresult *result);
|
||||||
|
~postgres_result_reader() override;
|
||||||
|
|
||||||
|
[[nodiscard]] size_t column_count() const override;
|
||||||
|
[[nodiscard]] const char *column(size_t index) const override;
|
||||||
|
bool fetch() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PGresult *result_{};
|
||||||
|
|
||||||
|
size_t row_count_{};
|
||||||
|
size_t column_count_{};
|
||||||
|
int row_index_{-1};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_RESULT_READER_HPP
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef QUERY_POSTGRES_STATEMENT_HPP
|
||||||
|
#define QUERY_POSTGRES_STATEMENT_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/statement_impl.hpp"
|
||||||
|
|
||||||
|
#include "postgres_parameter_binder.h"
|
||||||
|
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
class postgres_statement final : public sql::statement_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
postgres_statement(PGconn *db, PGresult *result, std::string name, 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:
|
||||||
|
PGconn *db_{nullptr};
|
||||||
|
PGresult *result_{nullptr};
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
|
||||||
|
postgres_parameter_binder binder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_POSTGRES_STATEMENT_HPP
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
#include "postgres_connection.hpp"
|
||||||
|
#include "postgres_error.hpp"
|
||||||
|
#include "postgres_result_reader.hpp"
|
||||||
|
#include "postgres_statement.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/record.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
postgres_connection::string_to_int_map postgres_connection::statement_name_map_{};
|
||||||
|
|
||||||
|
postgres_connection::postgres_connection(const sql::connection_info &info)
|
||||||
|
: connection_impl(info) {}
|
||||||
|
|
||||||
|
void postgres_connection::open()
|
||||||
|
{
|
||||||
|
if (is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string connection("user=" + info().user + " password=" + info().password + " host=" + info().hostname + " dbname=" + info().database + " port=" + std::to_string(info().port));
|
||||||
|
|
||||||
|
conn_ = PQconnectdb(connection.c_str());
|
||||||
|
if (PQstatus(conn_) == CONNECTION_BAD) {
|
||||||
|
const std::string msg = PQerrorMessage(conn_);
|
||||||
|
PQfinish(conn_);
|
||||||
|
conn_ = nullptr;
|
||||||
|
throw_postgres_error(msg.c_str(), "postgres");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_connection::close()
|
||||||
|
{
|
||||||
|
if (conn_) {
|
||||||
|
PQfinish(conn_);
|
||||||
|
conn_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool postgres_connection::is_open()
|
||||||
|
{
|
||||||
|
return conn_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> postgres_connection::fetch(const std::string &stmt)
|
||||||
|
{
|
||||||
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
|
|
||||||
|
std::vector<sql::column_definition> prototype;
|
||||||
|
auto num_col = PQnfields(res);
|
||||||
|
for (int i = 0; i < num_col; ++i) {
|
||||||
|
const char *col_name = PQfname(res, i);
|
||||||
|
auto type = PQftype(res, i);
|
||||||
|
auto size = PQfmod(res, i);
|
||||||
|
prototype.emplace_back(col_name);
|
||||||
|
}
|
||||||
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), std::move(prototype)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string postgres_connection::generate_statement_name(const sql::query_context &query)
|
||||||
|
{
|
||||||
|
std::stringstream name;
|
||||||
|
name << query.table.name << "_" << query.command_name;
|
||||||
|
auto result = postgres_connection::statement_name_map_.find(name.str());
|
||||||
|
|
||||||
|
if (result == postgres_connection::statement_name_map_.end()) {
|
||||||
|
result = postgres_connection::statement_name_map_.insert(std::make_pair(name.str(), 0)).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
name << "_" << ++result->second;
|
||||||
|
|
||||||
|
return name.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::statement_impl> postgres_connection::prepare(sql::query_context context)
|
||||||
|
{
|
||||||
|
auto statement_name = postgres_connection::generate_statement_name(context);
|
||||||
|
|
||||||
|
PGresult *result = PQprepare(conn_, statement_name.c_str(), context.sql.c_str(), static_cast<int>(context.bind_vars.size()), nullptr);
|
||||||
|
|
||||||
|
throw_postgres_error(result, conn_, "postgres", context.sql);
|
||||||
|
|
||||||
|
return std::make_unique<postgres_statement>(conn_, result, statement_name, std::move(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t postgres_connection::execute(const std::string &stmt)
|
||||||
|
{
|
||||||
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
|
|
||||||
|
const auto affected_rows = sql::to_long_long(PQcmdTuples(res));
|
||||||
|
|
||||||
|
PQclear(res);
|
||||||
|
|
||||||
|
return affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql::data_type_t string2type(const char *type)
|
||||||
|
{
|
||||||
|
if (strcmp(type, "int2") == 0) {
|
||||||
|
return sql::data_type_t::type_short;
|
||||||
|
} else if (strcmp(type, "int4") == 0) {
|
||||||
|
return sql::data_type_t::type_int;
|
||||||
|
} else if (strcmp(type, "int8") == 0) {
|
||||||
|
return sql::data_type_t::type_long_long;
|
||||||
|
} else if (strncmp(type, "int8", 6) == 0) {
|
||||||
|
return sql::data_type_t::type_long_long;
|
||||||
|
} else if (strcmp(type, "date") == 0) {
|
||||||
|
return sql::data_type_t::type_date;
|
||||||
|
} else if (strncmp(type, "timestamp", 8) == 0) {
|
||||||
|
return sql::data_type_t::type_time;
|
||||||
|
} else if (strcmp(type, "float4") == 0) {
|
||||||
|
return sql::data_type_t::type_float;
|
||||||
|
} else if (strcmp(type, "float8") == 0) {
|
||||||
|
return sql::data_type_t::type_double;
|
||||||
|
} else if (strncmp(type, "varchar", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_varchar;
|
||||||
|
} else if (strncmp(type, "character varying", 7) == 0) {
|
||||||
|
return sql::data_type_t::type_varchar;
|
||||||
|
} else if (strncmp(type, "text", 0) == 0) {
|
||||||
|
return sql::data_type_t::type_text;
|
||||||
|
} else {
|
||||||
|
return sql::data_type_t::type_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sql::column_definition> postgres_connection::describe(const std::string &table)
|
||||||
|
{
|
||||||
|
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='" + table + "'");
|
||||||
|
|
||||||
|
PGresult *res = PQexec(conn_, stmt.c_str());
|
||||||
|
|
||||||
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
|
|
||||||
|
postgres_result_reader reader(res);
|
||||||
|
std::vector<sql::column_definition> prototype;
|
||||||
|
while (reader.fetch()) {
|
||||||
|
char *end = nullptr;
|
||||||
|
// Todo: Handle error
|
||||||
|
auto index = strtoul(reader.column(0), &end, 10) - 1;
|
||||||
|
std::string name = reader.column(1);
|
||||||
|
|
||||||
|
// Todo: extract size
|
||||||
|
auto type = (string2type(reader.column(2)));
|
||||||
|
end = nullptr;
|
||||||
|
sql::null_option null_opt{sql::null_option::NULLABLE};
|
||||||
|
if (strtoul(reader.column(4), &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 postgres_connection::exists(const std::string &schema_name, const std::string &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());
|
||||||
|
|
||||||
|
throw_postgres_error(res, conn_, "postgres", stmt);
|
||||||
|
|
||||||
|
return sql::to_long_long(PQcmdTuples(res)) == 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
MATADOR_POSTGRES_API void destroy_database(matador::sql::connection_impl *db)
|
||||||
|
{
|
||||||
|
delete db;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include "postgres_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_placeholder_func([](size_t index) {
|
||||||
|
return "$" + std::to_string(index);
|
||||||
|
})
|
||||||
|
.with_token_replace_map({
|
||||||
|
{dialect::token_t::BEGIN_BINARY_DATA, "E'\\"}
|
||||||
|
})
|
||||||
|
.with_data_type_replace_map({
|
||||||
|
{data_type_t::type_blob, "BYTEA"}
|
||||||
|
})
|
||||||
|
.with_default_schema_name("public")
|
||||||
|
.build();
|
||||||
|
return &d;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#include "postgres_error.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
void throw_postgres_error(const char *what, const std::string &source)
|
||||||
|
{
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "postgres error (" << source << "): " << what;
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_postgres_error(PGconn *db, const std::string &source)
|
||||||
|
{
|
||||||
|
if (PQstatus(db) == CONNECTION_BAD) {
|
||||||
|
throw_postgres_error(PQerrorMessage(db), source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_postgres_error(PGresult *res, PGconn *db, const std::string &source, const std::string &sql)
|
||||||
|
{
|
||||||
|
if (res == nullptr) {
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "postgres error (" << source << ", " << PQerrorMessage(db) << ": " << sql;
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
} else if ((PQresultStatus(res) != PGRES_COMMAND_OK &&
|
||||||
|
PQresultStatus(res) != PGRES_TUPLES_OK)) {
|
||||||
|
std::stringstream msg;
|
||||||
|
msg << "postgres error (" << source << ", " << PQresultErrorField(res, PG_DIAG_SQLSTATE) << ") " << PQerrorMessage(db) << ": " << sql;
|
||||||
|
throw std::logic_error(msg.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
#include "postgres_parameter_binder.h"
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template < class T >
|
||||||
|
void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t index, T &x)
|
||||||
|
{
|
||||||
|
strings[index] = std::to_string(x);
|
||||||
|
params[index] = strings[index].c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t index, char &x)
|
||||||
|
{
|
||||||
|
strings[index] = std::to_string(x);
|
||||||
|
params[index] = strings[index].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t index, unsigned char &x)
|
||||||
|
{
|
||||||
|
strings[index] = std::to_string(x);
|
||||||
|
params[index] = strings[index].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
//template <>
|
||||||
|
//void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t &index, const matador::date &x)
|
||||||
|
//{
|
||||||
|
// strings[index] = matador::to_string(x, date_format::ISO8601);
|
||||||
|
// params[index] = strings[index].c_str();
|
||||||
|
// ++index;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//template <>
|
||||||
|
//void bind_value(std::vector<std::string> &strings, std::vector<const char*> ¶ms, size_t &index, const matador::time &x)
|
||||||
|
//{
|
||||||
|
// strings[index] = matador::to_string(x, "%Y-%m-%d %T.%f");
|
||||||
|
// params[index] = strings[index].c_str();
|
||||||
|
// ++index;
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
postgres_parameter_binder::postgres_parameter_binder(size_t size)
|
||||||
|
: strings_(size)
|
||||||
|
, params_(size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, char i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, short i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, long i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, long long int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, unsigned char i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, unsigned short i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, unsigned int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, unsigned long i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, unsigned long long int i)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, bool b)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, float d)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, double d)
|
||||||
|
{
|
||||||
|
detail::bind_value(strings_, params_, pos, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, const char *str)
|
||||||
|
{
|
||||||
|
params_[pos] = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, const char *str, size_t size)
|
||||||
|
{
|
||||||
|
params_[pos] = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, const std::string &str)
|
||||||
|
{
|
||||||
|
strings_[pos] = str;
|
||||||
|
params_[pos] = strings_[pos].c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, const std::string &str, size_t size)
|
||||||
|
{
|
||||||
|
bind(pos, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_parameter_binder::bind(size_t pos, const utils::blob &blob)
|
||||||
|
{
|
||||||
|
params_[pos] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<const char *> &postgres_parameter_binder::params() const
|
||||||
|
{
|
||||||
|
return params_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#include "postgres_result_reader.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/to_value.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
postgres_result_reader::postgres_result_reader(PGresult *result)
|
||||||
|
: result_(result)
|
||||||
|
, row_count_(PQntuples(result_))
|
||||||
|
, column_count_(PQnfields(result_))
|
||||||
|
{}
|
||||||
|
|
||||||
|
postgres_result_reader::~postgres_result_reader()
|
||||||
|
{
|
||||||
|
if (result_) {
|
||||||
|
PQclear(result_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t postgres_result_reader::column_count() const
|
||||||
|
{
|
||||||
|
return column_count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *postgres_result_reader::column(size_t index) const
|
||||||
|
{
|
||||||
|
return PQgetvalue(result_, static_cast<int>(row_index_), static_cast<int>(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool postgres_result_reader::fetch()
|
||||||
|
{
|
||||||
|
return ++row_index_ < row_count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#include "postgres_statement.hpp"
|
||||||
|
#include "postgres_error.hpp"
|
||||||
|
#include "postgres_result_reader.hpp"
|
||||||
|
|
||||||
|
namespace matador::backends::postgres {
|
||||||
|
|
||||||
|
postgres_statement::postgres_statement(PGconn *db, PGresult *result, std::string name, const sql::query_context &query)
|
||||||
|
: statement_impl(query)
|
||||||
|
, db_(db)
|
||||||
|
, result_(result)
|
||||||
|
, name_(std::move(name))
|
||||||
|
, binder_(query_.bind_vars.size())
|
||||||
|
{}
|
||||||
|
|
||||||
|
size_t postgres_statement::execute()
|
||||||
|
{
|
||||||
|
PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast<int>(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0);
|
||||||
|
|
||||||
|
throw_postgres_error(res, db_, "postgres", query_.sql);
|
||||||
|
|
||||||
|
return std::stoul(PQcmdTuples(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sql::query_result_impl> postgres_statement::fetch()
|
||||||
|
{
|
||||||
|
PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast<int>(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0);
|
||||||
|
|
||||||
|
throw_postgres_error(res, db_, "postgres", query_.sql);
|
||||||
|
|
||||||
|
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), std::move(query_.prototype)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void postgres_statement::reset() {}
|
||||||
|
|
||||||
|
sql::parameter_binder& postgres_statement::binder()
|
||||||
|
{
|
||||||
|
return binder_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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(POSTGRES_CONNECTION_STRING "postgres://test:test123@127.0.0.1:15432/test")
|
||||||
|
|
||||||
|
configure_file(Connection.hpp.in ${PROJECT_BINARY_DIR}/backends/postgres/test/connection.hpp @ONLY IMMEDIATE)
|
||||||
|
|
||||||
|
message(STATUS "postgresql connection string: ${POSTGRES_CONNECTION_STRING}")
|
||||||
|
|
||||||
|
set(TEST_SOURCES
|
||||||
|
../../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 postgres_tests)
|
||||||
|
|
||||||
|
add_executable(${LIBRARY_TEST_TARGET} ${TEST_SOURCES})
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_TEST_TARGET} PRIVATE
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
matador
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
${PostgreSQL_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 " (PostgreSQL)")
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef POSTGRES_CONNECTION_HPP
|
||||||
|
#define POSTGRES_CONNECTION_HPP
|
||||||
|
|
||||||
|
namespace matador::test::connection {
|
||||||
|
const char* const dns = "@POSTGRES_CONNECTION_STRING@";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*POSTGRES_CONNECTION_HPP*/
|
||||||
@@ -2,19 +2,36 @@ set(HEADER
|
|||||||
include/sqlite_connection.hpp
|
include/sqlite_connection.hpp
|
||||||
include/sqlite_error.hpp
|
include/sqlite_error.hpp
|
||||||
include/sqlite_dialect.hpp
|
include/sqlite_dialect.hpp
|
||||||
include/sqlite_query_result.hpp
|
include/sqlite_result_reader.hpp
|
||||||
|
include/sqlite_statement.hpp
|
||||||
|
include/sqlite_parameter_binder.h
|
||||||
|
include/sqlite_prepared_result_reader.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
src/sqlite_connection.cpp
|
src/sqlite_connection.cpp
|
||||||
src/sqlite_error.cpp
|
src/sqlite_error.cpp
|
||||||
src/sqlite_dialect.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
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(matador-sqlite SHARED ${SOURCES} ${HEADER})
|
set(LIBRARY_TARGET matador-sqlite)
|
||||||
target_include_directories(matador-sqlite PRIVATE
|
|
||||||
|
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}/include
|
||||||
${PROJECT_SOURCE_DIR}/backends/sqlite/include
|
${PROJECT_SOURCE_DIR}/backends/sqlite/include
|
||||||
${SQLite3_INCLUDE_DIRS})
|
${SQLite3_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_libraries(matador-sqlite matador ${SQLite3_LIBRARIES})
|
target_link_libraries(${LIBRARY_TARGET} matador ${SQLite3_LIBRARIES})
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
#include "matador/sql/connection_impl.hpp"
|
#include "matador/sql/connection_impl.hpp"
|
||||||
|
|
||||||
|
#include "sqlite_result_reader.hpp"
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
namespace matador::backends::sqlite {
|
namespace matador::backends::sqlite {
|
||||||
@@ -26,11 +28,29 @@ public:
|
|||||||
void close() override;
|
void close() override;
|
||||||
bool is_open() override;
|
bool is_open() override;
|
||||||
|
|
||||||
void execute(const std::string &stmt) override;
|
std::unique_ptr<sql::query_result_impl> fetch(const std::string &stmt) override;
|
||||||
void prepare(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:
|
private:
|
||||||
sqlite3 *sqlite_db_{};
|
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_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#ifndef QUERY_SQLITE_QUERY_RESULT_HPP
|
|
||||||
#define QUERY_SQLITE_QUERY_RESULT_HPP
|
|
||||||
|
|
||||||
namespace matador::backends::sqlite {
|
|
||||||
|
|
||||||
class sqlite_query_result {
|
|
||||||
public:
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //QUERY_SQLITE_QUERY_RESULT_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
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
#include "sqlite_connection.hpp"
|
#include "sqlite_connection.hpp"
|
||||||
#include "sqlite_error.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>
|
#include <utility>
|
||||||
|
|
||||||
namespace matador::backends::sqlite {
|
namespace matador::backends::sqlite {
|
||||||
@@ -11,35 +17,172 @@ sqlite_connection::sqlite_connection(const sql::connection_info &info)
|
|||||||
|
|
||||||
void sqlite_connection::open()
|
void sqlite_connection::open()
|
||||||
{
|
{
|
||||||
const auto ret = sqlite3_open(info().database.c_str(), &sqlite_db_);
|
if (is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = sqlite3_open(info().database.c_str(), &db_);
|
||||||
|
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
throw_sqlite_error(ret, sqlite_db_, "open");
|
throw_sqlite_error(ret, db_, "open");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sqlite_connection::close()
|
void sqlite_connection::close()
|
||||||
{
|
{
|
||||||
int ret = sqlite3_close(sqlite_db_);
|
int ret = sqlite3_close(db_);
|
||||||
|
|
||||||
throw_sqlite_error(ret, sqlite_db_, "close");
|
throw_sqlite_error(ret, db_, "close");
|
||||||
|
|
||||||
sqlite_db_ = nullptr;
|
db_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sqlite_connection::is_open()
|
bool sqlite_connection::is_open()
|
||||||
{
|
{
|
||||||
return sqlite_db_ != nullptr;
|
return db_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sqlite_connection::execute(const std::string &stmt)
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sqlite_connection::prepare(const std::string &stmt)
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
#include "sqlite_dialect.hpp"
|
#include "sqlite_dialect.hpp"
|
||||||
|
|
||||||
[[maybe_unused]] const matador::sql::dialect* get_dialect() {
|
#include "matador/sql/dialect_builder.hpp"
|
||||||
|
|
||||||
|
[[maybe_unused]] const matador::sql::dialect *get_dialect()
|
||||||
|
{
|
||||||
using namespace matador::sql;
|
using namespace matador::sql;
|
||||||
const static dialect d{{
|
const static dialect d = dialect_builder::builder()
|
||||||
{ dialect::token_t::BEGIN, "BEGIN TRANSACTION"},
|
.create()
|
||||||
{ dialect::token_t::COMMIT, "COMMIT TRANSACTION"},
|
.with_token_replace_map({
|
||||||
{ dialect::token_t::ROLLBACK, "ROLLBACK TRANSACTION"}
|
{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;
|
return &d;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ 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)
|
||||||
{
|
{
|
||||||
if (ec != SQLITE_OK) {
|
if (ec != SQLITE_OK && ec != SQLITE_DONE) {
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "sqlite error (" << source << "): " << sqlite3_errmsg(db);
|
msg << "sqlite error (" << source << "): " << sqlite3_errmsg(db);
|
||||||
throw std::logic_error(msg.str());
|
throw std::logic_error(msg.str());
|
||||||
@@ -18,7 +18,7 @@ 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)
|
void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source, const std::string &sql)
|
||||||
{
|
{
|
||||||
if (ec != SQLITE_OK) {
|
if (ec != SQLITE_OK&& ec != SQLITE_DONE) {
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "sqlite error (" << source << ", sql: " << sql << "): " << sqlite3_errmsg(db);
|
msg << "sqlite error (" << source << ", sql: " << sql << "): " << sqlite3_errmsg(db);
|
||||||
throw std::logic_error(msg.str());
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include "matador/sql/connection_info.hpp"
|
||||||
|
#include "matador/sql/connection_pool.hpp"
|
||||||
|
#include "matador/sql/session.hpp"
|
||||||
|
#include "matador/sql/statement_cache.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
using namespace matador;
|
||||||
|
|
||||||
|
class StatementCacheFixture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StatementCacheFixture()
|
||||||
|
: pool(matador::test::connection::dns, 4), ses(pool)
|
||||||
|
{}
|
||||||
|
~StatementCacheFixture() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
matador::sql::connection_pool<matador::sql::connection> pool;
|
||||||
|
matador::sql::session ses;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(StatementCacheFixture, "Acquire prepared statement", "[statement cache]") {
|
||||||
|
sql::statement_cache cache;
|
||||||
|
|
||||||
|
auto conn = pool.acquire();
|
||||||
|
|
||||||
|
std::string sql = R"(SELECT * FROM person WHERE name = 'george')";
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
add_executable(demo main.cpp)
|
||||||
|
target_link_libraries(demo PRIVATE
|
||||||
|
matador
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
${SQLite3_LIBRARIES}
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "book",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "unsigned long",
|
||||||
|
"constraint": "primary_key"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string",
|
||||||
|
"size": 63
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string",
|
||||||
|
"size": 63
|
||||||
|
},
|
||||||
|
"date_of_birth": {
|
||||||
|
"type": "string",
|
||||||
|
"size": 31
|
||||||
|
},
|
||||||
|
"year_of_birth": {
|
||||||
|
"type": "unsigned short"
|
||||||
|
},
|
||||||
|
"distinguished": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "book",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "unsigned long",
|
||||||
|
"constraint": "primary_key"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"size": 511
|
||||||
|
},
|
||||||
|
"book_author": {
|
||||||
|
"type": "author",
|
||||||
|
"constraint": {
|
||||||
|
"type": "foreign_key",
|
||||||
|
"references": "id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"published_in": {
|
||||||
|
"type": "unsigned_short"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+295
@@ -0,0 +1,295 @@
|
|||||||
|
#include "matador/sql/column.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
|
#include "matador/sql/schema.hpp"
|
||||||
|
#include "matador/sql/connection.hpp"
|
||||||
|
#include "matador/sql/entity.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/enum_mapper.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/query_helper.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct author {
|
||||||
|
unsigned long id{};
|
||||||
|
std::string first_name;
|
||||||
|
std::string last_name;
|
||||||
|
std::string date_of_birth;
|
||||||
|
unsigned short year_of_birth{};
|
||||||
|
bool distinguished{false};
|
||||||
|
|
||||||
|
template<typename Operator>
|
||||||
|
void process( Operator& op ) {
|
||||||
|
namespace field = matador::utils::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 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct book {
|
||||||
|
unsigned long id{};
|
||||||
|
matador::sql::entity<author> book_author;
|
||||||
|
std::string title;
|
||||||
|
unsigned short published_in{};
|
||||||
|
|
||||||
|
template<typename Operator>
|
||||||
|
void process( Operator& op ) {
|
||||||
|
namespace field = matador::utils::access;
|
||||||
|
field::primary_key( op, "id", id );
|
||||||
|
field::attribute( op, "title", title, 511 );
|
||||||
|
field::has_one( op, "author_id", book_author, matador::utils::default_foreign_attributes );
|
||||||
|
field::attribute( op, "published_in", published_in );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct payload {
|
||||||
|
unsigned long id{};
|
||||||
|
|
||||||
|
template<typename Operator>
|
||||||
|
void process( Operator& op ) {
|
||||||
|
namespace field = matador::utils::access;
|
||||||
|
field::primary_key( op, "id", id );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct job {
|
||||||
|
enum class job_state {
|
||||||
|
Pending,
|
||||||
|
Running,
|
||||||
|
Succeeded,
|
||||||
|
Failed,
|
||||||
|
Canceled
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class job_mode {
|
||||||
|
Foreground,
|
||||||
|
Background
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned long id{};
|
||||||
|
matador::sql::entity<payload> payload;
|
||||||
|
std::string type;
|
||||||
|
std::string description;
|
||||||
|
job_state state;
|
||||||
|
job_mode mode;
|
||||||
|
|
||||||
|
template<typename Operator>
|
||||||
|
void process( Operator& op ) {
|
||||||
|
namespace field = matador::utils::access;
|
||||||
|
field::primary_key( op, "id", id );
|
||||||
|
field::belongs_to( op, "payload", payload, matador::utils::default_foreign_attributes );
|
||||||
|
field::attribute( op, "type", type, 511 );
|
||||||
|
field::attribute( op, "description", description, 511 );
|
||||||
|
field::attribute( op, "state", state );
|
||||||
|
field::attribute( op, "mode", mode );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const matador::utils::enum_mapper<job::job_state> job_state_enum({
|
||||||
|
{job::job_state::Pending, "Pending"},
|
||||||
|
{job::job_state::Running, "Running"},
|
||||||
|
{job::job_state::Succeeded, "Succeeded"},
|
||||||
|
{job::job_state::Failed, "Failed"},
|
||||||
|
{job::job_state::Canceled, "Canceled"}
|
||||||
|
});
|
||||||
|
|
||||||
|
static const matador::utils::enum_mapper<job::job_mode> job_mode_enum({
|
||||||
|
{job::job_mode::Foreground, "Foreground"},
|
||||||
|
{job::job_mode::Background, "Background"}
|
||||||
|
});
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct matador::sql::data_type_traits<job::job_state, 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, job::job_state &value)
|
||||||
|
{
|
||||||
|
std::string enum_string;
|
||||||
|
reader.read_value(id, index, enum_string, 64);
|
||||||
|
if (const auto enum_opt = job_state_enum.to_enum(enum_string)) {
|
||||||
|
value = enum_opt.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static any_type create_value(const job::job_state &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<>
|
||||||
|
struct matador::sql::data_type_traits<job::job_mode, 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, job::job_mode &value)
|
||||||
|
{
|
||||||
|
std::string enum_string;
|
||||||
|
reader.read_value(id, index, enum_string, 64);
|
||||||
|
if (const auto enum_opt = job_mode_enum.to_enum(enum_string)) {
|
||||||
|
value = enum_opt.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static any_type create_value(const job::job_mode &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, id, first_name, last_name, date_of_birth, year_of_birth, distinguished )
|
||||||
|
|
||||||
|
QUERY_HELPER( books, id, author_id, title, published_in )
|
||||||
|
|
||||||
|
QUERY_HELPER( job, id, payload, type, description, state, mode )
|
||||||
|
|
||||||
|
QUERY_HELPER( payload, id )
|
||||||
|
|
||||||
|
QUERY_HELPER( temporary_table, id );
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace matador::sql;
|
||||||
|
using namespace matador;
|
||||||
|
|
||||||
|
const std::string env_var{"MATADOR_BACKENDS_PATH"};
|
||||||
|
|
||||||
|
std::string dns{"sqlite://demo.db"};
|
||||||
|
schema s( "main" );
|
||||||
|
s.attach<author>( "authors" );
|
||||||
|
s.attach<book>( "books" );
|
||||||
|
|
||||||
|
connection c( dns );
|
||||||
|
c.open();
|
||||||
|
s.create( c );
|
||||||
|
|
||||||
|
auto create_authors_sql = c.query( s )
|
||||||
|
.create()
|
||||||
|
.table<author>( qh::authors )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
c.query( s )
|
||||||
|
.create()
|
||||||
|
.table<book>( qh::books )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::cout << "SQL: " << create_authors_sql << "\n";
|
||||||
|
|
||||||
|
author mc;
|
||||||
|
mc.id = 1;
|
||||||
|
mc.first_name = "Michael";
|
||||||
|
mc.last_name = "Crichton";
|
||||||
|
mc.date_of_birth = "19.8.1954";
|
||||||
|
mc.year_of_birth = 1954;
|
||||||
|
mc.distinguished = true;
|
||||||
|
auto insert_authors_sql = c.query( s )
|
||||||
|
.insert()
|
||||||
|
.into( qh::authors )
|
||||||
|
.values( mc )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::cout << "SQL: " << insert_authors_sql << "\n";
|
||||||
|
|
||||||
|
auto result = c.query( s )
|
||||||
|
.select( qh::authors.columns )
|
||||||
|
.from( qh::authors )
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
for (const auto& row: result) { std::cout << "Author " << row.at( qh::authors.first_name ) << "\n"; }
|
||||||
|
|
||||||
|
auto update_authors_sql = c.query( s )
|
||||||
|
.update( qh::authors )
|
||||||
|
.set( {{qh::authors.first_name, "Stephen"},
|
||||||
|
{qh::authors.last_name, "King"}} )
|
||||||
|
.where( qh::authors.last_name == "Crichton" )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::cout << "SQL: " << update_authors_sql << "\n";
|
||||||
|
|
||||||
|
auto authors = c.query( s )
|
||||||
|
.select( qh::authors.columns )
|
||||||
|
.from( qh::authors )
|
||||||
|
.fetch_all<author>();
|
||||||
|
|
||||||
|
for (const auto& a: authors) { std::cout << "Author " << a.first_name << "\n"; }
|
||||||
|
|
||||||
|
c.query( s )
|
||||||
|
.insert()
|
||||||
|
.into( qh::books )
|
||||||
|
.values( {2, "It", mc.id, 1980} )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
c.query( s )
|
||||||
|
.insert()
|
||||||
|
.into( qh::books )
|
||||||
|
.values( {3, "Misery", mc.id, 1984} )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
auto select_books_sql = c.query( s )
|
||||||
|
.select( qh::books.columns, {qh::authors.last_name} )
|
||||||
|
.from( qh::books )
|
||||||
|
.join_left( qh::authors )
|
||||||
|
.on( qh::books.author_id == qh::authors.id )
|
||||||
|
.where( qh::books.published_in < 2008 && qh::authors.last_name == "King" )
|
||||||
|
.group_by( qh::books.published_in )
|
||||||
|
.order_by( qh::books.title ).asc()
|
||||||
|
.limit( 5 )
|
||||||
|
.offset( 2 )
|
||||||
|
.fetch_all();
|
||||||
|
|
||||||
|
for (const auto& r: select_books_sql) { std::cout << "R: " << r.at( qh::books.title ) << ", " << r.at( qh::authors.last_name ) << "\n"; }
|
||||||
|
// SELECT book.title, book.id, book.author_id, book.published_in, author.name
|
||||||
|
// FROM book
|
||||||
|
// INNER JOIN author ON book.author_id = author.id
|
||||||
|
// WHERE book.published_in < 2008 AND author.name = "Michael Crichton"
|
||||||
|
// ORDER BY "book.title" ASC
|
||||||
|
// OFFSET 2 LIMIT 5
|
||||||
|
|
||||||
|
c.query( s ).drop().table( qh::books ).execute();
|
||||||
|
|
||||||
|
auto drop_authors_sql = c.query( s )
|
||||||
|
.drop()
|
||||||
|
.table( qh::authors )
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
std::cout << "SQL: " << drop_authors_sql << "\n";
|
||||||
|
|
||||||
|
auto res = c.query( s )
|
||||||
|
.select( {qh::payload.id} )
|
||||||
|
.from( qh::payload )
|
||||||
|
.join_left( qh::job )
|
||||||
|
.on( qh::job.payload == qh::payload.id )
|
||||||
|
.where(
|
||||||
|
in( qh::payload.id, c.query( s )
|
||||||
|
.select( {qh::job.state} )
|
||||||
|
.from( qh::job )
|
||||||
|
.where( qh::job.state == job::job_state::Running )
|
||||||
|
) &&
|
||||||
|
in( qh::payload.id, c.query( s )
|
||||||
|
.select( {qh::temporary_table.id} )
|
||||||
|
.from( qh::temporary_table ) )
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
// .fetch_value<unsigned long>();
|
||||||
|
std::cout << "SQL: " << res.sql << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef QUERY_ANY_TYPE_HPP
|
||||||
|
#define QUERY_ANY_TYPE_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/placeholder.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/types.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
using any_db_type = std::variant<
|
||||||
|
long long,
|
||||||
|
unsigned long long,
|
||||||
|
double,
|
||||||
|
bool,
|
||||||
|
const char*,
|
||||||
|
std::string,
|
||||||
|
utils::blob,
|
||||||
|
placeholder,
|
||||||
|
nullptr_t>;
|
||||||
|
|
||||||
|
using any_type = std::variant<
|
||||||
|
char, short, int, long, long long,
|
||||||
|
unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long,
|
||||||
|
float, double,
|
||||||
|
bool,
|
||||||
|
const char*,
|
||||||
|
std::string,
|
||||||
|
utils::blob,
|
||||||
|
placeholder,
|
||||||
|
nullptr_t>;
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_ANY_TYPE_HPP
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#ifndef QUERY_ANY_TYPE_TO_STRING_VISITOR_HPP
|
||||||
|
#define QUERY_ANY_TYPE_TO_STRING_VISITOR_HPP
|
||||||
|
|
||||||
|
#include "matador/utils/types.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/placeholder.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class dialect;
|
||||||
|
class query_context;
|
||||||
|
|
||||||
|
struct any_type_to_string_visitor
|
||||||
|
{
|
||||||
|
explicit any_type_to_string_visitor(const dialect &d, query_context &query);
|
||||||
|
|
||||||
|
void operator()(char &x) { to_string(x); }
|
||||||
|
void operator()(short &x) { to_string(x); }
|
||||||
|
void operator()(int &x) { to_string(x); }
|
||||||
|
void operator()(long &x) { to_string(x); }
|
||||||
|
void operator()(long long &x) { to_string(x); }
|
||||||
|
void operator()(unsigned char &x) { to_string(x); }
|
||||||
|
void operator()(unsigned short &x) { to_string(x); }
|
||||||
|
void operator()(unsigned int &x) { to_string(x); }
|
||||||
|
void operator()(unsigned long &x) { to_string(x); }
|
||||||
|
void operator()(unsigned long long &x) { to_string(x); }
|
||||||
|
void operator()(bool &x) { to_string(x); }
|
||||||
|
void operator()(float &x) { to_string(x); }
|
||||||
|
void operator()(double &x) { to_string(x); }
|
||||||
|
void operator()(const char *x) { to_string(x); }
|
||||||
|
void operator()(std::string &x) { to_string(x); }
|
||||||
|
void operator()(utils::blob &x) { to_string(x); }
|
||||||
|
void operator()(placeholder &x) { to_string(x); }
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void to_string(Type &val)
|
||||||
|
{
|
||||||
|
result = std::to_string(val);
|
||||||
|
}
|
||||||
|
void to_string(const char *val);
|
||||||
|
void to_string(std::string &val);
|
||||||
|
void to_string(utils::blob &val);
|
||||||
|
void to_string(placeholder &val);
|
||||||
|
|
||||||
|
const dialect &d;
|
||||||
|
query_context &query;
|
||||||
|
std::string result;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_ANY_TYPE_TO_STRING_VISITOR_HPP
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef QUERY_ANY_TYPE_TO_VISITOR_HPP
|
||||||
|
#define QUERY_ANY_TYPE_TO_VISITOR_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/convert.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
struct placeholder;
|
||||||
|
|
||||||
|
template < typename Type >
|
||||||
|
struct any_type_to_visitor
|
||||||
|
{
|
||||||
|
void operator()(char &x) { convert(result, x); }
|
||||||
|
void operator()(short &x) { convert(result, x); }
|
||||||
|
void operator()(int &x) { convert(result, x); }
|
||||||
|
void operator()(long &x) { convert(result, x); }
|
||||||
|
void operator()(long long &x) { convert(result, x); }
|
||||||
|
void operator()(unsigned char &x) { convert(result, x); }
|
||||||
|
void operator()(unsigned short &x) { convert(result, x); }
|
||||||
|
void operator()(unsigned int &x) { convert(result, x); }
|
||||||
|
void operator()(unsigned long &x) { convert(result, x); }
|
||||||
|
void operator()(unsigned long long &x) { convert(result, x); }
|
||||||
|
void operator()(bool &x) { convert(result, x); }
|
||||||
|
void operator()(float &x) { convert(result, x); }
|
||||||
|
void operator()(double &x) { convert(result, x); }
|
||||||
|
void operator()(const char *x) { convert(result, x); }
|
||||||
|
void operator()(std::string &x) { convert(result, x); }
|
||||||
|
void operator()(utils::blob &x) { convert(result, x); }
|
||||||
|
void operator()(placeholder &/*x*/) {}
|
||||||
|
|
||||||
|
Type result{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_ANY_TYPE_TO_VISITOR_HPP
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
@@ -26,28 +27,50 @@ public:
|
|||||||
const dialect& connection_dialect(const std::string &connection_type);
|
const dialect& connection_dialect(const std::string &connection_type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct backend_context {
|
struct basic_backend_context {
|
||||||
backend_context(const std::string &connection_type,
|
virtual ~basic_backend_context() = default;
|
||||||
const std::string &backends_path);
|
[[nodiscard]] virtual connection_impl* create(const connection_info&) = 0;
|
||||||
|
virtual void destroy(connection_impl*) = 0;
|
||||||
|
[[nodiscard]] virtual const sql::dialect* dialect() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class backend_context final : public basic_backend_context {
|
||||||
|
public:
|
||||||
|
explicit backend_context(const std::string &connection_type);
|
||||||
backend_context(const backend_context&) = delete;
|
backend_context(const backend_context&) = delete;
|
||||||
backend_context& operator=(const backend_context&) = delete;
|
backend_context& operator=(const backend_context&) = delete;
|
||||||
backend_context(backend_context&&) noexcept = default;
|
backend_context(backend_context&&) noexcept = default;
|
||||||
backend_context& operator=(backend_context&&) noexcept = default;
|
backend_context& operator=(backend_context&&) noexcept = default;
|
||||||
~backend_context();
|
~backend_context() override;
|
||||||
|
|
||||||
|
[[nodiscard]] connection_impl* create(const connection_info&) override;
|
||||||
|
void destroy(connection_impl *conn) override;
|
||||||
|
[[nodiscard]] const class dialect* dialect() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
typedef connection_impl*(*create_func)(const connection_info&);
|
typedef connection_impl*(*create_func)(const connection_info&);
|
||||||
typedef void (*destroy_func)(connection_impl*);
|
typedef void (*destroy_func)(connection_impl*);
|
||||||
typedef const dialect*(*dialect_func)();
|
typedef const class dialect*(*dialect_func)();
|
||||||
|
|
||||||
create_func create_connection{};
|
create_func create_connection{};
|
||||||
destroy_func destroy_connection{};
|
destroy_func destroy_connection{};
|
||||||
dialect_func get_dialect{};
|
dialect_func get_dialect{};
|
||||||
utils::library lib;
|
utils::library lib;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class noop_backend_context final : public basic_backend_context {
|
||||||
|
public:
|
||||||
|
connection_impl *create(const connection_info &info) override;
|
||||||
|
void destroy(connection_impl *impl) override;
|
||||||
|
[[nodiscard]] const sql::dialect *dialect() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_set<std::unique_ptr<connection_impl>> noop_connections_;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using backends_t = std::unordered_map<std::string, std::unique_ptr<backend_context>>;
|
using backends_t = std::unordered_map<std::string, std::unique_ptr<basic_backend_context>>;
|
||||||
backends_t backends_;
|
backends_t backends_;
|
||||||
std::string backends_path_;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif //QUERY_BACKEND_PROVIDER_HPP
|
#endif //QUERY_BACKEND_PROVIDER_HPP
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
class dialect;
|
class dialect;
|
||||||
|
class query_context;
|
||||||
|
|
||||||
class basic_condition
|
class basic_condition
|
||||||
{
|
{
|
||||||
@@ -32,7 +33,7 @@ public:
|
|||||||
LIKE
|
LIKE
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual std::string evaluate(dialect &dialect) const = 0;
|
virtual std::string evaluate(const dialect &dialect, query_context &query) const = 0;
|
||||||
|
|
||||||
static std::unordered_map<operand_t, std::string> operands;
|
static std::unordered_map<operand_t, std::string> operands;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,72 +1,43 @@
|
|||||||
#ifndef QUERY_COLUMN_HPP
|
#ifndef QUERY_COLUMN_HPP
|
||||||
#define QUERY_COLUMN_HPP
|
#define QUERY_COLUMN_HPP
|
||||||
|
|
||||||
#include "matador/sql/types.hpp"
|
#include <string>
|
||||||
|
|
||||||
#include "matador/utils/field_attributes.hpp"
|
|
||||||
|
|
||||||
#include <any>
|
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
class column {
|
struct table;
|
||||||
public:
|
|
||||||
explicit column(std::string name)
|
|
||||||
: name_(std::move(name))
|
|
||||||
, attributes_(utils::null_attributes) {}
|
|
||||||
|
|
||||||
template<typename Type>
|
enum class sql_function_t {
|
||||||
explicit column(std::string name, utils::field_attributes attr = utils::null_attributes)
|
NONE,
|
||||||
: column(std::move(name), data_type_traits<Type>::data_type(), attr)
|
COUNT,
|
||||||
{}
|
AVG,
|
||||||
column(std::string name, data_type_t type, utils::field_attributes attr = utils::null_attributes);
|
SUM,
|
||||||
|
MIN,
|
||||||
[[nodiscard]] const std::string& name() const;
|
MAX
|
||||||
[[nodiscard]] const utils::field_attributes& attributes() const;
|
};
|
||||||
[[nodiscard]] data_type_t type() const;
|
|
||||||
|
struct column
|
||||||
private:
|
{
|
||||||
std::string name_;
|
column(const char *name); // NOLINT(*-explicit-constructor)
|
||||||
utils::field_attributes attributes_;
|
column(std::string name); // NOLINT(*-explicit-constructor)
|
||||||
data_type_t type_{};
|
column(sql_function_t func, std::string name); // NOLINT(*-explicit-constructor)
|
||||||
std::any value_;
|
column(std::string table_name, std::string name, std::string as = "");
|
||||||
|
column(std::string table_name, const char* name, std::string as = "");
|
||||||
|
column(struct table &t, const char* name, std::string as = "");
|
||||||
|
|
||||||
|
[[nodiscard]] bool equals(const column &x) const;
|
||||||
|
|
||||||
|
column& as(std::string a);
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_function() const;
|
||||||
|
|
||||||
|
std::string table;
|
||||||
|
std::string name;
|
||||||
|
std::string alias;
|
||||||
|
sql_function_t function_{sql_function_t::NONE};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* User defined literal to have a shortcut creating a column object
|
|
||||||
* @param name Name of the column
|
|
||||||
* @param len Length of the column name
|
|
||||||
* @return A column object with given name
|
|
||||||
*/
|
|
||||||
column operator "" _col(const char *name, size_t len);
|
column operator "" _col(const char *name, size_t len);
|
||||||
|
|
||||||
column make_column(const std::string &name, data_type_t type, utils::field_attributes attr = utils::null_attributes);
|
|
||||||
|
|
||||||
template < typename Type >
|
|
||||||
column make_column(const std::string &name, utils::field_attributes attr = utils::null_attributes)
|
|
||||||
{
|
|
||||||
return make_column(name, data_type_traits<Type>::builtin_type(0), attr);
|
|
||||||
}
|
|
||||||
template <>
|
|
||||||
column make_column<std::string>(const std::string &name, utils::field_attributes attr);
|
|
||||||
|
|
||||||
template < typename Type >
|
|
||||||
column make_pk_column(const std::string &name, size_t size = 0)
|
|
||||||
{
|
|
||||||
return make_column<Type>(name, { size, utils::constraints::PRIMARY_KEY | utils::constraints::NOT_NULL});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
column make_pk_column<std::string>(const std::string &name, size_t size);
|
|
||||||
|
|
||||||
template < typename Type >
|
|
||||||
column make_fk_column(const std::string &name, size_t size = 0)
|
|
||||||
{
|
|
||||||
return make_column<Type>(name, { size, utils::constraints::FOREIGN_KEY });
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
column make_fk_column<std::string>(const std::string &name, size_t size);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif //QUERY_COLUMN_HPP
|
#endif //QUERY_COLUMN_HPP
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#ifndef QUERY_COLUMN_DEFINITION_HPP
|
||||||
|
#define QUERY_COLUMN_DEFINITION_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/any_type.hpp"
|
||||||
|
#include "matador/sql/any_type_to_visitor.hpp"
|
||||||
|
#include "matador/sql/data_type_traits.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/field_attributes.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
enum class null_option : uint8_t {
|
||||||
|
NULLABLE, NOT_NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
class column_definition {
|
||||||
|
public:
|
||||||
|
explicit column_definition(const char *name); // NOLINT(*-explicit-constructor)
|
||||||
|
explicit column_definition(std::string name); // NOLINT(*-explicit-constructor)
|
||||||
|
|
||||||
|
column_definition(const column_definition&) = default;
|
||||||
|
column_definition& operator=(const column_definition&) = default;
|
||||||
|
column_definition(column_definition&&) noexcept = default;
|
||||||
|
column_definition& operator=(column_definition&&) noexcept = default;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
explicit column_definition(std::string name, utils::field_attributes attr)
|
||||||
|
: column_definition(std::move(name), data_type_traits<Type>::builtin_type(attr.size()), attr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
column_definition(std::string name, const Type &, utils::field_attributes attr, null_option null_opt)
|
||||||
|
: column_definition(std::move(name), data_type_traits<Type>::builtin_type(attr.size()), attr, null_opt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
column_definition(std::string name, data_type_t type, utils::field_attributes attr, null_option null_opt, size_t index = 0);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
column_definition(std::string name, std::string ref_table, std::string ref_column, utils::field_attributes attr, null_option null_opt)
|
||||||
|
: column_definition(std::move(name), data_type_traits<Type>::builtin_type(attr.size()), ref_table, ref_column, attr, null_opt)
|
||||||
|
{}
|
||||||
|
|
||||||
|
column_definition(std::string name, data_type_t type, size_t index, std::string ref_table, std::string ref_column, utils::field_attributes attr, null_option null_opt);
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& name() const;
|
||||||
|
[[nodiscard]] int index() const;
|
||||||
|
[[nodiscard]] const utils::field_attributes& attributes() const;
|
||||||
|
[[nodiscard]] bool is_nullable() const;
|
||||||
|
[[nodiscard]] data_type_t type() const;
|
||||||
|
[[nodiscard]] const std::string& ref_table() const;
|
||||||
|
[[nodiscard]] const std::string& ref_column() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_integer() const;
|
||||||
|
[[nodiscard]] bool is_floating_point() const;
|
||||||
|
[[nodiscard]] bool is_bool() const;
|
||||||
|
[[nodiscard]] bool is_string() const;
|
||||||
|
[[nodiscard]] bool is_varchar() const;
|
||||||
|
[[nodiscard]] bool is_blob() const;
|
||||||
|
[[nodiscard]] bool is_null() const;
|
||||||
|
[[nodiscard]] bool is_unknown() const;
|
||||||
|
|
||||||
|
void type(data_type_t type);
|
||||||
|
|
||||||
|
template< typename Type >
|
||||||
|
[[nodiscard]] bool is_type_of() const {
|
||||||
|
return std::holds_alternative<Type>(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string str() const;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void set(const Type &value, const utils::field_attributes &attr = utils::null_attributes)
|
||||||
|
{
|
||||||
|
type_ = data_type_traits<Type>::builtin_type(attr.size());
|
||||||
|
attributes_ = attr;
|
||||||
|
value_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(const std::string &value, const utils::field_attributes &attr)
|
||||||
|
{
|
||||||
|
type_ = data_type_traits<std::string>::builtin_type(attr.size());
|
||||||
|
attributes_ = attr;
|
||||||
|
value_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(const char *value, const utils::field_attributes &attr)
|
||||||
|
{
|
||||||
|
type_ = data_type_traits<std::string>::builtin_type(attr.size());
|
||||||
|
attributes_ = attr;
|
||||||
|
value_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
Type as() const
|
||||||
|
{
|
||||||
|
const Type* ptr= std::get_if<Type>(&value_);
|
||||||
|
if (ptr) {
|
||||||
|
return *ptr;
|
||||||
|
}
|
||||||
|
any_type_to_visitor<Type> visitor;
|
||||||
|
std::visit(visitor, const_cast<any_type&>(value_));
|
||||||
|
return visitor.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream &out, const column_definition &col);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class Operator>
|
||||||
|
void process(Operator &op)
|
||||||
|
{
|
||||||
|
op.on_attribute(name_.c_str(), value_, type_, attributes_);
|
||||||
|
}
|
||||||
|
|
||||||
|
using data_type_index = std::vector<data_type_t>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const data_type_index data_type_index_;
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
int index_{-1};
|
||||||
|
utils::field_attributes attributes_;
|
||||||
|
null_option null_option_{null_option::NOT_NULL};
|
||||||
|
data_type_t type_{data_type_t::type_unknown};
|
||||||
|
any_type value_;
|
||||||
|
std::string ref_table_;
|
||||||
|
std::string ref_column_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User defined literal to have a shortcut creating a column object
|
||||||
|
* @param name Name of the column
|
||||||
|
* @param len Length of the column name
|
||||||
|
* @return A column object with given name
|
||||||
|
*/
|
||||||
|
column_definition make_column(const std::string &name, data_type_t type, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL);
|
||||||
|
|
||||||
|
template < typename Type >
|
||||||
|
column_definition make_column(const std::string &name, utils::field_attributes attr = utils::null_attributes, null_option null_opt = null_option::NOT_NULL)
|
||||||
|
{
|
||||||
|
return make_column(name, data_type_traits<Type>::builtin_type(0), attr, null_opt);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
column_definition make_column<std::string>(const std::string &name, utils::field_attributes attr, null_option null_opt);
|
||||||
|
|
||||||
|
template < typename Type >
|
||||||
|
column_definition make_pk_column(const std::string &name, size_t size = 0)
|
||||||
|
{
|
||||||
|
return make_column<Type>(name, { size, utils::constraints::PRIMARY_KEY });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
column_definition make_pk_column<std::string>(const std::string &name, size_t size);
|
||||||
|
|
||||||
|
template < typename Type >
|
||||||
|
column_definition make_fk_column(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column)
|
||||||
|
{
|
||||||
|
return {name, data_type_traits<Type>::builtin_type(size), ref_table, ref_column, { size, utils::constraints::FOREIGN_KEY }};
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename Type >
|
||||||
|
[[maybe_unused]] column_definition make_fk_column(const std::string &name, const std::string &ref_table, const std::string &ref_column)
|
||||||
|
{
|
||||||
|
return {name, data_type_traits<Type>::builtin_type(0), 0, ref_table, ref_column, { 0, utils::constraints::FOREIGN_KEY }, null_option::NOT_NULL};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
column_definition make_fk_column<std::string>(const std::string &name, size_t size, const std::string &ref_table, const std::string &ref_column);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_COLUMN_DEFINITION_HPP
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#ifndef QUERY_COLUMN_DEFINITION_GENERATOR_HPP
|
||||||
|
#define QUERY_COLUMN_DEFINITION_GENERATOR_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/column_definition.hpp"
|
||||||
|
#include "matador/sql/data_type_traits.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/field_attributes.hpp"
|
||||||
|
#include "matador/utils/foreign_attributes.hpp"
|
||||||
|
|
||||||
|
#include <typeindex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class schema;
|
||||||
|
|
||||||
|
class fk_column_generator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fk_column_generator() = default;
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
column_definition generate(const char *id, Type &x, const std::string &ref_table, const std::string &ref_column)
|
||||||
|
{
|
||||||
|
utils::access::process(*this, x);
|
||||||
|
return column_definition{id, type_, 0, ref_table, ref_column, {utils::constraints::FOREIGN_KEY }, null_option::NOT_NULL};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ValueType>
|
||||||
|
void on_primary_key(const char *, ValueType &/*pk*/, typename std::enable_if<std::is_integral<ValueType>::value && !std::is_same<bool, ValueType>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
type_ = data_type_traits<ValueType>::builtin_type(0);
|
||||||
|
}
|
||||||
|
void on_primary_key(const char * /*id*/, std::string &/*pk*/, size_t size);
|
||||||
|
void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {}
|
||||||
|
template < class Type >
|
||||||
|
void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
void on_attribute(const char * /*id*/, char * /*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
data_type_t type_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class column_definition_generator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
column_definition_generator(std::vector<column_definition> &columns, const schema &repo);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~column_definition_generator() = default;
|
||||||
|
|
||||||
|
template < class Type >
|
||||||
|
static std::vector<column_definition> generate(const schema &repo)
|
||||||
|
{
|
||||||
|
std::vector<column_definition> columns;
|
||||||
|
column_definition_generator gen(columns, repo);
|
||||||
|
Type obj;
|
||||||
|
matador::utils::access::process(gen, obj);
|
||||||
|
return std::move(columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < class V >
|
||||||
|
void on_primary_key(const char *, V &x, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0);
|
||||||
|
void on_primary_key(const char *id, std::string &pk, size_t size);
|
||||||
|
void on_revision(const char *id, unsigned long long &rev);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, Type &x, const utils::field_attributes &attr = utils::null_attributes);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, std::optional<Type> &x, const utils::field_attributes &attr = utils::null_attributes);
|
||||||
|
|
||||||
|
template<class Pointer>
|
||||||
|
void on_belongs_to(const char *id, Pointer &x, const utils::foreign_attributes &/*attr*/)
|
||||||
|
{
|
||||||
|
const auto [ref_table, ref_column] = determine_foreign_ref(std::type_index(typeid(typename Pointer::value_type)));
|
||||||
|
columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column));
|
||||||
|
}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_has_one(const char *id, Pointer &x, const utils::foreign_attributes &/*attr*/)
|
||||||
|
{
|
||||||
|
const auto [ref_table, ref_column] = determine_foreign_ref(std::type_index(typeid(typename Pointer::value_type)));
|
||||||
|
columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column));
|
||||||
|
}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::pair<std::string, std::string> determine_foreign_ref(const std::type_index &ti);
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t index_ = 0;
|
||||||
|
std::vector<column_definition> &columns_;
|
||||||
|
const schema &repo_;
|
||||||
|
|
||||||
|
fk_column_generator fk_column_generator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename V>
|
||||||
|
void column_definition_generator::on_primary_key(const char *id, V &x, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type*)
|
||||||
|
{
|
||||||
|
on_attribute(id, x, { utils::constraints::PRIMARY_KEY });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void column_definition_generator::on_attribute(const char *id, Type &x, const utils::field_attributes &attr)
|
||||||
|
{
|
||||||
|
columns_.emplace_back(id, x, attr, null_option::NOT_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void column_definition_generator::on_attribute(const char *id, std::optional<Type> &x, const utils::field_attributes &attr)
|
||||||
|
{
|
||||||
|
columns_.emplace_back(id, data_type_traits<Type>::builtin_type(attr.size()), attr, null_option::NULLABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_COLUMN_DEFINITION_GENERATOR_HPP
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#ifndef QUERY_COLUMN_GENERATOR_HPP
|
||||||
|
#define QUERY_COLUMN_GENERATOR_HPP
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/field_attributes.hpp"
|
||||||
|
#include "matador/utils/foreign_attributes.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/column.hpp"
|
||||||
|
#include "matador/sql/schema.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class column_generator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
column_generator(std::vector<column> &column_infos,
|
||||||
|
const sql::schema &ts,
|
||||||
|
const std::string &table_name,
|
||||||
|
bool force_lazy);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~column_generator() = default;
|
||||||
|
|
||||||
|
template < class Type >
|
||||||
|
static std::vector<column> generate(const sql::schema &ts, bool force_lazy = false)
|
||||||
|
{
|
||||||
|
const auto info = ts.info<Type>();
|
||||||
|
if (!info) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<column> columns;
|
||||||
|
column_generator gen(columns, ts, info.value().name, force_lazy);
|
||||||
|
Type obj;
|
||||||
|
matador::utils::access::process(gen, obj);
|
||||||
|
return std::move(columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < class V >
|
||||||
|
void on_primary_key(const char *id, V &, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
push(id);
|
||||||
|
}
|
||||||
|
void on_primary_key(const char *id, std::string &, size_t);
|
||||||
|
void on_revision(const char *id, unsigned long long &/*rev*/);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||||
|
{
|
||||||
|
push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Pointer>
|
||||||
|
void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
||||||
|
push(id);
|
||||||
|
} else {
|
||||||
|
const auto info = table_schema_.info<typename Pointer::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table_name_stack_.push(info.value().name);
|
||||||
|
typename Pointer::value_type obj;
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
table_name_stack_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
||||||
|
push(id);
|
||||||
|
} else {
|
||||||
|
const auto info = table_schema_.info<typename Pointer::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table_name_stack_.push(info.value().name);
|
||||||
|
typename Pointer::value_type obj;
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
table_name_stack_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto info = table_schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table_name_stack_.push(info.value().name);
|
||||||
|
typename ContainerType::value_type::value_type obj;
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
table_name_stack_.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void push(const std::string &column_name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::stack<std::string> table_name_stack_;
|
||||||
|
std::vector<column> &column_infos_;
|
||||||
|
const sql::schema &table_schema_;
|
||||||
|
int column_index{0};
|
||||||
|
bool force_lazy_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_COLUMN_GENERATOR_HPP
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
#ifndef QUERY_CONDITION_HPP
|
#ifndef QUERY_CONDITION_HPP
|
||||||
#define QUERY_CONDITION_HPP
|
#define QUERY_CONDITION_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/any_type_to_string_visitor.hpp"
|
||||||
|
#include "matador/sql/query_result.hpp"
|
||||||
#include "matador/sql/basic_condition.hpp"
|
#include "matador/sql/basic_condition.hpp"
|
||||||
#include "matador/sql/dialect.hpp"
|
#include "matador/sql/dialect.hpp"
|
||||||
|
#include "matador/sql/placeholder.hpp"
|
||||||
|
#include "matador/sql/query_context.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
struct placeholder {};
|
|
||||||
|
|
||||||
const placeholder _;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class condition
|
* @class condition
|
||||||
* @brief Represents a sql query condition
|
* @brief Represents a sql query condition
|
||||||
@@ -27,7 +28,7 @@ const placeholder _;
|
|||||||
|
|
||||||
/// @cond MATADOR_DEV
|
/// @cond MATADOR_DEV
|
||||||
|
|
||||||
class query;
|
class query_select;
|
||||||
|
|
||||||
template<class L, class R, class Enabled = void>
|
template<class L, class R, class Enabled = void>
|
||||||
class condition;
|
class condition;
|
||||||
@@ -40,12 +41,13 @@ public:
|
|||||||
|
|
||||||
placeholder value;
|
placeholder value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d) const override;
|
std::string evaluate(const dialect &d, query_context &query) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
class condition<column, T, typename std::enable_if<
|
class condition<column, T, typename std::enable_if<
|
||||||
std::is_scalar<T>::value &&
|
std::is_scalar<T>::value &&
|
||||||
|
!std::is_enum<T>::value &&
|
||||||
!std::is_same<std::string, T>::value &&
|
!std::is_same<std::string, T>::value &&
|
||||||
!std::is_same<const char*, T>::value>::type> : public basic_column_condition
|
!std::is_same<const char*, T>::value>::type> : public basic_column_condition
|
||||||
{
|
{
|
||||||
@@ -57,10 +59,10 @@ public:
|
|||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
d.add_host_var(field_.name());
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_.name()) + " " + operand + " " + std::to_string(value);
|
return d.prepare_identifier(field_) + " " + operand + " " + std::to_string(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,18 +79,40 @@ public:
|
|||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
d.add_host_var(field_.name());
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_.name()) + " " + operand + " '" + value + "'";
|
return d.prepare_identifier(field_) + " " + operand + " '" + value + "'";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
class condition<T, column, typename std::enable_if<
|
class condition<column, T, std::enable_if_t<std::is_enum_v<T>>> final : public basic_column_condition
|
||||||
std::is_scalar<T>::value &&
|
{
|
||||||
!std::is_same<std::string, T>::value &&
|
public:
|
||||||
!std::is_same<const char*, T>::value>::type> : public basic_column_condition
|
condition(const column &fld, basic_condition::operand_t op, T val)
|
||||||
|
: basic_column_condition(fld, op)
|
||||||
|
, value(val)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
T value;
|
||||||
|
|
||||||
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
|
{
|
||||||
|
auto at = data_type_traits<T>::create_value(value);
|
||||||
|
any_type_to_string_visitor value_to_string(d, query);
|
||||||
|
std::visit(value_to_string, at);
|
||||||
|
return value_to_string.result + " " + operand + " " + d.prepare_identifier(field_);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class condition<T, column, std::enable_if_t<
|
||||||
|
std::is_scalar_v<T> &&
|
||||||
|
!std::is_same_v<std::string, T> &&
|
||||||
|
!std::is_same_v<const char*, T>>> final : public basic_column_condition
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
condition(T val, basic_condition::operand_t op, const column &fld)
|
condition(T val, basic_condition::operand_t op, const column &fld)
|
||||||
@@ -98,9 +122,9 @@ public:
|
|||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return std::to_string(value) + " " + operand + " " + d.prepare_identifier(field_.name());
|
return std::to_string(value) + " " + operand + " " + d.prepare_identifier(field_);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,9 +141,9 @@ public:
|
|||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return "'" + std::to_string(value) + "' " + operand + " " + d.prepare_identifier(field_.name());
|
return "'" + std::to_string(value) + "' " + operand + " " + d.prepare_identifier(field_);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,14 +183,13 @@ public:
|
|||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return A condition IN part of the query
|
* @return A condition IN part of the query
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d) const override {
|
std::string evaluate(const dialect &d, query_context &query) const override {
|
||||||
auto count = size();
|
auto count = size();
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
d.add_host_var(field_.name());
|
query.bind_vars.emplace_back(field_.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string result = d.prepare_identifier(field_) + " IN (";
|
||||||
std::string result = d.prepare_identifier(field_.name()) + " IN (";
|
|
||||||
if (args_.size() < 2) {
|
if (args_.size() < 2) {
|
||||||
for (const auto &val : args_) {
|
for (const auto &val : args_) {
|
||||||
result.append(std::to_string(val));
|
result.append(std::to_string(val));
|
||||||
@@ -205,7 +228,7 @@ private:
|
|||||||
* @endcode
|
* @endcode
|
||||||
*/
|
*/
|
||||||
template <>
|
template <>
|
||||||
class condition<column, query> : public basic_column_condition
|
class condition<column, query_context> : public basic_column_condition
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +242,7 @@ public:
|
|||||||
* @param op Operand of the condition
|
* @param op Operand of the condition
|
||||||
* @param q The query to be evaluated to the IN arguments
|
* @param q The query to be evaluated to the IN arguments
|
||||||
*/
|
*/
|
||||||
condition(column col, basic_condition::operand_t op, query &q);
|
condition(column col, basic_condition::operand_t op, const query_context &q);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Evaluates the condition
|
* @brief Evaluates the condition
|
||||||
@@ -230,10 +253,10 @@ public:
|
|||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return A condition IN part of the query
|
* @return A condition IN part of the query
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d) const override;
|
std::string evaluate(const dialect &d, query_context &query) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
query &query_;
|
query_context query_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,10 +288,10 @@ public:
|
|||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return A condition BETWEEN part of the query
|
* @return A condition BETWEEN part of the query
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d) const override {
|
std::string evaluate(const dialect &d, query_context &query) const override {
|
||||||
d.add_host_var(field_.name());
|
query.bind_vars.emplace_back(field_.name);
|
||||||
d.add_host_var(field_.name());
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_.name()) + " BETWEEN " + std::to_string(range_.first) + " AND " + std::to_string(range_.second);
|
return d.prepare_identifier(field_) + " BETWEEN " + std::to_string(range_.first) + " AND " + std::to_string(range_.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -307,11 +330,11 @@ public:
|
|||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return The evaluated string based on the compile type
|
* @return The evaluated string based on the compile type
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
// ensure the numbering order for host vars
|
// ensure the numbering order for host vars
|
||||||
auto cl = left.evaluate(d);
|
auto cl = left.evaluate(d, query);
|
||||||
auto cr = right.evaluate(d);
|
auto cr = right.evaluate(d, query);
|
||||||
if (operand == basic_condition::operand_t::AND) {
|
if (operand == basic_condition::operand_t::AND) {
|
||||||
return "(" + cl + " " + basic_condition::operands[operand] + " " + cr + ")";
|
return "(" + cl + " " + basic_condition::operands[operand] + " " + cr + ")";
|
||||||
} else {
|
} else {
|
||||||
@@ -350,7 +373,7 @@ public:
|
|||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return The evaluated string based on the compile type
|
* @return The evaluated string based on the compile type
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return operand + " (" + cond.evaluate(d) + ")";
|
return operand + " (" + cond.evaluate(d) + ")";
|
||||||
}
|
}
|
||||||
@@ -360,6 +383,28 @@ private:
|
|||||||
std::string operand;
|
std::string operand;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class condition<column, column> : public basic_column_condition
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
condition(const column &a, basic_condition::operand_t op, column b)
|
||||||
|
: basic_column_condition(a, op)
|
||||||
|
, other_column_(std::move(b)) {}
|
||||||
|
/**
|
||||||
|
* @brief Evaluates the condition
|
||||||
|
*
|
||||||
|
* @param d The d used to evaluate
|
||||||
|
* @return The evaluated string based on the compile type
|
||||||
|
*/
|
||||||
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
|
{
|
||||||
|
return d.prepare_identifier(field_) + " " + operand + " " + d.prepare_identifier(other_column_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
column other_column_;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file condition.hpp
|
* @file condition.hpp
|
||||||
* @brief Contains functions to create query conditions
|
* @brief Contains functions to create query conditions
|
||||||
@@ -397,7 +442,8 @@ condition<column, std::initializer_list<V>> in(const column &col, std::initializ
|
|||||||
* @param q The query to be executes as sub select
|
* @param q The query to be executes as sub select
|
||||||
* @return The condition object
|
* @return The condition object
|
||||||
*/
|
*/
|
||||||
condition<column, query> in(const column &col, query &&q);
|
condition<column, query_context> in(const column &col, const query_context &q);
|
||||||
|
condition<column, query_context> in(const column &col, const query_select &q);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Creates a between condition.
|
* @brief Creates a between condition.
|
||||||
@@ -446,6 +492,8 @@ condition<column, T> operator==(const column &col, T val)
|
|||||||
return condition<column, T>(col, basic_condition::operand_t::EQUAL, val);
|
return condition<column, T>(col, basic_condition::operand_t::EQUAL, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
condition<column, column> operator==(const column &a, const column &b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Condition equality method for a column and a query
|
* @brief Condition equality method for a column and a query
|
||||||
*
|
*
|
||||||
@@ -456,7 +504,7 @@ condition<column, T> operator==(const column &col, T val)
|
|||||||
* @param q The query to compare with
|
* @param q The query to compare with
|
||||||
* @return The condition object representing the equality operation
|
* @return The condition object representing the equality operation
|
||||||
*/
|
*/
|
||||||
condition<column, query> equals(const column &col, query &q);
|
condition<column, query_context> equals(const column &col, query_context &q);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Condition inequality operator for a column and a value
|
* @brief Condition inequality operator for a column and a value
|
||||||
|
|||||||
@@ -2,21 +2,30 @@
|
|||||||
#define QUERY_CONNECTION_HPP
|
#define QUERY_CONNECTION_HPP
|
||||||
|
|
||||||
#include "matador/sql/connection_info.hpp"
|
#include "matador/sql/connection_info.hpp"
|
||||||
|
#include "matador/sql/connection_impl.hpp"
|
||||||
#include "matador/sql/dialect.hpp"
|
#include "matador/sql/dialect.hpp"
|
||||||
|
#include "matador/sql/query.hpp"
|
||||||
|
#include "matador/sql/query_context.hpp"
|
||||||
#include "matador/sql/query_result.hpp"
|
#include "matador/sql/query_result.hpp"
|
||||||
#include "matador/sql/record.hpp"
|
#include "matador/sql/record.hpp"
|
||||||
|
#include "matador/sql/statement.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/logger.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
class connection_impl;
|
class schema;
|
||||||
|
|
||||||
class connection
|
class connection
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit connection(connection_info info);
|
explicit connection(connection_info info);
|
||||||
explicit connection(const std::string& dns);
|
explicit connection(const std::string& dns);
|
||||||
|
connection(const connection &x);
|
||||||
|
connection& operator=(const connection &x);
|
||||||
|
connection(connection &&x) noexcept = default;
|
||||||
~connection();
|
~connection();
|
||||||
|
|
||||||
void open();
|
void open();
|
||||||
@@ -24,13 +33,25 @@ public:
|
|||||||
[[nodiscard]] bool is_open() const;
|
[[nodiscard]] bool is_open() const;
|
||||||
[[nodiscard]] const connection_info& info() const;
|
[[nodiscard]] const connection_info& info() const;
|
||||||
|
|
||||||
query_result<record> fetch(const std::string &sql);
|
[[nodiscard]] std::vector<sql::column_definition> describe(const std::string &table_name) const;
|
||||||
std::pair<size_t, std::string> execute(const std::string &sql);
|
[[nodiscard]] bool exists(const std::string &schema_name, const std::string &table_name) const;
|
||||||
|
[[nodiscard]] bool exists(const std::string &table_name) const;
|
||||||
|
|
||||||
|
sql::query query(const sql::schema &schema) const;
|
||||||
|
|
||||||
|
query_result<record> fetch(const query_context &q) const;
|
||||||
|
[[nodiscard]] std::unique_ptr<query_result_impl> fetch(const std::string &sql) const;
|
||||||
|
[[nodiscard]] size_t execute(const std::string &sql) const;
|
||||||
|
|
||||||
|
statement prepare(query_context &&query) const;
|
||||||
|
|
||||||
|
const class dialect& dialect() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const connection_info connection_info_;
|
connection_info connection_info_;
|
||||||
bool is_open_{false};
|
std::unique_ptr<connection_impl> connection_;
|
||||||
connection_impl *connection_{};
|
utils::logger logger_;
|
||||||
|
const class dialect &dialect_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,17 @@
|
|||||||
#define QUERY_CONNECTION_IMPL_HPP
|
#define QUERY_CONNECTION_IMPL_HPP
|
||||||
|
|
||||||
#include "matador/sql/connection_info.hpp"
|
#include "matador/sql/connection_info.hpp"
|
||||||
|
#include "matador/sql/query_result_impl.hpp"
|
||||||
|
#include "matador/sql/query_context.hpp"
|
||||||
|
#include "matador/sql/record.hpp"
|
||||||
|
#include "matador/sql/statement_impl.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class query_result_impl;
|
||||||
|
|
||||||
class connection_impl
|
class connection_impl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -14,8 +22,12 @@ public:
|
|||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual bool is_open() = 0;
|
virtual bool is_open() = 0;
|
||||||
|
|
||||||
virtual void execute(const std::string &stmt) = 0;
|
virtual size_t execute(const std::string &stmt) = 0;
|
||||||
virtual void prepare(const std::string &stmt) = 0;
|
virtual std::unique_ptr<query_result_impl> fetch(const std::string &stmt) = 0;
|
||||||
|
virtual std::unique_ptr<statement_impl> prepare(query_context context) = 0;
|
||||||
|
|
||||||
|
virtual std::vector<sql::column_definition> describe(const std::string &table) = 0;
|
||||||
|
virtual bool exists(const std::string &schema_name, const std::string &table_name) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit connection_impl(const connection_info &info);
|
explicit connection_impl(const connection_info &info);
|
||||||
|
|||||||
@@ -1,75 +1,138 @@
|
|||||||
#ifndef QUERY_CONNECTION_POOL_HPP
|
#ifndef QUERY_CONNECTION_POOL_HPP
|
||||||
#define QUERY_CONNECTION_POOL_HPP
|
#define QUERY_CONNECTION_POOL_HPP
|
||||||
|
|
||||||
#include <utility>
|
#include "matador/sql/connection_info.hpp"
|
||||||
#include <queue>
|
|
||||||
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_set>
|
#include <optional>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
template < class Connection >
|
template < class Connection >
|
||||||
class connection_pool;
|
class connection_pool;
|
||||||
|
|
||||||
|
template < class Connection >
|
||||||
|
using IdConnection = std::pair<size_t, Connection>;
|
||||||
|
|
||||||
template < class Connection >
|
template < class Connection >
|
||||||
class connection_ptr
|
class connection_ptr
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
connection_ptr(Connection *c, connection_pool<Connection> &pool)
|
connection_ptr(IdConnection<Connection> *c, connection_pool<Connection> *pool)
|
||||||
: connection_(c), pool_(pool) {}
|
: connection_(c), pool_(pool) {}
|
||||||
~connection_ptr();
|
~connection_ptr();
|
||||||
connection_ptr(const connection_ptr &) = delete;
|
connection_ptr(const connection_ptr &) = delete;
|
||||||
connection_ptr& operator=(const connection_ptr &) = delete;
|
connection_ptr& operator=(const connection_ptr &) = delete;
|
||||||
connection_ptr(connection_ptr &&) noexcept = default;
|
connection_ptr(connection_ptr &&x) noexcept
|
||||||
connection_ptr& operator=(connection_ptr &&) noexcept = default;
|
: connection_(x.connection_)
|
||||||
|
, pool_(x.pool_)
|
||||||
|
{
|
||||||
|
x.connection_ = nullptr;
|
||||||
|
x.pool_ = nullptr;
|
||||||
|
}
|
||||||
|
connection_ptr& operator=(connection_ptr &&x) noexcept
|
||||||
|
{
|
||||||
|
if (this == &x) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
Connection* operator->() { return connection_; }
|
std::swap(connection_, x.connection_);
|
||||||
Connection& operator*() { return *connection_; }
|
std::swap(pool_, x.pool_);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection* operator->() { return &connection_->second; }
|
||||||
|
Connection& operator*() { return connection_->second; }
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<size_t> id() const
|
||||||
|
{
|
||||||
|
if (connection_) {
|
||||||
|
return connection_->first;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
[[nodiscard]] bool valid() const { return connection_ != nullptr; }
|
[[nodiscard]] bool valid() const { return connection_ != nullptr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class connection_pool<Connection>;
|
friend class connection_pool<Connection>;
|
||||||
|
|
||||||
Connection *connection_;
|
IdConnection<Connection> *connection_{};
|
||||||
connection_pool<Connection> &pool_;
|
connection_pool<Connection> *pool_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template < class Connection >
|
template < class Connection >
|
||||||
class connection_pool
|
class connection_pool
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
connection_pool(const std::string &dns, unsigned int count)
|
using connection_pointer = connection_ptr<Connection>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
connection_pool(const std::string &dns, size_t count)
|
||||||
: info_(connection_info::parse(dns)) {
|
: info_(connection_info::parse(dns)) {
|
||||||
connection_repo_.reserve(count);
|
connection_repo_.reserve(count);
|
||||||
for (auto i = 0U; i < count; ++i) {
|
while (count) {
|
||||||
connection_repo_.emplace_back(info_);
|
connection_repo_.emplace_back(count, info_);
|
||||||
idle_connections_.push(&connection_repo_.back());
|
auto &conn = connection_repo_.back();
|
||||||
idle_connections_.back()->open();
|
idle_connections_.emplace(conn.first, &conn);
|
||||||
|
conn.second.open();
|
||||||
|
--count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connection_ptr<Connection> acquire() {
|
connection_pointer acquire() {
|
||||||
std::lock_guard<std::mutex> guard(mutex_);
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
while (idle_connections_.empty()) {
|
||||||
|
cv.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_next_connection();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection_pointer try_acquire() {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
if (idle_connections_.empty()) {
|
if (idle_connections_.empty()) {
|
||||||
return {nullptr, *this};
|
return {nullptr, this};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ptr = idle_connections_.front();
|
return get_next_connection();
|
||||||
idle_connections_.pop();
|
|
||||||
inuse_connections_.insert(ptr);
|
|
||||||
return { ptr, *this };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void release(Connection *c) {
|
connection_pointer acquire(size_t id) {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
pointer next_connection{nullptr};
|
||||||
|
auto try_count{0};
|
||||||
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (auto it = idle_connections_.find(id); it != idle_connections_.end()) {
|
||||||
|
next_connection = it->second;
|
||||||
|
auto node = idle_connections_.extract(it);
|
||||||
|
inuse_connections_.insert(std::move(node));
|
||||||
|
} else {
|
||||||
|
lock.unlock();
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
} while(try_count++ < 5);
|
||||||
|
|
||||||
|
return {next_connection, this};
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(IdConnection<Connection> *c) {
|
||||||
if (c == nullptr) {
|
if (c == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::lock_guard<std::mutex> guard(mutex_);
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
if (auto it = inuse_connections_.find(c->first); it != inuse_connections_.end()) {
|
||||||
if (inuse_connections_.erase(c) > 0) {
|
auto node = inuse_connections_.extract(it);
|
||||||
idle_connections_.push(c);
|
idle_connections_.insert(std::move(node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,21 +154,33 @@ public:
|
|||||||
const connection_info &info() const {
|
const connection_info &info() const {
|
||||||
return info_;
|
return info_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
connection_pointer get_next_connection() {
|
||||||
|
pointer next_connection{nullptr};
|
||||||
|
for (auto &item : idle_connections_) {
|
||||||
|
next_connection = item.second;
|
||||||
|
auto node = idle_connections_.extract(item.first);
|
||||||
|
inuse_connections_.insert(std::move(node));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {next_connection, this};
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
std::vector<Connection> connection_repo_;
|
std::condition_variable cv;
|
||||||
using pointer = Connection*;
|
std::vector<IdConnection<Connection>> connection_repo_;
|
||||||
using connections = std::queue<pointer>;
|
using pointer = IdConnection<Connection>*;
|
||||||
using connection_set = std::unordered_set<pointer>;
|
using connection_map = std::unordered_map<size_t, pointer>;
|
||||||
connections idle_connections_;
|
connection_map inuse_connections_;
|
||||||
connection_set inuse_connections_;
|
connection_map idle_connections_;
|
||||||
|
|
||||||
const connection_info info_;
|
const connection_info info_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Connection>
|
template<class Connection>
|
||||||
connection_ptr<Connection>::~connection_ptr() {
|
connection_ptr<Connection>::~connection_ptr() {
|
||||||
pool_.release(connection_);
|
pool_->release(connection_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#ifndef QUERY_CONVERT_HPP
|
||||||
|
#define QUERY_CONVERT_HPP
|
||||||
|
|
||||||
|
#include "matador/utils/types.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <charconv>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
template < typename DestType, typename SourceType >
|
||||||
|
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_same<DestType, SourceType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType, typename SourceType >
|
||||||
|
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_integral<DestType>::value && std::is_arithmetic<SourceType>::value && !std::is_same<DestType, SourceType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = static_cast<DestType>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType, typename SourceType >
|
||||||
|
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_floating_point<DestType>::value && std::is_arithmetic<SourceType>::value && !std::is_same<DestType, SourceType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = static_cast<DestType>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert(std::string &dest, bool source);
|
||||||
|
|
||||||
|
template < typename SourceType >
|
||||||
|
void convert(std::string &dest, SourceType source, typename std::enable_if<std::is_integral<SourceType>::value && !std::is_same<bool, SourceType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
std::array<char, 128> buffer{};
|
||||||
|
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), source, 10);
|
||||||
|
if (ec == std::errc{}) {
|
||||||
|
dest.assign(buffer.data(), ptr);
|
||||||
|
} else {
|
||||||
|
throw std::logic_error("couldn't convert value to std::string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename SourceType >
|
||||||
|
void convert(std::string &dest, SourceType source, typename std::enable_if<std::is_floating_point<SourceType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
std::array<char, 128> buffer{};
|
||||||
|
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), source);
|
||||||
|
if (ec == std::errc{}) {
|
||||||
|
dest.assign(buffer.data(), ptr);
|
||||||
|
} else {
|
||||||
|
throw std::logic_error("couldn't convert value to std::string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename SourceType >
|
||||||
|
void convert(utils::blob &dest, SourceType source, typename std::enable_if<!std::is_same<utils::blob, SourceType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
throw std::logic_error("couldn't convert value to matador::utils::blob");
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert(std::string &dest, const char* source);
|
||||||
|
|
||||||
|
unsigned long long to_unsigned_long_long(const char *source);
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const std::string &source, typename std::enable_if<std::is_integral<DestType>::value && std::is_unsigned<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = to_unsigned_long_long(source.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const char *source, typename std::enable_if<std::is_integral<DestType>::value && std::is_unsigned<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = to_unsigned_long_long(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
long long to_long_long(const char *source);
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const std::string &source, typename std::enable_if<std::is_integral<DestType>::value && std::is_signed<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = to_long_long(source.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const char *source, typename std::enable_if<std::is_integral<DestType>::value && std::is_signed<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = to_long_long(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
long double to_double(const char *source);
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const std::string &source, typename std::enable_if<std::is_floating_point<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = to_double(source.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const char *source, typename std::enable_if<std::is_floating_point<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = to_double(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, bool source, typename std::enable_if<std::is_floating_point<DestType>::value>::type* = nullptr)
|
||||||
|
{
|
||||||
|
dest = static_cast<DestType>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename DestType >
|
||||||
|
void convert(DestType &dest, const utils::blob &data)
|
||||||
|
{
|
||||||
|
throw std::logic_error("couldn't convert matador::utils::blob into destination type");
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert(utils::blob &dest, const utils::blob &data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_CONVERT_HPP
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
#ifndef QUERY_DATA_TYPE_TRAITS_HPP
|
||||||
|
#define QUERY_DATA_TYPE_TRAITS_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/any_type.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/types.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class query_result_reader;
|
||||||
|
class parameter_binder;
|
||||||
|
class result_parameter_binder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration type of all supported builtin data types
|
||||||
|
*/
|
||||||
|
enum class data_type_t : uint8_t {
|
||||||
|
type_char = 0, /*!< Data type char */
|
||||||
|
type_short, /*!< Data type short */
|
||||||
|
type_int, /*!< Data type int */
|
||||||
|
type_long, /*!< Data type long */
|
||||||
|
type_long_long, /*!< Data type long long */
|
||||||
|
type_unsigned_char, /*!< Data type unsigned char */
|
||||||
|
type_unsigned_short, /*!< Data type unsigned short */
|
||||||
|
type_unsigned_int, /*!< Data type unsigned int */
|
||||||
|
type_unsigned_long, /*!< Data type unsigned long */
|
||||||
|
type_unsigned_long_long, /*!< Data type unsigned long long */
|
||||||
|
type_float, /*!< Data type float */
|
||||||
|
type_double, /*!< Data type double */
|
||||||
|
type_bool, /*!< Data type bool */
|
||||||
|
type_char_pointer, /*!< Data type character pointer */
|
||||||
|
type_varchar, /*!< Data type varchar */
|
||||||
|
type_text, /*!< Data type text */
|
||||||
|
type_date, /*!< Data type date */
|
||||||
|
type_time, /*!< Data type time */
|
||||||
|
type_blob, /*!< Data type blob */
|
||||||
|
type_null, /*!< Data type null */
|
||||||
|
type_unknown /*!< Data type unknown */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @tparam T The type of the traits
|
||||||
|
* @brief Type traits for database types
|
||||||
|
*
|
||||||
|
* This class is used to determine and
|
||||||
|
* provide the correct size information
|
||||||
|
* for a data type
|
||||||
|
*/
|
||||||
|
template < class Type, class Enable = void >
|
||||||
|
struct data_type_traits;
|
||||||
|
|
||||||
|
/// @cond MATADOR_DEV
|
||||||
|
template <> struct data_type_traits<nullptr_t, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_null; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, nullptr_t &/*value*/);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, nullptr_t &/*value*/);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, nullptr_t &/*value*/);
|
||||||
|
inline static any_type create_value(const char &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<char, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_char; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, char &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, char &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, char &value);
|
||||||
|
inline static any_type create_value(const char &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<short, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_short; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, short &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, short &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, short &value);
|
||||||
|
inline static any_type create_value(const short &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<int, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_int; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, int &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, int &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, int &value);
|
||||||
|
inline static any_type create_value(const int &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<long, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_long; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, long &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, long &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, long &value);
|
||||||
|
inline static any_type create_value(const long &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<long long, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_long_long; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, long long &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, long long &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, long long &value);
|
||||||
|
inline static any_type create_value(const long long &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<unsigned char, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_char; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned char &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, unsigned char &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned char &value);
|
||||||
|
inline static any_type create_value(const unsigned char &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<unsigned short, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_short; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned short &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, unsigned short &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned short &value);
|
||||||
|
inline static any_type create_value(const unsigned short &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<unsigned int, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_int; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned int &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, unsigned int &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned int &value);
|
||||||
|
inline static any_type create_value(const unsigned int &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<unsigned long, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/ = 0) { return data_type_t::type_unsigned_long; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned long &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, unsigned long &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned long &value);
|
||||||
|
inline static any_type create_value(const unsigned long &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<unsigned long long, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_long_long; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned long long &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, unsigned long long &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned long long &value);
|
||||||
|
inline static any_type create_value(const unsigned long long &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<bool, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_bool; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, bool &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, bool &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, bool &value);
|
||||||
|
inline static any_type create_value(const bool &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<float, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_float; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, float &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, float &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, float &value);
|
||||||
|
inline static any_type create_value(const float &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<double, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_double; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, double &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, double &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, double &value);
|
||||||
|
inline static any_type create_value(const double &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<const char*, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_char_pointer; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, const char* value, size_t size);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, const char *value, size_t size = 0);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, const char *value, size_t size = 0);
|
||||||
|
inline static any_type create_value(const char *value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<char*, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_varchar; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, char *value, size_t size);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, char *value, size_t size = 0);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, char *value, size_t size = 0);
|
||||||
|
inline static any_type create_value(const char *value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<std::string, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_varchar; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, std::string &value, size_t size);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, std::string &value, size_t size = 0);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, std::string &value, size_t size = 0);
|
||||||
|
inline static any_type create_value(const std::string &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct data_type_traits<utils::blob, void>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_blob; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, utils::blob &value);
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, utils::blob &value);
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, utils::blob &value);
|
||||||
|
inline static any_type create_value(const utils::blob &value) { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//template <> struct data_type_traits<matador::date>
|
||||||
|
//{
|
||||||
|
// inline static database_type_t type(std::size_t /*size*/) { return database_type_t::type_date; }
|
||||||
|
// inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_date; }
|
||||||
|
// inline static unsigned long size() { return 255; }
|
||||||
|
// inline static const char* name() { return "matador::date"; }
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//template <> struct data_type_traits<matador::time>
|
||||||
|
//{
|
||||||
|
// inline static database_type_t type(std::size_t /*size*/) { return database_type_t::type_time; }
|
||||||
|
// inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_time; }
|
||||||
|
// inline static unsigned long size() { return 255; }
|
||||||
|
// inline static const char* name() { return "matador::time"; }
|
||||||
|
//};
|
||||||
|
|
||||||
|
template < typename EnumType >
|
||||||
|
struct data_type_traits<EnumType, std::enable_if_t<std::is_enum_v<EnumType>>>
|
||||||
|
{
|
||||||
|
inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_int; }
|
||||||
|
static void read_value(query_result_reader &reader, const char *id, size_t index, EnumType &value)
|
||||||
|
{
|
||||||
|
data_type_traits<int>::read_value(reader, id, index, reinterpret_cast<int&>(value));
|
||||||
|
}
|
||||||
|
static void bind_value(parameter_binder &binder, size_t index, EnumType &value)
|
||||||
|
{
|
||||||
|
data_type_traits<int>::bind_value(binder, index, static_cast<int&>(value));
|
||||||
|
}
|
||||||
|
static void bind_result_value(result_parameter_binder &binder, size_t index, EnumType &value)
|
||||||
|
{
|
||||||
|
data_type_traits<int>::bind_result_value(binder, index, static_cast<int&>(value));
|
||||||
|
}
|
||||||
|
static any_type create_value(const EnumType &value) {
|
||||||
|
return static_cast<int>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// @endcond
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_DATA_TYPE_TRAITS_HPP
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
#ifndef QUERY_DIALECT_HPP
|
#ifndef QUERY_DIALECT_HPP
|
||||||
#define QUERY_DIALECT_HPP
|
#define QUERY_DIALECT_HPP
|
||||||
|
|
||||||
#include "matador/sql/types.hpp"
|
#include "matador/sql/column.hpp"
|
||||||
|
#include "matador/sql/data_type_traits.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
class dialect
|
class dialect final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class token_t : uint8_t
|
enum class token_t : uint8_t
|
||||||
@@ -23,11 +25,15 @@ public:
|
|||||||
SELECT,
|
SELECT,
|
||||||
TABLE,
|
TABLE,
|
||||||
VALUES,
|
VALUES,
|
||||||
|
INSERT_VALUES,
|
||||||
COLUMNS,
|
COLUMNS,
|
||||||
COLUMN,
|
COLUMN,
|
||||||
FROM,
|
FROM,
|
||||||
|
JOIN,
|
||||||
|
ON,
|
||||||
INTO,
|
INTO,
|
||||||
WHERE,
|
WHERE,
|
||||||
|
WHERE_CLAUSE,
|
||||||
AND,
|
AND,
|
||||||
OR,
|
OR,
|
||||||
LIKE,
|
LIKE,
|
||||||
@@ -40,6 +46,7 @@ public:
|
|||||||
OFFSET,
|
OFFSET,
|
||||||
DISTINCT,
|
DISTINCT,
|
||||||
SET,
|
SET,
|
||||||
|
UPDATE_VALUES,
|
||||||
NOT_NULL,
|
NOT_NULL,
|
||||||
PRIMARY_KEY,
|
PRIMARY_KEY,
|
||||||
BEGIN,
|
BEGIN,
|
||||||
@@ -48,6 +55,8 @@ public:
|
|||||||
START_QUOTE,
|
START_QUOTE,
|
||||||
END_QUOTE,
|
END_QUOTE,
|
||||||
STRING_QUOTE,
|
STRING_QUOTE,
|
||||||
|
BEGIN_BINARY_DATA,
|
||||||
|
END_BINARY_DATA,
|
||||||
NONE
|
NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,15 +70,13 @@ public:
|
|||||||
|
|
||||||
using token_to_string_map = std::unordered_map<token_t, std::string>;
|
using token_to_string_map = std::unordered_map<token_t, std::string>;
|
||||||
using data_type_to_string_map = std::unordered_map<data_type_t, std::string>;
|
using data_type_to_string_map = std::unordered_map<data_type_t, std::string>;
|
||||||
|
using sql_func_to_string_map = std::unordered_map<sql_function_t, std::string>;
|
||||||
|
|
||||||
|
using next_placeholder_func = std::function<std::string(size_t)>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
dialect() = default;
|
[[nodiscard]] const std::string& token_at(token_t token) const;
|
||||||
dialect(const token_to_string_map &token_replace_map, const data_type_to_string_map &data_type_replace_map);
|
[[nodiscard]] const std::string& data_type_at(data_type_t type) const;
|
||||||
explicit dialect(const data_type_to_string_map &data_type_replace_map);
|
|
||||||
explicit dialect(const token_to_string_map &token_replace_map);
|
|
||||||
|
|
||||||
const std::string& token_at(token_t token) const;
|
|
||||||
const std::string& data_type_at(data_type_t type) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare sql dialect identifier for execution
|
* Prepare sql dialect identifier for execution
|
||||||
@@ -79,14 +86,15 @@ public:
|
|||||||
* @param str The identifier string to be prepared
|
* @param str The identifier string to be prepared
|
||||||
* @return The prepared string
|
* @return The prepared string
|
||||||
*/
|
*/
|
||||||
std::string prepare_identifier(const std::string &str) const;
|
[[nodiscard]] std::string prepare_identifier(const column &col) const;
|
||||||
|
[[nodiscard]] std::string prepare_identifier_string(const std::string &col) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare string literal
|
* Prepare string literal
|
||||||
*
|
*
|
||||||
* @param str String literal to be prepared
|
* @param str String literal to be prepared
|
||||||
*/
|
*/
|
||||||
std::string prepare_literal(const std::string &str) const;
|
[[nodiscard]] std::string prepare_literal(const std::string &str) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap identifier quotes around a sql identifier keyword
|
* Wrap identifier quotes around a sql identifier keyword
|
||||||
@@ -115,7 +123,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @return How the identifier quotes should be escaped
|
* @return How the identifier quotes should be escaped
|
||||||
*/
|
*/
|
||||||
virtual escape_identifier_t identifier_escape_type() const;
|
[[nodiscard]] virtual escape_identifier_t identifier_escape_type() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a next placeholder string. default is
|
* Generates a next placeholder string. default is
|
||||||
@@ -123,17 +131,24 @@ public:
|
|||||||
*
|
*
|
||||||
* @return Placeholder string
|
* @return Placeholder string
|
||||||
*/
|
*/
|
||||||
virtual std::string next_placeholder() const;
|
[[nodiscard]] std::string next_placeholder(const std::vector<std::string> &bind_vars) const;
|
||||||
|
|
||||||
void add_host_var(const std::string &host_var);
|
/**
|
||||||
void add_column(const std::string &column);
|
* Returns the default schema name.
|
||||||
|
*
|
||||||
const std::vector<std::string>& host_vars() const;
|
* @return The default schema name.
|
||||||
const std::vector<std::string>& columns() const;
|
*/
|
||||||
|
[[nodiscard]] std::string default_schema_name() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::string> host_vars_;
|
dialect() = default;
|
||||||
std::vector<std::string> columns_;
|
|
||||||
|
private:
|
||||||
|
friend class dialect_builder;
|
||||||
|
|
||||||
|
next_placeholder_func placeholder_func_ = [](size_t) { return "?"; };
|
||||||
|
|
||||||
|
std::string default_schema_name_;
|
||||||
|
|
||||||
token_to_string_map tokens_ {
|
token_to_string_map tokens_ {
|
||||||
{token_t::CREATE, "CREATE"},
|
{token_t::CREATE, "CREATE"},
|
||||||
@@ -148,6 +163,8 @@ private:
|
|||||||
{token_t::COLUMNS, "COLUMNS"},
|
{token_t::COLUMNS, "COLUMNS"},
|
||||||
{token_t::COLUMN, "COLUMN"},
|
{token_t::COLUMN, "COLUMN"},
|
||||||
{token_t::FROM, "FROM"},
|
{token_t::FROM, "FROM"},
|
||||||
|
{token_t::JOIN, "INNER JOIN"},
|
||||||
|
{token_t::ON, "ON"},
|
||||||
{token_t::WHERE, "WHERE"},
|
{token_t::WHERE, "WHERE"},
|
||||||
{token_t::AND, "AND"},
|
{token_t::AND, "AND"},
|
||||||
{token_t::OR, "OR"},
|
{token_t::OR, "OR"},
|
||||||
@@ -170,6 +187,8 @@ private:
|
|||||||
{token_t::START_QUOTE, "\""},
|
{token_t::START_QUOTE, "\""},
|
||||||
{token_t::END_QUOTE, "\""},
|
{token_t::END_QUOTE, "\""},
|
||||||
{token_t::STRING_QUOTE, "'"},
|
{token_t::STRING_QUOTE, "'"},
|
||||||
|
{token_t::BEGIN_BINARY_DATA, "X'"},
|
||||||
|
{token_t::END_BINARY_DATA, "'"},
|
||||||
{token_t::NONE, ""}
|
{token_t::NONE, ""}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -196,6 +215,15 @@ private:
|
|||||||
{data_type_t::type_null, "NULL"},
|
{data_type_t::type_null, "NULL"},
|
||||||
{data_type_t::type_unknown, "UNKNOWN"}
|
{data_type_t::type_unknown, "UNKNOWN"}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sql_func_to_string_map sql_func_map_ {
|
||||||
|
{sql_function_t::NONE, "NONE" },
|
||||||
|
{sql_function_t::COUNT, "COUNT" },
|
||||||
|
{sql_function_t::AVG, "AVG" },
|
||||||
|
{sql_function_t::SUM, "SUM" },
|
||||||
|
{sql_function_t::MIN, "MIN" },
|
||||||
|
{sql_function_t::MAX, "MAX" },
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef QUERY_DIALECT_BUILDER_HPP
|
||||||
|
#define QUERY_DIALECT_BUILDER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/dialect.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class dialect_builder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static dialect_builder& builder();
|
||||||
|
|
||||||
|
dialect_builder& create();
|
||||||
|
|
||||||
|
dialect_builder& with_token_replace_map(const dialect::token_to_string_map &token_replace_map);
|
||||||
|
dialect_builder& with_data_type_replace_map(const dialect::data_type_to_string_map &data_type_replace_map);
|
||||||
|
dialect_builder& with_placeholder_func(const dialect::next_placeholder_func &func);
|
||||||
|
dialect_builder& with_default_schema_name(const std::string &schema_name);
|
||||||
|
|
||||||
|
dialect build();
|
||||||
|
|
||||||
|
private:
|
||||||
|
dialect_builder() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
dialect dialect_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_DIALECT_BUILDER_HPP
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef QUERY_ENTITY_HPP
|
||||||
|
#define QUERY_ENTITY_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
template < class Type >
|
||||||
|
class entity
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using value_type = Type;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
|
||||||
|
entity() = default;
|
||||||
|
explicit entity(Type *obj)
|
||||||
|
: obj_(obj) {}
|
||||||
|
entity(const entity&) = default;
|
||||||
|
entity& operator=(const entity&) = default;
|
||||||
|
entity(entity&&) noexcept = default;
|
||||||
|
entity& operator=(entity&&) noexcept = default;
|
||||||
|
~entity() = default;
|
||||||
|
|
||||||
|
void reset(Type *obj) { obj_.reset(obj); }
|
||||||
|
|
||||||
|
pointer operator->() { return obj_.get(); }
|
||||||
|
const value_type* operator->() const { return obj_.get(); }
|
||||||
|
|
||||||
|
reference operator*() { return *obj_; }
|
||||||
|
const value_type& operator*() const { return *obj_; }
|
||||||
|
|
||||||
|
pointer get() { return obj_.get(); }
|
||||||
|
const value_type* get() const { return obj_.get(); }
|
||||||
|
|
||||||
|
operator bool() const { return obj_.get() != nullptr; } // NOLINT(*-explicit-constructor)
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<value_type> obj_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Type, typename... Args>
|
||||||
|
[[maybe_unused]] entity<Type> make_entity(Args&&... args)
|
||||||
|
{
|
||||||
|
return entity(new Type(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_ENTITY_HPP
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
#ifndef QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||||
|
#define QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/connection.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
|
#include "matador/sql/query_context.hpp"
|
||||||
|
#include "matador/sql/query.hpp"
|
||||||
|
#include "matador/sql/query_intermediates.hpp"
|
||||||
|
#include "matador/sql/value.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/result.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
struct join_columns
|
||||||
|
{
|
||||||
|
std::string join_column;
|
||||||
|
std::string inverse_join_column;
|
||||||
|
};
|
||||||
|
|
||||||
|
class join_column_collector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template<class Type>
|
||||||
|
join_columns collect()
|
||||||
|
{
|
||||||
|
join_columns_ = {};
|
||||||
|
Type obj;
|
||||||
|
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
|
||||||
|
return join_columns_;
|
||||||
|
}
|
||||||
|
template < class V >
|
||||||
|
void on_primary_key(const char * /*id*/, V &, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0) {}
|
||||||
|
void on_primary_key(const char * /*id*/, std::string &, size_t) {}
|
||||||
|
void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {}
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char * /*id*/, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_belongs_to(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_has_one(const char * /*id*/, Pointer &obj, const utils::foreign_attributes &attr) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/)
|
||||||
|
{
|
||||||
|
join_columns_.join_column = join_column;
|
||||||
|
join_columns_.inverse_join_column = inverse_join_column;
|
||||||
|
}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char * /*id*/, ContainerType &/*c*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
join_columns join_columns_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct entity_query_data {
|
||||||
|
std::string root_table_name;
|
||||||
|
std::string pk_column_;
|
||||||
|
std::vector<column> columns;
|
||||||
|
std::vector<join_data> joins;
|
||||||
|
std::unique_ptr<basic_condition> where_clause;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class query_build_error : std::uint8_t {
|
||||||
|
Ok = 0,
|
||||||
|
UnknownType,
|
||||||
|
MissingPrimaryKey,
|
||||||
|
UnexpectedError
|
||||||
|
};
|
||||||
|
|
||||||
|
class query_builder_exception : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit query_builder_exception(query_build_error error) : error_(error) {}
|
||||||
|
|
||||||
|
[[nodiscard]] query_build_error error() const { return error_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const query_build_error error_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class entity_query_builder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit entity_query_builder(const schema &scm)
|
||||||
|
: schema_(scm) {}
|
||||||
|
|
||||||
|
template<class EntityType, typename PrimaryKeyType>
|
||||||
|
utils::result<entity_query_data, query_build_error> build(const PrimaryKeyType &pk) {
|
||||||
|
const auto info = schema_.info<EntityType>();
|
||||||
|
if (!info) {
|
||||||
|
return utils::error(query_build_error::UnknownType);
|
||||||
|
}
|
||||||
|
pk_ = pk;
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
entity_query_data_ = { info->name };
|
||||||
|
EntityType obj;
|
||||||
|
try {
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
|
||||||
|
return {utils::ok(std::move(entity_query_data_))};
|
||||||
|
} catch (const query_builder_exception &ex) {
|
||||||
|
return {utils::error(ex.error())};
|
||||||
|
} catch (...) {
|
||||||
|
return {utils::error(query_build_error::UnexpectedError)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class EntityType>
|
||||||
|
utils::result<entity_query_data, query_build_error> build() {
|
||||||
|
const auto info = schema_.info<EntityType>();
|
||||||
|
if (!info) {
|
||||||
|
return utils::error(query_build_error::UnknownType);
|
||||||
|
}
|
||||||
|
pk_ = nullptr;
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
entity_query_data_ = { info->name };
|
||||||
|
EntityType obj;
|
||||||
|
try {
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
|
||||||
|
return {utils::ok(std::move(entity_query_data_))};
|
||||||
|
} catch (const query_builder_exception &ex) {
|
||||||
|
return {utils::error(ex.error())};
|
||||||
|
} catch (...) {
|
||||||
|
return {utils::error(query_build_error::UnexpectedError)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template < class V >
|
||||||
|
void on_primary_key(const char *id, V &, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
push(id);
|
||||||
|
if (!is_root_entity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pk_.is_null()) {
|
||||||
|
entity_query_data_.pk_column_ = id;
|
||||||
|
} else if (pk_.is_integer()) {
|
||||||
|
entity_query_data_.where_clause = make_condition(column{table_info_stack_.top().name, id, ""} == *pk_.as<V>());
|
||||||
|
entity_query_data_.pk_column_ = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_primary_key(const char *id, std::string &, size_t);
|
||||||
|
void on_revision(const char *id, unsigned long long &/*rev*/);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||||
|
{
|
||||||
|
push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Pointer>
|
||||||
|
void on_belongs_to(const char *id, Pointer &obj, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
on_foreign_object(id, obj, attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Pointer>
|
||||||
|
void on_has_one(const char *id, Pointer &obj, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
on_foreign_object(id, obj, attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() == utils::fetch_type::EAGER) {
|
||||||
|
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
throw query_builder_exception{query_build_error::UnknownType};
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename ContainerType::value_type::value_type obj;
|
||||||
|
matador::utils::access::process(*this , obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||||
|
}
|
||||||
|
|
||||||
|
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {info->name, join_column});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() != utils::fetch_type::EAGER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
throw query_builder_exception{query_build_error::UnknownType};
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename ContainerType::value_type::value_type obj;
|
||||||
|
matador::utils::access::process(*this , obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||||
|
}
|
||||||
|
|
||||||
|
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_column});
|
||||||
|
append_join({id, inverse_join_column}, {info->name, pk->name()});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() != utils::fetch_type::EAGER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
throw query_builder_exception{query_build_error::UnknownType};
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename ContainerType::value_type::value_type obj;
|
||||||
|
matador::utils::access::process(*this , obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto join_columns = join_column_collector_.collect<typename ContainerType::value_type::value_type>();
|
||||||
|
|
||||||
|
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_columns.inverse_join_column});
|
||||||
|
append_join({id, join_columns.join_column}, {info->name, pk->name()});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class Pointer>
|
||||||
|
void on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr);
|
||||||
|
void push(const std::string &column_name);
|
||||||
|
[[nodiscard]] bool is_root_entity() const;
|
||||||
|
void append_join(const column &left, const column &right);
|
||||||
|
|
||||||
|
private:
|
||||||
|
value pk_;
|
||||||
|
std::stack<table_info> table_info_stack_;
|
||||||
|
const schema &schema_;
|
||||||
|
entity_query_data entity_query_data_;
|
||||||
|
int column_index{0};
|
||||||
|
join_column_collector join_column_collector_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Pointer>
|
||||||
|
void entity_query_builder::on_foreign_object(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
if (attr.fetch() == utils::fetch_type::EAGER) {
|
||||||
|
const auto info = schema_.info<typename Pointer::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
throw query_builder_exception{query_build_error::UnknownType};
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename Pointer::value_type obj;
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
throw query_builder_exception{query_build_error::MissingPrimaryKey};
|
||||||
|
}
|
||||||
|
append_join({table_info_stack_.top().name, id}, {info->name, pk->name()});
|
||||||
|
} else {
|
||||||
|
push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#ifndef QUERY_FIELD_HPP
|
||||||
|
#define QUERY_FIELD_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/value.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class field
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit field(std::string name);
|
||||||
|
template<typename Type>
|
||||||
|
field(std::string name, Type value, size_t size = 0, int index = -1)
|
||||||
|
: name_(std::move(name))
|
||||||
|
, index_(index)
|
||||||
|
, value_(value, size) {}
|
||||||
|
field(std::string name, data_type_t data_type, size_t size = 0, int index = -1);
|
||||||
|
field(const field &x) = default;
|
||||||
|
field& operator=(const field &x) = default;
|
||||||
|
field(field &&x) noexcept;
|
||||||
|
field& operator=(field &&x) noexcept;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
field& operator=(Type value) {
|
||||||
|
value_ = std::move(value);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& name() const;
|
||||||
|
[[nodiscard]] size_t size() const;
|
||||||
|
[[nodiscard]] int index() const;
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
std::optional<Type> as() const
|
||||||
|
{
|
||||||
|
return value_.as<Type>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string str() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_integer() const;
|
||||||
|
[[nodiscard]] bool is_floating_point() const;
|
||||||
|
[[nodiscard]] bool is_bool() const;
|
||||||
|
[[nodiscard]] bool is_string() const;
|
||||||
|
[[nodiscard]] bool is_varchar() const;
|
||||||
|
[[nodiscard]] bool is_blob() const;
|
||||||
|
[[nodiscard]] bool is_null() const;
|
||||||
|
[[nodiscard]] bool is_unknown() const;
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream &out, const field &col);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class Operator>
|
||||||
|
void process(Operator &op)
|
||||||
|
{
|
||||||
|
op.on_attribute(name_.c_str(), value_, value_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class record;
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
int index_{-1};
|
||||||
|
|
||||||
|
value value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_FIELD_HPP
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef QUERY_FK_VALUE_EXTRACTOR_HPP
|
||||||
|
#define QUERY_FK_VALUE_EXTRACTOR_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/any_type.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/field_attributes.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql::detail {
|
||||||
|
|
||||||
|
class fk_value_extractor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fk_value_extractor() = default;
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
any_type extract(Type &x)
|
||||||
|
{
|
||||||
|
matador::utils::access::process(*this, x);
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ValueType>
|
||||||
|
void on_primary_key(const char *, ValueType &pk, typename std::enable_if<std::is_integral<ValueType>::value && !std::is_same<bool, ValueType>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
value_ = pk;
|
||||||
|
}
|
||||||
|
void on_primary_key(const char * /*id*/, std::string &pk, size_t size);
|
||||||
|
void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {}
|
||||||
|
template < class Type >
|
||||||
|
void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
void on_attribute(const char * /*id*/, char * /*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class Pointer>
|
||||||
|
void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
any_type value_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_FK_VALUE_EXTRACTOR_HPP
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef QUERY_HAS_MANY_TO_MANY_RELATION_HPP
|
||||||
|
#define QUERY_HAS_MANY_TO_MANY_RELATION_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/entity.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/foreign_attributes.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
template < class LocalType, class ForeignType >
|
||||||
|
class has_many_to_many_relation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
has_many_to_many_relation() = default;
|
||||||
|
has_many_to_many_relation(std::string local_name, std::string remote_name)
|
||||||
|
: local_name_(std::move(local_name))
|
||||||
|
, remote_name_(std::move(remote_name)) {}
|
||||||
|
|
||||||
|
template<class Operator>
|
||||||
|
void process(Operator &op) {
|
||||||
|
namespace field = matador::utils::access;
|
||||||
|
field::belongs_to(op, local_name_.c_str(), local_, utils::default_foreign_attributes);
|
||||||
|
field::belongs_to(op, remote_name_.c_str(), remote_, utils::default_foreign_attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity<LocalType> local() const { return local_; }
|
||||||
|
entity<LocalType> remote() const { return remote_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string local_name_;
|
||||||
|
std::string remote_name_;
|
||||||
|
sql::entity<LocalType> local_;
|
||||||
|
sql::entity<ForeignType> remote_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_HAS_MANY_TO_MANY_RELATION_HPP
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#ifndef QUERY_KEY_VALUE_GENERATOR_HPP
|
||||||
|
#define QUERY_KEY_VALUE_GENERATOR_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/fk_value_extractor.hpp"
|
||||||
|
#include "matador/sql/key_value_pair.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class key_value_generator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
explicit key_value_generator(std::vector<key_value_pair> &result) : result_(result) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
template < class Type >
|
||||||
|
static std::vector<key_value_pair> generate(const Type &obj)
|
||||||
|
{
|
||||||
|
std::vector<key_value_pair> result;
|
||||||
|
key_value_generator generator(result);
|
||||||
|
matador::utils::access::process(generator, obj);
|
||||||
|
|
||||||
|
return std::move(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < class V >
|
||||||
|
void on_primary_key(const char *id, V &x, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
result_.emplace_back(id, x);
|
||||||
|
}
|
||||||
|
void on_primary_key(const char *id, std::string &, size_t);
|
||||||
|
void on_revision(const char *id, unsigned long long &/*rev*/);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, Type &x, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||||
|
{
|
||||||
|
result_.emplace_back(id, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type, template < class ... > class Pointer>
|
||||||
|
void on_belongs_to(const char *id, Pointer<Type> &x, const utils::foreign_attributes &/*attr*/)
|
||||||
|
{
|
||||||
|
result_.emplace_back(id, fk_value_extractor_.extract(*x));
|
||||||
|
}
|
||||||
|
template<class Type, template < class ... > class Pointer>
|
||||||
|
void on_has_one(const char *id, Pointer<Type> &x, const utils::foreign_attributes &/*attr*/)
|
||||||
|
{
|
||||||
|
result_.emplace_back(id, fk_value_extractor_.extract(*x));
|
||||||
|
}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
detail::fk_value_extractor fk_value_extractor_;
|
||||||
|
std::vector<key_value_pair> &result_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_KEY_VALUE_GENERATOR_HPP
|
||||||
@@ -1,24 +1,16 @@
|
|||||||
#ifndef QUERY_KEY_VALUE_PAIR_HPP
|
#ifndef QUERY_KEY_VALUE_PAIR_HPP
|
||||||
#define QUERY_KEY_VALUE_PAIR_HPP
|
#define QUERY_KEY_VALUE_PAIR_HPP
|
||||||
|
|
||||||
#include <any>
|
#include "matador/sql/any_type.hpp"
|
||||||
#include <string>
|
#include "matador/sql/column.hpp"
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
using any_type = std::variant<
|
|
||||||
char, short, int, long, long long,
|
|
||||||
unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long,
|
|
||||||
bool,
|
|
||||||
float, double,
|
|
||||||
const char*,
|
|
||||||
std::string>;
|
|
||||||
|
|
||||||
class key_value_pair
|
class key_value_pair
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
key_value_pair(const std::string &name, any_type value);
|
key_value_pair(const sql::column &col, any_type value);
|
||||||
|
key_value_pair(std::string name, any_type value);
|
||||||
key_value_pair(const char *name, any_type value);
|
key_value_pair(const char *name, any_type value);
|
||||||
|
|
||||||
[[nodiscard]] const std::string& name() const;
|
[[nodiscard]] const std::string& name() const;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef QUERY_NOOP_CONNECTION_HPP
|
||||||
|
#define QUERY_NOOP_CONNECTION_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/connection_impl.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class noop_connection final : public connection_impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit noop_connection(const connection_info &info);
|
||||||
|
|
||||||
|
void open() override;
|
||||||
|
void close() override;
|
||||||
|
bool is_open() override;
|
||||||
|
size_t execute(const std::string &stmt) override;
|
||||||
|
std::unique_ptr<query_result_impl> fetch(const std::string &stmt) override;
|
||||||
|
std::unique_ptr<statement_impl> prepare(query_context context) 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:
|
||||||
|
bool is_open_{false};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif //QUERY_NOOP_CONNECTION_HPP
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef QUERY_OBJECT_PARAMETER_BINDER_HPP
|
||||||
|
#define QUERY_OBJECT_PARAMETER_BINDER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/parameter_binder.hpp"
|
||||||
|
#include "matador/sql/data_type_traits.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/cascade_type.hpp"
|
||||||
|
#include "matador/utils/field_attributes.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
class fk_binder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit fk_binder(parameter_binder &binder);
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void bind(Type &obj, size_t column_index)
|
||||||
|
{
|
||||||
|
index_ = column_index;
|
||||||
|
utils::access::process(*this, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ValueType>
|
||||||
|
void on_primary_key(const char *id, ValueType &value, typename std::enable_if<std::is_integral<ValueType>::value && !std::is_same<bool, ValueType>::value>::type* = 0);
|
||||||
|
void on_primary_key(const char *id, std::string &value, size_t size);
|
||||||
|
void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {}
|
||||||
|
|
||||||
|
template < class Type >
|
||||||
|
void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
template < class Pointer >
|
||||||
|
void on_belongs_to(const char * /*id*/, Pointer &/*x*/, utils::cascade_type) {}
|
||||||
|
template < class Pointer >
|
||||||
|
void on_has_one(const char * /*id*/, Pointer &/*x*/, utils::cascade_type) {}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, utils::cascade_type) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
parameter_binder &binder_;
|
||||||
|
size_t index_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class object_parameter_binder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit object_parameter_binder(parameter_binder &binder);
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
template < class Type >
|
||||||
|
void on_primary_key(const char *id, Type &val, typename std::enable_if<std::is_integral<Type>::value && !std::is_same<bool, Type>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
data_type_traits<Type>::bind_value(binder_, index_++, val);
|
||||||
|
}
|
||||||
|
void on_primary_key(const char *id, std::string &, size_t size);
|
||||||
|
void on_revision(const char *id, unsigned long long &/*rev*/);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, Type &val, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||||
|
{
|
||||||
|
data_type_traits<Type>::bind_value(binder_, index_++, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type, template < class ... > class Pointer>
|
||||||
|
void on_belongs_to(const char *id, Pointer<Type> &x, utils::cascade_type)
|
||||||
|
{
|
||||||
|
fk_binder_.bind(index_++, x);
|
||||||
|
}
|
||||||
|
template<class Type, template < class ... > class Pointer>
|
||||||
|
void on_has_one(const char *id, Pointer<Type> &x, utils::cascade_type)
|
||||||
|
{
|
||||||
|
fk_binder_.bind(index_++, x);
|
||||||
|
}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, utils::cascade_type) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
parameter_binder &binder_;
|
||||||
|
size_t index_{0};
|
||||||
|
detail::fk_binder fk_binder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template<typename ValueType>
|
||||||
|
void fk_binder::on_primary_key(const char *id, ValueType &value, typename std::enable_if<std::is_integral<ValueType>::value && !std::is_same<bool, ValueType>::value>::type *)
|
||||||
|
{
|
||||||
|
data_type_traits<ValueType>::bind_value(binder_, index_++, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif //QUERY_OBJECT_PARAMETER_BINDER_HPP
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef QUERY_PARAMETER_BINDER_HPP
|
||||||
|
#define QUERY_PARAMETER_BINDER_HPP
|
||||||
|
|
||||||
|
#include "matador/utils/types.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class parameter_binder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~parameter_binder() = default;
|
||||||
|
|
||||||
|
virtual void bind(size_t pos, char) = 0;
|
||||||
|
virtual void bind(size_t pos, short) = 0;
|
||||||
|
virtual void bind(size_t pos, int) = 0;
|
||||||
|
virtual void bind(size_t pos, long) = 0;
|
||||||
|
virtual void bind(size_t pos, long long) = 0;
|
||||||
|
virtual void bind(size_t pos, unsigned char) = 0;
|
||||||
|
virtual void bind(size_t pos, unsigned short) = 0;
|
||||||
|
virtual void bind(size_t pos, unsigned int) = 0;
|
||||||
|
virtual void bind(size_t pos, unsigned long) = 0;
|
||||||
|
virtual void bind(size_t pos, unsigned long long) = 0;
|
||||||
|
virtual void bind(size_t pos, bool) = 0;
|
||||||
|
virtual void bind(size_t pos, float) = 0;
|
||||||
|
virtual void bind(size_t pos, double) = 0;
|
||||||
|
virtual void bind(size_t pos, const char *) = 0;
|
||||||
|
virtual void bind(size_t pos, const char *, size_t size) = 0;
|
||||||
|
virtual void bind(size_t pos, const std::string&) = 0;
|
||||||
|
virtual void bind(size_t pos, const std::string &x, size_t size) = 0;
|
||||||
|
virtual void bind(size_t pos, const utils::blob &) = 0;
|
||||||
|
// virtual void bind(size_t pos, const matador::time&) = 0;
|
||||||
|
// virtual void bind(size_t pos, const matador::date&) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_PARAMETER_BINDER_HPP
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef QUERY_PLACEHOLDER_HPP
|
||||||
|
#define QUERY_PLACEHOLDER_HPP
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
struct placeholder {};
|
||||||
|
|
||||||
|
inline constexpr bool operator==(const placeholder&, const placeholder&) { return true; }
|
||||||
|
|
||||||
|
const placeholder _;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_PLACEHOLDER_HPP
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef QUERY_PLACEHOLDER_GENERATOR_HPP
|
||||||
|
#define QUERY_PLACEHOLDER_GENERATOR_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/any_type.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/cascade_type.hpp"
|
||||||
|
#include "matador/utils/field_attributes.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class placeholder_generator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template < class V >
|
||||||
|
void on_primary_key(const char *id, V &val, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
|
||||||
|
{
|
||||||
|
placeholder_values.emplace_back(_);
|
||||||
|
}
|
||||||
|
void on_primary_key(const char *id, std::string &, size_t);
|
||||||
|
void on_revision(const char *id, unsigned long long &/*rev*/);
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
void on_attribute(const char *id, Type &val, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||||
|
{
|
||||||
|
placeholder_values.emplace_back(_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type, template < class ... > class Pointer>
|
||||||
|
void on_belongs_to(const char *id, Pointer<Type> &x, utils::cascade_type)
|
||||||
|
{
|
||||||
|
placeholder_values.emplace_back(_);
|
||||||
|
}
|
||||||
|
template<class Type, template < class ... > class Pointer>
|
||||||
|
void on_has_one(const char *id, Pointer<Type> &x, utils::cascade_type)
|
||||||
|
{
|
||||||
|
placeholder_values.emplace_back(_);
|
||||||
|
}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many(const char *, ContainerType &, utils::cascade_type) {}
|
||||||
|
|
||||||
|
std::vector<any_type> placeholder_values;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_PLACEHOLDER_GENERATOR_HPP
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef QUERY_QUERY_HPP
|
||||||
|
#define QUERY_QUERY_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_intermediates.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class connection;
|
||||||
|
|
||||||
|
class query
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit query(connection &db, const sql::schema &schema);
|
||||||
|
query(const query &) = delete;
|
||||||
|
query& operator=(const query &) = delete;
|
||||||
|
|
||||||
|
query_create_intermediate create();
|
||||||
|
query_drop_intermediate drop();
|
||||||
|
query_select_intermediate select(std::initializer_list<column> columns);
|
||||||
|
query_select_intermediate select(const std::vector<column>& columns);
|
||||||
|
query_select_intermediate select(std::vector<column> columns, std::initializer_list<column> additional_columns);
|
||||||
|
query_insert_intermediate insert();
|
||||||
|
query_update_intermediate update(const sql::table &table);
|
||||||
|
query_delete_intermediate remove();
|
||||||
|
|
||||||
|
private:
|
||||||
|
connection &connection_;
|
||||||
|
const sql::schema &schema_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_QUERY_HPP
|
||||||
@@ -2,22 +2,25 @@
|
|||||||
#define QUERY_QUERY_BUILDER_HPP
|
#define QUERY_QUERY_BUILDER_HPP
|
||||||
|
|
||||||
#include "matador/sql/basic_condition.hpp"
|
#include "matador/sql/basic_condition.hpp"
|
||||||
|
#include "matador/sql/column_definition.hpp"
|
||||||
#include "matador/sql/column.hpp"
|
#include "matador/sql/column.hpp"
|
||||||
|
#include "matador/sql/dialect.hpp"
|
||||||
#include "matador/sql/key_value_pair.hpp"
|
#include "matador/sql/key_value_pair.hpp"
|
||||||
|
#include "matador/sql/query_context.hpp"
|
||||||
|
#include "matador/sql/record.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
class dialect;
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct any_type_to_string_visitor
|
struct any_type_to_string_visitor
|
||||||
{
|
{
|
||||||
explicit any_type_to_string_visitor(const dialect &d) : d(d) {}
|
explicit any_type_to_string_visitor(const dialect &d, query_context &query);
|
||||||
|
|
||||||
void operator()(char &x) { to_string(x); }
|
void operator()(char &x) { to_string(x); }
|
||||||
void operator()(short &x) { to_string(x); }
|
void operator()(short &x) { to_string(x); }
|
||||||
@@ -34,6 +37,8 @@ struct any_type_to_string_visitor
|
|||||||
void operator()(double &x) { to_string(x); }
|
void operator()(double &x) { to_string(x); }
|
||||||
void operator()(const char *x) { to_string(x); }
|
void operator()(const char *x) { to_string(x); }
|
||||||
void operator()(std::string &x) { to_string(x); }
|
void operator()(std::string &x) { to_string(x); }
|
||||||
|
void operator()(utils::blob &x) { to_string(x); }
|
||||||
|
void operator()(placeholder &x) { to_string(x); }
|
||||||
|
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
void to_string(Type &val)
|
void to_string(Type &val)
|
||||||
@@ -42,13 +47,21 @@ struct any_type_to_string_visitor
|
|||||||
}
|
}
|
||||||
void to_string(const char *val);
|
void to_string(const char *val);
|
||||||
void to_string(std::string &val);
|
void to_string(std::string &val);
|
||||||
|
void to_string(utils::blob &val);
|
||||||
|
void to_string(placeholder &val);
|
||||||
|
|
||||||
const dialect &d;
|
const dialect &d;
|
||||||
|
query_context &query;
|
||||||
std::string result;
|
std::string result;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
column alias(const std::string &column, const std::string &as);
|
||||||
|
column alias(column &&col, const std::string &as);
|
||||||
|
column count(const std::string &column);
|
||||||
|
column count_all();
|
||||||
|
|
||||||
enum class join_type_t {
|
enum class join_type_t {
|
||||||
INNER, OUTER, LEFT, RIGHT
|
INNER, OUTER, LEFT, RIGHT
|
||||||
};
|
};
|
||||||
@@ -68,6 +81,8 @@ private:
|
|||||||
QUERY_DELETE,
|
QUERY_DELETE,
|
||||||
QUERY_SET,
|
QUERY_SET,
|
||||||
QUERY_FROM,
|
QUERY_FROM,
|
||||||
|
QUERY_JOIN,
|
||||||
|
QUERY_ON,
|
||||||
QUERY_INTO,
|
QUERY_INTO,
|
||||||
QUERY_WHERE,
|
QUERY_WHERE,
|
||||||
QUERY_VALUES,
|
QUERY_VALUES,
|
||||||
@@ -89,24 +104,38 @@ private:
|
|||||||
REMOVE /**< Remove query command */
|
REMOVE /**< Remove query command */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct query_part
|
||||||
|
{
|
||||||
|
query_part(dialect::token_t t, std::string p)
|
||||||
|
: token(t), part(std::move(p)) {}
|
||||||
|
|
||||||
|
dialect::token_t token;
|
||||||
|
std::string part;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit query_builder(const dialect &d);
|
explicit query_builder(const dialect &d);
|
||||||
|
|
||||||
query_builder& create();
|
query_builder& create();
|
||||||
query_builder& drop();
|
query_builder& drop();
|
||||||
query_builder& select(std::initializer_list<std::string> column_names);
|
query_builder& select(std::initializer_list<column> columns);
|
||||||
|
query_builder& select(const std::vector<column> &columns);
|
||||||
query_builder& insert();
|
query_builder& insert();
|
||||||
query_builder& update(const std::string &table);
|
query_builder& update(const std::string &table);
|
||||||
query_builder& remove();
|
query_builder& remove();
|
||||||
|
|
||||||
query_builder& table(const std::string &table, std::initializer_list<column> columns);
|
query_builder& table(const std::string &table, std::initializer_list<column_definition> columns);
|
||||||
|
query_builder& table(const std::string &table, const std::vector<column_definition> &columns);
|
||||||
query_builder& table(const std::string &table);
|
query_builder& table(const std::string &table);
|
||||||
query_builder& into(const std::string &table, std::initializer_list<std::string> column_names);
|
query_builder& into(const std::string &table, std::initializer_list<column> column_names);
|
||||||
|
query_builder& into(const std::string &table, const std::vector<column> &column_names);
|
||||||
query_builder& values(std::initializer_list<any_type> values);
|
query_builder& values(std::initializer_list<any_type> values);
|
||||||
|
query_builder& values(const std::vector<any_type> &values);
|
||||||
query_builder& from(const std::string &table, const std::string &as = "");
|
query_builder& from(const std::string &table, const std::string &as = "");
|
||||||
query_builder& join(const std::string &table, join_type_t);
|
query_builder& join(const std::string &table, join_type_t, const std::string &as = "");
|
||||||
query_builder& on(const std::string &column, const std::string &join_column);
|
query_builder& on(const std::string &column, const std::string &join_column);
|
||||||
query_builder& set(std::initializer_list<key_value_pair> key_values);
|
query_builder& set(std::initializer_list<key_value_pair> key_values);
|
||||||
|
query_builder& set(const std::vector<key_value_pair> &key_values);
|
||||||
query_builder& where(const basic_condition &cond);
|
query_builder& where(const basic_condition &cond);
|
||||||
query_builder& order_by(const std::string &column);
|
query_builder& order_by(const std::string &column);
|
||||||
query_builder& group_by(const std::string &column);
|
query_builder& group_by(const std::string &column);
|
||||||
@@ -115,7 +144,7 @@ public:
|
|||||||
query_builder& offset(size_t count);
|
query_builder& offset(size_t count);
|
||||||
query_builder& limit(size_t count);
|
query_builder& limit(size_t count);
|
||||||
|
|
||||||
std::string compile();
|
query_context compile();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void transition_to(state_t next);
|
void transition_to(state_t next);
|
||||||
@@ -127,10 +156,12 @@ private:
|
|||||||
command_t command_{command_t::UNKNOWN};
|
command_t command_{command_t::UNKNOWN};
|
||||||
state_t state_{state_t::QUERY_INIT};
|
state_t state_{state_t::QUERY_INIT};
|
||||||
|
|
||||||
std::vector<std::string> query_parts_;
|
std::vector<query_part> query_parts_;
|
||||||
|
|
||||||
detail::any_type_to_string_visitor value_to_string_;
|
detail::any_type_to_string_visitor value_to_string_;
|
||||||
|
|
||||||
|
query_context query_;
|
||||||
|
|
||||||
using query_state_set = std::unordered_set<state_t>;
|
using query_state_set = std::unordered_set<state_t>;
|
||||||
using query_state_transition_map = std::unordered_map<state_t, query_state_set>;
|
using query_state_transition_map = std::unordered_map<state_t, query_state_set>;
|
||||||
using query_state_to_string_map = std::unordered_map<state_t, std::string>;
|
using query_state_to_string_map = std::unordered_map<state_t, std::string>;
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef QUERY_QUERY_COMPILER_HPP
|
||||||
|
#define QUERY_QUERY_COMPILER_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_part_visitor.hpp"
|
||||||
|
#include "matador/sql/query_parts.hpp"
|
||||||
|
#include "matador/sql/query_context.hpp"
|
||||||
|
|
||||||
|
#include <typeindex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class dialect;
|
||||||
|
|
||||||
|
struct query_data;
|
||||||
|
|
||||||
|
class query_compiler : public query_part_visitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit query_compiler(const sql::dialect& d);
|
||||||
|
|
||||||
|
query_context compile(const query_data *data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void visit(query_select_part &select_part) override;
|
||||||
|
void visit(query_from_part &from_part) override;
|
||||||
|
void visit(query_join_part &join_part) override;
|
||||||
|
void visit(query_on_part &on_part) override;
|
||||||
|
void visit(query_where_part &where_part) override;
|
||||||
|
void visit(query_group_by_part &group_by_part) override;
|
||||||
|
void visit(query_order_by_part &order_by_part) override;
|
||||||
|
void visit(query_order_by_asc_part &order_by_asc_part) override;
|
||||||
|
void visit(query_order_by_desc_part &order_by_desc_part) override;
|
||||||
|
void visit(query_offset_part &offset_part) override;
|
||||||
|
void visit(query_limit_part &limit_part) override;
|
||||||
|
void visit(query_insert_part &insert_part) override;
|
||||||
|
void visit(query_into_part &into_part) override;
|
||||||
|
void visit(query_values_part &values_part) override;
|
||||||
|
|
||||||
|
void visit(query_update_part &update_part) override;
|
||||||
|
void visit(query_set_part &set_part) override;
|
||||||
|
|
||||||
|
void visit(query_delete_part &delete_part) override;
|
||||||
|
void visit(query_delete_from_part &delete_from_part) override;
|
||||||
|
|
||||||
|
void visit(query_create_part &create_part) override;
|
||||||
|
void visit(query_create_table_part &create_table_part) override;
|
||||||
|
|
||||||
|
void visit(query_drop_part &drop_part) override;
|
||||||
|
void visit(query_drop_table_part &drop_table_part) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const sql::dialect &dialect_;
|
||||||
|
query_context query_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_QUERY_COMPILER_HPP
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef QUERY_QUERY_CONTEXT_HPP
|
||||||
|
#define QUERY_QUERY_CONTEXT_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/column_definition.hpp"
|
||||||
|
#include "matador/sql/table.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
struct query_context
|
||||||
|
{
|
||||||
|
std::string sql;
|
||||||
|
std::string command_name;
|
||||||
|
sql::table table{""};
|
||||||
|
std::vector<column_definition> prototype;
|
||||||
|
std::vector<std::string> result_vars;
|
||||||
|
std::vector<std::string> bind_vars;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_QUERY_CONTEXT_HPP
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef QUERY_QUERY_DATA_HPP
|
||||||
|
#define QUERY_QUERY_DATA_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/query_part.hpp"
|
||||||
|
#include "matador/sql/table.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
class query_part;
|
||||||
|
|
||||||
|
struct query_data
|
||||||
|
{
|
||||||
|
// SqlCommands command;
|
||||||
|
std::vector<std::unique_ptr<query_part>> parts;
|
||||||
|
std::vector<column_definition> columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_QUERY_DATA_HPP
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef QUERY_QUERY_HELPER_HPP
|
||||||
|
#define QUERY_QUERY_HELPER_HPP
|
||||||
|
|
||||||
|
#include "matador/utils/macro_map.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/table.hpp"
|
||||||
|
#include "matador/sql/column.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#define FIELD(x) const sql::column x{*this, #x, ""};
|
||||||
|
|
||||||
|
#define QUERY_HELPER(C, ...) \
|
||||||
|
namespace matador::qh { \
|
||||||
|
namespace internal { \
|
||||||
|
struct C##_query : sql::table { \
|
||||||
|
C##_query() : table(#C) {} \
|
||||||
|
MAP(FIELD, __VA_ARGS__) \
|
||||||
|
}; } \
|
||||||
|
static const internal:: C##_query C; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //QUERY_QUERY_HELPER_HPP
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user