RetroZilla/storage/src/mozStorageConnection.cpp
2015-10-20 23:03:22 -04:00

518 lines
14 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>
* Brett Wilson <brettw@gmail.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 "nsIFile.h"
#include "mozIStorageFunction.h"
#include "mozStorageConnection.h"
#include "mozStorageService.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(mozIStorageService* aService)
: mDBConn(nsnull), mTransactionInProgress(PR_FALSE),
mStorageService(aService)
{
}
mozStorageConnection::~mozStorageConnection()
{
if (mDBConn) {
int srv = sqlite3_close (mDBConn);
if (srv != SQLITE_OK) {
NS_WARNING("sqlite3_close failed. There are probably outstanding statements!");
}
// make sure it really got closed
((mozStorageService*)(mStorageService.get()))->FlushAsyncIO();
}
}
// convert a sqlite srv to an nsresult
static nsresult
ConvertResultCode (int srv)
{
switch (srv) {
case SQLITE_OK:
return NS_OK;
case SQLITE_CORRUPT:
case SQLITE_NOTADB:
return NS_ERROR_FILE_CORRUPTED;
case SQLITE_PERM:
case SQLITE_CANTOPEN:
return NS_ERROR_FILE_ACCESS_DENIED;
case SQLITE_BUSY:
return NS_ERROR_FILE_IS_LOCKED;
}
// generic error
return NS_ERROR_FAILURE;
}
#ifdef PR_LOGGING
void tracefunc (void *closure, const char *stmt)
{
PR_LOG(gStorageLog, PR_LOG_DEBUG, ("%s", 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!");
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, nsnull);
#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
*****************************************************************************/
/**
** Core status/initialization
**/
NS_IMETHODIMP
mozStorageConnection::GetConnectionReady(PRBool *aConnectionReady)
{
*aConnectionReady = (mDBConn != nsnull);
return NS_OK;
}
NS_IMETHODIMP
mozStorageConnection::GetDatabaseFile(nsIFile **aFile)
{
NS_ASSERTION(mDBConn, "connection not initialized");
NS_IF_ADDREF(*aFile = mDatabaseFile);
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 rv;
}
*_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 ConvertResultCode(srv);
}
return NS_OK;
}
NS_IMETHODIMP
mozStorageConnection::TableExists(const nsACString& aSQLStatement, PRBool *_retval)
{
NS_ENSURE_ARG_POINTER(mDBConn);
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) {
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 if (srv == SQLITE_ERROR) {
HandleSqliteError("TableExists finalize");
return NS_ERROR_FAILURE;
}
*_retval = exists;
return NS_OK;
}
NS_IMETHODIMP
mozStorageConnection::IndexExists(const nsACString& aIndexName, PRBool* _retval)
{
NS_ENSURE_ARG_POINTER(mDBConn);
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) {
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 if (srv == SQLITE_ERROR) {
HandleSqliteError("IndexExists finalize");
return NS_ERROR_FAILURE;
}
*_retval = exists;
return NS_OK;
}
/**
** Transactions
**/
NS_IMETHODIMP
mozStorageConnection::GetTransactionInProgress(PRBool *_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::BeginTransactionAs(PRInt32 aTransactionType)
{
if (mTransactionInProgress)
return NS_ERROR_FAILURE; // XXX error code
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()
{
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);
return ConvertResultCode(srv);
}
/**
** Functions
**/
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);
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 ConvertResultCode(srv);
}
rv = mFunctions->AppendElement (aFunction, PR_FALSE);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
/**
* Mozilla-specific sqlite function to preload the DB into the cache. See the
* IDL and sqlite3.h
*/
nsresult
mozStorageConnection::Preload()
{
int srv = sqlite3Preload(mDBConn);
return ConvertResultCode(srv);
}
/**
** 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
}