summaryrefslogtreecommitdiffstats
path: root/dom/fs/parent/datamodel/SchemaVersion001.cpp
blob: c3bb9d216b030a31432339a5d46f9000c325c054 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/* -*- 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<KeepForeignKeysOffUntilScopeExit, QMResult> 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<Ok, nsresult> {
      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<bool, QMResult> 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<DatabaseVersion, QMResult> 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(&currentVersion)));
  }

  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(&currentVersion)));

  return currentVersion;
}

}  // namespace mozilla::dom::fs