|
| 1 | +#include <memory> |
| 2 | +#include <cstdlib> |
| 3 | +#include <iostream> |
| 4 | +#include <sqlite3.h> |
| 5 | + |
| 6 | + template< bool B, class T = void > |
| 7 | + using enable_if_t = typename std::enable_if<B,T>::type; |
| 8 | + |
| 9 | +// |
| 10 | +// not_null |
| 11 | +// borrowed from GLS, https://github.com/Microsoft/GSL |
| 12 | +// backported to copmile with gcc 4.8 on RHEL 6 |
| 13 | +// |
| 14 | +// Restricts a pointer or smart pointer to only hold non-null values. |
| 15 | +// |
| 16 | +// Has zero size overhead over T. |
| 17 | +// |
| 18 | +// If T is a pointer (i.e. T == U*) then |
| 19 | +// - allow construction from U* or U& |
| 20 | +// - disallow construction from nullptr_t |
| 21 | +// - disallow default construction |
| 22 | +// - ensure construction from U* fails with nullptr |
| 23 | +// - allow implicit conversion to U* |
| 24 | +// |
| 25 | +template <class T> |
| 26 | +class not_null |
| 27 | +{ |
| 28 | + static_assert(std::is_assignable<T&, std::nullptr_t>::value, "T cannot be assigned nullptr."); |
| 29 | + |
| 30 | +public: |
| 31 | + not_null(T t) : ptr_(t) { ensure_invariant(); } |
| 32 | + not_null& operator=(const T& t) |
| 33 | + { |
| 34 | + ptr_ = t; |
| 35 | + ensure_invariant(); |
| 36 | + return *this; |
| 37 | + } |
| 38 | + |
| 39 | + not_null(const not_null& other) = default; |
| 40 | + not_null& operator=(const not_null& other) = default; |
| 41 | + |
| 42 | + template <typename U, typename Dummy = enable_if_t<std::is_convertible<U, T>::value>> |
| 43 | + not_null(const not_null<U>& other) |
| 44 | + { |
| 45 | + *this = other; |
| 46 | + } |
| 47 | + |
| 48 | + template <typename U, typename Dummy = enable_if_t<std::is_convertible<U, T>::value>> |
| 49 | + not_null& operator=(const not_null<U>& other) |
| 50 | + { |
| 51 | + ptr_ = other.get(); |
| 52 | + return *this; |
| 53 | + } |
| 54 | + |
| 55 | + // prevents compilation when someone attempts to assign a nullptr |
| 56 | + not_null(std::nullptr_t) = delete; |
| 57 | + not_null(int) = delete; |
| 58 | + not_null<T>& operator=(std::nullptr_t) = delete; |
| 59 | + not_null<T>& operator=(int) = delete; |
| 60 | + |
| 61 | + T get() const |
| 62 | + { |
| 63 | +#ifdef _MSC_VER |
| 64 | + __assume(ptr_ != nullptr); |
| 65 | +#endif |
| 66 | + return ptr_; |
| 67 | + } // the assume() should help the optimizer |
| 68 | + |
| 69 | + operator T() const { return get(); } |
| 70 | + T operator->() const { return get(); } |
| 71 | + |
| 72 | + bool operator==(const T& rhs) const { return ptr_ == rhs; } |
| 73 | + bool operator!=(const T& rhs) const { return !(*this == rhs); } |
| 74 | +private: |
| 75 | + T ptr_; |
| 76 | + |
| 77 | + // we assume that the compiler can hoist/prove away most of the checks inlined from this |
| 78 | + // function |
| 79 | + // if not, we could make them optional via conditional compilation |
| 80 | + void ensure_invariant() const { Ensure(ptr_ != nullptr); } |
| 81 | + |
| 82 | + // tmpfix, until inlcude the defaultu assing |
| 83 | + void Ensure(bool flag) const { if(not flag) throw "Ensure failed" ;} |
| 84 | + |
| 85 | + // unwanted operators...pointers only point to single objects! |
| 86 | + // TODO ensure all arithmetic ops on this type are unavailable |
| 87 | + not_null<T>& operator++() = delete; |
| 88 | + not_null<T>& operator--() = delete; |
| 89 | + not_null<T> operator++(int) = delete; |
| 90 | + not_null<T> operator--(int) = delete; |
| 91 | + not_null<T>& operator+(size_t) = delete; |
| 92 | + not_null<T>& operator+=(size_t) = delete; |
| 93 | + not_null<T>& operator-(size_t) = delete; |
| 94 | + not_null<T>& operator-=(size_t) = delete; |
| 95 | +}; |
| 96 | + |
| 97 | +using database = std::unique_ptr<sqlite3, decltype(&sqlite3_close)> ; |
| 98 | + |
| 99 | +database open_database(const char* name) |
| 100 | +{ |
| 101 | + sqlite3* db = nullptr; |
| 102 | + auto rc = sqlite3_open (name, &db); |
| 103 | + if(rc != SQLITE_OK) { |
| 104 | + std::cerr << "Unable to open database '" << name << "': " |
| 105 | + << sqlite3_errmsg (db); |
| 106 | + sqlite3_close (db); |
| 107 | + std::exit(EXIT_FAILURE); |
| 108 | + } |
| 109 | + return database{db, sqlite3_close} ; |
| 110 | +} |
| 111 | + |
| 112 | +void execute (not_null<sqlite3*> db, const char* sql) |
| 113 | +{ |
| 114 | + char* errmsg = 0; |
| 115 | + int rc = sqlite3_exec (db, sql, 0, 0, &errmsg); |
| 116 | + if (rc != SQLITE_OK) { |
| 117 | + std::cerr << "Unable to execute '" << sql << "': " |
| 118 | + << errmsg ; |
| 119 | + sqlite3_free(errmsg) ; |
| 120 | + std::exit(EXIT_FAILURE); |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | + |
| 125 | +using statement = std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> ; |
| 126 | + |
| 127 | +statement create_statement(not_null<sqlite3*> db, const std::string& sql) |
| 128 | +{ |
| 129 | + sqlite3_stmt* stmt = nullptr; |
| 130 | + int rc = sqlite3_prepare_v2 (db, |
| 131 | + sql.c_str (), sql.length(), |
| 132 | + &stmt, nullptr); |
| 133 | + if (rc != SQLITE_OK) { |
| 134 | + std::cerr << "Unable to create statement '" << sql << "': " |
| 135 | + << sqlite3_errmsg(db); |
| 136 | + std::exit(EXIT_FAILURE); |
| 137 | + } |
| 138 | + return statement(stmt, sqlite3_finalize); |
| 139 | +} |
| 140 | + |
| 141 | + |
| 142 | +using stmt_callback = |
| 143 | + std::function<bool(not_null<sqlite3_stmt*>)> ; |
| 144 | + |
| 145 | +void run(not_null<sqlite3_stmt*> stmt, |
| 146 | + stmt_callback callback = stmt_callback{}) |
| 147 | +{ |
| 148 | + using reset_guard |
| 149 | + = std::unique_ptr<sqlite3_stmt, decltype (&sqlite3_reset)>; |
| 150 | + |
| 151 | + auto reset = reset_guard (stmt.get(), &sqlite3_reset); |
| 152 | + |
| 153 | + auto step_next = [&](int rc){ |
| 154 | + if (rc == SQLITE_OK || rc == SQLITE_DONE) |
| 155 | + return false ; |
| 156 | + else if (rc == SQLITE_ROW) |
| 157 | + if(callback) |
| 158 | + return callback(stmt); |
| 159 | + // else ... some error handling |
| 160 | + return false ; |
| 161 | + }; |
| 162 | + |
| 163 | + while(step_next(sqlite3_step(stmt))) ; |
| 164 | +} |
| 165 | + |
| 166 | + |
| 167 | + |
| 168 | + |
| 169 | +bool dump_current_row(not_null<sqlite3_stmt*> stmt) |
| 170 | +{ |
| 171 | + for (int i = 0 ; i < sqlite3_column_count(stmt); ++i) { |
| 172 | + auto columntype = sqlite3_column_type(stmt, i) ; |
| 173 | + |
| 174 | + if(columntype == SQLITE_NULL) { |
| 175 | + std::cout << "<NULL>" ; |
| 176 | + } |
| 177 | + else if (columntype == SQLITE_INTEGER){ |
| 178 | + std::cout << sqlite3_column_int64(stmt, i); |
| 179 | + } |
| 180 | + else if (columntype == SQLITE_FLOAT){ |
| 181 | + std::cout << sqlite3_column_double(stmt, i) ; |
| 182 | + } |
| 183 | + else if (columntype == SQLITE_TEXT ){ |
| 184 | + auto first = sqlite3_column_text (stmt, i); |
| 185 | + std::size_t s = sqlite3_column_bytes (stmt, i); |
| 186 | + std::cout << "'" << (s > 0 ? |
| 187 | + std::string((const char*)first, s) : "") << "'"; |
| 188 | + } |
| 189 | + else if (columntype == SQLITE_BLOB ){ |
| 190 | + std::cout << "<BLO000B>" ; |
| 191 | + } |
| 192 | + std::cout << "|" ; |
| 193 | + } |
| 194 | + std::cout << "\n" ; |
| 195 | + return true ; |
| 196 | +} |
| 197 | + |
| 198 | + |
| 199 | +bool print_thing(not_null<sqlite3_stmt*> stmt) { |
| 200 | + |
| 201 | + auto id = [&](){return sqlite3_column_int64(stmt, 0);} ; |
| 202 | + |
| 203 | + auto name = [&](){ auto first = sqlite3_column_text (stmt, 1); |
| 204 | + std::size_t s = sqlite3_column_bytes (stmt, 1); |
| 205 | + return s > 0 ? std::string ((const char*)first, s) |
| 206 | + : std::string{}; |
| 207 | + }; |
| 208 | + auto value = [&]() {return sqlite3_column_double(stmt, 2);}; |
| 209 | + |
| 210 | + std::cout << id() << ", " << name() << ", " << value() << std::endl; |
| 211 | + return true ; |
| 212 | +} |
| 213 | + |
| 214 | + |
| 215 | + |
| 216 | +int64_t key(not_null<sqlite3_stmt*> stmt) |
| 217 | +{ |
| 218 | + return sqlite3_column_int64(stmt, 0) ; |
| 219 | +} |
| 220 | + |
| 221 | +std::string value(not_null<sqlite3_stmt*> stmt) |
| 222 | +{ |
| 223 | + const char* first = (const char*)sqlite3_column_text (stmt, 1); |
| 224 | + std::size_t s = sqlite3_column_bytes (stmt, 1); |
| 225 | + return s > 0 ? std::string (first, s) : std::string{}; |
| 226 | +} |
| 227 | + |
| 228 | + |
| 229 | +void parameter(not_null<sqlite3_stmt*> stmt, int index, int64_t value) |
| 230 | +{ |
| 231 | + auto rc = sqlite3_bind_int64 (stmt, index, value); |
| 232 | + if (rc != SQLITE_OK) throw "TODO" ; |
| 233 | +} |
| 234 | + |
| 235 | +void parameter(not_null<sqlite3_stmt*> stmt, int index, double value) |
| 236 | +{ |
| 237 | + auto rc = sqlite3_bind_double (stmt, index, value); |
| 238 | + if (rc != SQLITE_OK) throw "TODO" ; |
| 239 | +} |
| 240 | + |
| 241 | +// real the same |
| 242 | +void parameter(not_null<sqlite3_stmt*> stmt, |
| 243 | + int index, |
| 244 | + const std::string& value) |
| 245 | +{ |
| 246 | + auto rc = sqlite3_bind_text (stmt.get(), index, |
| 247 | + value.c_str (), value.size (), |
| 248 | + SQLITE_TRANSIENT); |
| 249 | + |
| 250 | + if (rc != SQLITE_OK) throw "TODO" ; |
| 251 | +} |
| 252 | +// blob the same, SQLITE_STATIC/TRANSIENT copy + owner |
| 253 | + |
| 254 | + |
| 255 | + |
| 256 | +struct Transaction |
| 257 | +{ |
| 258 | + Transaction(not_null<sqlite3*> db) : _db{db}{ |
| 259 | + execute(_db, "BEGIN TRANSACTION;") ; |
| 260 | + } |
| 261 | + ~Transaction() { |
| 262 | + if(_db) execute(_db, "ROLLBACK TRANSACTION;") ; |
| 263 | + } |
| 264 | + void commit() { |
| 265 | + if(_db) execute(_db, "COMMIT TRANSACTION;") ; |
| 266 | + _db = nullptr ; |
| 267 | + } |
| 268 | + |
| 269 | + Transaction (Transaction&&) = default ; |
| 270 | + |
| 271 | + Transaction (Transaction&) = delete ; |
| 272 | + Transaction& operator=(Transaction&) = delete ; |
| 273 | + Transaction& operator=(Transaction&&) = delete ; |
| 274 | + |
| 275 | +private: sqlite3* _db ; |
| 276 | +}; |
| 277 | + |
| 278 | +constexpr const char* create_things() |
| 279 | +{ |
| 280 | + return R"~(BEGIN TRANSACTION ; |
| 281 | + CREATE TABLE things(id INTEGER PRIMARY KEY, name TEXT,value REAL); |
| 282 | + INSERT INTO things VALUES(1,'one', 1.1); |
| 283 | + INSERT INTO things VALUES(2,'two', 2.2); |
| 284 | + COMMIT TRANSACTION ; |
| 285 | + )~"; |
| 286 | +} |
| 287 | + |
| 288 | + |
| 289 | + |
| 290 | +statement create_things2(not_null<sqlite3*> db) { |
| 291 | + Transaction transaction(db) ; |
| 292 | + execute(db, R"~(CREATE TABLE things |
| 293 | + (id INTEGER PRIMARY KEY, name TEXT,value REAL); )~"); |
| 294 | + |
| 295 | + auto insert_thing = create_statement(db, |
| 296 | + "INSERT INTO things VALUES(@id,@name,@value);"); |
| 297 | + // create the identity thing |
| 298 | + parameter(insert_thing.get(), 1, int64_t{0}) ; |
| 299 | + parameter(insert_thing.get(), 2, "") ; |
| 300 | + parameter(insert_thing.get(), 3, double{0.0}) ; |
| 301 | + run (insert_thing.get()) ; |
| 302 | + transaction.commit() ; |
| 303 | + // return createor |
| 304 | + return insert_thing ; |
| 305 | +} |
| 306 | + |
| 307 | + |
| 308 | +void main1() |
| 309 | +{ |
| 310 | + auto db = open_database(":memory:"); |
| 311 | + auto add_thing = create_things2(db.get()); |
| 312 | + { Transaction transaction(db.get()) ; |
| 313 | + parameter(add_thing.get(), 1, int64_t{1}) ; |
| 314 | + parameter(add_thing.get(), 2, "first") ; |
| 315 | + parameter(add_thing.get(), 3, "second") ; // Mistake !! |
| 316 | + run(add_thing.get()); |
| 317 | + transaction.commit() ; |
| 318 | + } |
| 319 | + auto stmt = create_statement(db.get(), "SELECT * FROM things;"); |
| 320 | + run(stmt.get(), dump_current_row); |
| 321 | + run (stmt.get(), print_thing); |
| 322 | +} |
| 323 | + |
| 324 | + |
| 325 | +int main() |
| 326 | +{ |
| 327 | + main1(); |
| 328 | +} |
| 329 | + |
0 commit comments