[open-ils-commits] r13821 - trunk/Open-ILS/src/c-apps (scottmk)
svn at svn.open-ils.org
svn at svn.open-ils.org
Thu Aug 13 09:02:56 EDT 2009
Author: scottmk
Date: 2009-08-13 09:02:54 -0400 (Thu, 13 Aug 2009)
New Revision: 13821
Modified:
trunk/Open-ILS/src/c-apps/oils_cstore.c
trunk/Open-ILS/src/c-apps/test_json_query.c
Log:
Add support for UNION, INTERSECT, and EXCEPT.
(ORDER BY is accepted syntactically but not otherwise
supported yet.)
Modified: trunk/Open-ILS/src/c-apps/oils_cstore.c
===================================================================
--- trunk/Open-ILS/src/c-apps/oils_cstore.c 2009-08-12 19:42:00 UTC (rev 13820)
+++ trunk/Open-ILS/src/c-apps/oils_cstore.c 2009-08-13 13:02:54 UTC (rev 13821)
@@ -23,9 +23,16 @@
# endif
#endif
-#define SUBSELECT 4
-#define DISABLE_I18N 2
-#define SELECT_DISTINCT 1
+// The next four macros are OR'd together as needed to form a set
+// of bitflags. SUBCOMBO enables an extra pair of parentheses when
+// nesting one UNION, INTERSECT or EXCEPT inside another.
+// SUBSELECT tells us we're in a subquery, so don't add the
+// terminal semicolon yet.
+#define SUBCOMBO 8
+#define SUBSELECT 4
+#define DISABLE_I18N 2
+#define SELECT_DISTINCT 1
+
#define AND_OP_JOIN 0
#define OR_OP_JOIN 1
@@ -105,6 +112,7 @@
static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
+char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
@@ -1769,31 +1777,19 @@
buffer_add(sql_buf, "IN (");
}
- if (node->type == JSON_HASH) {
- // subquery predicate
- char* subpred = SELECT(
- ctx,
- jsonObjectGetKey( node, "select" ),
- jsonObjectGetKey( node, "from" ),
- jsonObjectGetKey( node, "where" ),
- jsonObjectGetKey( node, "having" ),
- jsonObjectGetKey( node, "order_by" ),
- jsonObjectGetKey( node, "limit" ),
- jsonObjectGetKey( node, "offset" ),
- SUBSELECT
- );
- pop_query_frame();
-
- if( subpred ) {
- buffer_add(sql_buf, subpred);
- free(subpred);
- } else {
+ if (node->type == JSON_HASH) {
+ // subquery predicate
+ char* subpred = buildQuery( ctx, node, SUBSELECT );
+ if( ! subpred ) {
buffer_free( sql_buf );
return NULL;
}
- } else if (node->type == JSON_ARRAY) {
- // literal value list
+ buffer_add(sql_buf, subpred);
+ free(subpred);
+
+ } else if (node->type == JSON_ARRAY) {
+ // literal value list
int in_item_index = 0;
int in_item_first = 1;
const jsonObject* in_item;
@@ -2688,19 +2684,7 @@
buffer_fadd(sql_buf, " NOT ( %s )", subpred);
free( subpred );
} else if ( !strcasecmp("-exists",search_itr->key) ) {
- char* subpred = SELECT(
- ctx,
- jsonObjectGetKey( node, "select" ),
- jsonObjectGetKey( node, "from" ),
- jsonObjectGetKey( node, "where" ),
- jsonObjectGetKey( node, "having" ),
- jsonObjectGetKey( node, "order_by" ),
- jsonObjectGetKey( node, "limit" ),
- jsonObjectGetKey( node, "offset" ),
- SUBSELECT
- );
- pop_query_frame();
-
+ char* subpred = buildQuery( ctx, node, SUBSELECT );
if( ! subpred ) {
jsonIteratorFree( search_itr );
buffer_free( sql_buf );
@@ -2710,19 +2694,7 @@
buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
free(subpred);
} else if ( !strcasecmp("-not-exists",search_itr->key) ) {
- char* subpred = SELECT(
- ctx,
- jsonObjectGetKey( node, "select" ),
- jsonObjectGetKey( node, "from" ),
- jsonObjectGetKey( node, "where" ),
- jsonObjectGetKey( node, "having" ),
- jsonObjectGetKey( node, "order_by" ),
- jsonObjectGetKey( node, "limit" ),
- jsonObjectGetKey( node, "offset" ),
- SUBSELECT
- );
- pop_query_frame();
-
+ char* subpred = buildQuery( ctx, node, SUBSELECT );
if( ! subpred ) {
jsonIteratorFree( search_itr );
buffer_free( sql_buf );
@@ -2826,6 +2798,303 @@
return array;
}
+// Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
+// The jsonObject must be a JSON_HASH with an single entry for "union",
+// "intersect", or "except". The data associated with this key must be an
+// array of hashes, each hash being a query.
+// Also allowed but currently ignored: entries for "order_by" and "alias".
+static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
+ // Sanity check
+ if( ! combo || combo->type != JSON_HASH )
+ return NULL; // should be impossible; validated by caller
+
+ const jsonObject* query_array = NULL; // array of subordinate queries
+ const char* op = NULL; // name of operator, e.g. UNION
+ const char* alias = NULL; // alias for the query (needed for ORDER BY)
+ int op_count = 0; // for detecting conflicting operators
+ int excepting = 0; // boolean
+ int all = 0; // boolean
+ jsonObject* order_obj = NULL;
+
+ // Identify the elements in the hash
+ jsonIterator* query_itr = jsonNewIterator( combo );
+ jsonObject* curr_obj = NULL;
+ while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
+ if( ! strcmp( "union", query_itr->key ) ) {
+ ++op_count;
+ op = " UNION ";
+ query_array = curr_obj;
+ } else if( ! strcmp( "intersect", query_itr->key ) ) {
+ ++op_count;
+ op = " INTERSECT ";
+ query_array = curr_obj;
+ } else if( ! strcmp( "except", query_itr->key ) ) {
+ ++op_count;
+ op = " EXCEPT ";
+ excepting = 1;
+ query_array = curr_obj;
+ } else if( ! strcmp( "order_by", query_itr->key ) ) {
+ osrfLogWarning(
+ OSRF_LOG_MARK,
+ "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
+ MODULENAME
+ );
+ order_obj = curr_obj;
+ } else if( ! strcmp( "alias", query_itr->key ) ) {
+ if( curr_obj->type != JSON_STRING ) {
+ jsonIteratorFree( query_itr );
+ return NULL;
+ }
+ alias = jsonObjectGetString( curr_obj );
+ } else if( ! strcmp( "all", query_itr->key ) ) {
+ if( obj_is_true( curr_obj ) )
+ all = 1;
+ } else {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Malformed query; unexpected entry in query object"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Unexpected entry for \"%s\" in%squery",
+ MODULENAME,
+ query_itr->key,
+ op
+ );
+ jsonIteratorFree( query_itr );
+ return NULL;
+ }
+ }
+ jsonIteratorFree( query_itr );
+
+ // More sanity checks
+ if( ! query_array ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Expected UNION, INTERSECT, or EXCEPT operator not found"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
+ MODULENAME
+ );
+ return NULL; // should be impossible...
+ } else if( op_count > 1 ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
+ MODULENAME
+ );
+ return NULL;
+ } if( query_array->type != JSON_ARRAY ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
+ MODULENAME,
+ op,
+ json_type( query_array->type )
+ );
+ return NULL;
+ } if( query_array->size < 2 ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s:%srequires multiple queries as operands",
+ MODULENAME,
+ op
+ );
+ return NULL;
+ } else if( excepting && query_array->size > 2 ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "EXCEPT operator has too many queries as operands"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s:EXCEPT operator has too many queries as operands",
+ MODULENAME
+ );
+ return NULL;
+ } else if( order_obj && ! alias ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
+ MODULENAME
+ );
+ return NULL;
+ }
+
+ // So far so good. Now build the SQL.
+ growing_buffer* sql = buffer_init( 256 );
+
+ // If we nested inside another UNION, INTERSECT, or EXCEPT,
+ // Add a layer of parentheses
+ if( flags & SUBCOMBO )
+ OSRF_BUFFER_ADD( sql, "( " );
+
+ // Traverse the query array. Each entry should be a hash.
+ int first = 1; // boolean
+ int i = 0;
+ jsonObject* query = NULL;
+ while((query = jsonObjectGetIndex( query_array, i++ ) )) {
+ if( query->type != JSON_HASH ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Malformed query under UNION, INTERSECT or EXCEPT"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Malformed query under%s -- expected JSON_HASH, found %s",
+ MODULENAME,
+ op,
+ json_type( query->type )
+ );
+ buffer_free( sql );
+ return NULL;
+ }
+
+ if( first )
+ first = 0;
+ else {
+ OSRF_BUFFER_ADD( sql, op );
+ if( all )
+ OSRF_BUFFER_ADD( sql, "ALL " );
+ }
+
+ char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
+ if( ! query_str ) {
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Error building query under%s",
+ MODULENAME,
+ op
+ );
+ buffer_free( sql );
+ return NULL;
+ }
+
+ OSRF_BUFFER_ADD( sql, query_str );
+ }
+
+ if( flags & SUBCOMBO )
+ OSRF_BUFFER_ADD_CHAR( sql, ')' );
+
+ if ( !(flags & SUBSELECT) )
+ OSRF_BUFFER_ADD_CHAR( sql, ';' );
+
+ return buffer_release( sql );
+}
+
+// Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
+// The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
+// or "except" to indicate the type of query.
+char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
+ // Sanity checks
+ if( ! query ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Malformed query; no query object"
+ );
+ osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
+ return NULL;
+ } else if( query->type != JSON_HASH ) {
+ if( ctx )
+ osrfAppSessionStatus(
+ ctx->session,
+ OSRF_STATUS_INTERNALSERVERERROR,
+ "osrfMethodException",
+ ctx->request,
+ "Malformed query object"
+ );
+ osrfLogError(
+ OSRF_LOG_MARK,
+ "%s: Query object is %s instead of JSON_HASH",
+ MODULENAME,
+ json_type( query->type )
+ );
+ return NULL;
+ }
+
+ // Determine what kind of query it purports to be, and dispatch accordingly.
+ if( jsonObjectGetKey( query, "union" ) ||
+ jsonObjectGetKey( query, "intersect" ) ||
+ jsonObjectGetKey( query, "except" ) ) {
+ return doCombo( ctx, query, flags );
+ } else {
+ // It is presumably a SELECT query
+
+ // Push a node onto the stack for the current query. Every level of
+ // subquery gets its own QueryFrame on the Stack.
+ push_query_frame();
+
+ // Build an SQL SELECT statement
+ char* sql = SELECT(
+ ctx,
+ jsonObjectGetKey( query, "select" ),
+ jsonObjectGetKey( query, "from" ),
+ jsonObjectGetKey( query, "where" ),
+ jsonObjectGetKey( query, "having" ),
+ jsonObjectGetKey( query, "order_by" ),
+ jsonObjectGetKey( query, "limit" ),
+ jsonObjectGetKey( query, "offset" ),
+ flags
+ );
+ pop_query_frame();
+ return sql;
+ }
+}
+
char* SELECT (
/* method context */ osrfMethodContext* ctx,
@@ -2872,10 +3141,6 @@
return NULL;
}
- // Push a node onto the stack for the current query. Every level of
- // subquery gets its own QueryFrame on the Stack.
- push_query_frame();
-
// the core search class
const char* core_class = NULL;
@@ -4302,17 +4567,8 @@
flags |= DISABLE_I18N;
osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
- char* sql = SELECT(
- ctx,
- jsonObjectGetKey( hash, "select" ),
- jsonObjectGetKey( hash, "from" ),
- jsonObjectGetKey( hash, "where" ),
- jsonObjectGetKey( hash, "having" ),
- jsonObjectGetKey( hash, "order_by" ),
- jsonObjectGetKey( hash, "limit" ),
- jsonObjectGetKey( hash, "offset" ),
- flags
- );
+ clear_query_stack(); // a possibly needless precaution
+ char* sql = buildQuery( ctx, hash, flags );
clear_query_stack();
if (!sql) {
Modified: trunk/Open-ILS/src/c-apps/test_json_query.c
===================================================================
--- trunk/Open-ILS/src/c-apps/test_json_query.c 2009-08-12 19:42:00 UTC (rev 13820)
+++ trunk/Open-ILS/src/c-apps/test_json_query.c 2009-08-13 13:02:54 UTC (rev 13821)
@@ -52,18 +52,7 @@
// Prototypes for two functions in oils_cstore.c, which
// are not defined in any header
-char* SELECT (
- /* method context */ osrfMethodContext* ctx,
-
- /* SELECT */ jsonObject* selhash,
- /* FROM */ jsonObject* join_hash,
- /* WHERE */ jsonObject* search_hash,
- /* HAVING */ jsonObject* having_hash,
- /* ORDER BY */ jsonObject* order_hash,
- /* LIMIT */ jsonObject* limit,
- /* OFFSET */ jsonObject* offset,
- /* flags */ int flags
-);
+char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
void set_cstore_dbi_conn( dbi_conn conn );
static int obj_is_true( const jsonObject* obj );
@@ -201,17 +190,7 @@
if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
flags |= DISABLE_I18N;
- char* sql_query = SELECT(
- NULL,
- jsonObjectGetKey( hash, "select" ),
- jsonObjectGetKey( hash, "from" ),
- jsonObjectGetKey( hash, "where" ),
- jsonObjectGetKey( hash, "having" ),
- jsonObjectGetKey( hash, "order_by" ),
- jsonObjectGetKey( hash, "limit" ),
- jsonObjectGetKey( hash, "offset" ),
- flags
- );
+ char* sql_query = buildQuery( NULL, hash, flags );
if ( !sql_query ) {
fprintf( stderr, "Invalid query\n" );
More information about the open-ils-commits
mailing list