Shaka Player Embedded
sqlite.cc
Go to the documentation of this file.
1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/js/idb/sqlite.h"
16 
17 #include <sqlite3.h>
18 
19 #include <functional>
20 #include <memory>
21 #include <utility>
22 
23 #include "src/util/utils.h"
24 
25 namespace shaka {
26 namespace js {
27 namespace idb {
28 
29 namespace {
30 
31 #define RETURN_IF_ERROR(code) \
32  do { \
33  const DatabaseStatus ret = (code); \
34  if (ret != DatabaseStatus::Success) \
35  return ret; \
36  } while (false)
37 
38 DatabaseStatus MapErrorCode(int ret) {
39  // See https://www.sqlite.org/rescode.html
40  switch (ret & 0xff) {
41  case SQLITE_DONE:
42  case SQLITE_OK:
44  case SQLITE_BUSY:
45  case SQLITE_LOCKED:
46  VLOG(2) << "Sqlite database busy";
47  return DatabaseStatus::Busy;
48  // We use the EMPTY key for when we expect a single value and none are
49  // returned. It is unused within sqlite.
50  case SQLITE_EMPTY:
51  VLOG(2) << "No entries returned";
53 
54  default:
55  if (ret == SQLITE_CONSTRAINT_FOREIGNKEY) {
56  VLOG(2) << "Foreign key not found";
58  } else if (ret == SQLITE_CONSTRAINT_PRIMARYKEY ||
59  ret == SQLITE_CONSTRAINT_UNIQUE) {
60  VLOG(2) << "Duplicate entries in table";
62  }
63 
64  LOG(DFATAL) << "Unknown error from sqlite (" << ret
65  << "): " << sqlite3_errstr(ret);
67  }
68 }
69 
70 template <typename T>
71 struct GetColumn;
72 template <>
73 struct GetColumn<std::string> {
74  static std::string Get(sqlite3_stmt* stmt, size_t index) {
75  const size_t size = sqlite3_column_bytes(stmt, index);
76  auto* ret = sqlite3_column_text(stmt, index);
77  return std::string{reinterpret_cast<const char*>(ret), size};
78  }
79 };
80 template <>
81 struct GetColumn<std::vector<uint8_t>> {
82  static std::vector<uint8_t> Get(sqlite3_stmt* stmt, size_t index) {
83  const size_t size = sqlite3_column_bytes(stmt, index);
84  auto* ret =
85  reinterpret_cast<const uint8_t*>(sqlite3_column_blob(stmt, index));
86  return std::vector<uint8_t>{ret, ret + size};
87  }
88 };
89 template <>
90 struct GetColumn<int64_t> {
91  static int64_t Get(sqlite3_stmt* stmt, size_t index) {
92  return sqlite3_column_int64(stmt, index);
93  }
94 };
95 
96 template <typename Func, typename... Columns>
97 struct GetColumns;
98 template <typename Func>
99 struct GetColumns<Func> {
100  template <typename... Args>
101  static int Get(sqlite3_stmt* /* stmt */, size_t /* index */, Func&& func,
102  Args&&... args) {
103  return func(std::forward<Args>(args)...);
104  }
105 };
106 template <typename Func, typename T, typename... Rest>
107 struct GetColumns<Func, T, Rest...> {
108  template <typename... Args>
109  static int Get(sqlite3_stmt* stmt, size_t index, Func&& func,
110  Args&&... args) {
111  T temp = GetColumn<T>::Get(stmt, index);
112  return GetColumns<Func, Rest...>::Get(
113  stmt, index + 1, std::forward<Func>(func), std::forward<Args>(args)...,
114  std::move(temp));
115  }
116 };
117 
118 template <typename T>
119 struct BindSingleArg;
120 template <>
121 struct BindSingleArg<std::string> {
122  static int Bind(sqlite3_stmt* stmt, size_t index, const std::string& arg) {
123  return sqlite3_bind_text(stmt, index, arg.c_str(), arg.size(),
124  SQLITE_TRANSIENT); // NOLINT
125  }
126 };
127 template <>
128 struct BindSingleArg<std::vector<uint8_t>> {
129  static int Bind(sqlite3_stmt* stmt, size_t index,
130  const std::vector<uint8_t>& arg) {
131  return sqlite3_bind_blob64(stmt, index, arg.data(), arg.size(),
132  SQLITE_TRANSIENT); // NOLINT
133  }
134 };
135 template <>
136 struct BindSingleArg<int64_t> {
137  static int Bind(sqlite3_stmt* stmt, size_t index, int64_t arg) {
138  return sqlite3_bind_int64(stmt, index, arg);
139  }
140 };
141 
142 template <typename... Args>
143 struct BindArgs;
144 template <>
145 struct BindArgs<> {
146  static int Bind(sqlite3_stmt* /* stmt */, size_t /* offset */) {
147  return SQLITE_OK;
148  }
149 };
150 template <typename T, typename... Rest>
151 struct BindArgs<T, Rest...> {
152  static int Bind(sqlite3_stmt* stmt, size_t offset, T&& cur, Rest&&... rest) {
153  const int ret = BindSingleArg<typename std::decay<T>::type>::Bind(
154  stmt, offset, std::forward<T>(cur));
155  if (ret != SQLITE_OK)
156  return ret;
157  return BindArgs<Rest...>::Bind(stmt, offset + 1,
158  std::forward<Rest>(rest)...);
159  }
160 };
161 
162 
163 template <typename... InParams, typename... Columns>
164 DatabaseStatus ExecGetResults(sqlite3* db, std::function<int(Columns...)> cb,
165  const std::string& cmd, InParams&&... params) {
166  VLOG(2) << "Querying sqlite: " << cmd;
167 
168  sqlite3_stmt* stmt;
169  int ret = sqlite3_prepare_v2(db, cmd.c_str(), cmd.size(), &stmt, nullptr);
170  if (ret != SQLITE_OK)
171  return MapErrorCode(ret);
172  std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)> stmt_safe(
173  stmt, &sqlite3_finalize);
174 
175  ret = BindArgs<InParams...>::Bind(stmt, 1, std::forward<InParams>(params)...);
176  if (ret != SQLITE_OK)
177  return MapErrorCode(ret);
178 
179  while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) {
180  ret = GetColumns<std::function<int(Columns...)>&, Columns...>::Get(stmt, 0,
181  cb);
182  if (ret != SQLITE_OK)
183  return MapErrorCode(ret);
184  }
185  return MapErrorCode(ret == SQLITE_DONE ? SQLITE_OK : ret);
186 }
187 
188 template <typename... Args>
189 DatabaseStatus ExecCommand(sqlite3* db, const std::string& cmd,
190  Args&&... args) {
191  std::function<int()> ignore = []() { return SQLITE_OK; };
192  return ExecGetResults(db, ignore, cmd, std::forward<Args>(args)...);
193 }
194 
195 template <typename T, typename... Args>
196 DatabaseStatus ExecGetSingleResult(sqlite3* db, T* result,
197  const std::string& cmd, Args&&... args) {
198  bool got = false;
199  std::function<int(T)> get = [&](T value) {
200  if (got)
201  return SQLITE_ERROR;
202 
203  using std::swap;
204  swap(value, *result);
205 
206  got = true;
207  return SQLITE_OK;
208  };
209  RETURN_IF_ERROR(ExecGetResults(db, get, cmd, std::forward<Args>(args)...));
211 }
212 
213 } // namespace
214 
215 
218  : db_(other.db_) {
219  other.db_ = nullptr;
220 }
222  if (db_) {
223  Rollback();
224  }
225 }
226 
228  if (db_) {
229  Rollback();
230  }
231  db_ = other.db_;
232  other.db_ = nullptr;
233  return *this;
234 }
235 
236 
237 DatabaseStatus SqliteTransaction::CreateDb(const std::string& db_name,
238  int64_t version) {
239  DCHECK(db_) << "Transaction is closed";
240  if (version <= 0)
242 
243  const std::string cmd =
244  "INSERT INTO databases (name, version) VALUES (?1, ?2)";
245  return ExecCommand(db_, cmd, db_name, version);
246 }
247 
249  int64_t version) {
250  DCHECK(db_) << "Transaction is closed";
251  int64_t old_version;
252  RETURN_IF_ERROR(GetDbVersion(db_name, &old_version));
253  if (version <= old_version)
255 
256  const std::string cmd = "UPDATE databases SET version = ?2 WHERE name == ?1";
257  return ExecCommand(db_, cmd, db_name, version);
258 }
259 
260 DatabaseStatus SqliteTransaction::DeleteDb(const std::string& db_name) {
261  DCHECK(db_) << "Transaction is closed";
262  // Check that it exists first so we can throw a not found error.
263  int64_t version;
264  RETURN_IF_ERROR(GetDbVersion(db_name, &version));
265 
266  // Because of the "ON CASCADE" on the table, we don't need to explicitly
267  // delete the stores or the data entries.
268  const std::string delete_cmd = "DELETE FROM databases WHERE name == ?1";
269  return ExecCommand(db_, delete_cmd, db_name);
270 }
271 
273  int64_t* version) {
274  DCHECK(db_) << "Transaction is closed";
275  const std::string cmd = "SELECT version FROM databases WHERE name == ?1";
276  return ExecGetSingleResult(db_, version, cmd, db_name);
277 }
278 
279 
281  const std::string& db_name, const std::string& store_name) {
282  DCHECK(db_) << "Transaction is closed";
283  const std::string cmd =
284  "INSERT INTO object_stores (db_name, store_name) VALUES (?1, ?2)";
285  // If the database doesn't exist, we'll get a foreign key error.
286  // If there is a store with the same name already, we'll get a primary key
287  // error.
288  return ExecCommand(db_, cmd, db_name, store_name);
289 }
290 
292  const std::string& db_name, const std::string& store_name) {
293  DCHECK(db_) << "Transaction is closed";
294  // Check that it exists first so we can throw a not found error.
295  int64_t store_id;
296  RETURN_IF_ERROR(GetStoreId(db_name, store_name, &store_id));
297 
298  // Because of the "ON CASCADE" on the table, we don't need to explicitly
299  // delete the data entries.
300  const std::string cmd =
301  "DELETE FROM object_stores WHERE db_name == ?1 AND store_name == ?2";
302  return ExecCommand(db_, cmd, db_name, store_name);
303 }
304 
306  const std::string& db_name, std::vector<std::string>* names) {
307  DCHECK(db_) << "Transaction is closed";
308  DCHECK(names);
309 
310  // Check that it exists first so we can throw a not found error.
311  int64_t version;
312  RETURN_IF_ERROR(GetDbVersion(db_name, &version));
313 
314  std::function<int(std::string)> cb = [&](std::string value) {
315  names->push_back(std::move(value));
316  return SQLITE_OK;
317  };
318  const std::string cmd =
319  "SELECT store_name FROM object_stores WHERE db_name == ?1";
320  return ExecGetResults(db_, cb, cmd, db_name);
321 }
322 
323 
324 DatabaseStatus SqliteTransaction::AddData(const std::string& db_name,
325  const std::string& store_name,
326  const std::vector<uint8_t>& data,
327  int64_t* key) {
328  DCHECK(db_) << "Transaction is closed";
329  int64_t store_id;
330  RETURN_IF_ERROR(GetStoreId(db_name, store_name, &store_id));
331 
332  const std::string select_cmd =
333  "SELECT COALESCE(MAX(key), 0) FROM objects WHERE store == ?1";
334  RETURN_IF_ERROR(ExecGetSingleResult(db_, key, select_cmd, store_id));
335  (*key)++;
336 
337  const std::string insert_cmd =
338  "INSERT INTO objects (store, key, body) VALUES (?1, ?2, ?3)";
339  return ExecCommand(db_, insert_cmd, store_id, *key, data);
340 }
341 
342 DatabaseStatus SqliteTransaction::GetData(const std::string& db_name,
343  const std::string& store_name,
344  int64_t key,
345  std::vector<uint8_t>* data) {
346  DCHECK(db_) << "Transaction is closed";
347  const std::string cmd =
348  "SELECT body FROM objects "
349  "INNER JOIN object_stores ON object_stores.id == objects.store "
350  "WHERE db_name == ?1 AND store_name == ?2 AND key == ?3";
351  return ExecGetSingleResult(db_, data, cmd, db_name, store_name, key);
352 }
353 
355  const std::string& store_name,
356  int64_t key,
357  const std::vector<uint8_t>& data) {
358  DCHECK(db_) << "Transaction is closed";
359  int64_t store_id;
360  RETURN_IF_ERROR(GetStoreId(db_name, store_name, &store_id));
361 
362  const std::string cmd =
363  "INSERT OR REPLACE INTO objects (store, key, body) VALUES (?1, ?2, ?3)";
364  return ExecCommand(db_, cmd, store_id, key, data);
365 }
366 
368  const std::string& store_name,
369  int64_t key) {
370  DCHECK(db_) << "Transaction is closed";
371  const std::string cmd = R"(
372  DELETE FROM objects WHERE key == ?3 AND store == (
373  SELECT id FROM object_stores WHERE db_name == ?1 AND store_name == ?2)
374  )";
375  return ExecCommand(db_, cmd, db_name, store_name, key);
376 }
377 
378 DatabaseStatus SqliteTransaction::FindData(const std::string& db_name,
379  const std::string& store_name,
380  optional<int64_t> key,
381  bool ascending, int64_t* found_key) {
382  DCHECK(db_) << "Transaction is closed";
383  std::string cmd;
384  // Use StringPrintf for part of this since Sqlite parameters can't introduce
385  // syntax, they are just for expressions.
386  if (!key.has_value()) {
387  cmd = util::StringPrintf(
388  R"(
389  SELECT key FROM objects
390  WHERE store == (SELECT id FROM object_stores
391  WHERE db_name == ?1 AND store_name == ?2)
392  ORDER BY key %s
393  LIMIT 1
394  )",
395  ascending ? "ASC" : "DESC");
396  return ExecGetSingleResult(db_, found_key, cmd, db_name, store_name);
397  }
398  cmd = util::StringPrintf(
399  R"(
400  SELECT key FROM objects
401  WHERE store == (SELECT id FROM object_stores
402  WHERE db_name == ?1 AND store_name == ?2) AND
403  key %s ?3
404  ORDER BY key %s
405  LIMIT 1
406  )",
407  ascending ? ">" : "<", ascending ? "ASC" : "DESC");
408  return ExecGetSingleResult(db_, found_key, cmd, db_name, store_name,
409  key.value());
410 }
411 
412 
414  DCHECK(db_) << "Transaction is closed";
415  auto* db = db_;
416  db_ = nullptr;
417  return ExecCommand(db, "COMMIT");
418 }
419 
421  DCHECK(db_) << "Transaction is closed";
422  auto* db = db_;
423  db_ = nullptr;
424  return ExecCommand(db, "ROLLBACK");
425 }
426 
427 
428 DatabaseStatus SqliteTransaction::GetStoreId(const std::string& db_name,
429  const std::string& store_name,
430  int64_t* store_id) {
431  const std::string get_cmd =
432  "SELECT id FROM object_stores "
433  "WHERE db_name == ?1 AND store_name == ?2";
434  return ExecGetSingleResult(db_, store_id, get_cmd, db_name, store_name);
435 }
436 
437 
438 SqliteConnection::SqliteConnection(const std::string& file_path)
439  : path_(file_path), db_(nullptr) {}
441  if (db_) {
442  const auto ret = sqlite3_close(db_);
443  if (ret != SQLITE_OK) {
444  LOG(ERROR) << "Error closing sqlite connection: " << sqlite3_errstr(ret);
445  }
446  }
447 }
448 
450  sqlite3* db;
451  RETURN_IF_ERROR(MapErrorCode(sqlite3_open(path_.c_str(), &db)));
452  db_ = db;
453 
454  // Enable extended error codes.
455  RETURN_IF_ERROR(MapErrorCode(sqlite3_extended_result_codes(db, 1)));
456 
457  const std::string init_cmd = R"(
458  -- Timeout, in milliseconds, to wait if there is an exclusive lock on the
459  -- database. When in WAL mode, we can have non-exclusive writes, so we
460  -- should never get busy normally.
461  PRAGMA busy_timeout = 250;
462  PRAGMA foreign_keys = ON;
463  -- Switch to WAL journaling mode; this is faster (usually) and allows for
464  -- non-exclusive write transactions.
465  PRAGMA journal_mode = WAL;
466 
467  CREATE TABLE IF NOT EXISTS databases (
468  name TEXT NOT NULL PRIMARY KEY,
469  version INTEGER NOT NULL,
470  CHECK (version > 0)
471  ) WITHOUT ROWID;
472 
473  CREATE TABLE IF NOT EXISTS object_stores (
474  id INTEGER PRIMARY KEY NOT NULL,
475  db_name TEXT NOT NULL,
476  store_name TEXT NOT NULL,
477  UNIQUE (db_name, store_name),
478  FOREIGN KEY (db_name) REFERENCES databases(name) ON DELETE CASCADE
479  );
480 
481  CREATE TABLE IF NOT EXISTS objects (
482  store INTEGER NOT NULL,
483  key INTEGER NOT NULL,
484  body BLOB NOT NULL,
485  PRIMARY KEY (store, key),
486  FOREIGN KEY (store) REFERENCES object_stores (id) ON DELETE CASCADE
487  ) WITHOUT ROWID;
488  )";
489  RETURN_IF_ERROR(MapErrorCode(
490  sqlite3_exec(db, init_cmd.c_str(), nullptr, nullptr, nullptr)));
491 
493 }
494 
496  SqliteTransaction* transaction) {
497  RETURN_IF_ERROR(ExecCommand(db_, "BEGIN TRANSACTION"));
498  transaction->db_ = db_;
500 }
501 
503  return MapErrorCode(sqlite3_wal_checkpoint_v2(
504  db_, nullptr, SQLITE_CHECKPOINT_PASSIVE, nullptr, nullptr));
505 }
506 
507 } // namespace idb
508 } // namespace js
509 } // namespace shaka
DatabaseStatus DeleteData(const std::string &db_name, const std::string &store_name, int64_t key)
Definition: sqlite.cc:367
DatabaseStatus DeleteObjectStore(const std::string &db_name, const std::string &store_name)
Definition: sqlite.cc:291
DatabaseStatus ListObjectStores(const std::string &db_name, std::vector< std::string > *names)
Definition: sqlite.cc:305
std::string StringPrintf(const char *format,...)
Definition: utils.cc:49
DatabaseStatus GetData(const std::string &db_name, const std::string &store_name, int64_t key, std::vector< uint8_t > *data)
Definition: sqlite.cc:342
DatabaseStatus AddData(const std::string &db_name, const std::string &store_name, const std::vector< uint8_t > &data, int64_t *key)
Definition: sqlite.cc:324
DatabaseStatus UpdateData(const std::string &db_name, const std::string &store_name, int64_t key, const std::vector< uint8_t > &data)
Definition: sqlite.cc:354
SqliteConnection(const std::string &file_path)
Definition: sqlite.cc:438
DatabaseStatus GetDbVersion(const std::string &db_name, int64_t *version)
Definition: sqlite.cc:272
const T & value() const &
Definition: optional.h:147
DatabaseStatus CreateObjectStore(const std::string &db_name, const std::string &store_name)
Definition: sqlite.cc:280
ExceptionCode type
DatabaseStatus CreateDb(const std::string &db_name, int64_t version)
Definition: sqlite.cc:237
#define RETURN_IF_ERROR(code)
Definition: sqlite.cc:31
void swap(shared_lock< Mutex > &a, shared_lock< Mutex > &b)
Definition: shared_lock.h:161
DatabaseStatus FindData(const std::string &db_name, const std::string &store_name, optional< int64_t > key, bool ascending, int64_t *found_key)
Definition: sqlite.cc:378
DatabaseStatus UpdateDbVersion(const std::string &db_name, int64_t version)
Definition: sqlite.cc:248
SqliteTransaction & operator=(SqliteTransaction &&)
Definition: sqlite.cc:227
bool has_value() const
Definition: optional.h:143
DatabaseStatus DeleteDb(const std::string &db_name)
Definition: sqlite.cc:260
DatabaseStatus BeginTransaction(SqliteTransaction *transaction)
Definition: sqlite.cc:495