• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 36374279342

27 Sep 2026 02:38PM UTC coverage: 89.831% (+0.02%) from 89.808%
36374279342

push

github

web-flow
Merge pull request #5972 from randombit/jack/psk-db-fixes

Fix some sqlite3/database edge cases

128278 of 142800 relevant lines covered (89.83%)

9191459.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.82
/src/lib/utils/sqlite3/sqlite3.cpp
1
/*
2
* SQLite wrapper
3
* (C) 2012 Jack Lloyd
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include <botan/sqlite3.h>
9

10
#include <botan/assert.h>
11
#include <botan/mem_ops.h>
12
#include <botan/internal/fmt.h>
13
#include <botan/internal/int_utils.h>
14
#include <sqlite3.h>
15

16
namespace Botan {
17

18
Sqlite3_Database::Sqlite3_Database(std::string_view db_filename, std::optional<int> sqlite_open_flags) {
140 ✔
19
   // SQLITE_OPEN_FULLMUTEX ensures that the database object can be used
20
   // concurrently from multiple threads.
21
   const int open_flags =
140 ✔
22
      sqlite_open_flags.value_or(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX);
140 ✔
23
   sqlite3* db = nullptr;
140 ✔
24
   const int rc = ::sqlite3_open_v2(std::string(db_filename).c_str(), &db, open_flags, nullptr);
280 ✔
25

26
   if(rc != 0) [[unlikely]] {
140 ✔
27
      const std::string err_msg = (db != nullptr) ? ::sqlite3_errmsg(db) : "unknown error";
×
28
      ::sqlite3_close_v2(db);
×
29
      throw SQL_DB_Error("sqlite3_open failed - " + err_msg);
×
30
   }
×
31

32
   m_db = std::shared_ptr<sqlite3>(db, [](sqlite3* p) noexcept { ::sqlite3_close_v2(p); });
420 ✔
33
}
140 ✔
34

35
Sqlite3_Database::~Sqlite3_Database() = default;
140 ✔
36

37
std::shared_ptr<SQL_Database::Statement> Sqlite3_Database::new_statement(std::string_view base_sql) const {
1,445 ✔
38
   return std::make_shared<Sqlite3_Statement>(m_db, base_sql);
1,445 ✔
39
}
40

41
std::shared_ptr<SQL_Database::Statement> Sqlite3_Database::upsert(
278 ✔
42
   std::string_view table, std::initializer_list<std::string_view> columns) const {
43
   BOTAN_ARG_CHECK(columns.size() > 0, "upsert requires at least one column");
278 ✔
44

45
   std::string sql = "INSERT OR REPLACE INTO ";
278 ✔
46
   sql += table;
278 ✔
47
   sql += " (";
278 ✔
48
   bool first = true;
278 ✔
49
   for(const auto& col : columns) {
1,844 ✔
50
      if(!first) {
1,566 ✔
51
         sql += ", ";
1,288 ✔
52
      }
53
      sql += col;
1,566 ✔
54
      first = false;
1,566 ✔
55
   }
56
   sql += ") VALUES (";
278 ✔
57
   for(size_t i = 1; i <= columns.size(); ++i) {
1,844 ✔
58
      if(i > 1) {
1,566 ✔
59
         sql += ", ";
1,288 ✔
60
      }
61
      sql += fmt("?{}", i);
3,132 ✔
62
   }
63
   sql += ")";
278 ✔
64

65
   return new_statement(sql);
278 ✔
66
}
278 ✔
67

68
size_t Sqlite3_Database::row_count(std::string_view table_name) {
128 ✔
69
   BOTAN_ARG_CHECK(is_valid_table_name(table_name), "Invalid table name");
128 ✔
70
   auto stmt = new_statement(fmt("select count(*) from {}", table_name));
255 ✔
71

72
   if(stmt->step()) {
1 ✔
73
      return stmt->get_size_t(0);
1 ✔
74
   } else {
75
      throw SQL_DB_Error(fmt("Querying size of table '{}' failed", table_name));
×
76
   }
77
}
1 ✔
78

79
void Sqlite3_Database::create_table(const Table_Schema& schema) {
277 ✔
80
   BOTAN_ARG_CHECK(!schema.name().empty(), "create_table requires a table name");
277 ✔
81
   BOTAN_ARG_CHECK(!schema.columns().empty(), "create_table requires at least one column");
277 ✔
82

83
   std::string sql = "CREATE TABLE ";
277 ✔
84
   if(schema.is_if_not_exists()) {
277 ✔
85
      sql += "IF NOT EXISTS ";
21 ✔
86
   }
87
   sql += schema.name();
277 ✔
88
   sql += " (";
277 ✔
89
   bool first = true;
277 ✔
90
   for(const auto& col : schema.columns()) {
1,743 ✔
91
      if(!first) {
1,466 ✔
92
         sql += ", ";
1,189 ✔
93
      }
94
      sql += col.name();
1,466 ✔
95
      sql += ' ';
1,466 ✔
96
      switch(col.type()) {
1,466 ✔
97
         case Column_Type::Blob:
416 ✔
98
            sql += "BLOB";
416 ✔
99
            break;
100
         case Column_Type::String:
402 ✔
101
            sql += "TEXT";
402 ✔
102
            break;
103
         case Column_Type::Integer:
648 ✔
104
            sql += "INTEGER";
648 ✔
105
            break;
106
      }
107
      if(col.is_primary_key()) {
1,466 ✔
108
         sql += " PRIMARY KEY";
149 ✔
109
      }
110
      if(col.is_unique()) {
1,466 ✔
111
         sql += " UNIQUE";
×
112
      }
113
      if(col.is_not_null()) {
1,466 ✔
114
         sql += " NOT NULL";
780 ✔
115
      }
116
      first = false;
1,466 ✔
117
   }
118
   sql += ")";
277 ✔
119

120
   char* errmsg = nullptr;
277 ✔
121
   const int rc = ::sqlite3_exec(m_db.get(), sql.c_str(), nullptr, nullptr, &errmsg);
277 ✔
122

123
   if(rc != SQLITE_OK) {
277 ✔
124
      const std::string err_msg = (errmsg != nullptr) ? errmsg : "unknown error";
×
125
      ::sqlite3_free(errmsg);
×
126
      throw SQL_DB_Error("sqlite3_exec for create_table failed - " + err_msg, rc);
×
127
   }
×
128
}
277 ✔
129

130
size_t Sqlite3_Database::rows_changed_by_last_statement() {
32 ✔
131
   const auto result = ::sqlite3_changes64(m_db.get());
32 ✔
132
   BOTAN_ASSERT_NOMSG(result >= 0);
32 ✔
133
   return static_cast<size_t>(result);
32 ✔
134
}
135

136
bool Sqlite3_Database::is_threadsafe() const {
421 ✔
137
   // sqlite3_db_mutex() returns the connection's mutex if the connection is in
138
   // serialized mode, and nullptr otherwise. This reflects both the compile-time
139
   // SQLITE_THREADSAFE setting and the per-connection SQLITE_OPEN_(FULL|NO)MUTEX
140
   // open flags actually used.
141
   //
142
   // https://www.sqlite.org/c3ref/db_mutex.html
143
   return ::sqlite3_db_mutex(m_db.get()) != nullptr;
421 ✔
144
}
145

146
Sqlite3_Database::Sqlite3_Statement::Sqlite3_Statement(std::shared_ptr<sqlite3> db, std::string_view base_sql) :
1,445 ✔
147
      m_db(std::move(db)), m_stmt{} {
1,445 ✔
148
   const int rc =
1,445 ✔
149
      ::sqlite3_prepare_v2(m_db.get(), base_sql.data(), static_cast<int>(base_sql.size()), &m_stmt, nullptr);
1,445 ✔
150

151
   if(rc != SQLITE_OK) {
1,445 ✔
152
      throw SQL_DB_Error(fmt("sqlite3_prepare failed on '{}' with err {}", base_sql, rc), rc);
128 ✔
153
   }
154
}
1,445 ✔
155

156
void Sqlite3_Database::Sqlite3_Statement::bind(int column, std::string_view val) {
900 ✔
157
   if(val.data() == nullptr) {
900 ✔
158
      bind_null(column);
×
159
      return;
×
160
   }
161
   const int rc = ::sqlite3_bind_text64(m_stmt, column, val.data(), val.size(), SQLITE_TRANSIENT, SQLITE_UTF8);
900 ✔
162
   if(rc != SQLITE_OK) {
900 ✔
163
      throw SQL_DB_Error("sqlite3_bind_text failed", rc);
×
164
   }
165
}
166

167
void Sqlite3_Database::Sqlite3_Statement::bind(int column, size_t val) {
1,282 ✔
168
   const int rc = ::sqlite3_bind_int64(m_stmt, column, val);
1,282 ✔
169
   if(rc != SQLITE_OK) {
1,282 ✔
170
      throw SQL_DB_Error("sqlite3_bind_int failed", rc);
×
171
   }
172
}
1,282 ✔
173

174
void Sqlite3_Database::Sqlite3_Statement::bind(int column, std::chrono::system_clock::time_point time) {
230 ✔
175
   const uint64_t timeval = std::chrono::duration_cast<std::chrono::seconds>(time.time_since_epoch()).count();
230 ✔
176
   bind(column, static_cast<size_t>(timeval));
230 ✔
177
}
230 ✔
178

179
void Sqlite3_Database::Sqlite3_Statement::bind(int column, const std::vector<uint8_t>& val) {
778 ✔
180
   bind(column, val.data(), val.size());
778 ✔
181
}
778 ✔
182

183
void Sqlite3_Database::Sqlite3_Statement::bind(int column, const uint8_t* p, size_t len) {
784 ✔
184
   if(p == nullptr) {
784 ✔
185
      bind_null(column);
217 ✔
186
      return;
217 ✔
187
   }
188
   const int rc = ::sqlite3_bind_blob64(m_stmt, column, p, len, SQLITE_TRANSIENT);
567 ✔
189
   if(rc != SQLITE_OK) {
567 ✔
190
      throw SQL_DB_Error("sqlite3_bind_blob failed", rc);
×
191
   }
192
}
193

194
void Sqlite3_Database::Sqlite3_Statement::bind_null(int column) {
220 ✔
195
   const int rc = ::sqlite3_bind_null(m_stmt, column);
220 ✔
196
   if(rc != SQLITE_OK) {
220 ✔
197
      throw SQL_DB_Error("sqlite3_bind_null failed", rc);
×
198
   }
199
}
220 ✔
200

201
std::span<const uint8_t> Sqlite3_Database::Sqlite3_Statement::get_blob(int column) {
255 ✔
202
   const auto column_type = ::sqlite3_column_type(m_stmt, column);
255 ✔
203
   if(column_type == SQLITE_NULL) {
255 ✔
204
      return {};
42 ✔
205
   }
206

207
   BOTAN_ASSERT(column_type == SQLITE_BLOB, "Return value is a blob");
213 ✔
208

209
   const void* session_blob = ::sqlite3_column_blob(m_stmt, column);
213 ✔
210
   const int session_blob_size = ::sqlite3_column_bytes(m_stmt, column);
213 ✔
211

212
   BOTAN_ASSERT(session_blob_size >= 0, "Blob size is non-negative");
213 ✔
213

214
   return {static_cast<const uint8_t*>(session_blob), static_cast<size_t>(session_blob_size)};
213 ✔
215
}
216

217
std::optional<std::string> Sqlite3_Database::Sqlite3_Statement::get_str(int column) {
74 ✔
218
   const auto column_type = ::sqlite3_column_type(m_stmt, column);
74 ✔
219
   if(column_type == SQLITE_NULL) {
74 ✔
220
      return std::nullopt;
×
221
   }
222

223
   BOTAN_ASSERT(column_type == SQLITE_TEXT, "Return value is text");
74 ✔
224

225
   const unsigned char* str = ::sqlite3_column_text(m_stmt, column);
74 ✔
226
   const int len = ::sqlite3_column_bytes(m_stmt, column);
74 ✔
227
   BOTAN_ASSERT(len >= 0, "Text length is non-negative");
74 ✔
228

229
   return std::string(cast_uint8_ptr_to_char(str), static_cast<size_t>(len));
148 ✔
230
}
231

232
size_t Sqlite3_Database::Sqlite3_Statement::get_size_t(int column) {
6 ✔
233
   BOTAN_ASSERT(::sqlite3_column_type(m_stmt, column) == SQLITE_INTEGER, "Return count is an integer");
6 ✔
234

235
   return checked_cast_to<size_t>(::sqlite3_column_int64(m_stmt, column));
6 ✔
236
}
237

238
size_t Sqlite3_Database::Sqlite3_Statement::spin() {
1,047 ✔
239
   size_t steps = 0;
1,047 ✔
240
   while(step()) {
1,047 ✔
241
      ++steps;
×
242
   }
243

244
   return steps;
1,047 ✔
245
}
246

247
bool Sqlite3_Database::Sqlite3_Statement::step() {
1,435 ✔
248
   const int rc = ::sqlite3_step(m_stmt);
1,435 ✔
249
   if(rc == SQLITE_ROW) {
1,435 ✔
250
      return true;
251
   }
252
   if(rc == SQLITE_DONE) {
1,218 ✔
253
      return false;
254
   }
255
   throw SQL_DB_Error(fmt("sqlite3_step failed - {}", ::sqlite3_errmsg(::sqlite3_db_handle(m_stmt))), rc);
×
256
}
257

258
Sqlite3_Database::Sqlite3_Statement::~Sqlite3_Statement() {
1,317 ✔
259
   ::sqlite3_finalize(m_stmt);
1,317 ✔
260
}
1,317 ✔
261

262
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc