diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn index e7db23c224dc2..525b78851ccf6 100644 --- a/impeller/BUILD.gn +++ b/impeller/BUILD.gn @@ -9,6 +9,7 @@ config("impeller_public_config") { group("impeller") { public_deps = [ "aiks", + "archivist", "base", "compiler", "display_list", @@ -24,6 +25,7 @@ executable("impeller_unittests") { deps = [ "aiks:aiks_unittests", + "archivist:archivist_unittests", "base:base_unittests", "compiler:compiler_unittests", "display_list:display_list_unittests", diff --git a/impeller/archivist/BUILD.gn b/impeller/archivist/BUILD.gn new file mode 100644 index 0000000000000..4aabe3e7b81ff --- /dev/null +++ b/impeller/archivist/BUILD.gn @@ -0,0 +1,39 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../tools/impeller.gni") + +impeller_component("archivist") { + # Only the umbrella header is public since all other TU's are implementation + # detail that will be compiled away in release modes. + # Because they are implementation details, they may expose dependency headers. + public = [ "archive.h" ] + + sources = [ + "archive.cc", + "archive_class_registration.cc", + "archive_class_registration.h", + "archive_database.cc", + "archive_database.h", + "archive_statement.cc", + "archive_statement.h", + "archive_transaction.cc", + "archive_transaction.h", + "archive_vector.cc", + "archive_vector.h", + ] + + public_deps = [ "../base" ] + + deps = [ "//third_party/sqlite" ] +} + +impeller_component("archivist_unittests") { + testonly = true + sources = [ "archivist_unittests.cc" ] + deps = [ + ":archivist", + "//flutter/testing", + ] +} diff --git a/impeller/archivist/archive.cc b/impeller/archivist/archive.cc new file mode 100644 index 0000000000000..ad8436ed8a1e7 --- /dev/null +++ b/impeller/archivist/archive.cc @@ -0,0 +1,317 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/archivist/archive.h" + +#include + +#include "flutter/fml/logging.h" +#include "impeller/archivist/archive_class_registration.h" +#include "impeller/archivist/archive_database.h" +#include "impeller/archivist/archive_statement.h" +#include "impeller/archivist/archive_vector.h" + +namespace impeller { + +Archive::Archive(const std::string& path, bool recreate) + : _db(std::make_unique(path, recreate)) {} + +Archive::~Archive() { + FML_DCHECK(_transactionCount == 0) << "There must be no pending transactions"; +} + +bool Archive::isReady() const { + return _db->isReady(); +} + +bool Archive::archiveInstance(const ArchiveDef& definition, + const ArchiveSerializable& archivable, + int64_t& lastInsertIDOut) { + if (!isReady()) { + return false; + } + + auto transaction = _db->acquireTransaction(_transactionCount); + + const auto* registration = _db->registrationForDefinition(definition); + + if (registration == nullptr) { + return false; + } + + auto statement = registration->insertStatement(); + + if (!statement.isReady() || !statement.reset()) { + /* + * Must be able to reset the statement for a new write + */ + return false; + } + + auto itemName = archivable.archiveName(); + + /* + * The lifecycle of the archive item is tied to this scope and there is no + * way for the user to create an instance of an archive item. So its safe + * for its members to be references. It does not manage the lifetimes of + * anything. + */ + ArchiveItem item(*this, statement, *registration, itemName); + + /* + * We need to bind the primary key only if the item does not provide its own + */ + if (!definition.autoAssignName && + !statement.bind(ArchiveClassRegistration::NameIndex, itemName)) { + return false; + } + + if (!archivable.serialize(item)) { + return false; + } + + if (statement.run() != ArchiveStatement::Result::Done) { + return false; + } + + int64_t lastInsert = _db->lastInsertRowID(); + + if (!definition.autoAssignName && + lastInsert != static_cast(itemName)) { + return false; + } + + lastInsertIDOut = lastInsert; + + /* + * If any of the nested calls fail, we would have already checked for the + * failure and returned. + */ + transaction.markWritesSuccessful(); + + return true; +} + +bool Archive::unarchiveInstance(const ArchiveDef& definition, + ArchiveSerializable::ArchiveName name, + ArchiveSerializable& archivable) { + UnarchiveStep stepper = [&archivable](ArchiveItem& item) { + archivable.deserialize(item); + return false /* no-more after single read */; + }; + + return unarchiveInstances(definition, stepper, name) == 1; +} + +size_t Archive::unarchiveInstances(const ArchiveDef& definition, + Archive::UnarchiveStep stepper, + ArchiveSerializable::ArchiveName name) { + if (!isReady()) { + return 0; + } + + const auto* registration = _db->registrationForDefinition(definition); + + if (registration == nullptr) { + return 0; + } + + const bool isQueryingSingle = name != ArchiveNameAuto; + + auto statement = registration->queryStatement(isQueryingSingle); + + if (!statement.isReady() || !statement.reset()) { + return 0; + } + + if (isQueryingSingle) { + /* + * If a single statement is being queried for, bind the name as a statement + * argument. + */ + if (!statement.bind(ArchiveClassRegistration::NameIndex, name)) { + return 0; + } + } + + if (statement.columnCount() != + registration->memberCount() + 1 /* primary key */) { + return 0; + } + + /* + * Acquire a transaction but never mark it successful since we will never + * be committing any writes to the database during unarchiving. + */ + auto transaction = _db->acquireTransaction(_transactionCount); + + size_t itemsRead = 0; + + while (statement.run() == ArchiveStatement::Result::Row) { + itemsRead++; + + /* + * Prepare a fresh archive item for the given statement + */ + ArchiveItem item(*this, statement, *registration, name); + + if (!stepper(item)) { + break; + } + + if (isQueryingSingle) { + break; + } + } + + return itemsRead; +} + +ArchiveItem::ArchiveItem(Archive& context, + ArchiveStatement& statement, + const ArchiveClassRegistration& registration, + ArchiveSerializable::ArchiveName name) + : _context(context), + _statement(statement), + _registration(registration), + _name(name), + _currentClass(registration.className()) {} + +ArchiveSerializable::ArchiveName ArchiveItem::name() const { + return _name; +} + +bool ArchiveItem::encode(ArchiveSerializable::Member member, + const std::string& item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.bind(found.first, item) : false; +} + +bool ArchiveItem::encodeIntegral(ArchiveSerializable::Member member, + int64_t item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.bind(found.first, item) : false; +} + +bool ArchiveItem::encode(ArchiveSerializable::Member member, double item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.bind(found.first, item) : false; +} + +bool ArchiveItem::encode(ArchiveSerializable::Member member, + const Allocation& item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.bind(found.first, item) : false; +} + +bool ArchiveItem::encode(ArchiveSerializable::Member member, + const ArchiveDef& otherDef, + const ArchiveSerializable& other) { + auto found = _registration.findColumn(_currentClass, member); + + if (!found.second) { + return false; + } + + /* + * We need to fully archive the other instance first because it could + * have a name that is auto assigned. In that case, we cannot ask it before + * archival (via `other.archiveName()`). + */ + int64_t lastInsert = 0; + if (!_context.archiveInstance(otherDef, other, lastInsert)) { + return false; + } + + /* + * Bind the name of the serialiable + */ + if (!_statement.bind(found.first, lastInsert)) { + return false; + } + + return true; +} + +std::pair ArchiveItem::encodeVectorKeys( + std::vector&& members) { + ArchiveVector vector(std::move(members)); + int64_t vectorID = 0; + if (!_context.archiveInstance(ArchiveVector::ArchiveDefinition, // + vector, // + vectorID)) { + return {false, 0}; + } + return {true, vectorID}; +} + +bool ArchiveItem::decodeVectorKeys(ArchiveSerializable::ArchiveName name, + std::vector& members) { + ArchiveVector vector; + + if (!_context.unarchiveInstance(ArchiveVector::ArchiveDefinition, name, + vector)) { + return false; + } + + const auto& keys = vector.keys(); + + std::move(keys.begin(), keys.end(), std::back_inserter(members)); + + return true; +} + +bool ArchiveItem::decode(ArchiveSerializable::Member member, + std::string& item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.column(found.first, item) : false; +} + +bool ArchiveItem::decodeIntegral(ArchiveSerializable::Member member, + int64_t& item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.column(found.first, item) : false; +} + +bool ArchiveItem::decode(ArchiveSerializable::Member member, double& item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.column(found.first, item) : false; +} + +bool ArchiveItem::decode(ArchiveSerializable::Member member, Allocation& item) { + auto found = _registration.findColumn(_currentClass, member); + return found.second ? _statement.column(found.first, item) : false; +} + +bool ArchiveItem::decode(ArchiveSerializable::Member member, + const ArchiveDef& otherDef, + ArchiveSerializable& other) { + auto found = _registration.findColumn(_currentClass, member); + + /* + * Make sure a member is present at that column + */ + if (!found.second) { + return false; + } + + /* + * Try to find the foreign key in the current items row + */ + int64_t foreignKey = 0; + if (!_statement.column(found.first, foreignKey)) { + return false; + } + + /* + * Find the other item and unarchive by this foreign key + */ + if (!_context.unarchiveInstance(otherDef, foreignKey, other)) { + return false; + } + + return true; +} + +} // namespace impeller diff --git a/impeller/archivist/archive.h b/impeller/archivist/archive.h new file mode 100644 index 0000000000000..aa3ae54989734 --- /dev/null +++ b/impeller/archivist/archive.h @@ -0,0 +1,282 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/allocation.h" + +namespace impeller { + +class ArchiveItem; +class ArchiveClassRegistration; +class ArchiveDatabase; +class ArchiveStatement; + +class ArchiveSerializable { + public: + using Member = uint64_t; + using Members = std::vector; + using ArchiveName = uint64_t; + + virtual ArchiveName archiveName() const = 0; + + virtual bool serialize(ArchiveItem& item) const = 0; + + virtual bool deserialize(ArchiveItem& item) = 0; +}; + +struct ArchiveDef { + const ArchiveDef* superClass; + const std::string className; + const bool autoAssignName; + const ArchiveSerializable::Members members; +}; + +static const ArchiveSerializable::ArchiveName ArchiveNameAuto = 0; + +class Archive { + public: + Archive(const std::string& path, bool recreate); + + ~Archive(); + + bool isReady() const; + + template < + class T, + class = std::enable_if::value>> + bool archive(const T& archivable) { + const ArchiveDef& def = T::ArchiveDefinition; + int64_t unusedLast = 0; + return archiveInstance(def, archivable, unusedLast); + } + + template < + class T, + class = std::enable_if::value>> + bool unarchive(ArchiveSerializable::ArchiveName name, T& archivable) { + const ArchiveDef& def = T::ArchiveDefinition; + return unarchiveInstance(def, name, archivable); + } + + using UnarchiveStep = std::function; + + template < + class T, + class = std::enable_if::value>> + size_t unarchive(UnarchiveStep stepper) { + const ArchiveDef& def = T::ArchiveDefinition; + return unarchiveInstances(def, stepper, ArchiveNameAuto); + } + + private: + std::unique_ptr _db; + int64_t _transactionCount = 0; + + friend class ArchiveItem; + + bool archiveInstance(const ArchiveDef& definition, + const ArchiveSerializable& archivable, + int64_t& lastInsertID); + bool unarchiveInstance(const ArchiveDef& definition, + ArchiveSerializable::ArchiveName name, + ArchiveSerializable& archivable); + size_t unarchiveInstances(const ArchiveDef& definition, + UnarchiveStep stepper, + ArchiveSerializable::ArchiveName optionalName); + + FML_DISALLOW_COPY_AND_ASSIGN(Archive); +}; + +class ArchiveItem { + public: + template ::value>> + bool encode(ArchiveSerializable::Member member, T item) { + return encodeIntegral(member, static_cast(item)); + } + + bool encode(ArchiveSerializable::Member member, double item); + + bool encode(ArchiveSerializable::Member member, const std::string& item); + + bool encode(ArchiveSerializable::Member member, const Allocation& allocation); + + template < + class T, + class = std::enable_if::value>> + bool encodeArchivable(ArchiveSerializable::Member member, const T& other) { + const ArchiveDef& otherDef = T::ArchiveDefinition; + return encode(member, otherDef, other); + } + + template ::value>> + bool encodeEnum(ArchiveSerializable::Member member, const T& item) { + return encodeIntegral(member, static_cast(item)); + } + + template < + class T, + class = std::enable_if::value>> + bool encode(ArchiveSerializable::Member member, const std::vector& items) { + /* + * All items in the vector are individually encoded and their keys noted + */ + std::vector members; + members.reserve(items.size()); + + const ArchiveDef& itemDefinition = T::ArchiveDefinition; + for (const auto& item : items) { + int64_t added = 0; + bool result = _context.archiveInstance(itemDefinition, item, added); + if (!result) { + return false; + } + members.emplace_back(added); + } + + /* + * The keys are flattened into the vectors table. Write to that table + */ + auto vectorInsert = encodeVectorKeys(std::move(members)); + + if (!vectorInsert.first) { + return false; + } + + return encodeIntegral(member, vectorInsert.second); + } + + template ::value && + std::is_base_of::value>> + bool encodeSuper(const Current& thiz) { + std::string oldClass = _currentClass; + _currentClass = Super::ArchiveDefinition.className; + auto success = thiz.Super::serialize(*this); + _currentClass = oldClass; + return success; + } + + template ::value>> + bool decode(ArchiveSerializable::Member member, T& item) { + int64_t decoded = 0; + auto result = decodeIntegral(member, decoded); + item = static_cast(decoded); + return result; + } + + bool decode(ArchiveSerializable::Member member, double& item); + + bool decode(ArchiveSerializable::Member member, std::string& item); + + bool decode(ArchiveSerializable::Member member, Allocation& allocation); + + template < + class T, + class = std::enable_if::value>> + bool decodeArchivable(ArchiveSerializable::Member member, T& other) { + const ArchiveDef& otherDef = T::ArchiveDefinition; + return decode(member, otherDef, other); + } + + template ::value>> + bool decodeEnum(ArchiveSerializable::Member member, T& item) { + int64_t desugared = 0; + if (decodeIntegral(member, desugared)) { + item = static_cast(desugared); + return true; + } + return false; + } + + template < + class T, + class = std::enable_if::value>> + bool decode(ArchiveSerializable::Member member, std::vector& items) { + /* + * From the member, find the foreign key of the vector + */ + int64_t vectorForeignKey = 0; + if (!decodeIntegral(member, vectorForeignKey)) { + return false; + } + + /* + * Get vector keys + */ + std::vector keys; + if (!decodeVectorKeys(vectorForeignKey, keys)) { + return false; + } + + const ArchiveDef& otherDef = T::ArchiveDefinition; + for (const auto& key : keys) { + items.emplace_back(); + + if (!_context.unarchiveInstance(otherDef, key, items.back())) { + return false; + } + } + + return true; + } + + template ::value && + std::is_base_of::value>> + bool decodeSuper(Current& thiz) { + std::string oldClass = _currentClass; + _currentClass = Super::ArchiveDefinition.className; + auto success = thiz.Super::deserialize(*this); + _currentClass = oldClass; + return success; + } + + ArchiveSerializable::ArchiveName name() const; + + private: + Archive& _context; + ArchiveStatement& _statement; + const ArchiveClassRegistration& _registration; + ArchiveSerializable::ArchiveName _name; + std::string _currentClass; + + friend class Archive; + + ArchiveItem(Archive& context, + ArchiveStatement& statement, + const ArchiveClassRegistration& registration, + ArchiveSerializable::ArchiveName name); + + bool encodeIntegral(ArchiveSerializable::Member member, int64_t item); + + bool decodeIntegral(ArchiveSerializable::Member member, int64_t& item); + + std::pair encodeVectorKeys(std::vector&& members); + + bool decodeVectorKeys(ArchiveSerializable::ArchiveName name, + std::vector& members); + + bool encode(ArchiveSerializable::Member member, + const ArchiveDef& otherDef, + const ArchiveSerializable& other); + + bool decode(ArchiveSerializable::Member member, + const ArchiveDef& otherDef, + ArchiveSerializable& other); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveItem); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_class_registration.cc b/impeller/archivist/archive_class_registration.cc new file mode 100644 index 0000000000000..cc797f6202e9a --- /dev/null +++ b/impeller/archivist/archive_class_registration.cc @@ -0,0 +1,150 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/archivist/archive_class_registration.h" + +#include + +#include "impeller/archivist/archive_database.h" +#include "impeller/archivist/archive_statement.h" + +namespace impeller { + +static const char* const ArchiveColumnPrefix = "item"; +static const char* const ArchivePrimaryKeyColumnName = "name"; +static const char* const ArchiveTablePrefix = "RL_"; + +ArchiveClassRegistration::ArchiveClassRegistration(ArchiveDatabase& database, + ArchiveDef definition) + : _database(database), _className(definition.className), _memberCount(0) { + /* + * Each class in the archive class hierarchy is assigned an entry in the + * class map. + */ + const ArchiveDef* current = &definition; + size_t currentMember = 1; + while (current != nullptr) { + auto membersInCurrent = current->members.size(); + _memberCount += membersInCurrent; + MemberColumnMap map; + for (const auto& member : current->members) { + map[member] = currentMember++; + } + _classMap[current->className] = map; + current = current->superClass; + } + + _isReady = createTable(definition.autoAssignName); +} + +const std::string& ArchiveClassRegistration::className() const { + return _className; +} + +size_t ArchiveClassRegistration::memberCount() const { + return _memberCount; +} + +bool ArchiveClassRegistration::isReady() const { + return _isReady; +} + +ArchiveClassRegistration::ColumnResult ArchiveClassRegistration::findColumn( + const std::string& className, + ArchiveSerializable::Member member) const { + auto found = _classMap.find(className); + + if (found == _classMap.end()) { + return {0, false}; + } + + const auto& memberToColumns = found->second; + + auto foundColumn = memberToColumns.find(member); + + if (foundColumn == memberToColumns.end()) { + return {0, false}; + } + + return {foundColumn->second, true}; +} + +bool ArchiveClassRegistration::createTable(bool autoIncrement) { + if (_className.size() == 0 || _memberCount == 0) { + return false; + } + + std::stringstream stream; + + /* + * Table names cannot participate in parameter substitution, so we prepare + * a statement and check its validity before running. + */ + stream << "CREATE TABLE IF NOT EXISTS " << ArchiveTablePrefix + << _className.c_str() << " (" << ArchivePrimaryKeyColumnName; + + if (autoIncrement) { + stream << " INTEGER PRIMARY KEY AUTOINCREMENT, "; + } else { + stream << " INTEGER PRIMARY KEY, "; + } + for (size_t i = 0, columns = _memberCount; i < columns; i++) { + stream << ArchiveColumnPrefix << std::to_string(i + 1); + if (i != columns - 1) { + stream << ", "; + } + } + stream << ");"; + + auto statement = _database.acquireStatement(stream.str()); + + if (!statement.isReady()) { + return false; + } + + if (!statement.reset()) { + return false; + } + + return statement.run() == ArchiveStatement::Result::Done; +} + +ArchiveStatement ArchiveClassRegistration::queryStatement(bool single) const { + std::stringstream stream; + stream << "SELECT " << ArchivePrimaryKeyColumnName << ", "; + for (size_t i = 0, members = _memberCount; i < members; i++) { + stream << ArchiveColumnPrefix << std::to_string(i + 1); + if (i != members - 1) { + stream << ","; + } + } + stream << " FROM " << ArchiveTablePrefix << _className; + + if (single) { + stream << " WHERE " << ArchivePrimaryKeyColumnName << " = ?"; + } else { + stream << " ORDER BY " << ArchivePrimaryKeyColumnName << " ASC"; + } + + stream << ";"; + + return _database.acquireStatement(stream.str()); +} + +ArchiveStatement ArchiveClassRegistration::insertStatement() const { + std::stringstream stream; + stream << "INSERT OR REPLACE INTO " << ArchiveTablePrefix << _className + << " VALUES ( ?, "; + for (size_t i = 0; i < _memberCount; i++) { + stream << "?"; + if (i != _memberCount - 1) { + stream << ", "; + } + } + stream << ");"; + + return _database.acquireStatement(stream.str()); +} + +} // namespace impeller diff --git a/impeller/archivist/archive_class_registration.h b/impeller/archivist/archive_class_registration.h new file mode 100644 index 0000000000000..22b8231c1e83b --- /dev/null +++ b/impeller/archivist/archive_class_registration.h @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archive.h" + +namespace impeller { + +class ArchiveClassRegistration { + public: + using ColumnResult = std::pair; + ColumnResult findColumn(const std::string& className, + ArchiveSerializable::Member member) const; + + const std::string& className() const; + + size_t memberCount() const; + + bool isReady() const; + + ArchiveStatement insertStatement() const; + + ArchiveStatement queryStatement(bool single) const; + + static const size_t NameIndex = 0; + + private: + using MemberColumnMap = std::map; + using ClassMap = std::map; + + friend class ArchiveDatabase; + + ArchiveClassRegistration(ArchiveDatabase& database, ArchiveDef definition); + + bool createTable(bool autoIncrement); + + ArchiveDatabase& _database; + ClassMap _classMap; + std::string _className; + size_t _memberCount; + bool _isReady; + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveClassRegistration); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_database.cc b/impeller/archivist/archive_database.cc new file mode 100644 index 0000000000000..a99707a513bf0 --- /dev/null +++ b/impeller/archivist/archive_database.cc @@ -0,0 +1,121 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/archivist/archive_database.h" + +#include "third_party/sqlite/sqlite3.h" + +#include +#include + +#include "impeller/archivist/archive.h" +#include "impeller/archivist/archive_class_registration.h" +#include "impeller/archivist/archive_statement.h" +#include "impeller/base/validation.h" + +namespace impeller { + +#define DB_HANDLE reinterpret_cast(_db) + +ArchiveDatabase::ArchiveDatabase(const std::string& filename, bool recreate) { + if (recreate) { + ::remove(filename.c_str()); + } + + if (::sqlite3_initialize() != SQLITE_OK) { + VALIDATION_LOG << "Could not initialize sqlite."; + return; + } + + sqlite3* db = nullptr; + auto res = ::sqlite3_open(filename.c_str(), &db); + _db = db; + + if (res != SQLITE_OK || _db == nullptr) { + return; + } + + _beginTransaction = std::unique_ptr( + new ArchiveStatement(_db, "BEGIN TRANSACTION;")); + + if (!_beginTransaction->isReady()) { + return; + } + + _endTransaction = std::unique_ptr( + new ArchiveStatement(_db, "END TRANSACTION;")); + + if (!_endTransaction->isReady()) { + return; + } + + _rollbackTransaction = std::unique_ptr( + new ArchiveStatement(_db, "ROLLBACK TRANSACTION;")); + + if (!_rollbackTransaction->isReady()) { + return; + } + + _ready = true; +} + +ArchiveDatabase::~ArchiveDatabase() { + ::sqlite3_close(DB_HANDLE); +} + +bool ArchiveDatabase::isReady() const { + return _ready; +} + +int64_t ArchiveDatabase::lastInsertRowID() { + return ::sqlite3_last_insert_rowid(DB_HANDLE); +} + +static inline const ArchiveClassRegistration* RegistrationIfReady( + const ArchiveClassRegistration* registration) { + if (registration == nullptr) { + return nullptr; + } + return registration->isReady() ? registration : nullptr; +} + +const ArchiveClassRegistration* ArchiveDatabase::registrationForDefinition( + const ArchiveDef& definition) { + auto found = _registrations.find(definition.className); + if (found != _registrations.end()) { + /* + * This class has already been registered. + */ + return RegistrationIfReady(found->second.get()); + } + + /* + * Initialize a new class registration for the given class definition. + */ + auto registration = std::unique_ptr( + new ArchiveClassRegistration(*this, definition)); + auto res = + _registrations.emplace(definition.className, std::move(registration)); + + /* + * If the new class registation is ready, return it to the caller. + */ + return res.second ? RegistrationIfReady((*(res.first)).second.get()) + : nullptr; +} + +ArchiveStatement ArchiveDatabase::acquireStatement( + const std::string& statementString) const { + return ArchiveStatement{_db, statementString}; +} + +ArchiveTransaction ArchiveDatabase::acquireTransaction( + int64_t& transactionCount) { + return ArchiveTransaction{transactionCount, // + *_beginTransaction, // + *_endTransaction, // + *_rollbackTransaction}; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_database.h b/impeller/archivist/archive_database.h new file mode 100644 index 0000000000000..aa368b85a90bb --- /dev/null +++ b/impeller/archivist/archive_database.h @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archive_transaction.h" + +namespace impeller { + +class ArchiveStatement; +class ArchiveClassRegistration; +struct ArchiveDef; + +class ArchiveDatabase { + public: + ArchiveDatabase(const std::string& filename, bool recreate); + + ~ArchiveDatabase(); + + bool isReady() const; + + int64_t lastInsertRowID(); + + const ArchiveClassRegistration* registrationForDefinition( + const ArchiveDef& definition); + + ArchiveTransaction acquireTransaction(int64_t& transactionCount); + + private: + void* _db = nullptr; + bool _ready = false; + std::map> + _registrations; + std::unique_ptr _beginTransaction; + std::unique_ptr _endTransaction; + std::unique_ptr _rollbackTransaction; + + friend class ArchiveClassRegistration; + + ArchiveStatement acquireStatement(const std::string& statementString) const; + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveDatabase); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_statement.cc b/impeller/archivist/archive_statement.cc new file mode 100644 index 0000000000000..69f143580bd81 --- /dev/null +++ b/impeller/archivist/archive_statement.cc @@ -0,0 +1,179 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/archivist/archive_statement.h" + +#include + +#include "flutter/fml/logging.h" +#include "third_party/sqlite/sqlite3.h" + +namespace impeller { + +#define STATEMENT_HANDLE reinterpret_cast<::sqlite3_stmt*>(_statement) + +ArchiveStatement::ArchiveStatement(void* db, const std::string& statememt) { + ::sqlite3_stmt* statementHandle = nullptr; + auto res = ::sqlite3_prepare_v2(reinterpret_cast(db), // + statememt.c_str(), // + static_cast(statememt.size()), // + &statementHandle, // + nullptr); + _statement = statementHandle; + _ready = res == SQLITE_OK && _statement != nullptr; +} + +ArchiveStatement::ArchiveStatement(ArchiveStatement&& other) + : _statement(other._statement), _ready(other._ready) { + other._statement = nullptr; + other._ready = false; +} + +ArchiveStatement::~ArchiveStatement() { + if (_statement != nullptr) { + auto res = ::sqlite3_finalize(STATEMENT_HANDLE); + FML_CHECK(res == SQLITE_OK) << "Unable to finalize the archive."; + } +} + +bool ArchiveStatement::isReady() const { + return _ready; +} + +bool ArchiveStatement::reset() { + if (::sqlite3_reset(STATEMENT_HANDLE) != SQLITE_OK) { + return false; + } + + if (::sqlite3_clear_bindings(STATEMENT_HANDLE) != SQLITE_OK) { + return false; + } + + return true; +} + +static constexpr int ToParam(size_t index) { + /* + * sqlite parameters begin from 1 + */ + return static_cast(index + 1); +} + +static constexpr int ToColumn(size_t index) { + /* + * sqlite columns begin from 1 + */ + return static_cast(index); +} + +size_t ArchiveStatement::columnCount() { + return ::sqlite3_column_count(STATEMENT_HANDLE); +} + +/* + * Bind Variants + */ +bool ArchiveStatement::bind(size_t index, const std::string& item) { + return ::sqlite3_bind_text(STATEMENT_HANDLE, // + ToParam(index), // + item.data(), // + static_cast(item.size()), // + SQLITE_TRANSIENT) == SQLITE_OK; +} + +bool ArchiveStatement::bindIntegral(size_t index, int64_t item) { + return ::sqlite3_bind_int64(STATEMENT_HANDLE, // + ToParam(index), // + item) == SQLITE_OK; +} + +bool ArchiveStatement::bind(size_t index, double item) { + return ::sqlite3_bind_double(STATEMENT_HANDLE, // + ToParam(index), // + item) == SQLITE_OK; +} + +bool ArchiveStatement::bind(size_t index, const Allocation& item) { + return ::sqlite3_bind_blob(STATEMENT_HANDLE, // + ToParam(index), // + item.GetBuffer(), // + static_cast(item.GetLength()), // + SQLITE_TRANSIENT) == SQLITE_OK; +} + +/* + * Column Variants + */ +bool ArchiveStatement::columnIntegral(size_t index, int64_t& item) { + item = ::sqlite3_column_int64(STATEMENT_HANDLE, ToColumn(index)); + return true; +} + +bool ArchiveStatement::column(size_t index, double& item) { + item = ::sqlite3_column_double(STATEMENT_HANDLE, ToColumn(index)); + return true; +} + +/* + * For cases where byte sizes of column data is necessary, the + * recommendations in https://www.sqlite.org/c3ref/column_blob.html regarding + * type conversions are followed. + * + * TL;DR: Access blobs then bytes. + */ + +bool ArchiveStatement::column(size_t index, std::string& item) { + /* + * Get the character data + */ + auto chars = reinterpret_cast( + ::sqlite3_column_text(STATEMENT_HANDLE, ToColumn(index))); + + /* + * Get the length of the string (in bytes) + */ + size_t textByteSize = + ::sqlite3_column_bytes(STATEMENT_HANDLE, ToColumn(index)); + + std::string text(chars, textByteSize); + item.swap(text); + + return true; +} + +bool ArchiveStatement::column(size_t index, Allocation& item) { + /* + * Get a blob pointer + */ + auto blob = reinterpret_cast( + ::sqlite3_column_blob(STATEMENT_HANDLE, ToColumn(index))); + + /* + * Decode the number of bytes in the blob + */ + size_t byteSize = ::sqlite3_column_bytes(STATEMENT_HANDLE, ToColumn(index)); + + /* + * Reszie the host allocation and move the blob contents into it + */ + if (!item.Truncate(byteSize, false /* npot */)) { + return false; + } + + memmove(item.GetBuffer(), blob, byteSize); + return true; +} + +ArchiveStatement::Result ArchiveStatement::run() { + switch (::sqlite3_step(STATEMENT_HANDLE)) { + case SQLITE_DONE: + return Result::Done; + case SQLITE_ROW: + return Result::Row; + default: + return Result::Failure; + } +} + +} // namespace impeller diff --git a/impeller/archivist/archive_statement.h b/impeller/archivist/archive_statement.h new file mode 100644 index 0000000000000..0110d853b2327 --- /dev/null +++ b/impeller/archivist/archive_statement.h @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/allocation.h" + +namespace impeller { + +class ArchiveStatement { + public: + ~ArchiveStatement(); + + ArchiveStatement(ArchiveStatement&& message); + + bool isReady() const; + + bool reset(); + + bool bind(size_t index, const std::string& item); + + template ::value>> + bool bind(size_t index, T item) { + return bindIntegral(index, static_cast(item)); + } + + bool bind(size_t index, double item); + + bool bind(size_t index, const Allocation& item); + + template ::value>> + bool column(size_t index, T& item) { + return columnIntegral(index, item); + } + + bool column(size_t index, double& item); + + bool column(size_t index, std::string& item); + + bool column(size_t index, Allocation& item); + + size_t columnCount(); + + enum class Result { + Done, + Row, + Failure, + }; + + Result run(); + + private: + void* _statement = nullptr; + bool _ready = false; + + friend class ArchiveDatabase; + + ArchiveStatement(void* db, const std::string& statememt); + + bool bindIntegral(size_t index, int64_t item); + + bool columnIntegral(size_t index, int64_t& item); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveStatement); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_transaction.cc b/impeller/archivist/archive_transaction.cc new file mode 100644 index 0000000000000..6fe7f03e2521c --- /dev/null +++ b/impeller/archivist/archive_transaction.cc @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/archivist/archive_transaction.h" + +#include "flutter/fml/logging.h" +#include "impeller/archivist/archive_statement.h" + +namespace impeller { + +ArchiveTransaction::ArchiveTransaction(int64_t& transactionCount, + ArchiveStatement& beginStatement, + ArchiveStatement& endStatement, + ArchiveStatement& rollbackStatement) + : _endStatement(endStatement), + _rollbackStatement(rollbackStatement), + _transactionCount(transactionCount) { + if (_transactionCount == 0) { + _cleanup = beginStatement.run() == ArchiveStatement::Result::Done; + } + _transactionCount++; +} + +ArchiveTransaction::ArchiveTransaction(ArchiveTransaction&& other) + : _endStatement(other._endStatement), + _rollbackStatement(other._rollbackStatement), + _transactionCount(other._transactionCount), + _cleanup(other._cleanup), + _successful(other._successful) { + other._abandoned = true; +} + +ArchiveTransaction::~ArchiveTransaction() { + if (_abandoned) { + return; + } + + FML_CHECK(_transactionCount != 0); + if (_transactionCount == 1 && _cleanup) { + auto res = _successful ? _endStatement.run() : _rollbackStatement.run(); + FML_CHECK(res == ArchiveStatement::Result::Done) + << "Must be able to commit the nested transaction"; + } + _transactionCount--; +} + +void ArchiveTransaction::markWritesSuccessful() { + _successful = true; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_transaction.h b/impeller/archivist/archive_transaction.h new file mode 100644 index 0000000000000..ed54c2281e7d8 --- /dev/null +++ b/impeller/archivist/archive_transaction.h @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#pragma once + +#include + +#include "flutter/fml/macros.h" + +namespace impeller { + +class ArchiveStatement; + +class ArchiveTransaction { + public: + ArchiveTransaction(ArchiveTransaction&& transaction); + + ~ArchiveTransaction(); + + void markWritesSuccessful(); + + private: + ArchiveStatement& _endStatement; + ArchiveStatement& _rollbackStatement; + int64_t& _transactionCount; + bool _cleanup = false; + bool _successful = false; + bool _abandoned = false; + + friend class ArchiveDatabase; + + ArchiveTransaction(int64_t& transactionCount, + ArchiveStatement& beginStatement, + ArchiveStatement& endStatement, + ArchiveStatement& rollbackStatement); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveTransaction); +}; + +} // namespace impeller diff --git a/impeller/archivist/archive_vector.cc b/impeller/archivist/archive_vector.cc new file mode 100644 index 0000000000000..6b99cda8c843a --- /dev/null +++ b/impeller/archivist/archive_vector.cc @@ -0,0 +1,58 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/archivist/archive_vector.h" + +#include + +namespace impeller { + +ArchiveVector::ArchiveVector(std::vector&& keys) + : _keys(std::move(keys)) {} + +ArchiveVector::ArchiveVector() {} + +const ArchiveDef ArchiveVector::ArchiveDefinition = { + /* .superClass = */ nullptr, + /* .className = */ "Meta_Vector", + /* .autoAssignName = */ true, + /* .members = */ {0}, +}; + +ArchiveSerializable::ArchiveName ArchiveVector::archiveName() const { + return ArchiveNameAuto; +} + +const std::vector ArchiveVector::keys() const { + return _keys; +} + +bool ArchiveVector::serialize(ArchiveItem& item) const { + std::stringstream stream; + for (size_t i = 0, count = _keys.size(); i < count; i++) { + stream << _keys[i]; + if (i != count - 1) { + stream << ","; + } + } + return item.encode(0, stream.str()); +} + +bool ArchiveVector::deserialize(ArchiveItem& item) { + std::string flattened; + if (!item.decode(0, flattened)) { + return false; + } + + std::stringstream stream(flattened); + int64_t single = 0; + while (stream >> single) { + _keys.emplace_back(single); + stream.ignore(); + } + + return true; +} + +} // namespace impeller diff --git a/impeller/archivist/archive_vector.h b/impeller/archivist/archive_vector.h new file mode 100644 index 0000000000000..99d23c02f05c1 --- /dev/null +++ b/impeller/archivist/archive_vector.h @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/archivist/archive.h" + +namespace impeller { + +class ArchiveVector : public ArchiveSerializable { + public: + static const ArchiveDef ArchiveDefinition; + + ArchiveName archiveName() const override; + + const std::vector keys() const; + + bool serialize(ArchiveItem& item) const override; + + bool deserialize(ArchiveItem& item) override; + + private: + std::vector _keys; + + friend class ArchiveItem; + + ArchiveVector(); + + ArchiveVector(std::vector&& keys); + + FML_DISALLOW_COPY_AND_ASSIGN(ArchiveVector); +}; + +} // namespace impeller diff --git a/impeller/archivist/archivist_unittests.cc b/impeller/archivist/archivist_unittests.cc new file mode 100644 index 0000000000000..e1e44497b967d --- /dev/null +++ b/impeller/archivist/archivist_unittests.cc @@ -0,0 +1,158 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/testing/testing.h" +#include "impeller/archivist/archive.h" + +namespace impeller { +namespace testing { + +static ArchiveSerializable::ArchiveName LastSample = 0; + +class Sample : public ArchiveSerializable { + public: + Sample(uint64_t count = 42) : _someData(count), _name(++LastSample) {} + + uint64_t someData() const { return _someData; } + + ArchiveName archiveName() const override { return _name; } + + bool serialize(ArchiveItem& item) const override { + return item.encode(999, _someData); + }; + + bool deserialize(ArchiveItem& item) override { + _name = item.name(); + return item.decode(999, _someData); + }; + + static const ArchiveDef ArchiveDefinition; + + private: + uint64_t _someData; + ArchiveName _name; + + FML_DISALLOW_COPY_AND_ASSIGN(Sample); +}; + +const ArchiveDef Sample::ArchiveDefinition = { + .superClass = nullptr, + .className = "Sample", + .autoAssignName = false, + .members = {999}, +}; + +TEST(ArchiveTest, SimpleInitialization) { + auto name = "/tmp/sample.db"; + { + Archive archive(name, true); + ASSERT_TRUE(archive.isReady()); + } + ASSERT_EQ(::remove(name), 0); +} + +TEST(ArchiveTest, AddStorageClass) { + auto name = "/tmp/sample2.db"; + { + Archive archive(name, true); + ASSERT_TRUE(archive.isReady()); + } + ASSERT_EQ(::remove(name), 0); +} + +TEST(ArchiveTest, AddData) { + auto name = "/tmp/sample3.db"; + { + Archive archive(name, true); + ASSERT_TRUE(archive.isReady()); + Sample sample; + ASSERT_TRUE(archive.archive(sample)); + } + ASSERT_EQ(::remove(name), 0); +} + +TEST(ArchiveTest, AddDataMultiple) { + auto name = "/tmp/sample4.db"; + { + Archive archive(name, true); + ASSERT_TRUE(archive.isReady()); + + for (size_t i = 0; i < 100; i++) { + Sample sample(i + 1); + ASSERT_TRUE(archive.archive(sample)); + } + } + ASSERT_EQ(::remove(name), 0); +} + +TEST(ArchiveTest, ReadData) { + auto name = "/tmp/sample5.db"; + { + Archive archive(name, true); + ASSERT_TRUE(archive.isReady()); + + size_t count = 50; + + std::vector keys; + std::vector values; + + keys.reserve(count); + values.reserve(count); + + for (size_t i = 0; i < count; i++) { + Sample sample(i + 1); + keys.push_back(sample.archiveName()); + values.push_back(sample.someData()); + ASSERT_TRUE(archive.archive(sample)); + } + + for (size_t i = 0; i < count; i++) { + Sample sample; + ASSERT_TRUE(archive.unarchive(keys[i], sample)); + ASSERT_EQ(values[i], sample.someData()); + } + } + ASSERT_EQ(::remove(name), 0); +} + +/* + * This shouldn't be slow. Need to cache compiled statements. + */ +TEST(ArchiveTest, ReadDataWithNames) { + auto name = "/tmp/sample6.db"; + { + Archive archive(name, true); + ASSERT_TRUE(archive.isReady()); + + size_t count = 8; + + std::vector keys; + std::vector values; + + keys.reserve(count); + values.reserve(count); + + for (size_t i = 0; i < count; i++) { + Sample sample(i + 1); + keys.push_back(sample.archiveName()); + values.push_back(sample.someData()); + ASSERT_TRUE(archive.archive(sample)); + } + + for (size_t i = 0; i < count; i++) { + Sample sample; + ASSERT_TRUE(archive.unarchive(keys[i], sample)); + ASSERT_EQ(values[i], sample.someData()); + ASSERT_EQ(keys[i], sample.archiveName()); + } + } + ASSERT_EQ(::remove(name), 0); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/base/allocation.h b/impeller/base/allocation.h index b4bef69baeace..f707e089298d3 100644 --- a/impeller/base/allocation.h +++ b/impeller/base/allocation.h @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#pragma once + #include #include