database.cpp (6122B)
1 #include "helper.hpp" 2 3 #include <database.hpp> 4 5 #include <errors.hpp> 6 7 #include <sqlite3.h> 8 9 static const char *M = "[database]"; 10 11 TEST_CASE("open bad sqlite file path", M) { 12 try { 13 Database db("/"); 14 FAIL(); 15 } 16 catch(const reapack_error &e) { 17 REQUIRE(std::string{e.what()} == "unable to open database file"); 18 } 19 } 20 21 TEST_CASE("execute invalid sql", M) { 22 Database db; 23 24 try { 25 db.exec("WHERE"); 26 FAIL(); 27 } 28 catch(const reapack_error &e) { 29 REQUIRE(std::string{e.what()} == "near \"WHERE\": syntax error"); 30 } 31 } 32 33 TEST_CASE("prepare invalid sql", M) { 34 Database db; 35 36 try { 37 db.prepare("WHERE"); 38 FAIL(); 39 } 40 catch(const reapack_error &e) { 41 REQUIRE(std::string{e.what()} == "near \"WHERE\": syntax error"); 42 } 43 } 44 45 TEST_CASE("get rows from prepared statement", M) { 46 Database db; 47 db.exec( 48 "CREATE TABLE test (value TEXT NOT NULL);" 49 "INSERT INTO test VALUES (\"hello\");" 50 "INSERT INTO test VALUES (\"世界\");" 51 ); 52 53 std::vector<std::string> values; 54 55 Statement *stmt = db.prepare("SELECT value FROM test"); 56 57 SECTION("continue") { 58 stmt->exec([&] { 59 values.push_back(stmt->stringColumn(0)); 60 return true; 61 }); 62 63 REQUIRE(values.size() == 2); 64 REQUIRE(values[0] == "hello"); 65 REQUIRE(values[1] == "世界"); 66 } 67 68 SECTION("abort") { 69 stmt->exec([&] { 70 values.push_back(stmt->stringColumn(0)); 71 return false; 72 }); 73 74 REQUIRE(values.size() == 1); 75 REQUIRE(values[0] == "hello"); 76 } 77 } 78 79 TEST_CASE("bind values and clear", M) { 80 Database db; 81 db.exec("CREATE TABLE test (value TEXT NOT NULL)"); 82 83 Statement *stmt = db.prepare("INSERT INTO test VALUES (?)"); 84 stmt->bind(1, "hello"); 85 stmt->exec(); 86 87 try { 88 stmt->exec(); 89 FAIL("bindings not cleared"); 90 } 91 catch(const reapack_error &e) { 92 REQUIRE(std::string{e.what()} == "NOT NULL constraint failed: test.value"); 93 } 94 } 95 96 TEST_CASE("database version", M) { 97 Database db; 98 REQUIRE(db.version().major == 0); 99 REQUIRE(db.version().minor == 0); 100 REQUIRE_FALSE(db.version()); 101 REQUIRE_FALSE(db.version() < (Database::Version{0, 0})); 102 REQUIRE(db.version() < (Database::Version{0, 1})); 103 REQUIRE(db.version() < (Database::Version{1, 0})); 104 105 db.setVersion({0, 1}); 106 REQUIRE(db.version().major == 0); 107 REQUIRE(db.version().minor == 1); 108 REQUIRE(db.version()); 109 110 db.setVersion({1, 2}); 111 REQUIRE(db.version() < (Database::Version{2, 3})); 112 REQUIRE_FALSE(db.version() < (Database::Version{1, 1})); 113 REQUIRE_FALSE(db.version() < (Database::Version{0, 3})); 114 115 db.setVersion({32767, 32767}); 116 REQUIRE(db.version().major == 32767); 117 REQUIRE(db.version().minor == 32767); 118 } 119 120 TEST_CASE("foreign keys", M) { 121 Database db; 122 db.exec( 123 "CREATE TABLE a (id INTEGER PRIMARY KEY);" 124 125 "CREATE TABLE b (" 126 " two INTEGER NOT NULL," 127 " FOREIGN KEY(two) REFERENCES a(id)" 128 ");" 129 130 "INSERT INTO a VALUES(NULL);" 131 "INSERT INTO b VALUES(1);" 132 ); 133 134 try { 135 db.exec("DELETE FROM a"); 136 FAIL("foreign keys checks are disabled"); 137 } 138 catch(const reapack_error &) {} 139 } 140 141 TEST_CASE("last insert id", M) { 142 Database db; 143 db.exec("CREATE TABLE a(text TEXT)"); 144 145 Statement *insert = db.prepare("INSERT INTO a VALUES(NULL)"); 146 147 REQUIRE(db.lastInsertId() == 0); 148 149 insert->exec(); 150 REQUIRE(db.lastInsertId() == 1); 151 152 insert->exec(); 153 REQUIRE(db.lastInsertId() == 2); 154 } 155 156 TEST_CASE("bind temporary strings", M) { 157 Database db; 158 db.exec("CREATE TABLE a(text TEXT NOT NULL)"); 159 160 Statement *insert = db.prepare("INSERT INTO a VALUES(?)"); 161 162 std::string str("hello"); 163 insert->bind(1, str); 164 str = "world"; 165 166 insert->exec(); 167 168 std::string got; 169 Statement *select = db.prepare("SELECT text FROM a LIMIT 1"); 170 select->exec([&] { 171 got = select->stringColumn(0); 172 return false; 173 }); 174 175 REQUIRE(got == "hello"); 176 } 177 178 TEST_CASE("get integers from sqlite", M) { 179 Database db; 180 db.exec("CREATE TABLE a(test INTEGER NOT NULL)"); 181 182 Statement *insert = db.prepare("INSERT INTO a VALUES(?)"); 183 184 insert->bind(1, 2147483647); 185 insert->exec(); 186 insert->bind(1, 4294967295); 187 insert->exec(); 188 189 std::vector<sqlite3_int64> signedVals; 190 Statement *select = db.prepare("SELECT test FROM a"); 191 select->exec([&] { 192 signedVals.push_back(select->intColumn(0)); 193 return true; 194 }); 195 196 CHECK(signedVals.size() == 2); 197 REQUIRE(signedVals[0] == 2147483647); 198 REQUIRE(signedVals[1] == 4294967295); 199 } 200 201 TEST_CASE("sqlite error code", M) { 202 Database db; 203 db.exec("CREATE TABLE a(b INTEGER UNIQUE); INSERT INTO a VALUES(1)"); 204 REQUIRE(db.errorCode() == SQLITE_OK); 205 206 try { 207 db.exec("INSERT INTO a VALUES(1)"); 208 } 209 catch(const reapack_error &) {} 210 211 REQUIRE(db.errorCode() == SQLITE_CONSTRAINT); 212 } 213 214 TEST_CASE("invalid string column", M) { 215 Database db; 216 db.exec("CREATE TABLE a(text TEXT NOT NULL)"); 217 218 Statement *insert = db.prepare("INSERT INTO a VALUES(?)"); 219 insert->bind(1, "hello"); 220 insert->exec(); 221 222 Statement *select = db.prepare("SELECT text FROM a LIMIT 1"); 223 select->exec([&] { 224 REQUIRE(select->stringColumn(4242).empty()); // don't crash! 225 return false; 226 }); 227 } 228 229 TEST_CASE("database transaction locking", M) { 230 Database db; 231 db.begin(); 232 233 try { 234 db.begin(); 235 FAIL("created a transaction within a transaction"); 236 } 237 catch(const reapack_error &) {} 238 239 db.commit(); 240 db.begin(); 241 } 242 243 TEST_CASE("save points", M) { 244 Database db; 245 db.exec("CREATE TABLE test (value INTEGER NOT NULL);"); 246 247 Statement *insert = db.prepare("INSERT INTO test VALUES (1);"); 248 Statement *select = db.prepare("SELECT COUNT(*) FROM test"); 249 250 auto count = [select] { 251 int64_t count = -255; 252 select->exec([&] { count = select->intColumn(0); return false; }); 253 return count; 254 }; 255 256 db.savepoint(); 257 258 insert->exec(); 259 REQUIRE(count() == 1); 260 261 SECTION("rollback to savepoint") { 262 db.restore(); 263 REQUIRE(count() == 0); 264 try { db.restore(); FAIL("rolled back unexistant savepoint"); } 265 catch(const reapack_error &) {} 266 } 267 268 SECTION("release savepoint") { 269 db.release(); 270 REQUIRE(count() == 1); 271 try { db.release(); FAIL("released unexistant savepoint"); } 272 catch(const reapack_error &) {} 273 } 274 }