3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static void pop_query_frame( void );
106 static void push_query_frame( void );
107 static int add_query_core( const char* alias, const char* class_name );
108 static inline ClassInfo* search_alias( const char* target );
109 static ClassInfo* search_all_alias( const char* target );
110 static ClassInfo* add_joined_class( const char* alias, const char* classname );
111 static void clear_query_stack( void );
113 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
114 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
115 static const char* org_tree_root( osrfMethodContext* ctx );
116 static jsonObject* single_hash( const char* key, const char* value );
118 static int child_initialized = 0; /* boolean */
120 static dbi_conn writehandle; /* our MASTER db connection */
121 static dbi_conn dbhandle; /* our CURRENT db connection */
122 //static osrfHash * readHandles;
124 // The following points to the top of a stack of QueryFrames. It's a little
125 // confusing because the top level of the query is at the bottom of the stack.
126 static QueryFrame* curr_query = NULL;
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 static int max_flesh_depth = 100;
134 static int enforce_pcrud = 0; // Boolean
135 static char* modulename = NULL;
138 @brief Connect to the database.
139 @return A database connection if successful, or NULL if not.
141 dbi_conn oilsConnectDB( const char* mod_name ) {
143 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
144 if( dbi_initialize( NULL ) == -1 ) {
145 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
148 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
150 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
151 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
152 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
153 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
154 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
155 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
158 dbi_conn handle = dbi_conn_new( driver );
161 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
164 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
166 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
167 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
169 if( host ) dbi_conn_set_option( handle, "host", host );
170 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
171 if( user ) dbi_conn_set_option( handle, "username", user );
172 if( pw ) dbi_conn_set_option( handle, "password", pw );
173 if( db ) dbi_conn_set_option( handle, "dbname", db );
181 if( dbi_conn_connect( handle ) < 0 ) {
183 if( dbi_conn_connect( handle ) < 0 ) {
185 dbi_conn_error( handle, &msg );
186 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
187 msg ? msg : "(No description available)" );
192 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
198 @brief Select some options.
199 @param module_name: Name of the server.
200 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
202 This source file is used (at this writing) to implement three different servers:
203 - open-ils.reporter-store
207 These servers behave mostly the same, but they implement different combinations of
208 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
210 Here we use the server name in messages to identify which kind of server issued them.
211 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
213 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
215 module_name = "open-ils.cstore"; // bulletproofing with a default
220 modulename = strdup( module_name );
221 enforce_pcrud = do_pcrud;
222 max_flesh_depth = flesh_depth;
226 @brief Install a database connection.
227 @param conn Pointer to a database connection.
229 In some contexts, @a conn may merely provide a driver so that we can process strings
230 properly, without providing an open database connection.
232 void oilsSetDBConnection( dbi_conn conn ) {
233 dbhandle = writehandle = conn;
237 @brief Determine whether a database connection is alive.
238 @param handle Handle for a database connection.
239 @return 1 if the connection is alive, or zero if it isn't.
241 int oilsIsDBConnected( dbi_conn handle ) {
242 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
244 dbi_result_free( result );
247 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
253 @brief Get a table name, view name, or subquery for use in a FROM clause.
254 @param class Pointer to the IDL class entry.
255 @return A table name, a view name, or a subquery in parentheses.
257 In some cases the IDL defines a class, not with a table name or a view name, but with
258 a SELECT statement, which may be used as a subquery.
260 char* oilsGetRelation( osrfHash* classdef ) {
262 char* source_def = NULL;
263 const char* tabledef = osrfHashGet( classdef, "tablename" );
266 source_def = strdup( tabledef ); // Return the name of a table or view
268 tabledef = osrfHashGet( classdef, "source_definition" );
270 // Return a subquery, enclosed in parentheses
271 source_def = safe_malloc( strlen( tabledef ) + 3 );
272 source_def[ 0 ] = '(';
273 strcpy( source_def + 1, tabledef );
274 strcat( source_def, ")" );
276 // Not found: return an error
277 const char* classname = osrfHashGet( classdef, "classname" );
282 "%s ERROR No tablename or source_definition for class \"%s\"",
293 @brief Add datatypes from the database to the fields in the IDL.
294 @param handle Handle for a database connection
295 @return Zero if successful, or 1 upon error.
297 For each relevant class in the IDL: ask the database for the datatype of every field.
298 In particular, determine which fields are text fields and which fields are numeric
299 fields, so that we know whether to enclose their values in quotes.
301 int oilsExtendIDL( dbi_conn handle ) {
302 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
303 osrfHash* class = NULL;
304 growing_buffer* query_buf = buffer_init( 64 );
305 int results_found = 0; // boolean
307 // For each class in the IDL...
308 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
309 const char* classname = osrfHashIteratorKey( class_itr );
310 osrfHash* fields = osrfHashGet( class, "fields" );
312 // If the class is virtual, ignore it
313 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
314 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
318 char* tabledef = oilsGetRelation( class );
320 continue; // No such relation -- a query of it would be doomed to failure
322 buffer_reset( query_buf );
323 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
327 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
328 modulename, OSRF_BUFFER_C_STR( query_buf ) );
330 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
335 const char* columnName;
336 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
338 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
341 /* fetch the fieldmapper index */
342 osrfHash* _f = osrfHashGet(fields, columnName);
345 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
347 /* determine the field type and storage attributes */
349 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
351 case DBI_TYPE_INTEGER : {
353 if( !osrfHashGet(_f, "primitive") )
354 osrfHashSet(_f, "number", "primitive");
356 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
357 if( attr & DBI_INTEGER_SIZE8 )
358 osrfHashSet( _f, "INT8", "datatype" );
360 osrfHashSet( _f, "INT", "datatype" );
363 case DBI_TYPE_DECIMAL :
364 if( !osrfHashGet( _f, "primitive" ))
365 osrfHashSet( _f, "number", "primitive" );
367 osrfHashSet( _f, "NUMERIC", "datatype" );
370 case DBI_TYPE_STRING :
371 if( !osrfHashGet( _f, "primitive" ))
372 osrfHashSet( _f, "string", "primitive" );
374 osrfHashSet( _f,"TEXT", "datatype" );
377 case DBI_TYPE_DATETIME :
378 if( !osrfHashGet( _f, "primitive" ))
379 osrfHashSet( _f, "string", "primitive" );
381 osrfHashSet( _f, "TIMESTAMP", "datatype" );
384 case DBI_TYPE_BINARY :
385 if( !osrfHashGet( _f, "primitive" ))
386 osrfHashSet( _f, "string", "primitive" );
388 osrfHashSet( _f, "BYTEA", "datatype" );
393 "Setting [%s] to primitive [%s] and datatype [%s]...",
395 osrfHashGet( _f, "primitive" ),
396 osrfHashGet( _f, "datatype" )
400 } // end while loop for traversing columns of result
401 dbi_result_free( result );
404 int errnum = dbi_conn_error( handle, &msg );
405 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
406 errnum, msg ? msg : "(No description available)" );
407 // We don't check the database connection here. It's routine to get failures at
408 // this point; we routinely try to query tables that don't exist, because they
409 // are defined in the IDL but not in the database.
411 } // end for each class in IDL
413 buffer_free( query_buf );
414 osrfHashIteratorFree( class_itr );
415 child_initialized = 1;
417 if( !results_found ) {
418 osrfLogError( OSRF_LOG_MARK,
419 "No results found for any class -- bad database connection?" );
421 } else if( ! oilsIsDBConnected( handle )) {
422 osrfLogError( OSRF_LOG_MARK,
423 "Unable to extend IDL: database connection isn't working" );
431 @brief Free an osrfHash that stores a transaction ID.
432 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
434 This function is a callback, to be called by the application session when it ends.
435 The application session stores the osrfHash via an opaque pointer.
437 If the osrfHash contains an entry for the key "xact_id", it means that an
438 uncommitted transaction is pending. Roll it back.
440 void userDataFree( void* blob ) {
441 osrfHash* hash = (osrfHash*) blob;
442 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
443 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
445 int errnum = dbi_conn_error( writehandle, &msg );
446 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
447 errnum, msg ? msg : "(No description available)" );
451 osrfHashFree( hash );
455 @name Managing session data
456 @brief Maintain data stored via the userData pointer of the application session.
458 Currently, session-level data is stored in an osrfHash. Other arrangements are
459 possible, and some would be more efficient. The application session calls a
460 callback function to free userData before terminating.
462 Currently, the only data we store at the session level is the transaction id. By this
463 means we can ensure that any pending transactions are rolled back before the application
469 @brief Free an item in the application session's userData.
470 @param key The name of a key for an osrfHash.
471 @param item An opaque pointer to the item associated with the key.
473 We store an osrfHash as userData with the application session, and arrange (by
474 installing userDataFree() as a different callback) for the session to free that
475 osrfHash before terminating.
477 This function is a callback for freeing items in the osrfHash. Currently we store
479 - Transaction id of a pending transaction; a character string. Key: "xact_id".
480 - Authkey; a character string. Key: "authkey".
481 - User object from the authentication server; a jsonObject. Key: "user_login".
483 If we ever store anything else in userData, we will need to revisit this function so
484 that it will free whatever else needs freeing.
486 static void sessionDataFree( char* key, void* item ) {
487 if( !strcmp( key, "xact_id" )
488 || !strcmp( key, "authkey" ) ) {
490 } else if( !strcmp( key, "user_login" ) )
491 jsonObjectFree( (jsonObject*) item );
495 @brief Save a transaction id.
496 @param ctx Pointer to the method context.
498 Save the session_id of the current application session as a transaction id.
500 static void setXactId( osrfMethodContext* ctx ) {
501 if( ctx && ctx->session ) {
502 osrfAppSession* session = ctx->session;
504 osrfHash* cache = session->userData;
506 // If the session doesn't already have a hash, create one. Make sure
507 // that the application session frees the hash when it terminates.
508 if( NULL == cache ) {
509 session->userData = cache = osrfNewHash();
510 osrfHashSetCallback( cache, &sessionDataFree );
511 ctx->session->userDataFree = &userDataFree;
514 // Save the transaction id in the hash, with the key "xact_id"
515 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
520 @brief Get the transaction ID for the current transaction, if any.
521 @param ctx Pointer to the method context.
522 @return Pointer to the transaction ID.
524 The return value points to an internal buffer, and will become invalid upon issuing
525 a commit or rollback.
527 static inline const char* getXactId( osrfMethodContext* ctx ) {
528 if( ctx && ctx->session && ctx->session->userData )
529 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
535 @brief Clear the current transaction id.
536 @param ctx Pointer to the method context.
538 static inline void clearXactId( osrfMethodContext* ctx ) {
539 if( ctx && ctx->session && ctx->session->userData )
540 osrfHashRemove( ctx->session->userData, "xact_id" );
545 @brief Save the user's login in the userData for the current application session.
546 @param ctx Pointer to the method context.
547 @param user_login Pointer to the user login object to be cached (we cache the original,
550 If @a user_login is NULL, remove the user login if one is already cached.
552 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
553 if( ctx && ctx->session ) {
554 osrfAppSession* session = ctx->session;
556 osrfHash* cache = session->userData;
558 // If the session doesn't already have a hash, create one. Make sure
559 // that the application session frees the hash when it terminates.
560 if( NULL == cache ) {
561 session->userData = cache = osrfNewHash();
562 osrfHashSetCallback( cache, &sessionDataFree );
563 ctx->session->userDataFree = &userDataFree;
567 osrfHashSet( cache, user_login, "user_login" );
569 osrfHashRemove( cache, "user_login" );
574 @brief Get the user login object for the current application session, if any.
575 @param ctx Pointer to the method context.
576 @return Pointer to the user login object if found; otherwise NULL.
578 The user login object was returned from the authentication server, and then cached so
579 we don't have to call the authentication server again for the same user.
581 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
582 if( ctx && ctx->session && ctx->session->userData )
583 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
589 @brief Save a copy of an authkey in the userData of the current application session.
590 @param ctx Pointer to the method context.
591 @param authkey The authkey to be saved.
593 If @a authkey is NULL, remove the authkey if one is already cached.
595 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
596 if( ctx && ctx->session && authkey ) {
597 osrfAppSession* session = ctx->session;
598 osrfHash* cache = session->userData;
600 // If the session doesn't already have a hash, create one. Make sure
601 // that the application session frees the hash when it terminates.
602 if( NULL == cache ) {
603 session->userData = cache = osrfNewHash();
604 osrfHashSetCallback( cache, &sessionDataFree );
605 ctx->session->userDataFree = &userDataFree;
608 // Save the transaction id in the hash, with the key "xact_id"
609 if( authkey && *authkey )
610 osrfHashSet( cache, strdup( authkey ), "authkey" );
612 osrfHashRemove( cache, "authkey" );
617 @brief Reset the login timeout.
618 @param authkey The authentication key for the current login session.
619 @param now The current time.
620 @return Zero if successful, or 1 if not.
622 Tell the authentication server to reset the timeout so that the login session won't
623 expire for a while longer.
625 We could dispense with the @a now parameter by calling time(). But we just called
626 time() in order to decide whether to reset the timeout, so we might as well reuse
627 the result instead of calling time() again.
629 static int reset_timeout( const char* authkey, time_t now ) {
630 jsonObject* auth_object = jsonNewObject( authkey );
632 // Ask the authentication server to reset the timeout. It returns an event
633 // indicating success or failure.
634 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
635 "open-ils.auth.session.reset_timeout", auth_object );
636 jsonObjectFree( auth_object );
638 if( !result || result->type != JSON_HASH ) {
639 osrfLogError( OSRF_LOG_MARK,
640 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
641 jsonObjectFree( result );
642 return 1; // Not the right sort of object returned
645 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
646 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
647 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
648 jsonObjectFree( result );
649 return 1; // Return code from method not available
652 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
653 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
655 desc = "(No reason available)"; // failsafe; shouldn't happen
656 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
657 jsonObjectFree( result );
661 // Revise our local proxy for the timeout deadline
662 // by a smallish fraction of the timeout interval
663 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
665 timeout = "1"; // failsafe; shouldn't happen
666 time_next_reset = now + atoi( timeout ) / 15;
668 jsonObjectFree( result );
669 return 0; // Successfully reset timeout
673 @brief Get the authkey string for the current application session, if any.
674 @param ctx Pointer to the method context.
675 @return Pointer to the cached authkey if found; otherwise NULL.
677 If present, the authkey string was cached from a previous method call.
679 static const char* getAuthkey( osrfMethodContext* ctx ) {
680 if( ctx && ctx->session && ctx->session->userData ) {
681 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
683 // Possibly reset the authentication timeout to keep the login alive. We do so
684 // no more than once per method call, and not at all if it has been only a short
685 // time since the last reset.
687 // Here we reset explicitly, if at all. We also implicitly reset the timeout
688 // whenever we call the "open-ils.auth.session.retrieve" method.
689 if( timeout_needs_resetting ) {
690 time_t now = time( NULL );
691 if( now >= time_next_reset && reset_timeout( authkey, now ) )
692 authkey = NULL; // timeout has apparently expired already
695 timeout_needs_resetting = 0;
703 @brief Implement the transaction.begin method.
704 @param ctx Pointer to the method context.
705 @return Zero if successful, or -1 upon error.
707 Start a transaction. Save a transaction ID for future reference.
710 - authkey (PCRUD only)
712 Return to client: Transaction ID
714 int beginTransaction( osrfMethodContext* ctx ) {
715 if(osrfMethodVerifyContext( ctx )) {
716 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
720 if( enforce_pcrud ) {
721 timeout_needs_resetting = 1;
722 const jsonObject* user = verifyUserPCRUD( ctx );
727 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
730 int errnum = dbi_conn_error( writehandle, &msg );
731 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
732 modulename, errnum, msg ? msg : "(No description available)" );
733 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
734 "osrfMethodException", ctx->request, "Error starting transaction" );
735 if( !oilsIsDBConnected( writehandle ))
736 osrfAppSessionPanic( ctx->session );
740 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
741 osrfAppRespondComplete( ctx, ret );
742 jsonObjectFree( ret );
748 @brief Implement the savepoint.set method.
749 @param ctx Pointer to the method context.
750 @return Zero if successful, or -1 if not.
752 Issue a SAVEPOINT to the database server.
755 - authkey (PCRUD only)
758 Return to client: Savepoint name
760 int setSavepoint( osrfMethodContext* ctx ) {
761 if(osrfMethodVerifyContext( ctx )) {
762 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
767 if( enforce_pcrud ) {
769 timeout_needs_resetting = 1;
770 const jsonObject* user = verifyUserPCRUD( ctx );
775 // Verify that a transaction is pending
776 const char* trans_id = getXactId( ctx );
777 if( NULL == trans_id ) {
778 osrfAppSessionStatus(
780 OSRF_STATUS_INTERNALSERVERERROR,
781 "osrfMethodException",
783 "No active transaction -- required for savepoints"
788 // Get the savepoint name from the method params
789 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
791 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
794 int errnum = dbi_conn_error( writehandle, &msg );
797 "%s: Error creating savepoint %s in transaction %s: %d %s",
802 msg ? msg : "(No description available)"
804 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
805 "osrfMethodException", ctx->request, "Error creating savepoint" );
806 if( !oilsIsDBConnected( writehandle ))
807 osrfAppSessionPanic( ctx->session );
810 jsonObject* ret = jsonNewObject( spName );
811 osrfAppRespondComplete( ctx, ret );
812 jsonObjectFree( ret );
818 @brief Implement the savepoint.release method.
819 @param ctx Pointer to the method context.
820 @return Zero if successful, or -1 if not.
822 Issue a RELEASE SAVEPOINT to the database server.
825 - authkey (PCRUD only)
828 Return to client: Savepoint name
830 int releaseSavepoint( osrfMethodContext* ctx ) {
831 if(osrfMethodVerifyContext( ctx )) {
832 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
837 if( enforce_pcrud ) {
839 timeout_needs_resetting = 1;
840 const jsonObject* user = verifyUserPCRUD( ctx );
845 // Verify that a transaction is pending
846 const char* trans_id = getXactId( ctx );
847 if( NULL == trans_id ) {
848 osrfAppSessionStatus(
850 OSRF_STATUS_INTERNALSERVERERROR,
851 "osrfMethodException",
853 "No active transaction -- required for savepoints"
858 // Get the savepoint name from the method params
859 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
861 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
864 int errnum = dbi_conn_error( writehandle, &msg );
867 "%s: Error releasing savepoint %s in transaction %s: %d %s",
872 msg ? msg : "(No description available)"
874 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
875 "osrfMethodException", ctx->request, "Error releasing savepoint" );
876 if( !oilsIsDBConnected( writehandle ))
877 osrfAppSessionPanic( ctx->session );
880 jsonObject* ret = jsonNewObject( spName );
881 osrfAppRespondComplete( ctx, ret );
882 jsonObjectFree( ret );
888 @brief Implement the savepoint.rollback method.
889 @param ctx Pointer to the method context.
890 @return Zero if successful, or -1 if not.
892 Issue a ROLLBACK TO SAVEPOINT to the database server.
895 - authkey (PCRUD only)
898 Return to client: Savepoint name
900 int rollbackSavepoint( osrfMethodContext* ctx ) {
901 if(osrfMethodVerifyContext( ctx )) {
902 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
907 if( enforce_pcrud ) {
909 timeout_needs_resetting = 1;
910 const jsonObject* user = verifyUserPCRUD( ctx );
915 // Verify that a transaction is pending
916 const char* trans_id = getXactId( ctx );
917 if( NULL == trans_id ) {
918 osrfAppSessionStatus(
920 OSRF_STATUS_INTERNALSERVERERROR,
921 "osrfMethodException",
923 "No active transaction -- required for savepoints"
928 // Get the savepoint name from the method params
929 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
931 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
934 int errnum = dbi_conn_error( writehandle, &msg );
937 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
942 msg ? msg : "(No description available)"
944 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
945 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
946 if( !oilsIsDBConnected( writehandle ))
947 osrfAppSessionPanic( ctx->session );
950 jsonObject* ret = jsonNewObject( spName );
951 osrfAppRespondComplete( ctx, ret );
952 jsonObjectFree( ret );
958 @brief Implement the transaction.commit method.
959 @param ctx Pointer to the method context.
960 @return Zero if successful, or -1 if not.
962 Issue a COMMIT to the database server.
965 - authkey (PCRUD only)
967 Return to client: Transaction ID.
969 int commitTransaction( osrfMethodContext* ctx ) {
970 if(osrfMethodVerifyContext( ctx )) {
971 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
975 if( enforce_pcrud ) {
976 timeout_needs_resetting = 1;
977 const jsonObject* user = verifyUserPCRUD( ctx );
982 // Verify that a transaction is pending
983 const char* trans_id = getXactId( ctx );
984 if( NULL == trans_id ) {
985 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
986 "osrfMethodException", ctx->request, "No active transaction to commit" );
990 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
993 int errnum = dbi_conn_error( writehandle, &msg );
994 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
995 modulename, errnum, msg ? msg : "(No description available)" );
996 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
997 "osrfMethodException", ctx->request, "Error committing transaction" );
998 if( !oilsIsDBConnected( writehandle ))
999 osrfAppSessionPanic( ctx->session );
1002 jsonObject* ret = jsonNewObject( trans_id );
1003 osrfAppRespondComplete( ctx, ret );
1004 jsonObjectFree( ret );
1011 @brief Implement the transaction.rollback method.
1012 @param ctx Pointer to the method context.
1013 @return Zero if successful, or -1 if not.
1015 Issue a ROLLBACK to the database server.
1018 - authkey (PCRUD only)
1020 Return to client: Transaction ID
1022 int rollbackTransaction( osrfMethodContext* ctx ) {
1023 if( osrfMethodVerifyContext( ctx )) {
1024 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1028 if( enforce_pcrud ) {
1029 timeout_needs_resetting = 1;
1030 const jsonObject* user = verifyUserPCRUD( ctx );
1035 // Verify that a transaction is pending
1036 const char* trans_id = getXactId( ctx );
1037 if( NULL == trans_id ) {
1038 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1039 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1043 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1046 int errnum = dbi_conn_error( writehandle, &msg );
1047 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1048 modulename, errnum, msg ? msg : "(No description available)" );
1049 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1050 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1051 if( !oilsIsDBConnected( writehandle ))
1052 osrfAppSessionPanic( ctx->session );
1055 jsonObject* ret = jsonNewObject( trans_id );
1056 osrfAppRespondComplete( ctx, ret );
1057 jsonObjectFree( ret );
1064 @brief Implement the "search" method.
1065 @param ctx Pointer to the method context.
1066 @return Zero if successful, or -1 if not.
1069 - authkey (PCRUD only)
1070 - WHERE clause, as jsonObject
1071 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1073 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1074 Optionally flesh linked fields.
1076 int doSearch( osrfMethodContext* ctx ) {
1077 if( osrfMethodVerifyContext( ctx )) {
1078 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1083 timeout_needs_resetting = 1;
1085 jsonObject* where_clause;
1086 jsonObject* rest_of_query;
1088 if( enforce_pcrud ) {
1089 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1090 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1092 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1093 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1096 // Get the class metadata
1097 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1098 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1102 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1104 osrfAppRespondComplete( ctx, NULL );
1108 // Return each row to the client (except that some may be suppressed by PCRUD)
1109 jsonObject* cur = 0;
1110 unsigned long res_idx = 0;
1111 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1112 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1114 osrfAppRespond( ctx, cur );
1116 jsonObjectFree( obj );
1118 osrfAppRespondComplete( ctx, NULL );
1123 @brief Implement the "id_list" method.
1124 @param ctx Pointer to the method context.
1125 @param err Pointer through which to return an error code.
1126 @return Zero if successful, or -1 if not.
1129 - authkey (PCRUD only)
1130 - WHERE clause, as jsonObject
1131 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1133 Return to client: The primary key values for all rows of the relevant class that
1134 satisfy a specified WHERE clause.
1136 This method relies on the assumption that every class has a primary key consisting of
1139 int doIdList( osrfMethodContext* ctx ) {
1140 if( osrfMethodVerifyContext( ctx )) {
1141 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1146 timeout_needs_resetting = 1;
1148 jsonObject* where_clause;
1149 jsonObject* rest_of_query;
1151 // We use the where clause without change. But we need to massage the rest of the
1152 // query, so we work with a copy of it instead of modifying the original.
1154 if( enforce_pcrud ) {
1155 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1156 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1158 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1159 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1162 // Eliminate certain SQL clauses, if present.
1163 if( rest_of_query ) {
1164 jsonObjectRemoveKey( rest_of_query, "select" );
1165 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1166 jsonObjectRemoveKey( rest_of_query, "flesh" );
1167 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1169 rest_of_query = jsonNewObjectType( JSON_HASH );
1172 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1174 // Get the class metadata
1175 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1176 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1178 // Build a SELECT list containing just the primary key,
1179 // i.e. like { "classname":["keyname"] }
1180 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1182 // Load array with name of primary key
1183 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1184 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1185 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1187 jsonObjectSetKey( rest_of_query, "select", select_clause );
1192 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1194 jsonObjectFree( rest_of_query );
1196 osrfAppRespondComplete( ctx, NULL );
1200 // Return each primary key value to the client
1202 unsigned long res_idx = 0;
1203 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1204 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1205 continue; // Suppress due to lack of permission
1207 osrfAppRespond( ctx,
1208 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1211 jsonObjectFree( obj );
1212 osrfAppRespondComplete( ctx, NULL );
1217 @brief Verify that we have a valid class reference.
1218 @param ctx Pointer to the method context.
1219 @param param Pointer to the method parameters.
1220 @return 1 if the class reference is valid, or zero if it isn't.
1222 The class of the method params must match the class to which the method id devoted.
1223 For PCRUD there are additional restrictions.
1225 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1227 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1228 osrfHash* class = osrfHashGet( method_meta, "class" );
1230 // Compare the method's class to the parameters' class
1231 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1233 // Oops -- they don't match. Complain.
1234 growing_buffer* msg = buffer_init( 128 );
1237 "%s: %s method for type %s was passed a %s",
1239 osrfHashGet( method_meta, "methodtype" ),
1240 osrfHashGet( class, "classname" ),
1241 param->classname ? param->classname : "(null)"
1244 char* m = buffer_release( msg );
1245 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1253 return verifyObjectPCRUD( ctx, param );
1259 @brief (PCRUD only) Verify that the user is properly logged in.
1260 @param ctx Pointer to the method context.
1261 @return If the user is logged in, a pointer to the user object from the authentication
1262 server; otherwise NULL.
1264 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1266 // Get the authkey (the first method parameter)
1267 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1269 // See if we have the same authkey, and a user object,
1270 // locally cached from a previous call
1271 const char* cached_authkey = getAuthkey( ctx );
1272 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1273 const jsonObject* cached_user = getUserLogin( ctx );
1278 // We have no matching authentication data in the cache. Authenticate from scratch.
1279 jsonObject* auth_object = jsonNewObject( auth );
1281 // Fetch the user object from the authentication server
1282 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1284 jsonObjectFree( auth_object );
1286 if( !user->classname || strcmp(user->classname, "au" )) {
1288 growing_buffer* msg = buffer_init( 128 );
1291 "%s: permacrud received a bad auth token: %s",
1296 char* m = buffer_release( msg );
1297 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1301 jsonObjectFree( user );
1305 setUserLogin( ctx, user );
1306 setAuthkey( ctx, auth );
1308 // Allow ourselves up to a second before we have to reset the login timeout.
1309 // It would be nice to use some fraction of the timeout interval enforced by the
1310 // authentication server, but that value is not readily available at this point.
1311 // Instead, we use a conservative default interval.
1312 time_next_reset = time( NULL ) + 1;
1318 @brief For PCRUD: Determine whether the current user may access the current row.
1319 @param ctx Pointer to the method context.
1320 @param obj Pointer to the row being potentially accessed.
1321 @return 1 if access is permitted, or 0 if it isn't.
1323 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1325 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1327 dbhandle = writehandle;
1329 // Figure out what class and method are involved
1330 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1331 osrfHash* class = osrfHashGet( method_metadata, "class" );
1332 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1334 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1335 // contexts we will do another lookup of the current row, even if we already have a
1336 // previously fetched row image, because the row image in hand may not include the
1337 // foreign key(s) that we need.
1339 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1340 // but they aren't implemented yet.
1343 if( *method_type == 's' || *method_type == 'i' ) {
1344 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1346 } else if( *method_type == 'u' || *method_type == 'd' ) {
1347 fetch = 1; // MUST go to the db for the object for update and delete
1350 // Get the appropriate permacrud entry from the IDL, depending on method type
1351 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1353 // No permacrud for this method type on this class
1355 growing_buffer* msg = buffer_init( 128 );
1358 "%s: %s on class %s has no permacrud IDL entry",
1360 osrfHashGet( method_metadata, "methodtype" ),
1361 osrfHashGet( class, "classname" )
1364 char* m = buffer_release( msg );
1365 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1366 "osrfMethodException", ctx->request, m );
1373 // Get the user id, and make sure the user is logged in
1374 const jsonObject* user = verifyUserPCRUD( ctx );
1376 return 0; // Not logged in? No access.
1378 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1380 // Get a list of permissions from the permacrud entry.
1381 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1382 if( permission->size == 0 ) {
1383 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1387 // Build a list of org units that own the row. This is fairly convoluted because there
1388 // are several different ways that an org unit may own the row, as defined by the
1391 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1392 // identifying an owning org_unit..
1393 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1395 // Foreign context adds a layer of indirection. The row points to some other row that
1396 // an org unit may own. The "jump" attribute, if present, adds another layer of
1398 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1400 // The following string array stores the list of org units. (We don't have a thingie
1401 // for storing lists of integers, so we fake it with a list of strings.)
1402 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1405 const char* pkey_value = NULL;
1406 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1407 // If the global_required attribute is present and true, then the only owning
1408 // org unit is the root org unit, i.e. the one with no parent.
1409 osrfLogDebug( OSRF_LOG_MARK,
1410 "global-level permissions required, fetching top of the org tree" );
1412 // check for perm at top of org tree
1413 const char* org_tree_root_id = org_tree_root( ctx );
1414 if( org_tree_root_id ) {
1415 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1416 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1418 osrfStringArrayFree( context_org_array );
1423 // If the global_required attribute is absent or false, then we look for
1424 // local and/or foreign context. In order to find the relevant foreign
1425 // keys, we must either read the relevant row from the database, or look at
1426 // the image of the row that we already have in memory.
1428 // Even if we have an image of the row in memory, that image may not include the
1429 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1430 // of the row to make sure that we have what we need.
1432 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1433 "fetching context org ids" );
1434 const char* pkey = osrfHashGet( class, "primarykey" );
1435 jsonObject *param = NULL;
1438 // There is no primary key, so we can't do a fresh lookup. Use the row
1439 // image that we already have. If it doesn't have everything we need, too bad.
1441 param = jsonObjectClone( obj );
1442 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1443 } else if( obj->classname ) {
1444 pkey_value = oilsFMGetStringConst( obj, pkey );
1446 param = jsonObjectClone( obj );
1447 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1450 pkey_value = jsonObjectGetString( obj );
1452 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1453 "of %s and retrieving from the database", pkey_value );
1457 // Fetch the row so that we can look at the foreign key(s)
1458 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1459 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1460 jsonObjectFree( _tmp_params );
1462 param = jsonObjectExtractIndex( _list, 0 );
1463 jsonObjectFree( _list );
1467 // The row doesn't exist. Complain, and deny access.
1468 osrfLogDebug( OSRF_LOG_MARK,
1469 "Object not found in the database with primary key %s of %s",
1472 growing_buffer* msg = buffer_init( 128 );
1475 "%s: no object found with primary key %s of %s",
1481 char* m = buffer_release( msg );
1482 osrfAppSessionStatus(
1484 OSRF_STATUS_INTERNALSERVERERROR,
1485 "osrfMethodException",
1494 if( local_context && local_context->size > 0 ) {
1495 // The IDL provides a list of column names for the foreign keys denoting
1496 // local context, i.e. columns identifying owing org units directly. Look up
1497 // the value of each one, and if it isn't null, add it to the list of org units.
1498 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1499 local_context->size );
1501 const char* lcontext = NULL;
1502 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1503 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1504 if( fkey_value ) { // if not null
1505 osrfStringArrayAdd( context_org_array, fkey_value );
1508 "adding class-local field %s (value: %s) to the context org list",
1510 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1516 if( foreign_context ) {
1517 unsigned long class_count = osrfHashGetCount( foreign_context );
1518 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1520 if( class_count > 0 ) {
1522 // The IDL provides a list of foreign key columns pointing to rows that
1523 // an org unit may own. Follow each link, identify the owning org unit,
1524 // and add it to the list.
1525 osrfHash* fcontext = NULL;
1526 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1527 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1528 // For each class to which a foreign key points:
1529 const char* class_name = osrfHashIteratorKey( class_itr );
1530 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1534 "%d foreign context fields(s) specified for class %s",
1535 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1539 // Get the name of the key field in the foreign table
1540 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1542 // Get the value of the foreign key pointing to the foreign table
1543 char* foreign_pkey_value =
1544 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1545 if( !foreign_pkey_value )
1546 continue; // Foreign key value is null; skip it
1548 // Look up the row to which the foreign key points
1549 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1550 jsonObject* _list = doFieldmapperSearch(
1551 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1553 jsonObject* _fparam = NULL;
1554 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1555 _fparam = jsonObjectExtractIndex( _list, 0 );
1557 jsonObjectFree( _tmp_params );
1558 jsonObjectFree( _list );
1560 // At this point _fparam either points to the row identified by the
1561 // foreign key, or it's NULL (no such row found).
1563 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1565 const char* bad_class = NULL; // For noting failed lookups
1567 bad_class = class_name; // Referenced row not found
1568 else if( jump_list ) {
1569 // Follow a chain of rows, linked by foreign keys, to find an owner
1570 const char* flink = NULL;
1572 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1573 // For each entry in the jump list. Each entry (i.e. flink) is
1574 // the name of a foreign key column in the current row.
1576 // From the IDL, get the linkage information for the next jump
1577 osrfHash* foreign_link_hash =
1578 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1580 // Get the class metadata for the class
1581 // to which the foreign key points
1582 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1583 osrfHashGet( foreign_link_hash, "class" ));
1585 // Get the name of the referenced key of that class
1586 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1588 // Get the value of the foreign key pointing to that class
1589 free( foreign_pkey_value );
1590 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1591 if( !foreign_pkey_value )
1592 break; // Foreign key is null; quit looking
1594 // Build a WHERE clause for the lookup
1595 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1598 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1599 _tmp_params, NULL, &err );
1601 // Get the resulting row
1602 jsonObjectFree( _fparam );
1603 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1604 _fparam = jsonObjectExtractIndex( _list, 0 );
1606 // Referenced row not found
1608 bad_class = osrfHashGet( foreign_link_hash, "class" );
1611 jsonObjectFree( _tmp_params );
1612 jsonObjectFree( _list );
1618 // We had a foreign key pointing to such-and-such a row, but then
1619 // we couldn't fetch that row. The data in the database are in an
1620 // inconsistent state; the database itself may even be corrupted.
1621 growing_buffer* msg = buffer_init( 128 );
1624 "%s: no object of class %s found with primary key %s of %s",
1628 foreign_pkey_value ? foreign_pkey_value : "(null)"
1631 char* m = buffer_release( msg );
1632 osrfAppSessionStatus(
1634 OSRF_STATUS_INTERNALSERVERERROR,
1635 "osrfMethodException",
1641 osrfHashIteratorFree( class_itr );
1642 free( foreign_pkey_value );
1643 jsonObjectFree( param );
1648 free( foreign_pkey_value );
1651 // Examine each context column of the foreign row,
1652 // and add its value to the list of org units.
1654 const char* foreign_field = NULL;
1655 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1656 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1657 osrfStringArrayAdd( context_org_array,
1658 oilsFMGetStringConst( _fparam, foreign_field ));
1659 osrfLogDebug( OSRF_LOG_MARK,
1660 "adding foreign class %s field %s (value: %s) "
1661 "to the context org list",
1664 osrfStringArrayGetString(
1665 context_org_array, context_org_array->size - 1 )
1669 jsonObjectFree( _fparam );
1673 osrfHashIteratorFree( class_itr );
1677 jsonObjectFree( param );
1680 const char* context_org = NULL;
1681 const char* perm = NULL;
1684 // For every combination of permission and context org unit: call a stored procedure
1685 // to determine if the user has this permission in the context of this org unit.
1686 // If the answer is yes at any point, then we're done, and the user has permission.
1687 // In other words permissions are additive.
1689 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1691 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1697 "Checking object permission [%s] for user %d "
1698 "on object %s (class %s) at org %d",
1702 osrfHashGet( class, "classname" ),
1706 result = dbi_conn_queryf(
1708 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1711 osrfHashGet( class, "classname" ),
1719 "Received a result for object permission [%s] "
1720 "for user %d on object %s (class %s) at org %d",
1724 osrfHashGet( class, "classname" ),
1728 if( dbi_result_first_row( result )) {
1729 jsonObject* return_val = oilsMakeJSONFromResult( result );
1730 const char* has_perm = jsonObjectGetString(
1731 jsonObjectGetKeyConst( return_val, "has_perm" ));
1735 "Status of object permission [%s] for user %d "
1736 "on object %s (class %s) at org %d is %s",
1740 osrfHashGet(class, "classname"),
1745 if( *has_perm == 't' )
1747 jsonObjectFree( return_val );
1750 dbi_result_free( result );
1755 int errnum = dbi_conn_error( writehandle, &msg );
1756 osrfLogWarning( OSRF_LOG_MARK,
1757 "Unable to call check object permissions: %d, %s",
1758 errnum, msg ? msg : "(No description available)" );
1759 if( !oilsIsDBConnected( writehandle ))
1760 osrfAppSessionPanic( ctx->session );
1764 osrfLogDebug( OSRF_LOG_MARK,
1765 "Checking non-object permission [%s] for user %d at org %d",
1766 perm, userid, atoi(context_org) );
1767 result = dbi_conn_queryf(
1769 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1776 osrfLogDebug( OSRF_LOG_MARK,
1777 "Received a result for permission [%s] for user %d at org %d",
1778 perm, userid, atoi( context_org ));
1779 if( dbi_result_first_row( result )) {
1780 jsonObject* return_val = oilsMakeJSONFromResult( result );
1781 const char* has_perm = jsonObjectGetString(
1782 jsonObjectGetKeyConst( return_val, "has_perm" ));
1783 osrfLogDebug( OSRF_LOG_MARK,
1784 "Status of permission [%s] for user %d at org %d is [%s]",
1785 perm, userid, atoi( context_org ), has_perm );
1786 if( *has_perm == 't' )
1788 jsonObjectFree( return_val );
1791 dbi_result_free( result );
1796 int errnum = dbi_conn_error( writehandle, &msg );
1797 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1798 errnum, msg ? msg : "(No description available)" );
1799 if( !oilsIsDBConnected( writehandle ))
1800 osrfAppSessionPanic( ctx->session );
1808 osrfStringArrayFree( context_org_array );
1814 @brief Look up the root of the org_unit tree.
1815 @param ctx Pointer to the method context.
1816 @return The id of the root org unit, as a character string.
1818 Query actor.org_unit where parent_ou is null, and return the id as a string.
1820 This function assumes that there is only one root org unit, i.e. that we
1821 have a single tree, not a forest.
1823 The calling code is responsible for freeing the returned string.
1825 static const char* org_tree_root( osrfMethodContext* ctx ) {
1827 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1828 static time_t last_lookup_time = 0;
1829 time_t current_time = time( NULL );
1831 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1832 // We successfully looked this up less than an hour ago.
1833 // It's not likely to have changed since then.
1834 return strdup( cached_root_id );
1836 last_lookup_time = current_time;
1839 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1840 jsonObject* result = doFieldmapperSearch(
1841 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1842 jsonObjectFree( where_clause );
1844 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1847 jsonObjectFree( result );
1849 growing_buffer* msg = buffer_init( 128 );
1850 OSRF_BUFFER_ADD( msg, modulename );
1851 OSRF_BUFFER_ADD( msg,
1852 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1854 char* m = buffer_release( msg );
1855 osrfAppSessionStatus( ctx->session,
1856 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1859 cached_root_id[ 0 ] = '\0';
1863 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1864 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1866 strcpy( cached_root_id, root_org_unit_id );
1867 jsonObjectFree( result );
1868 return cached_root_id;
1872 @brief Create a JSON_HASH with a single key/value pair.
1873 @param key The key of the key/value pair.
1874 @param value the value of the key/value pair.
1875 @return Pointer to a newly created jsonObject of type JSON_HASH.
1877 The value of the key/value is either a string or (if @a value is NULL) a null.
1879 static jsonObject* single_hash( const char* key, const char* value ) {
1881 if( ! key ) key = "";
1883 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1884 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1889 int doCreate( osrfMethodContext* ctx ) {
1890 if(osrfMethodVerifyContext( ctx )) {
1891 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1896 timeout_needs_resetting = 1;
1898 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1899 jsonObject* target = NULL;
1900 jsonObject* options = NULL;
1902 if( enforce_pcrud ) {
1903 target = jsonObjectGetIndex( ctx->params, 1 );
1904 options = jsonObjectGetIndex( ctx->params, 2 );
1906 target = jsonObjectGetIndex( ctx->params, 0 );
1907 options = jsonObjectGetIndex( ctx->params, 1 );
1910 if( !verifyObjectClass( ctx, target )) {
1911 osrfAppRespondComplete( ctx, NULL );
1915 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1917 const char* trans_id = getXactId( ctx );
1919 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1921 osrfAppSessionStatus(
1923 OSRF_STATUS_BADREQUEST,
1924 "osrfMethodException",
1926 "No active transaction -- required for CREATE"
1928 osrfAppRespondComplete( ctx, NULL );
1932 // The following test is harmless but redundant. If a class is
1933 // readonly, we don't register a create method for it.
1934 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1935 osrfAppSessionStatus(
1937 OSRF_STATUS_BADREQUEST,
1938 "osrfMethodException",
1940 "Cannot INSERT readonly class"
1942 osrfAppRespondComplete( ctx, NULL );
1946 // Set the last_xact_id
1947 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1949 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1950 trans_id, target->classname, index);
1951 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1954 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1956 dbhandle = writehandle;
1958 osrfHash* fields = osrfHashGet( meta, "fields" );
1959 char* pkey = osrfHashGet( meta, "primarykey" );
1960 char* seq = osrfHashGet( meta, "sequence" );
1962 growing_buffer* table_buf = buffer_init( 128 );
1963 growing_buffer* col_buf = buffer_init( 128 );
1964 growing_buffer* val_buf = buffer_init( 128 );
1966 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1967 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1968 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1969 buffer_add( val_buf,"VALUES (" );
1973 osrfHash* field = NULL;
1974 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1975 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1977 const char* field_name = osrfHashIteratorKey( field_itr );
1979 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1982 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1985 if( field_object && field_object->classname ) {
1986 value = oilsFMGetString(
1988 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1990 } else if( field_object && JSON_BOOL == field_object->type ) {
1991 if( jsonBoolIsTrue( field_object ) )
1992 value = strdup( "t" );
1994 value = strdup( "f" );
1996 value = jsonObjectToSimpleString( field_object );
2002 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2003 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2006 buffer_add( col_buf, field_name );
2008 if( !field_object || field_object->type == JSON_NULL ) {
2009 buffer_add( val_buf, "DEFAULT" );
2011 } else if( !strcmp( get_primitive( field ), "number" )) {
2012 const char* numtype = get_datatype( field );
2013 if( !strcmp( numtype, "INT8" )) {
2014 buffer_fadd( val_buf, "%lld", atoll( value ));
2016 } else if( !strcmp( numtype, "INT" )) {
2017 buffer_fadd( val_buf, "%d", atoi( value ));
2019 } else if( !strcmp( numtype, "NUMERIC" )) {
2020 buffer_fadd( val_buf, "%f", atof( value ));
2023 if( dbi_conn_quote_string( writehandle, &value )) {
2024 OSRF_BUFFER_ADD( val_buf, value );
2027 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2028 osrfAppSessionStatus(
2030 OSRF_STATUS_INTERNALSERVERERROR,
2031 "osrfMethodException",
2033 "Error quoting string -- please see the error log for more details"
2036 buffer_free( table_buf );
2037 buffer_free( col_buf );
2038 buffer_free( val_buf );
2039 osrfAppRespondComplete( ctx, NULL );
2047 osrfHashIteratorFree( field_itr );
2049 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2050 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2052 char* table_str = buffer_release( table_buf );
2053 char* col_str = buffer_release( col_buf );
2054 char* val_str = buffer_release( val_buf );
2055 growing_buffer* sql = buffer_init( 128 );
2056 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2061 char* query = buffer_release( sql );
2063 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2065 jsonObject* obj = NULL;
2068 dbi_result result = dbi_conn_query( writehandle, query );
2070 obj = jsonNewObject( NULL );
2072 int errnum = dbi_conn_error( writehandle, &msg );
2075 "%s ERROR inserting %s object using query [%s]: %d %s",
2077 osrfHashGet(meta, "fieldmapper"),
2080 msg ? msg : "(No description available)"
2082 osrfAppSessionStatus(
2084 OSRF_STATUS_INTERNALSERVERERROR,
2085 "osrfMethodException",
2087 "INSERT error -- please see the error log for more details"
2089 if( !oilsIsDBConnected( writehandle ))
2090 osrfAppSessionPanic( ctx->session );
2094 char* id = oilsFMGetString( target, pkey );
2096 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2097 growing_buffer* _id = buffer_init( 10 );
2098 buffer_fadd( _id, "%lld", new_id );
2099 id = buffer_release( _id );
2102 // Find quietness specification, if present
2103 const char* quiet_str = NULL;
2105 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2107 quiet_str = jsonObjectGetString( quiet_obj );
2110 if( str_is_true( quiet_str )) { // if quietness is specified
2111 obj = jsonNewObject( id );
2115 // Fetch the row that we just inserted, so that we can return it to the client
2116 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2117 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2120 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2124 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2126 jsonObjectFree( list );
2127 jsonObjectFree( where_clause );
2134 osrfAppRespondComplete( ctx, obj );
2135 jsonObjectFree( obj );
2140 @brief Implement the retrieve method.
2141 @param ctx Pointer to the method context.
2142 @param err Pointer through which to return an error code.
2143 @return If successful, a pointer to the result to be returned to the client;
2146 From the method's class, fetch a row with a specified value in the primary key. This
2147 method relies on the database design convention that a primary key consists of a single
2151 - authkey (PCRUD only)
2152 - value of the primary key for the desired row, for building the WHERE clause
2153 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2155 Return to client: One row from the query.
2157 int doRetrieve( osrfMethodContext* ctx ) {
2158 if(osrfMethodVerifyContext( ctx )) {
2159 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2164 timeout_needs_resetting = 1;
2169 if( enforce_pcrud ) {
2174 // Get the class metadata
2175 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2177 // Get the value of the primary key, from a method parameter
2178 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2182 "%s retrieving %s object with primary key value of %s",
2184 osrfHashGet( class_def, "fieldmapper" ),
2185 jsonObjectGetString( id_obj )
2188 // Build a WHERE clause based on the key value
2189 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2192 osrfHashGet( class_def, "primarykey" ), // name of key column
2193 jsonObjectClone( id_obj ) // value of key column
2196 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2200 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2202 jsonObjectFree( where_clause );
2204 osrfAppRespondComplete( ctx, NULL );
2208 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2209 jsonObjectFree( list );
2211 if( enforce_pcrud ) {
2212 if(!verifyObjectPCRUD( ctx, obj )) {
2213 jsonObjectFree( obj );
2215 growing_buffer* msg = buffer_init( 128 );
2216 OSRF_BUFFER_ADD( msg, modulename );
2217 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2219 char* m = buffer_release( msg );
2220 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2224 osrfAppRespondComplete( ctx, NULL );
2229 osrfAppRespondComplete( ctx, obj );
2230 jsonObjectFree( obj );
2235 @brief Translate a numeric value to a string representation for the database.
2236 @param field Pointer to the IDL field definition.
2237 @param value Pointer to a jsonObject holding the value of a field.
2238 @return Pointer to a newly allocated string.
2240 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2241 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2242 or (what is worse) valid SQL that is wrong.
2244 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2246 The calling code is responsible for freeing the resulting string by calling free().
2248 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2249 growing_buffer* val_buf = buffer_init( 32 );
2250 const char* numtype = get_datatype( field );
2252 // For historical reasons the following contains cruft that could be cleaned up.
2253 if( !strncmp( numtype, "INT", 3 ) ) {
2254 if( value->type == JSON_NUMBER )
2255 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2256 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2258 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2261 } else if( !strcmp( numtype, "NUMERIC" )) {
2262 if( value->type == JSON_NUMBER )
2263 buffer_fadd( val_buf, jsonObjectGetString( value ));
2265 buffer_fadd( val_buf, jsonObjectGetString( value ));
2269 // Presumably this was really intended to be a string, so quote it
2270 char* str = jsonObjectToSimpleString( value );
2271 if( dbi_conn_quote_string( dbhandle, &str )) {
2272 OSRF_BUFFER_ADD( val_buf, str );
2275 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2277 buffer_free( val_buf );
2282 return buffer_release( val_buf );
2285 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2286 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2287 growing_buffer* sql_buf = buffer_init( 32 );
2293 osrfHashGet( field, "name" )
2297 buffer_add( sql_buf, "IN (" );
2298 } else if( !strcasecmp( op,"not in" )) {
2299 buffer_add( sql_buf, "NOT IN (" );
2301 buffer_add( sql_buf, "IN (" );
2304 if( node->type == JSON_HASH ) {
2305 // subquery predicate
2306 char* subpred = buildQuery( ctx, node, SUBSELECT );
2308 buffer_free( sql_buf );
2312 buffer_add( sql_buf, subpred );
2315 } else if( node->type == JSON_ARRAY ) {
2316 // literal value list
2317 int in_item_index = 0;
2318 int in_item_first = 1;
2319 const jsonObject* in_item;
2320 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2325 buffer_add( sql_buf, ", " );
2328 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2329 osrfLogError( OSRF_LOG_MARK,
2330 "%s: Expected string or number within IN list; found %s",
2331 modulename, json_type( in_item->type ) );
2332 buffer_free( sql_buf );
2336 // Append the literal value -- quoted if not a number
2337 if( JSON_NUMBER == in_item->type ) {
2338 char* val = jsonNumberToDBString( field, in_item );
2339 OSRF_BUFFER_ADD( sql_buf, val );
2342 } else if( !strcmp( get_primitive( field ), "number" )) {
2343 char* val = jsonNumberToDBString( field, in_item );
2344 OSRF_BUFFER_ADD( sql_buf, val );
2348 char* key_string = jsonObjectToSimpleString( in_item );
2349 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2350 OSRF_BUFFER_ADD( sql_buf, key_string );
2353 osrfLogError( OSRF_LOG_MARK,
2354 "%s: Error quoting key string [%s]", modulename, key_string );
2356 buffer_free( sql_buf );
2362 if( in_item_first ) {
2363 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2364 buffer_free( sql_buf );
2368 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2369 modulename, json_type( node->type ));
2370 buffer_free( sql_buf );
2374 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2376 return buffer_release( sql_buf );
2379 // Receive a JSON_ARRAY representing a function call. The first
2380 // entry in the array is the function name. The rest are parameters.
2381 static char* searchValueTransform( const jsonObject* array ) {
2383 if( array->size < 1 ) {
2384 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2388 // Get the function name
2389 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2390 if( func_item->type != JSON_STRING ) {
2391 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2392 modulename, json_type( func_item->type ));
2396 growing_buffer* sql_buf = buffer_init( 32 );
2398 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2399 OSRF_BUFFER_ADD( sql_buf, "( " );
2401 // Get the parameters
2402 int func_item_index = 1; // We already grabbed the zeroth entry
2403 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2405 // Add a separator comma, if we need one
2406 if( func_item_index > 2 )
2407 buffer_add( sql_buf, ", " );
2409 // Add the current parameter
2410 if( func_item->type == JSON_NULL ) {
2411 buffer_add( sql_buf, "NULL" );
2413 char* val = jsonObjectToSimpleString( func_item );
2414 if( dbi_conn_quote_string( dbhandle, &val )) {
2415 OSRF_BUFFER_ADD( sql_buf, val );
2418 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2420 buffer_free( sql_buf );
2427 buffer_add( sql_buf, " )" );
2429 return buffer_release( sql_buf );
2432 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2433 const jsonObject* node, const char* op ) {
2435 if( ! is_good_operator( op ) ) {
2436 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2440 char* val = searchValueTransform( node );
2444 growing_buffer* sql_buf = buffer_init( 32 );
2449 osrfHashGet( field, "name" ),
2456 return buffer_release( sql_buf );
2459 // class_alias is a class name or other table alias
2460 // field is a field definition as stored in the IDL
2461 // node comes from the method parameter, and may represent an entry in the SELECT list
2462 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2463 const jsonObject* node ) {
2464 growing_buffer* sql_buf = buffer_init( 32 );
2466 const char* field_transform = jsonObjectGetString(
2467 jsonObjectGetKeyConst( node, "transform" ) );
2468 const char* transform_subcolumn = jsonObjectGetString(
2469 jsonObjectGetKeyConst( node, "result_field" ) );
2471 if( transform_subcolumn ) {
2472 if( ! is_identifier( transform_subcolumn ) ) {
2473 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2474 modulename, transform_subcolumn );
2475 buffer_free( sql_buf );
2478 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2481 if( field_transform ) {
2483 if( ! is_identifier( field_transform ) ) {
2484 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2485 modulename, field_transform );
2486 buffer_free( sql_buf );
2490 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2491 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2492 field_transform, class_alias, osrfHashGet( field, "name" ));
2494 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2495 field_transform, class_alias, osrfHashGet( field, "name" ));
2498 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2501 if( array->type != JSON_ARRAY ) {
2502 osrfLogError( OSRF_LOG_MARK,
2503 "%s: Expected JSON_ARRAY for function params; found %s",
2504 modulename, json_type( array->type ) );
2505 buffer_free( sql_buf );
2508 int func_item_index = 0;
2509 jsonObject* func_item;
2510 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2512 char* val = jsonObjectToSimpleString( func_item );
2515 buffer_add( sql_buf, ",NULL" );
2516 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2517 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2518 OSRF_BUFFER_ADD( sql_buf, val );
2520 osrfLogError( OSRF_LOG_MARK,
2521 "%s: Error quoting key string [%s]", modulename, val );
2523 buffer_free( sql_buf );
2530 buffer_add( sql_buf, " )" );
2533 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2536 if( transform_subcolumn )
2537 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2539 return buffer_release( sql_buf );
2542 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2543 const jsonObject* node, const char* op ) {
2545 if( ! is_good_operator( op ) ) {
2546 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2550 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2551 if( ! field_transform )
2554 int extra_parens = 0; // boolean
2556 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2558 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2560 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2562 free( field_transform );
2566 } else if( value_obj->type == JSON_ARRAY ) {
2567 value = searchValueTransform( value_obj );
2569 osrfLogError( OSRF_LOG_MARK,
2570 "%s: Error building value transform for field transform", modulename );
2571 free( field_transform );
2574 } else if( value_obj->type == JSON_HASH ) {
2575 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2577 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2579 free( field_transform );
2583 } else if( value_obj->type == JSON_NUMBER ) {
2584 value = jsonNumberToDBString( field, value_obj );
2585 } else if( value_obj->type == JSON_NULL ) {
2586 osrfLogError( OSRF_LOG_MARK,
2587 "%s: Error building predicate for field transform: null value", modulename );
2588 free( field_transform );
2590 } else if( value_obj->type == JSON_BOOL ) {
2591 osrfLogError( OSRF_LOG_MARK,
2592 "%s: Error building predicate for field transform: boolean value", modulename );
2593 free( field_transform );
2596 if( !strcmp( get_primitive( field ), "number") ) {
2597 value = jsonNumberToDBString( field, value_obj );
2599 value = jsonObjectToSimpleString( value_obj );
2600 if( !dbi_conn_quote_string( dbhandle, &value )) {
2601 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2602 modulename, value );
2604 free( field_transform );
2610 const char* left_parens = "";
2611 const char* right_parens = "";
2613 if( extra_parens ) {
2618 growing_buffer* sql_buf = buffer_init( 32 );
2622 "%s%s %s %s %s %s%s",
2633 free( field_transform );
2635 return buffer_release( sql_buf );
2638 static char* searchSimplePredicate( const char* op, const char* class_alias,
2639 osrfHash* field, const jsonObject* node ) {
2641 if( ! is_good_operator( op ) ) {
2642 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2648 // Get the value to which we are comparing the specified column
2649 if( node->type != JSON_NULL ) {
2650 if( node->type == JSON_NUMBER ) {
2651 val = jsonNumberToDBString( field, node );
2652 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2653 val = jsonNumberToDBString( field, node );
2655 val = jsonObjectToSimpleString( node );
2660 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2661 // Value is not numeric; enclose it in quotes
2662 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2663 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2670 // Compare to a null value
2671 val = strdup( "NULL" );
2672 if( strcmp( op, "=" ))
2678 growing_buffer* sql_buf = buffer_init( 32 );
2679 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2680 char* pred = buffer_release( sql_buf );
2687 static char* searchBETWEENPredicate( const char* class_alias,
2688 osrfHash* field, const jsonObject* node ) {
2690 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2691 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2693 if( NULL == y_node ) {
2694 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2697 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2698 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2705 if( !strcmp( get_primitive( field ), "number") ) {
2706 x_string = jsonNumberToDBString( field, x_node );
2707 y_string = jsonNumberToDBString( field, y_node );
2710 x_string = jsonObjectToSimpleString( x_node );
2711 y_string = jsonObjectToSimpleString( y_node );
2712 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2713 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2714 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2715 modulename, x_string, y_string );
2722 growing_buffer* sql_buf = buffer_init( 32 );
2723 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2724 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2728 return buffer_release( sql_buf );
2731 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2732 jsonObject* node, osrfMethodContext* ctx ) {
2735 if( node->type == JSON_ARRAY ) { // equality IN search
2736 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2737 } else if( node->type == JSON_HASH ) { // other search
2738 jsonIterator* pred_itr = jsonNewIterator( node );
2739 if( !jsonIteratorHasNext( pred_itr ) ) {
2740 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2741 modulename, osrfHashGet(field, "name" ));
2743 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2745 // Verify that there are no additional predicates
2746 if( jsonIteratorHasNext( pred_itr ) ) {
2747 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2748 modulename, osrfHashGet(field, "name" ));
2749 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2750 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2751 else if( !(strcasecmp( pred_itr->key,"in" ))
2752 || !(strcasecmp( pred_itr->key,"not in" )) )
2753 pred = searchINPredicate(
2754 class_info->alias, field, pred_node, pred_itr->key, ctx );
2755 else if( pred_node->type == JSON_ARRAY )
2756 pred = searchFunctionPredicate(
2757 class_info->alias, field, pred_node, pred_itr->key );
2758 else if( pred_node->type == JSON_HASH )
2759 pred = searchFieldTransformPredicate(
2760 class_info, field, pred_node, pred_itr->key );
2762 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2764 jsonIteratorFree( pred_itr );
2766 } else if( node->type == JSON_NULL ) { // IS NULL search
2767 growing_buffer* _p = buffer_init( 64 );
2770 "\"%s\".%s IS NULL",
2771 class_info->class_name,
2772 osrfHashGet( field, "name" )
2774 pred = buffer_release( _p );
2775 } else { // equality search
2776 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2795 field : call_number,
2811 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2813 const jsonObject* working_hash;
2814 jsonObject* freeable_hash = NULL;
2816 if( join_hash->type == JSON_HASH ) {
2817 working_hash = join_hash;
2818 } else if( join_hash->type == JSON_STRING ) {
2819 // turn it into a JSON_HASH by creating a wrapper
2820 // around a copy of the original
2821 const char* _tmp = jsonObjectGetString( join_hash );
2822 freeable_hash = jsonNewObjectType( JSON_HASH );
2823 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2824 working_hash = freeable_hash;
2828 "%s: JOIN failed; expected JSON object type not found",
2834 growing_buffer* join_buf = buffer_init( 128 );
2835 const char* leftclass = left_info->class_name;
2837 jsonObject* snode = NULL;
2838 jsonIterator* search_itr = jsonNewIterator( working_hash );
2840 while ( (snode = jsonIteratorNext( search_itr )) ) {
2841 const char* right_alias = search_itr->key;
2843 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2845 class = right_alias;
2847 const ClassInfo* right_info = add_joined_class( right_alias, class );
2851 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2855 jsonIteratorFree( search_itr );
2856 buffer_free( join_buf );
2858 jsonObjectFree( freeable_hash );
2861 osrfHash* links = right_info->links;
2862 const char* table = right_info->source_def;
2864 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2865 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2867 if( field && !fkey ) {
2868 // Look up the corresponding join column in the IDL.
2869 // The link must be defined in the child table,
2870 // and point to the right parent table.
2871 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2872 const char* reltype = NULL;
2873 const char* other_class = NULL;
2874 reltype = osrfHashGet( idl_link, "reltype" );
2875 if( reltype && strcmp( reltype, "has_many" ) )
2876 other_class = osrfHashGet( idl_link, "class" );
2877 if( other_class && !strcmp( other_class, leftclass ) )
2878 fkey = osrfHashGet( idl_link, "key" );
2882 "%s: JOIN failed. No link defined from %s.%s to %s",
2888 buffer_free( join_buf );
2890 jsonObjectFree( freeable_hash );
2891 jsonIteratorFree( search_itr );
2895 } else if( !field && fkey ) {
2896 // Look up the corresponding join column in the IDL.
2897 // The link must be defined in the child table,
2898 // and point to the right parent table.
2899 osrfHash* left_links = left_info->links;
2900 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2901 const char* reltype = NULL;
2902 const char* other_class = NULL;
2903 reltype = osrfHashGet( idl_link, "reltype" );
2904 if( reltype && strcmp( reltype, "has_many" ) )
2905 other_class = osrfHashGet( idl_link, "class" );
2906 if( other_class && !strcmp( other_class, class ) )
2907 field = osrfHashGet( idl_link, "key" );
2911 "%s: JOIN failed. No link defined from %s.%s to %s",
2917 buffer_free( join_buf );
2919 jsonObjectFree( freeable_hash );
2920 jsonIteratorFree( search_itr );
2924 } else if( !field && !fkey ) {
2925 osrfHash* left_links = left_info->links;
2927 // For each link defined for the left class:
2928 // see if the link references the joined class
2929 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2930 osrfHash* curr_link = NULL;
2931 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2932 const char* other_class = osrfHashGet( curr_link, "class" );
2933 if( other_class && !strcmp( other_class, class ) ) {
2935 // In the IDL, the parent class doesn't always know then names of the child
2936 // columns that are pointing to it, so don't use that end of the link
2937 const char* reltype = osrfHashGet( curr_link, "reltype" );
2938 if( reltype && strcmp( reltype, "has_many" ) ) {
2939 // Found a link between the classes
2940 fkey = osrfHashIteratorKey( itr );
2941 field = osrfHashGet( curr_link, "key" );
2946 osrfHashIteratorFree( itr );
2948 if( !field || !fkey ) {
2949 // Do another such search, with the classes reversed
2951 // For each link defined for the joined class:
2952 // see if the link references the left class
2953 osrfHashIterator* itr = osrfNewHashIterator( links );
2954 osrfHash* curr_link = NULL;
2955 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2956 const char* other_class = osrfHashGet( curr_link, "class" );
2957 if( other_class && !strcmp( other_class, leftclass ) ) {
2959 // In the IDL, the parent class doesn't know then names of the child
2960 // columns that are pointing to it, so don't use that end of the link
2961 const char* reltype = osrfHashGet( curr_link, "reltype" );
2962 if( reltype && strcmp( reltype, "has_many" ) ) {
2963 // Found a link between the classes
2964 field = osrfHashIteratorKey( itr );
2965 fkey = osrfHashGet( curr_link, "key" );
2970 osrfHashIteratorFree( itr );
2973 if( !field || !fkey ) {
2976 "%s: JOIN failed. No link defined between %s and %s",
2981 buffer_free( join_buf );
2983 jsonObjectFree( freeable_hash );
2984 jsonIteratorFree( search_itr );
2989 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2991 if( !strcasecmp( type,"left" )) {
2992 buffer_add( join_buf, " LEFT JOIN" );
2993 } else if( !strcasecmp( type,"right" )) {
2994 buffer_add( join_buf, " RIGHT JOIN" );
2995 } else if( !strcasecmp( type,"full" )) {
2996 buffer_add( join_buf, " FULL JOIN" );
2998 buffer_add( join_buf, " INNER JOIN" );
3001 buffer_add( join_buf, " INNER JOIN" );
3004 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3005 table, right_alias, right_alias, field, left_info->alias, fkey );
3007 // Add any other join conditions as specified by "filter"
3008 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3010 const char* filter_op = jsonObjectGetString(
3011 jsonObjectGetKeyConst( snode, "filter_op" ) );
3012 if( filter_op && !strcasecmp( "or",filter_op )) {
3013 buffer_add( join_buf, " OR " );
3015 buffer_add( join_buf, " AND " );
3018 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3020 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3021 OSRF_BUFFER_ADD( join_buf, jpred );
3026 "%s: JOIN failed. Invalid conditional expression.",
3029 jsonIteratorFree( search_itr );
3030 buffer_free( join_buf );
3032 jsonObjectFree( freeable_hash );
3037 buffer_add( join_buf, " ) " );
3039 // Recursively add a nested join, if one is present
3040 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3042 char* jpred = searchJOIN( join_filter, right_info );
3044 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3045 OSRF_BUFFER_ADD( join_buf, jpred );
3048 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3049 jsonIteratorFree( search_itr );
3050 buffer_free( join_buf );
3052 jsonObjectFree( freeable_hash );
3059 jsonObjectFree( freeable_hash );
3060 jsonIteratorFree( search_itr );
3062 return buffer_release( join_buf );
3067 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3068 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3069 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3071 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3073 search_hash is the JSON expression of the conditions.
3074 meta is the class definition from the IDL, for the relevant table.
3075 opjoin_type indicates whether multiple conditions, if present, should be
3076 connected by AND or OR.
3077 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3078 to pass it to other functions -- and all they do with it is to use the session
3079 and request members to send error messages back to the client.
3083 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3084 int opjoin_type, osrfMethodContext* ctx ) {
3088 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3089 "opjoin_type = %d, ctx addr = %p",
3092 class_info->class_def,
3097 growing_buffer* sql_buf = buffer_init( 128 );
3099 jsonObject* node = NULL;
3102 if( search_hash->type == JSON_ARRAY ) {
3103 if( 0 == search_hash->size ) {
3106 "%s: Invalid predicate structure: empty JSON array",
3109 buffer_free( sql_buf );
3113 unsigned long i = 0;
3114 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3118 if( opjoin_type == OR_OP_JOIN )
3119 buffer_add( sql_buf, " OR " );
3121 buffer_add( sql_buf, " AND " );
3124 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3126 buffer_free( sql_buf );
3130 buffer_fadd( sql_buf, "( %s )", subpred );
3134 } else if( search_hash->type == JSON_HASH ) {
3135 osrfLogDebug( OSRF_LOG_MARK,
3136 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3137 jsonIterator* search_itr = jsonNewIterator( search_hash );
3138 if( !jsonIteratorHasNext( search_itr ) ) {
3141 "%s: Invalid predicate structure: empty JSON object",
3144 jsonIteratorFree( search_itr );
3145 buffer_free( sql_buf );
3149 while( (node = jsonIteratorNext( search_itr )) ) {
3154 if( opjoin_type == OR_OP_JOIN )
3155 buffer_add( sql_buf, " OR " );
3157 buffer_add( sql_buf, " AND " );
3160 if( '+' == search_itr->key[ 0 ] ) {
3162 // This plus sign prefixes a class name or other table alias;
3163 // make sure the table alias is in scope
3164 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3165 if( ! alias_info ) {
3168 "%s: Invalid table alias \"%s\" in WHERE clause",
3172 jsonIteratorFree( search_itr );
3173 buffer_free( sql_buf );
3177 if( node->type == JSON_STRING ) {
3178 // It's the name of a column; make sure it belongs to the class
3179 const char* fieldname = jsonObjectGetString( node );
3180 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3183 "%s: Invalid column name \"%s\" in WHERE clause "
3184 "for table alias \"%s\"",
3189 jsonIteratorFree( search_itr );
3190 buffer_free( sql_buf );
3194 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3196 // It's something more complicated
3197 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3199 jsonIteratorFree( search_itr );
3200 buffer_free( sql_buf );
3204 buffer_fadd( sql_buf, "( %s )", subpred );
3207 } else if( '-' == search_itr->key[ 0 ] ) {
3208 if( !strcasecmp( "-or", search_itr->key )) {
3209 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3211 jsonIteratorFree( search_itr );
3212 buffer_free( sql_buf );
3216 buffer_fadd( sql_buf, "( %s )", subpred );
3218 } else if( !strcasecmp( "-and", search_itr->key )) {
3219 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3221 jsonIteratorFree( search_itr );
3222 buffer_free( sql_buf );
3226 buffer_fadd( sql_buf, "( %s )", subpred );
3228 } else if( !strcasecmp("-not",search_itr->key) ) {
3229 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3231 jsonIteratorFree( search_itr );
3232 buffer_free( sql_buf );
3236 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3238 } else if( !strcasecmp( "-exists", search_itr->key )) {
3239 char* subpred = buildQuery( ctx, node, SUBSELECT );
3241 jsonIteratorFree( search_itr );
3242 buffer_free( sql_buf );
3246 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3248 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3249 char* subpred = buildQuery( ctx, node, SUBSELECT );
3251 jsonIteratorFree( search_itr );
3252 buffer_free( sql_buf );
3256 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3258 } else { // Invalid "minus" operator
3261 "%s: Invalid operator \"%s\" in WHERE clause",
3265 jsonIteratorFree( search_itr );
3266 buffer_free( sql_buf );
3272 const char* class = class_info->class_name;
3273 osrfHash* fields = class_info->fields;
3274 osrfHash* field = osrfHashGet( fields, search_itr->key );
3277 const char* table = class_info->source_def;
3280 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3283 table ? table : "?",
3286 jsonIteratorFree( search_itr );
3287 buffer_free( sql_buf );
3291 char* subpred = searchPredicate( class_info, field, node, ctx );
3293 buffer_free( sql_buf );
3294 jsonIteratorFree( search_itr );
3298 buffer_add( sql_buf, subpred );
3302 jsonIteratorFree( search_itr );
3305 // ERROR ... only hash and array allowed at this level
3306 char* predicate_string = jsonObjectToJSON( search_hash );
3309 "%s: Invalid predicate structure: %s",
3313 buffer_free( sql_buf );
3314 free( predicate_string );
3318 return buffer_release( sql_buf );
3321 /* Build a JSON_ARRAY of field names for a given table alias
3323 static jsonObject* defaultSelectList( const char* table_alias ) {
3328 ClassInfo* class_info = search_all_alias( table_alias );
3329 if( ! class_info ) {
3332 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3339 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3340 osrfHash* field_def = NULL;
3341 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3342 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3343 const char* field_name = osrfHashIteratorKey( field_itr );
3344 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3345 jsonObjectPush( array, jsonNewObject( field_name ) );
3348 osrfHashIteratorFree( field_itr );
3353 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3354 // The jsonObject must be a JSON_HASH with an single entry for "union",
3355 // "intersect", or "except". The data associated with this key must be an
3356 // array of hashes, each hash being a query.
3357 // Also allowed but currently ignored: entries for "order_by" and "alias".
3358 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3360 if( ! combo || combo->type != JSON_HASH )
3361 return NULL; // should be impossible; validated by caller
3363 const jsonObject* query_array = NULL; // array of subordinate queries
3364 const char* op = NULL; // name of operator, e.g. UNION
3365 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3366 int op_count = 0; // for detecting conflicting operators
3367 int excepting = 0; // boolean
3368 int all = 0; // boolean
3369 jsonObject* order_obj = NULL;
3371 // Identify the elements in the hash
3372 jsonIterator* query_itr = jsonNewIterator( combo );
3373 jsonObject* curr_obj = NULL;
3374 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3375 if( ! strcmp( "union", query_itr->key ) ) {
3378 query_array = curr_obj;
3379 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3382 query_array = curr_obj;
3383 } else if( ! strcmp( "except", query_itr->key ) ) {
3387 query_array = curr_obj;
3388 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3391 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3394 order_obj = curr_obj;
3395 } else if( ! strcmp( "alias", query_itr->key ) ) {
3396 if( curr_obj->type != JSON_STRING ) {
3397 jsonIteratorFree( query_itr );
3400 alias = jsonObjectGetString( curr_obj );
3401 } else if( ! strcmp( "all", query_itr->key ) ) {
3402 if( obj_is_true( curr_obj ) )
3406 osrfAppSessionStatus(
3408 OSRF_STATUS_INTERNALSERVERERROR,
3409 "osrfMethodException",
3411 "Malformed query; unexpected entry in query object"
3415 "%s: Unexpected entry for \"%s\" in%squery",
3420 jsonIteratorFree( query_itr );
3424 jsonIteratorFree( query_itr );
3426 // More sanity checks
3427 if( ! query_array ) {
3429 osrfAppSessionStatus(
3431 OSRF_STATUS_INTERNALSERVERERROR,
3432 "osrfMethodException",
3434 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3438 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3441 return NULL; // should be impossible...
3442 } else if( op_count > 1 ) {
3444 osrfAppSessionStatus(
3446 OSRF_STATUS_INTERNALSERVERERROR,
3447 "osrfMethodException",
3449 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3453 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3457 } if( query_array->type != JSON_ARRAY ) {
3459 osrfAppSessionStatus(
3461 OSRF_STATUS_INTERNALSERVERERROR,
3462 "osrfMethodException",
3464 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3468 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3471 json_type( query_array->type )
3474 } if( query_array->size < 2 ) {
3476 osrfAppSessionStatus(
3478 OSRF_STATUS_INTERNALSERVERERROR,
3479 "osrfMethodException",
3481 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3485 "%s:%srequires multiple queries as operands",
3490 } else if( excepting && query_array->size > 2 ) {
3492 osrfAppSessionStatus(
3494 OSRF_STATUS_INTERNALSERVERERROR,
3495 "osrfMethodException",
3497 "EXCEPT operator has too many queries as operands"
3501 "%s:EXCEPT operator has too many queries as operands",
3505 } else if( order_obj && ! alias ) {
3507 osrfAppSessionStatus(
3509 OSRF_STATUS_INTERNALSERVERERROR,
3510 "osrfMethodException",
3512 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3516 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3522 // So far so good. Now build the SQL.
3523 growing_buffer* sql = buffer_init( 256 );
3525 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3526 // Add a layer of parentheses
3527 if( flags & SUBCOMBO )
3528 OSRF_BUFFER_ADD( sql, "( " );
3530 // Traverse the query array. Each entry should be a hash.
3531 int first = 1; // boolean
3533 jsonObject* query = NULL;
3534 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3535 if( query->type != JSON_HASH ) {
3537 osrfAppSessionStatus(
3539 OSRF_STATUS_INTERNALSERVERERROR,
3540 "osrfMethodException",
3542 "Malformed query under UNION, INTERSECT or EXCEPT"
3546 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3549 json_type( query->type )
3558 OSRF_BUFFER_ADD( sql, op );
3560 OSRF_BUFFER_ADD( sql, "ALL " );
3563 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3567 "%s: Error building query under%s",
3575 OSRF_BUFFER_ADD( sql, query_str );
3578 if( flags & SUBCOMBO )
3579 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3581 if( !(flags & SUBSELECT) )
3582 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3584 return buffer_release( sql );
3587 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3588 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3589 // or "except" to indicate the type of query.
3590 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3594 osrfAppSessionStatus(
3596 OSRF_STATUS_INTERNALSERVERERROR,
3597 "osrfMethodException",
3599 "Malformed query; no query object"
3601 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3603 } else if( query->type != JSON_HASH ) {
3605 osrfAppSessionStatus(
3607 OSRF_STATUS_INTERNALSERVERERROR,
3608 "osrfMethodException",
3610 "Malformed query object"
3614 "%s: Query object is %s instead of JSON_HASH",
3616 json_type( query->type )
3621 // Determine what kind of query it purports to be, and dispatch accordingly.
3622 if( jsonObjectGetKey( query, "union" ) ||
3623 jsonObjectGetKey( query, "intersect" ) ||
3624 jsonObjectGetKey( query, "except" ) ) {
3625 return doCombo( ctx, query, flags );
3627 // It is presumably a SELECT query
3629 // Push a node onto the stack for the current query. Every level of
3630 // subquery gets its own QueryFrame on the Stack.
3633 // Build an SQL SELECT statement
3636 jsonObjectGetKey( query, "select" ),
3637 jsonObjectGetKey( query, "from" ),
3638 jsonObjectGetKey( query, "where" ),
3639 jsonObjectGetKey( query, "having" ),
3640 jsonObjectGetKey( query, "order_by" ),
3641 jsonObjectGetKey( query, "limit" ),
3642 jsonObjectGetKey( query, "offset" ),
3651 /* method context */ osrfMethodContext* ctx,
3653 /* SELECT */ jsonObject* selhash,
3654 /* FROM */ jsonObject* join_hash,
3655 /* WHERE */ jsonObject* search_hash,
3656 /* HAVING */ jsonObject* having_hash,
3657 /* ORDER BY */ jsonObject* order_hash,
3658 /* LIMIT */ jsonObject* limit,
3659 /* OFFSET */ jsonObject* offset,
3660 /* flags */ int flags
3662 const char* locale = osrf_message_get_last_locale();
3664 // general tmp objects
3665 const jsonObject* tmp_const;
3666 jsonObject* selclass = NULL;
3667 jsonObject* snode = NULL;
3668 jsonObject* onode = NULL;
3670 char* string = NULL;
3671 int from_function = 0;
3676 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3678 // punt if there's no FROM clause
3679 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3682 "%s: FROM clause is missing or empty",
3686 osrfAppSessionStatus(
3688 OSRF_STATUS_INTERNALSERVERERROR,
3689 "osrfMethodException",
3691 "FROM clause is missing or empty in JSON query"
3696 // the core search class
3697 const char* core_class = NULL;
3699 // get the core class -- the only key of the top level FROM clause, or a string
3700 if( join_hash->type == JSON_HASH ) {
3701 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3702 snode = jsonIteratorNext( tmp_itr );
3704 // Populate the current QueryFrame with information
3705 // about the core class
3706 if( add_query_core( NULL, tmp_itr->key ) ) {
3708 osrfAppSessionStatus(
3710 OSRF_STATUS_INTERNALSERVERERROR,
3711 "osrfMethodException",
3713 "Unable to look up core class"
3717 core_class = curr_query->core.class_name;
3720 jsonObject* extra = jsonIteratorNext( tmp_itr );
3722 jsonIteratorFree( tmp_itr );
3725 // There shouldn't be more than one entry in join_hash
3729 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3733 osrfAppSessionStatus(
3735 OSRF_STATUS_INTERNALSERVERERROR,
3736 "osrfMethodException",
3738 "Malformed FROM clause in JSON query"
3740 return NULL; // Malformed join_hash; extra entry
3742 } else if( join_hash->type == JSON_ARRAY ) {
3743 // We're selecting from a function, not from a table
3745 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3748 } else if( join_hash->type == JSON_STRING ) {
3749 // Populate the current QueryFrame with information
3750 // about the core class
3751 core_class = jsonObjectGetString( join_hash );
3753 if( add_query_core( NULL, core_class ) ) {
3755 osrfAppSessionStatus(
3757 OSRF_STATUS_INTERNALSERVERERROR,
3758 "osrfMethodException",
3760 "Unable to look up core class"
3768 "%s: FROM clause is unexpected JSON type: %s",
3770 json_type( join_hash->type )
3773 osrfAppSessionStatus(
3775 OSRF_STATUS_INTERNALSERVERERROR,
3776 "osrfMethodException",
3778 "Ill-formed FROM clause in JSON query"
3783 // Build the join clause, if any, while filling out the list
3784 // of joined classes in the current QueryFrame.
3785 char* join_clause = NULL;
3786 if( join_hash && ! from_function ) {
3788 join_clause = searchJOIN( join_hash, &curr_query->core );
3789 if( ! join_clause ) {
3791 osrfAppSessionStatus(
3793 OSRF_STATUS_INTERNALSERVERERROR,
3794 "osrfMethodException",
3796 "Unable to construct JOIN clause(s)"
3802 // For in case we don't get a select list
3803 jsonObject* defaultselhash = NULL;
3805 // if there is no select list, build a default select list ...
3806 if( !selhash && !from_function ) {
3807 jsonObject* default_list = defaultSelectList( core_class );
3808 if( ! default_list ) {
3810 osrfAppSessionStatus(
3812 OSRF_STATUS_INTERNALSERVERERROR,
3813 "osrfMethodException",
3815 "Unable to build default SELECT clause in JSON query"
3817 free( join_clause );
3822 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3823 jsonObjectSetKey( selhash, core_class, default_list );
3826 // The SELECT clause can be encoded only by a hash
3827 if( !from_function && selhash->type != JSON_HASH ) {
3830 "%s: Expected JSON_HASH for SELECT clause; found %s",
3832 json_type( selhash->type )
3836 osrfAppSessionStatus(
3838 OSRF_STATUS_INTERNALSERVERERROR,
3839 "osrfMethodException",
3841 "Malformed SELECT clause in JSON query"
3843 free( join_clause );
3847 // If you see a null or wild card specifier for the core class, or an
3848 // empty array, replace it with a default SELECT list
3849 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3851 int default_needed = 0; // boolean
3852 if( JSON_STRING == tmp_const->type
3853 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3855 else if( JSON_NULL == tmp_const->type )
3858 if( default_needed ) {
3859 // Build a default SELECT list
3860 jsonObject* default_list = defaultSelectList( core_class );
3861 if( ! default_list ) {
3863 osrfAppSessionStatus(
3865 OSRF_STATUS_INTERNALSERVERERROR,
3866 "osrfMethodException",
3868 "Can't build default SELECT clause in JSON query"
3870 free( join_clause );
3875 jsonObjectSetKey( selhash, core_class, default_list );
3879 // temp buffers for the SELECT list and GROUP BY clause
3880 growing_buffer* select_buf = buffer_init( 128 );
3881 growing_buffer* group_buf = buffer_init( 128 );
3883 int aggregate_found = 0; // boolean
3885 // Build a select list
3886 if( from_function ) // From a function we select everything
3887 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3890 // Build the SELECT list as SQL
3894 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3895 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3897 const char* cname = selclass_itr->key;
3899 // Make sure the target relation is in the FROM clause.
3901 // At this point join_hash is a step down from the join_hash we
3902 // received as a parameter. If the original was a JSON_STRING,
3903 // then json_hash is now NULL. If the original was a JSON_HASH,
3904 // then json_hash is now the first (and only) entry in it,
3905 // denoting the core class. We've already excluded the
3906 // possibility that the original was a JSON_ARRAY, because in
3907 // that case from_function would be non-NULL, and we wouldn't
3910 // If the current table alias isn't in scope, bail out
3911 ClassInfo* class_info = search_alias( cname );
3912 if( ! class_info ) {
3915 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3920 osrfAppSessionStatus(
3922 OSRF_STATUS_INTERNALSERVERERROR,
3923 "osrfMethodException",
3925 "Selected class not in FROM clause in JSON query"
3927 jsonIteratorFree( selclass_itr );
3928 buffer_free( select_buf );
3929 buffer_free( group_buf );
3930 if( defaultselhash )
3931 jsonObjectFree( defaultselhash );
3932 free( join_clause );
3936 if( selclass->type != JSON_ARRAY ) {
3939 "%s: Malformed SELECT list for class \"%s\"; not an array",
3944 osrfAppSessionStatus(
3946 OSRF_STATUS_INTERNALSERVERERROR,
3947 "osrfMethodException",
3949 "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 // Look up some attributes of the current class
3962 osrfHash* idlClass = class_info->class_def;
3963 osrfHash* class_field_set = class_info->fields;
3964 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3965 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3967 if( 0 == selclass->size ) {
3970 "%s: No columns selected from \"%s\"",
3976 // stitch together the column list for the current table alias...
3977 unsigned long field_idx = 0;
3978 jsonObject* selfield = NULL;
3979 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3981 // If we need a separator comma, add one
3985 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3988 // if the field specification is a string, add it to the list
3989 if( selfield->type == JSON_STRING ) {
3991 // Look up the field in the IDL
3992 const char* col_name = jsonObjectGetString( selfield );
3993 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3995 // No such field in current class
3998 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4004 osrfAppSessionStatus(
4006 OSRF_STATUS_INTERNALSERVERERROR,
4007 "osrfMethodException",
4009 "Selected column not defined in JSON query"
4011 jsonIteratorFree( selclass_itr );
4012 buffer_free( select_buf );
4013 buffer_free( group_buf );
4014 if( defaultselhash )
4015 jsonObjectFree( defaultselhash );
4016 free( join_clause );
4018 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4019 // Virtual field not allowed
4022 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4028 osrfAppSessionStatus(
4030 OSRF_STATUS_INTERNALSERVERERROR,
4031 "osrfMethodException",
4033 "Selected column may not be virtual in JSON query"
4035 jsonIteratorFree( selclass_itr );
4036 buffer_free( select_buf );
4037 buffer_free( group_buf );
4038 if( defaultselhash )
4039 jsonObjectFree( defaultselhash );
4040 free( join_clause );
4046 if( flags & DISABLE_I18N )
4049 i18n = osrfHashGet( field_def, "i18n" );
4051 if( str_is_true( i18n ) ) {
4052 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4053 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4054 class_tname, cname, col_name, class_pkey,
4055 cname, class_pkey, locale, col_name );
4057 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4058 cname, col_name, col_name );
4061 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4062 cname, col_name, col_name );
4065 // ... but it could be an object, in which case we check for a Field Transform
4066 } else if( selfield->type == JSON_HASH ) {
4068 const char* col_name = jsonObjectGetString(
4069 jsonObjectGetKeyConst( selfield, "column" ) );
4071 // Get the field definition from the IDL
4072 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4074 // No such field in current class
4077 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4083 osrfAppSessionStatus(
4085 OSRF_STATUS_INTERNALSERVERERROR,
4086 "osrfMethodException",
4088 "Selected column is not defined in JSON query"
4090 jsonIteratorFree( selclass_itr );
4091 buffer_free( select_buf );
4092 buffer_free( group_buf );
4093 if( defaultselhash )
4094 jsonObjectFree( defaultselhash );
4095 free( join_clause );
4097 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4098 // No such field in current class
4101 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4107 osrfAppSessionStatus(
4109 OSRF_STATUS_INTERNALSERVERERROR,
4110 "osrfMethodException",
4112 "Selected column is virtual in JSON query"
4114 jsonIteratorFree( selclass_itr );
4115 buffer_free( select_buf );
4116 buffer_free( group_buf );
4117 if( defaultselhash )
4118 jsonObjectFree( defaultselhash );
4119 free( join_clause );
4123 // Decide what to use as a column alias
4125 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4126 _alias = jsonObjectGetString( tmp_const );
4127 } else { // Use field name as the alias
4131 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4132 char* transform_str = searchFieldTransform(
4133 class_info->alias, field_def, selfield );
4134 if( transform_str ) {
4135 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4136 free( transform_str );
4139 osrfAppSessionStatus(
4141 OSRF_STATUS_INTERNALSERVERERROR,
4142 "osrfMethodException",
4144 "Unable to generate transform function in JSON query"
4146 jsonIteratorFree( selclass_itr );
4147 buffer_free( select_buf );
4148 buffer_free( group_buf );
4149 if( defaultselhash )
4150 jsonObjectFree( defaultselhash );
4151 free( join_clause );
4158 if( flags & DISABLE_I18N )
4161 i18n = osrfHashGet( field_def, "i18n" );
4163 if( str_is_true( i18n ) ) {
4164 buffer_fadd( select_buf,
4165 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4166 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4167 class_tname, cname, col_name, class_pkey, cname,
4168 class_pkey, locale, _alias );
4170 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4171 cname, col_name, _alias );
4174 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4175 cname, col_name, _alias );
4182 "%s: Selected item is unexpected JSON type: %s",
4184 json_type( selfield->type )
4187 osrfAppSessionStatus(
4189 OSRF_STATUS_INTERNALSERVERERROR,
4190 "osrfMethodException",
4192 "Ill-formed SELECT item in JSON query"
4194 jsonIteratorFree( selclass_itr );
4195 buffer_free( select_buf );
4196 buffer_free( group_buf );
4197 if( defaultselhash )
4198 jsonObjectFree( defaultselhash );
4199 free( join_clause );
4203 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4204 if( obj_is_true( agg_obj ) )
4205 aggregate_found = 1;
4207 // Append a comma (except for the first one)
4208 // and add the column to a GROUP BY clause
4212 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4214 buffer_fadd( group_buf, " %d", sel_pos );
4218 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4220 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4221 if ( ! obj_is_true( aggregate_obj ) ) {
4225 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4228 buffer_fadd(group_buf, " %d", sel_pos);
4231 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4235 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4238 _column = searchFieldTransform(class_info->alias, field, selfield);
4239 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4240 OSRF_BUFFER_ADD(group_buf, _column);
4241 _column = searchFieldTransform(class_info->alias, field, selfield);
4248 } // end while -- iterating across SELECT columns
4250 } // end while -- iterating across classes
4252 jsonIteratorFree( selclass_itr );
4256 char* col_list = buffer_release( select_buf );
4258 // Make sure the SELECT list isn't empty. This can happen, for example,
4259 // if we try to build a default SELECT clause from a non-core table.
4262 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4264 osrfAppSessionStatus(
4266 OSRF_STATUS_INTERNALSERVERERROR,
4267 "osrfMethodException",
4269 "SELECT list is empty"
4272 buffer_free( group_buf );
4273 if( defaultselhash )
4274 jsonObjectFree( defaultselhash );
4275 free( join_clause );
4281 table = searchValueTransform( join_hash );
4283 table = strdup( curr_query->core.source_def );
4287 osrfAppSessionStatus(
4289 OSRF_STATUS_INTERNALSERVERERROR,
4290 "osrfMethodException",
4292 "Unable to identify table for core class"
4295 buffer_free( group_buf );
4296 if( defaultselhash )
4297 jsonObjectFree( defaultselhash );
4298 free( join_clause );
4302 // Put it all together
4303 growing_buffer* sql_buf = buffer_init( 128 );
4304 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4308 // Append the join clause, if any
4310 buffer_add(sql_buf, join_clause );
4311 free( join_clause );
4314 char* order_by_list = NULL;
4315 char* having_buf = NULL;
4317 if( !from_function ) {
4319 // Build a WHERE clause, if there is one
4321 buffer_add( sql_buf, " WHERE " );
4323 // and it's on the WHERE clause
4324 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4327 osrfAppSessionStatus(
4329 OSRF_STATUS_INTERNALSERVERERROR,
4330 "osrfMethodException",
4332 "Severe query error in WHERE predicate -- see error log for more details"
4335 buffer_free( group_buf );
4336 buffer_free( sql_buf );
4337 if( defaultselhash )
4338 jsonObjectFree( defaultselhash );
4342 buffer_add( sql_buf, pred );
4346 // Build a HAVING clause, if there is one
4349 // and it's on the the WHERE clause
4350 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4352 if( ! having_buf ) {
4354 osrfAppSessionStatus(
4356 OSRF_STATUS_INTERNALSERVERERROR,
4357 "osrfMethodException",
4359 "Severe query error in HAVING predicate -- see error log for more details"
4362 buffer_free( group_buf );
4363 buffer_free( sql_buf );
4364 if( defaultselhash )
4365 jsonObjectFree( defaultselhash );
4370 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4372 // Build an ORDER BY clause, if there is one
4373 if( NULL == order_hash )
4374 ; // No ORDER BY? do nothing
4375 else if( JSON_ARRAY == order_hash->type ) {
4376 // Array of field specifications, each specification being a
4377 // hash to define the class, field, and other details
4379 jsonObject* order_spec;
4380 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4382 if( JSON_HASH != order_spec->type ) {
4383 osrfLogError( OSRF_LOG_MARK,
4384 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4385 modulename, json_type( order_spec->type ) );
4387 osrfAppSessionStatus(
4389 OSRF_STATUS_INTERNALSERVERERROR,
4390 "osrfMethodException",
4392 "Malformed ORDER BY clause -- see error log for more details"
4394 buffer_free( order_buf );
4396 buffer_free( group_buf );
4397 buffer_free( sql_buf );
4398 if( defaultselhash )
4399 jsonObjectFree( defaultselhash );
4403 const char* class_alias =
4404 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4406 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4409 OSRF_BUFFER_ADD( order_buf, ", " );
4411 order_buf = buffer_init( 128 );
4413 if( !field || !class_alias ) {
4414 osrfLogError( OSRF_LOG_MARK,
4415 "%s: Missing class or field name in field specification "
4416 "of ORDER BY clause",
4419 osrfAppSessionStatus(
4421 OSRF_STATUS_INTERNALSERVERERROR,
4422 "osrfMethodException",
4424 "Malformed ORDER BY clause -- see error log for more details"
4426 buffer_free( order_buf );
4428 buffer_free( group_buf );
4429 buffer_free( sql_buf );
4430 if( defaultselhash )
4431 jsonObjectFree( defaultselhash );
4435 ClassInfo* order_class_info = search_alias( class_alias );
4436 if( ! order_class_info ) {
4437 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4438 "not in FROM clause", modulename, class_alias );
4440 osrfAppSessionStatus(
4442 OSRF_STATUS_INTERNALSERVERERROR,
4443 "osrfMethodException",
4445 "Invalid class referenced in ORDER BY clause -- "
4446 "see error log for more details"
4449 buffer_free( group_buf );
4450 buffer_free( sql_buf );
4451 if( defaultselhash )
4452 jsonObjectFree( defaultselhash );
4456 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4458 osrfLogError( OSRF_LOG_MARK,
4459 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4460 modulename, class_alias, field );
4462 osrfAppSessionStatus(
4464 OSRF_STATUS_INTERNALSERVERERROR,
4465 "osrfMethodException",
4467 "Invalid field referenced in ORDER BY clause -- "
4468 "see error log for more details"
4471 buffer_free( group_buf );
4472 buffer_free( sql_buf );
4473 if( defaultselhash )
4474 jsonObjectFree( defaultselhash );
4476 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4477 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4478 modulename, field );
4480 osrfAppSessionStatus(
4482 OSRF_STATUS_INTERNALSERVERERROR,
4483 "osrfMethodException",
4485 "Virtual field in ORDER BY clause -- see error log for more details"
4487 buffer_free( order_buf );
4489 buffer_free( group_buf );
4490 buffer_free( sql_buf );
4491 if( defaultselhash )
4492 jsonObjectFree( defaultselhash );
4496 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4497 char* transform_str = searchFieldTransform(
4498 class_alias, field_def, order_spec );
4499 if( ! transform_str ) {
4501 osrfAppSessionStatus(
4503 OSRF_STATUS_INTERNALSERVERERROR,
4504 "osrfMethodException",
4506 "Severe query error in ORDER BY clause -- "
4507 "see error log for more details"
4509 buffer_free( order_buf );
4511 buffer_free( group_buf );
4512 buffer_free( sql_buf );
4513 if( defaultselhash )
4514 jsonObjectFree( defaultselhash );
4518 OSRF_BUFFER_ADD( order_buf, transform_str );
4519 free( transform_str );
4522 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4524 const char* direction =
4525 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4527 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4528 OSRF_BUFFER_ADD( order_buf, " DESC" );
4530 OSRF_BUFFER_ADD( order_buf, " ASC" );
4533 } else if( JSON_HASH == order_hash->type ) {
4534 // This hash is keyed on class alias. Each class has either
4535 // an array of field names or a hash keyed on field name.
4536 jsonIterator* class_itr = jsonNewIterator( order_hash );
4537 while( (snode = jsonIteratorNext( class_itr )) ) {
4539 ClassInfo* order_class_info = search_alias( class_itr->key );
4540 if( ! order_class_info ) {
4541 osrfLogError( OSRF_LOG_MARK,
4542 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4543 modulename, class_itr->key );
4545 osrfAppSessionStatus(
4547 OSRF_STATUS_INTERNALSERVERERROR,
4548 "osrfMethodException",
4550 "Invalid class referenced in ORDER BY clause -- "
4551 "see error log for more details"
4553 jsonIteratorFree( class_itr );
4554 buffer_free( order_buf );
4556 buffer_free( group_buf );
4557 buffer_free( sql_buf );
4558 if( defaultselhash )
4559 jsonObjectFree( defaultselhash );
4563 osrfHash* field_list_def = order_class_info->fields;
4565 if( snode->type == JSON_HASH ) {
4567 // Hash is keyed on field names from the current class. For each field
4568 // there is another layer of hash to define the sorting details, if any,
4569 // or a string to indicate direction of sorting.
4570 jsonIterator* order_itr = jsonNewIterator( snode );
4571 while( (onode = jsonIteratorNext( order_itr )) ) {
4573 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4575 osrfLogError( OSRF_LOG_MARK,
4576 "%s: Invalid field \"%s\" in ORDER BY clause",
4577 modulename, order_itr->key );
4579 osrfAppSessionStatus(
4581 OSRF_STATUS_INTERNALSERVERERROR,
4582 "osrfMethodException",
4584 "Invalid field in ORDER BY clause -- "
4585 "see error log for more details"
4587 jsonIteratorFree( order_itr );
4588 jsonIteratorFree( class_itr );
4589 buffer_free( order_buf );
4591 buffer_free( group_buf );
4592 buffer_free( sql_buf );
4593 if( defaultselhash )
4594 jsonObjectFree( defaultselhash );
4596 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4597 osrfLogError( OSRF_LOG_MARK,
4598 "%s: Virtual field \"%s\" in ORDER BY clause",
4599 modulename, order_itr->key );
4601 osrfAppSessionStatus(
4603 OSRF_STATUS_INTERNALSERVERERROR,
4604 "osrfMethodException",
4606 "Virtual field in ORDER BY clause -- "
4607 "see error log for more details"
4609 jsonIteratorFree( order_itr );
4610 jsonIteratorFree( class_itr );
4611 buffer_free( order_buf );
4613 buffer_free( group_buf );
4614 buffer_free( sql_buf );
4615 if( defaultselhash )
4616 jsonObjectFree( defaultselhash );
4620 const char* direction = NULL;
4621 if( onode->type == JSON_HASH ) {
4622 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4623 string = searchFieldTransform(
4625 osrfHashGet( field_list_def, order_itr->key ),
4629 if( ctx ) osrfAppSessionStatus(
4631 OSRF_STATUS_INTERNALSERVERERROR,
4632 "osrfMethodException",
4634 "Severe query error in ORDER BY clause -- "
4635 "see error log for more details"
4637 jsonIteratorFree( order_itr );
4638 jsonIteratorFree( class_itr );
4640 buffer_free( group_buf );
4641 buffer_free( order_buf);
4642 buffer_free( sql_buf );
4643 if( defaultselhash )
4644 jsonObjectFree( defaultselhash );
4648 growing_buffer* field_buf = buffer_init( 16 );
4649 buffer_fadd( field_buf, "\"%s\".%s",
4650 class_itr->key, order_itr->key );
4651 string = buffer_release( field_buf );
4654 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4655 const char* dir = jsonObjectGetString( tmp_const );
4656 if(!strncasecmp( dir, "d", 1 )) {
4657 direction = " DESC";
4663 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4664 osrfLogError( OSRF_LOG_MARK,
4665 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4666 modulename, json_type( onode->type ) );
4668 osrfAppSessionStatus(
4670 OSRF_STATUS_INTERNALSERVERERROR,
4671 "osrfMethodException",
4673 "Malformed ORDER BY clause -- see error log for more details"
4675 jsonIteratorFree( order_itr );
4676 jsonIteratorFree( class_itr );
4678 buffer_free( group_buf );
4679 buffer_free( order_buf );
4680 buffer_free( sql_buf );
4681 if( defaultselhash )
4682 jsonObjectFree( defaultselhash );
4686 string = strdup( order_itr->key );
4687 const char* dir = jsonObjectGetString( onode );
4688 if( !strncasecmp( dir, "d", 1 )) {
4689 direction = " DESC";
4696 OSRF_BUFFER_ADD( order_buf, ", " );
4698 order_buf = buffer_init( 128 );
4700 OSRF_BUFFER_ADD( order_buf, string );
4704 OSRF_BUFFER_ADD( order_buf, direction );
4708 jsonIteratorFree( order_itr );
4710 } else if( snode->type == JSON_ARRAY ) {
4712 // Array is a list of fields from the current class
4713 unsigned long order_idx = 0;
4714 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4716 const char* _f = jsonObjectGetString( onode );
4718 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4720 osrfLogError( OSRF_LOG_MARK,
4721 "%s: Invalid field \"%s\" in ORDER BY clause",
4724 osrfAppSessionStatus(
4726 OSRF_STATUS_INTERNALSERVERERROR,
4727 "osrfMethodException",
4729 "Invalid field in ORDER BY clause -- "
4730 "see error log for more details"
4732 jsonIteratorFree( class_itr );
4733 buffer_free( order_buf );
4735 buffer_free( group_buf );
4736 buffer_free( sql_buf );
4737 if( defaultselhash )
4738 jsonObjectFree( defaultselhash );
4740 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4741 osrfLogError( OSRF_LOG_MARK,
4742 "%s: Virtual field \"%s\" in ORDER BY clause",
4745 osrfAppSessionStatus(
4747 OSRF_STATUS_INTERNALSERVERERROR,
4748 "osrfMethodException",
4750 "Virtual field in ORDER BY clause -- "
4751 "see error log for more details"
4753 jsonIteratorFree( class_itr );
4754 buffer_free( order_buf );
4756 buffer_free( group_buf );
4757 buffer_free( sql_buf );
4758 if( defaultselhash )
4759 jsonObjectFree( defaultselhash );
4764 OSRF_BUFFER_ADD( order_buf, ", " );
4766 order_buf = buffer_init( 128 );
4768 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4772 // IT'S THE OOOOOOOOOOOLD STYLE!
4774 osrfLogError( OSRF_LOG_MARK,
4775 "%s: Possible SQL injection attempt; direct order by is not allowed",
4778 osrfAppSessionStatus(
4780 OSRF_STATUS_INTERNALSERVERERROR,
4781 "osrfMethodException",
4783 "Severe query error -- see error log for more details"
4788 buffer_free( group_buf );
4789 buffer_free( order_buf );
4790 buffer_free( sql_buf );
4791 if( defaultselhash )
4792 jsonObjectFree( defaultselhash );
4793 jsonIteratorFree( class_itr );
4797 jsonIteratorFree( class_itr );
4799 osrfLogError( OSRF_LOG_MARK,
4800 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4801 modulename, json_type( order_hash->type ) );
4803 osrfAppSessionStatus(
4805 OSRF_STATUS_INTERNALSERVERERROR,
4806 "osrfMethodException",
4808 "Malformed ORDER BY clause -- see error log for more details"
4810 buffer_free( order_buf );
4812 buffer_free( group_buf );
4813 buffer_free( sql_buf );
4814 if( defaultselhash )
4815 jsonObjectFree( defaultselhash );
4820 order_by_list = buffer_release( order_buf );
4824 string = buffer_release( group_buf );
4826 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4827 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4828 OSRF_BUFFER_ADD( sql_buf, string );
4833 if( having_buf && *having_buf ) {
4834 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4835 OSRF_BUFFER_ADD( sql_buf, having_buf );
4839 if( order_by_list ) {
4841 if( *order_by_list ) {
4842 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4843 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4846 free( order_by_list );
4850 const char* str = jsonObjectGetString( limit );
4851 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4855 const char* str = jsonObjectGetString( offset );
4856 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4859 if( !(flags & SUBSELECT) )
4860 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4862 if( defaultselhash )
4863 jsonObjectFree( defaultselhash );
4865 return buffer_release( sql_buf );
4867 } // end of SELECT()
4869 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4871 const char* locale = osrf_message_get_last_locale();
4873 osrfHash* fields = osrfHashGet( meta, "fields" );
4874 char* core_class = osrfHashGet( meta, "classname" );
4876 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4878 jsonObject* node = NULL;
4879 jsonObject* snode = NULL;
4880 jsonObject* onode = NULL;
4881 const jsonObject* _tmp = NULL;
4882 jsonObject* selhash = NULL;
4883 jsonObject* defaultselhash = NULL;
4885 growing_buffer* sql_buf = buffer_init( 128 );
4886 growing_buffer* select_buf = buffer_init( 128 );
4888 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4889 defaultselhash = jsonNewObjectType( JSON_HASH );
4890 selhash = defaultselhash;
4893 // If there's no SELECT list for the core class, build one
4894 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4895 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4897 // Add every non-virtual field to the field list
4898 osrfHash* field_def = NULL;
4899 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4900 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4901 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4902 const char* field = osrfHashIteratorKey( field_itr );
4903 jsonObjectPush( field_list, jsonNewObject( field ) );
4906 osrfHashIteratorFree( field_itr );
4907 jsonObjectSetKey( selhash, core_class, field_list );
4911 jsonIterator* class_itr = jsonNewIterator( selhash );
4912 while( (snode = jsonIteratorNext( class_itr )) ) {
4914 const char* cname = class_itr->key;
4915 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4919 if( strcmp(core_class,class_itr->key )) {
4923 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4924 if( !found->size ) {
4925 jsonObjectFree( found );
4929 jsonObjectFree( found );
4932 jsonIterator* select_itr = jsonNewIterator( snode );
4933 while( (node = jsonIteratorNext( select_itr )) ) {
4934 const char* item_str = jsonObjectGetString( node );
4935 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4936 char* fname = osrfHashGet( field, "name" );
4944 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4949 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4950 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4953 i18n = osrfHashGet( field, "i18n" );
4955 if( str_is_true( i18n ) ) {
4956 char* pkey = osrfHashGet( idlClass, "primarykey" );
4957 char* tname = osrfHashGet( idlClass, "tablename" );
4959 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4960 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4961 tname, cname, fname, pkey, cname, pkey, locale, fname );
4963 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4966 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4970 jsonIteratorFree( select_itr );
4973 jsonIteratorFree( class_itr );
4975 char* col_list = buffer_release( select_buf );
4976 char* table = oilsGetRelation( meta );
4978 table = strdup( "(null)" );
4980 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4984 // Clear the query stack (as a fail-safe precaution against possible
4985 // leftover garbage); then push the first query frame onto the stack.
4986 clear_query_stack();
4988 if( add_query_core( NULL, core_class ) ) {
4990 osrfAppSessionStatus(
4992 OSRF_STATUS_INTERNALSERVERERROR,
4993 "osrfMethodException",
4995 "Unable to build query frame for core class"
5001 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5002 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5003 OSRF_BUFFER_ADD( sql_buf, join_clause );
5004 free( join_clause );
5007 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5008 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5010 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5012 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5014 osrfAppSessionStatus(
5016 OSRF_STATUS_INTERNALSERVERERROR,
5017 "osrfMethodException",
5019 "Severe query error -- see error log for more details"
5021 buffer_free( sql_buf );
5022 if( defaultselhash )
5023 jsonObjectFree( defaultselhash );
5024 clear_query_stack();
5027 buffer_add( sql_buf, pred );
5032 char* string = NULL;
5033 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5035 growing_buffer* order_buf = buffer_init( 128 );
5038 jsonIterator* class_itr = jsonNewIterator( _tmp );
5039 while( (snode = jsonIteratorNext( class_itr )) ) {
5041 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
5044 if( snode->type == JSON_HASH ) {
5046 jsonIterator* order_itr = jsonNewIterator( snode );
5047 while( (onode = jsonIteratorNext( order_itr )) ) {
5049 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5050 class_itr->key, order_itr->key );
5054 char* direction = NULL;
5055 if( onode->type == JSON_HASH ) {
5056 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5057 string = searchFieldTransform( class_itr->key, field_def, onode );
5059 osrfAppSessionStatus(
5061 OSRF_STATUS_INTERNALSERVERERROR,
5062 "osrfMethodException",
5064 "Severe query error in ORDER BY clause -- "
5065 "see error log for more details"
5067 jsonIteratorFree( order_itr );
5068 jsonIteratorFree( class_itr );
5069 buffer_free( order_buf );
5070 buffer_free( sql_buf );
5071 if( defaultselhash )
5072 jsonObjectFree( defaultselhash );
5073 clear_query_stack();
5077 growing_buffer* field_buf = buffer_init( 16 );
5078 buffer_fadd( field_buf, "\"%s\".%s",
5079 class_itr->key, order_itr->key );
5080 string = buffer_release( field_buf );
5083 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5084 const char* dir = jsonObjectGetString( _tmp );
5085 if(!strncasecmp( dir, "d", 1 )) {
5086 direction = " DESC";
5090 string = strdup( order_itr->key );
5091 const char* dir = jsonObjectGetString( onode );
5092 if( !strncasecmp( dir, "d", 1 )) {
5093 direction = " DESC";
5102 buffer_add( order_buf, ", " );
5105 buffer_add( order_buf, string );
5109 buffer_add( order_buf, direction );
5113 jsonIteratorFree( order_itr );
5116 const char* str = jsonObjectGetString( snode );
5117 buffer_add( order_buf, str );
5123 jsonIteratorFree( class_itr );
5125 string = buffer_release( order_buf );
5128 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5129 OSRF_BUFFER_ADD( sql_buf, string );
5135 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5136 const char* str = jsonObjectGetString( _tmp );
5144 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5146 const char* str = jsonObjectGetString( _tmp );
5155 if( defaultselhash )
5156 jsonObjectFree( defaultselhash );
5157 clear_query_stack();
5159 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5160 return buffer_release( sql_buf );
5163 int doJSONSearch ( osrfMethodContext* ctx ) {
5164 if(osrfMethodVerifyContext( ctx )) {
5165 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5169 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5173 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5177 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5178 flags |= SELECT_DISTINCT;
5180 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5181 flags |= DISABLE_I18N;
5183 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5184 clear_query_stack(); // a possibly needless precaution
5185 char* sql = buildQuery( ctx, hash, flags );
5186 clear_query_stack();
5193 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5196 dbhandle = writehandle;
5198 dbi_result result = dbi_conn_query( dbhandle, sql );
5201 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5203 if( dbi_result_first_row( result )) {
5204 /* JSONify the result */
5205 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5208 jsonObject* return_val = oilsMakeJSONFromResult( result );
5209 osrfAppRespond( ctx, return_val );
5210 jsonObjectFree( return_val );
5211 } while( dbi_result_next_row( result ));
5214 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5217 osrfAppRespondComplete( ctx, NULL );
5219 /* clean up the query */
5220 dbi_result_free( result );
5225 int errnum = dbi_conn_error( dbhandle, &msg );
5226 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5227 modulename, sql, errnum, msg ? msg : "(No description available)" );
5228 osrfAppSessionStatus(
5230 OSRF_STATUS_INTERNALSERVERERROR,
5231 "osrfMethodException",
5233 "Severe query error -- see error log for more details"
5235 if( !oilsIsDBConnected( dbhandle ))
5236 osrfAppSessionPanic( ctx->session );
5243 // The last parameter, err, is used to report an error condition by updating an int owned by
5244 // the calling code.
5246 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5247 // It is the responsibility of the calling code to initialize *err before the
5248 // call, so that it will be able to make sense of the result.
5250 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5251 // redundant anyway.
5252 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5253 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5256 dbhandle = writehandle;
5258 char* core_class = osrfHashGet( class_meta, "classname" );
5259 char* pkey = osrfHashGet( class_meta, "primarykey" );
5261 const jsonObject* _tmp;
5263 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5265 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5270 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5272 dbi_result result = dbi_conn_query( dbhandle, sql );
5273 if( NULL == result ) {
5275 int errnum = dbi_conn_error( dbhandle, &msg );
5276 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5277 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5278 msg ? msg : "(No description available)" );
5279 if( !oilsIsDBConnected( dbhandle ))
5280 osrfAppSessionPanic( ctx->session );
5281 osrfAppSessionStatus(
5283 OSRF_STATUS_INTERNALSERVERERROR,
5284 "osrfMethodException",
5286 "Severe query error -- see error log for more details"
5293 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5296 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5297 jsonObject* row_obj = NULL;
5299 if( dbi_result_first_row( result )) {
5301 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5302 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5303 // eliminate the duplicates.
5304 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5305 osrfHash* dedup = osrfNewHash();
5307 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5308 char* pkey_val = oilsFMGetString( row_obj, pkey );
5309 if( osrfHashGet( dedup, pkey_val ) ) {
5310 jsonObjectFree( row_obj );
5313 osrfHashSet( dedup, pkey_val, pkey_val );
5314 jsonObjectPush( res_list, row_obj );
5316 } while( dbi_result_next_row( result ));
5317 osrfHashFree( dedup );
5320 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5324 /* clean up the query */
5325 dbi_result_free( result );
5328 // If we're asked to flesh, and there's anything to flesh, then flesh it
5329 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5330 // something that he has no permission to look at).
5331 if( res_list->size && query_hash && ! enforce_pcrud ) {
5332 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5334 // Get the flesh depth
5335 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5336 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5337 flesh_depth = max_flesh_depth;
5339 // We need a non-zero flesh depth, and a list of fields to flesh
5340 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5341 if( temp_blob && flesh_depth > 0 ) {
5343 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5344 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5346 osrfStringArray* link_fields = NULL;
5347 osrfHash* links = osrfHashGet( class_meta, "links" );
5349 // Make an osrfStringArray of the names of fields to be fleshed
5350 if( flesh_fields ) {
5351 if( flesh_fields->size == 1 ) {
5352 const char* _t = jsonObjectGetString(
5353 jsonObjectGetIndex( flesh_fields, 0 ) );
5354 if( !strcmp( _t, "*" ))
5355 link_fields = osrfHashKeys( links );
5358 if( !link_fields ) {
5360 link_fields = osrfNewStringArray( 1 );
5361 jsonIterator* _i = jsonNewIterator( flesh_fields );
5362 while ((_f = jsonIteratorNext( _i ))) {
5363 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5365 jsonIteratorFree( _i );
5369 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5371 // Iterate over the JSON_ARRAY of rows
5373 unsigned long res_idx = 0;
5374 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5377 const char* link_field;
5379 // Iterate over the list of fleshable fields
5380 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5382 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5384 osrfHash* kid_link = osrfHashGet( links, link_field );
5386 continue; // Not a link field; skip it
5388 osrfHash* field = osrfHashGet( fields, link_field );
5390 continue; // Not a field at all; skip it (IDL is ill-formed)
5392 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5393 osrfHashGet( kid_link, "class" ));
5395 continue; // The class it links to doesn't exist; skip it
5397 const char* reltype = osrfHashGet( kid_link, "reltype" );
5399 continue; // No reltype; skip it (IDL is ill-formed)
5401 osrfHash* value_field = field;
5403 if( !strcmp( reltype, "has_many" )
5404 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5405 value_field = osrfHashGet(
5406 fields, osrfHashGet( class_meta, "primarykey" ) );
5409 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5411 if( link_map->size > 0 ) {
5412 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5415 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5420 osrfHashGet( kid_link, "class" ),
5427 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5428 osrfHashGet( kid_link, "field" ),
5429 osrfHashGet( kid_link, "class" ),
5430 osrfHashGet( kid_link, "key" ),
5431 osrfHashGet( kid_link, "reltype" )
5434 const char* search_key = jsonObjectGetString(
5435 jsonObjectGetIndex( cur,
5436 atoi( osrfHashGet( value_field, "array_position" ) )
5441 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5445 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5447 // construct WHERE clause
5448 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5451 osrfHashGet( kid_link, "key" ),
5452 jsonNewObject( search_key )
5455 // construct the rest of the query, mostly
5456 // by copying pieces of the previous level of query
5457 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5458 jsonObjectSetKey( rest_of_query, "flesh",
5459 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5463 jsonObjectSetKey( rest_of_query, "flesh_fields",
5464 jsonObjectClone( flesh_blob ));
5466 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5467 jsonObjectSetKey( rest_of_query, "order_by",
5468 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5472 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5473 jsonObjectSetKey( rest_of_query, "select",
5474 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5478 // do the query, recursively, to expand the fleshable field
5479 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5480 where_clause, rest_of_query, err );
5482 jsonObjectFree( where_clause );
5483 jsonObjectFree( rest_of_query );
5486 osrfStringArrayFree( link_fields );
5487 jsonObjectFree( res_list );
5488 jsonObjectFree( flesh_blob );
5492 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5493 osrfHashGet( kid_link, "class" ), kids->size );
5495 // Traverse the result set
5496 jsonObject* X = NULL;
5497 if( link_map->size > 0 && kids->size > 0 ) {
5499 kids = jsonNewObjectType( JSON_ARRAY );
5501 jsonObject* _k_node;
5502 unsigned long res_idx = 0;
5503 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5509 (unsigned long) atoi(
5515 osrfHashGet( kid_link, "class" )
5519 osrfStringArrayGetString( link_map, 0 )
5527 } // end while loop traversing X
5530 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5531 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5532 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5533 osrfHashGet( kid_link, "field" ));
5536 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5537 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5541 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5543 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5544 osrfHashGet( kid_link, "field" ) );
5547 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5548 jsonObjectClone( kids )
5553 jsonObjectFree( kids );
5557 jsonObjectFree( kids );
5559 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5560 osrfHashGet( kid_link, "field" ) );
5561 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5563 } // end while loop traversing list of fleshable fields
5564 } // end while loop traversing res_list
5565 jsonObjectFree( flesh_blob );
5566 osrfStringArrayFree( link_fields );
5575 int doUpdate( osrfMethodContext* ctx ) {
5576 if( osrfMethodVerifyContext( ctx )) {
5577 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5582 timeout_needs_resetting = 1;
5584 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5586 jsonObject* target = NULL;
5588 target = jsonObjectGetIndex( ctx->params, 1 );
5590 target = jsonObjectGetIndex( ctx->params, 0 );
5592 if(!verifyObjectClass( ctx, target )) {
5593 osrfAppRespondComplete( ctx, NULL );
5597 if( getXactId( ctx ) == NULL ) {
5598 osrfAppSessionStatus(
5600 OSRF_STATUS_BADREQUEST,
5601 "osrfMethodException",
5603 "No active transaction -- required for UPDATE"
5605 osrfAppRespondComplete( ctx, NULL );
5609 // The following test is harmless but redundant. If a class is
5610 // readonly, we don't register an update method for it.
5611 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5612 osrfAppSessionStatus(
5614 OSRF_STATUS_BADREQUEST,
5615 "osrfMethodException",
5617 "Cannot UPDATE readonly class"
5619 osrfAppRespondComplete( ctx, NULL );
5623 const char* trans_id = getXactId( ctx );
5625 // Set the last_xact_id
5626 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5628 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5629 trans_id, target->classname, index );
5630 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5633 char* pkey = osrfHashGet( meta, "primarykey" );
5634 osrfHash* fields = osrfHashGet( meta, "fields" );
5636 char* id = oilsFMGetString( target, pkey );
5640 "%s updating %s object with %s = %s",
5642 osrfHashGet( meta, "fieldmapper" ),
5647 dbhandle = writehandle;
5648 growing_buffer* sql = buffer_init( 128 );
5649 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5652 osrfHash* field_def = NULL;
5653 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5654 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5656 // Skip virtual fields, and the primary key
5657 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5660 const char* field_name = osrfHashIteratorKey( field_itr );
5661 if( ! strcmp( field_name, pkey ) )
5664 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5666 int value_is_numeric = 0; // boolean
5668 if( field_object && field_object->classname ) {
5669 value = oilsFMGetString(
5671 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5673 } else if( field_object && JSON_BOOL == field_object->type ) {
5674 if( jsonBoolIsTrue( field_object ) )
5675 value = strdup( "t" );
5677 value = strdup( "f" );
5679 value = jsonObjectToSimpleString( field_object );
5680 if( field_object && JSON_NUMBER == field_object->type )
5681 value_is_numeric = 1;
5684 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5685 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5687 if( !field_object || field_object->type == JSON_NULL ) {
5688 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5689 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5693 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5694 buffer_fadd( sql, " %s = NULL", field_name );
5697 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5701 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5703 const char* numtype = get_datatype( field_def );
5704 if( !strncmp( numtype, "INT", 3 ) ) {
5705 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5706 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5707 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5709 // Must really be intended as a string, so quote it
5710 if( dbi_conn_quote_string( dbhandle, &value )) {
5711 buffer_fadd( sql, " %s = %s", field_name, value );
5713 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5714 modulename, value );
5715 osrfAppSessionStatus(
5717 OSRF_STATUS_INTERNALSERVERERROR,
5718 "osrfMethodException",
5720 "Error quoting string -- please see the error log for more details"
5724 osrfHashIteratorFree( field_itr );
5726 osrfAppRespondComplete( ctx, NULL );
5731 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5734 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5738 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5739 buffer_fadd( sql, " %s = %s", field_name, value );
5741 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5742 osrfAppSessionStatus(
5744 OSRF_STATUS_INTERNALSERVERERROR,
5745 "osrfMethodException",
5747 "Error quoting string -- please see the error log for more details"
5751 osrfHashIteratorFree( field_itr );
5753 osrfAppRespondComplete( ctx, NULL );
5762 osrfHashIteratorFree( field_itr );
5764 jsonObject* obj = jsonNewObject( id );
5766 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5767 dbi_conn_quote_string( dbhandle, &id );
5769 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5771 char* query = buffer_release( sql );
5772 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5774 dbi_result result = dbi_conn_query( dbhandle, query );
5779 jsonObjectFree( obj );
5780 obj = jsonNewObject( NULL );
5782 int errnum = dbi_conn_error( dbhandle, &msg );
5785 "%s ERROR updating %s object with %s = %s: %d %s",
5787 osrfHashGet( meta, "fieldmapper" ),
5791 msg ? msg : "(No description available)"
5793 osrfAppSessionStatus(
5795 OSRF_STATUS_INTERNALSERVERERROR,
5796 "osrfMethodException",
5798 "Error in updating a row -- please see the error log for more details"
5800 if( !oilsIsDBConnected( dbhandle ))
5801 osrfAppSessionPanic( ctx->session );
5806 osrfAppRespondComplete( ctx, obj );
5807 jsonObjectFree( obj );
5811 int doDelete( osrfMethodContext* ctx ) {
5812 if( osrfMethodVerifyContext( ctx )) {
5813 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5818 timeout_needs_resetting = 1;
5820 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5822 if( getXactId( ctx ) == NULL ) {
5823 osrfAppSessionStatus(
5825 OSRF_STATUS_BADREQUEST,
5826 "osrfMethodException",
5828 "No active transaction -- required for DELETE"
5830 osrfAppRespondComplete( ctx, NULL );
5834 // The following test is harmless but redundant. If a class is
5835 // readonly, we don't register a delete method for it.
5836 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5837 osrfAppSessionStatus(
5839 OSRF_STATUS_BADREQUEST,
5840 "osrfMethodException",
5842 "Cannot DELETE readonly class"
5844 osrfAppRespondComplete( ctx, NULL );
5848 dbhandle = writehandle;
5850 char* pkey = osrfHashGet( meta, "primarykey" );
5857 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5858 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5859 osrfAppRespondComplete( ctx, NULL );
5863 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5865 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5866 osrfAppRespondComplete( ctx, NULL );
5869 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5874 "%s deleting %s object with %s = %s",
5876 osrfHashGet( meta, "fieldmapper" ),
5881 jsonObject* obj = jsonNewObject( id );
5883 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5884 dbi_conn_quote_string( writehandle, &id );
5886 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5887 osrfHashGet( meta, "tablename" ), pkey, id );
5892 jsonObjectFree( obj );
5893 obj = jsonNewObject( NULL );
5895 int errnum = dbi_conn_error( writehandle, &msg );
5898 "%s ERROR deleting %s object with %s = %s: %d %s",
5900 osrfHashGet( meta, "fieldmapper" ),
5904 msg ? msg : "(No description available)"
5906 osrfAppSessionStatus(
5908 OSRF_STATUS_INTERNALSERVERERROR,
5909 "osrfMethodException",
5911 "Error in deleting a row -- please see the error log for more details"
5913 if( !oilsIsDBConnected( writehandle ))
5914 osrfAppSessionPanic( ctx->session );
5919 osrfAppRespondComplete( ctx, obj );
5920 jsonObjectFree( obj );
5925 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5926 @param result An iterator for a result set; we only look at the current row.
5927 @param @meta Pointer to the class metadata for the core class.
5928 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5930 If a column is not defined in the IDL, or if it has no array_position defined for it in
5931 the IDL, or if it is defined as virtual, ignore it.
5933 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5934 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5935 array_position in the IDL.
5937 A field defined in the IDL but not represented in the returned row will leave a hole
5938 in the JSON_ARRAY. In effect it will be treated as a null value.
5940 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5941 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5942 classname corresponding to the @a meta argument.
5944 The calling code is responsible for freeing the the resulting jsonObject by calling
5947 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5948 if( !( result && meta )) return NULL;
5950 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5951 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5952 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5954 osrfHash* fields = osrfHashGet( meta, "fields" );
5956 int columnIndex = 1;
5957 const char* columnName;
5959 /* cycle through the columns in the row returned from the database */
5960 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5962 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5964 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5966 /* determine the field type and storage attributes */
5967 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5968 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5970 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5971 // or if it has no sequence number there, or if it's virtual, skip it.
5972 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5975 if( str_is_true( osrfHashGet( _f, "virtual" )))
5976 continue; // skip this column: IDL says it's virtual
5978 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5979 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5980 continue; // since we assign sequence numbers dynamically as we load the IDL.
5982 fmIndex = atoi( pos );
5983 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5985 continue; // This field is not defined in the IDL
5988 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5989 // sequence number from the IDL (which is likely to be different from the sequence
5990 // of columns in the SELECT clause).
5991 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5992 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5997 case DBI_TYPE_INTEGER :
5999 if( attr & DBI_INTEGER_SIZE8 )
6000 jsonObjectSetIndex( object, fmIndex,
6001 jsonNewNumberObject(
6002 dbi_result_get_longlong_idx( result, columnIndex )));
6004 jsonObjectSetIndex( object, fmIndex,
6005 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6009 case DBI_TYPE_DECIMAL :
6010 jsonObjectSetIndex( object, fmIndex,
6011 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6014 case DBI_TYPE_STRING :
6019 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6024 case DBI_TYPE_DATETIME : {
6026 char dt_string[ 256 ] = "";
6029 // Fetch the date column as a time_t
6030 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6032 // Translate the time_t to a human-readable string
6033 if( !( attr & DBI_DATETIME_DATE )) {
6034 gmtime_r( &_tmp_dt, &gmdt );
6035 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6036 } else if( !( attr & DBI_DATETIME_TIME )) {
6037 localtime_r( &_tmp_dt, &gmdt );
6038 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6040 localtime_r( &_tmp_dt, &gmdt );
6041 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6044 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6048 case DBI_TYPE_BINARY :
6049 osrfLogError( OSRF_LOG_MARK,
6050 "Can't do binary at column %s : index %d", columnName, columnIndex );
6059 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6060 if( !result ) return NULL;
6062 jsonObject* object = jsonNewObject( NULL );
6065 char dt_string[ 256 ];
6069 int columnIndex = 1;
6071 unsigned short type;
6072 const char* columnName;
6074 /* cycle through the column list */
6075 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6077 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6079 fmIndex = -1; // reset the position
6081 /* determine the field type and storage attributes */
6082 type = dbi_result_get_field_type_idx( result, columnIndex );
6083 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6085 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6086 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6091 case DBI_TYPE_INTEGER :
6093 if( attr & DBI_INTEGER_SIZE8 )
6094 jsonObjectSetKey( object, columnName,
6095 jsonNewNumberObject( dbi_result_get_longlong_idx(
6096 result, columnIndex )) );
6098 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6099 dbi_result_get_int_idx( result, columnIndex )) );
6102 case DBI_TYPE_DECIMAL :
6103 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6104 dbi_result_get_double_idx( result, columnIndex )) );
6107 case DBI_TYPE_STRING :
6108 jsonObjectSetKey( object, columnName,
6109 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6112 case DBI_TYPE_DATETIME :
6114 memset( dt_string, '\0', sizeof( dt_string ));
6115 memset( &gmdt, '\0', sizeof( gmdt ));
6117 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6119 if( !( attr & DBI_DATETIME_DATE )) {
6120 gmtime_r( &_tmp_dt, &gmdt );
6121 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6122 } else if( !( attr & DBI_DATETIME_TIME )) {
6123 localtime_r( &_tmp_dt, &gmdt );
6124 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6126 localtime_r( &_tmp_dt, &gmdt );
6127 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6130 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6133 case DBI_TYPE_BINARY :
6134 osrfLogError( OSRF_LOG_MARK,
6135 "Can't do binary at column %s : index %d", columnName, columnIndex );
6139 } // end while loop traversing result
6144 // Interpret a string as true or false
6145 int str_is_true( const char* str ) {
6146 if( NULL == str || strcasecmp( str, "true" ) )
6152 // Interpret a jsonObject as true or false
6153 static int obj_is_true( const jsonObject* obj ) {
6156 else switch( obj->type )
6164 if( strcasecmp( obj->value.s, "true" ) )
6168 case JSON_NUMBER : // Support 1/0 for perl's sake
6169 if( jsonObjectGetNumber( obj ) == 1.0 )
6178 // Translate a numeric code into a text string identifying a type of
6179 // jsonObject. To be used for building error messages.
6180 static const char* json_type( int code ) {
6186 return "JSON_ARRAY";
6188 return "JSON_STRING";
6190 return "JSON_NUMBER";
6196 return "(unrecognized)";
6200 // Extract the "primitive" attribute from an IDL field definition.
6201 // If we haven't initialized the app, then we must be running in
6202 // some kind of testbed. In that case, default to "string".
6203 static const char* get_primitive( osrfHash* field ) {
6204 const char* s = osrfHashGet( field, "primitive" );
6206 if( child_initialized )
6209 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6211 osrfHashGet( field, "name" )
6219 // Extract the "datatype" attribute from an IDL field definition.
6220 // If we haven't initialized the app, then we must be running in
6221 // some kind of testbed. In that case, default to to NUMERIC,
6222 // since we look at the datatype only for numbers.
6223 static const char* get_datatype( osrfHash* field ) {
6224 const char* s = osrfHashGet( field, "datatype" );
6226 if( child_initialized )
6229 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6231 osrfHashGet( field, "name" )
6240 @brief Determine whether a string is potentially a valid SQL identifier.
6241 @param s The identifier to be tested.
6242 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6244 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6245 need to follow all the rules exactly, such as requiring that the first character not
6248 We allow leading and trailing white space. In between, we do not allow punctuation
6249 (except for underscores and dollar signs), control characters, or embedded white space.
6251 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6252 for the foreseeable future such quoted identifiers are not likely to be an issue.
6254 int is_identifier( const char* s) {
6258 // Skip leading white space
6259 while( isspace( (unsigned char) *s ) )
6263 return 0; // Nothing but white space? Not okay.
6265 // Check each character until we reach white space or
6266 // end-of-string. Letters, digits, underscores, and
6267 // dollar signs are okay. With the exception of periods
6268 // (as in schema.identifier), control characters and other
6269 // punctuation characters are not okay. Anything else
6270 // is okay -- it could for example be part of a multibyte
6271 // UTF8 character such as a letter with diacritical marks,
6272 // and those are allowed.
6274 if( isalnum( (unsigned char) *s )
6278 ; // Fine; keep going
6279 else if( ispunct( (unsigned char) *s )
6280 || iscntrl( (unsigned char) *s ) )
6283 } while( *s && ! isspace( (unsigned char) *s ) );
6285 // If we found any white space in the above loop,
6286 // the rest had better be all white space.
6288 while( isspace( (unsigned char) *s ) )
6292 return 0; // White space was embedded within non-white space
6298 @brief Determine whether to accept a character string as a comparison operator.
6299 @param op The candidate comparison operator.
6300 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6302 We don't validate the operator for real. We just make sure that it doesn't contain
6303 any semicolons or white space (with special exceptions for a few specific operators).
6304 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6305 space but it's still not a valid operator, then the database will complain.
6307 Another approach would be to compare the string against a short list of approved operators.
6308 We don't do that because we want to allow custom operators like ">100*", which at this
6309 writing would be difficult or impossible to express otherwise in a JSON query.
6311 int is_good_operator( const char* op ) {
6312 if( !op ) return 0; // Sanity check
6316 if( isspace( (unsigned char) *s ) ) {
6317 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6318 // and IS NOT DISTINCT FROM.
6319 if( !strcasecmp( op, "similar to" ) )
6321 else if( !strcasecmp( op, "is distinct from" ) )
6323 else if( !strcasecmp( op, "is not distinct from" ) )
6328 else if( ';' == *s )
6336 @name Query Frame Management
6338 The following machinery supports a stack of query frames for use by SELECT().
6340 A query frame caches information about one level of a SELECT query. When we enter
6341 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6343 The query frame stores information about the core class, and about any joined classes
6346 The main purpose is to map table aliases to classes and tables, so that a query can
6347 join to the same table more than once. A secondary goal is to reduce the number of
6348 lookups in the IDL by caching the results.
6352 #define STATIC_CLASS_INFO_COUNT 3
6354 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6357 @brief Allocate a ClassInfo as raw memory.
6358 @return Pointer to the newly allocated ClassInfo.
6360 Except for the in_use flag, which is used only by the allocation and deallocation
6361 logic, we don't initialize the ClassInfo here.
6363 static ClassInfo* allocate_class_info( void ) {
6364 // In order to reduce the number of mallocs and frees, we return a static
6365 // instance of ClassInfo, if we can find one that we're not already using.
6366 // We rely on the fact that the compiler will implicitly initialize the
6367 // static instances so that in_use == 0.
6370 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6371 if( ! static_class_info[ i ].in_use ) {
6372 static_class_info[ i ].in_use = 1;
6373 return static_class_info + i;
6377 // The static ones are all in use. Malloc one.
6379 return safe_malloc( sizeof( ClassInfo ) );
6383 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6384 @param info Pointer to the ClassInfo to be cleared.
6386 static void clear_class_info( ClassInfo* info ) {
6391 // Free any malloc'd strings
6393 if( info->alias != info->alias_store )
6394 free( info->alias );
6396 if( info->class_name != info->class_name_store )
6397 free( info->class_name );
6399 free( info->source_def );
6401 info->alias = info->class_name = info->source_def = NULL;
6406 @brief Free a ClassInfo and everything it owns.
6407 @param info Pointer to the ClassInfo to be freed.
6409 static void free_class_info( ClassInfo* info ) {
6414 clear_class_info( info );
6416 // If it's one of the static instances, just mark it as not in use
6419 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6420 if( info == static_class_info + i ) {
6421 static_class_info[ i ].in_use = 0;
6426 // Otherwise it must have been malloc'd, so free it
6432 @brief Populate an already-allocated ClassInfo.
6433 @param info Pointer to the ClassInfo to be populated.
6434 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6436 @param class Name of the class.
6437 @return Zero if successful, or 1 if not.
6439 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6440 the relevant portions of the IDL for the specified class.
6442 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6445 osrfLogError( OSRF_LOG_MARK,
6446 "%s ERROR: No ClassInfo available to populate", modulename );
6447 info->alias = info->class_name = info->source_def = NULL;
6448 info->class_def = info->fields = info->links = NULL;
6453 osrfLogError( OSRF_LOG_MARK,
6454 "%s ERROR: No class name provided for lookup", modulename );
6455 info->alias = info->class_name = info->source_def = NULL;
6456 info->class_def = info->fields = info->links = NULL;
6460 // Alias defaults to class name if not supplied
6461 if( ! alias || ! alias[ 0 ] )
6464 // Look up class info in the IDL
6465 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6467 osrfLogError( OSRF_LOG_MARK,
6468 "%s ERROR: Class %s not defined in IDL", modulename, class );
6469 info->alias = info->class_name = info->source_def = NULL;
6470 info->class_def = info->fields = info->links = NULL;
6472 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6473 osrfLogError( OSRF_LOG_MARK,
6474 "%s ERROR: Class %s is defined as virtual", modulename, class );
6475 info->alias = info->class_name = info->source_def = NULL;
6476 info->class_def = info->fields = info->links = NULL;
6480 osrfHash* links = osrfHashGet( class_def, "links" );
6482 osrfLogError( OSRF_LOG_MARK,
6483 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6484 info->alias = info->class_name = info->source_def = NULL;
6485 info->class_def = info->fields = info->links = NULL;
6489 osrfHash* fields = osrfHashGet( class_def, "fields" );
6491 osrfLogError( OSRF_LOG_MARK,
6492 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6493 info->alias = info->class_name = info->source_def = NULL;
6494 info->class_def = info->fields = info->links = NULL;
6498 char* source_def = oilsGetRelation( class_def );
6502 // We got everything we need, so populate the ClassInfo
6503 if( strlen( alias ) > ALIAS_STORE_SIZE )
6504 info->alias = strdup( alias );
6506 strcpy( info->alias_store, alias );
6507 info->alias = info->alias_store;
6510 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6511 info->class_name = strdup( class );
6513 strcpy( info->class_name_store, class );
6514 info->class_name = info->class_name_store;
6517 info->source_def = source_def;
6519 info->class_def = class_def;
6520 info->links = links;
6521 info->fields = fields;
6526 #define STATIC_FRAME_COUNT 3
6528 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6531 @brief Allocate a QueryFrame as raw memory.
6532 @return Pointer to the newly allocated QueryFrame.
6534 Except for the in_use flag, which is used only by the allocation and deallocation
6535 logic, we don't initialize the QueryFrame here.
6537 static QueryFrame* allocate_frame( void ) {
6538 // In order to reduce the number of mallocs and frees, we return a static
6539 // instance of QueryFrame, if we can find one that we're not already using.
6540 // We rely on the fact that the compiler will implicitly initialize the
6541 // static instances so that in_use == 0.
6544 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6545 if( ! static_frame[ i ].in_use ) {
6546 static_frame[ i ].in_use = 1;
6547 return static_frame + i;
6551 // The static ones are all in use. Malloc one.
6553 return safe_malloc( sizeof( QueryFrame ) );
6557 @brief Free a QueryFrame, and all the memory it owns.
6558 @param frame Pointer to the QueryFrame to be freed.
6560 static void free_query_frame( QueryFrame* frame ) {
6565 clear_class_info( &frame->core );
6567 // Free the join list
6569 ClassInfo* info = frame->join_list;
6572 free_class_info( info );
6576 frame->join_list = NULL;
6579 // If the frame is a static instance, just mark it as unused
6581 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6582 if( frame == static_frame + i ) {
6583 static_frame[ i ].in_use = 0;
6588 // Otherwise it must have been malloc'd, so free it
6594 @brief Search a given QueryFrame for a specified alias.
6595 @param frame Pointer to the QueryFrame to be searched.
6596 @param target The alias for which to search.
6597 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6599 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6600 if( ! frame || ! target ) {
6604 ClassInfo* found_class = NULL;
6606 if( !strcmp( target, frame->core.alias ) )
6607 return &(frame->core);
6609 ClassInfo* curr_class = frame->join_list;
6610 while( curr_class ) {
6611 if( strcmp( target, curr_class->alias ) )
6612 curr_class = curr_class->next;
6614 found_class = curr_class;
6624 @brief Push a new (blank) QueryFrame onto the stack.
6626 static void push_query_frame( void ) {
6627 QueryFrame* frame = allocate_frame();
6628 frame->join_list = NULL;
6629 frame->next = curr_query;
6631 // Initialize the ClassInfo for the core class
6632 ClassInfo* core = &frame->core;
6633 core->alias = core->class_name = core->source_def = NULL;
6634 core->class_def = core->fields = core->links = NULL;
6640 @brief Pop a QueryFrame off the stack and destroy it.
6642 static void pop_query_frame( void ) {
6647 QueryFrame* popped = curr_query;
6648 curr_query = popped->next;
6650 free_query_frame( popped );
6654 @brief Populate the ClassInfo for the core class.
6655 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6656 class name as an alias.
6657 @param class_name Name of the core class.
6658 @return Zero if successful, or 1 if not.
6660 Populate the ClassInfo of the core class with copies of the alias and class name, and
6661 with pointers to the relevant portions of the IDL for the core class.
6663 static int add_query_core( const char* alias, const char* class_name ) {
6666 if( ! curr_query ) {
6667 osrfLogError( OSRF_LOG_MARK,
6668 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6670 } else if( curr_query->core.alias ) {
6671 osrfLogError( OSRF_LOG_MARK,
6672 "%s ERROR: Core class %s already populated as %s",
6673 modulename, curr_query->core.class_name, curr_query->core.alias );
6677 build_class_info( &curr_query->core, alias, class_name );
6678 if( curr_query->core.alias )
6681 osrfLogError( OSRF_LOG_MARK,
6682 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6688 @brief Search the current QueryFrame for a specified alias.
6689 @param target The alias for which to search.
6690 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6692 static inline ClassInfo* search_alias( const char* target ) {
6693 return search_alias_in_frame( curr_query, target );
6697 @brief Search all levels of query for a specified alias, starting with the current query.
6698 @param target The alias for which to search.
6699 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6701 static ClassInfo* search_all_alias( const char* target ) {
6702 ClassInfo* found_class = NULL;
6703 QueryFrame* curr_frame = curr_query;
6705 while( curr_frame ) {
6706 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6709 curr_frame = curr_frame->next;
6716 @brief Add a class to the list of classes joined to the current query.
6717 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6718 the class name as an alias.
6719 @param classname The name of the class to be added.
6720 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6722 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6724 if( ! classname || ! *classname ) { // sanity check
6725 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6732 const ClassInfo* conflict = search_alias( alias );
6734 osrfLogError( OSRF_LOG_MARK,
6735 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6736 modulename, alias, conflict->class_name );
6740 ClassInfo* info = allocate_class_info();
6742 if( build_class_info( info, alias, classname ) ) {
6743 free_class_info( info );
6747 // Add the new ClassInfo to the join list of the current QueryFrame
6748 info->next = curr_query->join_list;
6749 curr_query->join_list = info;
6755 @brief Destroy all nodes on the query stack.
6757 static void clear_query_stack( void ) {