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

svn at svn.open-ils.org svn at svn.open-ils.org
Mon Apr 19 12:04:29 EDT 2010


Author: scottmk
Date: 2010-04-19 12:04:26 -0400 (Mon, 19 Apr 2010)
New Revision: 16274

Added:
   trunk/Open-ILS/include/openils/oils_buildq.h
Modified:
   trunk/Open-ILS/src/c-apps/oils_qstore.c
Log:
Fleshing out the qstore server a bit:

1. Add a .bind_param method.

2. In the .prepare method: generate a query token, return it, and save
it in session-level userDate.

3. In other methods: look up the supplied query token.

A    Open-ILS/include/openils/oils_buildq.h
M    Open-ILS/src/c-apps/oils_qstore.c


Added: trunk/Open-ILS/include/openils/oils_buildq.h
===================================================================
--- trunk/Open-ILS/include/openils/oils_buildq.h	                        (rev 0)
+++ trunk/Open-ILS/include/openils/oils_buildq.h	2010-04-19 16:04:26 UTC (rev 16274)
@@ -0,0 +1,196 @@
+/**
+	@file buildquery.h
+	@brief Header for routines for building database queries.
+*/
+
+#ifndef OILS_BUILDQ_H
+#define OILS_BUILDQ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct StoredQ_;
+typedef struct StoredQ_ StoredQ;
+
+struct FromRelation_;
+typedef struct FromRelation_ FromRelation;
+
+struct SelectItem_;
+typedef struct SelectItem_ SelectItem;
+
+struct Expression_;
+typedef struct Expression_ Expression;
+
+struct QSeq_;
+typedef struct QSeq_ QSeq;
+
+struct OrderItem_;
+typedef struct OrderItem_ OrderItem;
+
+struct BuildSQLState_;
+typedef struct BuildSQLState_ BuildSQLState;
+
+struct IdNode_;
+typedef struct IdNode_ IdNode;
+
+/**
+	@brief Stores various things related to the construction of an SQL query.
+	
+	This struct carries around various bits and scraps of context for constructing an SQL
+	query.  It also provides a way for buildSQLQuery() to return more than one kind of thing
+	to its caller.  In particular it can return a status code, a list of error messages, and
+	(if there is no error) an SQL string.
+*/
+struct BuildSQLState_ {
+	dbi_conn dbhandle;            /**< Handle for the database connection */
+	int error;                    /**< Boolean; true if an error has occurred */
+	osrfStringArray* error_msgs;  /**< Descriptions of errors, if any */
+	growing_buffer* sql;          /**< To hold the constructed query */
+	IdNode* query_stack;          /**< For avoiding infinite recursion of nested queries */
+	IdNode* expr_stack;           /**< For avoiding infinite recursion of nested expressions */
+	IdNode* from_stack;           /**< For avoiding infinite recursion of from clauses */
+	int indent;                   /**< For prettifying output: level of indentation */
+};
+
+typedef enum {
+	QT_SELECT,
+	QT_UNION,
+	QT_INTERSECT,
+	QT_EXCEPT
+} QueryType;
+
+struct StoredQ_ {
+	StoredQ*      next;
+	int           id;
+	QueryType     type;
+	int           use_all;        /**< Boolean */
+	int           use_distinct;   /**< Boolean */
+	FromRelation* from_clause;
+	Expression*   where_clause;
+	SelectItem*   select_list;
+	QSeq*         child_list;
+	OrderItem*    order_by_list;
+};
+
+typedef enum {
+	FRT_RELATION,
+	FRT_SUBQUERY,
+	FRT_FUNCTION
+} FromRelationType;
+
+typedef enum {
+	JT_NONE,
+	JT_INNER,
+	JT_LEFT,
+	JT_RIGHT,
+	JT_FULL
+} JoinType;
+
+struct FromRelation_ {
+	FromRelation*    next;
+	int              id;
+	FromRelationType type;
+	char*            table_name;
+	char*            class_name;
+	int              subquery_id;
+	StoredQ*         subquery;
+	int              function_call_id;
+	char*            table_alias;
+	int              parent_relation_id;
+	int              seq_no;
+	JoinType         join_type;
+	Expression*      on_clause;
+	FromRelation*    join_list;
+};
+
+struct SelectItem_ {
+	SelectItem* next;
+	int         id;
+	int         stored_query_id;
+	int         seq_no;
+	Expression* expression;
+	char*       column_alias;
+	int         grouped_by;        // Boolean
+};
+
+typedef enum {
+	EXP_BETWEEN,
+	EXP_BOOL,
+	EXP_CASE,
+	EXP_CAST,
+	EXP_COLUMN,
+	EXP_EXIST,
+	EXP_FIELD,
+	EXP_FUNCTION,
+	EXP_IN,
+	EXP_NOT_BETWEEN,
+	EXP_NOT_EXIST,
+	EXP_NOT_IN,
+	EXP_NULL,
+	EXP_NUMBER,
+	EXP_OPERATOR,
+	EXP_STRING,
+	EXP_SUBQUERY
+} ExprType;
+
+struct Expression_ {
+	Expression* next;
+	int         id;
+	ExprType    type;
+	int         parenthesize;       // Boolean
+	int         parent_expr_id;
+	int         seq_no;
+	char*       literal;
+	char*       table_alias;
+	char*       column_name;
+	Expression* left_operand;
+	char*       op;
+	Expression* right_operand;
+	int         function_id;
+	int         subquery_id;
+	StoredQ*    subquery;
+	int         cast_type_id;
+};
+
+struct QSeq_ {
+	QSeq*    next;
+	int      id;
+	int      parent_query_id;
+	int      seq_no;
+	StoredQ* child_query;
+};
+
+struct OrderItem_ {
+	OrderItem* next;
+	int        id;
+	int        stored_query_id;
+	int        seq_no;
+	Expression* expression;
+};
+
+BuildSQLState* buildSQLStateNew( dbi_conn dbhandle );
+
+void buildSQLStateFree( BuildSQLState* state );
+
+void buildSQLCleanup( void );
+
+const char* sqlAddMsg( BuildSQLState* state, const char* msg, ... );
+
+StoredQ* getStoredQuery( BuildSQLState* state, int query_id );
+
+void pop_id( IdNode** stack );
+
+void storedQFree( StoredQ* sq );
+
+void storedQCleanup( void );
+
+int buildSQL( BuildSQLState* state, StoredQ* query );
+
+void oilsStoredQSetVerbose( void );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

Modified: trunk/Open-ILS/src/c-apps/oils_qstore.c
===================================================================
--- trunk/Open-ILS/src/c-apps/oils_qstore.c	2010-04-19 15:48:00 UTC (rev 16273)
+++ trunk/Open-ILS/src/c-apps/oils_qstore.c	2010-04-19 16:04:26 UTC (rev 16274)
@@ -13,7 +13,20 @@
 #include "opensrf/osrf_application.h"
 #include "openils/oils_utils.h"
 #include "openils/oils_sql.h"
+#include "openils/oils_buildq.h"
 
+/**
+	@brief Information about a previously prepared query.
+
+	We store an osrfHash of CachedQueries in the userData area of the application session,
+	keyed on query token.  That way we can fetch what a previous call to the prepare method
+	has prepared.
+*/
+typedef struct {
+	StoredQ*    query;
+	jsonObject* bind_map;
+} CachedQuery;
+
 static dbi_conn dbhandle; /* our db connection */
 
 static const char modulename[] = "open-ils.qstore";
@@ -22,6 +35,11 @@
 int doExecute( osrfMethodContext* ctx );
 int doSql( osrfMethodContext* ctx );
 
+static const char* save_query( osrfMethodContext* ctx, StoredQ* query, jsonObject* bind_map );
+static void free_cached_query( char* key, void* data );
+static void userDataFree( void* blob );
+static CachedQuery* search_token( osrfMethodContext* ctx, const char* token );
+
 /**
 	@brief Disconnect from the database.
 
@@ -63,10 +81,16 @@
 	OSRF_BUFFER_ADD( method_name, modulename );
 	OSRF_BUFFER_ADD( method_name, ".prepare" );
 	osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
-			"doBuild", "", 1, 0 );
+			"doPrepare", "", 1, 0 );
 
 	buffer_reset( method_name );
 	OSRF_BUFFER_ADD( method_name, modulename );
+	OSRF_BUFFER_ADD( method_name, ".bind_param" );
+	osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
+			"doBindParam", "", 2, 0 );
+
+	buffer_reset( method_name );
+	OSRF_BUFFER_ADD( method_name, modulename );
 	OSRF_BUFFER_ADD( method_name, ".execute" );
 	osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
 			"doExecute", "", 1, OSRF_METHOD_STREAMING );
@@ -148,6 +172,19 @@
 		return 0;
 }
 
+/**
+	@brief Load a specified query from the database query tables.
+	@param ctx Pointer to the current method context.
+	@return Zero if successful, or -1 if not.
+
+	Method parameters:
+	- query id (key of query.stored_query table)
+
+	Returns: a character string serving as a token for future references to the query.
+
+	NB: the method return type is temporary.  Eventually this method will return both a token
+	and a list of bind variables.
+*/
 int doPrepare( osrfMethodContext* ctx ) {
 	if(osrfMethodVerifyContext( ctx )) {
 		osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
@@ -161,6 +198,7 @@
 			ctx->request, "Invalid parameter; query id must be a number" );
 		return -1;
 	}
+
 	int query_id = atoi( jsonObjectGetString( query_id_obj ));
 	if( query_id <= 0 ) {
 		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
@@ -170,6 +208,42 @@
 
 	osrfLogInfo( OSRF_LOG_MARK, "Building query for id # %d", query_id );
 
+	// To do: prepare query
+	StoredQ* query = NULL;
+	jsonObject* bind_map = NULL;
+	const char* token = save_query( ctx, query, bind_map );
+
+	osrfLogInfo( OSRF_LOG_MARK, "Token for query id # %d is \"%s\"", query_id, token );
+
+	osrfAppRespondComplete( ctx, jsonNewObject( token ));
+	return 0;
+}
+
+int doBindParam( osrfMethodContext* ctx ) {
+	if(osrfMethodVerifyContext( ctx )) {
+		osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
+		return -1;
+	}
+
+	// Get the query token from a method parameter
+	const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
+	if( token_obj->type != JSON_STRING ) {
+		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
+			ctx->request, "Invalid parameter; query token must be a string" );
+		return -1;
+	}
+	const char* token = jsonObjectGetString( token_obj );
+
+	// Look up the query token in the session-level userData
+	CachedQuery* query = search_token( ctx, token );
+	if( !query ) {
+		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
+							  ctx->request, "Invalid query token" );
+		return -1;
+	}
+
+	osrfLogInfo( OSRF_LOG_MARK, "Binding parameter(s) for token %s", token );
+
 	osrfAppRespondComplete( ctx, jsonNewObject( "build method not yet implemented" ));
 	return 0;
 }
@@ -184,16 +258,16 @@
 	const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
 	if( token_obj->type != JSON_STRING ) {
 		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
-			ctx->request, "Invalid parameter; query id must be a string" );
+			ctx->request, "Invalid parameter; query token must be a string" );
 		return -1;
 	}
 	const char* token = jsonObjectGetString( token_obj );
 
-	// Get the list of bind variables, if there is one
-	jsonObject* bind_map = jsonObjectGetIndex( ctx->params, 1 );
-	if( bind_map && bind_map->type != JSON_HASH ) {
+	// Look up the query token in the session-level userData
+	CachedQuery* query = search_token( ctx, token );
+	if( !query ) {
 		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
-			ctx->request, "Invalid parameter; bind map must be a JSON object" );
+			ctx->request, "Invalid query token" );
 		return -1;
 	}
 
@@ -213,16 +287,16 @@
 	const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
 	if( token_obj->type != JSON_STRING ) {
 		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
-			ctx->request, "Invalid parameter; query id must be a string" );
+			ctx->request, "Invalid parameter; query token must be a string" );
 		return -1;
 	}
 	const char* token = jsonObjectGetString( token_obj );
 
-	// Get the list of bind variables, if there is one
-	jsonObject* bind_map = jsonObjectGetIndex( ctx->params, 1 );
-	if( bind_map && bind_map->type != JSON_HASH ) {
+	// Look up the query token in the session-level userData
+	CachedQuery* query = search_token( ctx, token );
+	if( !query ) {
 		osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
-			ctx->request, "Invalid parameter; bind map must be a JSON object" );
+			ctx->request, "Invalid query token" );
 		return -1;
 	}
 
@@ -231,3 +305,62 @@
 	osrfAppRespondComplete( ctx, jsonNewObject( "sql method not yet implemented" ));
 	return 0;
 }
+
+static const char* save_query( osrfMethodContext* ctx, StoredQ* query, jsonObject* bind_map ) {
+
+	CachedQuery* cached_query = safe_malloc( sizeof( CachedQuery ));
+	cached_query->query       = query;
+	cached_query->bind_map    = bind_map;
+
+	// Get the cache.  If we don't have one yet, make one.
+	osrfHash* cache = ctx->session->userData;
+	if( !cache ) {
+		cache = osrfNewHash();
+		osrfHashSetCallback( cache, free_cached_query );
+		ctx->session->userData = cache;
+		ctx->session->userDataFree = userDataFree;  // arrange to free it at end of session
+	}
+
+	// Create a token string to be used as a key
+	static unsigned int token_count = 0;
+	char* token = va_list_to_string(
+		"%u_%ld_%ld", ++token_count, (long) time( NULL ), (long) getpid() );
+
+	osrfHashSet( cache, cached_query, token );
+	return token;
+}
+
+/**
+	@brief Free a CachedQuery
+	@param Pointer to the CachedQuery to be freed.
+*/
+static void free_cached_query( char* key, void* data ) {
+	if( data ) {
+		CachedQuery* cached_query = data;
+		//storedQFree( cached_query->query );
+		if( cached_query->bind_map )
+			jsonObjectFree( cached_query->bind_map );
+	}
+}
+
+/**
+	@brief Callback for freeing session-level userData.
+	@param blob Opaque pointer t userData.
+*/
+static void userDataFree( void* blob ) {
+	osrfHashFree( (osrfHash*) blob );
+}
+
+/**
+	@brief Search for the cached query corresponding to a given token.
+	@param ctx Pointer to the current method context.
+	@param token Token string from a previous call to the prepare method.
+	@return A pointer to the cached query, if found, or NULL if not.
+*/
+static CachedQuery* search_token( osrfMethodContext* ctx, const char* token ) {
+	if( ctx && ctx->session->userData && token ) {
+		osrfHash* cache = ctx->session->userData;
+		return osrfHashGet( cache, token );
+	} else
+		return NULL;
+}



More information about the open-ils-commits mailing list