1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozStorage.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2010
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
24 : * Shawn Wilsher <me@shawnwilsher.com>
25 : * John Zhang <jzhang@aptana.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include <limits.h>
42 : #include <stdio.h>
43 :
44 : #include "nsError.h"
45 : #include "nsMemory.h"
46 : #include "nsProxyRelease.h"
47 : #include "nsThreadUtils.h"
48 : #include "nsIClassInfoImpl.h"
49 : #include "nsIProgrammingLanguage.h"
50 : #include "Variant.h"
51 :
52 : #include "mozIStorageError.h"
53 :
54 : #include "mozStorageBindingParams.h"
55 : #include "mozStorageConnection.h"
56 : #include "mozStorageAsyncStatementJSHelper.h"
57 : #include "mozStorageAsyncStatementParams.h"
58 : #include "mozStoragePrivateHelpers.h"
59 : #include "mozStorageStatementRow.h"
60 : #include "mozStorageStatement.h"
61 :
62 : #include "prlog.h"
63 :
64 : #ifdef PR_LOGGING
65 : extern PRLogModuleInfo *gStorageLog;
66 : #endif
67 :
68 : namespace mozilla {
69 : namespace storage {
70 :
71 : ////////////////////////////////////////////////////////////////////////////////
72 : //// nsIClassInfo
73 :
74 229 : NS_IMPL_CI_INTERFACE_GETTER4(
75 : AsyncStatement
76 : , mozIStorageAsyncStatement
77 : , mozIStorageBaseStatement
78 : , mozIStorageBindingParams
79 : , mozilla::storage::StorageBaseStatementInternal
80 229 : )
81 :
82 : class AsyncStatementClassInfo : public nsIClassInfo
83 1464 : {
84 : public:
85 : NS_DECL_ISUPPORTS
86 :
87 : NS_IMETHODIMP
88 229 : GetInterfaces(PRUint32 *_count, nsIID ***_array)
89 : {
90 229 : return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array);
91 : }
92 :
93 : NS_IMETHODIMP
94 3151 : GetHelperForLanguage(PRUint32 aLanguage, nsISupports **_helper)
95 : {
96 3151 : if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) {
97 3151 : static AsyncStatementJSHelper sJSHelper;
98 3151 : *_helper = &sJSHelper;
99 3151 : return NS_OK;
100 : }
101 :
102 0 : *_helper = nsnull;
103 0 : return NS_OK;
104 : }
105 :
106 : NS_IMETHODIMP
107 0 : GetContractID(char **_contractID)
108 : {
109 0 : *_contractID = nsnull;
110 0 : return NS_OK;
111 : }
112 :
113 : NS_IMETHODIMP
114 0 : GetClassDescription(char **_desc)
115 : {
116 0 : *_desc = nsnull;
117 0 : return NS_OK;
118 : }
119 :
120 : NS_IMETHODIMP
121 0 : GetClassID(nsCID **_id)
122 : {
123 0 : *_id = nsnull;
124 0 : return NS_OK;
125 : }
126 :
127 : NS_IMETHODIMP
128 0 : GetImplementationLanguage(PRUint32 *_language)
129 : {
130 0 : *_language = nsIProgrammingLanguage::CPLUSPLUS;
131 0 : return NS_OK;
132 : }
133 :
134 : NS_IMETHODIMP
135 5111 : GetFlags(PRUint32 *_flags)
136 : {
137 5111 : *_flags = nsnull;
138 5111 : return NS_OK;
139 : }
140 :
141 : NS_IMETHODIMP
142 0 : GetClassIDNoAlloc(nsCID *_cid)
143 : {
144 0 : return NS_ERROR_NOT_AVAILABLE;
145 : }
146 : };
147 :
148 3779 : NS_IMETHODIMP_(nsrefcnt) AsyncStatementClassInfo::AddRef() { return 2; }
149 3779 : NS_IMETHODIMP_(nsrefcnt) AsyncStatementClassInfo::Release() { return 1; }
150 3465 : NS_IMPL_QUERY_INTERFACE1(AsyncStatementClassInfo, nsIClassInfo)
151 :
152 1464 : static AsyncStatementClassInfo sAsyncStatementClassInfo;
153 :
154 : ////////////////////////////////////////////////////////////////////////////////
155 : //// AsyncStatement
156 :
157 5918 : AsyncStatement::AsyncStatement()
158 : : StorageBaseStatementInternal()
159 5918 : , mFinalized(false)
160 : {
161 5918 : }
162 :
163 : nsresult
164 5918 : AsyncStatement::initialize(Connection *aDBConnection,
165 : const nsACString &aSQLStatement)
166 : {
167 5918 : NS_ASSERTION(aDBConnection, "No database connection given!");
168 5918 : NS_ASSERTION(aDBConnection->GetNativeConnection(),
169 : "We should never be called with a null sqlite3 database!");
170 :
171 5918 : mDBConnection = aDBConnection;
172 5918 : mSQLString = aSQLStatement;
173 :
174 : #ifdef PR_LOGGING
175 5918 : PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Inited async statement '%s' (0x%p)",
176 : mSQLString.get()));
177 : #endif
178 :
179 : #ifdef DEBUG
180 : // We want to try and test for LIKE and that consumers are using
181 : // escapeStringForLIKE instead of just trusting user input. The idea to
182 : // check to see if they are binding a parameter after like instead of just
183 : // using a string. We only do this in debug builds because it's expensive!
184 5918 : const nsCaseInsensitiveCStringComparator c;
185 5918 : nsACString::const_iterator start, end, e;
186 5918 : aSQLStatement.BeginReading(start);
187 5918 : aSQLStatement.EndReading(end);
188 5918 : e = end;
189 11836 : while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) {
190 : // We have a LIKE in here, so we perform our tests
191 : // FindInReadable moves the iterator, so we have to get a new one for
192 : // each test we perform.
193 0 : nsACString::const_iterator s1, s2, s3;
194 0 : s1 = s2 = s3 = start;
195 :
196 0 : if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) ||
197 0 : ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) ||
198 0 : ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) {
199 : // At this point, we didn't find a LIKE statement followed by ?, :,
200 : // or @, all of which are valid characters for binding a parameter.
201 : // We will warn the consumer that they may not be safely using LIKE.
202 : NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
203 : "are using mozIStorageAsyncStatement::escapeStringForLIKE "
204 : "and that you are binding that result to the statement "
205 0 : "to prevent SQL injection attacks.");
206 : }
207 :
208 : // resetting start and e
209 0 : start = e;
210 0 : e = end;
211 : }
212 : #endif
213 :
214 5918 : return NS_OK;
215 : }
216 :
217 : mozIStorageBindingParams *
218 26640 : AsyncStatement::getParams()
219 : {
220 : nsresult rv;
221 :
222 : // If we do not have an array object yet, make it.
223 26640 : if (!mParamsArray) {
224 34134 : nsCOMPtr<mozIStorageBindingParamsArray> array;
225 17067 : rv = NewBindingParamsArray(getter_AddRefs(array));
226 17067 : NS_ENSURE_SUCCESS(rv, nsnull);
227 :
228 34134 : mParamsArray = static_cast<BindingParamsArray *>(array.get());
229 : }
230 :
231 : // If there isn't already any rows added, we'll have to add one to use.
232 26640 : if (mParamsArray->length() == 0) {
233 51201 : nsRefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray));
234 17067 : NS_ENSURE_TRUE(params, nsnull);
235 :
236 17067 : rv = mParamsArray->AddParams(params);
237 17067 : NS_ENSURE_SUCCESS(rv, nsnull);
238 :
239 : // We have to unlock our params because AddParams locks them. This is safe
240 : // because no reference to the params object was, or ever will be given out.
241 17067 : params->unlock(nsnull);
242 :
243 : // We also want to lock our array at this point - we don't want anything to
244 : // be added to it.
245 34134 : mParamsArray->lock();
246 : }
247 :
248 26640 : return *mParamsArray->begin();
249 : }
250 :
251 : /**
252 : * If we are here then we know there are no pending async executions relying on
253 : * us (StatementData holds a reference to us; this also goes for our own
254 : * AsyncStatementFinalizer which proxies its release to the calling thread) and
255 : * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
256 : * destroyed on the caller thread by garbage-collection/reference counting or on
257 : * the async thread by the last execution of a statement that already lost its
258 : * main-thread refs.
259 : */
260 11836 : AsyncStatement::~AsyncStatement()
261 : {
262 5918 : destructorAsyncFinalize();
263 5918 : cleanupJSHelpers();
264 :
265 : // If we are getting destroyed on the wrong thread, proxy the connection
266 : // release to the right thread. I'm not sure why we do this.
267 5918 : bool onCallingThread = false;
268 5918 : (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread);
269 5918 : if (!onCallingThread) {
270 : // NS_ProxyRelase only magic forgets for us if mDBConnection is an
271 : // nsCOMPtr. Which it is not; it's an nsRefPtr.
272 0 : Connection *forgottenConn = nsnull;
273 0 : mDBConnection.swap(forgottenConn);
274 : (void)::NS_ProxyRelease(forgottenConn->threadOpenedOn,
275 0 : static_cast<mozIStorageConnection *>(forgottenConn));
276 : }
277 5918 : }
278 :
279 : void
280 9927 : AsyncStatement::cleanupJSHelpers()
281 : {
282 : // We are considered dead at this point, so any wrappers for row or params
283 : // need to lose their reference to us.
284 9927 : if (mStatementParamsHolder) {
285 : nsCOMPtr<nsIXPConnectWrappedNative> wrapper =
286 2552 : do_QueryInterface(mStatementParamsHolder);
287 : nsCOMPtr<mozIStorageStatementParams> iParams =
288 2552 : do_QueryWrappedNative(wrapper);
289 : AsyncStatementParams *params =
290 1276 : static_cast<AsyncStatementParams *>(iParams.get());
291 1276 : params->mStatement = nsnull;
292 1276 : mStatementParamsHolder = nsnull;
293 : }
294 9927 : }
295 :
296 : ////////////////////////////////////////////////////////////////////////////////
297 : //// nsISupports
298 :
299 302165 : NS_IMPL_THREADSAFE_ADDREF(AsyncStatement)
300 308083 : NS_IMPL_THREADSAFE_RELEASE(AsyncStatement)
301 :
302 155642 : NS_INTERFACE_MAP_BEGIN(AsyncStatement)
303 155642 : NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement)
304 118628 : NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
305 111908 : NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
306 111908 : NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
307 24237 : if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
308 3151 : foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo);
309 : }
310 : else
311 21086 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
312 17935 : NS_INTERFACE_MAP_END
313 :
314 :
315 : ////////////////////////////////////////////////////////////////////////////////
316 : //// StorageBaseStatementInternal
317 :
318 : Connection *
319 0 : AsyncStatement::getOwner()
320 : {
321 0 : return mDBConnection;
322 : }
323 :
324 : int
325 37831 : AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt)
326 : {
327 : #ifdef DEBUG
328 : // Make sure we are never called on the connection's owning thread.
329 37831 : bool onOpenedThread = false;
330 37831 : (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
331 37831 : NS_ASSERTION(!onOpenedThread,
332 : "We should only be called on the async thread!");
333 : #endif
334 :
335 37831 : if (!mAsyncStatement) {
336 5195 : int rc = mDBConnection->prepareStatement(mSQLString, &mAsyncStatement);
337 5195 : if (rc != SQLITE_OK) {
338 : #ifdef PR_LOGGING
339 8 : PR_LOG(gStorageLog, PR_LOG_ERROR,
340 : ("Sqlite statement prepare error: %d '%s'", rc,
341 : ::sqlite3_errmsg(mDBConnection->GetNativeConnection())));
342 8 : PR_LOG(gStorageLog, PR_LOG_ERROR,
343 : ("Statement was: '%s'", mSQLString.get()));
344 : #endif
345 8 : *_stmt = nsnull;
346 8 : return rc;
347 : }
348 :
349 : #ifdef PR_LOGGING
350 5187 : PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Initialized statement '%s' (0x%p)",
351 : mSQLString.get(),
352 : mAsyncStatement));
353 : #endif
354 : }
355 :
356 37823 : *_stmt = mAsyncStatement;
357 37823 : return SQLITE_OK;
358 : }
359 :
360 : nsresult
361 37841 : AsyncStatement::getAsynchronousStatementData(StatementData &_data)
362 : {
363 37841 : if (mFinalized)
364 0 : return NS_ERROR_UNEXPECTED;
365 :
366 : // Pass null for the sqlite3_stmt; it will be requested on demand from the
367 : // async thread.
368 37841 : _data = StatementData(nsnull, bindingParamsArray(), this);
369 :
370 37841 : return NS_OK;
371 : }
372 :
373 : already_AddRefed<mozIStorageBindingParams>
374 16127 : AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
375 : {
376 16127 : if (mFinalized)
377 0 : return nsnull;
378 :
379 32254 : nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
380 16127 : return params.forget();
381 : }
382 :
383 :
384 : ////////////////////////////////////////////////////////////////////////////////
385 : //// mozIStorageAsyncStatement
386 :
387 : // (nothing is specific to mozIStorageAsyncStatement)
388 :
389 : ////////////////////////////////////////////////////////////////////////////////
390 : //// StorageBaseStatementInternal
391 :
392 : // proxy to StorageBaseStatementInternal using its define helper.
393 54134 : MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
394 : AsyncStatement,
395 : if (mFinalized) return NS_ERROR_UNEXPECTED;)
396 :
397 : NS_IMETHODIMP
398 4034 : AsyncStatement::Finalize()
399 : {
400 4034 : if (mFinalized)
401 25 : return NS_OK;
402 :
403 4009 : mFinalized = true;
404 :
405 : #ifdef PR_LOGGING
406 4009 : PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s'",
407 : mSQLString.get()));
408 : #endif
409 :
410 4009 : asyncFinalize();
411 4009 : cleanupJSHelpers();
412 :
413 4009 : return NS_OK;
414 : }
415 :
416 : NS_IMETHODIMP
417 15989 : AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters)
418 : {
419 15989 : if (mFinalized)
420 0 : return NS_ERROR_UNEXPECTED;
421 :
422 15989 : BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
423 15989 : if (array->getOwner() != this)
424 1 : return NS_ERROR_UNEXPECTED;
425 :
426 15988 : if (array->length() == 0)
427 1 : return NS_ERROR_UNEXPECTED;
428 :
429 15987 : mParamsArray = array;
430 15987 : mParamsArray->lock();
431 :
432 15987 : return NS_OK;
433 : }
434 :
435 : NS_IMETHODIMP
436 7842 : AsyncStatement::GetState(PRInt32 *_state)
437 : {
438 7842 : if (mFinalized)
439 2 : *_state = MOZ_STORAGE_STATEMENT_INVALID;
440 : else
441 7840 : *_state = MOZ_STORAGE_STATEMENT_READY;
442 :
443 7842 : return NS_OK;
444 : }
445 :
446 : ////////////////////////////////////////////////////////////////////////////////
447 : //// mozIStorageBindingParams
448 :
449 26640 : BOILERPLATE_BIND_PROXIES(
450 : AsyncStatement,
451 : if (mFinalized) return NS_ERROR_UNEXPECTED;
452 : )
453 :
454 : } // namespace storage
455 4392 : } // namespace mozilla
|