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 void pop_query_frame( void );
106 static void push_query_frame( void );
107 static int add_query_core( const char* alias, const char* class_name );
108 static inline ClassInfo* search_alias( const char* target );
109 static ClassInfo* search_all_alias( const char* target );
110 static ClassInfo* add_joined_class( const char* alias, const char* classname );
111 static void clear_query_stack( void );
113 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
114 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
115 static const char* org_tree_root( osrfMethodContext* ctx );
116 static jsonObject* single_hash( const char* key, const char* value );
118 static int child_initialized = 0; /* boolean */
120 static dbi_conn writehandle; /* our MASTER db connection */
121 static dbi_conn dbhandle; /* our CURRENT db connection */
122 //static osrfHash * readHandles;
124 // The following points to the top of a stack of QueryFrames. It's a little
125 // confusing because the top level of the query is at the bottom of the stack.
126 static QueryFrame* curr_query = NULL;
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 static int max_flesh_depth = 100;
134 static int enforce_pcrud = 0; // Boolean
135 static char* modulename = NULL;
138 @brief Connect to the database.
139 @return A database connection if successful, or NULL if not.
141 dbi_conn oilsConnectDB( const char* mod_name ) {
143 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
144 if( dbi_initialize( NULL ) == -1 ) {
145 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
148 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
150 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
151 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
152 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
153 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
154 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
155 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
158 dbi_conn handle = dbi_conn_new( driver );
161 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
164 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
166 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
167 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
169 if( host ) dbi_conn_set_option( handle, "host", host );
170 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
171 if( user ) dbi_conn_set_option( handle, "username", user );
172 if( pw ) dbi_conn_set_option( handle, "password", pw );
173 if( db ) dbi_conn_set_option( handle, "dbname", db );
181 if( dbi_conn_connect( handle ) < 0 ) {
183 if( dbi_conn_connect( handle ) < 0 ) {
185 dbi_conn_error( handle, &errmsg );
186 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
191 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
197 @brief Select some options.
198 @param module_name: Name of the server.
199 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
201 This source file is used (at this writing) to implement three different servers:
202 - open-ils.reporter-store
206 These servers behave mostly the same, but they implement different combinations of
207 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
209 Here we use the server name in messages to identify which kind of server issued them.
210 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
212 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
214 module_name = "open-ils.cstore"; // bulletproofing with a default
219 modulename = strdup( module_name );
220 enforce_pcrud = do_pcrud;
221 max_flesh_depth = flesh_depth;
225 @brief Install a database connection.
226 @param conn Pointer to a database connection.
228 In some contexts, @a conn may merely provide a driver so that we can process strings
229 properly, without providing an open database connection.
231 void oilsSetDBConnection( dbi_conn conn ) {
232 dbhandle = writehandle = conn;
236 @brief Get a table name, view name, or subquery for use in a FROM clause.
237 @param class Pointer to the IDL class entry.
238 @return A table name, a view name, or a subquery in parentheses.
240 In some cases the IDL defines a class, not with a table name or a view name, but with
241 a SELECT statement, which may be used as a subquery.
243 char* oilsGetRelation( osrfHash* classdef ) {
245 char* source_def = NULL;
246 const char* tabledef = osrfHashGet( classdef, "tablename" );
249 source_def = strdup( tabledef ); // Return the name of a table or view
251 tabledef = osrfHashGet( classdef, "source_definition" );
253 // Return a subquery, enclosed in parentheses
254 source_def = safe_malloc( strlen( tabledef ) + 3 );
255 source_def[ 0 ] = '(';
256 strcpy( source_def + 1, tabledef );
257 strcat( source_def, ")" );
259 // Not found: return an error
260 const char* classname = osrfHashGet( classdef, "classname" );
265 "%s ERROR No tablename or source_definition for class \"%s\"",
276 @brief Add datatypes from the database to the fields in the IDL.
277 @param handle Handle for a database connection
278 @return Zero if successful, or 1 upon error.
280 For each relevant class in the IDL: ask the database for the datatype of every field.
281 In particular, determine which fields are text fields and which fields are numeric
282 fields, so that we know whether to enclose their values in quotes.
284 int oilsExtendIDL( dbi_conn handle ) {
285 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
286 osrfHash* class = NULL;
287 growing_buffer* query_buf = buffer_init( 64 );
288 int results_found = 0; // boolean
290 // For each class in the IDL...
291 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
292 const char* classname = osrfHashIteratorKey( class_itr );
293 osrfHash* fields = osrfHashGet( class, "fields" );
295 // If the class is virtual, ignore it
296 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
297 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
301 char* tabledef = oilsGetRelation( class );
303 continue; // No such relation -- a query of it would be doomed to failure
305 buffer_reset( query_buf );
306 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
310 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
311 modulename, OSRF_BUFFER_C_STR( query_buf ) );
313 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
318 const char* columnName;
319 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
321 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
324 /* fetch the fieldmapper index */
325 osrfHash* _f = osrfHashGet(fields, columnName);
328 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
330 /* determine the field type and storage attributes */
332 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
334 case DBI_TYPE_INTEGER : {
336 if( !osrfHashGet(_f, "primitive") )
337 osrfHashSet(_f, "number", "primitive");
339 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
340 if( attr & DBI_INTEGER_SIZE8 )
341 osrfHashSet( _f, "INT8", "datatype" );
343 osrfHashSet( _f, "INT", "datatype" );
346 case DBI_TYPE_DECIMAL :
347 if( !osrfHashGet( _f, "primitive" ))
348 osrfHashSet( _f, "number", "primitive" );
350 osrfHashSet( _f, "NUMERIC", "datatype" );
353 case DBI_TYPE_STRING :
354 if( !osrfHashGet( _f, "primitive" ))
355 osrfHashSet( _f, "string", "primitive" );
357 osrfHashSet( _f,"TEXT", "datatype" );
360 case DBI_TYPE_DATETIME :
361 if( !osrfHashGet( _f, "primitive" ))
362 osrfHashSet( _f, "string", "primitive" );
364 osrfHashSet( _f, "TIMESTAMP", "datatype" );
367 case DBI_TYPE_BINARY :
368 if( !osrfHashGet( _f, "primitive" ))
369 osrfHashSet( _f, "string", "primitive" );
371 osrfHashSet( _f, "BYTEA", "datatype" );
376 "Setting [%s] to primitive [%s] and datatype [%s]...",
378 osrfHashGet( _f, "primitive" ),
379 osrfHashGet( _f, "datatype" )
383 } // end while loop for traversing columns of result
384 dbi_result_free( result );
386 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
388 } // end for each class in IDL
390 buffer_free( query_buf );
391 osrfHashIteratorFree( class_itr );
392 child_initialized = 1;
394 if( !results_found ) {
395 osrfLogError( OSRF_LOG_MARK,
396 "No results found for any class -- bad database connection?" );
404 @brief Free an osrfHash that stores a transaction ID.
405 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
407 This function is a callback, to be called by the application session when it ends.
408 The application session stores the osrfHash via an opaque pointer.
410 If the osrfHash contains an entry for the key "xact_id", it means that an
411 uncommitted transaction is pending. Roll it back.
413 void userDataFree( void* blob ) {
414 osrfHash* hash = (osrfHash*) blob;
415 if( osrfHashGet( hash, "xact_id" ) && writehandle )
416 dbi_conn_query( writehandle, "ROLLBACK;" );
418 osrfHashFree( hash );
422 @name Managing session data
423 @brief Maintain data stored via the userData pointer of the application session.
425 Currently, session-level data is stored in an osrfHash. Other arrangements are
426 possible, and some would be more efficient. The application session calls a
427 callback function to free userData before terminating.
429 Currently, the only data we store at the session level is the transaction id. By this
430 means we can ensure that any pending transactions are rolled back before the application
436 @brief Free an item in the application session's userData.
437 @param key The name of a key for an osrfHash.
438 @param item An opaque pointer to the item associated with the key.
440 We store an osrfHash as userData with the application session, and arrange (by
441 installing userDataFree() as a different callback) for the session to free that
442 osrfHash before terminating.
444 This function is a callback for freeing items in the osrfHash. Currently we store
446 - Transaction id of a pending transaction; a character string. Key: "xact_id".
447 - Authkey; a character string. Key: "authkey".
448 - User object from the authentication server; a jsonObject. Key: "user_login".
450 If we ever store anything else in userData, we will need to revisit this function so
451 that it will free whatever else needs freeing.
453 static void sessionDataFree( char* key, void* item ) {
454 if( !strcmp( key, "xact_id" )
455 || !strcmp( key, "authkey" ) ) {
457 } else if( !strcmp( key, "user_login" ) )
458 jsonObjectFree( (jsonObject*) item );
462 @brief Save a transaction id.
463 @param ctx Pointer to the method context.
465 Save the session_id of the current application session as a transaction id.
467 static void setXactId( osrfMethodContext* ctx ) {
468 if( ctx && ctx->session ) {
469 osrfAppSession* session = ctx->session;
471 osrfHash* cache = session->userData;
473 // If the session doesn't already have a hash, create one. Make sure
474 // that the application session frees the hash when it terminates.
475 if( NULL == cache ) {
476 session->userData = cache = osrfNewHash();
477 osrfHashSetCallback( cache, &sessionDataFree );
478 ctx->session->userDataFree = &userDataFree;
481 // Save the transaction id in the hash, with the key "xact_id"
482 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
487 @brief Get the transaction ID for the current transaction, if any.
488 @param ctx Pointer to the method context.
489 @return Pointer to the transaction ID.
491 The return value points to an internal buffer, and will become invalid upon issuing
492 a commit or rollback.
494 static inline const char* getXactId( osrfMethodContext* ctx ) {
495 if( ctx && ctx->session && ctx->session->userData )
496 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
502 @brief Clear the current transaction id.
503 @param ctx Pointer to the method context.
505 static inline void clearXactId( osrfMethodContext* ctx ) {
506 if( ctx && ctx->session && ctx->session->userData )
507 osrfHashRemove( ctx->session->userData, "xact_id" );
512 @brief Save the user's login in the userData for the current application session.
513 @param ctx Pointer to the method context.
514 @param user_login Pointer to the user login object to be cached (we cache the original,
517 If @a user_login is NULL, remove the user login if one is already cached.
519 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
520 if( ctx && ctx->session ) {
521 osrfAppSession* session = ctx->session;
523 osrfHash* cache = session->userData;
525 // If the session doesn't already have a hash, create one. Make sure
526 // that the application session frees the hash when it terminates.
527 if( NULL == cache ) {
528 session->userData = cache = osrfNewHash();
529 osrfHashSetCallback( cache, &sessionDataFree );
530 ctx->session->userDataFree = &userDataFree;
534 osrfHashSet( cache, user_login, "user_login" );
536 osrfHashRemove( cache, "user_login" );
541 @brief Get the user login object for the current application session, if any.
542 @param ctx Pointer to the method context.
543 @return Pointer to the user login object if found; otherwise NULL.
545 The user login object was returned from the authentication server, and then cached so
546 we don't have to call the authentication server again for the same user.
548 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
549 if( ctx && ctx->session && ctx->session->userData )
550 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
556 @brief Save a copy of an authkey in the userData of the current application session.
557 @param ctx Pointer to the method context.
558 @param authkey The authkey to be saved.
560 If @a authkey is NULL, remove the authkey if one is already cached.
562 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
563 if( ctx && ctx->session && authkey ) {
564 osrfAppSession* session = ctx->session;
565 osrfHash* cache = session->userData;
567 // If the session doesn't already have a hash, create one. Make sure
568 // that the application session frees the hash when it terminates.
569 if( NULL == cache ) {
570 session->userData = cache = osrfNewHash();
571 osrfHashSetCallback( cache, &sessionDataFree );
572 ctx->session->userDataFree = &userDataFree;
575 // Save the transaction id in the hash, with the key "xact_id"
576 if( authkey && *authkey )
577 osrfHashSet( cache, strdup( authkey ), "authkey" );
579 osrfHashRemove( cache, "authkey" );
584 @brief Reset the login timeout.
585 @param authkey The authentication key for the current login session.
586 @param now The current time.
587 @return Zero if successful, or 1 if not.
589 Tell the authentication server to reset the timeout so that the login session won't
590 expire for a while longer.
592 We could dispense with the @a now parameter by calling time(). But we just called
593 time() in order to decide whether to reset the timeout, so we might as well reuse
594 the result instead of calling time() again.
596 static int reset_timeout( const char* authkey, time_t now ) {
597 jsonObject* auth_object = jsonNewObject( authkey );
599 // Ask the authentication server to reset the timeout. It returns an event
600 // indicating success or failure.
601 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
602 "open-ils.auth.session.reset_timeout", auth_object );
603 jsonObjectFree( auth_object );
605 if( !result || result->type != JSON_HASH ) {
606 osrfLogError( OSRF_LOG_MARK,
607 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
608 jsonObjectFree( result );
609 return 1; // Not the right sort of object returned
612 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
613 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
614 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
615 jsonObjectFree( result );
616 return 1; // Return code from method not available
619 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
620 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
622 desc = "(No reason available)"; // failsafe; shouldn't happen
623 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
624 jsonObjectFree( result );
628 // Revise our local proxy for the timeout deadline
629 // by a smallish fraction of the timeout interval
630 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
632 timeout = "1"; // failsafe; shouldn't happen
633 time_next_reset = now + atoi( timeout ) / 15;
635 jsonObjectFree( result );
636 return 0; // Successfully reset timeout
640 @brief Get the authkey string for the current application session, if any.
641 @param ctx Pointer to the method context.
642 @return Pointer to the cached authkey if found; otherwise NULL.
644 If present, the authkey string was cached from a previous method call.
646 static const char* getAuthkey( osrfMethodContext* ctx ) {
647 if( ctx && ctx->session && ctx->session->userData ) {
648 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
650 // Possibly reset the authentication timeout to keep the login alive. We do so
651 // no more than once per method call, and not at all if it has been only a short
652 // time since the last reset.
654 // Here we reset explicitly, if at all. We also implicitly reset the timeout
655 // whenever we call the "open-ils.auth.session.retrieve" method.
656 if( timeout_needs_resetting ) {
657 time_t now = time( NULL );
658 if( now >= time_next_reset && reset_timeout( authkey, now ) )
659 authkey = NULL; // timeout has apparently expired already
662 timeout_needs_resetting = 0;
670 @brief Implement the transaction.begin method.
671 @param ctx Pointer to the method context.
672 @return Zero if successful, or -1 upon error.
674 Start a transaction. Save a transaction ID for future reference.
677 - authkey (PCRUD only)
679 Return to client: Transaction ID
681 int beginTransaction( osrfMethodContext* ctx ) {
682 if(osrfMethodVerifyContext( ctx )) {
683 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
687 if( enforce_pcrud ) {
688 timeout_needs_resetting = 1;
689 const jsonObject* user = verifyUserPCRUD( ctx );
694 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
696 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
697 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
698 "osrfMethodException", ctx->request, "Error starting transaction" );
702 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
703 osrfAppRespondComplete( ctx, ret );
704 jsonObjectFree( ret );
710 @brief Implement the savepoint.set method.
711 @param ctx Pointer to the method context.
712 @return Zero if successful, or -1 if not.
714 Issue a SAVEPOINT to the database server.
717 - authkey (PCRUD only)
720 Return to client: Savepoint name
722 int setSavepoint( osrfMethodContext* ctx ) {
723 if(osrfMethodVerifyContext( ctx )) {
724 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
729 if( enforce_pcrud ) {
731 timeout_needs_resetting = 1;
732 const jsonObject* user = verifyUserPCRUD( ctx );
737 // Verify that a transaction is pending
738 const char* trans_id = getXactId( ctx );
739 if( NULL == trans_id ) {
740 osrfAppSessionStatus(
742 OSRF_STATUS_INTERNALSERVERERROR,
743 "osrfMethodException",
745 "No active transaction -- required for savepoints"
750 // Get the savepoint name from the method params
751 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
753 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
757 "%s: Error creating savepoint %s in transaction %s",
762 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
763 "osrfMethodException", ctx->request, "Error creating savepoint" );
766 jsonObject* ret = jsonNewObject( spName );
767 osrfAppRespondComplete( ctx, ret );
768 jsonObjectFree( ret );
774 @brief Implement the savepoint.release method.
775 @param ctx Pointer to the method context.
776 @return Zero if successful, or -1 if not.
778 Issue a RELEASE SAVEPOINT to the database server.
781 - authkey (PCRUD only)
784 Return to client: Savepoint name
786 int releaseSavepoint( osrfMethodContext* ctx ) {
787 if(osrfMethodVerifyContext( ctx )) {
788 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
793 if( enforce_pcrud ) {
795 timeout_needs_resetting = 1;
796 const jsonObject* user = verifyUserPCRUD( ctx );
801 // Verify that a transaction is pending
802 const char* trans_id = getXactId( ctx );
803 if( NULL == trans_id ) {
804 osrfAppSessionStatus(
806 OSRF_STATUS_INTERNALSERVERERROR,
807 "osrfMethodException",
809 "No active transaction -- required for savepoints"
814 // Get the savepoint name from the method params
815 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
817 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
821 "%s: Error releasing savepoint %s in transaction %s",
826 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
827 "osrfMethodException", ctx->request, "Error releasing savepoint" );
830 jsonObject* ret = jsonNewObject( spName );
831 osrfAppRespondComplete( ctx, ret );
832 jsonObjectFree( ret );
838 @brief Implement the savepoint.rollback method.
839 @param ctx Pointer to the method context.
840 @return Zero if successful, or -1 if not.
842 Issue a ROLLBACK TO SAVEPOINT to the database server.
845 - authkey (PCRUD only)
848 Return to client: Savepoint name
850 int rollbackSavepoint( osrfMethodContext* ctx ) {
851 if(osrfMethodVerifyContext( ctx )) {
852 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
857 if( enforce_pcrud ) {
859 timeout_needs_resetting = 1;
860 const jsonObject* user = verifyUserPCRUD( ctx );
865 // Verify that a transaction is pending
866 const char* trans_id = getXactId( ctx );
867 if( NULL == trans_id ) {
868 osrfAppSessionStatus(
870 OSRF_STATUS_INTERNALSERVERERROR,
871 "osrfMethodException",
873 "No active transaction -- required for savepoints"
878 // Get the savepoint name from the method params
879 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
881 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
885 "%s: Error rolling back savepoint %s in transaction %s",
890 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
891 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
894 jsonObject* ret = jsonNewObject( spName );
895 osrfAppRespondComplete( ctx, ret );
896 jsonObjectFree( ret );
902 @brief Implement the transaction.commit method.
903 @param ctx Pointer to the method context.
904 @return Zero if successful, or -1 if not.
906 Issue a COMMIT to the database server.
909 - authkey (PCRUD only)
911 Return to client: Transaction ID.
913 int commitTransaction( osrfMethodContext* ctx ) {
914 if(osrfMethodVerifyContext( ctx )) {
915 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
919 if( enforce_pcrud ) {
920 timeout_needs_resetting = 1;
921 const jsonObject* user = verifyUserPCRUD( ctx );
926 // Verify that a transaction is pending
927 const char* trans_id = getXactId( ctx );
928 if( NULL == trans_id ) {
929 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
930 "osrfMethodException", ctx->request, "No active transaction to commit" );
934 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
936 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
937 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
938 "osrfMethodException", ctx->request, "Error committing transaction" );
941 jsonObject* ret = jsonNewObject( trans_id );
942 osrfAppRespondComplete( ctx, ret );
943 jsonObjectFree( ret );
950 @brief Implement the transaction.rollback method.
951 @param ctx Pointer to the method context.
952 @return Zero if successful, or -1 if not.
954 Issue a ROLLBACK to the database server.
957 - authkey (PCRUD only)
959 Return to client: Transaction ID
961 int rollbackTransaction( osrfMethodContext* ctx ) {
962 if( osrfMethodVerifyContext( ctx )) {
963 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
967 if( enforce_pcrud ) {
968 timeout_needs_resetting = 1;
969 const jsonObject* user = verifyUserPCRUD( ctx );
974 // Verify that a transaction is pending
975 const char* trans_id = getXactId( ctx );
976 if( NULL == trans_id ) {
977 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
978 "osrfMethodException", ctx->request, "No active transaction to roll back" );
982 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
984 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
985 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
986 "osrfMethodException", ctx->request, "Error rolling back transaction" );
989 jsonObject* ret = jsonNewObject( trans_id );
990 osrfAppRespondComplete( ctx, ret );
991 jsonObjectFree( ret );
998 @brief Implement the "search" method.
999 @param ctx Pointer to the method context.
1000 @return Zero if successful, or -1 if not.
1003 - authkey (PCRUD only)
1004 - WHERE clause, as jsonObject
1005 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1007 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1008 Optionally flesh linked fields.
1010 int doSearch( osrfMethodContext* ctx ) {
1011 if( osrfMethodVerifyContext( ctx )) {
1012 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1017 timeout_needs_resetting = 1;
1019 jsonObject* where_clause;
1020 jsonObject* rest_of_query;
1022 if( enforce_pcrud ) {
1023 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1024 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1026 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1027 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1030 // Get the class metadata
1031 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1032 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1036 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1038 osrfAppRespondComplete( ctx, NULL );
1042 // Return each row to the client (except that some may be suppressed by PCRUD)
1043 jsonObject* cur = 0;
1044 unsigned long res_idx = 0;
1045 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1046 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1048 osrfAppRespond( ctx, cur );
1050 jsonObjectFree( obj );
1052 osrfAppRespondComplete( ctx, NULL );
1057 @brief Implement the "id_list" method.
1058 @param ctx Pointer to the method context.
1059 @param err Pointer through which to return an error code.
1060 @return Zero if successful, or -1 if not.
1063 - authkey (PCRUD only)
1064 - WHERE clause, as jsonObject
1065 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1067 Return to client: The primary key values for all rows of the relevant class that
1068 satisfy a specified WHERE clause.
1070 This method relies on the assumption that every class has a primary key consisting of
1073 int doIdList( osrfMethodContext* ctx ) {
1074 if( osrfMethodVerifyContext( ctx )) {
1075 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1080 timeout_needs_resetting = 1;
1082 jsonObject* where_clause;
1083 jsonObject* rest_of_query;
1085 // We use the where clause without change. But we need to massage the rest of the
1086 // query, so we work with a copy of it instead of modifying the original.
1088 if( enforce_pcrud ) {
1089 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1090 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1092 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1093 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1096 // Eliminate certain SQL clauses, if present.
1097 if( rest_of_query ) {
1098 jsonObjectRemoveKey( rest_of_query, "select" );
1099 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1100 jsonObjectRemoveKey( rest_of_query, "flesh" );
1101 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1103 rest_of_query = jsonNewObjectType( JSON_HASH );
1106 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1108 // Get the class metadata
1109 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1110 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1112 // Build a SELECT list containing just the primary key,
1113 // i.e. like { "classname":["keyname"] }
1114 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1116 // Load array with name of primary key
1117 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1118 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1119 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1121 jsonObjectSetKey( rest_of_query, "select", select_clause );
1126 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1128 jsonObjectFree( rest_of_query );
1130 osrfAppRespondComplete( ctx, NULL );
1134 // Return each primary key value to the client
1136 unsigned long res_idx = 0;
1137 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1138 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1139 continue; // Suppress due to lack of permission
1141 osrfAppRespond( ctx,
1142 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1145 jsonObjectFree( obj );
1146 osrfAppRespondComplete( ctx, NULL );
1151 @brief Verify that we have a valid class reference.
1152 @param ctx Pointer to the method context.
1153 @param param Pointer to the method parameters.
1154 @return 1 if the class reference is valid, or zero if it isn't.
1156 The class of the method params must match the class to which the method id devoted.
1157 For PCRUD there are additional restrictions.
1159 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1161 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1162 osrfHash* class = osrfHashGet( method_meta, "class" );
1164 // Compare the method's class to the parameters' class
1165 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1167 // Oops -- they don't match. Complain.
1168 growing_buffer* msg = buffer_init( 128 );
1171 "%s: %s method for type %s was passed a %s",
1173 osrfHashGet( method_meta, "methodtype" ),
1174 osrfHashGet( class, "classname" ),
1175 param->classname ? param->classname : "(null)"
1178 char* m = buffer_release( msg );
1179 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1187 return verifyObjectPCRUD( ctx, param );
1193 @brief (PCRUD only) Verify that the user is properly logged in.
1194 @param ctx Pointer to the method context.
1195 @return If the user is logged in, a pointer to the user object from the authentication
1196 server; otherwise NULL.
1198 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1200 // Get the authkey (the first method parameter)
1201 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1203 // See if we have the same authkey, and a user object,
1204 // locally cached from a previous call
1205 const char* cached_authkey = getAuthkey( ctx );
1206 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1207 const jsonObject* cached_user = getUserLogin( ctx );
1212 // We have no matching authentication data in the cache. Authenticate from scratch.
1213 jsonObject* auth_object = jsonNewObject( auth );
1215 // Fetch the user object from the authentication server
1216 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1218 jsonObjectFree( auth_object );
1220 if( !user->classname || strcmp(user->classname, "au" )) {
1222 growing_buffer* msg = buffer_init( 128 );
1225 "%s: permacrud received a bad auth token: %s",
1230 char* m = buffer_release( msg );
1231 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1235 jsonObjectFree( user );
1239 setUserLogin( ctx, user );
1240 setAuthkey( ctx, auth );
1242 // Allow ourselves up to a second before we have to reset the login timeout.
1243 // It would be nice to use some fraction of the timeout interval enforced by the
1244 // authentication server, but that value is not readily available at this point.
1245 // Instead, we use a conservative default interval.
1246 time_next_reset = time( NULL ) + 1;
1252 @brief For PCRUD: Determine whether the current user may access the current row.
1253 @param ctx Pointer to the method context.
1254 @param obj Pointer to the row being potentially accessed.
1255 @return 1 if access is permitted, or 0 if it isn't.
1257 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1259 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1261 dbhandle = writehandle;
1263 // Figure out what class and method are involved
1264 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1265 osrfHash* class = osrfHashGet( method_metadata, "class" );
1266 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1268 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1269 // contexts we will do another lookup of the current row, even if we already have a
1270 // previously fetched row image, because the row image in hand may not include the
1271 // foreign key(s) that we need.
1273 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1274 // but they aren't implemented yet.
1277 if( *method_type == 's' || *method_type == 'i' ) {
1278 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1280 } else if( *method_type == 'u' || *method_type == 'd' ) {
1281 fetch = 1; // MUST go to the db for the object for update and delete
1284 // Get the appropriate permacrud entry from the IDL, depending on method type
1285 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1287 // No permacrud for this method type on this class
1289 growing_buffer* msg = buffer_init( 128 );
1292 "%s: %s on class %s has no permacrud IDL entry",
1294 osrfHashGet( method_metadata, "methodtype" ),
1295 osrfHashGet( class, "classname" )
1298 char* m = buffer_release( msg );
1299 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1300 "osrfMethodException", ctx->request, m );
1307 // Get the user id, and make sure the user is logged in
1308 const jsonObject* user = verifyUserPCRUD( ctx );
1310 return 0; // Not logged in? No access.
1312 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1314 // Get a list of permissions from the permacrud entry.
1315 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1317 // Build a list of org units that own the row. This is fairly convoluted because there
1318 // are several different ways that an org unit may own the row, as defined by the
1321 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1322 // identifying an owning org_unit..
1323 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1325 // Foreign context adds a layer of indirection. The row points to some other row that
1326 // an org unit may own. The "jump" attribute, if present, adds another layer of
1328 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1330 // The following string array stores the list of org units. (We don't have a thingie
1331 // for storing lists of integers, so we fake it with a list of strings.)
1332 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1335 const char* pkey_value = NULL;
1336 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1337 // If the global_required attribute is present and true, then the only owning
1338 // org unit is the root org unit, i.e. the one with no parent.
1339 osrfLogDebug( OSRF_LOG_MARK,
1340 "global-level permissions required, fetching top of the org tree" );
1342 // check for perm at top of org tree
1343 const char* org_tree_root_id = org_tree_root( ctx );
1344 if( org_tree_root_id ) {
1345 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1346 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1348 osrfStringArrayFree( context_org_array );
1353 // If the global_required attribute is absent or false, then we look for
1354 // local and/or foreign context. In order to find the relevant foreign
1355 // keys, we must either read the relevant row from the database, or look at
1356 // the image of the row that we already have in memory.
1358 // (Herein lies a bug. Even if we have an image of the row in memory, that
1359 // image may not include the foreign key column(s) that we need.)
1361 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1362 "fetching context org ids" );
1363 const char* pkey = osrfHashGet( class, "primarykey" );
1364 jsonObject *param = NULL;
1366 if( obj->classname ) {
1367 pkey_value = oilsFMGetStringConst( obj, pkey );
1369 param = jsonObjectClone( obj );
1370 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1373 pkey_value = jsonObjectGetString( obj );
1375 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1376 "of %s and retrieving from the database", pkey_value );
1380 // Fetch the row so that we can look at the foreign key(s)
1381 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1382 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1383 jsonObjectFree( _tmp_params );
1385 param = jsonObjectExtractIndex( _list, 0 );
1386 jsonObjectFree( _list );
1390 // The row doesn't exist. Complain, and deny access.
1391 osrfLogDebug( OSRF_LOG_MARK,
1392 "Object not found in the database with primary key %s of %s",
1395 growing_buffer* msg = buffer_init( 128 );
1398 "%s: no object found with primary key %s of %s",
1404 char* m = buffer_release( msg );
1405 osrfAppSessionStatus(
1407 OSRF_STATUS_INTERNALSERVERERROR,
1408 "osrfMethodException",
1417 if( local_context && local_context->size > 0 ) {
1418 // The IDL provides a list of column names for the foreign keys denoting
1419 // local context. Look up the value of each one, and if it isn't null,
1420 // add it to the list of org units.
1421 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1422 local_context->size );
1424 const char* lcontext = NULL;
1425 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1426 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1427 if( fkey_value ) { // if not null
1428 osrfStringArrayAdd( context_org_array, fkey_value );
1431 "adding class-local field %s (value: %s) to the context org list",
1433 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1439 if( foreign_context ) {
1440 unsigned long class_count = osrfHashGetCount( foreign_context );
1441 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1443 if( class_count > 0 ) {
1445 // The IDL provides a list of foreign key columns pointing to rows that
1446 // an org unit may own. Follow each link, identify the owning org unit,
1447 // and add it to the list.
1448 osrfHash* fcontext = NULL;
1449 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1450 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1451 // For each class to which a foreign key points:
1452 const char* class_name = osrfHashIteratorKey( class_itr );
1453 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1457 "%d foreign context fields(s) specified for class %s",
1458 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1462 // Get the name of the key field in the foreign table
1463 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1465 // Get the value of the foreign key pointing to the foreign table
1466 char* foreign_pkey_value =
1467 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1468 if( !foreign_pkey_value )
1469 continue; // Foreign key value is null; skip it
1471 // Look up the row to which the foreign key points
1472 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1473 jsonObject* _list = doFieldmapperSearch(
1474 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1476 jsonObject* _fparam = NULL;
1477 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1478 _fparam = jsonObjectExtractIndex( _list, 0 );
1480 jsonObjectFree( _tmp_params );
1481 jsonObjectFree( _list );
1483 // At this point _fparam either points to the row identified by the
1484 // foreign key, or it's NULL (no such row found).
1486 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1488 const char* bad_class = NULL; // For noting failed lookups
1490 bad_class = class_name; // Referenced row not found
1491 else if( jump_list ) {
1492 // Follow a chain of rows, linked by foreign keys, to find an owner
1493 const char* flink = NULL;
1495 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1496 // For each entry in the jump list. Each entry (i.e. flink) is
1497 // the name of a foreign key column in the current row.
1499 // From the IDL, get the linkage information for the next jump
1500 osrfHash* foreign_link_hash =
1501 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1503 // Get the class metadata for the class
1504 // to which the foreign key points
1505 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1506 osrfHashGet( foreign_link_hash, "class" ));
1508 // Get the name of the referenced key of that class
1509 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1511 // Get the value of the foreign key pointing to that class
1512 free( foreign_pkey_value );
1513 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1514 if( !foreign_pkey_value )
1515 break; // Foreign key is null; quit looking
1517 // Build a WHERE clause for the lookup
1518 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1521 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1522 _tmp_params, NULL, &err );
1524 // Get the resulting row
1525 jsonObjectFree( _fparam );
1526 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1527 _fparam = jsonObjectExtractIndex( _list, 0 );
1529 // Referenced row not found
1531 bad_class = osrfHashGet( foreign_link_hash, "class" );
1534 jsonObjectFree( _tmp_params );
1535 jsonObjectFree( _list );
1541 // We had a foreign key pointing to such-and-such a row, but then
1542 // we couldn't fetch that row. The data in the database are in an
1543 // inconsistent state; the database itself may even be corrupted.
1544 growing_buffer* msg = buffer_init( 128 );
1547 "%s: no object of class %s found with primary key %s of %s",
1551 foreign_pkey_value ? foreign_pkey_value : "(null)"
1554 char* m = buffer_release( msg );
1555 osrfAppSessionStatus(
1557 OSRF_STATUS_INTERNALSERVERERROR,
1558 "osrfMethodException",
1564 osrfHashIteratorFree( class_itr );
1565 free( foreign_pkey_value );
1566 jsonObjectFree( param );
1571 free( foreign_pkey_value );
1574 // Examine each context column of the foreign row,
1575 // and add its value to the list of org units.
1577 const char* foreign_field = NULL;
1578 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1579 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1580 osrfStringArrayAdd( context_org_array,
1581 oilsFMGetStringConst( _fparam, foreign_field ));
1582 osrfLogDebug( OSRF_LOG_MARK,
1583 "adding foreign class %s field %s (value: %s) "
1584 "to the context org list",
1587 osrfStringArrayGetString(
1588 context_org_array, context_org_array->size - 1 )
1592 jsonObjectFree( _fparam );
1596 osrfHashIteratorFree( class_itr );
1600 jsonObjectFree( param );
1603 const char* context_org = NULL;
1604 const char* perm = NULL;
1607 if( permission->size == 0 ) {
1608 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1612 // For every combination of permission and context org unit: call a stored procedure
1613 // to determine if the user has this permission in the context of this org unit.
1614 // If the answer is yes at any point, then we're done, and the user has permission.
1615 // In other words permissions are additive.
1617 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1619 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1625 "Checking object permission [%s] for user %d "
1626 "on object %s (class %s) at org %d",
1630 osrfHashGet( class, "classname" ),
1634 result = dbi_conn_queryf(
1636 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1639 osrfHashGet( class, "classname" ),
1647 "Received a result for object permission [%s] "
1648 "for user %d on object %s (class %s) at org %d",
1652 osrfHashGet( class, "classname" ),
1656 if( dbi_result_first_row( result )) {
1657 jsonObject* return_val = oilsMakeJSONFromResult( result );
1658 const char* has_perm = jsonObjectGetString(
1659 jsonObjectGetKeyConst( return_val, "has_perm" ));
1663 "Status of object permission [%s] for user %d "
1664 "on object %s (class %s) at org %d is %s",
1668 osrfHashGet(class, "classname"),
1673 if( *has_perm == 't' )
1675 jsonObjectFree( return_val );
1678 dbi_result_free( result );
1684 osrfLogDebug( OSRF_LOG_MARK,
1685 "Checking non-object permission [%s] for user %d at org %d",
1686 perm, userid, atoi(context_org) );
1687 result = dbi_conn_queryf(
1689 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1696 osrfLogDebug( OSRF_LOG_MARK,
1697 "Received a result for permission [%s] for user %d at org %d",
1698 perm, userid, atoi( context_org ));
1699 if( dbi_result_first_row( result )) {
1700 jsonObject* return_val = oilsMakeJSONFromResult( result );
1701 const char* has_perm = jsonObjectGetString(
1702 jsonObjectGetKeyConst( return_val, "has_perm" ));
1703 osrfLogDebug( OSRF_LOG_MARK,
1704 "Status of permission [%s] for user %d at org %d is [%s]",
1705 perm, userid, atoi( context_org ), has_perm );
1706 if( *has_perm == 't' )
1708 jsonObjectFree( return_val );
1711 dbi_result_free( result );
1721 osrfStringArrayFree( context_org_array );
1727 @brief Look up the root of the org_unit tree.
1728 @param ctx Pointer to the method context.
1729 @return The id of the root org unit, as a character string.
1731 Query actor.org_unit where parent_ou is null, and return the id as a string.
1733 This function assumes that there is only one root org unit, i.e. that we
1734 have a single tree, not a forest.
1736 The calling code is responsible for freeing the returned string.
1738 static const char* org_tree_root( osrfMethodContext* ctx ) {
1740 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1741 static time_t last_lookup_time = 0;
1742 time_t current_time = time( NULL );
1744 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1745 // We successfully looked this up less than an hour ago.
1746 // It's not likely to have changed since then.
1747 return strdup( cached_root_id );
1749 last_lookup_time = current_time;
1752 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1753 jsonObject* result = doFieldmapperSearch(
1754 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1755 jsonObjectFree( where_clause );
1757 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1760 jsonObjectFree( result );
1762 growing_buffer* msg = buffer_init( 128 );
1763 OSRF_BUFFER_ADD( msg, modulename );
1764 OSRF_BUFFER_ADD( msg,
1765 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1767 char* m = buffer_release( msg );
1768 osrfAppSessionStatus( ctx->session,
1769 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1772 cached_root_id[ 0 ] = '\0';
1776 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1777 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1779 strcpy( cached_root_id, root_org_unit_id );
1780 jsonObjectFree( result );
1781 return cached_root_id;
1785 @brief Create a JSON_HASH with a single key/value pair.
1786 @param key The key of the key/value pair.
1787 @param value the value of the key/value pair.
1788 @return Pointer to a newly created jsonObject of type JSON_HASH.
1790 The value of the key/value is either a string or (if @a value is NULL) a null.
1792 static jsonObject* single_hash( const char* key, const char* value ) {
1794 if( ! key ) key = "";
1796 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1797 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1802 int doCreate( osrfMethodContext* ctx ) {
1803 if(osrfMethodVerifyContext( ctx )) {
1804 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1809 timeout_needs_resetting = 1;
1811 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1812 jsonObject* target = NULL;
1813 jsonObject* options = NULL;
1815 if( enforce_pcrud ) {
1816 target = jsonObjectGetIndex( ctx->params, 1 );
1817 options = jsonObjectGetIndex( ctx->params, 2 );
1819 target = jsonObjectGetIndex( ctx->params, 0 );
1820 options = jsonObjectGetIndex( ctx->params, 1 );
1823 if( !verifyObjectClass( ctx, target )) {
1824 osrfAppRespondComplete( ctx, NULL );
1828 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1830 const char* trans_id = getXactId( ctx );
1832 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1834 osrfAppSessionStatus(
1836 OSRF_STATUS_BADREQUEST,
1837 "osrfMethodException",
1839 "No active transaction -- required for CREATE"
1841 osrfAppRespondComplete( ctx, NULL );
1845 // The following test is harmless but redundant. If a class is
1846 // readonly, we don't register a create method for it.
1847 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1848 osrfAppSessionStatus(
1850 OSRF_STATUS_BADREQUEST,
1851 "osrfMethodException",
1853 "Cannot INSERT readonly class"
1855 osrfAppRespondComplete( ctx, NULL );
1859 // Set the last_xact_id
1860 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1862 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1863 trans_id, target->classname, index);
1864 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1867 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1869 dbhandle = writehandle;
1871 osrfHash* fields = osrfHashGet( meta, "fields" );
1872 char* pkey = osrfHashGet( meta, "primarykey" );
1873 char* seq = osrfHashGet( meta, "sequence" );
1875 growing_buffer* table_buf = buffer_init( 128 );
1876 growing_buffer* col_buf = buffer_init( 128 );
1877 growing_buffer* val_buf = buffer_init( 128 );
1879 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1880 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1881 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1882 buffer_add( val_buf,"VALUES (" );
1886 osrfHash* field = NULL;
1887 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1888 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1890 const char* field_name = osrfHashIteratorKey( field_itr );
1892 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1895 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1898 if( field_object && field_object->classname ) {
1899 value = oilsFMGetString(
1901 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1903 } else if( field_object && JSON_BOOL == field_object->type ) {
1904 if( jsonBoolIsTrue( field_object ) )
1905 value = strdup( "t" );
1907 value = strdup( "f" );
1909 value = jsonObjectToSimpleString( field_object );
1915 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1916 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1919 buffer_add( col_buf, field_name );
1921 if( !field_object || field_object->type == JSON_NULL ) {
1922 buffer_add( val_buf, "DEFAULT" );
1924 } else if( !strcmp( get_primitive( field ), "number" )) {
1925 const char* numtype = get_datatype( field );
1926 if( !strcmp( numtype, "INT8" )) {
1927 buffer_fadd( val_buf, "%lld", atoll( value ));
1929 } else if( !strcmp( numtype, "INT" )) {
1930 buffer_fadd( val_buf, "%d", atoi( value ));
1932 } else if( !strcmp( numtype, "NUMERIC" )) {
1933 buffer_fadd( val_buf, "%f", atof( value ));
1936 if( dbi_conn_quote_string( writehandle, &value )) {
1937 OSRF_BUFFER_ADD( val_buf, value );
1940 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1941 osrfAppSessionStatus(
1943 OSRF_STATUS_INTERNALSERVERERROR,
1944 "osrfMethodException",
1946 "Error quoting string -- please see the error log for more details"
1949 buffer_free( table_buf );
1950 buffer_free( col_buf );
1951 buffer_free( val_buf );
1952 osrfAppRespondComplete( ctx, NULL );
1960 osrfHashIteratorFree( field_itr );
1962 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1963 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1965 char* table_str = buffer_release( table_buf );
1966 char* col_str = buffer_release( col_buf );
1967 char* val_str = buffer_release( val_buf );
1968 growing_buffer* sql = buffer_init( 128 );
1969 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1974 char* query = buffer_release( sql );
1976 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1978 jsonObject* obj = NULL;
1981 dbi_result result = dbi_conn_query( writehandle, query );
1983 obj = jsonNewObject( NULL );
1986 "%s ERROR inserting %s object using query [%s]",
1988 osrfHashGet(meta, "fieldmapper"),
1991 osrfAppSessionStatus(
1993 OSRF_STATUS_INTERNALSERVERERROR,
1994 "osrfMethodException",
1996 "INSERT error -- please see the error log for more details"
2001 char* id = oilsFMGetString( target, pkey );
2003 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2004 growing_buffer* _id = buffer_init( 10 );
2005 buffer_fadd( _id, "%lld", new_id );
2006 id = buffer_release( _id );
2009 // Find quietness specification, if present
2010 const char* quiet_str = NULL;
2012 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2014 quiet_str = jsonObjectGetString( quiet_obj );
2017 if( str_is_true( quiet_str )) { // if quietness is specified
2018 obj = jsonNewObject( id );
2022 // Fetch the row that we just inserted, so that we can return it to the client
2023 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2024 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2027 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2031 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2033 jsonObjectFree( list );
2034 jsonObjectFree( where_clause );
2041 osrfAppRespondComplete( ctx, obj );
2042 jsonObjectFree( obj );
2047 @brief Implement the retrieve method.
2048 @param ctx Pointer to the method context.
2049 @param err Pointer through which to return an error code.
2050 @return If successful, a pointer to the result to be returned to the client;
2053 From the method's class, fetch a row with a specified value in the primary key. This
2054 method relies on the database design convention that a primary key consists of a single
2058 - authkey (PCRUD only)
2059 - value of the primary key for the desired row, for building the WHERE clause
2060 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2062 Return to client: One row from the query.
2064 int doRetrieve( osrfMethodContext* ctx ) {
2065 if(osrfMethodVerifyContext( ctx )) {
2066 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2071 timeout_needs_resetting = 1;
2076 if( enforce_pcrud ) {
2081 // Get the class metadata
2082 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2084 // Get the value of the primary key, from a method parameter
2085 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2089 "%s retrieving %s object with primary key value of %s",
2091 osrfHashGet( class_def, "fieldmapper" ),
2092 jsonObjectGetString( id_obj )
2095 // Build a WHERE clause based on the key value
2096 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2099 osrfHashGet( class_def, "primarykey" ), // name of key column
2100 jsonObjectClone( id_obj ) // value of key column
2103 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2107 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2109 jsonObjectFree( where_clause );
2111 osrfAppRespondComplete( ctx, NULL );
2115 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2116 jsonObjectFree( list );
2118 if( enforce_pcrud ) {
2119 if(!verifyObjectPCRUD( ctx, obj )) {
2120 jsonObjectFree( obj );
2122 growing_buffer* msg = buffer_init( 128 );
2123 OSRF_BUFFER_ADD( msg, modulename );
2124 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2126 char* m = buffer_release( msg );
2127 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2131 osrfAppRespondComplete( ctx, NULL );
2136 osrfAppRespondComplete( ctx, obj );
2137 jsonObjectFree( obj );
2142 @brief Translate a numeric value to a string representation for the database.
2143 @param field Pointer to the IDL field definition.
2144 @param value Pointer to a jsonObject holding the value of a field.
2145 @return Pointer to a newly allocated string.
2147 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2148 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2149 or (what is worse) valid SQL that is wrong.
2151 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2153 The calling code is responsible for freeing the resulting string by calling free().
2155 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2156 growing_buffer* val_buf = buffer_init( 32 );
2157 const char* numtype = get_datatype( field );
2159 // For historical reasons the following contains cruft that could be cleaned up.
2160 if( !strncmp( numtype, "INT", 3 ) ) {
2161 if( value->type == JSON_NUMBER )
2162 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2163 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2165 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2168 } else if( !strcmp( numtype, "NUMERIC" )) {
2169 if( value->type == JSON_NUMBER )
2170 buffer_fadd( val_buf, jsonObjectGetString( value ));
2172 buffer_fadd( val_buf, jsonObjectGetString( value ));
2176 // Presumably this was really intended to be a string, so quote it
2177 char* str = jsonObjectToSimpleString( value );
2178 if( dbi_conn_quote_string( dbhandle, &str )) {
2179 OSRF_BUFFER_ADD( val_buf, str );
2182 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2184 buffer_free( val_buf );
2189 return buffer_release( val_buf );
2192 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2193 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2194 growing_buffer* sql_buf = buffer_init( 32 );
2200 osrfHashGet( field, "name" )
2204 buffer_add( sql_buf, "IN (" );
2205 } else if( !strcasecmp( op,"not in" )) {
2206 buffer_add( sql_buf, "NOT IN (" );
2208 buffer_add( sql_buf, "IN (" );
2211 if( node->type == JSON_HASH ) {
2212 // subquery predicate
2213 char* subpred = buildQuery( ctx, node, SUBSELECT );
2215 buffer_free( sql_buf );
2219 buffer_add( sql_buf, subpred );
2222 } else if( node->type == JSON_ARRAY ) {
2223 // literal value list
2224 int in_item_index = 0;
2225 int in_item_first = 1;
2226 const jsonObject* in_item;
2227 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2232 buffer_add( sql_buf, ", " );
2235 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2236 osrfLogError( OSRF_LOG_MARK,
2237 "%s: Expected string or number within IN list; found %s",
2238 modulename, json_type( in_item->type ) );
2239 buffer_free( sql_buf );
2243 // Append the literal value -- quoted if not a number
2244 if( JSON_NUMBER == in_item->type ) {
2245 char* val = jsonNumberToDBString( field, in_item );
2246 OSRF_BUFFER_ADD( sql_buf, val );
2249 } else if( !strcmp( get_primitive( field ), "number" )) {
2250 char* val = jsonNumberToDBString( field, in_item );
2251 OSRF_BUFFER_ADD( sql_buf, val );
2255 char* key_string = jsonObjectToSimpleString( in_item );
2256 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2257 OSRF_BUFFER_ADD( sql_buf, key_string );
2260 osrfLogError( OSRF_LOG_MARK,
2261 "%s: Error quoting key string [%s]", modulename, key_string );
2263 buffer_free( sql_buf );
2269 if( in_item_first ) {
2270 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2271 buffer_free( sql_buf );
2275 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2276 modulename, json_type( node->type ));
2277 buffer_free( sql_buf );
2281 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2283 return buffer_release( sql_buf );
2286 // Receive a JSON_ARRAY representing a function call. The first
2287 // entry in the array is the function name. The rest are parameters.
2288 static char* searchValueTransform( const jsonObject* array ) {
2290 if( array->size < 1 ) {
2291 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2295 // Get the function name
2296 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2297 if( func_item->type != JSON_STRING ) {
2298 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2299 modulename, json_type( func_item->type ));
2303 growing_buffer* sql_buf = buffer_init( 32 );
2305 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2306 OSRF_BUFFER_ADD( sql_buf, "( " );
2308 // Get the parameters
2309 int func_item_index = 1; // We already grabbed the zeroth entry
2310 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2312 // Add a separator comma, if we need one
2313 if( func_item_index > 2 )
2314 buffer_add( sql_buf, ", " );
2316 // Add the current parameter
2317 if( func_item->type == JSON_NULL ) {
2318 buffer_add( sql_buf, "NULL" );
2320 char* val = jsonObjectToSimpleString( func_item );
2321 if( dbi_conn_quote_string( dbhandle, &val )) {
2322 OSRF_BUFFER_ADD( sql_buf, val );
2325 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2327 buffer_free( sql_buf );
2334 buffer_add( sql_buf, " )" );
2336 return buffer_release( sql_buf );
2339 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2340 const jsonObject* node, const char* op ) {
2342 if( ! is_good_operator( op ) ) {
2343 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2347 char* val = searchValueTransform( node );
2351 growing_buffer* sql_buf = buffer_init( 32 );
2356 osrfHashGet( field, "name" ),
2363 return buffer_release( sql_buf );
2366 // class_alias is a class name or other table alias
2367 // field is a field definition as stored in the IDL
2368 // node comes from the method parameter, and may represent an entry in the SELECT list
2369 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2370 const jsonObject* node ) {
2371 growing_buffer* sql_buf = buffer_init( 32 );
2373 const char* field_transform = jsonObjectGetString(
2374 jsonObjectGetKeyConst( node, "transform" ) );
2375 const char* transform_subcolumn = jsonObjectGetString(
2376 jsonObjectGetKeyConst( node, "result_field" ) );
2378 if( transform_subcolumn ) {
2379 if( ! is_identifier( transform_subcolumn ) ) {
2380 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2381 modulename, transform_subcolumn );
2382 buffer_free( sql_buf );
2385 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2388 if( field_transform ) {
2390 if( ! is_identifier( field_transform ) ) {
2391 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2392 modulename, field_transform );
2393 buffer_free( sql_buf );
2397 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2398 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2399 field_transform, class_alias, osrfHashGet( field, "name" ));
2401 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2402 field_transform, class_alias, osrfHashGet( field, "name" ));
2405 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2408 if( array->type != JSON_ARRAY ) {
2409 osrfLogError( OSRF_LOG_MARK,
2410 "%s: Expected JSON_ARRAY for function params; found %s",
2411 modulename, json_type( array->type ) );
2412 buffer_free( sql_buf );
2415 int func_item_index = 0;
2416 jsonObject* func_item;
2417 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2419 char* val = jsonObjectToSimpleString( func_item );
2422 buffer_add( sql_buf, ",NULL" );
2423 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2424 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2425 OSRF_BUFFER_ADD( sql_buf, val );
2427 osrfLogError( OSRF_LOG_MARK,
2428 "%s: Error quoting key string [%s]", modulename, val );
2430 buffer_free( sql_buf );
2437 buffer_add( sql_buf, " )" );
2440 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2443 if( transform_subcolumn )
2444 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2446 return buffer_release( sql_buf );
2449 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2450 const jsonObject* node, const char* op ) {
2452 if( ! is_good_operator( op ) ) {
2453 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2457 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2458 if( ! field_transform )
2461 int extra_parens = 0; // boolean
2463 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2465 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2467 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2469 free( field_transform );
2473 } else if( value_obj->type == JSON_ARRAY ) {
2474 value = searchValueTransform( value_obj );
2476 osrfLogError( OSRF_LOG_MARK,
2477 "%s: Error building value transform for field transform", modulename );
2478 free( field_transform );
2481 } else if( value_obj->type == JSON_HASH ) {
2482 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2484 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2486 free( field_transform );
2490 } else if( value_obj->type == JSON_NUMBER ) {
2491 value = jsonNumberToDBString( field, value_obj );
2492 } else if( value_obj->type == JSON_NULL ) {
2493 osrfLogError( OSRF_LOG_MARK,
2494 "%s: Error building predicate for field transform: null value", modulename );
2495 free( field_transform );
2497 } else if( value_obj->type == JSON_BOOL ) {
2498 osrfLogError( OSRF_LOG_MARK,
2499 "%s: Error building predicate for field transform: boolean value", modulename );
2500 free( field_transform );
2503 if( !strcmp( get_primitive( field ), "number") ) {
2504 value = jsonNumberToDBString( field, value_obj );
2506 value = jsonObjectToSimpleString( value_obj );
2507 if( !dbi_conn_quote_string( dbhandle, &value )) {
2508 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2509 modulename, value );
2511 free( field_transform );
2517 const char* left_parens = "";
2518 const char* right_parens = "";
2520 if( extra_parens ) {
2525 growing_buffer* sql_buf = buffer_init( 32 );
2529 "%s%s %s %s %s %s%s",
2540 free( field_transform );
2542 return buffer_release( sql_buf );
2545 static char* searchSimplePredicate( const char* op, const char* class_alias,
2546 osrfHash* field, const jsonObject* node ) {
2548 if( ! is_good_operator( op ) ) {
2549 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2555 // Get the value to which we are comparing the specified column
2556 if( node->type != JSON_NULL ) {
2557 if( node->type == JSON_NUMBER ) {
2558 val = jsonNumberToDBString( field, node );
2559 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2560 val = jsonNumberToDBString( field, node );
2562 val = jsonObjectToSimpleString( node );
2567 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2568 // Value is not numeric; enclose it in quotes
2569 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2570 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2577 // Compare to a null value
2578 val = strdup( "NULL" );
2579 if( strcmp( op, "=" ))
2585 growing_buffer* sql_buf = buffer_init( 32 );
2586 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2587 char* pred = buffer_release( sql_buf );
2594 static char* searchBETWEENPredicate( const char* class_alias,
2595 osrfHash* field, const jsonObject* node ) {
2597 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2598 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2600 if( NULL == y_node ) {
2601 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2604 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2605 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2612 if( !strcmp( get_primitive( field ), "number") ) {
2613 x_string = jsonNumberToDBString( field, x_node );
2614 y_string = jsonNumberToDBString( field, y_node );
2617 x_string = jsonObjectToSimpleString( x_node );
2618 y_string = jsonObjectToSimpleString( y_node );
2619 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2620 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2621 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2622 modulename, x_string, y_string );
2629 growing_buffer* sql_buf = buffer_init( 32 );
2630 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2631 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2635 return buffer_release( sql_buf );
2638 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2639 jsonObject* node, osrfMethodContext* ctx ) {
2642 if( node->type == JSON_ARRAY ) { // equality IN search
2643 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2644 } else if( node->type == JSON_HASH ) { // other search
2645 jsonIterator* pred_itr = jsonNewIterator( node );
2646 if( !jsonIteratorHasNext( pred_itr ) ) {
2647 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2648 modulename, osrfHashGet(field, "name" ));
2650 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2652 // Verify that there are no additional predicates
2653 if( jsonIteratorHasNext( pred_itr ) ) {
2654 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2655 modulename, osrfHashGet(field, "name" ));
2656 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2657 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2658 else if( !(strcasecmp( pred_itr->key,"in" ))
2659 || !(strcasecmp( pred_itr->key,"not in" )) )
2660 pred = searchINPredicate(
2661 class_info->alias, field, pred_node, pred_itr->key, ctx );
2662 else if( pred_node->type == JSON_ARRAY )
2663 pred = searchFunctionPredicate(
2664 class_info->alias, field, pred_node, pred_itr->key );
2665 else if( pred_node->type == JSON_HASH )
2666 pred = searchFieldTransformPredicate(
2667 class_info, field, pred_node, pred_itr->key );
2669 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2671 jsonIteratorFree( pred_itr );
2673 } else if( node->type == JSON_NULL ) { // IS NULL search
2674 growing_buffer* _p = buffer_init( 64 );
2677 "\"%s\".%s IS NULL",
2678 class_info->class_name,
2679 osrfHashGet( field, "name" )
2681 pred = buffer_release( _p );
2682 } else { // equality search
2683 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2702 field : call_number,
2718 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2720 const jsonObject* working_hash;
2721 jsonObject* freeable_hash = NULL;
2723 if( join_hash->type == JSON_HASH ) {
2724 working_hash = join_hash;
2725 } else if( join_hash->type == JSON_STRING ) {
2726 // turn it into a JSON_HASH by creating a wrapper
2727 // around a copy of the original
2728 const char* _tmp = jsonObjectGetString( join_hash );
2729 freeable_hash = jsonNewObjectType( JSON_HASH );
2730 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2731 working_hash = freeable_hash;
2735 "%s: JOIN failed; expected JSON object type not found",
2741 growing_buffer* join_buf = buffer_init( 128 );
2742 const char* leftclass = left_info->class_name;
2744 jsonObject* snode = NULL;
2745 jsonIterator* search_itr = jsonNewIterator( working_hash );
2747 while ( (snode = jsonIteratorNext( search_itr )) ) {
2748 const char* right_alias = search_itr->key;
2750 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2752 class = right_alias;
2754 const ClassInfo* right_info = add_joined_class( right_alias, class );
2758 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2762 jsonIteratorFree( search_itr );
2763 buffer_free( join_buf );
2765 jsonObjectFree( freeable_hash );
2768 osrfHash* links = right_info->links;
2769 const char* table = right_info->source_def;
2771 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2772 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2774 if( field && !fkey ) {
2775 // Look up the corresponding join column in the IDL.
2776 // The link must be defined in the child table,
2777 // and point to the right parent table.
2778 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2779 const char* reltype = NULL;
2780 const char* other_class = NULL;
2781 reltype = osrfHashGet( idl_link, "reltype" );
2782 if( reltype && strcmp( reltype, "has_many" ) )
2783 other_class = osrfHashGet( idl_link, "class" );
2784 if( other_class && !strcmp( other_class, leftclass ) )
2785 fkey = osrfHashGet( idl_link, "key" );
2789 "%s: JOIN failed. No link defined from %s.%s to %s",
2795 buffer_free( join_buf );
2797 jsonObjectFree( freeable_hash );
2798 jsonIteratorFree( search_itr );
2802 } else if( !field && fkey ) {
2803 // Look up the corresponding join column in the IDL.
2804 // The link must be defined in the child table,
2805 // and point to the right parent table.
2806 osrfHash* left_links = left_info->links;
2807 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2808 const char* reltype = NULL;
2809 const char* other_class = NULL;
2810 reltype = osrfHashGet( idl_link, "reltype" );
2811 if( reltype && strcmp( reltype, "has_many" ) )
2812 other_class = osrfHashGet( idl_link, "class" );
2813 if( other_class && !strcmp( other_class, class ) )
2814 field = osrfHashGet( idl_link, "key" );
2818 "%s: JOIN failed. No link defined from %s.%s to %s",
2824 buffer_free( join_buf );
2826 jsonObjectFree( freeable_hash );
2827 jsonIteratorFree( search_itr );
2831 } else if( !field && !fkey ) {
2832 osrfHash* left_links = left_info->links;
2834 // For each link defined for the left class:
2835 // see if the link references the joined class
2836 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2837 osrfHash* curr_link = NULL;
2838 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2839 const char* other_class = osrfHashGet( curr_link, "class" );
2840 if( other_class && !strcmp( other_class, class ) ) {
2842 // In the IDL, the parent class doesn't always know then names of the child
2843 // columns that are pointing to it, so don't use that end of the link
2844 const char* reltype = osrfHashGet( curr_link, "reltype" );
2845 if( reltype && strcmp( reltype, "has_many" ) ) {
2846 // Found a link between the classes
2847 fkey = osrfHashIteratorKey( itr );
2848 field = osrfHashGet( curr_link, "key" );
2853 osrfHashIteratorFree( itr );
2855 if( !field || !fkey ) {
2856 // Do another such search, with the classes reversed
2858 // For each link defined for the joined class:
2859 // see if the link references the left class
2860 osrfHashIterator* itr = osrfNewHashIterator( links );
2861 osrfHash* curr_link = NULL;
2862 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2863 const char* other_class = osrfHashGet( curr_link, "class" );
2864 if( other_class && !strcmp( other_class, leftclass ) ) {