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 // (Herein lies a bug. Even if we have an image of the row in memory, that
1425 // image may not include the foreign key column(s) that we need.)
1427 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1428 "fetching context org ids" );
1429 const char* pkey = osrfHashGet( class, "primarykey" );
1430 jsonObject *param = NULL;
1432 if( obj->classname ) {
1433 pkey_value = oilsFMGetStringConst( obj, pkey );
1435 param = jsonObjectClone( obj );
1436 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1439 pkey_value = jsonObjectGetString( obj );
1441 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1442 "of %s and retrieving from the database", pkey_value );
1446 // Fetch the row so that we can look at the foreign key(s)
1447 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1448 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1449 jsonObjectFree( _tmp_params );
1451 param = jsonObjectExtractIndex( _list, 0 );
1452 jsonObjectFree( _list );
1456 // The row doesn't exist. Complain, and deny access.
1457 osrfLogDebug( OSRF_LOG_MARK,
1458 "Object not found in the database with primary key %s of %s",
1461 growing_buffer* msg = buffer_init( 128 );
1464 "%s: no object found with primary key %s of %s",
1470 char* m = buffer_release( msg );
1471 osrfAppSessionStatus(
1473 OSRF_STATUS_INTERNALSERVERERROR,
1474 "osrfMethodException",
1483 if( local_context && local_context->size > 0 ) {
1484 // The IDL provides a list of column names for the foreign keys denoting
1485 // local context. Look up the value of each one, and if it isn't null,
1486 // add it to the list of org units.
1487 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1488 local_context->size );
1490 const char* lcontext = NULL;
1491 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1492 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1493 if( fkey_value ) { // if not null
1494 osrfStringArrayAdd( context_org_array, fkey_value );
1497 "adding class-local field %s (value: %s) to the context org list",
1499 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1505 if( foreign_context ) {
1506 unsigned long class_count = osrfHashGetCount( foreign_context );
1507 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1509 if( class_count > 0 ) {
1511 // The IDL provides a list of foreign key columns pointing to rows that
1512 // an org unit may own. Follow each link, identify the owning org unit,
1513 // and add it to the list.
1514 osrfHash* fcontext = NULL;
1515 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1516 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1517 // For each class to which a foreign key points:
1518 const char* class_name = osrfHashIteratorKey( class_itr );
1519 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1523 "%d foreign context fields(s) specified for class %s",
1524 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1528 // Get the name of the key field in the foreign table
1529 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1531 // Get the value of the foreign key pointing to the foreign table
1532 char* foreign_pkey_value =
1533 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1534 if( !foreign_pkey_value )
1535 continue; // Foreign key value is null; skip it
1537 // Look up the row to which the foreign key points
1538 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1539 jsonObject* _list = doFieldmapperSearch(
1540 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1542 jsonObject* _fparam = NULL;
1543 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1544 _fparam = jsonObjectExtractIndex( _list, 0 );
1546 jsonObjectFree( _tmp_params );
1547 jsonObjectFree( _list );
1549 // At this point _fparam either points to the row identified by the
1550 // foreign key, or it's NULL (no such row found).
1552 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1554 const char* bad_class = NULL; // For noting failed lookups
1556 bad_class = class_name; // Referenced row not found
1557 else if( jump_list ) {
1558 // Follow a chain of rows, linked by foreign keys, to find an owner
1559 const char* flink = NULL;
1561 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1562 // For each entry in the jump list. Each entry (i.e. flink) is
1563 // the name of a foreign key column in the current row.
1565 // From the IDL, get the linkage information for the next jump
1566 osrfHash* foreign_link_hash =
1567 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1569 // Get the class metadata for the class
1570 // to which the foreign key points
1571 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1572 osrfHashGet( foreign_link_hash, "class" ));
1574 // Get the name of the referenced key of that class
1575 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1577 // Get the value of the foreign key pointing to that class
1578 free( foreign_pkey_value );
1579 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1580 if( !foreign_pkey_value )
1581 break; // Foreign key is null; quit looking
1583 // Build a WHERE clause for the lookup
1584 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1587 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1588 _tmp_params, NULL, &err );
1590 // Get the resulting row
1591 jsonObjectFree( _fparam );
1592 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1593 _fparam = jsonObjectExtractIndex( _list, 0 );
1595 // Referenced row not found
1597 bad_class = osrfHashGet( foreign_link_hash, "class" );
1600 jsonObjectFree( _tmp_params );
1601 jsonObjectFree( _list );
1607 // We had a foreign key pointing to such-and-such a row, but then
1608 // we couldn't fetch that row. The data in the database are in an
1609 // inconsistent state; the database itself may even be corrupted.
1610 growing_buffer* msg = buffer_init( 128 );
1613 "%s: no object of class %s found with primary key %s of %s",
1617 foreign_pkey_value ? foreign_pkey_value : "(null)"
1620 char* m = buffer_release( msg );
1621 osrfAppSessionStatus(
1623 OSRF_STATUS_INTERNALSERVERERROR,
1624 "osrfMethodException",
1630 osrfHashIteratorFree( class_itr );
1631 free( foreign_pkey_value );
1632 jsonObjectFree( param );
1637 free( foreign_pkey_value );
1640 // Examine each context column of the foreign row,
1641 // and add its value to the list of org units.
1643 const char* foreign_field = NULL;
1644 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1645 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1646 osrfStringArrayAdd( context_org_array,
1647 oilsFMGetStringConst( _fparam, foreign_field ));
1648 osrfLogDebug( OSRF_LOG_MARK,
1649 "adding foreign class %s field %s (value: %s) "
1650 "to the context org list",
1653 osrfStringArrayGetString(
1654 context_org_array, context_org_array->size - 1 )
1658 jsonObjectFree( _fparam );
1662 osrfHashIteratorFree( class_itr );
1666 jsonObjectFree( param );
1669 const char* context_org = NULL;
1670 const char* perm = NULL;
1673 if( permission->size == 0 ) {
1674 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1678 // For every combination of permission and context org unit: call a stored procedure
1679 // to determine if the user has this permission in the context of this org unit.
1680 // If the answer is yes at any point, then we're done, and the user has permission.
1681 // In other words permissions are additive.
1683 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1685 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1691 "Checking object permission [%s] for user %d "
1692 "on object %s (class %s) at org %d",
1696 osrfHashGet( class, "classname" ),
1700 result = dbi_conn_queryf(
1702 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1705 osrfHashGet( class, "classname" ),
1713 "Received a result for object permission [%s] "
1714 "for user %d on object %s (class %s) at org %d",
1718 osrfHashGet( class, "classname" ),
1722 if( dbi_result_first_row( result )) {
1723 jsonObject* return_val = oilsMakeJSONFromResult( result );
1724 const char* has_perm = jsonObjectGetString(
1725 jsonObjectGetKeyConst( return_val, "has_perm" ));
1729 "Status of object permission [%s] for user %d "
1730 "on object %s (class %s) at org %d is %s",
1734 osrfHashGet(class, "classname"),
1739 if( *has_perm == 't' )
1741 jsonObjectFree( return_val );
1744 dbi_result_free( result );
1749 int errnum = dbi_conn_error( writehandle, &msg );
1750 osrfLogWarning( OSRF_LOG_MARK,
1751 "Unable to call check object permissions: %d, %s",
1752 errnum, msg ? msg : "(No description available)" );
1753 if( !oilsIsDBConnected( writehandle ))
1754 osrfAppSessionPanic( ctx->session );
1758 osrfLogDebug( OSRF_LOG_MARK,
1759 "Checking non-object permission [%s] for user %d at org %d",
1760 perm, userid, atoi(context_org) );
1761 result = dbi_conn_queryf(
1763 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1770 osrfLogDebug( OSRF_LOG_MARK,
1771 "Received a result for permission [%s] for user %d at org %d",
1772 perm, userid, atoi( context_org ));
1773 if( dbi_result_first_row( result )) {
1774 jsonObject* return_val = oilsMakeJSONFromResult( result );
1775 const char* has_perm = jsonObjectGetString(
1776 jsonObjectGetKeyConst( return_val, "has_perm" ));
1777 osrfLogDebug( OSRF_LOG_MARK,
1778 "Status of permission [%s] for user %d at org %d is [%s]",
1779 perm, userid, atoi( context_org ), has_perm );
1780 if( *has_perm == 't' )
1782 jsonObjectFree( return_val );
1785 dbi_result_free( result );
1790 int errnum = dbi_conn_error( writehandle, &msg );
1791 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1792 errnum, msg ? msg : "(No description available)" );
1793 if( !oilsIsDBConnected( writehandle ))
1794 osrfAppSessionPanic( ctx->session );
1802 osrfStringArrayFree( context_org_array );
1808 @brief Look up the root of the org_unit tree.
1809 @param ctx Pointer to the method context.
1810 @return The id of the root org unit, as a character string.
1812 Query actor.org_unit where parent_ou is null, and return the id as a string.
1814 This function assumes that there is only one root org unit, i.e. that we
1815 have a single tree, not a forest.
1817 The calling code is responsible for freeing the returned string.
1819 static const char* org_tree_root( osrfMethodContext* ctx ) {
1821 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1822 static time_t last_lookup_time = 0;
1823 time_t current_time = time( NULL );
1825 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1826 // We successfully looked this up less than an hour ago.
1827 // It's not likely to have changed since then.
1828 return strdup( cached_root_id );
1830 last_lookup_time = current_time;
1833 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1834 jsonObject* result = doFieldmapperSearch(
1835 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1836 jsonObjectFree( where_clause );
1838 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1841 jsonObjectFree( result );
1843 growing_buffer* msg = buffer_init( 128 );
1844 OSRF_BUFFER_ADD( msg, modulename );
1845 OSRF_BUFFER_ADD( msg,
1846 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1848 char* m = buffer_release( msg );
1849 osrfAppSessionStatus( ctx->session,
1850 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1853 cached_root_id[ 0 ] = '\0';
1857 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1858 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1860 strcpy( cached_root_id, root_org_unit_id );
1861 jsonObjectFree( result );
1862 return cached_root_id;
1866 @brief Create a JSON_HASH with a single key/value pair.
1867 @param key The key of the key/value pair.
1868 @param value the value of the key/value pair.
1869 @return Pointer to a newly created jsonObject of type JSON_HASH.
1871 The value of the key/value is either a string or (if @a value is NULL) a null.
1873 static jsonObject* single_hash( const char* key, const char* value ) {
1875 if( ! key ) key = "";
1877 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1878 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1883 int doCreate( osrfMethodContext* ctx ) {
1884 if(osrfMethodVerifyContext( ctx )) {
1885 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1890 timeout_needs_resetting = 1;
1892 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1893 jsonObject* target = NULL;
1894 jsonObject* options = NULL;
1896 if( enforce_pcrud ) {
1897 target = jsonObjectGetIndex( ctx->params, 1 );
1898 options = jsonObjectGetIndex( ctx->params, 2 );
1900 target = jsonObjectGetIndex( ctx->params, 0 );
1901 options = jsonObjectGetIndex( ctx->params, 1 );
1904 if( !verifyObjectClass( ctx, target )) {
1905 osrfAppRespondComplete( ctx, NULL );
1909 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1911 const char* trans_id = getXactId( ctx );
1913 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1915 osrfAppSessionStatus(
1917 OSRF_STATUS_BADREQUEST,
1918 "osrfMethodException",
1920 "No active transaction -- required for CREATE"
1922 osrfAppRespondComplete( ctx, NULL );
1926 // The following test is harmless but redundant. If a class is
1927 // readonly, we don't register a create method for it.
1928 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1929 osrfAppSessionStatus(
1931 OSRF_STATUS_BADREQUEST,
1932 "osrfMethodException",
1934 "Cannot INSERT readonly class"
1936 osrfAppRespondComplete( ctx, NULL );
1940 // Set the last_xact_id
1941 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1943 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1944 trans_id, target->classname, index);
1945 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1948 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1950 dbhandle = writehandle;
1952 osrfHash* fields = osrfHashGet( meta, "fields" );
1953 char* pkey = osrfHashGet( meta, "primarykey" );
1954 char* seq = osrfHashGet( meta, "sequence" );
1956 growing_buffer* table_buf = buffer_init( 128 );
1957 growing_buffer* col_buf = buffer_init( 128 );
1958 growing_buffer* val_buf = buffer_init( 128 );
1960 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1961 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1962 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1963 buffer_add( val_buf,"VALUES (" );
1967 osrfHash* field = NULL;
1968 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1969 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1971 const char* field_name = osrfHashIteratorKey( field_itr );
1973 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1976 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1979 if( field_object && field_object->classname ) {
1980 value = oilsFMGetString(
1982 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1984 } else if( field_object && JSON_BOOL == field_object->type ) {
1985 if( jsonBoolIsTrue( field_object ) )
1986 value = strdup( "t" );
1988 value = strdup( "f" );
1990 value = jsonObjectToSimpleString( field_object );
1996 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1997 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2000 buffer_add( col_buf, field_name );
2002 if( !field_object || field_object->type == JSON_NULL ) {
2003 buffer_add( val_buf, "DEFAULT" );
2005 } else if( !strcmp( get_primitive( field ), "number" )) {
2006 const char* numtype = get_datatype( field );
2007 if( !strcmp( numtype, "INT8" )) {
2008 buffer_fadd( val_buf, "%lld", atoll( value ));
2010 } else if( !strcmp( numtype, "INT" )) {
2011 buffer_fadd( val_buf, "%d", atoi( value ));
2013 } else if( !strcmp( numtype, "NUMERIC" )) {
2014 buffer_fadd( val_buf, "%f", atof( value ));
2017 if( dbi_conn_quote_string( writehandle, &value )) {
2018 OSRF_BUFFER_ADD( val_buf, value );
2021 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2022 osrfAppSessionStatus(
2024 OSRF_STATUS_INTERNALSERVERERROR,
2025 "osrfMethodException",
2027 "Error quoting string -- please see the error log for more details"
2030 buffer_free( table_buf );
2031 buffer_free( col_buf );
2032 buffer_free( val_buf );
2033 osrfAppRespondComplete( ctx, NULL );
2041 osrfHashIteratorFree( field_itr );
2043 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2044 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2046 char* table_str = buffer_release( table_buf );
2047 char* col_str = buffer_release( col_buf );
2048 char* val_str = buffer_release( val_buf );
2049 growing_buffer* sql = buffer_init( 128 );
2050 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2055 char* query = buffer_release( sql );
2057 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2059 jsonObject* obj = NULL;
2062 dbi_result result = dbi_conn_query( writehandle, query );
2064 obj = jsonNewObject( NULL );
2066 int errnum = dbi_conn_error( writehandle, &msg );
2069 "%s ERROR inserting %s object using query [%s]: %d %s",
2071 osrfHashGet(meta, "fieldmapper"),
2074 msg ? msg : "(No description available)"
2076 osrfAppSessionStatus(
2078 OSRF_STATUS_INTERNALSERVERERROR,
2079 "osrfMethodException",
2081 "INSERT error -- please see the error log for more details"
2083 if( !oilsIsDBConnected( writehandle ))
2084 osrfAppSessionPanic( ctx->session );
2088 char* id = oilsFMGetString( target, pkey );
2090 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2091 growing_buffer* _id = buffer_init( 10 );
2092 buffer_fadd( _id, "%lld", new_id );
2093 id = buffer_release( _id );
2096 // Find quietness specification, if present
2097 const char* quiet_str = NULL;
2099 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2101 quiet_str = jsonObjectGetString( quiet_obj );
2104 if( str_is_true( quiet_str )) { // if quietness is specified
2105 obj = jsonNewObject( id );
2109 // Fetch the row that we just inserted, so that we can return it to the client
2110 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2111 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2114 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2118 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2120 jsonObjectFree( list );
2121 jsonObjectFree( where_clause );
2128 osrfAppRespondComplete( ctx, obj );
2129 jsonObjectFree( obj );
2134 @brief Implement the retrieve method.
2135 @param ctx Pointer to the method context.
2136 @param err Pointer through which to return an error code.
2137 @return If successful, a pointer to the result to be returned to the client;
2140 From the method's class, fetch a row with a specified value in the primary key. This
2141 method relies on the database design convention that a primary key consists of a single
2145 - authkey (PCRUD only)
2146 - value of the primary key for the desired row, for building the WHERE clause
2147 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2149 Return to client: One row from the query.
2151 int doRetrieve( osrfMethodContext* ctx ) {
2152 if(osrfMethodVerifyContext( ctx )) {
2153 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2158 timeout_needs_resetting = 1;
2163 if( enforce_pcrud ) {
2168 // Get the class metadata
2169 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2171 // Get the value of the primary key, from a method parameter
2172 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2176 "%s retrieving %s object with primary key value of %s",
2178 osrfHashGet( class_def, "fieldmapper" ),
2179 jsonObjectGetString( id_obj )
2182 // Build a WHERE clause based on the key value
2183 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2186 osrfHashGet( class_def, "primarykey" ), // name of key column
2187 jsonObjectClone( id_obj ) // value of key column
2190 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2194 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2196 jsonObjectFree( where_clause );
2198 osrfAppRespondComplete( ctx, NULL );
2202 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2203 jsonObjectFree( list );
2205 if( enforce_pcrud ) {
2206 if(!verifyObjectPCRUD( ctx, obj )) {
2207 jsonObjectFree( obj );
2209 growing_buffer* msg = buffer_init( 128 );
2210 OSRF_BUFFER_ADD( msg, modulename );
2211 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2213 char* m = buffer_release( msg );
2214 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2218 osrfAppRespondComplete( ctx, NULL );
2223 osrfAppRespondComplete( ctx, obj );
2224 jsonObjectFree( obj );
2229 @brief Translate a numeric value to a string representation for the database.
2230 @param field Pointer to the IDL field definition.
2231 @param value Pointer to a jsonObject holding the value of a field.
2232 @return Pointer to a newly allocated string.
2234 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2235 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2236 or (what is worse) valid SQL that is wrong.
2238 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2240 The calling code is responsible for freeing the resulting string by calling free().
2242 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2243 growing_buffer* val_buf = buffer_init( 32 );
2244 const char* numtype = get_datatype( field );
2246 // For historical reasons the following contains cruft that could be cleaned up.
2247 if( !strncmp( numtype, "INT", 3 ) ) {
2248 if( value->type == JSON_NUMBER )
2249 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2250 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2252 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2255 } else if( !strcmp( numtype, "NUMERIC" )) {
2256 if( value->type == JSON_NUMBER )
2257 buffer_fadd( val_buf, jsonObjectGetString( value ));
2259 buffer_fadd( val_buf, jsonObjectGetString( value ));
2263 // Presumably this was really intended to be a string, so quote it
2264 char* str = jsonObjectToSimpleString( value );
2265 if( dbi_conn_quote_string( dbhandle, &str )) {
2266 OSRF_BUFFER_ADD( val_buf, str );
2269 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2271 buffer_free( val_buf );
2276 return buffer_release( val_buf );
2279 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2280 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2281 growing_buffer* sql_buf = buffer_init( 32 );
2287 osrfHashGet( field, "name" )
2291 buffer_add( sql_buf, "IN (" );
2292 } else if( !strcasecmp( op,"not in" )) {
2293 buffer_add( sql_buf, "NOT IN (" );
2295 buffer_add( sql_buf, "IN (" );
2298 if( node->type == JSON_HASH ) {
2299 // subquery predicate
2300 char* subpred = buildQuery( ctx, node, SUBSELECT );
2302 buffer_free( sql_buf );
2306 buffer_add( sql_buf, subpred );
2309 } else if( node->type == JSON_ARRAY ) {
2310 // literal value list
2311 int in_item_index = 0;
2312 int in_item_first = 1;
2313 const jsonObject* in_item;
2314 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2319 buffer_add( sql_buf, ", " );
2322 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2323 osrfLogError( OSRF_LOG_MARK,
2324 "%s: Expected string or number within IN list; found %s",
2325 modulename, json_type( in_item->type ) );
2326 buffer_free( sql_buf );
2330 // Append the literal value -- quoted if not a number
2331 if( JSON_NUMBER == in_item->type ) {
2332 char* val = jsonNumberToDBString( field, in_item );
2333 OSRF_BUFFER_ADD( sql_buf, val );
2336 } else if( !strcmp( get_primitive( field ), "number" )) {
2337 char* val = jsonNumberToDBString( field, in_item );
2338 OSRF_BUFFER_ADD( sql_buf, val );
2342 char* key_string = jsonObjectToSimpleString( in_item );
2343 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2344 OSRF_BUFFER_ADD( sql_buf, key_string );
2347 osrfLogError( OSRF_LOG_MARK,
2348 "%s: Error quoting key string [%s]", modulename, key_string );
2350 buffer_free( sql_buf );
2356 if( in_item_first ) {
2357 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2358 buffer_free( sql_buf );
2362 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2363 modulename, json_type( node->type ));
2364 buffer_free( sql_buf );
2368 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2370 return buffer_release( sql_buf );
2373 // Receive a JSON_ARRAY representing a function call. The first
2374 // entry in the array is the function name. The rest are parameters.
2375 static char* searchValueTransform( const jsonObject* array ) {
2377 if( array->size < 1 ) {
2378 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2382 // Get the function name
2383 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2384 if( func_item->type != JSON_STRING ) {
2385 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2386 modulename, json_type( func_item->type ));
2390 growing_buffer* sql_buf = buffer_init( 32 );
2392 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2393 OSRF_BUFFER_ADD( sql_buf, "( " );
2395 // Get the parameters
2396 int func_item_index = 1; // We already grabbed the zeroth entry
2397 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2399 // Add a separator comma, if we need one
2400 if( func_item_index > 2 )
2401 buffer_add( sql_buf, ", " );
2403 // Add the current parameter
2404 if( func_item->type == JSON_NULL ) {
2405 buffer_add( sql_buf, "NULL" );
2407 char* val = jsonObjectToSimpleString( func_item );
2408 if( dbi_conn_quote_string( dbhandle, &val )) {
2409 OSRF_BUFFER_ADD( sql_buf, val );
2412 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2414 buffer_free( sql_buf );
2421 buffer_add( sql_buf, " )" );
2423 return buffer_release( sql_buf );
2426 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2427 const jsonObject* node, const char* op ) {
2429 if( ! is_good_operator( op ) ) {
2430 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2434 char* val = searchValueTransform( node );
2438 growing_buffer* sql_buf = buffer_init( 32 );
2443 osrfHashGet( field, "name" ),
2450 return buffer_release( sql_buf );
2453 // class_alias is a class name or other table alias
2454 // field is a field definition as stored in the IDL
2455 // node comes from the method parameter, and may represent an entry in the SELECT list
2456 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2457 const jsonObject* node ) {
2458 growing_buffer* sql_buf = buffer_init( 32 );
2460 const char* field_transform = jsonObjectGetString(
2461 jsonObjectGetKeyConst( node, "transform" ) );
2462 const char* transform_subcolumn = jsonObjectGetString(
2463 jsonObjectGetKeyConst( node, "result_field" ) );
2465 if( transform_subcolumn ) {
2466 if( ! is_identifier( transform_subcolumn ) ) {
2467 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2468 modulename, transform_subcolumn );
2469 buffer_free( sql_buf );
2472 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2475 if( field_transform ) {
2477 if( ! is_identifier( field_transform ) ) {
2478 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2479 modulename, field_transform );
2480 buffer_free( sql_buf );
2484 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2485 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2486 field_transform, class_alias, osrfHashGet( field, "name" ));
2488 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2489 field_transform, class_alias, osrfHashGet( field, "name" ));
2492 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2495 if( array->type != JSON_ARRAY ) {
2496 osrfLogError( OSRF_LOG_MARK,
2497 "%s: Expected JSON_ARRAY for function params; found %s",
2498 modulename, json_type( array->type ) );
2499 buffer_free( sql_buf );
2502 int func_item_index = 0;
2503 jsonObject* func_item;
2504 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2506 char* val = jsonObjectToSimpleString( func_item );
2509 buffer_add( sql_buf, ",NULL" );
2510 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2511 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2512 OSRF_BUFFER_ADD( sql_buf, val );
2514 osrfLogError( OSRF_LOG_MARK,
2515 "%s: Error quoting key string [%s]", modulename, val );
2517 buffer_free( sql_buf );
2524 buffer_add( sql_buf, " )" );
2527 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2530 if( transform_subcolumn )
2531 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2533 return buffer_release( sql_buf );
2536 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2537 const jsonObject* node, const char* op ) {
2539 if( ! is_good_operator( op ) ) {
2540 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2544 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2545 if( ! field_transform )
2548 int extra_parens = 0; // boolean
2550 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2552 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2554 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2556 free( field_transform );
2560 } else if( value_obj->type == JSON_ARRAY ) {
2561 value = searchValueTransform( value_obj );
2563 osrfLogError( OSRF_LOG_MARK,
2564 "%s: Error building value transform for field transform", modulename );
2565 free( field_transform );
2568 } else if( value_obj->type == JSON_HASH ) {
2569 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2571 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2573 free( field_transform );
2577 } else if( value_obj->type == JSON_NUMBER ) {
2578 value = jsonNumberToDBString( field, value_obj );
2579 } else if( value_obj->type == JSON_NULL ) {
2580 osrfLogError( OSRF_LOG_MARK,
2581 "%s: Error building predicate for field transform: null value", modulename );
2582 free( field_transform );
2584 } else if( value_obj->type == JSON_BOOL ) {
2585 osrfLogError( OSRF_LOG_MARK,
2586 "%s: Error building predicate for field transform: boolean value", modulename );
2587 free( field_transform );
2590 if( !strcmp( get_primitive( field ), "number") ) {
2591 value = jsonNumberToDBString( field, value_obj );
2593 value = jsonObjectToSimpleString( value_obj );
2594 if( !dbi_conn_quote_string( dbhandle, &value )) {
2595 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2596 modulename, value );
2598 free( field_transform );
2604 const char* left_parens = "";
2605 const char* right_parens = "";
2607 if( extra_parens ) {
2612 growing_buffer* sql_buf = buffer_init( 32 );
2616 "%s%s %s %s %s %s%s",
2627 free( field_transform );
2629 return buffer_release( sql_buf );
2632 static char* searchSimplePredicate( const char* op, const char* class_alias,
2633 osrfHash* field, const jsonObject* node ) {
2635 if( ! is_good_operator( op ) ) {
2636 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2642 // Get the value to which we are comparing the specified column
2643 if( node->type != JSON_NULL ) {
2644 if( node->type == JSON_NUMBER ) {
2645 val = jsonNumberToDBString( field, node );
2646 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2647 val = jsonNumberToDBString( field, node );
2649 val = jsonObjectToSimpleString( node );
2654 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2655 // Value is not numeric; enclose it in quotes
2656 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2657 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2664 // Compare to a null value
2665 val = strdup( "NULL" );
2666 if( strcmp( op, "=" ))
2672 growing_buffer* sql_buf = buffer_init( 32 );
2673 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2674 char* pred = buffer_release( sql_buf );
2681 static char* searchBETWEENPredicate( const char* class_alias,
2682 osrfHash* field, const jsonObject* node ) {
2684 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2685 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2687 if( NULL == y_node ) {
2688 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2691 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2692 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2699 if( !strcmp( get_primitive( field ), "number") ) {
2700 x_string = jsonNumberToDBString( field, x_node );
2701 y_string = jsonNumberToDBString( field, y_node );
2704 x_string = jsonObjectToSimpleString( x_node );
2705 y_string = jsonObjectToSimpleString( y_node );
2706 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2707 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2708 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2709 modulename, x_string, y_string );
2716 growing_buffer* sql_buf = buffer_init( 32 );
2717 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2718 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2722 return buffer_release( sql_buf );
2725 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2726 jsonObject* node, osrfMethodContext* ctx ) {
2729 if( node->type == JSON_ARRAY ) { // equality IN search
2730 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2731 } else if( node->type == JSON_HASH ) { // other search
2732 jsonIterator* pred_itr = jsonNewIterator( node );
2733 if( !jsonIteratorHasNext( pred_itr ) ) {
2734 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2735 modulename, osrfHashGet(field, "name" ));
2737 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2739 // Verify that there are no additional predicates
2740 if( jsonIteratorHasNext( pred_itr ) ) {
2741 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2742 modulename, osrfHashGet(field, "name" ));
2743 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2744 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2745 else if( !(strcasecmp( pred_itr->key,"in" ))
2746 || !(strcasecmp( pred_itr->key,"not in" )) )
2747 pred = searchINPredicate(
2748 class_info->alias, field, pred_node, pred_itr->key, ctx );
2749 else if( pred_node->type == JSON_ARRAY )
2750 pred = searchFunctionPredicate(
2751 class_info->alias, field, pred_node, pred_itr->key );
2752 else if( pred_node->type == JSON_HASH )
2753 pred = searchFieldTransformPredicate(
2754 class_info, field, pred_node, pred_itr->key );
2756 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2758 jsonIteratorFree( pred_itr );
2760 } else if( node->type == JSON_NULL ) { // IS NULL search
2761 growing_buffer* _p = buffer_init( 64 );
2764 "\"%s\".%s IS NULL",
2765 class_info->class_name,
2766 osrfHashGet( field, "name" )
2768 pred = buffer_release( _p );
2769 } else { // equality search
2770 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2789 field : call_number,
2805 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2807 const jsonObject* working_hash;
2808 jsonObject* freeable_hash = NULL;
2810 if( join_hash->type == JSON_HASH ) {
2811 working_hash = join_hash;
2812 } else if( join_hash->type == JSON_STRING ) {
2813 // turn it into a JSON_HASH by creating a wrapper
2814 // around a copy of the original
2815 const char* _tmp = jsonObjectGetString( join_hash );
2816 freeable_hash = jsonNewObjectType( JSON_HASH );
2817 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2818 working_hash = freeable_hash;
2822 "%s: JOIN failed; expected JSON object type not found",
2828 growing_buffer* join_buf = buffer_init( 128 );
2829 const char* leftclass = left_info->class_name;
2831 jsonObject* snode = NULL;
2832 jsonIterator* search_itr = jsonNewIterator( working_hash );
2834 while ( (snode = jsonIteratorNext( search_itr )) ) {
2835 const char* right_alias = search_itr->key;
2837 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2839 class = right_alias;
2841 const ClassInfo* right_info = add_joined_class( right_alias, class );
2845 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2849 jsonIteratorFree( search_itr );
2850 buffer_free( join_buf );
2852 jsonObjectFree( freeable_hash );
2855 osrfHash* links = right_info->links;
2856 const char* table = right_info->source_def;
2858 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2859 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2861 if( field && !fkey ) {
2862 // Look up the corresponding join column in the IDL.
2863 // The link must be defined in the child table,
2864 // and point to the right parent table.
2865 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2866 const char* reltype = NULL;
2867 const char* other_class = NULL;
2868 reltype = osrfHashGet( idl_link, "reltype" );
2869 if( reltype && strcmp( reltype, "has_many" ) )
2870 other_class = osrfHashGet( idl_link, "class" );
2871 if( other_class && !strcmp( other_class, leftclass ) )
2872 fkey = osrfHashGet( idl_link, "key" );
2876 "%s: JOIN failed. No link defined from %s.%s to %s",
2882 buffer_free( join_buf );
2884 jsonObjectFree( freeable_hash );
2885 jsonIteratorFree( search_itr );
2889 } else if( !field && fkey ) {
2890 // Look up the corresponding join column in the IDL.
2891 // The link must be defined in the child table,
2892 // and point to the right parent table.
2893 osrfHash* left_links = left_info->links;
2894 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2895 const char* reltype = NULL;
2896 const char* other_class = NULL;
2897 reltype = osrfHashGet( idl_link, "reltype" );
2898 if( reltype && strcmp( reltype, "has_many" ) )
2899 other_class = osrfHashGet( idl_link, "class" );
2900 if( other_class && !strcmp( other_class, class ) )
2901 field = osrfHashGet( idl_link, "key" );
2905 "%s: JOIN failed. No link defined from %s.%s to %s",
2911 buffer_free( join_buf );
2913 jsonObjectFree( freeable_hash );
2914 jsonIteratorFree( search_itr );
2918 } else if( !field && !fkey ) {
2919 osrfHash* left_links = left_info->links;
2921 // For each link defined for the left class:
2922 // see if the link references the joined class
2923 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2924 osrfHash* curr_link = NULL;
2925 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2926 const char* other_class = osrfHashGet( curr_link, "class" );
2927 if( other_class && !strcmp( other_class, class ) ) {
2929 // In the IDL, the parent class doesn't always know then names of the child
2930 // columns that are pointing to it, so don't use that end of the link
2931 const char* reltype = osrfHashGet( curr_link, "reltype" );
2932 if( reltype && strcmp( reltype, "has_many" ) ) {
2933 // Found a link between the classes
2934 fkey = osrfHashIteratorKey( itr );
2935 field = osrfHashGet( curr_link, "key" );
2940 osrfHashIteratorFree( itr );
2942 if( !field || !fkey ) {
2943 // Do another such search, with the classes reversed
2945 // For each link defined for the joined class:
2946 // see if the link references the left class
2947 osrfHashIterator* itr = osrfNewHashIterator( links );
2948 osrfHash* curr_link = NULL;
2949 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2950 const char* other_class = osrfHashGet( curr_link, "class" );
2951 if( other_class && !strcmp( other_class, leftclass ) ) {
2953 // In the IDL, the parent class doesn't know then names of the child
2954 // columns that are pointing to it, so don't use that end of the link
2955 const char* reltype = osrfHashGet( curr_link, "reltype" );
2956 if( reltype && strcmp( reltype, "has_many" ) ) {
2957 // Found a link between the classes
2958 field = osrfHashIteratorKey( itr );
2959 fkey = osrfHashGet( curr_link, "key" );
2964 osrfHashIteratorFree( itr );
2967 if( !field || !fkey ) {
2970 "%s: JOIN failed. No link defined between %s and %s",
2975 buffer_free( join_buf );
2977 jsonObjectFree( freeable_hash );
2978 jsonIteratorFree( search_itr );
2983 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2985 if( !strcasecmp( type,"left" )) {
2986 buffer_add( join_buf, " LEFT JOIN" );
2987 } else if( !strcasecmp( type,"right" )) {
2988 buffer_add( join_buf, " RIGHT JOIN" );
2989 } else if( !strcasecmp( type,"full" )) {
2990 buffer_add( join_buf, " FULL JOIN" );
2992 buffer_add( join_buf, " INNER JOIN" );
2995 buffer_add( join_buf, " INNER JOIN" );
2998 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2999 table, right_alias, right_alias, field, left_info->alias, fkey );
3001 // Add any other join conditions as specified by "filter"
3002 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3004 const char* filter_op = jsonObjectGetString(
3005 jsonObjectGetKeyConst( snode, "filter_op" ) );
3006 if( filter_op && !strcasecmp( "or",filter_op )) {
3007 buffer_add( join_buf, " OR " );
3009 buffer_add( join_buf, " AND " );
3012 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3014 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3015 OSRF_BUFFER_ADD( join_buf, jpred );
3020 "%s: JOIN failed. Invalid conditional expression.",
3023 jsonIteratorFree( search_itr );
3024 buffer_free( join_buf );
3026 jsonObjectFree( freeable_hash );
3031 buffer_add( join_buf, " ) " );
3033 // Recursively add a nested join, if one is present
3034 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3036 char* jpred = searchJOIN( join_filter, right_info );
3038 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3039 OSRF_BUFFER_ADD( join_buf, jpred );
3042 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3043 jsonIteratorFree( search_itr );
3044 buffer_free( join_buf );
3046 jsonObjectFree( freeable_hash );
3053 jsonObjectFree( freeable_hash );
3054 jsonIteratorFree( search_itr );
3056 return buffer_release( join_buf );
3061 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3062 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3063 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3065 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3067 search_hash is the JSON expression of the conditions.
3068 meta is the class definition from the IDL, for the relevant table.
3069 opjoin_type indicates whether multiple conditions, if present, should be
3070 connected by AND or OR.
3071 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3072 to pass it to other functions -- and all they do with it is to use the session
3073 and request members to send error messages back to the client.
3077 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3078 int opjoin_type, osrfMethodContext* ctx ) {
3082 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3083 "opjoin_type = %d, ctx addr = %p",
3086 class_info->class_def,
3091 growing_buffer* sql_buf = buffer_init( 128 );
3093 jsonObject* node = NULL;
3096 if( search_hash->type == JSON_ARRAY ) {
3097 if( 0 == search_hash->size ) {
3100 "%s: Invalid predicate structure: empty JSON array",
3103 buffer_free( sql_buf );
3107 unsigned long i = 0;
3108 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3112 if( opjoin_type == OR_OP_JOIN )
3113 buffer_add( sql_buf, " OR " );
3115 buffer_add( sql_buf, " AND " );
3118 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3120 buffer_free( sql_buf );
3124 buffer_fadd( sql_buf, "( %s )", subpred );
3128 } else if( search_hash->type == JSON_HASH ) {
3129 osrfLogDebug( OSRF_LOG_MARK,
3130 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3131 jsonIterator* search_itr = jsonNewIterator( search_hash );
3132 if( !jsonIteratorHasNext( search_itr ) ) {
3135 "%s: Invalid predicate structure: empty JSON object",
3138 jsonIteratorFree( search_itr );
3139 buffer_free( sql_buf );
3143 while( (node = jsonIteratorNext( search_itr )) ) {
3148 if( opjoin_type == OR_OP_JOIN )
3149 buffer_add( sql_buf, " OR " );
3151 buffer_add( sql_buf, " AND " );
3154 if( '+' == search_itr->key[ 0 ] ) {
3156 // This plus sign prefixes a class name or other table alias;
3157 // make sure the table alias is in scope
3158 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3159 if( ! alias_info ) {
3162 "%s: Invalid table alias \"%s\" in WHERE clause",
3166 jsonIteratorFree( search_itr );
3167 buffer_free( sql_buf );
3171 if( node->type == JSON_STRING ) {
3172 // It's the name of a column; make sure it belongs to the class
3173 const char* fieldname = jsonObjectGetString( node );
3174 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3177 "%s: Invalid column name \"%s\" in WHERE clause "
3178 "for table alias \"%s\"",
3183 jsonIteratorFree( search_itr );
3184 buffer_free( sql_buf );
3188 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3190 // It's something more complicated
3191 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3193 jsonIteratorFree( search_itr );
3194 buffer_free( sql_buf );
3198 buffer_fadd( sql_buf, "( %s )", subpred );
3201 } else if( '-' == search_itr->key[ 0 ] ) {
3202 if( !strcasecmp( "-or", search_itr->key )) {
3203 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3205 jsonIteratorFree( search_itr );
3206 buffer_free( sql_buf );
3210 buffer_fadd( sql_buf, "( %s )", subpred );
3212 } else if( !strcasecmp( "-and", search_itr->key )) {
3213 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3215 jsonIteratorFree( search_itr );
3216 buffer_free( sql_buf );
3220 buffer_fadd( sql_buf, "( %s )", subpred );
3222 } else if( !strcasecmp("-not",search_itr->key) ) {
3223 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3225 jsonIteratorFree( search_itr );
3226 buffer_free( sql_buf );
3230 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3232 } else if( !strcasecmp( "-exists", search_itr->key )) {
3233 char* subpred = buildQuery( ctx, node, SUBSELECT );
3235 jsonIteratorFree( search_itr );
3236 buffer_free( sql_buf );
3240 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3242 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3243 char* subpred = buildQuery( ctx, node, SUBSELECT );
3245 jsonIteratorFree( search_itr );
3246 buffer_free( sql_buf );
3250 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3252 } else { // Invalid "minus" operator
3255 "%s: Invalid operator \"%s\" in WHERE clause",
3259 jsonIteratorFree( search_itr );
3260 buffer_free( sql_buf );
3266 const char* class = class_info->class_name;
3267 osrfHash* fields = class_info->fields;
3268 osrfHash* field = osrfHashGet( fields, search_itr->key );
3271 const char* table = class_info->source_def;
3274 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3277 table ? table : "?",
3280 jsonIteratorFree( search_itr );
3281 buffer_free( sql_buf );
3285 char* subpred = searchPredicate( class_info, field, node, ctx );
3287 buffer_free( sql_buf );
3288 jsonIteratorFree( search_itr );
3292 buffer_add( sql_buf, subpred );
3296 jsonIteratorFree( search_itr );
3299 // ERROR ... only hash and array allowed at this level
3300 char* predicate_string = jsonObjectToJSON( search_hash );
3303 "%s: Invalid predicate structure: %s",
3307 buffer_free( sql_buf );
3308 free( predicate_string );
3312 return buffer_release( sql_buf );
3315 /* Build a JSON_ARRAY of field names for a given table alias
3317 static jsonObject* defaultSelectList( const char* table_alias ) {
3322 ClassInfo* class_info = search_all_alias( table_alias );
3323 if( ! class_info ) {
3326 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3333 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3334 osrfHash* field_def = NULL;
3335 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3336 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3337 const char* field_name = osrfHashIteratorKey( field_itr );
3338 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3339 jsonObjectPush( array, jsonNewObject( field_name ) );
3342 osrfHashIteratorFree( field_itr );
3347 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3348 // The jsonObject must be a JSON_HASH with an single entry for "union",
3349 // "intersect", or "except". The data associated with this key must be an
3350 // array of hashes, each hash being a query.
3351 // Also allowed but currently ignored: entries for "order_by" and "alias".
3352 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3354 if( ! combo || combo->type != JSON_HASH )
3355 return NULL; // should be impossible; validated by caller
3357 const jsonObject* query_array = NULL; // array of subordinate queries
3358 const char* op = NULL; // name of operator, e.g. UNION
3359 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3360 int op_count = 0; // for detecting conflicting operators
3361 int excepting = 0; // boolean
3362 int all = 0; // boolean
3363 jsonObject* order_obj = NULL;
3365 // Identify the elements in the hash
3366 jsonIterator* query_itr = jsonNewIterator( combo );
3367 jsonObject* curr_obj = NULL;
3368 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3369 if( ! strcmp( "union", query_itr->key ) ) {
3372 query_array = curr_obj;
3373 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3376 query_array = curr_obj;
3377 } else if( ! strcmp( "except", query_itr->key ) ) {
3381 query_array = curr_obj;
3382 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3385 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3388 order_obj = curr_obj;
3389 } else if( ! strcmp( "alias", query_itr->key ) ) {
3390 if( curr_obj->type != JSON_STRING ) {
3391 jsonIteratorFree( query_itr );
3394 alias = jsonObjectGetString( curr_obj );
3395 } else if( ! strcmp( "all", query_itr->key ) ) {
3396 if( obj_is_true( curr_obj ) )
3400 osrfAppSessionStatus(
3402 OSRF_STATUS_INTERNALSERVERERROR,
3403 "osrfMethodException",
3405 "Malformed query; unexpected entry in query object"
3409 "%s: Unexpected entry for \"%s\" in%squery",
3414 jsonIteratorFree( query_itr );
3418 jsonIteratorFree( query_itr );
3420 // More sanity checks
3421 if( ! query_array ) {
3423 osrfAppSessionStatus(
3425 OSRF_STATUS_INTERNALSERVERERROR,
3426 "osrfMethodException",
3428 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3432 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3435 return NULL; // should be impossible...
3436 } else if( op_count > 1 ) {
3438 osrfAppSessionStatus(
3440 OSRF_STATUS_INTERNALSERVERERROR,
3441 "osrfMethodException",
3443 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3447 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3451 } if( query_array->type != JSON_ARRAY ) {
3453 osrfAppSessionStatus(
3455 OSRF_STATUS_INTERNALSERVERERROR,
3456 "osrfMethodException",
3458 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3462 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3465 json_type( query_array->type )
3468 } if( query_array->size < 2 ) {
3470 osrfAppSessionStatus(
3472 OSRF_STATUS_INTERNALSERVERERROR,
3473 "osrfMethodException",
3475 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3479 "%s:%srequires multiple queries as operands",
3484 } else if( excepting && query_array->size > 2 ) {
3486 osrfAppSessionStatus(
3488 OSRF_STATUS_INTERNALSERVERERROR,
3489 "osrfMethodException",
3491 "EXCEPT operator has too many queries as operands"
3495 "%s:EXCEPT operator has too many queries as operands",
3499 } else if( order_obj && ! alias ) {
3501 osrfAppSessionStatus(
3503 OSRF_STATUS_INTERNALSERVERERROR,
3504 "osrfMethodException",
3506 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3510 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3516 // So far so good. Now build the SQL.
3517 growing_buffer* sql = buffer_init( 256 );
3519 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3520 // Add a layer of parentheses
3521 if( flags & SUBCOMBO )
3522 OSRF_BUFFER_ADD( sql, "( " );
3524 // Traverse the query array. Each entry should be a hash.
3525 int first = 1; // boolean
3527 jsonObject* query = NULL;
3528 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3529 if( query->type != JSON_HASH ) {
3531 osrfAppSessionStatus(
3533 OSRF_STATUS_INTERNALSERVERERROR,
3534 "osrfMethodException",
3536 "Malformed query under UNION, INTERSECT or EXCEPT"
3540 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3543 json_type( query->type )
3552 OSRF_BUFFER_ADD( sql, op );
3554 OSRF_BUFFER_ADD( sql, "ALL " );
3557 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3561 "%s: Error building query under%s",
3569 OSRF_BUFFER_ADD( sql, query_str );
3572 if( flags & SUBCOMBO )
3573 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3575 if( !(flags & SUBSELECT) )
3576 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3578 return buffer_release( sql );
3581 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3582 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3583 // or "except" to indicate the type of query.
3584 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3588 osrfAppSessionStatus(
3590 OSRF_STATUS_INTERNALSERVERERROR,
3591 "osrfMethodException",
3593 "Malformed query; no query object"
3595 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3597 } else if( query->type != JSON_HASH ) {
3599 osrfAppSessionStatus(
3601 OSRF_STATUS_INTERNALSERVERERROR,
3602 "osrfMethodException",
3604 "Malformed query object"
3608 "%s: Query object is %s instead of JSON_HASH",
3610 json_type( query->type )
3615 // Determine what kind of query it purports to be, and dispatch accordingly.
3616 if( jsonObjectGetKey( query, "union" ) ||
3617 jsonObjectGetKey( query, "intersect" ) ||
3618 jsonObjectGetKey( query, "except" ) ) {
3619 return doCombo( ctx, query, flags );
3621 // It is presumably a SELECT query
3623 // Push a node onto the stack for the current query. Every level of
3624 // subquery gets its own QueryFrame on the Stack.
3627 // Build an SQL SELECT statement
3630 jsonObjectGetKey( query, "select" ),
3631 jsonObjectGetKey( query, "from" ),
3632 jsonObjectGetKey( query, "where" ),
3633 jsonObjectGetKey( query, "having" ),
3634 jsonObjectGetKey( query, "order_by" ),
3635 jsonObjectGetKey( query, "limit" ),
3636 jsonObjectGetKey( query, "offset" ),
3645 /* method context */ osrfMethodContext* ctx,
3647 /* SELECT */ jsonObject* selhash,
3648 /* FROM */ jsonObject* join_hash,
3649 /* WHERE */ jsonObject* search_hash,
3650 /* HAVING */ jsonObject* having_hash,
3651 /* ORDER BY */ jsonObject* order_hash,
3652 /* LIMIT */ jsonObject* limit,
3653 /* OFFSET */ jsonObject* offset,
3654 /* flags */ int flags
3656 const char* locale = osrf_message_get_last_locale();
3658 // general tmp objects
3659 const jsonObject* tmp_const;
3660 jsonObject* selclass = NULL;
3661 jsonObject* snode = NULL;
3662 jsonObject* onode = NULL;
3664 char* string = NULL;
3665 int from_function = 0;
3670 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3672 // punt if there's no FROM clause
3673 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3676 "%s: FROM clause is missing or empty",
3680 osrfAppSessionStatus(
3682 OSRF_STATUS_INTERNALSERVERERROR,
3683 "osrfMethodException",
3685 "FROM clause is missing or empty in JSON query"
3690 // the core search class
3691 const char* core_class = NULL;
3693 // get the core class -- the only key of the top level FROM clause, or a string
3694 if( join_hash->type == JSON_HASH ) {
3695 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3696 snode = jsonIteratorNext( tmp_itr );
3698 // Populate the current QueryFrame with information
3699 // about the core class
3700 if( add_query_core( NULL, tmp_itr->key ) ) {
3702 osrfAppSessionStatus(
3704 OSRF_STATUS_INTERNALSERVERERROR,
3705 "osrfMethodException",
3707 "Unable to look up core class"
3711 core_class = curr_query->core.class_name;
3714 jsonObject* extra = jsonIteratorNext( tmp_itr );
3716 jsonIteratorFree( tmp_itr );
3719 // There shouldn't be more than one entry in join_hash
3723 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3727 osrfAppSessionStatus(
3729 OSRF_STATUS_INTERNALSERVERERROR,
3730 "osrfMethodException",
3732 "Malformed FROM clause in JSON query"
3734 return NULL; // Malformed join_hash; extra entry
3736 } else if( join_hash->type == JSON_ARRAY ) {
3737 // We're selecting from a function, not from a table
3739 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3742 } else if( join_hash->type == JSON_STRING ) {
3743 // Populate the current QueryFrame with information
3744 // about the core class
3745 core_class = jsonObjectGetString( join_hash );
3747 if( add_query_core( NULL, core_class ) ) {
3749 osrfAppSessionStatus(
3751 OSRF_STATUS_INTERNALSERVERERROR,
3752 "osrfMethodException",
3754 "Unable to look up core class"
3762 "%s: FROM clause is unexpected JSON type: %s",
3764 json_type( join_hash->type )
3767 osrfAppSessionStatus(
3769 OSRF_STATUS_INTERNALSERVERERROR,
3770 "osrfMethodException",
3772 "Ill-formed FROM clause in JSON query"
3777 // Build the join clause, if any, while filling out the list
3778 // of joined classes in the current QueryFrame.
3779 char* join_clause = NULL;
3780 if( join_hash && ! from_function ) {
3782 join_clause = searchJOIN( join_hash, &curr_query->core );
3783 if( ! join_clause ) {
3785 osrfAppSessionStatus(
3787 OSRF_STATUS_INTERNALSERVERERROR,
3788 "osrfMethodException",
3790 "Unable to construct JOIN clause(s)"
3796 // For in case we don't get a select list
3797 jsonObject* defaultselhash = NULL;
3799 // if there is no select list, build a default select list ...
3800 if( !selhash && !from_function ) {
3801 jsonObject* default_list = defaultSelectList( core_class );
3802 if( ! default_list ) {
3804 osrfAppSessionStatus(
3806 OSRF_STATUS_INTERNALSERVERERROR,
3807 "osrfMethodException",
3809 "Unable to build default SELECT clause in JSON query"
3811 free( join_clause );
3816 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3817 jsonObjectSetKey( selhash, core_class, default_list );
3820 // The SELECT clause can be encoded only by a hash
3821 if( !from_function && selhash->type != JSON_HASH ) {
3824 "%s: Expected JSON_HASH for SELECT clause; found %s",
3826 json_type( selhash->type )
3830 osrfAppSessionStatus(
3832 OSRF_STATUS_INTERNALSERVERERROR,
3833 "osrfMethodException",
3835 "Malformed SELECT clause in JSON query"
3837 free( join_clause );
3841 // If you see a null or wild card specifier for the core class, or an
3842 // empty array, replace it with a default SELECT list
3843 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3845 int default_needed = 0; // boolean
3846 if( JSON_STRING == tmp_const->type
3847 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3849 else if( JSON_NULL == tmp_const->type )
3852 if( default_needed ) {
3853 // Build a default SELECT list
3854 jsonObject* default_list = defaultSelectList( core_class );
3855 if( ! default_list ) {
3857 osrfAppSessionStatus(
3859 OSRF_STATUS_INTERNALSERVERERROR,
3860 "osrfMethodException",
3862 "Can't build default SELECT clause in JSON query"
3864 free( join_clause );
3869 jsonObjectSetKey( selhash, core_class, default_list );
3873 // temp buffers for the SELECT list and GROUP BY clause
3874 growing_buffer* select_buf = buffer_init( 128 );
3875 growing_buffer* group_buf = buffer_init( 128 );
3877 int aggregate_found = 0; // boolean
3879 // Build a select list
3880 if( from_function ) // From a function we select everything
3881 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3884 // Build the SELECT list as SQL
3888 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3889 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3891 const char* cname = selclass_itr->key;
3893 // Make sure the target relation is in the FROM clause.
3895 // At this point join_hash is a step down from the join_hash we
3896 // received as a parameter. If the original was a JSON_STRING,
3897 // then json_hash is now NULL. If the original was a JSON_HASH,
3898 // then json_hash is now the first (and only) entry in it,
3899 // denoting the core class. We've already excluded the
3900 // possibility that the original was a JSON_ARRAY, because in
3901 // that case from_function would be non-NULL, and we wouldn't
3904 // If the current table alias isn't in scope, bail out
3905 ClassInfo* class_info = search_alias( cname );
3906 if( ! class_info ) {
3909 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3914 osrfAppSessionStatus(
3916 OSRF_STATUS_INTERNALSERVERERROR,
3917 "osrfMethodException",
3919 "Selected class not in FROM clause in JSON query"
3921 jsonIteratorFree( selclass_itr );
3922 buffer_free( select_buf );
3923 buffer_free( group_buf );
3924 if( defaultselhash )
3925 jsonObjectFree( defaultselhash );
3926 free( join_clause );
3930 if( selclass->type != JSON_ARRAY ) {
3933 "%s: Malformed SELECT list for class \"%s\"; not an array",
3938 osrfAppSessionStatus(
3940 OSRF_STATUS_INTERNALSERVERERROR,
3941 "osrfMethodException",
3943 "Selected class not in FROM clause in JSON query"
3946 jsonIteratorFree( selclass_itr );
3947 buffer_free( select_buf );
3948 buffer_free( group_buf );
3949 if( defaultselhash )
3950 jsonObjectFree( defaultselhash );
3951 free( join_clause );
3955 // Look up some attributes of the current class
3956 osrfHash* idlClass = class_info->class_def;
3957 osrfHash* class_field_set = class_info->fields;
3958 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3959 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3961 if( 0 == selclass->size ) {
3964 "%s: No columns selected from \"%s\"",
3970 // stitch together the column list for the current table alias...
3971 unsigned long field_idx = 0;
3972 jsonObject* selfield = NULL;
3973 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3975 // If we need a separator comma, add one
3979 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3982 // if the field specification is a string, add it to the list
3983 if( selfield->type == JSON_STRING ) {
3985 // Look up the field in the IDL
3986 const char* col_name = jsonObjectGetString( selfield );
3987 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3989 // No such field in current class
3992 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3998 osrfAppSessionStatus(
4000 OSRF_STATUS_INTERNALSERVERERROR,
4001 "osrfMethodException",
4003 "Selected column not defined in JSON query"
4005 jsonIteratorFree( selclass_itr );
4006 buffer_free( select_buf );
4007 buffer_free( group_buf );
4008 if( defaultselhash )
4009 jsonObjectFree( defaultselhash );
4010 free( join_clause );
4012 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4013 // Virtual field not allowed
4016 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4022 osrfAppSessionStatus(
4024 OSRF_STATUS_INTERNALSERVERERROR,
4025 "osrfMethodException",
4027 "Selected column may not be virtual in JSON query"
4029 jsonIteratorFree( selclass_itr );
4030 buffer_free( select_buf );
4031 buffer_free( group_buf );
4032 if( defaultselhash )
4033 jsonObjectFree( defaultselhash );
4034 free( join_clause );
4040 if( flags & DISABLE_I18N )
4043 i18n = osrfHashGet( field_def, "i18n" );
4045 if( str_is_true( i18n ) ) {
4046 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4047 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4048 class_tname, cname, col_name, class_pkey,
4049 cname, class_pkey, locale, col_name );
4051 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4052 cname, col_name, col_name );
4055 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4056 cname, col_name, col_name );
4059 // ... but it could be an object, in which case we check for a Field Transform
4060 } else if( selfield->type == JSON_HASH ) {
4062 const char* col_name = jsonObjectGetString(
4063 jsonObjectGetKeyConst( selfield, "column" ) );
4065 // Get the field definition from the IDL
4066 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4068 // No such field in current class
4071 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4077 osrfAppSessionStatus(
4079 OSRF_STATUS_INTERNALSERVERERROR,
4080 "osrfMethodException",
4082 "Selected column is not defined in JSON query"
4084 jsonIteratorFree( selclass_itr );
4085 buffer_free( select_buf );
4086 buffer_free( group_buf );
4087 if( defaultselhash )
4088 jsonObjectFree( defaultselhash );
4089 free( join_clause );
4091 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4092 // No such field in current class
4095 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4101 osrfAppSessionStatus(
4103 OSRF_STATUS_INTERNALSERVERERROR,
4104 "osrfMethodException",
4106 "Selected column is virtual in JSON query"
4108 jsonIteratorFree( selclass_itr );
4109 buffer_free( select_buf );
4110 buffer_free( group_buf );
4111 if( defaultselhash )
4112 jsonObjectFree( defaultselhash );
4113 free( join_clause );
4117 // Decide what to use as a column alias
4119 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4120 _alias = jsonObjectGetString( tmp_const );
4121 } else { // Use field name as the alias
4125 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4126 char* transform_str = searchFieldTransform(
4127 class_info->alias, field_def, selfield );
4128 if( transform_str ) {
4129 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4130 free( transform_str );
4133 osrfAppSessionStatus(
4135 OSRF_STATUS_INTERNALSERVERERROR,
4136 "osrfMethodException",
4138 "Unable to generate transform function in JSON query"
4140 jsonIteratorFree( selclass_itr );
4141 buffer_free( select_buf );
4142 buffer_free( group_buf );
4143 if( defaultselhash )
4144 jsonObjectFree( defaultselhash );
4145 free( join_clause );
4152 if( flags & DISABLE_I18N )
4155 i18n = osrfHashGet( field_def, "i18n" );
4157 if( str_is_true( i18n ) ) {
4158 buffer_fadd( select_buf,
4159 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4160 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4161 class_tname, cname, col_name, class_pkey, cname,
4162 class_pkey, locale, _alias );
4164 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4165 cname, col_name, _alias );
4168 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4169 cname, col_name, _alias );
4176 "%s: Selected item is unexpected JSON type: %s",
4178 json_type( selfield->type )
4181 osrfAppSessionStatus(
4183 OSRF_STATUS_INTERNALSERVERERROR,
4184 "osrfMethodException",
4186 "Ill-formed SELECT item in JSON query"
4188 jsonIteratorFree( selclass_itr );
4189 buffer_free( select_buf );
4190 buffer_free( group_buf );
4191 if( defaultselhash )
4192 jsonObjectFree( defaultselhash );
4193 free( join_clause );
4197 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4198 if( obj_is_true( agg_obj ) )
4199 aggregate_found = 1;
4201 // Append a comma (except for the first one)
4202 // and add the column to a GROUP BY clause
4206 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4208 buffer_fadd( group_buf, " %d", sel_pos );
4212 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4214 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4215 if ( ! obj_is_true( aggregate_obj ) ) {
4219 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4222 buffer_fadd(group_buf, " %d", sel_pos);
4225 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4229 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4232 _column = searchFieldTransform(class_info->alias, field, selfield);
4233 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4234 OSRF_BUFFER_ADD(group_buf, _column);
4235 _column = searchFieldTransform(class_info->alias, field, selfield);
4242 } // end while -- iterating across SELECT columns
4244 } // end while -- iterating across classes
4246 jsonIteratorFree( selclass_itr );
4250 char* col_list = buffer_release( select_buf );
4252 // Make sure the SELECT list isn't empty. This can happen, for example,
4253 // if we try to build a default SELECT clause from a non-core table.
4256 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4258 osrfAppSessionStatus(
4260 OSRF_STATUS_INTERNALSERVERERROR,
4261 "osrfMethodException",
4263 "SELECT list is empty"
4266 buffer_free( group_buf );
4267 if( defaultselhash )
4268 jsonObjectFree( defaultselhash );
4269 free( join_clause );
4275 table = searchValueTransform( join_hash );
4277 table = strdup( curr_query->core.source_def );
4281 osrfAppSessionStatus(
4283 OSRF_STATUS_INTERNALSERVERERROR,
4284 "osrfMethodException",
4286 "Unable to identify table for core class"
4289 buffer_free( group_buf );
4290 if( defaultselhash )
4291 jsonObjectFree( defaultselhash );
4292 free( join_clause );
4296 // Put it all together
4297 growing_buffer* sql_buf = buffer_init( 128 );
4298 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4302 // Append the join clause, if any
4304 buffer_add(sql_buf, join_clause );
4305 free( join_clause );
4308 char* order_by_list = NULL;
4309 char* having_buf = NULL;
4311 if( !from_function ) {
4313 // Build a WHERE clause, if there is one
4315 buffer_add( sql_buf, " WHERE " );
4317 // and it's on the WHERE clause
4318 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4321 osrfAppSessionStatus(
4323 OSRF_STATUS_INTERNALSERVERERROR,
4324 "osrfMethodException",
4326 "Severe query error in WHERE predicate -- see error log for more details"
4329 buffer_free( group_buf );
4330 buffer_free( sql_buf );
4331 if( defaultselhash )
4332 jsonObjectFree( defaultselhash );
4336 buffer_add( sql_buf, pred );
4340 // Build a HAVING clause, if there is one
4343 // and it's on the the WHERE clause
4344 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4346 if( ! having_buf ) {
4348 osrfAppSessionStatus(
4350 OSRF_STATUS_INTERNALSERVERERROR,
4351 "osrfMethodException",
4353 "Severe query error in HAVING predicate -- see error log for more details"
4356 buffer_free( group_buf );
4357 buffer_free( sql_buf );
4358 if( defaultselhash )
4359 jsonObjectFree( defaultselhash );
4364 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4366 // Build an ORDER BY clause, if there is one
4367 if( NULL == order_hash )
4368 ; // No ORDER BY? do nothing
4369 else if( JSON_ARRAY == order_hash->type ) {
4370 // Array of field specifications, each specification being a
4371 // hash to define the class, field, and other details
4373 jsonObject* order_spec;
4374 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4376 if( JSON_HASH != order_spec->type ) {
4377 osrfLogError( OSRF_LOG_MARK,
4378 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4379 modulename, json_type( order_spec->type ) );
4381 osrfAppSessionStatus(
4383 OSRF_STATUS_INTERNALSERVERERROR,
4384 "osrfMethodException",
4386 "Malformed ORDER BY clause -- see error log for more details"
4388 buffer_free( order_buf );
4390 buffer_free( group_buf );
4391 buffer_free( sql_buf );
4392 if( defaultselhash )
4393 jsonObjectFree( defaultselhash );
4397 const char* class_alias =
4398 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4400 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4403 OSRF_BUFFER_ADD( order_buf, ", " );
4405 order_buf = buffer_init( 128 );
4407 if( !field || !class_alias ) {
4408 osrfLogError( OSRF_LOG_MARK,
4409 "%s: Missing class or field name in field specification "
4410 "of ORDER BY clause",
4413 osrfAppSessionStatus(
4415 OSRF_STATUS_INTERNALSERVERERROR,
4416 "osrfMethodException",
4418 "Malformed ORDER BY clause -- see error log for more details"
4420 buffer_free( order_buf );
4422 buffer_free( group_buf );
4423 buffer_free( sql_buf );
4424 if( defaultselhash )
4425 jsonObjectFree( defaultselhash );
4429 ClassInfo* order_class_info = search_alias( class_alias );
4430 if( ! order_class_info ) {
4431 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4432 "not in FROM clause", modulename, class_alias );
4434 osrfAppSessionStatus(
4436 OSRF_STATUS_INTERNALSERVERERROR,
4437 "osrfMethodException",
4439 "Invalid class referenced in ORDER BY clause -- "
4440 "see error log for more details"
4443 buffer_free( group_buf );
4444 buffer_free( sql_buf );
4445 if( defaultselhash )
4446 jsonObjectFree( defaultselhash );
4450 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4452 osrfLogError( OSRF_LOG_MARK,
4453 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4454 modulename, class_alias, field );
4456 osrfAppSessionStatus(
4458 OSRF_STATUS_INTERNALSERVERERROR,
4459 "osrfMethodException",
4461 "Invalid field referenced in ORDER BY clause -- "
4462 "see error log for more details"
4465 buffer_free( group_buf );
4466 buffer_free( sql_buf );
4467 if( defaultselhash )
4468 jsonObjectFree( defaultselhash );
4470 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4471 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4472 modulename, field );
4474 osrfAppSessionStatus(
4476 OSRF_STATUS_INTERNALSERVERERROR,
4477 "osrfMethodException",
4479 "Virtual field in ORDER BY clause -- see error log for more details"
4481 buffer_free( order_buf );
4483 buffer_free( group_buf );
4484 buffer_free( sql_buf );
4485 if( defaultselhash )
4486 jsonObjectFree( defaultselhash );
4490 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4491 char* transform_str = searchFieldTransform(
4492 class_alias, field_def, order_spec );
4493 if( ! transform_str ) {
4495 osrfAppSessionStatus(
4497 OSRF_STATUS_INTERNALSERVERERROR,
4498 "osrfMethodException",
4500 "Severe query error in ORDER BY clause -- "
4501 "see error log for more details"
4503 buffer_free( order_buf );
4505 buffer_free( group_buf );
4506 buffer_free( sql_buf );
4507 if( defaultselhash )
4508 jsonObjectFree( defaultselhash );
4512 OSRF_BUFFER_ADD( order_buf, transform_str );
4513 free( transform_str );
4516 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4518 const char* direction =
4519 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4521 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4522 OSRF_BUFFER_ADD( order_buf, " DESC" );
4524 OSRF_BUFFER_ADD( order_buf, " ASC" );
4527 } else if( JSON_HASH == order_hash->type ) {
4528 // This hash is keyed on class alias. Each class has either
4529 // an array of field names or a hash keyed on field name.
4530 jsonIterator* class_itr = jsonNewIterator( order_hash );
4531 while( (snode = jsonIteratorNext( class_itr )) ) {
4533 ClassInfo* order_class_info = search_alias( class_itr->key );
4534 if( ! order_class_info ) {
4535 osrfLogError( OSRF_LOG_MARK,
4536 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4537 modulename, class_itr->key );
4539 osrfAppSessionStatus(
4541 OSRF_STATUS_INTERNALSERVERERROR,
4542 "osrfMethodException",
4544 "Invalid class referenced in ORDER BY clause -- "
4545 "see error log for more details"
4547 jsonIteratorFree( class_itr );
4548 buffer_free( order_buf );
4550 buffer_free( group_buf );
4551 buffer_free( sql_buf );
4552 if( defaultselhash )
4553 jsonObjectFree( defaultselhash );
4557 osrfHash* field_list_def = order_class_info->fields;
4559 if( snode->type == JSON_HASH ) {
4561 // Hash is keyed on field names from the current class. For each field
4562 // there is another layer of hash to define the sorting details, if any,
4563 // or a string to indicate direction of sorting.
4564 jsonIterator* order_itr = jsonNewIterator( snode );
4565 while( (onode = jsonIteratorNext( order_itr )) ) {
4567 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4569 osrfLogError( OSRF_LOG_MARK,
4570 "%s: Invalid field \"%s\" in ORDER BY clause",
4571 modulename, order_itr->key );
4573 osrfAppSessionStatus(
4575 OSRF_STATUS_INTERNALSERVERERROR,
4576 "osrfMethodException",
4578 "Invalid field in ORDER BY clause -- "
4579 "see error log for more details"
4581 jsonIteratorFree( order_itr );
4582 jsonIteratorFree( class_itr );
4583 buffer_free( order_buf );
4585 buffer_free( group_buf );
4586 buffer_free( sql_buf );
4587 if( defaultselhash )
4588 jsonObjectFree( defaultselhash );
4590 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4591 osrfLogError( OSRF_LOG_MARK,
4592 "%s: Virtual field \"%s\" in ORDER BY clause",
4593 modulename, order_itr->key );
4595 osrfAppSessionStatus(
4597 OSRF_STATUS_INTERNALSERVERERROR,
4598 "osrfMethodException",
4600 "Virtual field in ORDER BY clause -- "
4601 "see error log for more details"
4603 jsonIteratorFree( order_itr );
4604 jsonIteratorFree( class_itr );
4605 buffer_free( order_buf );
4607 buffer_free( group_buf );
4608 buffer_free( sql_buf );
4609 if( defaultselhash )
4610 jsonObjectFree( defaultselhash );
4614 const char* direction = NULL;
4615 if( onode->type == JSON_HASH ) {
4616 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4617 string = searchFieldTransform(
4619 osrfHashGet( field_list_def, order_itr->key ),
4623 if( ctx ) osrfAppSessionStatus(
4625 OSRF_STATUS_INTERNALSERVERERROR,
4626 "osrfMethodException",
4628 "Severe query error in ORDER BY clause -- "
4629 "see error log for more details"
4631 jsonIteratorFree( order_itr );
4632 jsonIteratorFree( class_itr );
4634 buffer_free( group_buf );
4635 buffer_free( order_buf);
4636 buffer_free( sql_buf );
4637 if( defaultselhash )
4638 jsonObjectFree( defaultselhash );
4642 growing_buffer* field_buf = buffer_init( 16 );
4643 buffer_fadd( field_buf, "\"%s\".%s",
4644 class_itr->key, order_itr->key );
4645 string = buffer_release( field_buf );
4648 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4649 const char* dir = jsonObjectGetString( tmp_const );
4650 if(!strncasecmp( dir, "d", 1 )) {
4651 direction = " DESC";
4657 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4658 osrfLogError( OSRF_LOG_MARK,
4659 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4660 modulename, json_type( onode->type ) );
4662 osrfAppSessionStatus(
4664 OSRF_STATUS_INTERNALSERVERERROR,
4665 "osrfMethodException",
4667 "Malformed ORDER BY clause -- see error log for more details"
4669 jsonIteratorFree( order_itr );
4670 jsonIteratorFree( class_itr );
4672 buffer_free( group_buf );
4673 buffer_free( order_buf );
4674 buffer_free( sql_buf );
4675 if( defaultselhash )
4676 jsonObjectFree( defaultselhash );
4680 string = strdup( order_itr->key );
4681 const char* dir = jsonObjectGetString( onode );
4682 if( !strncasecmp( dir, "d", 1 )) {
4683 direction = " DESC";
4690 OSRF_BUFFER_ADD( order_buf, ", " );
4692 order_buf = buffer_init( 128 );
4694 OSRF_BUFFER_ADD( order_buf, string );
4698 OSRF_BUFFER_ADD( order_buf, direction );
4702 jsonIteratorFree( order_itr );
4704 } else if( snode->type == JSON_ARRAY ) {
4706 // Array is a list of fields from the current class
4707 unsigned long order_idx = 0;
4708 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4710 const char* _f = jsonObjectGetString( onode );
4712 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4714 osrfLogError( OSRF_LOG_MARK,
4715 "%s: Invalid field \"%s\" in ORDER BY clause",
4718 osrfAppSessionStatus(
4720 OSRF_STATUS_INTERNALSERVERERROR,
4721 "osrfMethodException",
4723 "Invalid field in ORDER BY clause -- "
4724 "see error log for more details"
4726 jsonIteratorFree( class_itr );
4727 buffer_free( order_buf );
4729 buffer_free( group_buf );
4730 buffer_free( sql_buf );
4731 if( defaultselhash )
4732 jsonObjectFree( defaultselhash );
4734 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4735 osrfLogError( OSRF_LOG_MARK,
4736 "%s: Virtual field \"%s\" in ORDER BY clause",
4739 osrfAppSessionStatus(
4741 OSRF_STATUS_INTERNALSERVERERROR,
4742 "osrfMethodException",
4744 "Virtual field in ORDER BY clause -- "
4745 "see error log for more details"
4747 jsonIteratorFree( class_itr );
4748 buffer_free( order_buf );
4750 buffer_free( group_buf );
4751 buffer_free( sql_buf );
4752 if( defaultselhash )
4753 jsonObjectFree( defaultselhash );
4758 OSRF_BUFFER_ADD( order_buf, ", " );
4760 order_buf = buffer_init( 128 );
4762 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4766 // IT'S THE OOOOOOOOOOOLD STYLE!
4768 osrfLogError( OSRF_LOG_MARK,
4769 "%s: Possible SQL injection attempt; direct order by is not allowed",
4772 osrfAppSessionStatus(
4774 OSRF_STATUS_INTERNALSERVERERROR,
4775 "osrfMethodException",
4777 "Severe query error -- see error log for more details"
4782 buffer_free( group_buf );
4783 buffer_free( order_buf );
4784 buffer_free( sql_buf );
4785 if( defaultselhash )
4786 jsonObjectFree( defaultselhash );
4787 jsonIteratorFree( class_itr );
4791 jsonIteratorFree( class_itr );
4793 osrfLogError( OSRF_LOG_MARK,
4794 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4795 modulename, json_type( order_hash->type ) );
4797 osrfAppSessionStatus(
4799 OSRF_STATUS_INTERNALSERVERERROR,
4800 "osrfMethodException",
4802 "Malformed ORDER BY clause -- see error log for more details"
4804 buffer_free( order_buf );
4806 buffer_free( group_buf );
4807 buffer_free( sql_buf );
4808 if( defaultselhash )
4809 jsonObjectFree( defaultselhash );
4814 order_by_list = buffer_release( order_buf );
4818 string = buffer_release( group_buf );
4820 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4821 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4822 OSRF_BUFFER_ADD( sql_buf, string );
4827 if( having_buf && *having_buf ) {
4828 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4829 OSRF_BUFFER_ADD( sql_buf, having_buf );
4833 if( order_by_list ) {
4835 if( *order_by_list ) {
4836 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4837 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4840 free( order_by_list );
4844 const char* str = jsonObjectGetString( limit );
4845 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4849 const char* str = jsonObjectGetString( offset );
4850 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4853 if( !(flags & SUBSELECT) )
4854 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4856 if( defaultselhash )
4857 jsonObjectFree( defaultselhash );
4859 return buffer_release( sql_buf );
4861 } // end of SELECT()
4863 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4865 const char* locale = osrf_message_get_last_locale();
4867 osrfHash* fields = osrfHashGet( meta, "fields" );
4868 char* core_class = osrfHashGet( meta, "classname" );
4870 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4872 jsonObject* node = NULL;
4873 jsonObject* snode = NULL;
4874 jsonObject* onode = NULL;
4875 const jsonObject* _tmp = NULL;
4876 jsonObject* selhash = NULL;
4877 jsonObject* defaultselhash = NULL;
4879 growing_buffer* sql_buf = buffer_init( 128 );
4880 growing_buffer* select_buf = buffer_init( 128 );
4882 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4883 defaultselhash = jsonNewObjectType( JSON_HASH );
4884 selhash = defaultselhash;
4887 // If there's no SELECT list for the core class, build one
4888 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4889 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4891 // Add every non-virtual field to the field list
4892 osrfHash* field_def = NULL;
4893 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4894 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4895 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4896 const char* field = osrfHashIteratorKey( field_itr );
4897 jsonObjectPush( field_list, jsonNewObject( field ) );
4900 osrfHashIteratorFree( field_itr );
4901 jsonObjectSetKey( selhash, core_class, field_list );
4905 jsonIterator* class_itr = jsonNewIterator( selhash );
4906 while( (snode = jsonIteratorNext( class_itr )) ) {
4908 const char* cname = class_itr->key;
4909 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4913 if( strcmp(core_class,class_itr->key )) {
4917 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4918 if( !found->size ) {
4919 jsonObjectFree( found );
4923 jsonObjectFree( found );
4926 jsonIterator* select_itr = jsonNewIterator( snode );
4927 while( (node = jsonIteratorNext( select_itr )) ) {
4928 const char* item_str = jsonObjectGetString( node );
4929 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4930 char* fname = osrfHashGet( field, "name" );
4938 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4943 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4944 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4947 i18n = osrfHashGet( field, "i18n" );
4949 if( str_is_true( i18n ) ) {
4950 char* pkey = osrfHashGet( idlClass, "primarykey" );
4951 char* tname = osrfHashGet( idlClass, "tablename" );
4953 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4954 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4955 tname, cname, fname, pkey, cname, pkey, locale, fname );
4957 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4960 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4964 jsonIteratorFree( select_itr );
4967 jsonIteratorFree( class_itr );
4969 char* col_list = buffer_release( select_buf );
4970 char* table = oilsGetRelation( meta );
4972 table = strdup( "(null)" );
4974 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4978 // Clear the query stack (as a fail-safe precaution against possible
4979 // leftover garbage); then push the first query frame onto the stack.
4980 clear_query_stack();
4982 if( add_query_core( NULL, core_class ) ) {
4984 osrfAppSessionStatus(
4986 OSRF_STATUS_INTERNALSERVERERROR,
4987 "osrfMethodException",
4989 "Unable to build query frame for core class"
4995 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4996 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4997 OSRF_BUFFER_ADD( sql_buf, join_clause );
4998 free( join_clause );
5001 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5002 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5004 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5006 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5008 osrfAppSessionStatus(
5010 OSRF_STATUS_INTERNALSERVERERROR,
5011 "osrfMethodException",
5013 "Severe query error -- see error log for more details"
5015 buffer_free( sql_buf );
5016 if( defaultselhash )
5017 jsonObjectFree( defaultselhash );
5018 clear_query_stack();
5021 buffer_add( sql_buf, pred );
5026 char* string = NULL;
5027 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5029 growing_buffer* order_buf = buffer_init( 128 );
5032 jsonIterator* class_itr = jsonNewIterator( _tmp );
5033 while( (snode = jsonIteratorNext( class_itr )) ) {
5035 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
5038 if( snode->type == JSON_HASH ) {
5040 jsonIterator* order_itr = jsonNewIterator( snode );
5041 while( (onode = jsonIteratorNext( order_itr )) ) {
5043 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5044 class_itr->key, order_itr->key );
5048 char* direction = NULL;
5049 if( onode->type == JSON_HASH ) {
5050 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5051 string = searchFieldTransform( class_itr->key, field_def, onode );
5053 osrfAppSessionStatus(
5055 OSRF_STATUS_INTERNALSERVERERROR,
5056 "osrfMethodException",
5058 "Severe query error in ORDER BY clause -- "
5059 "see error log for more details"
5061 jsonIteratorFree( order_itr );
5062 jsonIteratorFree( class_itr );
5063 buffer_free( order_buf );
5064 buffer_free( sql_buf );
5065 if( defaultselhash )
5066 jsonObjectFree( defaultselhash );
5067 clear_query_stack();
5071 growing_buffer* field_buf = buffer_init( 16 );
5072 buffer_fadd( field_buf, "\"%s\".%s",
5073 class_itr->key, order_itr->key );
5074 string = buffer_release( field_buf );
5077 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5078 const char* dir = jsonObjectGetString( _tmp );
5079 if(!strncasecmp( dir, "d", 1 )) {
5080 direction = " DESC";
5084 string = strdup( order_itr->key );
5085 const char* dir = jsonObjectGetString( onode );
5086 if( !strncasecmp( dir, "d", 1 )) {
5087 direction = " DESC";
5096 buffer_add( order_buf, ", " );
5099 buffer_add( order_buf, string );
5103 buffer_add( order_buf, direction );
5107 jsonIteratorFree( order_itr );
5110 const char* str = jsonObjectGetString( snode );
5111 buffer_add( order_buf, str );
5117 jsonIteratorFree( class_itr );
5119 string = buffer_release( order_buf );
5122 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5123 OSRF_BUFFER_ADD( sql_buf, string );
5129 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5130 const char* str = jsonObjectGetString( _tmp );
5138 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5140 const char* str = jsonObjectGetString( _tmp );
5149 if( defaultselhash )
5150 jsonObjectFree( defaultselhash );
5151 clear_query_stack();
5153 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5154 return buffer_release( sql_buf );
5157 int doJSONSearch ( osrfMethodContext* ctx ) {
5158 if(osrfMethodVerifyContext( ctx )) {
5159 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5163 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5167 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5171 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5172 flags |= SELECT_DISTINCT;
5174 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5175 flags |= DISABLE_I18N;
5177 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5178 clear_query_stack(); // a possibly needless precaution
5179 char* sql = buildQuery( ctx, hash, flags );
5180 clear_query_stack();
5187 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5190 dbhandle = writehandle;
5192 dbi_result result = dbi_conn_query( dbhandle, sql );
5195 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5197 if( dbi_result_first_row( result )) {
5198 /* JSONify the result */
5199 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5202 jsonObject* return_val = oilsMakeJSONFromResult( result );
5203 osrfAppRespond( ctx, return_val );
5204 jsonObjectFree( return_val );
5205 } while( dbi_result_next_row( result ));
5208 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5211 osrfAppRespondComplete( ctx, NULL );
5213 /* clean up the query */
5214 dbi_result_free( result );
5219 int errnum = dbi_conn_error( dbhandle, &msg );
5220 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5221 modulename, sql, errnum, msg ? msg : "(No description available)" );
5222 osrfAppSessionStatus(
5224 OSRF_STATUS_INTERNALSERVERERROR,
5225 "osrfMethodException",
5227 "Severe query error -- see error log for more details"
5229 if( !oilsIsDBConnected( dbhandle ))
5230 osrfAppSessionPanic( ctx->session );
5237 // The last parameter, err, is used to report an error condition by updating an int owned by
5238 // the calling code.
5240 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5241 // It is the responsibility of the calling code to initialize *err before the
5242 // call, so that it will be able to make sense of the result.
5244 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5245 // redundant anyway.
5246 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5247 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5250 dbhandle = writehandle;
5252 char* core_class = osrfHashGet( class_meta, "classname" );
5253 char* pkey = osrfHashGet( class_meta, "primarykey" );
5255 const jsonObject* _tmp;
5257 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5259 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5264 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5266 dbi_result result = dbi_conn_query( dbhandle, sql );
5267 if( NULL == result ) {
5269 int errnum = dbi_conn_error( dbhandle, &msg );
5270 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5271 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5272 msg ? msg : "(No description available)" );
5273 if( !oilsIsDBConnected( dbhandle ))
5274 osrfAppSessionPanic( ctx->session );
5275 osrfAppSessionStatus(
5277 OSRF_STATUS_INTERNALSERVERERROR,
5278 "osrfMethodException",
5280 "Severe query error -- see error log for more details"
5287 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5290 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5291 jsonObject* row_obj = NULL;
5293 if( dbi_result_first_row( result )) {
5295 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5296 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5297 // eliminate the duplicates.
5298 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5299 osrfHash* dedup = osrfNewHash();
5301 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5302 char* pkey_val = oilsFMGetString( row_obj, pkey );
5303 if( osrfHashGet( dedup, pkey_val ) ) {
5304 jsonObjectFree( row_obj );
5307 osrfHashSet( dedup, pkey_val, pkey_val );
5308 jsonObjectPush( res_list, row_obj );
5310 } while( dbi_result_next_row( result ));
5311 osrfHashFree( dedup );
5314 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5318 /* clean up the query */
5319 dbi_result_free( result );
5322 // If we're asked to flesh, and there's anything to flesh, then flesh it
5323 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5324 // something that he has no permission to look at).
5325 if( res_list->size && query_hash && ! enforce_pcrud ) {
5326 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5328 // Get the flesh depth
5329 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5330 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5331 flesh_depth = max_flesh_depth;
5333 // We need a non-zero flesh depth, and a list of fields to flesh
5334 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5335 if( temp_blob && flesh_depth > 0 ) {
5337 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5338 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5340 osrfStringArray* link_fields = NULL;
5341 osrfHash* links = osrfHashGet( class_meta, "links" );
5343 // Make an osrfStringArray of the names of fields to be fleshed
5344 if( flesh_fields ) {
5345 if( flesh_fields->size == 1 ) {
5346 const char* _t = jsonObjectGetString(
5347 jsonObjectGetIndex( flesh_fields, 0 ) );
5348 if( !strcmp( _t, "*" ))
5349 link_fields = osrfHashKeys( links );
5352 if( !link_fields ) {
5354 link_fields = osrfNewStringArray( 1 );
5355 jsonIterator* _i = jsonNewIterator( flesh_fields );
5356 while ((_f = jsonIteratorNext( _i ))) {
5357 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5359 jsonIteratorFree( _i );
5363 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5365 // Iterate over the JSON_ARRAY of rows
5367 unsigned long res_idx = 0;
5368 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5371 const char* link_field;
5373 // Iterate over the list of fleshable fields
5374 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5376 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5378 osrfHash* kid_link = osrfHashGet( links, link_field );
5380 continue; // Not a link field; skip it
5382 osrfHash* field = osrfHashGet( fields, link_field );
5384 continue; // Not a field at all; skip it (IDL is ill-formed)
5386 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5387 osrfHashGet( kid_link, "class" ));
5389 continue; // The class it links to doesn't exist; skip it
5391 const char* reltype = osrfHashGet( kid_link, "reltype" );
5393 continue; // No reltype; skip it (IDL is ill-formed)
5395 osrfHash* value_field = field;
5397 if( !strcmp( reltype, "has_many" )
5398 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5399 value_field = osrfHashGet(
5400 fields, osrfHashGet( class_meta, "primarykey" ) );
5403 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5405 if( link_map->size > 0 ) {
5406 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5409 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5414 osrfHashGet( kid_link, "class" ),
5421 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5422 osrfHashGet( kid_link, "field" ),
5423 osrfHashGet( kid_link, "class" ),
5424 osrfHashGet( kid_link, "key" ),
5425 osrfHashGet( kid_link, "reltype" )
5428 const char* search_key = jsonObjectGetString(
5429 jsonObjectGetIndex( cur,
5430 atoi( osrfHashGet( value_field, "array_position" ) )
5435 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5439 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5441 // construct WHERE clause
5442 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5445 osrfHashGet( kid_link, "key" ),
5446 jsonNewObject( search_key )
5449 // construct the rest of the query, mostly
5450 // by copying pieces of the previous level of query
5451 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5452 jsonObjectSetKey( rest_of_query, "flesh",
5453 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5457 jsonObjectSetKey( rest_of_query, "flesh_fields",
5458 jsonObjectClone( flesh_blob ));
5460 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5461 jsonObjectSetKey( rest_of_query, "order_by",
5462 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5466 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5467 jsonObjectSetKey( rest_of_query, "select",
5468 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5472 // do the query, recursively, to expand the fleshable field
5473 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5474 where_clause, rest_of_query, err );
5476 jsonObjectFree( where_clause );
5477 jsonObjectFree( rest_of_query );
5480 osrfStringArrayFree( link_fields );
5481 jsonObjectFree( res_list );
5482 jsonObjectFree( flesh_blob );
5486 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5487 osrfHashGet( kid_link, "class" ), kids->size );
5489 // Traverse the result set
5490 jsonObject* X = NULL;
5491 if( link_map->size > 0 && kids->size > 0 ) {
5493 kids = jsonNewObjectType( JSON_ARRAY );
5495 jsonObject* _k_node;
5496 unsigned long res_idx = 0;
5497 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5503 (unsigned long) atoi(
5509 osrfHashGet( kid_link, "class" )
5513 osrfStringArrayGetString( link_map, 0 )
5521 } // end while loop traversing X
5524 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5525 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5526 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5527 osrfHashGet( kid_link, "field" ));
5530 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5531 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5535 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5537 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5538 osrfHashGet( kid_link, "field" ) );
5541 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5542 jsonObjectClone( kids )
5547 jsonObjectFree( kids );
5551 jsonObjectFree( kids );
5553 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5554 osrfHashGet( kid_link, "field" ) );
5555 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5557 } // end while loop traversing list of fleshable fields
5558 } // end while loop traversing res_list
5559 jsonObjectFree( flesh_blob );
5560 osrfStringArrayFree( link_fields );
5569 int doUpdate( osrfMethodContext* ctx ) {
5570 if( osrfMethodVerifyContext( ctx )) {
5571 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5576 timeout_needs_resetting = 1;
5578 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5580 jsonObject* target = NULL;
5582 target = jsonObjectGetIndex( ctx->params, 1 );
5584 target = jsonObjectGetIndex( ctx->params, 0 );
5586 if(!verifyObjectClass( ctx, target )) {
5587 osrfAppRespondComplete( ctx, NULL );
5591 if( getXactId( ctx ) == NULL ) {
5592 osrfAppSessionStatus(
5594 OSRF_STATUS_BADREQUEST,
5595 "osrfMethodException",
5597 "No active transaction -- required for UPDATE"
5599 osrfAppRespondComplete( ctx, NULL );
5603 // The following test is harmless but redundant. If a class is
5604 // readonly, we don't register an update method for it.
5605 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5606 osrfAppSessionStatus(
5608 OSRF_STATUS_BADREQUEST,
5609 "osrfMethodException",
5611 "Cannot UPDATE readonly class"
5613 osrfAppRespondComplete( ctx, NULL );
5617 const char* trans_id = getXactId( ctx );
5619 // Set the last_xact_id
5620 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5622 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5623 trans_id, target->classname, index );
5624 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5627 char* pkey = osrfHashGet( meta, "primarykey" );
5628 osrfHash* fields = osrfHashGet( meta, "fields" );
5630 char* id = oilsFMGetString( target, pkey );
5634 "%s updating %s object with %s = %s",
5636 osrfHashGet( meta, "fieldmapper" ),
5641 dbhandle = writehandle;
5642 growing_buffer* sql = buffer_init( 128 );
5643 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5646 osrfHash* field_def = NULL;
5647 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5648 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5650 // Skip virtual fields, and the primary key
5651 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5654 const char* field_name = osrfHashIteratorKey( field_itr );
5655 if( ! strcmp( field_name, pkey ) )
5658 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5660 int value_is_numeric = 0; // boolean
5662 if( field_object && field_object->classname ) {
5663 value = oilsFMGetString(
5665 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5667 } else if( field_object && JSON_BOOL == field_object->type ) {
5668 if( jsonBoolIsTrue( field_object ) )
5669 value = strdup( "t" );
5671 value = strdup( "f" );
5673 value = jsonObjectToSimpleString( field_object );
5674 if( field_object && JSON_NUMBER == field_object->type )
5675 value_is_numeric = 1;
5678 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5679 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5681 if( !field_object || field_object->type == JSON_NULL ) {
5682 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5683 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5687 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5688 buffer_fadd( sql, " %s = NULL", field_name );
5691 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5695 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5697 const char* numtype = get_datatype( field_def );
5698 if( !strncmp( numtype, "INT", 3 ) ) {
5699 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5700 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5701 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5703 // Must really be intended as a string, so quote it
5704 if( dbi_conn_quote_string( dbhandle, &value )) {
5705 buffer_fadd( sql, " %s = %s", field_name, value );
5707 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5708 modulename, value );
5709 osrfAppSessionStatus(
5711 OSRF_STATUS_INTERNALSERVERERROR,
5712 "osrfMethodException",
5714 "Error quoting string -- please see the error log for more details"
5718 osrfHashIteratorFree( field_itr );
5720 osrfAppRespondComplete( ctx, NULL );
5725 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5728 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5732 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5733 buffer_fadd( sql, " %s = %s", field_name, value );
5735 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5736 osrfAppSessionStatus(
5738 OSRF_STATUS_INTERNALSERVERERROR,
5739 "osrfMethodException",
5741 "Error quoting string -- please see the error log for more details"
5745 osrfHashIteratorFree( field_itr );
5747 osrfAppRespondComplete( ctx, NULL );
5756 osrfHashIteratorFree( field_itr );
5758 jsonObject* obj = jsonNewObject( id );
5760 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5761 dbi_conn_quote_string( dbhandle, &id );
5763 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5765 char* query = buffer_release( sql );
5766 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5768 dbi_result result = dbi_conn_query( dbhandle, query );
5773 jsonObjectFree( obj );
5774 obj = jsonNewObject( NULL );
5776 int errnum = dbi_conn_error( dbhandle, &msg );
5779 "%s ERROR updating %s object with %s = %s: %d %s",
5781 osrfHashGet( meta, "fieldmapper" ),
5785 msg ? msg : "(No description available)"
5787 osrfAppSessionStatus(
5789 OSRF_STATUS_INTERNALSERVERERROR,
5790 "osrfMethodException",
5792 "Error in updating a row -- please see the error log for more details"
5794 if( !oilsIsDBConnected( dbhandle ))
5795 osrfAppSessionPanic( ctx->session );
5800 osrfAppRespondComplete( ctx, obj );
5801 jsonObjectFree( obj );
5805 int doDelete( osrfMethodContext* ctx ) {
5806 if( osrfMethodVerifyContext( ctx )) {
5807 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5812 timeout_needs_resetting = 1;
5814 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5816 if( getXactId( ctx ) == NULL ) {
5817 osrfAppSessionStatus(
5819 OSRF_STATUS_BADREQUEST,
5820 "osrfMethodException",
5822 "No active transaction -- required for DELETE"
5824 osrfAppRespondComplete( ctx, NULL );
5828 // The following test is harmless but redundant. If a class is
5829 // readonly, we don't register a delete method for it.
5830 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5831 osrfAppSessionStatus(
5833 OSRF_STATUS_BADREQUEST,
5834 "osrfMethodException",
5836 "Cannot DELETE readonly class"
5838 osrfAppRespondComplete( ctx, NULL );
5842 dbhandle = writehandle;
5844 char* pkey = osrfHashGet( meta, "primarykey" );
5851 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5852 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5853 osrfAppRespondComplete( ctx, NULL );
5857 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5859 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5860 osrfAppRespondComplete( ctx, NULL );
5863 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5868 "%s deleting %s object with %s = %s",
5870 osrfHashGet( meta, "fieldmapper" ),
5875 jsonObject* obj = jsonNewObject( id );
5877 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5878 dbi_conn_quote_string( writehandle, &id );
5880 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5881 osrfHashGet( meta, "tablename" ), pkey, id );
5886 jsonObjectFree( obj );
5887 obj = jsonNewObject( NULL );
5889 int errnum = dbi_conn_error( writehandle, &msg );
5892 "%s ERROR deleting %s object with %s = %s: %d %s",
5894 osrfHashGet( meta, "fieldmapper" ),
5898 msg ? msg : "(No description available)"
5900 osrfAppSessionStatus(
5902 OSRF_STATUS_INTERNALSERVERERROR,
5903 "osrfMethodException",
5905 "Error in deleting a row -- please see the error log for more details"
5907 if( !oilsIsDBConnected( writehandle ))
5908 osrfAppSessionPanic( ctx->session );
5913 osrfAppRespondComplete( ctx, obj );
5914 jsonObjectFree( obj );
5919 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5920 @param result An iterator for a result set; we only look at the current row.
5921 @param @meta Pointer to the class metadata for the core class.
5922 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5924 If a column is not defined in the IDL, or if it has no array_position defined for it in
5925 the IDL, or if it is defined as virtual, ignore it.
5927 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5928 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5929 array_position in the IDL.
5931 A field defined in the IDL but not represented in the returned row will leave a hole
5932 in the JSON_ARRAY. In effect it will be treated as a null value.
5934 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5935 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5936 classname corresponding to the @a meta argument.
5938 The calling code is responsible for freeing the the resulting jsonObject by calling
5941 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5942 if( !( result && meta )) return NULL;
5944 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5945 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5946 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5948 osrfHash* fields = osrfHashGet( meta, "fields" );
5950 int columnIndex = 1;
5951 const char* columnName;
5953 /* cycle through the columns in the row returned from the database */
5954 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5956 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5958 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5960 /* determine the field type and storage attributes */
5961 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5962 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5964 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5965 // or if it has no sequence number there, or if it's virtual, skip it.
5966 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5969 if( str_is_true( osrfHashGet( _f, "virtual" )))
5970 continue; // skip this column: IDL says it's virtual
5972 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5973 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5974 continue; // since we assign sequence numbers dynamically as we load the IDL.
5976 fmIndex = atoi( pos );
5977 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5979 continue; // This field is not defined in the IDL
5982 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5983 // sequence number from the IDL (which is likely to be different from the sequence
5984 // of columns in the SELECT clause).
5985 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5986 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5991 case DBI_TYPE_INTEGER :
5993 if( attr & DBI_INTEGER_SIZE8 )
5994 jsonObjectSetIndex( object, fmIndex,
5995 jsonNewNumberObject(
5996 dbi_result_get_longlong_idx( result, columnIndex )));
5998 jsonObjectSetIndex( object, fmIndex,
5999 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6003 case DBI_TYPE_DECIMAL :
6004 jsonObjectSetIndex( object, fmIndex,
6005 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6008 case DBI_TYPE_STRING :
6013 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6018 case DBI_TYPE_DATETIME : {
6020 char dt_string[ 256 ] = "";
6023 // Fetch the date column as a time_t
6024 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6026 // Translate the time_t to a human-readable string
6027 if( !( attr & DBI_DATETIME_DATE )) {
6028 gmtime_r( &_tmp_dt, &gmdt );
6029 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6030 } else if( !( attr & DBI_DATETIME_TIME )) {
6031 localtime_r( &_tmp_dt, &gmdt );
6032 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6034 localtime_r( &_tmp_dt, &gmdt );
6035 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6038 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6042 case DBI_TYPE_BINARY :
6043 osrfLogError( OSRF_LOG_MARK,
6044 "Can't do binary at column %s : index %d", columnName, columnIndex );
6053 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6054 if( !result ) return NULL;
6056 jsonObject* object = jsonNewObject( NULL );
6059 char dt_string[ 256 ];
6063 int columnIndex = 1;
6065 unsigned short type;
6066 const char* columnName;
6068 /* cycle through the column list */
6069 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6071 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6073 fmIndex = -1; // reset the position
6075 /* determine the field type and storage attributes */
6076 type = dbi_result_get_field_type_idx( result, columnIndex );
6077 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6079 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6080 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6085 case DBI_TYPE_INTEGER :
6087 if( attr & DBI_INTEGER_SIZE8 )
6088 jsonObjectSetKey( object, columnName,
6089 jsonNewNumberObject( dbi_result_get_longlong_idx(
6090 result, columnIndex )) );
6092 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6093 dbi_result_get_int_idx( result, columnIndex )) );
6096 case DBI_TYPE_DECIMAL :
6097 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6098 dbi_result_get_double_idx( result, columnIndex )) );
6101 case DBI_TYPE_STRING :
6102 jsonObjectSetKey( object, columnName,
6103 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6106 case DBI_TYPE_DATETIME :
6108 memset( dt_string, '\0', sizeof( dt_string ));
6109 memset( &gmdt, '\0', sizeof( gmdt ));
6111 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6113 if( !( attr & DBI_DATETIME_DATE )) {
6114 gmtime_r( &_tmp_dt, &gmdt );
6115 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6116 } else if( !( attr & DBI_DATETIME_TIME )) {
6117 localtime_r( &_tmp_dt, &gmdt );
6118 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6120 localtime_r( &_tmp_dt, &gmdt );
6121 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6124 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6127 case DBI_TYPE_BINARY :
6128 osrfLogError( OSRF_LOG_MARK,
6129 "Can't do binary at column %s : index %d", columnName, columnIndex );
6133 } // end while loop traversing result
6138 // Interpret a string as true or false
6139 int str_is_true( const char* str ) {
6140 if( NULL == str || strcasecmp( str, "true" ) )
6146 // Interpret a jsonObject as true or false
6147 static int obj_is_true( const jsonObject* obj ) {
6150 else switch( obj->type )
6158 if( strcasecmp( obj->value.s, "true" ) )
6162 case JSON_NUMBER : // Support 1/0 for perl's sake
6163 if( jsonObjectGetNumber( obj ) == 1.0 )
6172 // Translate a numeric code into a text string identifying a type of
6173 // jsonObject. To be used for building error messages.
6174 static const char* json_type( int code ) {
6180 return "JSON_ARRAY";
6182 return "JSON_STRING";
6184 return "JSON_NUMBER";
6190 return "(unrecognized)";
6194 // Extract the "primitive" attribute from an IDL field definition.
6195 // If we haven't initialized the app, then we must be running in
6196 // some kind of testbed. In that case, default to "string".
6197 static const char* get_primitive( osrfHash* field ) {
6198 const char* s = osrfHashGet( field, "primitive" );
6200 if( child_initialized )
6203 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6205 osrfHashGet( field, "name" )
6213 // Extract the "datatype" attribute from an IDL field definition.
6214 // If we haven't initialized the app, then we must be running in
6215 // some kind of testbed. In that case, default to to NUMERIC,
6216 // since we look at the datatype only for numbers.
6217 static const char* get_datatype( osrfHash* field ) {
6218 const char* s = osrfHashGet( field, "datatype" );
6220 if( child_initialized )
6223 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6225 osrfHashGet( field, "name" )
6234 @brief Determine whether a string is potentially a valid SQL identifier.
6235 @param s The identifier to be tested.
6236 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6238 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6239 need to follow all the rules exactly, such as requiring that the first character not
6242 We allow leading and trailing white space. In between, we do not allow punctuation
6243 (except for underscores and dollar signs), control characters, or embedded white space.
6245 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6246 for the foreseeable future such quoted identifiers are not likely to be an issue.
6248 int is_identifier( const char* s) {
6252 // Skip leading white space
6253 while( isspace( (unsigned char) *s ) )
6257 return 0; // Nothing but white space? Not okay.
6259 // Check each character until we reach white space or
6260 // end-of-string. Letters, digits, underscores, and
6261 // dollar signs are okay. With the exception of periods
6262 // (as in schema.identifier), control characters and other
6263 // punctuation characters are not okay. Anything else
6264 // is okay -- it could for example be part of a multibyte
6265 // UTF8 character such as a letter with diacritical marks,
6266 // and those are allowed.
6268 if( isalnum( (unsigned char) *s )
6272 ; // Fine; keep going
6273 else if( ispunct( (unsigned char) *s )
6274 || iscntrl( (unsigned char) *s ) )
6277 } while( *s && ! isspace( (unsigned char) *s ) );
6279 // If we found any white space in the above loop,
6280 // the rest had better be all white space.
6282 while( isspace( (unsigned char) *s ) )
6286 return 0; // White space was embedded within non-white space
6292 @brief Determine whether to accept a character string as a comparison operator.
6293 @param op The candidate comparison operator.
6294 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6296 We don't validate the operator for real. We just make sure that it doesn't contain
6297 any semicolons or white space (with special exceptions for a few specific operators).
6298 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6299 space but it's still not a valid operator, then the database will complain.
6301 Another approach would be to compare the string against a short list of approved operators.
6302 We don't do that because we want to allow custom operators like ">100*", which at this
6303 writing would be difficult or impossible to express otherwise in a JSON query.
6305 int is_good_operator( const char* op ) {
6306 if( !op ) return 0; // Sanity check
6310 if( isspace( (unsigned char) *s ) ) {
6311 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6312 // and IS NOT DISTINCT FROM.
6313 if( !strcasecmp( op, "similar to" ) )
6315 else if( !strcasecmp( op, "is distinct from" ) )
6317 else if( !strcasecmp( op, "is not distinct from" ) )
6322 else if( ';' == *s )
6330 @name Query Frame Management
6332 The following machinery supports a stack of query frames for use by SELECT().
6334 A query frame caches information about one level of a SELECT query. When we enter
6335 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6337 The query frame stores information about the core class, and about any joined classes
6340 The main purpose is to map table aliases to classes and tables, so that a query can
6341 join to the same table more than once. A secondary goal is to reduce the number of
6342 lookups in the IDL by caching the results.
6346 #define STATIC_CLASS_INFO_COUNT 3
6348 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6351 @brief Allocate a ClassInfo as raw memory.
6352 @return Pointer to the newly allocated ClassInfo.
6354 Except for the in_use flag, which is used only by the allocation and deallocation
6355 logic, we don't initialize the ClassInfo here.
6357 static ClassInfo* allocate_class_info( void ) {
6358 // In order to reduce the number of mallocs and frees, we return a static
6359 // instance of ClassInfo, if we can find one that we're not already using.
6360 // We rely on the fact that the compiler will implicitly initialize the
6361 // static instances so that in_use == 0.
6364 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6365 if( ! static_class_info[ i ].in_use ) {
6366 static_class_info[ i ].in_use = 1;
6367 return static_class_info + i;
6371 // The static ones are all in use. Malloc one.
6373 return safe_malloc( sizeof( ClassInfo ) );
6377 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6378 @param info Pointer to the ClassInfo to be cleared.
6380 static void clear_class_info( ClassInfo* info ) {
6385 // Free any malloc'd strings
6387 if( info->alias != info->alias_store )
6388 free( info->alias );
6390 if( info->class_name != info->class_name_store )
6391 free( info->class_name );
6393 free( info->source_def );
6395 info->alias = info->class_name = info->source_def = NULL;
6400 @brief Free a ClassInfo and everything it owns.
6401 @param info Pointer to the ClassInfo to be freed.
6403 static void free_class_info( ClassInfo* info ) {
6408 clear_class_info( info );
6410 // If it's one of the static instances, just mark it as not in use
6413 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6414 if( info == static_class_info + i ) {
6415 static_class_info[ i ].in_use = 0;
6420 // Otherwise it must have been malloc'd, so free it
6426 @brief Populate an already-allocated ClassInfo.
6427 @param info Pointer to the ClassInfo to be populated.
6428 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6430 @param class Name of the class.
6431 @return Zero if successful, or 1 if not.
6433 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6434 the relevant portions of the IDL for the specified class.
6436 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6439 osrfLogError( OSRF_LOG_MARK,
6440 "%s ERROR: No ClassInfo available to populate", modulename );
6441 info->alias = info->class_name = info->source_def = NULL;
6442 info->class_def = info->fields = info->links = NULL;
6447 osrfLogError( OSRF_LOG_MARK,
6448 "%s ERROR: No class name provided for lookup", modulename );
6449 info->alias = info->class_name = info->source_def = NULL;
6450 info->class_def = info->fields = info->links = NULL;
6454 // Alias defaults to class name if not supplied
6455 if( ! alias || ! alias[ 0 ] )
6458 // Look up class info in the IDL
6459 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6461 osrfLogError( OSRF_LOG_MARK,
6462 "%s ERROR: Class %s not defined in IDL", modulename, class );
6463 info->alias = info->class_name = info->source_def = NULL;
6464 info->class_def = info->fields = info->links = NULL;
6466 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6467 osrfLogError( OSRF_LOG_MARK,
6468 "%s ERROR: Class %s is defined as virtual", modulename, class );
6469 info->alias = info->class_name = info->source_def = NULL;
6470 info->class_def = info->fields = info->links = NULL;
6474 osrfHash* links = osrfHashGet( class_def, "links" );
6476 osrfLogError( OSRF_LOG_MARK,
6477 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6478 info->alias = info->class_name = info->source_def = NULL;
6479 info->class_def = info->fields = info->links = NULL;
6483 osrfHash* fields = osrfHashGet( class_def, "fields" );
6485 osrfLogError( OSRF_LOG_MARK,
6486 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6487 info->alias = info->class_name = info->source_def = NULL;
6488 info->class_def = info->fields = info->links = NULL;
6492 char* source_def = oilsGetRelation( class_def );
6496 // We got everything we need, so populate the ClassInfo
6497 if( strlen( alias ) > ALIAS_STORE_SIZE )
6498 info->alias = strdup( alias );
6500 strcpy( info->alias_store, alias );
6501 info->alias = info->alias_store;
6504 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6505 info->class_name = strdup( class );
6507 strcpy( info->class_name_store, class );
6508 info->class_name = info->class_name_store;
6511 info->source_def = source_def;
6513 info->class_def = class_def;
6514 info->links = links;
6515 info->fields = fields;
6520 #define STATIC_FRAME_COUNT 3
6522 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6525 @brief Allocate a QueryFrame as raw memory.
6526 @return Pointer to the newly allocated QueryFrame.
6528 Except for the in_use flag, which is used only by the allocation and deallocation
6529 logic, we don't initialize the QueryFrame here.
6531 static QueryFrame* allocate_frame( void ) {
6532 // In order to reduce the number of mallocs and frees, we return a static
6533 // instance of QueryFrame, if we can find one that we're not already using.
6534 // We rely on the fact that the compiler will implicitly initialize the
6535 // static instances so that in_use == 0.
6538 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6539 if( ! static_frame[ i ].in_use ) {
6540 static_frame[ i ].in_use = 1;
6541 return static_frame + i;
6545 // The static ones are all in use. Malloc one.
6547 return safe_malloc( sizeof( QueryFrame ) );
6551 @brief Free a QueryFrame, and all the memory it owns.
6552 @param frame Pointer to the QueryFrame to be freed.
6554 static void free_query_frame( QueryFrame* frame ) {
6559 clear_class_info( &frame->core );
6561 // Free the join list
6563 ClassInfo* info = frame->join_list;
6566 free_class_info( info );
6570 frame->join_list = NULL;
6573 // If the frame is a static instance, just mark it as unused
6575 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6576 if( frame == static_frame + i ) {
6577 static_frame[ i ].in_use = 0;
6582 // Otherwise it must have been malloc'd, so free it
6588 @brief Search a given QueryFrame for a specified alias.
6589 @param frame Pointer to the QueryFrame to be searched.
6590 @param target The alias for which to search.
6591 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6593 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6594 if( ! frame || ! target ) {
6598 ClassInfo* found_class = NULL;
6600 if( !strcmp( target, frame->core.alias ) )
6601 return &(frame->core);
6603 ClassInfo* curr_class = frame->join_list;
6604 while( curr_class ) {
6605 if( strcmp( target, curr_class->alias ) )
6606 curr_class = curr_class->next;
6608 found_class = curr_class;
6618 @brief Push a new (blank) QueryFrame onto the stack.
6620 static void push_query_frame( void ) {
6621 QueryFrame* frame = allocate_frame();
6622 frame->join_list = NULL;
6623 frame->next = curr_query;
6625 // Initialize the ClassInfo for the core class
6626 ClassInfo* core = &frame->core;
6627 core->alias = core->class_name = core->source_def = NULL;
6628 core->class_def = core->fields = core->links = NULL;
6634 @brief Pop a QueryFrame off the stack and destroy it.
6636 static void pop_query_frame( void ) {
6641 QueryFrame* popped = curr_query;
6642 curr_query = popped->next;
6644 free_query_frame( popped );
6648 @brief Populate the ClassInfo for the core class.
6649 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6650 class name as an alias.
6651 @param class_name Name of the core class.
6652 @return Zero if successful, or 1 if not.
6654 Populate the ClassInfo of the core class with copies of the alias and class name, and
6655 with pointers to the relevant portions of the IDL for the core class.
6657 static int add_query_core( const char* alias, const char* class_name ) {
6660 if( ! curr_query ) {
6661 osrfLogError( OSRF_LOG_MARK,
6662 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6664 } else if( curr_query->core.alias ) {
6665 osrfLogError( OSRF_LOG_MARK,
6666 "%s ERROR: Core class %s already populated as %s",
6667 modulename, curr_query->core.class_name, curr_query->core.alias );
6671 build_class_info( &curr_query->core, alias, class_name );
6672 if( curr_query->core.alias )
6675 osrfLogError( OSRF_LOG_MARK,
6676 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6682 @brief Search the current QueryFrame for a specified alias.
6683 @param target The alias for which to search.
6684 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6686 static inline ClassInfo* search_alias( const char* target ) {
6687 return search_alias_in_frame( curr_query, target );
6691 @brief Search all levels of query for a specified alias, starting with the current query.
6692 @param target The alias for which to search.
6693 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6695 static ClassInfo* search_all_alias( const char* target ) {
6696 ClassInfo* found_class = NULL;
6697 QueryFrame* curr_frame = curr_query;
6699 while( curr_frame ) {
6700 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6703 curr_frame = curr_frame->next;
6710 @brief Add a class to the list of classes joined to the current query.
6711 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6712 the class name as an alias.
6713 @param classname The name of the class to be added.
6714 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6716 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6718 if( ! classname || ! *classname ) { // sanity check
6719 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6726 const ClassInfo* conflict = search_alias( alias );
6728 osrfLogError( OSRF_LOG_MARK,
6729 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6730 modulename, alias, conflict->class_name );
6734 ClassInfo* info = allocate_class_info();
6736 if( build_class_info( info, alias, classname ) ) {
6737 free_class_info( info );
6741 // Add the new ClassInfo to the join list of the current QueryFrame
6742 info->next = curr_query->join_list;
6743 curr_query->join_list = info;
6749 @brief Destroy all nodes on the query stack.
6751 static void clear_query_stack( void ) {