sqlite3_wrapper.c

Go to the documentation of this file.
00001 /*
00002  * File Name: sqlite3_wrapper.c
00003  */
00004 
00005 /*
00006  * This file is part of libermetadb.
00007  *
00008  * libermetadb is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * libermetadb is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00020  */
00021 
00022 /**
00023  * Copyright (C) 2008 iRex Technologies B.V.
00024  * All rights reserved.
00025  */
00026 
00027 //----------------------------------------------------------------------------
00028 // Include Files
00029 //----------------------------------------------------------------------------
00030 
00031 // system include files, between < >
00032 #include <glib.h>
00033 #include <sqlite3.h>
00034 #include <sys/types.h>
00035 #include <unistd.h>
00036 
00037 // ereader include files, between < >
00038 #include <liberutils/er_error.h>
00039 
00040 // local include files, between " "
00041 #define LOGGING_ON 0
00042 #include "ermetadb_log.h"
00043 #include "ermetadb.h"
00044 #include "ermetadb_error.h"
00045 #include "ermetadb_private.h"
00046 #include "metadata_table.h"
00047 #include "metadata_table_private.h"
00048 #include "sqlite3_wrapper.h"
00049 
00050 #define UNUSED(x) (void)(x)
00051 
00052 
00053 //----------------------------------------------------------------------------
00054 // Global Constants
00055 //----------------------------------------------------------------------------
00056 
00057 static const int BEGIN_MAX_RETRIES  =  20;      // max. retries for a begin statement
00058 static const int BEGIN_RETRY_MS     = 250;      // interval in ms. between retries
00059                                                 // 20 x 250 ms = 5 seconds
00060 
00061 static const int COMMIT_MAX_RETRIES =  30;      // max. retries for a begin statement
00062 static const int COMMIT_RETRY_MS    = 100;      // interval in ms. between retries
00063                                                 // 30 x 100 ms = 3 seconds
00064 
00065 static const int STEP_MAX_RETRIES   =  10;      // max. retries per execution step
00066 static const int STEP_RETRY_MS      = 100;      // interval in ms. between retries
00067                                                 // 10 x 100 ms = 1 second
00068 
00069 //============================================================================
00070 // Functions Implementation
00071 //============================================================================
00072 
00073 /**---------------------------------------------------------------------------
00074  *
00075  * Name :  sql3_finalize_statements
00076  *
00077  * @brief  Release compiled sql statements
00078  *
00079  * @param  [in]  statements - GPtrArray with compiled statements
00080  *
00081  *--------------------------------------------------------------------------*/
00082 static void sql3_finalize_statements(GPtrArray *statements)
00083 {
00084     LOGPRINTF("entry");
00085 
00086     if (statements)
00087     {
00088         unsigned int i;
00089         for (i = 0 ; i < statements->len ; i++)
00090         {
00091             sqlite3_stmt *stmt = g_ptr_array_index(statements, i);
00092             sqlite3_finalize(stmt);
00093         }
00094         g_ptr_array_free(statements, TRUE);
00095     }
00096 }
00097 
00098 
00099 /**---------------------------------------------------------------------------
00100  *
00101  * Name :  sql3_prepare_statements
00102  *
00103  * @brief  Parse sql string and compile into byte code
00104  *
00105  * @param  [in]  db - sqlite database object
00106  * @param  [in]  sql - SQL statement(s) as text
00107  * @param  [out] statements - GPtrArray with compiled statements, or NULL
00108  *
00109  * @return ER_OK or error code
00110  *
00111  *--------------------------------------------------------------------------*/
00112 static int sql3_prepare_statements (sqlite3 *db, const char *sql, GPtrArray **statements)
00113 {
00114     int          ret = ER_OK;     // return code
00115     const char   *head = NULL;    // start of current sql statement
00116     const char   *tail = NULL;    // start of next sql statement, if any
00117     sqlite3_stmt *stmt = NULL;
00118     GPtrArray    *stmt_list = NULL;
00119 
00120     LOGPRINTF("entry: sql [%s]", sql);
00121 
00122     g_return_val_if_fail( db                  , ER_INVALID_PARAMETER);
00123     g_return_val_if_fail( sql                 , ER_INVALID_PARAMETER);
00124     g_return_val_if_fail((*sql != '\0'       ), ER_INVALID_PARAMETER);
00125     g_return_val_if_fail( statements          , ER_INVALID_PARAMETER);
00126     g_return_val_if_fail((*statements == NULL), ER_INVALID_PARAMETER);
00127 
00128     // parse sql string and store compiled statements
00129     gboolean done = FALSE;
00130     for ( done = FALSE, head = sql ; !done ; head = tail )
00131     {
00132         int rc = sqlite3_prepare_v2(db, head, -1, &stmt, &tail);
00133         if (rc != ER_OK)
00134         {
00135             ERRORPRINTF("sqlite error [%d] [%s] on prepare_v2 [%s]", rc, sqlite3_errmsg(db), head);
00136             ret  = ERMETADB_FAIL;
00137             done = TRUE;
00138         }
00139         else
00140         {
00141             if (stmt)
00142             {
00143                 // statement compiled: store it
00144                 if (stmt_list == NULL)
00145                 {
00146                     stmt_list = g_ptr_array_new();
00147                     if (stmt_list == NULL)
00148                     {
00149                         ERRORPRINTF("cannot create GPtrArray");
00150                         ret  = ER_OUT_OF_MEMORY;
00151                         done = TRUE;
00152                     }
00153                 }
00154                 if (stmt_list)
00155                 {
00156                     g_ptr_array_add(stmt_list, stmt);
00157                 }
00158                 stmt = NULL;
00159             }
00160 
00161             if (*tail == '\0')
00162             {
00163                 // no more sql to process
00164                 done = TRUE;
00165             }
00166         }
00167     }
00168 
00169     // discard compiled statements in case of error
00170     if (ret != ER_OK)
00171     {
00172         if (stmt_list)
00173         {
00174             sql3_finalize_statements(stmt_list);
00175             stmt_list = NULL;
00176         }
00177     }
00178 
00179     // return list of prepared statements
00180     *statements = stmt_list;
00181     return ret;
00182 }
00183 
00184 
00185 static void bind_single_statement (void *p1, void *p2)
00186 {
00187     sqlite3_stmt         *stmt       = (sqlite3_stmt*)    p1;
00188     const metadata_table *parameters = (const metadata_table*) p2;
00189 
00190     LOGPRINTF("entry: stmt [%p]", stmt);
00191 
00192     // get #parameters in statement
00193     int n_parms = sqlite3_bind_parameter_count(stmt);
00194 
00195     // bind parameters
00196     GString *parm_name = g_string_new("");
00197     int parm_idx;
00198     const metadata_cell *parm = NULL;
00199     for (parm_idx = 0 ; parm_idx < n_parms ; parm_idx++)
00200     {
00201         // get parameter name
00202         g_string_printf(parm_name, "?%d", parm_idx + 1);
00203 
00204         // find parameter value
00205         metadata_cell_type parm_type = METADATA_NULL;
00206         if (parameters)
00207         {
00208             parm = metadata_table_get_cell(parameters, parm_idx);
00209             if (parm)
00210             {
00211                 parm_type = parm->type;
00212             }
00213         }
00214 
00215         int rc;
00216         // bind parameter
00217         switch (parm_type)
00218         {
00219             case METADATA_INT64:
00220                 rc = sqlite3_bind_int64(stmt, parm_idx + 1, parm->value.v_int64);
00221                 break;
00222             case METADATA_DOUBLE:
00223                 rc = sqlite3_bind_double(stmt, parm_idx + 1, parm->value.v_double);
00224                 break;
00225             case METADATA_TEXT:
00226                 rc = sqlite3_bind_text(stmt, parm_idx + 1, parm->value.v_text->str, -1, SQLITE_STATIC);
00227                 break;
00228             case METADATA_BLOB:
00229                 rc = sqlite3_bind_blob(stmt, parm_idx + 1, parm->value.v_blob.data, parm->value.v_blob.len, SQLITE_STATIC);
00230                 break;
00231             default:
00232                 rc = sqlite3_bind_null(stmt, parm_idx + 1);
00233         }
00234         if (rc != ER_OK)
00235         {
00236             ERRORPRINTF("sqlite3_bind_xx returns [%d] on stmt [%p]", rc, stmt);
00237             sqlite3_bind_null(stmt, parm_idx + 1);
00238         }
00239     }
00240 
00241     g_string_free(parm_name, TRUE);
00242 }
00243 
00244 
00245 /**---------------------------------------------------------------------------
00246  *
00247  * Name :  sql3_bind_parameters
00248  *
00249  * @brief  Bind positional parameters to a set of compiled sql statements
00250  *         Assumes no parameters are currently bound to this statement
00251  *         Assumes TEXT and BLOB parameters unchanged until statements have executed
00252  *
00253  * @param  [in/out] statements - GPtrArray with compiled statements
00254  * @param  [in]     parameters - metadata_table with parameters (value), or NULL
00255  *                               table must have exactly one row
00256  *
00257  * @return ER_OK or error code
00258  *
00259  *--------------------------------------------------------------------------*/
00260 int sql3_bind_parameters (GPtrArray *statements, const metadata_table *parameters)
00261 {
00262     g_return_val_if_fail( statements          , ER_INVALID_PARAMETER);
00263     g_return_val_if_fail((statements->len > 0), ER_INVALID_PARAMETER);
00264 
00265     metadata_table *parms = (metadata_table*) parameters;  // const_cast
00266     g_ptr_array_foreach(statements, bind_single_statement, parms);
00267 
00268     return ER_OK;
00269 }
00270 
00271 
00272 static metadata_table* create_result_table (sqlite3_stmt *stmt)
00273 {
00274     metadata_table *result = metadata_table_new();
00275     g_assert(result);
00276 
00277     // add columns to result table
00278     int n_col = sqlite3_column_count(stmt);
00279     int col;
00280     for (col = 0 ; col < n_col ; col++)
00281     {
00282         const char* name = sqlite3_column_name(stmt, col);
00283         if (name == NULL)
00284         {
00285             ERRORPRINTF("sqlite3_column returns NULL");
00286         }
00287         LOGPRINTF("column [%d] name [%s]", col, name);
00288         metadata_table_add_column(result, name);
00289     }
00290     return result;
00291 }
00292 
00293 
00294 // execute one statement, get all result rows
00295 static int execute_single_statement (sqlite3_stmt *stmt, metadata_table **result_table)
00296 {
00297     int ret = ER_OK;
00298 
00299     // repeat step while new rows are delivered
00300     metadata_table *result = NULL;
00301     int retries_left = STEP_MAX_RETRIES;
00302     gboolean done = FALSE;
00303     while (!done)
00304     {
00305         int step_rc = sqlite3_step(stmt);
00306         switch (step_rc)
00307         {
00308             case SQLITE_ROW:
00309             {
00310                 if (result == NULL) result = create_result_table(stmt);
00311 
00312                 // read column values into result table
00313                 int n_row = metadata_table_n_rows(result);
00314                 int n_col = metadata_table_n_columns(result);
00315                 int idx = n_row * n_col;
00316                 int col;
00317                 for (col = 0 ; col < n_col && ret == ER_OK ; col++)
00318                 {
00319                     sqlite3_value *value = sqlite3_column_value(stmt, col);
00320                     int rc = metadata_table_set_value(result, idx, value);
00321                     if (rc != ER_OK)
00322                     {
00323                         ret = rc;
00324                         done = TRUE;
00325                     }
00326                     idx++;
00327                 }
00328                 retries_left = STEP_MAX_RETRIES;
00329                 break;
00330             }
00331             case SQLITE_DONE:
00332                 done = TRUE;
00333                 break;
00334 
00335             case SQLITE_BUSY:
00336                 if (--retries_left >= 0)
00337                 {
00338                     // wait a while, then try again
00339                     usleep(STEP_RETRY_MS * 1000);
00340                     break;
00341                 }
00342                 else
00343                 {
00344                     // no more retries: error
00345                     ; // FALLTHROUGH to case default
00346                 }
00347 
00348             default:
00349                 ERRORPRINTF("sqlite3_step reports error [%d]", step_rc);
00350                 ret  = ERMETADB_FAIL;
00351                 done = TRUE;
00352         }
00353     }
00354 
00355     if (result)
00356     {
00357         // report the result
00358         if (*result_table)
00359         {
00360             ERRORPRINTF( "Not allowed to have SQL statements produce multiple result sets.\n"
00361                          "Second result set discarded:" );
00362             metadata_table_dump(result);
00363             metadata_table_free(result);
00364             ret = ERMETADB_FAIL;
00365         }
00366         else
00367         {
00368             *result_table = result;
00369         }
00370     }
00371 
00372     return ret;
00373 }
00374 
00375 
00376 /**---------------------------------------------------------------------------
00377  *
00378  * Name :  sql3_execute_statements
00379  *
00380  * @brief  Parse sql string and compile into byte code
00381  *
00382  * @param  [in]  db - sqlite database object
00383  * @param  [in]  statements - GPtrArray with compiled statements
00384  * @param  [in]  result_table - location to store metadata_table holding query results,
00385  *                              or NULL when caller is not interested in query results
00386  * @param  [out] result_table - metadata_table holding query results, or NULL when no results
00387  *                              First row holds column names and cell values,
00388  *                              other rows hold cell values only.
00389  *
00390  * @return ER_OK or error code
00391  *
00392  *--------------------------------------------------------------------------*/
00393 static int sql3_execute_statements(sqlite3 *db, const GPtrArray *statements, metadata_table **result_table)
00394 {
00395     LOGPRINTF("entry");
00396 
00397     g_return_val_if_fail( db                       , ER_INVALID_PARAMETER);
00398     g_return_val_if_fail( statements               , ER_INVALID_PARAMETER);
00399     g_return_val_if_fail((statements->len > 0     ), ER_INVALID_PARAMETER);
00400     g_return_val_if_fail( result_table             , ER_INVALID_PARAMETER);
00401     g_return_val_if_fail((   result_table == NULL
00402                           || *result_table == NULL), ER_INVALID_PARAMETER);
00403 
00404     // execute each statement
00405     int ret = ER_OK;
00406     unsigned int stmt_idx;
00407     metadata_table *result = NULL;
00408     for ( stmt_idx = 0 ; stmt_idx < statements->len  &&  ret == ER_OK ; stmt_idx++ )
00409     {
00410         sqlite3_stmt *stmt = g_ptr_array_index(statements, stmt_idx);
00411         // NOTE: memleak on result if several loops?
00412         ret = execute_single_statement(stmt, &result);
00413         if (ret != ER_OK)
00414         {
00415             ERRORPRINTF( "execute_single_statement returns [%d] on stmt_idx [%d], sqlite error [%s]",
00416                          ret, stmt_idx, sqlite3_errmsg(db) );
00417         }
00418     }
00419 
00420     // unbind and reset all statements
00421     for (stmt_idx = 0 ; stmt_idx < statements->len ; stmt_idx++)
00422     {
00423         sqlite3_stmt *stmt = g_ptr_array_index(statements, stmt_idx);
00424 
00425         // unbind all parameters on this statement
00426         int n_parms = sqlite3_bind_parameter_count(stmt);
00427         int parm_idx;
00428         for (parm_idx = 0 ; parm_idx < n_parms ; parm_idx++)
00429         {
00430             sqlite3_bind_null(stmt, parm_idx + 1);
00431         }
00432 
00433         // reset statement
00434         sqlite3_reset(stmt);
00435     }
00436 
00437     // discard results on error
00438     if (ret != ER_OK)
00439     {
00440         metadata_table_free(result);
00441     }
00442 
00443     if (result_table)
00444     {
00445         *result_table = result;
00446         result = NULL;
00447     }
00448     metadata_table_free(result);
00449     return ret;
00450 }
00451 
00452 
00453 int sql3_execute_query(sqlite3 *db, const char *sql, const metadata_table *parameters, metadata_table **result_table)
00454 {
00455     QUERYPRINTF("entry: sql [%s]", sql);
00456     metadata_table_dump(parameters);
00457 
00458     g_return_val_if_fail( db                       , ER_INVALID_PARAMETER);
00459     g_return_val_if_fail( sql                      , ER_INVALID_PARAMETER);
00460     g_return_val_if_fail((   result_table == NULL
00461                           || *result_table == NULL), ER_INVALID_PARAMETER);
00462 
00463     int ret = ER_OK;
00464     GPtrArray *statements = NULL;
00465     int rc = sql3_prepare_statements(db, sql, &statements);
00466     if (rc != ER_OK)
00467     {
00468         ERRORPRINTF("sql3_prepare_statements returns [%d] sql [%s]", rc, sql);
00469         ret = rc;
00470     }
00471 
00472     if (ret == ER_OK  &&  parameters)
00473     {
00474         rc = sql3_bind_parameters(statements, parameters);
00475         if (rc != ER_OK)
00476         {
00477             ERRORPRINTF("sql3_bind_parameters returns [%d] sql [%s]", rc, sql);
00478             ret = rc;
00479         }
00480     }
00481 
00482     metadata_table *result = NULL;
00483     if (ret == ER_OK)
00484     {
00485         rc = sql3_execute_statements(db, statements, &result);
00486         if (rc != ER_OK)
00487         {
00488             ERRORPRINTF("sql3_execute_statements returns [%d] sql [%s]", rc, sql);
00489             ret = rc;
00490         }
00491     }
00492 
00493     // clean up
00494     if (statements)
00495     {
00496         sql3_finalize_statements(statements);
00497         statements = NULL;
00498     }
00499 
00500     // return result
00501     if (result_table)
00502     {
00503         *result_table = result;
00504         result = NULL;
00505     }
00506     metadata_table_free(result);
00507     return ret;
00508 }
00509 
00510 
00511 static int begin_transaction(erMetadb thiz, const gboolean is_readonly)
00512 {
00513     int         ret = ER_OK;
00514     int         rc;
00515     const char  *sql    = NULL;
00516     char        *errmsg = NULL;
00517     int         retries_left = BEGIN_MAX_RETRIES;
00518     gboolean    done = FALSE;
00519 
00520     g_assert(thiz && thiz->database);
00521     g_assert(thiz->transaction_nesting < 100);
00522 
00523     sqlite3 *db = thiz->database;
00524 
00525     LOGPRINTF( "entry: db [%p] is_readonly [%d] [%d] transaction_nesting [%d]",
00526                 db,
00527                 is_readonly,
00528                 thiz->is_transaction_readonly,
00529                 thiz->transaction_nesting      );
00530 
00531     g_return_val_if_fail(db, ER_INVALID_PARAMETER);
00532 
00533 
00534     // start a transaction, acquire write lock
00535     if (thiz->transaction_nesting == 0)
00536     {
00537         thiz->is_transaction_readonly = is_readonly;
00538         thiz->do_force_rollback       = FALSE;
00539 
00540         // repeat while database locked
00541         if ( is_readonly )
00542         {
00543             sql = "BEGIN DEFERRED;";
00544         }
00545         else
00546         {
00547             sql = "BEGIN IMMEDIATE;";
00548         }
00549         QUERYPRINTF("sql [%s]", sql);
00550         while (!done)
00551         {
00552             rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
00553             if (rc == SQLITE_OK)
00554             {
00555                 // database lock obtained: increase transaction nesting level
00556                 thiz->transaction_nesting++;
00557                 done = TRUE;
00558 
00559             }
00560             else if (   rc == SQLITE_BUSY
00561                      && --retries_left >= 0 )
00562             {
00563                 // database locked: wait a while then try again
00564                 WARNPRINTF("pid [%ld] waiting for database lock", (long)getpid());
00565                 usleep(BEGIN_RETRY_MS * 1000);
00566             }
00567             else
00568             {
00569                 ERRORPRINTF("pid [%ld] cannot begin transaction, error [%d] [%s]", (long)getpid(), rc, errmsg);
00570                 switch (rc)
00571                 {
00572                     case SQLITE_BUSY:
00573                         ret = ERMETADB_DATABASE_BUSY;
00574                         break;
00575                     case SQLITE_READONLY:
00576                         ret = ER_READONLY;
00577                         break;
00578                     default:
00579                         ret = ERMETADB_FAIL;
00580                 }
00581                 done = TRUE;
00582             }
00583 
00584             // free error message, if any
00585             sqlite3_free(errmsg);
00586             errmsg = NULL;
00587         }
00588     }
00589     else
00590     {
00591         // transaction in progress: check read-only status
00592         if ( is_readonly == thiz->is_transaction_readonly )
00593         {
00594             // same is_readonly: ok
00595             thiz->transaction_nesting++;
00596         }
00597         else if ( is_readonly )
00598         {
00599             // read-only begin on writable transaction: ok
00600             thiz->transaction_nesting++;
00601         }
00602         else
00603         {
00604             // error: read-only transaction cannot be promoted to writable
00605             ret = ER_READONLY;
00606         }
00607     }
00608 
00609     if (ret != ER_OK) WARNPRINTF("cannot begin transaction. ret [%d]", ret);
00610     return ret;
00611 }
00612 
00613 
00614 int sql3_begin_transaction(erMetadb thiz)
00615 {
00616     return begin_transaction(thiz, FALSE);
00617 }
00618 
00619 
00620 int sql3_begin_transaction_readonly(erMetadb thiz)
00621 {
00622     return begin_transaction(thiz, TRUE);
00623 }
00624 
00625 
00626 int sql3_commit_or_rollback(erMetadb thiz, int err)
00627 {
00628     g_assert(thiz && thiz->database);
00629 
00630     LOGPRINTF( "entry: err [%d] is_readonly [%d] transaction_nesting [%d]",
00631                 err,
00632                 thiz->is_transaction_readonly,
00633                 thiz->transaction_nesting      );
00634 
00635     // decide whether to commit or rollback
00636     if (err != ER_OK) thiz->do_force_rollback = TRUE;
00637 
00638     // never turn an error code into a good one
00639     int ret = err;
00640     if (thiz->transaction_nesting > 0)
00641     {
00642         // decrease transaction nesting level
00643         thiz->transaction_nesting--;
00644 
00645         if (thiz->transaction_nesting == 0)
00646         {
00647             const char *sql = NULL;
00648             // decide commit or rollback ...
00649             if ( err == ER_OK && thiz->do_force_rollback == FALSE )
00650             {
00651                 // ok: commit changes to database
00652                 sql = "COMMIT;";
00653             }
00654             else
00655             {
00656                 // error: rollback and return original error code
00657                 sql = "ROLLBACK;";
00658             }
00659             QUERYPRINTF("sql [%s]", sql);
00660 
00661             // ... and execute it
00662             gboolean done = FALSE;
00663             int retries_left = COMMIT_MAX_RETRIES;
00664             while (!done)
00665             {
00666                 char *errmsg = NULL;
00667                 int rc = sqlite3_exec(thiz->database, sql, NULL, NULL, &errmsg);
00668                 if (rc == SQLITE_OK)
00669                 {
00670                     // transaction ended
00671                     done = TRUE;
00672                 }
00673                 else if ( rc == SQLITE_BUSY && --retries_left >= 0 )
00674                 {
00675                     // database locked: wait a while then try again
00676                     WARNPRINTF("pid [%ld] waiting for database lock, sql [%s]", (long)getpid(), sql);
00677                     usleep(BEGIN_RETRY_MS * 1000);
00678                 }
00679                 else
00680                 {
00681                     ERRORPRINTF("cannot end transaction, sql [%s] error [%d] [%s]", sql, rc, errmsg);
00682                     if (rc == SQLITE_BUSY)
00683                     {
00684                         ret = ERMETADB_DATABASE_BUSY;
00685                     }
00686                     else
00687                     {
00688                         ret = ERMETADB_FAIL;
00689                     }
00690                     done = TRUE;
00691                 }
00692 
00693                 // free error message, if any
00694                 sqlite3_free(errmsg);
00695                 errmsg = NULL;
00696             }
00697         }
00698     }
00699 
00700     LOGPRINTF("leave: ret [%d]", ret);
00701     return ret;
00702 }
00703 
00704 
00705 // custom collation sequence for case-insensitive text ordering
00706 int sql3_strcase_compare( void        *user_data,
00707                           int         left_len,
00708                           const void  *left_void,
00709                           int         right_len,
00710                           const void  *right_void )
00711 {
00712     UNUSED(user_data);
00713     int         ret = 0;
00714     const gchar *left  = (gchar*)left_void;
00715     const gchar *right = (gchar*)right_void;
00716 
00717     int len = MIN(left_len, right_len);
00718     if (len == 0)
00719     {
00720         // empty string sorts at end of list
00721         if (left_len == right_len)
00722         {
00723             ret = 0;
00724         }
00725         else if (left_len == 0)
00726         {
00727             ret = 1;
00728         }
00729         else
00730         {
00731             ret = -1;
00732         }
00733     }
00734     else
00735     {
00736         // non-empty string sorts case-insensitive
00737         ret = g_ascii_strncasecmp(left, right, len);
00738         if (ret == 0)
00739         {
00740             if (left_len < right_len)
00741             {
00742                 ret = -1;
00743             }
00744             else
00745             {
00746                 ret = 1;
00747             }
00748         }
00749     }
00750 
00751     LOGPRINTF("return [%d] left [%.*s] right [%.*s]", ret, left_len, left, right_len, right);
00752     return ret;
00753 }
00754 
Generated by  doxygen 1.6.2-20100208