summaryrefslogtreecommitdiff
path: root/platform/qt/src/sqlite3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'platform/qt/src/sqlite3.cpp')
-rw-r--r--platform/qt/src/sqlite3.cpp294
1 files changed, 153 insertions, 141 deletions
diff --git a/platform/qt/src/sqlite3.cpp b/platform/qt/src/sqlite3.cpp
index 80efd6a326..351991f881 100644
--- a/platform/qt/src/sqlite3.cpp
+++ b/platform/qt/src/sqlite3.cpp
@@ -24,11 +24,11 @@ namespace mapbox {
namespace sqlite {
// https://www.sqlite.org/rescode.html#ok
-static_assert(mbgl::underlying_type(Exception::OK) == 0, "error");
+static_assert(mbgl::underlying_type(ResultCode::OK) == 0, "error");
// https://www.sqlite.org/rescode.html#cantopen
-static_assert(mbgl::underlying_type(Exception::CANTOPEN) == 14, "error");
+static_assert(mbgl::underlying_type(ResultCode::CantOpen) == 14, "error");
// https://www.sqlite.org/rescode.html#notadb
-static_assert(mbgl::underlying_type(Exception::NOTADB) == 26, "error");
+static_assert(mbgl::underlying_type(ResultCode::NotADB) == 26, "error");
void checkQueryError(const QSqlQuery& query) {
QSqlError lastError = query.lastError();
@@ -52,15 +52,6 @@ void checkDatabaseError(const QSqlDatabase &db) {
}
}
-void checkDatabaseOpenError(const QSqlDatabase &db) {
- // Assume every error when opening the data as CANTOPEN. Qt
- // always returns -1 for `nativeErrorCode()` on database errors.
- QSqlError lastError = db.lastError();
- if (lastError.type() != QSqlError::NoError) {
- throw Exception { Exception::Code::CANTOPEN, "Error opening the database." };
- }
-}
-
namespace {
QString incrementCounter() {
static QAtomicInt count = 0;
@@ -70,32 +61,9 @@ namespace {
class DatabaseImpl {
public:
- DatabaseImpl(const char* filename, int flags)
- : connectionName(QString::number(uint64_t(QThread::currentThread())) + incrementCounter())
+ DatabaseImpl(QString connectionName_)
+ : connectionName(std::move(connectionName_))
{
- if (!QSqlDatabase::drivers().contains("QSQLITE")) {
- throw Exception { Exception::Code::CANTOPEN, "SQLite driver not found." };
- }
-
- assert(!QSqlDatabase::contains(connectionName));
- auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
-
- QString connectOptions = db.connectOptions();
- if (flags & OpenFlag::ReadOnly) {
- if (!connectOptions.isEmpty()) connectOptions.append(';');
- connectOptions.append("QSQLITE_OPEN_READONLY");
- }
- if (flags & OpenFlag::SharedCache) {
- if (!connectOptions.isEmpty()) connectOptions.append(';');
- connectOptions.append("QSQLITE_ENABLE_SHARED_CACHE");
- }
-
- db.setConnectOptions(connectOptions);
- db.setDatabaseName(QString(filename));
-
- if (!db.open()) {
- checkDatabaseOpenError(db);
- }
}
~DatabaseImpl() {
@@ -127,12 +95,51 @@ public:
template <typename T>
using optional = std::experimental::optional<T>;
+mapbox::util::variant<Database, Exception> Database::tryOpen(const std::string &filename, int flags) {
+ if (!QSqlDatabase::drivers().contains("QSQLITE")) {
+ return Exception { ResultCode::CantOpen, "SQLite driver not found." };
+ }
+
+ QString connectionName = QString::number(uint64_t(QThread::currentThread())) + incrementCounter();
-Database::Database(const std::string& file, int flags)
- : impl(std::make_unique<DatabaseImpl>(file.c_str(), flags)) {
- assert(impl);
+ assert(!QSqlDatabase::contains(connectionName));
+ auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
+
+ QString connectOptions = db.connectOptions();
+ if (flags & OpenFlag::ReadOnly) {
+ if (!connectOptions.isEmpty()) connectOptions.append(';');
+ connectOptions.append("QSQLITE_OPEN_READONLY");
+ }
+ if (flags & OpenFlag::SharedCache) {
+ if (!connectOptions.isEmpty()) connectOptions.append(';');
+ connectOptions.append("QSQLITE_ENABLE_SHARED_CACHE");
+ }
+
+ db.setConnectOptions(connectOptions);
+ db.setDatabaseName(QString(filename.c_str()));
+
+ if (!db.open()) {
+ // Assume every error when opening the data as CANTOPEN. Qt
+ // always returns -1 for `nativeErrorCode()` on database errors.
+ return Exception { ResultCode::CantOpen, "Error opening the database." };
+ }
+
+ return Database(std::make_unique<DatabaseImpl>(connectionName));
}
+Database Database::open(const std::string &filename, int flags) {
+ auto result = tryOpen(filename, flags);
+ if (result.is<Exception>()) {
+ throw result.get<Exception>();
+ } else {
+ return std::move(result.get<Database>());
+ }
+}
+
+Database::Database(std::unique_ptr<DatabaseImpl> impl_)
+ : impl(std::move(impl_))
+{}
+
Database::Database(Database &&other)
: impl(std::move(other.impl)) {
assert(impl);
@@ -165,7 +172,9 @@ void Database::setBusyTimeout(std::chrono::milliseconds timeout) {
}
db.setConnectOptions(connectOptions);
if (!db.open()) {
- checkDatabaseOpenError(db);
+ // Assume every error when opening the data as CANTOPEN. Qt
+ // always returns -1 for `nativeErrorCode()` on database errors.
+ throw Exception { ResultCode::CantOpen, "Error opening the database." };
}
}
@@ -186,74 +195,82 @@ void Database::exec(const std::string &sql) {
}
}
-Statement Database::prepare(const char *query) {
- return Statement(this, query);
-}
-
-Statement::Statement(Database *db, const char *sql)
- : impl(std::make_unique<StatementImpl>(QString(sql), QSqlDatabase::database(db->impl->connectionName))) {
+Statement::Statement(Database& db, const char* sql)
+ : impl(std::make_unique<StatementImpl>(QString(sql),
+ QSqlDatabase::database(db.impl->connectionName))) {
assert(impl);
}
-Statement::Statement(Statement &&other)
- : impl(std::move(other.impl)) {
- assert(impl);
+Statement::~Statement() {
+#ifndef NDEBUG
+ // Crash if we're destructing this object while we know a Query object references this.
+ assert(!used);
+#endif
}
-Statement &Statement::operator=(Statement &&other) {
- assert(impl);
- std::swap(impl, other.impl);
- return *this;
+Query::Query(Statement& stmt_) : stmt(stmt_) {
+ assert(stmt.impl);
+
+#ifndef NDEBUG
+ assert(!stmt.used);
+ stmt.used = true;
+#endif
}
-Statement::~Statement() {
+Query::~Query() {
+ reset();
+ clearBindings();
+
+#ifndef NDEBUG
+ stmt.used = false;
+#endif
}
-template void Statement::bind(int, int64_t);
+template void Query::bind(int, int64_t);
template <typename T>
-void Statement::bind(int offset, T value) {
- assert(impl);
+void Query::bind(int offset, T value) {
+ assert(stmt.impl);
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, QVariant::fromValue<T>(value), QSql::In);
- checkQueryError(impl->query);
+ stmt.impl->query.bindValue(offset - 1, QVariant::fromValue<T>(value), QSql::In);
+ checkQueryError(stmt.impl->query);
}
template <>
-void Statement::bind(int offset, std::nullptr_t) {
- assert(impl);
+void Query::bind(int offset, std::nullptr_t) {
+ assert(stmt.impl);
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, QVariant(QVariant::Invalid), QSql::In);
- checkQueryError(impl->query);
+ stmt.impl->query.bindValue(offset - 1, QVariant(QVariant::Invalid), QSql::In);
+ checkQueryError(stmt.impl->query);
}
template <>
-void Statement::bind(int offset, int32_t value) {
+void Query::bind(int offset, int32_t value) {
bind(offset, static_cast<int64_t>(value));
}
template <>
-void Statement::bind(int offset, bool value) {
+void Query::bind(int offset, bool value) {
bind(offset, static_cast<int>(value));
}
template <>
-void Statement::bind(int offset, int8_t value) {
+void Query::bind(int offset, int8_t value) {
bind(offset, static_cast<int64_t>(value));
}
template <>
-void Statement::bind(int offset, uint8_t value) {
+void Query::bind(int offset, uint8_t value) {
bind(offset, static_cast<int64_t>(value));
}
template <>
-void Statement::bind(int offset, mbgl::Timestamp value) {
+void Query::bind(int offset, mbgl::Timestamp value) {
bind(offset, std::chrono::system_clock::to_time_t(value));
}
template <>
-void Statement::bind(int offset, optional<std::string> value) {
+void Query::bind(int offset, optional<std::string> value) {
if (value) {
bind(offset, *value);
} else {
@@ -262,7 +279,7 @@ void Statement::bind(int offset, optional<std::string> value) {
}
template <>
-void Statement::bind(int offset, optional<mbgl::Timestamp> value) {
+void Query::bind(int offset, optional<mbgl::Timestamp> value) {
if (value) {
bind(offset, *value);
} else {
@@ -270,30 +287,25 @@ void Statement::bind(int offset, optional<mbgl::Timestamp> value) {
}
}
-void Statement::bind(int offset, const char* value, std::size_t length, bool retain) {
- assert(impl);
+void Query::bind(int offset, const char* value, std::size_t length, bool /* retain */) {
+ assert(stmt.impl);
if (length > std::numeric_limits<int>::max()) {
// Kept for consistence with the default implementation.
throw std::range_error("value too long");
}
- // Qt SQLite driver treats QByteArray as blob: we need to explicitly
- // declare the variant type as string.
- QVariant text(QVariant::Type::String);
- text.setValue(retain ? QByteArray(value, length) : QByteArray::fromRawData(value, length));
-
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, std::move(text), QSql::In);
+ stmt.impl->query.bindValue(offset - 1, QString(QByteArray(value, length)), QSql::In);
- checkQueryError(impl->query);
+ checkQueryError(stmt.impl->query);
}
-void Statement::bind(int offset, const std::string& value, bool retain) {
+void Query::bind(int offset, const std::string& value, bool retain) {
bind(offset, value.data(), value.size(), retain);
}
-void Statement::bindBlob(int offset, const void* value_, std::size_t length, bool retain) {
- assert(impl);
+void Query::bindBlob(int offset, const void* value_, std::size_t length, bool retain) {
+ assert(stmt.impl);
const char* value = reinterpret_cast<const char*>(value_);
if (length > std::numeric_limits<int>::max()) {
// Kept for consistence with the default implementation.
@@ -301,123 +313,123 @@ void Statement::bindBlob(int offset, const void* value_, std::size_t length, boo
}
// Field numbering starts at 0.
- impl->query.bindValue(offset - 1, retain ? QByteArray(value, length) :
+ stmt.impl->query.bindValue(offset - 1, retain ? QByteArray(value, length) :
QByteArray::fromRawData(value, length), QSql::In | QSql::Binary);
- checkQueryError(impl->query);
+ checkQueryError(stmt.impl->query);
}
-void Statement::bindBlob(int offset, const std::vector<uint8_t>& value, bool retain) {
+void Query::bindBlob(int offset, const std::vector<uint8_t>& value, bool retain) {
bindBlob(offset, value.data(), value.size(), retain);
}
-bool Statement::run() {
- assert(impl);
+bool Query::run() {
+ assert(stmt.impl);
- if (!impl->query.isValid()) {
- if (impl->query.exec()) {
- impl->lastInsertRowId = impl->query.lastInsertId().value<int64_t>();
- impl->changes = impl->query.numRowsAffected();
+ if (!stmt.impl->query.isValid()) {
+ if (stmt.impl->query.exec()) {
+ stmt.impl->lastInsertRowId = stmt.impl->query.lastInsertId().value<int64_t>();
+ stmt.impl->changes = stmt.impl->query.numRowsAffected();
} else {
- checkQueryError(impl->query);
+ checkQueryError(stmt.impl->query);
}
}
- const bool hasNext = impl->query.next();
- if (!hasNext) impl->query.finish();
+ const bool hasNext = stmt.impl->query.next();
+ if (!hasNext) stmt.impl->query.finish();
return hasNext;
}
-template bool Statement::get(int);
-template int Statement::get(int);
-template int64_t Statement::get(int);
-template double Statement::get(int);
+template bool Query::get(int);
+template int Query::get(int);
+template int64_t Query::get(int);
+template double Query::get(int);
-template <typename T> T Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <typename T> T Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
return value.value<T>();
}
-template <> std::vector<uint8_t> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QByteArray byteArray = impl->query.value(offset).toByteArray();
- checkQueryError(impl->query);
+template <> std::vector<uint8_t> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QByteArray byteArray = stmt.impl->query.value(offset).toByteArray();
+ checkQueryError(stmt.impl->query);
std::vector<uint8_t> blob(byteArray.begin(), byteArray.end());
return blob;
}
-template <> mbgl::Timestamp Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> mbgl::Timestamp Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
return std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::from_time_t(value.value<::time_t>()));
}
-template <> optional<int64_t> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> optional<int64_t> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { value.value<int64_t>() };
}
-template <> optional<double> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> optional<double> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { value.value<double>() };
}
-template <> std::string Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QByteArray value = impl->query.value(offset).toByteArray();
- checkQueryError(impl->query);
+template <> std::string Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QByteArray value = stmt.impl->query.value(offset).toByteArray();
+ checkQueryError(stmt.impl->query);
return std::string(value.constData(), value.size());
}
-template <> optional<std::string> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QByteArray value = impl->query.value(offset).toByteArray();
- checkQueryError(impl->query);
+template <> optional<std::string> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QByteArray value = stmt.impl->query.value(offset).toByteArray();
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { std::string(value.constData(), value.size()) };
}
-template <> optional<mbgl::Timestamp> Statement::get(int offset) {
- assert(impl && impl->query.isValid());
- QVariant value = impl->query.value(offset);
- checkQueryError(impl->query);
+template <> optional<mbgl::Timestamp> Query::get(int offset) {
+ assert(stmt.impl && stmt.impl->query.isValid());
+ QVariant value = stmt.impl->query.value(offset);
+ checkQueryError(stmt.impl->query);
if (value.isNull())
return {};
return { std::chrono::time_point_cast<mbgl::Seconds>(
std::chrono::system_clock::from_time_t(value.value<::time_t>())) };
}
-void Statement::reset() {
- assert(impl);
- impl->query.finish();
+void Query::reset() {
+ assert(stmt.impl);
+ stmt.impl->query.finish();
}
-void Statement::clearBindings() {
+void Query::clearBindings() {
// no-op
}
-int64_t Statement::lastInsertRowId() const {
- assert(impl);
- return impl->lastInsertRowId;
+int64_t Query::lastInsertRowId() const {
+ assert(stmt.impl);
+ return stmt.impl->lastInsertRowId;
}
-uint64_t Statement::changes() const {
- assert(impl);
- return (impl->changes < 0 ? 0 : impl->changes);
+uint64_t Query::changes() const {
+ assert(stmt.impl);
+ return (stmt.impl->changes < 0 ? 0 : stmt.impl->changes);
}
Transaction::Transaction(Database& db_, Mode mode)