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 );
109 @brief Initialize a server drone.
110 @return Zero if successful, -1 if not.
112 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
113 query to get the datatype of each column. Record the datatypes in the loaded IDL.
115 This function is called by a server drone shortly after it is spawned by the listener.
117 int osrfAppChildInit() {
119 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
120 dbi_initialize( NULL );
121 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
123 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", modulename );
124 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", modulename );
125 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", modulename );
126 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", modulename );
127 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", modulename );
128 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", modulename );
130 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
131 dbhandle = dbi_conn_new( driver );
134 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
137 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
139 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
140 "port=%s, user=%s, db=%s", modulename, host, port, user, db );
142 if( host ) dbi_conn_set_option( dbhandle, "host", host );
143 if( port ) dbi_conn_set_option_numeric( dbhandle, "port", atoi( port ) );
144 if( user ) dbi_conn_set_option( dbhandle, "username", user );
145 if( pw ) dbi_conn_set_option( dbhandle, "password", pw );
146 if( db ) dbi_conn_set_option( dbhandle, "dbname", db );
155 if( dbi_conn_connect( dbhandle ) < 0 ) {
157 if( dbi_conn_connect( dbhandle ) < 0 ) {
158 dbi_conn_error( dbhandle, &err );
159 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err );
164 oilsSetDBConnection( dbhandle );
165 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
167 // Add datatypes from database to the fields in the IDL
168 if( oilsExtendIDL() ) {
169 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
177 @brief Load a specified query from the database query tables.
178 @param ctx Pointer to the current method context.
179 @return Zero if successful, or -1 if not.
182 - query id (key of query.stored_query table)
184 Returns: a character string serving as a token for future references to the query.
186 NB: the method return type is temporary. Eventually this method will return both a token
187 and a list of bind variables.
189 int doPrepare( osrfMethodContext* ctx ) {
190 if(osrfMethodVerifyContext( ctx )) {
191 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
195 // Get the query id from a method parameter
196 const jsonObject* query_id_obj = jsonObjectGetIndex( ctx->params, 0 );
197 if( query_id_obj->type != JSON_NUMBER ) {
198 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
199 ctx->request, "Invalid parameter; query id must be a number" );
203 int query_id = atoi( jsonObjectGetString( query_id_obj ));
204 if( query_id <= 0 ) {
205 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
206 ctx->request, "Invalid parameter: query id must be greater than zero" );
210 osrfLogInfo( OSRF_LOG_MARK, "Loading query for id # %d", query_id );
212 BuildSQLState* state = buildSQLStateNew( dbhandle );
213 StoredQ* query = getStoredQuery( state, query_id );
215 osrfLogWarning( OSRF_LOG_MARK, "Unable to load stored query # %d", query_id );
216 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
217 ctx->request, "Unable to load stored query" );
221 const char* token = save_query( ctx, state, query );
223 osrfLogInfo( OSRF_LOG_MARK, "Token for query id # %d is \"%s\"", query_id, token );
225 osrfAppRespondComplete( ctx, jsonNewObject( token ));
229 int doBindParam( osrfMethodContext* ctx ) {
230 if(osrfMethodVerifyContext( ctx )) {
231 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
235 // Get the query token from a method parameter
236 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
237 if( token_obj->type != JSON_STRING ) {
238 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
239 ctx->request, "Invalid parameter; query token must be a string" );
242 const char* token = jsonObjectGetString( token_obj );
244 // Look up the query token in the session-level userData
245 CachedQuery* query = search_token( ctx, token );
247 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
248 ctx->request, "Invalid query token" );
252 osrfLogInfo( OSRF_LOG_MARK, "Binding parameter(s) for token %s", token );
254 osrfAppRespondComplete( ctx, jsonNewObject( "build method not yet implemented" ));
258 int doExecute( osrfMethodContext* ctx ) {
259 if(osrfMethodVerifyContext( ctx )) {
260 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
264 // Get the query token
265 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
266 if( token_obj->type != JSON_STRING ) {
267 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
268 ctx->request, "Invalid parameter; query token must be a string" );
271 const char* token = jsonObjectGetString( token_obj );
273 // Look up the query token in the session-level userData
274 CachedQuery* query = search_token( ctx, token );
276 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
277 ctx->request, "Invalid query token" );
281 osrfLogInfo( OSRF_LOG_MARK, "Executing query for token \"%s\"", token );
283 osrfAppRespondComplete( ctx, jsonNewObject( "execute method not yet implemented" ));
287 int doSql( osrfMethodContext* ctx ) {
288 if(osrfMethodVerifyContext( ctx )) {
289 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
293 // Get the query token
294 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
295 if( token_obj->type != JSON_STRING ) {
296 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
297 ctx->request, "Invalid parameter; query token must be a string" );
300 const char* token = jsonObjectGetString( token_obj );
302 // Look up the query token in the session-level userData
303 CachedQuery* query = search_token( ctx, token );
305 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
306 ctx->request, "Invalid query token" );
310 osrfLogInfo( OSRF_LOG_MARK, "Returning SQL for token \"%s\"", token );
311 if( query->state->error ) {
312 osrfLogWarning( OSRF_LOG_MARK, "No valid prepared query available for query id # %d",
314 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
315 ctx->request, "No valid prepared query available" );
317 } else if( buildSQL( query->state, query->query )) {
318 osrfLogWarning( OSRF_LOG_MARK, "Unable to build SQL statement for query id # %d",
320 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
321 ctx->request, "Unable to build SQL statement" );
325 osrfAppRespondComplete( ctx, jsonNewObject( OSRF_BUFFER_C_STR( query->state->sql )));
330 @brief Save a query in session-level userData for reference in future method calls.
331 @param ctx Pointer to the current method context.
332 @param state Pointer to the state of the query.
333 @param query Pointer to the abstract representation of the query.
334 @return Pointer to an identifying token to be returned to the client.
336 static const char* save_query(
337 osrfMethodContext* ctx, BuildSQLState* state, StoredQ* query ) {
339 CachedQuery* cached_query = safe_malloc( sizeof( CachedQuery ));
340 cached_query->state = state;
341 cached_query->query = query;
343 // Get the cache. If we don't have one yet, make one.
344 osrfHash* cache = ctx->session->userData;
346 cache = osrfNewHash();
347 osrfHashSetCallback( cache, free_cached_query );
348 ctx->session->userData = cache;
349 ctx->session->userDataFree = userDataFree; // arrange to free it at end of session
352 // Create a token string to be used as a key
353 static unsigned int token_count = 0;
354 char* token = va_list_to_string(
355 "%u_%ld_%ld", ++token_count, (long) time( NULL ), (long) getpid() );
357 osrfHashSet( cache, cached_query, token );
362 @brief Free a CachedQuery
363 @param Pointer to the CachedQuery to be freed.
365 static void free_cached_query( char* key, void* data ) {
367 CachedQuery* cached_query = data;
368 buildSQLStateFree( cached_query->state );
369 storedQFree( cached_query->query );
374 @brief Callback for freeing session-level userData.
375 @param blob Opaque pointer t userData.
377 static void userDataFree( void* blob ) {
378 osrfHashFree( (osrfHash*) blob );
382 @brief Search for the cached query corresponding to a given token.
383 @param ctx Pointer to the current method context.
384 @param token Token string from a previous call to the prepare method.
385 @return A pointer to the cached query, if found, or NULL if not.
387 static CachedQuery* search_token( osrfMethodContext* ctx, const char* token ) {
388 if( ctx && ctx->session->userData && token ) {
389 osrfHash* cache = ctx->session->userData;
390 return osrfHashGet( cache, token );