diff options
Diffstat (limited to 'platform/qt/src/sqlite3.cpp')
-rw-r--r-- | platform/qt/src/sqlite3.cpp | 294 |
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) |