aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/core/lib/db
diff options
context:
space:
mode:
authorGravatar Justine Tunney <jart@google.com>2018-01-05 00:46:32 -0800
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2018-01-05 00:50:34 -0800
commitc026e3ffa08555927144e65abd9f681c6098d8c1 (patch)
tree798db3df0268ac8f585e0ab1fca5358a783fed25 /tensorflow/core/lib/db
parent05386f42f18d66bf9ff1d69edf5c47591e60f804 (diff)
Make SQLite veneer better
This iteration does a very good job following the contracts of the actual API. Smart pointers ensure objects are destroyed in the correct order. RAII locking and transactions are now possible, with Clang thread safety analysis. Tuning is now done with environment variables. PiperOrigin-RevId: 180897579
Diffstat (limited to 'tensorflow/core/lib/db')
-rw-r--r--tensorflow/core/lib/db/sqlite.cc333
-rw-r--r--tensorflow/core/lib/db/sqlite.h452
-rw-r--r--tensorflow/core/lib/db/sqlite_test.cc171
3 files changed, 567 insertions, 389 deletions
diff --git a/tensorflow/core/lib/db/sqlite.cc b/tensorflow/core/lib/db/sqlite.cc
index b0a9e2f0d8..76bf778b7b 100644
--- a/tensorflow/core/lib/db/sqlite.cc
+++ b/tensorflow/core/lib/db/sqlite.cc
@@ -14,224 +14,257 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/core/lib/db/sqlite.h"
-#include "tensorflow/core/lib/io/record_reader.h"
-#include "tensorflow/core/platform/logging.h"
+#include "tensorflow/core/lib/strings/stringprintf.h"
extern "C" int sqlite3_snapfn_init(sqlite3*, const char**, const void*);
namespace tensorflow {
namespace {
-void ExecuteOrLog(Sqlite* db, const char* sql) {
- Status s = db->Prepare(sql).StepAndReset();
- if (!s.ok()) {
- LOG(WARNING) << s.ToString();
- }
-}
-
-string ExecuteOrEmpty(Sqlite* db, const char* sql) {
- auto stmt = db->Prepare(sql);
- bool is_done = false;
- if (stmt.Step(&is_done).ok() && !is_done) {
- return stmt.ColumnString(0);
- }
- return "";
-}
-
-} // namespace
-
-/* static */
-xla::StatusOr<std::shared_ptr<Sqlite>> Sqlite::Open(const string& uri) {
- sqlite3* sqlite = nullptr;
- TF_RETURN_IF_ERROR(MakeStatus(sqlite3_open(uri.c_str(), &sqlite)));
- CHECK_EQ(SQLITE_OK, sqlite3_snapfn_init(sqlite, nullptr, nullptr));
- Sqlite* db = new Sqlite(sqlite, uri);
- // This is the SQLite default since 2016. However it's good to set
- // this anyway, since we might get linked against an older version of
- // the library, and it's pretty much impossible to change later.
- ExecuteOrLog(db, "PRAGMA page_size=4096");
- return std::shared_ptr<Sqlite>(db);
-}
-
-/* static */ Status Sqlite::MakeStatus(int resultCode) {
+error::Code GetTfErrorCode(int code) {
// See: https://sqlite.org/rescode.html
- switch (resultCode & 0xff) {
- case SQLITE_OK:
- case SQLITE_ROW: // sqlite3_step() has another row ready
- case SQLITE_DONE: // sqlite3_step() has finished executing
- return Status::OK();
+ switch (code & 0xff) {
+ case SQLITE_OK: // Successful result
+ case SQLITE_ROW: // Step has another row ready
+ case SQLITE_DONE: // Step has finished executing
+ return error::OK;
case SQLITE_ABORT: // Callback routine requested an abort
- return errors::Aborted(sqlite3_errstr(resultCode));
+ return error::ABORTED;
case SQLITE_READONLY: // Attempt to write a readonly database
case SQLITE_MISMATCH: // Data type mismatch
- return errors::FailedPrecondition(sqlite3_errstr(resultCode));
+ return error::FAILED_PRECONDITION;
case SQLITE_MISUSE: // Library used incorrectly
case SQLITE_INTERNAL: // Internal logic error in SQLite
- return errors::Internal(sqlite3_errstr(resultCode));
+ return error::INTERNAL;
case SQLITE_RANGE: // 2nd parameter to sqlite3_bind out of range
- return errors::OutOfRange(sqlite3_errstr(resultCode));
+ return error::OUT_OF_RANGE;
case SQLITE_CANTOPEN: // Unable to open the database file
case SQLITE_CONSTRAINT: // Abort due to constraint violation
case SQLITE_NOTFOUND: // Unknown opcode or statement parameter name
case SQLITE_NOTADB: // File opened that is not a database file
- return errors::InvalidArgument(sqlite3_errstr(resultCode));
+ return error::INVALID_ARGUMENT;
case SQLITE_CORRUPT: // The database disk image is malformed
- return errors::DataLoss(sqlite3_errstr(resultCode));
+ return error::DATA_LOSS;
case SQLITE_AUTH: // Authorization denied
case SQLITE_PERM: // Access permission denied
- return errors::PermissionDenied(sqlite3_errstr(resultCode));
+ return error::PERMISSION_DENIED;
case SQLITE_FULL: // Insertion failed because database is full
case SQLITE_TOOBIG: // String or BLOB exceeds size limit
case SQLITE_NOLFS: // Uses OS features not supported on host
- return errors::ResourceExhausted(sqlite3_errstr(resultCode));
+ return error::RESOURCE_EXHAUSTED;
case SQLITE_BUSY: // The database file is locked
case SQLITE_LOCKED: // A table in the database is locked
case SQLITE_PROTOCOL: // Database lock protocol error
- case SQLITE_NOMEM: // A malloc() failed
- return errors::Unavailable(sqlite3_errstr(resultCode));
+ case SQLITE_NOMEM: // Out of heap or perhaps lookaside memory
+ return error::UNAVAILABLE;
case SQLITE_INTERRUPT: // Operation terminated by sqlite3_interrupt
- return errors::Cancelled(sqlite3_errstr(resultCode));
+ return error::CANCELLED;
case SQLITE_ERROR: // SQL error or missing database
case SQLITE_IOERR: // Some kind of disk I/O error occurred
case SQLITE_SCHEMA: // The database schema changed
default:
- return errors::Unknown(sqlite3_errstr(resultCode));
- }
-}
-
-Sqlite::Sqlite(sqlite3* db, const string& uri) : db_(db), uri_(uri) {}
-
-Sqlite::~Sqlite() {
- // close_v2 doesn't care if a stmt hasn't been GC'd yet
- int rc = sqlite3_close_v2(db_);
- if (rc != SQLITE_OK) {
- LOG(ERROR) << "destruct sqlite3: " << MakeStatus(rc);
+ return error::UNKNOWN;
}
}
-Status Sqlite::Close() {
- if (db_ == nullptr) {
- return Status::OK();
- }
- // If Close is explicitly called, ordering must be correct.
- Status s = MakeStatus(sqlite3_close(db_));
- if (s.ok()) {
- db_ = nullptr;
- }
- return s;
+template <typename... Args>
+Status PrintfStatus(int rc, const char* fmt, Args&&... args) {
+ return {GetTfErrorCode(rc),
+ strings::Printf(fmt, std::forward<Args>(args)...)};
}
-void Sqlite::UseWriteAheadLogWithReducedDurabilityIfPossible() {
- // TensorFlow summaries are intensively write-heavy, cf. most apps.
- // This pragma loves writes and means that TensorBoard can read the
- // database even as the training job inserts stuff. In other words,
- // this makes SQLite almost as powerful as MySQL or PostgreSQL.
- // https://www.sqlite.org/wal.html
- string journal = ExecuteOrEmpty(this, "PRAGMA journal_mode=wal");
- if (journal != "wal") {
- LOG(WARNING) << "Failed to set journal_mode=wal because SQLite wants "
- << uri_ << " to be in '" << journal << "' mode, which might "
- << "be bad since WAL is important for the performance of "
- << "write-intensive apps. This might only happen for memory "
- << "databases or old versions of SQLite, but is definitely "
- << "worth fixing if that's not the case";
- } else {
- // This setting means we might lose transactions due to power loss,
- // but the database can't become corrupted. In exchange, we get the
- // the performance of a NoSQL database. This is a trade-off most data
- // scientists would consider acceptable.
- // https://www.sqlite.org/pragma.html#pragma_synchronous
- ExecuteOrLog(this, "PRAGMA synchronous=NORMAL");
- }
+Status AsStatus(Sqlite* db, int rc) EXCLUSIVE_LOCKS_REQUIRED(*db) {
+ if (TF_PREDICT_TRUE(rc == SQLITE_OK)) return Status::OK();
+ return {GetTfErrorCode(rc), db->errmsg()};
}
-SqliteStatement Sqlite::Prepare(const string& sql) {
+sqlite3_stmt* PrepareRawOrDie(sqlite3* db, const char* sql) {
sqlite3_stmt* stmt = nullptr;
- int rc = sqlite3_prepare_v2(db_, sql.c_str(), sql.size() + 1, &stmt, nullptr);
- if (rc == SQLITE_OK) {
- return {stmt, SQLITE_OK, std::unique_ptr<string>(nullptr)};
- } else {
- return {nullptr, rc, std::unique_ptr<string>(new string(sql))};
- }
+ int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
+ CHECK_EQ(SQLITE_OK, rc) << sql;
+ return stmt;
}
-Status SqliteStatement::status() const {
- Status s = Sqlite::MakeStatus(error_);
- if (!s.ok()) {
- if (stmt_ != nullptr) {
- errors::AppendToMessage(&s, sqlite3_sql(stmt_));
- } else {
- errors::AppendToMessage(&s, *prepare_error_sql_);
+Status SetEnvPragmaActual(Sqlite* db, const char* pragma, const char* var) {
+ const char* value = std::getenv(var);
+ if (value == nullptr || *value == '\0') return Status::OK();
+ for (const char* p = value; *p != '\0'; ++p) {
+ if (!(('0' <= *p && *p <= '9') || *p == '-' ||
+ ('A' <= *p && *p <= 'Z') ||
+ ('a' <= *p && *p <= 'z'))) {
+ return errors::InvalidArgument("Illegal character");
}
}
- return s;
+ // We can't use Bind*() for pragmas.
+ auto stmt = db->Prepare(strings::StrCat("PRAGMA ", pragma, "=", value));
+ TF_RETURN_IF_ERROR(stmt.status());
+ bool unused_done;
+ return stmt.ValueOrDie().Step(&unused_done);
}
-void SqliteStatement::CloseOrLog() {
- if (stmt_ != nullptr) {
- int rc = sqlite3_finalize(stmt_);
- if (rc != SQLITE_OK) {
- LOG(ERROR) << "destruct sqlite3_stmt: " << Sqlite::MakeStatus(rc);
- }
- stmt_ = nullptr;
- }
+Status EnvPragma(Sqlite* db, const char* pragma, const char* var) {
+ TF_RETURN_WITH_CONTEXT_IF_ERROR(SetEnvPragmaActual(db, pragma, var),
+ "getenv(", var, ")");
+ return Status::OK();
}
-Status SqliteStatement::Close() {
- if (stmt_ == nullptr) {
- return Status::OK();
- }
- int rc = sqlite3_finalize(stmt_);
- if (rc == SQLITE_OK) {
- stmt_ = nullptr;
+} // namespace
+
+/* static */
+xla::StatusOr<std::shared_ptr<Sqlite>> Sqlite::Open(string path, int flags) {
+ flags |= SQLITE_OPEN_PRIVATECACHE;
+ sqlite3* sqlite = nullptr;
+ int rc = sqlite3_open_v2(path.c_str(), &sqlite, flags, nullptr);
+ if (rc != SQLITE_OK) {
+ return PrintfStatus(rc, "Sqlite::Open(%s) failed: %s", path.c_str(),
+ sqlite3_errstr(rc));
}
- Update(rc);
- return status();
+ CHECK_EQ(SQLITE_OK, sqlite3_extended_result_codes(sqlite, 1));
+ CHECK_EQ(SQLITE_OK, sqlite3_snapfn_init(sqlite, nullptr, nullptr));
+ // Prepare these tiny privileged statements for SqliteTransaction
+ // so it can do less work, particularly in its constructor, per
+ // Google C++ Style.
+ sqlite3_stmt* begin = PrepareRawOrDie(sqlite, "BEGIN");
+ sqlite3_stmt* commit = PrepareRawOrDie(sqlite, "COMMIT");
+ sqlite3_stmt* rollback = PrepareRawOrDie(sqlite, "ROLLBACK");
+ auto r = std::shared_ptr<Sqlite>(
+ new Sqlite(sqlite, std::move(path), begin, commit, rollback));
+ r->self_ = std::weak_ptr<Sqlite>(r);
+ Sqlite* db = r.get();
+ // TensorFlow is designed to work well in all SQLite modes. However
+ // users might find tuning some these pragmas rewarding, depending on
+ // various considerations.
+ TF_RETURN_IF_ERROR(EnvPragma(db, "secure_delete", "TF_SQLITE_SECURE_DELETE"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "page_size", "TF_SQLITE_PAGE_SIZE"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "journal_mode", "TF_SQLITE_JOURNAL_MODE"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "synchronous", "TF_SQLITE_SYNCHRONOUS"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "mmap_size", "TF_SQLITE_MMAP_SIZE"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "locking_mode", "TF_SQLITE_LOCKING_MODE"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "cache_size", "TF_SQLITE_CACHE_SIZE"));
+ TF_RETURN_IF_ERROR(EnvPragma(db, "auto_vacuum", "TF_SQLITE_AUTO_VACUUM"));
+ return r;
}
-void SqliteStatement::Reset() {
- if (TF_PREDICT_TRUE(stmt_ != nullptr)) {
- sqlite3_reset(stmt_);
- sqlite3_clear_bindings(stmt_); // not nullptr friendly
+Sqlite::~Sqlite() {
+ sqlite3_finalize(rollback_);
+ sqlite3_finalize(commit_);
+ sqlite3_finalize(begin_);
+ CHECK_EQ(SQLITE_OK, sqlite3_close(db_));
+}
+
+xla::StatusOr<SqliteStatement> Sqlite::Prepare(const StringPiece& sql) {
+ SqliteLock lock(*this);
+ sqlite3_stmt* stmt = nullptr;
+ int rc = sqlite3_prepare_v2(db_, sql.data(), static_cast<int>(sql.size()),
+ &stmt, nullptr);
+ if (rc != SQLITE_OK) {
+ return PrintfStatus(rc, "Prepare() failed: %s: %.*s", errmsg(), sql.size(),
+ sql.data());
}
- error_ = SQLITE_OK;
+ return SqliteStatement(stmt, self_.lock());
}
-Status SqliteStatement::Step(bool* isDone) {
- if (TF_PREDICT_FALSE(error_ != SQLITE_OK)) {
- *isDone = true;
- return status();
+Status SqliteStatement::Step(bool* is_done) {
+ DCHECK(stmt_ != nullptr);
+ if (TF_PREDICT_FALSE(bind_error_ != SQLITE_OK)) {
+ *is_done = true;
+ return PrintfStatus(bind_error_, "Bind(%d) failed: %s: %s",
+ bind_error_parameter_, sqlite3_errstr(bind_error_),
+ sql());
}
+ SqliteLock lock(*db_);
int rc = sqlite3_step(stmt_);
switch (rc) {
case SQLITE_ROW:
- *isDone = false;
+ *is_done = false;
return Status::OK();
case SQLITE_DONE:
- *isDone = true;
+ *is_done = true;
return Status::OK();
default:
- *isDone = true;
- error_ = rc;
- return status();
+ *is_done = true;
+ return PrintfStatus(rc, "Step() failed: %s: %s", db_->errmsg(), sql());
}
}
-Status SqliteStatement::StepAndReset() {
- if (TF_PREDICT_FALSE(error_ != SQLITE_OK)) {
- return status();
+bool SqliteStatement::StepOrDie() {
+ bool is_done;
+ TF_CHECK_OK(Step(&is_done));
+ return !is_done;
+}
+
+Status SqliteStatement::StepOnce() {
+ bool is_done;
+ TF_RETURN_IF_ERROR(Step(&is_done));
+ if (TF_PREDICT_FALSE(is_done)) {
+ return errors::Internal("No rows returned: ", sql());
}
- Status s;
- int rc = sqlite3_step(stmt_);
- if (rc != SQLITE_DONE) {
- if (rc == SQLITE_ROW) {
- s.Update(errors::Internal("unexpected sqlite row"));
- } else {
- s.Update(Sqlite::MakeStatus(rc));
- }
+ return Status::OK();
+}
+
+const SqliteStatement& SqliteStatement::StepOnceOrDie() {
+ TF_CHECK_OK(StepOnce());
+ return *this;
+}
+
+Status SqliteStatement::StepAndReset() {
+ bool is_done;
+ Status s = Step(&is_done);
+ if (TF_PREDICT_FALSE(s.ok() && !is_done)) {
+ s = errors::Internal("Unexpected row: ", sql());
}
Reset();
return s;
}
+void SqliteStatement::StepAndResetOrDie() { TF_CHECK_OK(StepAndReset()); }
+
+void SqliteStatement::Reset() {
+ if (TF_PREDICT_TRUE(stmt_ != nullptr)) {
+ sqlite3_reset(stmt_);
+ sqlite3_clear_bindings(stmt_);
+ }
+ bind_error_ = SQLITE_OK;
+ size_ = 0;
+}
+
+SqliteTransaction::SqliteTransaction(Sqlite& db) : db_(&db) {
+ sqlite3_mutex_enter(sqlite3_db_mutex(db_->db_));
+ CHECK(!db_->is_in_transaction_);
+ db_->is_in_transaction_ = true;
+ Begin();
+}
+
+SqliteTransaction::~SqliteTransaction() {
+ // Rollback should only return an error if there's no transaction.
+ // Since the API performs auto-rollbacks in some cases, we ignore.
+ sqlite3_step(db_->rollback_);
+ sqlite3_reset(db_->rollback_);
+ sqlite3_reset(db_->begin_);
+ db_->is_in_transaction_ = false;
+ sqlite3_mutex_leave(sqlite3_db_mutex(db_->db_));
+}
+
+void SqliteTransaction::Begin() {
+ // This shouldn't allocate memory or perform I/O. All it does is
+ // execute OP_AutoCommit(0, 0) a.k.a. BEGIN DEFERRED which flips
+ // the sqlite3::autoCommit bit.
+ if (sqlite3_step(db_->begin_) != SQLITE_DONE) {
+ // It shouldn't be possible for this to fail since we already
+ // performed the reentrancy check.
+ LOG(FATAL) << "BEGIN failed: " << sqlite3_errmsg(db_->db_);
+ }
+}
+
+Status SqliteTransaction::Commit() {
+ int rc = sqlite3_step(db_->commit_);
+ if (rc != SQLITE_DONE) {
+ return PrintfStatus(rc, "COMMIT failed: %s", sqlite3_errmsg(db_->db_));
+ }
+ sqlite3_reset(db_->commit_);
+ sqlite3_reset(db_->begin_);
+ Begin();
+ return Status::OK();
+}
+
} // namespace tensorflow
diff --git a/tensorflow/core/lib/db/sqlite.h b/tensorflow/core/lib/db/sqlite.h
index 12840bd42b..49a989a91c 100644
--- a/tensorflow/core/lib/db/sqlite.h
+++ b/tensorflow/core/lib/db/sqlite.h
@@ -17,155 +17,212 @@ limitations under the License.
#include <cstddef>
#include <memory>
+#include <mutex>
#include <utility>
#include "sqlite3.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/macros.h"
+#include "tensorflow/core/platform/thread_annotations.h"
#include "tensorflow/core/platform/types.h"
+/// TensorFlow SQLite Veneer
+///
+/// - Memory safety
+/// - Less boilerplate
+/// - Removes deprecated stuff
+/// - Pretends UTF16 doesn't exist
+/// - Transaction compile-time safety
+/// - Statically loads our native extensions
+/// - Error reporting via tensorflow::Status et al.
+///
+/// SQLite>=3.8.2 needs to be supported until April 2019, which is when
+/// Ubuntu 14.04 LTS becomes EOL.
+
namespace tensorflow {
+class SqliteLock;
class SqliteStatement;
+class SqliteTransaction;
/// \brief SQLite connection object.
///
-/// This class is a thin wrapper around `sqlite3` that makes it easier
-/// and safer to use SQLite in the TensorFlow C++ codebase. It removes
-/// deprecated APIs, improves the safety of others, adds helpers, and
-/// pretends UTF16 doesn't exist.
+/// The SQLite connection is closed automatically by the destructor.
+/// Reference counting ensures that happens after its statements are
+/// destructed.
///
-/// Instances are thread safe, with the exception of Close().
-class Sqlite {
+/// This class offers the same thread safety behaviors and guarantees
+/// as the SQLite API itself.
+///
+/// This veneer uses auto-commit mode by default, which means a 4ms
+/// fsync() happens after every write unless a SqliteTransaction is
+/// used or WAL mode is enabled beforehand.
+class LOCKABLE Sqlite {
public:
+ /// \brief Closes SQLite connection, which can take milliseconds.
+ ~Sqlite();
+
/// \brief Opens SQLite database file.
///
- /// The `uri` parameter can be a filename, or a proper URI like
- /// `file:/tmp/tf.sqlite?mode=ro&cache=private`. It can also be
- /// `file::memory:` for testing.
+ /// Notes on a few of the flags:
///
- /// See https://sqlite.org/c3ref/open.html
- static xla::StatusOr<std::shared_ptr<Sqlite>> Open(const string& uri);
-
- /// \brief Makes tensorflow::Status for SQLite result code.
+ /// - SQLITE_OPEN_READONLY: Allowed if no WAL journal is active.
+ /// - SQLITE_OPEN_SHAREDCACHE: Will be ignored because this veneer
+ /// doesn't support the unlock notify API.
+ /// - SQLITE_OPEN_NOMUTEX: Means access to this connection MUST be
+ /// serialized by the caller in accordance with the same contracts
+ /// implemented by this API.
///
- /// See https://sqlite.org/rescode.html
- static Status MakeStatus(int resultCode);
+ /// This function sets PRAGMA values from TF_SQLITE_* environment
+ /// variables. See sqlite.cc to learn more.
+ static xla::StatusOr<std::shared_ptr<Sqlite>> Open(string path, int flags);
+ static xla::StatusOr<std::shared_ptr<Sqlite>> Open(string path) {
+ return Open(std::move(path), SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+ }
+ static std::shared_ptr<Sqlite> OpenOrDie(string path, int flags) {
+ return Open(std::move(path), flags).ValueOrDie();
+ }
+ static std::shared_ptr<Sqlite> OpenOrDie(string path) {
+ return Open(std::move(path)).ValueOrDie();
+ }
- /// \brief Destroys object and frees resources.
- ///
- /// This will free the underlying object if Close was not called. If
- /// an error code is returned then it will be logged.
+ /// \brief Creates SQLite statement.
///
- /// Note: Unlike Close() this destructor maps to sqlite3_close_v2(),
- /// which is lax about ordering and GC friendly.
- ~Sqlite();
+ /// If sql references tables then system calls taking microseconds
+ /// are needed and failure can happen on schema change. Otherwise
+ /// this should only fail on syntax error.
+ xla::StatusOr<SqliteStatement> Prepare(const StringPiece& sql);
+ SqliteStatement PrepareOrDie(const StringPiece& sql);
- /// \brief Frees underlying SQLite object.
+ /// \brief Returns extended result code of last error.
///
- /// Unlike the destructor, all SqliteStatement objects must be closed
- /// beforehand. This is a no-op if already closed
- Status Close();
+ /// If the most recent API call was successful, the result is
+ /// undefined. The legacy result code can be obtained by saying
+ /// errcode() & 0xff.
+ int errcode() const EXCLUSIVE_LOCKS_REQUIRED(this) {
+ return sqlite3_extended_errcode(db_);
+ }
- /// \brief Enables WAL mode with less fsync or log a warning.
- ///
- /// The synchronous pragma is only set to NORMAL if WAL mode was
- /// successfully enabled. This must be called immediately after
- /// creating the object.
- void UseWriteAheadLogWithReducedDurabilityIfPossible();
+ /// \brief Returns pointer to current error message state.
+ const char* errmsg() const EXCLUSIVE_LOCKS_REQUIRED(this) {
+ return sqlite3_errmsg(db_);
+ }
- /// \brief Creates SQLite statement.
- ///
- /// Call result.status() to determine whether or not this operation
- /// failed. It is also possible to punt the error checking to after
- /// the values have been binded and Step() or ExecuteWriteQuery() is
- /// called.
- SqliteStatement Prepare(const string& sql);
+ /// \brief Returns rowid assigned to last successful insert.
+ int64 last_insert_row_id() const EXCLUSIVE_LOCKS_REQUIRED(this) {
+ return sqlite3_last_insert_rowid(db_);
+ }
private:
- explicit Sqlite(sqlite3* db, const string& uri);
- sqlite3* db_;
- string uri_;
+ friend class SqliteLock;
+ friend class SqliteStatement;
+ friend class SqliteTransaction;
+
+ Sqlite(sqlite3* db, const string path, sqlite3_stmt* begin,
+ sqlite3_stmt* commit, sqlite3_stmt* rollback) noexcept
+ : db_(db),
+ path_(std::move(path)),
+ begin_(begin),
+ commit_(commit),
+ rollback_(rollback) {}
+
+ sqlite3* const db_;
+ const string path_;
+ sqlite3_stmt* const begin_;
+ sqlite3_stmt* const commit_;
+ sqlite3_stmt* const rollback_;
+ bool is_in_transaction_ = false;
+ std::weak_ptr<Sqlite> self_; // so prepare can pass to statements
+
TF_DISALLOW_COPY_AND_ASSIGN(Sqlite);
};
-/// \brief SQLite prepared statement cursor object.
+/// \brief SQLite prepared statement.
///
-/// This class tracks error state internally, like Status::Update.
+/// Instances can only be shared between threads if caller serializes
+/// access from first Bind*() to *Reset().
///
-/// Instances of this class are not thread safe.
+/// When reusing a statement in a loop, be certain to not have jumps
+/// betwixt Bind*() and *Reset().
class SqliteStatement {
public:
- /// \brief Constructs empty statement that should be assigned later.
- SqliteStatement() : stmt_(nullptr), error_(SQLITE_OK) {}
-
- /// \brief Empties object and finalizes statement if needed.
- ~SqliteStatement() { CloseOrLog(); }
+ /// \brief Initializes an empty statement to be assigned later.
+ SqliteStatement() noexcept = default;
- /// \brief Move constructor, after which <other> should not be used.
- SqliteStatement(SqliteStatement&& other);
-
- /// \brief Move assignment, after which <other> should not be used.
- SqliteStatement& operator=(SqliteStatement&& other);
+ /// \brief Finalizes statement.
+ ///
+ /// This can take milliseconds if it was blocking the Sqlite
+ /// connection object from being freed.
+ ~SqliteStatement() { /* ignore */ sqlite3_finalize(stmt_); }
- /// \brief Returns true if statement is not empty.
+ /// \brief Returns true if statement is initialized.
explicit operator bool() const { return stmt_ != nullptr; }
- /// \brief Returns SQLite result code state.
- ///
- /// This will be SQLITE_OK unless an error happened. If multiple
- /// errors happened, only the first error code will be returned.
- int error() const { return error_; }
+ /// \brief Returns SQL text from when this query was prepared.
+ const char* sql() const { return sqlite3_sql(stmt_); }
- /// \brief Returns error() as a tensorflow::Status.
- Status status() const;
+ /// \brief Number of bytes bound since last *Reset().
+ uint64 size() { return size_; }
- /// \brief Finalize statement object.
+ /// \brief Executes query for fetching arbitrary rows.
+ ///
+ /// `is_done` will always be set to true unless SQLITE_ROW is
+ /// returned by the underlying API. If status() is already in an
+ /// error state, then this method is a no-op and the existing status
+ /// is returned.
+ ///
+ /// The OrDie version returns `!is_done` which, if true, indicates a
+ /// row is available.
///
- /// Please note that the destructor can also do this. This method is
- /// a no-op if already closed.
- Status Close();
+ /// This statement should be Reset() or destructed when when finished
+ /// with the result.
+ Status Step(bool* is_done);
+ bool StepOrDie() TF_MUST_USE_RESULT;
- /// \brief Executes query and/or fetches next row.
+ /// \brief Executes query when only one row is desired.
///
- /// `isDone` will always be set to true unless SQLITE_ROW is returned
- /// by the underlying API. If status() is already in an error state,
- /// then this method is a no-op and the existing status is returned.
- Status Step(bool* isDone);
+ /// If a row isn't returned, an internal error Status is returned
+ /// that won't be reflected in the connection error state.
+ ///
+ /// This statement should be Reset() or destructed when when finished
+ /// with the result.
+ Status StepOnce();
+ const SqliteStatement& StepOnceOrDie();
- /// \brief Executes query that returns no data.
+ /// \brief Executes query, ensures zero rows returned, then Reset().
///
- /// This helper calls Step(), ensures SQLITE_DONE was returned, then
- /// resets the statement and clears the bindings. If status() is
- /// already in an error state, then this method is a no-op and the
- /// existing status is returned.
+ /// If a row is returned, an internal error Status is returned that
+ /// won't be reflected in the connection error state.
Status StepAndReset();
+ void StepAndResetOrDie();
/// \brief Resets statement so it can be executed again.
///
- /// - Resets the prepared statement
- /// - Sets all Bind*() values to NULL
- ///
- /// Support for calling sqlite3_reset() and sqlite3_clear_bindings()
- /// independently may be added in the future if a compelling use case
- /// can be demonstrated.
+ /// Implementation note: This method diverges from canonical API
+ /// behavior by calling sqlite3_clear_bindings() in addition to
+ /// sqlite3_reset(). That makes the veneer safer; we haven't found a
+ /// super compelling reason yet to call them independently.
void Reset();
/// \brief Binds signed 64-bit integer to 1-indexed query parameter.
void BindInt(int parameter, int64 value) {
- Update(sqlite3_bind_int64(stmt_, parameter, value));
+ Update(sqlite3_bind_int64(stmt_, parameter, value), parameter);
+ size_ += sizeof(int64);
}
- void BindInt(const string& parameter, int64 value) {
+ void BindInt(const char* parameter, int64 value) {
BindInt(GetParameterIndex(parameter), value);
}
/// \brief Binds double to 1-indexed query parameter.
void BindDouble(int parameter, double value) {
- Update(sqlite3_bind_double(stmt_, parameter, value));
+ Update(sqlite3_bind_double(stmt_, parameter, value), parameter);
+ size_ += sizeof(double);
}
- void BindDouble(const string& parameter, double value) {
+ void BindDouble(const char* parameter, double value) {
BindDouble(GetParameterIndex(parameter), value);
}
@@ -174,69 +231,67 @@ class SqliteStatement {
/// If NUL characters are present, they will still go in the DB and
/// be successfully retrieved by ColumnString(); however, the
/// behavior of these values with SQLite functions is undefined.
- void BindText(int parameter, const string& text) {
+ ///
+ /// When using the unsafe methods, the data must not be changed or
+ /// freed until this statement is Reset() or finalized.
+ void BindText(int parameter, const StringPiece& text) {
Update(sqlite3_bind_text64(stmt_, parameter, text.data(), text.size(),
- SQLITE_TRANSIENT, SQLITE_UTF8));
+ SQLITE_TRANSIENT, SQLITE_UTF8), parameter);
+ size_ += text.size();
}
- void BindText(const string& parameter, const string& text) {
+ void BindText(const char* parameter, const StringPiece& text) {
BindText(GetParameterIndex(parameter), text);
}
-
- /// \brief Copies binary data to 1-indexed query parameter.
- void BindBlob(int parameter, const string& blob) {
- Update(sqlite3_bind_blob64(stmt_, parameter, blob.data(), blob.size(),
- SQLITE_TRANSIENT));
- }
- void BindBlob(const string& parameter, const string& blob) {
- BindBlob(GetParameterIndex(parameter), blob);
- }
-
- /// \brief Binds UTF-8 text to 1-indexed query parameter.
- ///
- /// The contents of `text` must not be changed or freed until Reset()
- /// or Close() is called.
- ///
- /// If NUL characters are present, they will still go in the DB and
- /// be successfully retrieved by ColumnString(); however, the
- /// behavior of these values with SQLite functions is undefined.
- void BindTextUnsafe(int parameter, const string& text) {
+ void BindTextUnsafe(int parameter, const StringPiece& text) {
Update(sqlite3_bind_text64(stmt_, parameter, text.data(), text.size(),
- SQLITE_STATIC, SQLITE_UTF8));
+ SQLITE_STATIC, SQLITE_UTF8), parameter);
+ size_ += text.size();
}
- void BindTextUnsafe(const string& parameter, const string& text) {
+ void BindTextUnsafe(const char* parameter, const StringPiece& text) {
BindTextUnsafe(GetParameterIndex(parameter), text);
}
- /// \brief Binds binary data to 1-indexed query parameter.
+ /// \brief Copies binary data to 1-indexed query parameter.
///
- /// The contents of `blob` must not be changed or freed until Reset()
- /// or Close() is called.
- void BindBlobUnsafe(int parameter, const string& blob) {
+ /// When using the unsafe methods, the data must not be changed or
+ /// freed until this statement is Reset() or finalized.
+ void BindBlob(int parameter, const StringPiece& blob) {
+ Update(sqlite3_bind_blob64(stmt_, parameter, blob.data(), blob.size(),
+ SQLITE_TRANSIENT), parameter);
+ size_ += blob.size();
+ }
+ void BindBlob(const char* parameter, const StringPiece& blob) {
+ BindBlob(GetParameterIndex(parameter), blob);
+ }
+ void BindBlobUnsafe(int parameter, const StringPiece& blob) {
Update(sqlite3_bind_blob64(stmt_, parameter, blob.data(), blob.size(),
- SQLITE_STATIC));
+ SQLITE_STATIC), parameter);
+ size_ += blob.size();
}
- void BindBlobUnsafe(const string& parameter, const string& text) {
+ void BindBlobUnsafe(const char* parameter, const StringPiece& text) {
BindBlobUnsafe(GetParameterIndex(parameter), text);
}
/// \brief Returns number of columns in result set.
- int ColumnCount() TF_MUST_USE_RESULT { return sqlite3_column_count(stmt_); }
+ int ColumnCount() const TF_MUST_USE_RESULT {
+ return sqlite3_column_count(stmt_);
+ }
/// \brief Returns type of 0-indexed column value in row data.
///
/// Please note that SQLite is dynamically typed and the type of a
/// particular column can vary from row to row.
- int ColumnType(int column) TF_MUST_USE_RESULT {
+ int ColumnType(int column) const TF_MUST_USE_RESULT {
return sqlite3_column_type(stmt_, column);
}
/// \brief Returns 0-indexed column from row result coerced as an integer.
- int64 ColumnInt(int column) TF_MUST_USE_RESULT {
+ int64 ColumnInt(int column) const TF_MUST_USE_RESULT {
return sqlite3_column_int64(stmt_, column);
}
/// \brief Returns 0-indexed column from row result coerced as a double.
- double ColumnDouble(int column) TF_MUST_USE_RESULT {
+ double ColumnDouble(int column) const TF_MUST_USE_RESULT {
return sqlite3_column_double(stmt_, column);
}
@@ -244,80 +299,141 @@ class SqliteStatement {
///
/// NULL values are returned as empty string. This method should be
/// used for both BLOB and TEXT columns. See also: ColumnType().
- string ColumnString(int column) TF_MUST_USE_RESULT {
+ string ColumnString(int column) const TF_MUST_USE_RESULT {
auto data = sqlite3_column_blob(stmt_, column);
- if (data == nullptr) {
- return "";
- }
+ if (data == nullptr) return "";
return {static_cast<const char*>(data),
static_cast<size_t>(ColumnSize(column))};
}
/// \brief Returns pointer to binary data at 0-indexed column.
///
- /// The returned memory will be mutated or freed the next time
- /// Step() or Reset() is called. No NUL terminator is added. See
- /// ColumnSize(). Please note that an empty BLOB is NULL.
- const char* ColumnStringUnsafe(int column) TF_MUST_USE_RESULT {
- return static_cast<const char*>(sqlite3_column_blob(stmt_, column));
+ /// Empty values are returned as NULL. The returned memory will no
+ /// longer be valid the next time Step() or Reset() is called. No NUL
+ /// terminator is added.
+ StringPiece ColumnStringUnsafe(int column) const TF_MUST_USE_RESULT {
+ return {static_cast<const char*>(sqlite3_column_blob(stmt_, column)),
+ static_cast<size_t>(ColumnSize(column))};
}
/// \brief Returns number of bytes stored at 0-indexed column.
- int ColumnSize(int column) TF_MUST_USE_RESULT {
+ int ColumnSize(int column) const TF_MUST_USE_RESULT {
return sqlite3_column_bytes(stmt_, column);
}
+ /// \brief Move constructor, after which <other> is reset to empty.
+ SqliteStatement(SqliteStatement&& other) noexcept
+ : stmt_(other.stmt_),
+ db_(std::move(other.db_)),
+ bind_error_(other.bind_error_) {
+ other.stmt_ = nullptr;
+ other.bind_error_ = SQLITE_OK;
+ }
+
+ /// \brief Move assignment, after which <other> is reset to empty.
+ SqliteStatement& operator=(SqliteStatement&& other) noexcept {
+ if (&other != this) {
+ sqlite3_finalize(stmt_);
+ stmt_ = other.stmt_;
+ bind_error_ = other.bind_error_;
+ db_ = std::move(other.db_);
+ size_ = 0;
+ other.stmt_ = nullptr;
+ other.bind_error_ = SQLITE_OK;
+ }
+ return *this;
+ }
+
private:
- friend Sqlite;
- SqliteStatement(sqlite3_stmt* stmt, int error,
- std::unique_ptr<string> prepare_error_sql)
- : stmt_(stmt),
- error_(error),
- prepare_error_sql_(std::move(prepare_error_sql)) {}
- void CloseOrLog();
-
- void Update(int rc) {
+ friend class Sqlite;
+
+ SqliteStatement(sqlite3_stmt* stmt, std::shared_ptr<Sqlite> db) noexcept
+ : stmt_(stmt), db_(std::move(db)) {}
+
+ void Update(int rc, int parameter) {
+ // Binding strings can fail if they exceed length limit.
if (TF_PREDICT_FALSE(rc != SQLITE_OK)) {
- if (error_ == SQLITE_OK) {
- error_ = rc;
+ if (bind_error_ == SQLITE_OK) {
+ bind_error_ = rc;
+ bind_error_parameter_ = parameter;
}
}
}
- int GetParameterIndex(const string& parameter) {
- // Each call to this function requires O(n) strncmp().
- int index = sqlite3_bind_parameter_index(stmt_, parameter.c_str());
- if (TF_PREDICT_FALSE(index == 0)) {
- Update(SQLITE_NOTFOUND);
- }
+ int GetParameterIndex(const char* parameter) {
+ int index = sqlite3_bind_parameter_index(stmt_, parameter);
+ DCHECK(index > 0); // OK to compile away since it'll fail again
return index;
}
- sqlite3_stmt* stmt_;
- int error_;
- std::unique_ptr<string> prepare_error_sql_;
+ sqlite3_stmt* stmt_ = nullptr;
+ std::shared_ptr<Sqlite> db_;
+ int bind_error_ = SQLITE_OK;
+ int bind_error_parameter_ = 0;
+ uint64 size_ = 0;
TF_DISALLOW_COPY_AND_ASSIGN(SqliteStatement);
};
-inline SqliteStatement::SqliteStatement(SqliteStatement&& other)
- : stmt_(other.stmt_),
- error_(other.error_),
- prepare_error_sql_(std::move(other.prepare_error_sql_)) {
- other.stmt_ = nullptr;
- other.error_ = SQLITE_OK;
-}
-
-inline SqliteStatement& SqliteStatement::operator=(SqliteStatement&& other) {
- if (&other != this) {
- CloseOrLog();
- stmt_ = other.stmt_;
- error_ = other.error_;
- prepare_error_sql_ = std::move(other.prepare_error_sql_);
- other.stmt_ = nullptr;
- other.error_ = SQLITE_OK;
+/// \brief Reentrant SQLite connection object lock
+///
+/// This is a no-op if SQLITE_OPEN_NOMUTEX was used.
+class SCOPED_LOCKABLE SqliteLock {
+ public:
+ explicit SqliteLock(Sqlite& db) EXCLUSIVE_LOCK_FUNCTION(db)
+ : mutex_(sqlite3_db_mutex(db.db_)) {
+ sqlite3_mutex_enter(mutex_);
+ }
+ SqliteLock(Sqlite& db, std::try_to_lock_t) EXCLUSIVE_LOCK_FUNCTION(db)
+ : mutex_(sqlite3_db_mutex(db.db_)) {
+ if (TF_PREDICT_FALSE(sqlite3_mutex_try(mutex_) != SQLITE_OK)) {
+ is_locked_ = false;
+ }
+ }
+ ~SqliteLock() UNLOCK_FUNCTION() {
+ if (is_locked_) sqlite3_mutex_leave(mutex_);
}
- return *this;
+ explicit operator bool() const { return is_locked_; }
+
+ private:
+ sqlite3_mutex* const mutex_;
+ bool is_locked_ = true;
+ TF_DISALLOW_COPY_AND_ASSIGN(SqliteLock);
+};
+#define SqliteLock(x) static_assert(0, "sqlite_lock_decl_missing_name");
+
+/// \brief SQLite transaction scope.
+///
+/// This class acquires an exclusive lock on the connection object (if
+/// mutexes weren't disabled) and runs BEGIN / ROLLBACK automatically.
+/// Unlike SqliteLock this scope is non-reentrant. To avoid program
+/// crashes, business logic should use the EXCLUSIVE_LOCK_FUNCTION and
+/// LOCKS_EXCLUDED annotations as much as possible.
+class SCOPED_LOCKABLE SqliteTransaction {
+ public:
+ /// \brief Locks db and begins deferred transaction.
+ ///
+ /// This will crash if a transaction is already active.
+ explicit SqliteTransaction(Sqlite& db) EXCLUSIVE_LOCK_FUNCTION(db);
+
+ /// \brief Runs ROLLBACK and unlocks.
+ ~SqliteTransaction() UNLOCK_FUNCTION();
+
+ /// \brief Commits transaction.
+ ///
+ /// If this is successful, a new transaction will be started, which
+ /// is rolled back when exiting the scope.
+ Status Commit();
+
+ private:
+ void Begin();
+ Sqlite* const db_;
+
+ TF_DISALLOW_COPY_AND_ASSIGN(SqliteTransaction);
+};
+
+inline SqliteStatement Sqlite::PrepareOrDie(const StringPiece& sql) {
+ return Prepare(sql).ValueOrDie();
}
} // namespace tensorflow
diff --git a/tensorflow/core/lib/db/sqlite_test.cc b/tensorflow/core/lib/db/sqlite_test.cc
index 29772b88ea..f93b3d8d80 100644
--- a/tensorflow/core/lib/db/sqlite_test.cc
+++ b/tensorflow/core/lib/db/sqlite_test.cc
@@ -14,13 +14,13 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/core/lib/db/sqlite.h"
-#include <limits.h>
#include <array>
+#include <climits>
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/io/path.h"
-#include "tensorflow/core/lib/strings/strcat.h"
+#include "tensorflow/core/lib/strings/stringprintf.h"
#include "tensorflow/core/platform/test.h"
namespace tensorflow {
@@ -29,23 +29,22 @@ namespace {
class SqliteTest : public ::testing::Test {
protected:
void SetUp() override {
- db_ = Sqlite::Open(":memory:").ValueOrDie();
- auto stmt = db_->Prepare("CREATE TABLE T (a BLOB, b BLOB)");
- TF_ASSERT_OK(stmt.StepAndReset());
+ db_ = Sqlite::OpenOrDie(":memory:");
+ db_->PrepareOrDie("CREATE TABLE T (a BLOB, b BLOB)").StepAndResetOrDie();
}
std::shared_ptr<Sqlite> db_;
bool is_done_;
};
TEST_F(SqliteTest, InsertAndSelectInt) {
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindInt(1, 3);
stmt.BindInt(2, -7);
TF_ASSERT_OK(stmt.StepAndReset());
stmt.BindInt(1, 123);
stmt.BindInt(2, -123);
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT a, b FROM T ORDER BY b");
+ stmt = db_->PrepareOrDie("SELECT a, b FROM T ORDER BY b");
TF_ASSERT_OK(stmt.Step(&is_done_));
ASSERT_FALSE(is_done_);
EXPECT_EQ(123, stmt.ColumnInt(0));
@@ -59,11 +58,11 @@ TEST_F(SqliteTest, InsertAndSelectInt) {
}
TEST_F(SqliteTest, InsertAndSelectDouble) {
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindDouble(1, 6.28318530);
stmt.BindDouble(2, 1.61803399);
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT a, b FROM T");
+ stmt = db_->PrepareOrDie("SELECT a, b FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ(6.28318530, stmt.ColumnDouble(0));
EXPECT_EQ(1.61803399, stmt.ColumnDouble(1));
@@ -74,11 +73,11 @@ TEST_F(SqliteTest, InsertAndSelectDouble) {
TEST_F(SqliteTest, NulCharsInString) {
string s; // XXX: Want to write {2, '\0'} but not sure why not.
s.append(static_cast<size_t>(2), '\0');
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindBlob(1, s);
stmt.BindText(2, s);
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT a, b FROM T");
+ stmt = db_->PrepareOrDie("SELECT a, b FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ(2, stmt.ColumnSize(0));
EXPECT_EQ(2, stmt.ColumnString(0).size());
@@ -92,58 +91,38 @@ TEST_F(SqliteTest, NulCharsInString) {
TEST_F(SqliteTest, Unicode) {
string s = "要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非";
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindBlob(1, s);
stmt.BindText(2, s);
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT a, b FROM T");
+ stmt = db_->PrepareOrDie("SELECT a, b FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ(s, stmt.ColumnString(0));
EXPECT_EQ(s, stmt.ColumnString(1));
}
TEST_F(SqliteTest, StepAndResetClearsBindings) {
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindInt(1, 1);
stmt.BindInt(2, 123);
TF_ASSERT_OK(stmt.StepAndReset());
stmt.BindInt(1, 2);
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT b FROM T ORDER BY a");
+ stmt = db_->PrepareOrDie("SELECT b FROM T ORDER BY a");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ(123, stmt.ColumnInt(0));
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ(SQLITE_NULL, stmt.ColumnType(0));
}
-TEST_F(SqliteTest, CloseBeforeFinalizeFails) {
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
- Status s = db_->Close();
- EXPECT_FALSE(s.ok());
-}
-
-// Rather than bothering to check the status code of creating a
-// statement and every single bind call afterwards, SqliteStatement
-// is designed to carry the first error state forward to Step().
-TEST_F(SqliteTest, ErrorPuntingDoesNotReportLibraryAbuse) {
- auto stmt = db_->Prepare("lol cat");
- EXPECT_FALSE(stmt.status().ok());
- EXPECT_EQ(SQLITE_ERROR, stmt.error());
- stmt.BindInt(1, 1);
- stmt.BindInt(2, 2);
- Status s = stmt.Step(&is_done_);
- EXPECT_EQ(SQLITE_ERROR, stmt.error()); // first error of several
- EXPECT_FALSE(s.ok());
-}
-
TEST_F(SqliteTest, SafeBind) {
string s = "hello";
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindBlob(1, s);
stmt.BindText(2, s);
s.at(0) = 'y';
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT a, b FROM T");
+ stmt = db_->PrepareOrDie("SELECT a, b FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ("hello", stmt.ColumnString(0));
EXPECT_EQ("hello", stmt.ColumnString(1));
@@ -151,42 +130,42 @@ TEST_F(SqliteTest, SafeBind) {
TEST_F(SqliteTest, UnsafeBind) {
string s = "hello";
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindBlobUnsafe(1, s);
stmt.BindTextUnsafe(2, s);
s.at(0) = 'y';
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT a, b FROM T");
+ stmt = db_->PrepareOrDie("SELECT a, b FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ("yello", stmt.ColumnString(0));
EXPECT_EQ("yello", stmt.ColumnString(1));
}
TEST_F(SqliteTest, UnsafeColumn) {
- auto stmt = db_->Prepare("INSERT INTO T (a, b) VALUES (?, ?)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
stmt.BindInt(1, 1);
stmt.BindText(2, "hello");
TF_ASSERT_OK(stmt.StepAndReset());
stmt.BindInt(1, 2);
stmt.BindText(2, "there");
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT b FROM T ORDER BY a");
+ stmt = db_->PrepareOrDie("SELECT b FROM T ORDER BY a");
TF_ASSERT_OK(stmt.Step(&is_done_));
- const char* p = stmt.ColumnStringUnsafe(0);
- EXPECT_EQ('h', *p);
+ StringPiece p = stmt.ColumnStringUnsafe(0);
+ EXPECT_EQ('h', *p.data());
TF_ASSERT_OK(stmt.Step(&is_done_));
// This will actually happen, but it's not safe to test this behavior.
- // EXPECT_EQ('t', *p);
+ // EXPECT_EQ('t', *p.data());
}
TEST_F(SqliteTest, NamedParameterBind) {
- auto stmt = db_->Prepare("INSERT INTO T (a) VALUES (:a)");
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a) VALUES (:a)");
stmt.BindText(":a", "lol");
TF_ASSERT_OK(stmt.StepAndReset());
- stmt = db_->Prepare("SELECT COUNT(*) FROM T");
+ stmt = db_->PrepareOrDie("SELECT COUNT(*) FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_EQ(1, stmt.ColumnInt(0));
- stmt = db_->Prepare("SELECT a FROM T");
+ stmt = db_->PrepareOrDie("SELECT a FROM T");
TF_ASSERT_OK(stmt.Step(&is_done_));
EXPECT_FALSE(is_done_);
EXPECT_EQ("lol", stmt.ColumnString(0));
@@ -195,57 +174,107 @@ TEST_F(SqliteTest, NamedParameterBind) {
TEST_F(SqliteTest, Statement_DefaultConstructor) {
SqliteStatement stmt;
EXPECT_FALSE(stmt);
- EXPECT_FALSE(stmt.StepAndReset().ok());
- stmt = db_->Prepare("INSERT INTO T (a) VALUES (1)");
+ stmt = db_->PrepareOrDie("INSERT INTO T (a) VALUES (1)");
EXPECT_TRUE(stmt);
EXPECT_TRUE(stmt.StepAndReset().ok());
}
TEST_F(SqliteTest, Statement_MoveConstructor) {
- SqliteStatement stmt{db_->Prepare("INSERT INTO T (a) VALUES (1)")};
+ SqliteStatement stmt{db_->PrepareOrDie("INSERT INTO T (a) VALUES (1)")};
EXPECT_TRUE(stmt.StepAndReset().ok());
}
TEST_F(SqliteTest, Statement_MoveAssignment) {
- SqliteStatement stmt1 = db_->Prepare("INSERT INTO T (a) VALUES (1)");
+ SqliteStatement stmt1 = db_->PrepareOrDie("INSERT INTO T (a) VALUES (1)");
SqliteStatement stmt2;
EXPECT_TRUE(stmt1.StepAndReset().ok());
- EXPECT_FALSE(stmt2.StepAndReset().ok());
+ EXPECT_FALSE(stmt2);
stmt2 = std::move(stmt1);
EXPECT_TRUE(stmt2.StepAndReset().ok());
}
TEST_F(SqliteTest, PrepareFailed) {
- SqliteStatement s = db_->Prepare("SELECT");
- EXPECT_FALSE(s.status().ok());
- EXPECT_NE(string::npos, s.status().error_message().find("SELECT"));
+ SqliteLock lock(*db_);
+ Status s = db_->Prepare("SELECT").status();
+ ASSERT_FALSE(s.ok());
+ EXPECT_NE(string::npos, s.error_message().find("SELECT"));
+ EXPECT_EQ(SQLITE_ERROR, db_->errcode());
}
TEST_F(SqliteTest, BindFailed) {
- SqliteStatement s = db_->Prepare("INSERT INTO T (a) VALUES (123)");
- EXPECT_TRUE(s.status().ok());
- EXPECT_EQ("", s.status().error_message());
- s.BindInt(1, 123);
- EXPECT_FALSE(s.status().ok());
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a) VALUES (123)");
+ stmt.BindInt(1, 123);
+ Status s = stmt.StepOnce();
EXPECT_NE(string::npos,
- s.status().error_message().find("INSERT INTO T (a) VALUES (123)"));
+ s.error_message().find("INSERT INTO T (a) VALUES (123)"))
+ << s.error_message();
}
TEST_F(SqliteTest, SnappyExtension) {
- auto stmt = db_->Prepare("SELECT UNSNAP(SNAP(?))");
+ auto stmt = db_->PrepareOrDie("SELECT UNSNAP(SNAP(?))");
stmt.BindText(1, "hello");
- TF_ASSERT_OK(stmt.Step(&is_done_));
- EXPECT_FALSE(is_done_);
- EXPECT_EQ("hello", stmt.ColumnString(0));
+ EXPECT_EQ("hello", stmt.StepOnceOrDie().ColumnString(0));
}
TEST_F(SqliteTest, SnappyBinaryCompatibility) {
- auto stmt = db_->Prepare(
- "SELECT UNSNAP(X'03207C746F6461792069732074686520656E64206F66207468652"
- "072657075626C6963')");
- TF_ASSERT_OK(stmt.Step(&is_done_));
- EXPECT_FALSE(is_done_);
- EXPECT_EQ("today is the end of the republic", stmt.ColumnString(0));
+ EXPECT_EQ(
+ "today is the end of the republic",
+ db_->PrepareOrDie("SELECT UNSNAP(X'03207C746F6461792069732074686520656E64"
+ "206F66207468652072657075626C6963')")
+ .StepOnceOrDie()
+ .ColumnString(0));
+}
+
+TEST(SqliteOpenTest, CloseConnectionBeforeStatement_KeepsConnectionOpen) {
+ auto s = Sqlite::OpenOrDie(":memory:")->PrepareOrDie("SELECT ? + ?");
+ s.BindInt(1, 7);
+ s.BindInt(2, 3);
+ EXPECT_EQ(10, s.StepOnceOrDie().ColumnInt(0));
+}
+
+TEST_F(SqliteTest, TransactionRollback) {
+ {
+ SqliteTransaction txn(*db_);
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
+ stmt.BindDouble(1, 6.28318530);
+ stmt.BindDouble(2, 1.61803399);
+ TF_ASSERT_OK(stmt.StepAndReset());
+ }
+ EXPECT_EQ(
+ 0,
+ db_->PrepareOrDie("SELECT COUNT(*) FROM T").StepOnceOrDie().ColumnInt(0));
+}
+
+TEST_F(SqliteTest, TransactionCommit) {
+ {
+ SqliteTransaction txn(*db_);
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
+ stmt.BindDouble(1, 6.28318530);
+ stmt.BindDouble(2, 1.61803399);
+ TF_ASSERT_OK(stmt.StepAndReset());
+ TF_ASSERT_OK(txn.Commit());
+ }
+ EXPECT_EQ(
+ 1,
+ db_->PrepareOrDie("SELECT COUNT(*) FROM T").StepOnceOrDie().ColumnInt(0));
+}
+
+TEST_F(SqliteTest, TransactionCommitMultipleTimes) {
+ {
+ SqliteTransaction txn(*db_);
+ auto stmt = db_->PrepareOrDie("INSERT INTO T (a, b) VALUES (?, ?)");
+ stmt.BindDouble(1, 6.28318530);
+ stmt.BindDouble(2, 1.61803399);
+ TF_ASSERT_OK(stmt.StepAndReset());
+ TF_ASSERT_OK(txn.Commit());
+ stmt.BindDouble(1, 6.28318530);
+ stmt.BindDouble(2, 1.61803399);
+ TF_ASSERT_OK(stmt.StepAndReset());
+ TF_ASSERT_OK(txn.Commit());
+ }
+ EXPECT_EQ(
+ 2,
+ db_->PrepareOrDie("SELECT COUNT(*) FROM T").StepOnceOrDie().ColumnInt(0));
}
} // namespace