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_application.h"
13 #include "openils/oils_idl.h"
14 #include "openils/oils_sql.h"
15 #include "openils/oils_buildq.h"
17 static void build_Query( BuildSQLState* state, StoredQ* query );
18 static void buildCombo( BuildSQLState* state, StoredQ* query, const char* type_str );
19 static void buildSelect( BuildSQLState* state, StoredQ* query );
20 static void buildFrom( BuildSQLState* state, FromRelation* core_from );
21 static void buildJoin( BuildSQLState* state, FromRelation* join );
22 static void buildSelectList( BuildSQLState* state, SelectItem* item );
23 static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list );
24 static void buildExpression( BuildSQLState* state, Expression* expr );
26 static void add_newline( BuildSQLState* state );
27 static inline void incr_indent( BuildSQLState* state );
28 static inline void decr_indent( BuildSQLState* state );
31 @brief Build an SQL query.
32 @param state Pointer to the query-building context.
33 @param query Pointer to the query to be built.
34 @return Zero if successful, or 1 if not.
36 Clear the output buffer, call build_Query() to do the work, and add a closing semicolon.
38 int buildSQL( BuildSQLState* state, StoredQ* query ) {
40 buffer_reset( state->sql );
42 build_Query( state, query );
43 if( ! state->error ) {
44 // Remove the trailing space, if there is one, and add a semicolon.
45 char c = buffer_chomp( state->sql );
47 buffer_add_char( state->sql, c ); // oops, not a space; put it back
48 buffer_add( state->sql, ";\n" );
54 @brief Build an SQL query, appending it to what has been built so far.
55 @param state Pointer to the query-building context.
56 @param query Pointer to the query to be built.
58 Look at the query type and branch to the corresponding routine.
60 static void build_Query( BuildSQLState* state, StoredQ* query ) {
61 if( buffer_length( state->sql ))
64 switch( query->type ) {
66 buildSelect( state, query );
69 buildCombo( state, query, "UNION" );
72 buildCombo( state, query, "INTERSECT" );
75 buildCombo( state, query, "EXCEPT" );
78 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
79 "Internal error: invalid query type %d in query # %d",
80 query->type, query->id ));
87 @brief Build a UNION, INTERSECT, or EXCEPT query.
88 @param state Pointer to the query-building context.
89 @param query Pointer to the query to be built.
90 @param type_str The query type, as a string.
92 static void buildCombo( BuildSQLState* state, StoredQ* query, const char* type_str ) {
94 QSeq* seq = query->child_list;
96 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
97 "Internal error: No child queries within %s query # %d",
98 type_str, query->id ));
103 // Traverse the list of child queries
105 build_Query( state, seq->child_query );
107 sqlAddMsg( state, "Unable to build child query # %d within %s query %d",
108 seq->child_query->id, type_str, query->id );
113 add_newline( state );
114 buffer_add( state->sql, type_str );
115 buffer_add_char( state->sql, ' ' );
117 buffer_add( state->sql, "ALL " );
125 @brief Build a SELECT statement.
126 @param state Pointer to the query-building context.
127 @param query Pointer to the StoredQ structure that represents the query.
129 static void buildSelect( BuildSQLState* state, StoredQ* query ) {
131 FromRelation* from_clause = query->from_clause;
133 sqlAddMsg( state, "SELECT has no FROM clause in query # %d", query->id );
138 // To do: get SELECT list; just a stub here
139 buffer_add( state->sql, "SELECT" );
140 incr_indent( state );
141 buildSelectList( state, query->select_list );
143 sqlAddMsg( state, "Unable to build SELECT list for query # %d", query->id );
147 decr_indent( state );
149 // Build FROM clause, if there is one
150 if( query->from_clause ) {
151 buildFrom( state, query->from_clause );
153 sqlAddMsg( state, "Unable to build FROM clause for query # %d", query->id );
159 // Build WHERE clause, if there is one
160 if( query->where_clause ) {
161 add_newline( state );
162 buffer_add( state->sql, "WHERE" );
163 incr_indent( state );
164 add_newline( state );
165 buildExpression( state, query->where_clause );
167 sqlAddMsg( state, "Unable to build WHERE clause for query # %d", query->id );
171 decr_indent( state );
174 // To do: build GROUP BY clause, if there is one
176 // Build HAVING clause, if there is one
177 if( query->having_clause ) {
178 add_newline( state );
179 buffer_add( state->sql, "HAVING" );
180 incr_indent( state );
181 add_newline( state );
182 buildExpression( state, query->having_clause );
184 sqlAddMsg( state, "Unable to build HAVING clause for query # %d", query->id );
188 decr_indent( state );
191 // Build ORDER BY clause, if there is one
192 if( query->order_by_list ) {
193 buildOrderBy( state, query->order_by_list );
195 sqlAddMsg( state, "Unable to build ORDER BY clause for query # %d", query->id );
201 // To do: Build LIMIT clause, if there is one
203 // To do: Build OFFSET clause, if there is one
209 @brief Build a FROM clause.
210 @param Pointer to the query-building context.
211 @param Pointer to the StoredQ query to which the FROM clause belongs.
213 static void buildFrom( BuildSQLState* state, FromRelation* core_from ) {
215 add_newline( state );
216 buffer_add( state->sql, "FROM" );
217 incr_indent( state );
218 add_newline( state );
220 switch( core_from->type ) {
221 case FRT_RELATION : {
222 char* relation = core_from->table_name;
224 if( !core_from->class_name ) {
225 sqlAddMsg( state, "No relation specified for core relation # %d",
231 // Look up table name, view name, or source_definition in the IDL
232 osrfHash* class_hash = osrfHashGet( oilsIDL(), core_from->class_name );
233 relation = oilsGetRelation( class_hash );
237 buffer_add( state->sql, relation );
238 if( !core_from->table_name )
239 free( relation ); // In this case we strdup'd it, must free it
243 buffer_add_char( state->sql, '(' );
244 incr_indent( state );
245 build_Query( state, core_from->subquery );
246 decr_indent( state );
247 add_newline( state );
248 buffer_add_char( state->sql, ')' );
251 sqlAddMsg( state, "Functions in FROM clause not yet supported" );
256 // Add a table alias, if possible
257 if( core_from->table_alias ) {
258 buffer_add( state->sql, " AS \"" );
259 buffer_add( state->sql, core_from->table_alias );
260 buffer_add( state->sql, "\" " );
262 else if( core_from->class_name ) {
263 buffer_add( state->sql, " AS \"" );
264 buffer_add( state->sql, core_from->class_name );
265 buffer_add( state->sql, "\" " );
267 buffer_add_char( state->sql, ' ' );
269 incr_indent( state );
270 FromRelation* join = core_from->join_list;
272 buildJoin( state, join );
274 sqlAddMsg( state, "Unable to build JOIN clause(s) for relation # %d",
280 decr_indent( state );
281 decr_indent( state );
284 static void buildJoin( BuildSQLState* state, FromRelation* join ) {
285 add_newline( state );
286 switch( join->join_type ) {
288 sqlAddMsg( state, "Non-join relation # %d in JOIN clause", join->id );
292 buffer_add( state->sql, "INNER JOIN " );
295 buffer_add( state->sql, "LEFT JOIN " );
298 buffer_add( state->sql, "RIGHT JOIN " );
301 buffer_add( state->sql, "FULL JOIN " );
304 sqlAddMsg( state, "Unrecognized join type in relation # %d", join->id );
309 switch( join->type ) {
312 if( !join->table_name || ! *join->table_name ) {
313 sqlAddMsg( state, "No relation designated for relation # %d", join->id );
317 buffer_add( state->sql, join->table_name );
321 if( !join->subquery ) {
322 sqlAddMsg( state, "Subquery expected, not found for relation # %d", join->id );
325 } else if( !join->table_alias ) {
326 sqlAddMsg( state, "No table alias for subquery in FROM relation # %d",
331 buffer_add_char( state->sql, '(' );
332 incr_indent( state );
333 build_Query( state, join->subquery );
334 decr_indent( state );
335 add_newline( state );
336 buffer_add_char( state->sql, ')' );
339 if( !join->table_name || ! *join->table_name ) {
340 sqlAddMsg( state, "Joins to functions not yet supported in relation # %d",
348 const char* effective_alias = join->table_alias;
349 if( !effective_alias )
350 effective_alias = join->class_name;
352 if( effective_alias ) {
353 buffer_add( state->sql, " AS \"" );
354 buffer_add( state->sql, effective_alias );
355 buffer_add_char( state->sql, '\"' );
358 if( join->on_clause ) {
359 incr_indent( state );
360 add_newline( state );
361 buffer_add( state->sql, "ON " );
362 buildExpression( state, join->on_clause );
363 decr_indent( state );
366 FromRelation* subjoin = join->join_list;
368 buildJoin( state, subjoin );
370 sqlAddMsg( state, "Unable to build JOIN clause(s) for relation # %d", join->id );
373 subjoin = subjoin->next;
377 static void buildSelectList( BuildSQLState* state, SelectItem* item ) {
382 buffer_add_char( state->sql, ',' );
383 add_newline( state );
384 buildExpression( state, item->expression );
386 sqlAddMsg( state, "Unable to build an expression for SELECT item # %d", item->id );
391 if( item->column_alias ) {
392 buffer_add( state->sql, " AS \"" );
393 buffer_add( state->sql, item->column_alias );
394 buffer_add_char( state->sql, '\"' );
399 buffer_add_char( state->sql, ' ' );
403 @brief Add an ORDER BY clause to the current query.
404 @param state Pointer to the query-building context.
405 @param ord_list Pointer to the first node in a linked list of OrderItems.
407 static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list ) {
408 add_newline( state );
409 buffer_add( state->sql, "ORDER BY" );
410 incr_indent( state );
412 int first = 1; // boolean
417 buffer_add_char( state->sql, ',' );
418 add_newline( state );
419 buildExpression( state, ord_list->expression );
421 sqlAddMsg( state, "Unable to add ORDER BY expression # %d", ord_list->id );
425 ord_list = ord_list->next;
428 decr_indent( state );
433 @brief Build an arbitrary expression.
434 @param state Pointer to the query-building context.
435 @param expr Pointer to the Expression representing the expression to be built.
437 static void buildExpression( BuildSQLState* state, Expression* expr ) {
439 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
440 "Internal error: NULL pointer to Expression" ));
445 if( expr->parenthesize )
446 buffer_add_char( state->sql, '(' );
448 switch( expr->type ) {
451 buffer_add( state->sql, "NOT " );
453 sqlAddMsg( state, "BETWEEN expressions not yet supported" );
458 buffer_add( state->sql, "NOT " );
460 if( expr->literal ) {
461 buffer_add( state->sql, expr->literal );
462 buffer_add_char( state->sql, ' ' );
464 buffer_add( state->sql, "FALSE " );
468 buffer_add( state->sql, "NOT " );
470 sqlAddMsg( state, "CASE expressions not yet supported" );
473 case EXP_CAST : // Type cast
474 sqlAddMsg( state, "Cast expressions not yet supported" );
477 case EXP_COLUMN : // Table column
479 buffer_add( state->sql, "NOT " );
481 if( expr->table_alias ) {
482 buffer_add_char( state->sql, '\"' );
483 buffer_add( state->sql, expr->table_alias );
484 buffer_add( state->sql, "\"." );
486 if( expr->column_name ) {
487 buffer_add( state->sql, expr->column_name );
489 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
490 "Column name not present in expression # %d", expr->id ));
495 if( !expr->subquery ) {
496 osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
497 "No subquery found for EXIST expression # %d", expr->id ));
501 buffer_add( state->sql, "NOT " );
503 buffer_add( state->sql, "EXISTS (" );
504 incr_indent( state );
505 build_Query( state, expr->subquery );
506 decr_indent( state );
507 add_newline( state );
508 buffer_add_char( state->sql, ')' );
512 sqlAddMsg( state, "Field expressions not yet supported" );
517 buffer_add( state->sql, "NOT " );
519 sqlAddMsg( state, "Function expressions not yet supported" );
523 if( expr->left_operand ) {
524 buildExpression( state, expr->left_operand );
525 if( !state->error ) {
527 buffer_add( state->sql, "NOT " );
529 if( expr->subquery ) {
530 buffer_add( state->sql, " IN (" );
531 incr_indent( state );
532 build_Query( state, expr->subquery );
533 decr_indent( state );
534 add_newline( state );
535 buffer_add_char( state->sql, ')' );
537 sqlAddMsg( state, "IN lists not yet supported" );
545 buffer_add( state->sql, "NOT " );
547 buffer_add( state->sql, "NULL" );
549 case EXP_NUMBER : // Numeric literal
550 if( !expr->literal ) {
551 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
552 "Internal error: No numeric value in string expression # %d", expr->id ));
555 buffer_add( state->sql, expr->literal );
560 buffer_add( state->sql, "NOT (" );
562 if( expr->left_operand ) {
563 buildExpression( state, expr->left_operand );
565 sqlAddMsg( state, "Unable to emit left operand in expression # %d",
570 buffer_add_char( state->sql, ' ' );
571 buffer_add( state->sql, expr->op );
572 buffer_add_char( state->sql, ' ' );
573 if( expr->right_operand ) {
574 buildExpression( state, expr->right_operand );
576 sqlAddMsg( state, "Unable to emit right operand in expression # %d",
583 buffer_add_char( state->sql, ')' );
586 case EXP_STRING : // String literal
587 if( !expr->literal ) {
588 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
589 "Internal error: No string value in string expression # %d", expr->id ));
592 buffer_add_char( state->sql, '\'' );
593 buffer_add( state->sql, expr->literal );
594 buffer_add_char( state->sql, '\'' );
599 buffer_add( state->sql, "NOT " );
601 if( expr->subquery ) {
602 buffer_add_char( state->sql, '(' );
603 incr_indent( state );
604 build_Query( state, expr->subquery );
605 decr_indent( state );
606 add_newline( state );
607 buffer_add_char( state->sql, ')' );
609 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
610 "Internal error: No subquery in subquery expression # %d", expr->id ));
616 if( expr->parenthesize )
617 buffer_add_char( state->sql, ')' );
620 static void add_newline( BuildSQLState* state ) {
621 buffer_add_char( state->sql, '\n' );
624 static const char blanks[] = " "; // 32 blanks
625 static const size_t maxlen = sizeof( blanks ) - 1;
626 const int blanks_per_level = 3;
627 int n = state->indent * blanks_per_level;
629 size_t len = n >= maxlen ? maxlen : n;
630 buffer_add_n( state->sql, blanks, len );
635 static inline void incr_indent( BuildSQLState* state ) {
639 static inline void decr_indent( BuildSQLState* state ) {