Fleshing out the qstore server a bit:
authorscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 19 Apr 2010 16:04:26 +0000 (16:04 +0000)
committerscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 19 Apr 2010 16:04:26 +0000 (16:04 +0000)
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

git-svn-id: svn://svn.open-ils.org/ILS/trunk@16274 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/include/openils/oils_buildq.h [new file with mode: 0644]
Open-ILS/src/c-apps/oils_qstore.c

diff --git a/Open-ILS/include/openils/oils_buildq.h b/Open-ILS/include/openils/oils_buildq.h
new file mode 100644 (file)
index 0000000..994eb71
--- /dev/null
@@ -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
index e22e5d4..1ff05a3 100644 (file)
 #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 */
 
@@ -22,6 +35,11 @@ int doPrepare( osrfMethodContext* ctx );
 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,7 +81,13 @@ int osrfAppInitialize() {
        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 );
@@ -148,6 +172,19 @@ int osrfAppChildInit() {
                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 @@ int doPrepare( osrfMethodContext* ctx ) {
                        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 @@ int doPrepare( osrfMethodContext* ctx ) {
 
        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 @@ int doExecute( osrfMethodContext* ctx ) {
        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 @@ int doSql( osrfMethodContext* ctx ) {
        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 @@ int doSql( osrfMethodContext* ctx ) {
        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;
+}