418 lines
11 KiB
C++
418 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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>
|
|
*
|
|
* 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 "nsArray.h"
|
|
#include "nsIFile.h"
|
|
|
|
#include "mozIStorageFunction.h"
|
|
|
|
#include "mozStorageConnection.h"
|
|
#include "mozStorageStatement.h"
|
|
#include "mozStorageValueArray.h"
|
|
|
|
#include "prlog.h"
|
|
#include "prprf.h"
|
|
|
|
#ifdef PR_LOGGING
|
|
PRLogModuleInfo* gStorageLog = nsnull;
|
|
#endif
|
|
|
|
NS_IMPL_ISUPPORTS1(mozStorageConnection, mozIStorageConnection)
|
|
|
|
mozStorageConnection::mozStorageConnection()
|
|
: mDBConn(nsnull), mTransactionInProgress(PR_FALSE)
|
|
{
|
|
|
|
}
|
|
|
|
mozStorageConnection::~mozStorageConnection()
|
|
{
|
|
if (mDBConn) {
|
|
int srv = sqlite3_close (mDBConn);
|
|
if (srv != SQLITE_OK) {
|
|
NS_WARNING("sqlite3_close failed. There are probably outstanding statements!");
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef PR_LOGGING
|
|
void tracefunc (void *closure, const char *stmt)
|
|
{
|
|
PR_LOG(gStorageLog, PR_LOG_DEBUG, ("%s", stmt));
|
|
}
|
|
#endif
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::Initialize(nsIFile *aDatabaseFile)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aDatabaseFile);
|
|
|
|
NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
|
|
|
|
int srv;
|
|
nsresult rv;
|
|
|
|
rv = aDatabaseFile->GetNativeLeafName(mDatabaseName);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCAutoString nativePath;
|
|
rv = aDatabaseFile->GetNativePath(nativePath);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
srv = sqlite3_open (nativePath.get(), &mDBConn);
|
|
if (srv != SQLITE_OK) {
|
|
mDBConn = nsnull;
|
|
return NS_ERROR_FAILURE; // XXX error code
|
|
}
|
|
|
|
#ifdef PR_LOGGING
|
|
if (! gStorageLog)
|
|
gStorageLog = PR_NewLogModule("mozStorage");
|
|
|
|
sqlite3_trace (mDBConn, tracefunc, nsnull);
|
|
#endif
|
|
|
|
rv = NS_NewArray(getter_AddRefs(mFunctions));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** mozIStorageConnection interface
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
** Core status/initialization
|
|
**/
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::GetConnectionReady(PRBool *aConnectionReady)
|
|
{
|
|
*aConnectionReady = (mDBConn != nsnull);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::GetDatabaseName(nsACString& aDatabaseName)
|
|
{
|
|
NS_ASSERTION(mDBConn, "connection not initialized");
|
|
|
|
aDatabaseName = mDatabaseName;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::GetLastInsertRowID(PRInt64 *aLastInsertRowID)
|
|
{
|
|
NS_ASSERTION(mDBConn, "connection 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");
|
|
|
|
*aLastError = sqlite3_errcode(mDBConn);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::GetLastErrorString(nsACString& aLastErrorString)
|
|
{
|
|
NS_ASSERTION(mDBConn, "connection not initialized");
|
|
|
|
const char *serr = sqlite3_errmsg(mDBConn);
|
|
aLastErrorString.Assign(serr);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
** Statements & Queries
|
|
**/
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::CreateStatement(const nsACString& aSQLStatement,
|
|
mozIStorageStatement **_retval)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
NS_ASSERTION(mDBConn, "connection not initialized");
|
|
|
|
mozStorageStatement *statement = new mozStorageStatement();
|
|
NS_ADDREF(statement);
|
|
|
|
nsresult rv = statement->Initialize (this, aSQLStatement);
|
|
if (NS_FAILED(rv)) {
|
|
NS_RELEASE(statement);
|
|
return NS_ERROR_FAILURE; // XXX error code
|
|
}
|
|
|
|
*_retval = statement;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::ExecuteSimpleSQL(const nsACString& aSQLStatement)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(mDBConn);
|
|
|
|
int srv = sqlite3_exec (mDBConn, PromiseFlatCString(aSQLStatement).get(),
|
|
NULL, NULL, NULL);
|
|
if (srv != SQLITE_OK) {
|
|
HandleSqliteError(nsPromiseFlatCString(aSQLStatement).get());
|
|
return NS_ERROR_FAILURE; // XXX error code
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
** Transactions
|
|
**/
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::GetTransactionInProgress(PRInt32 *_retval)
|
|
{
|
|
*_retval = mTransactionInProgress;
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX do we want to just store compiled statements for these?
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::BeginTransaction()
|
|
{
|
|
if (mTransactionInProgress)
|
|
return NS_ERROR_FAILURE; // XXX error code
|
|
nsresult rv = ExecuteSimpleSQL (NS_LITERAL_CSTRING("BEGIN TRANSACTION"));
|
|
if (NS_SUCCEEDED(rv))
|
|
mTransactionInProgress = PR_TRUE;
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::CommitTransaction()
|
|
{
|
|
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()
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
|
|
if (srv != SQLITE_OK) {
|
|
return NS_ERROR_FAILURE; // XXX SQL_ERROR_TABLE_EXISTS
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
** Functions and Triggers
|
|
**/
|
|
|
|
static void
|
|
mozStorageSqlFuncHelper (sqlite3_context *ctx,
|
|
int argc,
|
|
sqlite3_value **argv)
|
|
{
|
|
fprintf (stderr, "mozStorageSqlFuncHelper: %p %d %p\n", ctx, argc, 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);
|
|
|
|
nsCOMPtr<mozStorageArgvValueArray> ava = new mozStorageArgvValueArray (argc, argv);
|
|
nsresult rv = userFunction->OnFunctionCall (ava);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("mozIStorageConnection: User function returned error code!\n");
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::CreateFunction(const char *aFunctionName,
|
|
PRInt32 aNumArguments,
|
|
mozIStorageFunction *aFunction)
|
|
{
|
|
nsresult rv;
|
|
|
|
// do we already have this function defined?
|
|
// XXX check for name as well
|
|
PRUint32 idx;
|
|
rv = mFunctions->IndexOf (0, aFunction, &idx);
|
|
if (rv != NS_ERROR_FAILURE) {
|
|
// already exists
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
int srv = sqlite3_create_function (mDBConn,
|
|
aFunctionName,
|
|
aNumArguments,
|
|
SQLITE_ANY,
|
|
aFunction,
|
|
mozStorageSqlFuncHelper,
|
|
nsnull,
|
|
nsnull);
|
|
if (srv != SQLITE_OK) {
|
|
HandleSqliteError(nsnull);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
rv = mFunctions->AppendElement (aFunction, PR_FALSE);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::CreateTrigger(const char *aTriggerName,
|
|
PRInt32 aTriggerType,
|
|
const char *aTableName,
|
|
const char *aTriggerFunction,
|
|
const char *aParameters)
|
|
{
|
|
#if 0
|
|
nsresult rv;
|
|
|
|
/* We don't need to split this until we need to generate
|
|
* our own IPC trigger
|
|
*/
|
|
nsCStringArray *cstr = new nsCStringArray();
|
|
if (!cstr)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
rv = cstr->ParseString (aParameters, ",");
|
|
if (NS_FAILED(rv)) return rv;
|
|
#endif
|
|
|
|
char *event = nsnull;
|
|
if (aTriggerType == TRIGGER_EVENT_DELETE)
|
|
event = "DELETE";
|
|
else if (aTriggerType == TRIGGER_EVENT_INSERT)
|
|
event = "INSERT";
|
|
else if (aTriggerType == TRIGGER_EVENT_UPDATE)
|
|
event = "UPDATE";
|
|
else
|
|
return NS_ERROR_FAILURE;
|
|
|
|
char *sql = PR_sprintf_append
|
|
(nsnull,
|
|
"CREATE TEMPORARY TRIGGER %s AFTER %s ON %s FOR EACH ROW BEGIN SELECT %s(%s); END;",
|
|
aTriggerName,
|
|
event,
|
|
aTableName,
|
|
aTriggerFunction,
|
|
aParameters);
|
|
if (!sql)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
/* Now create the trigger */
|
|
int srv = sqlite3_exec (mDBConn,
|
|
sql,
|
|
nsnull,
|
|
nsnull,
|
|
nsnull);
|
|
if (srv != SQLITE_OK) {
|
|
HandleSqliteError(nsnull);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
mozStorageConnection::RemoveTrigger(const char *aTriggerName)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
/**
|
|
** 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
|
|
}
|