Bug 333848 - add full-featured user-defined functions and progress handlers to storage. Patch by Lev Serebryakov <blacklion@gmail.com>. r=sdwilsh
authorsdwilsh@shawnwilsher.com
Sat, 07 Jul 2007 14:14:51 -0700
changeset 3228 8b56964ced65686ea612a025ccdedd3abc75ab95
parent 3227 827b05375e022a58405f569a8e3feac5c03bd072
child 3229 c25e726f9e644af973180fddc3900542cf207974
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssdwilsh
bugs333848
milestone1.9a7pre
Bug 333848 - add full-featured user-defined functions and progress handlers to storage. Patch by Lev Serebryakov <blacklion@gmail.com>. r=sdwilsh
storage/public/Makefile.in
storage/public/mozIStorageAggregateFunction.idl
storage/public/mozIStorageConnection.idl
storage/public/mozIStorageFunction.idl
storage/public/mozIStorageProgressHandler.idl
storage/src/mozStorageConnection.cpp
storage/src/mozStorageConnection.h
storage/test/storage1.cpp
storage/test/unit/head_storage.js
storage/test/unit/test_storage_aggregates.js
storage/test/unit/test_storage_function.js
storage/test/unit/test_storage_progresshandler.js
--- a/storage/public/Makefile.in
+++ b/storage/public/Makefile.in
@@ -16,16 +16,17 @@
 #
 # The Initial Developer of the Original Code is
 #   Oracle Corporation
 # Portions created by the Initial Developer are Copyright (C) 2004
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+#   Lev Serebryakov <lev@serebryakov.spb.ru>
 #
 # 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"),
 # 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
@@ -45,17 +46,19 @@ include $(DEPTH)/config/autoconf.mk
 
 MODULE       = storage
 XPIDL_MODULE = storage
 GRE_MODULE   = 1
 
 XPIDLSRCS = \
 	mozIStorageService.idl \
 	mozIStorageConnection.idl \
+	mozIStorageAggregateFunction.idl \
 	mozIStorageFunction.idl \
+	mozIStorageProgressHandler.idl \
 	mozIStorageStatement.idl \
 	mozIStorageStatementWrapper.idl \
 	mozIStorageDataSet.idl \
 	mozIStorageValueArray.idl \
 	$(NULL)
 
 EXPORTS = \
 	mozStorageHelper.h \
new file mode 100644
--- /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 <lev@serebryakov.spb.ru>
+ * 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();
+};
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -18,16 +18,17 @@
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  *   Brett Wilson <brettw@gmail.com>
  *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Lev Serebryakov <lev@serebryakov.spb.ru>
  *
  * 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
@@ -35,28 +36,30 @@
  * 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 mozIStorageAggregateFunction;
 interface mozIStorageFunction;
+interface mozIStorageProgressHandler;
 interface mozIStorageStatement;
 interface nsIFile;
 
 /**
  * mozIStorageConnection represents a database connection attached to
  * a specific file or to the in-memory data storage.  It is the
  * primary interface for interacting with a database, including
  * 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
    */
 
   /**
    * whether the database is open or not
    */
@@ -191,21 +194,67 @@ interface mozIStorageConnection : nsISup
                    in string aTableSchema);
 
   /*
    * Functions
    */
 
   /**
    * 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
    */
 
   /**
    * Copies the current database file to the specified parent directory with the
    * specified file name.  If the parent directory is not specified, it places
    * the backup in the same directory as the current file.  This function
--- a/storage/public/mozIStorageFunction.idl
+++ b/storage/public/mozIStorageFunction.idl
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+ *   Lev Serebryakov <lev@serebryakov.spb.ru>
  *
  * 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
@@ -36,26 +37,37 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 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);
 };
new file mode 100644
--- /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 <lev@serebryakov.spb.ru>
+ * 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);
+};
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -19,16 +19,17 @@
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  *   Brett Wilson <brettw@gmail.com>
  *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Lev Serebryakov <lev@serebryakov.spb.ru>
  *
  * 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
@@ -38,18 +39,22 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <stdio.h>
 
 #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"
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
 #include "mozStorageValueArray.h"
 #include "mozStorage.h"
 
@@ -59,31 +64,36 @@
 #ifdef PR_LOGGING
 PRLogModuleInfo* gStorageLog = nsnull;
 #endif
 
 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);
     }
 }
 
 #ifdef PR_LOGGING
 void tracefunc (void *closure, const char *stmt)
 {
     PR_LOG(gStorageLog, PR_LOG_DEBUG, ("%s", stmt));
 }
@@ -126,42 +136,39 @@ mozStorageConnection::Initialize(nsIFile
 #endif
 
     /* Execute a dummy statement to force the db open, and to verify
      * whether it's valid or not
      */
     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 {
         stmt = nsnull;
     }
 
     if (stmt != nsnull)
         sqlite3_finalize (stmt);
- 
+
     if (srv != SQLITE_OK) {
         sqlite3_close (mDBConn);
         mDBConn = nsnull;
 
         // make sure it really got closed
         ((mozStorageService*)(mStorageService.get()))->FlushAsyncIO();
 
         return ConvertResultCode(srv);
     }
 
-    mFunctions = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
-    if (NS_FAILED(rv)) return rv;
-
     return NS_OK;
 }
 
 /*****************************************************************************
  ** mozIStorageConnection interface
  *****************************************************************************/
 
 /**
@@ -173,59 +180,59 @@ mozStorageConnection::GetConnectionReady
 {
     *aConnectionReady = (mDBConn != nsnull);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 mozStorageConnection::GetDatabaseFile(nsIFile **aFile)
 {
-    NS_ASSERTION(mDBConn, "connection not initialized");
+    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
     NS_IF_ADDREF(*aFile = mDatabaseFile);
 
     return NS_OK;
 }
 
 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;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 mozStorageConnection::GetLastError(PRInt32 *aLastError)
 {
-    NS_ASSERTION(mDBConn, "connection not initialized");
+    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
     *aLastError = sqlite3_errcode(mDBConn);
 
     return NS_OK;
 }
 
 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);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 mozStorageConnection::GetSchemaVersion(PRInt32 *version)
 {
-    NS_ASSERTION(mDBConn, "Connection not initialized");
+    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
     nsCOMPtr<mozIStorageStatement> stmt;
     nsresult rv = CreateStatement(NS_LITERAL_CSTRING(
         "PRAGMA user_version"), getter_AddRefs(stmt));
     if (NS_FAILED(rv)) return rv;
 
     *version = 0;
     PRBool hasResult;
@@ -233,34 +240,34 @@ mozStorageConnection::GetSchemaVersion(P
         *version = stmt->AsInt32(0);
 
     return NS_OK;
 }
 
 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);
 
     return ExecuteSimpleSQL(stmt);
 }
 
 /**
  ** Statements & Queries
  **/
 
 NS_IMETHODIMP
 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)
       return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(statement);
 
     nsresult rv = statement->Initialize (this, aSQLStatement);
     if (NS_FAILED(rv)) {
@@ -270,32 +277,32 @@ mozStorageConnection::CreateStatement(co
 
     *_retval = statement;
     return NS_OK;
 }
 
 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);
     if (srv != SQLITE_OK) {
         HandleSqliteError(nsPromiseFlatCString(aSQLStatement).get());
         return ConvertResultCode(srv);
     }
 
     return NS_OK;
 }
 
 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);
     query.AppendLiteral("'");
 
     sqlite3_stmt *stmt = nsnull;
     int srv = sqlite3_prepare (mDBConn, query.get(), query.Length(), &stmt, nsnull);
     if (srv != SQLITE_OK) {
@@ -320,17 +327,17 @@ mozStorageConnection::TableExists(const 
 
     *_retval = exists;
     return NS_OK;
 }
 
 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);
     query.AppendLiteral("'");
 
     sqlite3_stmt *stmt = nsnull;
     int srv = sqlite3_prepare(mDBConn, query.get(), query.Length(), &stmt, nsnull);
     if (srv != SQLITE_OK) {
@@ -446,60 +453,368 @@ mozStorageConnection::CreateTable(/*cons
 
     return ConvertResultCode(srv);
 }
 
 /**
  ** Functions
  **/
 
+PLDHashOperator
+mozStorageConnection::s_FindFuncEnum(const nsACString &aKey,
+                                     nsISupports* aData,
+                                     void* userArg)
+{
+    FindFuncEnumArgs *args = static_cast<FindFuncEnumArgs *>(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,
                          sqlite3_value **argv)
 {
     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<mozIStorageFunction *>(userData);
 
-    nsCOMPtr<mozStorageArgvValueArray> ava = new mozStorageArgvValueArray (argc, argv);
-    nsresult rv = userFunction->OnFunctionCall (ava);
+    nsRefPtr<mozStorageArgvValueArray> ava = new mozStorageArgvValueArray (argc, argv);
+    if (!ava)
+        return;
+    nsCOMPtr<nsIVariant> 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<mozIStorageAggregateFunction *>(userData);
+
+    nsRefPtr<mozStorageArgvValueArray> 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<mozIStorageAggregateFunction *>(userData);
+
+    nsRefPtr<nsIVariant> 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<mozStorageConnection *>(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;
 }
 
 /**
  ** Utilities
  **/
 
 NS_IMETHODIMP
 mozStorageConnection::BackupDB(const nsAString &aFileName,
--- a/storage/src/mozStorageConnection.h
+++ b/storage/src/mozStorageConnection.h
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
+ *   Lev Serebryakov <lev@serebryakov.spb.ru>
  *
  * 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
@@ -37,16 +38,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _MOZSTORAGECONNECTION_H_
 #define _MOZSTORAGECONNECTION_H_
 
 #include "nsCOMPtr.h"
 
 #include "nsString.h"
+#include "nsDataHashtable.h"
+#include "mozIStorageProgressHandler.h"
 #include "mozIStorageConnection.h"
 
 #include "nsIMutableArray.h"
 
 #include <sqlite3.h>
 
 class nsIFile;
 class mozIStorageService;
@@ -65,23 +68,41 @@ public:
 
     // fetch the native handle
     sqlite3 *GetNativeConnection() { return mDBConn; }
 
 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<nsIFile> mDatabaseFile;
     PRBool mTransactionInProgress;
 
-    nsCOMPtr<nsIMutableArray> mFunctions;
+    nsDataHashtable<nsCStringHashKey, nsISupports*> mFunctions;
+
+    nsCOMPtr<mozIStorageProgressHandler> 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
     // in mozStorageAsyncIO file that the connections depend on.
     nsCOMPtr<mozIStorageService> mStorageService;
 };
 
 #endif /* _MOZSTORAGECONNECTION_H_ */
--- a/storage/test/storage1.cpp
+++ b/storage/test/storage1.cpp
@@ -1,116 +1,244 @@
 
 #include <stdlib.h>
 #include <stdio.h>
 
 #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); \
             dbConn->GetLastErrorString(gerrstr); \
             fprintf (stderr, "Error: %d 0x%08x %s\n", gerr, gerr, gerrstr.get()); \
             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;
 
 class TestFunc : public mozIStorageFunction {
 public:
     TestFunc() { }
     NS_DECL_ISUPPORTS
     NS_DECL_MOZISTORAGEFUNCTION
 };
 
 NS_IMPL_ISUPPORTS1(TestFunc, mozIStorageFunction)
 
 NS_IMETHODIMP
-TestFunc::OnFunctionCall (mozIStorageValueArray *sva)
+TestFunc::OnFunctionCall (mozIStorageValueArray *sva, nsIVariant **_retval)
+{
+    nsCOMPtr<nsIWritableVariant> 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)
 {
-    fprintf (stderr, "* function call!\n");
+    ++mCalls;
+    fprintf (stderr, "* aggregate step %d!\n", mCalls);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+TestAggregateFunc::OnFinal (nsIVariant **_retval)
+{
+    nsCOMPtr<nsIWritableVariant> 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;
 }
 
 int
 main (int argc, char **argv)
 {
     nsresult rv;
+    TestFunc *tf;
+    TestAggregateFunc *taf;
 
     NS_InitXPCOM2(nsnull, nsnull, nsnull);
 
     nsCOMPtr<mozIStorageService> dbSrv;
     dbSrv = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    nsCOMPtr<nsIFile> 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<nsILocalFile> 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<mozIStorageConnection> 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);
 
     rv = dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE foo (i INTEGER)"));
     TEST_CHECK_ERROR(rv);
 
     nsCOMPtr<mozIStorageStatement> dbFooInsertStatement;
     rv = dbConn->CreateStatement (NS_LITERAL_CSTRING("INSERT INTO foo VALUES ( ?1 )"), getter_AddRefs(dbFooInsertStatement));
     TEST_CHECK_ERROR(rv);
 
     nsCOMPtr<mozIStorageStatement> dbFooSelectStatement;
     rv = dbConn->CreateStatement (NS_LITERAL_CSTRING("SELECT i FROM foo"), getter_AddRefs(dbFooSelectStatement));
     TEST_CHECK_ERROR(rv);
 
+    nsCOMPtr<mozIStorageStatement> 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);
 
         rv = dbFooInsertStatement->Execute ();
         TEST_CHECK_ERROR(rv);
     }
 
     fprintf (stderr, "10 values written to foo...\n");
 
     nsCOMPtr<mozIStorageValueArray> dbRow = do_QueryInterface(dbFooSelectStatement);
     PRBool hasMore = PR_FALSE;
 
     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<mozIStorageProgressHandler> oldHandler;
+    rv = dbConn->SetProgressHandler(1, tph, getter_AddRefs(oldHandler));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<mozIStorageStatement> 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;
 }
--- a/storage/test/unit/head_storage.js
+++ b/storage/test/unit/head_storage.js
@@ -60,17 +60,17 @@ function cleanup()
 function getService()
 {
   return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService);
 }
 
 var gDBConn = null;
 function getOpenedDatabase()
 {
-  return gDBConn ? gDBConn : getService().openDatabase(getTestDB());
+  return gDBConn ? gDBConn : gDBConn = getService().openDatabase(getTestDB());
 }
 
 function createStatement(aSQL)
 {
   return getOpenedDatabase().createStatement(aSQL);
 }
 
 cleanup();
new file mode 100644
--- /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 <lev@serebryakov.spb.ru>
+ * 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();
+}
new file mode 100644
--- /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 <lev@serebryakov.spb.ru>
+ * 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();
+}
new file mode 100644
--- /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 <lev@serebryakov.spb.ru>
+ * 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();
+}