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