blob: 1179da2f2f2e2870dff655c26fade96966e595df [file] [log] [blame]
// 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 "flutter/fml/logging.h"
#include "impeller/archivist/archive_class_registration.h"
#include "impeller/archivist/archive_database.h"
#include "impeller/archivist/archive_location.h"
namespace impeller {
Archive::Archive(const std::string& path)
: database_(std::make_unique<ArchiveDatabase>(path)) {}
Archive::~Archive() {
FML_DCHECK(transaction_count_ == 0)
<< "There must be no pending transactions";
}
bool Archive::IsValid() const {
return database_->IsValid();
}
std::optional<int64_t /* row id */> Archive::ArchiveInstance(
const ArchiveDef& definition,
const Archivable& archivable) {
if (!IsValid()) {
return std::nullopt;
}
auto transaction = database_->CreateTransaction(transaction_count_);
const auto* registration =
database_->GetRegistrationForDefinition(definition);
if (registration == nullptr) {
return std::nullopt;
}
auto statement = registration->CreateInsertStatement();
if (!statement.IsValid() || !statement.Reset()) {
/*
* Must be able to reset the statement for a new write
*/
return std::nullopt;
}
auto primary_key = archivable.GetPrimaryKey();
/*
* 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.
*/
ArchiveLocation item(*this, statement, *registration, primary_key);
/*
* If the item provides its own primary key, we need to bind it now.
* Otherwise, one will be automatically assigned to it.
*/
if (primary_key.has_value() &&
!statement.WriteValue(ArchiveClassRegistration::kPrimaryKeyIndex,
primary_key.value())) {
return std::nullopt;
}
if (!archivable.Write(item)) {
return std::nullopt;
}
if (statement.Execute() != ArchiveStatement::Result::kDone) {
return std::nullopt;
}
int64_t lastInsert = database_->GetLastInsertRowID();
if (primary_key.has_value() &&
lastInsert != static_cast<int64_t>(primary_key.value())) {
return std::nullopt;
}
/*
* If any of the nested calls fail, we would have already checked for the
* failure and returned.
*/
transaction.MarkWritesAsReadyForCommit();
return lastInsert;
}
bool Archive::UnarchiveInstance(const ArchiveDef& definition,
PrimaryKey name,
Archivable& archivable) {
UnarchiveStep stepper = [&archivable](ArchiveLocation& item) {
archivable.Read(item);
return false /* no-more after single read */;
};
return UnarchiveInstances(definition, stepper, name) == 1;
}
size_t Archive::UnarchiveInstances(const ArchiveDef& definition,
const Archive::UnarchiveStep& stepper,
PrimaryKey primary_key) {
if (!IsValid()) {
return 0;
}
const auto* registration =
database_->GetRegistrationForDefinition(definition);
if (registration == nullptr) {
return 0;
}
const bool isQueryingSingle = primary_key.has_value();
auto statement = registration->CreateQueryStatement(isQueryingSingle);
if (!statement.IsValid() || !statement.Reset()) {
return 0;
}
if (isQueryingSingle) {
/*
* If a single statement is being queried for, bind the primary key as a
* statement argument.
*/
if (!statement.WriteValue(ArchiveClassRegistration::kPrimaryKeyIndex,
primary_key.value())) {
return 0;
}
}
if (statement.GetColumnCount() !=
registration->GetMemberCount() + 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 = database_->CreateTransaction(transaction_count_);
size_t itemsRead = 0;
while (statement.Execute() == ArchiveStatement::Result::kRow) {
itemsRead++;
/*
* Prepare a fresh archive item for the given statement
*/
ArchiveLocation item(*this, statement, *registration, primary_key);
if (!stepper(item)) {
break;
}
if (isQueryingSingle) {
break;
}
}
return itemsRead;
}
} // namespace impeller