3 @brief Load an abstract representation of a query from the database.
9 #include "opensrf/utils.h"
10 #include "opensrf/log.h"
11 #include "opensrf/string_array.h"
12 #include "opensrf/osrf_hash.h"
13 #include "openils/oils_buildq.h"
15 #define PRINT if( verbose ) printf
23 static int oils_result_get_bool_idx( dbi_result result, int i );
25 static FromRelation* getFromRelation( BuildSQLState* state, int id );
26 static FromRelation* constructFromRelation( BuildSQLState* state, dbi_result result );
27 static FromRelation* getJoinList( BuildSQLState* state, int id );
28 static void joinListFree( FromRelation* join_list );
29 static void fromRelationFree( FromRelation* fr );
31 static QSeq* loadChildQueries( BuildSQLState* state, int parent_id, const char* type_str );
32 static QSeq* constructQSeq( BuildSQLState* state, dbi_result result );
33 static void freeQSeqList( QSeq* seq );
34 static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result );
36 static SelectItem* getSelectList( BuildSQLState* state, int query_id );
37 static SelectItem* constructSelectItem( BuildSQLState* state, dbi_result result );
38 static void selectListFree( SelectItem* sel );
40 static BindVar* getBindVar( BuildSQLState* state, const char* name );
41 static BindVar* constructBindVar( BuildSQLState* state, dbi_result result );
42 static void bindVarFree( char* name, void* p );
44 static Expression* getExpression( BuildSQLState* state, int id );
45 static Expression* constructExpression( BuildSQLState* state, dbi_result result );
46 static void expressionFree( Expression* exp );
48 static OrderItem* getOrderByList( BuildSQLState* state, int query_id );
49 static OrderItem* constructOrderItem( BuildSQLState* state, dbi_result result );
50 static void orderItemListFree( OrderItem* ord );
52 static void push_id( IdNode** stack, int id, const char* alias );
53 static const IdNode* searchIdStack( const IdNode* stack, int id, const char* alias );
55 // A series of free lists to store already-allocated objects that are not in use, for
56 // potential reuse. This is a hack to reduce churning through malloc() and free().
57 static StoredQ* free_storedq_list = NULL;
58 static FromRelation* free_from_relation_list = NULL;
59 static SelectItem* free_select_item_list = NULL;
60 static BindVar* free_bindvar_list = NULL;
61 static Expression* free_expression_list = NULL;
62 static IdNode* free_id_node_list = NULL;
63 static QSeq* free_qseq_list = NULL;
64 static OrderItem* free_order_item_list = NULL;
66 // Boolean; settable by call to oilsStoredQSetVerbose(), used by PRINT macro.
67 // The idea is to allow debugging messages from a command line test driver for ease of
68 // testing and development, but not from a real server, where messages to stdout don't
70 static int verbose = 0;
73 @brief Load a stored query.
74 @param state Pointer to the query-building context.
75 @param query_id ID of the query in query.stored_query.
76 @return A pointer to the newly loaded StoredQ if successful, or NULL if not.
78 The calling code is responsible for freeing the StoredQ by calling storedQFree().
80 StoredQ* getStoredQuery( BuildSQLState* state, int query_id ) {
84 // Check the stack to see if the current query is nested inside itself. If it is, then
85 // abort in order to avoid infinite recursion. If it isn't, then add it to the stack.
86 // (Make sure to pop it off the stack before returning.)
87 if( searchIdStack( state->query_stack, query_id, NULL )) {
88 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
89 "Infinite recursion detected; query # %d is nested within itself", query_id ));
93 push_id( &state->query_stack, query_id, NULL );
96 dbi_result result = dbi_conn_queryf( state->dbhandle,
97 "SELECT id, type, use_all, use_distinct, from_clause, where_clause, having_clause "
98 "FROM query.stored_query WHERE id = %d;", query_id );
100 if( dbi_result_first_row( result ) ) {
101 sq = constructStoredQ( state, result );
103 PRINT( "Got a query row\n" );
104 PRINT( "\tid: %d\n", sq->id );
105 PRINT( "\ttype: %d\n", (int) sq->type );
106 PRINT( "\tuse_all: %s\n", sq->use_all ? "true" : "false" );
107 PRINT( "\tuse_distinct: %s\n", sq->use_distinct ? "true" : "false" );
109 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
110 "Unable to build a query for id = %d", query_id ));
112 sqlAddMsg( state, "Stored query not found for id %d", query_id );
115 dbi_result_free( result );
118 int errnum = dbi_conn_error( state->dbhandle, &msg );
119 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
120 "Unable to query query.stored_query table: #%d %s",
121 errnum, msg ? msg : "No description available" ));
124 pop_id( &state->query_stack );
129 @brief Construct a StoredQ.
130 @param Pointer to the query-building context.
131 @param result Database cursor positioned at a row in query.stored_query.
132 @return Pointer to a newly constructed StoredQ, if successful, or NULL if not.
134 The calling code is responsible for freeing the StoredQ by calling storedQFree().
136 static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
138 // Get the column values from the result
139 int id = dbi_result_get_int_idx( result, 1 );
140 const char* type_str = dbi_result_get_string_idx( result, 2 );
143 if( !strcmp( type_str, "SELECT" ))
145 else if( !strcmp( type_str, "UNION" ))
147 else if( !strcmp( type_str, "INTERSECT" ))
149 else if( !strcmp( type_str, "EXCEPT" ))
152 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
153 "Invalid query type \"%s\"", type_str ));
157 int use_all = oils_result_get_bool_idx( result, 3 );
158 int use_distinct = oils_result_get_bool_idx( result, 4 );
161 if( dbi_result_field_is_null_idx( result, 5 ) )
164 from_clause_id = dbi_result_get_int_idx( result, 5 );
167 if( dbi_result_field_is_null_idx( result, 6 ) )
168 where_clause_id = -1;
170 where_clause_id = dbi_result_get_int_idx( result, 6 );
172 int having_clause_id;
173 if( dbi_result_field_is_null_idx( result, 7 ) )
174 having_clause_id = -1;
176 having_clause_id = dbi_result_get_int_idx( result, 7 );
178 FromRelation* from_clause = NULL;
179 if( QT_SELECT == type ) {
180 // A SELECT query needs a FROM clause; go get it
181 if( from_clause_id != -1 ) {
182 from_clause = getFromRelation( state, from_clause_id );
184 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
185 "Unable to construct FROM clause for id = %d", from_clause_id ));
190 // Must be one of UNION, INTERSECT, or EXCEPT
191 if( from_clause_id != -1 )
192 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
193 "FROM clause found and ignored for %s query in query #%d", type_str, id ));
196 // If this is a SELECT query, we need a SELECT list. Go get one.
197 SelectItem* select_list = NULL;
198 QSeq* child_list = NULL;
199 if( QT_SELECT == type ) {
200 select_list = getSelectList( state, id );
202 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
203 "No SELECT list found for query id = %d", id ));
204 fromRelationFree( from_clause );
208 // Construct child queries of UNION, INTERSECT, or EXCEPT query
209 child_list = loadChildQueries( state, id, type_str );
211 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
212 "Unable to load child queries for %s query # %d", type_str, id ));
214 fromRelationFree( from_clause );
219 // Get the WHERE clause, if there is one
220 Expression* where_clause = NULL;
221 if( where_clause_id != -1 ) {
222 where_clause = getExpression( state, where_clause_id );
223 if( ! where_clause ) {
224 // shouldn't happen due to foreign key constraint
225 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
226 "Unable to fetch WHERE expression for query id = %d", id ));
227 freeQSeqList( child_list );
228 fromRelationFree( from_clause );
229 selectListFree( select_list );
235 Expression* having_clause = NULL;
236 if( having_clause_id != -1 ) {
237 having_clause = getExpression( state, having_clause_id );
238 if( ! having_clause ) {
239 // shouldn't happen due to foreign key constraint
240 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
241 "Unable to fetch HAVING expression for query id = %d", id ));
242 expressionFree( where_clause );
243 freeQSeqList( child_list );
244 fromRelationFree( from_clause );
245 selectListFree( select_list );
251 // Get the ORDER BY clause, if there is one
252 OrderItem* order_by_list = getOrderByList( state, id );
254 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
255 "Unable to load ORDER BY clause for query %d", id ));
256 expressionFree( having_clause );
257 expressionFree( where_clause );
258 freeQSeqList( child_list );
259 fromRelationFree( from_clause );
260 selectListFree( select_list );
264 // Allocate a StoredQ: from the free list if possible, from the heap if necessary
267 if( free_storedq_list ) {
268 sq = free_storedq_list;
269 free_storedq_list = free_storedq_list->next;
271 sq = safe_malloc( sizeof( StoredQ ) );
273 // Populate the StoredQ
278 sq->use_all = use_all;
279 sq->use_distinct = use_distinct;
280 sq->from_clause = from_clause;
281 sq->where_clause = where_clause;
282 sq->select_list = select_list;
283 sq->child_list = child_list;
284 sq->having_clause = having_clause;
285 sq->order_by_list = order_by_list;
291 @brief Load the child queries subordinate to a UNION, INTERSECT, or EXCEPT query.
292 @param state Pointer to the query-building context.
293 @param parent ID of the UNION, INTERSECT, or EXCEPT query.
294 @param type_str The type of the query ("UNION", "INTERSECT", or "EXCEPT").
295 @return If successful, a pointer to a linked list of QSeq, each bearing a pointer to a
296 StoredQ; otherwise NULL.
298 The @a type_str parameter is used only for building error messages.
300 static QSeq* loadChildQueries( BuildSQLState* state, int parent_id, const char* type_str ) {
301 QSeq* child_list = NULL;
303 // The ORDER BY is in descending order so that we can build the list by adding to
304 // the head, and it will wind up in the right order.
305 dbi_result result = dbi_conn_queryf( state->dbhandle,
306 "SELECT id, parent_query, seq_no, child_query "
307 "FROM query.query_sequence WHERE parent_query = %d ORDER BY seq_no DESC", parent_id );
309 if( dbi_result_first_row( result ) ) {
313 QSeq* seq = constructQSeq( state, result );
315 PRINT( "Found a child query\n" );
316 PRINT( "\tid: %d\n", seq->id );
317 PRINT( "\tparent id: %d\n", seq->parent_query_id );
318 PRINT( "\tseq_no: %d\n", seq->seq_no );
319 // Add to the head of the list
320 seq->next = child_list;
323 freeQSeqList( child_list );
326 if( !dbi_result_next_row( result ))
330 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
331 "%s query # %d has only one child query", type_str, parent_id ));
333 freeQSeqList( child_list );
337 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
338 "%s query # %d has no child queries within it", type_str, parent_id ));
344 int errnum = dbi_conn_error( state->dbhandle, &msg );
345 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
346 "Unable to query query.query_sequence table: # %d %s",
347 errnum, msg ? msg : "No description available" ));
356 @brief Construct a QSeq.
357 @param Pointer to the query-building context.
358 @param result Database cursor positioned at a row in query.query_sequence.
359 @return Pointer to a newly constructed QSeq, if successful, or NULL if not.
361 The calling code is responsible for freeing QSeqs by calling freeQSeqList().
363 static QSeq* constructQSeq( BuildSQLState* state, dbi_result result ) {
364 int id = dbi_result_get_int_idx( result, 1 );
365 int parent_query_id = dbi_result_get_int_idx( result, 2 );
366 int seq_no = dbi_result_get_int_idx( result, 3 );
367 int child_query_id = dbi_result_get_int_idx( result, 4 );
369 StoredQ* child_query = getStoredQuery( state, child_query_id );
371 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
372 "Unable to load child query # %d for parent query %d",
373 child_query_id, parent_query_id ));
378 // Allocate a QSeq; from the free list if possible, from the heap if necessary
380 if( free_qseq_list ) {
381 seq = free_qseq_list;
382 free_qseq_list = free_qseq_list->next;
384 seq = safe_malloc( sizeof( QSeq ));
388 seq->parent_query_id = parent_query_id;
389 seq->seq_no = seq_no;
390 seq->child_query = child_query;
395 static void freeQSeqList( QSeq* seq ) {
401 storedQFree( seq->child_query );
402 seq->child_query = NULL;
407 seq->next = free_qseq_list;
412 free_qseq_list = first;
416 @brief Deallocate the memory owned by a StoredQ.
417 @param sq Pointer to the StoredQ to be deallocated.
419 void storedQFree( StoredQ* sq ) {
421 fromRelationFree( sq->from_clause );
422 sq->from_clause = NULL;
423 selectListFree( sq->select_list );
424 sq->select_list = NULL;
425 expressionFree( sq->where_clause );
426 sq->where_clause = NULL;
427 if( sq->child_list ) {
428 freeQSeqList( sq->child_list );
429 sq->child_list = NULL;
431 if( sq->order_by_list ) {
432 orderItemListFree( sq->order_by_list );
433 sq->order_by_list = NULL;
435 if( sq->having_clause )
436 expressionFree( sq->having_clause );
438 // Stick the empty husk on the free list for potential reuse
439 sq->next = free_storedq_list;
440 free_storedq_list = sq;
444 static FromRelation* getFromRelation( BuildSQLState* state, int id ) {
445 FromRelation* fr = NULL;
446 dbi_result result = dbi_conn_queryf( state->dbhandle,
447 "SELECT id, type, table_name, class_name, subquery, function_call, "
448 "table_alias, parent_relation, seq_no, join_type, on_clause "
449 "FROM query.from_relation WHERE id = %d;", id );
451 if( dbi_result_first_row( result ) ) {
452 fr = constructFromRelation( state, result );
454 PRINT( "Got a from_relation row\n" );
455 PRINT( "\tid: %d\n", fr->id );
456 PRINT( "\ttype: %d\n", (int) fr->type );
457 PRINT( "\ttable_name: %s\n", fr->table_name ? fr->table_name : "(none)" );
458 PRINT( "\tclass_name: %s\n", fr->class_name ? fr->class_name : "(none)" );
459 PRINT( "\tsubquery_id: %d\n", fr->subquery_id );
460 PRINT( "\tfunction_call_id: %d\n", fr->function_call_id );
461 PRINT( "\ttable_alias: %s\n", fr->table_alias ? fr->table_alias : "(none)" );
462 PRINT( "\tparent_relation_id: %d\n", fr->parent_relation_id );
463 PRINT( "\tseq_no: %d\n", fr->seq_no );
464 PRINT( "\tjoin_type = %d\n", fr->join_type );
465 // Check the stack to see if the current from clause is nested inside itself.
466 // If it is, then abort in order to avoid infinite recursion. If it isn't,
467 // then add it to the stack. (Make sure to pop it off the stack before
469 const char* effective_alias = fr->table_alias;
470 if( !effective_alias )
471 effective_alias = fr->class_name;
472 const IdNode* node = searchIdStack( state->from_stack, id, effective_alias );
475 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
476 "Infinite recursion detected; from clause # %d is nested "
477 "within itself", id ));
479 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
480 "Conflicting nested table aliases \"%s\" in from clause # %d",
481 effective_alias, node->id ));
485 push_id( &state->from_stack, id, effective_alias );
487 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
488 "Unable to build a FromRelation for id = %d", id ));
490 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
491 "FROM relation not found for id = %d", id ));
493 dbi_result_free( result );
496 int errnum = dbi_conn_error( state->dbhandle, &msg );
497 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
498 "Unable to query query.from_relation table: #%d %s",
499 errnum, msg ? msg : "No description available" ));
503 pop_id( &state->from_stack );
509 @brief Construct a FromRelation.
510 @param Pointer to the query-building context.
511 @param result Database cursor positioned at a row in query.from_relation.
512 @return Pointer to a newly constructed FromRelation, if successful, or NULL if not.
514 The calling code is responsible for freeing FromRelations by calling joinListFree().
516 static FromRelation* constructFromRelation( BuildSQLState* state, dbi_result result ) {
517 // Get the column values from the result
518 int id = dbi_result_get_int_idx( result, 1 );
519 const char* type_str = dbi_result_get_string_idx( result, 2 );
521 FromRelationType type;
522 if( !strcmp( type_str, "RELATION" ))
524 else if( !strcmp( type_str, "SUBQUERY" ))
526 else if( !strcmp( type_str, "FUNCTION" ))
529 type = FRT_RELATION; // shouldn't happen due to database constraint
531 const char* table_name = dbi_result_get_string_idx( result, 3 );
532 const char* class_name = dbi_result_get_string_idx( result, 4 );
535 if( dbi_result_field_is_null_idx( result, 5 ) )
538 subquery_id = dbi_result_get_int_idx( result, 5 );
540 int function_call_id;
541 if( dbi_result_field_is_null_idx( result, 6 ) )
542 function_call_id = -1;
544 function_call_id = dbi_result_get_int_idx( result, 6 );
546 const char* table_alias = dbi_result_get_string_idx( result, 7 );
548 int parent_relation_id;
549 if( dbi_result_field_is_null_idx( result, 8 ) )
550 parent_relation_id = -1;
552 parent_relation_id = dbi_result_get_int_idx( result, 8 );
554 int seq_no = dbi_result_get_int_idx( result, 9 );
557 const char* join_type_str = dbi_result_get_string_idx( result, 10 );
560 else if( !strcmp( join_type_str, "INNER" ) )
561 join_type = JT_INNER;
562 else if( !strcmp( join_type_str, "LEFT" ) )
564 else if( !strcmp( join_type_str, "RIGHT" ) )
565 join_type = JT_RIGHT;
566 else if( !strcmp( join_type_str, "FULL" ) )
569 join_type = JT_NONE; // shouldn't happen due to database constraint
572 if( dbi_result_field_is_null_idx( result, 11 ) )
575 on_clause_id = dbi_result_get_int_idx( result, 11 );
577 StoredQ* subquery = NULL;
583 if( -1 == subquery_id ) {
584 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
585 "Internal error: no subquery specified for FROM relation # %d", id ));
589 if( ! table_alias ) {
590 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
591 "Subquery needs alias in FROM relation # %d", id ));
595 subquery = getStoredQuery( state, subquery_id );
597 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
598 "Unable to load subquery for FROM relation # %d", id ));
604 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
605 "Functions in FROM clause not yet supported" ));
610 FromRelation* join_list = getJoinList( state, id );
612 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
613 "Unable to load join list for FROM relation # %d", id ));
617 Expression* on_clause = NULL;
618 if( on_clause_id != -1 ) {
619 on_clause = getExpression( state, on_clause_id );
621 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
622 "Unable to load ON condition for FROM relation # %d", id ));
623 joinListFree( join_list );
628 PRINT( "\tGot an ON condition\n" );
631 // Allocate a FromRelation: from the free list if possible, from the heap if necessary
634 if( free_from_relation_list ) {
635 fr = free_from_relation_list;
636 free_from_relation_list = free_from_relation_list->next;
638 fr = safe_malloc( sizeof( FromRelation ) );
640 // Populate the FromRelation
645 fr->table_name = table_name ? strdup( table_name ) : NULL;
646 fr->class_name = class_name ? strdup( class_name ) : NULL;
647 fr->subquery_id = subquery_id;
648 fr->subquery = subquery;
649 fr->function_call_id = function_call_id;
650 fr->table_alias = table_alias ? strdup( table_alias ) : NULL;
651 fr->parent_relation_id = parent_relation_id;
653 fr->join_type = join_type;
654 fr->on_clause = on_clause;
655 fr->join_list = join_list;
661 @brief Build a list of joined relations.
662 @param state Pointer to the query-building context.
663 @param id ID of the parent relation.
664 @return A pointer to the first in a linked list of FromRelations, if there are any; or
665 NULL if there aren't any, or in case of an error.
667 Look for relations joined directly to the parent relation, and make a list of them.
669 static FromRelation* getJoinList( BuildSQLState* state, int id ) {
670 FromRelation* join_list = NULL;
672 // The ORDER BY is in descending order so that we can build the list by adding to
673 // the head, and it will wind up in the right order.
674 dbi_result result = dbi_conn_queryf( state->dbhandle,
675 "SELECT id, type, table_name, class_name, subquery, function_call, "
676 "table_alias, parent_relation, seq_no, join_type, on_clause "
677 "FROM query.from_relation WHERE parent_relation = %d ORDER BY seq_no DESC", id );
680 if( dbi_result_first_row( result ) ) {
682 FromRelation* relation = constructFromRelation( state, result );
684 PRINT( "Found a joined relation\n" );
685 PRINT( "\tjoin_type: %d\n", relation->join_type );
686 PRINT( "\ttable_name: %s\n", relation->table_name );
687 relation->next = join_list;
688 join_list = relation;
690 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
691 "Unable to build join list for from relation id #%d", id ));
692 joinListFree( join_list );
696 if( !dbi_result_next_row( result ) )
702 int errnum = dbi_conn_error( state->dbhandle, &msg );
703 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
704 "Unable to query query.from_relation table for join list: #%d %s",
705 errnum, msg ? msg : "No description available" ));
712 @brief Free a list of FromRelations.
713 @param join_list Pointer to the first FromRelation in the list.
715 static void joinListFree( FromRelation* join_list ) {
717 FromRelation* temp = join_list->next;
718 fromRelationFree( join_list );
724 @brief Deallocate a FromRelation.
725 @param fr Pointer to the FromRelation to be freed.
727 Free the strings that the FromRelation owns. The FromRelation itself goes onto a
728 free list for potential reuse.
730 static void fromRelationFree( FromRelation* fr ) {
732 free( fr->table_name );
733 fr->table_name = NULL;
734 free( fr->class_name );
735 fr->class_name = NULL;
737 storedQFree( fr->subquery );
740 free( fr->table_alias );
741 fr->table_alias = NULL;
742 if( fr->on_clause ) {
743 expressionFree( fr->on_clause );
744 fr->on_clause = NULL;
746 joinListFree( fr->join_list );
747 fr->join_list = NULL;
749 fr->next = free_from_relation_list;
750 free_from_relation_list = fr;
754 static SelectItem* getSelectList( BuildSQLState* state, int query_id ) {
755 SelectItem* select_list = NULL;
757 // The ORDER BY is in descending order so that we can build the list by adding to
758 // the head, and it will wind up in the right order.
759 dbi_result result = dbi_conn_queryf( state->dbhandle,
760 "SELECT id, stored_query, seq_no, expression, column_alias, grouped_by "
761 "FROM query.select_item WHERE stored_query = %d ORDER BY seq_no DESC", query_id );
763 if( dbi_result_first_row( result ) ) {
765 SelectItem* item = constructSelectItem( state, result );
767 PRINT( "Found a SELECT item\n" );
768 PRINT( "\tid: %d\n", item->id );
769 PRINT( "\tstored_query_id: %d\n", item->stored_query_id );
770 PRINT( "\tseq_no: %d\n", item->seq_no );
771 PRINT( "\tcolumn_alias: %s\n",
772 item->column_alias ? item->column_alias : "(none)" );
773 PRINT( "\tgrouped_by: %d\n", item->grouped_by );
775 item->next = select_list;
778 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
779 "Unable to build select list for query id #%d", query_id ));
780 selectListFree( select_list );
784 if( !dbi_result_next_row( result ) )
790 int errnum = dbi_conn_error( state->dbhandle, &msg );
791 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
792 "Unable to query query.select_list table: #%d %s",
793 errnum, msg ? msg : "No description available" ));
800 @brief Construct a SelectItem.
801 @param Pointer to the query-building context.
802 @param result Database cursor positioned at a row in query.select_item.
803 @return Pointer to a newly constructed SelectItem, if successful, or NULL if not.
805 The calling code is responsible for freeing the SelectItems by calling selectListFree().
807 static SelectItem* constructSelectItem( BuildSQLState* state, dbi_result result ) {
809 // Get the column values
810 int id = dbi_result_get_int_idx( result, 1 );
811 int stored_query_id = dbi_result_get_int_idx( result, 2 );
812 int seq_no = dbi_result_get_int_idx( result, 3 );
813 int expression_id = dbi_result_get_int_idx( result, 4 );
814 const char* column_alias = dbi_result_get_string_idx( result, 5 );
815 int grouped_by = oils_result_get_bool_idx( result, 6 );
817 // Construct an Expression
818 Expression* expression = getExpression( state, expression_id );
820 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
821 "Unable to fetch expression for id = %d", expression_id ));
826 // Allocate a SelectItem: from the free list if possible, from the heap if necessary
829 if( free_select_item_list ) {
830 sel = free_select_item_list;
831 free_select_item_list = free_select_item_list->next;
833 sel = safe_malloc( sizeof( SelectItem ) );
837 sel->stored_query_id = stored_query_id;
838 sel->seq_no = seq_no;
839 sel->expression = expression;
840 sel->column_alias = column_alias ? strdup( column_alias ) : NULL;
841 sel->grouped_by = grouped_by;
847 @brief Free a list of SelectItems.
848 @param sel Pointer to the first item in the list to be freed.
850 Free the column alias and expression owned by each item. Put the entire list into a free
853 static void selectListFree( SelectItem* sel ) {
855 return; // Nothing to free
857 SelectItem* first = sel;
859 free( sel->column_alias );
860 sel->column_alias = NULL;
861 expressionFree( sel->expression );
862 sel->expression = NULL;
864 if( NULL == sel->next ) {
865 sel->next = free_select_item_list;
871 // Transfer the entire list to the free list
872 free_select_item_list = first;
876 @brief Given the name of a bind variable, build a corresponding BindVar.
877 @param state Pointer to the query-building context.
878 @param name Name of the bind variable.
879 @return Pointer to the newly-built BindVar.
881 Since the same bind variable may appear multiple times, we load it only once for the
882 entire query, and reference the one copy wherever needed.
884 static BindVar* getBindVar( BuildSQLState* state, const char* name ) {
885 BindVar* bind = NULL;
886 if( state->bindvar_list ) {
887 bind = osrfHashGet( state->bindvar_list, name );
889 return bind; // Already loaded it...
892 // Load a BindVar from the Database.
893 dbi_result result = dbi_conn_queryf( state->dbhandle,
894 "SELECT name, type, description, default_value, label "
895 "FROM query.bind_variable WHERE name = \'%s\';", name );
897 if( dbi_result_first_row( result ) ) {
898 bind = constructBindVar( state, result );
900 PRINT( "Got a bind variable for %s\n", name );
902 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
903 "Unable to load bind variable \"%s\"", name ));
905 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
906 "No bind variable found with name \"%s\"", name ));
910 int errnum = dbi_conn_error( state->dbhandle, &msg );
911 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
912 "Unable to query query.bind_variable table for \"%s\": #%d %s",
913 name, errnum, msg ? msg : "No description available" ));
917 // Add the new bind variable to the list
918 if( !state->bindvar_list ) {
919 // Don't have a list yet? Start one.
920 state->bindvar_list = osrfNewHash();
921 osrfHashSetCallback( state->bindvar_list, bindVarFree );
923 osrfHashSet( state->bindvar_list, bind, name );
931 @brief Construct a BindVar to represent a bind variable.
932 @param Pointer to the query-building context.
933 @param result Database cursor positioned at a row in query.bind_variable.
934 @return Pointer to a newly constructed BindVar, if successful, or NULL if not.
936 The calling code is responsible for freeing the BindVar by calling bindVarFree().
938 static BindVar* constructBindVar( BuildSQLState* state, dbi_result result ) {
940 const char* name = dbi_result_get_string_idx( result, 1 );
942 const char* type_str = dbi_result_get_string_idx( result, 2 );
944 if( !strcmp( type_str, "string" ))
946 else if( !strcmp( type_str, "number" ))
948 else if( !strcmp( type_str, "string_list" ))
949 type = BIND_STR_LIST;
950 else if( !strcmp( type_str, "number_list" ))
951 type = BIND_NUM_LIST;;
953 const char* description = dbi_result_get_string_idx( result, 3 );
955 // The default value is encoded as JSON. Translate it into a jsonObject.
956 const char* default_value_str = dbi_result_get_string_idx( result, 4 );
957 jsonObject* default_value = NULL;
958 if( default_value_str ) {
959 default_value = jsonParse( default_value_str );
960 if( !default_value ) {
961 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
962 "Unable to parse JSON string for default value of bind variable \"%s\"",
969 const char* label = dbi_result_get_string_idx( result, 5 );
971 // Allocate a BindVar: from the free list if possible, from the heap if necessary
972 BindVar* bind = NULL;
973 if( free_bindvar_list ) {
974 bind = free_bindvar_list;
975 free_bindvar_list = free_bindvar_list->next;
977 bind = safe_malloc( sizeof( BindVar ) );
980 bind->name = strdup( name );
981 bind->label = strdup( label );
983 bind->description = strdup( description );
984 bind->default_value = default_value;
985 bind->actual_value = NULL;
991 @brief Deallocate a BindVar.
992 @param key Pointer to the bind variable name (not used).
993 @param p Pointer to the BindVar to be deallocated, cast to a void pointer.
995 Free the strings and jsonObjects owned by the BindVar, and put the BindVar itself into a
998 This function is a callback installed in an osrfHash; hence the peculiar signature.
1000 static void bindVarFree( char* key, void* p ) {
1004 free( bind->label );
1005 free( bind->description );
1006 if( bind->default_value ) {
1007 jsonObjectFree( bind->default_value );
1008 bind->default_value = NULL;
1010 if( bind->actual_value ) {
1011 jsonObjectFree( bind->actual_value );
1012 bind->actual_value = NULL;
1015 // Prepend to free list
1016 bind->next = free_bindvar_list;
1017 free_bindvar_list = bind;
1021 static Expression* getExpression( BuildSQLState* state, int id ) {
1023 // Check the stack to see if the current expression is nested inside itself. If it is,
1024 // then abort in order to avoid infinite recursion. If it isn't, then add it to the
1025 // stack. (Make sure to pop it off the stack before returning.)
1026 if( searchIdStack( state->expr_stack, id, NULL )) {
1027 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1028 "Infinite recursion detected; expression # %d is nested within itself", id ));
1032 push_id( &state->expr_stack, id, NULL );
1034 Expression* exp = NULL;
1035 dbi_result result = dbi_conn_queryf( state->dbhandle,
1036 "SELECT id, type, parenthesize, parent_expr, seq_no, literal, table_alias, column_name, "
1037 "left_operand, operator, right_operand, function_id, subquery, cast_type, negate, "
1039 "FROM query.expression WHERE id = %d;", id );
1041 if( dbi_result_first_row( result ) ) {
1042 exp = constructExpression( state, result );
1044 PRINT( "Got an expression\n" );
1045 PRINT( "\tid = %d\n", exp->id );
1046 PRINT( "\ttype = %d\n", exp->type );
1047 PRINT( "\tparenthesize = %d\n", exp->parenthesize );
1048 PRINT( "\tcolumn_name = %s\n", exp->column_name ? exp->column_name : "(none)" );
1050 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1051 "Unable to construct an Expression for id = %d", id ));
1055 int errnum = dbi_conn_error( state->dbhandle, &msg );
1056 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1057 "Unable to query query.expression table: #%d %s",
1058 errnum, msg ? msg : "No description available" ));
1061 pop_id( &state->expr_stack );
1066 @brief Construct an Expression.
1067 @param Pointer to the query-building context.
1068 @param result Database cursor positioned at a row in query.expression.
1069 @return Pointer to a newly constructed Expression, if successful, or NULL if not.
1071 The calling code is responsible for freeing the Expression by calling expressionFree().
1073 static Expression* constructExpression( BuildSQLState* state, dbi_result result ) {
1075 int id = dbi_result_get_int_idx( result, 1 );
1076 const char* type_str = dbi_result_get_string_idx( result, 2 );
1079 if( !strcmp( type_str, "xbet" ))
1081 else if( !strcmp( type_str, "xbind" ))
1083 else if( !strcmp( type_str, "xbool" ))
1085 else if( !strcmp( type_str, "xcase" ))
1087 else if( !strcmp( type_str, "xcast" ))
1089 else if( !strcmp( type_str, "xcol" ))
1091 else if( !strcmp( type_str, "xex" ))
1093 else if( !strcmp( type_str, "xfld" ))
1095 else if( !strcmp( type_str, "xfunc" ))
1096 type = EXP_FUNCTION;
1097 else if( !strcmp( type_str, "xin" ))
1099 else if( !strcmp( type_str, "xnull" ))
1101 else if( !strcmp( type_str, "xnum" ))
1103 else if( !strcmp( type_str, "xop" ))
1104 type = EXP_OPERATOR;
1105 else if( !strcmp( type_str, "xstr" ))
1107 else if( !strcmp( type_str, "xsubq" ))
1108 type = EXP_SUBQUERY;
1110 type = EXP_NULL; // shouldn't happen due to database constraint
1112 int parenthesize = oils_result_get_bool_idx( result, 3 );
1115 if( dbi_result_field_is_null_idx( result, 4 ))
1116 parent_expr_id = -1;
1118 parent_expr_id = dbi_result_get_int_idx( result, 4 );
1120 int seq_no = dbi_result_get_int_idx( result, 5 );
1121 const char* literal = dbi_result_get_string_idx( result, 6 );
1122 const char* table_alias = dbi_result_get_string_idx( result, 7 );
1123 const char* column_name = dbi_result_get_string_idx( result, 8 );
1125 int left_operand_id;
1126 if( dbi_result_field_is_null_idx( result, 9 ))
1127 left_operand_id = -1;
1129 left_operand_id = dbi_result_get_int_idx( result, 9 );
1131 const char* operator = dbi_result_get_string_idx( result, 10 );
1133 int right_operand_id;
1134 if( dbi_result_field_is_null_idx( result, 11 ))
1135 right_operand_id = -1;
1137 right_operand_id = dbi_result_get_int_idx( result, 11 );
1140 if( dbi_result_field_is_null_idx( result, 12 ))
1143 function_id = dbi_result_get_int_idx( result, 12 );
1146 if( dbi_result_field_is_null_idx( result, 13 ))
1149 subquery_id = dbi_result_get_int_idx( result, 13 );
1152 if( dbi_result_field_is_null_idx( result, 14 ))
1155 cast_type_id = dbi_result_get_int_idx( result, 14 );
1157 int negate = oils_result_get_bool_idx( result, 15 );
1158 const char* bind_variable = dbi_result_get_string_idx( result, 16 );
1160 Expression* left_operand = NULL;
1161 Expression* right_operand = NULL;
1162 StoredQ* subquery = NULL;
1163 BindVar* bind = NULL;
1165 if( EXP_OPERATOR == type ) {
1166 // Load left and/or right operands
1167 if( -1 == left_operand_id && -1 == right_operand_id ) {
1168 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1169 "Expression # %d is an operator with no operands", id ));
1174 if( left_operand_id != -1 ) {
1175 left_operand = getExpression( state, left_operand_id );
1176 if( !left_operand ) {
1177 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1178 "Unable to get left operand in expression # %d", id ));
1184 if( right_operand_id != -1 ) {
1185 right_operand = getExpression( state, right_operand_id );
1186 if( !right_operand ) {
1187 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1188 "Unable to get right operand in expression # %d", id ));
1190 expressionFree( left_operand );
1194 } else if( EXP_IN == type ) {
1195 if( -1 == left_operand_id ) {
1196 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1197 "IN condition has no left operand in expression # %d", id ));
1201 left_operand = getExpression( state, left_operand_id );
1202 if( !left_operand ) {
1203 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1204 "Unable to get left operand for IN condition in expression # %d", id ));
1210 if( -1 == subquery_id ) {
1211 // To do: load IN list of subexpressions
1212 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1213 "IN lists not yet supported for expression # %d", id ));
1217 subquery = getStoredQuery( state, subquery_id );
1219 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1220 "Unable to load subquery for IN expression # %d", id ));
1225 } else if( EXP_EXIST == type ) {
1226 if( -1 == subquery_id ) {
1227 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1228 "Internal error: No subquery found for EXIST expression # %d", id ));
1232 subquery = getStoredQuery( state, subquery_id );
1234 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1235 "Unable to load subquery for EXIST expression # %d", id ));
1240 } else if( EXP_SUBQUERY == type ) {
1241 if( -1 == subquery_id ) {
1242 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1243 "Subquery expression # %d has no query id", id ));
1247 // Load a subquery, if there is one
1248 subquery = getStoredQuery( state, subquery_id );
1250 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1251 "Unable to load subquery for expression # %d", id ));
1255 if( subquery->select_list && subquery->select_list->next ) {
1256 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1257 "Subquery # %d as expression returns more than one column", subquery_id ));
1261 PRINT( "\tExpression is subquery %d\n", subquery_id );
1263 } else if( EXP_BIND == type ) {
1264 if( bind_variable ) {
1265 // To do: Build a BindVar
1266 bind = getBindVar( state, bind_variable );
1268 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1269 "Unable to load bind variable \"%s\" for expression # %d",
1270 bind_variable, id ));
1274 PRINT( "\tBind variable is \"%s\"\n", bind_variable );
1276 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
1277 "No variable specified for bind variable expression # %d",
1278 bind_variable, id ));
1284 // Allocate an Expression: from the free list if possible, from the heap if necessary
1285 Expression* exp = NULL;
1286 if( free_expression_list ) {
1287 exp = free_expression_list;
1288 free_expression_list = free_expression_list->next;
1290 exp = safe_malloc( sizeof( Expression ) );
1292 // Populate the Expression
1296 exp->parenthesize = parenthesize;
1297 exp->parent_expr_id = parent_expr_id;
1298 exp->seq_no = seq_no;
1299 exp->literal = literal ? strdup( literal ) : NULL;
1300 exp->table_alias = table_alias ? strdup( table_alias ) : NULL;
1301 exp->column_name = column_name ? strdup( column_name ) : NULL;
1302 exp->left_operand = left_operand;
1303 exp->op = operator ? strdup( operator ) : NULL;
1304 exp->right_operand = right_operand;
1305 exp->function_id = function_id;
1306 exp->subquery_id = subquery_id;
1307 exp->subquery = subquery;
1308 exp->cast_type_id = subquery_id;
1309 exp->negate = negate;
1316 @brief Deallocate an Expression.
1317 @param exp Pointer to the Expression to be deallocated.
1319 Free the strings owned by the Expression. Put the Expressions itself into a free list.
1321 static void expressionFree( Expression* exp ) {
1323 free( exp->literal );
1324 exp->literal = NULL;
1325 free( exp->table_alias );
1326 exp->table_alias = NULL;
1327 free( exp->column_name );
1328 exp->column_name = NULL;
1329 if( exp->left_operand ) {
1330 expressionFree( exp->left_operand );
1331 exp->left_operand = NULL;
1335 if( exp->right_operand ) {
1336 expressionFree( exp->right_operand );
1337 exp->right_operand = NULL;
1339 if( exp->subquery ) {
1340 storedQFree( exp->subquery );
1341 exp->subquery = NULL;
1343 // We don't free the bind member here because the Expression doesn't own it;
1344 // the bindvar_list hash owns it, so that multiple Expressions can reference it.
1346 // Prepend to the free list
1347 exp->next = free_expression_list;
1348 free_expression_list = exp;
1353 @brief Build a list of ORDER BY items as a linked list of OrderItems.
1354 @param state Pointer to the query-building context.
1355 @param query_id ID for the query to which the ORDER BY belongs.
1356 @return Pointer to the first node in a linked list of OrderItems.
1358 The calling code is responsible for freeing the list by calling orderItemListFree().
1360 static OrderItem* getOrderByList( BuildSQLState* state, int query_id ) {
1361 OrderItem* ord_list = NULL;
1363 // The ORDER BY is in descending order so that we can build the list by adding to
1364 // the head, and it will wind up in the right order.
1365 dbi_result result = dbi_conn_queryf( state->dbhandle,
1366 "SELECT id, stored_query, seq_no, expression "
1367 "FROM query.order_by_item WHERE stored_query = %d ORDER BY seq_no DESC", query_id );
1369 if( dbi_result_first_row( result ) ) {
1371 OrderItem* item = constructOrderItem( state, result );
1373 PRINT( "Found an ORDER BY item\n" );
1375 item->next = ord_list;
1378 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1379 "Unable to build ORDER BY item for query id #%d", query_id ));
1380 orderItemListFree( ord_list );
1384 if( !dbi_result_next_row( result ) )
1390 int errnum = dbi_conn_error( state->dbhandle, &msg );
1391 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1392 "Unable to query query.order_by_list table: #%d %s",
1393 errnum, msg ? msg : "No description available" ));
1400 @brief Construct an OrderItem.
1401 @param Pointer to the query-building context.
1402 @param result Database cursor positioned at a row in query.order_by_item.
1403 @return Pointer to a newly constructed OrderItem, if successful, or NULL if not.
1405 The calling code is responsible for freeing the OrderItems by calling orderItemListFree().
1407 static OrderItem* constructOrderItem( BuildSQLState* state, dbi_result result ) {
1408 int id = dbi_result_get_int_idx( result, 1 );
1409 int stored_query_id = dbi_result_get_int_idx( result, 2 );
1410 int seq_no = dbi_result_get_int_idx( result, 3 );
1411 int expression_id = dbi_result_get_int_idx( result, 4 );
1412 // Allocate a SelectItem: from the free list if possible, from the heap if necessary
1414 // Construct an Expression
1415 Expression* expression = getExpression( state, expression_id );
1417 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1418 "Unable to fetch ORDER BY expression for id = %d", expression_id ));
1423 // Allocate an OrderItem; from the free list if possible, or from the heap if necessary.
1425 if( free_order_item_list ) {
1426 ord = free_order_item_list;
1427 free_order_item_list = free_order_item_list->next;
1429 ord = safe_malloc( sizeof( OrderItem ));
1433 ord->stored_query_id = stored_query_id;
1434 ord->seq_no = seq_no;
1435 ord->expression = expression;
1441 @brief Deallocate a linked list of OrderItems.
1442 @param exp Pointer to the first OrderItem in the list to be deallocated.
1444 Deallocate the memory owned by the OrderItems. Put the items themselves into a free list.
1446 static void orderItemListFree( OrderItem* ord ) {
1448 return; // Nothing to free
1450 OrderItem* first = ord;
1452 expressionFree( ord->expression );
1453 ord->expression = NULL;
1455 if( NULL == ord->next ) {
1456 ord->next = free_order_item_list;
1462 // Transfer the entire list to the free list
1463 free_order_item_list = first;
1467 @brief Build a list of column names for a specified query.
1468 @param state Pointer to the query-building context.
1469 @param query Pointer to the specified query.
1470 @return Pointer to a newly-allocated JSON_ARRAY of column names.
1472 In the resulting array, each entry is either a JSON_STRING or (when no column name is
1473 available) a JSON_NULL.
1475 The calling code is responsible for freeing the list by calling jsonObjectFree().
1477 jsonObject* oilsGetColNames( BuildSQLState* state, StoredQ* query ) {
1478 if( !state || !query )
1481 // Save the outermost query id for possible use in an error message
1484 // Find the first SELECT, from which we will take the column names
1485 while( query->type != QT_SELECT ) {
1486 QSeq* child_list = query->child_list;
1491 query = child_list->child_query;
1496 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1497 "Unable to find first SELECT in query # %d", id ));
1501 // Get the SELECT list for the first SELECT
1502 SelectItem* col = query->select_list;
1505 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
1506 "First SELECT in query # %d has empty SELECT list", id ));
1510 jsonObject* col_list = jsonNewObjectType( JSON_ARRAY );
1512 // Traverse the list, adding an entry for each
1514 const char* alias = NULL;
1515 if( col->column_alias )
1516 alias = col->column_alias;
1518 Expression* expression = col->expression;
1519 if( expression && EXP_COLUMN == expression->type && expression->column_name )
1520 alias = expression->column_name;
1523 jsonObjectPush( col_list, jsonNewObject( alias ) );
1531 @brief Push an IdNode onto a stack of IdNodes.
1532 @param stack Pointer to the stack.
1533 @param id Id of the new node.
1534 @param alias Alias, if any, of the new node.
1536 static void push_id( IdNode** stack, int id, const char* alias ) {
1539 // Allocate a node; from the free list if possible, from the heap if necessary.
1540 IdNode* node = NULL;
1541 if( free_id_node_list ) {
1542 node = free_id_node_list;
1543 free_id_node_list = free_id_node_list->next;
1545 node = safe_malloc( sizeof( IdNode ));
1548 node->next = *stack;
1551 node->alias = strdup( alias );
1561 @brief Remove the node at the top of an IdNode stack.
1562 @param stack Pointer to the IdNode stack.
1564 void pop_id( IdNode** stack ) {
1566 IdNode* node = *stack;
1567 *stack = node->next;
1570 free( node->alias );
1574 node->next = free_id_node_list;
1575 free_id_node_list = node;
1580 @brief Search a stack of IDs for a match by either ID or, optionally, by alias.
1581 @param stack Pointer to the stack.
1582 @param id The id to search for.
1583 @param alias (Optional) the alias to search for.
1584 @return A pointer to the matching node if one is found, or NULL if not.
1586 This search is used to detect cases where a query, expression, or FROM clause is nested
1587 inside itself, in order to avoid infinite recursion; or in order to avoid conflicting
1588 table references in a FROM clause.
1590 static const IdNode* searchIdStack( const IdNode* stack, int id, const char* alias ) {
1592 const IdNode* node = stack;
1594 if( node->id == id )
1595 return node; // Matched on id
1596 else if( alias && node->alias && !strcmp( alias, node->alias ))
1597 return node; // Matched on alias
1602 return NULL; // No match found
1606 @brief Free up any resources held by the StoredQ module.
1608 void storedQCleanup( void ) {
1610 // Free all the nodes in the free state list
1611 StoredQ* sq = free_storedq_list;
1613 free_storedq_list = sq->next;
1615 sq = free_storedq_list;
1618 // Free all the nodes in the free from_relation list
1619 FromRelation* fr = free_from_relation_list;
1621 free_from_relation_list = fr->next;
1623 fr = free_from_relation_list;
1626 // Free all the nodes in the free expression list
1627 Expression* exp = free_expression_list;
1629 free_expression_list = exp->next;
1631 exp = free_expression_list;
1634 // Free all the nodes in the free select item list
1635 SelectItem* sel = free_select_item_list;
1637 free_select_item_list = sel->next;
1639 sel = free_select_item_list;
1642 // Free all the nodes in the free select item list
1643 IdNode* node = free_id_node_list;
1645 free_id_node_list = node->next;
1647 node = free_id_node_list;
1650 // Free all the nodes in the free query sequence list
1651 QSeq* seq = free_qseq_list;
1653 free_qseq_list = seq->next;
1655 seq = free_qseq_list;
1658 // Free all the nodes in the free order item list
1659 OrderItem* ord = free_order_item_list;
1661 free_order_item_list = ord->next;
1663 ord = free_order_item_list;
1666 // Free all the nodes in the bind variable free list
1667 BindVar* bind = free_bindvar_list;
1669 free_bindvar_list = bind->next;
1671 bind = free_bindvar_list;
1676 @brief Return a boolean value from a database result.
1677 @param result The database result.
1678 @param i Index of the column in the result, starting with 1 );
1679 @return 1 if true, or 0 for false.
1681 Null values and error conditions are interpreted as FALSE.
1683 static int oils_result_get_bool_idx( dbi_result result, int i ) {
1685 const char* str = dbi_result_get_string_idx( result, i );
1686 return (str && *str == 't' ) ? 1 : 0;
1692 @brief Enable verbose messages.
1694 The messages are written to standard output, which for a server is /dev/null. Hence this
1695 option is useful only for a non-server. It is intended only as a convenience for
1696 development and debugging.
1698 void oilsStoredQSetVerbose( void ) {