include/simdb/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
113 / 116
97.4%
Branches:
101 / 151
66.9%
Functions:
16 / 16
100.0%

sqlite/Connection.hpp
Line Branch Exec Source
1 // <Connection.hpp> -*- C++ -*-
2
3 #pragma once
4
5 #include "simdb/Assert.hpp"
6 #include "simdb/schema/SchemaDef.hpp"
7 #include "simdb/sqlite/Constraints.hpp"
8 #include "simdb/sqlite/Transaction.hpp"
9 #include "simdb/utils/FloatCompare.hpp"
10
11 #include <fstream>
12 #include <memory>
13 #include <sqlite3.h>
14 #include <string>
15
16 namespace simdb {
17
18 /// Callback which gets invoked during SELECT queries that involve
19 /// floating point comparisons with a supplied tolerance.
20 348 inline void fuzzyMatch(sqlite3_context* context, int, sqlite3_value** argv)
21 {
22
1/1
✓ Branch 2 → 3 taken 348 times.
348 const double column_value = sqlite3_value_double(argv[0]);
23
1/1
✓ Branch 3 → 4 taken 348 times.
348 const double target_value = sqlite3_value_double(argv[1]);
24
1/1
✓ Branch 4 → 5 taken 348 times.
348 const int constraint = sqlite3_value_int(argv[2]);
25 static constexpr double tolerance = std::numeric_limits<double>::epsilon();
26
27
1/12
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 24 taken 348 times.
✗ Branch 8 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 22 → 23 not taken.
348 simdb_assert(constraint < static_cast<int>(SetConstraints::IN_SET),
28 "Invalid constraint in fuzzyMatch(). Should be Constraints enum.");
29
30 348 const Constraints e_constraint = static_cast<Constraints>(constraint);
31
32
2/2
✓ Branch 2 → 3 taken 180 times.
✓ Branch 2 → 4 taken 168 times.
348 auto set_is_match = [context](const bool match) { sqlite3_result_int(context, match ? 1 : 0); };
33
34 314 auto check_equal = [=](const bool should_be_equal) {
35 314 const bool approx_equal = approximatelyEqual(column_value, target_value, tolerance);
36
2/2
✓ Branch 3 → 4 taken 160 times.
✓ Branch 3 → 5 taken 154 times.
314 if (approx_equal == should_be_equal)
37 {
38 160 set_is_match(true);
39 } else
40 {
41 154 set_is_match(false);
42 }
43 662 };
44
45
6/8
✓ Branch 24 → 25 taken 156 times.
✓ Branch 24 → 27 taken 144 times.
✓ Branch 24 → 29 taken 12 times.
✓ Branch 24 → 31 taken 12 times.
✓ Branch 24 → 36 taken 12 times.
✓ Branch 24 → 38 taken 12 times.
✗ Branch 24 → 42 not taken.
✗ Branch 24 → 46 not taken.
348 switch (e_constraint)
46 {
47 156 case Constraints::EQUAL: {
48
1/1
✓ Branch 25 → 26 taken 156 times.
156 check_equal(true);
49 156 break;
50 }
51 144 case Constraints::NOT_EQUAL: {
52
1/1
✓ Branch 27 → 28 taken 144 times.
144 check_equal(false);
53 144 break;
54 }
55 12 case Constraints::LESS: {
56
1/1
✓ Branch 29 → 30 taken 12 times.
12 set_is_match(column_value < target_value);
57 12 break;
58 }
59 12 case Constraints::LESS_EQUAL: {
60
2/2
✓ Branch 31 → 32 taken 8 times.
✓ Branch 31 → 34 taken 4 times.
12 if (column_value < target_value)
61 {
62
1/1
✓ Branch 32 → 33 taken 8 times.
8 set_is_match(true);
63 8 break;
64 } else
65 {
66
1/1
✓ Branch 34 → 35 taken 4 times.
4 check_equal(true);
67 }
68 4 break;
69 }
70 12 case Constraints::GREATER: {
71
1/1
✓ Branch 36 → 37 taken 12 times.
12 set_is_match(column_value > target_value);
72 12 break;
73 }
74 12 case Constraints::GREATER_EQUAL: {
75
2/2
✓ Branch 38 → 39 taken 2 times.
✓ Branch 38 → 40 taken 10 times.
12 if (column_value > target_value)
76 {
77
1/1
✓ Branch 39 → 41 taken 2 times.
2 set_is_match(true);
78 } else
79 {
80
1/1
✓ Branch 40 → 41 taken 10 times.
10 check_equal(true);
81 }
82 12 break;
83 }
84 ✗ case Constraints::__NUM_CONSTRAINTS__: {
85
0/1
✗ Branch 44 → 45 not taken.
✗ throw DBException("Invalid constraint in fuzzyMatch()");
86 }
87 }
88 348 }
89
90 /*!
91 * \class Connection
92 *
93 * \brief This class instantiates the SQLite schema and issues database
94 * commands.
95 */
96 class Connection : public Transaction
97 {
98 public:
99 /// Close the sqlite3 connection.
100 56 ~Connection()
101 28 {
102
2/2
✓ Branch 2 → 3 taken 27 times.
✓ Branch 2 → 9 taken 1 time.
28 if (db_conn_)
103 {
104
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 9 taken 27 times.
27 if (sqlite3_close(db_conn_) != SQLITE_OK)
105 {
106 // Failed to close the database connection. Not much we can do
107 // here.
108 ✗ std::cerr << "Warning: Failed to close database connection: " << sqlite3_errmsg(db_conn_) << std::endl;
109 }
110 }
111 56 }
112
113 /// Instantiate tables, columns, indexes, etc. on the sqlite3 connection.
114 60 void realizeSchema(const Schema& schema)
115 {
116
1/1
✓ Branch 4 → 5 taken 60 times.
60 safeTransaction([&]() {
117
2/2
✓ Branch 30 → 5 taken 165 times.
✓ Branch 30 → 31 taken 60 times.
225 for (const auto& table : schema.getTables())
118 {
119 // First create the table and its columns
120
1/1
✓ Branch 6 → 7 taken 165 times.
165 std::ostringstream oss;
121
3/3
✓ Branch 7 → 8 taken 165 times.
✓ Branch 9 → 10 taken 165 times.
✓ Branch 10 → 11 taken 165 times.
165 oss << "CREATE TABLE IF NOT EXISTS " << table.getName() << "(";
122
123 // Fill in the rest of the CREATE TABLE command
124
3/3
✓ Branch 11 → 12 taken 165 times.
✓ Branch 12 → 13 taken 165 times.
✓ Branch 13 → 14 taken 165 times.
165 oss << getColumnsSqlCommand_(table) << ");";
125
126 // Create the table in the database
127
2/2
✓ Branch 15 → 16 taken 165 times.
✓ Branch 17 → 18 taken 165 times.
165 executeCommand(oss.str());
128
129 // Now create any table indexes
130
2/2
✓ Branch 26 → 21 taken 43 times.
✓ Branch 26 → 27 taken 165 times.
208 for (const auto& cmd : table.index_creation_strs_)
131 {
132
1/1
✓ Branch 23 → 24 taken 43 times.
43 executeCommand(cmd);
133 }
134 165 }
135 60 });
136 60 }
137
138 /// Get the full database filename being used.
139 3 const std::string& getDatabaseFilePath() const { return db_filepath_; }
140
141 /// Is this connection alive and well?
142 bool isValid() const { return (db_conn_ != nullptr); }
143
144 /// Execute the provided statement against the database
145 /// connection. This will validate the command, and throw
146 /// if this command is disallowed.
147 208 void executeCommand(const std::string_view command)
148 {
149
2/2
✓ Branch 3 → 4 taken 208 times.
✓ Branch 4 → 5 taken 208 times.
208 auto rc = SQLiteReturnCode(sqlite3_exec(db_conn_, command.data(), nullptr, nullptr, nullptr));
150
1/13
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 26 taken 208 times.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 24 → 25 not taken.
208 simdb_assert(!rc, sqlite3_errmsg(db_conn_));
151 208 }
152
153 /// Turn the given command into an SQL prepared statement.
154 210586 SQLitePreparedStatement prepareStatement(const std::string_view command)
155 {
156 210586 return SQLitePreparedStatement(db_conn_, command);
157 }
158
159 /// Get the database ID of the last INSERT statement.
160 620864 int getLastInsertRowId() const { return sqlite3_last_insert_rowid(db_conn_); }
161
162 /// Get direct access to the underlying SQLite database.
163 230071 sqlite3* getDatabase() const { return db_conn_; }
164
165 private:
166 /// Private constructor. Called by friend class DatabaseManager.
167 28 Connection() {}
168
169 /// First-time database file open.
170 28 std::string openDbFile_(const std::string& db_file)
171 {
172
1/1
✓ Branch 2 → 3 taken 28 times.
28 db_filepath_ = resolveDbFilename_(db_file);
173
2/2
✓ Branch 6 → 7 taken 24 times.
✓ Branch 6 → 8 taken 4 times.
28 if (db_filepath_.empty())
174 {
175
1/1
✓ Branch 7 → 8 taken 24 times.
24 db_filepath_ = db_file;
176 }
177
178 28 const int db_open_flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE;
179 28 sqlite3* sqlite_conn = nullptr;
180
1/1
✓ Branch 9 → 10 taken 28 times.
28 auto err_code = sqlite3_open_v2(db_filepath_.c_str(), &sqlite_conn, db_open_flags, 0);
181
182
1/13
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 30 taken 28 times.
✗ Branch 13 → 14 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 28 → 29 not taken.
28 simdb_assert(err_code == SQLITE_OK, "Unable to connect to the database file: " << db_file);
183
184
3/3
✓ Branch 30 → 31 taken 28 times.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 34 taken 27 times.
28 if (!validateConnectionIsSQLite_(sqlite_conn))
185 {
186
1/1
✓ Branch 32 → 33 taken 1 time.
1 sqlite3_close(sqlite_conn);
187 1 sqlite_conn = nullptr;
188 }
189
190
2/2
✓ Branch 34 → 35 taken 27 times.
✓ Branch 34 → 38 taken 1 time.
28 if (sqlite_conn)
191 {
192 27 db_conn_ = sqlite_conn;
193
1/1
✓ Branch 35 → 36 taken 27 times.
27 sqlite3_create_function(db_conn_, "fuzzyMatch", 3, SQLITE_UTF8, nullptr, &fuzzyMatch, nullptr, nullptr);
194
1/1
✓ Branch 36 → 37 taken 27 times.
27 return db_filepath_;
195 } else
196 {
197 1 db_conn_ = nullptr;
198
1/1
✓ Branch 40 → 41 taken 1 time.
2 return "";
199 }
200 }
201
202 /// Return a string that is used as part of the CREATE TABLE command:
203 /// First TEXT, Last TEXT, Age INT, Balance REAL DEFAULT 50.00
204 165 std::string getColumnsSqlCommand_(const Table& table) const
205 {
206
1/1
✓ Branch 2 → 3 taken 165 times.
165 std::ostringstream oss;
207
208 // If the table's primary key is "Id", then add that to the schema
209 // as the first column.
210 165 const auto& pkey = table.getPrimaryKey();
211
3/3
✓ Branch 4 → 5 taken 165 times.
✓ Branch 5 → 6 taken 142 times.
✓ Branch 5 → 7 taken 23 times.
165 if (pkey == "Id")
212 {
213
1/1
✓ Branch 6 → 7 taken 142 times.
142 oss << "Id INTEGER PRIMARY KEY AUTOINCREMENT, ";
214 }
215
216 165 const auto& columns = table.getColumns();
217
2/2
✓ Branch 59 → 9 taken 470 times.
✓ Branch 59 → 60 taken 165 times.
635 for (size_t idx = 0; idx < columns.size(); ++idx)
218 {
219 470 auto column = columns[idx];
220
3/3
✓ Branch 13 → 14 taken 470 times.
✓ Branch 14 → 15 taken 470 times.
✓ Branch 17 → 18 taken 470 times.
470 oss << column->getName() << " " << column->getDataType();
221
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 35 taken 469 times.
470 if (column->getName() == pkey)
222 {
223
2/10
✓ Branch 22 → 23 taken 1 time.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 34 taken 1 time.
✗ Branch 25 → 26 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 32 → 33 not taken.
1 simdb_assert(pkey != "Id");
224
1/1
✓ Branch 34 → 35 taken 1 time.
1 oss << " PRIMARY KEY";
225 }
226
2/2
✓ Branch 37 → 38 taken 65 times.
✓ Branch 37 → 42 taken 405 times.
470 if (column->hasDefaultValue())
227 {
228
2/2
✓ Branch 38 → 39 taken 65 times.
✓ Branch 41 → 42 taken 65 times.
65 oss << " DEFAULT " << column->getDefaultValueAsString();
229 }
230
5/6
✓ Branch 44 → 45 taken 21 times.
✓ Branch 44 → 50 taken 449 times.
✓ Branch 48 → 49 taken 21 times.
✗ Branch 48 → 50 not taken.
✓ Branch 51 → 52 taken 21 times.
✓ Branch 51 → 53 taken 449 times.
470 if (column->isUnique() && column->getName() != pkey)
231 {
232
1/1
✓ Branch 52 → 53 taken 21 times.
21 oss << " UNIQUE";
233 }
234
2/2
✓ Branch 54 → 55 taken 305 times.
✓ Branch 54 → 56 taken 165 times.
470 if (idx != columns.size() - 1)
235 {
236
1/1
✓ Branch 55 → 56 taken 305 times.
305 oss << ", ";
237 }
238 470 }
239
240
1/1
✓ Branch 60 → 61 taken 165 times.
330 return oss.str();
241 165 }
242
243 // See if there is an existing file by the name <dir/file>
244 // and return it. If not, return just <file> if it exists.
245 // Return "" if neither could be found.
246 28 std::string resolveDbFilename_(const std::string& db_file) const
247 {
248
1/1
✓ Branch 2 → 3 taken 28 times.
28 std::ifstream fin(db_file);
249
7/9
✓ Branch 3 → 4 taken 28 times.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 24 times.
✓ Branch 5 → 9 taken 4 times.
✓ Branch 8 → 9 taken 24 times.
✓ Branch 9 → 10 taken 24 times.
✓ Branch 9 → 12 taken 4 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 18 not taken.
80 return fin.good() ? db_file : "";
250 28 }
251
252 /// Attempt to run an SQL command against our open connection.
253 28 bool validateConnectionIsSQLite_(sqlite3* db_conn)
254 {
255 28 const auto command = "SELECT name FROM sqlite_master WHERE type='table'";
256
2/2
✓ Branch 2 → 3 taken 28 times.
✓ Branch 3 → 4 taken 28 times.
28 auto rc = SQLiteReturnCode(sqlite3_exec(db_conn, command, nullptr, nullptr, nullptr));
257 56 return rc == SQLITE_OK;
258 }
259
260 /// Filename of the database in use
261 std::string db_filepath_;
262
263 friend class DatabaseManager;
264 };
265
266 } // namespace simdb
267