SchemaVersion001.cpp (5963B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SchemaVersion001.h" 8 9 #include "FileSystemHashSource.h" 10 #include "ResultStatement.h" 11 #include "StartedTransaction.h" 12 #include "fs/FileSystemConstants.h" 13 #include "mozStorageHelper.h" 14 #include "mozilla/dom/quota/QuotaCommon.h" 15 #include "mozilla/dom/quota/ResultExtensions.h" 16 17 namespace mozilla::dom::fs { 18 19 namespace { 20 21 nsresult CreateEntries(ResultConnection& aConn) { 22 return aConn->ExecuteSimpleSQL( 23 "CREATE TABLE IF NOT EXISTS Entries ( " 24 "handle BLOB PRIMARY KEY, " // Generated from parent + name, unique 25 "parent BLOB, " // Not null due to constraint 26 "CONSTRAINT parent_is_a_directory " 27 "FOREIGN KEY (parent) " 28 "REFERENCES Directories (handle) " 29 "ON DELETE CASCADE ) " 30 ";"_ns); 31 } 32 33 nsresult CreateDirectories(ResultConnection& aConn) { 34 return aConn->ExecuteSimpleSQL( 35 "CREATE TABLE IF NOT EXISTS Directories ( " 36 "handle BLOB PRIMARY KEY, " 37 "name BLOB NOT NULL, " 38 "CONSTRAINT directories_are_entries " 39 "FOREIGN KEY (handle) " 40 "REFERENCES Entries (handle) " 41 "ON DELETE CASCADE ) " 42 ";"_ns); 43 } 44 45 nsresult CreateFiles(ResultConnection& aConn) { 46 return aConn->ExecuteSimpleSQL( 47 "CREATE TABLE IF NOT EXISTS Files ( " 48 "handle BLOB PRIMARY KEY, " 49 "type TEXT, " 50 "name BLOB NOT NULL, " 51 "CONSTRAINT files_are_entries " 52 "FOREIGN KEY (handle) " 53 "REFERENCES Entries (handle) " 54 "ON DELETE CASCADE ) " 55 ";"_ns); 56 } 57 58 nsresult CreateUsages(ResultConnection& aConn) { 59 return aConn->ExecuteSimpleSQL( 60 "CREATE TABLE IF NOT EXISTS Usages ( " 61 "handle BLOB PRIMARY KEY, " 62 "usage INTEGER NOT NULL DEFAULT 0, " 63 "tracked BOOLEAN NOT NULL DEFAULT 0 CHECK (tracked IN (0, 1)), " 64 "CONSTRAINT handles_are_files " 65 "FOREIGN KEY (handle) " 66 "REFERENCES Files (handle) " 67 "ON DELETE CASCADE ) " 68 ";"_ns); 69 } 70 71 class KeepForeignKeysOffUntilScopeExit final { 72 public: 73 explicit KeepForeignKeysOffUntilScopeExit(const ResultConnection& aConn) 74 : mConn(aConn) {} 75 76 static Result<KeepForeignKeysOffUntilScopeExit, QMResult> Create( 77 const ResultConnection& aConn) { 78 QM_TRY( 79 QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns))); 80 KeepForeignKeysOffUntilScopeExit result(aConn); 81 return result; 82 } 83 84 ~KeepForeignKeysOffUntilScopeExit() { 85 auto maskResult = [this]() -> Result<Ok, nsresult> { 86 QM_TRY(MOZ_TO_RESULT( 87 mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); 88 89 return Ok{}; 90 }; 91 QM_WARNONLY_TRY(maskResult()); 92 } 93 94 private: 95 ResultConnection mConn; 96 }; 97 98 nsresult CreateRootEntry(ResultConnection& aConn, const Origin& aOrigin) { 99 KeepForeignKeysOffUntilScopeExit foreignKeysGuard(aConn); 100 101 const nsLiteralCString createRootQuery = 102 "INSERT OR IGNORE INTO Entries " 103 "( handle, parent ) " 104 "VALUES ( :handle, NULL );"_ns; 105 106 const nsLiteralCString flagRootAsDirectoryQuery = 107 "INSERT OR IGNORE INTO Directories " 108 "( handle, name ) " 109 "VALUES ( :handle, :name );"_ns; 110 111 QM_TRY_UNWRAP(EntryId rootId, 112 data::FileSystemHashSource::GenerateHash(aOrigin, kRootString)); 113 114 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn)); 115 116 { 117 QM_TRY_UNWRAP(ResultStatement stmt, 118 ResultStatement::Create(aConn, createRootQuery)); 119 QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId))); 120 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 121 } 122 123 { 124 QM_TRY_UNWRAP(ResultStatement stmt, 125 ResultStatement::Create(aConn, flagRootAsDirectoryQuery)); 126 QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId))); 127 QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, kRootString))); 128 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 129 } 130 131 return transaction.Commit(); 132 } 133 134 } // namespace 135 136 nsresult SetEncoding(ResultConnection& aConn) { 137 return aConn->ExecuteSimpleSQL(R"(PRAGMA encoding = "UTF-16";)"_ns); 138 } 139 140 Result<bool, QMResult> CheckIfEmpty(ResultConnection& aConn) { 141 const nsLiteralCString areThereTablesQuery = 142 "SELECT EXISTS (" 143 "SELECT 1 FROM sqlite_master " 144 ");"_ns; 145 146 QM_TRY_UNWRAP(ResultStatement stmt, 147 ResultStatement::Create(aConn, areThereTablesQuery)); 148 149 QM_TRY_UNWRAP(bool tablesExist, stmt.YesOrNoQuery()); 150 151 return !tablesExist; 152 }; 153 154 nsresult SchemaVersion001::CreateTables(ResultConnection& aConn, 155 const Origin& aOrigin) { 156 QM_TRY(MOZ_TO_RESULT(CreateEntries(aConn))); 157 QM_TRY(MOZ_TO_RESULT(CreateDirectories(aConn))); 158 QM_TRY(MOZ_TO_RESULT(CreateFiles(aConn))); 159 QM_TRY(MOZ_TO_RESULT(CreateUsages(aConn))); 160 QM_TRY(MOZ_TO_RESULT(CreateRootEntry(aConn, aOrigin))); 161 162 return NS_OK; 163 } 164 165 Result<DatabaseVersion, QMResult> SchemaVersion001::InitializeConnection( 166 ResultConnection& aConn, const Origin& aOrigin) { 167 QM_TRY_UNWRAP(bool isEmpty, CheckIfEmpty(aConn)); 168 169 DatabaseVersion currentVersion = 0; 170 171 if (isEmpty) { 172 QM_TRY(QM_TO_RESULT(SetEncoding(aConn))); 173 } else { 174 QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); 175 } 176 177 if (currentVersion < sVersion) { 178 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn)); 179 180 QM_TRY(QM_TO_RESULT(SchemaVersion001::CreateTables(aConn, aOrigin))); 181 QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion))); 182 183 QM_TRY(QM_TO_RESULT(transaction.Commit())); 184 } 185 186 QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); 187 188 QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); 189 190 return currentVersion; 191 } 192 193 } // namespace mozilla::dom::fs