diff --git a/.vscode/settings.json b/.vscode/settings.json index 1e6e6100..7216b3ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,6 +73,7 @@ "system_error": "cpp", "type_traits": "cpp", "utility": "cpp", - "__config": "cpp" + "__config": "cpp", + "any": "cpp" } } \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c9268a7c..96621736 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,11 +1,32 @@ -cmake_minimum_required (VERSION 3.16) +cmake_minimum_required(VERSION 3.16) # note: find_package(SQLite3 REQUIRED) already done in top-level CMakeLists +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + file(GLOB files "*.cpp") + +set(run_example_targets) + foreach(file ${files}) get_filename_component(file_basename ${file} NAME_WE) + add_executable(${file_basename} ${file}) # note: sqlite3 already linked in top-level CMakeLists + target_link_libraries(${file_basename} PRIVATE sqlite_orm) + + add_custom_target(run_${file_basename} + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${file_basename} + DEPENDS ${file_basename} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Running example: ${file_basename}" + VERBATIM + ) + + list(APPEND run_example_targets run_${file_basename}) endforeach() + +add_custom_target(run_all_examples + DEPENDS ${run_example_targets} +) diff --git a/examples/any.cpp b/examples/any.cpp new file mode 100644 index 00000000..f49ba955 --- /dev/null +++ b/examples/any.cpp @@ -0,0 +1,155 @@ +/** + * This example demonstrates how to use std::any as a mapped type. + * It is a bit more complex than the other examples, because it + * uses custom row_extractor and statement_binder. + * Please note that this implementation is not the one and only + * option of implementation of std:any bindong to sqlite_orm. + * It is just an example of how to use std::any as a mapped type. + * Implementation is based on 5 types SQLite supports: + * NULL, INTEGER, REAL, TEXT, BLOB. + * NULL is mapped to std::nullopt. + * INTEGER is mapped to int. + * REAL is mapped to double. + * TEXT is mapped to std::string. + * BLOB is mapped to std::vector. +*/ +#include +#include +#include +#include + +using namespace sqlite_orm; +using std::cout; +using std::endl; + +namespace sqlite_orm { + + template<> + struct row_extractor { + std::any extract(sqlite3_stmt* stmt, int columnIndex) const { + const int type = sqlite3_column_type(stmt, columnIndex); + switch(type) { + case SQLITE_NULL: + return std::nullopt; + case SQLITE_INTEGER: + return sqlite3_column_int(stmt, columnIndex); + case SQLITE_FLOAT: + return sqlite3_column_double(stmt, columnIndex); + case SQLITE_TEXT: { + const unsigned char* text = sqlite3_column_text(stmt, columnIndex); + return std::string(reinterpret_cast(text)); + } + case SQLITE_BLOB: { + const void* blob = sqlite3_column_blob(stmt, columnIndex); + const int size = sqlite3_column_bytes(stmt, columnIndex); + return std::vector(reinterpret_cast(blob), + reinterpret_cast(blob) + size); + } + default: + throw std::runtime_error("Unsupported SQLite column type for std::any"); + } + } + + std::any extract(sqlite3_value* value) const { + const int type = sqlite3_value_type(value); + switch(type) { + case SQLITE_NULL: + return std::nullopt; + case SQLITE_INTEGER: + return sqlite3_value_int(value); + case SQLITE_FLOAT: + return sqlite3_value_double(value); + case SQLITE_TEXT: { + const unsigned char* text = sqlite3_value_text(value); + return std::string(reinterpret_cast(text)); + } + case SQLITE_BLOB: { + const void* blob = sqlite3_value_blob(value); + const int size = sqlite3_value_bytes(value); + return std::vector(reinterpret_cast(blob), + reinterpret_cast(blob) + size); // Handle BLOB + } + default: + throw std::runtime_error("Unsupported SQLite value type for std::any"); + } + } + }; + + template<> + struct statement_binder { + int bind(sqlite3_stmt* stmt, int index, const std::any& value) const { + if(!value.has_value()) { + return sqlite3_bind_null(stmt, index); + } + + if(value.type() == typeid(int)) { + return sqlite3_bind_int(stmt, index, std::any_cast(value)); + } else if(value.type() == typeid(double)) { + return sqlite3_bind_double(stmt, index, std::any_cast(value)); + } else if(value.type() == typeid(std::string)) { + const auto& text = std::any_cast(value); + return sqlite3_bind_text(stmt, index, text.c_str(), static_cast(text.size()), SQLITE_TRANSIENT); + } else if(value.type() == typeid(std::vector)) { + const auto& blob = std::any_cast>(value); + return sqlite3_bind_blob(stmt, index, blob.data(), static_cast(blob.size()), SQLITE_TRANSIENT); + } + + return SQLITE_MISMATCH; + } + }; + + template<> + struct type_printer : public text_printer {}; + + template<> + struct field_printer { + std::string operator()(const std::any& value) const { + if(!value.has_value()) { + return "NULL"; + } + + if(value.type() == typeid(int)) { + return std::to_string(std::any_cast(value)); + } else if(value.type() == typeid(double)) { + return std::to_string(std::any_cast(value)); + } else if(value.type() == typeid(std::string)) { + return std::any_cast(value); + } else if(value.type() == typeid(std::vector)) { + const auto& blob = std::any_cast>(value); + std::ostringstream oss; + oss << "0x"; + for(unsigned char c: blob) { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); + } + return oss.str(); + } + + throw std::runtime_error("Unsupported type in std::any field_printer"); + } + }; +} + +int main() { + struct Value { + int id = 0; + std::any value; + }; + auto filename = "any.sqlite"; + ::remove(filename); + auto storage = make_storage( + filename, + make_table("test", make_column("id", &Value::id, primary_key()), make_column("value", &Value::value))); + storage.sync_schema(); + storage.replace(Value{1, std::any{1}}); + storage.replace(Value{2, std::any{2.5}}); + storage.replace(Value{3, std::any{std::string("Hello, world!")}}); + storage.replace( + Value{4, std::any{std::vector{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'}}}); + + cout << "Test:" << endl; + for(auto& test: storage.iterate()) { + cout << storage.dump(test) << endl; + } + cout << endl; + return 0; +} diff --git a/examples/blob_binding.cpp b/examples/blob_binding.cpp index af23f5e7..a4caaf8e 100644 --- a/examples/blob_binding.cpp +++ b/examples/blob_binding.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/examples/generated_column.cpp b/examples/generated_column.cpp index 699cf396..7a3a0ef3 100644 --- a/examples/generated_column.cpp +++ b/examples/generated_column.cpp @@ -6,12 +6,17 @@ #include #if SQLITE_VERSION_NUMBER >= 3031000 +#define ENABLE_THIS_EXAMPLE +#endif +#ifdef ENABLE_THIS_EXAMPLE using namespace sqlite_orm; using std::cout; using std::endl; +#endif // ENABLE_THIS_EXAMPLE int main() { +#ifdef ENABLE_THIS_EXAMPLE struct Product { int id = 0; std::string name; @@ -57,8 +62,6 @@ int main() { cout << storage.dump(product) << endl; } cout << endl; - +#endif // ENABLE_THIS_EXAMPLE return 0; } - -#endif // SQLITE_VERSION_NUMBER >= 3031000