/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: sw=4 ts=4 sts=4
 * ***** 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 Oracle Corporation code.
 *
 * 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>
 *   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
 * 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 <stdio.h>

#include "nsError.h"
#include "nsIMutableArray.h"
#include "nsHashSets.h"
#include "nsAutoPtr.h"
#include "nsIFile.h"
#include "nsIVariant.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"

#include "mozIStorageAggregateFunction.h"
#include "mozIStorageFunction.h"

#include "mozStorageUnicodeFunctions.h"
#include "mozStorageConnection.h"
#include "mozStorageService.h"
#include "mozStorageStatement.h"
#include "mozStorageValueArray.h"
#include "mozStorage.h"

#include "prlog.h"
#include "prprf.h"

#ifdef PR_LOGGING
PRLogModuleInfo* gStorageLog = nsnull;
#endif

#define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"

NS_IMPL_THREADSAFE_ISUPPORTS1(mozStorageConnection, mozIStorageConnection)

mozStorageConnection::mozStorageConnection(mozIStorageService* aService) :
    mDBConn(nsnull)
,   mTransactionMutex(nsAutoLock::NewLock("TransactionMutex"))
,   mTransactionInProgress(PR_FALSE)
,   mFunctionsMutex(nsAutoLock::NewLock("FunctionsMutex"))
,   mProgressHandlerMutex(nsAutoLock::NewLock("ProgressHandlerMutex"))
,   mProgressHandler(nsnull)
,   mStorageService(aService)
{
    mFunctions.Init();
}

mozStorageConnection::~mozStorageConnection()
{
    (void)Close();
    nsAutoLock::DestroyLock(mTransactionMutex);
    nsAutoLock::DestroyLock(mFunctionsMutex);
    nsAutoLock::DestroyLock(mProgressHandlerMutex);
}

#ifdef PR_LOGGING
void tracefunc (void *closure, const char *stmt)
{
    PR_LOG(gStorageLog, PR_LOG_DEBUG, ("sqlite3_trace on %p for '%s'", closure,
                                       stmt));
}
#endif

/**
 * Actually creates the connection from the DB. Called by mozStorageService.
 * You can pass a NULL database file in to get an sqlite in-memory database.
 */
NS_IMETHODIMP
mozStorageConnection::Initialize(nsIFile *aDatabaseFile)
{
    NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
    NS_ENSURE_TRUE(mTransactionMutex, NS_ERROR_OUT_OF_MEMORY);
    NS_ENSURE_TRUE(mFunctionsMutex, NS_ERROR_OUT_OF_MEMORY);
    NS_ENSURE_TRUE(mProgressHandlerMutex, NS_ERROR_OUT_OF_MEMORY);

    int srv;
    nsresult rv;

    mDatabaseFile = aDatabaseFile;

    if (aDatabaseFile) {
        nsAutoString path;
        rv = aDatabaseFile->GetPath(path);
        NS_ENSURE_SUCCESS(rv, rv);

        srv = sqlite3_open (NS_ConvertUTF16toUTF8(path).get(), &mDBConn);
    } else {
        // in memory database requested, sqlite uses a magic file name
        srv = sqlite3_open (":memory:", &mDBConn);
    }
    if (srv != SQLITE_OK) {
        mDBConn = nsnull;
        return ConvertResultCode(srv);
    }

#ifdef PR_LOGGING
    if (! gStorageLog)
        gStorageLog = PR_NewLogModule("mozStorage");

    sqlite3_trace (mDBConn, tracefunc, this);

    nsCAutoString leafName(":memory");
    if (aDatabaseFile)
        (void)aDatabaseFile->GetNativeLeafName(leafName);
    PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s' (%p)",
                                        leafName.get(), this));
#endif

    // Hook up i18n functions
    srv = StorageUnicodeFunctions::RegisterFunctions(mDBConn);
    if (srv != SQLITE_OK) {
        mDBConn = nsnull;
        return ConvertResultCode(srv);
    }

    /* 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_v2(mDBConn, query.get(), query.Length(), &stmt, NULL);

    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;

        return ConvertResultCode(srv);
    }

    // Set the synchronous PRAGMA, according to the pref
    nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
    PRInt32 synchronous = 1; // Default to NORMAL if pref not set
    if (pref)
        (void)pref->GetIntPref(PREF_TS_SYNCHRONOUS, &synchronous);
    
    switch (synchronous) {
        case 2:
            (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
                "PRAGMA synchronous = FULL;"));
            break;
        case 0:
            (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
                "PRAGMA synchronous = OFF;"));
            break;
        case 1:
        default:
            (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
                "PRAGMA synchronous = NORMAL;"));
            break;
    }

    return NS_OK;
}

/*****************************************************************************
 ** mozIStorageConnection interface
 *****************************************************************************/

/**
 ** Core status/initialization
 **/

NS_IMETHODIMP
mozStorageConnection::Close()
{
    if (!mDBConn)
        return NS_ERROR_NOT_INITIALIZED;

#ifdef PR_LOGGING
    nsCAutoString leafName(":memory");
    if (mDatabaseFile)
        (void)mDatabaseFile->GetNativeLeafName(leafName);
    PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s'",
                                        leafName.get()));
#endif

    {
        nsAutoLock mutex(mProgressHandlerMutex);
        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!");

    mDBConn = NULL;
    return ConvertResultCode(srv);
}

NS_IMETHODIMP
mozStorageConnection::GetConnectionReady(PRBool *aConnectionReady)
{
    *aConnectionReady = (mDBConn != nsnull);
    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::GetDatabaseFile(nsIFile **aFile)
{
    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;

    NS_IF_ADDREF(*aFile = mDatabaseFile);

    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::GetLastInsertRowID(PRInt64 *aLastInsertRowID)
{
    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)
{
    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;

    *aLastError = sqlite3_errcode(mDBConn);

    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::GetLastErrorString(nsACString& aLastErrorString)
{
    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)
{
    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;
    if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult)
        *version = stmt->AsInt32(0);

    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::SetSchemaVersion(PRInt32 aVersion)
{
    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);
    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)) {
        NS_RELEASE(statement);
        return rv;
    }

    *_retval = statement;
    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::ExecuteSimpleSQL(const nsACString& aSQLStatement)
{
    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)
{
    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_v2(mDBConn, query.get(), query.Length(), &stmt,
                                 NULL);
    if (srv != SQLITE_OK) {
        HandleSqliteError(query.get());
        return ConvertResultCode(srv);
    }

    PRBool exists = PR_FALSE;

    srv = sqlite3_step(stmt);
    // we just care about the return value from step
    sqlite3_finalize(stmt);

    if (srv == SQLITE_ROW) {
        exists = PR_TRUE;
    } else if (srv == SQLITE_DONE) {
        exists = PR_FALSE;
    } else {
        HandleSqliteError("TableExists finalize");
        return ConvertResultCode(srv);
    }

    *_retval = exists;
    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::IndexExists(const nsACString& aIndexName, PRBool* _retval)
{
    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_v2(mDBConn, query.get(), query.Length(), &stmt,
                                 NULL);
    if (srv != SQLITE_OK) {
        HandleSqliteError(query.get());
        return ConvertResultCode(srv);
    }

    *_retval = PR_FALSE;

    srv = sqlite3_step(stmt);
    (void)sqlite3_finalize(stmt);

    if (srv == SQLITE_ROW) {
        *_retval = PR_TRUE;
    }

    return ConvertResultCode(srv);
}


/**
 ** Transactions
 **/

NS_IMETHODIMP
mozStorageConnection::GetTransactionInProgress(PRBool *_retval)
{
    nsAutoLock mutex(mTransactionMutex);
    *_retval = mTransactionInProgress;
    return NS_OK;
}

// XXX do we want to just store compiled statements for these?
NS_IMETHODIMP
mozStorageConnection::BeginTransaction()
{
    return BeginTransactionAs(mozIStorageConnection::TRANSACTION_DEFERRED);
}

NS_IMETHODIMP
mozStorageConnection::BeginTransactionAs(PRInt32 aTransactionType)
{
    nsAutoLock mutex(mTransactionMutex);
    if (mTransactionInProgress)
        return NS_ERROR_FAILURE;
    nsresult rv;
    switch(aTransactionType) {
        case TRANSACTION_DEFERRED:
            rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN DEFERRED"));
            break;
        case TRANSACTION_IMMEDIATE:
            rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN IMMEDIATE"));
            break;
        case TRANSACTION_EXCLUSIVE:
            rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN EXCLUSIVE"));
            break;
        default:
            return NS_ERROR_ILLEGAL_VALUE;
    }
    if (NS_SUCCEEDED(rv))
        mTransactionInProgress = PR_TRUE;
    return NS_OK;
}

NS_IMETHODIMP
mozStorageConnection::CommitTransaction()
{
    nsAutoLock mutex(mTransactionMutex);
    if (!mTransactionInProgress)
        return NS_ERROR_FAILURE;
    nsresult rv = ExecuteSimpleSQL (NS_LITERAL_CSTRING("COMMIT TRANSACTION"));
    // even if the commit fails, the transaction is aborted
    mTransactionInProgress = PR_FALSE;
    return rv;
}

NS_IMETHODIMP
mozStorageConnection::RollbackTransaction()
{
    nsAutoLock mutex(mTransactionMutex);
    if (!mTransactionInProgress)
        return NS_ERROR_FAILURE;
    nsresult rv = ExecuteSimpleSQL (NS_LITERAL_CSTRING("ROLLBACK TRANSACTION"));
    mTransactionInProgress = PR_FALSE;
    return rv;
}

/**
 ** Table creation
 **/

NS_IMETHODIMP
mozStorageConnection::CreateTable(/*const nsID& aID,*/
                                  const char *aTableName,
                                  const char *aTableSchema)
{
    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
    int srv;
    char *buf;

    buf = PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
    if (!buf)
        return NS_ERROR_OUT_OF_MEMORY;

    srv = sqlite3_exec (mDBConn, buf,
                        NULL, NULL, NULL);

    PR_smprintf_free(buf);

    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;
}

PRBool
mozStorageConnection::FindFunctionByInstance(nsISupports *aInstance)
{
    // The lock should already be held by calling functions
    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 =
        static_cast<mozIStorageFunction *>(userData);

    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 nsACString &aFunctionName,
                                     PRInt32 aNumArguments,
                                     mozIStorageFunction *aFunction)
{
    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;

    // do we already have this function defined?
    // Check for name only because simple function can
    // be defined multiple times with different names (aliases).
    nsAutoLock mutex(mFunctionsMutex);
    NS_ENSURE_FALSE(mFunctions.Get (aFunctionName, NULL), NS_ERROR_FAILURE);

    int srv = sqlite3_create_function (mDBConn,
                                       nsPromiseFlatCString(aFunctionName).get(),
                                       aNumArguments,
                                       SQLITE_ANY,
                                       aFunction,
                                       mozStorageSqlFuncHelper,
                                       NULL,
                                       NULL);
    if (srv != SQLITE_OK) {
        HandleSqliteError(nsnull);
        return ConvertResultCode(srv);
    }

    if (mFunctions.Put (aFunctionName, 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.
    nsAutoLock mutex(mFunctionsMutex);
    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)) {
        return NS_OK;
    }
    return NS_ERROR_OUT_OF_MEMORY;
}

NS_IMETHODIMP
mozStorageConnection::RemoveFunction(const nsACString &aFunctionName)
{
    if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;

    nsAutoLock mutex(mFunctionsMutex);
    NS_ENSURE_TRUE(mFunctions.Get (aFunctionName, NULL), 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);

    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
    nsAutoLock mutex(mProgressHandlerMutex);
    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
    nsAutoLock mutex(mProgressHandlerMutex);
    NS_IF_ADDREF(*aOldHandler = mProgressHandler);

    mProgressHandler = nsnull;
    sqlite3_progress_handler (mDBConn, 0, NULL, NULL);

    return NS_OK;
}

int
mozStorageConnection::ProgressHandler()
{
    nsAutoLock mutex(mProgressHandlerMutex);
    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;
}

/**
 ** Other bits
 **/
void
mozStorageConnection::HandleSqliteError(const char *aSqlStatement)
{
    // an error just occured!
#ifdef PR_LOGGING
    PR_LOG(gStorageLog, PR_LOG_DEBUG, ("Sqlite error: %d '%s'", sqlite3_errcode(mDBConn), sqlite3_errmsg(mDBConn)));
    if (aSqlStatement)
        PR_LOG(gStorageLog, PR_LOG_DEBUG, ("Statement was: %s", aSqlStatement));
#endif
}
