[open-ils-commits] r16526 - in trunk/Open-ILS: include/openils src/c-apps (scottmk)

svn at svn.open-ils.org svn at svn.open-ils.org
Thu May 27 21:46:31 EDT 2010


Author: scottmk
Date: 2010-05-27 21:46:29 -0400 (Thu, 27 May 2010)
New Revision: 16526

Modified:
   trunk/Open-ILS/include/openils/oils_buildq.h
   trunk/Open-ILS/src/c-apps/buildSQL.c
   trunk/Open-ILS/src/c-apps/oils_storedq.c
Log:
Support series expressions, i.e. a series of expressions
separated by operators or commas.  This construct will
be especially useful for chains of ANDs or ORs.

M    Open-ILS/include/openils/oils_buildq.h
M    Open-ILS/src/c-apps/oils_storedq.c
M    Open-ILS/src/c-apps/buildSQL.c


Modified: trunk/Open-ILS/include/openils/oils_buildq.h
===================================================================
--- trunk/Open-ILS/include/openils/oils_buildq.h	2010-05-27 20:16:33 UTC (rev 16525)
+++ trunk/Open-ILS/include/openils/oils_buildq.h	2010-05-28 01:46:29 UTC (rev 16526)
@@ -158,6 +158,7 @@
 	EXP_NULL,
 	EXP_NUMBER,
 	EXP_OPERATOR,
+    EXP_SERIES,
 	EXP_STRING,
 	EXP_SUBQUERY
 } ExprType;
@@ -181,6 +182,7 @@
 	int         cast_type_id;
 	int         negate;             // Boolean
 	BindVar*    bind;
+	Expression* subexp_list;        // Linked list of subexpressions
 };
 
 struct QSeq_ {
@@ -217,7 +219,7 @@
 
 void storedQCleanup( void );
 
-int buildSQL( BuildSQLState* state, StoredQ* query );
+int buildSQL( BuildSQLState* state, const StoredQ* query );
 
 void oilsStoredQSetVerbose( void );
 
@@ -227,7 +229,7 @@
 
 jsonObject* oilsBindVarList( osrfHash* bindvar_list );
 
-int oilsApplyBindValues( BuildSQLState* state, jsonObject* bindings );
+int oilsApplyBindValues( BuildSQLState* state, const jsonObject* bindings );
 
 #ifdef __cplusplus
 }

Modified: trunk/Open-ILS/src/c-apps/buildSQL.c
===================================================================
--- trunk/Open-ILS/src/c-apps/buildSQL.c	2010-05-27 20:16:33 UTC (rev 16525)
+++ trunk/Open-ILS/src/c-apps/buildSQL.c	2010-05-28 01:46:29 UTC (rev 16526)
@@ -15,15 +15,16 @@
 #include "openils/oils_sql.h"
 #include "openils/oils_buildq.h"
 
-static void build_Query( BuildSQLState* state, StoredQ* query );
-static void buildCombo( BuildSQLState* state, StoredQ* query, const char* type_str );
-static void buildSelect( BuildSQLState* state, StoredQ* query );
-static void buildFrom( BuildSQLState* state, FromRelation* core_from );
-static void buildJoin( BuildSQLState* state, FromRelation* join );
-static void buildSelectList( BuildSQLState* state, SelectItem* item );
-static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list );
-static void buildExpression( BuildSQLState* state, Expression* expr );
-static void buildBindVar( BuildSQLState* state, BindVar* bind );
+static void build_Query( BuildSQLState* state, const StoredQ* query );
+static void buildCombo( BuildSQLState* state, const StoredQ* query, const char* type_str );
+static void buildSelect( BuildSQLState* state, const StoredQ* query );
+static void buildFrom( BuildSQLState* state, const FromRelation* core_from );
+static void buildJoin( BuildSQLState* state, const FromRelation* join );
+static void buildSelectList( BuildSQLState* state, const SelectItem* item );
+static void buildOrderBy( BuildSQLState* state, const OrderItem* ord_list );
+static void buildExpression( BuildSQLState* state, const Expression* expr );
+static void buildSeries( BuildSQLState* state, const Expression* subexp_list, const char* op );
+static void buildBindVar( BuildSQLState* state, const BindVar* bind );
 static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj );
 
 static void add_newline( BuildSQLState* state );
@@ -111,7 +112,7 @@
 	The @a bindings parameter must be a JSON_HASH.  The keys are the names of bind variables.
 	The values are the corresponding values for the variables.
 */
-int oilsApplyBindValues( BuildSQLState* state, jsonObject* bindings ) {
+int oilsApplyBindValues( BuildSQLState* state, const jsonObject* bindings ) {
 	if( !state ) {
 		osrfLogError( OSRF_LOG_MARK, "NULL pointer to state" );
 		return 1;
@@ -155,7 +156,7 @@
 
 	Clear the output buffer, call build_Query() to do the work, and add a closing semicolon.
 */
-int buildSQL( BuildSQLState* state, StoredQ* query ) {
+int buildSQL( BuildSQLState* state, const StoredQ* query ) {
 	state->error  = 0;
 	buffer_reset( state->sql );
 	state->indent = 0;
@@ -177,7 +178,7 @@
 
 	Look at the query type and branch to the corresponding routine.
 */
-static void build_Query( BuildSQLState* state, StoredQ* query ) {
+static void build_Query( BuildSQLState* state, const StoredQ* query ) {
 	if( buffer_length( state->sql ))
 		add_newline( state );
 
@@ -209,7 +210,7 @@
 	@param query Pointer to the query to be built.
 	@param type_str The query type, as a string.
 */
-static void buildCombo( BuildSQLState* state, StoredQ* query, const char* type_str ) {
+static void buildCombo( BuildSQLState* state, const StoredQ* query, const char* type_str ) {
 
 	QSeq* seq = query->child_list;
 	if( !seq ) {
@@ -246,7 +247,7 @@
 	@param state Pointer to the query-building context.
 	@param query Pointer to the StoredQ structure that represents the query.
 */
-static void buildSelect( BuildSQLState* state, StoredQ* query ) {
+static void buildSelect( BuildSQLState* state, const StoredQ* query ) {
 
 	FromRelation* from_clause = query->from_clause;
 	if( !from_clause ) {
@@ -330,7 +331,7 @@
 	@param Pointer to the query-building context.
 	@param Pointer to the StoredQ query to which the FROM clause belongs.
 */
-static void buildFrom( BuildSQLState* state, FromRelation* core_from ) {
+static void buildFrom( BuildSQLState* state, const FromRelation* core_from ) {
 
 	add_newline( state );
 	buffer_add( state->sql, "FROM" );
@@ -401,7 +402,7 @@
 	decr_indent( state );
 }
 
-static void buildJoin( BuildSQLState* state, FromRelation* join ) {
+static void buildJoin( BuildSQLState* state, const FromRelation* join ) {
 	add_newline( state );
 	switch( join->join_type ) {
 		case JT_NONE :
@@ -494,7 +495,12 @@
 	}
 }
 
-static void buildSelectList( BuildSQLState* state, SelectItem* item ) {
+/**
+	@brief Build a SELECT list.
+	@param state Pointer to the query-building context.
+	@param item Pointer to the first in a linked list of SELECT items.
+*/
+static void buildSelectList( BuildSQLState* state, const SelectItem* item ) {
 
 	int first = 1;
 	while( item ) {
@@ -524,7 +530,7 @@
 	@param state Pointer to the query-building context.
 	@param ord_list Pointer to the first node in a linked list of OrderItems.
 */
-static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list ) {
+static void buildOrderBy( BuildSQLState* state, const OrderItem* ord_list ) {
 	add_newline( state );
 	buffer_add( state->sql, "ORDER BY" );
 	incr_indent( state );
@@ -554,7 +560,7 @@
 	@param state Pointer to the query-building context.
 	@param expr Pointer to the Expression representing the expression to be built.
 */
-static void buildExpression( BuildSQLState* state, Expression* expr ) {
+static void buildExpression( BuildSQLState* state, const Expression* expr ) {
 	if( !expr ) {
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
 			"Internal error: NULL pointer to Expression" ));
@@ -732,6 +738,19 @@
 				buffer_add_char( state->sql, ')' );
 
 			break;
+		case EXP_SERIES :
+			if( expr->negate )
+				buffer_add( state->sql, "NOT (" );
+
+			buildSeries( state, expr->subexp_list, expr->op );
+			if( state->error ) {
+				sqlAddMsg( state, "Unable to build series expression using operator \"%s\"",
+					expr->op ? expr->op : "," );
+			}
+			if( expr->negate )
+				buffer_add_char( state->sql, ')' );
+
+			break;
 		case EXP_STRING :                     // String literal
 			if( !expr->literal ) {
 				osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
@@ -768,6 +787,52 @@
 }
 
 /**
+	@brief Build a series of expressions separated by a specified operator, or by commas.
+	@param state Pointer to the query-building context.
+	@param subexp_list Pointer to the first Expression in a linked list.
+	@param op Pointer to the operator, or NULL for commas.
+
+	If the operator is AND or OR (in upper, lower, or mixed case), the second and all
+	subsequent operators will begin on a new line.
+*/
+static void buildSeries( BuildSQLState* state, const Expression* subexp_list, const char* op ) {
+
+	int comma = 0;             // Boolean; true if separator is a comma
+	int newline_needed = 0;    // Boolean; true if operator is AND or OR
+
+	if( !op ) {
+		op = ",";
+		comma = 1;
+	} else if( !strcmp( op, "," ))
+		comma = 1;
+	else if( !strcasecmp( op, "AND" ) || !strcasecmp( op, "OR" ))
+		newline_needed = 1;
+
+	int first = 1;               // Boolean; true for first item in list
+	while( subexp_list ) {
+		if( first )
+			first = 0;   // No separator needed yet
+		else {
+			// Insert a separator
+			if( comma )
+				buffer_add( state->sql, ", " );
+			else {
+				if( newline_needed )
+					add_newline( state );
+				else
+					buffer_add_char( state->sql, ' ' );
+
+				buffer_add( state->sql, op );
+				buffer_add_char( state->sql, ' ' );
+			}
+		}
+
+		buildExpression( state, subexp_list );
+		subexp_list = subexp_list->next;
+	}
+}
+
+/**
 	@brief Add the value of a bind variable to an SQL statement.
 	@param state Pointer to the query-building context.
 	@param bind Pointer to the bind variable whose value is to be added to the SQL.
@@ -775,7 +840,7 @@
 	The value may be a null, a scalar, or an array of nulls and/or scalars, depending on
 	the type of the bind variable.
 */
-static void buildBindVar( BuildSQLState* state, BindVar* bind ) {
+static void buildBindVar( BuildSQLState* state, const BindVar* bind ) {
 
 	// Decide where to get the value, if any
 	const jsonObject* value = NULL;
@@ -902,6 +967,10 @@
 	}
 }
 
+/**
+	@brief Start a new line in the output, with the current level of indentation.
+	@param state Pointer to the query-building context.
+*/
 static void add_newline( BuildSQLState* state ) {
 	buffer_add_char( state->sql, '\n' );
 
@@ -917,10 +986,18 @@
 	}
 }
 
+/**
+	@brief Increase the degree of indentation.
+	@param state Pointer to the query-building context.
+*/
 static inline void incr_indent( BuildSQLState* state ) {
 	++state->indent;
 }
 
+/**
+	@brief Reduce the degree of indentation.
+	@param state Pointer to the query-building context.
+*/
 static inline void decr_indent( BuildSQLState* state ) {
 	if( state->indent )
 		--state->indent;

Modified: trunk/Open-ILS/src/c-apps/oils_storedq.c
===================================================================
--- trunk/Open-ILS/src/c-apps/oils_storedq.c	2010-05-27 20:16:33 UTC (rev 16525)
+++ trunk/Open-ILS/src/c-apps/oils_storedq.c	2010-05-28 01:46:29 UTC (rev 16526)
@@ -43,7 +43,9 @@
 
 static Expression* getExpression( BuildSQLState* state, int id );
 static Expression* constructExpression( BuildSQLState* state, dbi_result result );
+static void expressionListFree( Expression* exp );
 static void expressionFree( Expression* exp );
+static Expression* getExpressionList( BuildSQLState* state, int id );
 
 static OrderItem* getOrderByList( BuildSQLState* state, int query_id );
 static OrderItem* constructOrderItem( BuildSQLState* state, dbi_result result );
@@ -119,6 +121,7 @@
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state, 
 			"Unable to query query.stored_query table: #%d %s",
 			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	pop_id( &state->query_stack );
@@ -503,6 +506,7 @@
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
 			"Unable to query query.from_relation table: #%d %s",
 			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	if( fr )
@@ -709,6 +713,7 @@
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
 			"Unable to query query.from_relation table for join list: #%d %s",
 			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	return join_list;
@@ -795,8 +800,9 @@
 		const char* msg;
 		int errnum = dbi_conn_error( state->dbhandle, &msg );
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
-					  "Unable to query query.select_list table: #%d %s",
-					  errnum, msg ? msg : "No description available" ));
+			"Unable to query query.select_list table: #%d %s",
+			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	return select_list;
@@ -917,6 +923,7 @@
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
 			"Unable to query query.bind_variable table for \"%s\": #%d %s",
 			name, errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	if( bind ) {
@@ -1068,6 +1075,7 @@
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
 			"Unable to query query.expression table: #%d %s",
 			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	pop_id( &state->expr_stack );
@@ -1116,6 +1124,8 @@
 		type = EXP_NUMBER;
 	else if( !strcmp( type_str, "xop" ))
 		type = EXP_OPERATOR;
+	else if( !strcmp( type_str, "xser" ))
+		type = EXP_SERIES;
 	else if( !strcmp( type_str, "xstr" ))
 		type = EXP_STRING;
 	else if( !strcmp( type_str, "xsubq" ))
@@ -1175,6 +1185,7 @@
 	Expression* right_operand = NULL;
 	StoredQ* subquery = NULL;
 	BindVar* bind = NULL;
+	Expression* subexp_list = NULL;
 
 	if( EXP_OPERATOR == type ) {
 		// Load left and/or right operands
@@ -1268,6 +1279,14 @@
 				return NULL;
 			}
 		}
+	} else if( EXP_SERIES == type ) {
+		subexp_list = getExpressionList( state, id );
+		if( state->error ) {
+			osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
+				"Unable to get subexpressions for expression series using operator \"%s\"",
+					operator ? operator : "," ));
+			return NULL;
+		}
 	} else if( EXP_SUBQUERY == type ) {
 		if( -1 == subquery_id ) {
 			osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
@@ -1339,15 +1358,29 @@
 	exp->cast_type_id = subquery_id;
 	exp->negate = negate;
 	exp->bind = bind;
+	exp->subexp_list = subexp_list;
 
 	return exp;
 }
 
 /**
+	@brief Free all the Expressions in a linked list of Expressions.
+	@param exp Pointer to the first Expression in the list.
+*/
+static void expressionListFree( Expression* exp ) {
+	while( exp ) {
+		Expression* next = exp->next;
+		expressionFree( exp );
+		exp = next;
+	}
+}
+
+/**
 	@brief Deallocate an Expression.
 	@param exp Pointer to the Expression to be deallocated.
 
-	Free the strings owned by the Expression.  Put the Expressions itself into a free list.
+	Free the strings owned by the Expression.  Put the Expression itself, and any
+	subexpressions that it owns, into a free list.
 */
 static void expressionFree( Expression* exp ) {
 	if( exp ) {
@@ -1371,9 +1404,16 @@
 			storedQFree( exp->subquery );
 			exp->subquery = NULL;
 		}
+
 		// We don't free the bind member here because the Expression doesn't own it;
 		// the bindvar_list hash owns it, so that multiple Expressions can reference it.
 
+		if( exp->subexp_list ) {
+			// Free the linked list of subexpressions
+			expressionListFree( exp->subexp_list );
+			exp->subexp_list = NULL;
+		}
+
 		// Prepend to the free list
 		exp->next = free_expression_list;
 		free_expression_list = exp;
@@ -1381,6 +1421,56 @@
 }
 
 /**
+	@brief Build a list of subexpressions.
+	@param state Pointer to the query-building context.
+	@param id ID of the parent Expression.
+	@return A pointer to the first in a linked list of Expressions, if there are any; or
+		NULL if there aren't any, or in case of an error.
+*/
+static Expression* getExpressionList( BuildSQLState* state, int id ) {
+	Expression* exp_list = NULL;
+	
+	// The ORDER BY is in descending order so that we can build the list by adding to
+	// the head, and it will wind up in the right order.
+	dbi_result result = dbi_conn_queryf( state->dbhandle,
+		"SELECT id, type, parenthesize, parent_expr, seq_no, literal, table_alias, column_name, "
+		"left_operand, operator, right_operand, function_id, subquery, cast_type, negate, "
+		"bind_variable "
+		"FROM query.expression WHERE parent_expr = %d "
+		"ORDER BY seq_no desc;", id );
+
+	if( result ) {
+		if( dbi_result_first_row( result ) ) {
+			while( 1 ) {
+				Expression* exp = constructExpression( state, result );
+				if( exp ) {
+					PRINT( "Found a subexpression\n" );
+					exp->next = exp_list;
+					exp_list  = exp;
+				} else {
+					osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+						"Unable to build subexpression list for expression id #%d", id ));
+					expressionListFree( exp_list );
+					exp_list = NULL;
+					break;
+				}
+				if( !dbi_result_next_row( result ) )
+					break;
+			};
+		}
+	} else {
+		const char* msg;
+		int errnum = dbi_conn_error( state->dbhandle, &msg );
+		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+			"Unable to query query.expression table for expression list: #%d %s",
+			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
+	}
+
+	return exp_list;
+}
+
+/**
 	@brief Build a list of ORDER BY items as a linked list of OrderItems.
 	@param state Pointer to the query-building context.
 	@param query_id ID for the query to which the ORDER BY belongs.
@@ -1422,6 +1512,7 @@
 		osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
 			"Unable to query query.order_by_list table: #%d %s",
 			errnum, msg ? msg : "No description available" ));
+		state->error = 1;
 	}
 
 	return ord_list;



More information about the open-ils-commits mailing list