From 0ea4c196b38590ff2afc88c547cd5df243cd36e6 Mon Sep 17 00:00:00 2001 From: scottmk Date: Mon, 11 Oct 2010 14:25:05 +0000 Subject: [PATCH] Pull out into a separate function: the code in SELECT() that builds a comma-separated list of ORDER BY expressions from a JSON_ARRAY. Invoke that function, not only from SELECT(), but also from the buildSELECT() function. As a result, the select methods will be able to use the same array syntax as json_query for ORDER BY clauses, as an alternative to the existing hash syntax. M Open-ILS/src/c-apps/oils_sql.c git-svn-id: svn://svn.open-ils.org/ILS/trunk@18258 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/src/c-apps/oils_sql.c | 542 ++++++++++++++++++--------------- 1 file changed, 288 insertions(+), 254 deletions(-) diff --git a/Open-ILS/src/c-apps/oils_sql.c b/Open-ILS/src/c-apps/oils_sql.c index b9f524e394..7fb0fb65ce 100644 --- a/Open-ILS/src/c-apps/oils_sql.c +++ b/Open-ILS/src/c-apps/oils_sql.c @@ -93,6 +93,8 @@ static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info ); static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* ); static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query, osrfHash* meta, osrfMethodContext* ctx ); +static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ); + char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ); char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*, @@ -4278,7 +4280,6 @@ char* SELECT ( jsonIteratorFree( selclass_itr ); } - char* col_list = buffer_release( select_buf ); // Make sure the SELECT list isn't empty. This can happen, for example, @@ -4393,172 +4394,23 @@ char* SELECT ( } } - growing_buffer* order_buf = NULL; // to collect ORDER BY list - // Build an ORDER BY clause, if there is one if( NULL == order_hash ) ; // No ORDER BY? do nothing else if( JSON_ARRAY == order_hash->type ) { - // Array of field specifications, each specification being a - // hash to define the class, field, and other details - int order_idx = 0; - jsonObject* order_spec; - while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) { - - if( JSON_HASH != order_spec->type ) { - osrfLogError( OSRF_LOG_MARK, - "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s", - modulename, json_type( order_spec->type ) ); - if( ctx ) - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Malformed ORDER BY clause -- see error log for more details" - ); - buffer_free( order_buf ); - free( having_buf ); - buffer_free( group_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - return NULL; - } - - const char* class_alias = - jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) ); - const char* field = - jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) ); - - if( order_buf ) - OSRF_BUFFER_ADD( order_buf, ", " ); - else - order_buf = buffer_init( 128 ); - - if( !field || !class_alias ) { - osrfLogError( OSRF_LOG_MARK, - "%s: Missing class or field name in field specification " - "of ORDER BY clause", - modulename ); - if( ctx ) - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Malformed ORDER BY clause -- see error log for more details" - ); - buffer_free( order_buf ); - free( having_buf ); - buffer_free( group_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - return NULL; - } - - ClassInfo* order_class_info = search_alias( class_alias ); - if( ! order_class_info ) { - osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" " - "not in FROM clause", modulename, class_alias ); - if( ctx ) - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Invalid class referenced in ORDER BY clause -- " - "see error log for more details" - ); - free( having_buf ); - buffer_free( group_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - return NULL; - } - - osrfHash* field_def = osrfHashGet( order_class_info->fields, field ); - if( !field_def ) { - osrfLogError( OSRF_LOG_MARK, - "%s: Invalid field \"%s\".%s referenced in ORDER BY clause", - modulename, class_alias, field ); - if( ctx ) - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Invalid field referenced in ORDER BY clause -- " - "see error log for more details" - ); - free( having_buf ); - buffer_free( group_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - return NULL; - } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) { - osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause", - modulename, field ); - if( ctx ) - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Virtual field in ORDER BY clause -- see error log for more details" - ); - buffer_free( order_buf ); - free( having_buf ); - buffer_free( group_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - return NULL; - } - - if( jsonObjectGetKeyConst( order_spec, "transform" ) ) { - char* transform_str = searchFieldTransform( - class_alias, field_def, order_spec ); - if( ! transform_str ) { - if( ctx ) - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Severe query error in ORDER BY clause -- " - "see error log for more details" - ); - buffer_free( order_buf ); - free( having_buf ); - buffer_free( group_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - return NULL; - } - - OSRF_BUFFER_ADD( order_buf, transform_str ); - free( transform_str ); - } - else - buffer_fadd( order_buf, "\"%s\".%s", class_alias, field ); - - const char* direction = - jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) ); - if( direction ) { - if( direction[ 0 ] || 'D' == direction[ 0 ] ) - OSRF_BUFFER_ADD( order_buf, " DESC" ); - else - OSRF_BUFFER_ADD( order_buf, " ASC" ); - } + order_by_list = buildOrderByFromArray( ctx, order_hash ); + if( !order_by_list ) { + free( having_buf ); + buffer_free( group_buf ); + buffer_free( sql_buf ); + if( defaultselhash ) + jsonObjectFree( defaultselhash ); + return NULL; } } else if( JSON_HASH == order_hash->type ) { // This hash is keyed on class alias. Each class has either // an array of field names or a hash keyed on field name. + growing_buffer* order_buf = NULL; // to collect ORDER BY list jsonIterator* class_itr = jsonNewIterator( order_hash ); while( (snode = jsonIteratorNext( class_itr )) ) { @@ -4821,6 +4673,8 @@ char* SELECT ( } } // end while jsonIteratorFree( class_itr ); + if( order_buf ) + order_by_list = buffer_release( order_buf ); } else { osrfLogError( OSRF_LOG_MARK, "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s", @@ -4833,7 +4687,6 @@ char* SELECT ( ctx->request, "Malformed ORDER BY clause -- see error log for more details" ); - buffer_free( order_buf ); free( having_buf ); buffer_free( group_buf ); buffer_free( sql_buf ); @@ -4841,12 +4694,8 @@ char* SELECT ( jsonObjectFree( defaultselhash ); return NULL; } - - if( order_buf ) - order_by_list = buffer_release( order_buf ); } - string = buffer_release( group_buf ); if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) { @@ -4892,6 +4741,178 @@ char* SELECT ( } // end of SELECT() +/** + @brief Build a list of ORDER BY expressions. + @param ctx Pointer to the method context. + @param order_array Pointer to a JSON_ARRAY of field specifications. + @return Pointer to a string containing a comma-separated list of ORDER BY expressions. + Each expression may be either a column reference or a function call whose first parameter + is a column reference. + + Each entry in @a order_array must be a JSON_HASH with values for "class" and "field". + It may optionally include entries for "direction" and/or "transform". + + The calling code is responsible for freeing the returned string. +*/ +static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) { + if( ! order_array ) { + osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause", + modulename ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Logic error: ORDER BY clause expected, not found; " + "see error log for more details" + ); + return NULL; + } else if( order_array->type != JSON_ARRAY ) { + osrfLogError( OSRF_LOG_MARK, + "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Logic error: Unexpected format for ORDER BY clause; see error log for more details" ); + return NULL; + } + + growing_buffer* order_buf = buffer_init( 128 ); + int first = 1; // boolean + int order_idx = 0; + jsonObject* order_spec; + while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) { + + if( JSON_HASH != order_spec->type ) { + osrfLogError( OSRF_LOG_MARK, + "%s: Malformed field specification in ORDER BY clause; " + "expected JSON_HASH, found %s", + modulename, json_type( order_spec->type ) ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Malformed ORDER BY clause -- see error log for more details" + ); + buffer_free( order_buf ); + return NULL; + } + + const char* class_alias = + jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" )); + const char* field = + jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" )); + + // Add a separating comma, except at the beginning + if( first ) + first = 0; + else + OSRF_BUFFER_ADD( order_buf, ", " ); + + if( !field || !class_alias ) { + osrfLogError( OSRF_LOG_MARK, + "%s: Missing class or field name in field specification of ORDER BY clause", + modulename ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Malformed ORDER BY clause -- see error log for more details" + ); + buffer_free( order_buf ); + return NULL; + } + + const ClassInfo* order_class_info = search_alias( class_alias ); + if( ! order_class_info ) { + osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" " + "not in FROM clause", modulename, class_alias ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Invalid class referenced in ORDER BY clause -- see error log for more details" + ); + free( order_buf ); + return NULL; + } + + osrfHash* field_def = osrfHashGet( order_class_info->fields, field ); + if( !field_def ) { + osrfLogError( OSRF_LOG_MARK, + "%s: Invalid field \"%s\".%s referenced in ORDER BY clause", + modulename, class_alias, field ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Invalid field referenced in ORDER BY clause -- " + "see error log for more details" + ); + free( order_buf ); + return NULL; + } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) { + osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause", + modulename, field ); + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Virtual field in ORDER BY clause -- see error log for more details" + ); + buffer_free( order_buf ); + return NULL; + } + + if( jsonObjectGetKeyConst( order_spec, "transform" )) { + char* transform_str = searchFieldTransform( class_alias, field_def, order_spec ); + if( ! transform_str ) { + if( ctx ) + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Severe query error in ORDER BY clause -- " + "see error log for more details" + ); + buffer_free( order_buf ); + return NULL; + } + + OSRF_BUFFER_ADD( order_buf, transform_str ); + free( transform_str ); + } + else + buffer_fadd( order_buf, "\"%s\".%s", class_alias, field ); + + const char* direction = + jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) ); + if( direction ) { + if( direction[ 0 ] || 'D' == direction[ 0 ] ) + OSRF_BUFFER_ADD( order_buf, " DESC" ); + else + OSRF_BUFFER_ADD( order_buf, " ASC" ); + } + } + + return buffer_release( order_buf ); +} + /** @brief Build a SELECT statement. @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause. @@ -5074,117 +5095,130 @@ static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_qu free( pred ); } - // Add the ORDER BY and/or LIMIT clauses, if present + // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present if( rest_of_query ) { const jsonObject* order_by = NULL; if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){ - growing_buffer* order_buf = buffer_init( 128 ); + char* order_by_list = NULL; - if( JSON_HASH != order_by->type ) - osrfLogWarning( OSRF_LOG_MARK, - "\"order_by\" object in a query is not a JSON_HASH; no ORDER BY generated" ); - - // We expect order_by to be a JSON_HASH keyed on class names. Traverse it - // and build a list of ORDER BY expressions. - first = 1; - jsonIterator* class_itr = jsonNewIterator( order_by ); - while( (snode = jsonIteratorNext( class_itr )) ) { // For each class: - - if( !jsonObjectGetKeyConst( selhash, class_itr->key )) - continue; // class not referenced by SELECT clause? Ignore it. - - if( snode->type == JSON_HASH ) { - - // If the data for the current class is a JSON_HASH, then it is - // keyed on field name. - - const jsonObject* onode = NULL; - jsonIterator* order_itr = jsonNewIterator( snode ); - while( (onode = jsonIteratorNext( order_itr )) ) { // For each field - - osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", - class_itr->key, order_itr->key ); - if( !field_def ) - continue; // Field not defined in IDL? Ignore it. + if( JSON_ARRAY == order_by->type ) { + order_by_list = buildOrderByFromArray( ctx, order_by ); + if( !order_by_list ) { + buffer_free( sql_buf ); + if( defaultselhash ) + jsonObjectFree( defaultselhash ); + clear_query_stack(); + return NULL; + } + } else if( JSON_HASH == order_by->type ) { + // We expect order_by to be a JSON_HASH keyed on class names. Traverse it + // and build a list of ORDER BY expressions. + growing_buffer* order_buf = buffer_init( 128 ); + first = 1; + jsonIterator* class_itr = jsonNewIterator( order_by ); + while( (snode = jsonIteratorNext( class_itr )) ) { // For each class: + + if( !jsonObjectGetKeyConst( selhash, class_itr->key )) + continue; // class not referenced by SELECT clause? Ignore it. + + if( snode->type == JSON_HASH ) { + + // If the data for the current class is a JSON_HASH, then it is + // keyed on field name. + + const jsonObject* onode = NULL; + jsonIterator* order_itr = jsonNewIterator( snode ); + while( (onode = jsonIteratorNext( order_itr )) ) { // For each field + + osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s", + class_itr->key, order_itr->key ); + if( !field_def ) + continue; // Field not defined in IDL? Ignore it. + + char* field_str = NULL; + char* direction = NULL; + if( onode->type == JSON_HASH ) { + if( jsonObjectGetKeyConst( onode, "transform" ) ) { + field_str = searchFieldTransform( + class_itr->key, field_def, onode ); + if( ! field_str ) { + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Severe query error in ORDER BY clause -- " + "see error log for more details" + ); + jsonIteratorFree( order_itr ); + jsonIteratorFree( class_itr ); + buffer_free( order_buf ); + buffer_free( sql_buf ); + if( defaultselhash ) + jsonObjectFree( defaultselhash ); + clear_query_stack(); + return NULL; + } + } else { + growing_buffer* field_buf = buffer_init( 16 ); + buffer_fadd( field_buf, "\"%s\".%s", + class_itr->key, order_itr->key ); + field_str = buffer_release( field_buf ); + } - char* field_str = NULL; - char* direction = NULL; - if( onode->type == JSON_HASH ) { - if( jsonObjectGetKeyConst( onode, "transform" ) ) { - field_str = searchFieldTransform( class_itr->key, field_def, onode); - if( ! field_str ) { - osrfAppSessionStatus( - ctx->session, - OSRF_STATUS_INTERNALSERVERERROR, - "osrfMethodException", - ctx->request, - "Severe query error in ORDER BY clause -- " - "see error log for more details" - ); - jsonIteratorFree( order_itr ); - jsonIteratorFree( class_itr ); - buffer_free( order_buf ); - buffer_free( sql_buf ); - if( defaultselhash ) - jsonObjectFree( defaultselhash ); - clear_query_stack(); - return NULL; + if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) { + const char* dir = jsonObjectGetString( order_by ); + if(!strncasecmp( dir, "d", 1 )) { + direction = " DESC"; + } } } else { - growing_buffer* field_buf = buffer_init( 16 ); - buffer_fadd( field_buf, "\"%s\".%s", - class_itr->key, order_itr->key ); - field_str = buffer_release( field_buf ); - } - - if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) { - const char* dir = jsonObjectGetString( order_by ); - if(!strncasecmp( dir, "d", 1 )) { + field_str = strdup( order_itr->key ); + const char* dir = jsonObjectGetString( onode ); + if( !strncasecmp( dir, "d", 1 )) { direction = " DESC"; + } else { + direction = " ASC"; } } - } else { - field_str = strdup( order_itr->key ); - const char* dir = jsonObjectGetString( onode ); - if( !strncasecmp( dir, "d", 1 )) { - direction = " DESC"; + + if( first ) { + first = 0; } else { - direction = " ASC"; + buffer_add( order_buf, ", " ); } - } - if( first ) { - first = 0; - } else { - buffer_add( order_buf, ", " ); - } - - buffer_add( order_buf, field_str ); - free( field_str ); + buffer_add( order_buf, field_str ); + free( field_str ); - if( direction ) { - buffer_add( order_buf, direction ); - } - } + if( direction ) { + buffer_add( order_buf, direction ); + } + } // end while; looping over ORDER BY expressions - jsonIteratorFree( order_itr ); + jsonIteratorFree( order_itr ); - } else { - // The data for the current class is not a JSON_HASH, so we expect - // it to be a JSON_STRING with a single field name. - const char* str = jsonObjectGetString( snode ); - buffer_add( order_buf, str ); - break; - } + } else { + // The data for the current class is not a JSON_HASH, so we expect + // it to be a JSON_STRING with a single field name. + const char* str = jsonObjectGetString( snode ); + buffer_add( order_buf, str ); + break; + } - } // end while; looping over order_by expressions + } // end while; looping over order_by classes - jsonIteratorFree( class_itr ); + jsonIteratorFree( class_itr ); + order_by_list = buffer_release( order_buf ); - char* order_by_list = buffer_release( order_buf ); + } else { + osrfLogWarning( OSRF_LOG_MARK, + "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;" + "no ORDER BY generated" ); + } - if( *order_by_list ) { + if( order_by_list && *order_by_list ) { OSRF_BUFFER_ADD( sql_buf, " ORDER BY " ); OSRF_BUFFER_ADD( sql_buf, order_by_list ); } -- 2.43.2