diff --git a/CMakeLists.txt b/CMakeLists.txt index 82b80baa..b96bde0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ OPTION(ENABLE_SQLCIPHER_TESTS "enable sqlchipher test") # Creates the file compile_commands.json in the build directory. SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set (CMAKE_CXX_STANDARD 14) +set (CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") include("cmake/HunterGate.cmake") @@ -43,6 +43,7 @@ else() endif() catch_discover_tests(tests) +target_compile_options(tests PUBLIC $<$:/Zc:__cplusplus> ) # Place the file in the source directory, permitting us to place a single configuration file for YCM there. # YCM is the code-completion engine for (neo)vim https://github.com/Valloric/YouCompleteMe diff --git a/README.md b/README.md index 4d85af2f..95e938db 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ int main() { // int ,long, long long, float, double // string , u16string // sqlite3 only supports utf8 and utf16 strings, you should use std::string for utf8 and std::u16string for utf16. + // If you're using c++17, it takes `string_view` and `u16string_view` as arguments // note that u"my text" is a utf16 string literal of type char16_t * . db << "insert into user (age,name,weight) values (?,?,?);" << 20 diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 964f993c..a19c9851 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -85,15 +85,15 @@ namespace sqlite { return ++_inx; } - sqlite3_stmt* _prepare(const std::u16string& sql) { + sqlite3_stmt* _prepare(u16str_ref sql) { return _prepare(utility::utf16_to_utf8(sql)); } - sqlite3_stmt* _prepare(const std::string& sql) { + sqlite3_stmt* _prepare(str_ref sql) { int hresult; sqlite3_stmt* tmp = nullptr; const char *remaining; - hresult = sqlite3_prepare_v2(_db.get(), sql.data(), -1, &tmp, &remaining); + hresult = sqlite3_prepare_v2(_db.get(), sql.data(), sql.length(), &tmp, &remaining); if(hresult != SQLITE_OK) errors::throw_sqlite_error(hresult, sql); if(!std::all_of(remaining, sql.data() + sql.size(), [](char ch) {return std::isspace(ch);})) throw errors::more_statements("Multiple semicolon separated statements are unsupported", sql); @@ -105,13 +105,13 @@ namespace sqlite { public: - database_binder(std::shared_ptr db, std::u16string const & sql): + database_binder(std::shared_ptr db, u16str_ref sql): _db(db), _stmt(_prepare(sql), sqlite3_finalize), _inx(0) { } - database_binder(std::shared_ptr db, std::string const & sql): + database_binder(std::shared_ptr db, str_ref sql): _db(db), _stmt(_prepare(sql), sqlite3_finalize), _inx(0) { @@ -372,36 +372,22 @@ namespace sqlite { *this << R"(PRAGMA encoding = "UTF-16";)"; } - database(const std::u16string &db_name, const sqlite_config &config = {}): _db(nullptr) { - auto db_name_utf8 = utility::utf16_to_utf8(db_name); - sqlite3* tmp = nullptr; - auto ret = sqlite3_open_v2(db_name_utf8.data(), &tmp, static_cast(config.flags), config.zVfs); - _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed. - if(ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret); - sqlite3_extended_result_codes(_db.get(), true); - if(config.encoding != Encoding::UTF8) + database(const std::u16string &db_name, const sqlite_config &config = {}): database(utility::utf16_to_utf8(db_name), config) { + if (config.encoding == Encoding::ANY) *this << R"(PRAGMA encoding = "UTF-16";)"; } database(std::shared_ptr db): _db(db) {} - database_binder operator<<(const std::string& sql) { + database_binder operator<<(str_ref sql) { return database_binder(_db, sql); } - database_binder operator<<(const char* sql) { - return *this << std::string(sql); - } - - database_binder operator<<(const std::u16string& sql) { + database_binder operator<<(u16str_ref sql) { return database_binder(_db, sql); } - database_binder operator<<(const char16_t* sql) { - return *this << std::u16string(sql); - } - connection_type connection() const { return _db; } sqlite3_int64 last_insert_rowid() const { @@ -418,7 +404,7 @@ namespace sqlite { auto funcPtr = new auto(std::forward(func)); if(int result = sqlite3_create_function_v2( - _db.get(), name.c_str(), traits::arity, SQLITE_UTF8, funcPtr, + _db.get(), name.data(), traits::arity, SQLITE_UTF8, funcPtr, sql_function_binder::scalar::type>, nullptr, nullptr, [](void* ptr){ delete static_cast(ptr); @@ -652,3 +638,4 @@ namespace sqlite { } } } + diff --git a/hdr/sqlite_modern_cpp/errors.h b/hdr/sqlite_modern_cpp/errors.h index cc25ae97..07d12e00 100644 --- a/hdr/sqlite_modern_cpp/errors.h +++ b/hdr/sqlite_modern_cpp/errors.h @@ -9,8 +9,8 @@ namespace sqlite { class sqlite_exception: public std::runtime_error { public: - sqlite_exception(const char* msg, std::string sql, int code = -1): runtime_error(msg), code(code), sql(sql) {} - sqlite_exception(int code, std::string sql): runtime_error(sqlite3_errstr(code)), code(code), sql(sql) {} + sqlite_exception(const char* msg, str_ref sql, int code = -1): runtime_error(msg), code(code), sql(sql) {} + sqlite_exception(int code, str_ref sql): runtime_error(sqlite3_errstr(code)), code(code), sql(sql) {} int get_code() const {return code & 0xFF;} int get_extended_code() const {return code;} std::string get_sql() const {return sql;} @@ -40,7 +40,7 @@ namespace sqlite { class more_statements: public sqlite_exception { using sqlite_exception::sqlite_exception; }; // Prepared statements can only contain one statement class invalid_utf16: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - static void throw_sqlite_error(const int& error_code, const std::string &sql = "") { + static void throw_sqlite_error(const int& error_code, str_ref sql = "") { switch(error_code & 0xFF) { #define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ case SQLITE_ ## NAME: switch(error_code) { \ diff --git a/hdr/sqlite_modern_cpp/log.h b/hdr/sqlite_modern_cpp/log.h index a8f7be22..5abe2933 100644 --- a/hdr/sqlite_modern_cpp/log.h +++ b/hdr/sqlite_modern_cpp/log.h @@ -93,9 +93,9 @@ namespace sqlite { } }); - sqlite3_config(SQLITE_CONFIG_LOG, (void(*)(void*,int,const char*))[](void *functor, int error_code, const char *errstr) { + sqlite3_config(SQLITE_CONFIG_LOG, static_cast([](void *functor, int error_code, const char *errstr) { (*static_cast(functor))(error_code, errstr); - }, ptr.get()); + }), ptr.get()); detail::store_error_log_data_pointer(std::move(ptr)); } } diff --git a/hdr/sqlite_modern_cpp/type_wrapper.h b/hdr/sqlite_modern_cpp/type_wrapper.h index 375f8d0f..e17932fe 100644 --- a/hdr/sqlite_modern_cpp/type_wrapper.h +++ b/hdr/sqlite_modern_cpp/type_wrapper.h @@ -4,7 +4,11 @@ #include #include #include - +#ifdef __has_include +#if __cplusplus >= 201703 && __has_include() +#define MODERN_SQLITE_STRINGVIEW_SUPPORT +#endif +#endif #ifdef __has_include #if __cplusplus > 201402 && __has_include() #define MODERN_SQLITE_STD_OPTIONAL_SUPPORT @@ -31,7 +35,20 @@ #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT #include #endif - +#ifdef MODERN_SQLITE_STRINGVIEW_SUPPORT +#include +namespace sqlite +{ + typedef const std::string_view str_ref; + typedef const std::u16string_view u16str_ref; +} +#else +namespace sqlite +{ + typedef const std::string& str_ref; + typedef const std::u16string& u16str_ref; +} +#endif #include #include "errors.h" @@ -150,16 +167,17 @@ namespace sqlite { sqlite3_result_null(db); } - // std::string + // str_ref template<> struct has_sqlite_type : std::true_type {}; - - inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const std::string& val) { - return sqlite3_bind_text(stmt, inx, val.data(), -1, SQLITE_TRANSIENT); + inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, str_ref val) { + return sqlite3_bind_text(stmt, inx, val.data(), val.length(), SQLITE_TRANSIENT); } - // Convert char* to string to trigger op<<(..., const std::string ) - template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const char(&STR)[N]) { return bind_col_in_db(stmt, inx, std::string(STR, N-1)); } + // Convert char* to string_view to trigger op<<(..., const str_ref ) + template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const char(&STR)[N]) { + return sqlite3_bind_text(stmt, inx, &STR[0], N-1, SQLITE_TRANSIENT); + } inline std::string get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { return sqlite3_column_type(stmt, inx) == SQLITE_NULL ? std::string() : @@ -170,31 +188,32 @@ namespace sqlite { std::string(reinterpret_cast(sqlite3_value_text(value)), sqlite3_value_bytes(value)); } - inline void store_result_in_db(sqlite3_context* db, const std::string& val) { - sqlite3_result_text(db, val.data(), -1, SQLITE_TRANSIENT); + inline void store_result_in_db(sqlite3_context* db, str_ref val) { + sqlite3_result_text(db, val.data(), val.length(), SQLITE_TRANSIENT); } - // std::u16string + // u16str_ref template<> struct has_sqlite_type : std::true_type {}; - - inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const std::u16string& val) { - return sqlite3_bind_text16(stmt, inx, val.data(), -1, SQLITE_TRANSIENT); + inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, u16str_ref val) { + return sqlite3_bind_text16(stmt, inx, val.data(), sizeof(char16_t) * val.length(), SQLITE_TRANSIENT); } - // Convert char* to string to trigger op<<(..., const std::string ) - template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const char16_t(&STR)[N]) { return bind_col_in_db(stmt, inx, std::u16string(STR, N-1)); } + // Convert char* to string_view to trigger op<<(..., const str_ref ) + template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const char16_t(&STR)[N]) { + return sqlite3_bind_text16(stmt, inx, &STR[0], sizeof(char16_t) * (N-1), SQLITE_TRANSIENT); + } inline std::u16string get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { return sqlite3_column_type(stmt, inx) == SQLITE_NULL ? std::u16string() : std::u16string(reinterpret_cast(sqlite3_column_text16(stmt, inx)), sqlite3_column_bytes16(stmt, inx)); } - inline std::u16string get_val_from_db(sqlite3_value *value, result_type) { + inline std::u16string get_val_from_db(sqlite3_value *value, result_type) { return sqlite3_value_type(value) == SQLITE_NULL ? std::u16string() : std::u16string(reinterpret_cast(sqlite3_value_text16(value)), sqlite3_value_bytes16(value)); } - inline void store_result_in_db(sqlite3_context* db, const std::u16string& val) { - sqlite3_result_text16(db, val.data(), -1, SQLITE_TRANSIENT); + inline void store_result_in_db(sqlite3_context* db, u16str_ref val) { + sqlite3_result_text16(db, val.data(), sizeof(char16_t) * val.length(), SQLITE_TRANSIENT); } // Other integer types diff --git a/hdr/sqlite_modern_cpp/utility/utf16_utf8.h b/hdr/sqlite_modern_cpp/utility/utf16_utf8.h index ea21723f..6903cd9d 100644 --- a/hdr/sqlite_modern_cpp/utility/utf16_utf8.h +++ b/hdr/sqlite_modern_cpp/utility/utf16_utf8.h @@ -8,7 +8,7 @@ namespace sqlite { namespace utility { - inline std::string utf16_to_utf8(const std::u16string &input) { + inline std::string utf16_to_utf8(u16str_ref input) { struct : std::codecvt { } codecvt; std::mbstate_t state{}; diff --git a/tests/flags.cc b/tests/flags.cc index 69b06641..44543151 100644 --- a/tests/flags.cc +++ b/tests/flags.cc @@ -13,8 +13,9 @@ struct TmpFile { TmpFile(): fname("./flags.db") { } ~TmpFile() { remove(fname.c_str()); } }; - -#if BYTE_ORDER == BIG_ENDIAN +#ifdef _WIN32 +#define OUR_UTF16 "UTF-16le" +#elif BYTE_ORDER == BIG_ENDIAN #define OUR_UTF16 "UTF-16be" #else #define OUR_UTF16 "UTF-16le" diff --git a/tests/string_view.cc b/tests/string_view.cc new file mode 100644 index 00000000..ecd8d251 --- /dev/null +++ b/tests/string_view.cc @@ -0,0 +1,26 @@ +#include +#include +#include +#include + + +#ifdef MODERN_SQLITE_STRINGVIEW_SUPPORT +#include + +using namespace sqlite; +using namespace std; +TEST_CASE("std::string_view works", "[string_view]") { + database db(":memory:");; + db << "CREATE TABLE foo (a integer, b string);\n"; + const std::string_view test1 = "null terminated string view"; + db << "INSERT INTO foo VALUES (?, ?)" << 1 << test1; + std::string str; + db << "SELECT b from FOO where a=?;" << 1 >> str; + REQUIRE(test1 == str); + const char s[] = "hello world"; + std::string_view test2(&s[0], 2); + db << "INSERT INTO foo VALUES (?,?)" << 2 << test2; + db << "SELECT b from FOO where a=?" << 2 >> str; + REQUIRE(str == "he"); +} +#endif \ No newline at end of file