/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SchemaVersion001.h" #include "FileSystemHashSource.h" #include "ResultStatement.h" #include "fs/FileSystemConstants.h" #include "mozStorageHelper.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/dom/quota/ResultExtensions.h" namespace mozilla::dom::fs { namespace { nsresult SetEncoding(ResultConnection& aConn) { return aConn->ExecuteSimpleSQL(R"(PRAGMA encoding = "UTF-16";)"_ns); } nsresult CreateEntries(ResultConnection& aConn) { return aConn->ExecuteSimpleSQL( "CREATE TABLE IF NOT EXISTS Entries ( " "handle BLOB PRIMARY KEY, " // Generated from parent + name, unique "parent BLOB, " // Not null due to constraint "CONSTRAINT parent_is_a_directory " "FOREIGN KEY (parent) " "REFERENCES Directories (handle) " "ON DELETE CASCADE ) " ";"_ns); } nsresult CreateDirectories(ResultConnection& aConn) { return aConn->ExecuteSimpleSQL( "CREATE TABLE IF NOT EXISTS Directories ( " "handle BLOB PRIMARY KEY, " "name BLOB NOT NULL, " "CONSTRAINT directories_are_entries " "FOREIGN KEY (handle) " "REFERENCES Entries (handle) " "ON DELETE CASCADE ) " ";"_ns); } nsresult CreateFiles(ResultConnection& aConn) { return aConn->ExecuteSimpleSQL( "CREATE TABLE IF NOT EXISTS Files ( " "handle BLOB PRIMARY KEY, " "type TEXT, " "name BLOB NOT NULL, " "CONSTRAINT files_are_entries " "FOREIGN KEY (handle) " "REFERENCES Entries (handle) " "ON DELETE CASCADE ) " ";"_ns); } nsresult CreateUsages(ResultConnection& aConn) { return aConn->ExecuteSimpleSQL( "CREATE TABLE IF NOT EXISTS Usages ( " "handle BLOB PRIMARY KEY, " "usage INTEGER NOT NULL DEFAULT 0, " "tracked BOOLEAN NOT NULL DEFAULT 0 CHECK (tracked IN (0, 1)), " "CONSTRAINT handles_are_files " "FOREIGN KEY (handle) " "REFERENCES Files (handle) " "ON DELETE CASCADE ) " ";"_ns); } class KeepForeignKeysOffUntilScopeExit final { public: explicit KeepForeignKeysOffUntilScopeExit(const ResultConnection& aConn) : mConn(aConn) {} static Result Create( const ResultConnection& aConn) { QM_TRY( QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns))); KeepForeignKeysOffUntilScopeExit result(aConn); return result; } ~KeepForeignKeysOffUntilScopeExit() { auto maskResult = [this]() -> Result { QM_TRY(MOZ_TO_RESULT( mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); return Ok{}; }; QM_WARNONLY_TRY(maskResult()); } private: ResultConnection mConn; }; nsresult CreateRootEntry(ResultConnection& aConn, const Origin& aOrigin) { KeepForeignKeysOffUntilScopeExit foreignKeysGuard(aConn); const nsLiteralCString createRootQuery = "INSERT OR IGNORE INTO Entries " "( handle, parent ) " "VALUES ( :handle, NULL );"_ns; const nsLiteralCString flagRootAsDirectoryQuery = "INSERT OR IGNORE INTO Directories " "( handle, name ) " "VALUES ( :handle, :name );"_ns; QM_TRY_UNWRAP(EntryId rootId, data::FileSystemHashSource::GenerateHash(aOrigin, kRootString)); mozStorageTransaction transaction( aConn.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); { QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, createRootQuery)); QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId))); QM_TRY(MOZ_TO_RESULT(stmt.Execute())); } { QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, flagRootAsDirectoryQuery)); QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId))); QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, kRootString))); QM_TRY(MOZ_TO_RESULT(stmt.Execute())); } return transaction.Commit(); } Result CheckIfEmpty(ResultConnection& aConn) { const nsLiteralCString areThereTablesQuery = "SELECT EXISTS (" "SELECT 1 FROM sqlite_master " ");"_ns; QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, areThereTablesQuery)); return stmt.YesOrNoQuery(); }; } // namespace Result SchemaVersion001::InitializeConnection( ResultConnection& aConn, const Origin& aOrigin) { QM_TRY_UNWRAP(bool isEmpty, CheckIfEmpty(aConn)); DatabaseVersion currentVersion = 0; if (isEmpty) { QM_TRY(QM_TO_RESULT(SetEncoding(aConn))); } else { QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); } if (currentVersion < sVersion) { mozStorageTransaction transaction( aConn.get(), /* commit on complete */ false, mozIStorageConnection::TRANSACTION_IMMEDIATE); QM_TRY(QM_TO_RESULT(CreateEntries(aConn))); QM_TRY(QM_TO_RESULT(CreateDirectories(aConn))); QM_TRY(QM_TO_RESULT(CreateFiles(aConn))); QM_TRY(QM_TO_RESULT(CreateUsages(aConn))); QM_TRY(QM_TO_RESULT(CreateRootEntry(aConn, aOrigin))); QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion))); QM_TRY(QM_TO_RESULT(transaction.Commit())); } QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); return currentVersion; } } // namespace mozilla::dom::fs