3 @brief Translate an abstract representation of a query into an SQL statement.
10 #include "opensrf/utils.h"
11 #include "opensrf/string_array.h"
12 #include "opensrf/osrf_hash.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_idl.h"
15 #include "openils/oils_sql.h"
16 #include "openils/oils_buildq.h"
18 static void build_Query( BuildSQLState* state, StoredQ* query );
19 static void buildCombo( BuildSQLState* state, StoredQ* query, const char* type_str );
20 static void buildSelect( BuildSQLState* state, StoredQ* query );
21 static void buildFrom( BuildSQLState* state, FromRelation* core_from );
22 static void buildJoin( BuildSQLState* state, FromRelation* join );
23 static void buildSelectList( BuildSQLState* state, SelectItem* item );
24 static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list );
25 static void buildExpression( BuildSQLState* state, Expression* expr );
26 static void buildBindVar( BuildSQLState* state, BindVar* bind );
27 static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj );
29 static void add_newline( BuildSQLState* state );
30 static inline void incr_indent( BuildSQLState* state );
31 static inline void decr_indent( BuildSQLState* state );
34 @brief Build an SQL query.
35 @param state Pointer to the query-building context.
36 @param query Pointer to the query to be built.
37 @return Zero if successful, or 1 if not.
39 Clear the output buffer, call build_Query() to do the work, and add a closing semicolon.
41 int buildSQL( BuildSQLState* state, StoredQ* query ) {
43 buffer_reset( state->sql );
45 build_Query( state, query );
46 if( ! state->error ) {
47 // Remove the trailing space, if there is one, and add a semicolon.
48 char c = buffer_chomp( state->sql );
50 buffer_add_char( state->sql, c ); // oops, not a space; put it back
51 buffer_add( state->sql, ";\n" );
57 @brief Build an SQL query, appending it to what has been built so far.
58 @param state Pointer to the query-building context.
59 @param query Pointer to the query to be built.
61 Look at the query type and branch to the corresponding routine.
63 static void build_Query( BuildSQLState* state, StoredQ* query ) {
64 if( buffer_length( state->sql ))
67 switch( query->type ) {
69 buildSelect( state, query );
72 buildCombo( state, query, "UNION" );
75 buildCombo( state, query, "INTERSECT" );
78 buildCombo( state, query, "EXCEPT" );
81 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
82 "Internal error: invalid query type %d in query # %d",
83 query->type, query->id ));
90 @brief Build a UNION, INTERSECT, or EXCEPT query.
91 @param state Pointer to the query-building context.
92 @param query Pointer to the query to be built.
93 @param type_str The query type, as a string.
95 static void buildCombo( BuildSQLState* state, StoredQ* query, const char* type_str ) {
97 QSeq* seq = query->child_list;
99 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
100 "Internal error: No child queries within %s query # %d",
101 type_str, query->id ));
106 // Traverse the list of child queries
108 build_Query( state, seq->child_query );
110 sqlAddMsg( state, "Unable to build child query # %d within %s query %d",
111 seq->child_query->id, type_str, query->id );
116 add_newline( state );
117 buffer_add( state->sql, type_str );
118 buffer_add_char( state->sql, ' ' );
120 buffer_add( state->sql, "ALL " );
128 @brief Build a SELECT statement.
129 @param state Pointer to the query-building context.
130 @param query Pointer to the StoredQ structure that represents the query.
132 static void buildSelect( BuildSQLState* state, StoredQ* query ) {
134 FromRelation* from_clause = query->from_clause;
136 sqlAddMsg( state, "SELECT has no FROM clause in query # %d", query->id );
141 // To do: get SELECT list; just a stub here
142 buffer_add( state->sql, "SELECT" );
143 incr_indent( state );
144 buildSelectList( state, query->select_list );
146 sqlAddMsg( state, "Unable to build SELECT list for query # %d", query->id );
150 decr_indent( state );
152 // Build FROM clause, if there is one
153 if( query->from_clause ) {
154 buildFrom( state, query->from_clause );
156 sqlAddMsg( state, "Unable to build FROM clause for query # %d", query->id );
162 // Build WHERE clause, if there is one
163 if( query->where_clause ) {
164 add_newline( state );
165 buffer_add( state->sql, "WHERE" );
166 incr_indent( state );
167 add_newline( state );
168 buildExpression( state, query->where_clause );
170 sqlAddMsg( state, "Unable to build WHERE clause for query # %d", query->id );
174 decr_indent( state );
177 // To do: build GROUP BY clause, if there is one
179 // Build HAVING clause, if there is one
180 if( query->having_clause ) {
181 add_newline( state );
182 buffer_add( state->sql, "HAVING" );
183 incr_indent( state );
184 add_newline( state );
185 buildExpression( state, query->having_clause );
187 sqlAddMsg( state, "Unable to build HAVING clause for query # %d", query->id );
191 decr_indent( state );
194 // Build ORDER BY clause, if there is one
195 if( query->order_by_list ) {
196 buildOrderBy( state, query->order_by_list );
198 sqlAddMsg( state, "Unable to build ORDER BY clause for query # %d", query->id );
204 // To do: Build LIMIT clause, if there is one
206 // To do: Build OFFSET clause, if there is one
212 @brief Build a FROM clause.
213 @param Pointer to the query-building context.
214 @param Pointer to the StoredQ query to which the FROM clause belongs.
216 static void buildFrom( BuildSQLState* state, FromRelation* core_from ) {
218 add_newline( state );
219 buffer_add( state->sql, "FROM" );
220 incr_indent( state );
221 add_newline( state );
223 switch( core_from->type ) {
224 case FRT_RELATION : {
225 char* relation = core_from->table_name;
227 if( !core_from->class_name ) {
228 sqlAddMsg( state, "No relation specified for core relation # %d",
234 // Look up table name, view name, or source_definition in the IDL
235 osrfHash* class_hash = osrfHashGet( oilsIDL(), core_from->class_name );
236 relation = oilsGetRelation( class_hash );
240 buffer_add( state->sql, relation );
241 if( !core_from->table_name )
242 free( relation ); // In this case we strdup'd it, must free it
246 buffer_add_char( state->sql, '(' );
247 incr_indent( state );
248 build_Query( state, core_from->subquery );
249 decr_indent( state );
250 add_newline( state );
251 buffer_add_char( state->sql, ')' );
254 sqlAddMsg( state, "Functions in FROM clause not yet supported" );
259 // Add a table alias, if possible
260 if( core_from->table_alias ) {
261 buffer_add( state->sql, " AS \"" );
262 buffer_add( state->sql, core_from->table_alias );
263 buffer_add( state->sql, "\" " );
265 else if( core_from->class_name ) {
266 buffer_add( state->sql, " AS \"" );
267 buffer_add( state->sql, core_from->class_name );
268 buffer_add( state->sql, "\" " );
270 buffer_add_char( state->sql, ' ' );
272 incr_indent( state );
273 FromRelation* join = core_from->join_list;
275 buildJoin( state, join );
277 sqlAddMsg( state, "Unable to build JOIN clause(s) for relation # %d",
283 decr_indent( state );
284 decr_indent( state );
287 static void buildJoin( BuildSQLState* state, FromRelation* join ) {
288 add_newline( state );
289 switch( join->join_type ) {
291 sqlAddMsg( state, "Non-join relation # %d in JOIN clause", join->id );
295 buffer_add( state->sql, "INNER JOIN " );
298 buffer_add( state->sql, "LEFT JOIN " );
301 buffer_add( state->sql, "RIGHT JOIN " );
304 buffer_add( state->sql, "FULL JOIN " );
307 sqlAddMsg( state, "Unrecognized join type in relation # %d", join->id );
312 switch( join->type ) {
315 if( !join->table_name || ! *join->table_name ) {
316 sqlAddMsg( state, "No relation designated for relation # %d", join->id );
320 buffer_add( state->sql, join->table_name );
324 if( !join->subquery ) {
325 sqlAddMsg( state, "Subquery expected, not found for relation # %d", join->id );
328 } else if( !join->table_alias ) {
329 sqlAddMsg( state, "No table alias for subquery in FROM relation # %d",
334 buffer_add_char( state->sql, '(' );
335 incr_indent( state );
336 build_Query( state, join->subquery );
337 decr_indent( state );
338 add_newline( state );
339 buffer_add_char( state->sql, ')' );
342 if( !join->table_name || ! *join->table_name ) {
343 sqlAddMsg( state, "Joins to functions not yet supported in relation # %d",
351 const char* effective_alias = join->table_alias;
352 if( !effective_alias )
353 effective_alias = join->class_name;
355 if( effective_alias ) {
356 buffer_add( state->sql, " AS \"" );
357 buffer_add( state->sql, effective_alias );
358 buffer_add_char( state->sql, '\"' );
361 if( join->on_clause ) {
362 incr_indent( state );
363 add_newline( state );
364 buffer_add( state->sql, "ON " );
365 buildExpression( state, join->on_clause );
366 decr_indent( state );
369 FromRelation* subjoin = join->join_list;
371 buildJoin( state, subjoin );
373 sqlAddMsg( state, "Unable to build JOIN clause(s) for relation # %d", join->id );
376 subjoin = subjoin->next;
380 static void buildSelectList( BuildSQLState* state, SelectItem* item ) {
385 buffer_add_char( state->sql, ',' );
386 add_newline( state );
387 buildExpression( state, item->expression );
389 sqlAddMsg( state, "Unable to build an expression for SELECT item # %d", item->id );
394 if( item->column_alias ) {
395 buffer_add( state->sql, " AS \"" );
396 buffer_add( state->sql, item->column_alias );
397 buffer_add_char( state->sql, '\"' );
402 buffer_add_char( state->sql, ' ' );
406 @brief Add an ORDER BY clause to the current query.
407 @param state Pointer to the query-building context.
408 @param ord_list Pointer to the first node in a linked list of OrderItems.
410 static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list ) {
411 add_newline( state );
412 buffer_add( state->sql, "ORDER BY" );
413 incr_indent( state );
415 int first = 1; // boolean
420 buffer_add_char( state->sql, ',' );
421 add_newline( state );
422 buildExpression( state, ord_list->expression );
424 sqlAddMsg( state, "Unable to add ORDER BY expression # %d", ord_list->id );
428 ord_list = ord_list->next;
431 decr_indent( state );
436 @brief Build an arbitrary expression.
437 @param state Pointer to the query-building context.
438 @param expr Pointer to the Expression representing the expression to be built.
440 static void buildExpression( BuildSQLState* state, Expression* expr ) {
442 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
443 "Internal error: NULL pointer to Expression" ));
448 if( expr->parenthesize )
449 buffer_add_char( state->sql, '(' );
451 switch( expr->type ) {
454 buffer_add( state->sql, "NOT " );
456 sqlAddMsg( state, "BETWEEN expressions not yet supported" );
460 if( !expr->bind ) { // Sanity check
461 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
462 "Internal error: no variable for bind variable expression" ));
465 buildBindVar( state, expr->bind );
469 buffer_add( state->sql, "NOT " );
471 if( expr->literal ) {
472 buffer_add( state->sql, expr->literal );
473 buffer_add_char( state->sql, ' ' );
475 buffer_add( state->sql, "FALSE " );
479 buffer_add( state->sql, "NOT " );
481 sqlAddMsg( state, "CASE expressions not yet supported" );
484 case EXP_CAST : // Type cast
486 buffer_add( state->sql, "NOT " );
488 sqlAddMsg( state, "Cast expressions not yet supported" );
491 case EXP_COLUMN : // Table column
493 buffer_add( state->sql, "NOT " );
495 if( expr->table_alias ) {
496 buffer_add_char( state->sql, '\"' );
497 buffer_add( state->sql, expr->table_alias );
498 buffer_add( state->sql, "\"." );
500 if( expr->column_name ) {
501 buffer_add( state->sql, expr->column_name );
503 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
504 "Column name not present in expression # %d", expr->id ));
509 if( !expr->subquery ) {
510 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
511 "No subquery found for EXIST expression # %d", expr->id ));
515 buffer_add( state->sql, "NOT " );
517 buffer_add( state->sql, "EXISTS (" );
518 incr_indent( state );
519 build_Query( state, expr->subquery );
520 decr_indent( state );
521 add_newline( state );
522 buffer_add_char( state->sql, ')' );
527 buffer_add( state->sql, "NOT " );
529 sqlAddMsg( state, "Field expressions not yet supported" );
534 buffer_add( state->sql, "NOT " );
536 sqlAddMsg( state, "Function expressions not yet supported" );
540 if( expr->left_operand ) {
541 buildExpression( state, expr->left_operand );
542 if( !state->error ) {
544 buffer_add( state->sql, "NOT " );
546 if( expr->subquery ) {
547 buffer_add( state->sql, " IN (" );
548 incr_indent( state );
549 build_Query( state, expr->subquery );
550 decr_indent( state );
551 add_newline( state );
552 buffer_add_char( state->sql, ')' );
554 sqlAddMsg( state, "IN lists not yet supported" );
562 buffer_add( state->sql, "NOT " );
564 buffer_add( state->sql, "NULL" );
566 case EXP_NUMBER : // Numeric literal
567 if( !expr->literal ) {
568 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
569 "Internal error: No numeric value in string expression # %d", expr->id ));
572 buffer_add( state->sql, expr->literal );
577 buffer_add( state->sql, "NOT (" );
579 if( expr->left_operand ) {
580 buildExpression( state, expr->left_operand );
582 sqlAddMsg( state, "Unable to emit left operand in expression # %d",
587 buffer_add_char( state->sql, ' ' );
588 buffer_add( state->sql, expr->op );
589 buffer_add_char( state->sql, ' ' );
590 if( expr->right_operand ) {
591 buildExpression( state, expr->right_operand );
593 sqlAddMsg( state, "Unable to emit right operand in expression # %d",
600 buffer_add_char( state->sql, ')' );
603 case EXP_STRING : // String literal
604 if( !expr->literal ) {
605 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
606 "Internal error: No string value in string expression # %d", expr->id ));
609 // To do: escape special characters in the string
610 buffer_add_char( state->sql, '\'' );
611 buffer_add( state->sql, expr->literal );
612 buffer_add_char( state->sql, '\'' );
617 buffer_add( state->sql, "NOT " );
619 if( expr->subquery ) {
620 buffer_add_char( state->sql, '(' );
621 incr_indent( state );
622 build_Query( state, expr->subquery );
623 decr_indent( state );
624 add_newline( state );
625 buffer_add_char( state->sql, ')' );
627 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
628 "Internal error: No subquery in subquery expression # %d", expr->id ));
634 if( expr->parenthesize )
635 buffer_add_char( state->sql, ')' );
639 @brief Add the value of a bind variable to an SQL statement.
640 @param state Pointer to the query-building context.
641 @param bind Pointer to the bind variable whose value is to be added to the SQL.
643 The value may be a null, a scalar, or an array of nulls and/or scalars, depending on
644 the type of the bind variable.
646 static void buildBindVar( BuildSQLState* state, BindVar* bind ) {
648 // Decide where to get the value, if any
649 const jsonObject* value = NULL;
650 if( bind->actual_value )
651 value = bind->actual_value;
652 else if( bind->default_value ) {
653 if( state->defaults_usable )
654 value = bind->default_value;
656 sqlAddMsg( state, "No confirmed value available for bind variable \"%s\"",
661 } else if( state->values_required ) {
662 sqlAddMsg( state, "No value available for bind variable \"%s\"", bind->name );
666 // No value available, and that's okay. Emit the name of the bind variable.
667 buffer_add_char( state->sql, ':' );
668 buffer_add( state->sql, bind->name );
672 // If we get to this point, we know that a value is available. Carry on.
674 int numeric = 0; // Boolean
675 if( BIND_NUM == bind->type || BIND_NUM_LIST == bind->type )
679 switch( bind->type ) {
682 buildScalar( state, numeric, value );
686 if( JSON_ARRAY == value->type ) {
687 // Iterate over array, emit each value
688 int first = 1; // Boolean
689 unsigned long max = value->size;
695 buffer_add( state->sql, ", " );
697 buildScalar( state, numeric, jsonObjectGetIndex( value, i ));
701 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
702 "Invalid value for bind variable; expected a list of values" ));
707 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
708 "Internal error: invalid type for bind variable" ));
714 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
715 "Unable to emit value of bind variable \"%s\"", bind->name ));
719 @brief Add a number or quoted string to an SQL statement.
720 @param state Pointer to the query-building context.
721 @param numeric Boolean; true if the value is expected to be a number
722 @param obj Pointer to the jsonObject whose value is to be added to the SQL.
724 static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj ) {
725 switch( obj->type ) {
727 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
728 "Internal error: hash value for bind variable" ));
732 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
733 "Internal error: array value for bind variable" ));
739 "Invalid value for bind variable: expected a string, found a number" );
742 // To do: escape special characters in the string
743 buffer_add_char( state->sql, '\'' );
744 buffer_add( state->sql, jsonObjectGetString( obj ));
745 buffer_add_char( state->sql, '\'' );
750 buffer_add( state->sql, jsonObjectGetString( obj ));
753 "Invalid value for bind variable: expected a number, found a string" );
758 buffer_add( state->sql, "NULL" );
761 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
762 "Internal error: boolean value for bind variable" ));
766 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
767 "Internal error: corrupted value for bind variable" ));
773 static void add_newline( BuildSQLState* state ) {
774 buffer_add_char( state->sql, '\n' );
777 static const char blanks[] = " "; // 32 blanks
778 static const size_t maxlen = sizeof( blanks ) - 1;
779 const int blanks_per_level = 3;
780 int n = state->indent * blanks_per_level;
782 size_t len = n >= maxlen ? maxlen : n;
783 buffer_add_n( state->sql, blanks, len );
788 static inline void incr_indent( BuildSQLState* state ) {
792 static inline void decr_indent( BuildSQLState* state ) {