--- /dev/null
+/**
+ @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
#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 */
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.
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 );
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" );
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",
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;
}
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;
}
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;
}
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;
+}