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*, const jsonObject*, const jsonObject*,
98 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
100 void userDataFree( void* );
101 static void sessionDataFree( char*, void* );
102 static int obj_is_true( const jsonObject* obj );
103 static const char* json_type( int code );
104 static const char* get_primitive( osrfHash* field );
105 static const char* get_datatype( osrfHash* field );
106 static void pop_query_frame( void );
107 static void push_query_frame( void );
108 static int add_query_core( const char* alias, const char* class_name );
109 static inline ClassInfo* search_alias( const char* target );
110 static ClassInfo* search_all_alias( const char* target );
111 static ClassInfo* add_joined_class( const char* alias, const char* classname );
112 static void clear_query_stack( void );
114 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
115 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
116 static const char* org_tree_root( osrfMethodContext* ctx );
117 static jsonObject* single_hash( const char* key, const char* value );
119 static int child_initialized = 0; /* boolean */
121 static dbi_conn writehandle; /* our MASTER db connection */
122 static dbi_conn dbhandle; /* our CURRENT db connection */
123 //static osrfHash * readHandles;
125 // The following points to the top of a stack of QueryFrames. It's a little
126 // confusing because the top level of the query is at the bottom of the stack.
127 static QueryFrame* curr_query = NULL;
129 static dbi_conn writehandle; /* our MASTER db connection */
130 static dbi_conn dbhandle; /* our CURRENT db connection */
131 //static osrfHash * readHandles;
133 static int max_flesh_depth = 100;
135 static int enforce_pcrud = 0; // Boolean
136 static char* modulename = NULL;
139 @brief Connect to the database.
140 @return A database connection if successful, or NULL if not.
142 dbi_conn oilsConnectDB( const char* mod_name ) {
144 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
145 if( dbi_initialize( NULL ) == -1 ) {
146 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
149 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
151 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
152 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
153 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
154 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
155 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
156 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
158 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
159 dbi_conn handle = dbi_conn_new( driver );
162 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
165 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
167 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
168 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
170 if( host ) dbi_conn_set_option( handle, "host", host );
171 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
172 if( user ) dbi_conn_set_option( handle, "username", user );
173 if( pw ) dbi_conn_set_option( handle, "password", pw );
174 if( db ) dbi_conn_set_option( handle, "dbname", db );
182 if( dbi_conn_connect( handle ) < 0 ) {
184 if( dbi_conn_connect( handle ) < 0 ) {
186 dbi_conn_error( handle, &msg );
187 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
188 msg ? msg : "(No description available)" );
193 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
199 @brief Select some options.
200 @param module_name: Name of the server.
201 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
203 This source file is used (at this writing) to implement three different servers:
204 - open-ils.reporter-store
208 These servers behave mostly the same, but they implement different combinations of
209 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
211 Here we use the server name in messages to identify which kind of server issued them.
212 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
214 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
216 module_name = "open-ils.cstore"; // bulletproofing with a default
221 modulename = strdup( module_name );
222 enforce_pcrud = do_pcrud;
223 max_flesh_depth = flesh_depth;
227 @brief Install a database connection.
228 @param conn Pointer to a database connection.
230 In some contexts, @a conn may merely provide a driver so that we can process strings
231 properly, without providing an open database connection.
233 void oilsSetDBConnection( dbi_conn conn ) {
234 dbhandle = writehandle = conn;
238 @brief Determine whether a database connection is alive.
239 @param handle Handle for a database connection.
240 @return 1 if the connection is alive, or zero if it isn't.
242 int oilsIsDBConnected( dbi_conn handle ) {
243 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
244 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
246 dbi_result_free( result );
249 // This is a terrible, horrible, no good, very bad kludge.
250 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
251 // but because (due to a previous error) the database is ignoring all commands,
252 // even innocuous SELECTs, until the current transaction is rolled back. The only
253 // known way to detect this condition via the dbi library is by looking at the error
254 // message. This approach will break if the language or wording of the message ever
256 // Note: the dbi_conn_ping function purports to determine whether the doatabase
257 // connection is live, but at this writing this function is unreliable and useless.
258 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
259 "ignored until end of transaction block\n";
261 dbi_conn_error( handle, &msg );
262 if( strcmp( msg, ok_msg )) {
263 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
266 return 1; // ignoring SELECT due to previous error; that's okay
271 @brief Get a table name, view name, or subquery for use in a FROM clause.
272 @param class Pointer to the IDL class entry.
273 @return A table name, a view name, or a subquery in parentheses.
275 In some cases the IDL defines a class, not with a table name or a view name, but with
276 a SELECT statement, which may be used as a subquery.
278 char* oilsGetRelation( osrfHash* classdef ) {
280 char* source_def = NULL;
281 const char* tabledef = osrfHashGet( classdef, "tablename" );
284 source_def = strdup( tabledef ); // Return the name of a table or view
286 tabledef = osrfHashGet( classdef, "source_definition" );
288 // Return a subquery, enclosed in parentheses
289 source_def = safe_malloc( strlen( tabledef ) + 3 );
290 source_def[ 0 ] = '(';
291 strcpy( source_def + 1, tabledef );
292 strcat( source_def, ")" );
294 // Not found: return an error
295 const char* classname = osrfHashGet( classdef, "classname" );
300 "%s ERROR No tablename or source_definition for class \"%s\"",
311 @brief Add datatypes from the database to the fields in the IDL.
312 @param handle Handle for a database connection
313 @return Zero if successful, or 1 upon error.
315 For each relevant class in the IDL: ask the database for the datatype of every field.
316 In particular, determine which fields are text fields and which fields are numeric
317 fields, so that we know whether to enclose their values in quotes.
319 int oilsExtendIDL( dbi_conn handle ) {
320 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
321 osrfHash* class = NULL;
322 growing_buffer* query_buf = buffer_init( 64 );
323 int results_found = 0; // boolean
325 // For each class in the IDL...
326 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
327 const char* classname = osrfHashIteratorKey( class_itr );
328 osrfHash* fields = osrfHashGet( class, "fields" );
330 // If the class is virtual, ignore it
331 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
332 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
336 char* tabledef = oilsGetRelation( class );
338 continue; // No such relation -- a query of it would be doomed to failure
340 buffer_reset( query_buf );
341 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
345 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
346 modulename, OSRF_BUFFER_C_STR( query_buf ) );
348 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
353 const char* columnName;
354 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
356 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
359 /* fetch the fieldmapper index */
360 osrfHash* _f = osrfHashGet(fields, columnName);
363 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
365 /* determine the field type and storage attributes */
367 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
369 case DBI_TYPE_INTEGER : {
371 if( !osrfHashGet(_f, "primitive") )
372 osrfHashSet(_f, "number", "primitive");
374 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
375 if( attr & DBI_INTEGER_SIZE8 )
376 osrfHashSet( _f, "INT8", "datatype" );
378 osrfHashSet( _f, "INT", "datatype" );
381 case DBI_TYPE_DECIMAL :
382 if( !osrfHashGet( _f, "primitive" ))
383 osrfHashSet( _f, "number", "primitive" );
385 osrfHashSet( _f, "NUMERIC", "datatype" );
388 case DBI_TYPE_STRING :
389 if( !osrfHashGet( _f, "primitive" ))
390 osrfHashSet( _f, "string", "primitive" );
392 osrfHashSet( _f,"TEXT", "datatype" );
395 case DBI_TYPE_DATETIME :
396 if( !osrfHashGet( _f, "primitive" ))
397 osrfHashSet( _f, "string", "primitive" );
399 osrfHashSet( _f, "TIMESTAMP", "datatype" );
402 case DBI_TYPE_BINARY :
403 if( !osrfHashGet( _f, "primitive" ))
404 osrfHashSet( _f, "string", "primitive" );
406 osrfHashSet( _f, "BYTEA", "datatype" );
411 "Setting [%s] to primitive [%s] and datatype [%s]...",
413 osrfHashGet( _f, "primitive" ),
414 osrfHashGet( _f, "datatype" )
418 } // end while loop for traversing columns of result
419 dbi_result_free( result );
422 int errnum = dbi_conn_error( handle, &msg );
423 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
424 errnum, msg ? msg : "(No description available)" );
425 // We don't check the database connection here. It's routine to get failures at
426 // this point; we routinely try to query tables that don't exist, because they
427 // are defined in the IDL but not in the database.
429 } // end for each class in IDL
431 buffer_free( query_buf );
432 osrfHashIteratorFree( class_itr );
433 child_initialized = 1;
435 if( !results_found ) {
436 osrfLogError( OSRF_LOG_MARK,
437 "No results found for any class -- bad database connection?" );
439 } else if( ! oilsIsDBConnected( handle )) {
440 osrfLogError( OSRF_LOG_MARK,
441 "Unable to extend IDL: database connection isn't working" );
449 @brief Free an osrfHash that stores a transaction ID.
450 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
452 This function is a callback, to be called by the application session when it ends.
453 The application session stores the osrfHash via an opaque pointer.
455 If the osrfHash contains an entry for the key "xact_id", it means that an
456 uncommitted transaction is pending. Roll it back.
458 void userDataFree( void* blob ) {
459 osrfHash* hash = (osrfHash*) blob;
460 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
461 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
463 int errnum = dbi_conn_error( writehandle, &msg );
464 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
465 errnum, msg ? msg : "(No description available)" );
469 osrfHashFree( hash );
473 @name Managing session data
474 @brief Maintain data stored via the userData pointer of the application session.
476 Currently, session-level data is stored in an osrfHash. Other arrangements are
477 possible, and some would be more efficient. The application session calls a
478 callback function to free userData before terminating.
480 Currently, the only data we store at the session level is the transaction id. By this
481 means we can ensure that any pending transactions are rolled back before the application
487 @brief Free an item in the application session's userData.
488 @param key The name of a key for an osrfHash.
489 @param item An opaque pointer to the item associated with the key.
491 We store an osrfHash as userData with the application session, and arrange (by
492 installing userDataFree() as a different callback) for the session to free that
493 osrfHash before terminating.
495 This function is a callback for freeing items in the osrfHash. Currently we store
497 - Transaction id of a pending transaction; a character string. Key: "xact_id".
498 - Authkey; a character string. Key: "authkey".
499 - User object from the authentication server; a jsonObject. Key: "user_login".
501 If we ever store anything else in userData, we will need to revisit this function so
502 that it will free whatever else needs freeing.
504 static void sessionDataFree( char* key, void* item ) {
505 if( !strcmp( key, "xact_id" )
506 || !strcmp( key, "authkey" ) ) {
508 } else if( !strcmp( key, "user_login" ) )
509 jsonObjectFree( (jsonObject*) item );
513 @brief Save a transaction id.
514 @param ctx Pointer to the method context.
516 Save the session_id of the current application session as a transaction id.
518 static void setXactId( osrfMethodContext* ctx ) {
519 if( ctx && ctx->session ) {
520 osrfAppSession* session = ctx->session;
522 osrfHash* cache = session->userData;
524 // If the session doesn't already have a hash, create one. Make sure
525 // that the application session frees the hash when it terminates.
526 if( NULL == cache ) {
527 session->userData = cache = osrfNewHash();
528 osrfHashSetCallback( cache, &sessionDataFree );
529 ctx->session->userDataFree = &userDataFree;
532 // Save the transaction id in the hash, with the key "xact_id"
533 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
538 @brief Get the transaction ID for the current transaction, if any.
539 @param ctx Pointer to the method context.
540 @return Pointer to the transaction ID.
542 The return value points to an internal buffer, and will become invalid upon issuing
543 a commit or rollback.
545 static inline const char* getXactId( osrfMethodContext* ctx ) {
546 if( ctx && ctx->session && ctx->session->userData )
547 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
553 @brief Clear the current transaction id.
554 @param ctx Pointer to the method context.
556 static inline void clearXactId( osrfMethodContext* ctx ) {
557 if( ctx && ctx->session && ctx->session->userData )
558 osrfHashRemove( ctx->session->userData, "xact_id" );
563 @brief Save the user's login in the userData for the current application session.
564 @param ctx Pointer to the method context.
565 @param user_login Pointer to the user login object to be cached (we cache the original,
568 If @a user_login is NULL, remove the user login if one is already cached.
570 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
571 if( ctx && ctx->session ) {
572 osrfAppSession* session = ctx->session;
574 osrfHash* cache = session->userData;
576 // If the session doesn't already have a hash, create one. Make sure
577 // that the application session frees the hash when it terminates.
578 if( NULL == cache ) {
579 session->userData = cache = osrfNewHash();
580 osrfHashSetCallback( cache, &sessionDataFree );
581 ctx->session->userDataFree = &userDataFree;
585 osrfHashSet( cache, user_login, "user_login" );
587 osrfHashRemove( cache, "user_login" );
592 @brief Get the user login object for the current application session, if any.
593 @param ctx Pointer to the method context.
594 @return Pointer to the user login object if found; otherwise NULL.
596 The user login object was returned from the authentication server, and then cached so
597 we don't have to call the authentication server again for the same user.
599 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
600 if( ctx && ctx->session && ctx->session->userData )
601 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
607 @brief Save a copy of an authkey in the userData of the current application session.
608 @param ctx Pointer to the method context.
609 @param authkey The authkey to be saved.
611 If @a authkey is NULL, remove the authkey if one is already cached.
613 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
614 if( ctx && ctx->session && authkey ) {
615 osrfAppSession* session = ctx->session;
616 osrfHash* cache = session->userData;
618 // If the session doesn't already have a hash, create one. Make sure
619 // that the application session frees the hash when it terminates.
620 if( NULL == cache ) {
621 session->userData = cache = osrfNewHash();
622 osrfHashSetCallback( cache, &sessionDataFree );
623 ctx->session->userDataFree = &userDataFree;
626 // Save the transaction id in the hash, with the key "xact_id"
627 if( authkey && *authkey )
628 osrfHashSet( cache, strdup( authkey ), "authkey" );
630 osrfHashRemove( cache, "authkey" );
635 @brief Reset the login timeout.
636 @param authkey The authentication key for the current login session.
637 @param now The current time.
638 @return Zero if successful, or 1 if not.
640 Tell the authentication server to reset the timeout so that the login session won't
641 expire for a while longer.
643 We could dispense with the @a now parameter by calling time(). But we just called
644 time() in order to decide whether to reset the timeout, so we might as well reuse
645 the result instead of calling time() again.
647 static int reset_timeout( const char* authkey, time_t now ) {
648 jsonObject* auth_object = jsonNewObject( authkey );
650 // Ask the authentication server to reset the timeout. It returns an event
651 // indicating success or failure.
652 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
653 "open-ils.auth.session.reset_timeout", auth_object );
654 jsonObjectFree( auth_object );
656 if( !result || result->type != JSON_HASH ) {
657 osrfLogError( OSRF_LOG_MARK,
658 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
659 jsonObjectFree( result );
660 return 1; // Not the right sort of object returned
663 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
664 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
665 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
666 jsonObjectFree( result );
667 return 1; // Return code from method not available
670 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
671 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
673 desc = "(No reason available)"; // failsafe; shouldn't happen
674 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
675 jsonObjectFree( result );
679 // Revise our local proxy for the timeout deadline
680 // by a smallish fraction of the timeout interval
681 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
683 timeout = "1"; // failsafe; shouldn't happen
684 time_next_reset = now + atoi( timeout ) / 15;
686 jsonObjectFree( result );
687 return 0; // Successfully reset timeout
691 @brief Get the authkey string for the current application session, if any.
692 @param ctx Pointer to the method context.
693 @return Pointer to the cached authkey if found; otherwise NULL.
695 If present, the authkey string was cached from a previous method call.
697 static const char* getAuthkey( osrfMethodContext* ctx ) {
698 if( ctx && ctx->session && ctx->session->userData ) {
699 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
701 // Possibly reset the authentication timeout to keep the login alive. We do so
702 // no more than once per method call, and not at all if it has been only a short
703 // time since the last reset.
705 // Here we reset explicitly, if at all. We also implicitly reset the timeout
706 // whenever we call the "open-ils.auth.session.retrieve" method.
707 if( timeout_needs_resetting ) {
708 time_t now = time( NULL );
709 if( now >= time_next_reset && reset_timeout( authkey, now ) )
710 authkey = NULL; // timeout has apparently expired already
713 timeout_needs_resetting = 0;
721 @brief Implement the transaction.begin method.
722 @param ctx Pointer to the method context.
723 @return Zero if successful, or -1 upon error.
725 Start a transaction. Save a transaction ID for future reference.
728 - authkey (PCRUD only)
730 Return to client: Transaction ID
732 int beginTransaction( osrfMethodContext* ctx ) {
733 if(osrfMethodVerifyContext( ctx )) {
734 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
738 if( enforce_pcrud ) {
739 timeout_needs_resetting = 1;
740 const jsonObject* user = verifyUserPCRUD( ctx );
745 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
748 int errnum = dbi_conn_error( writehandle, &msg );
749 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
750 modulename, errnum, msg ? msg : "(No description available)" );
751 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
752 "osrfMethodException", ctx->request, "Error starting transaction" );
753 if( !oilsIsDBConnected( writehandle ))
754 osrfAppSessionPanic( ctx->session );
757 dbi_result_free( result );
759 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
760 osrfAppRespondComplete( ctx, ret );
761 jsonObjectFree( ret );
767 @brief Implement the savepoint.set method.
768 @param ctx Pointer to the method context.
769 @return Zero if successful, or -1 if not.
771 Issue a SAVEPOINT to the database server.
774 - authkey (PCRUD only)
777 Return to client: Savepoint name
779 int setSavepoint( osrfMethodContext* ctx ) {
780 if(osrfMethodVerifyContext( ctx )) {
781 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
786 if( enforce_pcrud ) {
788 timeout_needs_resetting = 1;
789 const jsonObject* user = verifyUserPCRUD( ctx );
794 // Verify that a transaction is pending
795 const char* trans_id = getXactId( ctx );
796 if( NULL == trans_id ) {
797 osrfAppSessionStatus(
799 OSRF_STATUS_INTERNALSERVERERROR,
800 "osrfMethodException",
802 "No active transaction -- required for savepoints"
807 // Get the savepoint name from the method params
808 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
810 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
813 int errnum = dbi_conn_error( writehandle, &msg );
816 "%s: Error creating savepoint %s in transaction %s: %d %s",
821 msg ? msg : "(No description available)"
823 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
824 "osrfMethodException", ctx->request, "Error creating savepoint" );
825 if( !oilsIsDBConnected( writehandle ))
826 osrfAppSessionPanic( ctx->session );
829 dbi_result_free( result );
830 jsonObject* ret = jsonNewObject( spName );
831 osrfAppRespondComplete( ctx, ret );
832 jsonObjectFree( ret );
838 @brief Implement the savepoint.release method.
839 @param ctx Pointer to the method context.
840 @return Zero if successful, or -1 if not.
842 Issue a RELEASE SAVEPOINT to the database server.
845 - authkey (PCRUD only)
848 Return to client: Savepoint name
850 int releaseSavepoint( osrfMethodContext* ctx ) {
851 if(osrfMethodVerifyContext( ctx )) {
852 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
857 if( enforce_pcrud ) {
859 timeout_needs_resetting = 1;
860 const jsonObject* user = verifyUserPCRUD( ctx );
865 // Verify that a transaction is pending
866 const char* trans_id = getXactId( ctx );
867 if( NULL == trans_id ) {
868 osrfAppSessionStatus(
870 OSRF_STATUS_INTERNALSERVERERROR,
871 "osrfMethodException",
873 "No active transaction -- required for savepoints"
878 // Get the savepoint name from the method params
879 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
881 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
884 int errnum = dbi_conn_error( writehandle, &msg );
887 "%s: Error releasing savepoint %s in transaction %s: %d %s",
892 msg ? msg : "(No description available)"
894 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
895 "osrfMethodException", ctx->request, "Error releasing savepoint" );
896 if( !oilsIsDBConnected( writehandle ))
897 osrfAppSessionPanic( ctx->session );
900 dbi_result_free( result );
901 jsonObject* ret = jsonNewObject( spName );
902 osrfAppRespondComplete( ctx, ret );
903 jsonObjectFree( ret );
909 @brief Implement the savepoint.rollback method.
910 @param ctx Pointer to the method context.
911 @return Zero if successful, or -1 if not.
913 Issue a ROLLBACK TO SAVEPOINT to the database server.
916 - authkey (PCRUD only)
919 Return to client: Savepoint name
921 int rollbackSavepoint( osrfMethodContext* ctx ) {
922 if(osrfMethodVerifyContext( ctx )) {
923 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
928 if( enforce_pcrud ) {
930 timeout_needs_resetting = 1;
931 const jsonObject* user = verifyUserPCRUD( ctx );
936 // Verify that a transaction is pending
937 const char* trans_id = getXactId( ctx );
938 if( NULL == trans_id ) {
939 osrfAppSessionStatus(
941 OSRF_STATUS_INTERNALSERVERERROR,
942 "osrfMethodException",
944 "No active transaction -- required for savepoints"
949 // Get the savepoint name from the method params
950 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
952 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
955 int errnum = dbi_conn_error( writehandle, &msg );
958 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
963 msg ? msg : "(No description available)"
965 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
966 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
967 if( !oilsIsDBConnected( writehandle ))
968 osrfAppSessionPanic( ctx->session );
971 dbi_result_free( result );
972 jsonObject* ret = jsonNewObject( spName );
973 osrfAppRespondComplete( ctx, ret );
974 jsonObjectFree( ret );
980 @brief Implement the transaction.commit method.
981 @param ctx Pointer to the method context.
982 @return Zero if successful, or -1 if not.
984 Issue a COMMIT to the database server.
987 - authkey (PCRUD only)
989 Return to client: Transaction ID.
991 int commitTransaction( osrfMethodContext* ctx ) {
992 if(osrfMethodVerifyContext( ctx )) {
993 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
997 if( enforce_pcrud ) {
998 timeout_needs_resetting = 1;
999 const jsonObject* user = verifyUserPCRUD( ctx );
1004 // Verify that a transaction is pending
1005 const char* trans_id = getXactId( ctx );
1006 if( NULL == trans_id ) {
1007 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1008 "osrfMethodException", ctx->request, "No active transaction to commit" );
1012 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1015 int errnum = dbi_conn_error( writehandle, &msg );
1016 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1017 modulename, errnum, msg ? msg : "(No description available)" );
1018 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1019 "osrfMethodException", ctx->request, "Error committing transaction" );
1020 if( !oilsIsDBConnected( writehandle ))
1021 osrfAppSessionPanic( ctx->session );
1024 dbi_result_free( result );
1025 jsonObject* ret = jsonNewObject( trans_id );
1026 osrfAppRespondComplete( ctx, ret );
1027 jsonObjectFree( ret );
1034 @brief Implement the transaction.rollback method.
1035 @param ctx Pointer to the method context.
1036 @return Zero if successful, or -1 if not.
1038 Issue a ROLLBACK to the database server.
1041 - authkey (PCRUD only)
1043 Return to client: Transaction ID
1045 int rollbackTransaction( osrfMethodContext* ctx ) {
1046 if( osrfMethodVerifyContext( ctx )) {
1047 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1051 if( enforce_pcrud ) {
1052 timeout_needs_resetting = 1;
1053 const jsonObject* user = verifyUserPCRUD( ctx );
1058 // Verify that a transaction is pending
1059 const char* trans_id = getXactId( ctx );
1060 if( NULL == trans_id ) {
1061 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1062 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1066 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1069 int errnum = dbi_conn_error( writehandle, &msg );
1070 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1071 modulename, errnum, msg ? msg : "(No description available)" );
1072 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1073 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1074 if( !oilsIsDBConnected( writehandle ))
1075 osrfAppSessionPanic( ctx->session );
1078 dbi_result_free( result );
1079 jsonObject* ret = jsonNewObject( trans_id );
1080 osrfAppRespondComplete( ctx, ret );
1081 jsonObjectFree( ret );
1088 @brief Implement the "search" method.
1089 @param ctx Pointer to the method context.
1090 @return Zero if successful, or -1 if not.
1093 - authkey (PCRUD only)
1094 - WHERE clause, as jsonObject
1095 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1097 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1098 Optionally flesh linked fields.
1100 int doSearch( osrfMethodContext* ctx ) {
1101 if( osrfMethodVerifyContext( ctx )) {
1102 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1107 timeout_needs_resetting = 1;
1109 jsonObject* where_clause;
1110 jsonObject* rest_of_query;
1112 if( enforce_pcrud ) {
1113 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1114 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1116 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1117 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1120 // Get the class metadata
1121 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1122 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1126 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1128 osrfAppRespondComplete( ctx, NULL );
1132 // Return each row to the client (except that some may be suppressed by PCRUD)
1133 jsonObject* cur = 0;
1134 unsigned long res_idx = 0;
1135 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1136 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1138 osrfAppRespond( ctx, cur );
1140 jsonObjectFree( obj );
1142 osrfAppRespondComplete( ctx, NULL );
1147 @brief Implement the "id_list" method.
1148 @param ctx Pointer to the method context.
1149 @param err Pointer through which to return an error code.
1150 @return Zero if successful, or -1 if not.
1153 - authkey (PCRUD only)
1154 - WHERE clause, as jsonObject
1155 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1157 Return to client: The primary key values for all rows of the relevant class that
1158 satisfy a specified WHERE clause.
1160 This method relies on the assumption that every class has a primary key consisting of
1163 int doIdList( osrfMethodContext* ctx ) {
1164 if( osrfMethodVerifyContext( ctx )) {
1165 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1170 timeout_needs_resetting = 1;
1172 jsonObject* where_clause;
1173 jsonObject* rest_of_query;
1175 // We use the where clause without change. But we need to massage the rest of the
1176 // query, so we work with a copy of it instead of modifying the original.
1178 if( enforce_pcrud ) {
1179 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1180 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1182 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1183 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1186 // Eliminate certain SQL clauses, if present.
1187 if( rest_of_query ) {
1188 jsonObjectRemoveKey( rest_of_query, "select" );
1189 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1190 jsonObjectRemoveKey( rest_of_query, "flesh" );
1191 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1193 rest_of_query = jsonNewObjectType( JSON_HASH );
1196 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1198 // Get the class metadata
1199 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1200 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1202 // Build a SELECT list containing just the primary key,
1203 // i.e. like { "classname":["keyname"] }
1204 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1206 // Load array with name of primary key
1207 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1208 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1209 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1211 jsonObjectSetKey( rest_of_query, "select", select_clause );
1216 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1218 jsonObjectFree( rest_of_query );
1220 osrfAppRespondComplete( ctx, NULL );
1224 // Return each primary key value to the client
1226 unsigned long res_idx = 0;
1227 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1228 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1229 continue; // Suppress due to lack of permission
1231 osrfAppRespond( ctx,
1232 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1235 jsonObjectFree( obj );
1236 osrfAppRespondComplete( ctx, NULL );
1241 @brief Verify that we have a valid class reference.
1242 @param ctx Pointer to the method context.
1243 @param param Pointer to the method parameters.
1244 @return 1 if the class reference is valid, or zero if it isn't.
1246 The class of the method params must match the class to which the method id devoted.
1247 For PCRUD there are additional restrictions.
1249 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1251 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1252 osrfHash* class = osrfHashGet( method_meta, "class" );
1254 // Compare the method's class to the parameters' class
1255 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1257 // Oops -- they don't match. Complain.
1258 growing_buffer* msg = buffer_init( 128 );
1261 "%s: %s method for type %s was passed a %s",
1263 osrfHashGet( method_meta, "methodtype" ),
1264 osrfHashGet( class, "classname" ),
1265 param->classname ? param->classname : "(null)"
1268 char* m = buffer_release( msg );
1269 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1277 return verifyObjectPCRUD( ctx, param );
1283 @brief (PCRUD only) Verify that the user is properly logged in.
1284 @param ctx Pointer to the method context.
1285 @return If the user is logged in, a pointer to the user object from the authentication
1286 server; otherwise NULL.
1288 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1290 // Get the authkey (the first method parameter)
1291 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1293 // See if we have the same authkey, and a user object,
1294 // locally cached from a previous call
1295 const char* cached_authkey = getAuthkey( ctx );
1296 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1297 const jsonObject* cached_user = getUserLogin( ctx );
1302 // We have no matching authentication data in the cache. Authenticate from scratch.
1303 jsonObject* auth_object = jsonNewObject( auth );
1305 // Fetch the user object from the authentication server
1306 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1308 jsonObjectFree( auth_object );
1310 if( !user->classname || strcmp(user->classname, "au" )) {
1312 growing_buffer* msg = buffer_init( 128 );
1315 "%s: permacrud received a bad auth token: %s",
1320 char* m = buffer_release( msg );
1321 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1325 jsonObjectFree( user );
1329 setUserLogin( ctx, user );
1330 setAuthkey( ctx, auth );
1332 // Allow ourselves up to a second before we have to reset the login timeout.
1333 // It would be nice to use some fraction of the timeout interval enforced by the
1334 // authentication server, but that value is not readily available at this point.
1335 // Instead, we use a conservative default interval.
1336 time_next_reset = time( NULL ) + 1;
1342 @brief For PCRUD: Determine whether the current user may access the current row.
1343 @param ctx Pointer to the method context.
1344 @param obj Pointer to the row being potentially accessed.
1345 @return 1 if access is permitted, or 0 if it isn't.
1347 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1349 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1351 dbhandle = writehandle;
1353 // Figure out what class and method are involved
1354 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1355 osrfHash* class = osrfHashGet( method_metadata, "class" );
1356 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1358 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1359 // contexts we will do another lookup of the current row, even if we already have a
1360 // previously fetched row image, because the row image in hand may not include the
1361 // foreign key(s) that we need.
1363 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1364 // but they aren't implemented yet.
1367 if( *method_type == 's' || *method_type == 'i' ) {
1368 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1370 } else if( *method_type == 'u' || *method_type == 'd' ) {
1371 fetch = 1; // MUST go to the db for the object for update and delete
1374 // Get the appropriate permacrud entry from the IDL, depending on method type
1375 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1377 // No permacrud for this method type on this class
1379 growing_buffer* msg = buffer_init( 128 );
1382 "%s: %s on class %s has no permacrud IDL entry",
1384 osrfHashGet( method_metadata, "methodtype" ),
1385 osrfHashGet( class, "classname" )
1388 char* m = buffer_release( msg );
1389 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1390 "osrfMethodException", ctx->request, m );
1397 // Get the user id, and make sure the user is logged in
1398 const jsonObject* user = verifyUserPCRUD( ctx );
1400 return 0; // Not logged in? No access.
1402 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1404 // Get a list of permissions from the permacrud entry.
1405 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1406 if( permission->size == 0 ) {
1407 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1411 // Build a list of org units that own the row. This is fairly convoluted because there
1412 // are several different ways that an org unit may own the row, as defined by the
1415 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1416 // identifying an owning org_unit..
1417 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1419 // Foreign context adds a layer of indirection. The row points to some other row that
1420 // an org unit may own. The "jump" attribute, if present, adds another layer of
1422 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1424 // The following string array stores the list of org units. (We don't have a thingie
1425 // for storing lists of integers, so we fake it with a list of strings.)
1426 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1429 const char* pkey_value = NULL;
1430 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1431 // If the global_required attribute is present and true, then the only owning
1432 // org unit is the root org unit, i.e. the one with no parent.
1433 osrfLogDebug( OSRF_LOG_MARK,
1434 "global-level permissions required, fetching top of the org tree" );
1436 // check for perm at top of org tree
1437 const char* org_tree_root_id = org_tree_root( ctx );
1438 if( org_tree_root_id ) {
1439 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1440 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1442 osrfStringArrayFree( context_org_array );
1447 // If the global_required attribute is absent or false, then we look for
1448 // local and/or foreign context. In order to find the relevant foreign
1449 // keys, we must either read the relevant row from the database, or look at
1450 // the image of the row that we already have in memory.
1452 // Even if we have an image of the row in memory, that image may not include the
1453 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1454 // of the row to make sure that we have what we need.
1456 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1457 "fetching context org ids" );
1458 const char* pkey = osrfHashGet( class, "primarykey" );
1459 jsonObject *param = NULL;
1462 // There is no primary key, so we can't do a fresh lookup. Use the row
1463 // image that we already have. If it doesn't have everything we need, too bad.
1465 param = jsonObjectClone( obj );
1466 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1467 } else if( obj->classname ) {
1468 pkey_value = oilsFMGetStringConst( obj, pkey );
1470 param = jsonObjectClone( obj );
1471 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1474 pkey_value = jsonObjectGetString( obj );
1476 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1477 "of %s and retrieving from the database", pkey_value );
1481 // Fetch the row so that we can look at the foreign key(s)
1482 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1483 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1484 jsonObjectFree( _tmp_params );
1486 param = jsonObjectExtractIndex( _list, 0 );
1487 jsonObjectFree( _list );
1491 // The row doesn't exist. Complain, and deny access.
1492 osrfLogDebug( OSRF_LOG_MARK,
1493 "Object not found in the database with primary key %s of %s",
1496 growing_buffer* msg = buffer_init( 128 );
1499 "%s: no object found with primary key %s of %s",
1505 char* m = buffer_release( msg );
1506 osrfAppSessionStatus(
1508 OSRF_STATUS_INTERNALSERVERERROR,
1509 "osrfMethodException",
1518 if( local_context && local_context->size > 0 ) {
1519 // The IDL provides a list of column names for the foreign keys denoting
1520 // local context, i.e. columns identifying owing org units directly. Look up
1521 // the value of each one, and if it isn't null, add it to the list of org units.
1522 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1523 local_context->size );
1525 const char* lcontext = NULL;
1526 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1527 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1528 if( fkey_value ) { // if not null
1529 osrfStringArrayAdd( context_org_array, fkey_value );
1532 "adding class-local field %s (value: %s) to the context org list",
1534 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1540 if( foreign_context ) {
1541 unsigned long class_count = osrfHashGetCount( foreign_context );
1542 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1544 if( class_count > 0 ) {
1546 // The IDL provides a list of foreign key columns pointing to rows that
1547 // an org unit may own. Follow each link, identify the owning org unit,
1548 // and add it to the list.
1549 osrfHash* fcontext = NULL;
1550 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1551 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1552 // For each class to which a foreign key points:
1553 const char* class_name = osrfHashIteratorKey( class_itr );
1554 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1558 "%d foreign context fields(s) specified for class %s",
1559 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1563 // Get the name of the key field in the foreign table
1564 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1566 // Get the value of the foreign key pointing to the foreign table
1567 char* foreign_pkey_value =
1568 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1569 if( !foreign_pkey_value )
1570 continue; // Foreign key value is null; skip it
1572 // Look up the row to which the foreign key points
1573 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1574 jsonObject* _list = doFieldmapperSearch(
1575 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1577 jsonObject* _fparam = NULL;
1578 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1579 _fparam = jsonObjectExtractIndex( _list, 0 );
1581 jsonObjectFree( _tmp_params );
1582 jsonObjectFree( _list );
1584 // At this point _fparam either points to the row identified by the
1585 // foreign key, or it's NULL (no such row found).
1587 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1589 const char* bad_class = NULL; // For noting failed lookups
1591 bad_class = class_name; // Referenced row not found
1592 else if( jump_list ) {
1593 // Follow a chain of rows, linked by foreign keys, to find an owner
1594 const char* flink = NULL;
1596 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1597 // For each entry in the jump list. Each entry (i.e. flink) is
1598 // the name of a foreign key column in the current row.
1600 // From the IDL, get the linkage information for the next jump
1601 osrfHash* foreign_link_hash =
1602 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1604 // Get the class metadata for the class
1605 // to which the foreign key points
1606 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1607 osrfHashGet( foreign_link_hash, "class" ));
1609 // Get the name of the referenced key of that class
1610 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1612 // Get the value of the foreign key pointing to that class
1613 free( foreign_pkey_value );
1614 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1615 if( !foreign_pkey_value )
1616 break; // Foreign key is null; quit looking
1618 // Build a WHERE clause for the lookup
1619 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1622 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1623 _tmp_params, NULL, &err );
1625 // Get the resulting row
1626 jsonObjectFree( _fparam );
1627 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1628 _fparam = jsonObjectExtractIndex( _list, 0 );
1630 // Referenced row not found
1632 bad_class = osrfHashGet( foreign_link_hash, "class" );
1635 jsonObjectFree( _tmp_params );
1636 jsonObjectFree( _list );
1642 // We had a foreign key pointing to such-and-such a row, but then
1643 // we couldn't fetch that row. The data in the database are in an
1644 // inconsistent state; the database itself may even be corrupted.
1645 growing_buffer* msg = buffer_init( 128 );
1648 "%s: no object of class %s found with primary key %s of %s",
1652 foreign_pkey_value ? foreign_pkey_value : "(null)"
1655 char* m = buffer_release( msg );
1656 osrfAppSessionStatus(
1658 OSRF_STATUS_INTERNALSERVERERROR,
1659 "osrfMethodException",
1665 osrfHashIteratorFree( class_itr );
1666 free( foreign_pkey_value );
1667 jsonObjectFree( param );
1672 free( foreign_pkey_value );
1675 // Examine each context column of the foreign row,
1676 // and add its value to the list of org units.
1678 const char* foreign_field = NULL;
1679 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1680 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1681 osrfStringArrayAdd( context_org_array,
1682 oilsFMGetStringConst( _fparam, foreign_field ));
1683 osrfLogDebug( OSRF_LOG_MARK,
1684 "adding foreign class %s field %s (value: %s) "
1685 "to the context org list",
1688 osrfStringArrayGetString(
1689 context_org_array, context_org_array->size - 1 )
1693 jsonObjectFree( _fparam );
1697 osrfHashIteratorFree( class_itr );
1701 jsonObjectFree( param );
1704 const char* context_org = NULL;
1705 const char* perm = NULL;
1708 // For every combination of permission and context org unit: call a stored procedure
1709 // to determine if the user has this permission in the context of this org unit.
1710 // If the answer is yes at any point, then we're done, and the user has permission.
1711 // In other words permissions are additive.
1713 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1715 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1721 "Checking object permission [%s] for user %d "
1722 "on object %s (class %s) at org %d",
1726 osrfHashGet( class, "classname" ),
1730 result = dbi_conn_queryf(
1732 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1735 osrfHashGet( class, "classname" ),
1743 "Received a result for object permission [%s] "
1744 "for user %d on object %s (class %s) at org %d",
1748 osrfHashGet( class, "classname" ),
1752 if( dbi_result_first_row( result )) {
1753 jsonObject* return_val = oilsMakeJSONFromResult( result );
1754 const char* has_perm = jsonObjectGetString(
1755 jsonObjectGetKeyConst( return_val, "has_perm" ));
1759 "Status of object permission [%s] for user %d "
1760 "on object %s (class %s) at org %d is %s",
1764 osrfHashGet(class, "classname"),
1769 if( *has_perm == 't' )
1771 jsonObjectFree( return_val );
1774 dbi_result_free( result );
1779 int errnum = dbi_conn_error( writehandle, &msg );
1780 osrfLogWarning( OSRF_LOG_MARK,
1781 "Unable to call check object permissions: %d, %s",
1782 errnum, msg ? msg : "(No description available)" );
1783 if( !oilsIsDBConnected( writehandle ))
1784 osrfAppSessionPanic( ctx->session );
1788 osrfLogDebug( OSRF_LOG_MARK,
1789 "Checking non-object permission [%s] for user %d at org %d",
1790 perm, userid, atoi(context_org) );
1791 result = dbi_conn_queryf(
1793 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1800 osrfLogDebug( OSRF_LOG_MARK,
1801 "Received a result for permission [%s] for user %d at org %d",
1802 perm, userid, atoi( context_org ));
1803 if( dbi_result_first_row( result )) {
1804 jsonObject* return_val = oilsMakeJSONFromResult( result );
1805 const char* has_perm = jsonObjectGetString(
1806 jsonObjectGetKeyConst( return_val, "has_perm" ));
1807 osrfLogDebug( OSRF_LOG_MARK,
1808 "Status of permission [%s] for user %d at org %d is [%s]",
1809 perm, userid, atoi( context_org ), has_perm );
1810 if( *has_perm == 't' )
1812 jsonObjectFree( return_val );
1815 dbi_result_free( result );
1820 int errnum = dbi_conn_error( writehandle, &msg );
1821 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1822 errnum, msg ? msg : "(No description available)" );
1823 if( !oilsIsDBConnected( writehandle ))
1824 osrfAppSessionPanic( ctx->session );
1832 osrfStringArrayFree( context_org_array );
1838 @brief Look up the root of the org_unit tree.
1839 @param ctx Pointer to the method context.
1840 @return The id of the root org unit, as a character string.
1842 Query actor.org_unit where parent_ou is null, and return the id as a string.
1844 This function assumes that there is only one root org unit, i.e. that we
1845 have a single tree, not a forest.
1847 The calling code is responsible for freeing the returned string.
1849 static const char* org_tree_root( osrfMethodContext* ctx ) {
1851 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1852 static time_t last_lookup_time = 0;
1853 time_t current_time = time( NULL );
1855 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1856 // We successfully looked this up less than an hour ago.
1857 // It's not likely to have changed since then.
1858 return strdup( cached_root_id );
1860 last_lookup_time = current_time;
1863 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1864 jsonObject* result = doFieldmapperSearch(
1865 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1866 jsonObjectFree( where_clause );
1868 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1871 jsonObjectFree( result );
1873 growing_buffer* msg = buffer_init( 128 );
1874 OSRF_BUFFER_ADD( msg, modulename );
1875 OSRF_BUFFER_ADD( msg,
1876 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1878 char* m = buffer_release( msg );
1879 osrfAppSessionStatus( ctx->session,
1880 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1883 cached_root_id[ 0 ] = '\0';
1887 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1888 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1890 strcpy( cached_root_id, root_org_unit_id );
1891 jsonObjectFree( result );
1892 return cached_root_id;
1896 @brief Create a JSON_HASH with a single key/value pair.
1897 @param key The key of the key/value pair.
1898 @param value the value of the key/value pair.
1899 @return Pointer to a newly created jsonObject of type JSON_HASH.
1901 The value of the key/value is either a string or (if @a value is NULL) a null.
1903 static jsonObject* single_hash( const char* key, const char* value ) {
1905 if( ! key ) key = "";
1907 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1908 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1913 int doCreate( osrfMethodContext* ctx ) {
1914 if(osrfMethodVerifyContext( ctx )) {
1915 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1920 timeout_needs_resetting = 1;
1922 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1923 jsonObject* target = NULL;
1924 jsonObject* options = NULL;
1926 if( enforce_pcrud ) {
1927 target = jsonObjectGetIndex( ctx->params, 1 );
1928 options = jsonObjectGetIndex( ctx->params, 2 );
1930 target = jsonObjectGetIndex( ctx->params, 0 );
1931 options = jsonObjectGetIndex( ctx->params, 1 );
1934 if( !verifyObjectClass( ctx, target )) {
1935 osrfAppRespondComplete( ctx, NULL );
1939 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1941 const char* trans_id = getXactId( ctx );
1943 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1945 osrfAppSessionStatus(
1947 OSRF_STATUS_BADREQUEST,
1948 "osrfMethodException",
1950 "No active transaction -- required for CREATE"
1952 osrfAppRespondComplete( ctx, NULL );
1956 // The following test is harmless but redundant. If a class is
1957 // readonly, we don't register a create method for it.
1958 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1959 osrfAppSessionStatus(
1961 OSRF_STATUS_BADREQUEST,
1962 "osrfMethodException",
1964 "Cannot INSERT readonly class"
1966 osrfAppRespondComplete( ctx, NULL );
1970 // Set the last_xact_id
1971 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1973 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1974 trans_id, target->classname, index);
1975 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1978 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1980 dbhandle = writehandle;
1982 osrfHash* fields = osrfHashGet( meta, "fields" );
1983 char* pkey = osrfHashGet( meta, "primarykey" );
1984 char* seq = osrfHashGet( meta, "sequence" );
1986 growing_buffer* table_buf = buffer_init( 128 );
1987 growing_buffer* col_buf = buffer_init( 128 );
1988 growing_buffer* val_buf = buffer_init( 128 );
1990 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1991 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1992 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1993 buffer_add( val_buf,"VALUES (" );
1997 osrfHash* field = NULL;
1998 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1999 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2001 const char* field_name = osrfHashIteratorKey( field_itr );
2003 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2006 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2009 if( field_object && field_object->classname ) {
2010 value = oilsFMGetString(
2012 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2014 } else if( field_object && JSON_BOOL == field_object->type ) {
2015 if( jsonBoolIsTrue( field_object ) )
2016 value = strdup( "t" );
2018 value = strdup( "f" );
2020 value = jsonObjectToSimpleString( field_object );
2026 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2027 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2030 buffer_add( col_buf, field_name );
2032 if( !field_object || field_object->type == JSON_NULL ) {
2033 buffer_add( val_buf, "DEFAULT" );
2035 } else if( !strcmp( get_primitive( field ), "number" )) {
2036 const char* numtype = get_datatype( field );
2037 if( !strcmp( numtype, "INT8" )) {
2038 buffer_fadd( val_buf, "%lld", atoll( value ));
2040 } else if( !strcmp( numtype, "INT" )) {
2041 buffer_fadd( val_buf, "%d", atoi( value ));
2043 } else if( !strcmp( numtype, "NUMERIC" )) {
2044 buffer_fadd( val_buf, "%f", atof( value ));
2047 if( dbi_conn_quote_string( writehandle, &value )) {
2048 OSRF_BUFFER_ADD( val_buf, value );
2051 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2052 osrfAppSessionStatus(
2054 OSRF_STATUS_INTERNALSERVERERROR,
2055 "osrfMethodException",
2057 "Error quoting string -- please see the error log for more details"
2060 buffer_free( table_buf );
2061 buffer_free( col_buf );
2062 buffer_free( val_buf );
2063 osrfAppRespondComplete( ctx, NULL );
2071 osrfHashIteratorFree( field_itr );
2073 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2074 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2076 char* table_str = buffer_release( table_buf );
2077 char* col_str = buffer_release( col_buf );
2078 char* val_str = buffer_release( val_buf );
2079 growing_buffer* sql = buffer_init( 128 );
2080 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2085 char* query = buffer_release( sql );
2087 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2089 jsonObject* obj = NULL;
2092 dbi_result result = dbi_conn_query( writehandle, query );
2094 obj = jsonNewObject( NULL );
2096 int errnum = dbi_conn_error( writehandle, &msg );
2099 "%s ERROR inserting %s object using query [%s]: %d %s",
2101 osrfHashGet(meta, "fieldmapper"),
2104 msg ? msg : "(No description available)"
2106 osrfAppSessionStatus(
2108 OSRF_STATUS_INTERNALSERVERERROR,
2109 "osrfMethodException",
2111 "INSERT error -- please see the error log for more details"
2113 if( !oilsIsDBConnected( writehandle ))
2114 osrfAppSessionPanic( ctx->session );
2117 dbi_result_free( result );
2119 char* id = oilsFMGetString( target, pkey );
2121 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2122 growing_buffer* _id = buffer_init( 10 );
2123 buffer_fadd( _id, "%lld", new_id );
2124 id = buffer_release( _id );
2127 // Find quietness specification, if present
2128 const char* quiet_str = NULL;
2130 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2132 quiet_str = jsonObjectGetString( quiet_obj );
2135 if( str_is_true( quiet_str )) { // if quietness is specified
2136 obj = jsonNewObject( id );
2140 // Fetch the row that we just inserted, so that we can return it to the client
2141 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2142 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2145 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2149 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2151 jsonObjectFree( list );
2152 jsonObjectFree( where_clause );
2159 osrfAppRespondComplete( ctx, obj );
2160 jsonObjectFree( obj );
2165 @brief Implement the retrieve method.
2166 @param ctx Pointer to the method context.
2167 @param err Pointer through which to return an error code.
2168 @return If successful, a pointer to the result to be returned to the client;
2171 From the method's class, fetch a row with a specified value in the primary key. This
2172 method relies on the database design convention that a primary key consists of a single
2176 - authkey (PCRUD only)
2177 - value of the primary key for the desired row, for building the WHERE clause
2178 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2180 Return to client: One row from the query.
2182 int doRetrieve( osrfMethodContext* ctx ) {
2183 if(osrfMethodVerifyContext( ctx )) {
2184 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2189 timeout_needs_resetting = 1;
2194 if( enforce_pcrud ) {
2199 // Get the class metadata
2200 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2202 // Get the value of the primary key, from a method parameter
2203 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2207 "%s retrieving %s object with primary key value of %s",
2209 osrfHashGet( class_def, "fieldmapper" ),
2210 jsonObjectGetString( id_obj )
2213 // Build a WHERE clause based on the key value
2214 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2217 osrfHashGet( class_def, "primarykey" ), // name of key column
2218 jsonObjectClone( id_obj ) // value of key column
2221 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2225 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2227 jsonObjectFree( where_clause );
2229 osrfAppRespondComplete( ctx, NULL );
2233 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2234 jsonObjectFree( list );
2236 if( enforce_pcrud ) {
2237 if(!verifyObjectPCRUD( ctx, obj )) {
2238 jsonObjectFree( obj );
2240 growing_buffer* msg = buffer_init( 128 );
2241 OSRF_BUFFER_ADD( msg, modulename );
2242 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2244 char* m = buffer_release( msg );
2245 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2249 osrfAppRespondComplete( ctx, NULL );
2254 osrfAppRespondComplete( ctx, obj );
2255 jsonObjectFree( obj );
2260 @brief Translate a numeric value to a string representation for the database.
2261 @param field Pointer to the IDL field definition.
2262 @param value Pointer to a jsonObject holding the value of a field.
2263 @return Pointer to a newly allocated string.
2265 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2266 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2267 or (what is worse) valid SQL that is wrong.
2269 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2271 The calling code is responsible for freeing the resulting string by calling free().
2273 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2274 growing_buffer* val_buf = buffer_init( 32 );
2275 const char* numtype = get_datatype( field );
2277 // For historical reasons the following contains cruft that could be cleaned up.
2278 if( !strncmp( numtype, "INT", 3 ) ) {
2279 if( value->type == JSON_NUMBER )
2280 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2281 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2283 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2286 } else if( !strcmp( numtype, "NUMERIC" )) {
2287 if( value->type == JSON_NUMBER )
2288 buffer_fadd( val_buf, jsonObjectGetString( value ));
2290 buffer_fadd( val_buf, jsonObjectGetString( value ));
2294 // Presumably this was really intended to be a string, so quote it
2295 char* str = jsonObjectToSimpleString( value );
2296 if( dbi_conn_quote_string( dbhandle, &str )) {
2297 OSRF_BUFFER_ADD( val_buf, str );
2300 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2302 buffer_free( val_buf );
2307 return buffer_release( val_buf );
2310 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2311 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2312 growing_buffer* sql_buf = buffer_init( 32 );
2318 osrfHashGet( field, "name" )
2322 buffer_add( sql_buf, "IN (" );
2323 } else if( !strcasecmp( op,"not in" )) {
2324 buffer_add( sql_buf, "NOT IN (" );
2326 buffer_add( sql_buf, "IN (" );
2329 if( node->type == JSON_HASH ) {
2330 // subquery predicate
2331 char* subpred = buildQuery( ctx, node, SUBSELECT );
2333 buffer_free( sql_buf );
2337 buffer_add( sql_buf, subpred );
2340 } else if( node->type == JSON_ARRAY ) {
2341 // literal value list
2342 int in_item_index = 0;
2343 int in_item_first = 1;
2344 const jsonObject* in_item;
2345 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2350 buffer_add( sql_buf, ", " );
2353 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2354 osrfLogError( OSRF_LOG_MARK,
2355 "%s: Expected string or number within IN list; found %s",
2356 modulename, json_type( in_item->type ) );
2357 buffer_free( sql_buf );
2361 // Append the literal value -- quoted if not a number
2362 if( JSON_NUMBER == in_item->type ) {
2363 char* val = jsonNumberToDBString( field, in_item );
2364 OSRF_BUFFER_ADD( sql_buf, val );
2367 } else if( !strcmp( get_primitive( field ), "number" )) {
2368 char* val = jsonNumberToDBString( field, in_item );
2369 OSRF_BUFFER_ADD( sql_buf, val );
2373 char* key_string = jsonObjectToSimpleString( in_item );
2374 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2375 OSRF_BUFFER_ADD( sql_buf, key_string );
2378 osrfLogError( OSRF_LOG_MARK,
2379 "%s: Error quoting key string [%s]", modulename, key_string );
2381 buffer_free( sql_buf );
2387 if( in_item_first ) {
2388 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2389 buffer_free( sql_buf );
2393 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2394 modulename, json_type( node->type ));
2395 buffer_free( sql_buf );
2399 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2401 return buffer_release( sql_buf );
2404 // Receive a JSON_ARRAY representing a function call. The first
2405 // entry in the array is the function name. The rest are parameters.
2406 static char* searchValueTransform( const jsonObject* array ) {
2408 if( array->size < 1 ) {
2409 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2413 // Get the function name
2414 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2415 if( func_item->type != JSON_STRING ) {
2416 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2417 modulename, json_type( func_item->type ));
2421 growing_buffer* sql_buf = buffer_init( 32 );
2423 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2424 OSRF_BUFFER_ADD( sql_buf, "( " );
2426 // Get the parameters
2427 int func_item_index = 1; // We already grabbed the zeroth entry
2428 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2430 // Add a separator comma, if we need one
2431 if( func_item_index > 2 )
2432 buffer_add( sql_buf, ", " );
2434 // Add the current parameter
2435 if( func_item->type == JSON_NULL ) {
2436 buffer_add( sql_buf, "NULL" );
2438 char* val = jsonObjectToSimpleString( func_item );
2439 if( dbi_conn_quote_string( dbhandle, &val )) {
2440 OSRF_BUFFER_ADD( sql_buf, val );
2443 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2445 buffer_free( sql_buf );
2452 buffer_add( sql_buf, " )" );
2454 return buffer_release( sql_buf );
2457 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2458 const jsonObject* node, const char* op ) {
2460 if( ! is_good_operator( op ) ) {
2461 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2465 char* val = searchValueTransform( node );
2469 growing_buffer* sql_buf = buffer_init( 32 );
2474 osrfHashGet( field, "name" ),
2481 return buffer_release( sql_buf );
2484 // class_alias is a class name or other table alias
2485 // field is a field definition as stored in the IDL
2486 // node comes from the method parameter, and may represent an entry in the SELECT list
2487 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2488 const jsonObject* node ) {
2489 growing_buffer* sql_buf = buffer_init( 32 );
2491 const char* field_transform = jsonObjectGetString(
2492 jsonObjectGetKeyConst( node, "transform" ) );
2493 const char* transform_subcolumn = jsonObjectGetString(
2494 jsonObjectGetKeyConst( node, "result_field" ) );
2496 if( transform_subcolumn ) {
2497 if( ! is_identifier( transform_subcolumn ) ) {
2498 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2499 modulename, transform_subcolumn );
2500 buffer_free( sql_buf );
2503 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2506 if( field_transform ) {
2508 if( ! is_identifier( field_transform ) ) {
2509 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2510 modulename, field_transform );
2511 buffer_free( sql_buf );
2515 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2516 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2517 field_transform, class_alias, osrfHashGet( field, "name" ));
2519 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2520 field_transform, class_alias, osrfHashGet( field, "name" ));
2523 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2526 if( array->type != JSON_ARRAY ) {
2527 osrfLogError( OSRF_LOG_MARK,
2528 "%s: Expected JSON_ARRAY for function params; found %s",
2529 modulename, json_type( array->type ) );
2530 buffer_free( sql_buf );
2533 int func_item_index = 0;
2534 jsonObject* func_item;
2535 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2537 char* val = jsonObjectToSimpleString( func_item );
2540 buffer_add( sql_buf, ",NULL" );
2541 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2542 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2543 OSRF_BUFFER_ADD( sql_buf, val );
2545 osrfLogError( OSRF_LOG_MARK,
2546 "%s: Error quoting key string [%s]", modulename, val );
2548 buffer_free( sql_buf );
2555 buffer_add( sql_buf, " )" );
2558 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2561 if( transform_subcolumn )
2562 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2564 return buffer_release( sql_buf );
2567 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2568 const jsonObject* node, const char* op ) {
2570 if( ! is_good_operator( op ) ) {
2571 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2575 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2576 if( ! field_transform )
2579 int extra_parens = 0; // boolean
2581 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2583 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2585 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2587 free( field_transform );
2591 } else if( value_obj->type == JSON_ARRAY ) {
2592 value = searchValueTransform( value_obj );
2594 osrfLogError( OSRF_LOG_MARK,
2595 "%s: Error building value transform for field transform", modulename );
2596 free( field_transform );
2599 } else if( value_obj->type == JSON_HASH ) {
2600 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2602 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2604 free( field_transform );
2608 } else if( value_obj->type == JSON_NUMBER ) {
2609 value = jsonNumberToDBString( field, value_obj );
2610 } else if( value_obj->type == JSON_NULL ) {
2611 osrfLogError( OSRF_LOG_MARK,
2612 "%s: Error building predicate for field transform: null value", modulename );
2613 free( field_transform );
2615 } else if( value_obj->type == JSON_BOOL ) {
2616 osrfLogError( OSRF_LOG_MARK,
2617 "%s: Error building predicate for field transform: boolean value", modulename );
2618 free( field_transform );
2621 if( !strcmp( get_primitive( field ), "number") ) {
2622 value = jsonNumberToDBString( field, value_obj );
2624 value = jsonObjectToSimpleString( value_obj );
2625 if( !dbi_conn_quote_string( dbhandle, &value )) {
2626 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2627 modulename, value );
2629 free( field_transform );
2635 const char* left_parens = "";
2636 const char* right_parens = "";
2638 if( extra_parens ) {
2643 growing_buffer* sql_buf = buffer_init( 32 );
2647 "%s%s %s %s %s %s%s",
2658 free( field_transform );
2660 return buffer_release( sql_buf );
2663 static char* searchSimplePredicate( const char* op, const char* class_alias,
2664 osrfHash* field, const jsonObject* node ) {
2666 if( ! is_good_operator( op ) ) {
2667 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2673 // Get the value to which we are comparing the specified column
2674 if( node->type != JSON_NULL ) {
2675 if( node->type == JSON_NUMBER ) {
2676 val = jsonNumberToDBString( field, node );
2677 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2678 val = jsonNumberToDBString( field, node );
2680 val = jsonObjectToSimpleString( node );
2685 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2686 // Value is not numeric; enclose it in quotes
2687 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2688 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2695 // Compare to a null value
2696 val = strdup( "NULL" );
2697 if( strcmp( op, "=" ))
2703 growing_buffer* sql_buf = buffer_init( 32 );
2704 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2705 char* pred = buffer_release( sql_buf );
2712 static char* searchBETWEENPredicate( const char* class_alias,
2713 osrfHash* field, const jsonObject* node ) {
2715 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2716 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2718 if( NULL == y_node ) {
2719 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2722 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2723 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2730 if( !strcmp( get_primitive( field ), "number") ) {
2731 x_string = jsonNumberToDBString( field, x_node );
2732 y_string = jsonNumberToDBString( field, y_node );
2735 x_string = jsonObjectToSimpleString( x_node );
2736 y_string = jsonObjectToSimpleString( y_node );
2737 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2738 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2739 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2740 modulename, x_string, y_string );
2747 growing_buffer* sql_buf = buffer_init( 32 );
2748 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2749 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2753 return buffer_release( sql_buf );
2756 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2757 jsonObject* node, osrfMethodContext* ctx ) {
2760 if( node->type == JSON_ARRAY ) { // equality IN search
2761 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2762 } else if( node->type == JSON_HASH ) { // other search
2763 jsonIterator* pred_itr = jsonNewIterator( node );
2764 if( !jsonIteratorHasNext( pred_itr ) ) {
2765 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2766 modulename, osrfHashGet(field, "name" ));
2768 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2770 // Verify that there are no additional predicates
2771 if( jsonIteratorHasNext( pred_itr ) ) {
2772 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2773 modulename, osrfHashGet(field, "name" ));
2774 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2775 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2776 else if( !(strcasecmp( pred_itr->key,"in" ))
2777 || !(strcasecmp( pred_itr->key,"not in" )) )
2778 pred = searchINPredicate(
2779 class_info->alias, field, pred_node, pred_itr->key, ctx );
2780 else if( pred_node->type == JSON_ARRAY )
2781 pred = searchFunctionPredicate(
2782 class_info->alias, field, pred_node, pred_itr->key );
2783 else if( pred_node->type == JSON_HASH )
2784 pred = searchFieldTransformPredicate(
2785 class_info, field, pred_node, pred_itr->key );
2787 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2789 jsonIteratorFree( pred_itr );
2791 } else if( node->type == JSON_NULL ) { // IS NULL search
2792 growing_buffer* _p = buffer_init( 64 );
2795 "\"%s\".%s IS NULL",
2796 class_info->class_name,
2797 osrfHashGet( field, "name" )
2799 pred = buffer_release( _p );
2800 } else { // equality search
2801 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2820 field : call_number,
2836 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2838 const jsonObject* working_hash;
2839 jsonObject* freeable_hash = NULL;
2841 if( join_hash->type == JSON_HASH ) {
2842 working_hash = join_hash;
2843 } else if( join_hash->type == JSON_STRING ) {
2844 // turn it into a JSON_HASH by creating a wrapper
2845 // around a copy of the original
2846 const char* _tmp = jsonObjectGetString( join_hash );
2847 freeable_hash = jsonNewObjectType( JSON_HASH );
2848 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2849 working_hash = freeable_hash;
2853 "%s: JOIN failed; expected JSON object type not found",
2859 growing_buffer* join_buf = buffer_init( 128 );
2860 const char* leftclass = left_info->class_name;
2862 jsonObject* snode = NULL;
2863 jsonIterator* search_itr = jsonNewIterator( working_hash );
2865 while ( (snode = jsonIteratorNext( search_itr )) ) {
2866 const char* right_alias = search_itr->key;
2868 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2870 class = right_alias;
2872 const ClassInfo* right_info = add_joined_class( right_alias, class );
2876 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2880 jsonIteratorFree( search_itr );
2881 buffer_free( join_buf );
2883 jsonObjectFree( freeable_hash );
2886 osrfHash* links = right_info->links;
2887 const char* table = right_info->source_def;
2889 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2890 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2892 if( field && !fkey ) {
2893 // Look up the corresponding join column in the IDL.
2894 // The link must be defined in the child table,
2895 // and point to the right parent table.
2896 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2897 const char* reltype = NULL;
2898 const char* other_class = NULL;
2899 reltype = osrfHashGet( idl_link, "reltype" );
2900 if( reltype && strcmp( reltype, "has_many" ) )
2901 other_class = osrfHashGet( idl_link, "class" );
2902 if( other_class && !strcmp( other_class, leftclass ) )
2903 fkey = osrfHashGet( idl_link, "key" );
2907 "%s: JOIN failed. No link defined from %s.%s to %s",
2913 buffer_free( join_buf );
2915 jsonObjectFree( freeable_hash );
2916 jsonIteratorFree( search_itr );
2920 } else if( !field && fkey ) {
2921 // Look up the corresponding join column in the IDL.
2922 // The link must be defined in the child table,
2923 // and point to the right parent table.
2924 osrfHash* left_links = left_info->links;
2925 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2926 const char* reltype = NULL;
2927 const char* other_class = NULL;
2928 reltype = osrfHashGet( idl_link, "reltype" );
2929 if( reltype && strcmp( reltype, "has_many" ) )
2930 other_class = osrfHashGet( idl_link, "class" );
2931 if( other_class && !strcmp( other_class, class ) )
2932 field = osrfHashGet( idl_link, "key" );
2936 "%s: JOIN failed. No link defined from %s.%s to %s",
2942 buffer_free( join_buf );
2944 jsonObjectFree( freeable_hash );
2945 jsonIteratorFree( search_itr );
2949 } else if( !field && !fkey ) {
2950 osrfHash* left_links = left_info->links;
2952 // For each link defined for the left class:
2953 // see if the link references the joined class
2954 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2955 osrfHash* curr_link = NULL;
2956 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2957 const char* other_class = osrfHashGet( curr_link, "class" );
2958 if( other_class && !strcmp( other_class, class ) ) {
2960 // In the IDL, the parent class doesn't always know then names of the child
2961 // columns that are pointing to it, so don't use that end of the link
2962 const char* reltype = osrfHashGet( curr_link, "reltype" );
2963 if( reltype && strcmp( reltype, "has_many" ) ) {
2964 // Found a link between the classes
2965 fkey = osrfHashIteratorKey( itr );
2966 field = osrfHashGet( curr_link, "key" );
2971 osrfHashIteratorFree( itr );
2973 if( !field || !fkey ) {
2974 // Do another such search, with the classes reversed
2976 // For each link defined for the joined class:
2977 // see if the link references the left class
2978 osrfHashIterator* itr = osrfNewHashIterator( links );
2979 osrfHash* curr_link = NULL;
2980 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2981 const char* other_class = osrfHashGet( curr_link, "class" );
2982 if( other_class && !strcmp( other_class, leftclass ) ) {
2984 // In the IDL, the parent class doesn't know then names of the child
2985 // columns that are pointing to it, so don't use that end of the link
2986 const char* reltype = osrfHashGet( curr_link, "reltype" );
2987 if( reltype && strcmp( reltype, "has_many" ) ) {
2988 // Found a link between the classes
2989 field = osrfHashIteratorKey( itr );
2990 fkey = osrfHashGet( curr_link, "key" );
2995 osrfHashIteratorFree( itr );
2998 if( !field || !fkey ) {
3001 "%s: JOIN failed. No link defined between %s and %s",
3006 buffer_free( join_buf );
3008 jsonObjectFree( freeable_hash );
3009 jsonIteratorFree( search_itr );
3014 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3016 if( !strcasecmp( type,"left" )) {
3017 buffer_add( join_buf, " LEFT JOIN" );
3018 } else if( !strcasecmp( type,"right" )) {
3019 buffer_add( join_buf, " RIGHT JOIN" );
3020 } else if( !strcasecmp( type,"full" )) {
3021 buffer_add( join_buf, " FULL JOIN" );
3023 buffer_add( join_buf, " INNER JOIN" );
3026 buffer_add( join_buf, " INNER JOIN" );
3029 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3030 table, right_alias, right_alias, field, left_info->alias, fkey );
3032 // Add any other join conditions as specified by "filter"
3033 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3035 const char* filter_op = jsonObjectGetString(
3036 jsonObjectGetKeyConst( snode, "filter_op" ) );
3037 if( filter_op && !strcasecmp( "or",filter_op )) {
3038 buffer_add( join_buf, " OR " );
3040 buffer_add( join_buf, " AND " );
3043 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3045 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3046 OSRF_BUFFER_ADD( join_buf, jpred );
3051 "%s: JOIN failed. Invalid conditional expression.",
3054 jsonIteratorFree( search_itr );
3055 buffer_free( join_buf );
3057 jsonObjectFree( freeable_hash );
3062 buffer_add( join_buf, " ) " );
3064 // Recursively add a nested join, if one is present
3065 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3067 char* jpred = searchJOIN( join_filter, right_info );
3069 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3070 OSRF_BUFFER_ADD( join_buf, jpred );
3073 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3074 jsonIteratorFree( search_itr );
3075 buffer_free( join_buf );
3077 jsonObjectFree( freeable_hash );
3084 jsonObjectFree( freeable_hash );
3085 jsonIteratorFree( search_itr );
3087 return buffer_release( join_buf );
3092 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3093 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3094 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3096 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3098 search_hash is the JSON expression of the conditions.
3099 meta is the class definition from the IDL, for the relevant table.
3100 opjoin_type indicates whether multiple conditions, if present, should be
3101 connected by AND or OR.
3102 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3103 to pass it to other functions -- and all they do with it is to use the session
3104 and request members to send error messages back to the client.
3108 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3109 int opjoin_type, osrfMethodContext* ctx ) {
3113 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3114 "opjoin_type = %d, ctx addr = %p",
3117 class_info->class_def,
3122 growing_buffer* sql_buf = buffer_init( 128 );
3124 jsonObject* node = NULL;
3127 if( search_hash->type == JSON_ARRAY ) {
3128 if( 0 == search_hash->size ) {
3131 "%s: Invalid predicate structure: empty JSON array",
3134 buffer_free( sql_buf );
3138 unsigned long i = 0;
3139 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3143 if( opjoin_type == OR_OP_JOIN )
3144 buffer_add( sql_buf, " OR " );
3146 buffer_add( sql_buf, " AND " );
3149 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3151 buffer_free( sql_buf );
3155 buffer_fadd( sql_buf, "( %s )", subpred );
3159 } else if( search_hash->type == JSON_HASH ) {
3160 osrfLogDebug( OSRF_LOG_MARK,
3161 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3162 jsonIterator* search_itr = jsonNewIterator( search_hash );
3163 if( !jsonIteratorHasNext( search_itr ) ) {
3166 "%s: Invalid predicate structure: empty JSON object",
3169 jsonIteratorFree( search_itr );
3170 buffer_free( sql_buf );
3174 while( (node = jsonIteratorNext( search_itr )) ) {
3179 if( opjoin_type == OR_OP_JOIN )
3180 buffer_add( sql_buf, " OR " );
3182 buffer_add( sql_buf, " AND " );
3185 if( '+' == search_itr->key[ 0 ] ) {
3187 // This plus sign prefixes a class name or other table alias;
3188 // make sure the table alias is in scope
3189 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3190 if( ! alias_info ) {
3193 "%s: Invalid table alias \"%s\" in WHERE clause",
3197 jsonIteratorFree( search_itr );
3198 buffer_free( sql_buf );
3202 if( node->type == JSON_STRING ) {
3203 // It's the name of a column; make sure it belongs to the class
3204 const char* fieldname = jsonObjectGetString( node );
3205 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3208 "%s: Invalid column name \"%s\" in WHERE clause "
3209 "for table alias \"%s\"",
3214 jsonIteratorFree( search_itr );
3215 buffer_free( sql_buf );
3219 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3221 // It's something more complicated
3222 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3224 jsonIteratorFree( search_itr );
3225 buffer_free( sql_buf );
3229 buffer_fadd( sql_buf, "( %s )", subpred );
3232 } else if( '-' == search_itr->key[ 0 ] ) {
3233 if( !strcasecmp( "-or", search_itr->key )) {
3234 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3236 jsonIteratorFree( search_itr );
3237 buffer_free( sql_buf );
3241 buffer_fadd( sql_buf, "( %s )", subpred );
3243 } else if( !strcasecmp( "-and", search_itr->key )) {
3244 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3246 jsonIteratorFree( search_itr );
3247 buffer_free( sql_buf );
3251 buffer_fadd( sql_buf, "( %s )", subpred );
3253 } else if( !strcasecmp("-not",search_itr->key) ) {
3254 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3256 jsonIteratorFree( search_itr );
3257 buffer_free( sql_buf );
3261 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3263 } else if( !strcasecmp( "-exists", search_itr->key )) {
3264 char* subpred = buildQuery( ctx, node, SUBSELECT );
3266 jsonIteratorFree( search_itr );
3267 buffer_free( sql_buf );
3271 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3273 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3274 char* subpred = buildQuery( ctx, node, SUBSELECT );
3276 jsonIteratorFree( search_itr );
3277 buffer_free( sql_buf );
3281 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3283 } else { // Invalid "minus" operator
3286 "%s: Invalid operator \"%s\" in WHERE clause",
3290 jsonIteratorFree( search_itr );
3291 buffer_free( sql_buf );
3297 const char* class = class_info->class_name;
3298 osrfHash* fields = class_info->fields;
3299 osrfHash* field = osrfHashGet( fields, search_itr->key );
3302 const char* table = class_info->source_def;
3305 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3308 table ? table : "?",
3311 jsonIteratorFree( search_itr );
3312 buffer_free( sql_buf );
3316 char* subpred = searchPredicate( class_info, field, node, ctx );
3318 buffer_free( sql_buf );
3319 jsonIteratorFree( search_itr );
3323 buffer_add( sql_buf, subpred );
3327 jsonIteratorFree( search_itr );
3330 // ERROR ... only hash and array allowed at this level
3331 char* predicate_string = jsonObjectToJSON( search_hash );
3334 "%s: Invalid predicate structure: %s",
3338 buffer_free( sql_buf );
3339 free( predicate_string );
3343 return buffer_release( sql_buf );
3346 /* Build a JSON_ARRAY of field names for a given table alias
3348 static jsonObject* defaultSelectList( const char* table_alias ) {
3353 ClassInfo* class_info = search_all_alias( table_alias );
3354 if( ! class_info ) {
3357 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3364 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3365 osrfHash* field_def = NULL;
3366 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3367 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3368 const char* field_name = osrfHashIteratorKey( field_itr );
3369 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3370 jsonObjectPush( array, jsonNewObject( field_name ) );
3373 osrfHashIteratorFree( field_itr );
3378 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3379 // The jsonObject must be a JSON_HASH with an single entry for "union",
3380 // "intersect", or "except". The data associated with this key must be an
3381 // array of hashes, each hash being a query.
3382 // Also allowed but currently ignored: entries for "order_by" and "alias".
3383 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3385 if( ! combo || combo->type != JSON_HASH )
3386 return NULL; // should be impossible; validated by caller
3388 const jsonObject* query_array = NULL; // array of subordinate queries
3389 const char* op = NULL; // name of operator, e.g. UNION
3390 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3391 int op_count = 0; // for detecting conflicting operators
3392 int excepting = 0; // boolean
3393 int all = 0; // boolean
3394 jsonObject* order_obj = NULL;
3396 // Identify the elements in the hash
3397 jsonIterator* query_itr = jsonNewIterator( combo );
3398 jsonObject* curr_obj = NULL;
3399 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3400 if( ! strcmp( "union", query_itr->key ) ) {
3403 query_array = curr_obj;
3404 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3407 query_array = curr_obj;
3408 } else if( ! strcmp( "except", query_itr->key ) ) {
3412 query_array = curr_obj;
3413 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3416 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3419 order_obj = curr_obj;
3420 } else if( ! strcmp( "alias", query_itr->key ) ) {
3421 if( curr_obj->type != JSON_STRING ) {
3422 jsonIteratorFree( query_itr );
3425 alias = jsonObjectGetString( curr_obj );
3426 } else if( ! strcmp( "all", query_itr->key ) ) {
3427 if( obj_is_true( curr_obj ) )
3431 osrfAppSessionStatus(
3433 OSRF_STATUS_INTERNALSERVERERROR,
3434 "osrfMethodException",
3436 "Malformed query; unexpected entry in query object"
3440 "%s: Unexpected entry for \"%s\" in%squery",
3445 jsonIteratorFree( query_itr );
3449 jsonIteratorFree( query_itr );
3451 // More sanity checks
3452 if( ! query_array ) {
3454 osrfAppSessionStatus(
3456 OSRF_STATUS_INTERNALSERVERERROR,
3457 "osrfMethodException",
3459 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3463 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3466 return NULL; // should be impossible...
3467 } else if( op_count > 1 ) {
3469 osrfAppSessionStatus(
3471 OSRF_STATUS_INTERNALSERVERERROR,
3472 "osrfMethodException",
3474 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3478 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3482 } if( query_array->type != JSON_ARRAY ) {
3484 osrfAppSessionStatus(
3486 OSRF_STATUS_INTERNALSERVERERROR,
3487 "osrfMethodException",
3489 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3493 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3496 json_type( query_array->type )
3499 } if( query_array->size < 2 ) {
3501 osrfAppSessionStatus(
3503 OSRF_STATUS_INTERNALSERVERERROR,
3504 "osrfMethodException",
3506 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3510 "%s:%srequires multiple queries as operands",
3515 } else if( excepting && query_array->size > 2 ) {
3517 osrfAppSessionStatus(
3519 OSRF_STATUS_INTERNALSERVERERROR,
3520 "osrfMethodException",
3522 "EXCEPT operator has too many queries as operands"
3526 "%s:EXCEPT operator has too many queries as operands",
3530 } else if( order_obj && ! alias ) {
3532 osrfAppSessionStatus(
3534 OSRF_STATUS_INTERNALSERVERERROR,
3535 "osrfMethodException",
3537 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3541 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3547 // So far so good. Now build the SQL.
3548 growing_buffer* sql = buffer_init( 256 );
3550 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3551 // Add a layer of parentheses
3552 if( flags & SUBCOMBO )
3553 OSRF_BUFFER_ADD( sql, "( " );
3555 // Traverse the query array. Each entry should be a hash.
3556 int first = 1; // boolean
3558 jsonObject* query = NULL;
3559 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3560 if( query->type != JSON_HASH ) {
3562 osrfAppSessionStatus(
3564 OSRF_STATUS_INTERNALSERVERERROR,
3565 "osrfMethodException",
3567 "Malformed query under UNION, INTERSECT or EXCEPT"
3571 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3574 json_type( query->type )
3583 OSRF_BUFFER_ADD( sql, op );
3585 OSRF_BUFFER_ADD( sql, "ALL " );
3588 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3592 "%s: Error building query under%s",
3600 OSRF_BUFFER_ADD( sql, query_str );
3603 if( flags & SUBCOMBO )
3604 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3606 if( !(flags & SUBSELECT) )
3607 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3609 return buffer_release( sql );
3612 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3613 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3614 // or "except" to indicate the type of query.
3615 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3619 osrfAppSessionStatus(
3621 OSRF_STATUS_INTERNALSERVERERROR,
3622 "osrfMethodException",
3624 "Malformed query; no query object"
3626 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3628 } else if( query->type != JSON_HASH ) {
3630 osrfAppSessionStatus(
3632 OSRF_STATUS_INTERNALSERVERERROR,
3633 "osrfMethodException",
3635 "Malformed query object"
3639 "%s: Query object is %s instead of JSON_HASH",
3641 json_type( query->type )
3646 // Determine what kind of query it purports to be, and dispatch accordingly.
3647 if( jsonObjectGetKeyConst( query, "union" ) ||
3648 jsonObjectGetKeyConst( query, "intersect" ) ||
3649 jsonObjectGetKeyConst( query, "except" )) {
3650 return doCombo( ctx, query, flags );
3652 // It is presumably a SELECT query
3654 // Push a node onto the stack for the current query. Every level of
3655 // subquery gets its own QueryFrame on the Stack.
3658 // Build an SQL SELECT statement
3661 jsonObjectGetKey( query, "select" ),
3662 jsonObjectGetKeyConst( query, "from" ),
3663 jsonObjectGetKeyConst( query, "where" ),
3664 jsonObjectGetKeyConst( query, "having" ),
3665 jsonObjectGetKeyConst( query, "order_by" ),
3666 jsonObjectGetKeyConst( query, "limit" ),
3667 jsonObjectGetKeyConst( query, "offset" ),
3676 /* method context */ osrfMethodContext* ctx,
3678 /* SELECT */ jsonObject* selhash,
3679 /* FROM */ const jsonObject* join_hash,
3680 /* WHERE */ const jsonObject* search_hash,
3681 /* HAVING */ const jsonObject* having_hash,
3682 /* ORDER BY */ const jsonObject* order_hash,
3683 /* LIMIT */ const jsonObject* limit,
3684 /* OFFSET */ const jsonObject* offset,
3685 /* flags */ int flags
3687 const char* locale = osrf_message_get_last_locale();
3689 // general tmp objects
3690 const jsonObject* tmp_const;
3691 jsonObject* selclass = NULL;
3692 jsonObject* snode = NULL;
3693 jsonObject* onode = NULL;
3695 char* string = NULL;
3696 int from_function = 0;
3701 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3703 // punt if there's no FROM clause
3704 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3707 "%s: FROM clause is missing or empty",
3711 osrfAppSessionStatus(
3713 OSRF_STATUS_INTERNALSERVERERROR,
3714 "osrfMethodException",
3716 "FROM clause is missing or empty in JSON query"
3721 // the core search class
3722 const char* core_class = NULL;
3724 // get the core class -- the only key of the top level FROM clause, or a string
3725 if( join_hash->type == JSON_HASH ) {
3726 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3727 snode = jsonIteratorNext( tmp_itr );
3729 // Populate the current QueryFrame with information
3730 // about the core class
3731 if( add_query_core( NULL, tmp_itr->key ) ) {
3733 osrfAppSessionStatus(
3735 OSRF_STATUS_INTERNALSERVERERROR,
3736 "osrfMethodException",
3738 "Unable to look up core class"
3742 core_class = curr_query->core.class_name;
3745 jsonObject* extra = jsonIteratorNext( tmp_itr );
3747 jsonIteratorFree( tmp_itr );
3750 // There shouldn't be more than one entry in join_hash
3754 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3758 osrfAppSessionStatus(
3760 OSRF_STATUS_INTERNALSERVERERROR,
3761 "osrfMethodException",
3763 "Malformed FROM clause in JSON query"
3765 return NULL; // Malformed join_hash; extra entry
3767 } else if( join_hash->type == JSON_ARRAY ) {
3768 // We're selecting from a function, not from a table
3770 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3773 } else if( join_hash->type == JSON_STRING ) {
3774 // Populate the current QueryFrame with information
3775 // about the core class
3776 core_class = jsonObjectGetString( join_hash );
3778 if( add_query_core( NULL, core_class ) ) {
3780 osrfAppSessionStatus(
3782 OSRF_STATUS_INTERNALSERVERERROR,
3783 "osrfMethodException",
3785 "Unable to look up core class"
3793 "%s: FROM clause is unexpected JSON type: %s",
3795 json_type( join_hash->type )
3798 osrfAppSessionStatus(
3800 OSRF_STATUS_INTERNALSERVERERROR,
3801 "osrfMethodException",
3803 "Ill-formed FROM clause in JSON query"
3808 // Build the join clause, if any, while filling out the list
3809 // of joined classes in the current QueryFrame.
3810 char* join_clause = NULL;
3811 if( join_hash && ! from_function ) {
3813 join_clause = searchJOIN( join_hash, &curr_query->core );
3814 if( ! join_clause ) {
3816 osrfAppSessionStatus(
3818 OSRF_STATUS_INTERNALSERVERERROR,
3819 "osrfMethodException",
3821 "Unable to construct JOIN clause(s)"
3827 // For in case we don't get a select list
3828 jsonObject* defaultselhash = NULL;
3830 // if there is no select list, build a default select list ...
3831 if( !selhash && !from_function ) {
3832 jsonObject* default_list = defaultSelectList( core_class );
3833 if( ! default_list ) {
3835 osrfAppSessionStatus(
3837 OSRF_STATUS_INTERNALSERVERERROR,
3838 "osrfMethodException",
3840 "Unable to build default SELECT clause in JSON query"
3842 free( join_clause );
3847 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3848 jsonObjectSetKey( selhash, core_class, default_list );
3851 // The SELECT clause can be encoded only by a hash
3852 if( !from_function && selhash->type != JSON_HASH ) {
3855 "%s: Expected JSON_HASH for SELECT clause; found %s",
3857 json_type( selhash->type )
3861 osrfAppSessionStatus(
3863 OSRF_STATUS_INTERNALSERVERERROR,
3864 "osrfMethodException",
3866 "Malformed SELECT clause in JSON query"
3868 free( join_clause );
3872 // If you see a null or wild card specifier for the core class, or an
3873 // empty array, replace it with a default SELECT list
3874 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3876 int default_needed = 0; // boolean
3877 if( JSON_STRING == tmp_const->type
3878 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3880 else if( JSON_NULL == tmp_const->type )
3883 if( default_needed ) {
3884 // Build a default SELECT list
3885 jsonObject* default_list = defaultSelectList( core_class );
3886 if( ! default_list ) {
3888 osrfAppSessionStatus(
3890 OSRF_STATUS_INTERNALSERVERERROR,
3891 "osrfMethodException",
3893 "Can't build default SELECT clause in JSON query"
3895 free( join_clause );
3900 jsonObjectSetKey( selhash, core_class, default_list );
3904 // temp buffers for the SELECT list and GROUP BY clause
3905 growing_buffer* select_buf = buffer_init( 128 );
3906 growing_buffer* group_buf = buffer_init( 128 );
3908 int aggregate_found = 0; // boolean
3910 // Build a select list
3911 if( from_function ) // From a function we select everything
3912 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3915 // Build the SELECT list as SQL
3919 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3920 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3922 const char* cname = selclass_itr->key;
3924 // Make sure the target relation is in the FROM clause.
3926 // At this point join_hash is a step down from the join_hash we
3927 // received as a parameter. If the original was a JSON_STRING,
3928 // then json_hash is now NULL. If the original was a JSON_HASH,
3929 // then json_hash is now the first (and only) entry in it,
3930 // denoting the core class. We've already excluded the
3931 // possibility that the original was a JSON_ARRAY, because in
3932 // that case from_function would be non-NULL, and we wouldn't
3935 // If the current table alias isn't in scope, bail out
3936 ClassInfo* class_info = search_alias( cname );
3937 if( ! class_info ) {
3940 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3945 osrfAppSessionStatus(
3947 OSRF_STATUS_INTERNALSERVERERROR,
3948 "osrfMethodException",
3950 "Selected class not in FROM clause in JSON query"
3952 jsonIteratorFree( selclass_itr );
3953 buffer_free( select_buf );
3954 buffer_free( group_buf );
3955 if( defaultselhash )
3956 jsonObjectFree( defaultselhash );
3957 free( join_clause );
3961 if( selclass->type != JSON_ARRAY ) {
3964 "%s: Malformed SELECT list for class \"%s\"; not an array",
3969 osrfAppSessionStatus(
3971 OSRF_STATUS_INTERNALSERVERERROR,
3972 "osrfMethodException",
3974 "Selected class not in FROM clause in JSON query"
3977 jsonIteratorFree( selclass_itr );
3978 buffer_free( select_buf );
3979 buffer_free( group_buf );
3980 if( defaultselhash )
3981 jsonObjectFree( defaultselhash );
3982 free( join_clause );
3986 // Look up some attributes of the current class
3987 osrfHash* idlClass = class_info->class_def;
3988 osrfHash* class_field_set = class_info->fields;
3989 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3990 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3992 if( 0 == selclass->size ) {
3995 "%s: No columns selected from \"%s\"",
4001 // stitch together the column list for the current table alias...
4002 unsigned long field_idx = 0;
4003 jsonObject* selfield = NULL;
4004 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4006 // If we need a separator comma, add one
4010 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4013 // if the field specification is a string, add it to the list
4014 if( selfield->type == JSON_STRING ) {
4016 // Look up the field in the IDL
4017 const char* col_name = jsonObjectGetString( selfield );
4018 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4020 // No such field in current class
4023 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4029 osrfAppSessionStatus(
4031 OSRF_STATUS_INTERNALSERVERERROR,
4032 "osrfMethodException",
4034 "Selected column not defined in JSON query"
4036 jsonIteratorFree( selclass_itr );
4037 buffer_free( select_buf );
4038 buffer_free( group_buf );
4039 if( defaultselhash )
4040 jsonObjectFree( defaultselhash );
4041 free( join_clause );
4043 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4044 // Virtual field not allowed
4047 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4053 osrfAppSessionStatus(
4055 OSRF_STATUS_INTERNALSERVERERROR,
4056 "osrfMethodException",
4058 "Selected column may not be virtual in JSON query"
4060 jsonIteratorFree( selclass_itr );
4061 buffer_free( select_buf );
4062 buffer_free( group_buf );
4063 if( defaultselhash )
4064 jsonObjectFree( defaultselhash );
4065 free( join_clause );
4071 if( flags & DISABLE_I18N )
4074 i18n = osrfHashGet( field_def, "i18n" );
4076 if( str_is_true( i18n ) ) {
4077 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4078 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4079 class_tname, cname, col_name, class_pkey,
4080 cname, class_pkey, locale, col_name );
4082 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4083 cname, col_name, col_name );
4086 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4087 cname, col_name, col_name );
4090 // ... but it could be an object, in which case we check for a Field Transform
4091 } else if( selfield->type == JSON_HASH ) {
4093 const char* col_name = jsonObjectGetString(
4094 jsonObjectGetKeyConst( selfield, "column" ) );
4096 // Get the field definition from the IDL
4097 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4099 // No such field in current class
4102 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4108 osrfAppSessionStatus(
4110 OSRF_STATUS_INTERNALSERVERERROR,
4111 "osrfMethodException",
4113 "Selected column is not defined in JSON query"
4115 jsonIteratorFree( selclass_itr );
4116 buffer_free( select_buf );
4117 buffer_free( group_buf );
4118 if( defaultselhash )
4119 jsonObjectFree( defaultselhash );
4120 free( join_clause );
4122 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4123 // No such field in current class
4126 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4132 osrfAppSessionStatus(
4134 OSRF_STATUS_INTERNALSERVERERROR,
4135 "osrfMethodException",
4137 "Selected column is virtual in JSON query"
4139 jsonIteratorFree( selclass_itr );
4140 buffer_free( select_buf );
4141 buffer_free( group_buf );
4142 if( defaultselhash )
4143 jsonObjectFree( defaultselhash );
4144 free( join_clause );
4148 // Decide what to use as a column alias
4150 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4151 _alias = jsonObjectGetString( tmp_const );
4152 } else { // Use field name as the alias
4156 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4157 char* transform_str = searchFieldTransform(
4158 class_info->alias, field_def, selfield );
4159 if( transform_str ) {
4160 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4161 free( transform_str );
4164 osrfAppSessionStatus(
4166 OSRF_STATUS_INTERNALSERVERERROR,
4167 "osrfMethodException",
4169 "Unable to generate transform function in JSON query"
4171 jsonIteratorFree( selclass_itr );
4172 buffer_free( select_buf );
4173 buffer_free( group_buf );
4174 if( defaultselhash )
4175 jsonObjectFree( defaultselhash );
4176 free( join_clause );
4183 if( flags & DISABLE_I18N )
4186 i18n = osrfHashGet( field_def, "i18n" );
4188 if( str_is_true( i18n ) ) {
4189 buffer_fadd( select_buf,
4190 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4191 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4192 class_tname, cname, col_name, class_pkey, cname,
4193 class_pkey, locale, _alias );
4195 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4196 cname, col_name, _alias );
4199 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4200 cname, col_name, _alias );
4207 "%s: Selected item is unexpected JSON type: %s",
4209 json_type( selfield->type )
4212 osrfAppSessionStatus(
4214 OSRF_STATUS_INTERNALSERVERERROR,
4215 "osrfMethodException",
4217 "Ill-formed SELECT item in JSON query"
4219 jsonIteratorFree( selclass_itr );
4220 buffer_free( select_buf );
4221 buffer_free( group_buf );
4222 if( defaultselhash )
4223 jsonObjectFree( defaultselhash );
4224 free( join_clause );
4228 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4229 if( obj_is_true( agg_obj ) )
4230 aggregate_found = 1;
4232 // Append a comma (except for the first one)
4233 // and add the column to a GROUP BY clause
4237 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4239 buffer_fadd( group_buf, " %d", sel_pos );
4243 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4245 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4246 if ( ! obj_is_true( aggregate_obj ) ) {
4250 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4253 buffer_fadd(group_buf, " %d", sel_pos);
4256 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4260 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4263 _column = searchFieldTransform(class_info->alias, field, selfield);
4264 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4265 OSRF_BUFFER_ADD(group_buf, _column);
4266 _column = searchFieldTransform(class_info->alias, field, selfield);
4273 } // end while -- iterating across SELECT columns
4275 } // end while -- iterating across classes
4277 jsonIteratorFree( selclass_itr );
4281 char* col_list = buffer_release( select_buf );
4283 // Make sure the SELECT list isn't empty. This can happen, for example,
4284 // if we try to build a default SELECT clause from a non-core table.
4287 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4289 osrfAppSessionStatus(
4291 OSRF_STATUS_INTERNALSERVERERROR,
4292 "osrfMethodException",
4294 "SELECT list is empty"
4297 buffer_free( group_buf );
4298 if( defaultselhash )
4299 jsonObjectFree( defaultselhash );
4300 free( join_clause );
4306 table = searchValueTransform( join_hash );
4308 table = strdup( curr_query->core.source_def );
4312 osrfAppSessionStatus(
4314 OSRF_STATUS_INTERNALSERVERERROR,
4315 "osrfMethodException",
4317 "Unable to identify table for core class"
4320 buffer_free( group_buf );
4321 if( defaultselhash )
4322 jsonObjectFree( defaultselhash );
4323 free( join_clause );
4327 // Put it all together
4328 growing_buffer* sql_buf = buffer_init( 128 );
4329 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4333 // Append the join clause, if any
4335 buffer_add(sql_buf, join_clause );
4336 free( join_clause );
4339 char* order_by_list = NULL;
4340 char* having_buf = NULL;
4342 if( !from_function ) {
4344 // Build a WHERE clause, if there is one
4346 buffer_add( sql_buf, " WHERE " );
4348 // and it's on the WHERE clause
4349 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4352 osrfAppSessionStatus(
4354 OSRF_STATUS_INTERNALSERVERERROR,
4355 "osrfMethodException",
4357 "Severe query error in WHERE predicate -- see error log for more details"
4360 buffer_free( group_buf );
4361 buffer_free( sql_buf );
4362 if( defaultselhash )
4363 jsonObjectFree( defaultselhash );
4367 buffer_add( sql_buf, pred );
4371 // Build a HAVING clause, if there is one
4374 // and it's on the the WHERE clause
4375 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4377 if( ! having_buf ) {
4379 osrfAppSessionStatus(
4381 OSRF_STATUS_INTERNALSERVERERROR,
4382 "osrfMethodException",
4384 "Severe query error in HAVING predicate -- see error log for more details"
4387 buffer_free( group_buf );
4388 buffer_free( sql_buf );
4389 if( defaultselhash )
4390 jsonObjectFree( defaultselhash );
4395 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4397 // Build an ORDER BY clause, if there is one
4398 if( NULL == order_hash )
4399 ; // No ORDER BY? do nothing
4400 else if( JSON_ARRAY == order_hash->type ) {
4401 // Array of field specifications, each specification being a
4402 // hash to define the class, field, and other details
4404 jsonObject* order_spec;
4405 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4407 if( JSON_HASH != order_spec->type ) {
4408 osrfLogError( OSRF_LOG_MARK,
4409 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4410 modulename, json_type( order_spec->type ) );
4412 osrfAppSessionStatus(
4414 OSRF_STATUS_INTERNALSERVERERROR,
4415 "osrfMethodException",
4417 "Malformed ORDER BY clause -- see error log for more details"
4419 buffer_free( order_buf );
4421 buffer_free( group_buf );
4422 buffer_free( sql_buf );
4423 if( defaultselhash )
4424 jsonObjectFree( defaultselhash );
4428 const char* class_alias =
4429 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4431 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4434 OSRF_BUFFER_ADD( order_buf, ", " );
4436 order_buf = buffer_init( 128 );
4438 if( !field || !class_alias ) {
4439 osrfLogError( OSRF_LOG_MARK,
4440 "%s: Missing class or field name in field specification "
4441 "of ORDER BY clause",
4444 osrfAppSessionStatus(
4446 OSRF_STATUS_INTERNALSERVERERROR,
4447 "osrfMethodException",
4449 "Malformed ORDER BY clause -- see error log for more details"
4451 buffer_free( order_buf );
4453 buffer_free( group_buf );
4454 buffer_free( sql_buf );
4455 if( defaultselhash )
4456 jsonObjectFree( defaultselhash );
4460 ClassInfo* order_class_info = search_alias( class_alias );
4461 if( ! order_class_info ) {
4462 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4463 "not in FROM clause", modulename, class_alias );
4465 osrfAppSessionStatus(
4467 OSRF_STATUS_INTERNALSERVERERROR,
4468 "osrfMethodException",
4470 "Invalid class referenced in ORDER BY clause -- "
4471 "see error log for more details"
4474 buffer_free( group_buf );
4475 buffer_free( sql_buf );
4476 if( defaultselhash )
4477 jsonObjectFree( defaultselhash );
4481 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4483 osrfLogError( OSRF_LOG_MARK,
4484 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4485 modulename, class_alias, field );
4487 osrfAppSessionStatus(
4489 OSRF_STATUS_INTERNALSERVERERROR,
4490 "osrfMethodException",
4492 "Invalid field referenced in ORDER BY clause -- "
4493 "see error log for more details"
4496 buffer_free( group_buf );
4497 buffer_free( sql_buf );
4498 if( defaultselhash )
4499 jsonObjectFree( defaultselhash );
4501 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4502 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4503 modulename, field );
4505 osrfAppSessionStatus(
4507 OSRF_STATUS_INTERNALSERVERERROR,
4508 "osrfMethodException",
4510 "Virtual field in ORDER BY clause -- see error log for more details"
4512 buffer_free( order_buf );
4514 buffer_free( group_buf );
4515 buffer_free( sql_buf );
4516 if( defaultselhash )
4517 jsonObjectFree( defaultselhash );
4521 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4522 char* transform_str = searchFieldTransform(
4523 class_alias, field_def, order_spec );
4524 if( ! transform_str ) {
4526 osrfAppSessionStatus(
4528 OSRF_STATUS_INTERNALSERVERERROR,
4529 "osrfMethodException",
4531 "Severe query error in ORDER BY clause -- "
4532 "see error log for more details"
4534 buffer_free( order_buf );
4536 buffer_free( group_buf );
4537 buffer_free( sql_buf );
4538 if( defaultselhash )
4539 jsonObjectFree( defaultselhash );
4543 OSRF_BUFFER_ADD( order_buf, transform_str );
4544 free( transform_str );
4547 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4549 const char* direction =
4550 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4552 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4553 OSRF_BUFFER_ADD( order_buf, " DESC" );
4555 OSRF_BUFFER_ADD( order_buf, " ASC" );
4558 } else if( JSON_HASH == order_hash->type ) {
4559 // This hash is keyed on class alias. Each class has either
4560 // an array of field names or a hash keyed on field name.
4561 jsonIterator* class_itr = jsonNewIterator( order_hash );
4562 while( (snode = jsonIteratorNext( class_itr )) ) {
4564 ClassInfo* order_class_info = search_alias( class_itr->key );
4565 if( ! order_class_info ) {
4566 osrfLogError( OSRF_LOG_MARK,
4567 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4568 modulename, class_itr->key );
4570 osrfAppSessionStatus(
4572 OSRF_STATUS_INTERNALSERVERERROR,
4573 "osrfMethodException",
4575 "Invalid class referenced in ORDER BY clause -- "
4576 "see error log for more details"
4578 jsonIteratorFree( class_itr );
4579 buffer_free( order_buf );
4581 buffer_free( group_buf );
4582 buffer_free( sql_buf );
4583 if( defaultselhash )
4584 jsonObjectFree( defaultselhash );
4588 osrfHash* field_list_def = order_class_info->fields;
4590 if( snode->type == JSON_HASH ) {
4592 // Hash is keyed on field names from the current class. For each field
4593 // there is another layer of hash to define the sorting details, if any,
4594 // or a string to indicate direction of sorting.
4595 jsonIterator* order_itr = jsonNewIterator( snode );
4596 while( (onode = jsonIteratorNext( order_itr )) ) {
4598 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4600 osrfLogError( OSRF_LOG_MARK,
4601 "%s: Invalid field \"%s\" in ORDER BY clause",
4602 modulename, order_itr->key );
4604 osrfAppSessionStatus(
4606 OSRF_STATUS_INTERNALSERVERERROR,
4607 "osrfMethodException",
4609 "Invalid field in ORDER BY clause -- "
4610 "see error log for more details"
4612 jsonIteratorFree( order_itr );
4613 jsonIteratorFree( class_itr );
4614 buffer_free( order_buf );
4616 buffer_free( group_buf );
4617 buffer_free( sql_buf );
4618 if( defaultselhash )
4619 jsonObjectFree( defaultselhash );
4621 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4622 osrfLogError( OSRF_LOG_MARK,
4623 "%s: Virtual field \"%s\" in ORDER BY clause",
4624 modulename, order_itr->key );
4626 osrfAppSessionStatus(
4628 OSRF_STATUS_INTERNALSERVERERROR,
4629 "osrfMethodException",
4631 "Virtual field in ORDER BY clause -- "
4632 "see error log for more details"
4634 jsonIteratorFree( order_itr );
4635 jsonIteratorFree( class_itr );
4636 buffer_free( order_buf );
4638 buffer_free( group_buf );
4639 buffer_free( sql_buf );
4640 if( defaultselhash )
4641 jsonObjectFree( defaultselhash );
4645 const char* direction = NULL;
4646 if( onode->type == JSON_HASH ) {
4647 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4648 string = searchFieldTransform(
4650 osrfHashGet( field_list_def, order_itr->key ),
4654 if( ctx ) osrfAppSessionStatus(
4656 OSRF_STATUS_INTERNALSERVERERROR,
4657 "osrfMethodException",
4659 "Severe query error in ORDER BY clause -- "
4660 "see error log for more details"
4662 jsonIteratorFree( order_itr );
4663 jsonIteratorFree( class_itr );
4665 buffer_free( group_buf );
4666 buffer_free( order_buf);
4667 buffer_free( sql_buf );
4668 if( defaultselhash )
4669 jsonObjectFree( defaultselhash );
4673 growing_buffer* field_buf = buffer_init( 16 );
4674 buffer_fadd( field_buf, "\"%s\".%s",
4675 class_itr->key, order_itr->key );
4676 string = buffer_release( field_buf );
4679 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4680 const char* dir = jsonObjectGetString( tmp_const );
4681 if(!strncasecmp( dir, "d", 1 )) {
4682 direction = " DESC";
4688 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4689 osrfLogError( OSRF_LOG_MARK,
4690 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4691 modulename, json_type( onode->type ) );
4693 osrfAppSessionStatus(
4695 OSRF_STATUS_INTERNALSERVERERROR,
4696 "osrfMethodException",
4698 "Malformed ORDER BY clause -- see error log for more details"
4700 jsonIteratorFree( order_itr );
4701 jsonIteratorFree( class_itr );
4703 buffer_free( group_buf );
4704 buffer_free( order_buf );
4705 buffer_free( sql_buf );
4706 if( defaultselhash )
4707 jsonObjectFree( defaultselhash );
4711 string = strdup( order_itr->key );
4712 const char* dir = jsonObjectGetString( onode );
4713 if( !strncasecmp( dir, "d", 1 )) {
4714 direction = " DESC";
4721 OSRF_BUFFER_ADD( order_buf, ", " );
4723 order_buf = buffer_init( 128 );
4725 OSRF_BUFFER_ADD( order_buf, string );
4729 OSRF_BUFFER_ADD( order_buf, direction );
4733 jsonIteratorFree( order_itr );
4735 } else if( snode->type == JSON_ARRAY ) {
4737 // Array is a list of fields from the current class
4738 unsigned long order_idx = 0;
4739 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4741 const char* _f = jsonObjectGetString( onode );
4743 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4745 osrfLogError( OSRF_LOG_MARK,
4746 "%s: Invalid field \"%s\" in ORDER BY clause",
4749 osrfAppSessionStatus(
4751 OSRF_STATUS_INTERNALSERVERERROR,
4752 "osrfMethodException",
4754 "Invalid field in ORDER BY clause -- "
4755 "see error log for more details"
4757 jsonIteratorFree( class_itr );
4758 buffer_free( order_buf );
4760 buffer_free( group_buf );
4761 buffer_free( sql_buf );
4762 if( defaultselhash )
4763 jsonObjectFree( defaultselhash );
4765 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4766 osrfLogError( OSRF_LOG_MARK,
4767 "%s: Virtual field \"%s\" in ORDER BY clause",
4770 osrfAppSessionStatus(
4772 OSRF_STATUS_INTERNALSERVERERROR,
4773 "osrfMethodException",
4775 "Virtual field in ORDER BY clause -- "
4776 "see error log for more details"
4778 jsonIteratorFree( class_itr );
4779 buffer_free( order_buf );
4781 buffer_free( group_buf );
4782 buffer_free( sql_buf );
4783 if( defaultselhash )
4784 jsonObjectFree( defaultselhash );
4789 OSRF_BUFFER_ADD( order_buf, ", " );
4791 order_buf = buffer_init( 128 );
4793 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4797 // IT'S THE OOOOOOOOOOOLD STYLE!
4799 osrfLogError( OSRF_LOG_MARK,
4800 "%s: Possible SQL injection attempt; direct order by is not allowed",
4803 osrfAppSessionStatus(
4805 OSRF_STATUS_INTERNALSERVERERROR,
4806 "osrfMethodException",
4808 "Severe query error -- see error log for more details"
4813 buffer_free( group_buf );
4814 buffer_free( order_buf );
4815 buffer_free( sql_buf );
4816 if( defaultselhash )
4817 jsonObjectFree( defaultselhash );
4818 jsonIteratorFree( class_itr );
4822 jsonIteratorFree( class_itr );
4824 osrfLogError( OSRF_LOG_MARK,
4825 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4826 modulename, json_type( order_hash->type ) );
4828 osrfAppSessionStatus(
4830 OSRF_STATUS_INTERNALSERVERERROR,
4831 "osrfMethodException",
4833 "Malformed ORDER BY clause -- see error log for more details"
4835 buffer_free( order_buf );
4837 buffer_free( group_buf );
4838 buffer_free( sql_buf );
4839 if( defaultselhash )
4840 jsonObjectFree( defaultselhash );
4845 order_by_list = buffer_release( order_buf );
4849 string = buffer_release( group_buf );
4851 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4852 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4853 OSRF_BUFFER_ADD( sql_buf, string );
4858 if( having_buf && *having_buf ) {
4859 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4860 OSRF_BUFFER_ADD( sql_buf, having_buf );
4864 if( order_by_list ) {
4866 if( *order_by_list ) {
4867 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4868 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4871 free( order_by_list );
4875 const char* str = jsonObjectGetString( limit );
4876 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4880 const char* str = jsonObjectGetString( offset );
4881 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4884 if( !(flags & SUBSELECT) )
4885 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4887 if( defaultselhash )
4888 jsonObjectFree( defaultselhash );
4890 return buffer_release( sql_buf );
4892 } // end of SELECT()
4894 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4896 const char* locale = osrf_message_get_last_locale();
4898 osrfHash* fields = osrfHashGet( meta, "fields" );
4899 char* core_class = osrfHashGet( meta, "classname" );
4901 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4903 jsonObject* node = NULL;
4904 jsonObject* snode = NULL;
4905 jsonObject* onode = NULL;
4906 const jsonObject* _tmp = NULL;
4907 jsonObject* selhash = NULL;
4908 jsonObject* defaultselhash = NULL;
4910 growing_buffer* sql_buf = buffer_init( 128 );
4911 growing_buffer* select_buf = buffer_init( 128 );
4913 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4914 defaultselhash = jsonNewObjectType( JSON_HASH );
4915 selhash = defaultselhash;
4918 // If there's no SELECT list for the core class, build one
4919 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4920 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4922 // Add every non-virtual field to the field list
4923 osrfHash* field_def = NULL;
4924 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4925 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4926 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4927 const char* field = osrfHashIteratorKey( field_itr );
4928 jsonObjectPush( field_list, jsonNewObject( field ) );
4931 osrfHashIteratorFree( field_itr );
4932 jsonObjectSetKey( selhash, core_class, field_list );
4936 jsonIterator* class_itr = jsonNewIterator( selhash );
4937 while( (snode = jsonIteratorNext( class_itr )) ) {
4939 const char* cname = class_itr->key;
4940 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4944 if( strcmp(core_class,class_itr->key )) {
4948 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4949 if( !found->size ) {
4950 jsonObjectFree( found );
4954 jsonObjectFree( found );
4957 jsonIterator* select_itr = jsonNewIterator( snode );
4958 while( (node = jsonIteratorNext( select_itr )) ) {
4959 const char* item_str = jsonObjectGetString( node );
4960 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4961 char* fname = osrfHashGet( field, "name" );
4969 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4974 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( order_hash, "no_i18n" );
4975 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4978 i18n = osrfHashGet( field, "i18n" );
4980 if( str_is_true( i18n ) ) {
4981 char* pkey = osrfHashGet( idlClass, "primarykey" );
4982 char* tname = osrfHashGet( idlClass, "tablename" );
4984 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4985 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4986 tname, cname, fname, pkey, cname, pkey, locale, fname );
4988 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4991 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4995 jsonIteratorFree( select_itr );
4998 jsonIteratorFree( class_itr );
5000 char* col_list = buffer_release( select_buf );
5001 char* table = oilsGetRelation( meta );
5003 table = strdup( "(null)" );
5005 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5009 // Clear the query stack (as a fail-safe precaution against possible
5010 // leftover garbage); then push the first query frame onto the stack.
5011 clear_query_stack();
5013 if( add_query_core( NULL, core_class ) ) {
5015 osrfAppSessionStatus(
5017 OSRF_STATUS_INTERNALSERVERERROR,
5018 "osrfMethodException",
5020 "Unable to build query frame for core class"
5026 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5027 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5028 OSRF_BUFFER_ADD( sql_buf, join_clause );
5029 free( join_clause );
5032 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5033 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5035 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5037 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5039 osrfAppSessionStatus(
5041 OSRF_STATUS_INTERNALSERVERERROR,
5042 "osrfMethodException",
5044 "Severe query error -- see error log for more details"
5046 buffer_free( sql_buf );
5047 if( defaultselhash )
5048 jsonObjectFree( defaultselhash );
5049 clear_query_stack();
5052 buffer_add( sql_buf, pred );
5057 char* string = NULL;
5058 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5060 growing_buffer* order_buf = buffer_init( 128 );
5063 jsonIterator* class_itr = jsonNewIterator( _tmp );
5064 while( (snode = jsonIteratorNext( class_itr )) ) {
5066 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
5069 if( snode->type == JSON_HASH ) {
5071 jsonIterator* order_itr = jsonNewIterator( snode );
5072 while( (onode = jsonIteratorNext( order_itr )) ) {
5074 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5075 class_itr->key, order_itr->key );
5079 char* direction = NULL;
5080 if( onode->type == JSON_HASH ) {
5081 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5082 string = searchFieldTransform( class_itr->key, field_def, onode );
5084 osrfAppSessionStatus(
5086 OSRF_STATUS_INTERNALSERVERERROR,
5087 "osrfMethodException",
5089 "Severe query error in ORDER BY clause -- "
5090 "see error log for more details"
5092 jsonIteratorFree( order_itr );
5093 jsonIteratorFree( class_itr );
5094 buffer_free( order_buf );
5095 buffer_free( sql_buf );
5096 if( defaultselhash )
5097 jsonObjectFree( defaultselhash );
5098 clear_query_stack();
5102 growing_buffer* field_buf = buffer_init( 16 );
5103 buffer_fadd( field_buf, "\"%s\".%s",
5104 class_itr->key, order_itr->key );
5105 string = buffer_release( field_buf );
5108 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5109 const char* dir = jsonObjectGetString( _tmp );
5110 if(!strncasecmp( dir, "d", 1 )) {
5111 direction = " DESC";
5115 string = strdup( order_itr->key );
5116 const char* dir = jsonObjectGetString( onode );
5117 if( !strncasecmp( dir, "d", 1 )) {
5118 direction = " DESC";
5127 buffer_add( order_buf, ", " );
5130 buffer_add( order_buf, string );
5134 buffer_add( order_buf, direction );
5138 jsonIteratorFree( order_itr );
5141 const char* str = jsonObjectGetString( snode );
5142 buffer_add( order_buf, str );
5148 jsonIteratorFree( class_itr );
5150 string = buffer_release( order_buf );
5153 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5154 OSRF_BUFFER_ADD( sql_buf, string );
5160 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5161 const char* str = jsonObjectGetString( _tmp );
5169 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5171 const char* str = jsonObjectGetString( _tmp );
5180 if( defaultselhash )
5181 jsonObjectFree( defaultselhash );
5182 clear_query_stack();
5184 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5185 return buffer_release( sql_buf );
5188 int doJSONSearch ( osrfMethodContext* ctx ) {
5189 if(osrfMethodVerifyContext( ctx )) {
5190 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5194 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5198 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5202 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5203 flags |= SELECT_DISTINCT;
5205 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5206 flags |= DISABLE_I18N;
5208 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5209 clear_query_stack(); // a possibly needless precaution
5210 char* sql = buildQuery( ctx, hash, flags );
5211 clear_query_stack();
5218 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5221 dbhandle = writehandle;
5223 dbi_result result = dbi_conn_query( dbhandle, sql );
5226 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5228 if( dbi_result_first_row( result )) {
5229 /* JSONify the result */
5230 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5233 jsonObject* return_val = oilsMakeJSONFromResult( result );
5234 osrfAppRespond( ctx, return_val );
5235 jsonObjectFree( return_val );
5236 } while( dbi_result_next_row( result ));
5239 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5242 osrfAppRespondComplete( ctx, NULL );
5244 /* clean up the query */
5245 dbi_result_free( result );
5250 int errnum = dbi_conn_error( dbhandle, &msg );
5251 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5252 modulename, sql, errnum, msg ? msg : "(No description available)" );
5253 osrfAppSessionStatus(
5255 OSRF_STATUS_INTERNALSERVERERROR,
5256 "osrfMethodException",
5258 "Severe query error -- see error log for more details"
5260 if( !oilsIsDBConnected( dbhandle ))
5261 osrfAppSessionPanic( ctx->session );
5268 // The last parameter, err, is used to report an error condition by updating an int owned by
5269 // the calling code.
5271 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5272 // It is the responsibility of the calling code to initialize *err before the
5273 // call, so that it will be able to make sense of the result.
5275 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5276 // redundant anyway.
5277 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5278 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5281 dbhandle = writehandle;
5283 char* core_class = osrfHashGet( class_meta, "classname" );
5284 char* pkey = osrfHashGet( class_meta, "primarykey" );
5286 const jsonObject* _tmp;
5288 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5290 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5295 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5297 dbi_result result = dbi_conn_query( dbhandle, sql );
5298 if( NULL == result ) {
5300 int errnum = dbi_conn_error( dbhandle, &msg );
5301 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5302 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5303 msg ? msg : "(No description available)" );
5304 if( !oilsIsDBConnected( dbhandle ))
5305 osrfAppSessionPanic( ctx->session );
5306 osrfAppSessionStatus(
5308 OSRF_STATUS_INTERNALSERVERERROR,
5309 "osrfMethodException",
5311 "Severe query error -- see error log for more details"
5318 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5321 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5322 jsonObject* row_obj = NULL;
5324 if( dbi_result_first_row( result )) {
5326 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5327 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5328 // eliminate the duplicates.
5329 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5330 osrfHash* dedup = osrfNewHash();
5332 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5333 char* pkey_val = oilsFMGetString( row_obj, pkey );
5334 if( osrfHashGet( dedup, pkey_val ) ) {
5335 jsonObjectFree( row_obj );
5338 osrfHashSet( dedup, pkey_val, pkey_val );
5339 jsonObjectPush( res_list, row_obj );
5341 } while( dbi_result_next_row( result ));
5342 osrfHashFree( dedup );
5345 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5349 /* clean up the query */
5350 dbi_result_free( result );
5353 // If we're asked to flesh, and there's anything to flesh, then flesh it
5354 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5355 // something that he has no permission to look at).
5356 if( res_list->size && query_hash && ! enforce_pcrud ) {
5357 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5359 // Get the flesh depth
5360 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5361 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5362 flesh_depth = max_flesh_depth;
5364 // We need a non-zero flesh depth, and a list of fields to flesh
5365 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5366 if( temp_blob && flesh_depth > 0 ) {
5368 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5369 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5371 osrfStringArray* link_fields = NULL;
5372 osrfHash* links = osrfHashGet( class_meta, "links" );
5374 // Make an osrfStringArray of the names of fields to be fleshed
5375 if( flesh_fields ) {
5376 if( flesh_fields->size == 1 ) {
5377 const char* _t = jsonObjectGetString(
5378 jsonObjectGetIndex( flesh_fields, 0 ) );
5379 if( !strcmp( _t, "*" ))
5380 link_fields = osrfHashKeys( links );
5383 if( !link_fields ) {
5385 link_fields = osrfNewStringArray( 1 );
5386 jsonIterator* _i = jsonNewIterator( flesh_fields );
5387 while ((_f = jsonIteratorNext( _i ))) {
5388 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5390 jsonIteratorFree( _i );
5394 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5396 // Iterate over the JSON_ARRAY of rows
5398 unsigned long res_idx = 0;
5399 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5402 const char* link_field;
5404 // Iterate over the list of fleshable fields
5405 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5407 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5409 osrfHash* kid_link = osrfHashGet( links, link_field );
5411 continue; // Not a link field; skip it
5413 osrfHash* field = osrfHashGet( fields, link_field );
5415 continue; // Not a field at all; skip it (IDL is ill-formed)
5417 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5418 osrfHashGet( kid_link, "class" ));
5420 continue; // The class it links to doesn't exist; skip it
5422 const char* reltype = osrfHashGet( kid_link, "reltype" );
5424 continue; // No reltype; skip it (IDL is ill-formed)
5426 osrfHash* value_field = field;
5428 if( !strcmp( reltype, "has_many" )
5429 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5430 value_field = osrfHashGet(
5431 fields, osrfHashGet( class_meta, "primarykey" ) );
5434 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5436 if( link_map->size > 0 ) {
5437 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5440 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5445 osrfHashGet( kid_link, "class" ),
5452 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5453 osrfHashGet( kid_link, "field" ),
5454 osrfHashGet( kid_link, "class" ),
5455 osrfHashGet( kid_link, "key" ),
5456 osrfHashGet( kid_link, "reltype" )
5459 const char* search_key = jsonObjectGetString(
5460 jsonObjectGetIndex( cur,
5461 atoi( osrfHashGet( value_field, "array_position" ) )
5466 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5470 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5472 // construct WHERE clause
5473 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5476 osrfHashGet( kid_link, "key" ),
5477 jsonNewObject( search_key )
5480 // construct the rest of the query, mostly
5481 // by copying pieces of the previous level of query
5482 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5483 jsonObjectSetKey( rest_of_query, "flesh",
5484 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5488 jsonObjectSetKey( rest_of_query, "flesh_fields",
5489 jsonObjectClone( flesh_blob ));
5491 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5492 jsonObjectSetKey( rest_of_query, "order_by",
5493 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5497 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5498 jsonObjectSetKey( rest_of_query, "select",
5499 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5503 // do the query, recursively, to expand the fleshable field
5504 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5505 where_clause, rest_of_query, err );
5507 jsonObjectFree( where_clause );
5508 jsonObjectFree( rest_of_query );
5511 osrfStringArrayFree( link_fields );
5512 jsonObjectFree( res_list );
5513 jsonObjectFree( flesh_blob );
5517 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5518 osrfHashGet( kid_link, "class" ), kids->size );
5520 // Traverse the result set
5521 jsonObject* X = NULL;
5522 if( link_map->size > 0 && kids->size > 0 ) {
5524 kids = jsonNewObjectType( JSON_ARRAY );
5526 jsonObject* _k_node;
5527 unsigned long res_idx = 0;
5528 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5534 (unsigned long) atoi(
5540 osrfHashGet( kid_link, "class" )
5544 osrfStringArrayGetString( link_map, 0 )
5552 } // end while loop traversing X
5555 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5556 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5557 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5558 osrfHashGet( kid_link, "field" ));
5561 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5562 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5566 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5568 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5569 osrfHashGet( kid_link, "field" ) );
5572 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5573 jsonObjectClone( kids )
5578 jsonObjectFree( kids );
5582 jsonObjectFree( kids );
5584 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5585 osrfHashGet( kid_link, "field" ) );
5586 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5588 } // end while loop traversing list of fleshable fields
5589 } // end while loop traversing res_list
5590 jsonObjectFree( flesh_blob );
5591 osrfStringArrayFree( link_fields );
5600 int doUpdate( osrfMethodContext* ctx ) {
5601 if( osrfMethodVerifyContext( ctx )) {
5602 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5607 timeout_needs_resetting = 1;
5609 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5611 jsonObject* target = NULL;
5613 target = jsonObjectGetIndex( ctx->params, 1 );
5615 target = jsonObjectGetIndex( ctx->params, 0 );
5617 if(!verifyObjectClass( ctx, target )) {
5618 osrfAppRespondComplete( ctx, NULL );
5622 if( getXactId( ctx ) == NULL ) {
5623 osrfAppSessionStatus(
5625 OSRF_STATUS_BADREQUEST,
5626 "osrfMethodException",
5628 "No active transaction -- required for UPDATE"
5630 osrfAppRespondComplete( ctx, NULL );
5634 // The following test is harmless but redundant. If a class is
5635 // readonly, we don't register an update method for it.
5636 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5637 osrfAppSessionStatus(
5639 OSRF_STATUS_BADREQUEST,
5640 "osrfMethodException",
5642 "Cannot UPDATE readonly class"
5644 osrfAppRespondComplete( ctx, NULL );
5648 const char* trans_id = getXactId( ctx );
5650 // Set the last_xact_id
5651 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5653 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5654 trans_id, target->classname, index );
5655 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5658 char* pkey = osrfHashGet( meta, "primarykey" );
5659 osrfHash* fields = osrfHashGet( meta, "fields" );
5661 char* id = oilsFMGetString( target, pkey );
5665 "%s updating %s object with %s = %s",
5667 osrfHashGet( meta, "fieldmapper" ),
5672 dbhandle = writehandle;
5673 growing_buffer* sql = buffer_init( 128 );
5674 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5677 osrfHash* field_def = NULL;
5678 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5679 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5681 // Skip virtual fields, and the primary key
5682 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5685 const char* field_name = osrfHashIteratorKey( field_itr );
5686 if( ! strcmp( field_name, pkey ) )
5689 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5691 int value_is_numeric = 0; // boolean
5693 if( field_object && field_object->classname ) {
5694 value = oilsFMGetString(
5696 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5698 } else if( field_object && JSON_BOOL == field_object->type ) {
5699 if( jsonBoolIsTrue( field_object ) )
5700 value = strdup( "t" );
5702 value = strdup( "f" );
5704 value = jsonObjectToSimpleString( field_object );
5705 if( field_object && JSON_NUMBER == field_object->type )
5706 value_is_numeric = 1;
5709 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5710 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5712 if( !field_object || field_object->type == JSON_NULL ) {
5713 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5714 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5718 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5719 buffer_fadd( sql, " %s = NULL", field_name );
5722 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5726 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5728 const char* numtype = get_datatype( field_def );
5729 if( !strncmp( numtype, "INT", 3 ) ) {
5730 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5731 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5732 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5734 // Must really be intended as a string, so quote it
5735 if( dbi_conn_quote_string( dbhandle, &value )) {
5736 buffer_fadd( sql, " %s = %s", field_name, value );
5738 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5739 modulename, value );
5740 osrfAppSessionStatus(
5742 OSRF_STATUS_INTERNALSERVERERROR,
5743 "osrfMethodException",
5745 "Error quoting string -- please see the error log for more details"
5749 osrfHashIteratorFree( field_itr );
5751 osrfAppRespondComplete( ctx, NULL );
5756 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5759 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5763 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5764 buffer_fadd( sql, " %s = %s", field_name, value );
5766 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5767 osrfAppSessionStatus(
5769 OSRF_STATUS_INTERNALSERVERERROR,
5770 "osrfMethodException",
5772 "Error quoting string -- please see the error log for more details"
5776 osrfHashIteratorFree( field_itr );
5778 osrfAppRespondComplete( ctx, NULL );
5787 osrfHashIteratorFree( field_itr );
5789 jsonObject* obj = jsonNewObject( id );
5791 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5792 dbi_conn_quote_string( dbhandle, &id );
5794 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5796 char* query = buffer_release( sql );
5797 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5799 dbi_result result = dbi_conn_query( dbhandle, query );
5804 jsonObjectFree( obj );
5805 obj = jsonNewObject( NULL );
5807 int errnum = dbi_conn_error( dbhandle, &msg );
5810 "%s ERROR updating %s object with %s = %s: %d %s",
5812 osrfHashGet( meta, "fieldmapper" ),
5816 msg ? msg : "(No description available)"
5818 osrfAppSessionStatus(
5820 OSRF_STATUS_INTERNALSERVERERROR,
5821 "osrfMethodException",
5823 "Error in updating a row -- please see the error log for more details"
5825 if( !oilsIsDBConnected( dbhandle ))
5826 osrfAppSessionPanic( ctx->session );
5829 dbi_result_free( result );
5832 osrfAppRespondComplete( ctx, obj );
5833 jsonObjectFree( obj );
5837 int doDelete( osrfMethodContext* ctx ) {
5838 if( osrfMethodVerifyContext( ctx )) {
5839 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5844 timeout_needs_resetting = 1;
5846 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5848 if( getXactId( ctx ) == NULL ) {
5849 osrfAppSessionStatus(
5851 OSRF_STATUS_BADREQUEST,
5852 "osrfMethodException",
5854 "No active transaction -- required for DELETE"
5856 osrfAppRespondComplete( ctx, NULL );
5860 // The following test is harmless but redundant. If a class is
5861 // readonly, we don't register a delete method for it.
5862 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5863 osrfAppSessionStatus(
5865 OSRF_STATUS_BADREQUEST,
5866 "osrfMethodException",
5868 "Cannot DELETE readonly class"
5870 osrfAppRespondComplete( ctx, NULL );
5874 dbhandle = writehandle;
5876 char* pkey = osrfHashGet( meta, "primarykey" );
5883 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5884 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5885 osrfAppRespondComplete( ctx, NULL );
5889 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5891 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5892 osrfAppRespondComplete( ctx, NULL );
5895 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5900 "%s deleting %s object with %s = %s",
5902 osrfHashGet( meta, "fieldmapper" ),
5907 jsonObject* obj = jsonNewObject( id );
5909 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5910 dbi_conn_quote_string( writehandle, &id );
5912 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5913 osrfHashGet( meta, "tablename" ), pkey, id );
5918 jsonObjectFree( obj );
5919 obj = jsonNewObject( NULL );
5921 int errnum = dbi_conn_error( writehandle, &msg );
5924 "%s ERROR deleting %s object with %s = %s: %d %s",
5926 osrfHashGet( meta, "fieldmapper" ),
5930 msg ? msg : "(No description available)"
5932 osrfAppSessionStatus(
5934 OSRF_STATUS_INTERNALSERVERERROR,
5935 "osrfMethodException",
5937 "Error in deleting a row -- please see the error log for more details"
5939 if( !oilsIsDBConnected( writehandle ))
5940 osrfAppSessionPanic( ctx->session );
5942 dbi_result_free( result );
5946 osrfAppRespondComplete( ctx, obj );
5947 jsonObjectFree( obj );
5952 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5953 @param result An iterator for a result set; we only look at the current row.
5954 @param @meta Pointer to the class metadata for the core class.
5955 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5957 If a column is not defined in the IDL, or if it has no array_position defined for it in
5958 the IDL, or if it is defined as virtual, ignore it.
5960 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5961 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5962 array_position in the IDL.
5964 A field defined in the IDL but not represented in the returned row will leave a hole
5965 in the JSON_ARRAY. In effect it will be treated as a null value.
5967 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5968 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5969 classname corresponding to the @a meta argument.
5971 The calling code is responsible for freeing the the resulting jsonObject by calling
5974 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5975 if( !( result && meta )) return NULL;
5977 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5978 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5979 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5981 osrfHash* fields = osrfHashGet( meta, "fields" );
5983 int columnIndex = 1;
5984 const char* columnName;
5986 /* cycle through the columns in the row returned from the database */
5987 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5989 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5991 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5993 /* determine the field type and storage attributes */
5994 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5995 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5997 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5998 // or if it has no sequence number there, or if it's virtual, skip it.
5999 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6002 if( str_is_true( osrfHashGet( _f, "virtual" )))
6003 continue; // skip this column: IDL says it's virtual
6005 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6006 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6007 continue; // since we assign sequence numbers dynamically as we load the IDL.
6009 fmIndex = atoi( pos );
6010 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6012 continue; // This field is not defined in the IDL
6015 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6016 // sequence number from the IDL (which is likely to be different from the sequence
6017 // of columns in the SELECT clause).
6018 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6019 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6024 case DBI_TYPE_INTEGER :
6026 if( attr & DBI_INTEGER_SIZE8 )
6027 jsonObjectSetIndex( object, fmIndex,
6028 jsonNewNumberObject(
6029 dbi_result_get_longlong_idx( result, columnIndex )));
6031 jsonObjectSetIndex( object, fmIndex,
6032 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6036 case DBI_TYPE_DECIMAL :
6037 jsonObjectSetIndex( object, fmIndex,
6038 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6041 case DBI_TYPE_STRING :
6046 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6051 case DBI_TYPE_DATETIME : {
6053 char dt_string[ 256 ] = "";
6056 // Fetch the date column as a time_t
6057 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6059 // Translate the time_t to a human-readable string
6060 if( !( attr & DBI_DATETIME_DATE )) {
6061 gmtime_r( &_tmp_dt, &gmdt );
6062 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6063 } else if( !( attr & DBI_DATETIME_TIME )) {
6064 localtime_r( &_tmp_dt, &gmdt );
6065 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6067 localtime_r( &_tmp_dt, &gmdt );
6068 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6071 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6075 case DBI_TYPE_BINARY :
6076 osrfLogError( OSRF_LOG_MARK,
6077 "Can't do binary at column %s : index %d", columnName, columnIndex );
6086 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6087 if( !result ) return NULL;
6089 jsonObject* object = jsonNewObject( NULL );
6092 char dt_string[ 256 ];
6096 int columnIndex = 1;
6098 unsigned short type;
6099 const char* columnName;
6101 /* cycle through the column list */
6102 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6104 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6106 fmIndex = -1; // reset the position
6108 /* determine the field type and storage attributes */
6109 type = dbi_result_get_field_type_idx( result, columnIndex );
6110 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6112 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6113 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6118 case DBI_TYPE_INTEGER :
6120 if( attr & DBI_INTEGER_SIZE8 )
6121 jsonObjectSetKey( object, columnName,
6122 jsonNewNumberObject( dbi_result_get_longlong_idx(
6123 result, columnIndex )) );
6125 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6126 dbi_result_get_int_idx( result, columnIndex )) );
6129 case DBI_TYPE_DECIMAL :
6130 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6131 dbi_result_get_double_idx( result, columnIndex )) );
6134 case DBI_TYPE_STRING :
6135 jsonObjectSetKey( object, columnName,
6136 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6139 case DBI_TYPE_DATETIME :
6141 memset( dt_string, '\0', sizeof( dt_string ));
6142 memset( &gmdt, '\0', sizeof( gmdt ));
6144 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6146 if( !( attr & DBI_DATETIME_DATE )) {
6147 gmtime_r( &_tmp_dt, &gmdt );
6148 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6149 } else if( !( attr & DBI_DATETIME_TIME )) {
6150 localtime_r( &_tmp_dt, &gmdt );
6151 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6153 localtime_r( &_tmp_dt, &gmdt );
6154 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6157 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6160 case DBI_TYPE_BINARY :
6161 osrfLogError( OSRF_LOG_MARK,
6162 "Can't do binary at column %s : index %d", columnName, columnIndex );
6166 } // end while loop traversing result
6171 // Interpret a string as true or false
6172 int str_is_true( const char* str ) {
6173 if( NULL == str || strcasecmp( str, "true" ) )
6179 // Interpret a jsonObject as true or false
6180 static int obj_is_true( const jsonObject* obj ) {
6183 else switch( obj->type )
6191 if( strcasecmp( obj->value.s, "true" ) )
6195 case JSON_NUMBER : // Support 1/0 for perl's sake
6196 if( jsonObjectGetNumber( obj ) == 1.0 )
6205 // Translate a numeric code into a text string identifying a type of
6206 // jsonObject. To be used for building error messages.
6207 static const char* json_type( int code ) {
6213 return "JSON_ARRAY";
6215 return "JSON_STRING";
6217 return "JSON_NUMBER";
6223 return "(unrecognized)";
6227 // Extract the "primitive" attribute from an IDL field definition.
6228 // If we haven't initialized the app, then we must be running in
6229 // some kind of testbed. In that case, default to "string".
6230 static const char* get_primitive( osrfHash* field ) {
6231 const char* s = osrfHashGet( field, "primitive" );
6233 if( child_initialized )
6236 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6238 osrfHashGet( field, "name" )
6246 // Extract the "datatype" attribute from an IDL field definition.
6247 // If we haven't initialized the app, then we must be running in
6248 // some kind of testbed. In that case, default to to NUMERIC,
6249 // since we look at the datatype only for numbers.
6250 static const char* get_datatype( osrfHash* field ) {
6251 const char* s = osrfHashGet( field, "datatype" );
6253 if( child_initialized )
6256 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6258 osrfHashGet( field, "name" )
6267 @brief Determine whether a string is potentially a valid SQL identifier.
6268 @param s The identifier to be tested.
6269 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6271 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6272 need to follow all the rules exactly, such as requiring that the first character not
6275 We allow leading and trailing white space. In between, we do not allow punctuation
6276 (except for underscores and dollar signs), control characters, or embedded white space.
6278 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6279 for the foreseeable future such quoted identifiers are not likely to be an issue.
6281 int is_identifier( const char* s) {
6285 // Skip leading white space
6286 while( isspace( (unsigned char) *s ) )
6290 return 0; // Nothing but white space? Not okay.
6292 // Check each character until we reach white space or
6293 // end-of-string. Letters, digits, underscores, and
6294 // dollar signs are okay. With the exception of periods
6295 // (as in schema.identifier), control characters and other
6296 // punctuation characters are not okay. Anything else
6297 // is okay -- it could for example be part of a multibyte
6298 // UTF8 character such as a letter with diacritical marks,
6299 // and those are allowed.
6301 if( isalnum( (unsigned char) *s )
6305 ; // Fine; keep going
6306 else if( ispunct( (unsigned char) *s )
6307 || iscntrl( (unsigned char) *s ) )
6310 } while( *s && ! isspace( (unsigned char) *s ) );
6312 // If we found any white space in the above loop,
6313 // the rest had better be all white space.
6315 while( isspace( (unsigned char) *s ) )
6319 return 0; // White space was embedded within non-white space
6325 @brief Determine whether to accept a character string as a comparison operator.
6326 @param op The candidate comparison operator.
6327 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6329 We don't validate the operator for real. We just make sure that it doesn't contain
6330 any semicolons or white space (with special exceptions for a few specific operators).
6331 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6332 space but it's still not a valid operator, then the database will complain.
6334 Another approach would be to compare the string against a short list of approved operators.
6335 We don't do that because we want to allow custom operators like ">100*", which at this
6336 writing would be difficult or impossible to express otherwise in a JSON query.
6338 int is_good_operator( const char* op ) {
6339 if( !op ) return 0; // Sanity check
6343 if( isspace( (unsigned char) *s ) ) {
6344 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6345 // and IS NOT DISTINCT FROM.
6346 if( !strcasecmp( op, "similar to" ) )
6348 else if( !strcasecmp( op, "is distinct from" ) )
6350 else if( !strcasecmp( op, "is not distinct from" ) )
6355 else if( ';' == *s )
6363 @name Query Frame Management
6365 The following machinery supports a stack of query frames for use by SELECT().
6367 A query frame caches information about one level of a SELECT query. When we enter
6368 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6370 The query frame stores information about the core class, and about any joined classes
6373 The main purpose is to map table aliases to classes and tables, so that a query can
6374 join to the same table more than once. A secondary goal is to reduce the number of
6375 lookups in the IDL by caching the results.
6379 #define STATIC_CLASS_INFO_COUNT 3
6381 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6384 @brief Allocate a ClassInfo as raw memory.
6385 @return Pointer to the newly allocated ClassInfo.
6387 Except for the in_use flag, which is used only by the allocation and deallocation
6388 logic, we don't initialize the ClassInfo here.
6390 static ClassInfo* allocate_class_info( void ) {
6391 // In order to reduce the number of mallocs and frees, we return a static
6392 // instance of ClassInfo, if we can find one that we're not already using.
6393 // We rely on the fact that the compiler will implicitly initialize the
6394 // static instances so that in_use == 0.
6397 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6398 if( ! static_class_info[ i ].in_use ) {
6399 static_class_info[ i ].in_use = 1;
6400 return static_class_info + i;
6404 // The static ones are all in use. Malloc one.
6406 return safe_malloc( sizeof( ClassInfo ) );
6410 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6411 @param info Pointer to the ClassInfo to be cleared.
6413 static void clear_class_info( ClassInfo* info ) {
6418 // Free any malloc'd strings
6420 if( info->alias != info->alias_store )
6421 free( info->alias );
6423 if( info->class_name != info->class_name_store )
6424 free( info->class_name );
6426 free( info->source_def );
6428 info->alias = info->class_name = info->source_def = NULL;
6433 @brief Free a ClassInfo and everything it owns.
6434 @param info Pointer to the ClassInfo to be freed.
6436 static void free_class_info( ClassInfo* info ) {
6441 clear_class_info( info );
6443 // If it's one of the static instances, just mark it as not in use
6446 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6447 if( info == static_class_info + i ) {
6448 static_class_info[ i ].in_use = 0;
6453 // Otherwise it must have been malloc'd, so free it
6459 @brief Populate an already-allocated ClassInfo.
6460 @param info Pointer to the ClassInfo to be populated.
6461 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6463 @param class Name of the class.
6464 @return Zero if successful, or 1 if not.
6466 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6467 the relevant portions of the IDL for the specified class.
6469 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6472 osrfLogError( OSRF_LOG_MARK,
6473 "%s ERROR: No ClassInfo available to populate", modulename );
6474 info->alias = info->class_name = info->source_def = NULL;
6475 info->class_def = info->fields = info->links = NULL;
6480 osrfLogError( OSRF_LOG_MARK,
6481 "%s ERROR: No class name provided for lookup", modulename );
6482 info->alias = info->class_name = info->source_def = NULL;
6483 info->class_def = info->fields = info->links = NULL;
6487 // Alias defaults to class name if not supplied
6488 if( ! alias || ! alias[ 0 ] )
6491 // Look up class info in the IDL
6492 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6494 osrfLogError( OSRF_LOG_MARK,
6495 "%s ERROR: Class %s not defined in IDL", modulename, class );
6496 info->alias = info->class_name = info->source_def = NULL;
6497 info->class_def = info->fields = info->links = NULL;
6499 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6500 osrfLogError( OSRF_LOG_MARK,
6501 "%s ERROR: Class %s is defined as virtual", modulename, class );
6502 info->alias = info->class_name = info->source_def = NULL;
6503 info->class_def = info->fields = info->links = NULL;
6507 osrfHash* links = osrfHashGet( class_def, "links" );
6509 osrfLogError( OSRF_LOG_MARK,
6510 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6511 info->alias = info->class_name = info->source_def = NULL;
6512 info->class_def = info->fields = info->links = NULL;
6516 osrfHash* fields = osrfHashGet( class_def, "fields" );
6518 osrfLogError( OSRF_LOG_MARK,
6519 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6520 info->alias = info->class_name = info->source_def = NULL;
6521 info->class_def = info->fields = info->links = NULL;
6525 char* source_def = oilsGetRelation( class_def );
6529 // We got everything we need, so populate the ClassInfo
6530 if( strlen( alias ) > ALIAS_STORE_SIZE )
6531 info->alias = strdup( alias );
6533 strcpy( info->alias_store, alias );
6534 info->alias = info->alias_store;
6537 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6538 info->class_name = strdup( class );
6540 strcpy( info->class_name_store, class );
6541 info->class_name = info->class_name_store;
6544 info->source_def = source_def;
6546 info->class_def = class_def;
6547 info->links = links;
6548 info->fields = fields;
6553 #define STATIC_FRAME_COUNT 3
6555 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6558 @brief Allocate a QueryFrame as raw memory.
6559 @return Pointer to the newly allocated QueryFrame.
6561 Except for the in_use flag, which is used only by the allocation and deallocation
6562 logic, we don't initialize the QueryFrame here.
6564 static QueryFrame* allocate_frame( void ) {
6565 // In order to reduce the number of mallocs and frees, we return a static
6566 // instance of QueryFrame, if we can find one that we're not already using.
6567 // We rely on the fact that the compiler will implicitly initialize the
6568 // static instances so that in_use == 0.
6571 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6572 if( ! static_frame[ i ].in_use ) {
6573 static_frame[ i ].in_use = 1;
6574 return static_frame + i;
6578 // The static ones are all in use. Malloc one.
6580 return safe_malloc( sizeof( QueryFrame ) );
6584 @brief Free a QueryFrame, and all the memory it owns.
6585 @param frame Pointer to the QueryFrame to be freed.
6587 static void free_query_frame( QueryFrame* frame ) {
6592 clear_class_info( &frame->core );
6594 // Free the join list
6596 ClassInfo* info = frame->join_list;
6599 free_class_info( info );
6603 frame->join_list = NULL;
6606 // If the frame is a static instance, just mark it as unused
6608 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6609 if( frame == static_frame + i ) {
6610 static_frame[ i ].in_use = 0;
6615 // Otherwise it must have been malloc'd, so free it
6621 @brief Search a given QueryFrame for a specified alias.
6622 @param frame Pointer to the QueryFrame to be searched.
6623 @param target The alias for which to search.
6624 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6626 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6627 if( ! frame || ! target ) {
6631 ClassInfo* found_class = NULL;
6633 if( !strcmp( target, frame->core.alias ) )
6634 return &(frame->core);
6636 ClassInfo* curr_class = frame->join_list;
6637 while( curr_class ) {
6638 if( strcmp( target, curr_class->alias ) )
6639 curr_class = curr_class->next;
6641 found_class = curr_class;
6651 @brief Push a new (blank) QueryFrame onto the stack.
6653 static void push_query_frame( void ) {
6654 QueryFrame* frame = allocate_frame();
6655 frame->join_list = NULL;
6656 frame->next = curr_query;
6658 // Initialize the ClassInfo for the core class
6659 ClassInfo* core = &frame->core;
6660 core->alias = core->class_name = core->source_def = NULL;
6661 core->class_def = core->fields = core->links = NULL;
6667 @brief Pop a QueryFrame off the stack and destroy it.
6669 static void pop_query_frame( void ) {
6674 QueryFrame* popped = curr_query;
6675 curr_query = popped->next;
6677 free_query_frame( popped );
6681 @brief Populate the ClassInfo for the core class.
6682 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6683 class name as an alias.
6684 @param class_name Name of the core class.
6685 @return Zero if successful, or 1 if not.
6687 Populate the ClassInfo of the core class with copies of the alias and class name, and
6688 with pointers to the relevant portions of the IDL for the core class.
6690 static int add_query_core( const char* alias, const char* class_name ) {
6693 if( ! curr_query ) {
6694 osrfLogError( OSRF_LOG_MARK,
6695 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6697 } else if( curr_query->core.alias ) {
6698 osrfLogError( OSRF_LOG_MARK,
6699 "%s ERROR: Core class %s already populated as %s",
6700 modulename, curr_query->core.class_name, curr_query->core.alias );
6704 build_class_info( &curr_query->core, alias, class_name );
6705 if( curr_query->core.alias )
6708 osrfLogError( OSRF_LOG_MARK,
6709 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6715 @brief Search the current QueryFrame for a specified alias.
6716 @param target The alias for which to search.
6717 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6719 static inline ClassInfo* search_alias( const char* target ) {
6720 return search_alias_in_frame( curr_query, target );
6724 @brief Search all levels of query for a specified alias, starting with the current query.
6725 @param target The alias for which to search.
6726 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6728 static ClassInfo* search_all_alias( const char* target ) {
6729 ClassInfo* found_class = NULL;
6730 QueryFrame* curr_frame = curr_query;
6732 while( curr_frame ) {
6733 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6736 curr_frame = curr_frame->next;
6743 @brief Add a class to the list of classes joined to the current query.
6744 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6745 the class name as an alias.
6746 @param classname The name of the class to be added.
6747 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6749 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6751 if( ! classname || ! *classname ) { // sanity check
6752 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6759 const ClassInfo* conflict = search_alias( alias );
6761 osrfLogError( OSRF_LOG_MARK,
6762 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6763 modulename, alias, conflict->class_name );
6767 ClassInfo* info = allocate_class_info();
6769 if( build_class_info( info, alias, classname ) ) {
6770 free_class_info( info );
6774 // Add the new ClassInfo to the join list of the current QueryFrame
6775 info->next = curr_query->join_list;
6776 curr_query->join_list = info;
6782 @brief Destroy all nodes on the query stack.
6784 static void clear_query_stack( void ) {