SQL QUERY API: As an alternative to building queries one predicate at a time, you can use the SQL query interface. This interface will accept a string containing an SQL query, parse it, convert it into the core representation, and execute it.
STRUCTURE OF A QUERY: A Query is a logical function of any number of QueryTerms. A QueryTerm consists of a C function pointer (the Predicate) and a PredicateData structure containing data passed to the predicate funtion. The PredicateData structure is a constant associated with the Term and is identical for every object that is tested.
The terms of the Query may represent any logical function and are stored in canonical form, i.e. the function is expressed as a logical sum of logical products. So if you have QueryTerms a, b, c, d, e and you have the logical function a(b+c) + !(c(d+e)), it gets stored as ab + ac + !c + !c!e +!d!c + !d!e. This may not be optimal for evaluation of some functions but it's easy to store, easy to manipulate, and it doesn't require a complete algebra system to deal with.
The representation is of a GList of GLists of QueryTerms. The "backbone" GList q->terms represents the OR-chain, and every item on the backbone is a GList of QueryTerms representing an AND-chain corresponding to a single product-term in the canonical representation. QueryTerms are duplicated when necessary to fill out the canonical form, and the same predicate may be evaluated multiple times per split for complex queries. This is a place where we could probably optimize.
| #define QOF_PARAM_BOOK "book" |
"Known" Object Parameters -- all objects must support these
Definition at line 103 of file qofquery.h.
| #define QOF_PARAM_KVP "kvp" |
"Known" Object Parameters -- some objects might support these
Definition at line 107 of file qofquery.h.
| #define QOF_QUERY_FIRST_TERM QOF_QUERY_AND |
First/only term is same as 'and'
Definition at line 97 of file qofquery.h.
| #define QUERY_DEFAULT_SORT "QofQueryDefaultSort" |
Default sort object type
Definition at line 100 of file qofquery.h.
A Query
Definition at line 85 of file qofquery.h.
| typedef struct _QofQueryPredData QofQueryPredData |
PREDICATE DATA TYPES: All the predicate data types are rolled up into the union type PredicateData. The "type" field specifies which type the union is.
Definition at line 45 of file qofquerycore.h.
| enum QofCharMatch |
A CHAR type is for a RECNCell, Comparisons for QOF_TYPE_CHAR 'ANY' will match any charagter in the string.
Match 'ANY' is a convenience/performance-enhanced predicate for the compound statement (value==char1) || (value==char2) || etc. Match 'NONE' is equivalent to (value != char1) && (value != char2) && etc.
Definition at line 121 of file qofquerycore.h.
00121 { 00122 QOF_CHAR_MATCH_ANY = 1, 00123 QOF_CHAR_MATCH_NONE 00124 } QofCharMatch;
| enum QofDateMatch |
Comparisons for QOF_TYPE_DATE The QOF_DATE_MATCH_DAY comparison rounds the two time values to mid-day and then compares these rounded values. The QOF_DATE_MATCH_NORMAL comparison matches the time values, down to the second.
Definition at line 75 of file qofquerycore.h.
00075 { 00076 QOF_DATE_MATCH_NORMAL = 1, 00077 QOF_DATE_MATCH_DAY 00078 } QofDateMatch;
| enum QofGuidMatch |
Definition at line 99 of file qofquerycore.h.
00099 { 00102 QOF_GUID_MATCH_ANY = 1, 00103 QOF_GUID_MATCH_NONE, 00104 QOF_GUID_MATCH_NULL, 00107 QOF_GUID_MATCH_ALL, 00110 QOF_GUID_MATCH_LIST_ANY, 00111 } QofGuidMatch;
| enum QofNumericMatch |
Comparisons for QOF_TYPE_NUMERIC, QOF_TYPE_DEBCRED
XXX Should be deprecated, or at least wrapped up as a convenience function, this is based on the old bill gribble code, which assumed the amount was always positive, and then specified a funds-flow direction (credit, debit, or either).
The point being that 'match credit' is equivalent to the compound predicate (amount >= 0) && (amount 'op' value) while the 'match debit' predicate is equivalent to (amount <= 0) && (abs(amount) 'op' value)
Definition at line 92 of file qofquerycore.h.
00092 { 00093 QOF_NUMERIC_MATCH_DEBIT = 1, 00094 QOF_NUMERIC_MATCH_CREDIT, 00095 QOF_NUMERIC_MATCH_ANY 00096 } QofNumericMatch;
| enum QofQueryCompare |
Standard Query comparitors, for how to compare objects in a predicate. Note that not all core types implement all comparitors
| QOF_COMPARE_LT | |
| QOF_COMPARE_LTE | |
| QOF_COMPARE_EQUAL | |
| QOF_COMPARE_GT | |
| QOF_COMPARE_GTE | |
| QOF_COMPARE_NEQ |
Definition at line 50 of file qofquerycore.h.
00050 { 00051 QOF_COMPARE_LT = 1, 00052 QOF_COMPARE_LTE, 00053 QOF_COMPARE_EQUAL, 00054 QOF_COMPARE_GT, 00055 QOF_COMPARE_GTE, 00056 QOF_COMPARE_NEQ 00057 } QofQueryCompare;
| enum QofQueryOp |
Query Term Operators, for combining Query Terms
Definition at line 88 of file qofquery.h.
00088 { 00089 QOF_QUERY_AND=1, 00090 QOF_QUERY_OR, 00091 QOF_QUERY_NAND, 00092 QOF_QUERY_NOR, 00093 QOF_QUERY_XOR 00094 } QofQueryOp;
| enum QofStringMatch |
List of known core query data-types... Each core query type defines it's set of optional "comparitor qualifiers".
Definition at line 63 of file qofquerycore.h.
00063 { 00064 QOF_STRING_MATCH_NORMAL = 1, 00065 QOF_STRING_MATCH_CASEINSENSITIVE 00066 } QofStringMatch;
| void qof_query_add_boolean_match | ( | QofQuery * | q, | |
| GSList * | param_list, | |||
| gboolean | value, | |||
| QofQueryOp | op | |||
| ) |
Handy-dandy convenience routines, avoids having to create a separate predicate for boolean matches. We might want to create handy-dandy sugar routines for the other predicate types as well.
Definition at line 1282 of file qofquery.c.
01284 { 01285 QofQueryPredData *pdata; 01286 if (!q || !param_list) return; 01287 01288 pdata = qof_query_boolean_predicate (QOF_COMPARE_EQUAL, value); 01289 qof_query_add_term (q, param_list, pdata, op); 01290 }
| void qof_query_add_guid_list_match | ( | QofQuery * | q, | |
| GSList * | param_list, | |||
| GList * | guid_list, | |||
| QofGuidMatch | options, | |||
| QofQueryOp | op | |||
| ) |
DOCUMENT ME !!
Definition at line 1230 of file qofquery.c.
01233 { 01234 QofQueryPredData *pdata; 01235 01236 if (!q || !param_list) return; 01237 01238 if (!guid_list) 01239 g_return_if_fail (options == QOF_GUID_MATCH_NULL); 01240 01241 pdata = qof_query_guid_predicate (options, guid_list); 01242 qof_query_add_term (q, param_list, pdata, op); 01243 }
| void qof_query_add_guid_match | ( | QofQuery * | q, | |
| GSList * | param_list, | |||
| const GUID * | guid, | |||
| QofQueryOp | op | |||
| ) |
DOCUMENT ME !!
Definition at line 1245 of file qofquery.c.
01247 { 01248 GList *g = NULL; 01249 01250 if (!q || !param_list) return; 01251 01252 if (guid) 01253 g = g_list_prepend (g, (gpointer)guid); 01254 01255 qof_query_add_guid_list_match (q, param_list, g, 01256 g ? QOF_GUID_MATCH_ANY : QOF_GUID_MATCH_NULL, op); 01257 01258 g_list_free (g); 01259 }
| void qof_query_add_term | ( | QofQuery * | query, | |
| GSList * | param_list, | |||
| QofQueryPredData * | pred_data, | |||
| QofQueryOp | op | |||
| ) |
This is the general function that adds a new Query Term to a query. It will find the 'obj_type' object of the search item and compare the 'param_list' parameter to the predicate data via the comparitor.
The param_list is a recursive list of parameters. For example, you can say 'split->memo' by creating a list of one element, "SPLIT_MEMO". You can say 'split->account->name' by creating a list of two elements, "SPLIT_ACCOUNT" and "ACCOUNT_NAME". The list becomes the property of the Query.
For example:
acct_name_pred_data = make_string_pred_data(QOF_STRING_MATCH_CASEINSENSITIVE, account_name); param_list = make_list (SPLIT_ACCOUNT, ACCOUNT_NAME, NULL); qof_query_add_term (query, param_list, QOF_COMPARE_EQUAL, acct_name_pred_data, QOF_QUERY_AND);
Please note that QofQuery does not, at this time, support joins. That is, one cannot specify a predicate that is a parameter list. Put another way, one cannot search for objects where obja->thingy == objb->stuff
Definition at line 634 of file qofquery.c.
00636 { 00637 QofQueryTerm *qt; 00638 QofQuery *qr, *qs; 00639 00640 if (!q || !param_list || !pred_data) return; 00641 00642 qt = g_new0 (QofQueryTerm, 1); 00643 qt->param_list = param_list; 00644 qt->pdata = pred_data; 00645 qs = qof_query_create (); 00646 query_init (qs, qt); 00647 00648 if (qof_query_has_terms (q)) 00649 qr = qof_query_merge (q, qs, op); 00650 else 00651 qr = qof_query_merge (q, qs, QOF_QUERY_OR); 00652 00653 swap_terms (q, qr); 00654 qof_query_destroy (qs); 00655 qof_query_destroy (qr); 00656 }
| void qof_query_clear | ( | QofQuery * | query | ) |
Remove all query terms from query. query matches nothing after qof_query_clear().
Definition at line 850 of file qofquery.c.
00851 { 00852 QofQuery *q2 = qof_query_create (); 00853 swap_terms (query, q2); 00854 qof_query_destroy (q2); 00855 00856 g_list_free (query->books); 00857 query->books = NULL; 00858 g_list_free (query->results); 00859 query->results = NULL; 00860 query->changed = 1; 00861 }
Make a copy of the indicated query
Definition at line 956 of file qofquery.c.
00957 { 00958 QofQuery *copy; 00959 GHashTable *ht; 00960 00961 if (!q) return NULL; 00962 copy = qof_query_create (); 00963 ht = copy->be_compiled; 00964 free_members (copy); 00965 00966 memcpy (copy, q, sizeof (QofQuery)); 00967 00968 copy->be_compiled = ht; 00969 copy->terms = copy_or_terms (q->terms); 00970 copy->books = g_list_copy (q->books); 00971 copy->results = g_list_copy (q->results); 00972 00973 copy_sort (&(copy->primary_sort), &(q->primary_sort)); 00974 copy_sort (&(copy->secondary_sort), &(q->secondary_sort)); 00975 copy_sort (&(copy->tertiary_sort), &(q->tertiary_sort)); 00976 00977 copy->changed = 1; 00978 00979 return copy; 00980 }
| QofQueryPredData* qof_query_core_predicate_copy | ( | const QofQueryPredData * | pdata | ) |
Copy a predicate.
Definition at line 1822 of file qofquerycore.c.
01823 { 01824 QueryPredicateCopyFunc copy; 01825 01826 g_return_val_if_fail (pdata, NULL); 01827 g_return_val_if_fail (pdata->type_name, NULL); 01828 01829 copy = qof_query_copy_predicate (pdata->type_name); 01830 return (copy (pdata)); 01831 }
| void qof_query_core_predicate_free | ( | QofQueryPredData * | pdata | ) |
Destroy a predicate.
Definition at line 1810 of file qofquerycore.c.
01811 { 01812 QueryPredDataFree free_fcn; 01813 01814 g_return_if_fail (pdata); 01815 g_return_if_fail (pdata->type_name); 01816 01817 free_fcn = qof_query_predicate_free (pdata->type_name); 01818 free_fcn (pdata); 01819 }
Return a printable string for a core data object. Caller needs to g_free() the returned string.
Definition at line 1834 of file qofquerycore.c.
01836 { 01837 QueryToString toString; 01838 01839 g_return_val_if_fail (type, NULL); 01840 g_return_val_if_fail (object, NULL); 01841 g_return_val_if_fail (getter, NULL); 01842 01843 toString = g_hash_table_lookup (toStringTable, type); 01844 g_return_val_if_fail (toString, NULL); 01845 01846 return toString (object, getter); 01847 }
| QofQuery* qof_query_create | ( | void | ) |
Create a new query. Before running the query, a 'search-for' type must be set otherwise nothing will be returned. The results of the query is a list of the indicated search-for type.
Allocates and initializes a Query structure which must be freed by the user with qof_query_destroy(). A newly-allocated QofQuery object matches nothing (qof_query_run() will return NULL).
Definition at line 863 of file qofquery.c.
00864 { 00865 QofQuery *qp = g_new0 (QofQuery, 1); 00866 qp->be_compiled = g_hash_table_new (g_direct_hash, g_direct_equal); 00867 query_init (qp, NULL); 00868 return qp; 00869 }
| gboolean qof_query_date_predicate_get_date | ( | const QofQueryPredData * | pd, | |
| Timespec * | date | |||
| ) |
Retrieve a predicate.
Definition at line 412 of file qofquerycore.c.
00413 { 00414 const query_date_t pdata = (const query_date_t)pd; 00415 00416 if (pdata->pd.type_name != query_date_type) 00417 return FALSE; 00418 *date = pdata->date; 00419 return TRUE; 00420 }
| void qof_query_destroy | ( | QofQuery * | q | ) |
Frees the resources associate with a Query object.
Definition at line 947 of file qofquery.c.
00948 { 00949 if (!q) return; 00950 free_members (q); 00951 query_clear_compiles (q); 00952 g_hash_table_destroy (q->be_compiled); 00953 g_free (q); 00954 }
Compare two queries for equality. Query terms are compared each to each. This is a simplistic implementation -- logical equivalences between different and/or trees are ignored.
Definition at line 1407 of file qofquery.c.
01408 { 01409 GList *or1, *or2; 01410 01411 if (q1 == q2) return TRUE; 01412 if (!q1 || !q2) return FALSE; 01413 01414 if (g_list_length (q1->terms) != g_list_length (q2->terms)) return FALSE; 01415 if (q1->max_results != q2->max_results) return FALSE; 01416 01417 for (or1 = q1->terms, or2 = q2->terms; or1; 01418 or1 = or1->next, or2 = or2->next) 01419 { 01420 GList *and1, *and2; 01421 01422 and1 = or1->data; 01423 and2 = or2->data; 01424 01425 if (g_list_length (and1) != g_list_length (and2)) return FALSE; 01426 01427 for ( ; and1; and1 = and1->next, and2 = and2->next) 01428 if (!qof_query_term_equal (and1->data, and2->data)) 01429 return FALSE; 01430 } 01431 01432 if (!qof_query_sort_equal (&(q1->primary_sort), &(q2->primary_sort))) 01433 return FALSE; 01434 if (!qof_query_sort_equal (&(q1->secondary_sort), &(q2->secondary_sort))) 01435 return FALSE; 01436 if (!qof_query_sort_equal (&(q1->tertiary_sort), &(q2->tertiary_sort))) 01437 return FALSE; 01438 01439 return TRUE; 01440 }
| GList* qof_query_get_books | ( | QofQuery * | q | ) |
Return the list of books we're using
Definition at line 1276 of file qofquery.c.
01277 { 01278 if (!q) return NULL; 01279 return q->books; 01280 }
Return the type of data we're querying for
Definition at line 1315 of file qofquery.c.
01316 { 01317 if (!q) return NULL; 01318 return q->search_for; 01319 }
| gboolean qof_query_has_term_type | ( | QofQuery * | q, | |
| GSList * | term_param | |||
| ) |
DOCUMENT ME !!
Definition at line 908 of file qofquery.c.
00909 { 00910 GList *or; 00911 GList *and; 00912 00913 if (!q || !term_param) 00914 return FALSE; 00915 00916 for(or = q->terms; or; or = or->next) { 00917 for(and = or->data; and; and = and->next) { 00918 QofQueryTerm *qt = and->data; 00919 if (!param_list_cmp (term_param, qt->param_list)) 00920 return TRUE; 00921 } 00922 } 00923 00924 return FALSE; 00925 }
| int qof_query_has_terms | ( | QofQuery * | q | ) |
Return boolean FALSE if there are no terms in the query Can be used as a predicate to see if the query has been initialized (return value > 0) or is "blank" (return value == 0).
Definition at line 892 of file qofquery.c.
00893 { 00894 if (!q) return 0; 00895 return g_list_length (q->terms); 00896 }
| void qof_query_init | ( | void | ) |
Subsystem initialization and shutdown. Call init() once to initalize the query subsytem; call shutdown() to free up any resources associated with the query subsystem. Typically called during application startup, shutdown.
Definition at line 1295 of file qofquery.c.
01296 { 01297 ENTER (" "); 01298 qof_query_core_init (); 01299 qof_class_init (); 01300 LEAVE ("Completed initialization of QofQuery"); 01301 }
Make a copy of the indicated query, inverting the sense of the search. In other words, if the original query search for all objects with a certain condition, the inverted query will search for all object with NOT that condition. The union of the results returned by the original and inverted queries equals the set of all searched objects. These to sets are disjoint (share no members in common).
This will return a newly allocated QofQuery object, or NULL on error. Free it with qof_query_destroy() when no longer needed.
Definition at line 988 of file qofquery.c.
00989 { 00990 QofQuery * retval; 00991 QofQuery * right, * left, * iright, * ileft; 00992 QofQueryTerm * qt; 00993 GList * aterms; 00994 GList * cur; 00995 GList * new_oterm; 00996 int num_or_terms; 00997 00998 if (!q) 00999 return NULL; 01000 01001 num_or_terms = g_list_length(q->terms); 01002 01003 switch(num_or_terms) 01004 { 01005 case 0: 01006 retval = qof_query_create(); 01007 retval->max_results = q->max_results; 01008 break; 01009 01010 /* This is the DeMorgan expansion for a single AND expression. */ 01011 /* !(abc) = !a + !b + !c */ 01012 case 1: 01013 retval = qof_query_create(); 01014 retval->max_results = q->max_results; 01015 retval->books = g_list_copy (q->books); 01016 retval->search_for = q->search_for; 01017 retval->changed = 1; 01018 01019 aterms = g_list_nth_data(q->terms, 0); 01020 new_oterm = NULL; 01021 for(cur=aterms; cur; cur=cur->next) { 01022 qt = copy_query_term(cur->data); 01023 qt->invert = !(qt->invert); 01024 new_oterm = g_list_append(NULL, qt); 01025 retval->terms = g_list_prepend(retval->terms, new_oterm); 01026 } 01027 retval->terms = g_list_reverse(retval->terms); 01028 break; 01029 01030 /* If there are multiple OR-terms, we just recurse by 01031 * breaking it down to !(a + b + c) = 01032 * !a * !(b + c) = !a * !b * !c. */ 01033 default: 01034 right = qof_query_create(); 01035 right->terms = copy_or_terms(g_list_nth(q->terms, 1)); 01036 01037 left = qof_query_create(); 01038 left->terms = g_list_append(NULL, 01039 copy_and_terms(g_list_nth_data(q->terms, 0))); 01040 01041 iright = qof_query_invert(right); 01042 ileft = qof_query_invert(left); 01043 01044 retval = qof_query_merge(iright, ileft, QOF_QUERY_AND); 01045 retval->books = g_list_copy (q->books); 01046 retval->max_results = q->max_results; 01047 retval->search_for = q->search_for; 01048 retval->changed = 1; 01049 01050 qof_query_destroy(iright); 01051 qof_query_destroy(ileft); 01052 qof_query_destroy(right); 01053 qof_query_destroy(left); 01054 break; 01055 } 01056 01057 return retval; 01058 }
| QofQueryPredData* qof_query_kvp_predicate | ( | QofQueryCompare | how, | |
| GSList * | path, | |||
| const KvpValue * | value | |||
| ) |
The qof_query_kvp_predicate() matches the object that has the value 'value' located at the path 'path'. In a certain sense, the 'path' is handled as if it were a paramter.
Definition at line 1268 of file qofquerycore.c.
01270 { 01271 query_kvp_t pdata; 01272 GSList *node; 01273 01274 g_return_val_if_fail (path && value, NULL); 01275 01276 pdata = g_new0 (query_kvp_def, 1); 01277 pdata->pd.type_name = query_kvp_type; 01278 pdata->pd.how = how; 01279 pdata->value = kvp_value_copy (value); 01280 pdata->path = g_slist_copy (path); 01281 for (node = pdata->path; node; node = node->next) 01282 node->data = g_strdup (node->data); 01283 01284 return ((QofQueryPredData*)pdata); 01285 }
| QofQueryPredData* qof_query_kvp_predicate_path | ( | QofQueryCompare | how, | |
| const gchar * | path, | |||
| const KvpValue * | value | |||
| ) |
Same predicate as above, except that 'path' is assumed to be a string containing slash-separated pathname.
| GList* qof_query_last_run | ( | QofQuery * | query | ) |
Return the results of the last query, without causing the query to be re-run. Do NOT free the resulting list. This list is managed internally by QofQuery.
Definition at line 842 of file qofquery.c.
00843 { 00844 if (!query) 00845 return NULL; 00846 00847 return query->results; 00848 }
| QofQuery* qof_query_merge | ( | QofQuery * | q1, | |
| QofQuery * | q2, | |||
| QofQueryOp | op | |||
| ) |
Combine two queries together using the Boolean set (logical) operator 'op'. For example, if the operator 'op' is set to QUERY_AND, then the set of results returned by the query will will be the Boolean set intersection of the results returned by q1 and q2. Similarly, QUERY_OR maps to set union, etc.
Both queries must have compatible search-types. If both queries are set, they must search for the same object type. If only one is set, the resulting query will search for the set type. If neither query has the search-type set, the result will be unset as well.
This will return a newly allocated QofQuery object, or NULL on error. Free it with qof_query_destroy() when no longer needed. Note that if either input query is NULL then the returned query is NOT newly allocated -- it will return the non-NULL query. You only need to call this function when both q1 and q2 are non-NULL.
Definition at line 1066 of file qofquery.c.
01067 { 01068 01069 QofQuery * retval = NULL; 01070 QofQuery * i1, * i2; 01071 QofQuery * t1, * t2; 01072 GList * i, * j; 01073 QofIdType search_for; 01074 01075 if(!q1) return q2; 01076 if(!q2) return q1; 01077 01078 if (q1->search_for && q2->search_for) 01079 g_return_val_if_fail (safe_strcmp (q1->search_for, q2->search_for) == 0, 01080 NULL); 01081 01082 search_for = (q1->search_for ? q1->search_for : q2->search_for); 01083 01084 /* Avoid merge surprises if op==QOF_QUERY_AND but q1 is empty. 01085 * The goal of this tweak is to all the user to start with 01086 * an empty q1 and then append to it recursively 01087 * (and q1 (and q2 (and q3 (and q4 ....)))) 01088 * without bombing out because the append started with an 01089 * empty list. 01090 * We do essentially the same check in qof_query_add_term() 01091 * so that the first term added to an empty query doesn't screw up. 01092 */ 01093 if ((QOF_QUERY_AND == op) && (0 == qof_query_has_terms (q1))) 01094 { 01095 op = QOF_QUERY_OR; 01096 } 01097 01098 switch(op) 01099 { 01100 case QOF_QUERY_OR: 01101 retval = qof_query_create(); 01102 retval->terms = 01103 g_list_concat(copy_or_terms(q1->terms), copy_or_terms(q2->terms)); 01104 retval->books = merge_books (q1->books, q2->books); 01105 retval->max_results = q1->max_results; 01106 retval->changed = 1; 01107 break; 01108 01109 case QOF_QUERY_AND: 01110 retval = qof_query_create(); 01111 retval->books = merge_books (q1->books, q2->books); 01112 retval->max_results = q1->max_results; 01113 retval->changed = 1; 01114 01115 /* g_list_append() can take forever, so let's build the list in 01116 * reverse and then reverse it at the end, to deal better with 01117 * "large" queries. 01118 */ 01119 for(i=q1->terms; i; i=i->next) 01120 { 01121 for(j=q2->terms; j; j=j->next) 01122 { 01123 retval->terms = 01124 g_list_prepend(retval->terms, 01125 g_list_concat 01126 (copy_and_terms(i->data), 01127 copy_and_terms(j->data))); 01128 } 01129 } 01130 retval->terms = g_list_reverse(retval->terms); 01131 break; 01132 01133 case QOF_QUERY_NAND: 01134 /* !(a*b) = (!a + !b) */ 01135 i1 = qof_query_invert(q1); 01136 i2 = qof_query_invert(q2); 01137 retval = qof_query_merge(i1, i2, QOF_QUERY_OR); 01138 qof_query_destroy(i1); 01139 qof_query_destroy(i2); 01140 break; 01141 01142 case QOF_QUERY_NOR: 01143 /* !(a+b) = (!a*!b) */ 01144 i1 =