Import the archivist framework.

This commit is contained in:
Chinmay Garde 2021-12-10 12:24:49 -08:00 committed by Dan Field
parent cf4340e556
commit c5c8f1395f
16 changed files with 1606 additions and 0 deletions

View File

@ -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",

View File

@ -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",
]
}

View File

@ -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 <iterator>
#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<ArchiveDatabase>(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<int64_t>(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<bool, int64_t> ArchiveItem::encodeVectorKeys(
std::vector<int64_t>&& 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<int64_t>& 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

View File

@ -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 <memory>
#include <string>
#include <type_traits>
#include <vector>
#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<Member>;
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<std::is_base_of<ArchiveSerializable, T>::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<std::is_base_of<ArchiveSerializable, T>::value>>
bool unarchive(ArchiveSerializable::ArchiveName name, T& archivable) {
const ArchiveDef& def = T::ArchiveDefinition;
return unarchiveInstance(def, name, archivable);
}
using UnarchiveStep = std::function<bool /*continue*/ (ArchiveItem&)>;
template <
class T,
class = std::enable_if<std::is_base_of<ArchiveSerializable, T>::value>>
size_t unarchive(UnarchiveStep stepper) {
const ArchiveDef& def = T::ArchiveDefinition;
return unarchiveInstances(def, stepper, ArchiveNameAuto);
}
private:
std::unique_ptr<ArchiveDatabase> _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 <class T, class = std::enable_if<std::is_integral<T>::value>>
bool encode(ArchiveSerializable::Member member, T item) {
return encodeIntegral(member, static_cast<int64_t>(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<std::is_base_of<ArchiveSerializable, T>::value>>
bool encodeArchivable(ArchiveSerializable::Member member, const T& other) {
const ArchiveDef& otherDef = T::ArchiveDefinition;
return encode(member, otherDef, other);
}
template <class T, class = std::enable_if<std::is_enum<T>::value>>
bool encodeEnum(ArchiveSerializable::Member member, const T& item) {
return encodeIntegral(member, static_cast<int64_t>(item));
}
template <
class T,
class = std::enable_if<std::is_base_of<ArchiveSerializable, T>::value>>
bool encode(ArchiveSerializable::Member member, const std::vector<T>& items) {
/*
* All items in the vector are individually encoded and their keys noted
*/
std::vector<int64_t> 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 <class Super,
class Current,
class = std::enable_if<
std::is_base_of<ArchiveSerializable, Super>::value &&
std::is_base_of<ArchiveSerializable, Current>::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 <class T, class = std::enable_if<std::is_integral<T>::value>>
bool decode(ArchiveSerializable::Member member, T& item) {
int64_t decoded = 0;
auto result = decodeIntegral(member, decoded);
item = static_cast<T>(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<std::is_base_of<ArchiveSerializable, T>::value>>
bool decodeArchivable(ArchiveSerializable::Member member, T& other) {
const ArchiveDef& otherDef = T::ArchiveDefinition;
return decode(member, otherDef, other);
}
template <class T, class = std::enable_if<std::is_enum<T>::value>>
bool decodeEnum(ArchiveSerializable::Member member, T& item) {
int64_t desugared = 0;
if (decodeIntegral(member, desugared)) {
item = static_cast<T>(desugared);
return true;
}
return false;
}
template <
class T,
class = std::enable_if<std::is_base_of<ArchiveSerializable, T>::value>>
bool decode(ArchiveSerializable::Member member, std::vector<T>& 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<int64_t> 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 <class Super,
class Current,
class = std::enable_if<
std::is_base_of<ArchiveSerializable, Super>::value &&
std::is_base_of<ArchiveSerializable, Current>::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<bool, int64_t> encodeVectorKeys(std::vector<int64_t>&& members);
bool decodeVectorKeys(ArchiveSerializable::ArchiveName name,
std::vector<int64_t>& 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

View File

@ -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 <sstream>
#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

View File

@ -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 <map>
#include "flutter/fml/macros.h"
#include "impeller/archivist/archive.h"
namespace impeller {
class ArchiveClassRegistration {
public:
using ColumnResult = std::pair<size_t, bool>;
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<ArchiveSerializable::Member, size_t>;
using ClassMap = std::map<std::string, MemberColumnMap>;
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

View File

@ -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 <sstream>
#include <string>
#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<sqlite3*>(_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<ArchiveStatement>(
new ArchiveStatement(_db, "BEGIN TRANSACTION;"));
if (!_beginTransaction->isReady()) {
return;
}
_endTransaction = std::unique_ptr<ArchiveStatement>(
new ArchiveStatement(_db, "END TRANSACTION;"));
if (!_endTransaction->isReady()) {
return;
}
_rollbackTransaction = std::unique_ptr<ArchiveStatement>(
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<ArchiveClassRegistration>(
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

View File

@ -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 <map>
#include <memory>
#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<std::string, std::unique_ptr<ArchiveClassRegistration>>
_registrations;
std::unique_ptr<ArchiveStatement> _beginTransaction;
std::unique_ptr<ArchiveStatement> _endTransaction;
std::unique_ptr<ArchiveStatement> _rollbackTransaction;
friend class ArchiveClassRegistration;
ArchiveStatement acquireStatement(const std::string& statementString) const;
FML_DISALLOW_COPY_AND_ASSIGN(ArchiveDatabase);
};
} // namespace impeller

View File

@ -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 <string>
#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<sqlite3*>(db), //
statememt.c_str(), //
static_cast<int>(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<int>(index + 1);
}
static constexpr int ToColumn(size_t index) {
/*
* sqlite columns begin from 1
*/
return static_cast<int>(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<int>(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<int>(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<const char*>(
::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<const uint8_t*>(
::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

View File

@ -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 <type_traits>
#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 <class T, class = std::enable_if<std::is_integral<T>::value>>
bool bind(size_t index, T item) {
return bindIntegral(index, static_cast<int64_t>(item));
}
bool bind(size_t index, double item);
bool bind(size_t index, const Allocation& item);
template <class T, class = std::enable_if<std::is_integral<T>::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

View File

@ -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

View File

@ -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 <cstdint>
#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

View File

@ -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 <sstream>
namespace impeller {
ArchiveVector::ArchiveVector(std::vector<int64_t>&& 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<int64_t> 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

View File

@ -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<int64_t> keys() const;
bool serialize(ArchiveItem& item) const override;
bool deserialize(ArchiveItem& item) override;
private:
std::vector<int64_t> _keys;
friend class ArchiveItem;
ArchiveVector();
ArchiveVector(std::vector<int64_t>&& keys);
FML_DISALLOW_COPY_AND_ASSIGN(ArchiveVector);
};
} // namespace impeller

View File

@ -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 <cstdio>
#include <thread>
#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<ArchiveSerializable::ArchiveName> keys;
std::vector<uint64_t> 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<ArchiveSerializable::ArchiveName> keys;
std::vector<uint64_t> 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

View File

@ -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 <limits>
#include <memory>