/* -*- 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 #include "mozilla/UniquePtrExtensions.h" #include "nsString.h" #include "mozStorageError.h" #include "mozStoragePrivateHelpers.h" #include "mozStorageBindingParams.h" #include "Variant.h" #include "sqlite3_static_ext.h" namespace mozilla::storage { //////////////////////////////////////////////////////////////////////////////// //// Local Helper Objects namespace { struct BindingColumnData { BindingColumnData(sqlite3_stmt* aStmt, int aColumn) : stmt(aStmt), column(aColumn) {} sqlite3_stmt* stmt; int column; }; //////////////////////////////////////////////////////////////////////////////// //// Variant Specialization Functions (variantToSQLiteT) int sqlite3_T_int(BindingColumnData aData, int aValue) { return ::sqlite3_bind_int(aData.stmt, aData.column + 1, aValue); } int sqlite3_T_int64(BindingColumnData aData, sqlite3_int64 aValue) { return ::sqlite3_bind_int64(aData.stmt, aData.column + 1, aValue); } int sqlite3_T_double(BindingColumnData aData, double aValue) { return ::sqlite3_bind_double(aData.stmt, aData.column + 1, aValue); } int sqlite3_T_text(BindingColumnData aData, const nsCString& aValue) { return ::sqlite3_bind_text(aData.stmt, aData.column + 1, aValue.get(), aValue.Length(), SQLITE_TRANSIENT); } int sqlite3_T_text16(BindingColumnData aData, const nsString& aValue) { return ::sqlite3_bind_text16( aData.stmt, aData.column + 1, aValue.get(), aValue.Length() * sizeof(char16_t), // Length in bytes! SQLITE_TRANSIENT); } int sqlite3_T_null(BindingColumnData aData) { return ::sqlite3_bind_null(aData.stmt, aData.column + 1); } int sqlite3_T_blob(BindingColumnData aData, const void* aBlob, int aSize) { return ::sqlite3_bind_blob(aData.stmt, aData.column + 1, aBlob, aSize, free); } int sqlite3_T_array(BindingColumnData aData, void* aArray, int aSize, int aType) { // In debug builds ensure that the statement includes at least one `carray()`. MOZ_ASSERT( ::strstr(::sqlite3_sql(aData.stmt), "carray("), "Binding arrays to SQL statements requires using the carray() function."); if (aType == CARRAY_TEXT) { // We don't manage the string buffers lifecycle, thus let SQLite make its // own copy. int srv = ::sqlite3_carray_bind(aData.stmt, aData.column + 1, aArray, aSize, aType, SQLITE_TRANSIENT); free(aArray); return srv; } return ::sqlite3_carray_bind(aData.stmt, aData.column + 1, aArray, aSize, aType, free); } #include "variantToSQLiteT_impl.h" } // namespace //////////////////////////////////////////////////////////////////////////////// //// BindingParams BindingParams::BindingParams(mozIStorageBindingParamsArray* aOwningArray, Statement* aOwningStatement) : mLocked(false), mOwningArray(aOwningArray), mOwningStatement(aOwningStatement), mParamCount(0) { (void)mOwningStatement->GetParameterCount(&mParamCount); mParameters.SetCapacity(mParamCount); } BindingParams::BindingParams(mozIStorageBindingParamsArray* aOwningArray) : mLocked(false), mOwningArray(aOwningArray), mOwningStatement(nullptr), mParamCount(0) {} AsyncBindingParams::AsyncBindingParams( mozIStorageBindingParamsArray* aOwningArray) : BindingParams(aOwningArray) {} void BindingParams::lock() { NS_ASSERTION(mLocked == false, "Parameters have already been locked!"); mLocked = true; // We no longer need to hold a reference to our statement or our owning array. // The array owns us at this point, and it will own a reference to the // statement. mOwningStatement = nullptr; mOwningArray = nullptr; } void BindingParams::unlock(Statement* aOwningStatement) { NS_ASSERTION(mLocked == true, "Parameters were not yet locked!"); mLocked = false; mOwningStatement = aOwningStatement; } const mozIStorageBindingParamsArray* BindingParams::getOwner() const { return mOwningArray; } //////////////////////////////////////////////////////////////////////////////// //// nsISupports NS_IMPL_ISUPPORTS(BindingParams, mozIStorageBindingParams, IStorageBindingParamsInternal) //////////////////////////////////////////////////////////////////////////////// //// IStorageBindingParamsInternal already_AddRefed BindingParams::bind( sqlite3_stmt* aStatement) { // Iterate through all of our stored data, and bind it. for (size_t i = 0; i < mParameters.Length(); i++) { int rc = variantToSQLiteT(BindingColumnData(aStatement, i), mParameters[i]); if (rc != SQLITE_OK) { // We had an error while trying to bind. Now we need to create an error // object with the right message. Note that we special case // SQLITE_MISMATCH, but otherwise get the message from SQLite. const char* msg = "Could not covert nsIVariant to SQLite type."; if (rc != SQLITE_MISMATCH) { msg = ::sqlite3_errmsg(::sqlite3_db_handle(aStatement)); } nsCOMPtr err(new Error(rc, msg)); return err.forget(); } } return nullptr; } already_AddRefed AsyncBindingParams::bind( sqlite3_stmt* aStatement) { nsCOMPtr err; for (const auto& entry : mNamedParameters) { const nsACString& key = entry.GetKey(); // We do not accept any forms of names other than ":name", but we need to // add the colon for SQLite. nsAutoCString name(":"); name.Append(key); int oneIdx = ::sqlite3_bind_parameter_index(aStatement, name.get()); if (oneIdx == 0) { nsAutoCString errMsg(key); errMsg.AppendLiteral(" is not a valid named parameter."); err = new Error(SQLITE_RANGE, errMsg.get()); return err.forget(); } if (mParameters.Length() <= static_cast(oneIdx - 1)) { mParameters.SetLength(oneIdx - 1); mParameters.AppendElement(entry.GetWeak()); } else { mParameters.ReplaceElementAt(oneIdx - 1, entry.GetWeak()); } } // Now bind using the super class. return BindingParams::bind(aStatement); } /////////////////////////////////////////////////////////////////////////////// //// mozIStorageBindingParams NS_IMETHODIMP BindingParams::BindByName(const nsACString& aName, nsIVariant* aValue) { NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); // Get the column index that we need to store this at. uint32_t index; nsresult rv = mOwningStatement->GetParameterIndex(aName, &index); NS_ENSURE_SUCCESS(rv, rv); return BindByIndex(index, aValue); } NS_IMETHODIMP AsyncBindingParams::BindByName(const nsACString& aName, nsIVariant* aValue) { NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); RefPtr variant = convertVariantToStorageVariant(aValue); if (!variant) return NS_ERROR_UNEXPECTED; mNamedParameters.InsertOrUpdate(aName, std::move(variant)); return NS_OK; } NS_IMETHODIMP BindingParams::BindUTF8StringByName(const nsACString& aName, const nsACString& aValue) { nsCOMPtr value(new UTF8TextVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindStringByName(const nsACString& aName, const nsAString& aValue) { nsCOMPtr value(new TextVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindDoubleByName(const nsACString& aName, double aValue) { nsCOMPtr value(new FloatVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindInt32ByName(const nsACString& aName, int32_t aValue) { nsCOMPtr value(new IntegerVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindInt64ByName(const nsACString& aName, int64_t aValue) { nsCOMPtr value(new IntegerVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindNullByName(const nsACString& aName) { nsCOMPtr value(new NullVariant()); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindBlobByName(const nsACString& aName, const uint8_t* aValue, uint32_t aValueSize) { NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); std::pair data(static_cast(aValue), int(aValueSize)); nsCOMPtr value(new BlobVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindBlobArrayByName(const nsACString& aName, const nsTArray& aValue) { return BindBlobByName(aName, aValue.Elements(), aValue.Length()); } NS_IMETHODIMP BindingParams::BindStringAsBlobByName(const nsACString& aName, const nsAString& aValue) { return DoBindStringAsBlobByName(this, aName, aValue); } NS_IMETHODIMP BindingParams::BindUTF8StringAsBlobByName(const nsACString& aName, const nsACString& aValue) { return DoBindStringAsBlobByName(this, aName, aValue); } NS_IMETHODIMP BindingParams::BindAdoptedBlobByName(const nsACString& aName, uint8_t* aValue, uint32_t aValueSize) { UniqueFreePtr uniqueValue(aValue); NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); std::pair data(uniqueValue.release(), int(aValueSize)); nsCOMPtr value(new AdoptedBlobVariant(data)); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindArrayOfIntegersByName(const nsACString& aName, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); std::pair data(static_cast(aValue.Elements()), int(aValue.Length())); nsCOMPtr value(new ArrayOfIntegersVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindArrayOfDoublesByName(const nsACString& aName, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); std::pair data(static_cast(aValue.Elements()), int(aValue.Length())); nsCOMPtr value(new ArrayOfDoublesVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindArrayOfStringsByName(const nsACString& aName, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); nsTArray UTF8Strings(aValue.Length()); for (const nsString& str : aValue) { UTF8Strings.AppendElement(NS_ConvertUTF16toUTF8(str)); } std::pair data( static_cast(UTF8Strings.Elements()), int(UTF8Strings.Length())); // The variant will make a copy of all the buffers. nsCOMPtr value(new ArrayOfUTF8StringsVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindArrayOfUTF8StringsByName(const nsACString& aName, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); std::pair data(static_cast(aValue.Elements()), int(aValue.Length())); // The variant will make a copy of all the buffers. nsCOMPtr value(new ArrayOfUTF8StringsVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByName(aName, value); } NS_IMETHODIMP BindingParams::BindByIndex(uint32_t aIndex, nsIVariant* aValue) { NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); ENSURE_INDEX_VALUE(aIndex, mParamCount); // Store the variant for later use. RefPtr variant = convertVariantToStorageVariant(aValue); if (!variant) return NS_ERROR_UNEXPECTED; if (mParameters.Length() <= aIndex) { (void)mParameters.SetLength(aIndex); (void)mParameters.AppendElement(variant); } else { mParameters.ReplaceElementAt(aIndex, variant); } return NS_OK; } NS_IMETHODIMP AsyncBindingParams::BindByIndex(uint32_t aIndex, nsIVariant* aValue) { NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED); // In the asynchronous case we do not know how many parameters there are to // bind to, so we cannot check the validity of aIndex. RefPtr variant = convertVariantToStorageVariant(aValue); if (!variant) return NS_ERROR_UNEXPECTED; if (mParameters.Length() <= aIndex) { mParameters.SetLength(aIndex); mParameters.AppendElement(variant); } else { mParameters.ReplaceElementAt(aIndex, variant); } return NS_OK; } NS_IMETHODIMP BindingParams::BindUTF8StringByIndex(uint32_t aIndex, const nsACString& aValue) { nsCOMPtr value(new UTF8TextVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindStringByIndex(uint32_t aIndex, const nsAString& aValue) { nsCOMPtr value(new TextVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindDoubleByIndex(uint32_t aIndex, double aValue) { nsCOMPtr value(new FloatVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindInt32ByIndex(uint32_t aIndex, int32_t aValue) { nsCOMPtr value(new IntegerVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindInt64ByIndex(uint32_t aIndex, int64_t aValue) { nsCOMPtr value(new IntegerVariant(aValue)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindNullByIndex(uint32_t aIndex) { nsCOMPtr value(new NullVariant()); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindBlobByIndex(uint32_t aIndex, const uint8_t* aValue, uint32_t aValueSize) { NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); std::pair data(static_cast(aValue), int(aValueSize)); nsCOMPtr value(new BlobVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindBlobArrayByIndex(uint32_t aIndex, const nsTArray& aValue) { return BindBlobByIndex(aIndex, aValue.Elements(), aValue.Length()); } NS_IMETHODIMP BindingParams::BindStringAsBlobByIndex(uint32_t aIndex, const nsAString& aValue) { return DoBindStringAsBlobByIndex(this, aIndex, aValue); } NS_IMETHODIMP BindingParams::BindUTF8StringAsBlobByIndex(uint32_t aIndex, const nsACString& aValue) { return DoBindStringAsBlobByIndex(this, aIndex, aValue); } NS_IMETHODIMP BindingParams::BindAdoptedBlobByIndex(uint32_t aIndex, uint8_t* aValue, uint32_t aValueSize) { UniqueFreePtr uniqueValue(aValue); NS_ENSURE_ARG_MAX(aValueSize, INT_MAX); std::pair data(uniqueValue.release(), int(aValueSize)); nsCOMPtr value(new AdoptedBlobVariant(data)); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindArrayOfIntegersByIndex(uint32_t aIndex, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); std::pair data(static_cast(aValue.Elements()), int(aValue.Length())); nsCOMPtr value(new ArrayOfIntegersVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindArrayOfDoublesByIndex(uint32_t aIndex, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); std::pair data(static_cast(aValue.Elements()), int(aValue.Length())); nsCOMPtr value(new ArrayOfDoublesVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindArrayOfStringsByIndex(uint32_t aIndex, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); nsTArray UTF8Strings(aValue.Length()); for (const nsString& str : aValue) { UTF8Strings.AppendElement(NS_ConvertUTF16toUTF8(str).get()); } std::pair data( static_cast(UTF8Strings.Elements()), int(UTF8Strings.Length())); // The variant will make a copy of all the buffers. nsCOMPtr value(new ArrayOfUTF8StringsVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } NS_IMETHODIMP BindingParams::BindArrayOfUTF8StringsByIndex( uint32_t aIndex, const nsTArray& aValue) { NS_ENSURE_ARG_MAX(aValue.Length(), INT_MAX); std::pair data(static_cast(aValue.Elements()), int(aValue.Length())); // The variant will make a copy of all the buffers. nsCOMPtr value(new ArrayOfUTF8StringsVariant(data)); NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY); return BindByIndex(aIndex, value); } } // namespace mozilla::storage