// 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_location.h" #include "impeller/base/validation.h" namespace impeller { Archive::Archive(const std::string& path) : database_(std::make_unique(path)) {} Archive::~Archive() { FML_DCHECK(transaction_count_ == 0) << "There must be no pending transactions"; } bool Archive::IsValid() const { return database_->IsValid(); } std::optional 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(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