michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nsError.h" michael@0: #include "nsMemory.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIClassInfoImpl.h" michael@0: #include "nsIProgrammingLanguage.h" michael@0: #include "Variant.h" michael@0: michael@0: #include "mozIStorageError.h" michael@0: michael@0: #include "mozStorageBindingParams.h" michael@0: #include "mozStorageConnection.h" michael@0: #include "mozStorageStatementJSHelper.h" michael@0: #include "mozStoragePrivateHelpers.h" michael@0: #include "mozStorageStatementParams.h" michael@0: #include "mozStorageStatementRow.h" michael@0: #include "mozStorageStatement.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsDOMClassInfo.h" michael@0: michael@0: #include "prlog.h" michael@0: michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gStorageLog; michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace storage { michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIClassInfo michael@0: michael@0: NS_IMPL_CI_INTERFACE_GETTER(Statement, michael@0: mozIStorageStatement, michael@0: mozIStorageBaseStatement, michael@0: mozIStorageBindingParams, michael@0: mozIStorageValueArray, michael@0: mozilla::storage::StorageBaseStatementInternal) michael@0: michael@0: class StatementClassInfo : public nsIClassInfo michael@0: { michael@0: public: michael@0: MOZ_CONSTEXPR StatementClassInfo() {} michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: NS_IMETHODIMP michael@0: GetInterfaces(uint32_t *_count, nsIID ***_array) michael@0: { michael@0: return NS_CI_INTERFACE_GETTER_NAME(Statement)(_count, _array); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetHelperForLanguage(uint32_t aLanguage, nsISupports **_helper) michael@0: { michael@0: if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) { michael@0: static StatementJSHelper sJSHelper; michael@0: *_helper = &sJSHelper; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *_helper = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetContractID(char **_contractID) michael@0: { michael@0: *_contractID = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetClassDescription(char **_desc) michael@0: { michael@0: *_desc = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetClassID(nsCID **_id) michael@0: { michael@0: *_id = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetImplementationLanguage(uint32_t *_language) michael@0: { michael@0: *_language = nsIProgrammingLanguage::CPLUSPLUS; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetFlags(uint32_t *_flags) michael@0: { michael@0: *_flags = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: GetClassIDNoAlloc(nsCID *_cid) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: }; michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) StatementClassInfo::AddRef() { return 2; } michael@0: NS_IMETHODIMP_(MozExternalRefCountType) StatementClassInfo::Release() { return 1; } michael@0: NS_IMPL_QUERY_INTERFACE(StatementClassInfo, nsIClassInfo) michael@0: michael@0: static StatementClassInfo sStatementClassInfo; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Statement michael@0: michael@0: Statement::Statement() michael@0: : StorageBaseStatementInternal() michael@0: , mDBStatement(nullptr) michael@0: , mColumnNames() michael@0: , mExecuting(false) michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: Statement::initialize(Connection *aDBConnection, michael@0: sqlite3 *aNativeConnection, michael@0: const nsACString &aSQLStatement) michael@0: { michael@0: MOZ_ASSERT(aDBConnection, "No database connection given!"); michael@0: MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid"); michael@0: MOZ_ASSERT(!mDBStatement, "Statement already initialized!"); michael@0: MOZ_ASSERT(aNativeConnection, "No native connection given!"); michael@0: michael@0: int srv = aDBConnection->prepareStatement(aNativeConnection, michael@0: PromiseFlatCString(aSQLStatement), michael@0: &mDBStatement); michael@0: if (srv != SQLITE_OK) { michael@0: PR_LOG(gStorageLog, PR_LOG_ERROR, michael@0: ("Sqlite statement prepare error: %d '%s'", srv, michael@0: ::sqlite3_errmsg(aNativeConnection))); michael@0: PR_LOG(gStorageLog, PR_LOG_ERROR, michael@0: ("Statement was: '%s'", PromiseFlatCString(aSQLStatement).get())); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Initialized statement '%s' (0x%p)", michael@0: PromiseFlatCString(aSQLStatement).get(), michael@0: mDBStatement)); michael@0: michael@0: mDBConnection = aDBConnection; michael@0: mNativeConnection = aNativeConnection; michael@0: mParamCount = ::sqlite3_bind_parameter_count(mDBStatement); michael@0: mResultColumnCount = ::sqlite3_column_count(mDBStatement); michael@0: mColumnNames.Clear(); michael@0: michael@0: for (uint32_t i = 0; i < mResultColumnCount; i++) { michael@0: const char *name = ::sqlite3_column_name(mDBStatement, i); michael@0: (void)mColumnNames.AppendElement(nsDependentCString(name)); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // We want to try and test for LIKE and that consumers are using michael@0: // escapeStringForLIKE instead of just trusting user input. The idea to michael@0: // check to see if they are binding a parameter after like instead of just michael@0: // using a string. We only do this in debug builds because it's expensive! michael@0: const nsCaseInsensitiveCStringComparator c; michael@0: nsACString::const_iterator start, end, e; michael@0: aSQLStatement.BeginReading(start); michael@0: aSQLStatement.EndReading(end); michael@0: e = end; michael@0: while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) { michael@0: // We have a LIKE in here, so we perform our tests michael@0: // FindInReadable moves the iterator, so we have to get a new one for michael@0: // each test we perform. michael@0: nsACString::const_iterator s1, s2, s3; michael@0: s1 = s2 = s3 = start; michael@0: michael@0: if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) || michael@0: ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) || michael@0: ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) { michael@0: // At this point, we didn't find a LIKE statement followed by ?, :, michael@0: // or @, all of which are valid characters for binding a parameter. michael@0: // We will warn the consumer that they may not be safely using LIKE. michael@0: NS_WARNING("Unsafe use of LIKE detected! Please ensure that you " michael@0: "are using mozIStorageStatement::escapeStringForLIKE " michael@0: "and that you are binding that result to the statement " michael@0: "to prevent SQL injection attacks."); michael@0: } michael@0: michael@0: // resetting start and e michael@0: start = e; michael@0: e = end; michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: mozIStorageBindingParams * michael@0: Statement::getParams() michael@0: { michael@0: nsresult rv; michael@0: michael@0: // If we do not have an array object yet, make it. michael@0: if (!mParamsArray) { michael@0: nsCOMPtr array; michael@0: rv = NewBindingParamsArray(getter_AddRefs(array)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: mParamsArray = static_cast(array.get()); michael@0: } michael@0: michael@0: // If there isn't already any rows added, we'll have to add one to use. michael@0: if (mParamsArray->length() == 0) { michael@0: nsRefPtr params(new BindingParams(mParamsArray, this)); michael@0: NS_ENSURE_TRUE(params, nullptr); michael@0: michael@0: rv = mParamsArray->AddParams(params); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: // We have to unlock our params because AddParams locks them. This is safe michael@0: // because no reference to the params object was, or ever will be given out. michael@0: params->unlock(this); michael@0: michael@0: // We also want to lock our array at this point - we don't want anything to michael@0: // be added to it. Nothing has, or will ever get a reference to it, but we michael@0: // will get additional safety checks via assertions by doing this. michael@0: mParamsArray->lock(); michael@0: } michael@0: michael@0: return *mParamsArray->begin(); michael@0: } michael@0: michael@0: Statement::~Statement() michael@0: { michael@0: (void)internalFinalize(true); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsISupports michael@0: michael@0: NS_IMPL_ADDREF(Statement) michael@0: NS_IMPL_RELEASE(Statement) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(Statement) michael@0: NS_INTERFACE_MAP_ENTRY(mozIStorageStatement) michael@0: NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) michael@0: NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) michael@0: NS_INTERFACE_MAP_ENTRY(mozIStorageValueArray) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) michael@0: if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { michael@0: foundInterface = static_cast(&sStatementClassInfo); michael@0: } michael@0: else michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageStatement) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// StorageBaseStatementInternal michael@0: michael@0: Connection * michael@0: Statement::getOwner() michael@0: { michael@0: return mDBConnection; michael@0: } michael@0: michael@0: int michael@0: Statement::getAsyncStatement(sqlite3_stmt **_stmt) michael@0: { michael@0: // If we have no statement, we shouldn't be calling this method! michael@0: NS_ASSERTION(mDBStatement != nullptr, "We have no statement to clone!"); michael@0: michael@0: // If we do not yet have a cached async statement, clone our statement now. michael@0: if (!mAsyncStatement) { michael@0: nsDependentCString sql(::sqlite3_sql(mDBStatement)); michael@0: int rc = mDBConnection->prepareStatement(mNativeConnection, sql, michael@0: &mAsyncStatement); michael@0: if (rc != SQLITE_OK) { michael@0: *_stmt = nullptr; michael@0: return rc; michael@0: } michael@0: michael@0: PR_LOG(gStorageLog, PR_LOG_NOTICE, michael@0: ("Cloned statement 0x%p to 0x%p", mDBStatement, mAsyncStatement)); michael@0: } michael@0: michael@0: *_stmt = mAsyncStatement; michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Statement::getAsynchronousStatementData(StatementData &_data) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: sqlite3_stmt *stmt; michael@0: int rc = getAsyncStatement(&stmt); michael@0: if (rc != SQLITE_OK) michael@0: return convertResultCode(rc); michael@0: michael@0: _data = StatementData(stmt, bindingParamsArray(), this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: Statement::newBindingParams(mozIStorageBindingParamsArray *aOwner) michael@0: { michael@0: nsCOMPtr params = new BindingParams(aOwner, this); michael@0: return params.forget(); michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageStatement michael@0: michael@0: // proxy to StorageBaseStatementInternal using its define helper. michael@0: MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(Statement, (void)0;) michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::Clone(mozIStorageStatement **_statement) michael@0: { michael@0: nsRefPtr statement(new Statement()); michael@0: NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsAutoCString sql(::sqlite3_sql(mDBStatement)); michael@0: nsresult rv = statement->initialize(mDBConnection, mNativeConnection, sql); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: statement.forget(_statement); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::Finalize() michael@0: { michael@0: return internalFinalize(false); michael@0: } michael@0: michael@0: nsresult michael@0: Statement::internalFinalize(bool aDestructing) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_OK; michael@0: michael@0: int srv = SQLITE_OK; michael@0: michael@0: if (!mDBConnection->isClosed()) { michael@0: // michael@0: // The connection is still open. While statement finalization and michael@0: // closing may, in some cases, take place in two distinct threads, michael@0: // we have a guarantee that the connection will remain open until michael@0: // this method terminates: michael@0: // michael@0: // a. The connection will be closed synchronously. In this case, michael@0: // there is no race condition, as everything takes place on the michael@0: // same thread. michael@0: // michael@0: // b. The connection is closed asynchronously and this code is michael@0: // executed on the opener thread. In this case, asyncClose() has michael@0: // not been called yet and will not be called before we return michael@0: // from this function. michael@0: // michael@0: // c. The connection is closed asynchronously and this code is michael@0: // executed on the async execution thread. In this case, michael@0: // AsyncCloseConnection::Run() has not been called yet and will michael@0: // not be called before we return from this function. michael@0: // michael@0: // In either case, the connection is still valid, hence closing michael@0: // here is safe. michael@0: // michael@0: PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s' during garbage-collection", michael@0: ::sqlite3_sql(mDBStatement))); michael@0: srv = ::sqlite3_finalize(mDBStatement); michael@0: } michael@0: #ifdef DEBUG michael@0: else { michael@0: // michael@0: // The database connection is either closed or closing. The sqlite michael@0: // statement has either been finalized already by the connection michael@0: // or is about to be finalized by the connection. michael@0: // michael@0: // Finalizing it here would be useless and segfaultish. michael@0: // michael@0: michael@0: char *msg = ::PR_smprintf("SQL statement (%x) should have been finalized" michael@0: " before garbage-collection. For more details on this statement, set" michael@0: " NSPR_LOG_MESSAGES=mozStorage:5 .", michael@0: mDBStatement); michael@0: michael@0: // michael@0: // Note that we can't display the statement itself, as the data structure michael@0: // is not valid anymore. However, the address shown here should help michael@0: // developers correlate with the more complete debug message triggered michael@0: // by AsyncClose(). michael@0: // michael@0: michael@0: #if 0 michael@0: // Deactivate the warning until we have fixed the exising culprit michael@0: // (see bug 914070). michael@0: NS_WARNING(msg); michael@0: #endif // 0 michael@0: michael@0: PR_LOG(gStorageLog, PR_LOG_WARNING, (msg)); michael@0: michael@0: ::PR_smprintf_free(msg); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: mDBStatement = nullptr; michael@0: michael@0: if (mAsyncStatement) { michael@0: // If the destructor called us, there are no pending async statements (they michael@0: // hold a reference to us) and we can/must just kill the statement directly. michael@0: if (aDestructing) michael@0: destructorAsyncFinalize(); michael@0: else michael@0: asyncFinalize(); michael@0: } michael@0: michael@0: // We are considered dead at this point, so any wrappers for row or params michael@0: // need to lose their reference to us. michael@0: if (mStatementParamsHolder) { michael@0: nsCOMPtr wrapper = michael@0: do_QueryInterface(mStatementParamsHolder); michael@0: nsCOMPtr iParams = michael@0: do_QueryWrappedNative(wrapper); michael@0: StatementParams *params = static_cast(iParams.get()); michael@0: params->mStatement = nullptr; michael@0: mStatementParamsHolder = nullptr; michael@0: } michael@0: michael@0: if (mStatementRowHolder) { michael@0: nsCOMPtr wrapper = michael@0: do_QueryInterface(mStatementRowHolder); michael@0: nsCOMPtr iRow = michael@0: do_QueryWrappedNative(wrapper); michael@0: StatementRow *row = static_cast(iRow.get()); michael@0: row->mStatement = nullptr; michael@0: mStatementRowHolder = nullptr; michael@0: } michael@0: michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetParameterCount(uint32_t *_parameterCount) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *_parameterCount = mParamCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetParameterName(uint32_t aParamIndex, michael@0: nsACString &_name) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: ENSURE_INDEX_VALUE(aParamIndex, mParamCount); michael@0: michael@0: const char *name = ::sqlite3_bind_parameter_name(mDBStatement, michael@0: aParamIndex + 1); michael@0: if (name == nullptr) { michael@0: // this thing had no name, so fake one michael@0: nsAutoCString name(":"); michael@0: name.AppendInt(aParamIndex); michael@0: _name.Assign(name); michael@0: } michael@0: else { michael@0: _name.Assign(nsDependentCString(name)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetParameterIndex(const nsACString &aName, michael@0: uint32_t *_index) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // We do not accept any forms of names other than ":name", but we need to add michael@0: // the colon for SQLite. michael@0: nsAutoCString name(":"); michael@0: name.Append(aName); michael@0: int ind = ::sqlite3_bind_parameter_index(mDBStatement, name.get()); michael@0: if (ind == 0) // Named parameter not found. michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_index = ind - 1; // SQLite indexes are 1-based, we are 0-based. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetColumnCount(uint32_t *_columnCount) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: *_columnCount = mResultColumnCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetColumnName(uint32_t aColumnIndex, michael@0: nsACString &_name) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: ENSURE_INDEX_VALUE(aColumnIndex, mResultColumnCount); michael@0: michael@0: const char *cname = ::sqlite3_column_name(mDBStatement, aColumnIndex); michael@0: _name.Assign(nsDependentCString(cname)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetColumnIndex(const nsACString &aName, michael@0: uint32_t *_index) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // Surprisingly enough, SQLite doesn't provide an API for this. We have to michael@0: // determine it ourselves sadly. michael@0: for (uint32_t i = 0; i < mResultColumnCount; i++) { michael@0: if (mColumnNames[i].Equals(aName)) { michael@0: *_index = i; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::Reset() michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: #ifdef DEBUG michael@0: PR_LOG(gStorageLog, PR_LOG_DEBUG, ("Resetting statement: '%s'", michael@0: ::sqlite3_sql(mDBStatement))); michael@0: michael@0: checkAndLogStatementPerformance(mDBStatement); michael@0: #endif michael@0: michael@0: mParamsArray = nullptr; michael@0: (void)sqlite3_reset(mDBStatement); michael@0: (void)sqlite3_clear_bindings(mDBStatement); michael@0: michael@0: mExecuting = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::BindParameters(mozIStorageBindingParamsArray *aParameters) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: BindingParamsArray *array = static_cast(aParameters); michael@0: if (array->getOwner() != this) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: if (array->length() == 0) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: mParamsArray = array; michael@0: mParamsArray->lock(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::Execute() michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: bool ret; michael@0: nsresult rv = ExecuteStep(&ret); michael@0: nsresult rv2 = Reset(); michael@0: michael@0: return NS_FAILED(rv) ? rv : rv2; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::ExecuteStep(bool *_moreResults) michael@0: { michael@0: PROFILER_LABEL("storage", "Statement::ExecuteStep"); michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // Bind any parameters first before executing. michael@0: if (mParamsArray) { michael@0: // If we have more than one row of parameters to bind, they shouldn't be michael@0: // calling this method (and instead use executeAsync). michael@0: if (mParamsArray->length() != 1) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: BindingParamsArray::iterator row = mParamsArray->begin(); michael@0: nsCOMPtr bindingInternal = michael@0: do_QueryInterface(*row); michael@0: nsCOMPtr error = bindingInternal->bind(mDBStatement); michael@0: if (error) { michael@0: int32_t srv; michael@0: (void)error->GetResult(&srv); michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: // We have bound, so now we can clear our array. michael@0: mParamsArray = nullptr; michael@0: } michael@0: int srv = mDBConnection->stepStatement(mNativeConnection, mDBStatement); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (srv != SQLITE_ROW && srv != SQLITE_DONE) { michael@0: nsAutoCString errStr; michael@0: (void)mDBConnection->GetLastErrorString(errStr); michael@0: PR_LOG(gStorageLog, PR_LOG_DEBUG, michael@0: ("Statement::ExecuteStep error: %s", errStr.get())); michael@0: } michael@0: #endif michael@0: michael@0: // SQLITE_ROW and SQLITE_DONE are non-errors michael@0: if (srv == SQLITE_ROW) { michael@0: // we got a row back michael@0: mExecuting = true; michael@0: *_moreResults = true; michael@0: return NS_OK; michael@0: } michael@0: else if (srv == SQLITE_DONE) { michael@0: // statement is done (no row returned) michael@0: mExecuting = false; michael@0: *_moreResults = false; michael@0: return NS_OK; michael@0: } michael@0: else if (srv == SQLITE_BUSY || srv == SQLITE_MISUSE) { michael@0: mExecuting = false; michael@0: } michael@0: else if (mExecuting) { michael@0: PR_LOG(gStorageLog, PR_LOG_ERROR, michael@0: ("SQLite error after mExecuting was true!")); michael@0: mExecuting = false; michael@0: } michael@0: michael@0: return convertResultCode(srv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetState(int32_t *_state) michael@0: { michael@0: if (!mDBStatement) michael@0: *_state = MOZ_STORAGE_STATEMENT_INVALID; michael@0: else if (mExecuting) michael@0: *_state = MOZ_STORAGE_STATEMENT_EXECUTING; michael@0: else michael@0: *_state = MOZ_STORAGE_STATEMENT_READY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetColumnDecltype(uint32_t aParamIndex, michael@0: nsACString &_declType) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: ENSURE_INDEX_VALUE(aParamIndex, mResultColumnCount); michael@0: michael@0: _declType.Assign(::sqlite3_column_decltype(mDBStatement, aParamIndex)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageValueArray (now part of mozIStorageStatement too) michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetNumEntries(uint32_t *_length) michael@0: { michael@0: *_length = mResultColumnCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetTypeOfIndex(uint32_t aIndex, michael@0: int32_t *_type) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); michael@0: michael@0: if (!mExecuting) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: int t = ::sqlite3_column_type(mDBStatement, aIndex); michael@0: switch (t) { michael@0: case SQLITE_INTEGER: michael@0: *_type = mozIStorageStatement::VALUE_TYPE_INTEGER; michael@0: break; michael@0: case SQLITE_FLOAT: michael@0: *_type = mozIStorageStatement::VALUE_TYPE_FLOAT; michael@0: break; michael@0: case SQLITE_TEXT: michael@0: *_type = mozIStorageStatement::VALUE_TYPE_TEXT; michael@0: break; michael@0: case SQLITE_BLOB: michael@0: *_type = mozIStorageStatement::VALUE_TYPE_BLOB; michael@0: break; michael@0: case SQLITE_NULL: michael@0: *_type = mozIStorageStatement::VALUE_TYPE_NULL; michael@0: break; michael@0: default: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetInt32(uint32_t aIndex, michael@0: int32_t *_value) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); michael@0: michael@0: if (!mExecuting) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: *_value = ::sqlite3_column_int(mDBStatement, aIndex); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetInt64(uint32_t aIndex, michael@0: int64_t *_value) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); michael@0: michael@0: if (!mExecuting) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: *_value = ::sqlite3_column_int64(mDBStatement, aIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetDouble(uint32_t aIndex, michael@0: double *_value) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); michael@0: michael@0: if (!mExecuting) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: *_value = ::sqlite3_column_double(mDBStatement, aIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetUTF8String(uint32_t aIndex, michael@0: nsACString &_value) michael@0: { michael@0: // Get type of Index will check aIndex for us, so we don't have to. michael@0: int32_t type; michael@0: nsresult rv = GetTypeOfIndex(aIndex, &type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (type == mozIStorageStatement::VALUE_TYPE_NULL) { michael@0: // NULL columns should have IsVoid set to distinguish them from the empty michael@0: // string. michael@0: _value.Truncate(0); michael@0: _value.SetIsVoid(true); michael@0: } michael@0: else { michael@0: const char *value = michael@0: reinterpret_cast(::sqlite3_column_text(mDBStatement, michael@0: aIndex)); michael@0: _value.Assign(value, ::sqlite3_column_bytes(mDBStatement, aIndex)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetString(uint32_t aIndex, michael@0: nsAString &_value) michael@0: { michael@0: // Get type of Index will check aIndex for us, so we don't have to. michael@0: int32_t type; michael@0: nsresult rv = GetTypeOfIndex(aIndex, &type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (type == mozIStorageStatement::VALUE_TYPE_NULL) { michael@0: // NULL columns should have IsVoid set to distinguish them from the empty michael@0: // string. michael@0: _value.Truncate(0); michael@0: _value.SetIsVoid(true); michael@0: } else { michael@0: const char16_t *value = michael@0: static_cast(::sqlite3_column_text16(mDBStatement, michael@0: aIndex)); michael@0: _value.Assign(value, ::sqlite3_column_bytes16(mDBStatement, aIndex) / 2); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetBlob(uint32_t aIndex, michael@0: uint32_t *_size, michael@0: uint8_t **_blob) michael@0: { michael@0: if (!mDBStatement) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); michael@0: michael@0: if (!mExecuting) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: int size = ::sqlite3_column_bytes(mDBStatement, aIndex); michael@0: void *blob = nullptr; michael@0: if (size) { michael@0: blob = nsMemory::Clone(::sqlite3_column_blob(mDBStatement, aIndex), size); michael@0: NS_ENSURE_TRUE(blob, NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: *_blob = static_cast(blob); michael@0: *_size = size; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetSharedUTF8String(uint32_t aIndex, michael@0: uint32_t *_length, michael@0: const char **_value) michael@0: { michael@0: if (_length) michael@0: *_length = ::sqlite3_column_bytes(mDBStatement, aIndex); michael@0: michael@0: *_value = reinterpret_cast(::sqlite3_column_text(mDBStatement, michael@0: aIndex)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetSharedString(uint32_t aIndex, michael@0: uint32_t *_length, michael@0: const char16_t **_value) michael@0: { michael@0: if (_length) michael@0: *_length = ::sqlite3_column_bytes16(mDBStatement, aIndex); michael@0: michael@0: *_value = static_cast(::sqlite3_column_text16(mDBStatement, michael@0: aIndex)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetSharedBlob(uint32_t aIndex, michael@0: uint32_t *_size, michael@0: const uint8_t **_blob) michael@0: { michael@0: *_size = ::sqlite3_column_bytes(mDBStatement, aIndex); michael@0: *_blob = static_cast(::sqlite3_column_blob(mDBStatement, michael@0: aIndex)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Statement::GetIsNull(uint32_t aIndex, michael@0: bool *_isNull) michael@0: { michael@0: // Get type of Index will check aIndex for us, so we don't have to. michael@0: int32_t type; michael@0: nsresult rv = GetTypeOfIndex(aIndex, &type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *_isNull = (type == mozIStorageStatement::VALUE_TYPE_NULL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageBindingParams michael@0: michael@0: BOILERPLATE_BIND_PROXIES( michael@0: Statement, michael@0: if (!mDBStatement) return NS_ERROR_NOT_INITIALIZED; michael@0: ) michael@0: michael@0: } // namespace storage michael@0: } // namespace mozilla