3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static void pop_query_frame( void );
106 static void push_query_frame( void );
107 static int add_query_core( const char* alias, const char* class_name );
108 static inline ClassInfo* search_alias( const char* target );
109 static ClassInfo* search_all_alias( const char* target );
110 static ClassInfo* add_joined_class( const char* alias, const char* classname );
111 static void clear_query_stack( void );
113 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
114 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
115 static const char* org_tree_root( osrfMethodContext* ctx );
116 static jsonObject* single_hash( const char* key, const char* value );
118 static int child_initialized = 0; /* boolean */
120 static dbi_conn writehandle; /* our MASTER db connection */
121 static dbi_conn dbhandle; /* our CURRENT db connection */
122 //static osrfHash * readHandles;
124 // The following points to the top of a stack of QueryFrames. It's a little
125 // confusing because the top level of the query is at the bottom of the stack.
126 static QueryFrame* curr_query = NULL;
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 static int max_flesh_depth = 100;
134 static int enforce_pcrud = 0; // Boolean
135 static char* modulename = NULL;
138 @brief Connect to the database.
139 @return A database connection if successful, or NULL if not.
141 dbi_conn oilsConnectDB( const char* mod_name ) {
143 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
144 if( dbi_initialize( NULL ) == -1 ) {
145 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
148 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
150 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
151 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
152 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
153 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
154 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
155 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
158 dbi_conn handle = dbi_conn_new( driver );
161 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
164 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
166 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
167 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
169 if( host ) dbi_conn_set_option( handle, "host", host );
170 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
171 if( user ) dbi_conn_set_option( handle, "username", user );
172 if( pw ) dbi_conn_set_option( handle, "password", pw );
173 if( db ) dbi_conn_set_option( handle, "dbname", db );
181 if( dbi_conn_connect( handle ) < 0 ) {
183 if( dbi_conn_connect( handle ) < 0 ) {
185 dbi_conn_error( handle, &msg );
186 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
187 msg ? msg : "(No description available)" );
192 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
198 @brief Select some options.
199 @param module_name: Name of the server.
200 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
202 This source file is used (at this writing) to implement three different servers:
203 - open-ils.reporter-store
207 These servers behave mostly the same, but they implement different combinations of
208 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
210 Here we use the server name in messages to identify which kind of server issued them.
211 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
213 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
215 module_name = "open-ils.cstore"; // bulletproofing with a default
220 modulename = strdup( module_name );
221 enforce_pcrud = do_pcrud;
222 max_flesh_depth = flesh_depth;
226 @brief Install a database connection.
227 @param conn Pointer to a database connection.
229 In some contexts, @a conn may merely provide a driver so that we can process strings
230 properly, without providing an open database connection.
232 void oilsSetDBConnection( dbi_conn conn ) {
233 dbhandle = writehandle = conn;
237 @brief Determine whether a database connection is alive.
238 @param handle Handle for a database connection.
239 @return 1 if the connection is alive, or zero if it isn't.
241 int oilsIsDBConnected( dbi_conn handle ) {
242 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
243 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
245 dbi_result_free( result );
248 // This is a terrible, horrible, no good, very bad kludge.
249 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
250 // but because (due to a previous error) the database is ignoring all commands,
251 // even innocuous SELECTs, until the current transaction is rolled back. The only
252 // known way to detect this condition via the dbi library is by looking at the error
253 // message. This approach will break if the language or wording of the message ever
255 // Note: the dbi_conn_ping function purports to determine whether the doatabase
256 // connection is live, but at this writing this function is unreliable and useless.
257 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
258 "ignored until end of transaction block\n";
260 dbi_conn_error( handle, &msg );
261 if( strcmp( msg, ok_msg )) {
262 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
265 return 1; // ignoring SELECT due to previous error; that's okay
270 @brief Get a table name, view name, or subquery for use in a FROM clause.
271 @param class Pointer to the IDL class entry.
272 @return A table name, a view name, or a subquery in parentheses.
274 In some cases the IDL defines a class, not with a table name or a view name, but with
275 a SELECT statement, which may be used as a subquery.
277 char* oilsGetRelation( osrfHash* classdef ) {
279 char* source_def = NULL;
280 const char* tabledef = osrfHashGet( classdef, "tablename" );
283 source_def = strdup( tabledef ); // Return the name of a table or view
285 tabledef = osrfHashGet( classdef, "source_definition" );
287 // Return a subquery, enclosed in parentheses
288 source_def = safe_malloc( strlen( tabledef ) + 3 );
289 source_def[ 0 ] = '(';
290 strcpy( source_def + 1, tabledef );
291 strcat( source_def, ")" );
293 // Not found: return an error
294 const char* classname = osrfHashGet( classdef, "classname" );
299 "%s ERROR No tablename or source_definition for class \"%s\"",
310 @brief Add datatypes from the database to the fields in the IDL.
311 @param handle Handle for a database connection
312 @return Zero if successful, or 1 upon error.
314 For each relevant class in the IDL: ask the database for the datatype of every field.
315 In particular, determine which fields are text fields and which fields are numeric
316 fields, so that we know whether to enclose their values in quotes.
318 int oilsExtendIDL( dbi_conn handle ) {
319 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
320 osrfHash* class = NULL;
321 growing_buffer* query_buf = buffer_init( 64 );
322 int results_found = 0; // boolean
324 // For each class in the IDL...
325 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
326 const char* classname = osrfHashIteratorKey( class_itr );
327 osrfHash* fields = osrfHashGet( class, "fields" );
329 // If the class is virtual, ignore it
330 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
331 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
335 char* tabledef = oilsGetRelation( class );
337 continue; // No such relation -- a query of it would be doomed to failure
339 buffer_reset( query_buf );
340 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
344 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
345 modulename, OSRF_BUFFER_C_STR( query_buf ) );
347 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
352 const char* columnName;
353 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
355 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
358 /* fetch the fieldmapper index */
359 osrfHash* _f = osrfHashGet(fields, columnName);
362 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
364 /* determine the field type and storage attributes */
366 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
368 case DBI_TYPE_INTEGER : {
370 if( !osrfHashGet(_f, "primitive") )
371 osrfHashSet(_f, "number", "primitive");
373 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
374 if( attr & DBI_INTEGER_SIZE8 )
375 osrfHashSet( _f, "INT8", "datatype" );
377 osrfHashSet( _f, "INT", "datatype" );
380 case DBI_TYPE_DECIMAL :
381 if( !osrfHashGet( _f, "primitive" ))
382 osrfHashSet( _f, "number", "primitive" );
384 osrfHashSet( _f, "NUMERIC", "datatype" );
387 case DBI_TYPE_STRING :
388 if( !osrfHashGet( _f, "primitive" ))
389 osrfHashSet( _f, "string", "primitive" );
391 osrfHashSet( _f,"TEXT", "datatype" );
394 case DBI_TYPE_DATETIME :
395 if( !osrfHashGet( _f, "primitive" ))
396 osrfHashSet( _f, "string", "primitive" );
398 osrfHashSet( _f, "TIMESTAMP", "datatype" );
401 case DBI_TYPE_BINARY :
402 if( !osrfHashGet( _f, "primitive" ))
403 osrfHashSet( _f, "string", "primitive" );
405 osrfHashSet( _f, "BYTEA", "datatype" );
410 "Setting [%s] to primitive [%s] and datatype [%s]...",
412 osrfHashGet( _f, "primitive" ),
413 osrfHashGet( _f, "datatype" )
417 } // end while loop for traversing columns of result
418 dbi_result_free( result );
421 int errnum = dbi_conn_error( handle, &msg );
422 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
423 errnum, msg ? msg : "(No description available)" );
424 // We don't check the database connection here. It's routine to get failures at
425 // this point; we routinely try to query tables that don't exist, because they
426 // are defined in the IDL but not in the database.
428 } // end for each class in IDL
430 buffer_free( query_buf );
431 osrfHashIteratorFree( class_itr );
432 child_initialized = 1;
434 if( !results_found ) {
435 osrfLogError( OSRF_LOG_MARK,
436 "No results found for any class -- bad database connection?" );
438 } else if( ! oilsIsDBConnected( handle )) {
439 osrfLogError( OSRF_LOG_MARK,
440 "Unable to extend IDL: database connection isn't working" );
448 @brief Free an osrfHash that stores a transaction ID.
449 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
451 This function is a callback, to be called by the application session when it ends.
452 The application session stores the osrfHash via an opaque pointer.
454 If the osrfHash contains an entry for the key "xact_id", it means that an
455 uncommitted transaction is pending. Roll it back.
457 void userDataFree( void* blob ) {
458 osrfHash* hash = (osrfHash*) blob;
459 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
460 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
462 int errnum = dbi_conn_error( writehandle, &msg );
463 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
464 errnum, msg ? msg : "(No description available)" );
468 osrfHashFree( hash );
472 @name Managing session data
473 @brief Maintain data stored via the userData pointer of the application session.
475 Currently, session-level data is stored in an osrfHash. Other arrangements are
476 possible, and some would be more efficient. The application session calls a
477 callback function to free userData before terminating.
479 Currently, the only data we store at the session level is the transaction id. By this
480 means we can ensure that any pending transactions are rolled back before the application
486 @brief Free an item in the application session's userData.
487 @param key The name of a key for an osrfHash.
488 @param item An opaque pointer to the item associated with the key.
490 We store an osrfHash as userData with the application session, and arrange (by
491 installing userDataFree() as a different callback) for the session to free that
492 osrfHash before terminating.
494 This function is a callback for freeing items in the osrfHash. Currently we store
496 - Transaction id of a pending transaction; a character string. Key: "xact_id".
497 - Authkey; a character string. Key: "authkey".
498 - User object from the authentication server; a jsonObject. Key: "user_login".
500 If we ever store anything else in userData, we will need to revisit this function so
501 that it will free whatever else needs freeing.
503 static void sessionDataFree( char* key, void* item ) {
504 if( !strcmp( key, "xact_id" )
505 || !strcmp( key, "authkey" ) ) {
507 } else if( !strcmp( key, "user_login" ) )
508 jsonObjectFree( (jsonObject*) item );
512 @brief Save a transaction id.
513 @param ctx Pointer to the method context.
515 Save the session_id of the current application session as a transaction id.
517 static void setXactId( osrfMethodContext* ctx ) {
518 if( ctx && ctx->session ) {
519 osrfAppSession* session = ctx->session;
521 osrfHash* cache = session->userData;
523 // If the session doesn't already have a hash, create one. Make sure
524 // that the application session frees the hash when it terminates.
525 if( NULL == cache ) {
526 session->userData = cache = osrfNewHash();
527 osrfHashSetCallback( cache, &sessionDataFree );
528 ctx->session->userDataFree = &userDataFree;
531 // Save the transaction id in the hash, with the key "xact_id"
532 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
537 @brief Get the transaction ID for the current transaction, if any.
538 @param ctx Pointer to the method context.
539 @return Pointer to the transaction ID.
541 The return value points to an internal buffer, and will become invalid upon issuing
542 a commit or rollback.
544 static inline const char* getXactId( osrfMethodContext* ctx ) {
545 if( ctx && ctx->session && ctx->session->userData )
546 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
552 @brief Clear the current transaction id.
553 @param ctx Pointer to the method context.
555 static inline void clearXactId( osrfMethodContext* ctx ) {
556 if( ctx && ctx->session && ctx->session->userData )
557 osrfHashRemove( ctx->session->userData, "xact_id" );
562 @brief Save the user's login in the userData for the current application session.
563 @param ctx Pointer to the method context.
564 @param user_login Pointer to the user login object to be cached (we cache the original,
567 If @a user_login is NULL, remove the user login if one is already cached.
569 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
570 if( ctx && ctx->session ) {
571 osrfAppSession* session = ctx->session;
573 osrfHash* cache = session->userData;
575 // If the session doesn't already have a hash, create one. Make sure
576 // that the application session frees the hash when it terminates.
577 if( NULL == cache ) {
578 session->userData = cache = osrfNewHash();
579 osrfHashSetCallback( cache, &sessionDataFree );
580 ctx->session->userDataFree = &userDataFree;
584 osrfHashSet( cache, user_login, "user_login" );
586 osrfHashRemove( cache, "user_login" );
591 @brief Get the user login object for the current application session, if any.
592 @param ctx Pointer to the method context.
593 @return Pointer to the user login object if found; otherwise NULL.
595 The user login object was returned from the authentication server, and then cached so
596 we don't have to call the authentication server again for the same user.
598 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
599 if( ctx && ctx->session && ctx->session->userData )
600 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
606 @brief Save a copy of an authkey in the userData of the current application session.
607 @param ctx Pointer to the method context.
608 @param authkey The authkey to be saved.
610 If @a authkey is NULL, remove the authkey if one is already cached.
612 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
613 if( ctx && ctx->session && authkey ) {
614 osrfAppSession* session = ctx->session;
615 osrfHash* cache = session->userData;
617 // If the session doesn't already have a hash, create one. Make sure
618 // that the application session frees the hash when it terminates.
619 if( NULL == cache ) {
620 session->userData = cache = osrfNewHash();
621 osrfHashSetCallback( cache, &sessionDataFree );
622 ctx->session->userDataFree = &userDataFree;
625 // Save the transaction id in the hash, with the key "xact_id"
626 if( authkey && *authkey )
627 osrfHashSet( cache, strdup( authkey ), "authkey" );
629 osrfHashRemove( cache, "authkey" );
634 @brief Reset the login timeout.
635 @param authkey The authentication key for the current login session.
636 @param now The current time.
637 @return Zero if successful, or 1 if not.
639 Tell the authentication server to reset the timeout so that the login session won't
640 expire for a while longer.
642 We could dispense with the @a now parameter by calling time(). But we just called
643 time() in order to decide whether to reset the timeout, so we might as well reuse
644 the result instead of calling time() again.
646 static int reset_timeout( const char* authkey, time_t now ) {
647 jsonObject* auth_object = jsonNewObject( authkey );
649 // Ask the authentication server to reset the timeout. It returns an event
650 // indicating success or failure.
651 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
652 "open-ils.auth.session.reset_timeout", auth_object );
653 jsonObjectFree( auth_object );
655 if( !result || result->type != JSON_HASH ) {
656 osrfLogError( OSRF_LOG_MARK,
657 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
658 jsonObjectFree( result );
659 return 1; // Not the right sort of object returned
662 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
663 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
664 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
665 jsonObjectFree( result );
666 return 1; // Return code from method not available
669 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
670 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
672 desc = "(No reason available)"; // failsafe; shouldn't happen
673 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
674 jsonObjectFree( result );
678 // Revise our local proxy for the timeout deadline
679 // by a smallish fraction of the timeout interval
680 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
682 timeout = "1"; // failsafe; shouldn't happen
683 time_next_reset = now + atoi( timeout ) / 15;
685 jsonObjectFree( result );
686 return 0; // Successfully reset timeout
690 @brief Get the authkey string for the current application session, if any.
691 @param ctx Pointer to the method context.
692 @return Pointer to the cached authkey if found; otherwise NULL.
694 If present, the authkey string was cached from a previous method call.
696 static const char* getAuthkey( osrfMethodContext* ctx ) {
697 if( ctx && ctx->session && ctx->session->userData ) {
698 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
700 // Possibly reset the authentication timeout to keep the login alive. We do so
701 // no more than once per method call, and not at all if it has been only a short
702 // time since the last reset.
704 // Here we reset explicitly, if at all. We also implicitly reset the timeout
705 // whenever we call the "open-ils.auth.session.retrieve" method.
706 if( timeout_needs_resetting ) {
707 time_t now = time( NULL );
708 if( now >= time_next_reset && reset_timeout( authkey, now ) )
709 authkey = NULL; // timeout has apparently expired already
712 timeout_needs_resetting = 0;
720 @brief Implement the transaction.begin method.
721 @param ctx Pointer to the method context.
722 @return Zero if successful, or -1 upon error.
724 Start a transaction. Save a transaction ID for future reference.
727 - authkey (PCRUD only)
729 Return to client: Transaction ID
731 int beginTransaction( osrfMethodContext* ctx ) {
732 if(osrfMethodVerifyContext( ctx )) {
733 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
737 if( enforce_pcrud ) {
738 timeout_needs_resetting = 1;
739 const jsonObject* user = verifyUserPCRUD( ctx );
744 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
747 int errnum = dbi_conn_error( writehandle, &msg );
748 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
749 modulename, errnum, msg ? msg : "(No description available)" );
750 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
751 "osrfMethodException", ctx->request, "Error starting transaction" );
752 if( !oilsIsDBConnected( writehandle ))
753 osrfAppSessionPanic( ctx->session );
756 dbi_result_free( result );
758 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
759 osrfAppRespondComplete( ctx, ret );
760 jsonObjectFree( ret );
766 @brief Implement the savepoint.set method.
767 @param ctx Pointer to the method context.
768 @return Zero if successful, or -1 if not.
770 Issue a SAVEPOINT to the database server.
773 - authkey (PCRUD only)
776 Return to client: Savepoint name
778 int setSavepoint( osrfMethodContext* ctx ) {
779 if(osrfMethodVerifyContext( ctx )) {
780 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
785 if( enforce_pcrud ) {
787 timeout_needs_resetting = 1;
788 const jsonObject* user = verifyUserPCRUD( ctx );
793 // Verify that a transaction is pending
794 const char* trans_id = getXactId( ctx );
795 if( NULL == trans_id ) {
796 osrfAppSessionStatus(
798 OSRF_STATUS_INTERNALSERVERERROR,
799 "osrfMethodException",
801 "No active transaction -- required for savepoints"
806 // Get the savepoint name from the method params
807 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
809 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
812 int errnum = dbi_conn_error( writehandle, &msg );
815 "%s: Error creating savepoint %s in transaction %s: %d %s",
820 msg ? msg : "(No description available)"
822 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
823 "osrfMethodException", ctx->request, "Error creating savepoint" );
824 if( !oilsIsDBConnected( writehandle ))
825 osrfAppSessionPanic( ctx->session );
828 dbi_result_free( result );
829 jsonObject* ret = jsonNewObject( spName );
830 osrfAppRespondComplete( ctx, ret );
831 jsonObjectFree( ret );
837 @brief Implement the savepoint.release method.
838 @param ctx Pointer to the method context.
839 @return Zero if successful, or -1 if not.
841 Issue a RELEASE SAVEPOINT to the database server.
844 - authkey (PCRUD only)
847 Return to client: Savepoint name
849 int releaseSavepoint( osrfMethodContext* ctx ) {
850 if(osrfMethodVerifyContext( ctx )) {
851 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
856 if( enforce_pcrud ) {
858 timeout_needs_resetting = 1;
859 const jsonObject* user = verifyUserPCRUD( ctx );
864 // Verify that a transaction is pending
865 const char* trans_id = getXactId( ctx );
866 if( NULL == trans_id ) {
867 osrfAppSessionStatus(
869 OSRF_STATUS_INTERNALSERVERERROR,
870 "osrfMethodException",
872 "No active transaction -- required for savepoints"
877 // Get the savepoint name from the method params
878 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
880 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
883 int errnum = dbi_conn_error( writehandle, &msg );
886 "%s: Error releasing savepoint %s in transaction %s: %d %s",
891 msg ? msg : "(No description available)"
893 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
894 "osrfMethodException", ctx->request, "Error releasing savepoint" );
895 if( !oilsIsDBConnected( writehandle ))
896 osrfAppSessionPanic( ctx->session );
899 dbi_result_free( result );
900 jsonObject* ret = jsonNewObject( spName );
901 osrfAppRespondComplete( ctx, ret );
902 jsonObjectFree( ret );
908 @brief Implement the savepoint.rollback method.
909 @param ctx Pointer to the method context.
910 @return Zero if successful, or -1 if not.
912 Issue a ROLLBACK TO SAVEPOINT to the database server.
915 - authkey (PCRUD only)
918 Return to client: Savepoint name
920 int rollbackSavepoint( osrfMethodContext* ctx ) {
921 if(osrfMethodVerifyContext( ctx )) {
922 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
927 if( enforce_pcrud ) {
929 timeout_needs_resetting = 1;
930 const jsonObject* user = verifyUserPCRUD( ctx );
935 // Verify that a transaction is pending
936 const char* trans_id = getXactId( ctx );
937 if( NULL == trans_id ) {
938 osrfAppSessionStatus(
940 OSRF_STATUS_INTERNALSERVERERROR,
941 "osrfMethodException",
943 "No active transaction -- required for savepoints"
948 // Get the savepoint name from the method params
949 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
951 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
954 int errnum = dbi_conn_error( writehandle, &msg );
957 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
962 msg ? msg : "(No description available)"
964 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
965 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
966 if( !oilsIsDBConnected( writehandle ))
967 osrfAppSessionPanic( ctx->session );
970 dbi_result_free( result );
971 jsonObject* ret = jsonNewObject( spName );
972 osrfAppRespondComplete( ctx, ret );
973 jsonObjectFree( ret );
979 @brief Implement the transaction.commit method.
980 @param ctx Pointer to the method context.
981 @return Zero if successful, or -1 if not.
983 Issue a COMMIT to the database server.
986 - authkey (PCRUD only)
988 Return to client: Transaction ID.
990 int commitTransaction( osrfMethodContext* ctx ) {
991 if(osrfMethodVerifyContext( ctx )) {
992 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
996 if( enforce_pcrud ) {
997 timeout_needs_resetting = 1;
998 const jsonObject* user = verifyUserPCRUD( ctx );
1003 // Verify that a transaction is pending
1004 const char* trans_id = getXactId( ctx );
1005 if( NULL == trans_id ) {
1006 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1007 "osrfMethodException", ctx->request, "No active transaction to commit" );
1011 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1014 int errnum = dbi_conn_error( writehandle, &msg );
1015 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1016 modulename, errnum, msg ? msg : "(No description available)" );
1017 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1018 "osrfMethodException", ctx->request, "Error committing transaction" );
1019 if( !oilsIsDBConnected( writehandle ))
1020 osrfAppSessionPanic( ctx->session );
1023 dbi_result_free( result );
1024 jsonObject* ret = jsonNewObject( trans_id );
1025 osrfAppRespondComplete( ctx, ret );
1026 jsonObjectFree( ret );
1033 @brief Implement the transaction.rollback method.
1034 @param ctx Pointer to the method context.
1035 @return Zero if successful, or -1 if not.
1037 Issue a ROLLBACK to the database server.
1040 - authkey (PCRUD only)
1042 Return to client: Transaction ID
1044 int rollbackTransaction( osrfMethodContext* ctx ) {
1045 if( osrfMethodVerifyContext( ctx )) {
1046 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1050 if( enforce_pcrud ) {
1051 timeout_needs_resetting = 1;
1052 const jsonObject* user = verifyUserPCRUD( ctx );
1057 // Verify that a transaction is pending
1058 const char* trans_id = getXactId( ctx );
1059 if( NULL == trans_id ) {
1060 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1061 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1065 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1068 int errnum = dbi_conn_error( writehandle, &msg );
1069 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1070 modulename, errnum, msg ? msg : "(No description available)" );
1071 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1072 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1073 if( !oilsIsDBConnected( writehandle ))
1074 osrfAppSessionPanic( ctx->session );
1077 dbi_result_free( result );
1078 jsonObject* ret = jsonNewObject( trans_id );
1079 osrfAppRespondComplete( ctx, ret );
1080 jsonObjectFree( ret );
1087 @brief Implement the "search" method.
1088 @param ctx Pointer to the method context.
1089 @return Zero if successful, or -1 if not.
1092 - authkey (PCRUD only)
1093 - WHERE clause, as jsonObject
1094 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1096 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1097 Optionally flesh linked fields.
1099 int doSearch( osrfMethodContext* ctx ) {
1100 if( osrfMethodVerifyContext( ctx )) {
1101 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1106 timeout_needs_resetting = 1;
1108 jsonObject* where_clause;
1109 jsonObject* rest_of_query;
1111 if( enforce_pcrud ) {
1112 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1113 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1115 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1116 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1119 // Get the class metadata
1120 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1121 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1125 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1127 osrfAppRespondComplete( ctx, NULL );
1131 // Return each row to the client (except that some may be suppressed by PCRUD)
1132 jsonObject* cur = 0;
1133 unsigned long res_idx = 0;
1134 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1135 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1137 osrfAppRespond( ctx, cur );
1139 jsonObjectFree( obj );
1141 osrfAppRespondComplete( ctx, NULL );
1146 @brief Implement the "id_list" method.
1147 @param ctx Pointer to the method context.
1148 @param err Pointer through which to return an error code.
1149 @return Zero if successful, or -1 if not.
1152 - authkey (PCRUD only)
1153 - WHERE clause, as jsonObject
1154 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1156 Return to client: The primary key values for all rows of the relevant class that
1157 satisfy a specified WHERE clause.
1159 This method relies on the assumption that every class has a primary key consisting of
1162 int doIdList( osrfMethodContext* ctx ) {
1163 if( osrfMethodVerifyContext( ctx )) {
1164 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1169 timeout_needs_resetting = 1;
1171 jsonObject* where_clause;
1172 jsonObject* rest_of_query;
1174 // We use the where clause without change. But we need to massage the rest of the
1175 // query, so we work with a copy of it instead of modifying the original.
1177 if( enforce_pcrud ) {
1178 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1179 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1181 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1182 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1185 // Eliminate certain SQL clauses, if present.
1186 if( rest_of_query ) {
1187 jsonObjectRemoveKey( rest_of_query, "select" );
1188 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1189 jsonObjectRemoveKey( rest_of_query, "flesh" );
1190 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1192 rest_of_query = jsonNewObjectType( JSON_HASH );
1195 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1197 // Get the class metadata
1198 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1199 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1201 // Build a SELECT list containing just the primary key,
1202 // i.e. like { "classname":["keyname"] }
1203 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1205 // Load array with name of primary key
1206 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1207 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1208 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1210 jsonObjectSetKey( rest_of_query, "select", select_clause );
1215 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1217 jsonObjectFree( rest_of_query );
1219 osrfAppRespondComplete( ctx, NULL );
1223 // Return each primary key value to the client
1225 unsigned long res_idx = 0;
1226 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1227 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1228 continue; // Suppress due to lack of permission
1230 osrfAppRespond( ctx,
1231 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1234 jsonObjectFree( obj );
1235 osrfAppRespondComplete( ctx, NULL );
1240 @brief Verify that we have a valid class reference.
1241 @param ctx Pointer to the method context.
1242 @param param Pointer to the method parameters.
1243 @return 1 if the class reference is valid, or zero if it isn't.
1245 The class of the method params must match the class to which the method id devoted.
1246 For PCRUD there are additional restrictions.
1248 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1250 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1251 osrfHash* class = osrfHashGet( method_meta, "class" );
1253 // Compare the method's class to the parameters' class
1254 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1256 // Oops -- they don't match. Complain.
1257 growing_buffer* msg = buffer_init( 128 );
1260 "%s: %s method for type %s was passed a %s",
1262 osrfHashGet( method_meta, "methodtype" ),
1263 osrfHashGet( class, "classname" ),
1264 param->classname ? param->classname : "(null)"
1267 char* m = buffer_release( msg );
1268 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1276 return verifyObjectPCRUD( ctx, param );
1282 @brief (PCRUD only) Verify that the user is properly logged in.
1283 @param ctx Pointer to the method context.
1284 @return If the user is logged in, a pointer to the user object from the authentication
1285 server; otherwise NULL.
1287 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1289 // Get the authkey (the first method parameter)
1290 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1292 // See if we have the same authkey, and a user object,
1293 // locally cached from a previous call
1294 const char* cached_authkey = getAuthkey( ctx );
1295 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1296 const jsonObject* cached_user = getUserLogin( ctx );
1301 // We have no matching authentication data in the cache. Authenticate from scratch.
1302 jsonObject* auth_object = jsonNewObject( auth );
1304 // Fetch the user object from the authentication server
1305 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1307 jsonObjectFree( auth_object );
1309 if( !user->classname || strcmp(user->classname, "au" )) {
1311 growing_buffer* msg = buffer_init( 128 );
1314 "%s: permacrud received a bad auth token: %s",
1319 char* m = buffer_release( msg );
1320 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1324 jsonObjectFree( user );
1328 setUserLogin( ctx, user );
1329 setAuthkey( ctx, auth );
1331 // Allow ourselves up to a second before we have to reset the login timeout.
1332 // It would be nice to use some fraction of the timeout interval enforced by the
1333 // authentication server, but that value is not readily available at this point.
1334 // Instead, we use a conservative default interval.
1335 time_next_reset = time( NULL ) + 1;
1341 @brief For PCRUD: Determine whether the current user may access the current row.
1342 @param ctx Pointer to the method context.
1343 @param obj Pointer to the row being potentially accessed.
1344 @return 1 if access is permitted, or 0 if it isn't.
1346 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1348 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1350 dbhandle = writehandle;
1352 // Figure out what class and method are involved
1353 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1354 osrfHash* class = osrfHashGet( method_metadata, "class" );
1355 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1357 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1358 // contexts we will do another lookup of the current row, even if we already have a
1359 // previously fetched row image, because the row image in hand may not include the
1360 // foreign key(s) that we need.
1362 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1363 // but they aren't implemented yet.
1366 if( *method_type == 's' || *method_type == 'i' ) {
1367 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1369 } else if( *method_type == 'u' || *method_type == 'd' ) {
1370 fetch = 1; // MUST go to the db for the object for update and delete
1373 // Get the appropriate permacrud entry from the IDL, depending on method type
1374 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1376 // No permacrud for this method type on this class
1378 growing_buffer* msg = buffer_init( 128 );
1381 "%s: %s on class %s has no permacrud IDL entry",
1383 osrfHashGet( method_metadata, "methodtype" ),
1384 osrfHashGet( class, "classname" )
1387 char* m = buffer_release( msg );
1388 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1389 "osrfMethodException", ctx->request, m );
1396 // Get the user id, and make sure the user is logged in
1397 const jsonObject* user = verifyUserPCRUD( ctx );
1399 return 0; // Not logged in? No access.
1401 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1403 // Get a list of permissions from the permacrud entry.
1404 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1405 if( permission->size == 0 ) {
1406 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1410 // Build a list of org units that own the row. This is fairly convoluted because there
1411 // are several different ways that an org unit may own the row, as defined by the
1414 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1415 // identifying an owning org_unit..
1416 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1418 // Foreign context adds a layer of indirection. The row points to some other row that
1419 // an org unit may own. The "jump" attribute, if present, adds another layer of
1421 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1423 // The following string array stores the list of org units. (We don't have a thingie
1424 // for storing lists of integers, so we fake it with a list of strings.)
1425 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1428 const char* pkey_value = NULL;
1429 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1430 // If the global_required attribute is present and true, then the only owning
1431 // org unit is the root org unit, i.e. the one with no parent.
1432 osrfLogDebug( OSRF_LOG_MARK,
1433 "global-level permissions required, fetching top of the org tree" );
1435 // check for perm at top of org tree
1436 const char* org_tree_root_id = org_tree_root( ctx );
1437 if( org_tree_root_id ) {
1438 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1439 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1441 osrfStringArrayFree( context_org_array );
1446 // If the global_required attribute is absent or false, then we look for
1447 // local and/or foreign context. In order to find the relevant foreign
1448 // keys, we must either read the relevant row from the database, or look at
1449 // the image of the row that we already have in memory.
1451 // Even if we have an image of the row in memory, that image may not include the
1452 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1453 // of the row to make sure that we have what we need.
1455 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1456 "fetching context org ids" );
1457 const char* pkey = osrfHashGet( class, "primarykey" );
1458 jsonObject *param = NULL;
1461 // There is no primary key, so we can't do a fresh lookup. Use the row
1462 // image that we already have. If it doesn't have everything we need, too bad.
1464 param = jsonObjectClone( obj );
1465 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1466 } else if( obj->classname ) {
1467 pkey_value = oilsFMGetStringConst( obj, pkey );
1469 param = jsonObjectClone( obj );
1470 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1473 pkey_value = jsonObjectGetString( obj );
1475 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1476 "of %s and retrieving from the database", pkey_value );
1480 // Fetch the row so that we can look at the foreign key(s)
1481 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1482 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1483 jsonObjectFree( _tmp_params );
1485 param = jsonObjectExtractIndex( _list, 0 );
1486 jsonObjectFree( _list );
1490 // The row doesn't exist. Complain, and deny access.
1491 osrfLogDebug( OSRF_LOG_MARK,
1492 "Object not found in the database with primary key %s of %s",
1495 growing_buffer* msg = buffer_init( 128 );
1498 "%s: no object found with primary key %s of %s",
1504 char* m = buffer_release( msg );
1505 osrfAppSessionStatus(
1507 OSRF_STATUS_INTERNALSERVERERROR,
1508 "osrfMethodException",
1517 if( local_context && local_context->size > 0 ) {
1518 // The IDL provides a list of column names for the foreign keys denoting
1519 // local context, i.e. columns identifying owing org units directly. Look up
1520 // the value of each one, and if it isn't null, add it to the list of org units.
1521 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1522 local_context->size );
1524 const char* lcontext = NULL;
1525 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1526 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1527 if( fkey_value ) { // if not null
1528 osrfStringArrayAdd( context_org_array, fkey_value );
1531 "adding class-local field %s (value: %s) to the context org list",
1533 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1539 if( foreign_context ) {
1540 unsigned long class_count = osrfHashGetCount( foreign_context );
1541 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1543 if( class_count > 0 ) {
1545 // The IDL provides a list of foreign key columns pointing to rows that
1546 // an org unit may own. Follow each link, identify the owning org unit,
1547 // and add it to the list.
1548 osrfHash* fcontext = NULL;
1549 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1550 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1551 // For each class to which a foreign key points:
1552 const char* class_name = osrfHashIteratorKey( class_itr );
1553 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1557 "%d foreign context fields(s) specified for class %s",
1558 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1562 // Get the name of the key field in the foreign table
1563 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1565 // Get the value of the foreign key pointing to the foreign table
1566 char* foreign_pkey_value =
1567 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1568 if( !foreign_pkey_value )
1569 continue; // Foreign key value is null; skip it
1571 // Look up the row to which the foreign key points
1572 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1573 jsonObject* _list = doFieldmapperSearch(
1574 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1576 jsonObject* _fparam = NULL;
1577 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1578 _fparam = jsonObjectExtractIndex( _list, 0 );
1580 jsonObjectFree( _tmp_params );
1581 jsonObjectFree( _list );
1583 // At this point _fparam either points to the row identified by the
1584 // foreign key, or it's NULL (no such row found).
1586 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1588 const char* bad_class = NULL; // For noting failed lookups
1590 bad_class = class_name; // Referenced row not found
1591 else if( jump_list ) {
1592 // Follow a chain of rows, linked by foreign keys, to find an owner
1593 const char* flink = NULL;
1595 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1596 // For each entry in the jump list. Each entry (i.e. flink) is
1597 // the name of a foreign key column in the current row.
1599 // From the IDL, get the linkage information for the next jump
1600 osrfHash* foreign_link_hash =
1601 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1603 // Get the class metadata for the class
1604 // to which the foreign key points
1605 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1606 osrfHashGet( foreign_link_hash, "class" ));
1608 // Get the name of the referenced key of that class
1609 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1611 // Get the value of the foreign key pointing to that class
1612 free( foreign_pkey_value );
1613 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1614 if( !foreign_pkey_value )
1615 break; // Foreign key is null; quit looking
1617 // Build a WHERE clause for the lookup
1618 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1621 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1622 _tmp_params, NULL, &err );
1624 // Get the resulting row
1625 jsonObjectFree( _fparam );
1626 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1627 _fparam = jsonObjectExtractIndex( _list, 0 );
1629 // Referenced row not found
1631 bad_class = osrfHashGet( foreign_link_hash, "class" );
1634 jsonObjectFree( _tmp_params );
1635 jsonObjectFree( _list );
1641 // We had a foreign key pointing to such-and-such a row, but then
1642 // we couldn't fetch that row. The data in the database are in an
1643 // inconsistent state; the database itself may even be corrupted.
1644 growing_buffer* msg = buffer_init( 128 );
1647 "%s: no object of class %s found with primary key %s of %s",
1651 foreign_pkey_value ? foreign_pkey_value : "(null)"
1654 char* m = buffer_release( msg );
1655 osrfAppSessionStatus(
1657 OSRF_STATUS_INTERNALSERVERERROR,
1658 "osrfMethodException",
1664 osrfHashIteratorFree( class_itr );
1665 free( foreign_pkey_value );
1666 jsonObjectFree( param );
1671 free( foreign_pkey_value );
1674 // Examine each context column of the foreign row,
1675 // and add its value to the list of org units.
1677 const char* foreign_field = NULL;
1678 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1679 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1680 osrfStringArrayAdd( context_org_array,
1681 oilsFMGetStringConst( _fparam, foreign_field ));
1682 osrfLogDebug( OSRF_LOG_MARK,
1683 "adding foreign class %s field %s (value: %s) "
1684 "to the context org list",
1687 osrfStringArrayGetString(
1688 context_org_array, context_org_array->size - 1 )
1692 jsonObjectFree( _fparam );
1696 osrfHashIteratorFree( class_itr );
1700 jsonObjectFree( param );
1703 const char* context_org = NULL;
1704 const char* perm = NULL;
1707 // For every combination of permission and context org unit: call a stored procedure
1708 // to determine if the user has this permission in the context of this org unit.
1709 // If the answer is yes at any point, then we're done, and the user has permission.
1710 // In other words permissions are additive.
1712 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1714 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1720 "Checking object permission [%s] for user %d "
1721 "on object %s (class %s) at org %d",
1725 osrfHashGet( class, "classname" ),
1729 result = dbi_conn_queryf(
1731 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1734 osrfHashGet( class, "classname" ),
1742 "Received a result for object permission [%s] "
1743 "for user %d on object %s (class %s) at org %d",
1747 osrfHashGet( class, "classname" ),
1751 if( dbi_result_first_row( result )) {
1752 jsonObject* return_val = oilsMakeJSONFromResult( result );
1753 const char* has_perm = jsonObjectGetString(
1754 jsonObjectGetKeyConst( return_val, "has_perm" ));
1758 "Status of object permission [%s] for user %d "
1759 "on object %s (class %s) at org %d is %s",
1763 osrfHashGet(class, "classname"),
1768 if( *has_perm == 't' )
1770 jsonObjectFree( return_val );
1773 dbi_result_free( result );
1778 int errnum = dbi_conn_error( writehandle, &msg );
1779 osrfLogWarning( OSRF_LOG_MARK,
1780 "Unable to call check object permissions: %d, %s",
1781 errnum, msg ? msg : "(No description available)" );
1782 if( !oilsIsDBConnected( writehandle ))
1783 osrfAppSessionPanic( ctx->session );
1787 osrfLogDebug( OSRF_LOG_MARK,
1788 "Checking non-object permission [%s] for user %d at org %d",
1789 perm, userid, atoi(context_org) );
1790 result = dbi_conn_queryf(
1792 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1799 osrfLogDebug( OSRF_LOG_MARK,
1800 "Received a result for permission [%s] for user %d at org %d",
1801 perm, userid, atoi( context_org ));
1802 if( dbi_result_first_row( result )) {
1803 jsonObject* return_val = oilsMakeJSONFromResult( result );
1804 const char* has_perm = jsonObjectGetString(
1805 jsonObjectGetKeyConst( return_val, "has_perm" ));
1806 osrfLogDebug( OSRF_LOG_MARK,
1807 "Status of permission [%s] for user %d at org %d is [%s]",
1808 perm, userid, atoi( context_org ), has_perm );
1809 if( *has_perm == 't' )
1811 jsonObjectFree( return_val );
1814 dbi_result_free( result );
1819 int errnum = dbi_conn_error( writehandle, &msg );
1820 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1821 errnum, msg ? msg : "(No description available)" );
1822 if( !oilsIsDBConnected( writehandle ))
1823 osrfAppSessionPanic( ctx->session );
1831 osrfStringArrayFree( context_org_array );
1837 @brief Look up the root of the org_unit tree.
1838 @param ctx Pointer to the method context.
1839 @return The id of the root org unit, as a character string.
1841 Query actor.org_unit where parent_ou is null, and return the id as a string.
1843 This function assumes that there is only one root org unit, i.e. that we
1844 have a single tree, not a forest.
1846 The calling code is responsible for freeing the returned string.
1848 static const char* org_tree_root( osrfMethodContext* ctx ) {
1850 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1851 static time_t last_lookup_time = 0;
1852 time_t current_time = time( NULL );
1854 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1855 // We successfully looked this up less than an hour ago.
1856 // It's not likely to have changed since then.
1857 return strdup( cached_root_id );
1859 last_lookup_time = current_time;
1862 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1863 jsonObject* result = doFieldmapperSearch(
1864 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1865 jsonObjectFree( where_clause );
1867 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1870 jsonObjectFree( result );
1872 growing_buffer* msg = buffer_init( 128 );
1873 OSRF_BUFFER_ADD( msg, modulename );
1874 OSRF_BUFFER_ADD( msg,
1875 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1877 char* m = buffer_release( msg );
1878 osrfAppSessionStatus( ctx->session,
1879 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1882 cached_root_id[ 0 ] = '\0';
1886 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1887 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1889 strcpy( cached_root_id, root_org_unit_id );
1890 jsonObjectFree( result );
1891 return cached_root_id;
1895 @brief Create a JSON_HASH with a single key/value pair.
1896 @param key The key of the key/value pair.
1897 @param value the value of the key/value pair.
1898 @return Pointer to a newly created jsonObject of type JSON_HASH.
1900 The value of the key/value is either a string or (if @a value is NULL) a null.
1902 static jsonObject* single_hash( const char* key, const char* value ) {
1904 if( ! key ) key = "";
1906 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1907 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1912 int doCreate( osrfMethodContext* ctx ) {
1913 if(osrfMethodVerifyContext( ctx )) {
1914 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1919 timeout_needs_resetting = 1;
1921 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1922 jsonObject* target = NULL;
1923 jsonObject* options = NULL;
1925 if( enforce_pcrud ) {
1926 target = jsonObjectGetIndex( ctx->params, 1 );
1927 options = jsonObjectGetIndex( ctx->params, 2 );
1929 target = jsonObjectGetIndex( ctx->params, 0 );
1930 options = jsonObjectGetIndex( ctx->params, 1 );
1933 if( !verifyObjectClass( ctx, target )) {
1934 osrfAppRespondComplete( ctx, NULL );
1938 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1940 const char* trans_id = getXactId( ctx );
1942 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1944 osrfAppSessionStatus(
1946 OSRF_STATUS_BADREQUEST,
1947 "osrfMethodException",
1949 "No active transaction -- required for CREATE"
1951 osrfAppRespondComplete( ctx, NULL );
1955 // The following test is harmless but redundant. If a class is
1956 // readonly, we don't register a create method for it.
1957 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1958 osrfAppSessionStatus(
1960 OSRF_STATUS_BADREQUEST,
1961 "osrfMethodException",
1963 "Cannot INSERT readonly class"
1965 osrfAppRespondComplete( ctx, NULL );
1969 // Set the last_xact_id
1970 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1972 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1973 trans_id, target->classname, index);
1974 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1977 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1979 dbhandle = writehandle;
1981 osrfHash* fields = osrfHashGet( meta, "fields" );
1982 char* pkey = osrfHashGet( meta, "primarykey" );
1983 char* seq = osrfHashGet( meta, "sequence" );
1985 growing_buffer* table_buf = buffer_init( 128 );
1986 growing_buffer* col_buf = buffer_init( 128 );
1987 growing_buffer* val_buf = buffer_init( 128 );
1989 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1990 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1991 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1992 buffer_add( val_buf,"VALUES (" );
1996 osrfHash* field = NULL;
1997 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1998 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2000 const char* field_name = osrfHashIteratorKey( field_itr );
2002 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2005 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2008 if( field_object && field_object->classname ) {
2009 value = oilsFMGetString(
2011 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2013 } else if( field_object && JSON_BOOL == field_object->type ) {
2014 if( jsonBoolIsTrue( field_object ) )
2015 value = strdup( "t" );
2017 value = strdup( "f" );
2019 value = jsonObjectToSimpleString( field_object );
2025 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2026 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2029 buffer_add( col_buf, field_name );
2031 if( !field_object || field_object->type == JSON_NULL ) {
2032 buffer_add( val_buf, "DEFAULT" );
2034 } else if( !strcmp( get_primitive( field ), "number" )) {
2035 const char* numtype = get_datatype( field );
2036 if( !strcmp( numtype, "INT8" )) {
2037 buffer_fadd( val_buf, "%lld", atoll( value ));
2039 } else if( !strcmp( numtype, "INT" )) {
2040 buffer_fadd( val_buf, "%d", atoi( value ));
2042 } else if( !strcmp( numtype, "NUMERIC" )) {
2043 buffer_fadd( val_buf, "%f", atof( value ));
2046 if( dbi_conn_quote_string( writehandle, &value )) {
2047 OSRF_BUFFER_ADD( val_buf, value );
2050 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2051 osrfAppSessionStatus(
2053 OSRF_STATUS_INTERNALSERVERERROR,
2054 "osrfMethodException",
2056 "Error quoting string -- please see the error log for more details"
2059 buffer_free( table_buf );
2060 buffer_free( col_buf );
2061 buffer_free( val_buf );
2062 osrfAppRespondComplete( ctx, NULL );
2070 osrfHashIteratorFree( field_itr );
2072 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2073 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2075 char* table_str = buffer_release( table_buf );
2076 char* col_str = buffer_release( col_buf );
2077 char* val_str = buffer_release( val_buf );
2078 growing_buffer* sql = buffer_init( 128 );
2079 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2084 char* query = buffer_release( sql );
2086 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2088 jsonObject* obj = NULL;
2091 dbi_result result = dbi_conn_query( writehandle, query );
2093 obj = jsonNewObject( NULL );
2095 int errnum = dbi_conn_error( writehandle, &msg );
2098 "%s ERROR inserting %s object using query [%s]: %d %s",
2100 osrfHashGet(meta, "fieldmapper"),
2103 msg ? msg : "(No description available)"
2105 osrfAppSessionStatus(
2107 OSRF_STATUS_INTERNALSERVERERROR,
2108 "osrfMethodException",
2110 "INSERT error -- please see the error log for more details"
2112 if( !oilsIsDBConnected( writehandle ))
2113 osrfAppSessionPanic( ctx->session );
2116 dbi_result_free( result );
2118 char* id = oilsFMGetString( target, pkey );
2120 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2121 growing_buffer* _id = buffer_init( 10 );
2122 buffer_fadd( _id, "%lld", new_id );
2123 id = buffer_release( _id );
2126 // Find quietness specification, if present
2127 const char* quiet_str = NULL;
2129 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2131 quiet_str = jsonObjectGetString( quiet_obj );
2134 if( str_is_true( quiet_str )) { // if quietness is specified
2135 obj = jsonNewObject( id );
2139 // Fetch the row that we just inserted, so that we can return it to the client
2140 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2141 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2144 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2148 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2150 jsonObjectFree( list );
2151 jsonObjectFree( where_clause );
2158 osrfAppRespondComplete( ctx, obj );
2159 jsonObjectFree( obj );
2164 @brief Implement the retrieve method.
2165 @param ctx Pointer to the method context.
2166 @param err Pointer through which to return an error code.
2167 @return If successful, a pointer to the result to be returned to the client;
2170 From the method's class, fetch a row with a specified value in the primary key. This
2171 method relies on the database design convention that a primary key consists of a single
2175 - authkey (PCRUD only)
2176 - value of the primary key for the desired row, for building the WHERE clause
2177 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2179 Return to client: One row from the query.
2181 int doRetrieve( osrfMethodContext* ctx ) {
2182 if(osrfMethodVerifyContext( ctx )) {
2183 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2188 timeout_needs_resetting = 1;
2193 if( enforce_pcrud ) {
2198 // Get the class metadata
2199 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2201 // Get the value of the primary key, from a method parameter
2202 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2206 "%s retrieving %s object with primary key value of %s",
2208 osrfHashGet( class_def, "fieldmapper" ),
2209 jsonObjectGetString( id_obj )
2212 // Build a WHERE clause based on the key value
2213 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2216 osrfHashGet( class_def, "primarykey" ), // name of key column
2217 jsonObjectClone( id_obj ) // value of key column
2220 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2224 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2226 jsonObjectFree( where_clause );
2228 osrfAppRespondComplete( ctx, NULL );
2232 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2233 jsonObjectFree( list );
2235 if( enforce_pcrud ) {
2236 if(!verifyObjectPCRUD( ctx, obj )) {
2237 jsonObjectFree( obj );
2239 growing_buffer* msg = buffer_init( 128 );
2240 OSRF_BUFFER_ADD( msg, modulename );
2241 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2243 char* m = buffer_release( msg );
2244 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2248 osrfAppRespondComplete( ctx, NULL );
2253 osrfAppRespondComplete( ctx, obj );
2254 jsonObjectFree( obj );
2259 @brief Translate a numeric value to a string representation for the database.
2260 @param field Pointer to the IDL field definition.
2261 @param value Pointer to a jsonObject holding the value of a field.
2262 @return Pointer to a newly allocated string.
2264 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2265 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2266 or (what is worse) valid SQL that is wrong.
2268 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2270 The calling code is responsible for freeing the resulting string by calling free().
2272 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2273 growing_buffer* val_buf = buffer_init( 32 );
2274 const char* numtype = get_datatype( field );
2276 // For historical reasons the following contains cruft that could be cleaned up.
2277 if( !strncmp( numtype, "INT", 3 ) ) {
2278 if( value->type == JSON_NUMBER )
2279 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2280 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2282 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2285 } else if( !strcmp( numtype, "NUMERIC" )) {
2286 if( value->type == JSON_NUMBER )
2287 buffer_fadd( val_buf, jsonObjectGetString( value ));
2289 buffer_fadd( val_buf, jsonObjectGetString( value ));
2293 // Presumably this was really intended to be a string, so quote it
2294 char* str = jsonObjectToSimpleString( value );
2295 if( dbi_conn_quote_string( dbhandle, &str )) {
2296 OSRF_BUFFER_ADD( val_buf, str );
2299 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2301 buffer_free( val_buf );
2306 return buffer_release( val_buf );
2309 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2310 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2311 growing_buffer* sql_buf = buffer_init( 32 );
2317 osrfHashGet( field, "name" )
2321 buffer_add( sql_buf, "IN (" );
2322 } else if( !strcasecmp( op,"not in" )) {
2323 buffer_add( sql_buf, "NOT IN (" );
2325 buffer_add( sql_buf, "IN (" );
2328 if( node->type == JSON_HASH ) {
2329 // subquery predicate
2330 char* subpred = buildQuery( ctx, node, SUBSELECT );
2332 buffer_free( sql_buf );
2336 buffer_add( sql_buf, subpred );
2339 } else if( node->type == JSON_ARRAY ) {
2340 // literal value list
2341 int in_item_index = 0;
2342 int in_item_first = 1;
2343 const jsonObject* in_item;
2344 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2349 buffer_add( sql_buf, ", " );
2352 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2353 osrfLogError( OSRF_LOG_MARK,
2354 "%s: Expected string or number within IN list; found %s",
2355 modulename, json_type( in_item->type ) );
2356 buffer_free( sql_buf );
2360 // Append the literal value -- quoted if not a number
2361 if( JSON_NUMBER == in_item->type ) {
2362 char* val = jsonNumberToDBString( field, in_item );
2363 OSRF_BUFFER_ADD( sql_buf, val );
2366 } else if( !strcmp( get_primitive( field ), "number" )) {
2367 char* val = jsonNumberToDBString( field, in_item );
2368 OSRF_BUFFER_ADD( sql_buf, val );
2372 char* key_string = jsonObjectToSimpleString( in_item );
2373 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2374 OSRF_BUFFER_ADD( sql_buf, key_string );
2377 osrfLogError( OSRF_LOG_MARK,
2378 "%s: Error quoting key string [%s]", modulename, key_string );
2380 buffer_free( sql_buf );
2386 if( in_item_first ) {
2387 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2388 buffer_free( sql_buf );
2392 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2393 modulename, json_type( node->type ));
2394 buffer_free( sql_buf );
2398 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2400 return buffer_release( sql_buf );
2403 // Receive a JSON_ARRAY representing a function call. The first
2404 // entry in the array is the function name. The rest are parameters.
2405 static char* searchValueTransform( const jsonObject* array ) {
2407 if( array->size < 1 ) {
2408 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2412 // Get the function name
2413 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2414 if( func_item->type != JSON_STRING ) {
2415 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2416 modulename, json_type( func_item->type ));
2420 growing_buffer* sql_buf = buffer_init( 32 );
2422 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2423 OSRF_BUFFER_ADD( sql_buf, "( " );
2425 // Get the parameters
2426 int func_item_index = 1; // We already grabbed the zeroth entry
2427 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2429 // Add a separator comma, if we need one
2430 if( func_item_index > 2 )
2431 buffer_add( sql_buf, ", " );
2433 // Add the current parameter
2434 if( func_item->type == JSON_NULL ) {
2435 buffer_add( sql_buf, "NULL" );
2437 char* val = jsonObjectToSimpleString( func_item );
2438 if( dbi_conn_quote_string( dbhandle, &val )) {
2439 OSRF_BUFFER_ADD( sql_buf, val );
2442 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2444 buffer_free( sql_buf );
2451 buffer_add( sql_buf, " )" );
2453 return buffer_release( sql_buf );
2456 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2457 const jsonObject* node, const char* op ) {
2459 if( ! is_good_operator( op ) ) {
2460 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2464 char* val = searchValueTransform( node );
2468 growing_buffer* sql_buf = buffer_init( 32 );
2473 osrfHashGet( field, "name" ),
2480 return buffer_release( sql_buf );
2483 // class_alias is a class name or other table alias
2484 // field is a field definition as stored in the IDL
2485 // node comes from the method parameter, and may represent an entry in the SELECT list
2486 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2487 const jsonObject* node ) {
2488 growing_buffer* sql_buf = buffer_init( 32 );
2490 const char* field_transform = jsonObjectGetString(
2491 jsonObjectGetKeyConst( node, "transform" ) );
2492 const char* transform_subcolumn = jsonObjectGetString(
2493 jsonObjectGetKeyConst( node, "result_field" ) );
2495 if( transform_subcolumn ) {
2496 if( ! is_identifier( transform_subcolumn ) ) {
2497 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2498 modulename, transform_subcolumn );
2499 buffer_free( sql_buf );
2502 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2505 if( field_transform ) {
2507 if( ! is_identifier( field_transform ) ) {
2508 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2509 modulename, field_transform );
2510 buffer_free( sql_buf );
2514 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2515 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2516 field_transform, class_alias, osrfHashGet( field, "name" ));
2518 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2519 field_transform, class_alias, osrfHashGet( field, "name" ));
2522 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2525 if( array->type != JSON_ARRAY ) {
2526 osrfLogError( OSRF_LOG_MARK,
2527 "%s: Expected JSON_ARRAY for function params; found %s",
2528 modulename, json_type( array->type ) );
2529 buffer_free( sql_buf );
2532 int func_item_index = 0;
2533 jsonObject* func_item;
2534 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2536 char* val = jsonObjectToSimpleString( func_item );
2539 buffer_add( sql_buf, ",NULL" );
2540 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2541 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2542 OSRF_BUFFER_ADD( sql_buf, val );
2544 osrfLogError( OSRF_LOG_MARK,
2545 "%s: Error quoting key string [%s]", modulename, val );
2547 buffer_free( sql_buf );
2554 buffer_add( sql_buf, " )" );
2557 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2560 if( transform_subcolumn )
2561 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2563 return buffer_release( sql_buf );
2566 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2567 const jsonObject* node, const char* op ) {
2569 if( ! is_good_operator( op ) ) {
2570 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2574 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2575 if( ! field_transform )
2578 int extra_parens = 0; // boolean
2580 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2582 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2584 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2586 free( field_transform );
2590 } else if( value_obj->type == JSON_ARRAY ) {
2591 value = searchValueTransform( value_obj );
2593 osrfLogError( OSRF_LOG_MARK,
2594 "%s: Error building value transform for field transform", modulename );
2595 free( field_transform );
2598 } else if( value_obj->type == JSON_HASH ) {
2599 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2601 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2603 free( field_transform );
2607 } else if( value_obj->type == JSON_NUMBER ) {
2608 value = jsonNumberToDBString( field, value_obj );
2609 } else if( value_obj->type == JSON_NULL ) {
2610 osrfLogError( OSRF_LOG_MARK,
2611 "%s: Error building predicate for field transform: null value", modulename );
2612 free( field_transform );
2614 } else if( value_obj->type == JSON_BOOL ) {
2615 osrfLogError( OSRF_LOG_MARK,
2616 "%s: Error building predicate for field transform: boolean value", modulename );
2617 free( field_transform );
2620 if( !strcmp( get_primitive( field ), "number") ) {
2621 value = jsonNumberToDBString( field, value_obj );
2623 value = jsonObjectToSimpleString( value_obj );
2624 if( !dbi_conn_quote_string( dbhandle, &value )) {
2625 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2626 modulename, value );
2628 free( field_transform );
2634 const char* left_parens = "";
2635 const char* right_parens = "";
2637 if( extra_parens ) {
2642 growing_buffer* sql_buf = buffer_init( 32 );
2646 "%s%s %s %s %s %s%s",
2657 free( field_transform );
2659 return buffer_release( sql_buf );
2662 static char* searchSimplePredicate( const char* op, const char* class_alias,
2663 osrfHash* field, const jsonObject* node ) {
2665 if( ! is_good_operator( op ) ) {
2666 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2672 // Get the value to which we are comparing the specified column
2673 if( node->type != JSON_NULL ) {
2674 if( node->type == JSON_NUMBER ) {
2675 val = jsonNumberToDBString( field, node );
2676 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2677 val = jsonNumberToDBString( field, node );
2679 val = jsonObjectToSimpleString( node );
2684 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2685 // Value is not numeric; enclose it in quotes
2686 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2687 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2694 // Compare to a null value
2695 val = strdup( "NULL" );
2696 if( strcmp( op, "=" ))
2702 growing_buffer* sql_buf = buffer_init( 32 );
2703 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2704 char* pred = buffer_release( sql_buf );
2711 static char* searchBETWEENPredicate( const char* class_alias,
2712 osrfHash* field, const jsonObject* node ) {
2714 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2715 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2717 if( NULL == y_node ) {
2718 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2721 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2722 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2729 if( !strcmp( get_primitive( field ), "number") ) {
2730 x_string = jsonNumberToDBString( field, x_node );
2731 y_string = jsonNumberToDBString( field, y_node );
2734 x_string = jsonObjectToSimpleString( x_node );
2735 y_string = jsonObjectToSimpleString( y_node );
2736 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2737 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2738 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2739 modulename, x_string, y_string );
2746 growing_buffer* sql_buf = buffer_init( 32 );
2747 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2748 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2752 return buffer_release( sql_buf );
2755 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2756 jsonObject* node, osrfMethodContext* ctx ) {
2759 if( node->type == JSON_ARRAY ) { // equality IN search
2760 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2761 } else if( node->type == JSON_HASH ) { // other search
2762 jsonIterator* pred_itr = jsonNewIterator( node );
2763 if( !jsonIteratorHasNext( pred_itr ) ) {
2764 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2765 modulename, osrfHashGet(field, "name" ));
2767 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2769 // Verify that there are no additional predicates
2770 if( jsonIteratorHasNext( pred_itr ) ) {
2771 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2772 modulename, osrfHashGet(field, "name" ));
2773 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2774 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2775 else if( !(strcasecmp( pred_itr->key,"in" ))
2776 || !(strcasecmp( pred_itr->key,"not in" )) )
2777 pred = searchINPredicate(
2778 class_info->alias, field, pred_node, pred_itr->key, ctx );
2779 else if( pred_node->type == JSON_ARRAY )
2780 pred = searchFunctionPredicate(
2781 class_info->alias, field, pred_node, pred_itr->key );
2782 else if( pred_node->type == JSON_HASH )
2783 pred = searchFieldTransformPredicate(
2784 class_info, field, pred_node, pred_itr->key );
2786 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2788 jsonIteratorFree( pred_itr );
2790 } else if( node->type == JSON_NULL ) { // IS NULL search
2791 growing_buffer* _p = buffer_init( 64 );
2794 "\"%s\".%s IS NULL",
2795 class_info->class_name,
2796 osrfHashGet( field, "name" )
2798 pred = buffer_release( _p );
2799 } else { // equality search
2800 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2819 field : call_number,
2835 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2837 const jsonObject* working_hash;
2838 jsonObject* freeable_hash = NULL;
2840 if( join_hash->type == JSON_HASH ) {
2841 working_hash = join_hash;
2842 } else if( join_hash->type == JSON_STRING ) {
2843 // turn it into a JSON_HASH by creating a wrapper
2844 // around a copy of the original
2845 const char* _tmp = jsonObjectGetString( join_hash );
2846 freeable_hash = jsonNewObjectType( JSON_HASH );
2847 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2848 working_hash = freeable_hash;
2852 "%s: JOIN failed; expected JSON object type not found",
2858 growing_buffer* join_buf = buffer_init( 128 );
2859 const char* leftclass = left_info->class_name;
2861 jsonObject* snode = NULL;
2862 jsonIterator* search_itr = jsonNewIterator( working_hash );
2864 while ( (snode = jsonIteratorNext( search_itr )) ) {
2865 const char* right_alias = search_itr->key;
2867 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2869 class = right_alias;
2871 const ClassInfo* right_info = add_joined_class( right_alias, class );
2875 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2879 jsonIteratorFree( search_itr );
2880 buffer_free( join_buf );
2882 jsonObjectFree( freeable_hash );
2885 osrfHash* links = right_info->links;
2886 const char* table = right_info->source_def;
2888 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2889 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2891 if( field && !fkey ) {
2892 // Look up the corresponding join column in the IDL.
2893 // The link must be defined in the child table,
2894 // and point to the right parent table.
2895 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2896 const char* reltype = NULL;
2897 const char* other_class = NULL;
2898 reltype = osrfHashGet( idl_link, "reltype" );
2899 if( reltype && strcmp( reltype, "has_many" ) )
2900 other_class = osrfHashGet( idl_link, "class" );
2901 if( other_class && !strcmp( other_class, leftclass ) )
2902 fkey = osrfHashGet( idl_link, "key" );
2906 "%s: JOIN failed. No link defined from %s.%s to %s",
2912 buffer_free( join_buf );
2914 jsonObjectFree( freeable_hash );
2915 jsonIteratorFree( search_itr );
2919 } else if( !field && fkey ) {
2920 // Look up the corresponding join column in the IDL.
2921 // The link must be defined in the child table,
2922 // and point to the right parent table.
2923 osrfHash* left_links = left_info->links;
2924 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2925 const char* reltype = NULL;
2926 const char* other_class = NULL;
2927 reltype = osrfHashGet( idl_link, "reltype" );
2928 if( reltype && strcmp( reltype, "has_many" ) )
2929 other_class = osrfHashGet( idl_link, "class" );
2930 if( other_class && !strcmp( other_class, class ) )
2931 field = osrfHashGet( idl_link, "key" );
2935 "%s: JOIN failed. No link defined from %s.%s to %s",
2941 buffer_free( join_buf );
2943 jsonObjectFree( freeable_hash );
2944 jsonIteratorFree( search_itr );
2948 } else if( !field && !fkey ) {
2949 osrfHash* left_links = left_info->links;
2951 // For each link defined for the left class:
2952 // see if the link references the joined class
2953 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2954 osrfHash* curr_link = NULL;
2955 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2956 const char* other_class = osrfHashGet( curr_link, "class" );
2957 if( other_class && !strcmp( other_class, class ) ) {
2959 // In the IDL, the parent class doesn't always know then names of the child
2960 // columns that are pointing to it, so don't use that end of the link
2961 const char* reltype = osrfHashGet( curr_link, "reltype" );
2962 if( reltype && strcmp( reltype, "has_many" ) ) {
2963 // Found a link between the classes
2964 fkey = osrfHashIteratorKey( itr );
2965 field = osrfHashGet( curr_link, "key" );
2970 osrfHashIteratorFree( itr );
2972 if( !field || !fkey ) {
2973 // Do another such search, with the classes reversed
2975 // For each link defined for the joined class:
2976 // see if the link references the left class
2977 osrfHashIterator* itr = osrfNewHashIterator( links );
2978 osrfHash* curr_link = NULL;
2979 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2980 const char* other_class = osrfHashGet( curr_link, "class" );
2981 if( other_class && !strcmp( other_class, leftclass ) ) {
2983 // In the IDL, the parent class doesn't know then names of the child
2984 // columns that are pointing to it, so don't use that end of the link
2985 const char* reltype = osrfHashGet( curr_link, "reltype" );
2986 if( reltype && strcmp( reltype, "has_many" ) ) {
2987 // Found a link between the classes
2988 field = osrfHashIteratorKey( itr );
2989 fkey = osrfHashGet( curr_link, "key" );
2994 osrfHashIteratorFree( itr );
2997 if( !field || !fkey ) {
3000 "%s: JOIN failed. No link defined between %s and %s",
3005 buffer_free( join_buf );
3007 jsonObjectFree( freeable_hash );
3008 jsonIteratorFree( search_itr );
3013 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3015 if( !strcasecmp( type,"left" )) {
3016 buffer_add( join_buf, " LEFT JOIN" );
3017 } else if( !strcasecmp( type,"right" )) {
3018 buffer_add( join_buf, " RIGHT JOIN" );
3019 } else if( !strcasecmp( type,"full" )) {
3020 buffer_add( join_buf, " FULL JOIN" );
3022 buffer_add( join_buf, " INNER JOIN" );
3025 buffer_add( join_buf, " INNER JOIN" );
3028 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3029 table, right_alias, right_alias, field, left_info->alias, fkey );
3031 // Add any other join conditions as specified by "filter"
3032 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3034 const char* filter_op = jsonObjectGetString(
3035 jsonObjectGetKeyConst( snode, "filter_op" ) );
3036 if( filter_op && !strcasecmp( "or",filter_op )) {
3037 buffer_add( join_buf, " OR " );
3039 buffer_add( join_buf, " AND " );
3042 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3044 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3045 OSRF_BUFFER_ADD( join_buf, jpred );
3050 "%s: JOIN failed. Invalid conditional expression.",
3053 jsonIteratorFree( search_itr );
3054 buffer_free( join_buf );
3056 jsonObjectFree( freeable_hash );
3061 buffer_add( join_buf, " ) " );
3063 // Recursively add a nested join, if one is present
3064 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3066 char* jpred = searchJOIN( join_filter, right_info );
3068 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3069 OSRF_BUFFER_ADD( join_buf, jpred );
3072 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3073 jsonIteratorFree( search_itr );
3074 buffer_free( join_buf );
3076 jsonObjectFree( freeable_hash );
3083 jsonObjectFree( freeable_hash );
3084 jsonIteratorFree( search_itr );
3086 return buffer_release( join_buf );
3091 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3092 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3093 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3095 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3097 search_hash is the JSON expression of the conditions.
3098 meta is the class definition from the IDL, for the relevant table.
3099 opjoin_type indicates whether multiple conditions, if present, should be
3100 connected by AND or OR.
3101 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3102 to pass it to other functions -- and all they do with it is to use the session
3103 and request members to send error messages back to the client.
3107 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3108 int opjoin_type, osrfMethodContext* ctx ) {
3112 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3113 "opjoin_type = %d, ctx addr = %p",
3116 class_info->class_def,
3121 growing_buffer* sql_buf = buffer_init( 128 );
3123 jsonObject* node = NULL;
3126 if( search_hash->type == JSON_ARRAY ) {
3127 if( 0 == search_hash->size ) {
3130 "%s: Invalid predicate structure: empty JSON array",
3133 buffer_free( sql_buf );
3137 unsigned long i = 0;
3138 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3142 if( opjoin_type == OR_OP_JOIN )
3143 buffer_add( sql_buf, " OR " );
3145 buffer_add( sql_buf, " AND " );
3148 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3150 buffer_free( sql_buf );
3154 buffer_fadd( sql_buf, "( %s )", subpred );
3158 } else if( search_hash->type == JSON_HASH ) {
3159 osrfLogDebug( OSRF_LOG_MARK,
3160 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3161 jsonIterator* search_itr = jsonNewIterator( search_hash );
3162 if( !jsonIteratorHasNext( search_itr ) ) {
3165 "%s: Invalid predicate structure: empty JSON object",
3168 jsonIteratorFree( search_itr );
3169 buffer_free( sql_buf );
3173 while( (node = jsonIteratorNext( search_itr )) ) {
3178 if( opjoin_type == OR_OP_JOIN )
3179 buffer_add( sql_buf, " OR " );
3181 buffer_add( sql_buf, " AND " );
3184 if( '+' == search_itr->key[ 0 ] ) {
3186 // This plus sign prefixes a class name or other table alias;
3187 // make sure the table alias is in scope
3188 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3189 if( ! alias_info ) {
3192 "%s: Invalid table alias \"%s\" in WHERE clause",
3196 jsonIteratorFree( search_itr );
3197 buffer_free( sql_buf );
3201 if( node->type == JSON_STRING ) {
3202 // It's the name of a column; make sure it belongs to the class
3203 const char* fieldname = jsonObjectGetString( node );
3204 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3207 "%s: Invalid column name \"%s\" in WHERE clause "
3208 "for table alias \"%s\"",
3213 jsonIteratorFree( search_itr );
3214 buffer_free( sql_buf );
3218 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3220 // It's something more complicated
3221 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3223 jsonIteratorFree( search_itr );
3224 buffer_free( sql_buf );
3228 buffer_fadd( sql_buf, "( %s )", subpred );
3231 } else if( '-' == search_itr->key[ 0 ] ) {
3232 if( !strcasecmp( "-or", search_itr->key )) {
3233 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3235 jsonIteratorFree( search_itr );
3236 buffer_free( sql_buf );
3240 buffer_fadd( sql_buf, "( %s )", subpred );
3242 } else if( !strcasecmp( "-and", search_itr->key )) {
3243 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3245 jsonIteratorFree( search_itr );
3246 buffer_free( sql_buf );
3250 buffer_fadd( sql_buf, "( %s )", subpred );
3252 } else if( !strcasecmp("-not",search_itr->key) ) {
3253 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3255 jsonIteratorFree( search_itr );
3256 buffer_free( sql_buf );
3260 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3262 } else if( !strcasecmp( "-exists", search_itr->key )) {
3263 char* subpred = buildQuery( ctx, node, SUBSELECT );
3265 jsonIteratorFree( search_itr );
3266 buffer_free( sql_buf );
3270 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3272 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3273 char* subpred = buildQuery( ctx, node, SUBSELECT );
3275 jsonIteratorFree( search_itr );
3276 buffer_free( sql_buf );
3280 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3282 } else { // Invalid "minus" operator
3285 "%s: Invalid operator \"%s\" in WHERE clause",
3289 jsonIteratorFree( search_itr );
3290 buffer_free( sql_buf );
3296 const char* class = class_info->class_name;
3297 osrfHash* fields = class_info->fields;
3298 osrfHash* field = osrfHashGet( fields, search_itr->key );
3301 const char* table = class_info->source_def;
3304 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3307 table ? table : "?",
3310 jsonIteratorFree( search_itr );
3311 buffer_free( sql_buf );
3315 char* subpred = searchPredicate( class_info, field, node, ctx );
3317 buffer_free( sql_buf );
3318 jsonIteratorFree( search_itr );
3322 buffer_add( sql_buf, subpred );
3326 jsonIteratorFree( search_itr );
3329 // ERROR ... only hash and array allowed at this level
3330 char* predicate_string = jsonObjectToJSON( search_hash );
3333 "%s: Invalid predicate structure: %s",
3337 buffer_free( sql_buf );
3338 free( predicate_string );
3342 return buffer_release( sql_buf );
3345 /* Build a JSON_ARRAY of field names for a given table alias
3347 static jsonObject* defaultSelectList( const char* table_alias ) {
3352 ClassInfo* class_info = search_all_alias( table_alias );
3353 if( ! class_info ) {
3356 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3363 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3364 osrfHash* field_def = NULL;
3365 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3366 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3367 const char* field_name = osrfHashIteratorKey( field_itr );
3368 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3369 jsonObjectPush( array, jsonNewObject( field_name ) );
3372 osrfHashIteratorFree( field_itr );
3377 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3378 // The jsonObject must be a JSON_HASH with an single entry for "union",
3379 // "intersect", or "except". The data associated with this key must be an
3380 // array of hashes, each hash being a query.
3381 // Also allowed but currently ignored: entries for "order_by" and "alias".
3382 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3384 if( ! combo || combo->type != JSON_HASH )
3385 return NULL; // should be impossible; validated by caller
3387 const jsonObject* query_array = NULL; // array of subordinate queries
3388 const char* op = NULL; // name of operator, e.g. UNION
3389 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3390 int op_count = 0; // for detecting conflicting operators
3391 int excepting = 0; // boolean
3392 int all = 0; // boolean
3393 jsonObject* order_obj = NULL;
3395 // Identify the elements in the hash
3396 jsonIterator* query_itr = jsonNewIterator( combo );
3397 jsonObject* curr_obj = NULL;
3398 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3399 if( ! strcmp( "union", query_itr->key ) ) {
3402 query_array = curr_obj;
3403 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3406 query_array = curr_obj;
3407 } else if( ! strcmp( "except", query_itr->key ) ) {
3411 query_array = curr_obj;
3412 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3415 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3418 order_obj = curr_obj;
3419 } else if( ! strcmp( "alias", query_itr->key ) ) {
3420 if( curr_obj->type != JSON_STRING ) {
3421 jsonIteratorFree( query_itr );
3424 alias = jsonObjectGetString( curr_obj );
3425 } else if( ! strcmp( "all", query_itr->key ) ) {
3426 if( obj_is_true( curr_obj ) )
3430 osrfAppSessionStatus(
3432 OSRF_STATUS_INTERNALSERVERERROR,
3433 "osrfMethodException",
3435 "Malformed query; unexpected entry in query object"
3439 "%s: Unexpected entry for \"%s\" in%squery",
3444 jsonIteratorFree( query_itr );
3448 jsonIteratorFree( query_itr );
3450 // More sanity checks
3451 if( ! query_array ) {
3453 osrfAppSessionStatus(
3455 OSRF_STATUS_INTERNALSERVERERROR,
3456 "osrfMethodException",
3458 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3462 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3465 return NULL; // should be impossible...
3466 } else if( op_count > 1 ) {
3468 osrfAppSessionStatus(
3470 OSRF_STATUS_INTERNALSERVERERROR,
3471 "osrfMethodException",
3473 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3477 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3481 } if( query_array->type != JSON_ARRAY ) {
3483 osrfAppSessionStatus(
3485 OSRF_STATUS_INTERNALSERVERERROR,
3486 "osrfMethodException",
3488 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3492 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3495 json_type( query_array->type )
3498 } if( query_array->size < 2 ) {
3500 osrfAppSessionStatus(
3502 OSRF_STATUS_INTERNALSERVERERROR,
3503 "osrfMethodException",
3505 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3509 "%s:%srequires multiple queries as operands",
3514 } else if( excepting && query_array->size > 2 ) {
3516 osrfAppSessionStatus(
3518 OSRF_STATUS_INTERNALSERVERERROR,
3519 "osrfMethodException",
3521 "EXCEPT operator has too many queries as operands"
3525 "%s:EXCEPT operator has too many queries as operands",
3529 } else if( order_obj && ! alias ) {
3531 osrfAppSessionStatus(
3533 OSRF_STATUS_INTERNALSERVERERROR,
3534 "osrfMethodException",
3536 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3540 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3546 // So far so good. Now build the SQL.
3547 growing_buffer* sql = buffer_init( 256 );
3549 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3550 // Add a layer of parentheses
3551 if( flags & SUBCOMBO )
3552 OSRF_BUFFER_ADD( sql, "( " );
3554 // Traverse the query array. Each entry should be a hash.
3555 int first = 1; // boolean
3557 jsonObject* query = NULL;
3558 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3559 if( query->type != JSON_HASH ) {
3561 osrfAppSessionStatus(
3563 OSRF_STATUS_INTERNALSERVERERROR,
3564 "osrfMethodException",
3566 "Malformed query under UNION, INTERSECT or EXCEPT"
3570 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3573 json_type( query->type )
3582 OSRF_BUFFER_ADD( sql, op );
3584 OSRF_BUFFER_ADD( sql, "ALL " );
3587 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3591 "%s: Error building query under%s",
3599 OSRF_BUFFER_ADD( sql, query_str );
3602 if( flags & SUBCOMBO )
3603 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3605 if( !(flags & SUBSELECT) )
3606 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3608 return buffer_release( sql );
3611 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3612 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3613 // or "except" to indicate the type of query.
3614 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3618 osrfAppSessionStatus(
3620 OSRF_STATUS_INTERNALSERVERERROR,
3621 "osrfMethodException",
3623 "Malformed query; no query object"
3625 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3627 } else if( query->type != JSON_HASH ) {
3629 osrfAppSessionStatus(
3631 OSRF_STATUS_INTERNALSERVERERROR,
3632 "osrfMethodException",
3634 "Malformed query object"
3638 "%s: Query object is %s instead of JSON_HASH",
3640 json_type( query->type )
3645 // Determine what kind of query it purports to be, and dispatch accordingly.
3646 if( jsonObjectGetKey( query, "union" ) ||
3647 jsonObjectGetKey( query, "intersect" ) ||
3648 jsonObjectGetKey( query, "except" ) ) {
3649 return doCombo( ctx, query, flags );
3651 // It is presumably a SELECT query
3653 // Push a node onto the stack for the current query. Every level of
3654 // subquery gets its own QueryFrame on the Stack.
3657 // Build an SQL SELECT statement
3660 jsonObjectGetKey( query, "select" ),
3661 jsonObjectGetKey( query, "from" ),
3662 jsonObjectGetKey( query, "where" ),
3663 jsonObjectGetKey( query, "having" ),
3664 jsonObjectGetKey( query, "order_by" ),
3665 jsonObjectGetKey( query, "limit" ),
3666 jsonObjectGetKey( query, "offset" ),
3675 /* method context */ osrfMethodContext* ctx,
3677 /* SELECT */ jsonObject* selhash,
3678 /* FROM */ jsonObject* join_hash,
3679 /* WHERE */ jsonObject* search_hash,
3680 /* HAVING */ jsonObject* having_hash,
3681 /* ORDER BY */ jsonObject* order_hash,
3682 /* LIMIT */ jsonObject* limit,
3683 /* OFFSET */ jsonObject* offset,
3684 /* flags */ int flags
3686 const char* locale = osrf_message_get_last_locale();
3688 // general tmp objects
3689 const jsonObject* tmp_const;
3690 jsonObject* selclass = NULL;
3691 jsonObject* snode = NULL;
3692 jsonObject* onode = NULL;
3694 char* string = NULL;
3695 int from_function = 0;
3700 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3702 // punt if there's no FROM clause
3703 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3706 "%s: FROM clause is missing or empty",
3710 osrfAppSessionStatus(
3712 OSRF_STATUS_INTERNALSERVERERROR,
3713 "osrfMethodException",
3715 "FROM clause is missing or empty in JSON query"
3720 // the core search class
3721 const char* core_class = NULL;
3723 // get the core class -- the only key of the top level FROM clause, or a string
3724 if( join_hash->type == JSON_HASH ) {
3725 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3726 snode = jsonIteratorNext( tmp_itr );
3728 // Populate the current QueryFrame with information
3729 // about the core class
3730 if( add_query_core( NULL, tmp_itr->key ) ) {
3732 osrfAppSessionStatus(
3734 OSRF_STATUS_INTERNALSERVERERROR,
3735 "osrfMethodException",
3737 "Unable to look up core class"
3741 core_class = curr_query->core.class_name;
3744 jsonObject* extra = jsonIteratorNext( tmp_itr );
3746 jsonIteratorFree( tmp_itr );
3749 // There shouldn't be more than one entry in join_hash
3753 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3757 osrfAppSessionStatus(
3759 OSRF_STATUS_INTERNALSERVERERROR,
3760 "osrfMethodException",
3762 "Malformed FROM clause in JSON query"
3764 return NULL; // Malformed join_hash; extra entry
3766 } else if( join_hash->type == JSON_ARRAY ) {
3767 // We're selecting from a function, not from a table
3769 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3772 } else if( join_hash->type == JSON_STRING ) {
3773 // Populate the current QueryFrame with information
3774 // about the core class
3775 core_class = jsonObjectGetString( join_hash );
3777 if( add_query_core( NULL, core_class ) ) {
3779 osrfAppSessionStatus(
3781 OSRF_STATUS_INTERNALSERVERERROR,
3782 "osrfMethodException",
3784 "Unable to look up core class"
3792 "%s: FROM clause is unexpected JSON type: %s",
3794 json_type( join_hash->type )
3797 osrfAppSessionStatus(
3799 OSRF_STATUS_INTERNALSERVERERROR,
3800 "osrfMethodException",
3802 "Ill-formed FROM clause in JSON query"
3807 // Build the join clause, if any, while filling out the list
3808 // of joined classes in the current QueryFrame.
3809 char* join_clause = NULL;
3810 if( join_hash && ! from_function ) {
3812 join_clause = searchJOIN( join_hash, &curr_query->core );
3813 if( ! join_clause ) {
3815 osrfAppSessionStatus(
3817 OSRF_STATUS_INTERNALSERVERERROR,
3818 "osrfMethodException",
3820 "Unable to construct JOIN clause(s)"
3826 // For in case we don't get a select list
3827 jsonObject* defaultselhash = NULL;
3829 // if there is no select list, build a default select list ...
3830 if( !selhash && !from_function ) {
3831 jsonObject* default_list = defaultSelectList( core_class );
3832 if( ! default_list ) {
3834 osrfAppSessionStatus(
3836 OSRF_STATUS_INTERNALSERVERERROR,
3837 "osrfMethodException",
3839 "Unable to build default SELECT clause in JSON query"
3841 free( join_clause );
3846 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3847 jsonObjectSetKey( selhash, core_class, default_list );
3850 // The SELECT clause can be encoded only by a hash
3851 if( !from_function && selhash->type != JSON_HASH ) {
3854 "%s: Expected JSON_HASH for SELECT clause; found %s",
3856 json_type( selhash->type )
3860 osrfAppSessionStatus(
3862 OSRF_STATUS_INTERNALSERVERERROR,
3863 "osrfMethodException",
3865 "Malformed SELECT clause in JSON query"
3867 free( join_clause );
3871 // If you see a null or wild card specifier for the core class, or an
3872 // empty array, replace it with a default SELECT list
3873 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3875 int default_needed = 0; // boolean
3876 if( JSON_STRING == tmp_const->type
3877 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3879 else if( JSON_NULL == tmp_const->type )
3882 if( default_needed ) {
3883 // Build a default SELECT list
3884 jsonObject* default_list = defaultSelectList( core_class );
3885 if( ! default_list ) {
3887 osrfAppSessionStatus(
3889 OSRF_STATUS_INTERNALSERVERERROR,
3890 "osrfMethodException",
3892 "Can't build default SELECT clause in JSON query"
3894 free( join_clause );
3899 jsonObjectSetKey( selhash, core_class, default_list );
3903 // temp buffers for the SELECT list and GROUP BY clause
3904 growing_buffer* select_buf = buffer_init( 128 );
3905 growing_buffer* group_buf = buffer_init( 128 );
3907 int aggregate_found = 0; // boolean
3909 // Build a select list
3910 if( from_function ) // From a function we select everything
3911 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3914 // Build the SELECT list as SQL
3918 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3919 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3921 const char* cname = selclass_itr->key;
3923 // Make sure the target relation is in the FROM clause.
3925 // At this point join_hash is a step down from the join_hash we
3926 // received as a parameter. If the original was a JSON_STRING,
3927 // then json_hash is now NULL. If the original was a JSON_HASH,
3928 // then json_hash is now the first (and only) entry in it,
3929 // denoting the core class. We've already excluded the
3930 // possibility that the original was a JSON_ARRAY, because in
3931 // that case from_function would be non-NULL, and we wouldn't
3934 // If the current table alias isn't in scope, bail out
3935 ClassInfo* class_info = search_alias( cname );
3936 if( ! class_info ) {
3939 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3944 osrfAppSessionStatus(
3946 OSRF_STATUS_INTERNALSERVERERROR,
3947 "osrfMethodException",
3949 "Selected class not in FROM clause in JSON query"
3951 jsonIteratorFree( selclass_itr );
3952 buffer_free( select_buf );
3953 buffer_free( group_buf );
3954 if( defaultselhash )
3955 jsonObjectFree( defaultselhash );
3956 free( join_clause );
3960 if( selclass->type != JSON_ARRAY ) {
3963 "%s: Malformed SELECT list for class \"%s\"; not an array",
3968 osrfAppSessionStatus(
3970 OSRF_STATUS_INTERNALSERVERERROR,
3971 "osrfMethodException",
3973 "Selected class not in FROM clause in JSON query"
3976 jsonIteratorFree( selclass_itr );
3977 buffer_free( select_buf );
3978 buffer_free( group_buf );
3979 if( defaultselhash )
3980 jsonObjectFree( defaultselhash );
3981 free( join_clause );
3985 // Look up some attributes of the current class
3986 osrfHash* idlClass = class_info->class_def;
3987 osrfHash* class_field_set = class_info->fields;
3988 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3989 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3991 if( 0 == selclass->size ) {
3994 "%s: No columns selected from \"%s\"",
4000 // stitch together the column list for the current table alias...
4001 unsigned long field_idx = 0;
4002 jsonObject* selfield = NULL;
4003 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4005 // If we need a separator comma, add one
4009 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4012 // if the field specification is a string, add it to the list
4013 if( selfield->type == JSON_STRING ) {
4015 // Look up the field in the IDL
4016 const char* col_name = jsonObjectGetString( selfield );
4017 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4019 // No such field in current class
4022 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4028 osrfAppSessionStatus(
4030 OSRF_STATUS_INTERNALSERVERERROR,
4031 "osrfMethodException",
4033 "Selected column not defined in JSON query"
4035 jsonIteratorFree( selclass_itr );
4036 buffer_free( select_buf );
4037 buffer_free( group_buf );
4038 if( defaultselhash )
4039 jsonObjectFree( defaultselhash );
4040 free( join_clause );
4042 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4043 // Virtual field not allowed
4046 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4052 osrfAppSessionStatus(
4054 OSRF_STATUS_INTERNALSERVERERROR,
4055 "osrfMethodException",
4057 "Selected column may not be virtual in JSON query"
4059 jsonIteratorFree( selclass_itr );
4060 buffer_free( select_buf );
4061 buffer_free( group_buf );
4062 if( defaultselhash )
4063 jsonObjectFree( defaultselhash );
4064 free( join_clause );
4070 if( flags & DISABLE_I18N )
4073 i18n = osrfHashGet( field_def, "i18n" );
4075 if( str_is_true( i18n ) ) {
4076 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4077 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4078 class_tname, cname, col_name, class_pkey,
4079 cname, class_pkey, locale, col_name );
4081 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4082 cname, col_name, col_name );
4085 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4086 cname, col_name, col_name );
4089 // ... but it could be an object, in which case we check for a Field Transform
4090 } else if( selfield->type == JSON_HASH ) {
4092 const char* col_name = jsonObjectGetString(
4093 jsonObjectGetKeyConst( selfield, "column" ) );
4095 // Get the field definition from the IDL
4096 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4098 // No such field in current class
4101 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4107 osrfAppSessionStatus(
4109 OSRF_STATUS_INTERNALSERVERERROR,
4110 "osrfMethodException",
4112 "Selected column is not defined in JSON query"
4114 jsonIteratorFree( selclass_itr );
4115 buffer_free( select_buf );
4116 buffer_free( group_buf );
4117 if( defaultselhash )
4118 jsonObjectFree( defaultselhash );
4119 free( join_clause );
4121 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4122 // No such field in current class
4125 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4131 osrfAppSessionStatus(
4133 OSRF_STATUS_INTERNALSERVERERROR,
4134 "osrfMethodException",
4136 "Selected column is virtual in JSON query"
4138 jsonIteratorFree( selclass_itr );
4139 buffer_free( select_buf );
4140 buffer_free( group_buf );
4141 if( defaultselhash )
4142 jsonObjectFree( defaultselhash );
4143 free( join_clause );
4147 // Decide what to use as a column alias
4149 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4150 _alias = jsonObjectGetString( tmp_const );
4151 } else { // Use field name as the alias
4155 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4156 char* transform_str = searchFieldTransform(
4157 class_info->alias, field_def, selfield );
4158 if( transform_str ) {
4159 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4160 free( transform_str );
4163 osrfAppSessionStatus(
4165 OSRF_STATUS_INTERNALSERVERERROR,
4166 "osrfMethodException",
4168 "Unable to generate transform function in JSON query"
4170 jsonIteratorFree( selclass_itr );
4171 buffer_free( select_buf );
4172 buffer_free( group_buf );
4173 if( defaultselhash )
4174 jsonObjectFree( defaultselhash );
4175 free( join_clause );
4182 if( flags & DISABLE_I18N )
4185 i18n = osrfHashGet( field_def, "i18n" );
4187 if( str_is_true( i18n ) ) {
4188 buffer_fadd( select_buf,
4189 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4190 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4191 class_tname, cname, col_name, class_pkey, cname,
4192 class_pkey, locale, _alias );
4194 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4195 cname, col_name, _alias );
4198 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4199 cname, col_name, _alias );
4206 "%s: Selected item is unexpected JSON type: %s",
4208 json_type( selfield->type )
4211 osrfAppSessionStatus(
4213 OSRF_STATUS_INTERNALSERVERERROR,
4214 "osrfMethodException",
4216 "Ill-formed SELECT item in JSON query"
4218 jsonIteratorFree( selclass_itr );
4219 buffer_free( select_buf );
4220 buffer_free( group_buf );
4221 if( defaultselhash )
4222 jsonObjectFree( defaultselhash );
4223 free( join_clause );
4227 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4228 if( obj_is_true( agg_obj ) )
4229 aggregate_found = 1;
4231 // Append a comma (except for the first one)
4232 // and add the column to a GROUP BY clause
4236 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4238 buffer_fadd( group_buf, " %d", sel_pos );
4242 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4244 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4245 if ( ! obj_is_true( aggregate_obj ) ) {
4249 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4252 buffer_fadd(group_buf, " %d", sel_pos);
4255 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4259 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4262 _column = searchFieldTransform(class_info->alias, field, selfield);
4263 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4264 OSRF_BUFFER_ADD(group_buf, _column);
4265 _column = searchFieldTransform(class_info->alias, field, selfield);
4272 } // end while -- iterating across SELECT columns
4274 } // end while -- iterating across classes
4276 jsonIteratorFree( selclass_itr );
4280 char* col_list = buffer_release( select_buf );
4282 // Make sure the SELECT list isn't empty. This can happen, for example,
4283 // if we try to build a default SELECT clause from a non-core table.
4286 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4288 osrfAppSessionStatus(
4290 OSRF_STATUS_INTERNALSERVERERROR,
4291 "osrfMethodException",
4293 "SELECT list is empty"
4296 buffer_free( group_buf );
4297 if( defaultselhash )
4298 jsonObjectFree( defaultselhash );
4299 free( join_clause );
4305 table = searchValueTransform( join_hash );
4307 table = strdup( curr_query->core.source_def );
4311 osrfAppSessionStatus(
4313 OSRF_STATUS_INTERNALSERVERERROR,
4314 "osrfMethodException",
4316 "Unable to identify table for core class"
4319 buffer_free( group_buf );
4320 if( defaultselhash )
4321 jsonObjectFree( defaultselhash );
4322 free( join_clause );
4326 // Put it all together
4327 growing_buffer* sql_buf = buffer_init( 128 );
4328 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4332 // Append the join clause, if any
4334 buffer_add(sql_buf, join_clause );
4335 free( join_clause );
4338 char* order_by_list = NULL;
4339 char* having_buf = NULL;
4341 if( !from_function ) {
4343 // Build a WHERE clause, if there is one
4345 buffer_add( sql_buf, " WHERE " );
4347 // and it's on the WHERE clause
4348 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4351 osrfAppSessionStatus(
4353 OSRF_STATUS_INTERNALSERVERERROR,
4354 "osrfMethodException",
4356 "Severe query error in WHERE predicate -- see error log for more details"
4359 buffer_free( group_buf );
4360 buffer_free( sql_buf );
4361 if( defaultselhash )
4362 jsonObjectFree( defaultselhash );
4366 buffer_add( sql_buf, pred );
4370 // Build a HAVING clause, if there is one
4373 // and it's on the the WHERE clause
4374 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4376 if( ! having_buf ) {
4378 osrfAppSessionStatus(
4380 OSRF_STATUS_INTERNALSERVERERROR,
4381 "osrfMethodException",
4383 "Severe query error in HAVING predicate -- see error log for more details"
4386 buffer_free( group_buf );
4387 buffer_free( sql_buf );
4388 if( defaultselhash )
4389 jsonObjectFree( defaultselhash );
4394 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4396 // Build an ORDER BY clause, if there is one
4397 if( NULL == order_hash )
4398 ; // No ORDER BY? do nothing
4399 else if( JSON_ARRAY == order_hash->type ) {
4400 // Array of field specifications, each specification being a
4401 // hash to define the class, field, and other details
4403 jsonObject* order_spec;
4404 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4406 if( JSON_HASH != order_spec->type ) {
4407 osrfLogError( OSRF_LOG_MARK,
4408 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4409 modulename, json_type( order_spec->type ) );
4411 osrfAppSessionStatus(
4413 OSRF_STATUS_INTERNALSERVERERROR,
4414 "osrfMethodException",
4416 "Malformed ORDER BY clause -- see error log for more details"
4418 buffer_free( order_buf );
4420 buffer_free( group_buf );
4421 buffer_free( sql_buf );
4422 if( defaultselhash )
4423 jsonObjectFree( defaultselhash );
4427 const char* class_alias =
4428 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4430 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4433 OSRF_BUFFER_ADD( order_buf, ", " );
4435 order_buf = buffer_init( 128 );
4437 if( !field || !class_alias ) {
4438 osrfLogError( OSRF_LOG_MARK,
4439 "%s: Missing class or field name in field specification "
4440 "of ORDER BY clause",
4443 osrfAppSessionStatus(
4445 OSRF_STATUS_INTERNALSERVERERROR,
4446 "osrfMethodException",
4448 "Malformed ORDER BY clause -- see error log for more details"
4450 buffer_free( order_buf );
4452 buffer_free( group_buf );
4453 buffer_free( sql_buf );
4454 if( defaultselhash )
4455 jsonObjectFree( defaultselhash );
4459 ClassInfo* order_class_info = search_alias( class_alias );
4460 if( ! order_class_info ) {
4461 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4462 "not in FROM clause", modulename, class_alias );
4464 osrfAppSessionStatus(
4466 OSRF_STATUS_INTERNALSERVERERROR,
4467 "osrfMethodException",
4469 "Invalid class referenced in ORDER BY clause -- "
4470 "see error log for more details"
4473 buffer_free( group_buf );
4474 buffer_free( sql_buf );
4475 if( defaultselhash )
4476 jsonObjectFree( defaultselhash );
4480 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4482 osrfLogError( OSRF_LOG_MARK,
4483 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4484 modulename, class_alias, field );
4486 osrfAppSessionStatus(
4488 OSRF_STATUS_INTERNALSERVERERROR,
4489 "osrfMethodException",
4491 "Invalid field referenced in ORDER BY clause -- "
4492 "see error log for more details"
4495 buffer_free( group_buf );
4496 buffer_free( sql_buf );
4497 if( defaultselhash )
4498 jsonObjectFree( defaultselhash );
4500 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4501 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4502 modulename, field );
4504 osrfAppSessionStatus(
4506 OSRF_STATUS_INTERNALSERVERERROR,
4507 "osrfMethodException",
4509 "Virtual field in ORDER BY clause -- see error log for more details"
4511 buffer_free( order_buf );
4513 buffer_free( group_buf );
4514 buffer_free( sql_buf );
4515 if( defaultselhash )
4516 jsonObjectFree( defaultselhash );
4520 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4521 char* transform_str = searchFieldTransform(
4522 class_alias, field_def, order_spec );
4523 if( ! transform_str ) {
4525 osrfAppSessionStatus(
4527 OSRF_STATUS_INTERNALSERVERERROR,
4528 "osrfMethodException",
4530 "Severe query error in ORDER BY clause -- "
4531 "see error log for more details"
4533 buffer_free( order_buf );
4535 buffer_free( group_buf );
4536 buffer_free( sql_buf );
4537 if( defaultselhash )
4538 jsonObjectFree( defaultselhash );
4542 OSRF_BUFFER_ADD( order_buf, transform_str );
4543 free( transform_str );
4546 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4548 const char* direction =
4549 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4551 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4552 OSRF_BUFFER_ADD( order_buf, " DESC" );
4554 OSRF_BUFFER_ADD( order_buf, " ASC" );
4557 } else if( JSON_HASH == order_hash->type ) {
4558 // This hash is keyed on class alias. Each class has either
4559 // an array of field names or a hash keyed on field name.
4560 jsonIterator* class_itr = jsonNewIterator( order_hash );
4561 while( (snode = jsonIteratorNext( class_itr )) ) {
4563 ClassInfo* order_class_info = search_alias( class_itr->key );
4564 if( ! order_class_info ) {
4565 osrfLogError( OSRF_LOG_MARK,
4566 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4567 modulename, class_itr->key );
4569 osrfAppSessionStatus(
4571 OSRF_STATUS_INTERNALSERVERERROR,
4572 "osrfMethodException",
4574 "Invalid class referenced in ORDER BY clause -- "
4575 "see error log for more details"
4577 jsonIteratorFree( class_itr );
4578 buffer_free( order_buf );
4580 buffer_free( group_buf );
4581 buffer_free( sql_buf );
4582 if( defaultselhash )
4583 jsonObjectFree( defaultselhash );
4587 osrfHash* field_list_def = order_class_info->fields;
4589 if( snode->type == JSON_HASH ) {
4591 // Hash is keyed on field names from the current class. For each field
4592 // there is another layer of hash to define the sorting details, if any,
4593 // or a string to indicate direction of sorting.
4594 jsonIterator* order_itr = jsonNewIterator( snode );
4595 while( (onode = jsonIteratorNext( order_itr )) ) {
4597 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4599 osrfLogError( OSRF_LOG_MARK,
4600 "%s: Invalid field \"%s\" in ORDER BY clause",
4601 modulename, order_itr->key );
4603 osrfAppSessionStatus(
4605 OSRF_STATUS_INTERNALSERVERERROR,
4606 "osrfMethodException",
4608 "Invalid field in ORDER BY clause -- "
4609 "see error log for more details"
4611 jsonIteratorFree( order_itr );
4612 jsonIteratorFree( class_itr );
4613 buffer_free( order_buf );
4615 buffer_free( group_buf );
4616 buffer_free( sql_buf );
4617 if( defaultselhash )
4618 jsonObjectFree( defaultselhash );
4620 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4621 osrfLogError( OSRF_LOG_MARK,
4622 "%s: Virtual field \"%s\" in ORDER BY clause",
4623 modulename, order_itr->key );
4625 osrfAppSessionStatus(
4627 OSRF_STATUS_INTERNALSERVERERROR,
4628 "osrfMethodException",
4630 "Virtual field in ORDER BY clause -- "
4631 "see error log for more details"
4633 jsonIteratorFree( order_itr );
4634 jsonIteratorFree( class_itr );
4635 buffer_free( order_buf );
4637 buffer_free( group_buf );
4638 buffer_free( sql_buf );
4639 if( defaultselhash )
4640 jsonObjectFree( defaultselhash );
4644 const char* direction = NULL;
4645 if( onode->type == JSON_HASH ) {
4646 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4647 string = searchFieldTransform(
4649 osrfHashGet( field_list_def, order_itr->key ),
4653 if( ctx ) osrfAppSessionStatus(
4655 OSRF_STATUS_INTERNALSERVERERROR,
4656 "osrfMethodException",
4658 "Severe query error in ORDER BY clause -- "
4659 "see error log for more details"
4661 jsonIteratorFree( order_itr );
4662 jsonIteratorFree( class_itr );
4664 buffer_free( group_buf );
4665 buffer_free( order_buf);
4666 buffer_free( sql_buf );
4667 if( defaultselhash )
4668 jsonObjectFree( defaultselhash );
4672 growing_buffer* field_buf = buffer_init( 16 );
4673 buffer_fadd( field_buf, "\"%s\".%s",
4674 class_itr->key, order_itr->key );
4675 string = buffer_release( field_buf );
4678 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4679 const char* dir = jsonObjectGetString( tmp_const );
4680 if(!strncasecmp( dir, "d", 1 )) {
4681 direction = " DESC";
4687 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4688 osrfLogError( OSRF_LOG_MARK,
4689 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4690 modulename, json_type( onode->type ) );
4692 osrfAppSessionStatus(
4694 OSRF_STATUS_INTERNALSERVERERROR,
4695 "osrfMethodException",
4697 "Malformed ORDER BY clause -- see error log for more details"
4699 jsonIteratorFree( order_itr );
4700 jsonIteratorFree( class_itr );
4702 buffer_free( group_buf );
4703 buffer_free( order_buf );
4704 buffer_free( sql_buf );
4705 if( defaultselhash )
4706 jsonObjectFree( defaultselhash );
4710 string = strdup( order_itr->key );
4711 const char* dir = jsonObjectGetString( onode );
4712 if( !strncasecmp( dir, "d", 1 )) {
4713 direction = " DESC";
4720 OSRF_BUFFER_ADD( order_buf, ", " );
4722 order_buf = buffer_init( 128 );
4724 OSRF_BUFFER_ADD( order_buf, string );
4728 OSRF_BUFFER_ADD( order_buf, direction );
4732 jsonIteratorFree( order_itr );
4734 } else if( snode->type == JSON_ARRAY ) {
4736 // Array is a list of fields from the current class
4737 unsigned long order_idx = 0;
4738 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4740 const char* _f = jsonObjectGetString( onode );
4742 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4744 osrfLogError( OSRF_LOG_MARK,
4745 "%s: Invalid field \"%s\" in ORDER BY clause",
4748 osrfAppSessionStatus(
4750 OSRF_STATUS_INTERNALSERVERERROR,
4751 "osrfMethodException",
4753 "Invalid field in ORDER BY clause -- "
4754 "see error log for more details"
4756 jsonIteratorFree( class_itr );
4757 buffer_free( order_buf );
4759 buffer_free( group_buf );
4760 buffer_free( sql_buf );
4761 if( defaultselhash )
4762 jsonObjectFree( defaultselhash );
4764 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4765 osrfLogError( OSRF_LOG_MARK,
4766 "%s: Virtual field \"%s\" in ORDER BY clause",
4769 osrfAppSessionStatus(
4771 OSRF_STATUS_INTERNALSERVERERROR,
4772 "osrfMethodException",
4774 "Virtual field in ORDER BY clause -- "
4775 "see error log for more details"
4777 jsonIteratorFree( class_itr );
4778 buffer_free( order_buf );
4780 buffer_free( group_buf );
4781 buffer_free( sql_buf );
4782 if( defaultselhash )
4783 jsonObjectFree( defaultselhash );
4788 OSRF_BUFFER_ADD( order_buf, ", " );
4790 order_buf = buffer_init( 128 );
4792 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4796 // IT'S THE OOOOOOOOOOOLD STYLE!
4798 osrfLogError( OSRF_LOG_MARK,
4799 "%s: Possible SQL injection attempt; direct order by is not allowed",
4802 osrfAppSessionStatus(
4804 OSRF_STATUS_INTERNALSERVERERROR,
4805 "osrfMethodException",
4807 "Severe query error -- see error log for more details"
4812 buffer_free( group_buf );
4813 buffer_free( order_buf );
4814 buffer_free( sql_buf );
4815 if( defaultselhash )
4816 jsonObjectFree( defaultselhash );
4817 jsonIteratorFree( class_itr );
4821 jsonIteratorFree( class_itr );
4823 osrfLogError( OSRF_LOG_MARK,
4824 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4825 modulename, json_type( order_hash->type ) );
4827 osrfAppSessionStatus(
4829 OSRF_STATUS_INTERNALSERVERERROR,
4830 "osrfMethodException",
4832 "Malformed ORDER BY clause -- see error log for more details"
4834 buffer_free( order_buf );
4836 buffer_free( group_buf );
4837 buffer_free( sql_buf );
4838 if( defaultselhash )
4839 jsonObjectFree( defaultselhash );
4844 order_by_list = buffer_release( order_buf );
4848 string = buffer_release( group_buf );
4850 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4851 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4852 OSRF_BUFFER_ADD( sql_buf, string );
4857 if( having_buf && *having_buf ) {
4858 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4859 OSRF_BUFFER_ADD( sql_buf, having_buf );
4863 if( order_by_list ) {
4865 if( *order_by_list ) {
4866 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4867 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4870 free( order_by_list );
4874 const char* str = jsonObjectGetString( limit );
4875 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4879 const char* str = jsonObjectGetString( offset );
4880 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4883 if( !(flags & SUBSELECT) )
4884 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4886 if( defaultselhash )
4887 jsonObjectFree( defaultselhash );
4889 return buffer_release( sql_buf );
4891 } // end of SELECT()
4893 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4895 const char* locale = osrf_message_get_last_locale();
4897 osrfHash* fields = osrfHashGet( meta, "fields" );
4898 char* core_class = osrfHashGet( meta, "classname" );
4900 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4902 jsonObject* node = NULL;
4903 jsonObject* snode = NULL;
4904 jsonObject* onode = NULL;
4905 const jsonObject* _tmp = NULL;
4906 jsonObject* selhash = NULL;
4907 jsonObject* defaultselhash = NULL;
4909 growing_buffer* sql_buf = buffer_init( 128 );
4910 growing_buffer* select_buf = buffer_init( 128 );
4912 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4913 defaultselhash = jsonNewObjectType( JSON_HASH );
4914 selhash = defaultselhash;
4917 // If there's no SELECT list for the core class, build one
4918 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4919 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4921 // Add every non-virtual field to the field list
4922 osrfHash* field_def = NULL;
4923 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4924 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4925 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4926 const char* field = osrfHashIteratorKey( field_itr );
4927 jsonObjectPush( field_list, jsonNewObject( field ) );
4930 osrfHashIteratorFree( field_itr );
4931 jsonObjectSetKey( selhash, core_class, field_list );
4935 jsonIterator* class_itr = jsonNewIterator( selhash );
4936 while( (snode = jsonIteratorNext( class_itr )) ) {
4938 const char* cname = class_itr->key;
4939 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4943 if( strcmp(core_class,class_itr->key )) {
4947 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4948 if( !found->size ) {
4949 jsonObjectFree( found );
4953 jsonObjectFree( found );
4956 jsonIterator* select_itr = jsonNewIterator( snode );
4957 while( (node = jsonIteratorNext( select_itr )) ) {
4958 const char* item_str = jsonObjectGetString( node );
4959 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4960 char* fname = osrfHashGet( field, "name" );
4968 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4973 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4974 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4977 i18n = osrfHashGet( field, "i18n" );
4979 if( str_is_true( i18n ) ) {
4980 char* pkey = osrfHashGet( idlClass, "primarykey" );
4981 char* tname = osrfHashGet( idlClass, "tablename" );
4983 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4984 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4985 tname, cname, fname, pkey, cname, pkey, locale, fname );
4987 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4990 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4994 jsonIteratorFree( select_itr );
4997 jsonIteratorFree( class_itr );
4999 char* col_list = buffer_release( select_buf );
5000 char* table = oilsGetRelation( meta );
5002 table = strdup( "(null)" );
5004 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5008 // Clear the query stack (as a fail-safe precaution against possible
5009 // leftover garbage); then push the first query frame onto the stack.
5010 clear_query_stack();
5012 if( add_query_core( NULL, core_class ) ) {
5014 osrfAppSessionStatus(
5016 OSRF_STATUS_INTERNALSERVERERROR,
5017 "osrfMethodException",
5019 "Unable to build query frame for core class"
5025 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5026 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5027 OSRF_BUFFER_ADD( sql_buf, join_clause );
5028 free( join_clause );
5031 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5032 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5034 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5036 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5038 osrfAppSessionStatus(
5040 OSRF_STATUS_INTERNALSERVERERROR,
5041 "osrfMethodException",
5043 "Severe query error -- see error log for more details"
5045 buffer_free( sql_buf );
5046 if( defaultselhash )
5047 jsonObjectFree( defaultselhash );
5048 clear_query_stack();
5051 buffer_add( sql_buf, pred );
5056 char* string = NULL;
5057 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5059 growing_buffer* order_buf = buffer_init( 128 );
5062 jsonIterator* class_itr = jsonNewIterator( _tmp );
5063 while( (snode = jsonIteratorNext( class_itr )) ) {
5065 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
5068 if( snode->type == JSON_HASH ) {
5070 jsonIterator* order_itr = jsonNewIterator( snode );
5071 while( (onode = jsonIteratorNext( order_itr )) ) {
5073 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5074 class_itr->key, order_itr->key );
5078 char* direction = NULL;
5079 if( onode->type == JSON_HASH ) {
5080 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5081 string = searchFieldTransform( class_itr->key, field_def, onode );
5083 osrfAppSessionStatus(
5085 OSRF_STATUS_INTERNALSERVERERROR,
5086 "osrfMethodException",
5088 "Severe query error in ORDER BY clause -- "
5089 "see error log for more details"
5091 jsonIteratorFree( order_itr );
5092 jsonIteratorFree( class_itr );
5093 buffer_free( order_buf );
5094 buffer_free( sql_buf );
5095 if( defaultselhash )
5096 jsonObjectFree( defaultselhash );
5097 clear_query_stack();
5101 growing_buffer* field_buf = buffer_init( 16 );
5102 buffer_fadd( field_buf, "\"%s\".%s",
5103 class_itr->key, order_itr->key );
5104 string = buffer_release( field_buf );
5107 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5108 const char* dir = jsonObjectGetString( _tmp );
5109 if(!strncasecmp( dir, "d", 1 )) {
5110 direction = " DESC";
5114 string = strdup( order_itr->key );
5115 const char* dir = jsonObjectGetString( onode );
5116 if( !strncasecmp( dir, "d", 1 )) {
5117 direction = " DESC";
5126 buffer_add( order_buf, ", " );
5129 buffer_add( order_buf, string );
5133 buffer_add( order_buf, direction );
5137 jsonIteratorFree( order_itr );
5140 const char* str = jsonObjectGetString( snode );
5141 buffer_add( order_buf, str );
5147 jsonIteratorFree( class_itr );
5149 string = buffer_release( order_buf );
5152 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5153 OSRF_BUFFER_ADD( sql_buf, string );
5159 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5160 const char* str = jsonObjectGetString( _tmp );
5168 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5170 const char* str = jsonObjectGetString( _tmp );
5179 if( defaultselhash )
5180 jsonObjectFree( defaultselhash );
5181 clear_query_stack();
5183 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5184 return buffer_release( sql_buf );
5187 int doJSONSearch ( osrfMethodContext* ctx ) {
5188 if(osrfMethodVerifyContext( ctx )) {
5189 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5193 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5197 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5201 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5202 flags |= SELECT_DISTINCT;
5204 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5205 flags |= DISABLE_I18N;
5207 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5208 clear_query_stack(); // a possibly needless precaution
5209 char* sql = buildQuery( ctx, hash, flags );
5210 clear_query_stack();
5217 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5220 dbhandle = writehandle;
5222 dbi_result result = dbi_conn_query( dbhandle, sql );
5225 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5227 if( dbi_result_first_row( result )) {
5228 /* JSONify the result */
5229 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5232 jsonObject* return_val = oilsMakeJSONFromResult( result );
5233 osrfAppRespond( ctx, return_val );
5234 jsonObjectFree( return_val );
5235 } while( dbi_result_next_row( result ));
5238 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5241 osrfAppRespondComplete( ctx, NULL );
5243 /* clean up the query */
5244 dbi_result_free( result );
5249 int errnum = dbi_conn_error( dbhandle, &msg );
5250 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5251 modulename, sql, errnum, msg ? msg : "(No description available)" );
5252 osrfAppSessionStatus(
5254 OSRF_STATUS_INTERNALSERVERERROR,
5255 "osrfMethodException",
5257 "Severe query error -- see error log for more details"
5259 if( !oilsIsDBConnected( dbhandle ))
5260 osrfAppSessionPanic( ctx->session );
5267 // The last parameter, err, is used to report an error condition by updating an int owned by
5268 // the calling code.
5270 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5271 // It is the responsibility of the calling code to initialize *err before the
5272 // call, so that it will be able to make sense of the result.
5274 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5275 // redundant anyway.
5276 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5277 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5280 dbhandle = writehandle;
5282 char* core_class = osrfHashGet( class_meta, "classname" );
5283 char* pkey = osrfHashGet( class_meta, "primarykey" );
5285 const jsonObject* _tmp;
5287 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5289 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5294 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5296 dbi_result result = dbi_conn_query( dbhandle, sql );
5297 if( NULL == result ) {
5299 int errnum = dbi_conn_error( dbhandle, &msg );
5300 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5301 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5302 msg ? msg : "(No description available)" );
5303 if( !oilsIsDBConnected( dbhandle ))
5304 osrfAppSessionPanic( ctx->session );
5305 osrfAppSessionStatus(
5307 OSRF_STATUS_INTERNALSERVERERROR,
5308 "osrfMethodException",
5310 "Severe query error -- see error log for more details"
5317 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5320 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5321 jsonObject* row_obj = NULL;
5323 if( dbi_result_first_row( result )) {
5325 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5326 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5327 // eliminate the duplicates.
5328 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5329 osrfHash* dedup = osrfNewHash();
5331 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5332 char* pkey_val = oilsFMGetString( row_obj, pkey );
5333 if( osrfHashGet( dedup, pkey_val ) ) {
5334 jsonObjectFree( row_obj );
5337 osrfHashSet( dedup, pkey_val, pkey_val );
5338 jsonObjectPush( res_list, row_obj );
5340 } while( dbi_result_next_row( result ));
5341 osrfHashFree( dedup );
5344 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5348 /* clean up the query */
5349 dbi_result_free( result );
5352 // If we're asked to flesh, and there's anything to flesh, then flesh it
5353 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5354 // something that he has no permission to look at).
5355 if( res_list->size && query_hash && ! enforce_pcrud ) {
5356 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5358 // Get the flesh depth
5359 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5360 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5361 flesh_depth = max_flesh_depth;
5363 // We need a non-zero flesh depth, and a list of fields to flesh
5364 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5365 if( temp_blob && flesh_depth > 0 ) {
5367 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5368 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5370 osrfStringArray* link_fields = NULL;
5371 osrfHash* links = osrfHashGet( class_meta, "links" );
5373 // Make an osrfStringArray of the names of fields to be fleshed
5374 if( flesh_fields ) {
5375 if( flesh_fields->size == 1 ) {
5376 const char* _t = jsonObjectGetString(
5377 jsonObjectGetIndex( flesh_fields, 0 ) );
5378 if( !strcmp( _t, "*" ))
5379 link_fields = osrfHashKeys( links );
5382 if( !link_fields ) {
5384 link_fields = osrfNewStringArray( 1 );
5385 jsonIterator* _i = jsonNewIterator( flesh_fields );
5386 while ((_f = jsonIteratorNext( _i ))) {
5387 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5389 jsonIteratorFree( _i );
5393 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5395 // Iterate over the JSON_ARRAY of rows
5397 unsigned long res_idx = 0;
5398 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5401 const char* link_field;
5403 // Iterate over the list of fleshable fields
5404 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5406 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5408 osrfHash* kid_link = osrfHashGet( links, link_field );
5410 continue; // Not a link field; skip it
5412 osrfHash* field = osrfHashGet( fields, link_field );
5414 continue; // Not a field at all; skip it (IDL is ill-formed)
5416 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5417 osrfHashGet( kid_link, "class" ));
5419 continue; // The class it links to doesn't exist; skip it
5421 const char* reltype = osrfHashGet( kid_link, "reltype" );
5423 continue; // No reltype; skip it (IDL is ill-formed)
5425 osrfHash* value_field = field;
5427 if( !strcmp( reltype, "has_many" )
5428 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5429 value_field = osrfHashGet(
5430 fields, osrfHashGet( class_meta, "primarykey" ) );
5433 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5435 if( link_map->size > 0 ) {
5436 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5439 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5444 osrfHashGet( kid_link, "class" ),
5451 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5452 osrfHashGet( kid_link, "field" ),
5453 osrfHashGet( kid_link, "class" ),
5454 osrfHashGet( kid_link, "key" ),
5455 osrfHashGet( kid_link, "reltype" )
5458 const char* search_key = jsonObjectGetString(
5459 jsonObjectGetIndex( cur,
5460 atoi( osrfHashGet( value_field, "array_position" ) )
5465 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5469 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5471 // construct WHERE clause
5472 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5475 osrfHashGet( kid_link, "key" ),
5476 jsonNewObject( search_key )
5479 // construct the rest of the query, mostly
5480 // by copying pieces of the previous level of query
5481 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5482 jsonObjectSetKey( rest_of_query, "flesh",
5483 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5487 jsonObjectSetKey( rest_of_query, "flesh_fields",
5488 jsonObjectClone( flesh_blob ));
5490 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5491 jsonObjectSetKey( rest_of_query, "order_by",
5492 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5496 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5497 jsonObjectSetKey( rest_of_query, "select",
5498 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5502 // do the query, recursively, to expand the fleshable field
5503 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5504 where_clause, rest_of_query, err );
5506 jsonObjectFree( where_clause );
5507 jsonObjectFree( rest_of_query );
5510 osrfStringArrayFree( link_fields );
5511 jsonObjectFree( res_list );
5512 jsonObjectFree( flesh_blob );
5516 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5517 osrfHashGet( kid_link, "class" ), kids->size );
5519 // Traverse the result set
5520 jsonObject* X = NULL;
5521 if( link_map->size > 0 && kids->size > 0 ) {
5523 kids = jsonNewObjectType( JSON_ARRAY );
5525 jsonObject* _k_node;
5526 unsigned long res_idx = 0;
5527 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5533 (unsigned long) atoi(
5539 osrfHashGet( kid_link, "class" )
5543 osrfStringArrayGetString( link_map, 0 )
5551 } // end while loop traversing X
5554 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5555 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5556 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5557 osrfHashGet( kid_link, "field" ));
5560 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5561 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5565 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5567 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5568 osrfHashGet( kid_link, "field" ) );
5571 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5572 jsonObjectClone( kids )
5577 jsonObjectFree( kids );
5581 jsonObjectFree( kids );
5583 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5584 osrfHashGet( kid_link, "field" ) );
5585 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5587 } // end while loop traversing list of fleshable fields
5588 } // end while loop traversing res_list
5589 jsonObjectFree( flesh_blob );
5590 osrfStringArrayFree( link_fields );
5599 int doUpdate( osrfMethodContext* ctx ) {
5600 if( osrfMethodVerifyContext( ctx )) {
5601 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5606 timeout_needs_resetting = 1;
5608 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5610 jsonObject* target = NULL;
5612 target = jsonObjectGetIndex( ctx->params, 1 );
5614 target = jsonObjectGetIndex( ctx->params, 0 );
5616 if(!verifyObjectClass( ctx, target )) {
5617 osrfAppRespondComplete( ctx, NULL );
5621 if( getXactId( ctx ) == NULL ) {
5622 osrfAppSessionStatus(
5624 OSRF_STATUS_BADREQUEST,
5625 "osrfMethodException",
5627 "No active transaction -- required for UPDATE"
5629 osrfAppRespondComplete( ctx, NULL );
5633 // The following test is harmless but redundant. If a class is
5634 // readonly, we don't register an update method for it.
5635 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5636 osrfAppSessionStatus(
5638 OSRF_STATUS_BADREQUEST,
5639 "osrfMethodException",
5641 "Cannot UPDATE readonly class"
5643 osrfAppRespondComplete( ctx, NULL );
5647 const char* trans_id = getXactId( ctx );
5649 // Set the last_xact_id
5650 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5652 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5653 trans_id, target->classname, index );
5654 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5657 char* pkey = osrfHashGet( meta, "primarykey" );
5658 osrfHash* fields = osrfHashGet( meta, "fields" );
5660 char* id = oilsFMGetString( target, pkey );
5664 "%s updating %s object with %s = %s",
5666 osrfHashGet( meta, "fieldmapper" ),
5671 dbhandle = writehandle;
5672 growing_buffer* sql = buffer_init( 128 );
5673 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5676 osrfHash* field_def = NULL;
5677 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5678 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5680 // Skip virtual fields, and the primary key
5681 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5684 const char* field_name = osrfHashIteratorKey( field_itr );
5685 if( ! strcmp( field_name, pkey ) )
5688 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5690 int value_is_numeric = 0; // boolean
5692 if( field_object && field_object->classname ) {
5693 value = oilsFMGetString(
5695 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5697 } else if( field_object && JSON_BOOL == field_object->type ) {
5698 if( jsonBoolIsTrue( field_object ) )
5699 value = strdup( "t" );
5701 value = strdup( "f" );
5703 value = jsonObjectToSimpleString( field_object );
5704 if( field_object && JSON_NUMBER == field_object->type )
5705 value_is_numeric = 1;
5708 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5709 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5711 if( !field_object || field_object->type == JSON_NULL ) {
5712 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5713 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5717 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5718 buffer_fadd( sql, " %s = NULL", field_name );
5721 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5725 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5727 const char* numtype = get_datatype( field_def );
5728 if( !strncmp( numtype, "INT", 3 ) ) {
5729 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5730 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5731 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5733 // Must really be intended as a string, so quote it
5734 if( dbi_conn_quote_string( dbhandle, &value )) {
5735 buffer_fadd( sql, " %s = %s", field_name, value );
5737 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5738 modulename, value );
5739 osrfAppSessionStatus(
5741 OSRF_STATUS_INTERNALSERVERERROR,
5742 "osrfMethodException",
5744 "Error quoting string -- please see the error log for more details"
5748 osrfHashIteratorFree( field_itr );
5750 osrfAppRespondComplete( ctx, NULL );
5755 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5758 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5762 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5763 buffer_fadd( sql, " %s = %s", field_name, value );
5765 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5766 osrfAppSessionStatus(
5768 OSRF_STATUS_INTERNALSERVERERROR,
5769 "osrfMethodException",
5771 "Error quoting string -- please see the error log for more details"
5775 osrfHashIteratorFree( field_itr );
5777 osrfAppRespondComplete( ctx, NULL );
5786 osrfHashIteratorFree( field_itr );
5788 jsonObject* obj = jsonNewObject( id );
5790 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5791 dbi_conn_quote_string( dbhandle, &id );
5793 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5795 char* query = buffer_release( sql );
5796 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5798 dbi_result result = dbi_conn_query( dbhandle, query );
5803 jsonObjectFree( obj );
5804 obj = jsonNewObject( NULL );
5806 int errnum = dbi_conn_error( dbhandle, &msg );
5809 "%s ERROR updating %s object with %s = %s: %d %s",
5811 osrfHashGet( meta, "fieldmapper" ),
5815 msg ? msg : "(No description available)"
5817 osrfAppSessionStatus(
5819 OSRF_STATUS_INTERNALSERVERERROR,
5820 "osrfMethodException",
5822 "Error in updating a row -- please see the error log for more details"
5824 if( !oilsIsDBConnected( dbhandle ))
5825 osrfAppSessionPanic( ctx->session );
5828 dbi_result_free( result );
5831 osrfAppRespondComplete( ctx, obj );
5832 jsonObjectFree( obj );
5836 int doDelete( osrfMethodContext* ctx ) {
5837 if( osrfMethodVerifyContext( ctx )) {
5838 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5843 timeout_needs_resetting = 1;
5845 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5847 if( getXactId( ctx ) == NULL ) {
5848 osrfAppSessionStatus(
5850 OSRF_STATUS_BADREQUEST,
5851 "osrfMethodException",
5853 "No active transaction -- required for DELETE"
5855 osrfAppRespondComplete( ctx, NULL );
5859 // The following test is harmless but redundant. If a class is
5860 // readonly, we don't register a delete method for it.
5861 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5862 osrfAppSessionStatus(
5864 OSRF_STATUS_BADREQUEST,
5865 "osrfMethodException",
5867 "Cannot DELETE readonly class"
5869 osrfAppRespondComplete( ctx, NULL );
5873 dbhandle = writehandle;
5875 char* pkey = osrfHashGet( meta, "primarykey" );
5882 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5883 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5884 osrfAppRespondComplete( ctx, NULL );
5888 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5890 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5891 osrfAppRespondComplete( ctx, NULL );
5894 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5899 "%s deleting %s object with %s = %s",
5901 osrfHashGet( meta, "fieldmapper" ),
5906 jsonObject* obj = jsonNewObject( id );
5908 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5909 dbi_conn_quote_string( writehandle, &id );
5911 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5912 osrfHashGet( meta, "tablename" ), pkey, id );
5917 jsonObjectFree( obj );
5918 obj = jsonNewObject( NULL );
5920 int errnum = dbi_conn_error( writehandle, &msg );
5923 "%s ERROR deleting %s object with %s = %s: %d %s",
5925 osrfHashGet( meta, "fieldmapper" ),
5929 msg ? msg : "(No description available)"
5931 osrfAppSessionStatus(
5933 OSRF_STATUS_INTERNALSERVERERROR,
5934 "osrfMethodException",
5936 "Error in deleting a row -- please see the error log for more details"
5938 if( !oilsIsDBConnected( writehandle ))
5939 osrfAppSessionPanic( ctx->session );
5941 dbi_result_free( result );
5945 osrfAppRespondComplete( ctx, obj );
5946 jsonObjectFree( obj );
5951 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5952 @param result An iterator for a result set; we only look at the current row.
5953 @param @meta Pointer to the class metadata for the core class.
5954 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5956 If a column is not defined in the IDL, or if it has no array_position defined for it in
5957 the IDL, or if it is defined as virtual, ignore it.
5959 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5960 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5961 array_position in the IDL.
5963 A field defined in the IDL but not represented in the returned row will leave a hole
5964 in the JSON_ARRAY. In effect it will be treated as a null value.
5966 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5967 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5968 classname corresponding to the @a meta argument.
5970 The calling code is responsible for freeing the the resulting jsonObject by calling
5973 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5974 if( !( result && meta )) return NULL;
5976 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5977 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5978 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5980 osrfHash* fields = osrfHashGet( meta, "fields" );
5982 int columnIndex = 1;
5983 const char* columnName;
5985 /* cycle through the columns in the row returned from the database */
5986 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5988 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5990 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5992 /* determine the field type and storage attributes */
5993 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5994 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5996 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5997 // or if it has no sequence number there, or if it's virtual, skip it.
5998 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6001 if( str_is_true( osrfHashGet( _f, "virtual" )))
6002 continue; // skip this column: IDL says it's virtual
6004 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6005 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6006 continue; // since we assign sequence numbers dynamically as we load the IDL.
6008 fmIndex = atoi( pos );
6009 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6011 continue; // This field is not defined in the IDL
6014 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6015 // sequence number from the IDL (which is likely to be different from the sequence
6016 // of columns in the SELECT clause).
6017 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6018 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6023 case DBI_TYPE_INTEGER :
6025 if( attr & DBI_INTEGER_SIZE8 )
6026 jsonObjectSetIndex( object, fmIndex,
6027 jsonNewNumberObject(
6028 dbi_result_get_longlong_idx( result, columnIndex )));
6030 jsonObjectSetIndex( object, fmIndex,
6031 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6035 case DBI_TYPE_DECIMAL :
6036 jsonObjectSetIndex( object, fmIndex,
6037 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6040 case DBI_TYPE_STRING :
6045 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6050 case DBI_TYPE_DATETIME : {
6052 char dt_string[ 256 ] = "";
6055 // Fetch the date column as a time_t
6056 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6058 // Translate the time_t to a human-readable string
6059 if( !( attr & DBI_DATETIME_DATE )) {
6060 gmtime_r( &_tmp_dt, &gmdt );
6061 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6062 } else if( !( attr & DBI_DATETIME_TIME )) {
6063 localtime_r( &_tmp_dt, &gmdt );
6064 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6066 localtime_r( &_tmp_dt, &gmdt );
6067 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6070 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6074 case DBI_TYPE_BINARY :
6075 osrfLogError( OSRF_LOG_MARK,
6076 "Can't do binary at column %s : index %d", columnName, columnIndex );
6085 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6086 if( !result ) return NULL;
6088 jsonObject* object = jsonNewObject( NULL );
6091 char dt_string[ 256 ];
6095 int columnIndex = 1;
6097 unsigned short type;
6098 const char* columnName;
6100 /* cycle through the column list */
6101 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6103 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6105 fmIndex = -1; // reset the position
6107 /* determine the field type and storage attributes */
6108 type = dbi_result_get_field_type_idx( result, columnIndex );
6109 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6111 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6112 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6117 case DBI_TYPE_INTEGER :
6119 if( attr & DBI_INTEGER_SIZE8 )
6120 jsonObjectSetKey( object, columnName,
6121 jsonNewNumberObject( dbi_result_get_longlong_idx(
6122 result, columnIndex )) );
6124 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6125 dbi_result_get_int_idx( result, columnIndex )) );
6128 case DBI_TYPE_DECIMAL :
6129 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6130 dbi_result_get_double_idx( result, columnIndex )) );
6133 case DBI_TYPE_STRING :
6134 jsonObjectSetKey( object, columnName,
6135 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6138 case DBI_TYPE_DATETIME :
6140 memset( dt_string, '\0', sizeof( dt_string ));
6141 memset( &gmdt, '\0', sizeof( gmdt ));
6143 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6145 if( !( attr & DBI_DATETIME_DATE )) {
6146 gmtime_r( &_tmp_dt, &gmdt );
6147 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6148 } else if( !( attr & DBI_DATETIME_TIME )) {
6149 localtime_r( &_tmp_dt, &gmdt );
6150 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6152 localtime_r( &_tmp_dt, &gmdt );
6153 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6156 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6159 case DBI_TYPE_BINARY :
6160 osrfLogError( OSRF_LOG_MARK,
6161 "Can't do binary at column %s : index %d", columnName, columnIndex );
6165 } // end while loop traversing result
6170 // Interpret a string as true or false
6171 int str_is_true( const char* str ) {
6172 if( NULL == str || strcasecmp( str, "true" ) )
6178 // Interpret a jsonObject as true or false
6179 static int obj_is_true( const jsonObject* obj ) {
6182 else switch( obj->type )
6190 if( strcasecmp( obj->value.s, "true" ) )
6194 case JSON_NUMBER : // Support 1/0 for perl's sake
6195 if( jsonObjectGetNumber( obj ) == 1.0 )
6204 // Translate a numeric code into a text string identifying a type of
6205 // jsonObject. To be used for building error messages.
6206 static const char* json_type( int code ) {
6212 return "JSON_ARRAY";
6214 return "JSON_STRING";
6216 return "JSON_NUMBER";
6222 return "(unrecognized)";
6226 // Extract the "primitive" attribute from an IDL field definition.
6227 // If we haven't initialized the app, then we must be running in
6228 // some kind of testbed. In that case, default to "string".
6229 static const char* get_primitive( osrfHash* field ) {
6230 const char* s = osrfHashGet( field, "primitive" );
6232 if( child_initialized )
6235 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6237 osrfHashGet( field, "name" )
6245 // Extract the "datatype" attribute from an IDL field definition.
6246 // If we haven't initialized the app, then we must be running in
6247 // some kind of testbed. In that case, default to to NUMERIC,
6248 // since we look at the datatype only for numbers.
6249 static const char* get_datatype( osrfHash* field ) {
6250 const char* s = osrfHashGet( field, "datatype" );
6252 if( child_initialized )
6255 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6257 osrfHashGet( field, "name" )
6266 @brief Determine whether a string is potentially a valid SQL identifier.
6267 @param s The identifier to be tested.
6268 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6270 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6271 need to follow all the rules exactly, such as requiring that the first character not
6274 We allow leading and trailing white space. In between, we do not allow punctuation
6275 (except for underscores and dollar signs), control characters, or embedded white space.
6277 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6278 for the foreseeable future such quoted identifiers are not likely to be an issue.
6280 int is_identifier( const char* s) {
6284 // Skip leading white space
6285 while( isspace( (unsigned char) *s ) )
6289 return 0; // Nothing but white space? Not okay.
6291 // Check each character until we reach white space or
6292 // end-of-string. Letters, digits, underscores, and
6293 // dollar signs are okay. With the exception of periods
6294 // (as in schema.identifier), control characters and other
6295 // punctuation characters are not okay. Anything else
6296 // is okay -- it could for example be part of a multibyte
6297 // UTF8 character such as a letter with diacritical marks,
6298 // and those are allowed.
6300 if( isalnum( (unsigned char) *s )
6304 ; // Fine; keep going
6305 else if( ispunct( (unsigned char) *s )
6306 || iscntrl( (unsigned char) *s ) )
6309 } while( *s && ! isspace( (unsigned char) *s ) );
6311 // If we found any white space in the above loop,
6312 // the rest had better be all white space.
6314 while( isspace( (unsigned char) *s ) )
6318 return 0; // White space was embedded within non-white space
6324 @brief Determine whether to accept a character string as a comparison operator.
6325 @param op The candidate comparison operator.
6326 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6328 We don't validate the operator for real. We just make sure that it doesn't contain
6329 any semicolons or white space (with special exceptions for a few specific operators).
6330 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6331 space but it's still not a valid operator, then the database will complain.
6333 Another approach would be to compare the string against a short list of approved operators.
6334 We don't do that because we want to allow custom operators like ">100*", which at this
6335 writing would be difficult or impossible to express otherwise in a JSON query.
6337 int is_good_operator( const char* op ) {
6338 if( !op ) return 0; // Sanity check
6342 if( isspace( (unsigned char) *s ) ) {
6343 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6344 // and IS NOT DISTINCT FROM.
6345 if( !strcasecmp( op, "similar to" ) )
6347 else if( !strcasecmp( op, "is distinct from" ) )
6349 else if( !strcasecmp( op, "is not distinct from" ) )
6354 else if( ';' == *s )
6362 @name Query Frame Management
6364 The following machinery supports a stack of query frames for use by SELECT().
6366 A query frame caches information about one level of a SELECT query. When we enter
6367 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6369 The query frame stores information about the core class, and about any joined classes
6372 The main purpose is to map table aliases to classes and tables, so that a query can
6373 join to the same table more than once. A secondary goal is to reduce the number of
6374 lookups in the IDL by caching the results.
6378 #define STATIC_CLASS_INFO_COUNT 3
6380 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6383 @brief Allocate a ClassInfo as raw memory.
6384 @return Pointer to the newly allocated ClassInfo.
6386 Except for the in_use flag, which is used only by the allocation and deallocation
6387 logic, we don't initialize the ClassInfo here.
6389 static ClassInfo* allocate_class_info( void ) {
6390 // In order to reduce the number of mallocs and frees, we return a static
6391 // instance of ClassInfo, if we can find one that we're not already using.
6392 // We rely on the fact that the compiler will implicitly initialize the
6393 // static instances so that in_use == 0.
6396 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6397 if( ! static_class_info[ i ].in_use ) {
6398 static_class_info[ i ].in_use = 1;
6399 return static_class_info + i;
6403 // The static ones are all in use. Malloc one.
6405 return safe_malloc( sizeof( ClassInfo ) );
6409 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6410 @param info Pointer to the ClassInfo to be cleared.
6412 static void clear_class_info( ClassInfo* info ) {
6417 // Free any malloc'd strings
6419 if( info->alias != info->alias_store )
6420 free( info->alias );
6422 if( info->class_name != info->class_name_store )
6423 free( info->class_name );
6425 free( info->source_def );
6427 info->alias = info->class_name = info->source_def = NULL;
6432 @brief Free a ClassInfo and everything it owns.
6433 @param info Pointer to the ClassInfo to be freed.
6435 static void free_class_info( ClassInfo* info ) {
6440 clear_class_info( info );
6442 // If it's one of the static instances, just mark it as not in use
6445 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6446 if( info == static_class_info + i ) {
6447 static_class_info[ i ].in_use = 0;
6452 // Otherwise it must have been malloc'd, so free it
6458 @brief Populate an already-allocated ClassInfo.
6459 @param info Pointer to the ClassInfo to be populated.
6460 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6462 @param class Name of the class.
6463 @return Zero if successful, or 1 if not.
6465 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6466 the relevant portions of the IDL for the specified class.
6468 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6471 osrfLogError( OSRF_LOG_MARK,
6472 "%s ERROR: No ClassInfo available to populate", modulename );
6473 info->alias = info->class_name = info->source_def = NULL;
6474 info->class_def = info->fields = info->links = NULL;
6479 osrfLogError( OSRF_LOG_MARK,
6480 "%s ERROR: No class name provided for lookup", modulename );
6481 info->alias = info->class_name = info->source_def = NULL;
6482 info->class_def = info->fields = info->links = NULL;
6486 // Alias defaults to class name if not supplied
6487 if( ! alias || ! alias[ 0 ] )
6490 // Look up class info in the IDL
6491 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6493 osrfLogError( OSRF_LOG_MARK,
6494 "%s ERROR: Class %s not defined in IDL", modulename, class );
6495 info->alias = info->class_name = info->source_def = NULL;
6496 info->class_def = info->fields = info->links = NULL;
6498 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6499 osrfLogError( OSRF_LOG_MARK,
6500 "%s ERROR: Class %s is defined as virtual", modulename, class );
6501 info->alias = info->class_name = info->source_def = NULL;
6502 info->class_def = info->fields = info->links = NULL;
6506 osrfHash* links = osrfHashGet( class_def, "links" );
6508 osrfLogError( OSRF_LOG_MARK,
6509 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6510 info->alias = info->class_name = info->source_def = NULL;
6511 info->class_def = info->fields = info->links = NULL;
6515 osrfHash* fields = osrfHashGet( class_def, "fields" );
6517 osrfLogError( OSRF_LOG_MARK,
6518 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6519 info->alias = info->class_name = info->source_def = NULL;
6520 info->class_def = info->fields = info->links = NULL;
6524 char* source_def = oilsGetRelation( class_def );
6528 // We got everything we need, so populate the ClassInfo
6529 if( strlen( alias ) > ALIAS_STORE_SIZE )
6530 info->alias = strdup( alias );
6532 strcpy( info->alias_store, alias );
6533 info->alias = info->alias_store;
6536 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6537 info->class_name = strdup( class );
6539 strcpy( info->class_name_store, class );
6540 info->class_name = info->class_name_store;
6543 info->source_def = source_def;
6545 info->class_def = class_def;
6546 info->links = links;
6547 info->fields = fields;
6552 #define STATIC_FRAME_COUNT 3
6554 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6557 @brief Allocate a QueryFrame as raw memory.
6558 @return Pointer to the newly allocated QueryFrame.
6560 Except for the in_use flag, which is used only by the allocation and deallocation
6561 logic, we don't initialize the QueryFrame here.
6563 static QueryFrame* allocate_frame( void ) {
6564 // In order to reduce the number of mallocs and frees, we return a static
6565 // instance of QueryFrame, if we can find one that we're not already using.
6566 // We rely on the fact that the compiler will implicitly initialize the
6567 // static instances so that in_use == 0.
6570 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6571 if( ! static_frame[ i ].in_use ) {
6572 static_frame[ i ].in_use = 1;
6573 return static_frame + i;
6577 // The static ones are all in use. Malloc one.
6579 return safe_malloc( sizeof( QueryFrame ) );
6583 @brief Free a QueryFrame, and all the memory it owns.
6584 @param frame Pointer to the QueryFrame to be freed.
6586 static void free_query_frame( QueryFrame* frame ) {
6591 clear_class_info( &frame->core );
6593 // Free the join list
6595 ClassInfo* info = frame->join_list;
6598 free_class_info( info );
6602 frame->join_list = NULL;
6605 // If the frame is a static instance, just mark it as unused
6607 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6608 if( frame == static_frame + i ) {
6609 static_frame[ i ].in_use = 0;
6614 // Otherwise it must have been malloc'd, so free it
6620 @brief Search a given QueryFrame for a specified alias.
6621 @param frame Pointer to the QueryFrame to be searched.
6622 @param target The alias for which to search.
6623 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6625 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6626 if( ! frame || ! target ) {
6630 ClassInfo* found_class = NULL;
6632 if( !strcmp( target, frame->core.alias ) )
6633 return &(frame->core);
6635 ClassInfo* curr_class = frame->join_list;
6636 while( curr_class ) {
6637 if( strcmp( target, curr_class->alias ) )
6638 curr_class = curr_class->next;
6640 found_class = curr_class;
6650 @brief Push a new (blank) QueryFrame onto the stack.
6652 static void push_query_frame( void ) {
6653 QueryFrame* frame = allocate_frame();
6654 frame->join_list = NULL;
6655 frame->next = curr_query;
6657 // Initialize the ClassInfo for the core class
6658 ClassInfo* core = &frame->core;
6659 core->alias = core->class_name = core->source_def = NULL;
6660 core->class_def = core->fields = core->links = NULL;
6666 @brief Pop a QueryFrame off the stack and destroy it.
6668 static void pop_query_frame( void ) {
6673 QueryFrame* popped = curr_query;
6674 curr_query = popped->next;
6676 free_query_frame( popped );
6680 @brief Populate the ClassInfo for the core class.
6681 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6682 class name as an alias.
6683 @param class_name Name of the core class.
6684 @return Zero if successful, or 1 if not.
6686 Populate the ClassInfo of the core class with copies of the alias and class name, and
6687 with pointers to the relevant portions of the IDL for the core class.
6689 static int add_query_core( const char* alias, const char* class_name ) {
6692 if( ! curr_query ) {
6693 osrfLogError( OSRF_LOG_MARK,
6694 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6696 } else if( curr_query->core.alias ) {
6697 osrfLogError( OSRF_LOG_MARK,
6698 "%s ERROR: Core class %s already populated as %s",
6699 modulename, curr_query->core.class_name, curr_query->core.alias );
6703 build_class_info( &curr_query->core, alias, class_name );
6704 if( curr_query->core.alias )
6707 osrfLogError( OSRF_LOG_MARK,
6708 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6714 @brief Search the current QueryFrame for a specified alias.
6715 @param target The alias for which to search.
6716 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6718 static inline ClassInfo* search_alias( const char* target ) {
6719 return search_alias_in_frame( curr_query, target );
6723 @brief Search all levels of query for a specified alias, starting with the current query.
6724 @param target The alias for which to search.
6725 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6727 static ClassInfo* search_all_alias( const char* target ) {
6728 ClassInfo* found_class = NULL;
6729 QueryFrame* curr_frame = curr_query;
6731 while( curr_frame ) {
6732 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6735 curr_frame = curr_frame->next;
6742 @brief Add a class to the list of classes joined to the current query.
6743 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6744 the class name as an alias.
6745 @param classname The name of the class to be added.
6746 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6748 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6750 if( ! classname || ! *classname ) { // sanity check
6751 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6758 const ClassInfo* conflict = search_alias( alias );
6760 osrfLogError( OSRF_LOG_MARK,
6761 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6762 modulename, alias, conflict->class_name );
6766 ClassInfo* info = allocate_class_info();
6768 if( build_class_info( info, alias, classname ) ) {
6769 free_class_info( info );
6773 // Add the new ClassInfo to the join list of the current QueryFrame
6774 info->next = curr_query->join_list;
6775 curr_query->join_list = info;
6781 @brief Destroy all nodes on the query stack.
6783 static void clear_query_stack( void ) {