From 63ca792cfb43cad7e3c4647fabc673393a502373 Mon Sep 17 00:00:00 2001 From: Phil Longstaff Date: Fri, 19 Jun 2009 16:48:48 +0000 Subject: [PATCH] Implement QofQuery for splits. At this point, the queries used by current reports work, but more split fields could be implemented. Implemented are: account.guid, reconcile-flag, trans.desc and trans.date-posted. Fixes Bug #585167 git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18135 57a11ea4-9604-0410-9ed3-97b8803252fd --- src/backend/sql/Makefile.am | 6 +- src/backend/sql/escape.c | 150 ++++++++++++ src/backend/sql/escape.h | 45 ++++ src/backend/sql/gnc-backend-sql.c | 202 +++++++++------- src/backend/sql/gnc-backend-sql.h | 8 + src/backend/sql/gnc-transaction-sql.c | 316 ++++++++++++++++++++++++-- 6 files changed, 623 insertions(+), 104 deletions(-) create mode 100644 src/backend/sql/escape.c create mode 100644 src/backend/sql/escape.h diff --git a/src/backend/sql/Makefile.am b/src/backend/sql/Makefile.am index de5b75a0b9..9dd77ff50c 100644 --- a/src/backend/sql/Makefile.am +++ b/src/backend/sql/Makefile.am @@ -26,7 +26,8 @@ libgnc_backend_sql_la_SOURCES = \ gnc-recurrence-sql.c \ gnc-schedxaction-sql.c \ gnc-slots-sql.c \ - gnc-transaction-sql.c + gnc-transaction-sql.c \ + escape.c noinst_HEADERS = \ gnc-account-sql.h \ @@ -39,7 +40,8 @@ noinst_HEADERS = \ gnc-recurrence-sql.h \ gnc-schedxaction-sql.h \ gnc-slots-sql.h \ - gnc-transaction-sql.h + gnc-transaction-sql.h \ + escape.h libgnc_backend_sql_la_LIBADD = \ ${GLIB_LIBS} \ diff --git a/src/backend/sql/escape.c b/src/backend/sql/escape.c new file mode 100644 index 0000000000..d96c683f71 --- /dev/null +++ b/src/backend/sql/escape.c @@ -0,0 +1,150 @@ +/********************************************************************\ + * escape.c : escape SQL reserved characters * + * Copyright (C) 2001 Linas Vepstas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +/* + * FILE: + * esacpe.c + * + * FUNCTION: + * Escapes the ' and \ characters in a string + */ + +#include "config.h" +#include +#include + +#include "gnc-engine.h" +#include "escape.h" + +static QofLogModule log_module = GNC_MOD_BACKEND; + +/* ================================================ */ + +struct _escape { + /* pointer to memory used for escaping arguments */ + char * escape; + size_t esc_buflen; +}; + +/* ================================================ */ +/* escape single-quote marks and backslashes so that the + * database SQL parser doesn't puke on the query string + */ + +const char * +sqlEscapeString (sqlEscape *b, const char *str) +{ + const char *p, *src_head; + char *dst_tail; + size_t len, slen; + + ENTER("str = %s", str); + + if (!b || !str) { LEAVE("(null) args"); return NULL; } + + /* if a string is escaped twice, just return the first */ + if (b->escape == str) { + LEAVE("%s: already escaped", str); + return str; + } + + /* if nothing to escape, just return */ + len = strlen (str); + slen = strcspn (str, "\\\'"); + if (len == slen) { + LEAVE("nothing to escape"); + return str; + } + + /* count to see how much space we'll need */ + p = str + slen + 1; + while (*p) + { + len ++; + p += 1 + strcspn (p, "\\\'"); + } + + /* get more space, if needed */ + if (len >= b->esc_buflen) + { + b->escape = g_realloc(b->escape, len+100); + b->esc_buflen = len+100; + } + + /* copy and escape */ + src_head = (char *) str; + dst_tail = b->escape; + p = src_head + strcspn (src_head, "\\\'"); + while (*p) + { + size_t cp_len = p - src_head; + + strncpy (dst_tail, src_head, cp_len); + dst_tail += cp_len; + *dst_tail = '\\'; + dst_tail ++; + *dst_tail = *p; + dst_tail ++; + + src_head = p+1; + p = src_head + strcspn (src_head, "\\\'"); + } + if (p != src_head) + { + size_t cp_len = p - src_head; + + strncpy (dst_tail, src_head, cp_len); + dst_tail += cp_len; + } + *dst_tail = 0; + + LEAVE("b->escape = %s", b->escape); + return b->escape; +} + +/* ================================================ */ + +#define INITIAL_BUFSZ 2000 + +sqlEscape * +sqlEscape_new (void) +{ + sqlEscape *b = g_new (sqlEscape, 1); + + b->escape = g_malloc (INITIAL_BUFSZ); + b->esc_buflen = INITIAL_BUFSZ; + return (b); +} + +/* ================================================ */ + +void +sqlEscape_destroy (sqlEscape *b) +{ + ENTER(" "); + if (!b) { LEAVE("b is (null)"); return; } + g_free (b->escape); b->escape = NULL; + g_free (b); + LEAVE(" "); +} + +/* ================ END OF FILE ==================== */ diff --git a/src/backend/sql/escape.h b/src/backend/sql/escape.h new file mode 100644 index 0000000000..711d4b85cb --- /dev/null +++ b/src/backend/sql/escape.h @@ -0,0 +1,45 @@ +/********************************************************************\ + * escape.h : Escape SQL reserved characters in strings * + * Copyright (C) 2001 Linas Vepstas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +/* + * FILE: + * escape.h + * + * FUNCTION: + * Escape SQL reserved characters \ and ' from strings + * + * HISTORY: + * Linas Vepstas January 2001 + */ + +#ifndef SQL_ESCAPE_H +#define SQL_ESCAPE_H + +typedef struct _escape sqlEscape; + +sqlEscape * sqlEscape_new(void); +void sqlEscape_destroy (sqlEscape *); + +const char * sqlEscapeString (sqlEscape *, const char *orig_string); + +#endif /* SQL_ESCAPE_H */ + diff --git a/src/backend/sql/gnc-backend-sql.c b/src/backend/sql/gnc-backend-sql.c index 70451354f1..65e1fd3315 100644 --- a/src/backend/sql/gnc-backend-sql.c +++ b/src/backend/sql/gnc-backend-sql.c @@ -562,15 +562,8 @@ gnc_sql_commit_edit( GncSqlBackend *be, QofInstance *inst ) /* ---------------------------------------------------------------------- */ /* Query processing */ -#if 0 -static const gchar* -convert_search_obj( QofIdType objType ) -{ - return (gchar*)objType; -} - static void -handle_and_term( QofQueryTerm* pTerm, gchar* sql ) +handle_and_term( QofQueryTerm* pTerm, GString* sql ) { GSList* pParamPath; QofQueryPredData* pPredData; @@ -585,81 +578,100 @@ handle_and_term( QofQueryTerm* pTerm, gchar* sql ) pPredData = qof_query_term_get_pred_data( pTerm ); isInverted = qof_query_term_is_inverted( pTerm ); - strcat( sql, "(" ); + if( strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 ) { + query_guid_t guid_data = (query_guid_t)pPredData; + GList* guid_entry; + + for( name = pParamPath; name != NULL; name = name->next ) { + if( name != pParamPath ) g_string_append( sql, "." ); + g_string_append( sql, name->data ); + } + + if( guid_data->options == QOF_GUID_MATCH_ANY ) { + if( isInverted ) g_string_append( sql, " NOT " ); + g_string_append( sql, " IN (" ); + } + for( guid_entry = guid_data->guids; guid_entry != NULL; guid_entry = guid_entry->next ) { + if( guid_entry != guid_data->guids ) g_string_append( sql, "." ); + (void)guid_to_string_buff( guid_entry->data, val ); + g_string_append( sql, "'" ); + g_string_append( sql, val ); + g_string_append( sql, "'" ); + } + if( guid_data->options == QOF_GUID_MATCH_ANY ) { + g_string_append( sql, ")" ); + } + } + + g_string_append( sql, "(" ); if( isInverted ) { - strcat( sql, "!" ); + g_string_append( sql, "!" ); } for( name = pParamPath; name != NULL; name = name->next ) { - if( name != pParamPath ) strcat( sql, "." ); - strcat( sql, name->data ); + if( name != pParamPath ) g_string_append( sql, "." ); + g_string_append( sql, name->data ); } if( pPredData->how == QOF_COMPARE_LT ) { - strcat( sql, "<" ); + g_string_append( sql, "<" ); } else if( pPredData->how == QOF_COMPARE_LTE ) { - strcat( sql, "<=" ); + g_string_append( sql, "<=" ); } else if( pPredData->how == QOF_COMPARE_EQUAL ) { - strcat( sql, "=" ); + g_string_append( sql, "=" ); } else if( pPredData->how == QOF_COMPARE_GT ) { - strcat( sql, ">" ); + g_string_append( sql, ">" ); } else if( pPredData->how == QOF_COMPARE_GTE ) { - strcat( sql, ">=" ); + g_string_append( sql, ">=" ); } else if( pPredData->how == QOF_COMPARE_NEQ ) { - strcat( sql, "~=" ); + g_string_append( sql, "~=" ); } else { - strcat( sql, "??" ); + g_string_append( sql, "??" ); } if( strcmp( pPredData->type_name, "string" ) == 0 ) { query_string_t pData = (query_string_t)pPredData; - strcat( sql, "'" ); - strcat( sql, pData->matchstring ); - strcat( sql, "'" ); + g_string_append( sql, "'" ); + g_string_append( sql, pData->matchstring ); + g_string_append( sql, "'" ); } else if( strcmp( pPredData->type_name, "date" ) == 0 ) { query_date_t pData = (query_date_t)pPredData; (void)gnc_timespec_to_iso8601_buff( pData->date, val ); - strcat( sql, "'" ); - strncat( sql, val, 4+1+2+1+2 ); - strcat( sql, "'" ); + g_string_append( sql, "'" ); + //g_string_append( sql, val, 4+1+2+1+2 ); + g_string_append( sql, "'" ); } else if( strcmp( pPredData->type_name, "numeric" ) == 0 ) { query_numeric_t pData = (query_numeric_t)pPredData; - strcat( sql, "numeric" ); - } else if( strcmp( pPredData->type_name, "guid" ) == 0 ) { - query_guid_t pData = (query_guid_t)pPredData; - (void)guid_to_string_buff( pData->guids->data, val ); - strcat( sql, "'" ); - strcat( sql, val ); - strcat( sql, "'" ); + g_string_append( sql, "numeric" ); + } else if( strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 ) { } else if( strcmp( pPredData->type_name, "gint32" ) == 0 ) { query_int32_t pData = (query_int32_t)pPredData; sprintf( val, "%d", pData->val ); - strcat( sql, val ); + g_string_append( sql, val ); } else if( strcmp( pPredData->type_name, "gint64" ) == 0 ) { query_int64_t pData = (query_int64_t)pPredData; sprintf( val, "%" G_GINT64_FORMAT, pData->val ); - strcat( sql, val ); + g_string_append( sql, val ); } else if( strcmp( pPredData->type_name, "double" ) == 0 ) { query_double_t pData = (query_double_t)pPredData; sprintf( val, "%f", pData->val ); - strcat( sql, val ); + g_string_append( sql, val ); } else if( strcmp( pPredData->type_name, "boolean" ) == 0 ) { query_boolean_t pData = (query_boolean_t)pPredData; sprintf( val, "%d", pData->val ); - strcat( sql, val ); + g_string_append( sql, val ); } else { g_assert( FALSE ); } - strcat( sql, ")" ); + g_string_append( sql, ")" ); } -#endif static void compile_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) @@ -682,6 +694,8 @@ compile_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) } } +gchar* gnc_sql_compile_query_to_sql( GncSqlBackend* be, QofQuery* query ); + /*@ null @*/ gpointer gnc_sql_compile_query( QofBackend* pBEnd, QofQuery* pQuery ) { @@ -695,6 +709,7 @@ gnc_sql_compile_query( QofBackend* pBEnd, QofQuery* pQuery ) ENTER( " " ); +//gnc_sql_compile_query_to_sql( be, pQuery ); searchObj = qof_query_get_search_for( pQuery ); pQueryInfo = g_malloc( (gsize)sizeof( gnc_sql_query_info ) ); @@ -714,42 +729,60 @@ gnc_sql_compile_query( QofBackend* pBEnd, QofQuery* pQuery ) return be_data.pQueryInfo; } -#if 0 - pBookList = qof_query_get_books( pQuery ); - - /* Convert search object type to table name */ - sprintf( sql, "SELECT * from %s", convert_search_obj( searchObj ) ); - if( !qof_query_has_terms( pQuery ) ) { - strcat( sql, ";" ); - } else { - GList* pOrTerms = qof_query_get_terms( pQuery ); - GList* orTerm; - - strcat( sql, " WHERE " ); - - for( orTerm = pOrTerms; orTerm != NULL; orTerm = orTerm->next ) { - GList* pAndTerms = (GList*)orTerm->data; - GList* andTerm; - - if( orTerm != pOrTerms ) strcat( sql, " OR " ); - strcat( sql, "(" ); - for( andTerm = pAndTerms; andTerm != NULL; andTerm = andTerm->next ) { - if( andTerm != pAndTerms ) strcat( sql, " AND " ); - handle_and_term( (QofQueryTerm*)andTerm->data, sql ); - } - strcat( sql, ")" ); - } - } - - DEBUG( "Compiled: %s\n", sql ); - pQueryInfo->pCompiledQuery = g_strdup( sql ); -#endif - LEAVE( "" ); return pQueryInfo; } +static const gchar* +convert_search_obj( QofIdType objType ) +{ + return (gchar*)objType; +} + +gchar* +gnc_sql_compile_query_to_sql( GncSqlBackend* be, QofQuery* query ) +{ + QofIdType searchObj; + GList* bookList; + GString* sql; + + g_return_val_if_fail( be != NULL, NULL ); + g_return_val_if_fail( query != NULL, NULL ); + + searchObj = qof_query_get_search_for( query ); + bookList = qof_query_get_books( query ); + + /* Convert search object type to table name */ + sql = g_string_new( "" ); + g_string_append( sql, "SELECT * FROM " ); + g_string_append( sql, convert_search_obj( searchObj ) ); + if( !qof_query_has_terms( query ) ) { + g_string_append( sql, ";" ); + } else { + GList* orterms = qof_query_get_terms( query ); + GList* orTerm; + + g_string_append( sql, " WHERE " ); + + for( orTerm = orterms; orTerm != NULL; orTerm = orTerm->next ) { + GList* andterms = (GList*)orTerm->data; + GList* andTerm; + + if( orTerm != orterms ) g_string_append( sql, " OR " ); + g_string_append( sql, "(" ); + for( andTerm = andterms; andTerm != NULL; andTerm = andTerm->next ) { + if( andTerm != andterms ) g_string_append( sql, " AND " ); + handle_and_term( (QofQueryTerm*)andTerm->data, sql ); + } + g_string_append( sql, ")" ); + } + } + + DEBUG( "Compiled: %s\n", sql->str ); + return g_string_free( sql, FALSE ); +} + static void free_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) { @@ -1520,6 +1553,25 @@ typedef void (*TimespecSetterFunc)( const gpointer, Timespec ); #define TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d" #define TIMESPEC_COL_SIZE (4+2+2+2+2+2) +gchar* +gnc_sql_convert_timespec_to_string( Timespec ts ) +{ + time_t time; + struct tm* tm; + gint year; + gchar* datebuf; + + time = timespecToTime_t( ts ); + tm = gmtime( &time ); + + if( tm->tm_year < 60 ) year = tm->tm_year + 2000; + else year = tm->tm_year + 1900; + + datebuf = g_strdup_printf( TIMESPEC_STR_FORMAT, + year, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec ); + return datebuf; +} + static void load_timespec( const GncSqlBackend* be, GncSqlRow* row, /*@ null @*/ QofSetterFunc setter, gpointer pObject, @@ -1582,9 +1634,6 @@ add_gvalue_timespec_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, TimespecAccessFunc ts_getter; Timespec ts; gchar* datebuf; - time_t time; - struct tm* tm; - gint year; GValue* value; g_return_if_fail( be != NULL ); @@ -1597,14 +1646,7 @@ add_gvalue_timespec_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, g_return_if_fail( ts_getter != NULL ); ts = (*ts_getter)( pObject ); - time = timespecToTime_t( ts ); - tm = gmtime( &time ); - - if( tm->tm_year < 60 ) year = tm->tm_year + 2000; - else year = tm->tm_year + 1900; - - datebuf = g_strdup_printf( TIMESPEC_STR_FORMAT, - year, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec ); + datebuf = gnc_sql_convert_timespec_to_string( ts ); value = g_new0( GValue, 1 ); g_assert( value != NULL ); (void)g_value_init( value, G_TYPE_STRING ); diff --git a/src/backend/sql/gnc-backend-sql.h b/src/backend/sql/gnc-backend-sql.h index 27b57dc8f9..b0dd4860c4 100644 --- a/src/backend/sql/gnc-backend-sql.h +++ b/src/backend/sql/gnc-backend-sql.h @@ -678,6 +678,14 @@ gboolean gnc_sql_commit_standard_item( GncSqlBackend* be, QofInstance* inst, con */ gint64 gnc_sql_get_integer_value( const GValue* value ); +/** + * Converts a Timespec value to a string value for the database. + * + * @param ts Timespec to be converted + * @return String representation of the Timespec + */ +gchar* gnc_sql_convert_timespec_to_string( Timespec ts ); + /** * Upgrades a table to a new structure. The upgrade is done by creating a new table with * the new structure, SELECTing the old data into the new table, deleting the old table, diff --git a/src/backend/sql/gnc-transaction-sql.c b/src/backend/sql/gnc-transaction-sql.c index 699f85442c..5d92a12d2a 100644 --- a/src/backend/sql/gnc-transaction-sql.c +++ b/src/backend/sql/gnc-transaction-sql.c @@ -47,6 +47,8 @@ #include "gnc-engine.h" +#include "escape.h" + #ifdef S_SPLINT_S #include "splint-defs.h" #endif @@ -722,42 +724,316 @@ void gnc_sql_transaction_load_all_tx( GncSqlBackend* be ) } } +static void +convert_query_comparison_to_sql( QofQueryPredData* pPredData, gboolean isInverted, GString* sql ) +{ + if( pPredData->how == QOF_COMPARE_LT + || ( isInverted && pPredData->how == QOF_COMPARE_GTE ) ) { + g_string_append( sql, "<" ); + } else if( pPredData->how == QOF_COMPARE_LTE + || ( isInverted && pPredData->how == QOF_COMPARE_GT ) ) { + g_string_append( sql, "<=" ); + } else if( pPredData->how == QOF_COMPARE_EQUAL + || ( isInverted && pPredData->how == QOF_COMPARE_NEQ ) ) { + g_string_append( sql, "=" ); + } else if( pPredData->how == QOF_COMPARE_GT + || ( isInverted && pPredData->how == QOF_COMPARE_LTE ) ) { + g_string_append( sql, ">" ); + } else if( pPredData->how == QOF_COMPARE_GTE + || ( isInverted && pPredData->how == QOF_COMPARE_LT ) ) { + g_string_append( sql, ">=" ); + } else if( pPredData->how == QOF_COMPARE_NEQ + || ( isInverted && pPredData->how == QOF_COMPARE_EQUAL ) ) { + g_string_append( sql, "~=" ); + } else { + PERR( "Unknown comparison type\n" ); + g_string_append( sql, "??" ); + } +} + +static void +convert_query_term_to_sql( const gchar* fieldName, QofQueryTerm* pTerm, GString* sql ) +{ + GSList* pParamPath; + QofQueryPredData* pPredData; + gboolean isInverted; + GSList* name; + gchar val[GUID_ENCODING_LENGTH+1]; + + g_return_if_fail( pTerm != NULL ); + g_return_if_fail( sql != NULL ); + + pParamPath = qof_query_term_get_param_path( pTerm ); + pPredData = qof_query_term_get_pred_data( pTerm ); + isInverted = qof_query_term_is_inverted( pTerm ); + + if( safe_strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 ) { + query_guid_t guid_data = (query_guid_t)pPredData; + GList* guid_entry; + + g_string_append( sql, "(" ); + g_string_append( sql, fieldName ); + + switch( guid_data->options ) { + case QOF_GUID_MATCH_ANY: + if( isInverted ) g_string_append( sql, " NOT IN (" ); + else g_string_append( sql, " IN (" ); + break; + + case QOF_GUID_MATCH_NONE: + if( isInverted ) g_string_append( sql, " IN (" ); + else g_string_append( sql, " NOT IN (" ); + break; + + default: + PERR( "Unexpected GUID match type: %d\n", guid_data->options ); + } + + for( guid_entry = guid_data->guids; guid_entry != NULL; guid_entry = guid_entry->next ) { + if( guid_entry != guid_data->guids ) g_string_append( sql, "," ); + (void)guid_to_string_buff( guid_entry->data, val ); + g_string_append( sql, "'" ); + g_string_append( sql, val ); + g_string_append( sql, "'" ); + break; + } + g_string_append( sql, ")" ); + g_string_append( sql, ")" ); + return; + + } else if( safe_strcmp( pPredData->type_name, QOF_TYPE_CHAR ) == 0 ) { + query_char_t char_data = (query_char_t)pPredData; + int i; + + if( isInverted ) { + g_string_append( sql, "NOT(" ); + } + if( char_data->options == QOF_CHAR_MATCH_NONE ) { + g_string_append( sql, "NOT " ); + } + g_string_append( sql, "(" ); + for( i = 0; char_data->char_list[i] != '\0'; i++ ) { + if( i != 0 ) { + g_string_append( sql, " OR " ); + } + g_string_append( sql, fieldName ); + g_string_append( sql, " = '" ); + g_string_append_c( sql, char_data->char_list[i] ); + g_string_append( sql, "'" ); + } + g_string_append( sql, ") " ); + if( isInverted ) { + g_string_append( sql, ") " ); + } + return; + + } else if( safe_strcmp( pPredData->type_name, QOF_TYPE_DATE ) == 0 ) { + query_date_t date_data = (query_date_t)pPredData; + gchar* datebuf; + + g_string_append( sql, "(" ); + g_string_append( sql, fieldName ); + convert_query_comparison_to_sql( pPredData, isInverted, sql ); + datebuf = gnc_sql_convert_timespec_to_string( date_data->date ); + g_string_append( sql, "'" ); + g_string_append( sql, datebuf ); + g_string_append( sql, "')" ); + return; + + } else if( safe_strcmp( pPredData->type_name, QOF_TYPE_STRING ) == 0 ) { + query_string_t string_data = (query_string_t)pPredData; + sqlEscape* escape = sqlEscape_new(); + + if( isInverted ) { + g_string_append( sql, "NOT(" ); + } + if( pPredData->how == QOF_COMPARE_NEQ ) { + g_string_append( sql, "NOT(" ); + } + g_string_append( sql, fieldName ); + if( string_data->is_regex || string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE ) { + PWARN( "String is_regex || option = QOF_STRING_MATCH_INSENSITIVE\n" ); + } +// g_string_append( sql, " ~" ); +// } else { + g_string_append( sql, " =" ); +// } +// if( string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE ) { +// g_string_append( sql, "*" ); +// } + g_string_append( sql, "'" ); + g_string_append( sql, sqlEscapeString( escape, string_data->matchstring ) ); + g_string_append( sql, "'" ); + if( pPredData->how == QOF_COMPARE_NEQ ) { + g_string_append( sql, ")" ); + } + if( isInverted ) { + g_string_append( sql, ")" ); + } + sqlEscape_destroy( escape ); + return; + } + + g_string_append( sql, "(" ); + g_string_append( sql, fieldName ); + convert_query_comparison_to_sql( pPredData, isInverted, sql ); + + if( strcmp( pPredData->type_name, "numeric" ) == 0 ) { + query_numeric_t pData = (query_numeric_t)pPredData; + + g_string_append( sql, "numeric" ); + } else if( strcmp( pPredData->type_name, "gint32" ) == 0 ) { + query_int32_t pData = (query_int32_t)pPredData; + + sprintf( val, "%d", pData->val ); + g_string_append( sql, val ); + } else if( strcmp( pPredData->type_name, "gint64" ) == 0 ) { + query_int64_t pData = (query_int64_t)pPredData; + + sprintf( val, "%" G_GINT64_FORMAT, pData->val ); + g_string_append( sql, val ); + } else if( strcmp( pPredData->type_name, "double" ) == 0 ) { + query_double_t pData = (query_double_t)pPredData; + + sprintf( val, "%f", pData->val ); + g_string_append( sql, val ); + } else if( strcmp( pPredData->type_name, "boolean" ) == 0 ) { + query_boolean_t pData = (query_boolean_t)pPredData; + + sprintf( val, "%d", pData->val ); + g_string_append( sql, val ); + } else { + PERR( "Unknown query predicate type: %s\n", pPredData->type_name ); + } + + g_string_append( sql, ")" ); +} + typedef struct { GncSqlStatement* stmt; - Account* acct; gboolean has_been_run; } split_query_info_t; static /*@ null @*/ gpointer -compile_split_query( GncSqlBackend* be, QofQuery* pQuery ) +compile_split_query( GncSqlBackend* be, QofQuery* query ) { const GUID* acct_guid; gchar guid_buf[GUID_ENCODING_LENGTH+1]; split_query_info_t* query_info = NULL; gchar* subquery_sql; gchar* query_sql; + GString* subquery_tables; + gboolean has_transactions_table = FALSE; g_return_val_if_fail( be != NULL, NULL ); - g_return_val_if_fail( pQuery != NULL, NULL ); + g_return_val_if_fail( query != NULL, NULL ); - acct_guid = get_guid_from_query( pQuery ); - if( acct_guid != NULL ) { - (void)guid_to_string_buff( acct_guid, guid_buf ); - subquery_sql = g_strdup_printf( "SELECT DISTINCT tx_guid FROM %s WHERE account_guid='%s'", SPLIT_TABLE, guid_buf ); - query_sql = g_strdup_printf( "SELECT * FROM %s WHERE guid IN (%s)", TRANSACTION_TABLE, subquery_sql ); + query_info = g_malloc( (gsize)sizeof(split_query_info_t) ); + g_assert( query_info != NULL ); + query_info->has_been_run = FALSE; - query_info = g_malloc( (gsize)sizeof(split_query_info_t) ); - g_assert( query_info != NULL ); + subquery_tables = g_string_new( SPLIT_TABLE ); + if( qof_query_has_terms( query ) ) { + GList* orterms = qof_query_get_terms( query ); + GList* orTerm; + GString* sql = g_string_new( "" ); + gboolean need_OR = FALSE; + + for( orTerm = orterms; orTerm != NULL; orTerm = orTerm->next ) { + GList* andterms = (GList*)orTerm->data; + GList* andTerm; + gboolean need_AND = FALSE; + gboolean has_tx_guid_check = FALSE; + + if( need_OR ) { + g_string_append( sql, " OR " ); + } + g_string_append( sql, "(" ); + for( andTerm = andterms; andTerm != NULL; andTerm = andTerm->next ) { + QofQueryTerm* term; + GSList* paramPath; + gboolean unknownPath = FALSE; + + term = (QofQueryTerm*)andTerm->data; + paramPath = qof_query_term_get_param_path( term ); + + if( strcmp( paramPath->data, QOF_PARAM_BOOK ) == 0 ) continue; + + if( need_AND ) g_string_append( sql, " AND " ); + + if( strcmp( paramPath->data, SPLIT_ACCOUNT ) == 0 + && strcmp( paramPath->next->data, QOF_PARAM_GUID ) == 0 ) { + convert_query_term_to_sql( "account_guid", term, sql ); + + } else if( strcmp( paramPath->data, SPLIT_RECONCILE ) == 0 ) { + convert_query_term_to_sql( "reconcile_state", term, sql ); + + } else if( strcmp( paramPath->data, SPLIT_TRANS ) == 0 ) { + if( !has_transactions_table ) { + g_string_append( subquery_tables, ", " ); + g_string_append( subquery_tables, TRANSACTION_TABLE ); + has_transactions_table = TRUE; + } + if( !has_tx_guid_check ) { + g_string_append( sql, "(splits.tx_guid = transactions.guid) AND " ); + has_tx_guid_check = TRUE; + } + if( strcmp( paramPath->next->data, TRANS_DATE_POSTED ) == 0 ) { + convert_query_term_to_sql( "transactions.post_date", term, sql ); + } else if( strcmp( paramPath->next->data, TRANS_DESCRIPTION ) == 0 ) { + convert_query_term_to_sql( "transactions.description", term, sql ); + } else { + unknownPath = TRUE; + } + + } else { + unknownPath = TRUE; + } + + if( unknownPath ) { + GString* name = g_string_new( (gchar*)paramPath->data ); + while( paramPath->next != NULL ) { + g_string_append( name, "." ); + g_string_append( name, paramPath->next->data ); + paramPath = paramPath->next; + } + PERR( "Unknown SPLIT query field: %s\n", name->str ); + g_string_free( name, TRUE ); + } + need_AND = TRUE; + } + + /* If the last char in the string is a '(', then for some reason, there were + no terms added to the SQL. If so, remove it and ignore the OR term. */ + if( sql->str[sql->len-1] == '(' ) { + g_string_truncate( sql, sql->len-1 ); + need_OR = FALSE; + } else { + g_string_append( sql, ")" ); + need_OR = TRUE; + } + } + + if( sql->len != 0 ) { + subquery_sql = g_strdup_printf( "SELECT DISTINCT tx_guid FROM %s WHERE %s", + g_string_free( subquery_tables, FALSE ), + sql->str ); + query_sql = g_strdup_printf( "SELECT * FROM %s WHERE guid IN (%s)", + TRANSACTION_TABLE, subquery_sql ); + g_free( subquery_sql ); + } else { + query_sql = g_strdup_printf( "SELECT * FROM %s", TRANSACTION_TABLE ); + } + query_info->stmt = gnc_sql_create_statement_from_sql( be, query_sql ); + + g_string_free( sql, TRUE ); + g_free( query_sql ); + + } else { + query_sql = g_strdup_printf( "SELECT * FROM %s", TRANSACTION_TABLE ); query_info->stmt = gnc_sql_create_statement_from_sql( be, query_sql ); g_free( query_sql ); - query_info->has_been_run = FALSE; - query_info->acct = xaccAccountLookup( acct_guid, be->primary_book ); - if( query_info->acct == NULL ) { - PWARN( "Unable to find account with guid='%s'\n", guid_buf ); - } - - g_free( subquery_sql ); } return query_info; @@ -770,16 +1046,12 @@ run_split_query( GncSqlBackend* be, gpointer pQuery ) g_return_if_fail( be != NULL ); g_return_if_fail( pQuery != NULL ); - g_return_if_fail( query_info->acct != NULL ); - // When the query to load all splits for the account has been run, set the - // mark so that this account's query is not reexecuted. - if( !query_info->has_been_run && xaccAccountGetMark( query_info->acct ) == 0 ) { + if( !query_info->has_been_run ) { query_transactions( be, query_info->stmt ); query_info->has_been_run = TRUE; gnc_sql_statement_dispose( query_info->stmt ); query_info->stmt = NULL; - xaccAccountSetMark( query_info->acct, 1 ); } }