3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static int is_identifier( const char* s );
106 static int is_good_operator( const char* op );
107 static void pop_query_frame( void );
108 static void push_query_frame( void );
109 static int add_query_core( const char* alias, const char* class_name );
110 static inline ClassInfo* search_alias( const char* target );
111 static ClassInfo* search_all_alias( const char* target );
112 static ClassInfo* add_joined_class( const char* alias, const char* classname );
113 static void clear_query_stack( void );
115 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
116 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
117 static char* org_tree_root( osrfMethodContext* ctx );
118 static jsonObject* single_hash( const char* key, const char* value );
120 static int child_initialized = 0; /* boolean */
122 static dbi_conn writehandle; /* our MASTER db connection */
123 static dbi_conn dbhandle; /* our CURRENT db connection */
124 //static osrfHash * readHandles;
126 // The following points to the top of a stack of QueryFrames. It's a little
127 // confusing because the top level of the query is at the bottom of the stack.
128 static QueryFrame* curr_query = NULL;
130 static dbi_conn writehandle; /* our MASTER db connection */
131 static dbi_conn dbhandle; /* our CURRENT db connection */
132 //static osrfHash * readHandles;
134 static int max_flesh_depth = 100;
136 static int enforce_pcrud = 0; // Boolean
137 static char* modulename = NULL;
140 @brief Connect to the database.
141 @return A database connection if successful, or NULL if not.
143 dbi_conn oilsConnectDB( const char* mod_name ) {
145 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
146 if( dbi_initialize( NULL ) == -1 ) {
147 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
150 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
152 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
153 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
154 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
155 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
156 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
157 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
159 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
160 dbi_conn handle = dbi_conn_new( driver );
163 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
166 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
168 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
169 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
171 if( host ) dbi_conn_set_option( handle, "host", host );
172 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
173 if( user ) dbi_conn_set_option( handle, "username", user );
174 if( pw ) dbi_conn_set_option( handle, "password", pw );
175 if( db ) dbi_conn_set_option( handle, "dbname", db );
183 if( dbi_conn_connect( handle ) < 0 ) {
185 if( dbi_conn_connect( handle ) < 0 ) {
187 dbi_conn_error( handle, &errmsg );
188 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
193 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
199 @brief Select some options.
200 @param module_name: Name of the server.
201 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
203 This source file is used (at this writing) to implement three different servers:
204 - open-ils.reporter-store
208 These servers behave mostly the same, but they implement different combinations of
209 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
211 Here we use the server name in messages to identify which kind of server issued them.
212 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
214 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
216 module_name = "open-ils.cstore"; // bulletproofing with a default
221 modulename = strdup( module_name );
222 enforce_pcrud = do_pcrud;
223 max_flesh_depth = flesh_depth;
227 @brief Install a database connection.
228 @param conn Pointer to a database connection.
230 In some contexts, @a conn may merely provide a driver so that we can process strings
231 properly, without providing an open database connection.
233 void oilsSetDBConnection( dbi_conn conn ) {
234 dbhandle = writehandle = conn;
238 @brief Get a table name, view name, or subquery for use in a FROM clause.
239 @param class Pointer to the IDL class entry.
240 @return A table name, a view name, or a subquery in parentheses.
242 In some cases the IDL defines a class, not with a table name or a view name, but with
243 a SELECT statement, which may be used as a subquery.
245 char* oilsGetRelation( osrfHash* classdef ) {
247 char* source_def = NULL;
248 const char* tabledef = osrfHashGet( classdef, "tablename" );
251 source_def = strdup( tabledef ); // Return the name of a table or view
253 tabledef = osrfHashGet( classdef, "source_definition" );
255 // Return a subquery, enclosed in parentheses
256 source_def = safe_malloc( strlen( tabledef ) + 3 );
257 source_def[ 0 ] = '(';
258 strcpy( source_def + 1, tabledef );
259 strcat( source_def, ")" );
261 // Not found: return an error
262 const char* classname = osrfHashGet( classdef, "classname" );
267 "%s ERROR No tablename or source_definition for class \"%s\"",
278 @brief Add datatypes from the database to the fields in the IDL.
279 @param handle Handle for a database connection
280 @return Zero if successful, or 1 upon error.
282 For each relevant class in the IDL: ask the database for the datatype of every field.
283 In particular, determine which fields are text fields and which fields are numeric
284 fields, so that we know whether to enclose their values in quotes.
286 int oilsExtendIDL( dbi_conn handle ) {
287 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
288 osrfHash* class = NULL;
289 growing_buffer* query_buf = buffer_init( 64 );
290 int results_found = 0; // boolean
292 // For each class in the IDL...
293 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
294 const char* classname = osrfHashIteratorKey( class_itr );
295 osrfHash* fields = osrfHashGet( class, "fields" );
297 // If the class is virtual, ignore it
298 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
299 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
303 char* tabledef = oilsGetRelation( class );
305 continue; // No such relation -- a query of it would be doomed to failure
307 buffer_reset( query_buf );
308 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
312 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
313 modulename, OSRF_BUFFER_C_STR( query_buf ) );
315 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
320 const char* columnName;
321 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
323 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
326 /* fetch the fieldmapper index */
327 osrfHash* _f = osrfHashGet(fields, columnName);
330 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
332 /* determine the field type and storage attributes */
334 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
336 case DBI_TYPE_INTEGER : {
338 if( !osrfHashGet(_f, "primitive") )
339 osrfHashSet(_f, "number", "primitive");
341 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
342 if( attr & DBI_INTEGER_SIZE8 )
343 osrfHashSet( _f, "INT8", "datatype" );
345 osrfHashSet( _f, "INT", "datatype" );
348 case DBI_TYPE_DECIMAL :
349 if( !osrfHashGet( _f, "primitive" ))
350 osrfHashSet( _f, "number", "primitive" );
352 osrfHashSet( _f, "NUMERIC", "datatype" );
355 case DBI_TYPE_STRING :
356 if( !osrfHashGet( _f, "primitive" ))
357 osrfHashSet( _f, "string", "primitive" );
359 osrfHashSet( _f,"TEXT", "datatype" );
362 case DBI_TYPE_DATETIME :
363 if( !osrfHashGet( _f, "primitive" ))
364 osrfHashSet( _f, "string", "primitive" );
366 osrfHashSet( _f, "TIMESTAMP", "datatype" );
369 case DBI_TYPE_BINARY :
370 if( !osrfHashGet( _f, "primitive" ))
371 osrfHashSet( _f, "string", "primitive" );
373 osrfHashSet( _f, "BYTEA", "datatype" );
378 "Setting [%s] to primitive [%s] and datatype [%s]...",
380 osrfHashGet( _f, "primitive" ),
381 osrfHashGet( _f, "datatype" )
385 } // end while loop for traversing columns of result
386 dbi_result_free( result );
388 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
390 } // end for each class in IDL
392 buffer_free( query_buf );
393 osrfHashIteratorFree( class_itr );
394 child_initialized = 1;
396 if( !results_found ) {
397 osrfLogError( OSRF_LOG_MARK,
398 "No results found for any class -- bad database connection?" );
406 @brief Free an osrfHash that stores a transaction ID.
407 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
409 This function is a callback, to be called by the application session when it ends.
410 The application session stores the osrfHash via an opaque pointer.
412 If the osrfHash contains an entry for the key "xact_id", it means that an
413 uncommitted transaction is pending. Roll it back.
415 void userDataFree( void* blob ) {
416 osrfHash* hash = (osrfHash*) blob;
417 if( osrfHashGet( hash, "xact_id" ) && writehandle )
418 dbi_conn_query( writehandle, "ROLLBACK;" );
420 osrfHashFree( hash );
424 @name Managing session data
425 @brief Maintain data stored via the userData pointer of the application session.
427 Currently, session-level data is stored in an osrfHash. Other arrangements are
428 possible, and some would be more efficient. The application session calls a
429 callback function to free userData before terminating.
431 Currently, the only data we store at the session level is the transaction id. By this
432 means we can ensure that any pending transactions are rolled back before the application
438 @brief Free an item in the application session's userData.
439 @param key The name of a key for an osrfHash.
440 @param item An opaque pointer to the item associated with the key.
442 We store an osrfHash as userData with the application session, and arrange (by
443 installing userDataFree() as a different callback) for the session to free that
444 osrfHash before terminating.
446 This function is a callback for freeing items in the osrfHash. Currently we store
448 - Transaction id of a pending transaction; a character string. Key: "xact_id".
449 - Authkey; a character string. Key: "authkey".
450 - User object from the authentication server; a jsonObject. Key: "user_login".
452 If we ever store anything else in userData, we will need to revisit this function so
453 that it will free whatever else needs freeing.
455 static void sessionDataFree( char* key, void* item ) {
456 if( !strcmp( key, "xact_id" )
457 || !strcmp( key, "authkey" ) ) {
459 } else if( !strcmp( key, "user_login" ) )
460 jsonObjectFree( (jsonObject*) item );
464 @brief Save a transaction id.
465 @param ctx Pointer to the method context.
467 Save the session_id of the current application session as a transaction id.
469 static void setXactId( osrfMethodContext* ctx ) {
470 if( ctx && ctx->session ) {
471 osrfAppSession* session = ctx->session;
473 osrfHash* cache = session->userData;
475 // If the session doesn't already have a hash, create one. Make sure
476 // that the application session frees the hash when it terminates.
477 if( NULL == cache ) {
478 session->userData = cache = osrfNewHash();
479 osrfHashSetCallback( cache, &sessionDataFree );
480 ctx->session->userDataFree = &userDataFree;
483 // Save the transaction id in the hash, with the key "xact_id"
484 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
489 @brief Get the transaction ID for the current transaction, if any.
490 @param ctx Pointer to the method context.
491 @return Pointer to the transaction ID.
493 The return value points to an internal buffer, and will become invalid upon issuing
494 a commit or rollback.
496 static inline const char* getXactId( osrfMethodContext* ctx ) {
497 if( ctx && ctx->session && ctx->session->userData )
498 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
504 @brief Clear the current transaction id.
505 @param ctx Pointer to the method context.
507 static inline void clearXactId( osrfMethodContext* ctx ) {
508 if( ctx && ctx->session && ctx->session->userData )
509 osrfHashRemove( ctx->session->userData, "xact_id" );
514 @brief Save the user's login in the userData for the current application session.
515 @param ctx Pointer to the method context.
516 @param user_login Pointer to the user login object to be cached (we cache the original,
519 If @a user_login is NULL, remove the user login if one is already cached.
521 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
522 if( ctx && ctx->session ) {
523 osrfAppSession* session = ctx->session;
525 osrfHash* cache = session->userData;
527 // If the session doesn't already have a hash, create one. Make sure
528 // that the application session frees the hash when it terminates.
529 if( NULL == cache ) {
530 session->userData = cache = osrfNewHash();
531 osrfHashSetCallback( cache, &sessionDataFree );
532 ctx->session->userDataFree = &userDataFree;
536 osrfHashSet( cache, user_login, "user_login" );
538 osrfHashRemove( cache, "user_login" );
543 @brief Get the user login object for the current application session, if any.
544 @param ctx Pointer to the method context.
545 @return Pointer to the user login object if found; otherwise NULL.
547 The user login object was returned from the authentication server, and then cached so
548 we don't have to call the authentication server again for the same user.
550 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
551 if( ctx && ctx->session && ctx->session->userData )
552 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
558 @brief Save a copy of an authkey in the userData of the current application session.
559 @param ctx Pointer to the method context.
560 @param authkey The authkey to be saved.
562 If @a authkey is NULL, remove the authkey if one is already cached.
564 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
565 if( ctx && ctx->session && authkey ) {
566 osrfAppSession* session = ctx->session;
567 osrfHash* cache = session->userData;
569 // If the session doesn't already have a hash, create one. Make sure
570 // that the application session frees the hash when it terminates.
571 if( NULL == cache ) {
572 session->userData = cache = osrfNewHash();
573 osrfHashSetCallback( cache, &sessionDataFree );
574 ctx->session->userDataFree = &userDataFree;
577 // Save the transaction id in the hash, with the key "xact_id"
578 if( authkey && *authkey )
579 osrfHashSet( cache, strdup( authkey ), "authkey" );
581 osrfHashRemove( cache, "authkey" );
586 @brief Reset the login timeout.
587 @param authkey The authentication key for the current login session.
588 @param now The current time.
589 @return Zero if successful, or 1 if not.
591 Tell the authentication server to reset the timeout so that the login session won't
592 expire for a while longer.
594 We could dispense with the @a now parameter by calling time(). But we just called
595 time() in order to decide whether to reset the timeout, so we might as well reuse
596 the result instead of calling time() again.
598 static int reset_timeout( const char* authkey, time_t now ) {
599 jsonObject* auth_object = jsonNewObject( authkey );
601 // Ask the authentication server to reset the timeout. It returns an event
602 // indicating success or failure.
603 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
604 "open-ils.auth.session.reset_timeout", auth_object );
605 jsonObjectFree( auth_object );
607 if( !result || result->type != JSON_HASH ) {
608 osrfLogError( OSRF_LOG_MARK,
609 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
610 jsonObjectFree( result );
611 return 1; // Not the right sort of object returned
614 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
615 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
616 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
617 jsonObjectFree( result );
618 return 1; // Return code from method not available
621 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
622 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
624 desc = "(No reason available)"; // failsafe; shouldn't happen
625 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
626 jsonObjectFree( result );
630 // Revise our local proxy for the timeout deadline
631 // by a smallish fraction of the timeout interval
632 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
634 timeout = "1"; // failsafe; shouldn't happen
635 time_next_reset = now + atoi( timeout ) / 15;
637 jsonObjectFree( result );
638 return 0; // Successfully reset timeout
642 @brief Get the authkey string for the current application session, if any.
643 @param ctx Pointer to the method context.
644 @return Pointer to the cached authkey if found; otherwise NULL.
646 If present, the authkey string was cached from a previous method call.
648 static const char* getAuthkey( osrfMethodContext* ctx ) {
649 if( ctx && ctx->session && ctx->session->userData ) {
650 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
652 // Possibly reset the authentication timeout to keep the login alive. We do so
653 // no more than once per method call, and not at all if it has been only a short
654 // time since the last reset.
656 // Here we reset explicitly, if at all. We also implicitly reset the timeout
657 // whenever we call the "open-ils.auth.session.retrieve" method.
658 if( timeout_needs_resetting ) {
659 time_t now = time( NULL );
660 if( now >= time_next_reset && reset_timeout( authkey, now ) )
661 authkey = NULL; // timeout has apparently expired already
664 timeout_needs_resetting = 0;
672 @brief Implement the transaction.begin method.
673 @param ctx Pointer to the method context.
674 @return Zero if successful, or -1 upon error.
676 Start a transaction. Save a transaction ID for future reference.
679 - authkey (PCRUD only)
681 Return to client: Transaction ID
683 int beginTransaction( osrfMethodContext* ctx ) {
684 if(osrfMethodVerifyContext( ctx )) {
685 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
689 if( enforce_pcrud ) {
690 timeout_needs_resetting = 1;
691 const jsonObject* user = verifyUserPCRUD( ctx );
696 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
698 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
699 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
700 "osrfMethodException", ctx->request, "Error starting transaction" );
704 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
705 osrfAppRespondComplete( ctx, ret );
706 jsonObjectFree( ret );
712 @brief Implement the savepoint.set method.
713 @param ctx Pointer to the method context.
714 @return Zero if successful, or -1 if not.
716 Issue a SAVEPOINT to the database server.
719 - authkey (PCRUD only)
722 Return to client: Savepoint name
724 int setSavepoint( osrfMethodContext* ctx ) {
725 if(osrfMethodVerifyContext( ctx )) {
726 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
731 if( enforce_pcrud ) {
733 timeout_needs_resetting = 1;
734 const jsonObject* user = verifyUserPCRUD( ctx );
739 // Verify that a transaction is pending
740 const char* trans_id = getXactId( ctx );
741 if( NULL == trans_id ) {
742 osrfAppSessionStatus(
744 OSRF_STATUS_INTERNALSERVERERROR,
745 "osrfMethodException",
747 "No active transaction -- required for savepoints"
752 // Get the savepoint name from the method params
753 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
755 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
759 "%s: Error creating savepoint %s in transaction %s",
764 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
765 "osrfMethodException", ctx->request, "Error creating savepoint" );
768 jsonObject* ret = jsonNewObject( spName );
769 osrfAppRespondComplete( ctx, ret );
770 jsonObjectFree( ret );
776 @brief Implement the savepoint.release method.
777 @param ctx Pointer to the method context.
778 @return Zero if successful, or -1 if not.
780 Issue a RELEASE SAVEPOINT to the database server.
783 - authkey (PCRUD only)
786 Return to client: Savepoint name
788 int releaseSavepoint( osrfMethodContext* ctx ) {
789 if(osrfMethodVerifyContext( ctx )) {
790 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
795 if( enforce_pcrud ) {
797 timeout_needs_resetting = 1;
798 const jsonObject* user = verifyUserPCRUD( ctx );
803 // Verify that a transaction is pending
804 const char* trans_id = getXactId( ctx );
805 if( NULL == trans_id ) {
806 osrfAppSessionStatus(
808 OSRF_STATUS_INTERNALSERVERERROR,
809 "osrfMethodException",
811 "No active transaction -- required for savepoints"
816 // Get the savepoint name from the method params
817 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
819 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
823 "%s: Error releasing savepoint %s in transaction %s",
828 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
829 "osrfMethodException", ctx->request, "Error releasing savepoint" );
832 jsonObject* ret = jsonNewObject( spName );
833 osrfAppRespondComplete( ctx, ret );
834 jsonObjectFree( ret );
840 @brief Implement the savepoint.rollback method.
841 @param ctx Pointer to the method context.
842 @return Zero if successful, or -1 if not.
844 Issue a ROLLBACK TO SAVEPOINT to the database server.
847 - authkey (PCRUD only)
850 Return to client: Savepoint name
852 int rollbackSavepoint( osrfMethodContext* ctx ) {
853 if(osrfMethodVerifyContext( ctx )) {
854 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
859 if( enforce_pcrud ) {
861 timeout_needs_resetting = 1;
862 const jsonObject* user = verifyUserPCRUD( ctx );
867 // Verify that a transaction is pending
868 const char* trans_id = getXactId( ctx );
869 if( NULL == trans_id ) {
870 osrfAppSessionStatus(
872 OSRF_STATUS_INTERNALSERVERERROR,
873 "osrfMethodException",
875 "No active transaction -- required for savepoints"
880 // Get the savepoint name from the method params
881 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
883 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
887 "%s: Error rolling back savepoint %s in transaction %s",
892 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
893 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
896 jsonObject* ret = jsonNewObject( spName );
897 osrfAppRespondComplete( ctx, ret );
898 jsonObjectFree( ret );
904 @brief Implement the transaction.commit method.
905 @param ctx Pointer to the method context.
906 @return Zero if successful, or -1 if not.
908 Issue a COMMIT to the database server.
911 - authkey (PCRUD only)
913 Return to client: Transaction ID.
915 int commitTransaction( osrfMethodContext* ctx ) {
916 if(osrfMethodVerifyContext( ctx )) {
917 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
921 if( enforce_pcrud ) {
922 timeout_needs_resetting = 1;
923 const jsonObject* user = verifyUserPCRUD( ctx );
928 // Verify that a transaction is pending
929 const char* trans_id = getXactId( ctx );
930 if( NULL == trans_id ) {
931 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
932 "osrfMethodException", ctx->request, "No active transaction to commit" );
936 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
938 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
939 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
940 "osrfMethodException", ctx->request, "Error committing transaction" );
943 jsonObject* ret = jsonNewObject( trans_id );
944 osrfAppRespondComplete( ctx, ret );
945 jsonObjectFree( ret );
952 @brief Implement the transaction.rollback method.
953 @param ctx Pointer to the method context.
954 @return Zero if successful, or -1 if not.
956 Issue a ROLLBACK to the database server.
959 - authkey (PCRUD only)
961 Return to client: Transaction ID
963 int rollbackTransaction( osrfMethodContext* ctx ) {
964 if( osrfMethodVerifyContext( ctx )) {
965 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
969 if( enforce_pcrud ) {
970 timeout_needs_resetting = 1;
971 const jsonObject* user = verifyUserPCRUD( ctx );
976 // Verify that a transaction is pending
977 const char* trans_id = getXactId( ctx );
978 if( NULL == trans_id ) {
979 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
980 "osrfMethodException", ctx->request, "No active transaction to roll back" );
984 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
986 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
987 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
988 "osrfMethodException", ctx->request, "Error rolling back transaction" );
991 jsonObject* ret = jsonNewObject( trans_id );
992 osrfAppRespondComplete( ctx, ret );
993 jsonObjectFree( ret );
1000 @brief Implement the "search" method.
1001 @param ctx Pointer to the method context.
1002 @return Zero if successful, or -1 if not.
1005 - authkey (PCRUD only)
1006 - WHERE clause, as jsonObject
1007 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1009 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1010 Optionally flesh linked fields.
1012 int doSearch( osrfMethodContext* ctx ) {
1013 if( osrfMethodVerifyContext( ctx )) {
1014 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1019 timeout_needs_resetting = 1;
1021 jsonObject* where_clause;
1022 jsonObject* rest_of_query;
1024 if( enforce_pcrud ) {
1025 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1026 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1028 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1029 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1032 // Get the class metadata
1033 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1034 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1038 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1040 osrfAppRespondComplete( ctx, NULL );
1044 // Return each row to the client (except that some may be suppressed by PCRUD)
1045 jsonObject* cur = 0;
1046 unsigned long res_idx = 0;
1047 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1048 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1050 osrfAppRespond( ctx, cur );
1052 jsonObjectFree( obj );
1054 osrfAppRespondComplete( ctx, NULL );
1059 @brief Implement the "id_list" method.
1060 @param ctx Pointer to the method context.
1061 @param err Pointer through which to return an error code.
1062 @return Zero if successful, or -1 if not.
1065 - authkey (PCRUD only)
1066 - WHERE clause, as jsonObject
1067 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1069 Return to client: The primary key values for all rows of the relevant class that
1070 satisfy a specified WHERE clause.
1072 This method relies on the assumption that every class has a primary key consisting of
1075 int doIdList( osrfMethodContext* ctx ) {
1076 if( osrfMethodVerifyContext( ctx )) {
1077 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1082 timeout_needs_resetting = 1;
1084 jsonObject* where_clause;
1085 jsonObject* rest_of_query;
1087 // We use the where clause without change. But we need to massage the rest of the
1088 // query, so we work with a copy of it instead of modifying the original.
1090 if( enforce_pcrud ) {
1091 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1092 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1094 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1095 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1098 // Eliminate certain SQL clauses, if present.
1099 if( rest_of_query ) {
1100 jsonObjectRemoveKey( rest_of_query, "select" );
1101 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1102 jsonObjectRemoveKey( rest_of_query, "flesh" );
1103 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1105 rest_of_query = jsonNewObjectType( JSON_HASH );
1108 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1110 // Get the class metadata
1111 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1112 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1114 // Build a SELECT list containing just the primary key,
1115 // i.e. like { "classname":["keyname"] }
1116 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1118 // Load array with name of primary key
1119 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1120 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1121 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1123 jsonObjectSetKey( rest_of_query, "select", select_clause );
1128 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1130 jsonObjectFree( rest_of_query );
1132 osrfAppRespondComplete( ctx, NULL );
1136 // Return each primary key value to the client
1138 unsigned long res_idx = 0;
1139 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1140 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1141 continue; // Suppress due to lack of permission
1143 osrfAppRespond( ctx,
1144 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1147 jsonObjectFree( obj );
1148 osrfAppRespondComplete( ctx, NULL );
1153 @brief Verify that we have a valid class reference.
1154 @param ctx Pointer to the method context.
1155 @param param Pointer to the method parameters.
1156 @return 1 if the class reference is valid, or zero if it isn't.
1158 The class of the method params must match the class to which the method id devoted.
1159 For PCRUD there are additional restrictions.
1161 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1163 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1164 osrfHash* class = osrfHashGet( method_meta, "class" );
1166 // Compare the method's class to the parameters' class
1167 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1169 // Oops -- they don't match. Complain.
1170 growing_buffer* msg = buffer_init( 128 );
1173 "%s: %s method for type %s was passed a %s",
1175 osrfHashGet( method_meta, "methodtype" ),
1176 osrfHashGet( class, "classname" ),
1177 param->classname ? param->classname : "(null)"
1180 char* m = buffer_release( msg );
1181 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1189 return verifyObjectPCRUD( ctx, param );
1195 @brief (PCRUD only) Verify that the user is properly logged in.
1196 @param ctx Pointer to the method context.
1197 @return If the user is logged in, a pointer to the user object from the authentication
1198 server; otherwise NULL.
1200 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1202 // Get the authkey (the first method parameter)
1203 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1205 // See if we have the same authkey, and a user object,
1206 // locally cached from a previous call
1207 const char* cached_authkey = getAuthkey( ctx );
1208 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1209 const jsonObject* cached_user = getUserLogin( ctx );
1214 // We have no matching authentication data in the cache. Authenticate from scratch.
1215 jsonObject* auth_object = jsonNewObject( auth );
1217 // Fetch the user object from the authentication server
1218 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1220 jsonObjectFree( auth_object );
1222 if( !user->classname || strcmp(user->classname, "au" )) {
1224 growing_buffer* msg = buffer_init( 128 );
1227 "%s: permacrud received a bad auth token: %s",
1232 char* m = buffer_release( msg );
1233 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1237 jsonObjectFree( user );
1241 setUserLogin( ctx, user );
1242 setAuthkey( ctx, auth );
1244 // Allow ourselves up to a second before we have to reset the login timeout.
1245 // It would be nice to use some fraction of the timeout interval enforced by the
1246 // authentication server, but that value is not readily available at this point.
1247 // Instead, we use a conservative default interval.
1248 time_next_reset = time( NULL ) + 1;
1254 @brief For PCRUD: Determine whether the current user may access the current row.
1255 @param ctx Pointer to the method context.
1256 @param obj Pointer to the row being potentially accessed.
1257 @return 1 if access is permitted, or 0 if it isn't.
1259 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1261 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1263 dbhandle = writehandle;
1265 // Figure out what class and method are involved
1266 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1267 osrfHash* class = osrfHashGet( method_metadata, "class" );
1268 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1271 if( *method_type == 's' || *method_type == 'i' ) {
1272 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1273 } else if( *method_type == 'u' || *method_type == 'd' ) {
1274 fetch = 1; // MUST go to the db for the object for update and delete
1277 // Get the appropriate permacrud entry from the IDL, depending on method type
1278 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1280 // No permacrud for this method type on this class
1282 growing_buffer* msg = buffer_init( 128 );
1285 "%s: %s on class %s has no permacrud IDL entry",
1287 osrfHashGet( method_metadata, "methodtype" ),
1288 osrfHashGet( class, "classname" )
1291 char* m = buffer_release( msg );
1292 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1293 "osrfMethodException", ctx->request, m );
1300 // Get the user id, and make sure the user is logged in
1301 const jsonObject* user = verifyUserPCRUD( ctx );
1303 return 0; // Not logged in? No access.
1305 int userid = atoi( oilsFMGetString( user, "id" ) );
1307 // Get a list of permissions from the permacrud entry.
1308 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1310 // Build a list of org units that own the row. This is fairly convoluted because there
1311 // are several different ways that an org unit may own the row, as defined by the
1314 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1315 // identifying an owning org_unit..
1316 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1318 // Foreign context adds a layer of indirection. The row points to some other row that
1319 // an org unit may own. The "jump" attribute, if present, adds another layer of
1321 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1323 // The following string array stores the list of org units. (We don't have a thingie
1324 // for storing lists of integers, so we fake it with a list of strings.)
1325 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1328 char* pkey_value = NULL;
1329 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1330 // If the global_required attribute is present and true, then the only owning
1331 // org unit is the root org unit, i.e. the one with no parent.
1332 osrfLogDebug( OSRF_LOG_MARK,
1333 "global-level permissions required, fetching top of the org tree" );
1335 // check for perm at top of org tree
1336 char* org_tree_root_id = org_tree_root( ctx );
1337 if( org_tree_root_id ) {
1338 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1339 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1341 osrfStringArrayFree( context_org_array );
1346 // If the global_required attribute is absent or false, then we look for
1347 // local and/or foreign context. In order to find the relevant foreign
1348 // keys, we must either read the relevant row from the database, or look at
1349 // the image of the row that we already have in memory.
1351 // (Herein lies a bug. Even if we have an image of the row in memory, that
1352 // image may not include the foreign key column(s) that we need.)
1354 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1355 "fetching context org ids" );
1356 const char* pkey = osrfHashGet( class, "primarykey" );
1357 jsonObject *param = NULL;
1359 if( obj->classname ) {
1360 pkey_value = oilsFMGetString( obj, pkey );
1362 param = jsonObjectClone( obj );
1363 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1366 pkey_value = jsonObjectToSimpleString( obj );
1368 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1369 "of %s and retrieving from the database", pkey_value );
1373 // Fetch the row so that we can look at the foreign key(s)
1374 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1375 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1376 jsonObjectFree( _tmp_params );
1378 param = jsonObjectExtractIndex( _list, 0 );
1379 jsonObjectFree( _list );
1383 // The row doesn't exist. Complain, and deny access.
1384 osrfLogDebug( OSRF_LOG_MARK,
1385 "Object not found in the database with primary key %s of %s",
1388 growing_buffer* msg = buffer_init( 128 );
1391 "%s: no object found with primary key %s of %s",
1397 char* m = buffer_release( msg );
1398 osrfAppSessionStatus(
1400 OSRF_STATUS_INTERNALSERVERERROR,
1401 "osrfMethodException",
1413 if( local_context && local_context->size > 0 ) {
1414 // The IDL provides a list of column names for the foreign keys denoting
1415 // local context. Look up the value of each one, and add it to the
1416 // list of org units.
1417 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1418 local_context->size );
1420 const char* lcontext = NULL;
1421 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1422 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1425 "adding class-local field %s (value: %s) to the context org list",
1427 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1432 if( foreign_context ) {
1433 unsigned long class_count = osrfHashGetCount( foreign_context );
1434 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1436 if( class_count > 0 ) {
1438 // The IDL provides a list of foreign key columns pointing to rows that
1439 // an org unit may own. Follow each link, identify the owning org unit,
1440 // and add it to the list.
1441 osrfHash* fcontext = NULL;
1442 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1443 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1444 // For each linked class...
1445 const char* class_name = osrfHashIteratorKey( class_itr );
1446 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1450 "%d foreign context fields(s) specified for class %s",
1451 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1455 // Get the name of the key field in the foreign table
1456 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1458 // Get the value of the foreign key pointing to the foreign table
1459 char* foreign_pkey_value =
1460 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1462 // Look up the row to which the foreign key points
1463 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1464 jsonObject* _list = doFieldmapperSearch(
1465 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1467 jsonObject* _fparam = NULL;
1468 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1469 _fparam = jsonObjectExtractIndex( _list, 0 );
1471 jsonObjectFree( _tmp_params );
1472 jsonObjectFree( _list );
1474 // At this point _fparam either points to the row identified by the
1475 // foreign key, or it's NULL (no such row found).
1477 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1479 if( _fparam && jump_list ) {
1480 // Follow another level of indirection to find one or more owners
1481 const char* flink = NULL;
1483 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1484 // For each entry in the jump list. Each entry is the name of a
1485 // foreign key colum in the foreign table.
1487 osrfHash* foreign_link_hash =
1488 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1490 free( foreign_pkey_value );
1491 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1492 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1494 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1496 _list = doFieldmapperSearch(
1498 osrfHashGet( oilsIDL(),
1499 osrfHashGet( foreign_link_hash, "class" ) ),
1505 jsonObjectFree( _fparam );
1507 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1508 _fparam = jsonObjectExtractIndex( _list, 0 );
1509 jsonObjectFree( _tmp_params );
1510 jsonObjectFree( _list );
1516 growing_buffer* msg = buffer_init( 128 );
1519 "%s: no object found with primary key %s of %s",
1525 char* m = buffer_release( msg );
1526 osrfAppSessionStatus(
1528 OSRF_STATUS_INTERNALSERVERERROR,
1529 "osrfMethodException",
1535 osrfHashIteratorFree( class_itr );
1536 free( foreign_pkey_value );
1537 jsonObjectFree( param );
1542 free( foreign_pkey_value );
1544 // Examine each context column of the foreign row,
1545 // and add its value to the list of org units.
1547 const char* foreign_field = NULL;
1548 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1549 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1550 osrfStringArrayAdd( context_org_array,
1551 oilsFMGetString( _fparam, foreign_field ) );
1554 "adding foreign class %s field %s (value: %s) to the context org list",
1557 osrfStringArrayGetString(
1558 context_org_array, context_org_array->size - 1 )
1562 jsonObjectFree( _fparam );
1565 osrfHashIteratorFree( class_itr );
1569 jsonObjectFree( param );
1572 const char* context_org = NULL;
1573 const char* perm = NULL;
1576 if( permission->size == 0 ) {
1577 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1582 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1584 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1590 "Checking object permission [%s] for user %d "
1591 "on object %s (class %s) at org %d",
1595 osrfHashGet( class, "classname" ),
1599 result = dbi_conn_queryf(
1601 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1604 osrfHashGet( class, "classname" ),
1612 "Received a result for object permission [%s] "
1613 "for user %d on object %s (class %s) at org %d",
1617 osrfHashGet( class, "classname" ),
1621 if( dbi_result_first_row( result )) {
1622 jsonObject* return_val = oilsMakeJSONFromResult( result );
1623 const char* has_perm = jsonObjectGetString(
1624 jsonObjectGetKeyConst( return_val, "has_perm" ));
1628 "Status of object permission [%s] for user %d "
1629 "on object %s (class %s) at org %d is %s",
1633 osrfHashGet(class, "classname"),
1638 if( *has_perm == 't' )
1640 jsonObjectFree( return_val );
1643 dbi_result_free( result );
1649 osrfLogDebug( OSRF_LOG_MARK,
1650 "Checking non-object permission [%s] for user %d at org %d",
1651 perm, userid, atoi(context_org) );
1652 result = dbi_conn_queryf(
1654 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1661 osrfLogDebug( OSRF_LOG_MARK,
1662 "Received a result for permission [%s] for user %d at org %d",
1663 perm, userid, atoi( context_org ));
1664 if( dbi_result_first_row( result )) {
1665 jsonObject* return_val = oilsMakeJSONFromResult( result );
1666 const char* has_perm = jsonObjectGetString(
1667 jsonObjectGetKeyConst( return_val, "has_perm" ));
1668 osrfLogDebug( OSRF_LOG_MARK,
1669 "Status of permission [%s] for user %d at org %d is [%s]",
1670 perm, userid, atoi( context_org ), has_perm );
1671 if( *has_perm == 't' )
1673 jsonObjectFree( return_val );
1676 dbi_result_free( result );
1688 osrfStringArrayFree( context_org_array );
1694 @brief Look up the root of the org_unit tree.
1695 @param ctx Pointer to the method context.
1696 @return The id of the root org unit, as a character string.
1698 Query actor.org_unit where parent_ou is null, and return the id as a string.
1700 This function assumes that there is only one root org unit, i.e. that we
1701 have a single tree, not a forest.
1703 The calling code is responsible for freeing the returned string.
1705 static char* org_tree_root( osrfMethodContext* ctx ) {
1707 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1708 static time_t last_lookup_time = 0;
1709 time_t current_time = time( NULL );
1711 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1712 // We successfully looked this up less than an hour ago.
1713 // It's not likely to have changed since then.
1714 return strdup( cached_root_id );
1716 last_lookup_time = current_time;
1719 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1720 jsonObject* result = doFieldmapperSearch(
1721 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1722 jsonObjectFree( where_clause );
1724 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1727 jsonObjectFree( result );
1729 growing_buffer* msg = buffer_init( 128 );
1730 OSRF_BUFFER_ADD( msg, modulename );
1731 OSRF_BUFFER_ADD( msg,
1732 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1734 char* m = buffer_release( msg );
1735 osrfAppSessionStatus( ctx->session,
1736 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1739 cached_root_id[ 0 ] = '\0';
1743 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1744 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1746 jsonObjectFree( result );
1748 strcpy( cached_root_id, root_org_unit_id );
1749 return root_org_unit_id;
1753 @brief Create a JSON_HASH with a single key/value pair.
1754 @param key The key of the key/value pair.
1755 @param value the value of the key/value pair.
1756 @return Pointer to a newly created jsonObject of type JSON_HASH.
1758 The value of the key/value is either a string or (if @a value is NULL) a null.
1760 static jsonObject* single_hash( const char* key, const char* value ) {
1762 if( ! key ) key = "";
1764 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1765 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1770 int doCreate( osrfMethodContext* ctx ) {
1771 if(osrfMethodVerifyContext( ctx )) {
1772 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1777 timeout_needs_resetting = 1;
1779 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1780 jsonObject* target = NULL;
1781 jsonObject* options = NULL;
1783 if( enforce_pcrud ) {
1784 target = jsonObjectGetIndex( ctx->params, 1 );
1785 options = jsonObjectGetIndex( ctx->params, 2 );
1787 target = jsonObjectGetIndex( ctx->params, 0 );
1788 options = jsonObjectGetIndex( ctx->params, 1 );
1791 if( !verifyObjectClass( ctx, target )) {
1792 osrfAppRespondComplete( ctx, NULL );
1796 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1798 const char* trans_id = getXactId( ctx );
1800 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1802 osrfAppSessionStatus(
1804 OSRF_STATUS_BADREQUEST,
1805 "osrfMethodException",
1807 "No active transaction -- required for CREATE"
1809 osrfAppRespondComplete( ctx, NULL );
1813 // The following test is harmless but redundant. If a class is
1814 // readonly, we don't register a create method for it.
1815 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1816 osrfAppSessionStatus(
1818 OSRF_STATUS_BADREQUEST,
1819 "osrfMethodException",
1821 "Cannot INSERT readonly class"
1823 osrfAppRespondComplete( ctx, NULL );
1827 // Set the last_xact_id
1828 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1830 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1831 trans_id, target->classname, index);
1832 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1835 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1837 dbhandle = writehandle;
1839 osrfHash* fields = osrfHashGet( meta, "fields" );
1840 char* pkey = osrfHashGet( meta, "primarykey" );
1841 char* seq = osrfHashGet( meta, "sequence" );
1843 growing_buffer* table_buf = buffer_init( 128 );
1844 growing_buffer* col_buf = buffer_init( 128 );
1845 growing_buffer* val_buf = buffer_init( 128 );
1847 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1848 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1849 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1850 buffer_add( val_buf,"VALUES (" );
1854 osrfHash* field = NULL;
1855 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1856 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1858 const char* field_name = osrfHashIteratorKey( field_itr );
1860 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1863 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1866 if( field_object && field_object->classname ) {
1867 value = oilsFMGetString(
1869 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1871 } else if( field_object && JSON_BOOL == field_object->type ) {
1872 if( jsonBoolIsTrue( field_object ) )
1873 value = strdup( "t" );
1875 value = strdup( "f" );
1877 value = jsonObjectToSimpleString( field_object );
1883 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1884 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1887 buffer_add( col_buf, field_name );
1889 if( !field_object || field_object->type == JSON_NULL ) {
1890 buffer_add( val_buf, "DEFAULT" );
1892 } else if( !strcmp( get_primitive( field ), "number" )) {
1893 const char* numtype = get_datatype( field );
1894 if( !strcmp( numtype, "INT8" )) {
1895 buffer_fadd( val_buf, "%lld", atoll( value ));
1897 } else if( !strcmp( numtype, "INT" )) {
1898 buffer_fadd( val_buf, "%d", atoi( value ));
1900 } else if( !strcmp( numtype, "NUMERIC" )) {
1901 buffer_fadd( val_buf, "%f", atof( value ));
1904 if( dbi_conn_quote_string( writehandle, &value )) {
1905 OSRF_BUFFER_ADD( val_buf, value );
1908 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1909 osrfAppSessionStatus(
1911 OSRF_STATUS_INTERNALSERVERERROR,
1912 "osrfMethodException",
1914 "Error quoting string -- please see the error log for more details"
1917 buffer_free( table_buf );
1918 buffer_free( col_buf );
1919 buffer_free( val_buf );
1920 osrfAppRespondComplete( ctx, NULL );
1928 osrfHashIteratorFree( field_itr );
1930 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1931 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1933 char* table_str = buffer_release( table_buf );
1934 char* col_str = buffer_release( col_buf );
1935 char* val_str = buffer_release( val_buf );
1936 growing_buffer* sql = buffer_init( 128 );
1937 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1942 char* query = buffer_release( sql );
1944 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1946 jsonObject* obj = NULL;
1949 dbi_result result = dbi_conn_query( writehandle, query );
1951 obj = jsonNewObject( NULL );
1954 "%s ERROR inserting %s object using query [%s]",
1956 osrfHashGet(meta, "fieldmapper"),
1959 osrfAppSessionStatus(
1961 OSRF_STATUS_INTERNALSERVERERROR,
1962 "osrfMethodException",
1964 "INSERT error -- please see the error log for more details"
1969 char* id = oilsFMGetString( target, pkey );
1971 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1972 growing_buffer* _id = buffer_init( 10 );
1973 buffer_fadd( _id, "%lld", new_id );
1974 id = buffer_release( _id );
1977 // Find quietness specification, if present
1978 const char* quiet_str = NULL;
1980 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1982 quiet_str = jsonObjectGetString( quiet_obj );
1985 if( str_is_true( quiet_str )) { // if quietness is specified
1986 obj = jsonNewObject( id );
1990 // Fetch the row that we just inserted, so that we can return it to the client
1991 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1992 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
1995 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
1999 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2001 jsonObjectFree( list );
2002 jsonObjectFree( where_clause );
2009 osrfAppRespondComplete( ctx, obj );
2010 jsonObjectFree( obj );
2015 @brief Implement the retrieve method.
2016 @param ctx Pointer to the method context.
2017 @param err Pointer through which to return an error code.
2018 @return If successful, a pointer to the result to be returned to the client;
2021 From the method's class, fetch a row with a specified value in the primary key. This
2022 method relies on the database design convention that a primary key consists of a single
2026 - authkey (PCRUD only)
2027 - value of the primary key for the desired row, for building the WHERE clause
2028 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2030 Return to client: One row from the query.
2032 int doRetrieve( osrfMethodContext* ctx ) {
2033 if(osrfMethodVerifyContext( ctx )) {
2034 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2039 timeout_needs_resetting = 1;
2044 if( enforce_pcrud ) {
2049 // Get the class metadata
2050 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2052 // Get the value of the primary key, from a method parameter
2053 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2057 "%s retrieving %s object with primary key value of %s",
2059 osrfHashGet( class_def, "fieldmapper" ),
2060 jsonObjectGetString( id_obj )
2063 // Build a WHERE clause based on the key value
2064 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2067 osrfHashGet( class_def, "primarykey" ), // name of key column
2068 jsonObjectClone( id_obj ) // value of key column
2071 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2075 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2077 jsonObjectFree( where_clause );
2079 osrfAppRespondComplete( ctx, NULL );
2083 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2084 jsonObjectFree( list );
2086 if( enforce_pcrud ) {
2087 if(!verifyObjectPCRUD( ctx, obj )) {
2088 jsonObjectFree( obj );
2090 growing_buffer* msg = buffer_init( 128 );
2091 OSRF_BUFFER_ADD( msg, modulename );
2092 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2094 char* m = buffer_release( msg );
2095 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2099 osrfAppRespondComplete( ctx, NULL );
2104 osrfAppRespondComplete( ctx, obj );
2105 jsonObjectFree( obj );
2110 @brief Translate a numeric value to a string representation for the database.
2111 @param field Pointer to the IDL field definition.
2112 @param value Pointer to a jsonObject holding the value of a field.
2113 @return Pointer to a newly allocated string.
2115 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2116 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2117 or (what is worse) valid SQL that is wrong.
2119 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2121 The calling code is responsible for freeing the resulting string by calling free().
2123 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2124 growing_buffer* val_buf = buffer_init( 32 );
2125 const char* numtype = get_datatype( field );
2127 // For historical reasons the following contains cruft that could be cleaned up.
2128 if( !strncmp( numtype, "INT", 3 ) ) {
2129 if( value->type == JSON_NUMBER )
2130 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2131 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2133 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2136 } else if( !strcmp( numtype, "NUMERIC" )) {
2137 if( value->type == JSON_NUMBER )
2138 buffer_fadd( val_buf, jsonObjectGetString( value ));
2140 buffer_fadd( val_buf, jsonObjectGetString( value ));
2144 // Presumably this was really intended to be a string, so quote it
2145 char* str = jsonObjectToSimpleString( value );
2146 if( dbi_conn_quote_string( dbhandle, &str )) {
2147 OSRF_BUFFER_ADD( val_buf, str );
2150 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2152 buffer_free( val_buf );
2157 return buffer_release( val_buf );
2160 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2161 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2162 growing_buffer* sql_buf = buffer_init( 32 );
2168 osrfHashGet( field, "name" )
2172 buffer_add( sql_buf, "IN (" );
2173 } else if( !strcasecmp( op,"not in" )) {
2174 buffer_add( sql_buf, "NOT IN (" );
2176 buffer_add( sql_buf, "IN (" );
2179 if( node->type == JSON_HASH ) {
2180 // subquery predicate
2181 char* subpred = buildQuery( ctx, node, SUBSELECT );
2183 buffer_free( sql_buf );
2187 buffer_add( sql_buf, subpred );
2190 } else if( node->type == JSON_ARRAY ) {
2191 // literal value list
2192 int in_item_index = 0;
2193 int in_item_first = 1;
2194 const jsonObject* in_item;
2195 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2200 buffer_add( sql_buf, ", " );
2203 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2204 osrfLogError( OSRF_LOG_MARK,
2205 "%s: Expected string or number within IN list; found %s",
2206 modulename, json_type( in_item->type ) );
2207 buffer_free( sql_buf );
2211 // Append the literal value -- quoted if not a number
2212 if( JSON_NUMBER == in_item->type ) {
2213 char* val = jsonNumberToDBString( field, in_item );
2214 OSRF_BUFFER_ADD( sql_buf, val );
2217 } else if( !strcmp( get_primitive( field ), "number" )) {
2218 char* val = jsonNumberToDBString( field, in_item );
2219 OSRF_BUFFER_ADD( sql_buf, val );
2223 char* key_string = jsonObjectToSimpleString( in_item );
2224 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2225 OSRF_BUFFER_ADD( sql_buf, key_string );
2228 osrfLogError( OSRF_LOG_MARK,
2229 "%s: Error quoting key string [%s]", modulename, key_string );
2231 buffer_free( sql_buf );
2237 if( in_item_first ) {
2238 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2239 buffer_free( sql_buf );
2243 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2244 modulename, json_type( node->type ));
2245 buffer_free( sql_buf );
2249 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2251 return buffer_release( sql_buf );
2254 // Receive a JSON_ARRAY representing a function call. The first
2255 // entry in the array is the function name. The rest are parameters.
2256 static char* searchValueTransform( const jsonObject* array ) {
2258 if( array->size < 1 ) {
2259 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2263 // Get the function name
2264 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2265 if( func_item->type != JSON_STRING ) {
2266 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2267 modulename, json_type( func_item->type ));
2271 growing_buffer* sql_buf = buffer_init( 32 );
2273 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2274 OSRF_BUFFER_ADD( sql_buf, "( " );
2276 // Get the parameters
2277 int func_item_index = 1; // We already grabbed the zeroth entry
2278 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2280 // Add a separator comma, if we need one
2281 if( func_item_index > 2 )
2282 buffer_add( sql_buf, ", " );
2284 // Add the current parameter
2285 if( func_item->type == JSON_NULL ) {
2286 buffer_add( sql_buf, "NULL" );
2288 char* val = jsonObjectToSimpleString( func_item );
2289 if( dbi_conn_quote_string( dbhandle, &val )) {
2290 OSRF_BUFFER_ADD( sql_buf, val );
2293 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2295 buffer_free( sql_buf );
2302 buffer_add( sql_buf, " )" );
2304 return buffer_release( sql_buf );
2307 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2308 const jsonObject* node, const char* op ) {
2310 if( ! is_good_operator( op ) ) {
2311 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2315 char* val = searchValueTransform( node );
2319 growing_buffer* sql_buf = buffer_init( 32 );
2324 osrfHashGet( field, "name" ),
2331 return buffer_release( sql_buf );
2334 // class_alias is a class name or other table alias
2335 // field is a field definition as stored in the IDL
2336 // node comes from the method parameter, and may represent an entry in the SELECT list
2337 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2338 const jsonObject* node ) {
2339 growing_buffer* sql_buf = buffer_init( 32 );
2341 const char* field_transform = jsonObjectGetString(
2342 jsonObjectGetKeyConst( node, "transform" ) );
2343 const char* transform_subcolumn = jsonObjectGetString(
2344 jsonObjectGetKeyConst( node, "result_field" ) );
2346 if( transform_subcolumn ) {
2347 if( ! is_identifier( transform_subcolumn ) ) {
2348 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2349 modulename, transform_subcolumn );
2350 buffer_free( sql_buf );
2353 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2356 if( field_transform ) {
2358 if( ! is_identifier( field_transform ) ) {
2359 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2360 modulename, field_transform );
2361 buffer_free( sql_buf );
2365 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2366 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2367 field_transform, class_alias, osrfHashGet( field, "name" ));
2369 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2370 field_transform, class_alias, osrfHashGet( field, "name" ));
2373 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2376 if( array->type != JSON_ARRAY ) {
2377 osrfLogError( OSRF_LOG_MARK,
2378 "%s: Expected JSON_ARRAY for function params; found %s",
2379 modulename, json_type( array->type ) );
2380 buffer_free( sql_buf );
2383 int func_item_index = 0;
2384 jsonObject* func_item;
2385 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2387 char* val = jsonObjectToSimpleString( func_item );
2390 buffer_add( sql_buf, ",NULL" );
2391 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2392 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2393 OSRF_BUFFER_ADD( sql_buf, val );
2395 osrfLogError( OSRF_LOG_MARK,
2396 "%s: Error quoting key string [%s]", modulename, val );
2398 buffer_free( sql_buf );
2405 buffer_add( sql_buf, " )" );
2408 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2411 if( transform_subcolumn )
2412 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2414 return buffer_release( sql_buf );
2417 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2418 const jsonObject* node, const char* op ) {
2420 if( ! is_good_operator( op ) ) {
2421 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2425 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2426 if( ! field_transform )
2429 int extra_parens = 0; // boolean
2431 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2433 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2435 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2437 free( field_transform );
2441 } else if( value_obj->type == JSON_ARRAY ) {
2442 value = searchValueTransform( value_obj );
2444 osrfLogError( OSRF_LOG_MARK,
2445 "%s: Error building value transform for field transform", modulename );
2446 free( field_transform );
2449 } else if( value_obj->type == JSON_HASH ) {
2450 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2452 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2454 free( field_transform );
2458 } else if( value_obj->type == JSON_NUMBER ) {
2459 value = jsonNumberToDBString( field, value_obj );
2460 } else if( value_obj->type == JSON_NULL ) {
2461 osrfLogError( OSRF_LOG_MARK,
2462 "%s: Error building predicate for field transform: null value", modulename );
2463 free( field_transform );
2465 } else if( value_obj->type == JSON_BOOL ) {
2466 osrfLogError( OSRF_LOG_MARK,
2467 "%s: Error building predicate for field transform: boolean value", modulename );
2468 free( field_transform );
2471 if( !strcmp( get_primitive( field ), "number") ) {
2472 value = jsonNumberToDBString( field, value_obj );
2474 value = jsonObjectToSimpleString( value_obj );
2475 if( !dbi_conn_quote_string( dbhandle, &value )) {
2476 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2477 modulename, value );
2479 free( field_transform );
2485 const char* left_parens = "";
2486 const char* right_parens = "";
2488 if( extra_parens ) {
2493 growing_buffer* sql_buf = buffer_init( 32 );
2497 "%s%s %s %s %s %s%s",
2508 free( field_transform );
2510 return buffer_release( sql_buf );
2513 static char* searchSimplePredicate( const char* op, const char* class_alias,
2514 osrfHash* field, const jsonObject* node ) {
2516 if( ! is_good_operator( op ) ) {
2517 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2523 // Get the value to which we are comparing the specified column
2524 if( node->type != JSON_NULL ) {
2525 if( node->type == JSON_NUMBER ) {
2526 val = jsonNumberToDBString( field, node );
2527 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2528 val = jsonNumberToDBString( field, node );
2530 val = jsonObjectToSimpleString( node );
2535 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2536 // Value is not numeric; enclose it in quotes
2537 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2538 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2545 // Compare to a null value
2546 val = strdup( "NULL" );
2547 if( strcmp( op, "=" ))
2553 growing_buffer* sql_buf = buffer_init( 32 );
2554 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2555 char* pred = buffer_release( sql_buf );
2562 static char* searchBETWEENPredicate( const char* class_alias,
2563 osrfHash* field, const jsonObject* node ) {
2565 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2566 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2568 if( NULL == y_node ) {
2569 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2572 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2573 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2580 if( !strcmp( get_primitive( field ), "number") ) {
2581 x_string = jsonNumberToDBString( field, x_node );
2582 y_string = jsonNumberToDBString( field, y_node );
2585 x_string = jsonObjectToSimpleString( x_node );
2586 y_string = jsonObjectToSimpleString( y_node );
2587 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2588 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2589 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2590 modulename, x_string, y_string );
2597 growing_buffer* sql_buf = buffer_init( 32 );
2598 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2599 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2603 return buffer_release( sql_buf );
2606 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2607 jsonObject* node, osrfMethodContext* ctx ) {
2610 if( node->type == JSON_ARRAY ) { // equality IN search
2611 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2612 } else if( node->type == JSON_HASH ) { // other search
2613 jsonIterator* pred_itr = jsonNewIterator( node );
2614 if( !jsonIteratorHasNext( pred_itr ) ) {
2615 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2616 modulename, osrfHashGet(field, "name" ));
2618 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2620 // Verify that there are no additional predicates
2621 if( jsonIteratorHasNext( pred_itr ) ) {
2622 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2623 modulename, osrfHashGet(field, "name" ));
2624 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2625 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2626 else if( !(strcasecmp( pred_itr->key,"in" ))
2627 || !(strcasecmp( pred_itr->key,"not in" )) )
2628 pred = searchINPredicate(
2629 class_info->alias, field, pred_node, pred_itr->key, ctx );
2630 else if( pred_node->type == JSON_ARRAY )
2631 pred = searchFunctionPredicate(
2632 class_info->alias, field, pred_node, pred_itr->key );
2633 else if( pred_node->type == JSON_HASH )
2634 pred = searchFieldTransformPredicate(
2635 class_info, field, pred_node, pred_itr->key );
2637 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2639 jsonIteratorFree( pred_itr );
2641 } else if( node->type == JSON_NULL ) { // IS NULL search
2642 growing_buffer* _p = buffer_init( 64 );
2645 "\"%s\".%s IS NULL",
2646 class_info->class_name,
2647 osrfHashGet( field, "name" )
2649 pred = buffer_release( _p );
2650 } else { // equality search
2651 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2670 field : call_number,
2686 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2688 const jsonObject* working_hash;
2689 jsonObject* freeable_hash = NULL;
2691 if( join_hash->type == JSON_HASH ) {
2692 working_hash = join_hash;
2693 } else if( join_hash->type == JSON_STRING ) {
2694 // turn it into a JSON_HASH by creating a wrapper
2695 // around a copy of the original
2696 const char* _tmp = jsonObjectGetString( join_hash );
2697 freeable_hash = jsonNewObjectType( JSON_HASH );
2698 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2699 working_hash = freeable_hash;
2703 "%s: JOIN failed; expected JSON object type not found",
2709 growing_buffer* join_buf = buffer_init( 128 );
2710 const char* leftclass = left_info->class_name;
2712 jsonObject* snode = NULL;
2713 jsonIterator* search_itr = jsonNewIterator( working_hash );
2715 while ( (snode = jsonIteratorNext( search_itr )) ) {
2716 const char* right_alias = search_itr->key;
2718 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2720 class = right_alias;
2722 const ClassInfo* right_info = add_joined_class( right_alias, class );
2726 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2730 jsonIteratorFree( search_itr );
2731 buffer_free( join_buf );
2733 jsonObjectFree( freeable_hash );
2736 osrfHash* links = right_info->links;
2737 const char* table = right_info->source_def;
2739 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2740 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2742 if( field && !fkey ) {
2743 // Look up the corresponding join column in the IDL.
2744 // The link must be defined in the child table,
2745 // and point to the right parent table.
2746 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2747 const char* reltype = NULL;
2748 const char* other_class = NULL;
2749 reltype = osrfHashGet( idl_link, "reltype" );
2750 if( reltype && strcmp( reltype, "has_many" ) )
2751 other_class = osrfHashGet( idl_link, "class" );
2752 if( other_class && !strcmp( other_class, leftclass ) )
2753 fkey = osrfHashGet( idl_link, "key" );
2757 "%s: JOIN failed. No link defined from %s.%s to %s",
2763 buffer_free( join_buf );
2765 jsonObjectFree( freeable_hash );
2766 jsonIteratorFree( search_itr );
2770 } else if( !field && fkey ) {
2771 // Look up the corresponding join column in the IDL.
2772 // The link must be defined in the child table,
2773 // and point to the right parent table.
2774 osrfHash* left_links = left_info->links;
2775 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2776 const char* reltype = NULL;
2777 const char* other_class = NULL;
2778 reltype = osrfHashGet( idl_link, "reltype" );
2779 if( reltype && strcmp( reltype, "has_many" ) )
2780 other_class = osrfHashGet( idl_link, "class" );
2781 if( other_class && !strcmp( other_class, class ) )
2782 field = osrfHashGet( idl_link, "key" );
2786 "%s: JOIN failed. No link defined from %s.%s to %s",
2792 buffer_free( join_buf );
2794 jsonObjectFree( freeable_hash );
2795 jsonIteratorFree( search_itr );
2799 } else if( !field && !fkey ) {
2800 osrfHash* left_links = left_info->links;
2802 // For each link defined for the left class:
2803 // see if the link references the joined class
2804 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2805 osrfHash* curr_link = NULL;
2806 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2807 const char* other_class = osrfHashGet( curr_link, "class" );
2808 if( other_class && !strcmp( other_class, class ) ) {
2810 // In the IDL, the parent class doesn't always know then names of the child
2811 // columns that are pointing to it, so don't use that end of the link
2812 const char* reltype = osrfHashGet( curr_link, "reltype" );
2813 if( reltype && strcmp( reltype, "has_many" ) ) {
2814 // Found a link between the classes
2815 fkey = osrfHashIteratorKey( itr );
2816 field = osrfHashGet( curr_link, "key" );
2821 osrfHashIteratorFree( itr );
2823 if( !field || !fkey ) {
2824 // Do another such search, with the classes reversed
2826 // For each link defined for the joined class:
2827 // see if the link references the left class
2828 osrfHashIterator* itr = osrfNewHashIterator( links );
2829 osrfHash* curr_link = NULL;
2830 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2831 const char* other_class = osrfHashGet( curr_link, "class" );
2832 if( other_class && !strcmp( other_class, leftclass ) ) {
2834 // In the IDL, the parent class doesn't know then names of the child
2835 // columns that are pointing to it, so don't use that end of the link
2836 const char* reltype = osrfHashGet( curr_link, "reltype" );
2837 if( reltype && strcmp( reltype, "has_many" ) ) {
2838 // Found a link between the classes
2839 field = osrfHashIteratorKey( itr );
2840 fkey = osrfHashGet( curr_link, "key" );
2845 osrfHashIteratorFree( itr );
2848 if( !field || !fkey ) {
2851 "%s: JOIN failed. No link defined between %s and %s",
2856 buffer_free( join_buf );
2858 jsonObjectFree( freeable_hash );
2859 jsonIteratorFree( search_itr );
2864 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2866 if( !strcasecmp( type,"left" )) {
2867 buffer_add( join_buf, " LEFT JOIN" );
2868 } else if( !strcasecmp( type,"right" )) {
2869 buffer_add( join_buf, " RIGHT JOIN" );
2870 } else if( !strcasecmp( type,"full" )) {
2871 buffer_add( join_buf, " FULL JOIN" );
2873 buffer_add( join_buf, " INNER JOIN" );
2876 buffer_add( join_buf, " INNER JOIN" );
2879 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2880 table, right_alias, right_alias, field, left_info->alias, fkey );
2882 // Add any other join conditions as specified by "filter"
2883 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2885 const char* filter_op = jsonObjectGetString(
2886 jsonObjectGetKeyConst( snode, "filter_op" ) );
2887 if( filter_op && !strcasecmp( "or",filter_op )) {
2888 buffer_add( join_buf, " OR " );
2890 buffer_add( join_buf, " AND " );
2893 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2895 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2896 OSRF_BUFFER_ADD( join_buf, jpred );
2901 "%s: JOIN failed. Invalid conditional expression.",
2904 jsonIteratorFree( search_itr );
2905 buffer_free( join_buf );
2907 jsonObjectFree( freeable_hash );
2912 buffer_add( join_buf, " ) " );
2914 // Recursively add a nested join, if one is present
2915 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2917 char* jpred = searchJOIN( join_filter, right_info );
2919 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2920 OSRF_BUFFER_ADD( join_buf, jpred );
2923 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2924 jsonIteratorFree( search_itr );
2925 buffer_free( join_buf );
2927 jsonObjectFree( freeable_hash );
2934 jsonObjectFree( freeable_hash );
2935 jsonIteratorFree( search_itr );
2937 return buffer_release( join_buf );
2942 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2943 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2944 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2946 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2948 search_hash is the JSON expression of the conditions.
2949 meta is the class definition from the IDL, for the relevant table.
2950 opjoin_type indicates whether multiple conditions, if present, should be
2951 connected by AND or OR.
2952 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2953 to pass it to other functions -- and all they do with it is to use the session
2954 and request members to send error messages back to the client.
2958 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2959 int opjoin_type, osrfMethodContext* ctx ) {
2963 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2964 "opjoin_type = %d, ctx addr = %p",
2967 class_info->class_def,
2972 growing_buffer* sql_buf = buffer_init( 128 );
2974 jsonObject* node = NULL;
2977 if( search_hash->type == JSON_ARRAY ) {
2978 osrfLogDebug( OSRF_LOG_MARK,
2979 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2980 if( 0 == search_hash->size ) {
2983 "%s: Invalid predicate structure: empty JSON array",
2986 buffer_free( sql_buf );
2990 unsigned long i = 0;
2991 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
2995 if( opjoin_type == OR_OP_JOIN )
2996 buffer_add( sql_buf, " OR " );
2998 buffer_add( sql_buf, " AND " );
3001 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3003 buffer_free( sql_buf );
3007 buffer_fadd( sql_buf, "( %s )", subpred );
3011 } else if( search_hash->type == JSON_HASH ) {
3012 osrfLogDebug( OSRF_LOG_MARK,
3013 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3014 jsonIterator* search_itr = jsonNewIterator( search_hash );
3015 if( !jsonIteratorHasNext( search_itr ) ) {
3018 "%s: Invalid predicate structure: empty JSON object",
3021 jsonIteratorFree( search_itr );
3022 buffer_free( sql_buf );
3026 while( (node = jsonIteratorNext( search_itr )) ) {
3031 if( opjoin_type == OR_OP_JOIN )
3032 buffer_add( sql_buf, " OR " );
3034 buffer_add( sql_buf, " AND " );
3037 if( '+' == search_itr->key[ 0 ] ) {
3039 // This plus sign prefixes a class name or other table alias;
3040 // make sure the table alias is in scope
3041 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3042 if( ! alias_info ) {
3045 "%s: Invalid table alias \"%s\" in WHERE clause",
3049 jsonIteratorFree( search_itr );
3050 buffer_free( sql_buf );
3054 if( node->type == JSON_STRING ) {
3055 // It's the name of a column; make sure it belongs to the class
3056 const char* fieldname = jsonObjectGetString( node );
3057 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3060 "%s: Invalid column name \"%s\" in WHERE clause "
3061 "for table alias \"%s\"",
3066 jsonIteratorFree( search_itr );
3067 buffer_free( sql_buf );
3071 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3073 // It's something more complicated
3074 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3076 jsonIteratorFree( search_itr );
3077 buffer_free( sql_buf );
3081 buffer_fadd( sql_buf, "( %s )", subpred );
3084 } else if( '-' == search_itr->key[ 0 ] ) {
3085 if( !strcasecmp( "-or", search_itr->key )) {
3086 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3088 jsonIteratorFree( search_itr );
3089 buffer_free( sql_buf );
3093 buffer_fadd( sql_buf, "( %s )", subpred );
3095 } else if( !strcasecmp( "-and", search_itr->key )) {
3096 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3098 jsonIteratorFree( search_itr );
3099 buffer_free( sql_buf );
3103 buffer_fadd( sql_buf, "( %s )", subpred );
3105 } else if( !strcasecmp("-not",search_itr->key) ) {
3106 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3108 jsonIteratorFree( search_itr );
3109 buffer_free( sql_buf );
3113 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3115 } else if( !strcasecmp( "-exists", search_itr->key )) {
3116 char* subpred = buildQuery( ctx, node, SUBSELECT );
3118 jsonIteratorFree( search_itr );
3119 buffer_free( sql_buf );
3123 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3125 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3126 char* subpred = buildQuery( ctx, node, SUBSELECT );
3128 jsonIteratorFree( search_itr );
3129 buffer_free( sql_buf );
3133 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3135 } else { // Invalid "minus" operator
3138 "%s: Invalid operator \"%s\" in WHERE clause",
3142 jsonIteratorFree( search_itr );
3143 buffer_free( sql_buf );
3149 const char* class = class_info->class_name;
3150 osrfHash* fields = class_info->fields;
3151 osrfHash* field = osrfHashGet( fields, search_itr->key );
3154 const char* table = class_info->source_def;
3157 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3160 table ? table : "?",
3163 jsonIteratorFree( search_itr );
3164 buffer_free( sql_buf );
3168 char* subpred = searchPredicate( class_info, field, node, ctx );
3170 buffer_free( sql_buf );
3171 jsonIteratorFree( search_itr );
3175 buffer_add( sql_buf, subpred );
3179 jsonIteratorFree( search_itr );
3182 // ERROR ... only hash and array allowed at this level
3183 char* predicate_string = jsonObjectToJSON( search_hash );
3186 "%s: Invalid predicate structure: %s",
3190 buffer_free( sql_buf );
3191 free( predicate_string );
3195 return buffer_release( sql_buf );
3198 /* Build a JSON_ARRAY of field names for a given table alias
3200 static jsonObject* defaultSelectList( const char* table_alias ) {
3205 ClassInfo* class_info = search_all_alias( table_alias );
3206 if( ! class_info ) {
3209 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",