3 @brief As a server, perform database queries as defined in the database itself.
10 #include "opensrf/utils.h"
11 #include "opensrf/log.h"
12 #include "opensrf/osrf_json.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
16 #include "openils/oils_buildq.h"
19 @brief Information about a previously prepared query.
21 We store an osrfHash of CachedQueries in the userData area of the application session,
22 keyed on query token. That way we can fetch what a previous call to the prepare method
30 static dbi_conn dbhandle; /* our db connection */
32 static const char modulename[] = "open-ils.qstore";
34 int doPrepare( osrfMethodContext* ctx );
35 int doExecute( osrfMethodContext* ctx );
36 int doSql( osrfMethodContext* ctx );
38 static const char* save_query(
39 osrfMethodContext* ctx, BuildSQLState* state, StoredQ* query );
40 static void free_cached_query( char* key, void* data );
41 static void userDataFree( void* blob );
42 static CachedQuery* search_token( osrfMethodContext* ctx, const char* token );
45 @brief Disconnect from the database.
47 This function is called when the server drone is about to terminate.
49 void osrfAppChildExit() {
50 osrfLogDebug( OSRF_LOG_MARK, "Child is exiting, disconnecting from database..." );
53 dbi_conn_query( dbhandle, "ROLLBACK;" );
54 dbi_conn_close( dbhandle );
60 @brief Initialize the application.
61 @return Zero if successful, or non-zero if not.
63 Load the IDL file into an internal data structure for future reference. Each non-virtual
64 class in the IDL corresponds to a table or view in the database, or to a subquery defined
65 in the IDL. Ignore all virtual tables and virtual fields.
67 Register the functions for remote procedure calls.
69 This function is called when the registering the application, and is executed by the
70 listener before spawning the drones.
72 int osrfAppInitialize() {
74 osrfLogInfo( OSRF_LOG_MARK, "Initializing the QStore Server..." );
75 osrfLogInfo( OSRF_LOG_MARK, "Finding XML file..." );
77 if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
78 return 1; /* return non-zero to indicate error */
80 growing_buffer* method_name = buffer_init( 64 );
82 OSRF_BUFFER_ADD( method_name, modulename );
83 OSRF_BUFFER_ADD( method_name, ".prepare" );
84 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
85 "doPrepare", "", 1, 0 );
87 buffer_reset( method_name );
88 OSRF_BUFFER_ADD( method_name, modulename );
89 OSRF_BUFFER_ADD( method_name, ".bind_param" );
90 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
91 "doBindParam", "", 2, 0 );
93 buffer_reset( method_name );
94 OSRF_BUFFER_ADD( method_name, modulename );
95 OSRF_BUFFER_ADD( method_name, ".execute" );
96 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
97 "doExecute", "", 1, OSRF_METHOD_STREAMING );
99 buffer_reset( method_name );
100 OSRF_BUFFER_ADD( method_name, modulename );
101 OSRF_BUFFER_ADD( method_name, ".sql" );
102 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
103 "doSql", "", 1, OSRF_METHOD_STREAMING );
105 buffer_reset( method_name );
106 OSRF_BUFFER_ADD( method_name, modulename );
107 OSRF_BUFFER_ADD( method_name, ".finish" );
108 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
109 "doFinish", "", 1, 0 );
115 @brief Initialize a server drone.
116 @return Zero if successful, -1 if not.
118 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
119 query to get the datatype of each column. Record the datatypes in the loaded IDL.
121 This function is called by a server drone shortly after it is spawned by the listener.
123 int osrfAppChildInit() {
125 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
126 dbi_initialize( NULL );
127 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
129 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", modulename );
130 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", modulename );
131 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", modulename );
132 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", modulename );
133 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", modulename );
134 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", modulename );
136 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
137 dbhandle = dbi_conn_new( driver );
140 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
143 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
145 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
146 "port=%s, user=%s, db=%s", modulename, host, port, user, db );
148 if( host ) dbi_conn_set_option( dbhandle, "host", host );
149 if( port ) dbi_conn_set_option_numeric( dbhandle, "port", atoi( port ) );
150 if( user ) dbi_conn_set_option( dbhandle, "username", user );
151 if( pw ) dbi_conn_set_option( dbhandle, "password", pw );
152 if( db ) dbi_conn_set_option( dbhandle, "dbname", db );
161 if( dbi_conn_connect( dbhandle ) < 0 ) {
163 if( dbi_conn_connect( dbhandle ) < 0 ) {
164 dbi_conn_error( dbhandle, &err );
165 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err );
170 oilsSetDBConnection( dbhandle );
171 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
173 // Add datatypes from database to the fields in the IDL
174 if( oilsExtendIDL() ) {
175 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
183 @brief Load a specified query from the database query tables.
184 @param ctx Pointer to the current method context.
185 @return Zero if successful, or -1 if not.
188 - query id (key of query.stored_query table)
190 Returns: a character string serving as a token for future references to the query.
192 NB: the method return type is temporary. Eventually this method will return both a token
193 and a list of bind variables.
195 int doPrepare( osrfMethodContext* ctx ) {
196 if(osrfMethodVerifyContext( ctx )) {
197 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
201 // Get the query id from a method parameter
202 const jsonObject* query_id_obj = jsonObjectGetIndex( ctx->params, 0 );
203 if( query_id_obj->type != JSON_NUMBER ) {
204 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
205 ctx->request, "Invalid parameter; query id must be a number" );
209 int query_id = atoi( jsonObjectGetString( query_id_obj ));
210 if( query_id <= 0 ) {
211 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
212 ctx->request, "Invalid parameter: query id must be greater than zero" );
216 osrfLogInfo( OSRF_LOG_MARK, "Loading query for id # %d", query_id );
218 BuildSQLState* state = buildSQLStateNew( dbhandle );
219 StoredQ* query = getStoredQuery( state, query_id );
221 osrfLogWarning( OSRF_LOG_MARK, "Unable to load stored query # %d", query_id );
222 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
223 ctx->request, "Unable to load stored query" );
227 const char* token = save_query( ctx, state, query );
229 osrfLogInfo( OSRF_LOG_MARK, "Token for query id # %d is \"%s\"", query_id, token );
231 osrfAppRespondComplete( ctx, jsonNewObject( token ));
235 int doBindParam( osrfMethodContext* ctx ) {
236 if(osrfMethodVerifyContext( ctx )) {
237 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
241 // Get the query token from a method parameter
242 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
243 if( token_obj->type != JSON_STRING ) {
244 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
245 ctx->request, "Invalid parameter; query token must be a string" );
248 const char* token = jsonObjectGetString( token_obj );
250 // Look up the query token in the session-level userData
251 CachedQuery* query = search_token( ctx, token );
253 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
254 ctx->request, "Invalid query token" );
258 osrfLogInfo( OSRF_LOG_MARK, "Binding parameter(s) for token %s", token );
260 osrfAppRespondComplete( ctx, jsonNewObject( "build method not yet implemented" ));
264 int doExecute( osrfMethodContext* ctx ) {
265 if(osrfMethodVerifyContext( ctx )) {
266 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
270 // Get the query token
271 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
272 if( token_obj->type != JSON_STRING ) {
273 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
274 ctx->request, "Invalid parameter; query token must be a string" );
277 const char* token = jsonObjectGetString( token_obj );
279 // Look up the query token in the session-level userData
280 CachedQuery* query = search_token( ctx, token );
282 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
283 ctx->request, "Invalid query token" );
287 osrfLogInfo( OSRF_LOG_MARK, "Executing query for token \"%s\"", token );
288 if( query->state->error ) {
289 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
290 "No valid prepared query available for query id # %d", query->query->id ));
291 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
292 ctx->request, "No valid prepared query available" );
294 } else if( buildSQL( query->state, query->query )) {
295 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
296 "Unable to build SQL statement for query id # %d", query->query->id ));
297 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
298 ctx->request, "Unable to build SQL statement" );
302 jsonObject* row = oilsFirstRow( query->state );
304 osrfAppRespond( ctx, row );
305 row = oilsNextRow( query->state );
308 if( query->state->error ) {
309 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
310 "Unable to execute SQL statement for query id # %d", query->query->id ));
311 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
312 ctx->request, "Unable to execute SQL statement" );
316 osrfAppRespondComplete( ctx, NULL );
320 int doSql( osrfMethodContext* ctx ) {
321 if(osrfMethodVerifyContext( ctx )) {
322 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
326 // Get the query token
327 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
328 if( token_obj->type != JSON_STRING ) {
329 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
330 ctx->request, "Invalid parameter; query token must be a string" );
333 const char* token = jsonObjectGetString( token_obj );
335 // Look up the query token in the session-level userData
336 CachedQuery* query = search_token( ctx, token );
338 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
339 ctx->request, "Invalid query token" );
343 osrfLogInfo( OSRF_LOG_MARK, "Returning SQL for token \"%s\"", token );
344 if( query->state->error ) {
345 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
346 "No valid prepared query available for query id # %d", query->query->id ));
347 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
348 ctx->request, "No valid prepared query available" );
350 } else if( buildSQL( query->state, query->query )) {
351 osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( query->state,
352 "Unable to build SQL statement for query id # %d", query->query->id ));
353 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
354 ctx->request, "Unable to build SQL statement" );
358 osrfAppRespondComplete( ctx, jsonNewObject( OSRF_BUFFER_C_STR( query->state->sql )));
363 @brief Discard a previously stored query, as identified by a token.
364 @param ctx Pointer to the current method context.
365 @return Zero if successful, or -1 if not.
368 - query token, as previously returned by the .prepare method.
372 int doFinish( osrfMethodContext* ctx ) {
373 if(osrfMethodVerifyContext( ctx )) {
374 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
378 // Get the query token.
379 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
380 if( token_obj->type != JSON_STRING ) {
381 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
382 ctx->request, "Invalid parameter; query token must be a string" );
385 const char* token = jsonObjectGetString( token_obj );
387 // Delete the corresponding entry from the cache. If there is no cache, or no such entry,
388 // just ignore the problem and report success.
389 osrfHash* cache = ctx->session->userData;
391 osrfHashRemove( cache, token );
393 osrfAppRespondComplete( ctx, NULL );
398 @brief Save a query in session-level userData for reference in future method calls.
399 @param ctx Pointer to the current method context.
400 @param state Pointer to the state of the query.
401 @param query Pointer to the abstract representation of the query.
402 @return Pointer to an identifying token to be returned to the client.
404 static const char* save_query(
405 osrfMethodContext* ctx, BuildSQLState* state, StoredQ* query ) {
407 CachedQuery* cached_query = safe_malloc( sizeof( CachedQuery ));
408 cached_query->state = state;
409 cached_query->query = query;
411 // Get the cache. If we don't have one yet, make one.
412 osrfHash* cache = ctx->session->userData;
414 cache = osrfNewHash();
415 osrfHashSetCallback( cache, free_cached_query );
416 ctx->session->userData = cache;
417 ctx->session->userDataFree = userDataFree; // arrange to free it at end of session
420 // Create a token string to be used as a key
421 static unsigned int token_count = 0;
422 char* token = va_list_to_string(
423 "%u_%ld_%ld", ++token_count, (long) time( NULL ), (long) getpid() );
425 osrfHashSet( cache, cached_query, token );
430 @brief Free a CachedQuery
431 @param Pointer to the CachedQuery to be freed.
433 static void free_cached_query( char* key, void* data ) {
435 CachedQuery* cached_query = data;
436 buildSQLStateFree( cached_query->state );
437 storedQFree( cached_query->query );
442 @brief Callback for freeing session-level userData.
443 @param blob Opaque pointer t userData.
445 static void userDataFree( void* blob ) {
446 osrfHashFree( (osrfHash*) blob );
450 @brief Search for the cached query corresponding to a given token.
451 @param ctx Pointer to the current method context.
452 @param token Token string from a previous call to the prepare method.
453 @return A pointer to the cached query, if found, or NULL if not.
455 static CachedQuery* search_token( osrfMethodContext* ctx, const char* token ) {
456 if( ctx && ctx->session->userData && token ) {
457 osrfHash* cache = ctx->session->userData;
458 return osrfHashGet( cache, token );