Back to index

lightning-sunbird  0.9+nobinonly
mozSqlConnectionSqlite.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is mozilla.org code.
00015  *
00016  * The Initial Developer of the Original Code is Jan Varga
00017  * Portions created by the Initial Developer are Copyright (C) 2003
00018  * the Initial Developer. All Rights Reserved.
00019  *
00020  * Contributor(s):
00021  *   Valia Vaneeva <fattie@altlinux.ru>
00022  *
00023  * Alternatively, the contents of this file may be used under the terms of
00024  * either the GNU General Public License Version 2 or later (the "GPL"), or
00025  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00026  * in which case the provisions of the GPL or the LGPL are applicable instead
00027  * of those above. If you wish to allow use of your version of this file only
00028  * under the terms of either the GPL or the LGPL, and not to allow others to
00029  * use your version of this file under the terms of the MPL, indicate your
00030  * decision by deleting the provisions above and replace them with the notice
00031  * and other provisions required by the GPL or the LGPL. If you do not delete
00032  * the provisions above, a recipient may use your version of this file under
00033  * the terms of any one of the MPL, the GPL or the LGPL.
00034  *
00035  * ***** END LICENSE BLOCK ***** */
00036 
00037 #include "nsReadableUtils.h"
00038 #include "mozSqlConnectionSqlite.h"
00039 #include "mozSqlResultSqlite.h"
00040 #include <ctype.h>
00041 #include "nsLocalFile.h"
00042 #include "nsAppDirectoryServiceDefs.h"
00043 #include "nsUnicharUtils.h"
00044 
00045 mozSqlConnectionSqlite::mozSqlConnectionSqlite()
00046   : mConnection(nsnull)
00047 {
00048 }
00049 
00050 mozSqlConnectionSqlite::~mozSqlConnectionSqlite()
00051 {
00052   if (mConnection)
00053     sqlite3_close(mConnection);
00054 }
00055 
00056 NS_IMPL_ADDREF_INHERITED(mozSqlConnectionSqlite, mozSqlConnection)
00057 NS_IMPL_RELEASE_INHERITED(mozSqlConnectionSqlite, mozSqlConnection)
00058 
00059 // QueryInterface
00060 NS_INTERFACE_MAP_BEGIN(mozSqlConnectionSqlite)
00061   NS_INTERFACE_MAP_ENTRY(mozISqlConnectionSqlite)
00062 NS_INTERFACE_MAP_END_INHERITING(mozSqlConnection)
00063 
00064 NS_IMETHODIMP
00065 mozSqlConnectionSqlite::Init(const nsAString& aHost,
00066                              PRInt32 aPort,
00067                              const nsAString& aDatabase,
00068                              const nsAString& aUsername,
00069                              const nsAString& aPassword)
00070 {
00071   if (mConnection)
00072     return NS_OK;
00073 
00074   nsAString::const_iterator start;
00075   nsresult rv;
00076   nsAutoString path;
00077   aDatabase.BeginReading(start);
00078 
00079   nsCOMPtr<nsILocalFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
00080   if (NS_FAILED(rv))
00081     return rv;
00082     
00083   rv = file->InitWithPath(aDatabase);
00084 
00085   if (rv == NS_ERROR_FILE_UNRECOGNIZED_PATH) {
00086     nsCOMPtr<nsIProperties> directoryService = do_GetService(
00087                                                NS_DIRECTORY_SERVICE_CONTRACTID,
00088                                                &rv);
00089     if (NS_FAILED(rv))
00090       return rv;
00091     rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR,
00092                                NS_GET_IID(nsILocalFile),
00093                                getter_AddRefs(file));
00094     if (NS_FAILED(rv))
00095       return rv;
00096     rv = file->Append(aDatabase);
00097     if (NS_FAILED(rv))
00098       return rv;
00099     file->GetPath(path);
00100     if (path.IsEmpty())
00101       return NS_ERROR_FAILURE;
00102   }
00103   else if (NS_FAILED(rv))
00104     return NS_ERROR_FAILURE;
00105   else
00106     path = aDatabase;
00107 
00108   PRBool exists;
00109   rv = file->Exists(&exists);
00110   if (NS_FAILED(rv))
00111     return NS_ERROR_FAILURE;
00112   
00113   rv = file->IsWritable(&mWritable);
00114   if (NS_FAILED(rv))
00115     return NS_ERROR_FAILURE;
00116        
00117   rv = sqlite3_open(NS_ConvertUCS2toUTF8(path).get(), &mConnection);
00118   if (rv != SQLITE_OK)
00119     return rv;
00120 
00121   return Setup();
00122 }
00123 
00124 NS_IMETHODIMP
00125 mozSqlConnectionSqlite::GetPrimaryKeys(const nsAString& aSchema,
00126                                        const nsAString& aTable,
00127                                        mozISqlResult** _retval)
00128 {
00129   if (! mConnection)
00130     return NS_ERROR_NOT_INITIALIZED;
00131     
00132   /* the purpose of this all is to get primary keys in a structure used in
00133    * GetPrimaryKeys() for pgsql. as sqlite3 doesn't allow to do it using a
00134    * single select query, we have to create it by hand
00135    * i thought of other variants but this one seems to be the easiest, if you
00136    * know how to implement GetPrimaryKeys() in a better way, please tell me
00137    */
00138   char **r, *errmsg;
00139   PRInt32 stat, nrow, ncolumn;
00140   nsAutoString preselect, select1, select2, uni, select, common;
00141   
00142   common.AssignLiteral("select NULL as TABLE_SCHEM, '");
00143   select1.AssignLiteral("' as COLUMN_NAME, ");
00144   select2.AssignLiteral(" as KEY_SEQ, NULL as PK_NAME");
00145   uni.AssignLiteral(" UNION ");
00146   
00147   // first select: we need it to get the "create table" statement
00148   preselect.AssignLiteral("select sql from sqlite_master where type='table' and name='");
00149   if (!aTable.IsEmpty()) {
00150     preselect.Append(aTable);
00151     common.Append(aTable);
00152   }
00153   else
00154     return NS_ERROR_FAILURE;
00155   preselect.Append(NS_LITERAL_STRING("';"));
00156   common.Append(NS_LITERAL_STRING("' as TABLE_NAME, '"));
00157   
00158   stat = sqlite3_get_table(mConnection, NS_ConvertUCS2toUTF8(preselect).get(),
00159                            &r, &nrow, &ncolumn, &errmsg);
00160   if (stat != SQLITE_OK) {
00161     CopyUTF8toUTF16(errmsg, mErrorMessage);
00162     sqlite3_free_table(r);
00163     return NS_ERROR_FAILURE;
00164   }
00165   
00166   // now we parse that statement in order to find primary key columns
00167   nsAutoString aToken = NS_ConvertUTF8toUCS2(nsDependentCString("PRIMARY KEY"));
00168   nsAutoString aKeyColumn;
00169   NS_ConvertUTF8toUCS2 buffer(r[1]);
00170   nsAString::const_iterator start, end, iter, iter2;
00171   buffer.BeginReading(start);
00172   buffer.EndReading(end);
00173   
00174   if (CaseInsensitiveFindInReadable(aToken, start, end))
00175     iter = end;
00176   else
00177     return NS_ERROR_FAILURE;
00178   buffer.BeginReading(start);
00179   buffer.EndReading(end);
00180 
00181   PRInt32 count = 1;
00182   while (iter != end && (*iter == PRUnichar(' ') || *iter == PRUnichar('\t') ||
00183          *iter == PRUnichar('\n')))
00184     ++iter;
00185   
00186   // if we have "primary key (colname1, colname2, ...)" we can have multiple
00187   // columns
00188   if (*iter == PRUnichar('(')) {
00189     char str[16];
00190     ++iter;
00191     while (iter != end && *iter != PRUnichar(')')) {
00192       while ((*iter == PRUnichar(' ') || *iter == PRUnichar('\n') ||
00193              *iter == PRUnichar('\t') || *iter == PRUnichar(',')) &&
00194              *iter != PRUnichar(')') && iter != end)
00195         ++iter;
00196       if (iter != end && *iter != PRUnichar(')')) {
00197        // we get column names and create a fake select which selects nothing
00198        if (count > 1)
00199           select.Append(uni);
00200         select.Append(common);
00201         iter2 = iter;
00202        while (iter2 != end && *iter2 != PRUnichar(',') &&
00203                *iter2 != PRUnichar(' ') && *iter2 != PRUnichar('\n') &&
00204                *iter2 != PRUnichar('\t') && *iter2 != PRUnichar(')'))
00205          ++iter2;
00206        aKeyColumn = Substring(iter,iter2);
00207        select.Append(aKeyColumn);
00208        iter = iter2;
00209        select.Append(select1);
00210         PRInt32 i = 0, j, tmp, cnt = count;
00211         do {
00212           str[i++] = cnt % 10 + 48;
00213           str[i] = '\0';
00214         } while ((cnt /= 10) > 0);
00215         for (i = 0, j = strlen(str) - 1; i < j; i++, j--) {
00216          tmp = str[i];
00217           str[i] = str[j];
00218           str[j] = tmp;
00219        }
00220        select.Append(UTF8ToNewUnicode(nsDependentCString(str)));
00221        select.Append(select2);
00222        count++;
00223       }
00224     }
00225   }
00226   // we have only one primary key column: "colname ... primary key ..."
00227   else {
00228     PRInt32 openParenth = 0;
00229     while (iter != start && (*iter != PRUnichar(',') && openParenth == 0)) {
00230       if (*iter == PRUnichar(')'))
00231         openParenth++;
00232       else if (*iter == PRUnichar('('))
00233         openParenth--;
00234       --iter;
00235     }
00236     if (iter == start) {
00237       while (*iter != PRUnichar('(') && iter != end)
00238         ++iter;
00239     }
00240     ++iter;
00241     while ((*iter == PRUnichar(' ') || *iter == PRUnichar('\n') ||
00242            *iter == PRUnichar('\t')) && iter != end)
00243       ++iter;
00244     select.Append(common);
00245     iter2 = iter;
00246     while (iter2 != end && *iter2 != PRUnichar(' ') &&
00247            *iter2 != PRUnichar('\n') && *iter2 != PRUnichar('\t'))
00248       ++iter2;
00249     aKeyColumn = Substring(iter,iter2);
00250     select.Append(aKeyColumn);
00251     select.Append(select1);
00252     select.Append(PRUnichar('1'));
00253     select.Append(select2);
00254   }
00255   select.Append(PRUnichar(';'));
00256   
00257   sqlite3_free_table(r);
00258 
00259   /* by this time we have either this select:
00260    *   select NULL as TABLE_SCHEM, 'table_name' as TABLE_NAME, 'colname1' as
00261    *   COLUMN_NAME, 1 as KEY_SEQ, NULL as PK_NAME UNION select NULL as
00262    *   TABLE_SCHEM, 'table_name' as TABLE_NAME, 'colname2' as COLUMN_NAME, 2 as
00263    *   KEY_SEQ, NULL as PK_NAME ...;
00264    * or this one:
00265    *   select NULL as TABLE_SCHEM, 'table_name' as TABLE_NAME, 'colname' as
00266    *   COLUMN_NAME, 1 as KEY_SEQ, NULL as PK_NAME;
00267    * anyway, they do not select anything, they just assign the values
00268    */
00269   return RealExec(select, _retval, nsnull);
00270 }
00271 
00272 nsresult
00273 mozSqlConnectionSqlite::Setup()
00274 {
00275   if (sqlite3_errcode(mConnection) != SQLITE_OK) {
00276     CopyUTF8toUTF16(sqlite3_errmsg(mConnection), mErrorMessage);
00277     
00278     mConnection = nsnull;
00279     return NS_ERROR_FAILURE;
00280   }
00281 
00282   NS_ConvertUTF8toUCS2 buffer(sqlite3_version);
00283   nsAString::const_iterator start, end, iter;
00284   PRInt32 numbers[3] = {0,0,0};
00285   buffer.BeginReading(iter);
00286   buffer.EndReading(end);
00287   for (PRInt32 i = 0; i < 3; i++) {
00288     start = iter;
00289     while (iter != end && *iter != PRUnichar('.'))
00290       ++iter;
00291     nsAutoString v(Substring(start,iter));
00292     PRInt32 err;
00293     numbers[i] = v.ToInteger(&err);
00294     while (iter != end && *iter == PRUnichar('.'))
00295       ++iter;
00296   }
00297   mVersion = SERVER_VERSION(numbers[0], numbers[1], numbers[2]);
00298 
00299   if (mVersion < SERVER_VERSION(3,0,2))
00300     return NS_ERROR_FAILURE;
00301 
00302   return NS_OK;
00303 }
00304 
00305 nsresult
00306 mozSqlConnectionSqlite::RealExec(const nsAString& aQuery,
00307                                  mozISqlResult** aResult,
00308                                  PRInt32* aAffectedRows)
00309 {
00310   //sqlite3_changes doesn't reset its count to 0 after selects
00311   static PRInt32 oldChange = 0;
00312   
00313   if (! mConnection)
00314     return NS_ERROR_NOT_INITIALIZED;
00315 
00316   char **r, *errmsg;
00317   PRInt32 stat, nrow, ncolumn;
00318 
00319   stat = sqlite3_get_table(mConnection, NS_ConvertUCS2toUTF8(aQuery).get(), &r,
00320                            &nrow, &ncolumn, &errmsg);
00321   PRInt32 changed = sqlite3_total_changes(mConnection) - oldChange;
00322   oldChange += changed;
00323 
00324   if (stat == SQLITE_OK && !changed) {
00325     if (!aResult)
00326       return NS_ERROR_NULL_POINTER;
00327     
00328     if (*aResult) {
00329       ((mozSqlResultSqlite*)*aResult)->SetResult(r, nrow, ncolumn, mWritable);
00330       nsresult rv = ((mozSqlResult*)*aResult)->Rebuild();
00331       if (NS_FAILED(rv))
00332         return rv;
00333       NS_ADDREF(*aResult);
00334     }
00335     else {
00336       mozSqlResult* result = new mozSqlResultSqlite(this, aQuery);
00337       if (!result)
00338         return NS_ERROR_OUT_OF_MEMORY;
00339       ((mozSqlResultSqlite*)result)->SetResult(r, nrow, ncolumn, mWritable);
00340       nsresult rv = result->Init();
00341       if (NS_FAILED(rv))
00342         return rv;
00343       NS_ADDREF(*aResult = result);
00344     }
00345   }
00346   else if (stat == SQLITE_OK && changed) {
00347     if (!aAffectedRows)
00348       return NS_ERROR_NULL_POINTER;
00349     *aAffectedRows = changed;
00350     mLastID = sqlite3_last_insert_rowid(mConnection);
00351   }
00352   else {
00353     CopyUTF8toUTF16(errmsg, mErrorMessage);
00354     sqlite3_free_table(r);
00355     return NS_ERROR_FAILURE;
00356   }
00357 
00358   return NS_OK;
00359 }
00360 
00361 nsresult
00362 mozSqlConnectionSqlite::CancelExec()
00363 {
00364   sqlite3_interrupt(mConnection);
00365   
00366   if (sqlite3_errcode(mConnection) != SQLITE_OK) {
00367     CopyUTF8toUTF16(sqlite3_errmsg(mConnection), mErrorMessage);
00368     return NS_ERROR_FAILURE;
00369   }
00370 
00371   return NS_OK;
00372 }
00373 
00374 nsresult
00375 mozSqlConnectionSqlite::GetIDName(nsAString& aIDName)
00376 {
00377   aIDName.AssignLiteral("OID");
00378   return NS_OK;
00379 }