Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef MOZSTORAGEHELPER_H
7 : #define MOZSTORAGEHELPER_H
8 :
9 : #include "nsAutoPtr.h"
10 : #include "nsStringGlue.h"
11 : #include "mozilla/DebugOnly.h"
12 : #include "nsIConsoleService.h"
13 : #include "nsIScriptError.h"
14 :
15 : #include "mozIStorageAsyncConnection.h"
16 : #include "mozIStorageConnection.h"
17 : #include "mozIStorageStatement.h"
18 : #include "mozIStoragePendingStatement.h"
19 : #include "nsError.h"
20 : #include "nsIXPConnect.h"
21 :
22 : /**
23 : * This class wraps a transaction inside a given C++ scope, guaranteeing that
24 : * the transaction will be completed even if you have an exception or
25 : * return early.
26 : *
27 : * A common use is to create an instance with aCommitOnComplete = false (rollback),
28 : * then call Commit() on this object manually when your function completes
29 : * successfully.
30 : *
31 : * @note nested transactions are not supported by Sqlite, so if a transaction
32 : * is already in progress, this object does nothing. Note that in this case,
33 : * you may not get the transaction type you asked for, and you won't be able
34 : * to rollback.
35 : *
36 : * @param aConnection
37 : * The connection to create the transaction on.
38 : * @param aCommitOnComplete
39 : * Controls whether the transaction is committed or rolled back when
40 : * this object goes out of scope.
41 : * @param aType [optional]
42 : * The transaction type, as defined in mozIStorageConnection. Defaults
43 : * to TRANSACTION_DEFERRED.
44 : * @param aAsyncCommit [optional]
45 : * Whether commit should be executed asynchronously on the helper thread.
46 : * This is a special option introduced as an interim solution to reduce
47 : * main-thread fsyncs in Places. Can only be used on main-thread.
48 : *
49 : * WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS!
50 : *
51 : * Notice that async commit might cause synchronous statements to fail
52 : * with SQLITE_BUSY. A possible mitigation strategy is to use
53 : * PRAGMA busy_timeout, but notice that might cause main-thread jank.
54 : * Finally, if the database is using WAL journaling mode, other
55 : * connections won't see the changes done in async committed transactions
56 : * until commit is complete.
57 : *
58 : * For all of the above reasons, this should only be used as an interim
59 : * solution and avoided completely if possible.
60 : */
61 : class mozStorageTransaction
62 : {
63 : public:
64 6 : mozStorageTransaction(mozIStorageConnection* aConnection,
65 : bool aCommitOnComplete,
66 : int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED,
67 : bool aAsyncCommit = false)
68 6 : : mConnection(aConnection),
69 : mHasTransaction(false),
70 : mCommitOnComplete(aCommitOnComplete),
71 : mCompleted(false),
72 6 : mAsyncCommit(aAsyncCommit)
73 : {
74 6 : if (mConnection) {
75 10 : nsAutoCString query("BEGIN");
76 5 : switch(aType) {
77 : case mozIStorageConnection::TRANSACTION_IMMEDIATE:
78 1 : query.AppendLiteral(" IMMEDIATE");
79 1 : break;
80 : case mozIStorageConnection::TRANSACTION_EXCLUSIVE:
81 0 : query.AppendLiteral(" EXCLUSIVE");
82 0 : break;
83 : case mozIStorageConnection::TRANSACTION_DEFERRED:
84 4 : query.AppendLiteral(" DEFERRED");
85 4 : break;
86 : default:
87 0 : MOZ_ASSERT(false, "Unknown transaction type");
88 : }
89 : // If a transaction is already in progress, this will fail, since Sqlite
90 : // doesn't support nested transactions.
91 5 : mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query));
92 : }
93 6 : }
94 :
95 6 : ~mozStorageTransaction()
96 6 : {
97 6 : if (mConnection && mHasTransaction && !mCompleted) {
98 1 : if (mCommitOnComplete) {
99 2 : mozilla::DebugOnly<nsresult> rv = Commit();
100 1 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
101 : "A transaction didn't commit correctly");
102 : }
103 : else {
104 0 : mozilla::DebugOnly<nsresult> rv = Rollback();
105 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
106 : "A transaction didn't rollback correctly");
107 : }
108 : }
109 6 : }
110 :
111 : /**
112 : * Commits the transaction if one is in progress. If one is not in progress,
113 : * this is a NOP since the actual owner of the transaction outside of our
114 : * scope is in charge of finally committing or rolling back the transaction.
115 : */
116 5 : nsresult Commit()
117 : {
118 5 : if (!mConnection || mCompleted || !mHasTransaction)
119 0 : return NS_OK;
120 5 : mCompleted = true;
121 :
122 : // TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle
123 : // it, thus the transaction might stay open until the next COMMIT.
124 : nsresult rv;
125 5 : if (mAsyncCommit) {
126 0 : nsCOMPtr<mozIStoragePendingStatement> ps;
127 0 : rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"),
128 0 : nullptr, getter_AddRefs(ps));
129 : }
130 : else {
131 5 : rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT"));
132 : }
133 :
134 5 : if (NS_SUCCEEDED(rv))
135 5 : mHasTransaction = false;
136 :
137 5 : return rv;
138 : }
139 :
140 : /**
141 : * Rolls back the transaction if one is in progress. If one is not in progress,
142 : * this is a NOP since the actual owner of the transaction outside of our
143 : * scope is in charge of finally rolling back the transaction.
144 : */
145 0 : nsresult Rollback()
146 : {
147 0 : if (!mConnection || mCompleted || !mHasTransaction)
148 0 : return NS_OK;
149 0 : mCompleted = true;
150 :
151 : // TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
152 : // a busy error, so this handling can be removed.
153 0 : nsresult rv = NS_OK;
154 0 : do {
155 0 : rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
156 0 : if (rv == NS_ERROR_STORAGE_BUSY)
157 0 : (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
158 0 : } while (rv == NS_ERROR_STORAGE_BUSY);
159 :
160 0 : if (NS_SUCCEEDED(rv))
161 0 : mHasTransaction = false;
162 :
163 0 : return rv;
164 : }
165 :
166 : protected:
167 : nsCOMPtr<mozIStorageConnection> mConnection;
168 : bool mHasTransaction;
169 : bool mCommitOnComplete;
170 : bool mCompleted;
171 : bool mAsyncCommit;
172 : };
173 :
174 : /**
175 : * This class wraps a statement so that it is guaraneed to be reset when
176 : * this object goes out of scope.
177 : *
178 : * Note that this always just resets the statement. If the statement doesn't
179 : * need resetting, the reset operation is inexpensive.
180 : */
181 : class MOZ_STACK_CLASS mozStorageStatementScoper
182 : {
183 : public:
184 14 : explicit mozStorageStatementScoper(mozIStorageStatement* aStatement)
185 14 : : mStatement(aStatement)
186 : {
187 14 : }
188 14 : ~mozStorageStatementScoper()
189 14 : {
190 14 : if (mStatement)
191 14 : mStatement->Reset();
192 14 : }
193 :
194 : /**
195 : * Call this to make the statement not reset. You might do this if you know
196 : * that the statement has been reset.
197 : */
198 0 : void Abandon()
199 : {
200 0 : mStatement = nullptr;
201 0 : }
202 :
203 : protected:
204 : nsCOMPtr<mozIStorageStatement> mStatement;
205 : };
206 :
207 : // Use this to make queries uniquely identifiable in telemetry
208 : // statistics, especially PRAGMAs. We don't include __LINE__ so that
209 : // queries are stable in the face of source code changes.
210 : #define MOZ_STORAGE_UNIQUIFY_QUERY_STR "/* " __FILE__ " */ "
211 :
212 : // Use this to show a console warning when using deprecated methods.
213 : #define WARN_DEPRECATED() \
214 : PR_BEGIN_MACRO \
215 : \
216 : if (NS_IsMainThread()) { \
217 : nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); \
218 : \
219 : if (cs) { \
220 : nsCString msg(__FUNCTION__); \
221 : msg.AppendLiteral(" is deprecated and will be removed soon."); \
222 : \
223 : nsCOMPtr<nsIScriptError> e = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); \
224 : if (e && NS_SUCCEEDED(e->Init(NS_ConvertUTF8toUTF16(msg), EmptyString(), \
225 : EmptyString(), 0, 0, \
226 : nsIScriptError::errorFlag, "Storage"))) { \
227 : cs->LogMessage(e); \
228 : } \
229 : } \
230 : } \
231 : if (NS_IsMainThread()) { \
232 : nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID()); \
233 : if (xpc) { \
234 : mozilla::Unused << xpc->DebugDumpJSStack(false, false, false); \
235 : } \
236 : } \
237 : MOZ_ASSERT(false, "You are trying to use a deprecated mozStorage method. " \
238 : "Check error message in console to identify the method name.");\
239 : PR_END_MACRO
240 :
241 : #endif /* MOZSTORAGEHELPER_H */
|