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 );
757 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
758 osrfAppRespondComplete( ctx, ret );
759 jsonObjectFree( ret );
765 @brief Implement the savepoint.set method.
766 @param ctx Pointer to the method context.
767 @return Zero if successful, or -1 if not.
769 Issue a SAVEPOINT to the database server.
772 - authkey (PCRUD only)
775 Return to client: Savepoint name
777 int setSavepoint( osrfMethodContext* ctx ) {
778 if(osrfMethodVerifyContext( ctx )) {
779 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
784 if( enforce_pcrud ) {
786 timeout_needs_resetting = 1;
787 const jsonObject* user = verifyUserPCRUD( ctx );
792 // Verify that a transaction is pending
793 const char* trans_id = getXactId( ctx );
794 if( NULL == trans_id ) {
795 osrfAppSessionStatus(
797 OSRF_STATUS_INTERNALSERVERERROR,
798 "osrfMethodException",
800 "No active transaction -- required for savepoints"
805 // Get the savepoint name from the method params
806 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
808 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
811 int errnum = dbi_conn_error( writehandle, &msg );
814 "%s: Error creating savepoint %s in transaction %s: %d %s",
819 msg ? msg : "(No description available)"
821 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
822 "osrfMethodException", ctx->request, "Error creating savepoint" );
823 if( !oilsIsDBConnected( writehandle ))
824 osrfAppSessionPanic( ctx->session );
827 jsonObject* ret = jsonNewObject( spName );
828 osrfAppRespondComplete( ctx, ret );
829 jsonObjectFree( ret );
835 @brief Implement the savepoint.release method.
836 @param ctx Pointer to the method context.
837 @return Zero if successful, or -1 if not.
839 Issue a RELEASE SAVEPOINT to the database server.
842 - authkey (PCRUD only)
845 Return to client: Savepoint name
847 int releaseSavepoint( osrfMethodContext* ctx ) {
848 if(osrfMethodVerifyContext( ctx )) {
849 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
854 if( enforce_pcrud ) {
856 timeout_needs_resetting = 1;
857 const jsonObject* user = verifyUserPCRUD( ctx );
862 // Verify that a transaction is pending
863 const char* trans_id = getXactId( ctx );
864 if( NULL == trans_id ) {
865 osrfAppSessionStatus(
867 OSRF_STATUS_INTERNALSERVERERROR,
868 "osrfMethodException",
870 "No active transaction -- required for savepoints"
875 // Get the savepoint name from the method params
876 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
878 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
881 int errnum = dbi_conn_error( writehandle, &msg );
884 "%s: Error releasing savepoint %s in transaction %s: %d %s",
889 msg ? msg : "(No description available)"
891 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException", ctx->request, "Error releasing savepoint" );
893 if( !oilsIsDBConnected( writehandle ))
894 osrfAppSessionPanic( ctx->session );
897 jsonObject* ret = jsonNewObject( spName );
898 osrfAppRespondComplete( ctx, ret );
899 jsonObjectFree( ret );
905 @brief Implement the savepoint.rollback method.
906 @param ctx Pointer to the method context.
907 @return Zero if successful, or -1 if not.
909 Issue a ROLLBACK TO SAVEPOINT to the database server.
912 - authkey (PCRUD only)
915 Return to client: Savepoint name
917 int rollbackSavepoint( osrfMethodContext* ctx ) {
918 if(osrfMethodVerifyContext( ctx )) {
919 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
924 if( enforce_pcrud ) {
926 timeout_needs_resetting = 1;
927 const jsonObject* user = verifyUserPCRUD( ctx );
932 // Verify that a transaction is pending
933 const char* trans_id = getXactId( ctx );
934 if( NULL == trans_id ) {
935 osrfAppSessionStatus(
937 OSRF_STATUS_INTERNALSERVERERROR,
938 "osrfMethodException",
940 "No active transaction -- required for savepoints"
945 // Get the savepoint name from the method params
946 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
948 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
951 int errnum = dbi_conn_error( writehandle, &msg );
954 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
959 msg ? msg : "(No description available)"
961 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
962 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
963 if( !oilsIsDBConnected( writehandle ))
964 osrfAppSessionPanic( ctx->session );
967 jsonObject* ret = jsonNewObject( spName );
968 osrfAppRespondComplete( ctx, ret );
969 jsonObjectFree( ret );
975 @brief Implement the transaction.commit method.
976 @param ctx Pointer to the method context.
977 @return Zero if successful, or -1 if not.
979 Issue a COMMIT to the database server.
982 - authkey (PCRUD only)
984 Return to client: Transaction ID.
986 int commitTransaction( osrfMethodContext* ctx ) {
987 if(osrfMethodVerifyContext( ctx )) {
988 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
992 if( enforce_pcrud ) {
993 timeout_needs_resetting = 1;
994 const jsonObject* user = verifyUserPCRUD( ctx );
999 // Verify that a transaction is pending
1000 const char* trans_id = getXactId( ctx );
1001 if( NULL == trans_id ) {
1002 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1003 "osrfMethodException", ctx->request, "No active transaction to commit" );
1007 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1010 int errnum = dbi_conn_error( writehandle, &msg );
1011 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1012 modulename, errnum, msg ? msg : "(No description available)" );
1013 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1014 "osrfMethodException", ctx->request, "Error committing transaction" );
1015 if( !oilsIsDBConnected( writehandle ))
1016 osrfAppSessionPanic( ctx->session );
1019 jsonObject* ret = jsonNewObject( trans_id );
1020 osrfAppRespondComplete( ctx, ret );
1021 jsonObjectFree( ret );
1028 @brief Implement the transaction.rollback method.
1029 @param ctx Pointer to the method context.
1030 @return Zero if successful, or -1 if not.
1032 Issue a ROLLBACK to the database server.
1035 - authkey (PCRUD only)
1037 Return to client: Transaction ID
1039 int rollbackTransaction( osrfMethodContext* ctx ) {
1040 if( osrfMethodVerifyContext( ctx )) {
1041 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1045 if( enforce_pcrud ) {
1046 timeout_needs_resetting = 1;
1047 const jsonObject* user = verifyUserPCRUD( ctx );
1052 // Verify that a transaction is pending
1053 const char* trans_id = getXactId( ctx );
1054 if( NULL == trans_id ) {
1055 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1056 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1060 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1063 int errnum = dbi_conn_error( writehandle, &msg );
1064 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1065 modulename, errnum, msg ? msg : "(No description available)" );
1066 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1067 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1068 if( !oilsIsDBConnected( writehandle ))
1069 osrfAppSessionPanic( ctx->session );
1072 jsonObject* ret = jsonNewObject( trans_id );
1073 osrfAppRespondComplete( ctx, ret );
1074 jsonObjectFree( ret );
1081 @brief Implement the "search" method.
1082 @param ctx Pointer to the method context.
1083 @return Zero if successful, or -1 if not.
1086 - authkey (PCRUD only)
1087 - WHERE clause, as jsonObject
1088 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1090 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1091 Optionally flesh linked fields.
1093 int doSearch( osrfMethodContext* ctx ) {
1094 if( osrfMethodVerifyContext( ctx )) {
1095 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1100 timeout_needs_resetting = 1;
1102 jsonObject* where_clause;
1103 jsonObject* rest_of_query;
1105 if( enforce_pcrud ) {
1106 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1107 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1109 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1110 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1113 // Get the class metadata
1114 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1115 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1119 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1121 osrfAppRespondComplete( ctx, NULL );
1125 // Return each row to the client (except that some may be suppressed by PCRUD)
1126 jsonObject* cur = 0;
1127 unsigned long res_idx = 0;
1128 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1129 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1131 osrfAppRespond( ctx, cur );
1133 jsonObjectFree( obj );
1135 osrfAppRespondComplete( ctx, NULL );
1140 @brief Implement the "id_list" method.
1141 @param ctx Pointer to the method context.
1142 @param err Pointer through which to return an error code.
1143 @return Zero if successful, or -1 if not.
1146 - authkey (PCRUD only)
1147 - WHERE clause, as jsonObject
1148 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1150 Return to client: The primary key values for all rows of the relevant class that
1151 satisfy a specified WHERE clause.
1153 This method relies on the assumption that every class has a primary key consisting of
1156 int doIdList( osrfMethodContext* ctx ) {
1157 if( osrfMethodVerifyContext( ctx )) {
1158 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1163 timeout_needs_resetting = 1;
1165 jsonObject* where_clause;
1166 jsonObject* rest_of_query;
1168 // We use the where clause without change. But we need to massage the rest of the
1169 // query, so we work with a copy of it instead of modifying the original.
1171 if( enforce_pcrud ) {
1172 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1173 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1175 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1176 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1179 // Eliminate certain SQL clauses, if present.
1180 if( rest_of_query ) {
1181 jsonObjectRemoveKey( rest_of_query, "select" );
1182 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1183 jsonObjectRemoveKey( rest_of_query, "flesh" );
1184 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1186 rest_of_query = jsonNewObjectType( JSON_HASH );
1189 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1191 // Get the class metadata
1192 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1193 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1195 // Build a SELECT list containing just the primary key,
1196 // i.e. like { "classname":["keyname"] }
1197 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1199 // Load array with name of primary key
1200 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1201 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1202 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1204 jsonObjectSetKey( rest_of_query, "select", select_clause );
1209 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1211 jsonObjectFree( rest_of_query );
1213 osrfAppRespondComplete( ctx, NULL );
1217 // Return each primary key value to the client
1219 unsigned long res_idx = 0;
1220 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1221 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1222 continue; // Suppress due to lack of permission
1224 osrfAppRespond( ctx,
1225 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1228 jsonObjectFree( obj );
1229 osrfAppRespondComplete( ctx, NULL );
1234 @brief Verify that we have a valid class reference.
1235 @param ctx Pointer to the method context.
1236 @param param Pointer to the method parameters.
1237 @return 1 if the class reference is valid, or zero if it isn't.
1239 The class of the method params must match the class to which the method id devoted.
1240 For PCRUD there are additional restrictions.
1242 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1244 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1245 osrfHash* class = osrfHashGet( method_meta, "class" );
1247 // Compare the method's class to the parameters' class
1248 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1250 // Oops -- they don't match. Complain.
1251 growing_buffer* msg = buffer_init( 128 );
1254 "%s: %s method for type %s was passed a %s",
1256 osrfHashGet( method_meta, "methodtype" ),
1257 osrfHashGet( class, "classname" ),
1258 param->classname ? param->classname : "(null)"
1261 char* m = buffer_release( msg );
1262 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1270 return verifyObjectPCRUD( ctx, param );
1276 @brief (PCRUD only) Verify that the user is properly logged in.
1277 @param ctx Pointer to the method context.
1278 @return If the user is logged in, a pointer to the user object from the authentication
1279 server; otherwise NULL.
1281 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1283 // Get the authkey (the first method parameter)
1284 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1286 // See if we have the same authkey, and a user object,
1287 // locally cached from a previous call
1288 const char* cached_authkey = getAuthkey( ctx );
1289 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1290 const jsonObject* cached_user = getUserLogin( ctx );
1295 // We have no matching authentication data in the cache. Authenticate from scratch.
1296 jsonObject* auth_object = jsonNewObject( auth );
1298 // Fetch the user object from the authentication server
1299 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1301 jsonObjectFree( auth_object );
1303 if( !user->classname || strcmp(user->classname, "au" )) {
1305 growing_buffer* msg = buffer_init( 128 );
1308 "%s: permacrud received a bad auth token: %s",
1313 char* m = buffer_release( msg );
1314 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1318 jsonObjectFree( user );
1322 setUserLogin( ctx, user );
1323 setAuthkey( ctx, auth );
1325 // Allow ourselves up to a second before we have to reset the login timeout.
1326 // It would be nice to use some fraction of the timeout interval enforced by the
1327 // authentication server, but that value is not readily available at this point.
1328 // Instead, we use a conservative default interval.
1329 time_next_reset = time( NULL ) + 1;
1335 @brief For PCRUD: Determine whether the current user may access the current row.
1336 @param ctx Pointer to the method context.
1337 @param obj Pointer to the row being potentially accessed.
1338 @return 1 if access is permitted, or 0 if it isn't.
1340 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1342 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1344 dbhandle = writehandle;
1346 // Figure out what class and method are involved
1347 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1348 osrfHash* class = osrfHashGet( method_metadata, "class" );
1349 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1351 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1352 // contexts we will do another lookup of the current row, even if we already have a
1353 // previously fetched row image, because the row image in hand may not include the
1354 // foreign key(s) that we need.
1356 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1357 // but they aren't implemented yet.
1360 if( *method_type == 's' || *method_type == 'i' ) {
1361 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1363 } else if( *method_type == 'u' || *method_type == 'd' ) {
1364 fetch = 1; // MUST go to the db for the object for update and delete
1367 // Get the appropriate permacrud entry from the IDL, depending on method type
1368 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1370 // No permacrud for this method type on this class
1372 growing_buffer* msg = buffer_init( 128 );
1375 "%s: %s on class %s has no permacrud IDL entry",
1377 osrfHashGet( method_metadata, "methodtype" ),
1378 osrfHashGet( class, "classname" )
1381 char* m = buffer_release( msg );
1382 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1383 "osrfMethodException", ctx->request, m );
1390 // Get the user id, and make sure the user is logged in
1391 const jsonObject* user = verifyUserPCRUD( ctx );
1393 return 0; // Not logged in? No access.
1395 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1397 // Get a list of permissions from the permacrud entry.
1398 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1399 if( permission->size == 0 ) {
1400 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1404 // Build a list of org units that own the row. This is fairly convoluted because there
1405 // are several different ways that an org unit may own the row, as defined by the
1408 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1409 // identifying an owning org_unit..
1410 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1412 // Foreign context adds a layer of indirection. The row points to some other row that
1413 // an org unit may own. The "jump" attribute, if present, adds another layer of
1415 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1417 // The following string array stores the list of org units. (We don't have a thingie
1418 // for storing lists of integers, so we fake it with a list of strings.)
1419 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1422 const char* pkey_value = NULL;
1423 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1424 // If the global_required attribute is present and true, then the only owning
1425 // org unit is the root org unit, i.e. the one with no parent.
1426 osrfLogDebug( OSRF_LOG_MARK,
1427 "global-level permissions required, fetching top of the org tree" );
1429 // check for perm at top of org tree
1430 const char* org_tree_root_id = org_tree_root( ctx );
1431 if( org_tree_root_id ) {
1432 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1433 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1435 osrfStringArrayFree( context_org_array );
1440 // If the global_required attribute is absent or false, then we look for
1441 // local and/or foreign context. In order to find the relevant foreign
1442 // keys, we must either read the relevant row from the database, or look at
1443 // the image of the row that we already have in memory.
1445 // Even if we have an image of the row in memory, that image may not include the
1446 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1447 // of the row to make sure that we have what we need.
1449 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1450 "fetching context org ids" );
1451 const char* pkey = osrfHashGet( class, "primarykey" );
1452 jsonObject *param = NULL;
1455 // There is no primary key, so we can't do a fresh lookup. Use the row
1456 // image that we already have. If it doesn't have everything we need, too bad.
1458 param = jsonObjectClone( obj );
1459 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1460 } else if( obj->classname ) {
1461 pkey_value = oilsFMGetStringConst( obj, pkey );
1463 param = jsonObjectClone( obj );
1464 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1467 pkey_value = jsonObjectGetString( obj );
1469 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1470 "of %s and retrieving from the database", pkey_value );
1474 // Fetch the row so that we can look at the foreign key(s)
1475 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1476 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1477 jsonObjectFree( _tmp_params );
1479 param = jsonObjectExtractIndex( _list, 0 );
1480 jsonObjectFree( _list );
1484 // The row doesn't exist. Complain, and deny access.
1485 osrfLogDebug( OSRF_LOG_MARK,
1486 "Object not found in the database with primary key %s of %s",
1489 growing_buffer* msg = buffer_init( 128 );
1492 "%s: no object found with primary key %s of %s",
1498 char* m = buffer_release( msg );
1499 osrfAppSessionStatus(
1501 OSRF_STATUS_INTERNALSERVERERROR,
1502 "osrfMethodException",
1511 if( local_context && local_context->size > 0 ) {
1512 // The IDL provides a list of column names for the foreign keys denoting
1513 // local context, i.e. columns identifying owing org units directly. Look up
1514 // the value of each one, and if it isn't null, add it to the list of org units.
1515 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1516 local_context->size );
1518 const char* lcontext = NULL;
1519 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1520 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1521 if( fkey_value ) { // if not null
1522 osrfStringArrayAdd( context_org_array, fkey_value );
1525 "adding class-local field %s (value: %s) to the context org list",
1527 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1533 if( foreign_context ) {
1534 unsigned long class_count = osrfHashGetCount( foreign_context );
1535 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1537 if( class_count > 0 ) {
1539 // The IDL provides a list of foreign key columns pointing to rows that
1540 // an org unit may own. Follow each link, identify the owning org unit,
1541 // and add it to the list.
1542 osrfHash* fcontext = NULL;
1543 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1544 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1545 // For each class to which a foreign key points:
1546 const char* class_name = osrfHashIteratorKey( class_itr );
1547 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1551 "%d foreign context fields(s) specified for class %s",
1552 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1556 // Get the name of the key field in the foreign table
1557 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1559 // Get the value of the foreign key pointing to the foreign table
1560 char* foreign_pkey_value =
1561 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1562 if( !foreign_pkey_value )
1563 continue; // Foreign key value is null; skip it
1565 // Look up the row to which the foreign key points
1566 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1567 jsonObject* _list = doFieldmapperSearch(
1568 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1570 jsonObject* _fparam = NULL;
1571 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1572 _fparam = jsonObjectExtractIndex( _list, 0 );
1574 jsonObjectFree( _tmp_params );
1575 jsonObjectFree( _list );
1577 // At this point _fparam either points to the row identified by the
1578 // foreign key, or it's NULL (no such row found).
1580 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1582 const char* bad_class = NULL; // For noting failed lookups
1584 bad_class = class_name; // Referenced row not found
1585 else if( jump_list ) {
1586 // Follow a chain of rows, linked by foreign keys, to find an owner
1587 const char* flink = NULL;
1589 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1590 // For each entry in the jump list. Each entry (i.e. flink) is
1591 // the name of a foreign key column in the current row.
1593 // From the IDL, get the linkage information for the next jump
1594 osrfHash* foreign_link_hash =
1595 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1597 // Get the class metadata for the class
1598 // to which the foreign key points
1599 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1600 osrfHashGet( foreign_link_hash, "class" ));
1602 // Get the name of the referenced key of that class
1603 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1605 // Get the value of the foreign key pointing to that class
1606 free( foreign_pkey_value );
1607 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1608 if( !foreign_pkey_value )
1609 break; // Foreign key is null; quit looking
1611 // Build a WHERE clause for the lookup
1612 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1615 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1616 _tmp_params, NULL, &err );
1618 // Get the resulting row
1619 jsonObjectFree( _fparam );
1620 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1621 _fparam = jsonObjectExtractIndex( _list, 0 );
1623 // Referenced row not found
1625 bad_class = osrfHashGet( foreign_link_hash, "class" );
1628 jsonObjectFree( _tmp_params );
1629 jsonObjectFree( _list );
1635 // We had a foreign key pointing to such-and-such a row, but then
1636 // we couldn't fetch that row. The data in the database are in an
1637 // inconsistent state; the database itself may even be corrupted.
1638 growing_buffer* msg = buffer_init( 128 );
1641 "%s: no object of class %s found with primary key %s of %s",
1645 foreign_pkey_value ? foreign_pkey_value : "(null)"
1648 char* m = buffer_release( msg );
1649 osrfAppSessionStatus(
1651 OSRF_STATUS_INTERNALSERVERERROR,
1652 "osrfMethodException",
1658 osrfHashIteratorFree( class_itr );
1659 free( foreign_pkey_value );
1660 jsonObjectFree( param );
1665 free( foreign_pkey_value );
1668 // Examine each context column of the foreign row,
1669 // and add its value to the list of org units.
1671 const char* foreign_field = NULL;
1672 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1673 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1674 osrfStringArrayAdd( context_org_array,
1675 oilsFMGetStringConst( _fparam, foreign_field ));
1676 osrfLogDebug( OSRF_LOG_MARK,
1677 "adding foreign class %s field %s (value: %s) "
1678 "to the context org list",
1681 osrfStringArrayGetString(
1682 context_org_array, context_org_array->size - 1 )
1686 jsonObjectFree( _fparam );
1690 osrfHashIteratorFree( class_itr );
1694 jsonObjectFree( param );
1697 const char* context_org = NULL;
1698 const char* perm = NULL;
1701 // For every combination of permission and context org unit: call a stored procedure
1702 // to determine if the user has this permission in the context of this org unit.
1703 // If the answer is yes at any point, then we're done, and the user has permission.
1704 // In other words permissions are additive.
1706 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1708 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1714 "Checking object permission [%s] for user %d "
1715 "on object %s (class %s) at org %d",
1719 osrfHashGet( class, "classname" ),
1723 result = dbi_conn_queryf(
1725 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1728 osrfHashGet( class, "classname" ),
1736 "Received a result for object permission [%s] "
1737 "for user %d on object %s (class %s) at org %d",
1741 osrfHashGet( class, "classname" ),
1745 if( dbi_result_first_row( result )) {
1746 jsonObject* return_val = oilsMakeJSONFromResult( result );
1747 const char* has_perm = jsonObjectGetString(
1748 jsonObjectGetKeyConst( return_val, "has_perm" ));
1752 "Status of object permission [%s] for user %d "
1753 "on object %s (class %s) at org %d is %s",
1757 osrfHashGet(class, "classname"),
1762 if( *has_perm == 't' )
1764 jsonObjectFree( return_val );
1767 dbi_result_free( result );
1772 int errnum = dbi_conn_error( writehandle, &msg );
1773 osrfLogWarning( OSRF_LOG_MARK,
1774 "Unable to call check object permissions: %d, %s",
1775 errnum, msg ? msg : "(No description available)" );
1776 if( !oilsIsDBConnected( writehandle ))
1777 osrfAppSessionPanic( ctx->session );
1781 osrfLogDebug( OSRF_LOG_MARK,
1782 "Checking non-object permission [%s] for user %d at org %d",
1783 perm, userid, atoi(context_org) );
1784 result = dbi_conn_queryf(
1786 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1793 osrfLogDebug( OSRF_LOG_MARK,
1794 "Received a result for permission [%s] for user %d at org %d",
1795 perm, userid, atoi( context_org ));
1796 if( dbi_result_first_row( result )) {
1797 jsonObject* return_val = oilsMakeJSONFromResult( result );
1798 const char* has_perm = jsonObjectGetString(
1799 jsonObjectGetKeyConst( return_val, "has_perm" ));
1800 osrfLogDebug( OSRF_LOG_MARK,
1801 "Status of permission [%s] for user %d at org %d is [%s]",
1802 perm, userid, atoi( context_org ), has_perm );
1803 if( *has_perm == 't' )
1805 jsonObjectFree( return_val );
1808 dbi_result_free( result );
1813 int errnum = dbi_conn_error( writehandle, &msg );
1814 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1815 errnum, msg ? msg : "(No description available)" );
1816 if( !oilsIsDBConnected( writehandle ))
1817 osrfAppSessionPanic( ctx->session );
1825 osrfStringArrayFree( context_org_array );
1831 @brief Look up the root of the org_unit tree.
1832 @param ctx Pointer to the method context.
1833 @return The id of the root org unit, as a character string.
1835 Query actor.org_unit where parent_ou is null, and return the id as a string.
1837 This function assumes that there is only one root org unit, i.e. that we
1838 have a single tree, not a forest.
1840 The calling code is responsible for freeing the returned string.
1842 static const char* org_tree_root( osrfMethodContext* ctx ) {
1844 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1845 static time_t last_lookup_time = 0;
1846 time_t current_time = time( NULL );
1848 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1849 // We successfully looked this up less than an hour ago.
1850 // It's not likely to have changed since then.
1851 return strdup( cached_root_id );
1853 last_lookup_time = current_time;
1856 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1857 jsonObject* result = doFieldmapperSearch(
1858 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1859 jsonObjectFree( where_clause );
1861 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1864 jsonObjectFree( result );
1866 growing_buffer* msg = buffer_init( 128 );
1867 OSRF_BUFFER_ADD( msg, modulename );
1868 OSRF_BUFFER_ADD( msg,
1869 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1871 char* m = buffer_release( msg );
1872 osrfAppSessionStatus( ctx->session,
1873 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1876 cached_root_id[ 0 ] = '\0';
1880 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1881 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1883 strcpy( cached_root_id, root_org_unit_id );
1884 jsonObjectFree( result );
1885 return cached_root_id;
1889 @brief Create a JSON_HASH with a single key/value pair.
1890 @param key The key of the key/value pair.
1891 @param value the value of the key/value pair.
1892 @return Pointer to a newly created jsonObject of type JSON_HASH.
1894 The value of the key/value is either a string or (if @a value is NULL) a null.
1896 static jsonObject* single_hash( const char* key, const char* value ) {
1898 if( ! key ) key = "";
1900 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1901 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1906 int doCreate( osrfMethodContext* ctx ) {
1907 if(osrfMethodVerifyContext( ctx )) {
1908 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1913 timeout_needs_resetting = 1;
1915 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1916 jsonObject* target = NULL;
1917 jsonObject* options = NULL;
1919 if( enforce_pcrud ) {
1920 target = jsonObjectGetIndex( ctx->params, 1 );
1921 options = jsonObjectGetIndex( ctx->params, 2 );
1923 target = jsonObjectGetIndex( ctx->params, 0 );
1924 options = jsonObjectGetIndex( ctx->params, 1 );
1927 if( !verifyObjectClass( ctx, target )) {
1928 osrfAppRespondComplete( ctx, NULL );
1932 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1934 const char* trans_id = getXactId( ctx );
1936 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1938 osrfAppSessionStatus(
1940 OSRF_STATUS_BADREQUEST,
1941 "osrfMethodException",
1943 "No active transaction -- required for CREATE"
1945 osrfAppRespondComplete( ctx, NULL );
1949 // The following test is harmless but redundant. If a class is
1950 // readonly, we don't register a create method for it.
1951 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1952 osrfAppSessionStatus(
1954 OSRF_STATUS_BADREQUEST,
1955 "osrfMethodException",
1957 "Cannot INSERT readonly class"
1959 osrfAppRespondComplete( ctx, NULL );
1963 // Set the last_xact_id
1964 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1966 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1967 trans_id, target->classname, index);
1968 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1971 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1973 dbhandle = writehandle;
1975 osrfHash* fields = osrfHashGet( meta, "fields" );
1976 char* pkey = osrfHashGet( meta, "primarykey" );
1977 char* seq = osrfHashGet( meta, "sequence" );
1979 growing_buffer* table_buf = buffer_init( 128 );
1980 growing_buffer* col_buf = buffer_init( 128 );
1981 growing_buffer* val_buf = buffer_init( 128 );
1983 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1984 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1985 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1986 buffer_add( val_buf,"VALUES (" );
1990 osrfHash* field = NULL;
1991 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1992 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1994 const char* field_name = osrfHashIteratorKey( field_itr );
1996 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1999 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2002 if( field_object && field_object->classname ) {
2003 value = oilsFMGetString(
2005 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2007 } else if( field_object && JSON_BOOL == field_object->type ) {
2008 if( jsonBoolIsTrue( field_object ) )
2009 value = strdup( "t" );
2011 value = strdup( "f" );
2013 value = jsonObjectToSimpleString( field_object );
2019 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2020 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2023 buffer_add( col_buf, field_name );
2025 if( !field_object || field_object->type == JSON_NULL ) {
2026 buffer_add( val_buf, "DEFAULT" );
2028 } else if( !strcmp( get_primitive( field ), "number" )) {
2029 const char* numtype = get_datatype( field );
2030 if( !strcmp( numtype, "INT8" )) {
2031 buffer_fadd( val_buf, "%lld", atoll( value ));
2033 } else if( !strcmp( numtype, "INT" )) {
2034 buffer_fadd( val_buf, "%d", atoi( value ));
2036 } else if( !strcmp( numtype, "NUMERIC" )) {
2037 buffer_fadd( val_buf, "%f", atof( value ));
2040 if( dbi_conn_quote_string( writehandle, &value )) {
2041 OSRF_BUFFER_ADD( val_buf, value );
2044 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2045 osrfAppSessionStatus(
2047 OSRF_STATUS_INTERNALSERVERERROR,
2048 "osrfMethodException",
2050 "Error quoting string -- please see the error log for more details"
2053 buffer_free( table_buf );
2054 buffer_free( col_buf );
2055 buffer_free( val_buf );
2056 osrfAppRespondComplete( ctx, NULL );
2064 osrfHashIteratorFree( field_itr );
2066 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2067 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2069 char* table_str = buffer_release( table_buf );
2070 char* col_str = buffer_release( col_buf );
2071 char* val_str = buffer_release( val_buf );
2072 growing_buffer* sql = buffer_init( 128 );
2073 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2078 char* query = buffer_release( sql );
2080 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2082 jsonObject* obj = NULL;
2085 dbi_result result = dbi_conn_query( writehandle, query );
2087 obj = jsonNewObject( NULL );
2089 int errnum = dbi_conn_error( writehandle, &msg );
2092 "%s ERROR inserting %s object using query [%s]: %d %s",
2094 osrfHashGet(meta, "fieldmapper"),
2097 msg ? msg : "(No description available)"
2099 osrfAppSessionStatus(
2101 OSRF_STATUS_INTERNALSERVERERROR,
2102 "osrfMethodException",
2104 "INSERT error -- please see the error log for more details"
2106 if( !oilsIsDBConnected( writehandle ))
2107 osrfAppSessionPanic( ctx->session );
2111 char* id = oilsFMGetString( target, pkey );
2113 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2114 growing_buffer* _id = buffer_init( 10 );
2115 buffer_fadd( _id, "%lld", new_id );
2116 id = buffer_release( _id );
2119 // Find quietness specification, if present
2120 const char* quiet_str = NULL;
2122 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2124 quiet_str = jsonObjectGetString( quiet_obj );
2127 if( str_is_true( quiet_str )) { // if quietness is specified
2128 obj = jsonNewObject( id );
2132 // Fetch the row that we just inserted, so that we can return it to the client
2133 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2134 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2137 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2141 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2143 jsonObjectFree( list );
2144 jsonObjectFree( where_clause );
2151 osrfAppRespondComplete( ctx, obj );
2152 jsonObjectFree( obj );
2157 @brief Implement the retrieve method.
2158 @param ctx Pointer to the method context.
2159 @param err Pointer through which to return an error code.
2160 @return If successful, a pointer to the result to be returned to the client;
2163 From the method's class, fetch a row with a specified value in the primary key. This
2164 method relies on the database design convention that a primary key consists of a single
2168 - authkey (PCRUD only)
2169 - value of the primary key for the desired row, for building the WHERE clause
2170 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2172 Return to client: One row from the query.
2174 int doRetrieve( osrfMethodContext* ctx ) {
2175 if(osrfMethodVerifyContext( ctx )) {
2176 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2181 timeout_needs_resetting = 1;
2186 if( enforce_pcrud ) {
2191 // Get the class metadata
2192 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2194 // Get the value of the primary key, from a method parameter
2195 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2199 "%s retrieving %s object with primary key value of %s",
2201 osrfHashGet( class_def, "fieldmapper" ),
2202 jsonObjectGetString( id_obj )
2205 // Build a WHERE clause based on the key value
2206 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2209 osrfHashGet( class_def, "primarykey" ), // name of key column
2210 jsonObjectClone( id_obj ) // value of key column
2213 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2217 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2219 jsonObjectFree( where_clause );
2221 osrfAppRespondComplete( ctx, NULL );
2225 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2226 jsonObjectFree( list );
2228 if( enforce_pcrud ) {
2229 if(!verifyObjectPCRUD( ctx, obj )) {
2230 jsonObjectFree( obj );
2232 growing_buffer* msg = buffer_init( 128 );
2233 OSRF_BUFFER_ADD( msg, modulename );
2234 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2236 char* m = buffer_release( msg );
2237 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2241 osrfAppRespondComplete( ctx, NULL );
2246 osrfAppRespondComplete( ctx, obj );
2247 jsonObjectFree( obj );
2252 @brief Translate a numeric value to a string representation for the database.
2253 @param field Pointer to the IDL field definition.
2254 @param value Pointer to a jsonObject holding the value of a field.
2255 @return Pointer to a newly allocated string.
2257 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2258 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2259 or (what is worse) valid SQL that is wrong.
2261 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2263 The calling code is responsible for freeing the resulting string by calling free().
2265 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2266 growing_buffer* val_buf = buffer_init( 32 );
2267 const char* numtype = get_datatype( field );
2269 // For historical reasons the following contains cruft that could be cleaned up.
2270 if( !strncmp( numtype, "INT", 3 ) ) {
2271 if( value->type == JSON_NUMBER )
2272 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2273 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2275 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2278 } else if( !strcmp( numtype, "NUMERIC" )) {
2279 if( value->type == JSON_NUMBER )
2280 buffer_fadd( val_buf, jsonObjectGetString( value ));
2282 buffer_fadd( val_buf, jsonObjectGetString( value ));
2286 // Presumably this was really intended to be a string, so quote it
2287 char* str = jsonObjectToSimpleString( value );
2288 if( dbi_conn_quote_string( dbhandle, &str )) {
2289 OSRF_BUFFER_ADD( val_buf, str );
2292 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2294 buffer_free( val_buf );
2299 return buffer_release( val_buf );
2302 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2303 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2304 growing_buffer* sql_buf = buffer_init( 32 );
2310 osrfHashGet( field, "name" )
2314 buffer_add( sql_buf, "IN (" );
2315 } else if( !strcasecmp( op,"not in" )) {
2316 buffer_add( sql_buf, "NOT IN (" );
2318 buffer_add( sql_buf, "IN (" );
2321 if( node->type == JSON_HASH ) {
2322 // subquery predicate
2323 char* subpred = buildQuery( ctx, node, SUBSELECT );
2325 buffer_free( sql_buf );
2329 buffer_add( sql_buf, subpred );
2332 } else if( node->type == JSON_ARRAY ) {
2333 // literal value list
2334 int in_item_index = 0;
2335 int in_item_first = 1;
2336 const jsonObject* in_item;
2337 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2342 buffer_add( sql_buf, ", " );
2345 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2346 osrfLogError( OSRF_LOG_MARK,
2347 "%s: Expected string or number within IN list; found %s",
2348 modulename, json_type( in_item->type ) );
2349 buffer_free( sql_buf );
2353 // Append the literal value -- quoted if not a number
2354 if( JSON_NUMBER == in_item->type ) {
2355 char* val = jsonNumberToDBString( field, in_item );
2356 OSRF_BUFFER_ADD( sql_buf, val );
2359 } else if( !strcmp( get_primitive( field ), "number" )) {
2360 char* val = jsonNumberToDBString( field, in_item );
2361 OSRF_BUFFER_ADD( sql_buf, val );
2365 char* key_string = jsonObjectToSimpleString( in_item );
2366 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2367 OSRF_BUFFER_ADD( sql_buf, key_string );
2370 osrfLogError( OSRF_LOG_MARK,
2371 "%s: Error quoting key string [%s]", modulename, key_string );
2373 buffer_free( sql_buf );
2379 if( in_item_first ) {
2380 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2381 buffer_free( sql_buf );
2385 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2386 modulename, json_type( node->type ));
2387 buffer_free( sql_buf );
2391 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2393 return buffer_release( sql_buf );
2396 // Receive a JSON_ARRAY representing a function call. The first
2397 // entry in the array is the function name. The rest are parameters.
2398 static char* searchValueTransform( const jsonObject* array ) {
2400 if( array->size < 1 ) {
2401 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2405 // Get the function name
2406 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2407 if( func_item->type != JSON_STRING ) {
2408 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2409 modulename, json_type( func_item->type ));
2413 growing_buffer* sql_buf = buffer_init( 32 );
2415 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2416 OSRF_BUFFER_ADD( sql_buf, "( " );
2418 // Get the parameters
2419 int func_item_index = 1; // We already grabbed the zeroth entry
2420 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2422 // Add a separator comma, if we need one
2423 if( func_item_index > 2 )
2424 buffer_add( sql_buf, ", " );
2426 // Add the current parameter
2427 if( func_item->type == JSON_NULL ) {
2428 buffer_add( sql_buf, "NULL" );
2430 char* val = jsonObjectToSimpleString( func_item );
2431 if( dbi_conn_quote_string( dbhandle, &val )) {
2432 OSRF_BUFFER_ADD( sql_buf, val );
2435 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2437 buffer_free( sql_buf );
2444 buffer_add( sql_buf, " )" );
2446 return buffer_release( sql_buf );
2449 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2450 const jsonObject* node, const char* op ) {
2452 if( ! is_good_operator( op ) ) {
2453 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2457 char* val = searchValueTransform( node );
2461 growing_buffer* sql_buf = buffer_init( 32 );
2466 osrfHashGet( field, "name" ),
2473 return buffer_release( sql_buf );
2476 // class_alias is a class name or other table alias
2477 // field is a field definition as stored in the IDL
2478 // node comes from the method parameter, and may represent an entry in the SELECT list
2479 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2480 const jsonObject* node ) {
2481 growing_buffer* sql_buf = buffer_init( 32 );
2483 const char* field_transform = jsonObjectGetString(
2484 jsonObjectGetKeyConst( node, "transform" ) );
2485 const char* transform_subcolumn = jsonObjectGetString(
2486 jsonObjectGetKeyConst( node, "result_field" ) );
2488 if( transform_subcolumn ) {
2489 if( ! is_identifier( transform_subcolumn ) ) {
2490 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2491 modulename, transform_subcolumn );
2492 buffer_free( sql_buf );
2495 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2498 if( field_transform ) {
2500 if( ! is_identifier( field_transform ) ) {
2501 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2502 modulename, field_transform );
2503 buffer_free( sql_buf );
2507 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2508 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2509 field_transform, class_alias, osrfHashGet( field, "name" ));
2511 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2512 field_transform, class_alias, osrfHashGet( field, "name" ));
2515 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2518 if( array->type != JSON_ARRAY ) {
2519 osrfLogError( OSRF_LOG_MARK,
2520 "%s: Expected JSON_ARRAY for function params; found %s",
2521 modulename, json_type( array->type ) );
2522 buffer_free( sql_buf );
2525 int func_item_index = 0;
2526 jsonObject* func_item;
2527 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2529 char* val = jsonObjectToSimpleString( func_item );
2532 buffer_add( sql_buf, ",NULL" );
2533 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2534 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2535 OSRF_BUFFER_ADD( sql_buf, val );
2537 osrfLogError( OSRF_LOG_MARK,
2538 "%s: Error quoting key string [%s]", modulename, val );
2540 buffer_free( sql_buf );
2547 buffer_add( sql_buf, " )" );
2550 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2553 if( transform_subcolumn )
2554 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2556 return buffer_release( sql_buf );
2559 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2560 const jsonObject* node, const char* op ) {
2562 if( ! is_good_operator( op ) ) {
2563 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2567 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2568 if( ! field_transform )
2571 int extra_parens = 0; // boolean
2573 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2575 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2577 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2579 free( field_transform );
2583 } else if( value_obj->type == JSON_ARRAY ) {
2584 value = searchValueTransform( value_obj );
2586 osrfLogError( OSRF_LOG_MARK,
2587 "%s: Error building value transform for field transform", modulename );
2588 free( field_transform );
2591 } else if( value_obj->type == JSON_HASH ) {
2592 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2594 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2596 free( field_transform );
2600 } else if( value_obj->type == JSON_NUMBER ) {
2601 value = jsonNumberToDBString( field, value_obj );
2602 } else if( value_obj->type == JSON_NULL ) {
2603 osrfLogError( OSRF_LOG_MARK,
2604 "%s: Error building predicate for field transform: null value", modulename );
2605 free( field_transform );
2607 } else if( value_obj->type == JSON_BOOL ) {
2608 osrfLogError( OSRF_LOG_MARK,
2609 "%s: Error building predicate for field transform: boolean value", modulename );
2610 free( field_transform );
2613 if( !strcmp( get_primitive( field ), "number") ) {
2614 value = jsonNumberToDBString( field, value_obj );
2616 value = jsonObjectToSimpleString( value_obj );
2617 if( !dbi_conn_quote_string( dbhandle, &value )) {
2618 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2619 modulename, value );
2621 free( field_transform );
2627 const char* left_parens = "";
2628 const char* right_parens = "";
2630 if( extra_parens ) {
2635 growing_buffer* sql_buf = buffer_init( 32 );
2639 "%s%s %s %s %s %s%s",
2650 free( field_transform );
2652 return buffer_release( sql_buf );
2655 static char* searchSimplePredicate( const char* op, const char* class_alias,
2656 osrfHash* field, const jsonObject* node ) {
2658 if( ! is_good_operator( op ) ) {
2659 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2665 // Get the value to which we are comparing the specified column
2666 if( node->type != JSON_NULL ) {
2667 if( node->type == JSON_NUMBER ) {
2668 val = jsonNumberToDBString( field, node );
2669 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2670 val = jsonNumberToDBString( field, node );
2672 val = jsonObjectToSimpleString( node );
2677 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2678 // Value is not numeric; enclose it in quotes
2679 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2680 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2687 // Compare to a null value
2688 val = strdup( "NULL" );
2689 if( strcmp( op, "=" ))
2695 growing_buffer* sql_buf = buffer_init( 32 );
2696 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2697 char* pred = buffer_release( sql_buf );
2704 static char* searchBETWEENPredicate( const char* class_alias,
2705 osrfHash* field, const jsonObject* node ) {
2707 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2708 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2710 if( NULL == y_node ) {
2711 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2714 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2715 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2722 if( !strcmp( get_primitive( field ), "number") ) {
2723 x_string = jsonNumberToDBString( field, x_node );
2724 y_string = jsonNumberToDBString( field, y_node );
2727 x_string = jsonObjectToSimpleString( x_node );
2728 y_string = jsonObjectToSimpleString( y_node );
2729 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2730 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2731 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2732 modulename, x_string, y_string );
2739 growing_buffer* sql_buf = buffer_init( 32 );
2740 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2741 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2745 return buffer_release( sql_buf );
2748 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2749 jsonObject* node, osrfMethodContext* ctx ) {
2752 if( node->type == JSON_ARRAY ) { // equality IN search
2753 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2754 } else if( node->type == JSON_HASH ) { // other search
2755 jsonIterator* pred_itr = jsonNewIterator( node );
2756 if( !jsonIteratorHasNext( pred_itr ) ) {
2757 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2758 modulename, osrfHashGet(field, "name" ));
2760 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2762 // Verify that there are no additional predicates
2763 if( jsonIteratorHasNext( pred_itr ) ) {
2764 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2765 modulename, osrfHashGet(field, "name" ));
2766 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2767 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2768 else if( !(strcasecmp( pred_itr->key,"in" ))
2769 || !(strcasecmp( pred_itr->key,"not in" )) )
2770 pred = searchINPredicate(
2771 class_info->alias, field, pred_node, pred_itr->key, ctx );
2772 else if( pred_node->type == JSON_ARRAY )
2773 pred = searchFunctionPredicate(
2774 class_info->alias, field, pred_node, pred_itr->key );
2775 else if( pred_node->type == JSON_HASH )
2776 pred = searchFieldTransformPredicate(
2777 class_info, field, pred_node, pred_itr->key );
2779 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2781 jsonIteratorFree( pred_itr );
2783 } else if( node->type == JSON_NULL ) { // IS NULL search
2784 growing_buffer* _p = buffer_init( 64 );
2787 "\"%s\".%s IS NULL",
2788 class_info->class_name,
2789 osrfHashGet( field, "name" )
2791 pred = buffer_release( _p );
2792 } else { // equality search
2793 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2812 field : call_number,
2828 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2830 const jsonObject* working_hash;
2831 jsonObject* freeable_hash = NULL;
2833 if( join_hash->type == JSON_HASH ) {
2834 working_hash = join_hash;
2835 } else if( join_hash->type == JSON_STRING ) {
2836 // turn it into a JSON_HASH by creating a wrapper
2837 // around a copy of the original
2838 const char* _tmp = jsonObjectGetString( join_hash );
2839 freeable_hash = jsonNewObjectType( JSON_HASH );
2840 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2841 working_hash = freeable_hash;
2845 "%s: JOIN failed; expected JSON object type not found",
2851 growing_buffer* join_buf = buffer_init( 128 );
2852 const char* leftclass = left_info->class_name;
2854 jsonObject* snode = NULL;
2855 jsonIterator* search_itr = jsonNewIterator( working_hash );
2857 while ( (snode = jsonIteratorNext( search_itr )) ) {
2858 const char* right_alias = search_itr->key;
2860 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2862 class = right_alias;
2864 const ClassInfo* right_info = add_joined_class( right_alias, class );
2868 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2872 jsonIteratorFree( search_itr );
2873 buffer_free( join_buf );
2875 jsonObjectFree( freeable_hash );
2878 osrfHash* links = right_info->links;
2879 const char* table = right_info->source_def;
2881 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2882 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2884 if( field && !fkey ) {
2885 // Look up the corresponding join column in the IDL.
2886 // The link must be defined in the child table,
2887 // and point to the right parent table.
2888 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2889 const char* reltype = NULL;
2890 const char* other_class = NULL;
2891 reltype = osrfHashGet( idl_link, "reltype" );
2892 if( reltype && strcmp( reltype, "has_many" ) )
2893 other_class = osrfHashGet( idl_link, "class" );
2894 if( other_class && !strcmp( other_class, leftclass ) )
2895 fkey = osrfHashGet( idl_link, "key" );
2899 "%s: JOIN failed. No link defined from %s.%s to %s",
2905 buffer_free( join_buf );
2907 jsonObjectFree( freeable_hash );
2908 jsonIteratorFree( search_itr );
2912 } else if( !field && fkey ) {
2913 // Look up the corresponding join column in the IDL.
2914 // The link must be defined in the child table,
2915 // and point to the right parent table.
2916 osrfHash* left_links = left_info->links;
2917 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2918 const char* reltype = NULL;
2919 const char* other_class = NULL;
2920 reltype = osrfHashGet( idl_link, "reltype" );
2921 if( reltype && strcmp( reltype, "has_many" ) )
2922 other_class = osrfHashGet( idl_link, "class" );
2923 if( other_class && !strcmp( other_class, class ) )
2924 field = osrfHashGet( idl_link, "key" );
2928 "%s: JOIN failed. No link defined from %s.%s to %s",
2934 buffer_free( join_buf );
2936 jsonObjectFree( freeable_hash );
2937 jsonIteratorFree( search_itr );
2941 } else if( !field && !fkey ) {
2942 osrfHash* left_links = left_info->links;
2944 // For each link defined for the left class:
2945 // see if the link references the joined class
2946 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2947 osrfHash* curr_link = NULL;
2948 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2949 const char* other_class = osrfHashGet( curr_link, "class" );
2950 if( other_class && !strcmp( other_class, class ) ) {
2952 // In the IDL, the parent class doesn't always know then names of the child
2953 // columns that are pointing to it, so don't use that end of the link
2954 const char* reltype = osrfHashGet( curr_link, "reltype" );
2955 if( reltype && strcmp( reltype, "has_many" ) ) {
2956 // Found a link between the classes
2957 fkey = osrfHashIteratorKey( itr );
2958 field = osrfHashGet( curr_link, "key" );
2963 osrfHashIteratorFree( itr );
2965 if( !field || !fkey ) {
2966 // Do another such search, with the classes reversed
2968 // For each link defined for the joined class:
2969 // see if the link references the left class
2970 osrfHashIterator* itr = osrfNewHashIterator( links );
2971 osrfHash* curr_link = NULL;
2972 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2973 const char* other_class = osrfHashGet( curr_link, "class" );
2974 if( other_class && !strcmp( other_class, leftclass ) ) {
2976 // In the IDL, the parent class doesn't know then names of the child
2977 // columns that are pointing to it, so don't use that end of the link
2978 const char* reltype = osrfHashGet( curr_link, "reltype" );
2979 if( reltype && strcmp( reltype, "has_many" ) ) {
2980 // Found a link between the classes
2981 field = osrfHashIteratorKey( itr );
2982 fkey = osrfHashGet( curr_link, "key" );
2987 osrfHashIteratorFree( itr );
2990 if( !field || !fkey ) {
2993 "%s: JOIN failed. No link defined between %s and %s",
2998 buffer_free( join_buf );
3000 jsonObjectFree( freeable_hash );
3001 jsonIteratorFree( search_itr );
3006 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3008 if( !strcasecmp( type,"left" )) {
3009 buffer_add( join_buf, " LEFT JOIN" );
3010 } else if( !strcasecmp( type,"right" )) {
3011 buffer_add( join_buf, " RIGHT JOIN" );
3012 } else if( !strcasecmp( type,"full" )) {
3013 buffer_add( join_buf, " FULL JOIN" );
3015 buffer_add( join_buf, " INNER JOIN" );
3018 buffer_add( join_buf, " INNER JOIN" );
3021 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3022 table, right_alias, right_alias, field, left_info->alias, fkey );
3024 // Add any other join conditions as specified by "filter"
3025 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3027 const char* filter_op = jsonObjectGetString(
3028 jsonObjectGetKeyConst( snode, "filter_op" ) );
3029 if( filter_op && !strcasecmp( "or",filter_op )) {
3030 buffer_add( join_buf, " OR " );
3032 buffer_add( join_buf, " AND " );
3035 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3037 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3038 OSRF_BUFFER_ADD( join_buf, jpred );
3043 "%s: JOIN failed. Invalid conditional expression.",
3046 jsonIteratorFree( search_itr );
3047 buffer_free( join_buf );
3049 jsonObjectFree( freeable_hash );
3054 buffer_add( join_buf, " ) " );
3056 // Recursively add a nested join, if one is present
3057 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3059 char* jpred = searchJOIN( join_filter, right_info );
3061 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3062 OSRF_BUFFER_ADD( join_buf, jpred );
3065 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3066 jsonIteratorFree( search_itr );
3067 buffer_free( join_buf );
3069 jsonObjectFree( freeable_hash );
3076 jsonObjectFree( freeable_hash );
3077 jsonIteratorFree( search_itr );
3079 return buffer_release( join_buf );
3084 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3085 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3086 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3088 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3090 search_hash is the JSON expression of the conditions.
3091 meta is the class definition from the IDL, for the relevant table.
3092 opjoin_type indicates whether multiple conditions, if present, should be
3093 connected by AND or OR.
3094 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3095 to pass it to other functions -- and all they do with it is to use the session
3096 and request members to send error messages back to the client.
3100 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3101 int opjoin_type, osrfMethodContext* ctx ) {
3105 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3106 "opjoin_type = %d, ctx addr = %p",
3109 class_info->class_def,
3114 growing_buffer* sql_buf = buffer_init( 128 );
3116 jsonObject* node = NULL;
3119 if( search_hash->type == JSON_ARRAY ) {
3120 if( 0 == search_hash->size ) {
3123 "%s: Invalid predicate structure: empty JSON array",
3126 buffer_free( sql_buf );
3130 unsigned long i = 0;
3131 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3135 if( opjoin_type == OR_OP_JOIN )
3136 buffer_add( sql_buf, " OR " );
3138 buffer_add( sql_buf, " AND " );
3141 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3143 buffer_free( sql_buf );
3147 buffer_fadd( sql_buf, "( %s )", subpred );
3151 } else if( search_hash->type == JSON_HASH ) {
3152 osrfLogDebug( OSRF_LOG_MARK,
3153 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3154 jsonIterator* search_itr = jsonNewIterator( search_hash );
3155 if( !jsonIteratorHasNext( search_itr ) ) {
3158 "%s: Invalid predicate structure: empty JSON object",
3161 jsonIteratorFree( search_itr );
3162 buffer_free( sql_buf );
3166 while( (node = jsonIteratorNext( search_itr )) ) {
3171 if( opjoin_type == OR_OP_JOIN )
3172 buffer_add( sql_buf, " OR " );
3174 buffer_add( sql_buf, " AND " );
3177 if( '+' == search_itr->key[ 0 ] ) {
3179 // This plus sign prefixes a class name or other table alias;
3180 // make sure the table alias is in scope
3181 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3182 if( ! alias_info ) {
3185 "%s: Invalid table alias \"%s\" in WHERE clause",
3189 jsonIteratorFree( search_itr );
3190 buffer_free( sql_buf );
3194 if( node->type == JSON_STRING ) {
3195 // It's the name of a column; make sure it belongs to the class
3196 const char* fieldname = jsonObjectGetString( node );
3197 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3200 "%s: Invalid column name \"%s\" in WHERE clause "
3201 "for table alias \"%s\"",
3206 jsonIteratorFree( search_itr );
3207 buffer_free( sql_buf );
3211 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3213 // It's something more complicated
3214 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3216 jsonIteratorFree( search_itr );
3217 buffer_free( sql_buf );
3221 buffer_fadd( sql_buf, "( %s )", subpred );
3224 } else if( '-' == search_itr->key[ 0 ] ) {
3225 if( !strcasecmp( "-or", search_itr->key )) {
3226 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3228 jsonIteratorFree( search_itr );
3229 buffer_free( sql_buf );
3233 buffer_fadd( sql_buf, "( %s )", subpred );
3235 } else if( !strcasecmp( "-and", search_itr->key )) {
3236 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3238 jsonIteratorFree( search_itr );
3239 buffer_free( sql_buf );
3243 buffer_fadd( sql_buf, "( %s )", subpred );
3245 } else if( !strcasecmp("-not",search_itr->key) ) {
3246 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3248 jsonIteratorFree( search_itr );
3249 buffer_free( sql_buf );
3253 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3255 } else if( !strcasecmp( "-exists", search_itr->key )) {
3256 char* subpred = buildQuery( ctx, node, SUBSELECT );
3258 jsonIteratorFree( search_itr );
3259 buffer_free( sql_buf );
3263 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3265 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3266 char* subpred = buildQuery( ctx, node, SUBSELECT );
3268 jsonIteratorFree( search_itr );
3269 buffer_free( sql_buf );
3273 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3275 } else { // Invalid "minus" operator
3278 "%s: Invalid operator \"%s\" in WHERE clause",
3282 jsonIteratorFree( search_itr );
3283 buffer_free( sql_buf );
3289 const char* class = class_info->class_name;
3290 osrfHash* fields = class_info->fields;
3291 osrfHash* field = osrfHashGet( fields, search_itr->key );
3294 const char* table = class_info->source_def;
3297 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3300 table ? table : "?",
3303 jsonIteratorFree( search_itr );
3304 buffer_free( sql_buf );
3308 char* subpred = searchPredicate( class_info, field, node, ctx );
3310 buffer_free( sql_buf );
3311 jsonIteratorFree( search_itr );
3315 buffer_add( sql_buf, subpred );
3319 jsonIteratorFree( search_itr );
3322 // ERROR ... only hash and array allowed at this level
3323 char* predicate_string = jsonObjectToJSON( search_hash );
3326 "%s: Invalid predicate structure: %s",
3330 buffer_free( sql_buf );
3331 free( predicate_string );
3335 return buffer_release( sql_buf );
3338 /* Build a JSON_ARRAY of field names for a given table alias
3340 static jsonObject* defaultSelectList( const char* table_alias ) {
3345 ClassInfo* class_info = search_all_alias( table_alias );
3346 if( ! class_info ) {
3349 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3356 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3357 osrfHash* field_def = NULL;
3358 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3359 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3360 const char* field_name = osrfHashIteratorKey( field_itr );
3361 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3362 jsonObjectPush( array, jsonNewObject( field_name ) );
3365 osrfHashIteratorFree( field_itr );
3370 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3371 // The jsonObject must be a JSON_HASH with an single entry for "union",
3372 // "intersect", or "except". The data associated with this key must be an
3373 // array of hashes, each hash being a query.
3374 // Also allowed but currently ignored: entries for "order_by" and "alias".
3375 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3377 if( ! combo || combo->type != JSON_HASH )
3378 return NULL; // should be impossible; validated by caller
3380 const jsonObject* query_array = NULL; // array of subordinate queries
3381 const char* op = NULL; // name of operator, e.g. UNION
3382 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3383 int op_count = 0; // for detecting conflicting operators
3384 int excepting = 0; // boolean
3385 int all = 0; // boolean
3386 jsonObject* order_obj = NULL;
3388 // Identify the elements in the hash
3389 jsonIterator* query_itr = jsonNewIterator( combo );
3390 jsonObject* curr_obj = NULL;
3391 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3392 if( ! strcmp( "union", query_itr->key ) ) {
3395 query_array = curr_obj;
3396 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3399 query_array = curr_obj;
3400 } else if( ! strcmp( "except", query_itr->key ) ) {
3404 query_array = curr_obj;
3405 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3408 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3411 order_obj = curr_obj;
3412 } else if( ! strcmp( "alias", query_itr->key ) ) {
3413 if( curr_obj->type != JSON_STRING ) {
3414 jsonIteratorFree( query_itr );
3417 alias = jsonObjectGetString( curr_obj );
3418 } else if( ! strcmp( "all", query_itr->key ) ) {
3419 if( obj_is_true( curr_obj ) )
3423 osrfAppSessionStatus(
3425 OSRF_STATUS_INTERNALSERVERERROR,
3426 "osrfMethodException",
3428 "Malformed query; unexpected entry in query object"
3432 "%s: Unexpected entry for \"%s\" in%squery",
3437 jsonIteratorFree( query_itr );
3441 jsonIteratorFree( query_itr );
3443 // More sanity checks
3444 if( ! query_array ) {
3446 osrfAppSessionStatus(
3448 OSRF_STATUS_INTERNALSERVERERROR,
3449 "osrfMethodException",
3451 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3455 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3458 return NULL; // should be impossible...
3459 } else if( op_count > 1 ) {
3461 osrfAppSessionStatus(
3463 OSRF_STATUS_INTERNALSERVERERROR,
3464 "osrfMethodException",
3466 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3470 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3474 } if( query_array->type != JSON_ARRAY ) {
3476 osrfAppSessionStatus(
3478 OSRF_STATUS_INTERNALSERVERERROR,
3479 "osrfMethodException",
3481 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3485 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3488 json_type( query_array->type )
3491 } if( query_array->size < 2 ) {
3493 osrfAppSessionStatus(
3495 OSRF_STATUS_INTERNALSERVERERROR,
3496 "osrfMethodException",
3498 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3502 "%s:%srequires multiple queries as operands",
3507 } else if( excepting && query_array->size > 2 ) {
3509 osrfAppSessionStatus(
3511 OSRF_STATUS_INTERNALSERVERERROR,
3512 "osrfMethodException",
3514 "EXCEPT operator has too many queries as operands"
3518 "%s:EXCEPT operator has too many queries as operands",
3522 } else if( order_obj && ! alias ) {
3524 osrfAppSessionStatus(
3526 OSRF_STATUS_INTERNALSERVERERROR,
3527 "osrfMethodException",
3529 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3533 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3539 // So far so good. Now build the SQL.
3540 growing_buffer* sql = buffer_init( 256 );
3542 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3543 // Add a layer of parentheses
3544 if( flags & SUBCOMBO )
3545 OSRF_BUFFER_ADD( sql, "( " );
3547 // Traverse the query array. Each entry should be a hash.
3548 int first = 1; // boolean
3550 jsonObject* query = NULL;
3551 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3552 if( query->type != JSON_HASH ) {
3554 osrfAppSessionStatus(
3556 OSRF_STATUS_INTERNALSERVERERROR,
3557 "osrfMethodException",
3559 "Malformed query under UNION, INTERSECT or EXCEPT"
3563 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3566 json_type( query->type )
3575 OSRF_BUFFER_ADD( sql, op );
3577 OSRF_BUFFER_ADD( sql, "ALL " );
3580 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3584 "%s: Error building query under%s",
3592 OSRF_BUFFER_ADD( sql, query_str );
3595 if( flags & SUBCOMBO )
3596 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3598 if( !(flags & SUBSELECT) )
3599 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3601 return buffer_release( sql );
3604 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3605 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3606 // or "except" to indicate the type of query.
3607 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3611 osrfAppSessionStatus(
3613 OSRF_STATUS_INTERNALSERVERERROR,
3614 "osrfMethodException",
3616 "Malformed query; no query object"
3618 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3620 } else if( query->type != JSON_HASH ) {
3622 osrfAppSessionStatus(
3624 OSRF_STATUS_INTERNALSERVERERROR,
3625 "osrfMethodException",
3627 "Malformed query object"
3631 "%s: Query object is %s instead of JSON_HASH",
3633 json_type( query->type )
3638 // Determine what kind of query it purports to be, and dispatch accordingly.
3639 if( jsonObjectGetKey( query, "union" ) ||
3640 jsonObjectGetKey( query, "intersect" ) ||
3641 jsonObjectGetKey( query, "except" ) ) {
3642 return doCombo( ctx, query, flags );
3644 // It is presumably a SELECT query
3646 // Push a node onto the stack for the current query. Every level of
3647 // subquery gets its own QueryFrame on the Stack.
3650 // Build an SQL SELECT statement
3653 jsonObjectGetKey( query, "select" ),
3654 jsonObjectGetKey( query, "from" ),
3655 jsonObjectGetKey( query, "where" ),
3656 jsonObjectGetKey( query, "having" ),
3657 jsonObjectGetKey( query, "order_by" ),
3658 jsonObjectGetKey( query, "limit" ),
3659 jsonObjectGetKey( query, "offset" ),
3668 /* method context */ osrfMethodContext* ctx,
3670 /* SELECT */ jsonObject* selhash,
3671 /* FROM */ jsonObject* join_hash,
3672 /* WHERE */ jsonObject* search_hash,
3673 /* HAVING */ jsonObject* having_hash,
3674 /* ORDER BY */ jsonObject* order_hash,
3675 /* LIMIT */ jsonObject* limit,
3676 /* OFFSET */ jsonObject* offset,
3677 /* flags */ int flags
3679 const char* locale = osrf_message_get_last_locale();
3681 // general tmp objects
3682 const jsonObject* tmp_const;
3683 jsonObject* selclass = NULL;
3684 jsonObject* snode = NULL;
3685 jsonObject* onode = NULL;
3687 char* string = NULL;
3688 int from_function = 0;
3693 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3695 // punt if there's no FROM clause
3696 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3699 "%s: FROM clause is missing or empty",
3703 osrfAppSessionStatus(
3705 OSRF_STATUS_INTERNALSERVERERROR,
3706 "osrfMethodException",
3708 "FROM clause is missing or empty in JSON query"
3713 // the core search class
3714 const char* core_class = NULL;
3716 // get the core class -- the only key of the top level FROM clause, or a string
3717 if( join_hash->type == JSON_HASH ) {
3718 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3719 snode = jsonIteratorNext( tmp_itr );
3721 // Populate the current QueryFrame with information
3722 // about the core class
3723 if( add_query_core( NULL, tmp_itr->key ) ) {
3725 osrfAppSessionStatus(
3727 OSRF_STATUS_INTERNALSERVERERROR,
3728 "osrfMethodException",
3730 "Unable to look up core class"
3734 core_class = curr_query->core.class_name;
3737 jsonObject* extra = jsonIteratorNext( tmp_itr );
3739 jsonIteratorFree( tmp_itr );
3742 // There shouldn't be more than one entry in join_hash
3746 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3750 osrfAppSessionStatus(
3752 OSRF_STATUS_INTERNALSERVERERROR,
3753 "osrfMethodException",
3755 "Malformed FROM clause in JSON query"
3757 return NULL; // Malformed join_hash; extra entry
3759 } else if( join_hash->type == JSON_ARRAY ) {
3760 // We're selecting from a function, not from a table
3762 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3765 } else if( join_hash->type == JSON_STRING ) {
3766 // Populate the current QueryFrame with information
3767 // about the core class
3768 core_class = jsonObjectGetString( join_hash );
3770 if( add_query_core( NULL, core_class ) ) {
3772 osrfAppSessionStatus(
3774 OSRF_STATUS_INTERNALSERVERERROR,
3775 "osrfMethodException",
3777 "Unable to look up core class"
3785 "%s: FROM clause is unexpected JSON type: %s",
3787 json_type( join_hash->type )
3790 osrfAppSessionStatus(
3792 OSRF_STATUS_INTERNALSERVERERROR,
3793 "osrfMethodException",
3795 "Ill-formed FROM clause in JSON query"
3800 // Build the join clause, if any, while filling out the list
3801 // of joined classes in the current QueryFrame.
3802 char* join_clause = NULL;
3803 if( join_hash && ! from_function ) {
3805 join_clause = searchJOIN( join_hash, &curr_query->core );
3806 if( ! join_clause ) {
3808 osrfAppSessionStatus(
3810 OSRF_STATUS_INTERNALSERVERERROR,
3811 "osrfMethodException",
3813 "Unable to construct JOIN clause(s)"
3819 // For in case we don't get a select list
3820 jsonObject* defaultselhash = NULL;
3822 // if there is no select list, build a default select list ...
3823 if( !selhash && !from_function ) {
3824 jsonObject* default_list = defaultSelectList( core_class );
3825 if( ! default_list ) {
3827 osrfAppSessionStatus(
3829 OSRF_STATUS_INTERNALSERVERERROR,
3830 "osrfMethodException",
3832 "Unable to build default SELECT clause in JSON query"
3834 free( join_clause );
3839 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3840 jsonObjectSetKey( selhash, core_class, default_list );
3843 // The SELECT clause can be encoded only by a hash
3844 if( !from_function && selhash->type != JSON_HASH ) {
3847 "%s: Expected JSON_HASH for SELECT clause; found %s",
3849 json_type( selhash->type )
3853 osrfAppSessionStatus(
3855 OSRF_STATUS_INTERNALSERVERERROR,
3856 "osrfMethodException",
3858 "Malformed SELECT clause in JSON query"
3860 free( join_clause );
3864 // If you see a null or wild card specifier for the core class, or an
3865 // empty array, replace it with a default SELECT list
3866 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3868 int default_needed = 0; // boolean
3869 if( JSON_STRING == tmp_const->type
3870 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3872 else if( JSON_NULL == tmp_const->type )
3875 if( default_needed ) {
3876 // Build a default SELECT list
3877 jsonObject* default_list = defaultSelectList( core_class );
3878 if( ! default_list ) {
3880 osrfAppSessionStatus(
3882 OSRF_STATUS_INTERNALSERVERERROR,
3883 "osrfMethodException",
3885 "Can't build default SELECT clause in JSON query"
3887 free( join_clause );
3892 jsonObjectSetKey( selhash, core_class, default_list );
3896 // temp buffers for the SELECT list and GROUP BY clause
3897 growing_buffer* select_buf = buffer_init( 128 );
3898 growing_buffer* group_buf = buffer_init( 128 );
3900 int aggregate_found = 0; // boolean
3902 // Build a select list
3903 if( from_function ) // From a function we select everything
3904 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3907 // Build the SELECT list as SQL
3911 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3912 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3914 const char* cname = selclass_itr->key;
3916 // Make sure the target relation is in the FROM clause.
3918 // At this point join_hash is a step down from the join_hash we
3919 // received as a parameter. If the original was a JSON_STRING,
3920 // then json_hash is now NULL. If the original was a JSON_HASH,
3921 // then json_hash is now the first (and only) entry in it,
3922 // denoting the core class. We've already excluded the
3923 // possibility that the original was a JSON_ARRAY, because in
3924 // that case from_function would be non-NULL, and we wouldn't
3927 // If the current table alias isn't in scope, bail out
3928 ClassInfo* class_info = search_alias( cname );
3929 if( ! class_info ) {
3932 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3937 osrfAppSessionStatus(
3939 OSRF_STATUS_INTERNALSERVERERROR,
3940 "osrfMethodException",
3942 "Selected class not in FROM clause in JSON query"
3944 jsonIteratorFree( selclass_itr );
3945 buffer_free( select_buf );
3946 buffer_free( group_buf );
3947 if( defaultselhash )
3948 jsonObjectFree( defaultselhash );
3949 free( join_clause );
3953 if( selclass->type != JSON_ARRAY ) {
3956 "%s: Malformed SELECT list for class \"%s\"; not an array",
3961 osrfAppSessionStatus(
3963 OSRF_STATUS_INTERNALSERVERERROR,
3964 "osrfMethodException",
3966 "Selected class not in FROM clause in JSON query"
3969 jsonIteratorFree( selclass_itr );
3970 buffer_free( select_buf );
3971 buffer_free( group_buf );
3972 if( defaultselhash )
3973 jsonObjectFree( defaultselhash );
3974 free( join_clause );
3978 // Look up some attributes of the current class
3979 osrfHash* idlClass = class_info->class_def;
3980 osrfHash* class_field_set = class_info->fields;
3981 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3982 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3984 if( 0 == selclass->size ) {
3987 "%s: No columns selected from \"%s\"",
3993 // stitch together the column list for the current table alias...
3994 unsigned long field_idx = 0;
3995 jsonObject* selfield = NULL;
3996 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3998 // If we need a separator comma, add one
4002 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4005 // if the field specification is a string, add it to the list
4006 if( selfield->type == JSON_STRING ) {
4008 // Look up the field in the IDL
4009 const char* col_name = jsonObjectGetString( selfield );
4010 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4012 // No such field in current class
4015 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4021 osrfAppSessionStatus(
4023 OSRF_STATUS_INTERNALSERVERERROR,
4024 "osrfMethodException",
4026 "Selected column not defined in JSON query"
4028 jsonIteratorFree( selclass_itr );
4029 buffer_free( select_buf );
4030 buffer_free( group_buf );
4031 if( defaultselhash )
4032 jsonObjectFree( defaultselhash );
4033 free( join_clause );
4035 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4036 // Virtual field not allowed
4039 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4045 osrfAppSessionStatus(
4047 OSRF_STATUS_INTERNALSERVERERROR,
4048 "osrfMethodException",
4050 "Selected column may not be virtual in JSON query"
4052 jsonIteratorFree( selclass_itr );
4053 buffer_free( select_buf );
4054 buffer_free( group_buf );
4055 if( defaultselhash )
4056 jsonObjectFree( defaultselhash );
4057 free( join_clause );
4063 if( flags & DISABLE_I18N )
4066 i18n = osrfHashGet( field_def, "i18n" );
4068 if( str_is_true( i18n ) ) {
4069 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4070 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4071 class_tname, cname, col_name, class_pkey,
4072 cname, class_pkey, locale, col_name );
4074 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4075 cname, col_name, col_name );
4078 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4079 cname, col_name, col_name );
4082 // ... but it could be an object, in which case we check for a Field Transform
4083 } else if( selfield->type == JSON_HASH ) {
4085 const char* col_name = jsonObjectGetString(
4086 jsonObjectGetKeyConst( selfield, "column" ) );
4088 // Get the field definition from the IDL
4089 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4091 // No such field in current class
4094 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4100 osrfAppSessionStatus(
4102 OSRF_STATUS_INTERNALSERVERERROR,
4103 "osrfMethodException",
4105 "Selected column is not defined in JSON query"
4107 jsonIteratorFree( selclass_itr );
4108 buffer_free( select_buf );
4109 buffer_free( group_buf );
4110 if( defaultselhash )
4111 jsonObjectFree( defaultselhash );
4112 free( join_clause );
4114 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4115 // No such field in current class
4118 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4124 osrfAppSessionStatus(
4126 OSRF_STATUS_INTERNALSERVERERROR,
4127 "osrfMethodException",
4129 "Selected column is virtual in JSON query"
4131 jsonIteratorFree( selclass_itr );
4132 buffer_free( select_buf );
4133 buffer_free( group_buf );
4134 if( defaultselhash )
4135 jsonObjectFree( defaultselhash );
4136 free( join_clause );
4140 // Decide what to use as a column alias
4142 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4143 _alias = jsonObjectGetString( tmp_const );
4144 } else { // Use field name as the alias
4148 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4149 char* transform_str = searchFieldTransform(
4150 class_info->alias, field_def, selfield );
4151 if( transform_str ) {
4152 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4153 free( transform_str );
4156 osrfAppSessionStatus(
4158 OSRF_STATUS_INTERNALSERVERERROR,
4159 "osrfMethodException",
4161 "Unable to generate transform function in JSON query"
4163 jsonIteratorFree( selclass_itr );
4164 buffer_free( select_buf );
4165 buffer_free( group_buf );
4166 if( defaultselhash )
4167 jsonObjectFree( defaultselhash );
4168 free( join_clause );
4175 if( flags & DISABLE_I18N )
4178 i18n = osrfHashGet( field_def, "i18n" );
4180 if( str_is_true( i18n ) ) {
4181 buffer_fadd( select_buf,
4182 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4183 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4184 class_tname, cname, col_name, class_pkey, cname,
4185 class_pkey, locale, _alias );
4187 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4188 cname, col_name, _alias );
4191 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4192 cname, col_name, _alias );
4199 "%s: Selected item is unexpected JSON type: %s",
4201 json_type( selfield->type )
4204 osrfAppSessionStatus(
4206 OSRF_STATUS_INTERNALSERVERERROR,
4207 "osrfMethodException",
4209 "Ill-formed SELECT item in JSON query"
4211 jsonIteratorFree( selclass_itr );
4212 buffer_free( select_buf );
4213 buffer_free( group_buf );
4214 if( defaultselhash )
4215 jsonObjectFree( defaultselhash );
4216 free( join_clause );
4220 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4221 if( obj_is_true( agg_obj ) )
4222 aggregate_found = 1;
4224 // Append a comma (except for the first one)
4225 // and add the column to a GROUP BY clause
4229 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4231 buffer_fadd( group_buf, " %d", sel_pos );
4235 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4237 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4238 if ( ! obj_is_true( aggregate_obj ) ) {
4242 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4245 buffer_fadd(group_buf, " %d", sel_pos);
4248 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4252 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4255 _column = searchFieldTransform(class_info->alias, field, selfield);
4256 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4257 OSRF_BUFFER_ADD(group_buf, _column);
4258 _column = searchFieldTransform(class_info->alias, field, selfield);
4265 } // end while -- iterating across SELECT columns
4267 } // end while -- iterating across classes
4269 jsonIteratorFree( selclass_itr );
4273 char* col_list = buffer_release( select_buf );
4275 // Make sure the SELECT list isn't empty. This can happen, for example,
4276 // if we try to build a default SELECT clause from a non-core table.
4279 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4281 osrfAppSessionStatus(
4283 OSRF_STATUS_INTERNALSERVERERROR,
4284 "osrfMethodException",
4286 "SELECT list is empty"
4289 buffer_free( group_buf );
4290 if( defaultselhash )
4291 jsonObjectFree( defaultselhash );
4292 free( join_clause );
4298 table = searchValueTransform( join_hash );
4300 table = strdup( curr_query->core.source_def );
4304 osrfAppSessionStatus(
4306 OSRF_STATUS_INTERNALSERVERERROR,
4307 "osrfMethodException",
4309 "Unable to identify table for core class"
4312 buffer_free( group_buf );
4313 if( defaultselhash )
4314 jsonObjectFree( defaultselhash );
4315 free( join_clause );
4319 // Put it all together
4320 growing_buffer* sql_buf = buffer_init( 128 );
4321 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4325 // Append the join clause, if any
4327 buffer_add(sql_buf, join_clause );
4328 free( join_clause );
4331 char* order_by_list = NULL;
4332 char* having_buf = NULL;
4334 if( !from_function ) {
4336 // Build a WHERE clause, if there is one
4338 buffer_add( sql_buf, " WHERE " );
4340 // and it's on the WHERE clause
4341 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4344 osrfAppSessionStatus(
4346 OSRF_STATUS_INTERNALSERVERERROR,
4347 "osrfMethodException",
4349 "Severe query error in WHERE predicate -- see error log for more details"
4352 buffer_free( group_buf );
4353 buffer_free( sql_buf );
4354 if( defaultselhash )
4355 jsonObjectFree( defaultselhash );
4359 buffer_add( sql_buf, pred );
4363 // Build a HAVING clause, if there is one
4366 // and it's on the the WHERE clause
4367 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4369 if( ! having_buf ) {
4371 osrfAppSessionStatus(
4373 OSRF_STATUS_INTERNALSERVERERROR,
4374 "osrfMethodException",
4376 "Severe query error in HAVING predicate -- see error log for more details"
4379 buffer_free( group_buf );
4380 buffer_free( sql_buf );
4381 if( defaultselhash )
4382 jsonObjectFree( defaultselhash );
4387 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4389 // Build an ORDER BY clause, if there is one
4390 if( NULL == order_hash )
4391 ; // No ORDER BY? do nothing
4392 else if( JSON_ARRAY == order_hash->type ) {
4393 // Array of field specifications, each specification being a
4394 // hash to define the class, field, and other details
4396 jsonObject* order_spec;
4397 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4399 if( JSON_HASH != order_spec->type ) {
4400 osrfLogError( OSRF_LOG_MARK,
4401 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4402 modulename, json_type( order_spec->type ) );
4404 osrfAppSessionStatus(
4406 OSRF_STATUS_INTERNALSERVERERROR,
4407 "osrfMethodException",
4409 "Malformed ORDER BY clause -- see error log for more details"
4411 buffer_free( order_buf );
4413 buffer_free( group_buf );
4414 buffer_free( sql_buf );
4415 if( defaultselhash )
4416 jsonObjectFree( defaultselhash );
4420 const char* class_alias =
4421 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4423 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4426 OSRF_BUFFER_ADD( order_buf, ", " );
4428 order_buf = buffer_init( 128 );
4430 if( !field || !class_alias ) {
4431 osrfLogError( OSRF_LOG_MARK,
4432 "%s: Missing class or field name in field specification "
4433 "of ORDER BY clause",
4436 osrfAppSessionStatus(
4438 OSRF_STATUS_INTERNALSERVERERROR,
4439 "osrfMethodException",
4441 "Malformed ORDER BY clause -- see error log for more details"
4443 buffer_free( order_buf );
4445 buffer_free( group_buf );
4446 buffer_free( sql_buf );
4447 if( defaultselhash )
4448 jsonObjectFree( defaultselhash );
4452 ClassInfo* order_class_info = search_alias( class_alias );
4453 if( ! order_class_info ) {
4454 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4455 "not in FROM clause", modulename, class_alias );
4457 osrfAppSessionStatus(
4459 OSRF_STATUS_INTERNALSERVERERROR,
4460 "osrfMethodException",
4462 "Invalid class referenced in ORDER BY clause -- "
4463 "see error log for more details"
4466 buffer_free( group_buf );
4467 buffer_free( sql_buf );
4468 if( defaultselhash )
4469 jsonObjectFree( defaultselhash );
4473 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4475 osrfLogError( OSRF_LOG_MARK,
4476 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4477 modulename, class_alias, field );
4479 osrfAppSessionStatus(
4481 OSRF_STATUS_INTERNALSERVERERROR,
4482 "osrfMethodException",
4484 "Invalid field referenced in ORDER BY clause -- "
4485 "see error log for more details"
4488 buffer_free( group_buf );
4489 buffer_free( sql_buf );
4490 if( defaultselhash )
4491 jsonObjectFree( defaultselhash );
4493 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4494 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4495 modulename, field );
4497 osrfAppSessionStatus(
4499 OSRF_STATUS_INTERNALSERVERERROR,
4500 "osrfMethodException",
4502 "Virtual field in ORDER BY clause -- see error log for more details"
4504 buffer_free( order_buf );
4506 buffer_free( group_buf );
4507 buffer_free( sql_buf );
4508 if( defaultselhash )
4509 jsonObjectFree( defaultselhash );
4513 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4514 char* transform_str = searchFieldTransform(
4515 class_alias, field_def, order_spec );
4516 if( ! transform_str ) {
4518 osrfAppSessionStatus(
4520 OSRF_STATUS_INTERNALSERVERERROR,
4521 "osrfMethodException",
4523 "Severe query error in ORDER BY clause -- "
4524 "see error log for more details"
4526 buffer_free( order_buf );
4528 buffer_free( group_buf );
4529 buffer_free( sql_buf );
4530 if( defaultselhash )
4531 jsonObjectFree( defaultselhash );
4535 OSRF_BUFFER_ADD( order_buf, transform_str );
4536 free( transform_str );
4539 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4541 const char* direction =
4542 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4544 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4545 OSRF_BUFFER_ADD( order_buf, " DESC" );
4547 OSRF_BUFFER_ADD( order_buf, " ASC" );
4550 } else if( JSON_HASH == order_hash->type ) {
4551 // This hash is keyed on class alias. Each class has either
4552 // an array of field names or a hash keyed on field name.
4553 jsonIterator* class_itr = jsonNewIterator( order_hash );
4554 while( (snode = jsonIteratorNext( class_itr )) ) {
4556 ClassInfo* order_class_info = search_alias( class_itr->key );
4557 if( ! order_class_info ) {
4558 osrfLogError( OSRF_LOG_MARK,
4559 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4560 modulename, class_itr->key );
4562 osrfAppSessionStatus(
4564 OSRF_STATUS_INTERNALSERVERERROR,
4565 "osrfMethodException",
4567 "Invalid class referenced in ORDER BY clause -- "
4568 "see error log for more details"
4570 jsonIteratorFree( class_itr );
4571 buffer_free( order_buf );
4573 buffer_free( group_buf );
4574 buffer_free( sql_buf );
4575 if( defaultselhash )
4576 jsonObjectFree( defaultselhash );
4580 osrfHash* field_list_def = order_class_info->fields;
4582 if( snode->type == JSON_HASH ) {
4584 // Hash is keyed on field names from the current class. For each field
4585 // there is another layer of hash to define the sorting details, if any,
4586 // or a string to indicate direction of sorting.
4587 jsonIterator* order_itr = jsonNewIterator( snode );
4588 while( (onode = jsonIteratorNext( order_itr )) ) {
4590 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4592 osrfLogError( OSRF_LOG_MARK,
4593 "%s: Invalid field \"%s\" in ORDER BY clause",
4594 modulename, order_itr->key );
4596 osrfAppSessionStatus(
4598 OSRF_STATUS_INTERNALSERVERERROR,
4599 "osrfMethodException",
4601 "Invalid field in ORDER BY clause -- "
4602 "see error log for more details"
4604 jsonIteratorFree( order_itr );
4605 jsonIteratorFree( class_itr );
4606 buffer_free( order_buf );
4608 buffer_free( group_buf );
4609 buffer_free( sql_buf );
4610 if( defaultselhash )
4611 jsonObjectFree( defaultselhash );
4613 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4614 osrfLogError( OSRF_LOG_MARK,
4615 "%s: Virtual field \"%s\" in ORDER BY clause",
4616 modulename, order_itr->key );
4618 osrfAppSessionStatus(
4620 OSRF_STATUS_INTERNALSERVERERROR,
4621 "osrfMethodException",
4623 "Virtual field in ORDER BY clause -- "
4624 "see error log for more details"
4626 jsonIteratorFree( order_itr );
4627 jsonIteratorFree( class_itr );
4628 buffer_free( order_buf );
4630 buffer_free( group_buf );
4631 buffer_free( sql_buf );
4632 if( defaultselhash )
4633 jsonObjectFree( defaultselhash );
4637 const char* direction = NULL;
4638 if( onode->type == JSON_HASH ) {
4639 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4640 string = searchFieldTransform(
4642 osrfHashGet( field_list_def, order_itr->key ),
4646 if( ctx ) osrfAppSessionStatus(
4648 OSRF_STATUS_INTERNALSERVERERROR,
4649 "osrfMethodException",
4651 "Severe query error in ORDER BY clause -- "
4652 "see error log for more details"
4654 jsonIteratorFree( order_itr );
4655 jsonIteratorFree( class_itr );
4657 buffer_free( group_buf );
4658 buffer_free( order_buf);
4659 buffer_free( sql_buf );
4660 if( defaultselhash )
4661 jsonObjectFree( defaultselhash );
4665 growing_buffer* field_buf = buffer_init( 16 );
4666 buffer_fadd( field_buf, "\"%s\".%s",
4667 class_itr->key, order_itr->key );
4668 string = buffer_release( field_buf );
4671 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4672 const char* dir = jsonObjectGetString( tmp_const );
4673 if(!strncasecmp( dir, "d", 1 )) {
4674 direction = " DESC";
4680 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4681 osrfLogError( OSRF_LOG_MARK,
4682 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4683 modulename, json_type( onode->type ) );
4685 osrfAppSessionStatus(
4687 OSRF_STATUS_INTERNALSERVERERROR,
4688 "osrfMethodException",
4690 "Malformed ORDER BY clause -- see error log for more details"
4692 jsonIteratorFree( order_itr );
4693 jsonIteratorFree( class_itr );
4695 buffer_free( group_buf );
4696 buffer_free( order_buf );
4697 buffer_free( sql_buf );
4698 if( defaultselhash )
4699 jsonObjectFree( defaultselhash );
4703 string = strdup( order_itr->key );
4704 const char* dir = jsonObjectGetString( onode );
4705 if( !strncasecmp( dir, "d", 1 )) {
4706 direction = " DESC";
4713 OSRF_BUFFER_ADD( order_buf, ", " );
4715 order_buf = buffer_init( 128 );
4717 OSRF_BUFFER_ADD( order_buf, string );
4721 OSRF_BUFFER_ADD( order_buf, direction );
4725 jsonIteratorFree( order_itr );
4727 } else if( snode->type == JSON_ARRAY ) {
4729 // Array is a list of fields from the current class
4730 unsigned long order_idx = 0;
4731 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4733 const char* _f = jsonObjectGetString( onode );
4735 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4737 osrfLogError( OSRF_LOG_MARK,
4738 "%s: Invalid field \"%s\" in ORDER BY clause",
4741 osrfAppSessionStatus(
4743 OSRF_STATUS_INTERNALSERVERERROR,
4744 "osrfMethodException",
4746 "Invalid field in ORDER BY clause -- "
4747 "see error log for more details"
4749 jsonIteratorFree( class_itr );
4750 buffer_free( order_buf );
4752 buffer_free( group_buf );
4753 buffer_free( sql_buf );
4754 if( defaultselhash )
4755 jsonObjectFree( defaultselhash );
4757 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4758 osrfLogError( OSRF_LOG_MARK,
4759 "%s: Virtual field \"%s\" in ORDER BY clause",
4762 osrfAppSessionStatus(
4764 OSRF_STATUS_INTERNALSERVERERROR,
4765 "osrfMethodException",
4767 "Virtual field in ORDER BY clause -- "
4768 "see error log for more details"
4770 jsonIteratorFree( class_itr );
4771 buffer_free( order_buf );
4773 buffer_free( group_buf );
4774 buffer_free( sql_buf );
4775 if( defaultselhash )
4776 jsonObjectFree( defaultselhash );
4781 OSRF_BUFFER_ADD( order_buf, ", " );
4783 order_buf = buffer_init( 128 );
4785 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4789 // IT'S THE OOOOOOOOOOOLD STYLE!
4791 osrfLogError( OSRF_LOG_MARK,
4792 "%s: Possible SQL injection attempt; direct order by is not allowed",
4795 osrfAppSessionStatus(
4797 OSRF_STATUS_INTERNALSERVERERROR,
4798 "osrfMethodException",
4800 "Severe query error -- see error log for more details"
4805 buffer_free( group_buf );
4806 buffer_free( order_buf );
4807 buffer_free( sql_buf );
4808 if( defaultselhash )
4809 jsonObjectFree( defaultselhash );
4810 jsonIteratorFree( class_itr );
4814 jsonIteratorFree( class_itr );
4816 osrfLogError( OSRF_LOG_MARK,
4817 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4818 modulename, json_type( order_hash->type ) );
4820 osrfAppSessionStatus(
4822 OSRF_STATUS_INTERNALSERVERERROR,
4823 "osrfMethodException",
4825 "Malformed ORDER BY clause -- see error log for more details"
4827 buffer_free( order_buf );
4829 buffer_free( group_buf );
4830 buffer_free( sql_buf );
4831 if( defaultselhash )
4832 jsonObjectFree( defaultselhash );
4837 order_by_list = buffer_release( order_buf );
4841 string = buffer_release( group_buf );
4843 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4844 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4845 OSRF_BUFFER_ADD( sql_buf, string );
4850 if( having_buf && *having_buf ) {
4851 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4852 OSRF_BUFFER_ADD( sql_buf, having_buf );
4856 if( order_by_list ) {
4858 if( *order_by_list ) {
4859 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4860 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4863 free( order_by_list );
4867 const char* str = jsonObjectGetString( limit );
4868 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4872 const char* str = jsonObjectGetString( offset );
4873 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4876 if( !(flags & SUBSELECT) )
4877 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4879 if( defaultselhash )
4880 jsonObjectFree( defaultselhash );
4882 return buffer_release( sql_buf );
4884 } // end of SELECT()
4886 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4888 const char* locale = osrf_message_get_last_locale();
4890 osrfHash* fields = osrfHashGet( meta, "fields" );
4891 char* core_class = osrfHashGet( meta, "classname" );
4893 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4895 jsonObject* node = NULL;
4896 jsonObject* snode = NULL;
4897 jsonObject* onode = NULL;
4898 const jsonObject* _tmp = NULL;
4899 jsonObject* selhash = NULL;
4900 jsonObject* defaultselhash = NULL;
4902 growing_buffer* sql_buf = buffer_init( 128 );
4903 growing_buffer* select_buf = buffer_init( 128 );
4905 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4906 defaultselhash = jsonNewObjectType( JSON_HASH );
4907 selhash = defaultselhash;
4910 // If there's no SELECT list for the core class, build one
4911 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4912 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4914 // Add every non-virtual field to the field list
4915 osrfHash* field_def = NULL;
4916 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4917 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4918 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4919 const char* field = osrfHashIteratorKey( field_itr );
4920 jsonObjectPush( field_list, jsonNewObject( field ) );
4923 osrfHashIteratorFree( field_itr );
4924 jsonObjectSetKey( selhash, core_class, field_list );
4928 jsonIterator* class_itr = jsonNewIterator( selhash );
4929 while( (snode = jsonIteratorNext( class_itr )) ) {
4931 const char* cname = class_itr->key;
4932 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4936 if( strcmp(core_class,class_itr->key )) {
4940 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4941 if( !found->size ) {
4942 jsonObjectFree( found );
4946 jsonObjectFree( found );
4949 jsonIterator* select_itr = jsonNewIterator( snode );
4950 while( (node = jsonIteratorNext( select_itr )) ) {
4951 const char* item_str = jsonObjectGetString( node );
4952 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4953 char* fname = osrfHashGet( field, "name" );
4961 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4966 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4967 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4970 i18n = osrfHashGet( field, "i18n" );
4972 if( str_is_true( i18n ) ) {
4973 char* pkey = osrfHashGet( idlClass, "primarykey" );
4974 char* tname = osrfHashGet( idlClass, "tablename" );
4976 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4977 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4978 tname, cname, fname, pkey, cname, pkey, locale, fname );
4980 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4983 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4987 jsonIteratorFree( select_itr );
4990 jsonIteratorFree( class_itr );
4992 char* col_list = buffer_release( select_buf );
4993 char* table = oilsGetRelation( meta );
4995 table = strdup( "(null)" );
4997 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5001 // Clear the query stack (as a fail-safe precaution against possible
5002 // leftover garbage); then push the first query frame onto the stack.
5003 clear_query_stack();
5005 if( add_query_core( NULL, core_class ) ) {
5007 osrfAppSessionStatus(
5009 OSRF_STATUS_INTERNALSERVERERROR,
5010 "osrfMethodException",
5012 "Unable to build query frame for core class"
5018 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5019 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5020 OSRF_BUFFER_ADD( sql_buf, join_clause );
5021 free( join_clause );
5024 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5025 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5027 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5029 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5031 osrfAppSessionStatus(
5033 OSRF_STATUS_INTERNALSERVERERROR,
5034 "osrfMethodException",
5036 "Severe query error -- see error log for more details"
5038 buffer_free( sql_buf );
5039 if( defaultselhash )
5040 jsonObjectFree( defaultselhash );
5041 clear_query_stack();
5044 buffer_add( sql_buf, pred );
5049 char* string = NULL;
5050 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5052 growing_buffer* order_buf = buffer_init( 128 );
5055 jsonIterator* class_itr = jsonNewIterator( _tmp );
5056 while( (snode = jsonIteratorNext( class_itr )) ) {
5058 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
5061 if( snode->type == JSON_HASH ) {
5063 jsonIterator* order_itr = jsonNewIterator( snode );
5064 while( (onode = jsonIteratorNext( order_itr )) ) {
5066 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5067 class_itr->key, order_itr->key );
5071 char* direction = NULL;
5072 if( onode->type == JSON_HASH ) {
5073 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5074 string = searchFieldTransform( class_itr->key, field_def, onode );
5076 osrfAppSessionStatus(
5078 OSRF_STATUS_INTERNALSERVERERROR,
5079 "osrfMethodException",
5081 "Severe query error in ORDER BY clause -- "
5082 "see error log for more details"
5084 jsonIteratorFree( order_itr );
5085 jsonIteratorFree( class_itr );
5086 buffer_free( order_buf );
5087 buffer_free( sql_buf );
5088 if( defaultselhash )
5089 jsonObjectFree( defaultselhash );
5090 clear_query_stack();
5094 growing_buffer* field_buf = buffer_init( 16 );
5095 buffer_fadd( field_buf, "\"%s\".%s",
5096 class_itr->key, order_itr->key );
5097 string = buffer_release( field_buf );
5100 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5101 const char* dir = jsonObjectGetString( _tmp );
5102 if(!strncasecmp( dir, "d", 1 )) {
5103 direction = " DESC";
5107 string = strdup( order_itr->key );
5108 const char* dir = jsonObjectGetString( onode );
5109 if( !strncasecmp( dir, "d", 1 )) {
5110 direction = " DESC";
5119 buffer_add( order_buf, ", " );
5122 buffer_add( order_buf, string );
5126 buffer_add( order_buf, direction );
5130 jsonIteratorFree( order_itr );
5133 const char* str = jsonObjectGetString( snode );
5134 buffer_add( order_buf, str );
5140 jsonIteratorFree( class_itr );
5142 string = buffer_release( order_buf );
5145 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5146 OSRF_BUFFER_ADD( sql_buf, string );
5152 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5153 const char* str = jsonObjectGetString( _tmp );
5161 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5163 const char* str = jsonObjectGetString( _tmp );
5172 if( defaultselhash )
5173 jsonObjectFree( defaultselhash );
5174 clear_query_stack();
5176 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5177 return buffer_release( sql_buf );
5180 int doJSONSearch ( osrfMethodContext* ctx ) {
5181 if(osrfMethodVerifyContext( ctx )) {
5182 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5186 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5190 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5194 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5195 flags |= SELECT_DISTINCT;
5197 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5198 flags |= DISABLE_I18N;
5200 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5201 clear_query_stack(); // a possibly needless precaution
5202 char* sql = buildQuery( ctx, hash, flags );
5203 clear_query_stack();
5210 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5213 dbhandle = writehandle;
5215 dbi_result result = dbi_conn_query( dbhandle, sql );
5218 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5220 if( dbi_result_first_row( result )) {
5221 /* JSONify the result */
5222 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5225 jsonObject* return_val = oilsMakeJSONFromResult( result );
5226 osrfAppRespond( ctx, return_val );
5227 jsonObjectFree( return_val );
5228 } while( dbi_result_next_row( result ));
5231 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5234 osrfAppRespondComplete( ctx, NULL );
5236 /* clean up the query */
5237 dbi_result_free( result );
5242 int errnum = dbi_conn_error( dbhandle, &msg );
5243 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5244 modulename, sql, errnum, msg ? msg : "(No description available)" );
5245 osrfAppSessionStatus(
5247 OSRF_STATUS_INTERNALSERVERERROR,
5248 "osrfMethodException",
5250 "Severe query error -- see error log for more details"
5252 if( !oilsIsDBConnected( dbhandle ))
5253 osrfAppSessionPanic( ctx->session );
5260 // The last parameter, err, is used to report an error condition by updating an int owned by
5261 // the calling code.
5263 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5264 // It is the responsibility of the calling code to initialize *err before the
5265 // call, so that it will be able to make sense of the result.
5267 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5268 // redundant anyway.
5269 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5270 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5273 dbhandle = writehandle;
5275 char* core_class = osrfHashGet( class_meta, "classname" );
5276 char* pkey = osrfHashGet( class_meta, "primarykey" );
5278 const jsonObject* _tmp;
5280 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5282 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5287 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5289 dbi_result result = dbi_conn_query( dbhandle, sql );
5290 if( NULL == result ) {
5292 int errnum = dbi_conn_error( dbhandle, &msg );
5293 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5294 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5295 msg ? msg : "(No description available)" );
5296 if( !oilsIsDBConnected( dbhandle ))
5297 osrfAppSessionPanic( ctx->session );
5298 osrfAppSessionStatus(
5300 OSRF_STATUS_INTERNALSERVERERROR,
5301 "osrfMethodException",
5303 "Severe query error -- see error log for more details"
5310 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5313 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5314 jsonObject* row_obj = NULL;
5316 if( dbi_result_first_row( result )) {
5318 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5319 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5320 // eliminate the duplicates.
5321 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5322 osrfHash* dedup = osrfNewHash();
5324 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5325 char* pkey_val = oilsFMGetString( row_obj, pkey );
5326 if( osrfHashGet( dedup, pkey_val ) ) {
5327 jsonObjectFree( row_obj );
5330 osrfHashSet( dedup, pkey_val, pkey_val );
5331 jsonObjectPush( res_list, row_obj );
5333 } while( dbi_result_next_row( result ));
5334 osrfHashFree( dedup );
5337 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5341 /* clean up the query */
5342 dbi_result_free( result );
5345 // If we're asked to flesh, and there's anything to flesh, then flesh it
5346 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5347 // something that he has no permission to look at).
5348 if( res_list->size && query_hash && ! enforce_pcrud ) {
5349 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5351 // Get the flesh depth
5352 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5353 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5354 flesh_depth = max_flesh_depth;
5356 // We need a non-zero flesh depth, and a list of fields to flesh
5357 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5358 if( temp_blob && flesh_depth > 0 ) {
5360 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5361 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5363 osrfStringArray* link_fields = NULL;
5364 osrfHash* links = osrfHashGet( class_meta, "links" );
5366 // Make an osrfStringArray of the names of fields to be fleshed
5367 if( flesh_fields ) {
5368 if( flesh_fields->size == 1 ) {
5369 const char* _t = jsonObjectGetString(
5370 jsonObjectGetIndex( flesh_fields, 0 ) );
5371 if( !strcmp( _t, "*" ))
5372 link_fields = osrfHashKeys( links );
5375 if( !link_fields ) {
5377 link_fields = osrfNewStringArray( 1 );
5378 jsonIterator* _i = jsonNewIterator( flesh_fields );
5379 while ((_f = jsonIteratorNext( _i ))) {
5380 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5382 jsonIteratorFree( _i );
5386 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5388 // Iterate over the JSON_ARRAY of rows
5390 unsigned long res_idx = 0;
5391 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5394 const char* link_field;
5396 // Iterate over the list of fleshable fields
5397 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5399 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5401 osrfHash* kid_link = osrfHashGet( links, link_field );
5403 continue; // Not a link field; skip it
5405 osrfHash* field = osrfHashGet( fields, link_field );
5407 continue; // Not a field at all; skip it (IDL is ill-formed)
5409 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5410 osrfHashGet( kid_link, "class" ));
5412 continue; // The class it links to doesn't exist; skip it
5414 const char* reltype = osrfHashGet( kid_link, "reltype" );
5416 continue; // No reltype; skip it (IDL is ill-formed)
5418 osrfHash* value_field = field;
5420 if( !strcmp( reltype, "has_many" )
5421 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5422 value_field = osrfHashGet(
5423 fields, osrfHashGet( class_meta, "primarykey" ) );
5426 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5428 if( link_map->size > 0 ) {
5429 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5432 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5437 osrfHashGet( kid_link, "class" ),
5444 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5445 osrfHashGet( kid_link, "field" ),
5446 osrfHashGet( kid_link, "class" ),
5447 osrfHashGet( kid_link, "key" ),
5448 osrfHashGet( kid_link, "reltype" )
5451 const char* search_key = jsonObjectGetString(
5452 jsonObjectGetIndex( cur,
5453 atoi( osrfHashGet( value_field, "array_position" ) )
5458 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5462 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5464 // construct WHERE clause
5465 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5468 osrfHashGet( kid_link, "key" ),
5469 jsonNewObject( search_key )
5472 // construct the rest of the query, mostly
5473 // by copying pieces of the previous level of query
5474 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5475 jsonObjectSetKey( rest_of_query, "flesh",
5476 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5480 jsonObjectSetKey( rest_of_query, "flesh_fields",
5481 jsonObjectClone( flesh_blob ));
5483 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5484 jsonObjectSetKey( rest_of_query, "order_by",
5485 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5489 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5490 jsonObjectSetKey( rest_of_query, "select",
5491 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5495 // do the query, recursively, to expand the fleshable field
5496 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5497 where_clause, rest_of_query, err );
5499 jsonObjectFree( where_clause );
5500 jsonObjectFree( rest_of_query );
5503 osrfStringArrayFree( link_fields );
5504 jsonObjectFree( res_list );
5505 jsonObjectFree( flesh_blob );
5509 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5510 osrfHashGet( kid_link, "class" ), kids->size );
5512 // Traverse the result set
5513 jsonObject* X = NULL;
5514 if( link_map->size > 0 && kids->size > 0 ) {
5516 kids = jsonNewObjectType( JSON_ARRAY );
5518 jsonObject* _k_node;
5519 unsigned long res_idx = 0;
5520 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5526 (unsigned long) atoi(
5532 osrfHashGet( kid_link, "class" )
5536 osrfStringArrayGetString( link_map, 0 )
5544 } // end while loop traversing X
5547 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5548 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5549 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5550 osrfHashGet( kid_link, "field" ));
5553 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5554 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5558 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5560 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5561 osrfHashGet( kid_link, "field" ) );
5564 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5565 jsonObjectClone( kids )
5570 jsonObjectFree( kids );
5574 jsonObjectFree( kids );
5576 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5577 osrfHashGet( kid_link, "field" ) );
5578 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5580 } // end while loop traversing list of fleshable fields
5581 } // end while loop traversing res_list
5582 jsonObjectFree( flesh_blob );
5583 osrfStringArrayFree( link_fields );
5592 int doUpdate( osrfMethodContext* ctx ) {
5593 if( osrfMethodVerifyContext( ctx )) {
5594 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5599 timeout_needs_resetting = 1;
5601 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5603 jsonObject* target = NULL;
5605 target = jsonObjectGetIndex( ctx->params, 1 );
5607 target = jsonObjectGetIndex( ctx->params, 0 );
5609 if(!verifyObjectClass( ctx, target )) {
5610 osrfAppRespondComplete( ctx, NULL );
5614 if( getXactId( ctx ) == NULL ) {
5615 osrfAppSessionStatus(
5617 OSRF_STATUS_BADREQUEST,
5618 "osrfMethodException",
5620 "No active transaction -- required for UPDATE"
5622 osrfAppRespondComplete( ctx, NULL );
5626 // The following test is harmless but redundant. If a class is
5627 // readonly, we don't register an update method for it.
5628 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5629 osrfAppSessionStatus(
5631 OSRF_STATUS_BADREQUEST,
5632 "osrfMethodException",
5634 "Cannot UPDATE readonly class"
5636 osrfAppRespondComplete( ctx, NULL );
5640 const char* trans_id = getXactId( ctx );
5642 // Set the last_xact_id
5643 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5645 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5646 trans_id, target->classname, index );
5647 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5650 char* pkey = osrfHashGet( meta, "primarykey" );
5651 osrfHash* fields = osrfHashGet( meta, "fields" );
5653 char* id = oilsFMGetString( target, pkey );
5657 "%s updating %s object with %s = %s",
5659 osrfHashGet( meta, "fieldmapper" ),
5664 dbhandle = writehandle;
5665 growing_buffer* sql = buffer_init( 128 );
5666 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5669 osrfHash* field_def = NULL;
5670 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5671 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5673 // Skip virtual fields, and the primary key
5674 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5677 const char* field_name = osrfHashIteratorKey( field_itr );
5678 if( ! strcmp( field_name, pkey ) )
5681 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5683 int value_is_numeric = 0; // boolean
5685 if( field_object && field_object->classname ) {
5686 value = oilsFMGetString(
5688 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5690 } else if( field_object && JSON_BOOL == field_object->type ) {
5691 if( jsonBoolIsTrue( field_object ) )
5692 value = strdup( "t" );
5694 value = strdup( "f" );
5696 value = jsonObjectToSimpleString( field_object );
5697 if( field_object && JSON_NUMBER == field_object->type )
5698 value_is_numeric = 1;
5701 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5702 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5704 if( !field_object || field_object->type == JSON_NULL ) {
5705 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5706 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5710 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5711 buffer_fadd( sql, " %s = NULL", field_name );
5714 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5718 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5720 const char* numtype = get_datatype( field_def );
5721 if( !strncmp( numtype, "INT", 3 ) ) {
5722 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5723 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5724 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5726 // Must really be intended as a string, so quote it
5727 if( dbi_conn_quote_string( dbhandle, &value )) {
5728 buffer_fadd( sql, " %s = %s", field_name, value );
5730 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5731 modulename, value );
5732 osrfAppSessionStatus(
5734 OSRF_STATUS_INTERNALSERVERERROR,
5735 "osrfMethodException",
5737 "Error quoting string -- please see the error log for more details"
5741 osrfHashIteratorFree( field_itr );
5743 osrfAppRespondComplete( ctx, NULL );
5748 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5751 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5755 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5756 buffer_fadd( sql, " %s = %s", field_name, value );
5758 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5759 osrfAppSessionStatus(
5761 OSRF_STATUS_INTERNALSERVERERROR,
5762 "osrfMethodException",
5764 "Error quoting string -- please see the error log for more details"
5768 osrfHashIteratorFree( field_itr );
5770 osrfAppRespondComplete( ctx, NULL );
5779 osrfHashIteratorFree( field_itr );
5781 jsonObject* obj = jsonNewObject( id );
5783 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5784 dbi_conn_quote_string( dbhandle, &id );
5786 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5788 char* query = buffer_release( sql );
5789 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5791 dbi_result result = dbi_conn_query( dbhandle, query );
5796 jsonObjectFree( obj );
5797 obj = jsonNewObject( NULL );
5799 int errnum = dbi_conn_error( dbhandle, &msg );
5802 "%s ERROR updating %s object with %s = %s: %d %s",
5804 osrfHashGet( meta, "fieldmapper" ),
5808 msg ? msg : "(No description available)"
5810 osrfAppSessionStatus(
5812 OSRF_STATUS_INTERNALSERVERERROR,
5813 "osrfMethodException",
5815 "Error in updating a row -- please see the error log for more details"
5817 if( !oilsIsDBConnected( dbhandle ))
5818 osrfAppSessionPanic( ctx->session );
5823 osrfAppRespondComplete( ctx, obj );
5824 jsonObjectFree( obj );
5828 int doDelete( osrfMethodContext* ctx ) {
5829 if( osrfMethodVerifyContext( ctx )) {
5830 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5835 timeout_needs_resetting = 1;
5837 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5839 if( getXactId( ctx ) == NULL ) {
5840 osrfAppSessionStatus(
5842 OSRF_STATUS_BADREQUEST,
5843 "osrfMethodException",
5845 "No active transaction -- required for DELETE"
5847 osrfAppRespondComplete( ctx, NULL );
5851 // The following test is harmless but redundant. If a class is
5852 // readonly, we don't register a delete method for it.
5853 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5854 osrfAppSessionStatus(
5856 OSRF_STATUS_BADREQUEST,
5857 "osrfMethodException",
5859 "Cannot DELETE readonly class"
5861 osrfAppRespondComplete( ctx, NULL );
5865 dbhandle = writehandle;
5867 char* pkey = osrfHashGet( meta, "primarykey" );
5874 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5875 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5876 osrfAppRespondComplete( ctx, NULL );
5880 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5882 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5883 osrfAppRespondComplete( ctx, NULL );
5886 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5891 "%s deleting %s object with %s = %s",
5893 osrfHashGet( meta, "fieldmapper" ),
5898 jsonObject* obj = jsonNewObject( id );
5900 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5901 dbi_conn_quote_string( writehandle, &id );
5903 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5904 osrfHashGet( meta, "tablename" ), pkey, id );
5909 jsonObjectFree( obj );
5910 obj = jsonNewObject( NULL );
5912 int errnum = dbi_conn_error( writehandle, &msg );
5915 "%s ERROR deleting %s object with %s = %s: %d %s",
5917 osrfHashGet( meta, "fieldmapper" ),
5921 msg ? msg : "(No description available)"
5923 osrfAppSessionStatus(
5925 OSRF_STATUS_INTERNALSERVERERROR,
5926 "osrfMethodException",
5928 "Error in deleting a row -- please see the error log for more details"
5930 if( !oilsIsDBConnected( writehandle ))
5931 osrfAppSessionPanic( ctx->session );
5936 osrfAppRespondComplete( ctx, obj );
5937 jsonObjectFree( obj );
5942 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5943 @param result An iterator for a result set; we only look at the current row.
5944 @param @meta Pointer to the class metadata for the core class.
5945 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5947 If a column is not defined in the IDL, or if it has no array_position defined for it in
5948 the IDL, or if it is defined as virtual, ignore it.
5950 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5951 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5952 array_position in the IDL.
5954 A field defined in the IDL but not represented in the returned row will leave a hole
5955 in the JSON_ARRAY. In effect it will be treated as a null value.
5957 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5958 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5959 classname corresponding to the @a meta argument.
5961 The calling code is responsible for freeing the the resulting jsonObject by calling
5964 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5965 if( !( result && meta )) return NULL;
5967 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5968 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5969 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5971 osrfHash* fields = osrfHashGet( meta, "fields" );
5973 int columnIndex = 1;
5974 const char* columnName;
5976 /* cycle through the columns in the row returned from the database */
5977 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5979 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5981 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5983 /* determine the field type and storage attributes */
5984 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5985 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5987 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5988 // or if it has no sequence number there, or if it's virtual, skip it.
5989 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5992 if( str_is_true( osrfHashGet( _f, "virtual" )))
5993 continue; // skip this column: IDL says it's virtual
5995 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5996 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5997 continue; // since we assign sequence numbers dynamically as we load the IDL.
5999 fmIndex = atoi( pos );
6000 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6002 continue; // This field is not defined in the IDL
6005 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6006 // sequence number from the IDL (which is likely to be different from the sequence
6007 // of columns in the SELECT clause).
6008 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6009 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6014 case DBI_TYPE_INTEGER :
6016 if( attr & DBI_INTEGER_SIZE8 )
6017 jsonObjectSetIndex( object, fmIndex,
6018 jsonNewNumberObject(
6019 dbi_result_get_longlong_idx( result, columnIndex )));
6021 jsonObjectSetIndex( object, fmIndex,
6022 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6026 case DBI_TYPE_DECIMAL :
6027 jsonObjectSetIndex( object, fmIndex,
6028 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6031 case DBI_TYPE_STRING :
6036 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6041 case DBI_TYPE_DATETIME : {
6043 char dt_string[ 256 ] = "";
6046 // Fetch the date column as a time_t
6047 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6049 // Translate the time_t to a human-readable string
6050 if( !( attr & DBI_DATETIME_DATE )) {
6051 gmtime_r( &_tmp_dt, &gmdt );
6052 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6053 } else if( !( attr & DBI_DATETIME_TIME )) {
6054 localtime_r( &_tmp_dt, &gmdt );
6055 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6057 localtime_r( &_tmp_dt, &gmdt );
6058 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6061 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6065 case DBI_TYPE_BINARY :
6066 osrfLogError( OSRF_LOG_MARK,
6067 "Can't do binary at column %s : index %d", columnName, columnIndex );
6076 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6077 if( !result ) return NULL;
6079 jsonObject* object = jsonNewObject( NULL );
6082 char dt_string[ 256 ];
6086 int columnIndex = 1;
6088 unsigned short type;
6089 const char* columnName;
6091 /* cycle through the column list */
6092 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6094 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6096 fmIndex = -1; // reset the position
6098 /* determine the field type and storage attributes */
6099 type = dbi_result_get_field_type_idx( result, columnIndex );
6100 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6102 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6103 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6108 case DBI_TYPE_INTEGER :
6110 if( attr & DBI_INTEGER_SIZE8 )
6111 jsonObjectSetKey( object, columnName,
6112 jsonNewNumberObject( dbi_result_get_longlong_idx(
6113 result, columnIndex )) );
6115 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6116 dbi_result_get_int_idx( result, columnIndex )) );
6119 case DBI_TYPE_DECIMAL :
6120 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6121 dbi_result_get_double_idx( result, columnIndex )) );
6124 case DBI_TYPE_STRING :
6125 jsonObjectSetKey( object, columnName,
6126 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6129 case DBI_TYPE_DATETIME :
6131 memset( dt_string, '\0', sizeof( dt_string ));
6132 memset( &gmdt, '\0', sizeof( gmdt ));
6134 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6136 if( !( attr & DBI_DATETIME_DATE )) {
6137 gmtime_r( &_tmp_dt, &gmdt );
6138 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6139 } else if( !( attr & DBI_DATETIME_TIME )) {
6140 localtime_r( &_tmp_dt, &gmdt );
6141 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6143 localtime_r( &_tmp_dt, &gmdt );
6144 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6147 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6150 case DBI_TYPE_BINARY :
6151 osrfLogError( OSRF_LOG_MARK,
6152 "Can't do binary at column %s : index %d", columnName, columnIndex );
6156 } // end while loop traversing result
6161 // Interpret a string as true or false
6162 int str_is_true( const char* str ) {
6163 if( NULL == str || strcasecmp( str, "true" ) )
6169 // Interpret a jsonObject as true or false
6170 static int obj_is_true( const jsonObject* obj ) {
6173 else switch( obj->type )
6181 if( strcasecmp( obj->value.s, "true" ) )
6185 case JSON_NUMBER : // Support 1/0 for perl's sake
6186 if( jsonObjectGetNumber( obj ) == 1.0 )
6195 // Translate a numeric code into a text string identifying a type of
6196 // jsonObject. To be used for building error messages.
6197 static const char* json_type( int code ) {
6203 return "JSON_ARRAY";
6205 return "JSON_STRING";
6207 return "JSON_NUMBER";
6213 return "(unrecognized)";
6217 // Extract the "primitive" attribute from an IDL field definition.
6218 // If we haven't initialized the app, then we must be running in
6219 // some kind of testbed. In that case, default to "string".
6220 static const char* get_primitive( osrfHash* field ) {
6221 const char* s = osrfHashGet( field, "primitive" );
6223 if( child_initialized )
6226 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6228 osrfHashGet( field, "name" )
6236 // Extract the "datatype" attribute from an IDL field definition.
6237 // If we haven't initialized the app, then we must be running in
6238 // some kind of testbed. In that case, default to to NUMERIC,
6239 // since we look at the datatype only for numbers.
6240 static const char* get_datatype( osrfHash* field ) {
6241 const char* s = osrfHashGet( field, "datatype" );
6243 if( child_initialized )
6246 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6248 osrfHashGet( field, "name" )
6257 @brief Determine whether a string is potentially a valid SQL identifier.
6258 @param s The identifier to be tested.
6259 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6261 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6262 need to follow all the rules exactly, such as requiring that the first character not
6265 We allow leading and trailing white space. In between, we do not allow punctuation
6266 (except for underscores and dollar signs), control characters, or embedded white space.
6268 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6269 for the foreseeable future such quoted identifiers are not likely to be an issue.
6271 int is_identifier( const char* s) {
6275 // Skip leading white space
6276 while( isspace( (unsigned char) *s ) )
6280 return 0; // Nothing but white space? Not okay.
6282 // Check each character until we reach white space or
6283 // end-of-string. Letters, digits, underscores, and
6284 // dollar signs are okay. With the exception of periods
6285 // (as in schema.identifier), control characters and other
6286 // punctuation characters are not okay. Anything else
6287 // is okay -- it could for example be part of a multibyte
6288 // UTF8 character such as a letter with diacritical marks,
6289 // and those are allowed.
6291 if( isalnum( (unsigned char) *s )
6295 ; // Fine; keep going
6296 else if( ispunct( (unsigned char) *s )
6297 || iscntrl( (unsigned char) *s ) )
6300 } while( *s && ! isspace( (unsigned char) *s ) );
6302 // If we found any white space in the above loop,
6303 // the rest had better be all white space.
6305 while( isspace( (unsigned char) *s ) )
6309 return 0; // White space was embedded within non-white space
6315 @brief Determine whether to accept a character string as a comparison operator.
6316 @param op The candidate comparison operator.
6317 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6319 We don't validate the operator for real. We just make sure that it doesn't contain
6320 any semicolons or white space (with special exceptions for a few specific operators).
6321 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6322 space but it's still not a valid operator, then the database will complain.
6324 Another approach would be to compare the string against a short list of approved operators.
6325 We don't do that because we want to allow custom operators like ">100*", which at this
6326 writing would be difficult or impossible to express otherwise in a JSON query.
6328 int is_good_operator( const char* op ) {
6329 if( !op ) return 0; // Sanity check
6333 if( isspace( (unsigned char) *s ) ) {
6334 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6335 // and IS NOT DISTINCT FROM.
6336 if( !strcasecmp( op, "similar to" ) )
6338 else if( !strcasecmp( op, "is distinct from" ) )
6340 else if( !strcasecmp( op, "is not distinct from" ) )
6345 else if( ';' == *s )
6353 @name Query Frame Management
6355 The following machinery supports a stack of query frames for use by SELECT().
6357 A query frame caches information about one level of a SELECT query. When we enter
6358 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6360 The query frame stores information about the core class, and about any joined classes
6363 The main purpose is to map table aliases to classes and tables, so that a query can
6364 join to the same table more than once. A secondary goal is to reduce the number of
6365 lookups in the IDL by caching the results.
6369 #define STATIC_CLASS_INFO_COUNT 3
6371 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6374 @brief Allocate a ClassInfo as raw memory.
6375 @return Pointer to the newly allocated ClassInfo.
6377 Except for the in_use flag, which is used only by the allocation and deallocation
6378 logic, we don't initialize the ClassInfo here.
6380 static ClassInfo* allocate_class_info( void ) {
6381 // In order to reduce the number of mallocs and frees, we return a static
6382 // instance of ClassInfo, if we can find one that we're not already using.
6383 // We rely on the fact that the compiler will implicitly initialize the
6384 // static instances so that in_use == 0.
6387 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6388 if( ! static_class_info[ i ].in_use ) {
6389 static_class_info[ i ].in_use = 1;
6390 return static_class_info + i;
6394 // The static ones are all in use. Malloc one.
6396 return safe_malloc( sizeof( ClassInfo ) );
6400 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6401 @param info Pointer to the ClassInfo to be cleared.
6403 static void clear_class_info( ClassInfo* info ) {
6408 // Free any malloc'd strings
6410 if( info->alias != info->alias_store )
6411 free( info->alias );
6413 if( info->class_name != info->class_name_store )
6414 free( info->class_name );
6416 free( info->source_def );
6418 info->alias = info->class_name = info->source_def = NULL;
6423 @brief Free a ClassInfo and everything it owns.
6424 @param info Pointer to the ClassInfo to be freed.
6426 static void free_class_info( ClassInfo* info ) {
6431 clear_class_info( info );
6433 // If it's one of the static instances, just mark it as not in use
6436 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6437 if( info == static_class_info + i ) {
6438 static_class_info[ i ].in_use = 0;
6443 // Otherwise it must have been malloc'd, so free it
6449 @brief Populate an already-allocated ClassInfo.
6450 @param info Pointer to the ClassInfo to be populated.
6451 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6453 @param class Name of the class.
6454 @return Zero if successful, or 1 if not.
6456 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6457 the relevant portions of the IDL for the specified class.
6459 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6462 osrfLogError( OSRF_LOG_MARK,
6463 "%s ERROR: No ClassInfo available to populate", modulename );
6464 info->alias = info->class_name = info->source_def = NULL;
6465 info->class_def = info->fields = info->links = NULL;
6470 osrfLogError( OSRF_LOG_MARK,
6471 "%s ERROR: No class name provided for lookup", modulename );
6472 info->alias = info->class_name = info->source_def = NULL;
6473 info->class_def = info->fields = info->links = NULL;
6477 // Alias defaults to class name if not supplied
6478 if( ! alias || ! alias[ 0 ] )
6481 // Look up class info in the IDL
6482 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6484 osrfLogError( OSRF_LOG_MARK,
6485 "%s ERROR: Class %s not defined in IDL", modulename, class );
6486 info->alias = info->class_name = info->source_def = NULL;
6487 info->class_def = info->fields = info->links = NULL;
6489 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6490 osrfLogError( OSRF_LOG_MARK,
6491 "%s ERROR: Class %s is defined as virtual", modulename, class );
6492 info->alias = info->class_name = info->source_def = NULL;
6493 info->class_def = info->fields = info->links = NULL;
6497 osrfHash* links = osrfHashGet( class_def, "links" );
6499 osrfLogError( OSRF_LOG_MARK,
6500 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6501 info->alias = info->class_name = info->source_def = NULL;
6502 info->class_def = info->fields = info->links = NULL;
6506 osrfHash* fields = osrfHashGet( class_def, "fields" );
6508 osrfLogError( OSRF_LOG_MARK,
6509 "%s ERROR: No fields 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 char* source_def = oilsGetRelation( class_def );
6519 // We got everything we need, so populate the ClassInfo
6520 if( strlen( alias ) > ALIAS_STORE_SIZE )
6521 info->alias = strdup( alias );
6523 strcpy( info->alias_store, alias );
6524 info->alias = info->alias_store;
6527 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6528 info->class_name = strdup( class );
6530 strcpy( info->class_name_store, class );
6531 info->class_name = info->class_name_store;
6534 info->source_def = source_def;
6536 info->class_def = class_def;
6537 info->links = links;
6538 info->fields = fields;
6543 #define STATIC_FRAME_COUNT 3
6545 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6548 @brief Allocate a QueryFrame as raw memory.
6549 @return Pointer to the newly allocated QueryFrame.
6551 Except for the in_use flag, which is used only by the allocation and deallocation
6552 logic, we don't initialize the QueryFrame here.
6554 static QueryFrame* allocate_frame( void ) {
6555 // In order to reduce the number of mallocs and frees, we return a static
6556 // instance of QueryFrame, if we can find one that we're not already using.
6557 // We rely on the fact that the compiler will implicitly initialize the
6558 // static instances so that in_use == 0.
6561 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6562 if( ! static_frame[ i ].in_use ) {
6563 static_frame[ i ].in_use = 1;
6564 return static_frame + i;
6568 // The static ones are all in use. Malloc one.
6570 return safe_malloc( sizeof( QueryFrame ) );
6574 @brief Free a QueryFrame, and all the memory it owns.
6575 @param frame Pointer to the QueryFrame to be freed.
6577 static void free_query_frame( QueryFrame* frame ) {
6582 clear_class_info( &frame->core );
6584 // Free the join list
6586 ClassInfo* info = frame->join_list;
6589 free_class_info( info );
6593 frame->join_list = NULL;
6596 // If the frame is a static instance, just mark it as unused
6598 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6599 if( frame == static_frame + i ) {
6600 static_frame[ i ].in_use = 0;
6605 // Otherwise it must have been malloc'd, so free it
6611 @brief Search a given QueryFrame for a specified alias.
6612 @param frame Pointer to the QueryFrame to be searched.
6613 @param target The alias for which to search.
6614 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6616 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6617 if( ! frame || ! target ) {
6621 ClassInfo* found_class = NULL;
6623 if( !strcmp( target, frame->core.alias ) )
6624 return &(frame->core);
6626 ClassInfo* curr_class = frame->join_list;
6627 while( curr_class ) {
6628 if( strcmp( target, curr_class->alias ) )
6629 curr_class = curr_class->next;
6631 found_class = curr_class;
6641 @brief Push a new (blank) QueryFrame onto the stack.
6643 static void push_query_frame( void ) {
6644 QueryFrame* frame = allocate_frame();
6645 frame->join_list = NULL;
6646 frame->next = curr_query;
6648 // Initialize the ClassInfo for the core class
6649 ClassInfo* core = &frame->core;
6650 core->alias = core->class_name = core->source_def = NULL;
6651 core->class_def = core->fields = core->links = NULL;
6657 @brief Pop a QueryFrame off the stack and destroy it.
6659 static void pop_query_frame( void ) {
6664 QueryFrame* popped = curr_query;
6665 curr_query = popped->next;
6667 free_query_frame( popped );
6671 @brief Populate the ClassInfo for the core class.
6672 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6673 class name as an alias.
6674 @param class_name Name of the core class.
6675 @return Zero if successful, or 1 if not.
6677 Populate the ClassInfo of the core class with copies of the alias and class name, and
6678 with pointers to the relevant portions of the IDL for the core class.
6680 static int add_query_core( const char* alias, const char* class_name ) {
6683 if( ! curr_query ) {
6684 osrfLogError( OSRF_LOG_MARK,
6685 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6687 } else if( curr_query->core.alias ) {
6688 osrfLogError( OSRF_LOG_MARK,
6689 "%s ERROR: Core class %s already populated as %s",
6690 modulename, curr_query->core.class_name, curr_query->core.alias );
6694 build_class_info( &curr_query->core, alias, class_name );
6695 if( curr_query->core.alias )
6698 osrfLogError( OSRF_LOG_MARK,
6699 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6705 @brief Search the current QueryFrame for a specified alias.
6706 @param target The alias for which to search.
6707 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6709 static inline ClassInfo* search_alias( const char* target ) {
6710 return search_alias_in_frame( curr_query, target );
6714 @brief Search all levels of query for a specified alias, starting with the current query.
6715 @param target The alias for which to search.
6716 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6718 static ClassInfo* search_all_alias( const char* target ) {
6719 ClassInfo* found_class = NULL;
6720 QueryFrame* curr_frame = curr_query;
6722 while( curr_frame ) {
6723 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6726 curr_frame = curr_frame->next;
6733 @brief Add a class to the list of classes joined to the current query.
6734 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6735 the class name as an alias.
6736 @param classname The name of the class to be added.
6737 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6739 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6741 if( ! classname || ! *classname ) { // sanity check
6742 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6749 const ClassInfo* conflict = search_alias( alias );
6751 osrfLogError( OSRF_LOG_MARK,
6752 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6753 modulename, alias, conflict->class_name );
6757 ClassInfo* info = allocate_class_info();
6759 if( build_class_info( info, alias, classname ) ) {
6760 free_class_info( info );
6764 // Add the new ClassInfo to the join list of the current QueryFrame
6765 info->next = curr_query->join_list;
6766 curr_query->join_list = info;
6772 @brief Destroy all nodes on the query stack.
6774 static void clear_query_stack( void ) {