diff options
Diffstat (limited to 'storage/mozStorageStatement.cpp')
-rw-r--r-- | storage/mozStorageStatement.cpp | 843 |
1 files changed, 843 insertions, 0 deletions
diff --git a/storage/mozStorageStatement.cpp b/storage/mozStorageStatement.cpp new file mode 100644 index 0000000000..6de46e6ea8 --- /dev/null +++ b/storage/mozStorageStatement.cpp @@ -0,0 +1,843 @@ +/* -*- 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 "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 "mozilla/Logging.h" +#include "mozilla/Printf.h" +#include "mozilla/ProfilerLabels.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() = default; + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + GetInterfaces(nsTArray<nsIID>& _array) override { + return NS_CI_INTERFACE_GETTER_NAME(Statement)(_array); + } + + NS_IMETHOD + GetScriptableHelper(nsIXPCScriptable** _helper) override { + static StatementJSHelper sJSHelper; + *_helper = &sJSHelper; + return NS_OK; + } + + NS_IMETHOD + GetContractID(nsACString& aContractID) override { + aContractID.SetIsVoid(true); + return NS_OK; + } + + NS_IMETHOD + GetClassDescription(nsACString& aDesc) override { + aDesc.SetIsVoid(true); + 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() + : mDBStatement(nullptr), + mParamCount(0), + mResultColumnCount(0), + mExecuting(false), + mQueryStatusRecorded(false), + mHasExecuted(false) {} + +nsresult Statement::initialize(Connection* aDBConnection, + sqlite3* aNativeConnection, + const nsACString& aSQLStatement) { + MOZ_ASSERT(aDBConnection, "No database connection given!"); + MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(), + "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())); + + aDBConnection->RecordQueryStatus(srv); + mQueryStatusRecorded = true; + return convertResultCode(srv); + } + + 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! + auto c = nsCaseInsensitiveCStringComparator; + nsACString::const_iterator start, end, e; + aSQLStatement.BeginReading(start); + aSQLStatement.EndReading(end); + e = end; + while (::FindInReadable(" LIKE"_ns, 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(" LIKE ?"_ns, s1, end, c) || + ::FindInReadable(" LIKE :"_ns, s2, end, c) || + ::FindInReadable(" LIKE @"_ns, 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(); +} + +void Statement::MaybeRecordQueryStatus(int srv, bool isResetting) { + // If the statement hasn't been executed synchronously since it was last reset + // or created then there is no need to record anything. Asynchronous + // statements have their status tracked and recorded by StatementData. + if (!mHasExecuted) { + return; + } + + if (!isResetting && !isErrorCode(srv)) { + // Non-errors will be recorded when finalizing. + return; + } + + // We only record a status if no status has been recorded previously. + if (!mQueryStatusRecorded && mDBConnection) { + mDBConnection->RecordQueryStatus(srv); + } + + // Allow another status to be recorded if we are resetting this statement. + mQueryStatusRecorded = !isResetting; +} + +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) { + mDBConnection->RecordQueryStatus(rc); + *_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 the statement ends up being finalized twice, the second finalization + // would apply to a dangling pointer and may cause unexpected consequences. + // Thus we must be sure that the connection state won't change during this + // operation, to avoid racing with finalizations made by the closing + // connection. See Connection::internalClose(). + MutexAutoLock lockedScope(mDBConnection->sharedAsyncExecutionMutex); + if (!mDBConnection->isClosed(lockedScope)) { + 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 closed. 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. + // + // 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(). + + SmprintfPointer msg = ::mozilla::Smprintf( + "SQL statement (%p) should have been finalized" + " before garbage-collection. For more details on this statement, set" + " NSPR_LOG_MESSAGES=mozStorage:5 .", + mDBStatement); + NS_WARNING(msg.get()); + + // Use %s so we aren't exposing random strings to printf interpolation. + MOZ_LOG(gStorageLog, LogLevel::Warning, ("%s", msg.get())); + } +#endif // DEBUG + } + + // This will be a no-op if the status has already been recorded or if this + // statement has not been executed. Async statements have their status + // tracked and recorded in StatementData. + MaybeRecordQueryStatus(srv, true); + + 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; + + // This will be a no-op if the status has already been recorded or if this + // statement has not been executed. Async statements have their status + // tracked and recorded in StatementData. + MaybeRecordQueryStatus(SQLITE_OK, true); + mHasExecuted = 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) { + AUTO_PROFILER_LABEL("Statement::ExecuteStep", OTHER); + + 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); + mHasExecuted = true; + MaybeRecordQueryStatus(srv); + + 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) / + sizeof(char16_t)); + } + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetVariant(uint32_t aIndex, nsIVariant** _value) { + if (!mDBStatement) { + return NS_ERROR_NOT_INITIALIZED; + } + + ENSURE_INDEX_VALUE(aIndex, mResultColumnCount); + + if (!mExecuting) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIVariant> variant; + int type = ::sqlite3_column_type(mDBStatement, aIndex); + switch (type) { + case SQLITE_INTEGER: + variant = + new IntegerVariant(::sqlite3_column_int64(mDBStatement, aIndex)); + break; + case SQLITE_FLOAT: + variant = new FloatVariant(::sqlite3_column_double(mDBStatement, aIndex)); + break; + case SQLITE_TEXT: { + const char16_t* value = static_cast<const char16_t*>( + ::sqlite3_column_text16(mDBStatement, aIndex)); + nsDependentString str( + value, + ::sqlite3_column_bytes16(mDBStatement, aIndex) / sizeof(char16_t)); + variant = new TextVariant(str); + break; + } + case SQLITE_NULL: + variant = new NullVariant(); + break; + case SQLITE_BLOB: { + int size = ::sqlite3_column_bytes(mDBStatement, aIndex); + const void* data = ::sqlite3_column_blob(mDBStatement, aIndex); + variant = new BlobVariant(std::pair<const void*, int>(data, size)); + break; + } + } + NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED); + + variant.forget(_value); + 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 = moz_xmemdup(::sqlite3_column_blob(mDBStatement, aIndex), size); + } + + *_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* _byteLength, + const char** _value) { + *_value = reinterpret_cast<const char*>( + ::sqlite3_column_text(mDBStatement, aIndex)); + if (_byteLength) { + *_byteLength = ::sqlite3_column_bytes(mDBStatement, aIndex); + } + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetSharedString(uint32_t aIndex, uint32_t* _byteLength, + const char16_t** _value) { + *_value = static_cast<const char16_t*>( + ::sqlite3_column_text16(mDBStatement, aIndex)); + if (_byteLength) { + *_byteLength = ::sqlite3_column_bytes16(mDBStatement, aIndex); + } + return NS_OK; +} + +NS_IMETHODIMP +Statement::GetSharedBlob(uint32_t aIndex, uint32_t* _byteLength, + const uint8_t** _blob) { + *_blob = + static_cast<const uint8_t*>(::sqlite3_column_blob(mDBStatement, aIndex)); + if (_byteLength) { + *_byteLength = ::sqlite3_column_bytes(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 |