Implement .columns method of qstore server, to return a list
authorscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 29 Apr 2010 16:55:04 +0000 (16:55 +0000)
committerscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 29 Apr 2010 16:55:04 +0000 (16:55 +0000)
of column names.

Also: add several doxygen-style comments to oils_storedq.c.

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

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

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

index 5f820a3..c031c1f 100644 (file)
@@ -182,6 +182,8 @@ const char* sqlAddMsg( BuildSQLState* state, const char* msg, ... );
 
 StoredQ* getStoredQuery( BuildSQLState* state, int query_id );
 
+jsonObject* oilsGetColNames( BuildSQLState* state, StoredQ* query );
+
 void pop_id( IdNode** stack );
 
 void storedQFree( StoredQ* sq );
index 9329cbf..395b16b 100644 (file)
@@ -86,6 +86,12 @@ int osrfAppInitialize() {
 
        buffer_reset( method_name );
        OSRF_BUFFER_ADD( method_name, modulename );
+       OSRF_BUFFER_ADD( method_name, ".columns" );
+       osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
+                                                  "doColumns", "", 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 );
@@ -198,6 +204,52 @@ int doPrepare( osrfMethodContext* ctx ) {
        return 0;
 }
 
+/**
+       @brief Execute an SQL query and return a result set.
+       @param ctx Pointer to the current method context.
+       @return Zero if successful, or -1 if not.
+
+       Method parameters:
+       - query token, as previously returned by the .prepare method.
+
+       Returns: An array of column names; unavailable names are represented as nulls.
+*/
+int doColumns( 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, "Listing column names for token %s", token );
+       
+       jsonObject* col_list = oilsGetColNames( query->state, query->query );
+       if( query->state->error ) {
+               osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
+                       ctx->request, "Unable to get column names" );
+               return -1;
+       } else {
+               osrfAppRespondComplete( ctx, col_list );
+               return 0;
+       }
+}
+
 int doBindParam( osrfMethodContext* ctx ) {
        if(osrfMethodVerifyContext( ctx )) {
                osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
index fbfdc14..44fd37f 100644 (file)
@@ -119,6 +119,14 @@ StoredQ* getStoredQuery( BuildSQLState* state, int query_id ) {
        return sq;
 }
 
+/**
+       @brief Construct a StoredQ.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.stored_query.
+       @return Pointer to a newly constructed StoredQ, if successful, or NULL if not.
+
+       The calling code is responsible for freeing the StoredQ by calling storedQFree().
+*/
 static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
 
        // Get the column values from the result
@@ -319,6 +327,14 @@ static QSeq* loadChildQueries( BuildSQLState* state, int parent_id, const char*
        return child_list;
 }
 
+/**
+       @brief Construct a QSeq.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.query_sequence.
+       @return Pointer to a newly constructed QSeq, if successful, or NULL if not.
+
+       The calling code is responsible for freeing QSeqs by calling freeQSeqList().
+*/
 static QSeq* constructQSeq( BuildSQLState* state, dbi_result result ) {
        int id = dbi_result_get_int_idx( result, 1 );
        int parent_query_id = dbi_result_get_int_idx( result, 2 );
@@ -367,7 +383,7 @@ static void freeQSeqList( QSeq* seq ) {
                        seq = NULL;
                }
        }
-       
+
        free_qseq_list = first;
 }
 
@@ -462,6 +478,14 @@ static FromRelation* getFromRelation( BuildSQLState* state, int id ) {
        return fr;
 }
 
+/**
+       @brief Construct a FromRelation.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.from_relation.
+       @return Pointer to a newly constructed FromRelation, if successful, or NULL if not.
+
+       The calling code is responsible for freeing FromRelations by calling joinListFree().
+*/
 static FromRelation* constructFromRelation( BuildSQLState* state, dbi_result result ) {
        // Get the column values from the result
        int id                  = dbi_result_get_int_idx( result, 1 );
@@ -744,6 +768,14 @@ static SelectItem* getSelectList( BuildSQLState* state, int query_id ) {
        return select_list;
 }
 
+/**
+       @brief Construct a SelectItem.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.select_item.
+       @return Pointer to a newly constructed SelectItem, if successful, or NULL if not.
+
+       The calling code is responsible for freeing the SelectItems by calling selectListFree().
+*/
 static SelectItem* constructSelectItem( BuildSQLState* state, dbi_result result ) {
 
        // Get the column values
@@ -847,6 +879,14 @@ static Expression* getExpression( BuildSQLState* state, int id ) {
        return exp;
 }
 
+/**
+       @brief Construct an Expression.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.expression.
+       @return Pointer to a newly constructed Expression, if successful, or NULL if not.
+
+       The calling code is responsible for freeing the Expression by calling expressionFree().
+*/
 static Expression* constructExpression( BuildSQLState* state, dbi_result result ) {
 
        int id = dbi_result_get_int_idx( result, 1 );
@@ -1102,6 +1142,14 @@ static void expressionFree( Expression* exp ) {
        }
 }
 
+/**
+       @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.
+       @return Pointer to the first node in a linked list of OrderItems.
+
+       The calling code is responsible for freeing the list by calling orderItemListFree().
+*/
 static OrderItem* getOrderByList( BuildSQLState* state, int query_id ) {
        OrderItem* ord_list = NULL;
 
@@ -1141,6 +1189,14 @@ static OrderItem* getOrderByList( BuildSQLState* state, int query_id ) {
        return ord_list;
 }
 
+/**
+       @brief Construct an OrderItem.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.order_by_item.
+       @return Pointer to a newly constructed OrderItem, if successful, or NULL if not.
+
+       The calling code is responsible for freeing the OrderItems by calling orderItemListFree().
+*/
 static OrderItem* constructOrderItem( BuildSQLState* state, dbi_result result ) {
        int id                   = dbi_result_get_int_idx( result, 1 );
        int stored_query_id      = dbi_result_get_int_idx( result, 2 );
@@ -1200,6 +1256,70 @@ static void orderItemListFree( OrderItem* ord ) {
 }
 
 /**
+       @brief Build a list of column names for a specified query.
+       @param state Pointer to the query-building context.
+       @param query Pointer to the specified query.
+       @return Pointer to a newly-allocated JSON_ARRAY of column names.
+
+       In the resulting array, each entry is either a JSON_STRING or (when no column name is
+       available) a JSON_NULL.
+
+       The calling code is responsible for freeing the list by calling jsonObjectFree().
+*/
+jsonObject* oilsGetColNames( BuildSQLState* state, StoredQ* query ) {
+       if( !state || !query )
+               return NULL;
+
+       // Save the outermost query id for possible use in an error message
+       int id = query->id;
+
+       // Find the first SELECT, from which we will take the column names
+       while( query->type != QT_SELECT ) {
+               QSeq* child_list = query->child_list;
+               if( !child_list ) {
+                       query = NULL;
+                       break;
+               } else
+                       query = child_list->child_query;
+       }
+
+       if( !query ) {
+               state->error = 1;
+               osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                       "Unable to find first SELECT in query # %d", id ));
+               return NULL;
+       }
+
+       // Get the SELECT list for the first SELECT
+       SelectItem* col = query->select_list;
+       if( !col ) {
+               state->error = 1;
+               osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                       "First SELECT in query # %d has empty SELECT list", id ));
+                       return NULL;
+       }
+
+       jsonObject* col_list = jsonNewObjectType( JSON_ARRAY );
+
+       // Traverse the list, adding an entry for each
+       do {
+               const char* alias = NULL;
+               if( col->column_alias )
+                       alias = col->column_alias;
+               else {
+                       Expression* expression = col->expression;
+                       if( expression && EXP_COLUMN == expression->type && expression->column_name )
+                               alias = expression->column_name;
+               }
+
+               jsonObjectPush( col_list, jsonNewObject( alias ) );
+               col = col->next;
+       } while( col );
+
+       return col_list;
+}
+
+/**
        @brief Push an IdNode onto a stack of IdNodes.
        @param stack Pointer to the stack.
        @param id Id of the new node.
@@ -1352,6 +1472,13 @@ static int oils_result_get_bool_idx( dbi_result result, int i ) {
                return 0;
 }
 
+/**
+       @brief Enable verbose messages.
+
+       The messages are written to standard output, which for a server is /dev/null.  Hence this
+       option is useful only for a non-server.  It is intended only as a convenience for
+       development and debugging.
+*/
 void oilsStoredQSetVerbose( void ) {
        verbose = 1;
 }