#pragma once #include "ILog.h" // this is pure virtual interface base class between sqlite3, which speaks only C and utf8 char[] // and wxWidgets which speaks only C++ and unicode strings. // Usage: Call the factory function std::shared_ptr sqlite3_open(const char *) to get a shared // pointer to the // Sqlite3 database object. Then call the factory function // sqlite3_prepare(std::shared_ptr, const char *) to get a unique pointer to // a compiled SQL statement // Its primary purpose is to avoid code that needs both the wxWidgets header files, // and the sqlite3.h header file. // // It speaks only utf8 char[], and needs to be called in wxWidgets code using // wxString.utf8_str() and its return values need to be interpreted in wxWidgets code // using wxString::FromUTF8(). // // This header file can be included in code that has the sqlite3.h header file // and in code that has the wxWidgets header file, for it has no dependencies on either one // // In code that has wxWidgets headers, we call members of this interface class, // rather than directly calling sqlite3 functions. // // I originally implemented the pimpl idiom, but it turns out that pimpl has become // substantially more difficult in C++14, because one is effectively rolling one's own // unique pointer. // // It is therefore easier to implement a pure virtual base class with a virtual destructor and // factory function that returns a smart pointer to a member of the derived implementation // /* This code is at a low level abstraction, because it provides low level C++ interface to inherently low level C It is intended to be wrapped in higher level code that does not know about the nuts and bolts of sqlite3, but which supports throwing, templated functions, and all that.*/ // //___________________________________ // This class wraps a compiled sql statement. class Icompiled_sql { protected: Icompiled_sql() = default; // needed for derived constructor public: virtual ~Icompiled_sql() = default; // needed for derived destructor // Bind is used when writing stuff into the database. These objects should continue to exist until the write is finalized or reset. virtual void Isqlite3_bind( int, const std::span) = 0; // https://sqlite.org/c3ref/bind.html virtual void Isqlite3_bind(int, int) = 0; virtual void Isqlite3_bind(int, int64_t) = 0; virtual void Isqlite3_bind(int) = 0; virtual void Isqlite3_bind(int, const char*) = 0; enum sql_result { DONE, ROW, BUSY, SQL_ERROR, MISUSE }; virtual sql_result Isqlite3_step() = 0; // when reading, you don't use bind. Sqlite creates a temporary in the memory that it manages. If you want the object to live beyond the next step operation, need to make a copy // When writing objects, we reinterpret a pointer to a typed object as a blob pointer, when reading them, we need a typed copy, otherwise calling the destructor could be bad. // We don't want Sqlite3 calling destructors on our objects, hence write them as static, and create them from raw bytes on reading. virtual std::span Isqlite3_column_blob (int) const = 0; // returns the null pointer and zero length if null. virtual int Isqlite3_column_int (int) const = 0; virtual int64_t Isqlite3_column_int64 (int) const = 0; virtual char* Isqlite3_column_text (int) const = 0; // returns pointer to zero length // string if null. If we need to distinguish betweem zero length strings and nulls, need the // type function. // We can store any type in any column, and read any type from any column, but if something // unexpected is in a column, it gets coerced to the expected type on being read back. // Thus something stored as a number and read back as blob will come back as the decimal character string. // It is very rarely valid to store different types in the same column, except that // null is permissible. The difference between null and zero matters, but the case of // null is usually dealt with by sql code, not C code. virtual void Isqlite3_reset() = 0; // https://sqlite.org/c3ref/reset.html }; //___________________________________ // This class wraps a database. Its derived implementation will hold an old type C pointer // to an opened database object, which is destroyed when the class object is destroyed class ISqlite3 { protected: ISqlite3() = default; // needed for derived constructor public: virtual ~ISqlite3() = default; // needed for derived destructor virtual void exec(const char*) = 0; }; // Factory method to open a database and produce a shared object wrapping the database ISqlite3* Sqlite3_open(const char*); // Factory method to create a database and produce a shared object wrapping the database ISqlite3* Sqlite3_create(const char*); // Factory method to prepare a compiled sql statement Icompiled_sql* sqlite3_prepare(ISqlite3*, const char *); void sqlite3_init(); extern "C" { int sqlite3_shutdown(void); }