diff --git a/storage/public/Makefile.in b/storage/public/Makefile.in index 6dfcfc69aee..2a29df7a6ad 100644 --- a/storage/public/Makefile.in +++ b/storage/public/Makefile.in @@ -21,6 +21,7 @@ # # Contributor(s): # Vladimir Vukicevic +# Lev Serebryakov # # Alternatively, the contents of this file may be used under the terms of # either of the GNU General Public License Version 2 or later (the "GPL"), @@ -50,7 +51,9 @@ GRE_MODULE = 1 XPIDLSRCS = \ mozIStorageService.idl \ mozIStorageConnection.idl \ + mozIStorageAggregateFunction.idl \ mozIStorageFunction.idl \ + mozIStorageProgressHandler.idl \ mozIStorageStatement.idl \ mozIStorageStatementWrapper.idl \ mozIStorageDataSet.idl \ diff --git a/storage/public/mozIStorageAggregateFunction.idl b/storage/public/mozIStorageAggregateFunction.idl new file mode 100644 index 00000000000..93ab04c52bf --- /dev/null +++ b/storage/public/mozIStorageAggregateFunction.idl @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Storage Code. + * + * The Initial Developer of the Original Code is + * Lev Serebryakov + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +interface mozIStorageConnection; +interface mozIStorageValueArray; +interface nsIArray; +interface nsIVariant; + +/** + * mozIStorageAggregateFunction represents aggregate SQL function. + * Common examples of aggregate functions are SUM() and COUNT(). + * + * An aggregate function calculates one result for a given set of data, where + * a set of data is a group of tuples. There can be one group + * per request or many of them, if GROUP BY clause is used or not. + */ +[scriptable, uuid(763217b7-3123-11da-918d-000347412e16)] +interface mozIStorageAggregateFunction : nsISupports { + /** + * onStep is called when next value should be passed to + * a custom function. + * + * @param aFunctionArguments The arguments passed in to the function + */ + void onStep(in mozIStorageValueArray aFunctionArguments); + + /** + * Called when all tuples in a group have been processed and the engine + * needs the aggregate function's value. + * + * @returns aggregate result as Variant. + */ + nsIVariant onFinal(); +}; diff --git a/storage/public/mozIStorageConnection.idl b/storage/public/mozIStorageConnection.idl index b1fb77b88cd..6b68c08ce86 100644 --- a/storage/public/mozIStorageConnection.idl +++ b/storage/public/mozIStorageConnection.idl @@ -23,6 +23,7 @@ * Vladimir Vukicevic * Brett Wilson * Shawn Wilsher + * Lev Serebryakov * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -40,7 +41,9 @@ #include "nsISupports.idl" +interface mozIStorageAggregateFunction; interface mozIStorageFunction; +interface mozIStorageProgressHandler; interface mozIStorageStatement; interface nsIFile; @@ -51,7 +54,7 @@ interface nsIFile; * creating prepared statements, executing SQL, and examining database * errors. */ -[scriptable, uuid(43933f76-3376-4999-9096-6f1d545fe5f6)] +[scriptable, uuid(9f36de9d-6471-4249-afed-1ee7760e325c)] interface mozIStorageConnection : nsISupports { /* * Initialization and status @@ -196,11 +199,57 @@ interface mozIStorageConnection : nsISupports { /** * Create a new SQLite function + * + * @param aFunctionName The name of function to create, as seen in SQL. + * @param aNumArguments The number of arguments the function takes. Pass + * -1 for variable-argument functions. + * @param aFunction The instance of mozIStorageFunction, which implements + * the function in question. */ - void createFunction(in string aFunctionName, + void createFunction(in AUTF8String aFunctionName, in long aNumArguments, in mozIStorageFunction aFunction); + /** + * Create a new SQLite aggregate function + * + * @param aFunctionName The name of aggregate function to create, as seen + * in SQL. + * @param aNumArguments The number of arguments the function takes. Pass + * -1 for variable-argument functions. + * @param aFunction The instance of mozIStorageAggreagteFunction, + * which implements the function in question. + */ + void createAggregateFunction(in AUTF8String aFunctionName, + in long aNumArguments, + in mozIStorageAggregateFunction aFunction); + /** + * Delete custom SQLite function (simple or aggregate one) + * + * @param aFunctionName The name of function to remove. + */ + void removeFunction(in AUTF8String aFunctionName); + + /** + * Sets a progress handler. Only one handler can be registered at a time. + * If you need more than one, you need to chain them yourself. + * + * @param aGranularity The number of SQL virtual machine steps between + * progress handler callbacks. + * @param aHandler The instance of mozIStorageProgressHandler. + * + * @return previous registered handler. + */ + mozIStorageProgressHandler setProgressHandler(in PRInt32 aGranularity, + in mozIStorageProgressHandler aHandler); + + /** + * Remove a progress handler. + * + * @return previous registered handler. + */ + mozIStorageProgressHandler removeProgressHandler(); + /* * Utilities */ diff --git a/storage/public/mozIStorageFunction.idl b/storage/public/mozIStorageFunction.idl index 90ed9ecbcb8..1252050fee3 100644 --- a/storage/public/mozIStorageFunction.idl +++ b/storage/public/mozIStorageFunction.idl @@ -21,6 +21,7 @@ * * Contributor(s): * Vladimir Vukicevic + * Lev Serebryakov * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -41,21 +42,32 @@ interface mozIStorageConnection; interface mozIStorageValueArray; interface nsIArray; +interface nsIVariant; /** * mozIStorageFunction is to be implemented by storage consumers that - * wish to define custom storage functions, through - * mozIStorageConnection's createFunction method. + * wish to receive callbacks during the request execution. + * + * SQL can apply functions to values from tables. Examples of + * such functions are MIN(a1,a2) or SQRT(num). Many functions are + * implemented in SQL engine. + * + * This interface allows consumers to implement their own, + * problem-specific functions. + * These functions can be called from triggers, too. + * */ -[scriptable, uuid(898d4189-7012-4ae9-a2af-435491cfa114)] +[scriptable, uuid(9ff02465-21cb-49f3-b975-7d5b38ceec73)] interface mozIStorageFunction : nsISupports { /** * onFunctionCall is called when execution of a custom - * function should occur. There are no return values. + * function should occur. * * @param aNumArguments The number of arguments * @param aFunctionArguments The arguments passed in to the function + * + * @returns any value as Variant type. */ - void onFunctionCall(in mozIStorageValueArray aFunctionArguments); + nsIVariant onFunctionCall(in mozIStorageValueArray aFunctionArguments); }; diff --git a/storage/public/mozIStorageProgressHandler.idl b/storage/public/mozIStorageProgressHandler.idl new file mode 100644 index 00000000000..6048182d89d --- /dev/null +++ b/storage/public/mozIStorageProgressHandler.idl @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Storage Code. + * + * The Initial Developer of the Original Code is + * Lev Serebryakov + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +interface mozIStorageConnection; + +/** + * mozIProgressHandler is to be implemented by storage consumers that + * wish to receive callbacks during the request execution. + */ +[scriptable, uuid(a3a6fcd4-bf89-4208-a837-bf2a73afd30c)] +interface mozIStorageProgressHandler : nsISupports { + /** + * onProgress is invoked periodically during long running calls. + * + * @param aConnection connection, for which progress handler is + * invoked. + * + * @return true to abort request, false to continue work. + */ + + boolean onProgress(in mozIStorageConnection aConnection); +}; diff --git a/storage/src/mozStorageConnection.cpp b/storage/src/mozStorageConnection.cpp index d73dc0bcc79..e0a42ccd97e 100644 --- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -24,6 +24,7 @@ * Vladimir Vukicevic * Brett Wilson * Shawn Wilsher + * Lev Serebryakov * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -43,8 +44,12 @@ #include "nsError.h" #include "nsIMutableArray.h" +#include "nsHashSets.h" +#include "nsAutoPtr.h" #include "nsIFile.h" +#include "nsIVariant.h" +#include "mozIStorageAggregateFunction.h" #include "mozIStorageFunction.h" #include "mozStorageConnection.h" @@ -64,21 +69,26 @@ NS_IMPL_ISUPPORTS1(mozStorageConnection, mozIStorageConnection) mozStorageConnection::mozStorageConnection(mozIStorageService* aService) : mDBConn(nsnull), mTransactionInProgress(PR_FALSE), + mProgressHandler(nsnull), mStorageService(aService) { - + mFunctions.Init(); } mozStorageConnection::~mozStorageConnection() { if (mDBConn) { + if (mProgressHandler) + sqlite3_progress_handler(mDBConn, 0, NULL, NULL); int srv = sqlite3_close (mDBConn); - if (srv != SQLITE_OK) { - NS_WARNING("sqlite3_close failed. There are probably outstanding statements!"); - } + if (srv != SQLITE_OK) + NS_WARNING("sqlite3_close failed. There are probably outstanding statements!"); // make sure it really got closed ((mozStorageService*)(mStorageService.get()))->FlushAsyncIO(); + + // Release all functions + mFunctions.EnumerateRead(s_ReleaseFuncEnum, NULL); } } @@ -131,10 +141,10 @@ mozStorageConnection::Initialize(nsIFile *aDatabaseFile) sqlite3_stmt *stmt = nsnull; nsCString query("SELECT * FROM sqlite_master"); srv = sqlite3_prepare (mDBConn, query.get(), query.Length(), &stmt, nsnull); - + if (srv == SQLITE_OK) { srv = sqlite3_step(stmt); - + if (srv == SQLITE_DONE || srv == SQLITE_ROW) srv = SQLITE_OK; } else { @@ -143,7 +153,7 @@ mozStorageConnection::Initialize(nsIFile *aDatabaseFile) if (stmt != nsnull) sqlite3_finalize (stmt); - + if (srv != SQLITE_OK) { sqlite3_close (mDBConn); mDBConn = nsnull; @@ -154,9 +164,6 @@ mozStorageConnection::Initialize(nsIFile *aDatabaseFile) return ConvertResultCode(srv); } - mFunctions = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); - if (NS_FAILED(rv)) return rv; - return NS_OK; } @@ -178,7 +185,7 @@ mozStorageConnection::GetConnectionReady(PRBool *aConnectionReady) NS_IMETHODIMP mozStorageConnection::GetDatabaseFile(nsIFile **aFile) { - NS_ASSERTION(mDBConn, "connection not initialized"); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; NS_IF_ADDREF(*aFile = mDatabaseFile); @@ -188,7 +195,7 @@ mozStorageConnection::GetDatabaseFile(nsIFile **aFile) NS_IMETHODIMP mozStorageConnection::GetLastInsertRowID(PRInt64 *aLastInsertRowID) { - NS_ASSERTION(mDBConn, "connection not initialized"); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; sqlite_int64 id = sqlite3_last_insert_rowid(mDBConn); *aLastInsertRowID = id; @@ -199,7 +206,7 @@ mozStorageConnection::GetLastInsertRowID(PRInt64 *aLastInsertRowID) NS_IMETHODIMP mozStorageConnection::GetLastError(PRInt32 *aLastError) { - NS_ASSERTION(mDBConn, "connection not initialized"); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; *aLastError = sqlite3_errcode(mDBConn); @@ -209,7 +216,7 @@ mozStorageConnection::GetLastError(PRInt32 *aLastError) NS_IMETHODIMP mozStorageConnection::GetLastErrorString(nsACString& aLastErrorString) { - NS_ASSERTION(mDBConn, "connection not initialized"); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; const char *serr = sqlite3_errmsg(mDBConn); aLastErrorString.Assign(serr); @@ -220,7 +227,7 @@ mozStorageConnection::GetLastErrorString(nsACString& aLastErrorString) NS_IMETHODIMP mozStorageConnection::GetSchemaVersion(PRInt32 *version) { - NS_ASSERTION(mDBConn, "Connection not initialized"); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr stmt; nsresult rv = CreateStatement(NS_LITERAL_CSTRING( @@ -238,8 +245,8 @@ mozStorageConnection::GetSchemaVersion(PRInt32 *version) NS_IMETHODIMP mozStorageConnection::SetSchemaVersion(PRInt32 aVersion) { - NS_ASSERTION(mDBConn, "Connection not initialized"); - + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + nsCAutoString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = ")); stmt.AppendInt(aVersion); @@ -255,7 +262,7 @@ mozStorageConnection::CreateStatement(const nsACString& aSQLStatement, mozIStorageStatement **_retval) { NS_ENSURE_ARG_POINTER(_retval); - NS_ASSERTION(mDBConn, "connection not initialized"); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; mozStorageStatement *statement = new mozStorageStatement(); if (!statement) @@ -275,7 +282,7 @@ mozStorageConnection::CreateStatement(const nsACString& aSQLStatement, NS_IMETHODIMP mozStorageConnection::ExecuteSimpleSQL(const nsACString& aSQLStatement) { - NS_ENSURE_ARG_POINTER(mDBConn); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; int srv = sqlite3_exec (mDBConn, PromiseFlatCString(aSQLStatement).get(), NULL, NULL, NULL); @@ -290,7 +297,7 @@ mozStorageConnection::ExecuteSimpleSQL(const nsACString& aSQLStatement) NS_IMETHODIMP mozStorageConnection::TableExists(const nsACString& aSQLStatement, PRBool *_retval) { - NS_ENSURE_ARG_POINTER(mDBConn); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; nsCString query("SELECT name FROM sqlite_master WHERE type = 'table' AND name ='"); query.Append(aSQLStatement); @@ -325,7 +332,7 @@ mozStorageConnection::TableExists(const nsACString& aSQLStatement, PRBool *_retv NS_IMETHODIMP mozStorageConnection::IndexExists(const nsACString& aIndexName, PRBool* _retval) { - NS_ENSURE_ARG_POINTER(mDBConn); + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; nsCString query("SELECT name FROM sqlite_master WHERE type = 'index' AND name ='"); query.Append(aIndexName); @@ -451,6 +458,130 @@ mozStorageConnection::CreateTable(/*const nsID& aID,*/ ** Functions **/ +PLDHashOperator +mozStorageConnection::s_FindFuncEnum(const nsACString &aKey, + nsISupports* aData, + void* userArg) +{ + FindFuncEnumArgs *args = static_cast(userArg); + if ((void*)aData == args->mTarget) { + args->mFound = PR_TRUE; + return PL_DHASH_STOP; + } + return PL_DHASH_NEXT; +} + +PLDHashOperator +mozStorageConnection::s_ReleaseFuncEnum(const nsACString &aKey, + nsISupports* aData, + void* userArg) +{ + NS_RELEASE(aData); + return PL_DHASH_NEXT; +} + +PRBool +mozStorageConnection::FindFunctionByInstance(nsISupports *aInstance) +{ + FindFuncEnumArgs args = { aInstance, PR_FALSE }; + mFunctions.EnumerateRead(s_FindFuncEnum, &args); + return args.mFound; +} + +static nsresult +mozStorageVariantToSQLite3Result(sqlite3_context *ctx, + nsIVariant *var) +{ + nsresult rv; + PRUint16 dt; + // Allow to return NULL not wrapped to + // nsIVariant for speed + if (!var) { + sqlite3_result_null (ctx); + return NS_OK; + } + (void)var->GetDataType( &dt ); + switch (dt) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + { + PRInt32 v; + rv = var->GetAsInt32 (&v); + if (NS_FAILED(rv)) return rv; + sqlite3_result_int (ctx, v); + } + break; + case nsIDataType::VTYPE_UINT32: // Try to preserve full range + case nsIDataType::VTYPE_INT64: + // Data loss possible, but there is no unsigned types in SQLite + case nsIDataType::VTYPE_UINT64: + { + PRInt64 v; + rv = var->GetAsInt64 (&v); + if (NS_FAILED(rv)) return rv; + sqlite3_result_int64 (ctx, v); + } + break; + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + { + double v; + rv = var->GetAsDouble (&v); + if (NS_FAILED(rv)) return rv; + sqlite3_result_double (ctx, v); + } + break; + case nsIDataType::VTYPE_BOOL: + { + PRBool v; + rv = var->GetAsBool(&v); + if (NS_FAILED(rv)) return rv; + sqlite3_result_int (ctx, v ? 1 : 0); + } + break; + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_ASTRING: + { + nsAutoString v; + // GetAsAString does proper conversion to UCS2 + // from all string-like types. It can be used + // universally without problems. + rv = var->GetAsAString (v); + if (NS_FAILED(rv)) return rv; + sqlite3_result_text16 (ctx, + nsPromiseFlatString(v).get(), + v.Length() * 2, + SQLITE_TRANSIENT); + } + break; + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + sqlite3_result_null (ctx); + break; + // Maybe, it'll be possible to convert these + // in future too. + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } + return NS_OK; +} + static void mozStorageSqlFuncHelper (sqlite3_context *ctx, int argc, @@ -458,43 +589,227 @@ mozStorageSqlFuncHelper (sqlite3_context *ctx, { void *userData = sqlite3_user_data (ctx); // We don't want to QI here, because this will be called a -lot- - mozIStorageFunction *userFunction = NS_STATIC_CAST(mozIStorageFunction *, userData); + mozIStorageFunction *userFunction = + static_cast(userData); - nsCOMPtr ava = new mozStorageArgvValueArray (argc, argv); - nsresult rv = userFunction->OnFunctionCall (ava); + nsRefPtr ava = new mozStorageArgvValueArray (argc, argv); + if (!ava) + return; + nsCOMPtr retval; + nsresult rv = userFunction->OnFunctionCall (ava, getter_AddRefs(retval)); if (NS_FAILED(rv)) { NS_WARNING("mozIStorageConnection: User function returned error code!\n"); + sqlite3_result_error(ctx, + "User function returned error code", + -1); + return; + } + rv = mozStorageVariantToSQLite3Result(ctx,retval); + if (NS_FAILED(rv)) { + NS_WARNING("mozIStorageConnection: User function returned invalid data type!\n"); + sqlite3_result_error(ctx, + "User function returned invalid data type", + -1); } } NS_IMETHODIMP -mozStorageConnection::CreateFunction(const char *aFunctionName, +mozStorageConnection::CreateFunction(const nsACString &aFunctionName, PRInt32 aNumArguments, mozIStorageFunction *aFunction) { + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + // do we already have this function defined? - // XXX check for name as well - PRUint32 idx; - nsresult rv = mFunctions->IndexOf (0, aFunction, &idx); - if (rv != NS_ERROR_FAILURE) { - // already exists - return NS_ERROR_FAILURE; - } + // Check for name only because simple function can + // be defined multiple times with different names (aliases). + NS_ENSURE_FALSE(mFunctions.Get (aFunctionName, NULL), NS_ERROR_FAILURE); int srv = sqlite3_create_function (mDBConn, - aFunctionName, + nsPromiseFlatCString(aFunctionName).get(), aNumArguments, SQLITE_ANY, aFunction, mozStorageSqlFuncHelper, - nsnull, - nsnull); + NULL, + NULL); if (srv != SQLITE_OK) { HandleSqliteError(nsnull); return ConvertResultCode(srv); } - return mFunctions->AppendElement(aFunction, PR_FALSE); + if (mFunctions.Put (aFunctionName, aFunction)) { + // We hold function object -- add ref to it + NS_ADDREF(aFunction); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; +} + +static void +mozStorageSqlAggrFuncStepHelper (sqlite3_context *ctx, + int argc, + sqlite3_value **argv) +{ + void *userData = sqlite3_user_data (ctx); + // We don't want to QI here, because this will be called a -lot- + mozIStorageAggregateFunction *userFunction = + static_cast(userData); + + nsRefPtr ava = + new mozStorageArgvValueArray (argc, argv); + if (!ava) + return; + nsresult rv = userFunction->OnStep(ava); + if (NS_FAILED(rv)) + NS_WARNING("mozIStorageConnection: User aggregate step function returned error code!\n"); +} + +static void +mozStorageSqlAggrFuncFinalHelper (sqlite3_context *ctx) +{ + void *userData = sqlite3_user_data (ctx); + // We don't want to QI here, because this will be called a -lot- + mozIStorageAggregateFunction *userFunction = + static_cast(userData); + + nsRefPtr retval; + nsresult rv = userFunction->OnFinal (getter_AddRefs(retval)); + if (NS_FAILED(rv)) { + NS_WARNING("mozIStorageConnection: User aggregate final function returned error code!\n"); + sqlite3_result_error(ctx, + "User aggregate final function returned error code", + -1); + return; + } + rv = mozStorageVariantToSQLite3Result(ctx,retval); + if (NS_FAILED(rv)) { + NS_WARNING("mozIStorageConnection: User aggregate final function returned invalid data type!\n"); + sqlite3_result_error(ctx, + "User aggregate final function returned invalid data type", + -1); + } +} + +NS_IMETHODIMP +mozStorageConnection::CreateAggregateFunction(const nsACString &aFunctionName, + PRInt32 aNumArguments, + mozIStorageAggregateFunction *aFunction) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // do we already have this function defined? + // Check for name. + NS_ENSURE_FALSE(mFunctions.Get (aFunctionName, NULL), NS_ERROR_FAILURE); + + // Aggregate functions are stateful, so we cannot have + // aliases for them. + // Enumerate all functions and determine if this one is already + // implemented + NS_ENSURE_FALSE(FindFunctionByInstance (aFunction), NS_ERROR_FAILURE); + + int srv = sqlite3_create_function (mDBConn, + nsPromiseFlatCString(aFunctionName).get(), + aNumArguments, + SQLITE_ANY, + aFunction, + NULL, + mozStorageSqlAggrFuncStepHelper, + mozStorageSqlAggrFuncFinalHelper); + if (srv != SQLITE_OK) { + HandleSqliteError(nsnull); + return ConvertResultCode(srv); + } + + if (mFunctions.Put (aFunctionName, aFunction)) { + // We hold function object -- add ref to it + NS_ADDREF(aFunction); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +mozStorageConnection::RemoveFunction(const nsACString &aFunctionName) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + nsISupports *func; + + NS_ENSURE_TRUE(mFunctions.Get (aFunctionName, &func), NS_ERROR_FAILURE); + + int srv = sqlite3_create_function (mDBConn, + nsPromiseFlatCString(aFunctionName).get(), + 0, + SQLITE_ANY, + NULL, + NULL, + NULL, + NULL); + if (srv != SQLITE_OK) { + HandleSqliteError(nsnull); + return ConvertResultCode(srv); + } + + mFunctions.Remove (aFunctionName); + + // We don't hold function object anymore -- remove ref to it + NS_RELEASE(func); + + return NS_OK; +} + +int +mozStorageConnection::s_ProgressHelper(void *arg) +{ + mozStorageConnection *_this = static_cast(arg); + return _this->ProgressHandler(); +} + +NS_IMETHODIMP +mozStorageConnection::SetProgressHandler(PRInt32 aGranularity, + mozIStorageProgressHandler *aHandler, + mozIStorageProgressHandler **aOldHandler) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // Return previous one + NS_IF_ADDREF(*aOldHandler = mProgressHandler); + + if (!aHandler || aGranularity <= 0) { + aHandler = nsnull; + aGranularity = 0; + } + mProgressHandler = aHandler; + sqlite3_progress_handler (mDBConn, aGranularity, s_ProgressHelper, this); + + return NS_OK; +} + +NS_IMETHODIMP +mozStorageConnection::RemoveProgressHandler(mozIStorageProgressHandler **aOldHandler) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + // Return previous one + NS_IF_ADDREF(*aOldHandler = mProgressHandler); + + mProgressHandler = nsnull; + sqlite3_progress_handler (mDBConn, 0, NULL, NULL); + + return NS_OK; +} + +int +mozStorageConnection::ProgressHandler() +{ + if (mProgressHandler) { + PRBool res; + nsresult rv = mProgressHandler->OnProgress(this, &res); + if (NS_FAILED(rv)) return 0; // Don't break request + return res ? 1 : 0; + } + return 0; } /** diff --git a/storage/src/mozStorageConnection.h b/storage/src/mozStorageConnection.h index c52097bc05f..3666845e25d 100644 --- a/storage/src/mozStorageConnection.h +++ b/storage/src/mozStorageConnection.h @@ -21,6 +21,7 @@ * * Contributor(s): * Vladimir Vukicevic + * Lev Serebryakov * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -42,6 +43,8 @@ #include "nsCOMPtr.h" #include "nsString.h" +#include "nsDataHashtable.h" +#include "mozIStorageProgressHandler.h" #include "mozIStorageConnection.h" #include "nsIMutableArray.h" @@ -70,13 +73,31 @@ private: ~mozStorageConnection(); protected: + struct FindFuncEnumArgs { + nsISupports *mTarget; + PRBool mFound; + }; + void HandleSqliteError(const char *aSqlStatement); + static PLDHashOperator s_FindFuncEnum(const nsACString &aKey, + nsISupports* aData, void* userArg); + static PLDHashOperator s_ReleaseFuncEnum(const nsACString &aKey, + nsISupports* aData, void* userArg); + PRBool FindFunctionByInstance(nsISupports *aInstance); + + static int s_ProgressHelper(void *arg); + // Generic progress handler + // Dispatch call to registered progress handler, + // if there is one. Do nothing in other cases. + int ProgressHandler(); sqlite3 *mDBConn; nsCOMPtr mDatabaseFile; PRBool mTransactionInProgress; - nsCOMPtr mFunctions; + nsDataHashtable mFunctions; + + nsCOMPtr mProgressHandler; // This isn't accessed but is used to make sure that the connections do // not outlive the service. The service, for example, owns certain locks diff --git a/storage/test/storage1.cpp b/storage/test/storage1.cpp index 6c7009baa9c..c390b9e5f88 100644 --- a/storage/test/storage1.cpp +++ b/storage/test/storage1.cpp @@ -5,19 +5,27 @@ #include "nsIComponentManager.h" #include "nsISimpleEnumerator.h" #include "nsIServiceManager.h" +#include "nsILocalFile.h" +#include "nsIVariant.h" +#include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" #include "nsCOMPtr.h" #include "nsStringAPI.h" -#include "nsILocalFile.h" #include "mozIStorageService.h" #include "mozIStorageConnection.h" #include "mozIStorageValueArray.h" #include "mozIStorageStatement.h" #include "mozIStorageFunction.h" +#include "mozIStorageAggregateFunction.h" +#include "mozIStorageProgressHandler.h" #include "mozStorageCID.h" +#include "nsXPCOMCID.h" + #define TEST_CHECK_ERROR(rv) \ do { if (NS_FAILED(rv)) { \ dbConn->GetLastError(&gerr); \ @@ -26,11 +34,7 @@ return 0; \ } } while (0) -#ifdef XP_UNIX -#define TEST_DB NS_LITERAL_CSTRING("/tmp/foo.sdb") -#else #define TEST_DB NS_LITERAL_CSTRING("foo.sdb") -#endif int gerr; nsCString gerrstr; @@ -45,9 +49,71 @@ public: NS_IMPL_ISUPPORTS1(TestFunc, mozIStorageFunction) NS_IMETHODIMP -TestFunc::OnFunctionCall (mozIStorageValueArray *sva) +TestFunc::OnFunctionCall (mozIStorageValueArray *sva, nsIVariant **_retval) { + nsCOMPtr outVar; + fprintf (stderr, "* function call!\n"); + + outVar = do_CreateInstance(NS_VARIANT_CONTRACTID); + if(!outVar) + return NS_ERROR_FAILURE; + outVar->SetAsInt32(0xDEADBEEF); + NS_ADDREF(*_retval = outVar); + return NS_OK; +} + +class TestAggregateFunc : public mozIStorageAggregateFunction { +private: + PRInt32 mCalls; +public: + TestAggregateFunc() : mCalls(0) { } + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGEAGGREGATEFUNCTION +}; + +NS_IMPL_ISUPPORTS1(TestAggregateFunc, mozIStorageAggregateFunction) + +NS_IMETHODIMP +TestAggregateFunc::OnStep (mozIStorageValueArray *sva) +{ + ++mCalls; + fprintf (stderr, "* aggregate step %d!\n", mCalls); + return NS_OK; +} + +NS_IMETHODIMP +TestAggregateFunc::OnFinal (nsIVariant **_retval) +{ + nsCOMPtr outVar; + + fprintf (stderr, "* aggregate result %d!\n", - mCalls * mCalls); + + outVar = do_CreateInstance(NS_VARIANT_CONTRACTID); + if(!outVar) + return NS_ERROR_FAILURE; + outVar->SetAsInt32(-mCalls * mCalls); + NS_ADDREF(*_retval = outVar); + return NS_OK; +} + +class TestProgressHandler : public mozIStorageProgressHandler { +private: + PRUint32 mTicks; +public: + TestProgressHandler() : mTicks(0) { } + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGEPROGRESSHANDLER + PRUint32 getTicks() const { return mTicks; } +}; + +NS_IMPL_ISUPPORTS1(TestProgressHandler, mozIStorageProgressHandler) + +NS_IMETHODIMP +TestProgressHandler::OnProgress (mozIStorageConnection *aConnection, PRBool *_retval) +{ + ++mTicks; + *_retval = PR_FALSE; return NS_OK; } @@ -55,6 +121,8 @@ int main (int argc, char **argv) { nsresult rv; + TestFunc *tf; + TestAggregateFunc *taf; NS_InitXPCOM2(nsnull, nsnull, nsnull); @@ -62,20 +130,48 @@ main (int argc, char **argv) dbSrv = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr tmpDir; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = tmpDir->AppendNative(TEST_DB); + NS_ENSURE_SUCCESS(rv, rv); + + nsCAutoString tmpPath; + rv = tmpDir->GetNativePath(tmpPath); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr f; - rv = NS_NewNativeLocalFile (TEST_DB, PR_FALSE, getter_AddRefs(f)); + rv = NS_NewNativeLocalFile (tmpPath, PR_FALSE, getter_AddRefs(f)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr dbConn; rv = dbSrv->OpenDatabase(f, getter_AddRefs(dbConn)); NS_ENSURE_SUCCESS(rv, rv); - rv = dbConn->CreateFunction("x_test", -1, new TestFunc()); + tf = new TestFunc(); + rv = dbConn->CreateFunction(NS_LITERAL_CSTRING("x_test"), -1, tf); NS_ENSURE_SUCCESS(rv, rv); + // Must be error: function exists + rv = dbConn->CreateFunction(NS_LITERAL_CSTRING("x_test"), -1, tf); + NS_ENSURE_TRUE((rv == NS_ERROR_FAILURE), NS_ERROR_FAILURE); + rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("SELECT x_test(1)")); NS_ENSURE_SUCCESS(rv, rv); + taf = new TestAggregateFunc(); + rv = dbConn->CreateAggregateFunction(NS_LITERAL_CSTRING("x_aggr"), -1, taf); + NS_ENSURE_SUCCESS(rv, rv); + + // Must be error: function exists with this name + rv = dbConn->CreateAggregateFunction(NS_LITERAL_CSTRING("x_aggr"), -1, taf); + NS_ENSURE_TRUE((rv == NS_ERROR_FAILURE), NS_ERROR_FAILURE); + + // Must be error: function exists with this implementation + rv = dbConn->CreateAggregateFunction(NS_LITERAL_CSTRING("x_aggr2"), -1, taf); + NS_ENSURE_TRUE((rv == NS_ERROR_FAILURE), NS_ERROR_FAILURE); + rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE foo")); // TEST_CHECK_ERROR(rv); @@ -90,6 +186,10 @@ main (int argc, char **argv) rv = dbConn->CreateStatement (NS_LITERAL_CSTRING("SELECT i FROM foo"), getter_AddRefs(dbFooSelectStatement)); TEST_CHECK_ERROR(rv); + nsCOMPtr dbAggrSelectStatement; + rv = dbConn->CreateStatement (NS_LITERAL_CSTRING("SELECT x_aggr(i) FROM foo"), getter_AddRefs(dbAggrSelectStatement)); + TEST_CHECK_ERROR(rv); + for (int i = 0; i < 10; i++) { rv = dbFooInsertStatement->BindInt32Parameter (0, i); TEST_CHECK_ERROR(rv); @@ -106,11 +206,39 @@ main (int argc, char **argv) while ((dbFooSelectStatement->ExecuteStep(&hasMore) == NS_OK) && hasMore) { PRUint32 len; - + dbRow->GetNumEntries (&len); fprintf (stderr, "Row[length %d]: %d '%s'\n", len, dbRow->AsInt32(0), dbRow->AsSharedUTF8String(0, 0)); } TEST_CHECK_ERROR(rv); fprintf (stderr, "Done. %d 0x%08x %p\n", rv, rv, dbRow.get()); + + dbRow = do_QueryInterface(dbAggrSelectStatement); + hasMore = PR_FALSE; + + while ((dbAggrSelectStatement->ExecuteStep(&hasMore) == NS_OK) && hasMore) + { + PRUint32 len; + dbRow->GetNumEntries (&len); + fprintf (stderr, "Row[length %d]: %d '%s'\n", len, dbRow->AsInt32(0), dbRow->AsSharedUTF8String(0, 0)); + } + + TEST_CHECK_ERROR(rv); + fprintf (stderr, "Done. %d 0x%08x %p\n", rv, rv, dbRow.get()); + + TestProgressHandler *tph = new TestProgressHandler(); + nsCOMPtr oldHandler; + rv = dbConn->SetProgressHandler(1, tph, getter_AddRefs(oldHandler)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr dbSortSelectStatement; + rv = dbConn->CreateStatement (NS_LITERAL_CSTRING("SELECT i FROM foo ORDER BY i DESC"), getter_AddRefs(dbSortSelectStatement)); + TEST_CHECK_ERROR(rv); + + rv = dbSortSelectStatement->ExecuteStep(&hasMore); + TEST_CHECK_ERROR(rv); + fprintf (stderr, "Statement execution takes %d ticks\n", tph->getTicks()); + + return 0; } diff --git a/storage/test/unit/head_storage.js b/storage/test/unit/head_storage.js index f79b6ba3cd1..fe60d31af80 100644 --- a/storage/test/unit/head_storage.js +++ b/storage/test/unit/head_storage.js @@ -65,7 +65,7 @@ function getService() var gDBConn = null; function getOpenedDatabase() { - return gDBConn ? gDBConn : getService().openDatabase(getTestDB()); + return gDBConn ? gDBConn : gDBConn = getService().openDatabase(getTestDB()); } function createStatement(aSQL) diff --git a/storage/test/unit/test_storage_aggregates.js b/storage/test/unit/test_storage_aggregates.js new file mode 100644 index 00000000000..5ca2515895b --- /dev/null +++ b/storage/test/unit/test_storage_aggregates.js @@ -0,0 +1,143 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Storage Test Code. + * + * The Initial Developer of the Original Code is + * Lev Serebryakov + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// This file tests the custom aggregate functions + +var testNums = [1, 2, 3, 4]; + +function setup() +{ + getOpenedDatabase().createTable("function_tests", "id INTEGER PRIMARY KEY"); + + var stmt = createStatement("INSERT INTO function_tests (id) VALUES(?1)"); + for(var i = 0; i < testNums.length; ++i) { + stmt.bindInt32Parameter(0, testNums[i]); + stmt.execute(); + } + stmt.reset(); +} + +var testSquareAndSumFunction = { + calls: 0, + _sas: 0, + + reset: function() { + this.calls = 0; + this._sas = 0; + }, + + onStep: function(val) { + ++this.calls; + this._sas += val.getInt32(0) * val.getInt32(0); + }, + + onFinal: function() { + var retval = this._sas; + this._sas = 0; // Prepare for next group + return retval; + } +}; + +function test_aggregate_registration() +{ + var msc = getOpenedDatabase(); + msc.createAggregateFunction("test_sas_aggr", 1, testSquareAndSumFunction); +} + +function test_aggregate_no_double_registration() +{ + var msc = getOpenedDatabase(); + try { + msc.createAggregateFunction("test_sas_aggr", 2, testSquareAndSumFunction); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + } +} + +function test_aggregate_removal() +{ + var msc = getOpenedDatabase(); + msc.removeFunction("test_sas_aggr"); + // Should be Ok now + msc.createAggregateFunction("test_sas_aggr", 1, testSquareAndSumFunction); +} + +function test_aggregate_no_aliases() +{ + var msc = getOpenedDatabase(); + try { + msc.createAggregateFunction("test_sas_aggr2", 1, testSquareAndSumFunction); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + } +} + +function test_aggregate_call() +{ + var stmt = createStatement("SELECT test_sas_aggr(id) FROM function_tests"); + while(stmt.executeStep()); + do_check_eq(testNums.length, testSquareAndSumFunction.calls); + testSquareAndSumFunction.reset(); +} + +function test_aggregate_result() +{ + var sas = 0; + for(var i = 0; i < testNums.length; ++i) { + sas += testNums[i] * testNums[i]; + } + var stmt = createStatement("SELECT test_sas_aggr(id) FROM function_tests"); + stmt.executeStep(); + do_check_eq(sas, stmt.getInt32(0)); + testSquareAndSumFunction.reset(); +} + +var tests = [test_aggregate_registration, test_aggregate_no_double_registration, + test_aggregate_removal, test_aggregate_no_aliases, test_aggregate_call, + test_aggregate_result]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} diff --git a/storage/test/unit/test_storage_function.js b/storage/test/unit/test_storage_function.js new file mode 100644 index 00000000000..1395205ddc1 --- /dev/null +++ b/storage/test/unit/test_storage_function.js @@ -0,0 +1,122 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Storage Test Code. + * + * The Initial Developer of the Original Code is + * Lev Serebryakov + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// This file tests the custom functions + +var testNums = [1, 2, 3, 4]; + +function setup() +{ + getOpenedDatabase().createTable("function_tests", "id INTEGER PRIMARY KEY"); + + var stmt = createStatement("INSERT INTO function_tests (id) VALUES(?1)"); + for(var i = 0; i < testNums.length; ++i) { + stmt.bindInt32Parameter(0, testNums[i]); + stmt.execute(); + } + stmt.reset(); +} + +var testSquareFunction = { + calls: 0, + + onFunctionCall: function(val) { + ++this.calls; + return val.getInt32(0) * val.getInt32(0); + } +}; + +function test_function_registration() +{ + var msc = getOpenedDatabase(); + msc.createFunction("test_square", 1, testSquareFunction); +} + +function test_function_no_double_registration() +{ + var msc = getOpenedDatabase(); + try { + msc.createFunction("test_square", 2, testSquareFunction); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + } +} + +function test_function_removal() +{ + var msc = getOpenedDatabase(); + msc.removeFunction("test_square"); + // Should be Ok now + msc.createFunction("test_square", 1, testSquareFunction); +} + +function test_function_aliases() +{ + var msc = getOpenedDatabase(); + msc.createFunction("test_square2", 1, testSquareFunction); +} + +function test_function_call() +{ + var stmt = createStatement("SELECT test_square(id) FROM function_tests"); + while(stmt.executeStep()); + do_check_eq(testNums.length, testSquareFunction.calls); + testSquareFunction.calls = 0; +} + +function test_function_result() +{ + var stmt = createStatement("SELECT test_square(42) FROM function_tests"); + stmt.executeStep(); + do_check_eq(42*42, stmt.getInt32(0)); + testSquareFunction.calls = 0; +} + +var tests = [test_function_registration, test_function_no_double_registration, + test_function_removal, test_function_aliases, test_function_call, + test_function_result]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} diff --git a/storage/test/unit/test_storage_progresshandler.js b/storage/test/unit/test_storage_progresshandler.js new file mode 100644 index 00000000000..74e3f4d2114 --- /dev/null +++ b/storage/test/unit/test_storage_progresshandler.js @@ -0,0 +1,128 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Storage Test Code. + * + * The Initial Developer of the Original Code is + * Lev Serebryakov + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// This file tests the custom progress handlers + +function setup() +{ + var msc = getOpenedDatabase(); + msc.createTable("handler_tests", "id INTEGER PRIMARY KEY, num INTEGER"); + msc.beginTransaction(); + + var stmt = createStatement("INSERT INTO handler_tests (id, num) VALUES(?1, ?2)"); + for(var i = 0; i < 100; ++i) { + stmt.bindInt32Parameter(0, i); + stmt.bindInt32Parameter(1, Math.floor(Math.random()*1000)); + stmt.execute(); + } + stmt.reset(); + msc.commitTransaction(); +} + +var testProgressHandler = { + calls: 0, + abort: false, + + onProgress: function(comm) { + ++this.calls; + return this.abort; + } +}; + +function test_handler_registration() +{ + var msc = getOpenedDatabase(); + msc.setProgressHandler(10, testProgressHandler); +} + +function test_handler_return() +{ + var msc = getOpenedDatabase(); + var oldH = msc.setProgressHandler(5, testProgressHandler); + do_check_true(oldH instanceof Ci.mozIStorageProgressHandler); +} + +function test_handler_removal() +{ + var msc = getOpenedDatabase(); + msc.removeProgressHandler(); + var oldH = msc.removeProgressHandler(); + do_check_eq(oldH, null); +} + +function test_handler_call() +{ + var msc = getOpenedDatabase(); + msc.setProgressHandler(50, testProgressHandler); + // Some long-executing request + var stmt = createStatement( + "SELECT SUM(t1.num * t2.num) FROM handler_tests AS t1, handler_tests AS t2"); + while(stmt.executeStep()); + do_check_true(testProgressHandler.calls > 0); +} + +function test_handler_abort() +{ + var msc = getOpenedDatabase(); + testProgressHandler.abort = true; + msc.setProgressHandler(50, testProgressHandler); + // Some long-executing request + var stmt = createStatement( + "SELECT SUM(t1.num * t2.num) FROM handler_tests AS t1, handler_tests AS t2"); + try { + while(stmt.executeStep()); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + // Magic value: callback abort + do_check_eq(msc.lastError, 4); + } +} + +var tests = [test_handler_registration, test_handler_return, + test_handler_removal, test_handler_call, + test_handler_abort]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +}