Back to index

lightning-sunbird  0.9+nobinonly
mozStorageConnection.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Oracle Corporation code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  *  Oracle Corporation
00019  * Portions created by the Initial Developer are Copyright (C) 2004
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
00024  *   Brett Wilson <brettw@gmail.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include <stdio.h>
00041 
00042 #include "nsError.h"
00043 #include "nsIFile.h"
00044 
00045 #include "mozIStorageFunction.h"
00046 
00047 #include "mozStorageConnection.h"
00048 #include "mozStorageService.h"
00049 #include "mozStorageStatement.h"
00050 #include "mozStorageValueArray.h"
00051 
00052 #include "prlog.h"
00053 #include "prprf.h"
00054 
00055 #ifdef PR_LOGGING
00056 PRLogModuleInfo* gStorageLog = nsnull;
00057 #endif
00058 
00059 NS_IMPL_ISUPPORTS1(mozStorageConnection, mozIStorageConnection)
00060 
00061 mozStorageConnection::mozStorageConnection(mozIStorageService* aService)
00062     : mDBConn(nsnull), mTransactionInProgress(PR_FALSE),
00063       mStorageService(aService)
00064 {
00065     
00066 }
00067 
00068 mozStorageConnection::~mozStorageConnection()
00069 {
00070     if (mDBConn) {
00071         int srv = sqlite3_close (mDBConn);
00072         if (srv != SQLITE_OK) {
00073             NS_WARNING("sqlite3_close failed.  There are probably outstanding statements!");
00074         }
00075 
00076         // make sure it really got closed
00077         ((mozStorageService*)(mStorageService.get()))->FlushAsyncIO();
00078     }
00079 }
00080 
00081 // convert a sqlite srv to an nsresult
00082 static nsresult
00083 ConvertResultCode (int srv)
00084 {
00085     switch (srv) {
00086         case SQLITE_OK:
00087             return NS_OK;
00088         case SQLITE_CORRUPT:
00089         case SQLITE_NOTADB:
00090             return NS_ERROR_FILE_CORRUPTED;
00091         case SQLITE_PERM:
00092         case SQLITE_CANTOPEN:
00093             return NS_ERROR_FILE_ACCESS_DENIED;
00094         case SQLITE_BUSY:
00095             return NS_ERROR_FILE_IS_LOCKED;
00096     }
00097 
00098     // generic error
00099     return NS_ERROR_FAILURE;
00100 }
00101 
00102 #ifdef PR_LOGGING
00103 void tracefunc (void *closure, const char *stmt)
00104 {
00105     PR_LOG(gStorageLog, PR_LOG_DEBUG, ("%s", stmt));
00106 }
00107 #endif
00108 
00113 NS_IMETHODIMP
00114 mozStorageConnection::Initialize(nsIFile *aDatabaseFile)
00115 {
00116     NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
00117 
00118     int srv;
00119     nsresult rv;
00120 
00121     mDatabaseFile = aDatabaseFile;
00122 
00123     if (aDatabaseFile) {
00124         nsAutoString path;
00125         rv = aDatabaseFile->GetPath(path);
00126         NS_ENSURE_SUCCESS(rv, rv);
00127 
00128         srv = sqlite3_open (NS_ConvertUTF16toUTF8(path).get(), &mDBConn);
00129     } else {
00130         // in memory database requested, sqlite uses a magic file name
00131         srv = sqlite3_open (":memory:", &mDBConn);
00132     }
00133     if (srv != SQLITE_OK) {
00134         mDBConn = nsnull;
00135         return ConvertResultCode(srv);
00136     }
00137 
00138 #ifdef PR_LOGGING
00139     if (! gStorageLog)
00140         gStorageLog = PR_NewLogModule("mozStorage");
00141 
00142     sqlite3_trace (mDBConn, tracefunc, nsnull);
00143 #endif
00144 
00145     /* Execute a dummy statement to force the db open, and to verify
00146      * whether it's valid or not
00147      */
00148     sqlite3_stmt *stmt = nsnull;
00149     nsCString query("SELECT * FROM sqlite_master");
00150     srv = sqlite3_prepare (mDBConn, query.get(), query.Length(), &stmt, nsnull);
00151  
00152     if (srv == SQLITE_OK) {
00153         srv = sqlite3_step(stmt);
00154  
00155         if (srv == SQLITE_DONE || srv == SQLITE_ROW)
00156             srv = SQLITE_OK;
00157     } else {
00158         stmt = nsnull;
00159     }
00160 
00161     if (stmt != nsnull)
00162         sqlite3_finalize (stmt);
00163  
00164     if (srv != SQLITE_OK) {
00165         sqlite3_close (mDBConn);
00166         mDBConn = nsnull;
00167 
00168         // make sure it really got closed
00169         ((mozStorageService*)(mStorageService.get()))->FlushAsyncIO();
00170 
00171         return ConvertResultCode(srv);
00172     }
00173 
00174     mFunctions = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
00175     if (NS_FAILED(rv)) return rv;
00176 
00177     return NS_OK;
00178 }
00179 
00180 /*****************************************************************************
00181  ** mozIStorageConnection interface
00182  *****************************************************************************/
00183 
00188 NS_IMETHODIMP
00189 mozStorageConnection::GetConnectionReady(PRBool *aConnectionReady)
00190 {
00191     *aConnectionReady = (mDBConn != nsnull);
00192     return NS_OK;
00193 }
00194 
00195 NS_IMETHODIMP
00196 mozStorageConnection::GetDatabaseFile(nsIFile **aFile)
00197 {
00198     NS_ASSERTION(mDBConn, "connection not initialized");
00199 
00200     NS_IF_ADDREF(*aFile = mDatabaseFile);
00201 
00202     return NS_OK;
00203 }
00204 
00205 NS_IMETHODIMP
00206 mozStorageConnection::GetLastInsertRowID(PRInt64 *aLastInsertRowID)
00207 {
00208     NS_ASSERTION(mDBConn, "connection not initialized");
00209 
00210     sqlite_int64 id = sqlite3_last_insert_rowid(mDBConn);
00211     *aLastInsertRowID = id;
00212 
00213     return NS_OK;
00214 }
00215 
00216 NS_IMETHODIMP
00217 mozStorageConnection::GetLastError(PRInt32 *aLastError)
00218 {
00219     NS_ASSERTION(mDBConn, "connection not initialized");
00220 
00221     *aLastError = sqlite3_errcode(mDBConn);
00222 
00223     return NS_OK;
00224 }
00225 
00226 NS_IMETHODIMP
00227 mozStorageConnection::GetLastErrorString(nsACString& aLastErrorString)
00228 {
00229     NS_ASSERTION(mDBConn, "connection not initialized");
00230 
00231     const char *serr = sqlite3_errmsg(mDBConn);
00232     aLastErrorString.Assign(serr);
00233 
00234     return NS_OK;
00235 }
00236 
00241 NS_IMETHODIMP
00242 mozStorageConnection::CreateStatement(const nsACString& aSQLStatement,
00243                                       mozIStorageStatement **_retval)
00244 {
00245     NS_ENSURE_ARG_POINTER(_retval);
00246     NS_ASSERTION(mDBConn, "connection not initialized");
00247 
00248     mozStorageStatement *statement = new mozStorageStatement();
00249     NS_ADDREF(statement);
00250 
00251     nsresult rv = statement->Initialize (this, aSQLStatement);
00252     if (NS_FAILED(rv)) {
00253         NS_RELEASE(statement);
00254         return rv;
00255     }
00256 
00257     *_retval = statement;
00258     return NS_OK;
00259 }
00260 
00261 NS_IMETHODIMP
00262 mozStorageConnection::ExecuteSimpleSQL(const nsACString& aSQLStatement)
00263 {
00264     NS_ENSURE_ARG_POINTER(mDBConn);
00265 
00266     int srv = sqlite3_exec (mDBConn, PromiseFlatCString(aSQLStatement).get(),
00267                             NULL, NULL, NULL);
00268     if (srv != SQLITE_OK) {
00269         HandleSqliteError(nsPromiseFlatCString(aSQLStatement).get());
00270         return ConvertResultCode(srv);
00271     }
00272 
00273     return NS_OK;
00274 }
00275 
00276 NS_IMETHODIMP
00277 mozStorageConnection::TableExists(const nsACString& aSQLStatement, PRBool *_retval)
00278 {
00279     NS_ENSURE_ARG_POINTER(mDBConn);
00280 
00281     nsCString query("SELECT name FROM sqlite_master WHERE type = 'table' AND name ='");
00282     query.Append(aSQLStatement);
00283     query.AppendLiteral("'");
00284 
00285     sqlite3_stmt *stmt = nsnull;
00286     int srv = sqlite3_prepare (mDBConn, query.get(), query.Length(), &stmt, nsnull);
00287     if (srv != SQLITE_OK) {
00288         HandleSqliteError(query.get());
00289         return ConvertResultCode(srv);
00290     }
00291 
00292     PRBool exists = PR_FALSE;
00293 
00294     srv = sqlite3_step(stmt);
00295     // we just care about the return value from step
00296     sqlite3_finalize(stmt);
00297 
00298     if (srv == SQLITE_ROW) {
00299         exists = PR_TRUE;
00300     } else if (srv == SQLITE_DONE) {
00301         exists = PR_FALSE;
00302     } else if (srv == SQLITE_ERROR) {
00303         HandleSqliteError("TableExists finalize");
00304         return NS_ERROR_FAILURE;
00305     }
00306 
00307     *_retval = exists;
00308     return NS_OK;
00309 }
00310 
00311 NS_IMETHODIMP
00312 mozStorageConnection::IndexExists(const nsACString& aIndexName, PRBool* _retval)
00313 {
00314     NS_ENSURE_ARG_POINTER(mDBConn);
00315 
00316     nsCString query("SELECT name FROM sqlite_master WHERE type = 'index' AND name ='");
00317     query.Append(aIndexName);
00318     query.AppendLiteral("'");
00319 
00320     sqlite3_stmt *stmt = nsnull;
00321     int srv = sqlite3_prepare(mDBConn, query.get(), query.Length(), &stmt, nsnull);
00322     if (srv != SQLITE_OK) {
00323         HandleSqliteError(query.get());
00324         return ConvertResultCode(srv);
00325     }
00326 
00327     PRBool exists = PR_FALSE;
00328 
00329     srv = sqlite3_step(stmt);
00330     // we just care about the return value from step
00331     sqlite3_finalize(stmt);
00332 
00333     if (srv == SQLITE_ROW) {
00334         exists = PR_TRUE;
00335     } else if (srv == SQLITE_DONE) {
00336         exists = PR_FALSE;
00337     } else if (srv == SQLITE_ERROR) {
00338         HandleSqliteError("IndexExists finalize");
00339         return NS_ERROR_FAILURE;
00340     }
00341 
00342     *_retval = exists;
00343     return NS_OK;
00344 }
00345 
00346 
00351 NS_IMETHODIMP
00352 mozStorageConnection::GetTransactionInProgress(PRBool *_retval)
00353 {
00354     *_retval = mTransactionInProgress;
00355     return NS_OK;
00356 }
00357 
00358 // XXX do we want to just store compiled statements for these?
00359 NS_IMETHODIMP
00360 mozStorageConnection::BeginTransaction()
00361 {
00362     if (mTransactionInProgress)
00363         return NS_ERROR_FAILURE; // XXX error code
00364     nsresult rv = ExecuteSimpleSQL (NS_LITERAL_CSTRING("BEGIN TRANSACTION"));
00365     if (NS_SUCCEEDED(rv))
00366         mTransactionInProgress = PR_TRUE;
00367     return rv;
00368 }
00369 
00370 NS_IMETHODIMP
00371 mozStorageConnection::BeginTransactionAs(PRInt32 aTransactionType)
00372 {
00373     if (mTransactionInProgress)
00374         return NS_ERROR_FAILURE; // XXX error code
00375     nsresult rv;
00376     switch(aTransactionType) {
00377         case TRANSACTION_DEFERRED:
00378             rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN DEFERRED"));
00379             break;
00380         case TRANSACTION_IMMEDIATE:
00381             rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN IMMEDIATE"));
00382             break;
00383         case TRANSACTION_EXCLUSIVE:
00384             rv = ExecuteSimpleSQL(NS_LITERAL_CSTRING("BEGIN EXCLUSIVE"));
00385             break;
00386         default:
00387             return NS_ERROR_ILLEGAL_VALUE;
00388     }
00389     if (NS_SUCCEEDED(rv))
00390         mTransactionInProgress = PR_TRUE;
00391     return NS_OK;
00392 }
00393 
00394 NS_IMETHODIMP
00395 mozStorageConnection::CommitTransaction()
00396 {
00397     if (!mTransactionInProgress)
00398         return NS_ERROR_FAILURE;
00399     nsresult rv = ExecuteSimpleSQL (NS_LITERAL_CSTRING("COMMIT TRANSACTION"));
00400     // even if the commit fails, the transaction is aborted
00401     mTransactionInProgress = PR_FALSE;
00402     return rv;
00403 }
00404 
00405 NS_IMETHODIMP
00406 mozStorageConnection::RollbackTransaction()
00407 {
00408     if (!mTransactionInProgress)
00409         return NS_ERROR_FAILURE;
00410     nsresult rv = ExecuteSimpleSQL (NS_LITERAL_CSTRING("ROLLBACK TRANSACTION"));
00411     mTransactionInProgress = PR_FALSE;
00412     return rv;
00413 }
00414 
00419 NS_IMETHODIMP
00420 mozStorageConnection::CreateTable(/*const nsID& aID,*/
00421                                   const char *aTableName,
00422                                   const char *aTableSchema)
00423 {
00424     int srv;
00425     char *buf;
00426 
00427     buf = PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
00428     if (!buf)
00429         return NS_ERROR_OUT_OF_MEMORY;
00430 
00431     srv = sqlite3_exec (mDBConn, buf,
00432                         NULL, NULL, NULL);
00433 
00434     PR_smprintf_free(buf);
00435 
00436     return ConvertResultCode(srv);
00437 }
00438 
00443 static void
00444 mozStorageSqlFuncHelper (sqlite3_context *ctx,
00445                          int argc,
00446                          sqlite3_value **argv)
00447 {
00448     void *userData = sqlite3_user_data (ctx);
00449     // We don't want to QI here, because this will be called a -lot-
00450     mozIStorageFunction *userFunction = NS_STATIC_CAST(mozIStorageFunction *, userData);
00451 
00452     nsCOMPtr<mozStorageArgvValueArray> ava = new mozStorageArgvValueArray (argc, argv);
00453     nsresult rv = userFunction->OnFunctionCall (ava);
00454     if (NS_FAILED(rv)) {
00455         NS_WARNING("mozIStorageConnection: User function returned error code!\n");
00456     }
00457 }
00458 
00459 NS_IMETHODIMP
00460 mozStorageConnection::CreateFunction(const char *aFunctionName,
00461                                      PRInt32 aNumArguments,
00462                                      mozIStorageFunction *aFunction)
00463 {
00464     nsresult rv;
00465 
00466     // do we already have this function defined?
00467     // XXX check for name as well
00468     PRUint32 idx;
00469     rv = mFunctions->IndexOf (0, aFunction, &idx);
00470     if (rv != NS_ERROR_FAILURE) {
00471         // already exists
00472         return NS_ERROR_FAILURE;
00473     }
00474 
00475     int srv = sqlite3_create_function (mDBConn,
00476                                        aFunctionName,
00477                                        aNumArguments,
00478                                        SQLITE_ANY,
00479                                        aFunction,
00480                                        mozStorageSqlFuncHelper,
00481                                        nsnull,
00482                                        nsnull);
00483     if (srv != SQLITE_OK) {
00484         HandleSqliteError(nsnull);
00485         return ConvertResultCode(srv);
00486     }
00487 
00488     rv = mFunctions->AppendElement (aFunction, PR_FALSE);
00489     if (NS_FAILED(rv)) return rv;
00490 
00491     return NS_OK;
00492 }
00493 
00498 nsresult
00499 mozStorageConnection::Preload()
00500 {
00501   int srv = sqlite3Preload(mDBConn);
00502   return ConvertResultCode(srv);
00503 }
00504 
00508 void
00509 mozStorageConnection::HandleSqliteError(const char *aSqlStatement)
00510 {
00511     // an error just occured!
00512 #ifdef PR_LOGGING
00513     PR_LOG(gStorageLog, PR_LOG_DEBUG, ("Sqlite error: %d '%s'", sqlite3_errcode(mDBConn), sqlite3_errmsg(mDBConn)));
00514     if (aSqlStatement)
00515         PR_LOG(gStorageLog, PR_LOG_DEBUG, ("Statement was: %s", aSqlStatement));
00516 #endif
00517 }