1/*
2 * Copyright (C) 2013, 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25#include "config.h"
26#include "SQLiteIDBTransaction.h"
27
28#if ENABLE(INDEXED_DATABASE)
29
30#include "IDBCursorInfo.h"
31#include "IndexedDB.h"
32#include "Logging.h"
33#include "SQLiteIDBBackingStore.h"
34#include "SQLiteIDBCursor.h"
35#include "SQLiteTransaction.h"
36#include <wtf/FileSystem.h>
37
38namespace WebCore {
39namespace IDBServer {
40
41SQLiteIDBTransaction::SQLiteIDBTransaction(SQLiteIDBBackingStore& backingStore, const IDBTransactionInfo& info)
42 : m_info(info)
43 , m_backingStore(backingStore)
44{
45}
46
47SQLiteIDBTransaction::~SQLiteIDBTransaction()
48{
49 if (inProgress())
50 m_sqliteTransaction->rollback();
51
52 // Explicitly clear cursors, as that also unregisters them from the backing store.
53 clearCursors();
54}
55
56
57IDBError SQLiteIDBTransaction::begin(SQLiteDatabase& database)
58{
59 ASSERT(!m_sqliteTransaction);
60
61 m_sqliteTransaction = std::make_unique<SQLiteTransaction>(database, m_info.mode() == IDBTransactionMode::Readonly);
62 m_sqliteTransaction->begin();
63
64 if (m_sqliteTransaction->inProgress())
65 return IDBError { };
66
67 return IDBError { UnknownError, "Could not start SQLite transaction in database backend"_s };
68}
69
70IDBError SQLiteIDBTransaction::commit()
71{
72 LOG(IndexedDB, "SQLiteIDBTransaction::commit");
73 if (!m_sqliteTransaction || !m_sqliteTransaction->inProgress())
74 return IDBError { UnknownError, "No SQLite transaction in progress to commit"_s };
75
76 m_sqliteTransaction->commit();
77
78 if (m_sqliteTransaction->inProgress())
79 return IDBError { UnknownError, "Unable to commit SQLite transaction in database backend"_s };
80
81 deleteBlobFilesIfNecessary();
82 moveBlobFilesIfNecessary();
83
84 reset();
85 return IDBError { };
86}
87
88void SQLiteIDBTransaction::moveBlobFilesIfNecessary()
89{
90 String databaseDirectory = m_backingStore.databaseDirectory();
91 for (auto& entry : m_blobTemporaryAndStoredFilenames) {
92 if (!FileSystem::hardLinkOrCopyFile(entry.first, FileSystem::pathByAppendingComponent(databaseDirectory, entry.second)))
93 LOG_ERROR("Failed to link/copy temporary blob file '%s' to location '%s'", entry.first.utf8().data(), FileSystem::pathByAppendingComponent(databaseDirectory, entry.second).utf8().data());
94
95 m_backingStore.temporaryFileHandler().accessToTemporaryFileComplete(entry.first);
96 }
97
98 m_blobTemporaryAndStoredFilenames.clear();
99}
100
101void SQLiteIDBTransaction::deleteBlobFilesIfNecessary()
102{
103 if (m_blobRemovedFilenames.isEmpty())
104 return;
105
106 String databaseDirectory = m_backingStore.databaseDirectory();
107 for (auto& entry : m_blobRemovedFilenames) {
108 String fullPath = FileSystem::pathByAppendingComponent(databaseDirectory, entry);
109 m_backingStore.temporaryFileHandler().accessToTemporaryFileComplete(fullPath);
110 }
111
112 m_blobRemovedFilenames.clear();
113}
114
115IDBError SQLiteIDBTransaction::abort()
116{
117 for (auto& entry : m_blobTemporaryAndStoredFilenames)
118 m_backingStore.temporaryFileHandler().accessToTemporaryFileComplete(entry.first);
119
120 m_blobTemporaryAndStoredFilenames.clear();
121
122 if (!m_sqliteTransaction || !m_sqliteTransaction->inProgress())
123 return IDBError { UnknownError, "No SQLite transaction in progress to abort"_s };
124
125 m_sqliteTransaction->rollback();
126
127 if (m_sqliteTransaction->inProgress())
128 return IDBError { UnknownError, "Unable to abort SQLite transaction in database backend"_s };
129
130 reset();
131 return IDBError { };
132}
133
134void SQLiteIDBTransaction::reset()
135{
136 m_sqliteTransaction = nullptr;
137 clearCursors();
138 ASSERT(m_blobTemporaryAndStoredFilenames.isEmpty());
139}
140
141std::unique_ptr<SQLiteIDBCursor> SQLiteIDBTransaction::maybeOpenBackingStoreCursor(uint64_t objectStoreID, uint64_t indexID, const IDBKeyRangeData& range)
142{
143 ASSERT(m_sqliteTransaction);
144 ASSERT(m_sqliteTransaction->inProgress());
145
146 auto cursor = SQLiteIDBCursor::maybeCreateBackingStoreCursor(*this, objectStoreID, indexID, range);
147
148 if (cursor)
149 m_backingStoreCursors.add(cursor.get());
150
151 return cursor;
152}
153
154SQLiteIDBCursor* SQLiteIDBTransaction::maybeOpenCursor(const IDBCursorInfo& info)
155{
156 ASSERT(m_sqliteTransaction);
157 if (!m_sqliteTransaction->inProgress())
158 return nullptr;
159
160 auto addResult = m_cursors.add(info.identifier(), SQLiteIDBCursor::maybeCreate(*this, info));
161
162 ASSERT(addResult.isNewEntry);
163
164 // It is possible the cursor failed to create and we just stored a null value.
165 if (!addResult.iterator->value) {
166 m_cursors.remove(addResult.iterator);
167 return nullptr;
168 }
169
170 return addResult.iterator->value.get();
171}
172
173void SQLiteIDBTransaction::closeCursor(SQLiteIDBCursor& cursor)
174{
175 auto backingStoreTake = m_backingStoreCursors.take(&cursor);
176 if (backingStoreTake) {
177 ASSERT(!m_cursors.contains(cursor.identifier()));
178 return;
179 }
180
181 ASSERT(m_cursors.contains(cursor.identifier()));
182
183 m_backingStore.unregisterCursor(cursor);
184 m_cursors.remove(cursor.identifier());
185}
186
187void SQLiteIDBTransaction::notifyCursorsOfChanges(int64_t objectStoreID)
188{
189 for (auto& i : m_cursors) {
190 if (i.value->objectStoreID() == objectStoreID)
191 i.value->objectStoreRecordsChanged();
192 }
193
194 for (auto* cursor : m_backingStoreCursors) {
195 if (cursor->objectStoreID() == objectStoreID)
196 cursor->objectStoreRecordsChanged();
197 }
198}
199
200void SQLiteIDBTransaction::clearCursors()
201{
202 for (auto& cursor : m_cursors.values())
203 m_backingStore.unregisterCursor(*cursor);
204
205 m_cursors.clear();
206}
207
208bool SQLiteIDBTransaction::inProgress() const
209{
210 return m_sqliteTransaction && m_sqliteTransaction->inProgress();
211}
212
213void SQLiteIDBTransaction::addBlobFile(const String& temporaryPath, const String& storedFilename)
214{
215 m_blobTemporaryAndStoredFilenames.append({ temporaryPath, storedFilename });
216}
217
218void SQLiteIDBTransaction::addRemovedBlobFile(const String& removedFilename)
219{
220 ASSERT(!m_blobRemovedFilenames.contains(removedFilename));
221 m_blobRemovedFilenames.add(removedFilename);
222}
223
224
225} // namespace IDBServer
226} // namespace WebCore
227
228#endif // ENABLE(INDEXED_DATABASE)
229