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( osrfMethodContext* ctx, StoredQ* query, jsonObject* bind_map );
39 static void free_cached_query( char* key, void* data );
40 static void userDataFree( void* blob );
41 static CachedQuery* search_token( osrfMethodContext* ctx, const char* token );
44 @brief Disconnect from the database.
46 This function is called when the server drone is about to terminate.
48 void osrfAppChildExit() {
49 osrfLogDebug( OSRF_LOG_MARK, "Child is exiting, disconnecting from database..." );
52 dbi_conn_query( dbhandle, "ROLLBACK;" );
53 dbi_conn_close( dbhandle );
59 @brief Initialize the application.
60 @return Zero if successful, or non-zero if not.
62 Load the IDL file into an internal data structure for future reference. Each non-virtual
63 class in the IDL corresponds to a table or view in the database, or to a subquery defined
64 in the IDL. Ignore all virtual tables and virtual fields.
66 Register the functions for remote procedure calls.
68 This function is called when the registering the application, and is executed by the
69 listener before spawning the drones.
71 int osrfAppInitialize() {
73 osrfLogInfo( OSRF_LOG_MARK, "Initializing the QStore Server..." );
74 osrfLogInfo( OSRF_LOG_MARK, "Finding XML file..." );
76 if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
77 return 1; /* return non-zero to indicate error */
79 growing_buffer* method_name = buffer_init( 64 );
81 OSRF_BUFFER_ADD( method_name, modulename );
82 OSRF_BUFFER_ADD( method_name, ".prepare" );
83 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
84 "doPrepare", "", 1, 0 );
86 buffer_reset( method_name );
87 OSRF_BUFFER_ADD( method_name, modulename );
88 OSRF_BUFFER_ADD( method_name, ".bind_param" );
89 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
90 "doBindParam", "", 2, 0 );
92 buffer_reset( method_name );
93 OSRF_BUFFER_ADD( method_name, modulename );
94 OSRF_BUFFER_ADD( method_name, ".execute" );
95 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
96 "doExecute", "", 1, OSRF_METHOD_STREAMING );
98 buffer_reset( method_name );
99 OSRF_BUFFER_ADD( method_name, modulename );
100 OSRF_BUFFER_ADD( method_name, ".sql" );
101 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
102 "doSql", "", 1, OSRF_METHOD_STREAMING );
108 @brief Initialize a server drone.
109 @return Zero if successful, -1 if not.
111 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
112 query to get the datatype of each column. Record the datatypes in the loaded IDL.
114 This function is called by a server drone shortly after it is spawned by the listener.
116 int osrfAppChildInit() {
118 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
119 dbi_initialize( NULL );
120 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
122 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", modulename );
123 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", modulename );
124 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", modulename );
125 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", modulename );
126 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", modulename );
127 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", modulename );
129 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
130 dbhandle = dbi_conn_new( driver );
133 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
136 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
138 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
139 "port=%s, user=%s, db=%s", modulename, host, port, user, db );
141 if( host ) dbi_conn_set_option( dbhandle, "host", host );
142 if( port ) dbi_conn_set_option_numeric( dbhandle, "port", atoi( port ) );
143 if( user ) dbi_conn_set_option( dbhandle, "username", user );
144 if( pw ) dbi_conn_set_option( dbhandle, "password", pw );
145 if( db ) dbi_conn_set_option( dbhandle, "dbname", db );
154 if( dbi_conn_connect( dbhandle ) < 0 ) {
156 if( dbi_conn_connect( dbhandle ) < 0 ) {
157 dbi_conn_error( dbhandle, &err );
158 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err );
163 oilsSetDBConnection( dbhandle );
164 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
166 // Add datatypes from database to the fields in the IDL
167 if( oilsExtendIDL() ) {
168 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
176 @brief Load a specified query from the database query tables.
177 @param ctx Pointer to the current method context.
178 @return Zero if successful, or -1 if not.
181 - query id (key of query.stored_query table)
183 Returns: a character string serving as a token for future references to the query.
185 NB: the method return type is temporary. Eventually this method will return both a token
186 and a list of bind variables.
188 int doPrepare( osrfMethodContext* ctx ) {
189 if(osrfMethodVerifyContext( ctx )) {
190 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
194 // Get the query id from a method parameter
195 const jsonObject* query_id_obj = jsonObjectGetIndex( ctx->params, 0 );
196 if( query_id_obj->type != JSON_NUMBER ) {
197 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
198 ctx->request, "Invalid parameter; query id must be a number" );
202 int query_id = atoi( jsonObjectGetString( query_id_obj ));
203 if( query_id <= 0 ) {
204 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
205 ctx->request, "Invalid parameter: query id must be greater than zero" );
209 osrfLogInfo( OSRF_LOG_MARK, "Building query for id # %d", query_id );
211 // To do: prepare query
212 StoredQ* query = NULL;
213 jsonObject* bind_map = NULL;
214 const char* token = save_query( ctx, query, bind_map );
216 osrfLogInfo( OSRF_LOG_MARK, "Token for query id # %d is \"%s\"", query_id, token );
218 osrfAppRespondComplete( ctx, jsonNewObject( token ));
222 int doBindParam( osrfMethodContext* ctx ) {
223 if(osrfMethodVerifyContext( ctx )) {
224 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
228 // Get the query token from a method parameter
229 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
230 if( token_obj->type != JSON_STRING ) {
231 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
232 ctx->request, "Invalid parameter; query token must be a string" );
235 const char* token = jsonObjectGetString( token_obj );
237 // Look up the query token in the session-level userData
238 CachedQuery* query = search_token( ctx, token );
240 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
241 ctx->request, "Invalid query token" );
245 osrfLogInfo( OSRF_LOG_MARK, "Binding parameter(s) for token %s", token );
247 osrfAppRespondComplete( ctx, jsonNewObject( "build method not yet implemented" ));
251 int doExecute( osrfMethodContext* ctx ) {
252 if(osrfMethodVerifyContext( ctx )) {
253 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
257 // Get the query token
258 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
259 if( token_obj->type != JSON_STRING ) {
260 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
261 ctx->request, "Invalid parameter; query token must be a string" );
264 const char* token = jsonObjectGetString( token_obj );
266 // Look up the query token in the session-level userData
267 CachedQuery* query = search_token( ctx, token );
269 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
270 ctx->request, "Invalid query token" );
274 osrfLogInfo( OSRF_LOG_MARK, "Executing query for token \"%s\"", token );
276 osrfAppRespondComplete( ctx, jsonNewObject( "execute method not yet implemented" ));
280 int doSql( osrfMethodContext* ctx ) {
281 if(osrfMethodVerifyContext( ctx )) {
282 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
286 // Get the query token
287 const jsonObject* token_obj = jsonObjectGetIndex( ctx->params, 0 );
288 if( token_obj->type != JSON_STRING ) {
289 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
290 ctx->request, "Invalid parameter; query token must be a string" );
293 const char* token = jsonObjectGetString( token_obj );
295 // Look up the query token in the session-level userData
296 CachedQuery* query = search_token( ctx, token );
298 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
299 ctx->request, "Invalid query token" );
303 osrfLogInfo( OSRF_LOG_MARK, "Returning SQL for token \"%s\"", token );
305 osrfAppRespondComplete( ctx, jsonNewObject( "sql method not yet implemented" ));
309 static const char* save_query( osrfMethodContext* ctx, StoredQ* query, jsonObject* bind_map ) {
311 CachedQuery* cached_query = safe_malloc( sizeof( CachedQuery ));
312 cached_query->query = query;
313 cached_query->bind_map = bind_map;
315 // Get the cache. If we don't have one yet, make one.
316 osrfHash* cache = ctx->session->userData;
318 cache = osrfNewHash();
319 osrfHashSetCallback( cache, free_cached_query );
320 ctx->session->userData = cache;
321 ctx->session->userDataFree = userDataFree; // arrange to free it at end of session
324 // Create a token string to be used as a key
325 static unsigned int token_count = 0;
326 char* token = va_list_to_string(
327 "%u_%ld_%ld", ++token_count, (long) time( NULL ), (long) getpid() );
329 osrfHashSet( cache, cached_query, token );
334 @brief Free a CachedQuery
335 @param Pointer to the CachedQuery to be freed.
337 static void free_cached_query( char* key, void* data ) {
339 CachedQuery* cached_query = data;
340 //storedQFree( cached_query->query );
341 if( cached_query->bind_map )
342 jsonObjectFree( cached_query->bind_map );
347 @brief Callback for freeing session-level userData.
348 @param blob Opaque pointer t userData.
350 static void userDataFree( void* blob ) {
351 osrfHashFree( (osrfHash*) blob );
355 @brief Search for the cached query corresponding to a given token.
356 @param ctx Pointer to the current method context.
357 @param token Token string from a previous call to the prepare method.
358 @return A pointer to the cached query, if found, or NULL if not.
360 static CachedQuery* search_token( osrfMethodContext* ctx, const char* token ) {
361 if( ctx && ctx->session->userData && token ) {
362 osrfHash* cache = ctx->session->userData;
363 return osrfHashGet( cache, token );