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* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
126 static int child_initialized = 0; /* boolean */
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 // The following points to the top of a stack of QueryFrames. It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
140 static int max_flesh_depth = 100;
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0; // Boolean
144 static char* modulename = NULL;
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
148 static char* _sanitize_savepoint_name( const char* sp );
151 @brief Connect to the database.
152 @return A database connection if successful, or NULL if not.
154 dbi_conn oilsConnectDB( const char* mod_name ) {
156 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
157 if( dbi_initialize( NULL ) == -1 ) {
158 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
161 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
163 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
164 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
165 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
166 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
167 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
168 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
170 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
171 dbi_conn handle = dbi_conn_new( driver );
174 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
177 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
179 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
180 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
182 if( host ) dbi_conn_set_option( handle, "host", host );
183 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
184 if( user ) dbi_conn_set_option( handle, "username", user );
185 if( pw ) dbi_conn_set_option( handle, "password", pw );
186 if( db ) dbi_conn_set_option( handle, "dbname", db );
194 if( dbi_conn_connect( handle ) < 0 ) {
196 if( dbi_conn_connect( handle ) < 0 ) {
198 dbi_conn_error( handle, &msg );
199 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
200 msg ? msg : "(No description available)" );
205 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
211 @brief Select some options.
212 @param module_name: Name of the server.
213 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
215 This source file is used (at this writing) to implement three different servers:
216 - open-ils.reporter-store
220 These servers behave mostly the same, but they implement different combinations of
221 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
223 Here we use the server name in messages to identify which kind of server issued them.
224 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
226 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
228 module_name = "open-ils.cstore"; // bulletproofing with a default
233 modulename = strdup( module_name );
234 enforce_pcrud = do_pcrud;
235 max_flesh_depth = flesh_depth;
239 @brief Install a database connection.
240 @param conn Pointer to a database connection.
242 In some contexts, @a conn may merely provide a driver so that we can process strings
243 properly, without providing an open database connection.
245 void oilsSetDBConnection( dbi_conn conn ) {
246 dbhandle = writehandle = conn;
250 @brief Determine whether a database connection is alive.
251 @param handle Handle for a database connection.
252 @return 1 if the connection is alive, or zero if it isn't.
254 int oilsIsDBConnected( dbi_conn handle ) {
255 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
256 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
258 dbi_result_free( result );
261 // This is a terrible, horrible, no good, very bad kludge.
262 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
263 // but because (due to a previous error) the database is ignoring all commands,
264 // even innocuous SELECTs, until the current transaction is rolled back. The only
265 // known way to detect this condition via the dbi library is by looking at the error
266 // message. This approach will break if the language or wording of the message ever
268 // Note: the dbi_conn_ping function purports to determine whether the database
269 // connection is live, but at this writing this function is unreliable and useless.
270 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
271 "ignored until end of transaction block\n";
273 dbi_conn_error( handle, &msg );
274 if( strcmp( msg, ok_msg )) {
275 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
278 return 1; // ignoring SELECT due to previous error; that's okay
283 @brief Get a table name, view name, or subquery for use in a FROM clause.
284 @param class Pointer to the IDL class entry.
285 @return A table name, a view name, or a subquery in parentheses.
287 In some cases the IDL defines a class, not with a table name or a view name, but with
288 a SELECT statement, which may be used as a subquery.
290 char* oilsGetRelation( osrfHash* classdef ) {
292 char* source_def = NULL;
293 const char* tabledef = osrfHashGet( classdef, "tablename" );
296 source_def = strdup( tabledef ); // Return the name of a table or view
298 tabledef = osrfHashGet( classdef, "source_definition" );
300 // Return a subquery, enclosed in parentheses
301 source_def = safe_malloc( strlen( tabledef ) + 3 );
302 source_def[ 0 ] = '(';
303 strcpy( source_def + 1, tabledef );
304 strcat( source_def, ")" );
306 // Not found: return an error
307 const char* classname = osrfHashGet( classdef, "classname" );
312 "%s ERROR No tablename or source_definition for class \"%s\"",
323 @brief Add datatypes from the database to the fields in the IDL.
324 @param handle Handle for a database connection
325 @return Zero if successful, or 1 upon error.
327 For each relevant class in the IDL: ask the database for the datatype of every field.
328 In particular, determine which fields are text fields and which fields are numeric
329 fields, so that we know whether to enclose their values in quotes.
331 int oilsExtendIDL( dbi_conn handle ) {
332 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
333 osrfHash* class = NULL;
334 growing_buffer* query_buf = buffer_init( 64 );
335 int results_found = 0; // boolean
337 // For each class in the IDL...
338 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
339 const char* classname = osrfHashIteratorKey( class_itr );
340 osrfHash* fields = osrfHashGet( class, "fields" );
342 // If the class is virtual, ignore it
343 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
344 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
348 char* tabledef = oilsGetRelation( class );
350 continue; // No such relation -- a query of it would be doomed to failure
352 buffer_reset( query_buf );
353 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
357 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
358 modulename, OSRF_BUFFER_C_STR( query_buf ) );
360 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
365 const char* columnName;
366 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
368 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
371 /* fetch the fieldmapper index */
372 osrfHash* _f = osrfHashGet(fields, columnName);
375 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
377 /* determine the field type and storage attributes */
379 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
381 case DBI_TYPE_INTEGER : {
383 if( !osrfHashGet(_f, "primitive") )
384 osrfHashSet(_f, "number", "primitive");
386 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
387 if( attr & DBI_INTEGER_SIZE8 )
388 osrfHashSet( _f, "INT8", "datatype" );
390 osrfHashSet( _f, "INT", "datatype" );
393 case DBI_TYPE_DECIMAL :
394 if( !osrfHashGet( _f, "primitive" ))
395 osrfHashSet( _f, "number", "primitive" );
397 osrfHashSet( _f, "NUMERIC", "datatype" );
400 case DBI_TYPE_STRING :
401 if( !osrfHashGet( _f, "primitive" ))
402 osrfHashSet( _f, "string", "primitive" );
404 osrfHashSet( _f,"TEXT", "datatype" );
407 case DBI_TYPE_DATETIME :
408 if( !osrfHashGet( _f, "primitive" ))
409 osrfHashSet( _f, "string", "primitive" );
411 osrfHashSet( _f, "TIMESTAMP", "datatype" );
414 case DBI_TYPE_BINARY :
415 if( !osrfHashGet( _f, "primitive" ))
416 osrfHashSet( _f, "string", "primitive" );
418 osrfHashSet( _f, "BYTEA", "datatype" );
423 "Setting [%s] to primitive [%s] and datatype [%s]...",
425 osrfHashGet( _f, "primitive" ),
426 osrfHashGet( _f, "datatype" )
430 } // end while loop for traversing columns of result
431 dbi_result_free( result );
434 int errnum = dbi_conn_error( handle, &msg );
435 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
436 errnum, msg ? msg : "(No description available)" );
437 // We don't check the database connection here. It's routine to get failures at
438 // this point; we routinely try to query tables that don't exist, because they
439 // are defined in the IDL but not in the database.
441 } // end for each class in IDL
443 buffer_free( query_buf );
444 osrfHashIteratorFree( class_itr );
445 child_initialized = 1;
447 if( !results_found ) {
448 osrfLogError( OSRF_LOG_MARK,
449 "No results found for any class -- bad database connection?" );
451 } else if( ! oilsIsDBConnected( handle )) {
452 osrfLogError( OSRF_LOG_MARK,
453 "Unable to extend IDL: database connection isn't working" );
461 @brief Free an osrfHash that stores a transaction ID.
462 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
464 This function is a callback, to be called by the application session when it ends.
465 The application session stores the osrfHash via an opaque pointer.
467 If the osrfHash contains an entry for the key "xact_id", it means that an
468 uncommitted transaction is pending. Roll it back.
470 void userDataFree( void* blob ) {
471 osrfHash* hash = (osrfHash*) blob;
472 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
473 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
475 int errnum = dbi_conn_error( writehandle, &msg );
476 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
477 errnum, msg ? msg : "(No description available)" );
481 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
483 int errnum = dbi_conn_error( writehandle, &msg );
484 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
485 errnum, msg ? msg : "(No description available)" );
489 osrfHashFree( hash );
493 @name Managing session data
494 @brief Maintain data stored via the userData pointer of the application session.
496 Currently, session-level data is stored in an osrfHash. Other arrangements are
497 possible, and some would be more efficient. The application session calls a
498 callback function to free userData before terminating.
500 Currently, the only data we store at the session level is the transaction id. By this
501 means we can ensure that any pending transactions are rolled back before the application
507 @brief Free an item in the application session's userData.
508 @param key The name of a key for an osrfHash.
509 @param item An opaque pointer to the item associated with the key.
511 We store an osrfHash as userData with the application session, and arrange (by
512 installing userDataFree() as a different callback) for the session to free that
513 osrfHash before terminating.
515 This function is a callback for freeing items in the osrfHash. Currently we store
517 - Transaction id of a pending transaction; a character string. Key: "xact_id".
518 - Authkey; a character string. Key: "authkey".
519 - User object from the authentication server; a jsonObject. Key: "user_login".
521 If we ever store anything else in userData, we will need to revisit this function so
522 that it will free whatever else needs freeing.
524 static void sessionDataFree( char* key, void* item ) {
525 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
527 else if( !strcmp( key, "user_login" ) )
528 jsonObjectFree( (jsonObject*) item );
529 else if( !strcmp( key, "pcache" ) )
530 osrfHashFree( (osrfHash*) item );
533 static void pcacheFree( char* key, void* item ) {
534 osrfStringArrayFree( (osrfStringArray*) item );
538 @brief Initialize session cache.
539 @param ctx Pointer to the method context.
541 Create a cache for the session by making the session's userData member point
542 to an osrfHash instance.
544 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
545 ctx->session->userData = osrfNewHash();
546 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
547 ctx->session->userDataFree = &userDataFree;
548 return ctx->session->userData;
552 @brief Save a transaction id.
553 @param ctx Pointer to the method context.
555 Save the session_id of the current application session as a transaction id.
557 static void setXactId( osrfMethodContext* ctx ) {
558 if( ctx && ctx->session ) {
559 osrfAppSession* session = ctx->session;
561 osrfHash* cache = session->userData;
563 // If the session doesn't already have a hash, create one. Make sure
564 // that the application session frees the hash when it terminates.
566 cache = initSessionCache( ctx );
568 // Save the transaction id in the hash, with the key "xact_id"
569 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
574 @brief Get the transaction ID for the current transaction, if any.
575 @param ctx Pointer to the method context.
576 @return Pointer to the transaction ID.
578 The return value points to an internal buffer, and will become invalid upon issuing
579 a commit or rollback.
581 static inline const char* getXactId( osrfMethodContext* ctx ) {
582 if( ctx && ctx->session && ctx->session->userData )
583 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
589 @brief Clear the current transaction id.
590 @param ctx Pointer to the method context.
592 static inline void clearXactId( osrfMethodContext* ctx ) {
593 if( ctx && ctx->session && ctx->session->userData )
594 osrfHashRemove( ctx->session->userData, "xact_id" );
599 @brief Stash the location for a particular perm in the sessionData cache
600 @param ctx Pointer to the method context.
601 @param perm Name of the permission we're looking at
602 @param array StringArray of perm location ids
604 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
605 if( ctx && ctx->session ) {
606 osrfAppSession* session = ctx->session;
608 osrfHash* cache = session->userData;
610 // If the session doesn't already have a hash, create one. Make sure
611 // that the application session frees the hash when it terminates.
613 cache = initSessionCache( ctx );
615 osrfHash* pcache = osrfHashGet(cache, "pcache");
617 if( NULL == pcache ) {
618 pcache = osrfNewHash();
619 osrfHashSetCallback( pcache, &pcacheFree );
620 osrfHashSet( cache, pcache, "pcache" );
623 if( perm && locations )
624 osrfHashSet( pcache, locations, strdup(perm) );
629 @brief Grab stashed location for a particular perm in the sessionData cache
630 @param ctx Pointer to the method context.
631 @param perm Name of the permission we're looking at
633 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
634 if( ctx && ctx->session ) {
635 osrfAppSession* session = ctx->session;
636 osrfHash* cache = session->userData;
638 osrfHash* pcache = osrfHashGet(cache, "pcache");
640 return osrfHashGet( pcache, perm );
649 @brief Save the user's login in the userData for the current application session.
650 @param ctx Pointer to the method context.
651 @param user_login Pointer to the user login object to be cached (we cache the original,
654 If @a user_login is NULL, remove the user login if one is already cached.
656 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
657 if( ctx && ctx->session ) {
658 osrfAppSession* session = ctx->session;
660 osrfHash* cache = session->userData;
662 // If the session doesn't already have a hash, create one. Make sure
663 // that the application session frees the hash when it terminates.
665 cache = initSessionCache( ctx );
668 osrfHashSet( cache, user_login, "user_login" );
670 osrfHashRemove( cache, "user_login" );
675 @brief Get the user login object for the current application session, if any.
676 @param ctx Pointer to the method context.
677 @return Pointer to the user login object if found; otherwise NULL.
679 The user login object was returned from the authentication server, and then cached so
680 we don't have to call the authentication server again for the same user.
682 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
683 if( ctx && ctx->session && ctx->session->userData )
684 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
690 @brief Save a copy of an authkey in the userData of the current application session.
691 @param ctx Pointer to the method context.
692 @param authkey The authkey to be saved.
694 If @a authkey is NULL, remove the authkey if one is already cached.
696 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
697 if( ctx && ctx->session && authkey ) {
698 osrfAppSession* session = ctx->session;
699 osrfHash* cache = session->userData;
701 // If the session doesn't already have a hash, create one. Make sure
702 // that the application session frees the hash when it terminates.
704 cache = initSessionCache( ctx );
706 // Save the transaction id in the hash, with the key "xact_id"
707 if( authkey && *authkey )
708 osrfHashSet( cache, strdup( authkey ), "authkey" );
710 osrfHashRemove( cache, "authkey" );
715 @brief Reset the login timeout.
716 @param authkey The authentication key for the current login session.
717 @param now The current time.
718 @return Zero if successful, or 1 if not.
720 Tell the authentication server to reset the timeout so that the login session won't
721 expire for a while longer.
723 We could dispense with the @a now parameter by calling time(). But we just called
724 time() in order to decide whether to reset the timeout, so we might as well reuse
725 the result instead of calling time() again.
727 static int reset_timeout( const char* authkey, time_t now ) {
728 jsonObject* auth_object = jsonNewObject( authkey );
730 // Ask the authentication server to reset the timeout. It returns an event
731 // indicating success or failure.
732 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
733 "open-ils.auth.session.reset_timeout", auth_object );
734 jsonObjectFree( auth_object );
736 if( !result || result->type != JSON_HASH ) {
737 osrfLogError( OSRF_LOG_MARK,
738 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
739 jsonObjectFree( result );
740 return 1; // Not the right sort of object returned
743 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
744 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
745 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
746 jsonObjectFree( result );
747 return 1; // Return code from method not available
750 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
751 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
753 desc = "(No reason available)"; // failsafe; shouldn't happen
754 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
755 jsonObjectFree( result );
759 // Revise our local proxy for the timeout deadline
760 // by a smallish fraction of the timeout interval
761 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
763 timeout = "1"; // failsafe; shouldn't happen
764 time_next_reset = now + atoi( timeout ) / 15;
766 jsonObjectFree( result );
767 return 0; // Successfully reset timeout
771 @brief Get the authkey string for the current application session, if any.
772 @param ctx Pointer to the method context.
773 @return Pointer to the cached authkey if found; otherwise NULL.
775 If present, the authkey string was cached from a previous method call.
777 static const char* getAuthkey( osrfMethodContext* ctx ) {
778 if( ctx && ctx->session && ctx->session->userData ) {
779 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
780 // LFW recent changes mean the userData hash gets set up earlier, but
781 // doesn't necessarily have an authkey yet
785 // Possibly reset the authentication timeout to keep the login alive. We do so
786 // no more than once per method call, and not at all if it has been only a short
787 // time since the last reset.
789 // Here we reset explicitly, if at all. We also implicitly reset the timeout
790 // whenever we call the "open-ils.auth.session.retrieve" method.
791 if( timeout_needs_resetting ) {
792 time_t now = time( NULL );
793 if( now >= time_next_reset && reset_timeout( authkey, now ) )
794 authkey = NULL; // timeout has apparently expired already
797 timeout_needs_resetting = 0;
805 @brief Implement the transaction.begin method.
806 @param ctx Pointer to the method context.
807 @return Zero if successful, or -1 upon error.
809 Start a transaction. Save a transaction ID for future reference.
812 - authkey (PCRUD only)
814 Return to client: Transaction ID
816 int beginTransaction( osrfMethodContext* ctx ) {
817 if(osrfMethodVerifyContext( ctx )) {
818 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
822 if( enforce_pcrud ) {
823 timeout_needs_resetting = 1;
824 const jsonObject* user = verifyUserPCRUD( ctx );
829 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
832 int errnum = dbi_conn_error( writehandle, &msg );
833 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
834 modulename, errnum, msg ? msg : "(No description available)" );
835 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
836 "osrfMethodException", ctx->request, "Error starting transaction" );
837 if( !oilsIsDBConnected( writehandle ))
838 osrfAppSessionPanic( ctx->session );
841 dbi_result_free( result );
843 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
844 osrfAppRespondComplete( ctx, ret );
845 jsonObjectFree( ret );
851 @brief Implement the savepoint.set method.
852 @param ctx Pointer to the method context.
853 @return Zero if successful, or -1 if not.
855 Issue a SAVEPOINT to the database server.
858 - authkey (PCRUD only)
861 Return to client: Savepoint name
863 int setSavepoint( osrfMethodContext* ctx ) {
864 if(osrfMethodVerifyContext( ctx )) {
865 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
870 if( enforce_pcrud ) {
872 timeout_needs_resetting = 1;
873 const jsonObject* user = verifyUserPCRUD( ctx );
878 // Verify that a transaction is pending
879 const char* trans_id = getXactId( ctx );
880 if( NULL == trans_id ) {
881 osrfAppSessionStatus(
883 OSRF_STATUS_INTERNALSERVERERROR,
884 "osrfMethodException",
886 "No active transaction -- required for savepoints"
891 // Get the savepoint name from the method params
892 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
895 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
899 char *safeSpName = _sanitize_savepoint_name( spName );
901 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
905 int errnum = dbi_conn_error( writehandle, &msg );
908 "%s: Error creating savepoint %s in transaction %s: %d %s",
913 msg ? msg : "(No description available)"
915 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
916 "osrfMethodException", ctx->request, "Error creating savepoint" );
917 if( !oilsIsDBConnected( writehandle ))
918 osrfAppSessionPanic( ctx->session );
921 dbi_result_free( result );
922 jsonObject* ret = jsonNewObject( spName );
923 osrfAppRespondComplete( ctx, ret );
924 jsonObjectFree( ret );
930 @brief Implement the savepoint.release method.
931 @param ctx Pointer to the method context.
932 @return Zero if successful, or -1 if not.
934 Issue a RELEASE SAVEPOINT to the database server.
937 - authkey (PCRUD only)
940 Return to client: Savepoint name
942 int releaseSavepoint( osrfMethodContext* ctx ) {
943 if(osrfMethodVerifyContext( ctx )) {
944 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
949 if( enforce_pcrud ) {
951 timeout_needs_resetting = 1;
952 const jsonObject* user = verifyUserPCRUD( ctx );
957 // Verify that a transaction is pending
958 const char* trans_id = getXactId( ctx );
959 if( NULL == trans_id ) {
960 osrfAppSessionStatus(
962 OSRF_STATUS_INTERNALSERVERERROR,
963 "osrfMethodException",
965 "No active transaction -- required for savepoints"
970 // Get the savepoint name from the method params
971 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
974 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
978 char *safeSpName = _sanitize_savepoint_name( spName );
980 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
984 int errnum = dbi_conn_error( writehandle, &msg );
987 "%s: Error releasing savepoint %s in transaction %s: %d %s",
992 msg ? msg : "(No description available)"
994 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
995 "osrfMethodException", ctx->request, "Error releasing savepoint" );
996 if( !oilsIsDBConnected( writehandle ))
997 osrfAppSessionPanic( ctx->session );
1000 dbi_result_free( result );
1001 jsonObject* ret = jsonNewObject( spName );
1002 osrfAppRespondComplete( ctx, ret );
1003 jsonObjectFree( ret );
1009 @brief Implement the savepoint.rollback method.
1010 @param ctx Pointer to the method context.
1011 @return Zero if successful, or -1 if not.
1013 Issue a ROLLBACK TO SAVEPOINT to the database server.
1016 - authkey (PCRUD only)
1019 Return to client: Savepoint name
1021 int rollbackSavepoint( osrfMethodContext* ctx ) {
1022 if(osrfMethodVerifyContext( ctx )) {
1023 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1028 if( enforce_pcrud ) {
1030 timeout_needs_resetting = 1;
1031 const jsonObject* user = verifyUserPCRUD( ctx );
1036 // Verify that a transaction is pending
1037 const char* trans_id = getXactId( ctx );
1038 if( NULL == trans_id ) {
1039 osrfAppSessionStatus(
1041 OSRF_STATUS_INTERNALSERVERERROR,
1042 "osrfMethodException",
1044 "No active transaction -- required for savepoints"
1049 // Get the savepoint name from the method params
1050 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1053 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1057 char *safeSpName = _sanitize_savepoint_name( spName );
1059 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1063 int errnum = dbi_conn_error( writehandle, &msg );
1066 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1071 msg ? msg : "(No description available)"
1073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1075 if( !oilsIsDBConnected( writehandle ))
1076 osrfAppSessionPanic( ctx->session );
1079 dbi_result_free( result );
1080 jsonObject* ret = jsonNewObject( spName );
1081 osrfAppRespondComplete( ctx, ret );
1082 jsonObjectFree( ret );
1088 @brief Implement the transaction.commit method.
1089 @param ctx Pointer to the method context.
1090 @return Zero if successful, or -1 if not.
1092 Issue a COMMIT to the database server.
1095 - authkey (PCRUD only)
1097 Return to client: Transaction ID.
1099 int commitTransaction( osrfMethodContext* ctx ) {
1100 if(osrfMethodVerifyContext( ctx )) {
1101 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1105 if( enforce_pcrud ) {
1106 timeout_needs_resetting = 1;
1107 const jsonObject* user = verifyUserPCRUD( ctx );
1112 // Verify that a transaction is pending
1113 const char* trans_id = getXactId( ctx );
1114 if( NULL == trans_id ) {
1115 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1116 "osrfMethodException", ctx->request, "No active transaction to commit" );
1120 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1123 int errnum = dbi_conn_error( writehandle, &msg );
1124 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1125 modulename, errnum, msg ? msg : "(No description available)" );
1126 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1127 "osrfMethodException", ctx->request, "Error committing transaction" );
1128 if( !oilsIsDBConnected( writehandle ))
1129 osrfAppSessionPanic( ctx->session );
1132 dbi_result_free( result );
1133 jsonObject* ret = jsonNewObject( trans_id );
1134 osrfAppRespondComplete( ctx, ret );
1135 jsonObjectFree( ret );
1142 @brief Implement the transaction.rollback method.
1143 @param ctx Pointer to the method context.
1144 @return Zero if successful, or -1 if not.
1146 Issue a ROLLBACK to the database server.
1149 - authkey (PCRUD only)
1151 Return to client: Transaction ID
1153 int rollbackTransaction( osrfMethodContext* ctx ) {
1154 if( osrfMethodVerifyContext( ctx )) {
1155 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1159 if( enforce_pcrud ) {
1160 timeout_needs_resetting = 1;
1161 const jsonObject* user = verifyUserPCRUD( ctx );
1166 // Verify that a transaction is pending
1167 const char* trans_id = getXactId( ctx );
1168 if( NULL == trans_id ) {
1169 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1174 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1177 int errnum = dbi_conn_error( writehandle, &msg );
1178 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1179 modulename, errnum, msg ? msg : "(No description available)" );
1180 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1181 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1182 if( !oilsIsDBConnected( writehandle ))
1183 osrfAppSessionPanic( ctx->session );
1186 dbi_result_free( result );
1187 jsonObject* ret = jsonNewObject( trans_id );
1188 osrfAppRespondComplete( ctx, ret );
1189 jsonObjectFree( ret );
1196 @brief Implement the "search" method.
1197 @param ctx Pointer to the method context.
1198 @return Zero if successful, or -1 if not.
1201 - authkey (PCRUD only)
1202 - WHERE clause, as jsonObject
1203 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1205 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1206 Optionally flesh linked fields.
1208 int doSearch( osrfMethodContext* ctx ) {
1209 if( osrfMethodVerifyContext( ctx )) {
1210 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1215 timeout_needs_resetting = 1;
1217 jsonObject* where_clause;
1218 jsonObject* rest_of_query;
1220 if( enforce_pcrud ) {
1221 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1222 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1224 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1225 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1228 if( !where_clause ) {
1229 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1233 // Get the class metadata
1234 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1235 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1239 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1241 osrfAppRespondComplete( ctx, NULL );
1245 // doFieldmapperSearch() now takes care of our responding for us
1246 // // Return each row to the client
1247 // jsonObject* cur = 0;
1248 // unsigned long res_idx = 0;
1250 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1251 // // We used to discard based on perms here, but now that's
1252 // // inside doFieldmapperSearch()
1253 // osrfAppRespond( ctx, cur );
1256 jsonObjectFree( obj );
1258 osrfAppRespondComplete( ctx, NULL );
1263 @brief Implement the "id_list" method.
1264 @param ctx Pointer to the method context.
1265 @param err Pointer through which to return an error code.
1266 @return Zero if successful, or -1 if not.
1269 - authkey (PCRUD only)
1270 - WHERE clause, as jsonObject
1271 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1273 Return to client: The primary key values for all rows of the relevant class that
1274 satisfy a specified WHERE clause.
1276 This method relies on the assumption that every class has a primary key consisting of
1279 int doIdList( osrfMethodContext* ctx ) {
1280 if( osrfMethodVerifyContext( ctx )) {
1281 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1286 timeout_needs_resetting = 1;
1288 jsonObject* where_clause;
1289 jsonObject* rest_of_query;
1291 // We use the where clause without change. But we need to massage the rest of the
1292 // query, so we work with a copy of it instead of modifying the original.
1294 if( enforce_pcrud ) {
1295 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1296 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1298 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1299 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1302 if( !where_clause ) {
1303 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1307 // Eliminate certain SQL clauses, if present.
1308 if( rest_of_query ) {
1309 jsonObjectRemoveKey( rest_of_query, "select" );
1310 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1311 jsonObjectRemoveKey( rest_of_query, "flesh" );
1312 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1314 rest_of_query = jsonNewObjectType( JSON_HASH );
1317 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1319 // Get the class metadata
1320 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1321 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1323 // Build a SELECT list containing just the primary key,
1324 // i.e. like { "classname":["keyname"] }
1325 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1327 // Load array with name of primary key
1328 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1329 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1330 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1332 jsonObjectSetKey( rest_of_query, "select", select_clause );
1337 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1339 jsonObjectFree( rest_of_query );
1341 osrfAppRespondComplete( ctx, NULL );
1345 // Return each primary key value to the client
1347 unsigned long res_idx = 0;
1348 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1349 // We used to discard based on perms here, but now that's
1350 // inside doFieldmapperSearch()
1351 osrfAppRespond( ctx,
1352 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1355 jsonObjectFree( obj );
1356 osrfAppRespondComplete( ctx, NULL );
1361 @brief Verify that we have a valid class reference.
1362 @param ctx Pointer to the method context.
1363 @param param Pointer to the method parameters.
1364 @return 1 if the class reference is valid, or zero if it isn't.
1366 The class of the method params must match the class to which the method id devoted.
1367 For PCRUD there are additional restrictions.
1369 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1371 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1372 osrfHash* class = osrfHashGet( method_meta, "class" );
1374 // Compare the method's class to the parameters' class
1375 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1377 // Oops -- they don't match. Complain.
1378 growing_buffer* msg = buffer_init( 128 );
1381 "%s: %s method for type %s was passed a %s",
1383 osrfHashGet( method_meta, "methodtype" ),
1384 osrfHashGet( class, "classname" ),
1385 param->classname ? param->classname : "(null)"
1388 char* m = buffer_release( msg );
1389 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1397 return verifyObjectPCRUD( ctx, class, param, 1 );
1403 @brief (PCRUD only) Verify that the user is properly logged in.
1404 @param ctx Pointer to the method context.
1405 @return If the user is logged in, a pointer to the user object from the authentication
1406 server; otherwise NULL.
1408 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1410 // Get the authkey (the first method parameter)
1411 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1413 // See if we have the same authkey, and a user object,
1414 // locally cached from a previous call
1415 const char* cached_authkey = getAuthkey( ctx );
1416 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1417 const jsonObject* cached_user = getUserLogin( ctx );
1422 // We have no matching authentication data in the cache. Authenticate from scratch.
1423 jsonObject* auth_object = jsonNewObject( auth );
1425 // Fetch the user object from the authentication server
1426 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1428 jsonObjectFree( auth_object );
1430 if( !user->classname || strcmp(user->classname, "au" )) {
1432 growing_buffer* msg = buffer_init( 128 );
1435 "%s: permacrud received a bad auth token: %s",
1440 char* m = buffer_release( msg );
1441 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1445 jsonObjectFree( user );
1447 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1448 // Failed to set audit information - But note that write_audit_info already set error information.
1449 jsonObjectFree( user );
1453 setUserLogin( ctx, user );
1454 setAuthkey( ctx, auth );
1456 // Allow ourselves up to a second before we have to reset the login timeout.
1457 // It would be nice to use some fraction of the timeout interval enforced by the
1458 // authentication server, but that value is not readily available at this point.
1459 // Instead, we use a conservative default interval.
1460 time_next_reset = time( NULL ) + 1;
1466 @brief For PCRUD: Determine whether the current user may access the current row.
1467 @param ctx Pointer to the method context.
1468 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1469 @param obj Pointer to the row being potentially accessed.
1470 @return 1 if access is permitted, or 0 if it isn't.
1472 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1474 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1476 dbhandle = writehandle;
1478 // Figure out what class and method are involved
1479 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1480 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1483 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1484 if (rs_size_from_hash) {
1485 rs_size = *rs_size_from_hash;
1486 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1490 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1491 // contexts we will do another lookup of the current row, even if we already have a
1492 // previously fetched row image, because the row image in hand may not include the
1493 // foreign key(s) that we need.
1495 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1496 // but they aren't implemented yet.
1499 if( *method_type == 's' || *method_type == 'i' ) {
1500 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1502 } else if( *method_type == 'u' || *method_type == 'd' ) {
1503 fetch = 1; // MUST go to the db for the object for update and delete
1506 // Get the appropriate permacrud entry from the IDL, depending on method type
1507 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1509 // No permacrud for this method type on this class
1511 growing_buffer* msg = buffer_init( 128 );
1514 "%s: %s on class %s has no permacrud IDL entry",
1516 osrfHashGet( method_metadata, "methodtype" ),
1517 osrfHashGet( class, "classname" )
1520 char* m = buffer_release( msg );
1521 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1522 "osrfMethodException", ctx->request, m );
1529 // Get the user id, and make sure the user is logged in
1530 const jsonObject* user = verifyUserPCRUD( ctx );
1532 return 0; // Not logged in? No access.
1534 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1536 // Get a list of permissions from the permacrud entry.
1537 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1538 if( permission->size == 0 ) {
1541 "No permissions required for this action (class %s), passing through",
1542 osrfHashGet(class, "classname")
1547 // Build a list of org units that own the row. This is fairly convoluted because there
1548 // are several different ways that an org unit may own the row, as defined by the
1551 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1552 // identifying an owning org_unit..
1553 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1555 // Foreign context adds a layer of indirection. The row points to some other row that
1556 // an org unit may own. The "jump" attribute, if present, adds another layer of
1558 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1560 // The following string array stores the list of org units. (We don't have a thingie
1561 // for storing lists of integers, so we fake it with a list of strings.)
1562 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1565 const char* pkey_value = NULL;
1566 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1567 // If the global_required attribute is present and true, then the only owning
1568 // org unit is the root org unit, i.e. the one with no parent.
1569 osrfLogDebug( OSRF_LOG_MARK,
1570 "global-level permissions required, fetching top of the org tree" );
1572 // no need to check perms for org tree root retrieval
1573 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1574 // check for perm at top of org tree
1575 const char* org_tree_root_id = org_tree_root( ctx );
1576 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1578 if( org_tree_root_id ) {
1579 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1580 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1582 osrfStringArrayFree( context_org_array );
1587 // If the global_required attribute is absent or false, then we look for
1588 // local and/or foreign context. In order to find the relevant foreign
1589 // keys, we must either read the relevant row from the database, or look at
1590 // the image of the row that we already have in memory.
1592 // Even if we have an image of the row in memory, that image may not include the
1593 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1594 // of the row to make sure that we have what we need.
1596 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1597 "fetching context org ids" );
1598 const char* pkey = osrfHashGet( class, "primarykey" );
1599 jsonObject *param = NULL;
1602 // There is no primary key, so we can't do a fresh lookup. Use the row
1603 // image that we already have. If it doesn't have everything we need, too bad.
1605 param = jsonObjectClone( obj );
1606 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1607 } else if( obj->classname ) {
1608 pkey_value = oilsFMGetStringConst( obj, pkey );
1610 param = jsonObjectClone( obj );
1611 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1614 pkey_value = jsonObjectGetString( obj );
1616 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1617 "of %s and retrieving from the database", pkey_value );
1621 // Fetch the row so that we can look at the foreign key(s)
1622 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1623 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1624 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1625 jsonObjectFree( _tmp_params );
1626 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1628 param = jsonObjectExtractIndex( _list, 0 );
1629 jsonObjectFree( _list );
1633 // The row doesn't exist. Complain, and deny access.
1634 osrfLogDebug( OSRF_LOG_MARK,
1635 "Object not found in the database with primary key %s of %s",
1638 growing_buffer* msg = buffer_init( 128 );
1641 "%s: no object found with primary key %s of %s",
1647 char* m = buffer_release( msg );
1648 osrfAppSessionStatus(
1650 OSRF_STATUS_INTERNALSERVERERROR,
1651 "osrfMethodException",
1660 if( local_context && local_context->size > 0 ) {
1661 // The IDL provides a list of column names for the foreign keys denoting
1662 // local context, i.e. columns identifying owing org units directly. Look up
1663 // the value of each one, and if it isn't null, add it to the list of org units.
1664 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1665 local_context->size );
1667 const char* lcontext = NULL;
1668 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1669 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1670 if( fkey_value ) { // if not null
1671 osrfStringArrayAdd( context_org_array, fkey_value );
1674 "adding class-local field %s (value: %s) to the context org list",
1676 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1682 if( foreign_context ) {
1683 unsigned long class_count = osrfHashGetCount( foreign_context );
1684 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1686 if( class_count > 0 ) {
1688 // The IDL provides a list of foreign key columns pointing to rows that
1689 // an org unit may own. Follow each link, identify the owning org unit,
1690 // and add it to the list.
1691 osrfHash* fcontext = NULL;
1692 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1693 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1694 // For each class to which a foreign key points:
1695 const char* class_name = osrfHashIteratorKey( class_itr );
1696 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1700 "%d foreign context fields(s) specified for class %s",
1701 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1705 // Get the name of the key field in the foreign table
1706 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1708 // Get the value of the foreign key pointing to the foreign table
1709 char* foreign_pkey_value =
1710 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1711 if( !foreign_pkey_value )
1712 continue; // Foreign key value is null; skip it
1714 // Look up the row to which the foreign key points
1715 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1717 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1718 jsonObject* _list = doFieldmapperSearch(
1719 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1720 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1722 jsonObject* _fparam = NULL;
1723 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1724 _fparam = jsonObjectExtractIndex( _list, 0 );
1726 jsonObjectFree( _tmp_params );
1727 jsonObjectFree( _list );
1729 // At this point _fparam either points to the row identified by the
1730 // foreign key, or it's NULL (no such row found).
1732 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1734 const char* bad_class = NULL; // For noting failed lookups
1736 bad_class = class_name; // Referenced row not found
1737 else if( jump_list ) {
1738 // Follow a chain of rows, linked by foreign keys, to find an owner
1739 const char* flink = NULL;
1741 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1742 // For each entry in the jump list. Each entry (i.e. flink) is
1743 // the name of a foreign key column in the current row.
1745 // From the IDL, get the linkage information for the next jump
1746 osrfHash* foreign_link_hash =
1747 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1749 // Get the class metadata for the class
1750 // to which the foreign key points
1751 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1752 osrfHashGet( foreign_link_hash, "class" ));
1754 // Get the name of the referenced key of that class
1755 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1757 // Get the value of the foreign key pointing to that class
1758 free( foreign_pkey_value );
1759 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1760 if( !foreign_pkey_value )
1761 break; // Foreign key is null; quit looking
1763 // Build a WHERE clause for the lookup
1764 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1767 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1768 _tmp_params, NULL, &err );
1770 // Get the resulting row
1771 jsonObjectFree( _fparam );
1772 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1773 _fparam = jsonObjectExtractIndex( _list, 0 );
1775 // Referenced row not found
1777 bad_class = osrfHashGet( foreign_link_hash, "class" );
1780 jsonObjectFree( _tmp_params );
1781 jsonObjectFree( _list );
1787 // We had a foreign key pointing to such-and-such a row, but then
1788 // we couldn't fetch that row. The data in the database are in an
1789 // inconsistent state; the database itself may even be corrupted.
1790 growing_buffer* msg = buffer_init( 128 );
1793 "%s: no object of class %s found with primary key %s of %s",
1797 foreign_pkey_value ? foreign_pkey_value : "(null)"
1800 char* m = buffer_release( msg );
1801 osrfAppSessionStatus(
1803 OSRF_STATUS_INTERNALSERVERERROR,
1804 "osrfMethodException",
1810 osrfHashIteratorFree( class_itr );
1811 free( foreign_pkey_value );
1812 jsonObjectFree( param );
1817 free( foreign_pkey_value );
1820 // Examine each context column of the foreign row,
1821 // and add its value to the list of org units.
1823 const char* foreign_field = NULL;
1824 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1825 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1826 osrfStringArrayAdd( context_org_array,
1827 oilsFMGetStringConst( _fparam, foreign_field ));
1828 osrfLogDebug( OSRF_LOG_MARK,
1829 "adding foreign class %s field %s (value: %s) "
1830 "to the context org list",
1833 osrfStringArrayGetString(
1834 context_org_array, context_org_array->size - 1 )
1838 jsonObjectFree( _fparam );
1842 osrfHashIteratorFree( class_itr );
1846 jsonObjectFree( param );
1849 const char* context_org = NULL;
1850 const char* perm = NULL;
1853 // For every combination of permission and context org unit: call a stored procedure
1854 // to determine if the user has this permission in the context of this org unit.
1855 // If the answer is yes at any point, then we're done, and the user has permission.
1856 // In other words permissions are additive.
1858 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1861 osrfStringArray* pcache = NULL;
1862 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1863 pcache = getPermLocationCache(ctx, perm);
1866 pcache = osrfNewStringArray(0);
1868 result = dbi_conn_queryf(
1870 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1878 "Received a result for permission [%s] for user %d",
1883 if( dbi_result_first_row( result )) {
1885 jsonObject* return_val = oilsMakeJSONFromResult( result );
1886 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1887 jsonObjectFree( return_val );
1888 } while( dbi_result_next_row( result ));
1890 setPermLocationCache(ctx, perm, pcache);
1893 dbi_result_free( result );
1899 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1901 if (rs_size > perm_at_threshold) {
1902 if (osrfStringArrayContains( pcache, context_org )) {
1911 "Checking object permission [%s] for user %d "
1912 "on object %s (class %s) at org %d",
1916 osrfHashGet( class, "classname" ),
1920 result = dbi_conn_queryf(
1922 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1925 osrfHashGet( class, "classname" ),
1933 "Received a result for object permission [%s] "
1934 "for user %d on object %s (class %s) at org %d",
1938 osrfHashGet( class, "classname" ),
1942 if( dbi_result_first_row( result )) {
1943 jsonObject* return_val = oilsMakeJSONFromResult( result );
1944 const char* has_perm = jsonObjectGetString(
1945 jsonObjectGetKeyConst( return_val, "has_perm" ));
1949 "Status of object permission [%s] for user %d "
1950 "on object %s (class %s) at org %d is %s",
1954 osrfHashGet(class, "classname"),
1959 if( *has_perm == 't' )
1961 jsonObjectFree( return_val );
1964 dbi_result_free( result );
1969 int errnum = dbi_conn_error( writehandle, &msg );
1970 osrfLogWarning( OSRF_LOG_MARK,
1971 "Unable to call check object permissions: %d, %s",
1972 errnum, msg ? msg : "(No description available)" );
1973 if( !oilsIsDBConnected( writehandle ))
1974 osrfAppSessionPanic( ctx->session );
1978 if (rs_size > perm_at_threshold) break;
1980 osrfLogDebug( OSRF_LOG_MARK,
1981 "Checking non-object permission [%s] for user %d at org %d",
1982 perm, userid, atoi(context_org) );
1983 result = dbi_conn_queryf(
1985 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1992 osrfLogDebug( OSRF_LOG_MARK,
1993 "Received a result for permission [%s] for user %d at org %d",
1994 perm, userid, atoi( context_org ));
1995 if( dbi_result_first_row( result )) {
1996 jsonObject* return_val = oilsMakeJSONFromResult( result );
1997 const char* has_perm = jsonObjectGetString(
1998 jsonObjectGetKeyConst( return_val, "has_perm" ));
1999 osrfLogDebug( OSRF_LOG_MARK,
2000 "Status of permission [%s] for user %d at org %d is [%s]",
2001 perm, userid, atoi( context_org ), has_perm );
2002 if( *has_perm == 't' )
2004 jsonObjectFree( return_val );
2007 dbi_result_free( result );
2012 int errnum = dbi_conn_error( writehandle, &msg );
2013 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2014 errnum, msg ? msg : "(No description available)" );
2015 if( !oilsIsDBConnected( writehandle ))
2016 osrfAppSessionPanic( ctx->session );
2025 osrfStringArrayFree( context_org_array );
2031 @brief Look up the root of the org_unit tree.
2032 @param ctx Pointer to the method context.
2033 @return The id of the root org unit, as a character string.
2035 Query actor.org_unit where parent_ou is null, and return the id as a string.
2037 This function assumes that there is only one root org unit, i.e. that we
2038 have a single tree, not a forest.
2040 The calling code is responsible for freeing the returned string.
2042 static const char* org_tree_root( osrfMethodContext* ctx ) {
2044 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2045 static time_t last_lookup_time = 0;
2046 time_t current_time = time( NULL );
2048 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2049 // We successfully looked this up less than an hour ago.
2050 // It's not likely to have changed since then.
2051 return strdup( cached_root_id );
2053 last_lookup_time = current_time;
2056 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2057 jsonObject* result = doFieldmapperSearch(
2058 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2059 jsonObjectFree( where_clause );
2061 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2064 jsonObjectFree( result );
2066 growing_buffer* msg = buffer_init( 128 );
2067 OSRF_BUFFER_ADD( msg, modulename );
2068 OSRF_BUFFER_ADD( msg,
2069 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2071 char* m = buffer_release( msg );
2072 osrfAppSessionStatus( ctx->session,
2073 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2076 cached_root_id[ 0 ] = '\0';
2080 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2081 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2083 strcpy( cached_root_id, root_org_unit_id );
2084 jsonObjectFree( result );
2085 return cached_root_id;
2089 @brief Create a JSON_HASH with a single key/value pair.
2090 @param key The key of the key/value pair.
2091 @param value the value of the key/value pair.
2092 @return Pointer to a newly created jsonObject of type JSON_HASH.
2094 The value of the key/value is either a string or (if @a value is NULL) a null.
2096 static jsonObject* single_hash( const char* key, const char* value ) {
2098 if( ! key ) key = "";
2100 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2101 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2106 int doCreate( osrfMethodContext* ctx ) {
2107 if(osrfMethodVerifyContext( ctx )) {
2108 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2113 timeout_needs_resetting = 1;
2115 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2116 jsonObject* target = NULL;
2117 jsonObject* options = NULL;
2119 if( enforce_pcrud ) {
2120 target = jsonObjectGetIndex( ctx->params, 1 );
2121 options = jsonObjectGetIndex( ctx->params, 2 );
2123 target = jsonObjectGetIndex( ctx->params, 0 );
2124 options = jsonObjectGetIndex( ctx->params, 1 );
2127 if( !verifyObjectClass( ctx, target )) {
2128 osrfAppRespondComplete( ctx, NULL );
2132 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2134 const char* trans_id = getXactId( ctx );
2136 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2138 osrfAppSessionStatus(
2140 OSRF_STATUS_BADREQUEST,
2141 "osrfMethodException",
2143 "No active transaction -- required for CREATE"
2145 osrfAppRespondComplete( ctx, NULL );
2149 // The following test is harmless but redundant. If a class is
2150 // readonly, we don't register a create method for it.
2151 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2152 osrfAppSessionStatus(
2154 OSRF_STATUS_BADREQUEST,
2155 "osrfMethodException",
2157 "Cannot INSERT readonly class"
2159 osrfAppRespondComplete( ctx, NULL );
2163 // Set the last_xact_id
2164 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2166 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2167 trans_id, target->classname, index);
2168 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2171 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2173 dbhandle = writehandle;
2175 osrfHash* fields = osrfHashGet( meta, "fields" );
2176 char* pkey = osrfHashGet( meta, "primarykey" );
2177 char* seq = osrfHashGet( meta, "sequence" );
2179 growing_buffer* table_buf = buffer_init( 128 );
2180 growing_buffer* col_buf = buffer_init( 128 );
2181 growing_buffer* val_buf = buffer_init( 128 );
2183 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2184 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2185 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2186 buffer_add( val_buf,"VALUES (" );
2190 osrfHash* field = NULL;
2191 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2192 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2194 const char* field_name = osrfHashIteratorKey( field_itr );
2196 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2199 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2202 if( field_object && field_object->classname ) {
2203 value = oilsFMGetString(
2205 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2207 } else if( field_object && JSON_BOOL == field_object->type ) {
2208 if( jsonBoolIsTrue( field_object ) )
2209 value = strdup( "t" );
2211 value = strdup( "f" );
2213 value = jsonObjectToSimpleString( field_object );
2219 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2220 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2223 buffer_add( col_buf, field_name );
2225 if( !field_object || field_object->type == JSON_NULL ) {
2226 buffer_add( val_buf, "DEFAULT" );
2228 } else if( !strcmp( get_primitive( field ), "number" )) {
2229 const char* numtype = get_datatype( field );
2230 if( !strcmp( numtype, "INT8" )) {
2231 buffer_fadd( val_buf, "%lld", atoll( value ));
2233 } else if( !strcmp( numtype, "INT" )) {
2234 buffer_fadd( val_buf, "%d", atoi( value ));
2236 } else if( !strcmp( numtype, "NUMERIC" )) {
2237 buffer_fadd( val_buf, "%f", atof( value ));
2240 if( dbi_conn_quote_string( writehandle, &value )) {
2241 OSRF_BUFFER_ADD( val_buf, value );
2244 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2245 osrfAppSessionStatus(
2247 OSRF_STATUS_INTERNALSERVERERROR,
2248 "osrfMethodException",
2250 "Error quoting string -- please see the error log for more details"
2253 buffer_free( table_buf );
2254 buffer_free( col_buf );
2255 buffer_free( val_buf );
2256 osrfAppRespondComplete( ctx, NULL );
2264 osrfHashIteratorFree( field_itr );
2266 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2267 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2269 char* table_str = buffer_release( table_buf );
2270 char* col_str = buffer_release( col_buf );
2271 char* val_str = buffer_release( val_buf );
2272 growing_buffer* sql = buffer_init( 128 );
2273 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2278 char* query = buffer_release( sql );
2280 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2282 jsonObject* obj = NULL;
2285 dbi_result result = dbi_conn_query( writehandle, query );
2287 obj = jsonNewObject( NULL );
2289 int errnum = dbi_conn_error( writehandle, &msg );
2292 "%s ERROR inserting %s object using query [%s]: %d %s",
2294 osrfHashGet(meta, "fieldmapper"),
2297 msg ? msg : "(No description available)"
2299 osrfAppSessionStatus(
2301 OSRF_STATUS_INTERNALSERVERERROR,
2302 "osrfMethodException",
2304 "INSERT error -- please see the error log for more details"
2306 if( !oilsIsDBConnected( writehandle ))
2307 osrfAppSessionPanic( ctx->session );
2310 dbi_result_free( result );
2312 char* id = oilsFMGetString( target, pkey );
2314 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2315 growing_buffer* _id = buffer_init( 10 );
2316 buffer_fadd( _id, "%lld", new_id );
2317 id = buffer_release( _id );
2320 // Find quietness specification, if present
2321 const char* quiet_str = NULL;
2323 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2325 quiet_str = jsonObjectGetString( quiet_obj );
2328 if( str_is_true( quiet_str )) { // if quietness is specified
2329 obj = jsonNewObject( id );
2333 // Fetch the row that we just inserted, so that we can return it to the client
2334 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2335 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2338 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2342 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2344 jsonObjectFree( list );
2345 jsonObjectFree( where_clause );
2352 osrfAppRespondComplete( ctx, obj );
2353 jsonObjectFree( obj );
2358 @brief Implement the retrieve method.
2359 @param ctx Pointer to the method context.
2360 @param err Pointer through which to return an error code.
2361 @return If successful, a pointer to the result to be returned to the client;
2364 From the method's class, fetch a row with a specified value in the primary key. This
2365 method relies on the database design convention that a primary key consists of a single
2369 - authkey (PCRUD only)
2370 - value of the primary key for the desired row, for building the WHERE clause
2371 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2373 Return to client: One row from the query.
2375 int doRetrieve( osrfMethodContext* ctx ) {
2376 if(osrfMethodVerifyContext( ctx )) {
2377 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2382 timeout_needs_resetting = 1;
2387 if( enforce_pcrud ) {
2392 // Get the class metadata
2393 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2395 // Get the value of the primary key, from a method parameter
2396 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2400 "%s retrieving %s object with primary key value of %s",
2402 osrfHashGet( class_def, "fieldmapper" ),
2403 jsonObjectGetString( id_obj )
2406 // Build a WHERE clause based on the key value
2407 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2410 osrfHashGet( class_def, "primarykey" ), // name of key column
2411 jsonObjectClone( id_obj ) // value of key column
2414 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2418 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2420 jsonObjectFree( where_clause );
2422 osrfAppRespondComplete( ctx, NULL );
2426 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2427 jsonObjectFree( list );
2429 if( enforce_pcrud ) {
2430 // no result, skip this entirely
2431 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2432 jsonObjectFree( obj );
2434 growing_buffer* msg = buffer_init( 128 );
2435 OSRF_BUFFER_ADD( msg, modulename );
2436 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2438 char* m = buffer_release( msg );
2439 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2443 osrfAppRespondComplete( ctx, NULL );
2448 // doFieldmapperSearch() now does the responding for us
2449 //osrfAppRespondComplete( ctx, obj );
2450 osrfAppRespondComplete( ctx, NULL );
2452 jsonObjectFree( obj );
2457 @brief Translate a numeric value to a string representation for the database.
2458 @param field Pointer to the IDL field definition.
2459 @param value Pointer to a jsonObject holding the value of a field.
2460 @return Pointer to a newly allocated string.
2462 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2463 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2464 or (what is worse) valid SQL that is wrong.
2466 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2468 The calling code is responsible for freeing the resulting string by calling free().
2470 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2471 growing_buffer* val_buf = buffer_init( 32 );
2472 const char* numtype = get_datatype( field );
2474 // For historical reasons the following contains cruft that could be cleaned up.
2475 if( !strncmp( numtype, "INT", 3 ) ) {
2476 if( value->type == JSON_NUMBER )
2477 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2478 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2480 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2483 } else if( !strcmp( numtype, "NUMERIC" )) {
2484 if( value->type == JSON_NUMBER )
2485 buffer_fadd( val_buf, jsonObjectGetString( value ));
2487 buffer_fadd( val_buf, jsonObjectGetString( value ));
2491 // Presumably this was really intended to be a string, so quote it
2492 char* str = jsonObjectToSimpleString( value );
2493 if( dbi_conn_quote_string( dbhandle, &str )) {
2494 OSRF_BUFFER_ADD( val_buf, str );
2497 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2499 buffer_free( val_buf );
2504 return buffer_release( val_buf );
2507 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2508 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2509 growing_buffer* sql_buf = buffer_init( 32 );
2515 osrfHashGet( field, "name" )
2519 buffer_add( sql_buf, "IN (" );
2520 } else if( !strcasecmp( op,"not in" )) {
2521 buffer_add( sql_buf, "NOT IN (" );
2523 buffer_add( sql_buf, "IN (" );
2526 if( node->type == JSON_HASH ) {
2527 // subquery predicate
2528 char* subpred = buildQuery( ctx, node, SUBSELECT );
2530 buffer_free( sql_buf );
2534 buffer_add( sql_buf, subpred );
2537 } else if( node->type == JSON_ARRAY ) {
2538 // literal value list
2539 int in_item_index = 0;
2540 int in_item_first = 1;
2541 const jsonObject* in_item;
2542 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2547 buffer_add( sql_buf, ", " );
2550 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2551 osrfLogError( OSRF_LOG_MARK,
2552 "%s: Expected string or number within IN list; found %s",
2553 modulename, json_type( in_item->type ) );
2554 buffer_free( sql_buf );
2558 // Append the literal value -- quoted if not a number
2559 if( JSON_NUMBER == in_item->type ) {
2560 char* val = jsonNumberToDBString( field, in_item );
2561 OSRF_BUFFER_ADD( sql_buf, val );
2564 } else if( !strcmp( get_primitive( field ), "number" )) {
2565 char* val = jsonNumberToDBString( field, in_item );
2566 OSRF_BUFFER_ADD( sql_buf, val );
2570 char* key_string = jsonObjectToSimpleString( in_item );
2571 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2572 OSRF_BUFFER_ADD( sql_buf, key_string );
2575 osrfLogError( OSRF_LOG_MARK,
2576 "%s: Error quoting key string [%s]", modulename, key_string );
2578 buffer_free( sql_buf );
2584 if( in_item_first ) {
2585 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2586 buffer_free( sql_buf );
2590 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2591 modulename, json_type( node->type ));
2592 buffer_free( sql_buf );
2596 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2598 return buffer_release( sql_buf );
2601 // Receive a JSON_ARRAY representing a function call. The first
2602 // entry in the array is the function name. The rest are parameters.
2603 static char* searchValueTransform( const jsonObject* array ) {
2605 if( array->size < 1 ) {
2606 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2610 // Get the function name
2611 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2612 if( func_item->type != JSON_STRING ) {
2613 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2614 modulename, json_type( func_item->type ));
2618 growing_buffer* sql_buf = buffer_init( 32 );
2620 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2621 OSRF_BUFFER_ADD( sql_buf, "( " );
2623 // Get the parameters
2624 int func_item_index = 1; // We already grabbed the zeroth entry
2625 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2627 // Add a separator comma, if we need one
2628 if( func_item_index > 2 )
2629 buffer_add( sql_buf, ", " );
2631 // Add the current parameter
2632 if( func_item->type == JSON_NULL ) {
2633 buffer_add( sql_buf, "NULL" );
2635 if( func_item->type == JSON_BOOL ) {
2636 if( jsonBoolIsTrue(func_item) ) {
2637 buffer_add( sql_buf, "TRUE" );
2639 buffer_add( sql_buf, "FALSE" );
2642 char* val = jsonObjectToSimpleString( func_item );
2643 if( dbi_conn_quote_string( dbhandle, &val )) {
2644 OSRF_BUFFER_ADD( sql_buf, val );
2647 osrfLogError( OSRF_LOG_MARK,
2648 "%s: Error quoting key string [%s]", modulename, val );
2649 buffer_free( sql_buf );
2657 buffer_add( sql_buf, " )" );
2659 return buffer_release( sql_buf );
2662 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2663 const jsonObject* node, const char* op ) {
2665 if( ! is_good_operator( op ) ) {
2666 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2670 char* val = searchValueTransform( node );
2674 growing_buffer* sql_buf = buffer_init( 32 );
2679 osrfHashGet( field, "name" ),
2686 return buffer_release( sql_buf );
2689 // class_alias is a class name or other table alias
2690 // field is a field definition as stored in the IDL
2691 // node comes from the method parameter, and may represent an entry in the SELECT list
2692 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2693 const jsonObject* node ) {
2694 growing_buffer* sql_buf = buffer_init( 32 );
2696 const char* field_transform = jsonObjectGetString(
2697 jsonObjectGetKeyConst( node, "transform" ) );
2698 const char* transform_subcolumn = jsonObjectGetString(
2699 jsonObjectGetKeyConst( node, "result_field" ) );
2701 if( transform_subcolumn ) {
2702 if( ! is_identifier( transform_subcolumn ) ) {
2703 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2704 modulename, transform_subcolumn );
2705 buffer_free( sql_buf );
2708 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2711 if( field_transform ) {
2713 if( ! is_identifier( field_transform ) ) {
2714 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2715 modulename, field_transform );
2716 buffer_free( sql_buf );
2720 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2721 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2722 field_transform, class_alias, osrfHashGet( field, "name" ));
2724 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2725 field_transform, class_alias, osrfHashGet( field, "name" ));
2728 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2731 if( array->type != JSON_ARRAY ) {
2732 osrfLogError( OSRF_LOG_MARK,
2733 "%s: Expected JSON_ARRAY for function params; found %s",
2734 modulename, json_type( array->type ) );
2735 buffer_free( sql_buf );
2738 int func_item_index = 0;
2739 jsonObject* func_item;
2740 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2742 char* val = jsonObjectToSimpleString( func_item );
2745 buffer_add( sql_buf, ",NULL" );
2746 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2747 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2748 OSRF_BUFFER_ADD( sql_buf, val );
2750 osrfLogError( OSRF_LOG_MARK,
2751 "%s: Error quoting key string [%s]", modulename, val );
2753 buffer_free( sql_buf );
2760 buffer_add( sql_buf, " )" );
2763 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2766 if( transform_subcolumn )
2767 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2769 return buffer_release( sql_buf );
2772 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2773 const jsonObject* node, const char* op ) {
2775 if( ! is_good_operator( op ) ) {
2776 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2780 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2781 if( ! field_transform )
2784 int extra_parens = 0; // boolean
2786 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2788 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2790 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2792 free( field_transform );
2796 } else if( value_obj->type == JSON_ARRAY ) {
2797 value = searchValueTransform( value_obj );
2799 osrfLogError( OSRF_LOG_MARK,
2800 "%s: Error building value transform for field transform", modulename );
2801 free( field_transform );
2804 } else if( value_obj->type == JSON_HASH ) {
2805 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2807 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2809 free( field_transform );
2813 } else if( value_obj->type == JSON_NUMBER ) {
2814 value = jsonNumberToDBString( field, value_obj );
2815 } else if( value_obj->type == JSON_NULL ) {
2816 osrfLogError( OSRF_LOG_MARK,
2817 "%s: Error building predicate for field transform: null value", modulename );
2818 free( field_transform );
2820 } else if( value_obj->type == JSON_BOOL ) {
2821 osrfLogError( OSRF_LOG_MARK,
2822 "%s: Error building predicate for field transform: boolean value", modulename );
2823 free( field_transform );
2826 if( !strcmp( get_primitive( field ), "number") ) {
2827 value = jsonNumberToDBString( field, value_obj );
2829 value = jsonObjectToSimpleString( value_obj );
2830 if( !dbi_conn_quote_string( dbhandle, &value )) {
2831 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2832 modulename, value );
2834 free( field_transform );
2840 const char* left_parens = "";
2841 const char* right_parens = "";
2843 if( extra_parens ) {
2848 const char* right_percent = "";
2849 const char* real_op = op;
2851 if( !strcasecmp( op, "startwith") ) {
2853 right_percent = "|| '%'";
2856 growing_buffer* sql_buf = buffer_init( 32 );
2860 "%s%s %s %s %s%s %s%s",
2872 free( field_transform );
2874 return buffer_release( sql_buf );
2877 static char* searchSimplePredicate( const char* op, const char* class_alias,
2878 osrfHash* field, const jsonObject* node ) {
2880 if( ! is_good_operator( op ) ) {
2881 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2887 // Get the value to which we are comparing the specified column
2888 if( node->type != JSON_NULL ) {
2889 if( node->type == JSON_NUMBER ) {
2890 val = jsonNumberToDBString( field, node );
2891 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2892 val = jsonNumberToDBString( field, node );
2894 val = jsonObjectToSimpleString( node );
2899 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2900 // Value is not numeric; enclose it in quotes
2901 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2902 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2909 // Compare to a null value
2910 val = strdup( "NULL" );
2911 if( strcmp( op, "=" ))
2917 growing_buffer* sql_buf = buffer_init( 32 );
2918 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2919 char* pred = buffer_release( sql_buf );
2926 static char* searchBETWEENPredicate( const char* class_alias,
2927 osrfHash* field, const jsonObject* node ) {
2929 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2930 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2932 if( NULL == y_node ) {
2933 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2936 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2937 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2944 if( !strcmp( get_primitive( field ), "number") ) {
2945 x_string = jsonNumberToDBString( field, x_node );
2946 y_string = jsonNumberToDBString( field, y_node );
2949 x_string = jsonObjectToSimpleString( x_node );
2950 y_string = jsonObjectToSimpleString( y_node );
2951 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2952 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2953 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2954 modulename, x_string, y_string );
2961 growing_buffer* sql_buf = buffer_init( 32 );
2962 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2963 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2967 return buffer_release( sql_buf );
2970 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2971 jsonObject* node, osrfMethodContext* ctx ) {
2974 if( node->type == JSON_ARRAY ) { // equality IN search
2975 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2976 } else if( node->type == JSON_HASH ) { // other search
2977 jsonIterator* pred_itr = jsonNewIterator( node );
2978 if( !jsonIteratorHasNext( pred_itr ) ) {
2979 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2980 modulename, osrfHashGet(field, "name" ));
2982 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2984 // Verify that there are no additional predicates
2985 if( jsonIteratorHasNext( pred_itr ) ) {
2986 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2987 modulename, osrfHashGet(field, "name" ));
2988 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2989 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2990 else if( !(strcasecmp( pred_itr->key,"in" ))
2991 || !(strcasecmp( pred_itr->key,"not in" )) )
2992 pred = searchINPredicate(
2993 class_info->alias, field, pred_node, pred_itr->key, ctx );
2994 else if( pred_node->type == JSON_ARRAY )
2995 pred = searchFunctionPredicate(
2996 class_info->alias, field, pred_node, pred_itr->key );
2997 else if( pred_node->type == JSON_HASH )
2998 pred = searchFieldTransformPredicate(
2999 class_info, field, pred_node, pred_itr->key );
3001 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3003 jsonIteratorFree( pred_itr );
3005 } else if( node->type == JSON_NULL ) { // IS NULL search
3006 growing_buffer* _p = buffer_init( 64 );
3009 "\"%s\".%s IS NULL",
3011 osrfHashGet( field, "name" )
3013 pred = buffer_release( _p );
3014 } else { // equality search
3015 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3034 field : call_number,
3050 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3052 const jsonObject* working_hash;
3053 jsonObject* freeable_hash = NULL;
3055 if( join_hash->type == JSON_HASH ) {
3056 working_hash = join_hash;
3057 } else if( join_hash->type == JSON_STRING ) {
3058 // turn it into a JSON_HASH by creating a wrapper
3059 // around a copy of the original
3060 const char* _tmp = jsonObjectGetString( join_hash );
3061 freeable_hash = jsonNewObjectType( JSON_HASH );
3062 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3063 working_hash = freeable_hash;
3067 "%s: JOIN failed; expected JSON object type not found",
3073 growing_buffer* join_buf = buffer_init( 128 );
3074 const char* leftclass = left_info->class_name;
3076 jsonObject* snode = NULL;
3077 jsonIterator* search_itr = jsonNewIterator( working_hash );
3079 while ( (snode = jsonIteratorNext( search_itr )) ) {
3080 const char* right_alias = search_itr->key;
3082 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3084 class = right_alias;
3086 const ClassInfo* right_info = add_joined_class( right_alias, class );
3090 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3094 jsonIteratorFree( search_itr );
3095 buffer_free( join_buf );
3097 jsonObjectFree( freeable_hash );
3100 osrfHash* links = right_info->links;
3101 const char* table = right_info->source_def;
3103 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3104 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3106 if( field && !fkey ) {
3107 // Look up the corresponding join column in the IDL.
3108 // The link must be defined in the child table,
3109 // and point to the right parent table.
3110 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3111 const char* reltype = NULL;
3112 const char* other_class = NULL;
3113 reltype = osrfHashGet( idl_link, "reltype" );
3114 if( reltype && strcmp( reltype, "has_many" ) )
3115 other_class = osrfHashGet( idl_link, "class" );
3116 if( other_class && !strcmp( other_class, leftclass ) )
3117 fkey = osrfHashGet( idl_link, "key" );
3121 "%s: JOIN failed. No link defined from %s.%s to %s",
3127 buffer_free( join_buf );
3129 jsonObjectFree( freeable_hash );
3130 jsonIteratorFree( search_itr );
3134 } else if( !field && fkey ) {
3135 // Look up the corresponding join column in the IDL.
3136 // The link must be defined in the child table,
3137 // and point to the right parent table.
3138 osrfHash* left_links = left_info->links;
3139 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3140 const char* reltype = NULL;
3141 const char* other_class = NULL;
3142 reltype = osrfHashGet( idl_link, "reltype" );
3143 if( reltype && strcmp( reltype, "has_many" ) )
3144 other_class = osrfHashGet( idl_link, "class" );
3145 if( other_class && !strcmp( other_class, class ) )
3146 field = osrfHashGet( idl_link, "key" );
3150 "%s: JOIN failed. No link defined from %s.%s to %s",
3156 buffer_free( join_buf );
3158 jsonObjectFree( freeable_hash );
3159 jsonIteratorFree( search_itr );
3163 } else if( !field && !fkey ) {
3164 osrfHash* left_links = left_info->links;
3166 // For each link defined for the left class:
3167 // see if the link references the joined class
3168 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3169 osrfHash* curr_link = NULL;
3170 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3171 const char* other_class = osrfHashGet( curr_link, "class" );
3172 if( other_class && !strcmp( other_class, class ) ) {
3174 // In the IDL, the parent class doesn't always know then names of the child
3175 // columns that are pointing to it, so don't use that end of the link
3176 const char* reltype = osrfHashGet( curr_link, "reltype" );
3177 if( reltype && strcmp( reltype, "has_many" ) ) {
3178 // Found a link between the classes
3179 fkey = osrfHashIteratorKey( itr );
3180 field = osrfHashGet( curr_link, "key" );
3185 osrfHashIteratorFree( itr );
3187 if( !field || !fkey ) {
3188 // Do another such search, with the classes reversed
3190 // For each link defined for the joined class:
3191 // see if the link references the left class
3192 osrfHashIterator* itr = osrfNewHashIterator( links );
3193 osrfHash* curr_link = NULL;
3194 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3195 const char* other_class = osrfHashGet( curr_link, "class" );
3196 if( other_class && !strcmp( other_class, leftclass ) ) {
3198 // In the IDL, the parent class doesn't know then names of the child
3199 // columns that are pointing to it, so don't use that end of the link
3200 const char* reltype = osrfHashGet( curr_link, "reltype" );
3201 if( reltype && strcmp( reltype, "has_many" ) ) {
3202 // Found a link between the classes
3203 field = osrfHashIteratorKey( itr );
3204 fkey = osrfHashGet( curr_link, "key" );
3209 osrfHashIteratorFree( itr );
3212 if( !field || !fkey ) {
3215 "%s: JOIN failed. No link defined between %s and %s",
3220 buffer_free( join_buf );
3222 jsonObjectFree( freeable_hash );
3223 jsonIteratorFree( search_itr );
3228 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3230 if( !strcasecmp( type,"left" )) {
3231 buffer_add( join_buf, " LEFT JOIN" );
3232 } else if( !strcasecmp( type,"right" )) {
3233 buffer_add( join_buf, " RIGHT JOIN" );
3234 } else if( !strcasecmp( type,"full" )) {
3235 buffer_add( join_buf, " FULL JOIN" );
3237 buffer_add( join_buf, " INNER JOIN" );