sqlite/Query.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // <Query.hpp> -*- C++ -*- | ||
| 2 | |||
| 3 | #pragma once | ||
| 4 | |||
| 5 | #include "simdb/Assert.hpp" | ||
| 6 | #include "simdb/sqlite/Constraints.hpp" | ||
| 7 | #include "simdb/sqlite/Iterator.hpp" | ||
| 8 | #include <iomanip> | ||
| 9 | #include <iostream> | ||
| 10 | #include <limits> | ||
| 11 | |||
| 12 | namespace simdb { | ||
| 13 | |||
| 14 | /// Used in query->orderBy("ColA", ASC|DESC) | ||
| 15 | enum class QueryOrder { ASC, DESC }; | ||
| 16 | |||
| 17 | /// Stringify QueryOrder enums for SELECT commands | ||
| 18 | 6 | inline std::ostream& operator<<(std::ostream& os, const QueryOrder order) | |
| 19 | { | ||
| 20 | 6 | switch (order) | |
| 21 | { | ||
| 22 | 4 | case QueryOrder::ASC: | |
| 23 | 4 | os << "ASC"; | |
| 24 | 4 | break; | |
| 25 | 2 | case QueryOrder::DESC: | |
| 26 | 2 | os << "DESC"; | |
| 27 | 2 | break; | |
| 28 | } | ||
| 29 | |||
| 30 | 6 | return os; | |
| 31 | } | ||
| 32 | |||
| 33 | /*! | ||
| 34 | * \class SqlQuery | ||
| 35 | * | ||
| 36 | * \brief This class issues SELECT statements with Constraints, and is used | ||
| 37 | * in order to iterate over the result set and automatically write | ||
| 38 | * record values into users' local variables. | ||
| 39 | */ | ||
| 40 | class SqlQuery | ||
| 41 | { | ||
| 42 | public: | ||
| 43 | 19478 | SqlQuery(const char* table_name, sqlite3* db_conn) : | |
| 44 | 38956 | table_name_(table_name), | |
| 45 | 19478 | db_conn_(db_conn) | |
| 46 | { | ||
| 47 | 19478 | } | |
| 48 | |||
| 49 | /// Query for at most N matching records. | ||
| 50 | 1 | void setLimit(uint32_t limit) { limit_ = limit; } | |
| 51 | |||
| 52 | /// Remove the LIMIT clause. | ||
| 53 | 1 | void resetLimit() { limit_ = 0; } | |
| 54 | |||
| 55 | /// Order the query result set by the given column. | ||
| 56 | /// | ||
| 57 | /// // SELECT ... ORDER BY foo DESC | ||
| 58 | /// query->orderBy("foo", QueryOrder::DESC); | ||
| 59 | /// | ||
| 60 | /// // SELECT ... ORDER BY foo DESC, bar ASC | ||
| 61 | /// query->orderBy("bar", QueryOrder::ASC); | ||
| 62 | /// | ||
| 63 | /// // Remove the ORDER BY clauses | ||
| 64 | /// query->resetOrderBy(); | ||
| 65 | /// | ||
| 66 | /// // SELECT ... ORDER BY bar ASC | ||
| 67 | /// query->orderBy("bar", QueryOrder::ASC); | ||
| 68 | 6 | void orderBy(const char* col_name, const QueryOrder order) { order_clauses_.emplace_back(col_name, order); } | |
| 69 | |||
| 70 | /// Remove the ORDER BY clauses. | ||
| 71 | 1 | void resetOrderBy() { order_clauses_.clear(); } | |
| 72 | |||
| 73 | /// Add a GROUP BY clause. Only one allowed. | ||
| 74 | void groupBy(const char* col_name) | ||
| 75 | { | ||
| 76 | simdb_assert(group_by_column_.empty(), "Only one GROUP BY clause allowed"); | ||
| 77 | group_by_column_ = col_name; | ||
| 78 | } | ||
| 79 | |||
| 80 | /// Remove the GROUP BY clause. | ||
| 81 | void resetGroupBy() { group_by_column_.clear(); } | ||
| 82 | |||
| 83 | /// Add a constraint to this query specific to integer types. | ||
| 84 | 8240 | template <typename T> void addConstraintForInt(const char* col_name, const Constraints constraint, const T target) | |
| 85 | { | ||
| 86 | static_assert(!std::is_same<T, uint64_t>::value, "Wrong addConstraint*() API"); | ||
| 87 | |||
| 88 | 8240 | std::ostringstream oss; | |
| 89 | 8240 | oss << col_name << stringify(constraint) << target; | |
| 90 | 8240 | constraint_clauses_.emplace_back(oss.str()); | |
| 91 | 8240 | } | |
| 92 | |||
| 93 | /// Same as addConstraintForInt() but for users who want explicit APIs. | ||
| 94 | void addConstraintForInt32(const char* col_name, const Constraints constraint, const int32_t target) | ||
| 95 | { | ||
| 96 | addConstraintForInt<int32_t>(col_name, constraint, target); | ||
| 97 | } | ||
| 98 | |||
| 99 | /// Same as addConstraintForInt() but for users who want explicit APIs. | ||
| 100 | 1 | void addConstraintForUInt32(const char* col_name, const Constraints constraint, const uint32_t target) | |
| 101 | { | ||
| 102 | 1 | addConstraintForInt<uint32_t>(col_name, constraint, target); | |
| 103 | 1 | } | |
| 104 | |||
| 105 | /// Same as addConstraintForInt() but for users who want explicit APIs. | ||
| 106 | void addConstraintForInt64(const char* col_name, const Constraints constraint, const int64_t target) | ||
| 107 | { | ||
| 108 | addConstraintForInt<int64_t>(col_name, constraint, target); | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Add a uint64_t constraint to this query. | ||
| 112 | 3015 | void addConstraintForUInt64(const char* col_name, const Constraints constraint, uint64_t target) | |
| 113 | { | ||
| 114 | char digits[21]; | ||
| 115 | 63315 | for (int i = 19; i >= 0; --i) | |
| 116 | { | ||
| 117 | 60300 | digits[i] = '0' + (target % 10); | |
| 118 | 60300 | target /= 10; | |
| 119 | } | ||
| 120 | 3015 | digits[20] = '\0'; | |
| 121 | |||
| 122 | 3015 | std::string clause; | |
| 123 | 3015 | auto constraint_str = stringify(constraint); | |
| 124 | |||
| 125 | // column + op + digits + quotes | ||
| 126 | 3015 | clause.reserve(strlen(col_name) + constraint_str.size() + 20 + 2); | |
| 127 | |||
| 128 | 3015 | clause += col_name; | |
| 129 | 3015 | clause += constraint_str; | |
| 130 | 3015 | clause += '"'; | |
| 131 | 3015 | clause += digits; | |
| 132 | 3015 | clause += '"'; | |
| 133 | |||
| 134 | 3015 | constraint_clauses_.emplace_back(std::move(clause)); | |
| 135 | 3015 | } | |
| 136 | |||
| 137 | /// Add a constraint to this query specific to floating-point types. | ||
| 138 | /// Pass in fuzzy=TRUE to tell SQLite to look for matches that are | ||
| 139 | /// within EPS of the target value. | ||
| 140 | template <typename T> | ||
| 141 | 45 | void addConstraintForDouble(const char* col_name, const Constraints constraint, const T target, bool fuzzy = false) | |
| 142 | { | ||
| 143 | static_assert(std::is_floating_point<T>::value, "Wrong addConstraint*() API"); | ||
| 144 | |||
| 145 | 45 | std::ostringstream oss; | |
| 146 | 45 | if (fuzzy) | |
| 147 | { | ||
| 148 | 22 | oss << "fuzzyMatch(" << col_name << ","; | |
| 149 | 22 | oss << std::setprecision(std::numeric_limits<T>::max_digits10); | |
| 150 | 22 | oss << target << ","; | |
| 151 | 22 | oss << static_cast<int>(constraint) << ")"; | |
| 152 | } else | ||
| 153 | { | ||
| 154 | 23 | oss << col_name << stringify(constraint); | |
| 155 | 23 | oss << std::setprecision(std::numeric_limits<T>::max_digits10) << target; | |
| 156 | } | ||
| 157 | |||
| 158 | 45 | constraint_clauses_.emplace_back(oss.str()); | |
| 159 | 45 | } | |
| 160 | |||
| 161 | /// Add a constraint to this query specific to string types. | ||
| 162 | 8186 | void addConstraintForString(const char* col_name, const Constraints constraint, const std::string& target) | |
| 163 | { | ||
| 164 | 8186 | addConstraintForString(col_name, constraint, target.c_str()); | |
| 165 | 8186 | } | |
| 166 | |||
| 167 | /// Add a constraint to this query specific to string types. | ||
| 168 | 8193 | void addConstraintForString(const char* col_name, const Constraints constraint, const char* target) | |
| 169 | { | ||
| 170 | 8193 | std::ostringstream oss; | |
| 171 | 8193 | oss << col_name << stringify(constraint) << "'" << target << "'"; | |
| 172 | 8193 | constraint_clauses_.emplace_back(oss.str()); | |
| 173 | 8193 | } | |
| 174 | |||
| 175 | /// Add a constraint to this query specific to integer types (except | ||
| 176 | /// uint64_t) and multiple target values. | ||
| 177 | template <typename T> | ||
| 178 | 1 | void addConstraintForInt(const char* col_name, const SetConstraints constraint, | |
| 179 | const std::initializer_list<T>& targets) | ||
| 180 | { | ||
| 181 | 2 | addConstraintForInt(col_name, constraint, std::vector<T>{targets.begin(), targets.end()}); | |
| 182 | 1 | } | |
| 183 | |||
| 184 | /// Add a constraint to this query specific to integer types (except | ||
| 185 | /// uint64_t) and multiple target values. | ||
| 186 | template <typename T> | ||
| 187 | 6 | void addConstraintForInt(const char* col_name, const SetConstraints constraint, const std::vector<T>& targets) | |
| 188 | { | ||
| 189 | static_assert(!std::is_same<T, uint64_t>::value, "Wrong addConstraint*() API"); | ||
| 190 | |||
| 191 | 6 | std::ostringstream oss; | |
| 192 | 6 | oss << col_name << stringify(constraint) << "("; | |
| 193 | |||
| 194 | 99 | for (size_t idx = 0; idx < targets.size(); ++idx) | |
| 195 | { | ||
| 196 | 93 | oss << targets[idx]; | |
| 197 | 93 | if (idx != targets.size() - 1) | |
| 198 | { | ||
| 199 | 88 | oss << ","; | |
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | 6 | oss << ")"; | |
| 204 | 6 | constraint_clauses_.emplace_back(oss.str()); | |
| 205 | 6 | } | |
| 206 | |||
| 207 | /// Add a constraint to this query for uint64_t types and multiple target | ||
| 208 | /// values. | ||
| 209 | 2 | void addConstraintForUInt64(const char* col_name, const SetConstraints constraint, | |
| 210 | const std::initializer_list<uint64_t>& targets) | ||
| 211 | { | ||
| 212 | 4 | addConstraintForUInt64(col_name, constraint, std::vector<uint64_t>{targets.begin(), targets.end()}); | |
| 213 | 2 | } | |
| 214 | |||
| 215 | /// Add a constraint to this query for uint64_t types and multiple target | ||
| 216 | /// values. | ||
| 217 | 2 | void addConstraintForUInt64(const char* col_name, const SetConstraints constraint, | |
| 218 | const std::vector<uint64_t>& targets) | ||
| 219 | { | ||
| 220 | 2 | std::string clause; | |
| 221 | 2 | auto constraint_str = stringify(constraint); | |
| 222 | |||
| 223 | // column + op + digits + quotes + commas + parens | ||
| 224 | 2 | clause.reserve(strlen(col_name) + constraint_str.size() + 20 + 2 + (targets.size() - 1) + 2); | |
| 225 | 2 | clause += col_name; | |
| 226 | 2 | clause += constraint_str; | |
| 227 | 2 | clause += "("; | |
| 228 | |||
| 229 | 4 | for (size_t i = 0; i < targets.size(); ++i) | |
| 230 | { | ||
| 231 | 2 | auto target = targets[i]; | |
| 232 | char digits[21]; | ||
| 233 | 42 | for (int i = 19; i >= 0; --i) | |
| 234 | { | ||
| 235 | 40 | digits[i] = '0' + (target % 10); | |
| 236 | 40 | target /= 10; | |
| 237 | } | ||
| 238 | 2 | digits[20] = '\0'; | |
| 239 | 2 | clause += '"'; | |
| 240 | 2 | clause += digits; | |
| 241 | 2 | clause += '"'; | |
| 242 | |||
| 243 | 2 | if (i != targets.size() - 1) | |
| 244 | { | ||
| 245 | ✗ | clause += ","; | |
| 246 | } | ||
| 247 | } | ||
| 248 | 2 | clause += ")"; | |
| 249 | |||
| 250 | 2 | constraint_clauses_.emplace_back(std::move(clause)); | |
| 251 | 2 | } | |
| 252 | |||
| 253 | /// Add a constraint to this query specific to floating-point types | ||
| 254 | /// and multiple target values. | ||
| 255 | /// | ||
| 256 | /// Pass in fuzzy=TRUE to tell SQLite to look for matches that are | ||
| 257 | /// within EPS of the target values. | ||
| 258 | template <typename T> | ||
| 259 | 24 | void addConstraintForDouble(const char* col_name, const SetConstraints constraint, | |
| 260 | const std::initializer_list<T>& targets, bool fuzzy = false) | ||
| 261 | { | ||
| 262 | 48 | addConstraintForDouble(col_name, constraint, std::vector<T>{targets.begin(), targets.end()}, fuzzy); | |
| 263 | 24 | } | |
| 264 | |||
| 265 | /// Add a constraint to this query specific to floating-point types | ||
| 266 | /// and multiple target values. | ||
| 267 | /// | ||
| 268 | /// Pass in fuzzy=TRUE to tell SQLite to look for matches that are | ||
| 269 | /// within EPS of the target values. | ||
| 270 | template <typename T> | ||
| 271 | 24 | void addConstraintForDouble(const char* col_name, const SetConstraints constraint, const std::vector<T>& targets, | |
| 272 | bool fuzzy = false) | ||
| 273 | { | ||
| 274 | static_assert(std::is_floating_point<T>::value, "Wrong addConstraint*() API"); | ||
| 275 | |||
| 276 | 24 | std::ostringstream oss; | |
| 277 | 24 | if (fuzzy) | |
| 278 | { | ||
| 279 | 12 | oss << "("; | |
| 280 | |||
| 281 | 24 | for (size_t idx = 0; idx < targets.size(); ++idx) | |
| 282 | { | ||
| 283 | 12 | std::ostringstream target_oss; | |
| 284 | 12 | target_oss << "fuzzyMatch(" << col_name << ","; | |
| 285 | 12 | target_oss << std::setprecision(std::numeric_limits<T>::max_digits10); | |
| 286 | 12 | target_oss << targets[idx] << ","; | |
| 287 | |||
| 288 | 12 | if (constraint == SetConstraints::IN_SET) | |
| 289 | { | ||
| 290 | 6 | target_oss << static_cast<int>(Constraints::EQUAL); | |
| 291 | } else | ||
| 292 | { | ||
| 293 | 6 | target_oss << static_cast<int>(Constraints::NOT_EQUAL); | |
| 294 | } | ||
| 295 | |||
| 296 | 12 | target_oss << ")"; | |
| 297 | 12 | oss << target_oss.str(); | |
| 298 | |||
| 299 | 12 | if (idx != targets.size() - 1) | |
| 300 | { | ||
| 301 | ✗ | if (constraint == SetConstraints::IN_SET) | |
| 302 | { | ||
| 303 | ✗ | oss << " OR "; | |
| 304 | } else | ||
| 305 | { | ||
| 306 | ✗ | oss << " AND "; | |
| 307 | } | ||
| 308 | } | ||
| 309 | } | ||
| 310 | |||
| 311 | 12 | oss << ")"; | |
| 312 | } else | ||
| 313 | { | ||
| 314 | 12 | oss << col_name << stringify(constraint) << " ("; | |
| 315 | 24 | for (size_t idx = 0; idx < targets.size(); ++idx) | |
| 316 | { | ||
| 317 | 12 | oss << std::setprecision(std::numeric_limits<T>::max_digits10) << targets[idx]; | |
| 318 | 12 | if (idx != targets.size() - 1) | |
| 319 | { | ||
| 320 | ✗ | oss << ","; | |
| 321 | } | ||
| 322 | } | ||
| 323 | 12 | oss << ")"; | |
| 324 | } | ||
| 325 | |||
| 326 | 24 | constraint_clauses_.emplace_back(oss.str()); | |
| 327 | 24 | } | |
| 328 | |||
| 329 | /// Add a constraint to this query specific to string types and | ||
| 330 | /// multiple target values. | ||
| 331 | 2 | void addConstraintForString(const char* col_name, const SetConstraints constraint, | |
| 332 | const std::initializer_list<const char*>& targets) | ||
| 333 | { | ||
| 334 | 4 | addConstraintForString(col_name, constraint, std::vector<std::string>{targets.begin(), targets.end()}); | |
| 335 | 2 | } | |
| 336 | |||
| 337 | /// Add a constraint to this query specific to string types and | ||
| 338 | /// multiple target values. | ||
| 339 | 2 | void addConstraintForString(const char* col_name, const SetConstraints constraint, | |
| 340 | const std::vector<std::string>& targets) | ||
| 341 | { | ||
| 342 | 2 | std::ostringstream oss; | |
| 343 | 2 | oss << col_name << stringify(constraint) << "("; | |
| 344 | |||
| 345 | 9 | for (size_t idx = 0; idx < targets.size(); ++idx) | |
| 346 | { | ||
| 347 | 7 | oss << "'" << targets[idx] << "'"; | |
| 348 | 7 | if (idx != targets.size() - 1) | |
| 349 | { | ||
| 350 | 5 | oss << ","; | |
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | 2 | oss << ")"; | |
| 355 | 2 | constraint_clauses_.emplace_back(oss.str()); | |
| 356 | 2 | } | |
| 357 | |||
| 358 | /// After one or more calls to addConstraint*(), this function can be called | ||
| 359 | /// to create a compound constraint using AND or OR. | ||
| 360 | /// | ||
| 361 | /// // SELECT ... (WHERE ColA = 1 AND ColB = 2) OR (ColC = 3) | ||
| 362 | /// query->addConstraintForInt("ColA", Constraints::EQUAL, 1); | ||
| 363 | /// query->addConstraintForInt("ColB", Constraints::EQUAL, 2); | ||
| 364 | /// auto clause1 = query->releaseConstraintClauses(); | ||
| 365 | /// | ||
| 366 | /// query->addConstraintForInt("ColC", Constraints::EQUAL, 3); | ||
| 367 | /// auto clause2 = query->releaseConstraintClauses(); | ||
| 368 | /// | ||
| 369 | /// query->addCompoundConstraint(clause1, QueryOperator::OR, clause2); | ||
| 370 | 1 | void addCompoundConstraint(const std::vector<std::string>& clause1, const QueryOperator compound_constraint, | |
| 371 | const std::vector<std::string>& clause2) | ||
| 372 | { | ||
| 373 | 1 | std::ostringstream oss; | |
| 374 | 1 | oss << "("; | |
| 375 | 3 | for (size_t idx = 0; idx < clause1.size(); ++idx) | |
| 376 | { | ||
| 377 | 2 | oss << clause1[idx]; | |
| 378 | 2 | if (idx != clause1.size() - 1) | |
| 379 | { | ||
| 380 | 1 | oss << " AND "; | |
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | 1 | if (compound_constraint == QueryOperator::AND) | |
| 385 | { | ||
| 386 | ✗ | oss << ") AND ("; | |
| 387 | } else | ||
| 388 | { | ||
| 389 | 1 | oss << ") OR ("; | |
| 390 | } | ||
| 391 | |||
| 392 | 2 | for (size_t idx = 0; idx < clause2.size(); ++idx) | |
| 393 | { | ||
| 394 | 1 | oss << clause2[idx]; | |
| 395 | 1 | if (idx != clause2.size() - 1) | |
| 396 | { | ||
| 397 | ✗ | oss << " AND "; | |
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | 1 | oss << ")"; | |
| 402 | 1 | constraint_clauses_.emplace_back(oss.str()); | |
| 403 | 1 | } | |
| 404 | |||
| 405 | /// Release the current constraint clauses. | ||
| 406 | 2 | std::vector<std::string> releaseConstraintClauses() { return std::move(constraint_clauses_); } | |
| 407 | |||
| 408 | /// Reset the query constraints. | ||
| 409 | 84 | void resetConstraints() { constraint_clauses_.clear(); } | |
| 410 | |||
| 411 | /// Non-optional scalar SELECT overloads throw if a column value is SQL NULL. | ||
| 412 | /// Use the std::optional overloads when a column may be unset. | ||
| 413 | /// | ||
| 414 | /// SELECT column values and write to the local variable on each iteration | ||
| 415 | /// (int32_t). | ||
| 416 | /// | ||
| 417 | /// int32_t val; | ||
| 418 | /// query->select("Col", val); | ||
| 419 | 11195 | void select(const char* col_name, int32_t& user_var) | |
| 420 | { | ||
| 421 | 11195 | result_writers_.emplace_back(new ResultWriterInt32(col_name, &user_var)); | |
| 422 | 11195 | } | |
| 423 | |||
| 424 | /// Use std::optional overload to query record values that may be NULL/unset | ||
| 425 | 17 | void select(const char* col_name, std::optional<int32_t>& user_var) | |
| 426 | { | ||
| 427 | 17 | result_writers_.emplace_back(new ResultWriterInt32<std::optional<int32_t>>(col_name, &user_var)); | |
| 428 | 17 | } | |
| 429 | |||
| 430 | /// SELECT column values and write to the local variable on each iteration | ||
| 431 | /// (uint32_t). | ||
| 432 | 15 | void select(const char* col_name, uint32_t& user_var) | |
| 433 | { | ||
| 434 | 15 | result_writers_.emplace_back(new ResultWriterUInt32(col_name, &user_var)); | |
| 435 | 15 | } | |
| 436 | |||
| 437 | /// Use std::optional overload to query record values that may be NULL/unset | ||
| 438 | 4 | void select(const char* col_name, std::optional<uint32_t>& user_var) | |
| 439 | { | ||
| 440 | 4 | result_writers_.emplace_back(new ResultWriterUInt32<std::optional<uint32_t>>(col_name, &user_var)); | |
| 441 | 4 | } | |
| 442 | |||
| 443 | /// SELECT column values and write to the local variable on each iteration | ||
| 444 | /// (int64_t). | ||
| 445 | /// | ||
| 446 | /// int64_t val; | ||
| 447 | /// query->select("Col", val); | ||
| 448 | 17 | void select(const char* col_name, int64_t& user_var) | |
| 449 | { | ||
| 450 | 17 | result_writers_.emplace_back(new ResultWriterInt64(col_name, &user_var)); | |
| 451 | 17 | } | |
| 452 | |||
| 453 | /// Use std::optional overload to query record values that may be NULL/unset | ||
| 454 | 1 | void select(const char* col_name, std::optional<int64_t>& user_var) | |
| 455 | { | ||
| 456 | 1 | result_writers_.emplace_back(new ResultWriterInt64<std::optional<int64_t>>(col_name, &user_var)); | |
| 457 | 1 | } | |
| 458 | |||
| 459 | /// SELECT column values and write to the local variable on each iteration | ||
| 460 | /// (uint64_t). | ||
| 461 | /// | ||
| 462 | /// uint64_t val; | ||
| 463 | /// query->select("Col", val); | ||
| 464 | 13 | void select(const char* col_name, uint64_t& user_var) | |
| 465 | { | ||
| 466 | 13 | result_writers_.emplace_back(new ResultWriterUInt64(col_name, &user_var)); | |
| 467 | 13 | } | |
| 468 | |||
| 469 | /// Use std::optional overload to query record values that may be NULL/unset | ||
| 470 | 2 | void select(const char* col_name, std::optional<uint64_t>& user_var) | |
| 471 | { | ||
| 472 | 2 | result_writers_.emplace_back(new ResultWriterUInt64<std::optional<uint64_t>>(col_name, &user_var)); | |
| 473 | 2 | } | |
| 474 | |||
| 475 | /// SELECT column values and write to the local variable on each iteration | ||
| 476 | /// (double). | ||
| 477 | /// | ||
| 478 | /// double val; | ||
| 479 | /// query->select("Col", val); | ||
| 480 | 7 | void select(const char* col_name, double& user_var) | |
| 481 | { | ||
| 482 | 7 | result_writers_.emplace_back(new ResultWriterDouble(col_name, &user_var)); | |
| 483 | 7 | } | |
| 484 | |||
| 485 | /// Use std::optional overload to query record values that may be NULL/unset | ||
| 486 | 1 | void select(const char* col_name, std::optional<double>& user_var) | |
| 487 | { | ||
| 488 | 1 | result_writers_.emplace_back(new ResultWriterDouble<std::optional<double>>(col_name, &user_var)); | |
| 489 | 1 | } | |
| 490 | |||
| 491 | /// SELECT column values and write to the local variable on each iteration | ||
| 492 | /// (string). | ||
| 493 | /// | ||
| 494 | /// std::string val; | ||
| 495 | /// query->select("Col", val); | ||
| 496 | /// | ||
| 497 | /// NULL columns are written as the empty string. Use the std::optional overload | ||
| 498 | /// to distinguish SQL NULL from an explicitly stored empty string. | ||
| 499 | 105 | void select(const char* col_name, std::string& user_var) | |
| 500 | { | ||
| 501 | 105 | result_writers_.emplace_back(new ResultWriterString(col_name, &user_var)); | |
| 502 | 105 | } | |
| 503 | |||
| 504 | /// Use std::optional overload to distinguish SQL NULL from "". | ||
| 505 | 13 | void select(const char* col_name, std::optional<std::string>& user_var) | |
| 506 | { | ||
| 507 | 13 | result_writers_.emplace_back(new ResultWriterString<std::optional<std::string>>(col_name, &user_var)); | |
| 508 | 13 | } | |
| 509 | |||
| 510 | /// SELECT column values and write to the local variable on each iteration | ||
| 511 | /// (blob). | ||
| 512 | /// | ||
| 513 | /// std::vector<int> val; | ||
| 514 | /// query->select("Col", val); | ||
| 515 | /// | ||
| 516 | /// Note that this does not have a std::optional overload since std::vector::empty() | ||
| 517 | /// makes NULL obvious. | ||
| 518 | 8190 | template <typename T> void select(const char* col_name, std::vector<T>& user_var) | |
| 519 | { | ||
| 520 | 8190 | result_writers_.emplace_back(new ResultWriterBlob<T>(col_name, &user_var)); | |
| 521 | 8190 | } | |
| 522 | |||
| 523 | /// Deselect all record property values. | ||
| 524 | void resetSelections() { result_writers_.clear(); } | ||
| 525 | |||
| 526 | /// Count the number of records matching this query's search constraints. | ||
| 527 | /// If no constraints were added, counts all records in the table. | ||
| 528 | 87 | uint64_t count() | |
| 529 | { | ||
| 530 | 87 | std::ostringstream oss; | |
| 531 | 87 | oss << "SELECT COUNT(*) FROM " << table_name_ << " "; | |
| 532 | 87 | appendConstraintClauses_(oss); | |
| 533 | |||
| 534 | 87 | const auto cmd = oss.str(); | |
| 535 | 87 | auto stmt = SQLitePreparedStatement(db_conn_, cmd); | |
| 536 | 87 | auto rc = SQLiteReturnCode(sqlite3_step(stmt)); | |
| 537 | |||
| 538 | 87 | if (rc == SQLITE_ROW) | |
| 539 | { | ||
| 540 | 87 | return sqlite3_column_int64(stmt, 0); | |
| 541 | } | ||
| 542 | |||
| 543 | ✗ | if (rc == SQLITE_DONE) | |
| 544 | { | ||
| 545 | ✗ | return 0; | |
| 546 | } | ||
| 547 | |||
| 548 | ✗ | throw DBException(sqlite3_errmsg(db_conn_)); | |
| 549 | 87 | } | |
| 550 | |||
| 551 | /// DELETE all records that match the query's current search constraints. | ||
| 552 | 2 | void deleteResultSet() | |
| 553 | { | ||
| 554 | 2 | std::ostringstream oss; | |
| 555 | 2 | oss << "DELETE FROM " << table_name_ << " "; | |
| 556 | 2 | appendConstraintClauses_(oss); | |
| 557 | |||
| 558 | 2 | const auto cmd = oss.str(); | |
| 559 | 2 | auto stmt = SQLitePreparedStatement(db_conn_, cmd); | |
| 560 | 2 | simdb_assert(sqlite3_step(stmt) == SQLITE_DONE, sqlite3_errmsg(db_conn_)); | |
| 561 | 2 | } | |
| 562 | |||
| 563 | /// Execute the query. Returns an iterator to walk the result set | ||
| 564 | /// row-by-row and populate the user's local variables given to | ||
| 565 | /// the select() calls. | ||
| 566 | 19487 | SqlResultIterator getResultSet() | |
| 567 | { | ||
| 568 | 19487 | std::ostringstream oss; | |
| 569 | 19487 | oss << "SELECT "; | |
| 570 | 39098 | for (size_t idx = 0; idx < result_writers_.size(); ++idx) | |
| 571 | { | ||
| 572 | 19611 | oss << result_writers_[idx]->getColName(); | |
| 573 | 19611 | if (idx != result_writers_.size() - 1) | |
| 574 | { | ||
| 575 | 124 | oss << ","; | |
| 576 | } | ||
| 577 | } | ||
| 578 | |||
| 579 | 19487 | oss << " FROM " << table_name_ << " "; | |
| 580 | |||
| 581 | 19487 | appendConstraintClauses_(oss); | |
| 582 | 19487 | appendGroupByClause_(oss); | |
| 583 | 19487 | appendOrderByClauses_(oss); | |
| 584 | 19487 | appendLimitClause_(oss); | |
| 585 | |||
| 586 | 19487 | const auto cmd = oss.str(); | |
| 587 | 19487 | auto stmt = SQLitePreparedStatement(db_conn_, cmd); | |
| 588 | |||
| 589 | 19486 | std::vector<std::shared_ptr<ResultWriterBase>> result_writers; | |
| 590 | 39094 | for (const auto& writer : result_writers_) | |
| 591 | { | ||
| 592 | 19608 | result_writers.emplace_back(writer->clone()); | |
| 593 | } | ||
| 594 | |||
| 595 | 38972 | return SqlResultIterator(stmt.release(), std::move(result_writers)); | |
| 596 | 19488 | } | |
| 597 | |||
| 598 | private: | ||
| 599 | /// Append WHERE clause(s). | ||
| 600 | 19576 | void appendConstraintClauses_(std::ostringstream& oss) const | |
| 601 | { | ||
| 602 | 19576 | if (!constraint_clauses_.empty()) | |
| 603 | { | ||
| 604 | 19519 | oss << " WHERE "; | |
| 605 | 39045 | for (size_t idx = 0; idx < constraint_clauses_.size(); ++idx) | |
| 606 | { | ||
| 607 | 19526 | oss << constraint_clauses_[idx] << " "; | |
| 608 | 19526 | if (idx != constraint_clauses_.size() - 1) | |
| 609 | { | ||
| 610 | 7 | oss << " AND "; | |
| 611 | } | ||
| 612 | } | ||
| 613 | 19519 | oss << " "; | |
| 614 | } | ||
| 615 | 19576 | } | |
| 616 | |||
| 617 | /// Append GROUP BY clause. | ||
| 618 | 19487 | void appendGroupByClause_(std::ostringstream& oss) const | |
| 619 | { | ||
| 620 | 19487 | if (!group_by_column_.empty()) | |
| 621 | { | ||
| 622 | ✗ | oss << " GROUP BY " << group_by_column_ << " "; | |
| 623 | } | ||
| 624 | 19487 | } | |
| 625 | |||
| 626 | /// Append ORDER BY clause(s). | ||
| 627 | 19487 | void appendOrderByClauses_(std::ostringstream& oss) const | |
| 628 | { | ||
| 629 | 19487 | if (!order_clauses_.empty()) | |
| 630 | { | ||
| 631 | 5 | oss << " ORDER BY "; | |
| 632 | 11 | for (size_t idx = 0; idx < order_clauses_.size(); ++idx) | |
| 633 | { | ||
| 634 | 6 | oss << order_clauses_[idx].col_name << " " << order_clauses_[idx].order; | |
| 635 | 6 | if (idx != order_clauses_.size() - 1) | |
| 636 | { | ||
| 637 | 1 | oss << ","; | |
| 638 | } | ||
| 639 | } | ||
| 640 | 5 | oss << " "; | |
| 641 | } | ||
| 642 | 19487 | } | |
| 643 | |||
| 644 | /// Append LIMIT clause. | ||
| 645 | 19487 | void appendLimitClause_(std::ostringstream& oss) const | |
| 646 | { | ||
| 647 | 19487 | if (limit_) | |
| 648 | { | ||
| 649 | 1 | oss << " LIMIT " << limit_; | |
| 650 | } | ||
| 651 | 19487 | } | |
| 652 | |||
| 653 | /// \struct QueryOrderClause | ||
| 654 | /// \brief Used to build clauses like "ORDER BY ColA ASC, ColB DESC" | ||
| 655 | struct QueryOrderClause | ||
| 656 | { | ||
| 657 | std::string col_name; | ||
| 658 | QueryOrder order; | ||
| 659 | |||
| 660 | 6 | QueryOrderClause(const char* col_name, const QueryOrder order) : | |
| 661 | 12 | col_name(col_name), | |
| 662 | 6 | order(order) | |
| 663 | { | ||
| 664 | 6 | } | |
| 665 | }; | ||
| 666 | |||
| 667 | /// SELECT ColA,ColB FROM <table_name_> WHERE ... | ||
| 668 | const std::string table_name_; | ||
| 669 | |||
| 670 | /// Underlying sqlite3 database | ||
| 671 | sqlite3* const db_conn_; | ||
| 672 | |||
| 673 | /// SELECT ColA,ColB FROM Table WHERE ... LIMIT <limit_> | ||
| 674 | uint32_t limit_ = 0; | ||
| 675 | |||
| 676 | /// SELECT ColA,ColB FROM Table WHERE ... ORDER BY <order_clauses_> | ||
| 677 | std::vector<QueryOrderClause> order_clauses_; | ||
| 678 | |||
| 679 | /// SELECT ColA,ColB FROM Table WHERE ... GROUP BY <group_by_column_> | ||
| 680 | std::string group_by_column_; | ||
| 681 | |||
| 682 | /// SELECT ColA,ColB FROM Table WHERE <constraint_clauses_> | ||
| 683 | std::vector<std::string> constraint_clauses_; | ||
| 684 | |||
| 685 | /// SELECT <result_writers_> FROM Table WHERE ... | ||
| 686 | std::vector<std::shared_ptr<ResultWriterBase>> result_writers_; | ||
| 687 | }; | ||
| 688 | |||
| 689 | } // namespace simdb | ||
| 690 |