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" );
1383 // Build a list of org units that own the row. This is fairly convoluted because there
1384 // are several different ways that an org unit may own the row, as defined by the
1387 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1388 // identifying an owning org_unit..
1389 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1391 // Foreign context adds a layer of indirection. The row points to some other row that
1392 // an org unit may own. The "jump" attribute, if present, adds another layer of
1394 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1396 // The following string array stores the list of org units. (We don't have a thingie
1397 // for storing lists of integers, so we fake it with a list of strings.)
1398 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1401 const char* pkey_value = NULL;
1402 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1403 // If the global_required attribute is present and true, then the only owning
1404 // org unit is the root org unit, i.e. the one with no parent.
1405 osrfLogDebug( OSRF_LOG_MARK,
1406 "global-level permissions required, fetching top of the org tree" );
1408 // check for perm at top of org tree
1409 const char* org_tree_root_id = org_tree_root( ctx );
1410 if( org_tree_root_id ) {
1411 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1412 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1414 osrfStringArrayFree( context_org_array );
1419 // If the global_required attribute is absent or false, then we look for
1420 // local and/or foreign context. In order to find the relevant foreign
1421 // keys, we must either read the relevant row from the database, or look at
1422 // the image of the row that we already have in memory.
1424 // Even if we have an image of the row in memory, that image may not include the
1425 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1426 // of the row to make sure that we have what we need.
1428 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1429 "fetching context org ids" );
1430 const char* pkey = osrfHashGet( class, "primarykey" );
1431 jsonObject *param = NULL;
1434 // There is no primary key, so we can't do a fresh lookup. Use the row
1435 // image that we already have. If it doesn't have everything we need, too bad.
1437 param = jsonObjectClone( obj );
1438 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1439 } else if( obj->classname ) {
1440 pkey_value = oilsFMGetStringConst( obj, pkey );
1442 param = jsonObjectClone( obj );
1443 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1446 pkey_value = jsonObjectGetString( obj );
1448 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1449 "of %s and retrieving from the database", pkey_value );
1453 // Fetch the row so that we can look at the foreign key(s)
1454 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1455 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1456 jsonObjectFree( _tmp_params );
1458 param = jsonObjectExtractIndex( _list, 0 );
1459 jsonObjectFree( _list );
1463 // The row doesn't exist. Complain, and deny access.
1464 osrfLogDebug( OSRF_LOG_MARK,
1465 "Object not found in the database with primary key %s of %s",
1468 growing_buffer* msg = buffer_init( 128 );
1471 "%s: no object found with primary key %s of %s",
1477 char* m = buffer_release( msg );
1478 osrfAppSessionStatus(
1480 OSRF_STATUS_INTERNALSERVERERROR,
1481 "osrfMethodException",
1490 if( local_context && local_context->size > 0 ) {
1491 // The IDL provides a list of column names for the foreign keys denoting
1492 // local context, i.e. columns identifying owing org units directly. Look up
1493 // the value of each one, and if it isn't null, add it to the list of org units.
1494 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1495 local_context->size );
1497 const char* lcontext = NULL;
1498 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1499 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1500 if( fkey_value ) { // if not null
1501 osrfStringArrayAdd( context_org_array, fkey_value );
1504 "adding class-local field %s (value: %s) to the context org list",
1506 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1512 if( foreign_context ) {
1513 unsigned long class_count = osrfHashGetCount( foreign_context );
1514 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1516 if( class_count > 0 ) {
1518 // The IDL provides a list of foreign key columns pointing to rows that
1519 // an org unit may own. Follow each link, identify the owning org unit,
1520 // and add it to the list.
1521 osrfHash* fcontext = NULL;
1522 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1523 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1524 // For each class to which a foreign key points:
1525 const char* class_name = osrfHashIteratorKey( class_itr );
1526 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1530 "%d foreign context fields(s) specified for class %s",
1531 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1535 // Get the name of the key field in the foreign table
1536 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1538 // Get the value of the foreign key pointing to the foreign table
1539 char* foreign_pkey_value =
1540 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1541 if( !foreign_pkey_value )
1542 continue; // Foreign key value is null; skip it
1544 // Look up the row to which the foreign key points
1545 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1546 jsonObject* _list = doFieldmapperSearch(
1547 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1549 jsonObject* _fparam = NULL;
1550 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1551 _fparam = jsonObjectExtractIndex( _list, 0 );
1553 jsonObjectFree( _tmp_params );
1554 jsonObjectFree( _list );
1556 // At this point _fparam either points to the row identified by the
1557 // foreign key, or it's NULL (no such row found).
1559 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1561 const char* bad_class = NULL; // For noting failed lookups
1563 bad_class = class_name; // Referenced row not found
1564 else if( jump_list ) {
1565 // Follow a chain of rows, linked by foreign keys, to find an owner
1566 const char* flink = NULL;
1568 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1569 // For each entry in the jump list. Each entry (i.e. flink) is
1570 // the name of a foreign key column in the current row.
1572 // From the IDL, get the linkage information for the next jump
1573 osrfHash* foreign_link_hash =
1574 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1576 // Get the class metadata for the class
1577 // to which the foreign key points
1578 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1579 osrfHashGet( foreign_link_hash, "class" ));
1581 // Get the name of the referenced key of that class
1582 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1584 // Get the value of the foreign key pointing to that class
1585 free( foreign_pkey_value );
1586 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1587 if( !foreign_pkey_value )
1588 break; // Foreign key is null; quit looking
1590 // Build a WHERE clause for the lookup
1591 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1594 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1595 _tmp_params, NULL, &err );
1597 // Get the resulting row
1598 jsonObjectFree( _fparam );
1599 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1600 _fparam = jsonObjectExtractIndex( _list, 0 );
1602 // Referenced row not found
1604 bad_class = osrfHashGet( foreign_link_hash, "class" );
1607 jsonObjectFree( _tmp_params );
1608 jsonObjectFree( _list );
1614 // We had a foreign key pointing to such-and-such a row, but then
1615 // we couldn't fetch that row. The data in the database are in an
1616 // inconsistent state; the database itself may even be corrupted.
1617 growing_buffer* msg = buffer_init( 128 );
1620 "%s: no object of class %s found with primary key %s of %s",
1624 foreign_pkey_value ? foreign_pkey_value : "(null)"
1627 char* m = buffer_release( msg );
1628 osrfAppSessionStatus(
1630 OSRF_STATUS_INTERNALSERVERERROR,
1631 "osrfMethodException",
1637 osrfHashIteratorFree( class_itr );
1638 free( foreign_pkey_value );
1639 jsonObjectFree( param );
1644 free( foreign_pkey_value );
1647 // Examine each context column of the foreign row,
1648 // and add its value to the list of org units.
1650 const char* foreign_field = NULL;
1651 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1652 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1653 osrfStringArrayAdd( context_org_array,
1654 oilsFMGetStringConst( _fparam, foreign_field ));
1655 osrfLogDebug( OSRF_LOG_MARK,
1656 "adding foreign class %s field %s (value: %s) "
1657 "to the context org list",
1660 osrfStringArrayGetString(
1661 context_org_array, context_org_array->size - 1 )
1665 jsonObjectFree( _fparam );
1669 osrfHashIteratorFree( class_itr );
1673 jsonObjectFree( param );
1676 const char* context_org = NULL;
1677 const char* perm = NULL;
1680 if( permission->size == 0 ) {
1681 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1685 // For every combination of permission and context org unit: call a stored procedure
1686 // to determine if the user has this permission in the context of this org unit.
1687 // If the answer is yes at any point, then we're done, and the user has permission.
1688 // In other words permissions are additive.
1690 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1692 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1698 "Checking object permission [%s] for user %d "
1699 "on object %s (class %s) at org %d",
1703 osrfHashGet( class, "classname" ),
1707 result = dbi_conn_queryf(
1709 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1712 osrfHashGet( class, "classname" ),
1720 "Received a result for object permission [%s] "
1721 "for user %d on object %s (class %s) at org %d",
1725 osrfHashGet( class, "classname" ),
1729 if( dbi_result_first_row( result )) {
1730 jsonObject* return_val = oilsMakeJSONFromResult( result );
1731 const char* has_perm = jsonObjectGetString(
1732 jsonObjectGetKeyConst( return_val, "has_perm" ));
1736 "Status of object permission [%s] for user %d "
1737 "on object %s (class %s) at org %d is %s",
1741 osrfHashGet(class, "classname"),
1746 if( *has_perm == 't' )
1748 jsonObjectFree( return_val );
1751 dbi_result_free( result );
1756 int errnum = dbi_conn_error( writehandle, &msg );
1757 osrfLogWarning( OSRF_LOG_MARK,
1758 "Unable to call check object permissions: %d, %s",
1759 errnum, msg ? msg : "(No description available)" );
1760 if( !oilsIsDBConnected( writehandle ))
1761 osrfAppSessionPanic( ctx->session );
1765 osrfLogDebug( OSRF_LOG_MARK,
1766 "Checking non-object permission [%s] for user %d at org %d",
1767 perm, userid, atoi(context_org) );
1768 result = dbi_conn_queryf(
1770 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1777 osrfLogDebug( OSRF_LOG_MARK,
1778 "Received a result for permission [%s] for user %d at org %d",
1779 perm, userid, atoi( context_org ));
1780 if( dbi_result_first_row( result )) {
1781 jsonObject* return_val = oilsMakeJSONFromResult( result );
1782 const char* has_perm = jsonObjectGetString(
1783 jsonObjectGetKeyConst( return_val, "has_perm" ));
1784 osrfLogDebug( OSRF_LOG_MARK,
1785 "Status of permission [%s] for user %d at org %d is [%s]",
1786 perm, userid, atoi( context_org ), has_perm );
1787 if( *has_perm == 't' )
1789 jsonObjectFree( return_val );
1792 dbi_result_free( result );
1797 int errnum = dbi_conn_error( writehandle, &msg );
1798 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1799 errnum, msg ? msg : "(No description available)" );
1800 if( !oilsIsDBConnected( writehandle ))
1801 osrfAppSessionPanic( ctx->session );
1809 osrfStringArrayFree( context_org_array );
1815 @brief Look up the root of the org_unit tree.
1816 @param ctx Pointer to the method context.
1817 @return The id of the root org unit, as a character string.
1819 Query actor.org_unit where parent_ou is null, and return the id as a string.
1821 This function assumes that there is only one root org unit, i.e. that we
1822 have a single tree, not a forest.
1824 The calling code is responsible for freeing the returned string.
1826 static const char* org_tree_root( osrfMethodContext* ctx ) {
1828 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1829 static time_t last_lookup_time = 0;
1830 time_t current_time = time( NULL );
1832 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1833 // We successfully looked this up less than an hour ago.
1834 // It's not likely to have changed since then.
1835 return strdup( cached_root_id );
1837 last_lookup_time = current_time;
1840 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1841 jsonObject* result = doFieldmapperSearch(
1842 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1843 jsonObjectFree( where_clause );
1845 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1848 jsonObjectFree( result );
1850 growing_buffer* msg = buffer_init( 128 );
1851 OSRF_BUFFER_ADD( msg, modulename );
1852 OSRF_BUFFER_ADD( msg,
1853 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1855 char* m = buffer_release( msg );
1856 osrfAppSessionStatus( ctx->session,
1857 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1860 cached_root_id[ 0 ] = '\0';
1864 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1865 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1867 strcpy( cached_root_id, root_org_unit_id );
1868 jsonObjectFree( result );
1869 return cached_root_id;
1873 @brief Create a JSON_HASH with a single key/value pair.
1874 @param key The key of the key/value pair.
1875 @param value the value of the key/value pair.
1876 @return Pointer to a newly created jsonObject of type JSON_HASH.
1878 The value of the key/value is either a string or (if @a value is NULL) a null.
1880 static jsonObject* single_hash( const char* key, const char* value ) {
1882 if( ! key ) key = "";
1884 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1885 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1890 int doCreate( osrfMethodContext* ctx ) {
1891 if(osrfMethodVerifyContext( ctx )) {
1892 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1897 timeout_needs_resetting = 1;
1899 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1900 jsonObject* target = NULL;
1901 jsonObject* options = NULL;
1903 if( enforce_pcrud ) {
1904 target = jsonObjectGetIndex( ctx->params, 1 );
1905 options = jsonObjectGetIndex( ctx->params, 2 );
1907 target = jsonObjectGetIndex( ctx->params, 0 );
1908 options = jsonObjectGetIndex( ctx->params, 1 );
1911 if( !verifyObjectClass( ctx, target )) {
1912 osrfAppRespondComplete( ctx, NULL );
1916 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1918 const char* trans_id = getXactId( ctx );
1920 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1922 osrfAppSessionStatus(
1924 OSRF_STATUS_BADREQUEST,
1925 "osrfMethodException",
1927 "No active transaction -- required for CREATE"
1929 osrfAppRespondComplete( ctx, NULL );
1933 // The following test is harmless but redundant. If a class is
1934 // readonly, we don't register a create method for it.
1935 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1936 osrfAppSessionStatus(
1938 OSRF_STATUS_BADREQUEST,
1939 "osrfMethodException",
1941 "Cannot INSERT readonly class"
1943 osrfAppRespondComplete( ctx, NULL );
1947 // Set the last_xact_id
1948 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1950 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1951 trans_id, target->classname, index);
1952 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1955 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1957 dbhandle = writehandle;
1959 osrfHash* fields = osrfHashGet( meta, "fields" );
1960 char* pkey = osrfHashGet( meta, "primarykey" );
1961 char* seq = osrfHashGet( meta, "sequence" );
1963 growing_buffer* table_buf = buffer_init( 128 );
1964 growing_buffer* col_buf = buffer_init( 128 );
1965 growing_buffer* val_buf = buffer_init( 128 );
1967 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1968 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1969 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1970 buffer_add( val_buf,"VALUES (" );
1974 osrfHash* field = NULL;
1975 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1976 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1978 const char* field_name = osrfHashIteratorKey( field_itr );
1980 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1983 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1986 if( field_object && field_object->classname ) {
1987 value = oilsFMGetString(
1989 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1991 } else if( field_object && JSON_BOOL == field_object->type ) {
1992 if( jsonBoolIsTrue( field_object ) )
1993 value = strdup( "t" );
1995 value = strdup( "f" );
1997 value = jsonObjectToSimpleString( field_object );
2003 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2004 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2007 buffer_add( col_buf, field_name );
2009 if( !field_object || field_object->type == JSON_NULL ) {
2010 buffer_add( val_buf, "DEFAULT" );
2012 } else if( !strcmp( get_primitive( field ), "number" )) {
2013 const char* numtype = get_datatype( field );
2014 if( !strcmp( numtype, "INT8" )) {
2015 buffer_fadd( val_buf, "%lld", atoll( value ));
2017 } else if( !strcmp( numtype, "INT" )) {
2018 buffer_fadd( val_buf, "%d", atoi( value ));
2020 } else if( !strcmp( numtype, "NUMERIC" )) {
2021 buffer_fadd( val_buf, "%f", atof( value ));
2024 if( dbi_conn_quote_string( writehandle, &value )) {
2025 OSRF_BUFFER_ADD( val_buf, value );
2028 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2029 osrfAppSessionStatus(
2031 OSRF_STATUS_INTERNALSERVERERROR,
2032 "osrfMethodException",
2034 "Error quoting string -- please see the error log for more details"
2037 buffer_free( table_buf );
2038 buffer_free( col_buf );
2039 buffer_free( val_buf );
2040 osrfAppRespondComplete( ctx, NULL );
2048 osrfHashIteratorFree( field_itr );
2050 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2051 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2053 char* table_str = buffer_release( table_buf );
2054 char* col_str = buffer_release( col_buf );
2055 char* val_str = buffer_release( val_buf );
2056 growing_buffer* sql = buffer_init( 128 );
2057 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2062 char* query = buffer_release( sql );
2064 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2066 jsonObject* obj = NULL;
2069 dbi_result result = dbi_conn_query( writehandle, query );
2071 obj = jsonNewObject( NULL );
2073 int errnum = dbi_conn_error( writehandle, &msg );
2076 "%s ERROR inserting %s object using query [%s]: %d %s",
2078 osrfHashGet(meta, "fieldmapper"),
2081 msg ? msg : "(No description available)"
2083 osrfAppSessionStatus(
2085 OSRF_STATUS_INTERNALSERVERERROR,
2086 "osrfMethodException",
2088 "INSERT error -- please see the error log for more details"
2090 if( !oilsIsDBConnected( writehandle ))
2091 osrfAppSessionPanic( ctx->session );
2095 char* id = oilsFMGetString( target, pkey );
2097 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2098 growing_buffer* _id = buffer_init( 10 );
2099 buffer_fadd( _id, "%lld", new_id );
2100 id = buffer_release( _id );
2103 // Find quietness specification, if present
2104 const char* quiet_str = NULL;
2106 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2108 quiet_str = jsonObjectGetString( quiet_obj );
2111 if( str_is_true( quiet_str )) { // if quietness is specified
2112 obj = jsonNewObject( id );
2116 // Fetch the row that we just inserted, so that we can return it to the client
2117 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2118 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2121 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2125 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2127 jsonObjectFree( list );
2128 jsonObjectFree( where_clause );
2135 osrfAppRespondComplete( ctx, obj );
2136 jsonObjectFree( obj );
2141 @brief Implement the retrieve method.
2142 @param ctx Pointer to the method context.
2143 @param err Pointer through which to return an error code.
2144 @return If successful, a pointer to the result to be returned to the client;
2147 From the method's class, fetch a row with a specified value in the primary key. This
2148 method relies on the database design convention that a primary key consists of a single
2152 - authkey (PCRUD only)
2153 - value of the primary key for the desired row, for building the WHERE clause
2154 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2156 Return to client: One row from the query.
2158 int doRetrieve( osrfMethodContext* ctx ) {
2159 if(osrfMethodVerifyContext( ctx )) {
2160 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2165 timeout_needs_resetting = 1;
2170 if( enforce_pcrud ) {
2175 // Get the class metadata
2176 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2178 // Get the value of the primary key, from a method parameter
2179 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2183 "%s retrieving %s object with primary key value of %s",
2185 osrfHashGet( class_def, "fieldmapper" ),
2186 jsonObjectGetString( id_obj )
2189 // Build a WHERE clause based on the key value
2190 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2193 osrfHashGet( class_def, "primarykey" ), // name of key column
2194 jsonObjectClone( id_obj ) // value of key column
2197 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2201 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2203 jsonObjectFree( where_clause );
2205 osrfAppRespondComplete( ctx, NULL );
2209 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2210 jsonObjectFree( list );
2212 if( enforce_pcrud ) {
2213 if(!verifyObjectPCRUD( ctx, obj )) {
2214 jsonObjectFree( obj );
2216 growing_buffer* msg = buffer_init( 128 );
2217 OSRF_BUFFER_ADD( msg, modulename );
2218 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2220 char* m = buffer_release( msg );
2221 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2225 osrfAppRespondComplete( ctx, NULL );
2230 osrfAppRespondComplete( ctx, obj );
2231 jsonObjectFree( obj );
2236 @brief Translate a numeric value to a string representation for the database.
2237 @param field Pointer to the IDL field definition.
2238 @param value Pointer to a jsonObject holding the value of a field.
2239 @return Pointer to a newly allocated string.
2241 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2242 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2243 or (what is worse) valid SQL that is wrong.
2245 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2247 The calling code is responsible for freeing the resulting string by calling free().
2249 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2250 growing_buffer* val_buf = buffer_init( 32 );
2251 const char* numtype = get_datatype( field );
2253 // For historical reasons the following contains cruft that could be cleaned up.
2254 if( !strncmp( numtype, "INT", 3 ) ) {
2255 if( value->type == JSON_NUMBER )
2256 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2257 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2259 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2262 } else if( !strcmp( numtype, "NUMERIC" )) {
2263 if( value->type == JSON_NUMBER )
2264 buffer_fadd( val_buf, jsonObjectGetString( value ));
2266 buffer_fadd( val_buf, jsonObjectGetString( value ));
2270 // Presumably this was really intended to be a string, so quote it
2271 char* str = jsonObjectToSimpleString( value );
2272 if( dbi_conn_quote_string( dbhandle, &str )) {
2273 OSRF_BUFFER_ADD( val_buf, str );
2276 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2278 buffer_free( val_buf );
2283 return buffer_release( val_buf );
2286 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2287 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2288 growing_buffer* sql_buf = buffer_init( 32 );
2294 osrfHashGet( field, "name" )
2298 buffer_add( sql_buf, "IN (" );
2299 } else if( !strcasecmp( op,"not in" )) {
2300 buffer_add( sql_buf, "NOT IN (" );
2302 buffer_add( sql_buf, "IN (" );
2305 if( node->type == JSON_HASH ) {
2306 // subquery predicate
2307 char* subpred = buildQuery( ctx, node, SUBSELECT );
2309 buffer_free( sql_buf );
2313 buffer_add( sql_buf, subpred );
2316 } else if( node->type == JSON_ARRAY ) {
2317 // literal value list
2318 int in_item_index = 0;
2319 int in_item_first = 1;
2320 const jsonObject* in_item;
2321 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2326 buffer_add( sql_buf, ", " );
2329 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2330 osrfLogError( OSRF_LOG_MARK,
2331 "%s: Expected string or number within IN list; found %s",
2332 modulename, json_type( in_item->type ) );
2333 buffer_free( sql_buf );
2337 // Append the literal value -- quoted if not a number
2338 if( JSON_NUMBER == in_item->type ) {
2339 char* val = jsonNumberToDBString( field, in_item );
2340 OSRF_BUFFER_ADD( sql_buf, val );
2343 } else if( !strcmp( get_primitive( field ), "number" )) {
2344 char* val = jsonNumberToDBString( field, in_item );
2345 OSRF_BUFFER_ADD( sql_buf, val );
2349 char* key_string = jsonObjectToSimpleString( in_item );
2350 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2351 OSRF_BUFFER_ADD( sql_buf, key_string );
2354 osrfLogError( OSRF_LOG_MARK,
2355 "%s: Error quoting key string [%s]", modulename, key_string );
2357 buffer_free( sql_buf );
2363 if( in_item_first ) {
2364 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2365 buffer_free( sql_buf );
2369 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2370 modulename, json_type( node->type ));
2371 buffer_free( sql_buf );
2375 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2377 return buffer_release( sql_buf );
2380 // Receive a JSON_ARRAY representing a function call. The first
2381 // entry in the array is the function name. The rest are parameters.
2382 static char* searchValueTransform( const jsonObject* array ) {
2384 if( array->size < 1 ) {
2385 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2389 // Get the function name
2390 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2391 if( func_item->type != JSON_STRING ) {
2392 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2393 modulename, json_type( func_item->type ));
2397 growing_buffer* sql_buf = buffer_init( 32 );
2399 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2400 OSRF_BUFFER_ADD( sql_buf, "( " );
2402 // Get the parameters
2403 int func_item_index = 1; // We already grabbed the zeroth entry
2404 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2406 // Add a separator comma, if we need one
2407 if( func_item_index > 2 )
2408 buffer_add( sql_buf, ", " );
2410 // Add the current parameter
2411 if( func_item->type == JSON_NULL ) {
2412 buffer_add( sql_buf, "NULL" );
2414 char* val = jsonObjectToSimpleString( func_item );
2415 if( dbi_conn_quote_string( dbhandle, &val )) {
2416 OSRF_BUFFER_ADD( sql_buf, val );
2419 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2421 buffer_free( sql_buf );
2428 buffer_add( sql_buf, " )" );
2430 return buffer_release( sql_buf );
2433 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2434 const jsonObject* node, const char* op ) {
2436 if( ! is_good_operator( op ) ) {
2437 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2441 char* val = searchValueTransform( node );
2445 growing_buffer* sql_buf = buffer_init( 32 );
2450 osrfHashGet( field, "name" ),
2457 return buffer_release( sql_buf );
2460 // class_alias is a class name or other table alias
2461 // field is a field definition as stored in the IDL
2462 // node comes from the method parameter, and may represent an entry in the SELECT list
2463 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2464 const jsonObject* node ) {
2465 growing_buffer* sql_buf = buffer_init( 32 );
2467 const char* field_transform = jsonObjectGetString(
2468 jsonObjectGetKeyConst( node, "transform" ) );
2469 const char* transform_subcolumn = jsonObjectGetString(
2470 jsonObjectGetKeyConst( node, "result_field" ) );
2472 if( transform_subcolumn ) {
2473 if( ! is_identifier( transform_subcolumn ) ) {
2474 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2475 modulename, transform_subcolumn );
2476 buffer_free( sql_buf );
2479 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2482 if( field_transform ) {
2484 if( ! is_identifier( field_transform ) ) {
2485 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2486 modulename, field_transform );
2487 buffer_free( sql_buf );
2491 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2492 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2493 field_transform, class_alias, osrfHashGet( field, "name" ));
2495 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2496 field_transform, class_alias, osrfHashGet( field, "name" ));
2499 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2502 if( array->type != JSON_ARRAY ) {
2503 osrfLogError( OSRF_LOG_MARK,
2504 "%s: Expected JSON_ARRAY for function params; found %s",
2505 modulename, json_type( array->type ) );
2506 buffer_free( sql_buf );
2509 int func_item_index = 0;
2510 jsonObject* func_item;
2511 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2513 char* val = jsonObjectToSimpleString( func_item );
2516 buffer_add( sql_buf, ",NULL" );
2517 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2518 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2519 OSRF_BUFFER_ADD( sql_buf, val );
2521 osrfLogError( OSRF_LOG_MARK,
2522 "%s: Error quoting key string [%s]", modulename, val );
2524 buffer_free( sql_buf );
2531 buffer_add( sql_buf, " )" );
2534 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2537 if( transform_subcolumn )
2538 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2540 return buffer_release( sql_buf );
2543 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2544 const jsonObject* node, const char* op ) {
2546 if( ! is_good_operator( op ) ) {
2547 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2551 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2552 if( ! field_transform )
2555 int extra_parens = 0; // boolean
2557 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2559 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2561 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2563 free( field_transform );
2567 } else if( value_obj->type == JSON_ARRAY ) {
2568 value = searchValueTransform( value_obj );
2570 osrfLogError( OSRF_LOG_MARK,
2571 "%s: Error building value transform for field transform", modulename );
2572 free( field_transform );
2575 } else if( value_obj->type == JSON_HASH ) {
2576 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2578 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2580 free( field_transform );
2584 } else if( value_obj->type == JSON_NUMBER ) {
2585 value = jsonNumberToDBString( field, value_obj );
2586 } else if( value_obj->type == JSON_NULL ) {
2587 osrfLogError( OSRF_LOG_MARK,
2588 "%s: Error building predicate for field transform: null value", modulename );
2589 free( field_transform );
2591 } else if( value_obj->type == JSON_BOOL ) {
2592 osrfLogError( OSRF_LOG_MARK,
2593 "%s: Error building predicate for field transform: boolean value", modulename );
2594 free( field_transform );
2597 if( !strcmp( get_primitive( field ), "number") ) {
2598 value = jsonNumberToDBString( field, value_obj );
2600 value = jsonObjectToSimpleString( value_obj );
2601 if( !dbi_conn_quote_string( dbhandle, &value )) {
2602 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2603 modulename, value );
2605 free( field_transform );
2611 const char* left_parens = "";
2612 const char* right_parens = "";
2614 if( extra_parens ) {
2619 growing_buffer* sql_buf = buffer_init( 32 );
2623 "%s%s %s %s %s %s%s",
2634 free( field_transform );
2636 return buffer_release( sql_buf );
2639 static char* searchSimplePredicate( const char* op, const char* class_alias,
2640 osrfHash* field, const jsonObject* node ) {
2642 if( ! is_good_operator( op ) ) {
2643 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2649 // Get the value to which we are comparing the specified column
2650 if( node->type != JSON_NULL ) {
2651 if( node->type == JSON_NUMBER ) {
2652 val = jsonNumberToDBString( field, node );
2653 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2654 val = jsonNumberToDBString( field, node );
2656 val = jsonObjectToSimpleString( node );
2661 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2662 // Value is not numeric; enclose it in quotes
2663 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2664 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2671 // Compare to a null value
2672 val = strdup( "NULL" );
2673 if( strcmp( op, "=" ))
2679 growing_buffer* sql_buf = buffer_init( 32 );
2680 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2681 char* pred = buffer_release( sql_buf );
2688 static char* searchBETWEENPredicate( const char* class_alias,
2689 osrfHash* field, const jsonObject* node ) {
2691 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2692 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2694 if( NULL == y_node ) {
2695 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2698 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2699 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2706 if( !strcmp( get_primitive( field ), "number") ) {
2707 x_string = jsonNumberToDBString( field, x_node );
2708 y_string = jsonNumberToDBString( field, y_node );
2711 x_string = jsonObjectToSimpleString( x_node );
2712 y_string = jsonObjectToSimpleString( y_node );
2713 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2714 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2715 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2716 modulename, x_string, y_string );
2723 growing_buffer* sql_buf = buffer_init( 32 );
2724 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2725 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2729 return buffer_release( sql_buf );
2732 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2733 jsonObject* node, osrfMethodContext* ctx ) {
2736 if( node->type == JSON_ARRAY ) { // equality IN search
2737 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2738 } else if( node->type == JSON_HASH ) { // other search
2739 jsonIterator* pred_itr = jsonNewIterator( node );
2740 if( !jsonIteratorHasNext( pred_itr ) ) {
2741 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2742 modulename, osrfHashGet(field, "name" ));
2744 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2746 // Verify that there are no additional predicates
2747 if( jsonIteratorHasNext( pred_itr ) ) {
2748 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2749 modulename, osrfHashGet(field, "name" ));
2750 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2751 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2752 else if( !(strcasecmp( pred_itr->key,"in" ))
2753 || !(strcasecmp( pred_itr->key,"not in" )) )
2754 pred = searchINPredicate(
2755 class_info->alias, field, pred_node, pred_itr->key, ctx );
2756 else if( pred_node->type == JSON_ARRAY )
2757 pred = searchFunctionPredicate(
2758 class_info->alias, field, pred_node, pred_itr->key );
2759 else if( pred_node->type == JSON_HASH )
2760 pred = searchFieldTransformPredicate(
2761 class_info, field, pred_node, pred_itr->key );
2763 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2765 jsonIteratorFree( pred_itr );
2767 } else if( node->type == JSON_NULL ) { // IS NULL search
2768 growing_buffer* _p = buffer_init( 64 );
2771 "\"%s\".%s IS NULL",
2772 class_info->class_name,
2773 osrfHashGet( field, "name" )
2775 pred = buffer_release( _p );
2776 } else { // equality search
2777 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2796 field : call_number,
2812 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2814 const jsonObject* working_hash;
2815 jsonObject* freeable_hash = NULL;
2817 if( join_hash->type == JSON_HASH ) {
2818 working_hash = join_hash;
2819 } else if( join_hash->type == JSON_STRING ) {
2820 // turn it into a JSON_HASH by creating a wrapper
2821 // around a copy of the original
2822 const char* _tmp = jsonObjectGetString( join_hash );
2823 freeable_hash = jsonNewObjectType( JSON_HASH );
2824 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2825 working_hash = freeable_hash;
2829 "%s: JOIN failed; expected JSON object type not found",
2835 growing_buffer* join_buf = buffer_init( 128 );
2836 const char* leftclass = left_info->class_name;
2838 jsonObject* snode = NULL;
2839 jsonIterator* search_itr = jsonNewIterator( working_hash );
2841 while ( (snode = jsonIteratorNext( search_itr )) ) {
2842 const char* right_alias = search_itr->key;
2844 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2846 class = right_alias;
2848 const ClassInfo* right_info = add_joined_class( right_alias, class );
2852 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2856 jsonIteratorFree( search_itr );
2857 buffer_free( join_buf );
2859 jsonObjectFree( freeable_hash );
2862 osrfHash* links = right_info->links;
2863 const char* table = right_info->source_def;
2865 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2866 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2868 if( field && !fkey ) {
2869 // Look up the corresponding join column in the IDL.
2870 // The link must be defined in the child table,
2871 // and point to the right parent table.
2872 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2873 const char* reltype = NULL;
2874 const char* other_class = NULL;
2875 reltype = osrfHashGet( idl_link, "reltype" );
2876 if( reltype && strcmp( reltype, "has_many" ) )
2877 other_class = osrfHashGet( idl_link, "class" );
2878 if( other_class && !strcmp( other_class, leftclass ) )
2879 fkey = osrfHashGet( idl_link, "key" );
2883 "%s: JOIN failed. No link defined from %s.%s to %s",
2889 buffer_free( join_buf );
2891 jsonObjectFree( freeable_hash );
2892 jsonIteratorFree( search_itr );
2896 } else if( !field && fkey ) {
2897 // Look up the corresponding join column in the IDL.
2898 // The link must be defined in the child table,
2899 // and point to the right parent table.
2900 osrfHash* left_links = left_info->links;
2901 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2902 const char* reltype = NULL;
2903 const char* other_class = NULL;
2904 reltype = osrfHashGet( idl_link, "reltype" );
2905 if( reltype && strcmp( reltype, "has_many" ) )
2906 other_class = osrfHashGet( idl_link, "class" );
2907 if( other_class && !strcmp( other_class, class ) )
2908 field = osrfHashGet( idl_link, "key" );
2912 "%s: JOIN failed. No link defined from %s.%s to %s",
2918 buffer_free( join_buf );
2920 jsonObjectFree( freeable_hash );
2921 jsonIteratorFree( search_itr );
2925 } else if( !field && !fkey ) {
2926 osrfHash* left_links = left_info->links;
2928 // For each link defined for the left class:
2929 // see if the link references the joined class
2930 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2931 osrfHash* curr_link = NULL;
2932 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2933 const char* other_class = osrfHashGet( curr_link, "class" );
2934 if( other_class && !strcmp( other_class, class ) ) {
2936 // In the IDL, the parent class doesn't always know then names of the child
2937 // columns that are pointing to it, so don't use that end of the link
2938 const char* reltype = osrfHashGet( curr_link, "reltype" );
2939 if( reltype && strcmp( reltype, "has_many" ) ) {
2940 // Found a link between the classes
2941 fkey = osrfHashIteratorKey( itr );
2942 field = osrfHashGet( curr_link, "key" );
2947 osrfHashIteratorFree( itr );
2949 if( !field || !fkey ) {
2950 // Do another such search, with the classes reversed
2952 // For each link defined for the joined class:
2953 // see if the link references the left class
2954 osrfHashIterator* itr = osrfNewHashIterator( links );
2955 osrfHash* curr_link = NULL;
2956 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2957 const char* other_class = osrfHashGet( curr_link, "class" );
2958 if( other_class && !strcmp( other_class, leftclass ) ) {
2960 // In the IDL, the parent class doesn't know then names of the child
2961 // columns that are pointing to it, so don't use that end of the link
2962 const char* reltype = osrfHashGet( curr_link, "reltype" );
2963 if( reltype && strcmp( reltype, "has_many" ) ) {
2964 // Found a link between the classes
2965 field = osrfHashIteratorKey( itr );
2966 fkey = osrfHashGet( curr_link, "key" );
2971 osrfHashIteratorFree( itr );
2974 if( !field || !fkey ) {
2977 "%s: JOIN failed. No link defined between %s and %s",
2982 buffer_free( join_buf );
2984 jsonObjectFree( freeable_hash );
2985 jsonIteratorFree( search_itr );
2990 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2992 if( !strcasecmp( type,"left" )) {
2993 buffer_add( join_buf, " LEFT JOIN" );
2994 } else if( !strcasecmp( type,"right" )) {
2995 buffer_add( join_buf, " RIGHT JOIN" );
2996 } else if( !strcasecmp( type,"full" )) {
2997 buffer_add( join_buf, " FULL JOIN" );
2999 buffer_add( join_buf, " INNER JOIN" );
3002 buffer_add( join_buf, " INNER JOIN" );
3005 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3006 table, right_alias, right_alias, field, left_info->alias, fkey );
3008 // Add any other join conditions as specified by "filter"
3009 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3011 const char* filter_op = jsonObjectGetString(
3012 jsonObjectGetKeyConst( snode, "filter_op" ) );
3013 if( filter_op && !strcasecmp( "or",filter_op )) {
3014 buffer_add( join_buf, " OR " );
3016 buffer_add( join_buf, " AND " );
3019 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3021 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3022 OSRF_BUFFER_ADD( join_buf, jpred );
3027 "%s: JOIN failed. Invalid conditional expression.",
3030 jsonIteratorFree( search_itr );
3031 buffer_free( join_buf );
3033 jsonObjectFree( freeable_hash );
3038 buffer_add( join_buf, " ) " );
3040 // Recursively add a nested join, if one is present
3041 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3043 char* jpred = searchJOIN( join_filter, right_info );
3045 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3046 OSRF_BUFFER_ADD( join_buf, jpred );
3049 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3050 jsonIteratorFree( search_itr );
3051 buffer_free( join_buf );
3053 jsonObjectFree( freeable_hash );
3060 jsonObjectFree( freeable_hash );
3061 jsonIteratorFree( search_itr );
3063 return buffer_release( join_buf );
3068 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3069 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3070 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3072 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3074 search_hash is the JSON expression of the conditions.
3075 meta is the class definition from the IDL, for the relevant table.
3076 opjoin_type indicates whether multiple conditions, if present, should be
3077 connected by AND or OR.
3078 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3079 to pass it to other functions -- and all they do with it is to use the session
3080 and request members to send error messages back to the client.
3084 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3085 int opjoin_type, osrfMethodContext* ctx ) {
3089 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3090 "opjoin_type = %d, ctx addr = %p",
3093 class_info->class_def,
3098 growing_buffer* sql_buf = buffer_init( 128 );
3100 jsonObject* node = NULL;
3103 if( search_hash->type == JSON_ARRAY ) {
3104 if( 0 == search_hash->size ) {
3107 "%s: Invalid predicate structure: empty JSON array",
3110 buffer_free( sql_buf );
3114 unsigned long i = 0;
3115 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3119 if( opjoin_type == OR_OP_JOIN )
3120 buffer_add( sql_buf, " OR " );
3122 buffer_add( sql_buf, " AND " );
3125 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3127 buffer_free( sql_buf );
3131 buffer_fadd( sql_buf, "( %s )", subpred );
3135 } else if( search_hash->type == JSON_HASH ) {
3136 osrfLogDebug( OSRF_LOG_MARK,
3137 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3138 jsonIterator* search_itr = jsonNewIterator( search_hash );
3139 if( !jsonIteratorHasNext( search_itr ) ) {
3142 "%s: Invalid predicate structure: empty JSON object",
3145 jsonIteratorFree( search_itr );
3146 buffer_free( sql_buf );
3150 while( (node = jsonIteratorNext( search_itr )) ) {
3155 if( opjoin_type == OR_OP_JOIN )
3156 buffer_add( sql_buf, " OR " );
3158 buffer_add( sql_buf, " AND " );
3161 if( '+' == search_itr->key[ 0 ] ) {
3163 // This plus sign prefixes a class name or other table alias;
3164 // make sure the table alias is in scope
3165 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3166 if( ! alias_info ) {
3169 "%s: Invalid table alias \"%s\" in WHERE clause",
3173 jsonIteratorFree( search_itr );
3174 buffer_free( sql_buf );
3178 if( node->type == JSON_STRING ) {
3179 // It's the name of a column; make sure it belongs to the class
3180 const char* fieldname = jsonObjectGetString( node );
3181 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3184 "%s: Invalid column name \"%s\" in WHERE clause "
3185 "for table alias \"%s\"",
3190 jsonIteratorFree( search_itr );
3191 buffer_free( sql_buf );
3195 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3197 // It's something more complicated
3198 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3200 jsonIteratorFree( search_itr );
3201 buffer_free( sql_buf );
3205 buffer_fadd( sql_buf, "( %s )", subpred );
3208 } else if( '-' == search_itr->key[ 0 ] ) {
3209 if( !strcasecmp( "-or", search_itr->key )) {
3210 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3212 jsonIteratorFree( search_itr );
3213 buffer_free( sql_buf );
3217 buffer_fadd( sql_buf, "( %s )", subpred );
3219 } else if( !strcasecmp( "-and", search_itr->key )) {
3220 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3222 jsonIteratorFree( search_itr );
3223 buffer_free( sql_buf );
3227 buffer_fadd( sql_buf, "( %s )", subpred );
3229 } else if( !strcasecmp("-not",search_itr->key) ) {
3230 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3232 jsonIteratorFree( search_itr );
3233 buffer_free( sql_buf );
3237 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3239 } else if( !strcasecmp( "-exists", search_itr->key )) {
3240 char* subpred = buildQuery( ctx, node, SUBSELECT );
3242 jsonIteratorFree( search_itr );
3243 buffer_free( sql_buf );
3247 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3249 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3250 char* subpred = buildQuery( ctx, node, SUBSELECT );
3252 jsonIteratorFree( search_itr );
3253 buffer_free( sql_buf );
3257 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3259 } else { // Invalid "minus" operator
3262 "%s: Invalid operator \"%s\" in WHERE clause",
3266 jsonIteratorFree( search_itr );
3267 buffer_free( sql_buf );
3273 const char* class = class_info->class_name;
3274 osrfHash* fields = class_info->fields;
3275 osrfHash* field = osrfHashGet( fields, search_itr->key );
3278 const char* table = class_info->source_def;
3281 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3284 table ? table : "?",
3287 jsonIteratorFree( search_itr );
3288 buffer_free( sql_buf );
3292 char* subpred = searchPredicate( class_info, field, node, ctx );
3294 buffer_free( sql_buf );
3295 jsonIteratorFree( search_itr );
3299 buffer_add( sql_buf, subpred );
3303 jsonIteratorFree( search_itr );
3306 // ERROR ... only hash and array allowed at this level
3307 char* predicate_string = jsonObjectToJSON( search_hash );
3310 "%s: Invalid predicate structure: %s",
3314 buffer_free( sql_buf );
3315 free( predicate_string );
3319 return buffer_release( sql_buf );
3322 /* Build a JSON_ARRAY of field names for a given table alias
3324 static jsonObject* defaultSelectList( const char* table_alias ) {
3329 ClassInfo* class_info = search_all_alias( table_alias );
3330 if( ! class_info ) {
3333 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3340 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3341 osrfHash* field_def = NULL;
3342 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3343 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3344 const char* field_name = osrfHashIteratorKey( field_itr );
3345 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3346 jsonObjectPush( array, jsonNewObject( field_name ) );
3349 osrfHashIteratorFree( field_itr );
3354 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3355 // The jsonObject must be a JSON_HASH with an single entry for "union",
3356 // "intersect", or "except". The data associated with this key must be an
3357 // array of hashes, each hash being a query.
3358 // Also allowed but currently ignored: entries for "order_by" and "alias".
3359 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3361 if( ! combo || combo->type != JSON_HASH )
3362 return NULL; // should be impossible; validated by caller
3364 const jsonObject* query_array = NULL; // array of subordinate queries
3365 const char* op = NULL; // name of operator, e.g. UNION
3366 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3367 int op_count = 0; // for detecting conflicting operators
3368 int excepting = 0; // boolean
3369 int all = 0; // boolean
3370 jsonObject* order_obj = NULL;
3372 // Identify the elements in the hash
3373 jsonIterator* query_itr = jsonNewIterator( combo );
3374 jsonObject* curr_obj = NULL;
3375 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3376 if( ! strcmp( "union", query_itr->key ) ) {
3379 query_array = curr_obj;
3380 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3383 query_array = curr_obj;
3384 } else if( ! strcmp( "except", query_itr->key ) ) {
3388 query_array = curr_obj;
3389 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3392 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3395 order_obj = curr_obj;
3396 } else if( ! strcmp( "alias", query_itr->key ) ) {
3397 if( curr_obj->type != JSON_STRING ) {
3398 jsonIteratorFree( query_itr );
3401 alias = jsonObjectGetString( curr_obj );
3402 } else if( ! strcmp( "all", query_itr->key ) ) {
3403 if( obj_is_true( curr_obj ) )
3407 osrfAppSessionStatus(
3409 OSRF_STATUS_INTERNALSERVERERROR,
3410 "osrfMethodException",
3412 "Malformed query; unexpected entry in query object"
3416 "%s: Unexpected entry for \"%s\" in%squery",
3421 jsonIteratorFree( query_itr );
3425 jsonIteratorFree( query_itr );
3427 // More sanity checks
3428 if( ! query_array ) {
3430 osrfAppSessionStatus(
3432 OSRF_STATUS_INTERNALSERVERERROR,
3433 "osrfMethodException",
3435 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3439 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3442 return NULL; // should be impossible...
3443 } else if( op_count > 1 ) {
3445 osrfAppSessionStatus(
3447 OSRF_STATUS_INTERNALSERVERERROR,
3448 "osrfMethodException",
3450 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3454 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3458 } if( query_array->type != JSON_ARRAY ) {
3460 osrfAppSessionStatus(
3462 OSRF_STATUS_INTERNALSERVERERROR,
3463 "osrfMethodException",
3465 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3469 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3472 json_type( query_array->type )
3475 } if( query_array->size < 2 ) {
3477 osrfAppSessionStatus(
3479 OSRF_STATUS_INTERNALSERVERERROR,
3480 "osrfMethodException",
3482 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3486 "%s:%srequires multiple queries as operands",
3491 } else if( excepting && query_array->size > 2 ) {
3493 osrfAppSessionStatus(
3495 OSRF_STATUS_INTERNALSERVERERROR,
3496 "osrfMethodException",
3498 "EXCEPT operator has too many queries as operands"
3502 "%s:EXCEPT operator has too many queries as operands",
3506 } else if( order_obj && ! alias ) {
3508 osrfAppSessionStatus(
3510 OSRF_STATUS_INTERNALSERVERERROR,
3511 "osrfMethodException",
3513 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3517 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3523 // So far so good. Now build the SQL.
3524 growing_buffer* sql = buffer_init( 256 );
3526 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3527 // Add a layer of parentheses
3528 if( flags & SUBCOMBO )
3529 OSRF_BUFFER_ADD( sql, "( " );
3531 // Traverse the query array. Each entry should be a hash.
3532 int first = 1; // boolean
3534 jsonObject* query = NULL;
3535 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3536 if( query->type != JSON_HASH ) {
3538 osrfAppSessionStatus(
3540 OSRF_STATUS_INTERNALSERVERERROR,
3541 "osrfMethodException",
3543 "Malformed query under UNION, INTERSECT or EXCEPT"
3547 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3550 json_type( query->type )
3559 OSRF_BUFFER_ADD( sql, op );
3561 OSRF_BUFFER_ADD( sql, "ALL " );
3564 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3568 "%s: Error building query under%s",
3576 OSRF_BUFFER_ADD( sql, query_str );
3579 if( flags & SUBCOMBO )
3580 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3582 if( !(flags & SUBSELECT) )
3583 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3585 return buffer_release( sql );
3588 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3589 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3590 // or "except" to indicate the type of query.
3591 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3595 osrfAppSessionStatus(
3597 OSRF_STATUS_INTERNALSERVERERROR,
3598 "osrfMethodException",
3600 "Malformed query; no query object"
3602 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3604 } else if( query->type != JSON_HASH ) {
3606 osrfAppSessionStatus(
3608 OSRF_STATUS_INTERNALSERVERERROR,
3609 "osrfMethodException",
3611 "Malformed query object"
3615 "%s: Query object is %s instead of JSON_HASH",
3617 json_type( query->type )
3622 // Determine what kind of query it purports to be, and dispatch accordingly.
3623 if( jsonObjectGetKey( query, "union" ) ||
3624 jsonObjectGetKey( query, "intersect" ) ||
3625 jsonObjectGetKey( query, "except" ) ) {
3626 return doCombo( ctx, query, flags );
3628 // It is presumably a SELECT query
3630 // Push a node onto the stack for the current query. Every level of
3631 // subquery gets its own QueryFrame on the Stack.
3634 // Build an SQL SELECT statement
3637 jsonObjectGetKey( query, "select" ),
3638 jsonObjectGetKey( query, "from" ),
3639 jsonObjectGetKey( query, "where" ),
3640 jsonObjectGetKey( query, "having" ),
3641 jsonObjectGetKey( query, "order_by" ),
3642 jsonObjectGetKey( query, "limit" ),
3643 jsonObjectGetKey( query, "offset" ),
3652 /* method context */ osrfMethodContext* ctx,
3654 /* SELECT */ jsonObject* selhash,
3655 /* FROM */ jsonObject* join_hash,
3656 /* WHERE */ jsonObject* search_hash,
3657 /* HAVING */ jsonObject* having_hash,
3658 /* ORDER BY */ jsonObject* order_hash,
3659 /* LIMIT */ jsonObject* limit,
3660 /* OFFSET */ jsonObject* offset,
3661 /* flags */ int flags
3663 const char* locale = osrf_message_get_last_locale();
3665 // general tmp objects
3666 const jsonObject* tmp_const;
3667 jsonObject* selclass = NULL;
3668 jsonObject* snode = NULL;
3669 jsonObject* onode = NULL;
3671 char* string = NULL;
3672 int from_function = 0;
3677 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3679 // punt if there's no FROM clause
3680 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3683 "%s: FROM clause is missing or empty",
3687 osrfAppSessionStatus(
3689 OSRF_STATUS_INTERNALSERVERERROR,
3690 "osrfMethodException",
3692 "FROM clause is missing or empty in JSON query"
3697 // the core search class
3698 const char* core_class = NULL;
3700 // get the core class -- the only key of the top level FROM clause, or a string
3701 if( join_hash->type == JSON_HASH ) {
3702 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3703 snode = jsonIteratorNext( tmp_itr );
3705 // Populate the current QueryFrame with information
3706 // about the core class
3707 if( add_query_core( NULL, tmp_itr->key ) ) {
3709 osrfAppSessionStatus(
3711 OSRF_STATUS_INTERNALSERVERERROR,
3712 "osrfMethodException",
3714 "Unable to look up core class"
3718 core_class = curr_query->core.class_name;
3721 jsonObject* extra = jsonIteratorNext( tmp_itr );
3723 jsonIteratorFree( tmp_itr );
3726 // There shouldn't be more than one entry in join_hash
3730 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3734 osrfAppSessionStatus(
3736 OSRF_STATUS_INTERNALSERVERERROR,
3737 "osrfMethodException",
3739 "Malformed FROM clause in JSON query"
3741 return NULL; // Malformed join_hash; extra entry
3743 } else if( join_hash->type == JSON_ARRAY ) {
3744 // We're selecting from a function, not from a table
3746 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3749 } else if( join_hash->type == JSON_STRING ) {
3750 // Populate the current QueryFrame with information
3751 // about the core class
3752 core_class = jsonObjectGetString( join_hash );
3754 if( add_query_core( NULL, core_class ) ) {
3756 osrfAppSessionStatus(
3758 OSRF_STATUS_INTERNALSERVERERROR,
3759 "osrfMethodException",
3761 "Unable to look up core class"
3769 "%s: FROM clause is unexpected JSON type: %s",
3771 json_type( join_hash->type )
3774 osrfAppSessionStatus(
3776 OSRF_STATUS_INTERNALSERVERERROR,
3777 "osrfMethodException",
3779 "Ill-formed FROM clause in JSON query"
3784 // Build the join clause, if any, while filling out the list
3785 // of joined classes in the current QueryFrame.
3786 char* join_clause = NULL;
3787 if( join_hash && ! from_function ) {
3789 join_clause = searchJOIN( join_hash, &curr_query->core );
3790 if( ! join_clause ) {
3792 osrfAppSessionStatus(
3794 OSRF_STATUS_INTERNALSERVERERROR,
3795 "osrfMethodException",
3797 "Unable to construct JOIN clause(s)"
3803 // For in case we don't get a select list
3804 jsonObject* defaultselhash = NULL;
3806 // if there is no select list, build a default select list ...
3807 if( !selhash && !from_function ) {
3808 jsonObject* default_list = defaultSelectList( core_class );
3809 if( ! default_list ) {
3811 osrfAppSessionStatus(
3813 OSRF_STATUS_INTERNALSERVERERROR,
3814 "osrfMethodException",
3816 "Unable to build default SELECT clause in JSON query"
3818 free( join_clause );
3823 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3824 jsonObjectSetKey( selhash, core_class, default_list );
3827 // The SELECT clause can be encoded only by a hash
3828 if( !from_function && selhash->type != JSON_HASH ) {
3831 "%s: Expected JSON_HASH for SELECT clause; found %s",
3833 json_type( selhash->type )
3837 osrfAppSessionStatus(
3839 OSRF_STATUS_INTERNALSERVERERROR,
3840 "osrfMethodException",
3842 "Malformed SELECT clause in JSON query"
3844 free( join_clause );
3848 // If you see a null or wild card specifier for the core class, or an
3849 // empty array, replace it with a default SELECT list
3850 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3852 int default_needed = 0; // boolean
3853 if( JSON_STRING == tmp_const->type
3854 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3856 else if( JSON_NULL == tmp_const->type )
3859 if( default_needed ) {
3860 // Build a default SELECT list
3861 jsonObject* default_list = defaultSelectList( core_class );
3862 if( ! default_list ) {
3864 osrfAppSessionStatus(
3866 OSRF_STATUS_INTERNALSERVERERROR,
3867 "osrfMethodException",
3869 "Can't build default SELECT clause in JSON query"
3871 free( join_clause );
3876 jsonObjectSetKey( selhash, core_class, default_list );
3880 // temp buffers for the SELECT list and GROUP BY clause
3881 growing_buffer* select_buf = buffer_init( 128 );
3882 growing_buffer* group_buf = buffer_init( 128 );
3884 int aggregate_found = 0; // boolean
3886 // Build a select list
3887 if( from_function ) // From a function we select everything
3888 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3891 // Build the SELECT list as SQL
3895 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3896 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3898 const char* cname = selclass_itr->key;
3900 // Make sure the target relation is in the FROM clause.
3902 // At this point join_hash is a step down from the join_hash we
3903 // received as a parameter. If the original was a JSON_STRING,
3904 // then json_hash is now NULL. If the original was a JSON_HASH,
3905 // then json_hash is now the first (and only) entry in it,
3906 // denoting the core class. We've already excluded the
3907 // possibility that the original was a JSON_ARRAY, because in
3908 // that case from_function would be non-NULL, and we wouldn't
3911 // If the current table alias isn't in scope, bail out
3912 ClassInfo* class_info = search_alias( cname );
3913 if( ! class_info ) {
3916 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3921 osrfAppSessionStatus(
3923 OSRF_STATUS_INTERNALSERVERERROR,
3924 "osrfMethodException",
3926 "Selected class not in FROM clause in JSON query"
3928 jsonIteratorFree( selclass_itr );
3929 buffer_free( select_buf );
3930 buffer_free( group_buf );
3931 if( defaultselhash )
3932 jsonObjectFree( defaultselhash );
3933 free( join_clause );
3937 if( selclass->type != JSON_ARRAY ) {
3940 "%s: Malformed SELECT list for class \"%s\"; not an array",
3945 osrfAppSessionStatus(
3947 OSRF_STATUS_INTERNALSERVERERROR,
3948 "osrfMethodException",
3950 "Selected class not in FROM clause in JSON query"
3953 jsonIteratorFree( selclass_itr );
3954 buffer_free( select_buf );
3955 buffer_free( group_buf );
3956 if( defaultselhash )
3957 jsonObjectFree( defaultselhash );
3958 free( join_clause );
3962 // Look up some attributes of the current class
3963 osrfHash* idlClass = class_info->class_def;
3964 osrfHash* class_field_set = class_info->fields;
3965 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3966 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3968 if( 0 == selclass->size ) {
3971 "%s: No columns selected from \"%s\"",
3977 // stitch together the column list for the current table alias...
3978 unsigned long field_idx = 0;
3979 jsonObject* selfield = NULL;
3980 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3982 // If we need a separator comma, add one
3986 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3989 // if the field specification is a string, add it to the list
3990 if( selfield->type == JSON_STRING ) {
3992 // Look up the field in the IDL
3993 const char* col_name = jsonObjectGetString( selfield );
3994 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3996 // No such field in current class
3999 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4005 osrfAppSessionStatus(
4007 OSRF_STATUS_INTERNALSERVERERROR,
4008 "osrfMethodException",
4010 "Selected column not defined in JSON query"
4012 jsonIteratorFree( selclass_itr );
4013 buffer_free( select_buf );
4014 buffer_free( group_buf );
4015 if( defaultselhash )
4016 jsonObjectFree( defaultselhash );
4017 free( join_clause );
4019 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4020 // Virtual field not allowed
4023 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4029 osrfAppSessionStatus(
4031 OSRF_STATUS_INTERNALSERVERERROR,
4032 "osrfMethodException",
4034 "Selected column may not be virtual in JSON query"
4036 jsonIteratorFree( selclass_itr );
4037 buffer_free( select_buf );
4038 buffer_free( group_buf );
4039 if( defaultselhash )
4040 jsonObjectFree( defaultselhash );
4041 free( join_clause );
4047 if( flags & DISABLE_I18N )
4050 i18n = osrfHashGet( field_def, "i18n" );
4052 if( str_is_true( i18n ) ) {
4053 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4054 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4055 class_tname, cname, col_name, class_pkey,
4056 cname, class_pkey, locale, col_name );
4058 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4059 cname, col_name, col_name );
4062 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4063 cname, col_name, col_name );
4066 // ... but it could be an object, in which case we check for a Field Transform
4067 } else if( selfield->type == JSON_HASH ) {
4069 const char* col_name = jsonObjectGetString(
4070 jsonObjectGetKeyConst( selfield, "column" ) );
4072 // Get the field definition from the IDL
4073 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4075 // No such field in current class
4078 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4084 osrfAppSessionStatus(
4086 OSRF_STATUS_INTERNALSERVERERROR,
4087 "osrfMethodException",
4089 "Selected column is not defined in JSON query"
4091 jsonIteratorFree( selclass_itr );
4092 buffer_free( select_buf );
4093 buffer_free( group_buf );
4094 if( defaultselhash )
4095 jsonObjectFree( defaultselhash );
4096 free( join_clause );
4098 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4099 // No such field in current class
4102 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4108 osrfAppSessionStatus(
4110 OSRF_STATUS_INTERNALSERVERERROR,
4111 "osrfMethodException",
4113 "Selected column is virtual in JSON query"
4115 jsonIteratorFree( selclass_itr );
4116 buffer_free( select_buf );
4117 buffer_free( group_buf );
4118 if( defaultselhash )
4119 jsonObjectFree( defaultselhash );
4120 free( join_clause );
4124 // Decide what to use as a column alias
4126 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4127 _alias = jsonObjectGetString( tmp_const );
4128 } else { // Use field name as the alias
4132 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4133 char* transform_str = searchFieldTransform(
4134 class_info->alias, field_def, selfield );
4135 if( transform_str ) {
4136 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4137 free( transform_str );
4140 osrfAppSessionStatus(
4142 OSRF_STATUS_INTERNALSERVERERROR,
4143 "osrfMethodException",
4145 "Unable to generate transform function in JSON query"
4147 jsonIteratorFree( selclass_itr );
4148 buffer_free( select_buf );
4149 buffer_free( group_buf );
4150 if( defaultselhash )
4151 jsonObjectFree( defaultselhash );
4152 free( join_clause );
4159 if( flags & DISABLE_I18N )
4162 i18n = osrfHashGet( field_def, "i18n" );
4164 if( str_is_true( i18n ) ) {
4165 buffer_fadd( select_buf,
4166 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4167 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4168 class_tname, cname, col_name, class_pkey, cname,
4169 class_pkey, locale, _alias );
4171 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4172 cname, col_name, _alias );
4175 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4176 cname, col_name, _alias );
4183 "%s: Selected item is unexpected JSON type: %s",
4185 json_type( selfield->type )
4188 osrfAppSessionStatus(
4190 OSRF_STATUS_INTERNALSERVERERROR,
4191 "osrfMethodException",
4193 "Ill-formed SELECT item in JSON query"
4195 jsonIteratorFree( selclass_itr );
4196 buffer_free( select_buf );
4197 buffer_free( group_buf );
4198 if( defaultselhash )
4199 jsonObjectFree( defaultselhash );
4200 free( join_clause );
4204 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4205 if( obj_is_true( agg_obj ) )
4206 aggregate_found = 1;
4208 // Append a comma (except for the first one)
4209 // and add the column to a GROUP BY clause
4213 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4215 buffer_fadd( group_buf, " %d", sel_pos );
4219 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4221 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4222 if ( ! obj_is_true( aggregate_obj ) ) {
4226 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4229 buffer_fadd(group_buf, " %d", sel_pos);
4232 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4236 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4239 _column = searchFieldTransform(class_info->alias, field, selfield);
4240 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4241 OSRF_BUFFER_ADD(group_buf, _column);
4242 _column = searchFieldTransform(class_info->alias, field, selfield);
4249 } // end while -- iterating across SELECT columns
4251 } // end while -- iterating across classes
4253 jsonIteratorFree( selclass_itr );
4257 char* col_list = buffer_release( select_buf );
4259 // Make sure the SELECT list isn't empty. This can happen, for example,
4260 // if we try to build a default SELECT clause from a non-core table.
4263 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4265 osrfAppSessionStatus(
4267 OSRF_STATUS_INTERNALSERVERERROR,
4268 "osrfMethodException",
4270 "SELECT list is empty"
4273 buffer_free( group_buf );
4274 if( defaultselhash )
4275 jsonObjectFree( defaultselhash );
4276 free( join_clause );
4282 table = searchValueTransform( join_hash );
4284 table = strdup( curr_query->core.source_def );
4288 osrfAppSessionStatus(
4290 OSRF_STATUS_INTERNALSERVERERROR,
4291 "osrfMethodException",
4293 "Unable to identify table for core class"
4296 buffer_free( group_buf );
4297 if( defaultselhash )
4298 jsonObjectFree( defaultselhash );
4299 free( join_clause );
4303 // Put it all together
4304 growing_buffer* sql_buf = buffer_init( 128 );
4305 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4309 // Append the join clause, if any
4311 buffer_add(sql_buf, join_clause );
4312 free( join_clause );
4315 char* order_by_list = NULL;
4316 char* having_buf = NULL;
4318 if( !from_function ) {
4320 // Build a WHERE clause, if there is one
4322 buffer_add( sql_buf, " WHERE " );
4324 // and it's on the WHERE clause
4325 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4328 osrfAppSessionStatus(
4330 OSRF_STATUS_INTERNALSERVERERROR,
4331 "osrfMethodException",
4333 "Severe query error in WHERE predicate -- see error log for more details"
4336 buffer_free( group_buf );
4337 buffer_free( sql_buf );
4338 if( defaultselhash )
4339 jsonObjectFree( defaultselhash );
4343 buffer_add( sql_buf, pred );
4347 // Build a HAVING clause, if there is one
4350 // and it's on the the WHERE clause
4351 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4353 if( ! having_buf ) {
4355 osrfAppSessionStatus(
4357 OSRF_STATUS_INTERNALSERVERERROR,
4358 "osrfMethodException",
4360 "Severe query error in HAVING predicate -- see error log for more details"
4363 buffer_free( group_buf );
4364 buffer_free( sql_buf );
4365 if( defaultselhash )
4366 jsonObjectFree( defaultselhash );
4371 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4373 // Build an ORDER BY clause, if there is one
4374 if( NULL == order_hash )
4375 ; // No ORDER BY? do nothing
4376 else if( JSON_ARRAY == order_hash->type ) {
4377 // Array of field specifications, each specification being a
4378 // hash to define the class, field, and other details
4380 jsonObject* order_spec;
4381 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4383 if( JSON_HASH != order_spec->type ) {
4384 osrfLogError( OSRF_LOG_MARK,
4385 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4386 modulename, json_type( order_spec->type ) );
4388 osrfAppSessionStatus(
4390 OSRF_STATUS_INTERNALSERVERERROR,
4391 "osrfMethodException",
4393 "Malformed ORDER BY clause -- see error log for more details"
4395 buffer_free( order_buf );
4397 buffer_free( group_buf );
4398 buffer_free( sql_buf );
4399 if( defaultselhash )
4400 jsonObjectFree( defaultselhash );
4404 const char* class_alias =
4405 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4407 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4410 OSRF_BUFFER_ADD( order_buf, ", " );
4412 order_buf = buffer_init( 128 );
4414 if( !field || !class_alias ) {
4415 osrfLogError( OSRF_LOG_MARK,
4416 "%s: Missing class or field name in field specification "
4417 "of ORDER BY clause",
4420 osrfAppSessionStatus(
4422 OSRF_STATUS_INTERNALSERVERERROR,
4423 "osrfMethodException",
4425 "Malformed ORDER BY clause -- see error log for more details"
4427 buffer_free( order_buf );
4429 buffer_free( group_buf );
4430 buffer_free( sql_buf );
4431 if( defaultselhash )
4432 jsonObjectFree( defaultselhash );
4436 ClassInfo* order_class_info = search_alias( class_alias );
4437 if( ! order_class_info ) {
4438 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4439 "not in FROM clause", modulename, class_alias );
4441 osrfAppSessionStatus(
4443 OSRF_STATUS_INTERNALSERVERERROR,
4444 "osrfMethodException",
4446 "Invalid class referenced in ORDER BY clause -- "
4447 "see error log for more details"
4450 buffer_free( group_buf );
4451 buffer_free( sql_buf );
4452 if( defaultselhash )
4453 jsonObjectFree( defaultselhash );
4457 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4459 osrfLogError( OSRF_LOG_MARK,
4460 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4461 modulename, class_alias, field );
4463 osrfAppSessionStatus(
4465 OSRF_STATUS_INTERNALSERVERERROR,
4466 "osrfMethodException",
4468 "Invalid field referenced in ORDER BY clause -- "
4469 "see error log for more details"
4472 buffer_free( group_buf );
4473 buffer_free( sql_buf );
4474 if( defaultselhash )
4475 jsonObjectFree( defaultselhash );
4477 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4478 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4479 modulename, field );
4481 osrfAppSessionStatus(
4483 OSRF_STATUS_INTERNALSERVERERROR,
4484 "osrfMethodException",
4486 "Virtual field in ORDER BY clause -- see error log for more details"
4488 buffer_free( order_buf );
4490 buffer_free( group_buf );
4491 buffer_free( sql_buf );
4492 if( defaultselhash )
4493 jsonObjectFree( defaultselhash );
4497 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4498 char* transform_str = searchFieldTransform(
4499 class_alias, field_def, order_spec );
4500 if( ! transform_str ) {
4502 osrfAppSessionStatus(
4504 OSRF_STATUS_INTERNALSERVERERROR,
4505 "osrfMethodException",
4507 "Severe query error in ORDER BY clause -- "
4508 "see error log for more details"
4510 buffer_free( order_buf );
4512 buffer_free( group_buf );
4513 buffer_free( sql_buf );
4514 if( defaultselhash )
4515 jsonObjectFree( defaultselhash );
4519 OSRF_BUFFER_ADD( order_buf, transform_str );
4520 free( transform_str );
4523 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4525 const char* direction =
4526 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4528 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4529 OSRF_BUFFER_ADD( order_buf, " DESC" );
4531 OSRF_BUFFER_ADD( order_buf, " ASC" );
4534 } else if( JSON_HASH == order_hash->type ) {
4535 // This hash is keyed on class alias. Each class has either
4536 // an array of field names or a hash keyed on field name.
4537 jsonIterator* class_itr = jsonNewIterator( order_hash );
4538 while( (snode = jsonIteratorNext( class_itr )) ) {
4540 ClassInfo* order_class_info = search_alias( class_itr->key );
4541 if( ! order_class_info ) {
4542 osrfLogError( OSRF_LOG_MARK,
4543 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4544 modulename, class_itr->key );
4546 osrfAppSessionStatus(
4548 OSRF_STATUS_INTERNALSERVERERROR,
4549 "osrfMethodException",
4551 "Invalid class referenced in ORDER BY clause -- "
4552 "see error log for more details"
4554 jsonIteratorFree( class_itr );
4555 buffer_free( order_buf );
4557 buffer_free( group_buf );
4558 buffer_free( sql_buf );
4559 if( defaultselhash )
4560 jsonObjectFree( defaultselhash );
4564 osrfHash* field_list_def = order_class_info->fields;
4566 if( snode->type == JSON_HASH ) {
4568 // Hash is keyed on field names from the current class. For each field
4569 // there is another layer of hash to define the sorting details, if any,
4570 // or a string to indicate direction of sorting.
4571 jsonIterator* order_itr = jsonNewIterator( snode );
4572 while( (onode = jsonIteratorNext( order_itr )) ) {
4574 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4576 osrfLogError( OSRF_LOG_MARK,
4577 "%s: Invalid field \"%s\" in ORDER BY clause",
4578 modulename, order_itr->key );
4580 osrfAppSessionStatus(
4582 OSRF_STATUS_INTERNALSERVERERROR,
4583 "osrfMethodException",
4585 "Invalid field in ORDER BY clause -- "
4586 "see error log for more details"
4588 jsonIteratorFree( order_itr );
4589 jsonIteratorFree( class_itr );
4590 buffer_free( order_buf );
4592 buffer_free( group_buf );
4593 buffer_free( sql_buf );
4594 if( defaultselhash )
4595 jsonObjectFree( defaultselhash );
4597 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4598 osrfLogError( OSRF_LOG_MARK,
4599 "%s: Virtual field \"%s\" in ORDER BY clause",
4600 modulename, order_itr->key );
4602 osrfAppSessionStatus(
4604 OSRF_STATUS_INTERNALSERVERERROR,
4605 "osrfMethodException",
4607 "Virtual field in ORDER BY clause -- "
4608 "see error log for more details"
4610 jsonIteratorFree( order_itr );
4611 jsonIteratorFree( class_itr );
4612 buffer_free( order_buf );
4614 buffer_free( group_buf );
4615 buffer_free( sql_buf );
4616 if( defaultselhash )
4617 jsonObjectFree( defaultselhash );
4621 const char* direction = NULL;
4622 if( onode->type == JSON_HASH ) {
4623 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4624 string = searchFieldTransform(
4626 osrfHashGet( field_list_def, order_itr->key ),
4630 if( ctx ) osrfAppSessionStatus(
4632 OSRF_STATUS_INTERNALSERVERERROR,
4633 "osrfMethodException",
4635 "Severe query error in ORDER BY clause -- "
4636 "see error log for more details"
4638 jsonIteratorFree( order_itr );
4639 jsonIteratorFree( class_itr );
4641 buffer_free( group_buf );
4642 buffer_free( order_buf);
4643 buffer_free( sql_buf );
4644 if( defaultselhash )
4645 jsonObjectFree( defaultselhash );
4649 growing_buffer* field_buf = buffer_init( 16 );
4650 buffer_fadd( field_buf, "\"%s\".%s",
4651 class_itr->key, order_itr->key );
4652 string = buffer_release( field_buf );
4655 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4656 const char* dir = jsonObjectGetString( tmp_const );
4657 if(!strncasecmp( dir, "d", 1 )) {
4658 direction = " DESC";
4664 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4665 osrfLogError( OSRF_LOG_MARK,
4666 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4667 modulename, json_type( onode->type ) );
4669 osrfAppSessionStatus(
4671 OSRF_STATUS_INTERNALSERVERERROR,
4672 "osrfMethodException",
4674 "Malformed ORDER BY clause -- see error log for more details"
4676 jsonIteratorFree( order_itr );
4677 jsonIteratorFree( class_itr );
4679 buffer_free( group_buf );
4680 buffer_free( order_buf );
4681 buffer_free( sql_buf );
4682 if( defaultselhash )
4683 jsonObjectFree( defaultselhash );
4687 string = strdup( order_itr->key );
4688 const char* dir = jsonObjectGetString( onode );
4689 if( !strncasecmp( dir, "d", 1 )) {
4690 direction = " DESC";
4697 OSRF_BUFFER_ADD( order_buf, ", " );
4699 order_buf = buffer_init( 128 );
4701 OSRF_BUFFER_ADD( order_buf, string );
4705 OSRF_BUFFER_ADD( order_buf, direction );
4709 jsonIteratorFree( order_itr );
4711 } else if( snode->type == JSON_ARRAY ) {
4713 // Array is a list of fields from the current class
4714 unsigned long order_idx = 0;
4715 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4717 const char* _f = jsonObjectGetString( onode );
4719 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4721 osrfLogError( OSRF_LOG_MARK,
4722 "%s: Invalid field \"%s\" in ORDER BY clause",
4725 osrfAppSessionStatus(
4727 OSRF_STATUS_INTERNALSERVERERROR,
4728 "osrfMethodException",
4730 "Invalid field in ORDER BY clause -- "
4731 "see error log for more details"
4733 jsonIteratorFree( class_itr );
4734 buffer_free( order_buf );
4736 buffer_free( group_buf );
4737 buffer_free( sql_buf );
4738 if( defaultselhash )
4739 jsonObjectFree( defaultselhash );
4741 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4742 osrfLogError( OSRF_LOG_MARK,
4743 "%s: Virtual field \"%s\" in ORDER BY clause",
4746 osrfAppSessionStatus(
4748 OSRF_STATUS_INTERNALSERVERERROR,
4749 "osrfMethodException",
4751 "Virtual field in ORDER BY clause -- "
4752 "see error log for more details"
4754 jsonIteratorFree( class_itr );
4755 buffer_free( order_buf );
4757 buffer_free( group_buf );
4758 buffer_free( sql_buf );
4759 if( defaultselhash )
4760 jsonObjectFree( defaultselhash );
4765 OSRF_BUFFER_ADD( order_buf, ", " );
4767 order_buf = buffer_init( 128 );
4769 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4773 // IT'S THE OOOOOOOOOOOLD STYLE!
4775 osrfLogError( OSRF_LOG_MARK,
4776 "%s: Possible SQL injection attempt; direct order by is not allowed",
4779 osrfAppSessionStatus(
4781 OSRF_STATUS_INTERNALSERVERERROR,
4782 "osrfMethodException",
4784 "Severe query error -- see error log for more details"
4789 buffer_free( group_buf );
4790 buffer_free( order_buf );
4791 buffer_free( sql_buf );
4792 if( defaultselhash )
4793 jsonObjectFree( defaultselhash );
4794 jsonIteratorFree( class_itr );
4798 jsonIteratorFree( class_itr );
4800 osrfLogError( OSRF_LOG_MARK,
4801 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4802 modulename, json_type( order_hash->type ) );
4804 osrfAppSessionStatus(
4806 OSRF_STATUS_INTERNALSERVERERROR,
4807 "osrfMethodException",
4809 "Malformed ORDER BY clause -- see error log for more details"
4811 buffer_free( order_buf );
4813 buffer_free( group_buf );
4814 buffer_free( sql_buf );
4815 if( defaultselhash )
4816 jsonObjectFree( defaultselhash );
4821 order_by_list = buffer_release( order_buf );
4825 string = buffer_release( group_buf );
4827 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4828 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4829 OSRF_BUFFER_ADD( sql_buf, string );
4834 if( having_buf && *having_buf ) {
4835 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4836 OSRF_BUFFER_ADD( sql_buf, having_buf );
4840 if( order_by_list ) {
4842 if( *order_by_list ) {
4843 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4844 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4847 free( order_by_list );
4851 const char* str = jsonObjectGetString( limit );
4852 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4856 const char* str = jsonObjectGetString( offset );
4857 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4860 if( !(flags & SUBSELECT) )
4861 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4863 if( defaultselhash )
4864 jsonObjectFree( defaultselhash );
4866 return buffer_release( sql_buf );
4868 } // end of SELECT()
4870 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4872 const char* locale = osrf_message_get_last_locale();
4874 osrfHash* fields = osrfHashGet( meta, "fields" );
4875 char* core_class = osrfHashGet( meta, "classname" );
4877 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4879 jsonObject* node = NULL;
4880 jsonObject* snode = NULL;
4881 jsonObject* onode = NULL;
4882 const jsonObject* _tmp = NULL;
4883 jsonObject* selhash = NULL;
4884 jsonObject* defaultselhash = NULL;
4886 growing_buffer* sql_buf = buffer_init( 128 );
4887 growing_buffer* select_buf = buffer_init( 128 );
4889 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4890 defaultselhash = jsonNewObjectType( JSON_HASH );
4891 selhash = defaultselhash;
4894 // If there's no SELECT list for the core class, build one
4895 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4896 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4898 // Add every non-virtual field to the field list
4899 osrfHash* field_def = NULL;
4900 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4901 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4902 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4903 const char* field = osrfHashIteratorKey( field_itr );
4904 jsonObjectPush( field_list, jsonNewObject( field ) );
4907 osrfHashIteratorFree( field_itr );
4908 jsonObjectSetKey( selhash, core_class, field_list );
4912 jsonIterator* class_itr = jsonNewIterator( selhash );
4913 while( (snode = jsonIteratorNext( class_itr )) ) {
4915 const char* cname = class_itr->key;
4916 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4920 if( strcmp(core_class,class_itr->key )) {
4924 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4925 if( !found->size ) {
4926 jsonObjectFree( found );
4930 jsonObjectFree( found );
4933 jsonIterator* select_itr = jsonNewIterator( snode );
4934 while( (node = jsonIteratorNext( select_itr )) ) {
4935 const char* item_str = jsonObjectGetString( node );
4936 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4937 char* fname = osrfHashGet( field, "name" );
4945 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4950 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4951 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4954 i18n = osrfHashGet( field, "i18n" );
4956 if( str_is_true( i18n ) ) {
4957 char* pkey = osrfHashGet( idlClass, "primarykey" );
4958 char* tname = osrfHashGet( idlClass, "tablename" );
4960 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4961 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4962 tname, cname, fname, pkey, cname, pkey, locale, fname );
4964 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4967 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4971 jsonIteratorFree( select_itr );
4974 jsonIteratorFree( class_itr );
4976 char* col_list = buffer_release( select_buf );
4977 char* table = oilsGetRelation( meta );
4979 table = strdup( "(null)" );
4981 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4985 // Clear the query stack (as a fail-safe precaution against possible
4986 // leftover garbage); then push the first query frame onto the stack.
4987 clear_query_stack();
4989 if( add_query_core( NULL, core_class ) ) {
4991 osrfAppSessionStatus(
4993 OSRF_STATUS_INTERNALSERVERERROR,
4994 "osrfMethodException",
4996 "Unable to build query frame for core class"
5002 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5003 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5004 OSRF_BUFFER_ADD( sql_buf, join_clause );
5005 free( join_clause );
5008 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5009 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5011 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5013 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5015 osrfAppSessionStatus(
5017 OSRF_STATUS_INTERNALSERVERERROR,
5018 "osrfMethodException",
5020 "Severe query error -- see error log for more details"
5022 buffer_free( sql_buf );
5023 if( defaultselhash )
5024 jsonObjectFree( defaultselhash );
5025 clear_query_stack();
5028 buffer_add( sql_buf, pred );
5033 char* string = NULL;
5034 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5036 growing_buffer* order_buf = buffer_init( 128 );
5039 jsonIterator* class_itr = jsonNewIterator( _tmp );
5040 while( (snode = jsonIteratorNext( class_itr )) ) {
5042 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
5045 if( snode->type == JSON_HASH ) {
5047 jsonIterator* order_itr = jsonNewIterator( snode );
5048 while( (onode = jsonIteratorNext( order_itr )) ) {
5050 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5051 class_itr->key, order_itr->key );
5055 char* direction = NULL;
5056 if( onode->type == JSON_HASH ) {
5057 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5058 string = searchFieldTransform( class_itr->key, field_def, onode );
5060 osrfAppSessionStatus(
5062 OSRF_STATUS_INTERNALSERVERERROR,
5063 "osrfMethodException",
5065 "Severe query error in ORDER BY clause -- "
5066 "see error log for more details"
5068 jsonIteratorFree( order_itr );
5069 jsonIteratorFree( class_itr );
5070 buffer_free( order_buf );
5071 buffer_free( sql_buf );
5072 if( defaultselhash )
5073 jsonObjectFree( defaultselhash );
5074 clear_query_stack();
5078 growing_buffer* field_buf = buffer_init( 16 );
5079 buffer_fadd( field_buf, "\"%s\".%s",
5080 class_itr->key, order_itr->key );
5081 string = buffer_release( field_buf );
5084 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5085 const char* dir = jsonObjectGetString( _tmp );
5086 if(!strncasecmp( dir, "d", 1 )) {
5087 direction = " DESC";
5091 string = strdup( order_itr->key );
5092 const char* dir = jsonObjectGetString( onode );
5093 if( !strncasecmp( dir, "d", 1 )) {
5094 direction = " DESC";
5103 buffer_add( order_buf, ", " );
5106 buffer_add( order_buf, string );
5110 buffer_add( order_buf, direction );
5114 jsonIteratorFree( order_itr );
5117 const char* str = jsonObjectGetString( snode );
5118 buffer_add( order_buf, str );
5124 jsonIteratorFree( class_itr );
5126 string = buffer_release( order_buf );
5129 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5130 OSRF_BUFFER_ADD( sql_buf, string );
5136 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5137 const char* str = jsonObjectGetString( _tmp );
5145 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5147 const char* str = jsonObjectGetString( _tmp );
5156 if( defaultselhash )
5157 jsonObjectFree( defaultselhash );
5158 clear_query_stack();
5160 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5161 return buffer_release( sql_buf );
5164 int doJSONSearch ( osrfMethodContext* ctx ) {
5165 if(osrfMethodVerifyContext( ctx )) {
5166 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5170 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5174 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5178 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5179 flags |= SELECT_DISTINCT;
5181 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5182 flags |= DISABLE_I18N;
5184 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5185 clear_query_stack(); // a possibly needless precaution
5186 char* sql = buildQuery( ctx, hash, flags );
5187 clear_query_stack();
5194 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5197 dbhandle = writehandle;
5199 dbi_result result = dbi_conn_query( dbhandle, sql );
5202 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5204 if( dbi_result_first_row( result )) {
5205 /* JSONify the result */
5206 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5209 jsonObject* return_val = oilsMakeJSONFromResult( result );
5210 osrfAppRespond( ctx, return_val );
5211 jsonObjectFree( return_val );
5212 } while( dbi_result_next_row( result ));
5215 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5218 osrfAppRespondComplete( ctx, NULL );
5220 /* clean up the query */
5221 dbi_result_free( result );
5226 int errnum = dbi_conn_error( dbhandle, &msg );
5227 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5228 modulename, sql, errnum, msg ? msg : "(No description available)" );
5229 osrfAppSessionStatus(
5231 OSRF_STATUS_INTERNALSERVERERROR,
5232 "osrfMethodException",
5234 "Severe query error -- see error log for more details"
5236 if( !oilsIsDBConnected( dbhandle ))
5237 osrfAppSessionPanic( ctx->session );
5244 // The last parameter, err, is used to report an error condition by updating an int owned by
5245 // the calling code.
5247 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5248 // It is the responsibility of the calling code to initialize *err before the
5249 // call, so that it will be able to make sense of the result.
5251 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5252 // redundant anyway.
5253 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5254 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5257 dbhandle = writehandle;
5259 char* core_class = osrfHashGet( class_meta, "classname" );
5260 char* pkey = osrfHashGet( class_meta, "primarykey" );
5262 const jsonObject* _tmp;
5264 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5266 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5271 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5273 dbi_result result = dbi_conn_query( dbhandle, sql );
5274 if( NULL == result ) {
5276 int errnum = dbi_conn_error( dbhandle, &msg );
5277 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5278 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5279 msg ? msg : "(No description available)" );
5280 if( !oilsIsDBConnected( dbhandle ))
5281 osrfAppSessionPanic( ctx->session );
5282 osrfAppSessionStatus(
5284 OSRF_STATUS_INTERNALSERVERERROR,
5285 "osrfMethodException",
5287 "Severe query error -- see error log for more details"
5294 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5297 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5298 jsonObject* row_obj = NULL;
5300 if( dbi_result_first_row( result )) {
5302 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5303 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5304 // eliminate the duplicates.
5305 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5306 osrfHash* dedup = osrfNewHash();
5308 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5309 char* pkey_val = oilsFMGetString( row_obj, pkey );
5310 if( osrfHashGet( dedup, pkey_val ) ) {
5311 jsonObjectFree( row_obj );
5314 osrfHashSet( dedup, pkey_val, pkey_val );
5315 jsonObjectPush( res_list, row_obj );
5317 } while( dbi_result_next_row( result ));
5318 osrfHashFree( dedup );
5321 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5325 /* clean up the query */
5326 dbi_result_free( result );
5329 // If we're asked to flesh, and there's anything to flesh, then flesh it
5330 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5331 // something that he has no permission to look at).
5332 if( res_list->size && query_hash && ! enforce_pcrud ) {
5333 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5335 // Get the flesh depth
5336 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5337 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5338 flesh_depth = max_flesh_depth;
5340 // We need a non-zero flesh depth, and a list of fields to flesh
5341 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5342 if( temp_blob && flesh_depth > 0 ) {
5344 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5345 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5347 osrfStringArray* link_fields = NULL;
5348 osrfHash* links = osrfHashGet( class_meta, "links" );
5350 // Make an osrfStringArray of the names of fields to be fleshed
5351 if( flesh_fields ) {
5352 if( flesh_fields->size == 1 ) {
5353 const char* _t = jsonObjectGetString(
5354 jsonObjectGetIndex( flesh_fields, 0 ) );
5355 if( !strcmp( _t, "*" ))
5356 link_fields = osrfHashKeys( links );
5359 if( !link_fields ) {
5361 link_fields = osrfNewStringArray( 1 );
5362 jsonIterator* _i = jsonNewIterator( flesh_fields );
5363 while ((_f = jsonIteratorNext( _i ))) {
5364 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5366 jsonIteratorFree( _i );
5370 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5372 // Iterate over the JSON_ARRAY of rows
5374 unsigned long res_idx = 0;
5375 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5378 const char* link_field;
5380 // Iterate over the list of fleshable fields
5381 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5383 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5385 osrfHash* kid_link = osrfHashGet( links, link_field );
5387 continue; // Not a link field; skip it
5389 osrfHash* field = osrfHashGet( fields, link_field );
5391 continue; // Not a field at all; skip it (IDL is ill-formed)
5393 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5394 osrfHashGet( kid_link, "class" ));
5396 continue; // The class it links to doesn't exist; skip it
5398 const char* reltype = osrfHashGet( kid_link, "reltype" );
5400 continue; // No reltype; skip it (IDL is ill-formed)
5402 osrfHash* value_field = field;
5404 if( !strcmp( reltype, "has_many" )
5405 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5406 value_field = osrfHashGet(
5407 fields, osrfHashGet( class_meta, "primarykey" ) );
5410 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5412 if( link_map->size > 0 ) {
5413 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5416 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5421 osrfHashGet( kid_link, "class" ),
5428 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5429 osrfHashGet( kid_link, "field" ),
5430 osrfHashGet( kid_link, "class" ),
5431 osrfHashGet( kid_link, "key" ),
5432 osrfHashGet( kid_link, "reltype" )
5435 const char* search_key = jsonObjectGetString(
5436 jsonObjectGetIndex( cur,
5437 atoi( osrfHashGet( value_field, "array_position" ) )
5442 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5446 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5448 // construct WHERE clause
5449 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5452 osrfHashGet( kid_link, "key" ),
5453 jsonNewObject( search_key )
5456 // construct the rest of the query, mostly
5457 // by copying pieces of the previous level of query
5458 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5459 jsonObjectSetKey( rest_of_query, "flesh",
5460 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5464 jsonObjectSetKey( rest_of_query, "flesh_fields",
5465 jsonObjectClone( flesh_blob ));
5467 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5468 jsonObjectSetKey( rest_of_query, "order_by",
5469 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5473 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5474 jsonObjectSetKey( rest_of_query, "select",
5475 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5479 // do the query, recursively, to expand the fleshable field
5480 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5481 where_clause, rest_of_query, err );
5483 jsonObjectFree( where_clause );
5484 jsonObjectFree( rest_of_query );
5487 osrfStringArrayFree( link_fields );
5488 jsonObjectFree( res_list );
5489 jsonObjectFree( flesh_blob );
5493 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5494 osrfHashGet( kid_link, "class" ), kids->size );
5496 // Traverse the result set
5497 jsonObject* X = NULL;
5498 if( link_map->size > 0 && kids->size > 0 ) {
5500 kids = jsonNewObjectType( JSON_ARRAY );
5502 jsonObject* _k_node;
5503 unsigned long res_idx = 0;
5504 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5510 (unsigned long) atoi(
5516 osrfHashGet( kid_link, "class" )
5520 osrfStringArrayGetString( link_map, 0 )
5528 } // end while loop traversing X
5531 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5532 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5533 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5534 osrfHashGet( kid_link, "field" ));
5537 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5538 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5542 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5544 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5545 osrfHashGet( kid_link, "field" ) );
5548 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5549 jsonObjectClone( kids )
5554 jsonObjectFree( kids );
5558 jsonObjectFree( kids );
5560 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5561 osrfHashGet( kid_link, "field" ) );
5562 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5564 } // end while loop traversing list of fleshable fields
5565 } // end while loop traversing res_list
5566 jsonObjectFree( flesh_blob );
5567 osrfStringArrayFree( link_fields );
5576 int doUpdate( osrfMethodContext* ctx ) {
5577 if( osrfMethodVerifyContext( ctx )) {
5578 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5583 timeout_needs_resetting = 1;
5585 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5587 jsonObject* target = NULL;
5589 target = jsonObjectGetIndex( ctx->params, 1 );
5591 target = jsonObjectGetIndex( ctx->params, 0 );
5593 if(!verifyObjectClass( ctx, target )) {
5594 osrfAppRespondComplete( ctx, NULL );
5598 if( getXactId( ctx ) == NULL ) {
5599 osrfAppSessionStatus(
5601 OSRF_STATUS_BADREQUEST,
5602 "osrfMethodException",
5604 "No active transaction -- required for UPDATE"
5606 osrfAppRespondComplete( ctx, NULL );
5610 // The following test is harmless but redundant. If a class is
5611 // readonly, we don't register an update method for it.
5612 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5613 osrfAppSessionStatus(
5615 OSRF_STATUS_BADREQUEST,
5616 "osrfMethodException",
5618 "Cannot UPDATE readonly class"
5620 osrfAppRespondComplete( ctx, NULL );
5624 const char* trans_id = getXactId( ctx );
5626 // Set the last_xact_id
5627 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5629 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5630 trans_id, target->classname, index );
5631 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5634 char* pkey = osrfHashGet( meta, "primarykey" );
5635 osrfHash* fields = osrfHashGet( meta, "fields" );
5637 char* id = oilsFMGetString( target, pkey );
5641 "%s updating %s object with %s = %s",
5643 osrfHashGet( meta, "fieldmapper" ),
5648 dbhandle = writehandle;
5649 growing_buffer* sql = buffer_init( 128 );
5650 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5653 osrfHash* field_def = NULL;
5654 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5655 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5657 // Skip virtual fields, and the primary key
5658 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5661 const char* field_name = osrfHashIteratorKey( field_itr );
5662 if( ! strcmp( field_name, pkey ) )
5665 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5667 int value_is_numeric = 0; // boolean
5669 if( field_object && field_object->classname ) {
5670 value = oilsFMGetString(
5672 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5674 } else if( field_object && JSON_BOOL == field_object->type ) {
5675 if( jsonBoolIsTrue( field_object ) )
5676 value = strdup( "t" );
5678 value = strdup( "f" );
5680 value = jsonObjectToSimpleString( field_object );
5681 if( field_object && JSON_NUMBER == field_object->type )
5682 value_is_numeric = 1;
5685 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5686 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5688 if( !field_object || field_object->type == JSON_NULL ) {
5689 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5690 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5694 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5695 buffer_fadd( sql, " %s = NULL", field_name );
5698 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5702 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5704 const char* numtype = get_datatype( field_def );
5705 if( !strncmp( numtype, "INT", 3 ) ) {
5706 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5707 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5708 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5710 // Must really be intended as a string, so quote it
5711 if( dbi_conn_quote_string( dbhandle, &value )) {
5712 buffer_fadd( sql, " %s = %s", field_name, value );
5714 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5715 modulename, value );
5716 osrfAppSessionStatus(
5718 OSRF_STATUS_INTERNALSERVERERROR,
5719 "osrfMethodException",
5721 "Error quoting string -- please see the error log for more details"
5725 osrfHashIteratorFree( field_itr );
5727 osrfAppRespondComplete( ctx, NULL );
5732 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5735 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5739 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5740 buffer_fadd( sql, " %s = %s", field_name, value );
5742 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5743 osrfAppSessionStatus(
5745 OSRF_STATUS_INTERNALSERVERERROR,
5746 "osrfMethodException",
5748 "Error quoting string -- please see the error log for more details"
5752 osrfHashIteratorFree( field_itr );
5754 osrfAppRespondComplete( ctx, NULL );
5763 osrfHashIteratorFree( field_itr );
5765 jsonObject* obj = jsonNewObject( id );
5767 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5768 dbi_conn_quote_string( dbhandle, &id );
5770 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5772 char* query = buffer_release( sql );
5773 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5775 dbi_result result = dbi_conn_query( dbhandle, query );
5780 jsonObjectFree( obj );
5781 obj = jsonNewObject( NULL );
5783 int errnum = dbi_conn_error( dbhandle, &msg );
5786 "%s ERROR updating %s object with %s = %s: %d %s",
5788 osrfHashGet( meta, "fieldmapper" ),
5792 msg ? msg : "(No description available)"
5794 osrfAppSessionStatus(
5796 OSRF_STATUS_INTERNALSERVERERROR,
5797 "osrfMethodException",
5799 "Error in updating a row -- please see the error log for more details"
5801 if( !oilsIsDBConnected( dbhandle ))
5802 osrfAppSessionPanic( ctx->session );
5807 osrfAppRespondComplete( ctx, obj );
5808 jsonObjectFree( obj );
5812 int doDelete( osrfMethodContext* ctx ) {
5813 if( osrfMethodVerifyContext( ctx )) {
5814 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5819 timeout_needs_resetting = 1;
5821 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5823 if( getXactId( ctx ) == NULL ) {
5824 osrfAppSessionStatus(
5826 OSRF_STATUS_BADREQUEST,
5827 "osrfMethodException",
5829 "No active transaction -- required for DELETE"
5831 osrfAppRespondComplete( ctx, NULL );
5835 // The following test is harmless but redundant. If a class is
5836 // readonly, we don't register a delete method for it.
5837 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5838 osrfAppSessionStatus(
5840 OSRF_STATUS_BADREQUEST,
5841 "osrfMethodException",
5843 "Cannot DELETE readonly class"
5845 osrfAppRespondComplete( ctx, NULL );
5849 dbhandle = writehandle;
5851 char* pkey = osrfHashGet( meta, "primarykey" );
5858 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5859 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5860 osrfAppRespondComplete( ctx, NULL );
5864 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5866 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5867 osrfAppRespondComplete( ctx, NULL );
5870 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5875 "%s deleting %s object with %s = %s",
5877 osrfHashGet( meta, "fieldmapper" ),
5882 jsonObject* obj = jsonNewObject( id );
5884 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5885 dbi_conn_quote_string( writehandle, &id );
5887 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5888 osrfHashGet( meta, "tablename" ), pkey, id );
5893 jsonObjectFree( obj );
5894 obj = jsonNewObject( NULL );
5896 int errnum = dbi_conn_error( writehandle, &msg );
5899 "%s ERROR deleting %s object with %s = %s: %d %s",
5901 osrfHashGet( meta, "fieldmapper" ),
5905 msg ? msg : "(No description available)"
5907 osrfAppSessionStatus(
5909 OSRF_STATUS_INTERNALSERVERERROR,
5910 "osrfMethodException",
5912 "Error in deleting a row -- please see the error log for more details"
5914 if( !oilsIsDBConnected( writehandle ))
5915 osrfAppSessionPanic( ctx->session );
5920 osrfAppRespondComplete( ctx, obj );
5921 jsonObjectFree( obj );
5926 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5927 @param result An iterator for a result set; we only look at the current row.
5928 @param @meta Pointer to the class metadata for the core class.
5929 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5931 If a column is not defined in the IDL, or if it has no array_position defined for it in
5932 the IDL, or if it is defined as virtual, ignore it.
5934 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5935 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5936 array_position in the IDL.
5938 A field defined in the IDL but not represented in the returned row will leave a hole
5939 in the JSON_ARRAY. In effect it will be treated as a null value.
5941 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5942 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5943 classname corresponding to the @a meta argument.
5945 The calling code is responsible for freeing the the resulting jsonObject by calling
5948 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5949 if( !( result && meta )) return NULL;
5951 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5952 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5953 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5955 osrfHash* fields = osrfHashGet( meta, "fields" );
5957 int columnIndex = 1;
5958 const char* columnName;
5960 /* cycle through the columns in the row returned from the database */
5961 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5963 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5965 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5967 /* determine the field type and storage attributes */
5968 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5969 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5971 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5972 // or if it has no sequence number there, or if it's virtual, skip it.
5973 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5976 if( str_is_true( osrfHashGet( _f, "virtual" )))
5977 continue; // skip this column: IDL says it's virtual
5979 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5980 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5981 continue; // since we assign sequence numbers dynamically as we load the IDL.
5983 fmIndex = atoi( pos );
5984 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5986 continue; // This field is not defined in the IDL
5989 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5990 // sequence number from the IDL (which is likely to be different from the sequence
5991 // of columns in the SELECT clause).
5992 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5993 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5998 case DBI_TYPE_INTEGER :
6000 if( attr & DBI_INTEGER_SIZE8 )
6001 jsonObjectSetIndex( object, fmIndex,
6002 jsonNewNumberObject(
6003 dbi_result_get_longlong_idx( result, columnIndex )));
6005 jsonObjectSetIndex( object, fmIndex,
6006 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6010 case DBI_TYPE_DECIMAL :
6011 jsonObjectSetIndex( object, fmIndex,
6012 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6015 case DBI_TYPE_STRING :
6020 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6025 case DBI_TYPE_DATETIME : {
6027 char dt_string[ 256 ] = "";
6030 // Fetch the date column as a time_t
6031 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6033 // Translate the time_t to a human-readable string
6034 if( !( attr & DBI_DATETIME_DATE )) {
6035 gmtime_r( &_tmp_dt, &gmdt );
6036 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6037 } else if( !( attr & DBI_DATETIME_TIME )) {
6038 localtime_r( &_tmp_dt, &gmdt );
6039 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6041 localtime_r( &_tmp_dt, &gmdt );
6042 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6045 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6049 case DBI_TYPE_BINARY :
6050 osrfLogError( OSRF_LOG_MARK,
6051 "Can't do binary at column %s : index %d", columnName, columnIndex );
6060 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6061 if( !result ) return NULL;
6063 jsonObject* object = jsonNewObject( NULL );
6066 char dt_string[ 256 ];
6070 int columnIndex = 1;
6072 unsigned short type;
6073 const char* columnName;
6075 /* cycle through the column list */
6076 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6078 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6080 fmIndex = -1; // reset the position
6082 /* determine the field type and storage attributes */
6083 type = dbi_result_get_field_type_idx( result, columnIndex );
6084 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6086 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6087 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6092 case DBI_TYPE_INTEGER :
6094 if( attr & DBI_INTEGER_SIZE8 )
6095 jsonObjectSetKey( object, columnName,
6096 jsonNewNumberObject( dbi_result_get_longlong_idx(
6097 result, columnIndex )) );
6099 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6100 dbi_result_get_int_idx( result, columnIndex )) );
6103 case DBI_TYPE_DECIMAL :
6104 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6105 dbi_result_get_double_idx( result, columnIndex )) );
6108 case DBI_TYPE_STRING :
6109 jsonObjectSetKey( object, columnName,
6110 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6113 case DBI_TYPE_DATETIME :
6115 memset( dt_string, '\0', sizeof( dt_string ));
6116 memset( &gmdt, '\0', sizeof( gmdt ));
6118 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6120 if( !( attr & DBI_DATETIME_DATE )) {
6121 gmtime_r( &_tmp_dt, &gmdt );
6122 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6123 } else if( !( attr & DBI_DATETIME_TIME )) {
6124 localtime_r( &_tmp_dt, &gmdt );
6125 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6127 localtime_r( &_tmp_dt, &gmdt );
6128 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6131 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6134 case DBI_TYPE_BINARY :
6135 osrfLogError( OSRF_LOG_MARK,
6136 "Can't do binary at column %s : index %d", columnName, columnIndex );
6140 } // end while loop traversing result
6145 // Interpret a string as true or false
6146 int str_is_true( const char* str ) {
6147 if( NULL == str || strcasecmp( str, "true" ) )
6153 // Interpret a jsonObject as true or false
6154 static int obj_is_true( const jsonObject* obj ) {
6157 else switch( obj->type )
6165 if( strcasecmp( obj->value.s, "true" ) )
6169 case JSON_NUMBER : // Support 1/0 for perl's sake
6170 if( jsonObjectGetNumber( obj ) == 1.0 )
6179 // Translate a numeric code into a text string identifying a type of
6180 // jsonObject. To be used for building error messages.
6181 static const char* json_type( int code ) {
6187 return "JSON_ARRAY";
6189 return "JSON_STRING";
6191 return "JSON_NUMBER";
6197 return "(unrecognized)";
6201 // Extract the "primitive" attribute from an IDL field definition.
6202 // If we haven't initialized the app, then we must be running in
6203 // some kind of testbed. In that case, default to "string".
6204 static const char* get_primitive( osrfHash* field ) {
6205 const char* s = osrfHashGet( field, "primitive" );
6207 if( child_initialized )
6210 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6212 osrfHashGet( field, "name" )
6220 // Extract the "datatype" attribute from an IDL field definition.
6221 // If we haven't initialized the app, then we must be running in
6222 // some kind of testbed. In that case, default to to NUMERIC,
6223 // since we look at the datatype only for numbers.
6224 static const char* get_datatype( osrfHash* field ) {
6225 const char* s = osrfHashGet( field, "datatype" );
6227 if( child_initialized )
6230 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6232 osrfHashGet( field, "name" )
6241 @brief Determine whether a string is potentially a valid SQL identifier.
6242 @param s The identifier to be tested.
6243 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6245 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6246 need to follow all the rules exactly, such as requiring that the first character not
6249 We allow leading and trailing white space. In between, we do not allow punctuation
6250 (except for underscores and dollar signs), control characters, or embedded white space.
6252 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6253 for the foreseeable future such quoted identifiers are not likely to be an issue.
6255 int is_identifier( const char* s) {
6259 // Skip leading white space
6260 while( isspace( (unsigned char) *s ) )
6264 return 0; // Nothing but white space? Not okay.
6266 // Check each character until we reach white space or
6267 // end-of-string. Letters, digits, underscores, and
6268 // dollar signs are okay. With the exception of periods
6269 // (as in schema.identifier), control characters and other
6270 // punctuation characters are not okay. Anything else
6271 // is okay -- it could for example be part of a multibyte
6272 // UTF8 character such as a letter with diacritical marks,
6273 // and those are allowed.
6275 if( isalnum( (unsigned char) *s )
6279 ; // Fine; keep going
6280 else if( ispunct( (unsigned char) *s )
6281 || iscntrl( (unsigned char) *s ) )
6284 } while( *s && ! isspace( (unsigned char) *s ) );
6286 // If we found any white space in the above loop,
6287 // the rest had better be all white space.
6289 while( isspace( (unsigned char) *s ) )
6293 return 0; // White space was embedded within non-white space
6299 @brief Determine whether to accept a character string as a comparison operator.
6300 @param op The candidate comparison operator.
6301 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6303 We don't validate the operator for real. We just make sure that it doesn't contain
6304 any semicolons or white space (with special exceptions for a few specific operators).
6305 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6306 space but it's still not a valid operator, then the database will complain.
6308 Another approach would be to compare the string against a short list of approved operators.
6309 We don't do that because we want to allow custom operators like ">100*", which at this
6310 writing would be difficult or impossible to express otherwise in a JSON query.
6312 int is_good_operator( const char* op ) {
6313 if( !op ) return 0; // Sanity check
6317 if( isspace( (unsigned char) *s ) ) {
6318 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6319 // and IS NOT DISTINCT FROM.
6320 if( !strcasecmp( op, "similar to" ) )
6322 else if( !strcasecmp( op, "is distinct from" ) )
6324 else if( !strcasecmp( op, "is not distinct from" ) )
6329 else if( ';' == *s )
6337 @name Query Frame Management
6339 The following machinery supports a stack of query frames for use by SELECT().
6341 A query frame caches information about one level of a SELECT query. When we enter
6342 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6344 The query frame stores information about the core class, and about any joined classes
6347 The main purpose is to map table aliases to classes and tables, so that a query can
6348 join to the same table more than once. A secondary goal is to reduce the number of
6349 lookups in the IDL by caching the results.
6353 #define STATIC_CLASS_INFO_COUNT 3
6355 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6358 @brief Allocate a ClassInfo as raw memory.
6359 @return Pointer to the newly allocated ClassInfo.
6361 Except for the in_use flag, which is used only by the allocation and deallocation
6362 logic, we don't initialize the ClassInfo here.
6364 static ClassInfo* allocate_class_info( void ) {
6365 // In order to reduce the number of mallocs and frees, we return a static
6366 // instance of ClassInfo, if we can find one that we're not already using.
6367 // We rely on the fact that the compiler will implicitly initialize the
6368 // static instances so that in_use == 0.
6371 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6372 if( ! static_class_info[ i ].in_use ) {
6373 static_class_info[ i ].in_use = 1;
6374 return static_class_info + i;
6378 // The static ones are all in use. Malloc one.
6380 return safe_malloc( sizeof( ClassInfo ) );
6384 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6385 @param info Pointer to the ClassInfo to be cleared.
6387 static void clear_class_info( ClassInfo* info ) {
6392 // Free any malloc'd strings
6394 if( info->alias != info->alias_store )
6395 free( info->alias );
6397 if( info->class_name != info->class_name_store )
6398 free( info->class_name );
6400 free( info->source_def );
6402 info->alias = info->class_name = info->source_def = NULL;
6407 @brief Free a ClassInfo and everything it owns.
6408 @param info Pointer to the ClassInfo to be freed.
6410 static void free_class_info( ClassInfo* info ) {
6415 clear_class_info( info );
6417 // If it's one of the static instances, just mark it as not in use
6420 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6421 if( info == static_class_info + i ) {
6422 static_class_info[ i ].in_use = 0;
6427 // Otherwise it must have been malloc'd, so free it
6433 @brief Populate an already-allocated ClassInfo.
6434 @param info Pointer to the ClassInfo to be populated.
6435 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6437 @param class Name of the class.
6438 @return Zero if successful, or 1 if not.
6440 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6441 the relevant portions of the IDL for the specified class.
6443 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6446 osrfLogError( OSRF_LOG_MARK,
6447 "%s ERROR: No ClassInfo available to populate", modulename );
6448 info->alias = info->class_name = info->source_def = NULL;
6449 info->class_def = info->fields = info->links = NULL;
6454 osrfLogError( OSRF_LOG_MARK,
6455 "%s ERROR: No class name provided for lookup", modulename );
6456 info->alias = info->class_name = info->source_def = NULL;
6457 info->class_def = info->fields = info->links = NULL;
6461 // Alias defaults to class name if not supplied
6462 if( ! alias || ! alias[ 0 ] )
6465 // Look up class info in the IDL
6466 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6468 osrfLogError( OSRF_LOG_MARK,
6469 "%s ERROR: Class %s not defined in IDL", modulename, class );
6470 info->alias = info->class_name = info->source_def = NULL;
6471 info->class_def = info->fields = info->links = NULL;
6473 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6474 osrfLogError( OSRF_LOG_MARK,
6475 "%s ERROR: Class %s is defined as virtual", modulename, class );
6476 info->alias = info->class_name = info->source_def = NULL;
6477 info->class_def = info->fields = info->links = NULL;
6481 osrfHash* links = osrfHashGet( class_def, "links" );
6483 osrfLogError( OSRF_LOG_MARK,
6484 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6485 info->alias = info->class_name = info->source_def = NULL;
6486 info->class_def = info->fields = info->links = NULL;
6490 osrfHash* fields = osrfHashGet( class_def, "fields" );
6492 osrfLogError( OSRF_LOG_MARK,
6493 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6494 info->alias = info->class_name = info->source_def = NULL;
6495 info->class_def = info->fields = info->links = NULL;
6499 char* source_def = oilsGetRelation( class_def );
6503 // We got everything we need, so populate the ClassInfo
6504 if( strlen( alias ) > ALIAS_STORE_SIZE )
6505 info->alias = strdup( alias );
6507 strcpy( info->alias_store, alias );
6508 info->alias = info->alias_store;
6511 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6512 info->class_name = strdup( class );
6514 strcpy( info->class_name_store, class );
6515 info->class_name = info->class_name_store;
6518 info->source_def = source_def;
6520 info->class_def = class_def;
6521 info->links = links;
6522 info->fields = fields;
6527 #define STATIC_FRAME_COUNT 3
6529 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6532 @brief Allocate a QueryFrame as raw memory.
6533 @return Pointer to the newly allocated QueryFrame.
6535 Except for the in_use flag, which is used only by the allocation and deallocation
6536 logic, we don't initialize the QueryFrame here.
6538 static QueryFrame* allocate_frame( void ) {
6539 // In order to reduce the number of mallocs and frees, we return a static
6540 // instance of QueryFrame, if we can find one that we're not already using.
6541 // We rely on the fact that the compiler will implicitly initialize the
6542 // static instances so that in_use == 0.
6545 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6546 if( ! static_frame[ i ].in_use ) {
6547 static_frame[ i ].in_use = 1;
6548 return static_frame + i;
6552 // The static ones are all in use. Malloc one.
6554 return safe_malloc( sizeof( QueryFrame ) );
6558 @brief Free a QueryFrame, and all the memory it owns.
6559 @param frame Pointer to the QueryFrame to be freed.
6561 static void free_query_frame( QueryFrame* frame ) {
6566 clear_class_info( &frame->core );
6568 // Free the join list
6570 ClassInfo* info = frame->join_list;
6573 free_class_info( info );
6577 frame->join_list = NULL;
6580 // If the frame is a static instance, just mark it as unused
6582 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6583 if( frame == static_frame + i ) {
6584 static_frame[ i ].in_use = 0;
6589 // Otherwise it must have been malloc'd, so free it
6595 @brief Search a given QueryFrame for a specified alias.
6596 @param frame Pointer to the QueryFrame to be searched.
6597 @param target The alias for which to search.
6598 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6600 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6601 if( ! frame || ! target ) {
6605 ClassInfo* found_class = NULL;
6607 if( !strcmp( target, frame->core.alias ) )
6608 return &(frame->core);
6610 ClassInfo* curr_class = frame->join_list;
6611 while( curr_class ) {
6612 if( strcmp( target, curr_class->alias ) )
6613 curr_class = curr_class->next;
6615 found_class = curr_class;
6625 @brief Push a new (blank) QueryFrame onto the stack.
6627 static void push_query_frame( void ) {
6628 QueryFrame* frame = allocate_frame();
6629 frame->join_list = NULL;
6630 frame->next = curr_query;
6632 // Initialize the ClassInfo for the core class
6633 ClassInfo* core = &frame->core;
6634 core->alias = core->class_name = core->source_def = NULL;
6635 core->class_def = core->fields = core->links = NULL;
6641 @brief Pop a QueryFrame off the stack and destroy it.
6643 static void pop_query_frame( void ) {
6648 QueryFrame* popped = curr_query;
6649 curr_query = popped->next;
6651 free_query_frame( popped );
6655 @brief Populate the ClassInfo for the core class.
6656 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6657 class name as an alias.
6658 @param class_name Name of the core class.
6659 @return Zero if successful, or 1 if not.
6661 Populate the ClassInfo of the core class with copies of the alias and class name, and
6662 with pointers to the relevant portions of the IDL for the core class.
6664 static int add_query_core( const char* alias, const char* class_name ) {
6667 if( ! curr_query ) {
6668 osrfLogError( OSRF_LOG_MARK,
6669 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6671 } else if( curr_query->core.alias ) {
6672 osrfLogError( OSRF_LOG_MARK,
6673 "%s ERROR: Core class %s already populated as %s",
6674 modulename, curr_query->core.class_name, curr_query->core.alias );
6678 build_class_info( &curr_query->core, alias, class_name );
6679 if( curr_query->core.alias )
6682 osrfLogError( OSRF_LOG_MARK,
6683 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6689 @brief Search the current QueryFrame for a specified alias.
6690 @param target The alias for which to search.
6691 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6693 static inline ClassInfo* search_alias( const char* target ) {
6694 return search_alias_in_frame( curr_query, target );
6698 @brief Search all levels of query for a specified alias, starting with the current query.
6699 @param target The alias for which to search.
6700 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6702 static ClassInfo* search_all_alias( const char* target ) {
6703 ClassInfo* found_class = NULL;
6704 QueryFrame* curr_frame = curr_query;
6706 while( curr_frame ) {
6707 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6710 curr_frame = curr_frame->next;
6717 @brief Add a class to the list of classes joined to the current query.
6718 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6719 the class name as an alias.
6720 @param classname The name of the class to be added.
6721 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6723 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6725 if( ! classname || ! *classname ) { // sanity check
6726 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6733 const ClassInfo* conflict = search_alias( alias );
6735 osrfLogError( OSRF_LOG_MARK,
6736 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6737 modulename, alias, conflict->class_name );
6741 ClassInfo* info = allocate_class_info();
6743 if( build_class_info( info, alias, classname ) ) {
6744 free_class_info( info );
6748 // Add the new ClassInfo to the join list of the current QueryFrame
6749 info->next = curr_query->join_list;
6750 curr_query->join_list = info;
6756 @brief Destroy all nodes on the query stack.
6758 static void clear_query_stack( void ) {