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
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/
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
16 * The Original Code is Oracle Corporation code.
18 * The Initial Developer of the Original Code is
20 * Portions created by the Initial Developer are Copyright (C) 2004
21 * the Initial Developer. All Rights Reserved.
24 * Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
25 * Shawn Wilsher <me@shawnwilsher.com>
26 * John Zhang <jzhang@aptana.com>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
46 #include "nsIClassInfoImpl.h"
47 #include "nsIProgrammingLanguage.h"
49 #include "mozStorageConnection.h"
50 #include "mozStorageStatementJSHelper.h"
51 #include "mozStoragePrivateHelpers.h"
52 #include "mozStorageStatementParams.h"
53 #include "mozStorageStatementRow.h"
54 #include "mozStorageStatement.h"
59 extern PRLogModuleInfo
* gStorageLog
;
65 ////////////////////////////////////////////////////////////////////////////////
68 NS_IMPL_CI_INTERFACE_GETTER2(
74 class StatementClassInfo
: public nsIClassInfo
80 GetInterfaces(PRUint32
*_count
, nsIID
***_array
)
82 return NS_CI_INTERFACE_GETTER_NAME(Statement
)(_count
, _array
);
86 GetHelperForLanguage(PRUint32 aLanguage
, nsISupports
**_helper
)
88 if (aLanguage
== nsIProgrammingLanguage::JAVASCRIPT
) {
89 static StatementJSHelper sJSHelper
;
90 *_helper
= &sJSHelper
;
99 GetContractID(char **_contractID
)
101 *_contractID
= nsnull
;
106 GetClassDescription(char **_desc
)
113 GetClassID(nsCID
**_id
)
120 GetImplementationLanguage(PRUint32
*_language
)
122 *_language
= nsIProgrammingLanguage::CPLUSPLUS
;
127 GetFlags(PRUint32
*_flags
)
134 GetClassIDNoAlloc(nsCID
*_cid
)
136 return NS_ERROR_NOT_AVAILABLE
;
140 NS_IMETHODIMP_(nsrefcnt
) StatementClassInfo::AddRef() { return 2; }
141 NS_IMETHODIMP_(nsrefcnt
) StatementClassInfo::Release() { return 1; }
142 NS_IMPL_QUERY_INTERFACE1(StatementClassInfo
, nsIClassInfo
)
144 static StatementClassInfo sStatementClassInfo
;
146 ////////////////////////////////////////////////////////////////////////////////
149 Statement::Statement()
150 : mDBConnection(nsnull
)
158 Statement::initialize(Connection
*aDBConnection
,
159 const nsACString
&aSQLStatement
)
161 NS_ASSERTION(aDBConnection
, "No database connection given!");
162 NS_ASSERTION(!mDBStatement
, "Statement already initialized!");
164 sqlite3
*db
= aDBConnection
->GetNativeConnection();
165 NS_ASSERTION(db
, "We should never be called with a null sqlite3 database!");
167 int srv
= ::sqlite3_prepare_v2(db
, PromiseFlatCString(aSQLStatement
).get(),
168 -1, &mDBStatement
, NULL
);
169 if (srv
!= SQLITE_OK
) {
171 PR_LOG(gStorageLog
, PR_LOG_ERROR
,
172 ("Sqlite statement prepare error: %d '%s'", srv
,
173 ::sqlite3_errmsg(db
)));
174 PR_LOG(gStorageLog
, PR_LOG_ERROR
,
175 ("Statement was: '%s'", PromiseFlatCString(aSQLStatement
).get()));
177 return NS_ERROR_FAILURE
;
181 PR_LOG(gStorageLog
, PR_LOG_NOTICE
, ("Initialized statement '%s' (0x%p)",
182 PromiseFlatCString(aSQLStatement
).get(),
186 mDBConnection
= aDBConnection
;
187 mParamCount
= ::sqlite3_bind_parameter_count(mDBStatement
);
188 mResultColumnCount
= ::sqlite3_column_count(mDBStatement
);
189 mColumnNames
.Clear();
191 for (PRUint32 i
= 0; i
< mResultColumnCount
; i
++) {
192 const char *name
= ::sqlite3_column_name(mDBStatement
, i
);
193 (void)mColumnNames
.AppendElement(nsDependentCString(name
));
197 // We want to try and test for LIKE and that consumers are using
198 // escapeStringForLIKE instead of just trusting user input. The idea to
199 // check to see if they are binding a parameter after like instead of just
200 // using a string. We only do this in debug builds because it's expensive!
201 const nsCaseInsensitiveCStringComparator c
;
202 nsACString::const_iterator start
, end
, e
;
203 aSQLStatement
.BeginReading(start
);
204 aSQLStatement
.EndReading(end
);
206 while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start
, e
, c
)) {
207 // We have a LIKE in here, so we perform our tests
208 // FindInReadable moves the iterator, so we have to get a new one for
209 // each test we perform.
210 nsACString::const_iterator s1
, s2
, s3
;
211 s1
= s2
= s3
= start
;
213 if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1
, end
, c
) ||
214 ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2
, end
, c
) ||
215 ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3
, end
, c
))) {
216 // At this point, we didn't find a LIKE statement followed by ?, :,
217 // or @, all of which are valid characters for binding a parameter.
218 // We will warn the consumer that they may not be safely using LIKE.
219 NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
220 "are using mozIStorageConnection::escapeStringForLIKE "
221 "and that you are binding that result to the statement "
222 "to prevent SQL injection attacks.");
225 // resetting start and e
234 Statement::~Statement()
239 NS_IMPL_THREADSAFE_ADDREF(Statement
)
240 NS_IMPL_THREADSAFE_RELEASE(Statement
)
242 NS_INTERFACE_MAP_BEGIN(Statement
)
243 NS_INTERFACE_MAP_ENTRY(mozIStorageStatement
)
244 NS_INTERFACE_MAP_ENTRY(mozIStorageValueArray
)
245 if (aIID
.Equals(NS_GET_IID(nsIClassInfo
))) {
246 foundInterface
= static_cast<nsIClassInfo
*>(&sStatementClassInfo
);
249 NS_INTERFACE_MAP_ENTRY(nsISupports
)
252 ////////////////////////////////////////////////////////////////////////////////
253 //// mozIStorageStatement
256 Statement::Clone(mozIStorageStatement
**_statement
)
258 nsRefPtr
<Statement
> statement(new Statement());
259 NS_ENSURE_TRUE(statement
, NS_ERROR_OUT_OF_MEMORY
);
261 nsCAutoString
sql(::sqlite3_sql(mDBStatement
));
262 nsresult rv
= statement
->initialize(mDBConnection
, sql
);
263 NS_ENSURE_SUCCESS(rv
, rv
);
265 statement
.forget(_statement
);
270 Statement::Finalize()
276 PR_LOG(gStorageLog
, PR_LOG_NOTICE
, ("Finalizing statement '%s'",
277 ::sqlite3_sql(mDBStatement
)));
280 int srv
= ::sqlite3_finalize(mDBStatement
);
283 // We are considered dead at this point, so any wrappers for row or params
284 // need to lose their reference to us.
285 if (mStatementParamsHolder
) {
286 nsCOMPtr
<nsIXPConnectWrappedNative
> wrapper
=
287 do_QueryInterface(mStatementParamsHolder
);
288 nsCOMPtr
<mozIStorageStatementParams
> iParams
=
289 do_QueryWrappedNative(wrapper
);
290 StatementParams
*params
= static_cast<StatementParams
*>(iParams
.get());
291 params
->mStatement
= nsnull
;
292 mStatementParamsHolder
= nsnull
;
295 if (mStatementRowHolder
) {
296 nsCOMPtr
<nsIXPConnectWrappedNative
> wrapper
=
297 do_QueryInterface(mStatementRowHolder
);
298 nsCOMPtr
<mozIStorageStatementRow
> iRow
=
299 do_QueryWrappedNative(wrapper
);
300 StatementRow
*row
= static_cast<StatementRow
*>(iRow
.get());
301 row
->mStatement
= nsnull
;
302 mStatementRowHolder
= nsnull
;
305 return convertResultCode(srv
);
309 Statement::GetParameterCount(PRUint32
*_parameterCount
)
312 return NS_ERROR_NOT_INITIALIZED
;
314 *_parameterCount
= mParamCount
;
319 Statement::GetParameterName(PRUint32 aParamIndex
,
323 return NS_ERROR_NOT_INITIALIZED
;
324 ENSURE_INDEX_VALUE(aParamIndex
, mParamCount
);
326 const char *name
= ::sqlite3_bind_parameter_name(mDBStatement
,
329 // this thing had no name, so fake one
330 nsCAutoString
name(":");
331 name
.AppendInt(aParamIndex
);
335 _name
.Assign(nsDependentCString(name
));
342 Statement::GetParameterIndex(const nsACString
&aName
,
346 return NS_ERROR_NOT_INITIALIZED
;
348 // We do not accept any forms of names other than ":name", but we need to add
349 // the colon for SQLite.
350 nsCAutoString
name(":");
352 int ind
= ::sqlite3_bind_parameter_index(mDBStatement
,
353 PromiseFlatCString(name
).get());
354 if (ind
== 0) // Named parameter not found.
355 return NS_ERROR_INVALID_ARG
;
357 *_index
= ind
- 1; // SQLite indexes are 1-based, we are 0-based.
363 Statement::GetColumnCount(PRUint32
*_columnCount
)
366 return NS_ERROR_NOT_INITIALIZED
;
368 *_columnCount
= mResultColumnCount
;
373 Statement::GetColumnName(PRUint32 aColumnIndex
,
377 return NS_ERROR_NOT_INITIALIZED
;
378 ENSURE_INDEX_VALUE(aColumnIndex
, mResultColumnCount
);
380 const char *cname
= ::sqlite3_column_name(mDBStatement
, aColumnIndex
);
381 _name
.Assign(nsDependentCString(cname
));
387 Statement::GetColumnIndex(const nsACString
&aName
,
391 return NS_ERROR_NOT_INITIALIZED
;
393 // Surprisingly enough, SQLite doesn't provide an API for this. We have to
394 // determine it ourselves sadly.
395 for (PRUint32 i
= 0; i
< mResultColumnCount
; i
++) {
396 if (mColumnNames
[i
].Equals(aName
)) {
402 return NS_ERROR_INVALID_ARG
;
409 return NS_ERROR_NOT_INITIALIZED
;
412 PR_LOG(gStorageLog
, PR_LOG_DEBUG
, ("Resetting statement: '%s'",
413 ::sqlite3_sql(mDBStatement
)));
415 checkAndLogStatementPerformance(mDBStatement
);
418 (void)sqlite3_reset(mDBStatement
);
419 (void)sqlite3_clear_bindings(mDBStatement
);
427 Statement::BindUTF8StringParameter(PRUint32 aParamIndex
,
428 const nsACString
&aValue
)
431 return NS_ERROR_NOT_INITIALIZED
;
433 int srv
= ::sqlite3_bind_text(mDBStatement
, aParamIndex
+ 1,
434 PromiseFlatCString(aValue
).get(),
435 aValue
.Length(), SQLITE_TRANSIENT
);
436 return convertResultCode(srv
);
440 Statement::BindStringParameter(PRUint32 aParamIndex
,
441 const nsAString
&aValue
)
444 return NS_ERROR_NOT_INITIALIZED
;
446 int srv
= ::sqlite3_bind_text16(mDBStatement
, aParamIndex
+ 1,
447 PromiseFlatString(aValue
).get(),
448 aValue
.Length() * 2, SQLITE_TRANSIENT
);
449 return convertResultCode(srv
);
453 Statement::BindDoubleParameter(PRUint32 aParamIndex
,
457 return NS_ERROR_NOT_INITIALIZED
;
459 int srv
= ::sqlite3_bind_double(mDBStatement
, aParamIndex
+ 1, aValue
);
460 return convertResultCode(srv
);
464 Statement::BindInt32Parameter(PRUint32 aParamIndex
,
468 return NS_ERROR_NOT_INITIALIZED
;
470 int srv
= ::sqlite3_bind_int(mDBStatement
, aParamIndex
+ 1, aValue
);
471 return convertResultCode(srv
);
475 Statement::BindInt64Parameter(PRUint32 aParamIndex
,
479 return NS_ERROR_NOT_INITIALIZED
;
481 int srv
= ::sqlite3_bind_int64(mDBStatement
, aParamIndex
+ 1, aValue
);
482 return convertResultCode(srv
);
486 Statement::BindNullParameter(PRUint32 aParamIndex
)
489 return NS_ERROR_NOT_INITIALIZED
;
491 int srv
= ::sqlite3_bind_null(mDBStatement
, aParamIndex
+ 1);
492 return convertResultCode(srv
);
496 Statement::BindBlobParameter(PRUint32 aParamIndex
,
497 const PRUint8
*aValue
,
501 return NS_ERROR_NOT_INITIALIZED
;
503 int srv
= ::sqlite3_bind_blob(mDBStatement
, aParamIndex
+ 1, aValue
,
504 aValueSize
, SQLITE_TRANSIENT
);
505 return convertResultCode(srv
);
509 Statement::BindParameters(mozIStorageBindingParamsArray
*aParameters
)
512 return NS_ERROR_NOT_INITIALIZED
;
514 BindingParamsArray
*array
= static_cast<BindingParamsArray
*>(aParameters
);
515 if (array
->getOwner() != this)
516 return NS_ERROR_UNEXPECTED
;
518 mParamsArray
= array
;
519 mParamsArray
->lock();
525 Statement::NewBindingParamsArray(mozIStorageBindingParamsArray
**_array
)
527 nsCOMPtr
<mozIStorageBindingParamsArray
> array
=
528 new BindingParamsArray(this);
529 NS_ENSURE_TRUE(array
, NS_ERROR_OUT_OF_MEMORY
);
531 array
.forget(_array
);
539 return NS_ERROR_NOT_INITIALIZED
;
542 nsresult rv
= ExecuteStep(&ret
);
543 NS_ENSURE_SUCCESS(rv
, rv
);
549 Statement::ExecuteStep(PRBool
*_moreResults
)
552 return NS_ERROR_NOT_INITIALIZED
;
554 int srv
= ::sqlite3_step(mDBStatement
);
557 if (srv
!= SQLITE_ROW
&& srv
!= SQLITE_DONE
) {
558 nsCAutoString errStr
;
559 (void)mDBConnection
->GetLastErrorString(errStr
);
560 PR_LOG(gStorageLog
, PR_LOG_DEBUG
,
561 ("Statement::ExecuteStep error: %s", errStr
.get()));
565 // SQLITE_ROW and SQLITE_DONE are non-errors
566 if (srv
== SQLITE_ROW
) {
569 *_moreResults
= PR_TRUE
;
572 else if (srv
== SQLITE_DONE
) {
573 // statement is done (no row returned)
575 *_moreResults
= PR_FALSE
;
578 else if (srv
== SQLITE_BUSY
|| srv
== SQLITE_MISUSE
) {
579 mExecuting
= PR_FALSE
;
581 else if (mExecuting
) {
583 PR_LOG(gStorageLog
, PR_LOG_ERROR
,
584 ("SQLite error after mExecuting was true!"));
586 mExecuting
= PR_FALSE
;
589 return convertResultCode(srv
);
593 Statement::ExecuteAsync(mozIStorageStatementCallback
*aCallback
,
594 mozIStoragePendingStatement
**_stmt
)
596 mozIStorageStatement
*stmts
[1] = {this};
597 return mDBConnection
->ExecuteAsync(stmts
, 1, aCallback
, _stmt
);
601 Statement::GetState(PRInt32
*_state
)
604 *_state
= MOZ_STORAGE_STATEMENT_INVALID
;
606 *_state
= MOZ_STORAGE_STATEMENT_EXECUTING
;
608 *_state
= MOZ_STORAGE_STATEMENT_READY
;
614 Statement::EscapeStringForLIKE(const nsAString
&aValue
,
615 const PRUnichar aEscapeChar
,
616 nsAString
&_escapedString
)
618 const PRUnichar
MATCH_ALL('%');
619 const PRUnichar
MATCH_ONE('_');
621 _escapedString
.Truncate(0);
623 for (PRUint32 i
= 0; i
< aValue
.Length(); i
++) {
624 if (aValue
[i
] == aEscapeChar
|| aValue
[i
] == MATCH_ALL
||
625 aValue
[i
] == MATCH_ONE
)
626 _escapedString
+= aEscapeChar
;
627 _escapedString
+= aValue
[i
];
633 Statement::GetColumnDecltype(PRUint32 aParamIndex
,
634 nsACString
&_declType
)
637 return NS_ERROR_NOT_INITIALIZED
;
639 ENSURE_INDEX_VALUE(aParamIndex
, mResultColumnCount
);
641 _declType
.Assign(::sqlite3_column_decltype(mDBStatement
, aParamIndex
));
645 ////////////////////////////////////////////////////////////////////////////////
646 //// mozIStorageValueArray
649 Statement::GetNumEntries(PRUint32
*_length
)
651 *_length
= mResultColumnCount
;
656 Statement::GetTypeOfIndex(PRUint32 aIndex
,
660 return NS_ERROR_NOT_INITIALIZED
;
662 ENSURE_INDEX_VALUE(aIndex
, mResultColumnCount
);
665 return NS_ERROR_UNEXPECTED
;
667 int t
= ::sqlite3_column_type(mDBStatement
, aIndex
);
670 *_type
= VALUE_TYPE_INTEGER
;
673 *_type
= VALUE_TYPE_FLOAT
;
676 *_type
= VALUE_TYPE_TEXT
;
679 *_type
= VALUE_TYPE_BLOB
;
682 *_type
= VALUE_TYPE_NULL
;
685 return NS_ERROR_FAILURE
;
692 Statement::GetInt32(PRUint32 aIndex
,
696 return NS_ERROR_NOT_INITIALIZED
;
698 ENSURE_INDEX_VALUE(aIndex
, mResultColumnCount
);
701 return NS_ERROR_UNEXPECTED
;
703 *_value
= ::sqlite3_column_int(mDBStatement
, aIndex
);
708 Statement::GetInt64(PRUint32 aIndex
,
712 return NS_ERROR_NOT_INITIALIZED
;
714 ENSURE_INDEX_VALUE(aIndex
, mResultColumnCount
);
717 return NS_ERROR_UNEXPECTED
;
719 *_value
= ::sqlite3_column_int64(mDBStatement
, aIndex
);
725 Statement::GetDouble(PRUint32 aIndex
,
729 return NS_ERROR_NOT_INITIALIZED
;
731 ENSURE_INDEX_VALUE(aIndex
, mResultColumnCount
);
734 return NS_ERROR_UNEXPECTED
;
736 *_value
= ::sqlite3_column_double(mDBStatement
, aIndex
);
742 Statement::GetUTF8String(PRUint32 aIndex
,
745 // Get type of Index will check aIndex for us, so we don't have to.
747 nsresult rv
= GetTypeOfIndex(aIndex
, &type
);
748 NS_ENSURE_SUCCESS(rv
, rv
);
749 if (type
== VALUE_TYPE_NULL
) {
750 // NULL columns should have IsVod set to distinguis them from an empty
753 _value
.SetIsVoid(PR_TRUE
);
757 reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement
,
759 _value
.Assign(value
, ::sqlite3_column_bytes(mDBStatement
, aIndex
));
765 Statement::GetString(PRUint32 aIndex
,
768 // Get type of Index will check aIndex for us, so we don't have to.
770 nsresult rv
= GetTypeOfIndex(aIndex
, &type
);
771 NS_ENSURE_SUCCESS(rv
, rv
);
772 if (type
== VALUE_TYPE_NULL
) {
773 // NULL columns should have IsVod set to distinguis them from an empty
776 _value
.SetIsVoid(PR_TRUE
);
778 const PRUnichar
*value
=
779 static_cast<const PRUnichar
*>(::sqlite3_column_text16(mDBStatement
,
781 _value
.Assign(value
, ::sqlite3_column_bytes16(mDBStatement
, aIndex
) / 2);
787 Statement::GetBlob(PRUint32 aIndex
,
792 return NS_ERROR_NOT_INITIALIZED
;
794 ENSURE_INDEX_VALUE(aIndex
, mResultColumnCount
);
797 return NS_ERROR_UNEXPECTED
;
799 int size
= ::sqlite3_column_bytes(mDBStatement
, aIndex
);
802 blob
= nsMemory::Clone(::sqlite3_column_blob(mDBStatement
, aIndex
), size
);
803 NS_ENSURE_TRUE(blob
, NS_ERROR_OUT_OF_MEMORY
);
806 *_blob
= static_cast<PRUint8
*>(blob
);
812 Statement::GetSharedUTF8String(PRUint32 aIndex
,
817 *_length
= ::sqlite3_column_bytes(mDBStatement
, aIndex
);
819 *_value
= reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement
,
825 Statement::GetSharedString(PRUint32 aIndex
,
827 const PRUnichar
**_value
)
830 *_length
= ::sqlite3_column_bytes16(mDBStatement
, aIndex
);
832 *_value
= static_cast<const PRUnichar
*>(::sqlite3_column_text16(mDBStatement
,
838 Statement::GetSharedBlob(PRUint32 aIndex
,
840 const PRUint8
**_blob
)
842 *_size
= ::sqlite3_column_bytes(mDBStatement
, aIndex
);
843 *_blob
= static_cast<const PRUint8
*>(::sqlite3_column_blob(mDBStatement
,
849 Statement::GetIsNull(PRUint32 aIndex
,
852 // Get type of Index will check aIndex for us, so we don't have to.
854 nsresult rv
= GetTypeOfIndex(aIndex
, &type
);
855 NS_ENSURE_SUCCESS(rv
, rv
);
856 *_isNull
= (type
== VALUE_TYPE_NULL
);
860 } // namespace storage
861 } // namespace mozilla