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* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
98 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
99 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
101 void userDataFree( void* );
102 static void sessionDataFree( char*, void* );
103 static int obj_is_true( const jsonObject* obj );
104 static const char* json_type( int code );
105 static const char* get_primitive( osrfHash* field );
106 static const char* get_datatype( osrfHash* field );
107 static void pop_query_frame( void );
108 static void push_query_frame( void );
109 static int add_query_core( const char* alias, const char* class_name );
110 static inline ClassInfo* search_alias( const char* target );
111 static ClassInfo* search_all_alias( const char* target );
112 static ClassInfo* add_joined_class( const char* alias, const char* classname );
113 static void clear_query_stack( void );
115 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
116 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
117 static const char* org_tree_root( osrfMethodContext* ctx );
118 static jsonObject* single_hash( const char* key, const char* value );
120 static int child_initialized = 0; /* boolean */
122 static dbi_conn writehandle; /* our MASTER db connection */
123 static dbi_conn dbhandle; /* our CURRENT db connection */
124 //static osrfHash * readHandles;
126 // The following points to the top of a stack of QueryFrames. It's a little
127 // confusing because the top level of the query is at the bottom of the stack.
128 static QueryFrame* curr_query = NULL;
130 static dbi_conn writehandle; /* our MASTER db connection */
131 static dbi_conn dbhandle; /* our CURRENT db connection */
132 //static osrfHash * readHandles;
134 static int max_flesh_depth = 100;
136 static int enforce_pcrud = 0; // Boolean
137 static char* modulename = NULL;
140 @brief Connect to the database.
141 @return A database connection if successful, or NULL if not.
143 dbi_conn oilsConnectDB( const char* mod_name ) {
145 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
146 if( dbi_initialize( NULL ) == -1 ) {
147 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
150 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
152 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
153 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
154 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
155 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
156 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
157 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
159 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
160 dbi_conn handle = dbi_conn_new( driver );
163 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
166 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
168 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
169 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
171 if( host ) dbi_conn_set_option( handle, "host", host );
172 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
173 if( user ) dbi_conn_set_option( handle, "username", user );
174 if( pw ) dbi_conn_set_option( handle, "password", pw );
175 if( db ) dbi_conn_set_option( handle, "dbname", db );
183 if( dbi_conn_connect( handle ) < 0 ) {
185 if( dbi_conn_connect( handle ) < 0 ) {
187 dbi_conn_error( handle, &msg );
188 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
189 msg ? msg : "(No description available)" );
194 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
200 @brief Select some options.
201 @param module_name: Name of the server.
202 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
204 This source file is used (at this writing) to implement three different servers:
205 - open-ils.reporter-store
209 These servers behave mostly the same, but they implement different combinations of
210 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
212 Here we use the server name in messages to identify which kind of server issued them.
213 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
215 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
217 module_name = "open-ils.cstore"; // bulletproofing with a default
222 modulename = strdup( module_name );
223 enforce_pcrud = do_pcrud;
224 max_flesh_depth = flesh_depth;
228 @brief Install a database connection.
229 @param conn Pointer to a database connection.
231 In some contexts, @a conn may merely provide a driver so that we can process strings
232 properly, without providing an open database connection.
234 void oilsSetDBConnection( dbi_conn conn ) {
235 dbhandle = writehandle = conn;
239 @brief Determine whether a database connection is alive.
240 @param handle Handle for a database connection.
241 @return 1 if the connection is alive, or zero if it isn't.
243 int oilsIsDBConnected( dbi_conn handle ) {
244 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
245 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
247 dbi_result_free( result );
250 // This is a terrible, horrible, no good, very bad kludge.
251 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
252 // but because (due to a previous error) the database is ignoring all commands,
253 // even innocuous SELECTs, until the current transaction is rolled back. The only
254 // known way to detect this condition via the dbi library is by looking at the error
255 // message. This approach will break if the language or wording of the message ever
257 // Note: the dbi_conn_ping function purports to determine whether the doatabase
258 // connection is live, but at this writing this function is unreliable and useless.
259 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
260 "ignored until end of transaction block\n";
262 dbi_conn_error( handle, &msg );
263 if( strcmp( msg, ok_msg )) {
264 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
267 return 1; // ignoring SELECT due to previous error; that's okay
272 @brief Get a table name, view name, or subquery for use in a FROM clause.
273 @param class Pointer to the IDL class entry.
274 @return A table name, a view name, or a subquery in parentheses.
276 In some cases the IDL defines a class, not with a table name or a view name, but with
277 a SELECT statement, which may be used as a subquery.
279 char* oilsGetRelation( osrfHash* classdef ) {
281 char* source_def = NULL;
282 const char* tabledef = osrfHashGet( classdef, "tablename" );
285 source_def = strdup( tabledef ); // Return the name of a table or view
287 tabledef = osrfHashGet( classdef, "source_definition" );
289 // Return a subquery, enclosed in parentheses
290 source_def = safe_malloc( strlen( tabledef ) + 3 );
291 source_def[ 0 ] = '(';
292 strcpy( source_def + 1, tabledef );
293 strcat( source_def, ")" );
295 // Not found: return an error
296 const char* classname = osrfHashGet( classdef, "classname" );
301 "%s ERROR No tablename or source_definition for class \"%s\"",
312 @brief Add datatypes from the database to the fields in the IDL.
313 @param handle Handle for a database connection
314 @return Zero if successful, or 1 upon error.
316 For each relevant class in the IDL: ask the database for the datatype of every field.
317 In particular, determine which fields are text fields and which fields are numeric
318 fields, so that we know whether to enclose their values in quotes.
320 int oilsExtendIDL( dbi_conn handle ) {
321 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
322 osrfHash* class = NULL;
323 growing_buffer* query_buf = buffer_init( 64 );
324 int results_found = 0; // boolean
326 // For each class in the IDL...
327 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
328 const char* classname = osrfHashIteratorKey( class_itr );
329 osrfHash* fields = osrfHashGet( class, "fields" );
331 // If the class is virtual, ignore it
332 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
333 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
337 char* tabledef = oilsGetRelation( class );
339 continue; // No such relation -- a query of it would be doomed to failure
341 buffer_reset( query_buf );
342 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
346 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
347 modulename, OSRF_BUFFER_C_STR( query_buf ) );
349 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
354 const char* columnName;
355 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
357 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
360 /* fetch the fieldmapper index */
361 osrfHash* _f = osrfHashGet(fields, columnName);
364 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
366 /* determine the field type and storage attributes */
368 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
370 case DBI_TYPE_INTEGER : {
372 if( !osrfHashGet(_f, "primitive") )
373 osrfHashSet(_f, "number", "primitive");
375 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
376 if( attr & DBI_INTEGER_SIZE8 )
377 osrfHashSet( _f, "INT8", "datatype" );
379 osrfHashSet( _f, "INT", "datatype" );
382 case DBI_TYPE_DECIMAL :
383 if( !osrfHashGet( _f, "primitive" ))
384 osrfHashSet( _f, "number", "primitive" );
386 osrfHashSet( _f, "NUMERIC", "datatype" );
389 case DBI_TYPE_STRING :
390 if( !osrfHashGet( _f, "primitive" ))
391 osrfHashSet( _f, "string", "primitive" );
393 osrfHashSet( _f,"TEXT", "datatype" );
396 case DBI_TYPE_DATETIME :
397 if( !osrfHashGet( _f, "primitive" ))
398 osrfHashSet( _f, "string", "primitive" );
400 osrfHashSet( _f, "TIMESTAMP", "datatype" );
403 case DBI_TYPE_BINARY :
404 if( !osrfHashGet( _f, "primitive" ))
405 osrfHashSet( _f, "string", "primitive" );
407 osrfHashSet( _f, "BYTEA", "datatype" );
412 "Setting [%s] to primitive [%s] and datatype [%s]...",
414 osrfHashGet( _f, "primitive" ),
415 osrfHashGet( _f, "datatype" )
419 } // end while loop for traversing columns of result
420 dbi_result_free( result );
423 int errnum = dbi_conn_error( handle, &msg );
424 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
425 errnum, msg ? msg : "(No description available)" );
426 // We don't check the database connection here. It's routine to get failures at
427 // this point; we routinely try to query tables that don't exist, because they
428 // are defined in the IDL but not in the database.
430 } // end for each class in IDL
432 buffer_free( query_buf );
433 osrfHashIteratorFree( class_itr );
434 child_initialized = 1;
436 if( !results_found ) {
437 osrfLogError( OSRF_LOG_MARK,
438 "No results found for any class -- bad database connection?" );
440 } else if( ! oilsIsDBConnected( handle )) {
441 osrfLogError( OSRF_LOG_MARK,
442 "Unable to extend IDL: database connection isn't working" );
450 @brief Free an osrfHash that stores a transaction ID.
451 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
453 This function is a callback, to be called by the application session when it ends.
454 The application session stores the osrfHash via an opaque pointer.
456 If the osrfHash contains an entry for the key "xact_id", it means that an
457 uncommitted transaction is pending. Roll it back.
459 void userDataFree( void* blob ) {
460 osrfHash* hash = (osrfHash*) blob;
461 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
462 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
464 int errnum = dbi_conn_error( writehandle, &msg );
465 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
466 errnum, msg ? msg : "(No description available)" );
470 osrfHashFree( hash );
474 @name Managing session data
475 @brief Maintain data stored via the userData pointer of the application session.
477 Currently, session-level data is stored in an osrfHash. Other arrangements are
478 possible, and some would be more efficient. The application session calls a
479 callback function to free userData before terminating.
481 Currently, the only data we store at the session level is the transaction id. By this
482 means we can ensure that any pending transactions are rolled back before the application
488 @brief Free an item in the application session's userData.
489 @param key The name of a key for an osrfHash.
490 @param item An opaque pointer to the item associated with the key.
492 We store an osrfHash as userData with the application session, and arrange (by
493 installing userDataFree() as a different callback) for the session to free that
494 osrfHash before terminating.
496 This function is a callback for freeing items in the osrfHash. Currently we store
498 - Transaction id of a pending transaction; a character string. Key: "xact_id".
499 - Authkey; a character string. Key: "authkey".
500 - User object from the authentication server; a jsonObject. Key: "user_login".
502 If we ever store anything else in userData, we will need to revisit this function so
503 that it will free whatever else needs freeing.
505 static void sessionDataFree( char* key, void* item ) {
506 if( !strcmp( key, "xact_id" )
507 || !strcmp( key, "authkey" ) ) {
509 } else if( !strcmp( key, "user_login" ) )
510 jsonObjectFree( (jsonObject*) item );
514 @brief Save a transaction id.
515 @param ctx Pointer to the method context.
517 Save the session_id of the current application session as a transaction id.
519 static void setXactId( osrfMethodContext* ctx ) {
520 if( ctx && ctx->session ) {
521 osrfAppSession* session = ctx->session;
523 osrfHash* cache = session->userData;
525 // If the session doesn't already have a hash, create one. Make sure
526 // that the application session frees the hash when it terminates.
527 if( NULL == cache ) {
528 session->userData = cache = osrfNewHash();
529 osrfHashSetCallback( cache, &sessionDataFree );
530 ctx->session->userDataFree = &userDataFree;
533 // Save the transaction id in the hash, with the key "xact_id"
534 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
539 @brief Get the transaction ID for the current transaction, if any.
540 @param ctx Pointer to the method context.
541 @return Pointer to the transaction ID.
543 The return value points to an internal buffer, and will become invalid upon issuing
544 a commit or rollback.
546 static inline const char* getXactId( osrfMethodContext* ctx ) {
547 if( ctx && ctx->session && ctx->session->userData )
548 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
554 @brief Clear the current transaction id.
555 @param ctx Pointer to the method context.
557 static inline void clearXactId( osrfMethodContext* ctx ) {
558 if( ctx && ctx->session && ctx->session->userData )
559 osrfHashRemove( ctx->session->userData, "xact_id" );
564 @brief Save the user's login in the userData for the current application session.
565 @param ctx Pointer to the method context.
566 @param user_login Pointer to the user login object to be cached (we cache the original,
569 If @a user_login is NULL, remove the user login if one is already cached.
571 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
572 if( ctx && ctx->session ) {
573 osrfAppSession* session = ctx->session;
575 osrfHash* cache = session->userData;
577 // If the session doesn't already have a hash, create one. Make sure
578 // that the application session frees the hash when it terminates.
579 if( NULL == cache ) {
580 session->userData = cache = osrfNewHash();
581 osrfHashSetCallback( cache, &sessionDataFree );
582 ctx->session->userDataFree = &userDataFree;
586 osrfHashSet( cache, user_login, "user_login" );
588 osrfHashRemove( cache, "user_login" );
593 @brief Get the user login object for the current application session, if any.
594 @param ctx Pointer to the method context.
595 @return Pointer to the user login object if found; otherwise NULL.
597 The user login object was returned from the authentication server, and then cached so
598 we don't have to call the authentication server again for the same user.
600 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
601 if( ctx && ctx->session && ctx->session->userData )
602 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
608 @brief Save a copy of an authkey in the userData of the current application session.
609 @param ctx Pointer to the method context.
610 @param authkey The authkey to be saved.
612 If @a authkey is NULL, remove the authkey if one is already cached.
614 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
615 if( ctx && ctx->session && authkey ) {
616 osrfAppSession* session = ctx->session;
617 osrfHash* cache = session->userData;
619 // If the session doesn't already have a hash, create one. Make sure
620 // that the application session frees the hash when it terminates.
621 if( NULL == cache ) {
622 session->userData = cache = osrfNewHash();
623 osrfHashSetCallback( cache, &sessionDataFree );
624 ctx->session->userDataFree = &userDataFree;
627 // Save the transaction id in the hash, with the key "xact_id"
628 if( authkey && *authkey )
629 osrfHashSet( cache, strdup( authkey ), "authkey" );
631 osrfHashRemove( cache, "authkey" );
636 @brief Reset the login timeout.
637 @param authkey The authentication key for the current login session.
638 @param now The current time.
639 @return Zero if successful, or 1 if not.
641 Tell the authentication server to reset the timeout so that the login session won't
642 expire for a while longer.
644 We could dispense with the @a now parameter by calling time(). But we just called
645 time() in order to decide whether to reset the timeout, so we might as well reuse
646 the result instead of calling time() again.
648 static int reset_timeout( const char* authkey, time_t now ) {
649 jsonObject* auth_object = jsonNewObject( authkey );
651 // Ask the authentication server to reset the timeout. It returns an event
652 // indicating success or failure.
653 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
654 "open-ils.auth.session.reset_timeout", auth_object );
655 jsonObjectFree( auth_object );
657 if( !result || result->type != JSON_HASH ) {
658 osrfLogError( OSRF_LOG_MARK,
659 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
660 jsonObjectFree( result );
661 return 1; // Not the right sort of object returned
664 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
665 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
666 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
667 jsonObjectFree( result );
668 return 1; // Return code from method not available
671 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
672 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
674 desc = "(No reason available)"; // failsafe; shouldn't happen
675 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
676 jsonObjectFree( result );
680 // Revise our local proxy for the timeout deadline
681 // by a smallish fraction of the timeout interval
682 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
684 timeout = "1"; // failsafe; shouldn't happen
685 time_next_reset = now + atoi( timeout ) / 15;
687 jsonObjectFree( result );
688 return 0; // Successfully reset timeout
692 @brief Get the authkey string for the current application session, if any.
693 @param ctx Pointer to the method context.
694 @return Pointer to the cached authkey if found; otherwise NULL.
696 If present, the authkey string was cached from a previous method call.
698 static const char* getAuthkey( osrfMethodContext* ctx ) {
699 if( ctx && ctx->session && ctx->session->userData ) {
700 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
702 // Possibly reset the authentication timeout to keep the login alive. We do so
703 // no more than once per method call, and not at all if it has been only a short
704 // time since the last reset.
706 // Here we reset explicitly, if at all. We also implicitly reset the timeout
707 // whenever we call the "open-ils.auth.session.retrieve" method.
708 if( timeout_needs_resetting ) {
709 time_t now = time( NULL );
710 if( now >= time_next_reset && reset_timeout( authkey, now ) )
711 authkey = NULL; // timeout has apparently expired already
714 timeout_needs_resetting = 0;
722 @brief Implement the transaction.begin method.
723 @param ctx Pointer to the method context.
724 @return Zero if successful, or -1 upon error.
726 Start a transaction. Save a transaction ID for future reference.
729 - authkey (PCRUD only)
731 Return to client: Transaction ID
733 int beginTransaction( osrfMethodContext* ctx ) {
734 if(osrfMethodVerifyContext( ctx )) {
735 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
739 if( enforce_pcrud ) {
740 timeout_needs_resetting = 1;
741 const jsonObject* user = verifyUserPCRUD( ctx );
746 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
749 int errnum = dbi_conn_error( writehandle, &msg );
750 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
751 modulename, errnum, msg ? msg : "(No description available)" );
752 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
753 "osrfMethodException", ctx->request, "Error starting transaction" );
754 if( !oilsIsDBConnected( writehandle ))
755 osrfAppSessionPanic( ctx->session );
758 dbi_result_free( result );
760 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
761 osrfAppRespondComplete( ctx, ret );
762 jsonObjectFree( ret );
768 @brief Implement the savepoint.set method.
769 @param ctx Pointer to the method context.
770 @return Zero if successful, or -1 if not.
772 Issue a SAVEPOINT to the database server.
775 - authkey (PCRUD only)
778 Return to client: Savepoint name
780 int setSavepoint( osrfMethodContext* ctx ) {
781 if(osrfMethodVerifyContext( ctx )) {
782 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
787 if( enforce_pcrud ) {
789 timeout_needs_resetting = 1;
790 const jsonObject* user = verifyUserPCRUD( ctx );
795 // Verify that a transaction is pending
796 const char* trans_id = getXactId( ctx );
797 if( NULL == trans_id ) {
798 osrfAppSessionStatus(
800 OSRF_STATUS_INTERNALSERVERERROR,
801 "osrfMethodException",
803 "No active transaction -- required for savepoints"
808 // Get the savepoint name from the method params
809 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
811 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
814 int errnum = dbi_conn_error( writehandle, &msg );
817 "%s: Error creating savepoint %s in transaction %s: %d %s",
822 msg ? msg : "(No description available)"
824 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
825 "osrfMethodException", ctx->request, "Error creating savepoint" );
826 if( !oilsIsDBConnected( writehandle ))
827 osrfAppSessionPanic( ctx->session );
830 dbi_result_free( result );
831 jsonObject* ret = jsonNewObject( spName );
832 osrfAppRespondComplete( ctx, ret );
833 jsonObjectFree( ret );
839 @brief Implement the savepoint.release method.
840 @param ctx Pointer to the method context.
841 @return Zero if successful, or -1 if not.
843 Issue a RELEASE SAVEPOINT to the database server.
846 - authkey (PCRUD only)
849 Return to client: Savepoint name
851 int releaseSavepoint( osrfMethodContext* ctx ) {
852 if(osrfMethodVerifyContext( ctx )) {
853 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
858 if( enforce_pcrud ) {
860 timeout_needs_resetting = 1;
861 const jsonObject* user = verifyUserPCRUD( ctx );
866 // Verify that a transaction is pending
867 const char* trans_id = getXactId( ctx );
868 if( NULL == trans_id ) {
869 osrfAppSessionStatus(
871 OSRF_STATUS_INTERNALSERVERERROR,
872 "osrfMethodException",
874 "No active transaction -- required for savepoints"
879 // Get the savepoint name from the method params
880 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
882 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
885 int errnum = dbi_conn_error( writehandle, &msg );
888 "%s: Error releasing savepoint %s in transaction %s: %d %s",
893 msg ? msg : "(No description available)"
895 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
896 "osrfMethodException", ctx->request, "Error releasing savepoint" );
897 if( !oilsIsDBConnected( writehandle ))
898 osrfAppSessionPanic( ctx->session );
901 dbi_result_free( result );
902 jsonObject* ret = jsonNewObject( spName );
903 osrfAppRespondComplete( ctx, ret );
904 jsonObjectFree( ret );
910 @brief Implement the savepoint.rollback method.
911 @param ctx Pointer to the method context.
912 @return Zero if successful, or -1 if not.
914 Issue a ROLLBACK TO SAVEPOINT to the database server.
917 - authkey (PCRUD only)
920 Return to client: Savepoint name
922 int rollbackSavepoint( osrfMethodContext* ctx ) {
923 if(osrfMethodVerifyContext( ctx )) {
924 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
929 if( enforce_pcrud ) {
931 timeout_needs_resetting = 1;
932 const jsonObject* user = verifyUserPCRUD( ctx );
937 // Verify that a transaction is pending
938 const char* trans_id = getXactId( ctx );
939 if( NULL == trans_id ) {
940 osrfAppSessionStatus(
942 OSRF_STATUS_INTERNALSERVERERROR,
943 "osrfMethodException",
945 "No active transaction -- required for savepoints"
950 // Get the savepoint name from the method params
951 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
953 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
956 int errnum = dbi_conn_error( writehandle, &msg );
959 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
964 msg ? msg : "(No description available)"
966 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
967 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
968 if( !oilsIsDBConnected( writehandle ))
969 osrfAppSessionPanic( ctx->session );
972 dbi_result_free( result );
973 jsonObject* ret = jsonNewObject( spName );
974 osrfAppRespondComplete( ctx, ret );
975 jsonObjectFree( ret );
981 @brief Implement the transaction.commit method.
982 @param ctx Pointer to the method context.
983 @return Zero if successful, or -1 if not.
985 Issue a COMMIT to the database server.
988 - authkey (PCRUD only)
990 Return to client: Transaction ID.
992 int commitTransaction( osrfMethodContext* ctx ) {
993 if(osrfMethodVerifyContext( ctx )) {
994 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
998 if( enforce_pcrud ) {
999 timeout_needs_resetting = 1;
1000 const jsonObject* user = verifyUserPCRUD( ctx );
1005 // Verify that a transaction is pending
1006 const char* trans_id = getXactId( ctx );
1007 if( NULL == trans_id ) {
1008 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1009 "osrfMethodException", ctx->request, "No active transaction to commit" );
1013 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1016 int errnum = dbi_conn_error( writehandle, &msg );
1017 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1018 modulename, errnum, msg ? msg : "(No description available)" );
1019 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1020 "osrfMethodException", ctx->request, "Error committing transaction" );
1021 if( !oilsIsDBConnected( writehandle ))
1022 osrfAppSessionPanic( ctx->session );
1025 dbi_result_free( result );
1026 jsonObject* ret = jsonNewObject( trans_id );
1027 osrfAppRespondComplete( ctx, ret );
1028 jsonObjectFree( ret );
1035 @brief Implement the transaction.rollback method.
1036 @param ctx Pointer to the method context.
1037 @return Zero if successful, or -1 if not.
1039 Issue a ROLLBACK to the database server.
1042 - authkey (PCRUD only)
1044 Return to client: Transaction ID
1046 int rollbackTransaction( osrfMethodContext* ctx ) {
1047 if( osrfMethodVerifyContext( ctx )) {
1048 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1052 if( enforce_pcrud ) {
1053 timeout_needs_resetting = 1;
1054 const jsonObject* user = verifyUserPCRUD( ctx );
1059 // Verify that a transaction is pending
1060 const char* trans_id = getXactId( ctx );
1061 if( NULL == trans_id ) {
1062 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1063 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1067 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1070 int errnum = dbi_conn_error( writehandle, &msg );
1071 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1072 modulename, errnum, msg ? msg : "(No description available)" );
1073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1075 if( !oilsIsDBConnected( writehandle ))
1076 osrfAppSessionPanic( ctx->session );
1079 dbi_result_free( result );
1080 jsonObject* ret = jsonNewObject( trans_id );
1081 osrfAppRespondComplete( ctx, ret );
1082 jsonObjectFree( ret );
1089 @brief Implement the "search" method.
1090 @param ctx Pointer to the method context.
1091 @return Zero if successful, or -1 if not.
1094 - authkey (PCRUD only)
1095 - WHERE clause, as jsonObject
1096 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1098 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1099 Optionally flesh linked fields.
1101 int doSearch( osrfMethodContext* ctx ) {
1102 if( osrfMethodVerifyContext( ctx )) {
1103 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1108 timeout_needs_resetting = 1;
1110 jsonObject* where_clause;
1111 jsonObject* rest_of_query;
1113 if( enforce_pcrud ) {
1114 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1115 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1117 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1118 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1121 // Get the class metadata
1122 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1123 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1127 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1129 osrfAppRespondComplete( ctx, NULL );
1133 // Return each row to the client (except that some may be suppressed by PCRUD)
1134 jsonObject* cur = 0;
1135 unsigned long res_idx = 0;
1136 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1137 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1139 osrfAppRespond( ctx, cur );
1141 jsonObjectFree( obj );
1143 osrfAppRespondComplete( ctx, NULL );
1148 @brief Implement the "id_list" method.
1149 @param ctx Pointer to the method context.
1150 @param err Pointer through which to return an error code.
1151 @return Zero if successful, or -1 if not.
1154 - authkey (PCRUD only)
1155 - WHERE clause, as jsonObject
1156 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1158 Return to client: The primary key values for all rows of the relevant class that
1159 satisfy a specified WHERE clause.
1161 This method relies on the assumption that every class has a primary key consisting of
1164 int doIdList( osrfMethodContext* ctx ) {
1165 if( osrfMethodVerifyContext( ctx )) {
1166 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1171 timeout_needs_resetting = 1;
1173 jsonObject* where_clause;
1174 jsonObject* rest_of_query;
1176 // We use the where clause without change. But we need to massage the rest of the
1177 // query, so we work with a copy of it instead of modifying the original.
1179 if( enforce_pcrud ) {
1180 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1181 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1183 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1184 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1187 // Eliminate certain SQL clauses, if present.
1188 if( rest_of_query ) {
1189 jsonObjectRemoveKey( rest_of_query, "select" );
1190 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1191 jsonObjectRemoveKey( rest_of_query, "flesh" );
1192 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1194 rest_of_query = jsonNewObjectType( JSON_HASH );
1197 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1199 // Get the class metadata
1200 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1201 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1203 // Build a SELECT list containing just the primary key,
1204 // i.e. like { "classname":["keyname"] }
1205 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1207 // Load array with name of primary key
1208 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1209 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1210 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1212 jsonObjectSetKey( rest_of_query, "select", select_clause );
1217 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1219 jsonObjectFree( rest_of_query );
1221 osrfAppRespondComplete( ctx, NULL );
1225 // Return each primary key value to the client
1227 unsigned long res_idx = 0;
1228 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1229 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1230 continue; // Suppress due to lack of permission
1232 osrfAppRespond( ctx,
1233 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1236 jsonObjectFree( obj );
1237 osrfAppRespondComplete( ctx, NULL );
1242 @brief Verify that we have a valid class reference.
1243 @param ctx Pointer to the method context.
1244 @param param Pointer to the method parameters.
1245 @return 1 if the class reference is valid, or zero if it isn't.
1247 The class of the method params must match the class to which the method id devoted.
1248 For PCRUD there are additional restrictions.
1250 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1252 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1253 osrfHash* class = osrfHashGet( method_meta, "class" );
1255 // Compare the method's class to the parameters' class
1256 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1258 // Oops -- they don't match. Complain.
1259 growing_buffer* msg = buffer_init( 128 );
1262 "%s: %s method for type %s was passed a %s",
1264 osrfHashGet( method_meta, "methodtype" ),
1265 osrfHashGet( class, "classname" ),
1266 param->classname ? param->classname : "(null)"
1269 char* m = buffer_release( msg );
1270 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1278 return verifyObjectPCRUD( ctx, param );
1284 @brief (PCRUD only) Verify that the user is properly logged in.
1285 @param ctx Pointer to the method context.
1286 @return If the user is logged in, a pointer to the user object from the authentication
1287 server; otherwise NULL.
1289 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1291 // Get the authkey (the first method parameter)
1292 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1294 // See if we have the same authkey, and a user object,
1295 // locally cached from a previous call
1296 const char* cached_authkey = getAuthkey( ctx );
1297 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1298 const jsonObject* cached_user = getUserLogin( ctx );
1303 // We have no matching authentication data in the cache. Authenticate from scratch.
1304 jsonObject* auth_object = jsonNewObject( auth );
1306 // Fetch the user object from the authentication server
1307 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1309 jsonObjectFree( auth_object );
1311 if( !user->classname || strcmp(user->classname, "au" )) {
1313 growing_buffer* msg = buffer_init( 128 );
1316 "%s: permacrud received a bad auth token: %s",
1321 char* m = buffer_release( msg );
1322 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1326 jsonObjectFree( user );
1330 setUserLogin( ctx, user );
1331 setAuthkey( ctx, auth );
1333 // Allow ourselves up to a second before we have to reset the login timeout.
1334 // It would be nice to use some fraction of the timeout interval enforced by the
1335 // authentication server, but that value is not readily available at this point.
1336 // Instead, we use a conservative default interval.
1337 time_next_reset = time( NULL ) + 1;
1343 @brief For PCRUD: Determine whether the current user may access the current row.
1344 @param ctx Pointer to the method context.
1345 @param obj Pointer to the row being potentially accessed.
1346 @return 1 if access is permitted, or 0 if it isn't.
1348 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1350 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1352 dbhandle = writehandle;
1354 // Figure out what class and method are involved
1355 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1356 osrfHash* class = osrfHashGet( method_metadata, "class" );
1357 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1359 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1360 // contexts we will do another lookup of the current row, even if we already have a
1361 // previously fetched row image, because the row image in hand may not include the
1362 // foreign key(s) that we need.
1364 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1365 // but they aren't implemented yet.
1368 if( *method_type == 's' || *method_type == 'i' ) {
1369 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1371 } else if( *method_type == 'u' || *method_type == 'd' ) {
1372 fetch = 1; // MUST go to the db for the object for update and delete
1375 // Get the appropriate permacrud entry from the IDL, depending on method type
1376 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1378 // No permacrud for this method type on this class
1380 growing_buffer* msg = buffer_init( 128 );
1383 "%s: %s on class %s has no permacrud IDL entry",
1385 osrfHashGet( method_metadata, "methodtype" ),
1386 osrfHashGet( class, "classname" )
1389 char* m = buffer_release( msg );
1390 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1391 "osrfMethodException", ctx->request, m );
1398 // Get the user id, and make sure the user is logged in
1399 const jsonObject* user = verifyUserPCRUD( ctx );
1401 return 0; // Not logged in? No access.
1403 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1405 // Get a list of permissions from the permacrud entry.
1406 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1407 if( permission->size == 0 ) {
1408 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1412 // Build a list of org units that own the row. This is fairly convoluted because there
1413 // are several different ways that an org unit may own the row, as defined by the
1416 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1417 // identifying an owning org_unit..
1418 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1420 // Foreign context adds a layer of indirection. The row points to some other row that
1421 // an org unit may own. The "jump" attribute, if present, adds another layer of
1423 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1425 // The following string array stores the list of org units. (We don't have a thingie
1426 // for storing lists of integers, so we fake it with a list of strings.)
1427 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1430 const char* pkey_value = NULL;
1431 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1432 // If the global_required attribute is present and true, then the only owning
1433 // org unit is the root org unit, i.e. the one with no parent.
1434 osrfLogDebug( OSRF_LOG_MARK,
1435 "global-level permissions required, fetching top of the org tree" );
1437 // check for perm at top of org tree
1438 const char* org_tree_root_id = org_tree_root( ctx );
1439 if( org_tree_root_id ) {
1440 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1441 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1443 osrfStringArrayFree( context_org_array );
1448 // If the global_required attribute is absent or false, then we look for
1449 // local and/or foreign context. In order to find the relevant foreign
1450 // keys, we must either read the relevant row from the database, or look at
1451 // the image of the row that we already have in memory.
1453 // Even if we have an image of the row in memory, that image may not include the
1454 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1455 // of the row to make sure that we have what we need.
1457 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1458 "fetching context org ids" );
1459 const char* pkey = osrfHashGet( class, "primarykey" );
1460 jsonObject *param = NULL;
1463 // There is no primary key, so we can't do a fresh lookup. Use the row
1464 // image that we already have. If it doesn't have everything we need, too bad.
1466 param = jsonObjectClone( obj );
1467 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1468 } else if( obj->classname ) {
1469 pkey_value = oilsFMGetStringConst( obj, pkey );
1471 param = jsonObjectClone( obj );
1472 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1475 pkey_value = jsonObjectGetString( obj );
1477 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1478 "of %s and retrieving from the database", pkey_value );
1482 // Fetch the row so that we can look at the foreign key(s)
1483 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1484 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1485 jsonObjectFree( _tmp_params );
1487 param = jsonObjectExtractIndex( _list, 0 );
1488 jsonObjectFree( _list );
1492 // The row doesn't exist. Complain, and deny access.
1493 osrfLogDebug( OSRF_LOG_MARK,
1494 "Object not found in the database with primary key %s of %s",
1497 growing_buffer* msg = buffer_init( 128 );
1500 "%s: no object found with primary key %s of %s",
1506 char* m = buffer_release( msg );
1507 osrfAppSessionStatus(
1509 OSRF_STATUS_INTERNALSERVERERROR,
1510 "osrfMethodException",
1519 if( local_context && local_context->size > 0 ) {
1520 // The IDL provides a list of column names for the foreign keys denoting
1521 // local context, i.e. columns identifying owing org units directly. Look up
1522 // the value of each one, and if it isn't null, add it to the list of org units.
1523 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1524 local_context->size );
1526 const char* lcontext = NULL;
1527 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1528 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1529 if( fkey_value ) { // if not null
1530 osrfStringArrayAdd( context_org_array, fkey_value );
1533 "adding class-local field %s (value: %s) to the context org list",
1535 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1541 if( foreign_context ) {
1542 unsigned long class_count = osrfHashGetCount( foreign_context );
1543 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1545 if( class_count > 0 ) {
1547 // The IDL provides a list of foreign key columns pointing to rows that
1548 // an org unit may own. Follow each link, identify the owning org unit,
1549 // and add it to the list.
1550 osrfHash* fcontext = NULL;
1551 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1552 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1553 // For each class to which a foreign key points:
1554 const char* class_name = osrfHashIteratorKey( class_itr );
1555 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1559 "%d foreign context fields(s) specified for class %s",
1560 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1564 // Get the name of the key field in the foreign table
1565 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1567 // Get the value of the foreign key pointing to the foreign table
1568 char* foreign_pkey_value =
1569 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1570 if( !foreign_pkey_value )
1571 continue; // Foreign key value is null; skip it
1573 // Look up the row to which the foreign key points
1574 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1575 jsonObject* _list = doFieldmapperSearch(
1576 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1578 jsonObject* _fparam = NULL;
1579 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1580 _fparam = jsonObjectExtractIndex( _list, 0 );
1582 jsonObjectFree( _tmp_params );
1583 jsonObjectFree( _list );
1585 // At this point _fparam either points to the row identified by the
1586 // foreign key, or it's NULL (no such row found).
1588 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1590 const char* bad_class = NULL; // For noting failed lookups
1592 bad_class = class_name; // Referenced row not found
1593 else if( jump_list ) {
1594 // Follow a chain of rows, linked by foreign keys, to find an owner
1595 const char* flink = NULL;
1597 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1598 // For each entry in the jump list. Each entry (i.e. flink) is
1599 // the name of a foreign key column in the current row.
1601 // From the IDL, get the linkage information for the next jump
1602 osrfHash* foreign_link_hash =
1603 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1605 // Get the class metadata for the class
1606 // to which the foreign key points
1607 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1608 osrfHashGet( foreign_link_hash, "class" ));
1610 // Get the name of the referenced key of that class
1611 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1613 // Get the value of the foreign key pointing to that class
1614 free( foreign_pkey_value );
1615 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1616 if( !foreign_pkey_value )
1617 break; // Foreign key is null; quit looking
1619 // Build a WHERE clause for the lookup
1620 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1623 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1624 _tmp_params, NULL, &err );
1626 // Get the resulting row
1627 jsonObjectFree( _fparam );
1628 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1629 _fparam = jsonObjectExtractIndex( _list, 0 );
1631 // Referenced row not found
1633 bad_class = osrfHashGet( foreign_link_hash, "class" );
1636 jsonObjectFree( _tmp_params );
1637 jsonObjectFree( _list );
1643 // We had a foreign key pointing to such-and-such a row, but then
1644 // we couldn't fetch that row. The data in the database are in an
1645 // inconsistent state; the database itself may even be corrupted.
1646 growing_buffer* msg = buffer_init( 128 );
1649 "%s: no object of class %s found with primary key %s of %s",
1653 foreign_pkey_value ? foreign_pkey_value : "(null)"
1656 char* m = buffer_release( msg );
1657 osrfAppSessionStatus(
1659 OSRF_STATUS_INTERNALSERVERERROR,
1660 "osrfMethodException",
1666 osrfHashIteratorFree( class_itr );
1667 free( foreign_pkey_value );
1668 jsonObjectFree( param );
1673 free( foreign_pkey_value );
1676 // Examine each context column of the foreign row,
1677 // and add its value to the list of org units.
1679 const char* foreign_field = NULL;
1680 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1681 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1682 osrfStringArrayAdd( context_org_array,
1683 oilsFMGetStringConst( _fparam, foreign_field ));
1684 osrfLogDebug( OSRF_LOG_MARK,
1685 "adding foreign class %s field %s (value: %s) "
1686 "to the context org list",
1689 osrfStringArrayGetString(
1690 context_org_array, context_org_array->size - 1 )
1694 jsonObjectFree( _fparam );
1698 osrfHashIteratorFree( class_itr );
1702 jsonObjectFree( param );
1705 const char* context_org = NULL;
1706 const char* perm = NULL;
1709 // For every combination of permission and context org unit: call a stored procedure
1710 // to determine if the user has this permission in the context of this org unit.
1711 // If the answer is yes at any point, then we're done, and the user has permission.
1712 // In other words permissions are additive.
1714 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1716 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1722 "Checking object permission [%s] for user %d "
1723 "on object %s (class %s) at org %d",
1727 osrfHashGet( class, "classname" ),
1731 result = dbi_conn_queryf(
1733 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1736 osrfHashGet( class, "classname" ),
1744 "Received a result for object permission [%s] "
1745 "for user %d on object %s (class %s) at org %d",
1749 osrfHashGet( class, "classname" ),
1753 if( dbi_result_first_row( result )) {
1754 jsonObject* return_val = oilsMakeJSONFromResult( result );
1755 const char* has_perm = jsonObjectGetString(
1756 jsonObjectGetKeyConst( return_val, "has_perm" ));
1760 "Status of object permission [%s] for user %d "
1761 "on object %s (class %s) at org %d is %s",
1765 osrfHashGet(class, "classname"),
1770 if( *has_perm == 't' )
1772 jsonObjectFree( return_val );
1775 dbi_result_free( result );
1780 int errnum = dbi_conn_error( writehandle, &msg );
1781 osrfLogWarning( OSRF_LOG_MARK,
1782 "Unable to call check object permissions: %d, %s",
1783 errnum, msg ? msg : "(No description available)" );
1784 if( !oilsIsDBConnected( writehandle ))
1785 osrfAppSessionPanic( ctx->session );
1789 osrfLogDebug( OSRF_LOG_MARK,
1790 "Checking non-object permission [%s] for user %d at org %d",
1791 perm, userid, atoi(context_org) );
1792 result = dbi_conn_queryf(
1794 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1801 osrfLogDebug( OSRF_LOG_MARK,
1802 "Received a result for permission [%s] for user %d at org %d",
1803 perm, userid, atoi( context_org ));
1804 if( dbi_result_first_row( result )) {
1805 jsonObject* return_val = oilsMakeJSONFromResult( result );
1806 const char* has_perm = jsonObjectGetString(
1807 jsonObjectGetKeyConst( return_val, "has_perm" ));
1808 osrfLogDebug( OSRF_LOG_MARK,
1809 "Status of permission [%s] for user %d at org %d is [%s]",
1810 perm, userid, atoi( context_org ), has_perm );
1811 if( *has_perm == 't' )
1813 jsonObjectFree( return_val );
1816 dbi_result_free( result );
1821 int errnum = dbi_conn_error( writehandle, &msg );
1822 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1823 errnum, msg ? msg : "(No description available)" );
1824 if( !oilsIsDBConnected( writehandle ))
1825 osrfAppSessionPanic( ctx->session );
1833 osrfStringArrayFree( context_org_array );
1839 @brief Look up the root of the org_unit tree.
1840 @param ctx Pointer to the method context.
1841 @return The id of the root org unit, as a character string.
1843 Query actor.org_unit where parent_ou is null, and return the id as a string.
1845 This function assumes that there is only one root org unit, i.e. that we
1846 have a single tree, not a forest.
1848 The calling code is responsible for freeing the returned string.
1850 static const char* org_tree_root( osrfMethodContext* ctx ) {
1852 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1853 static time_t last_lookup_time = 0;
1854 time_t current_time = time( NULL );
1856 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1857 // We successfully looked this up less than an hour ago.
1858 // It's not likely to have changed since then.
1859 return strdup( cached_root_id );
1861 last_lookup_time = current_time;
1864 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1865 jsonObject* result = doFieldmapperSearch(
1866 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1867 jsonObjectFree( where_clause );
1869 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1872 jsonObjectFree( result );
1874 growing_buffer* msg = buffer_init( 128 );
1875 OSRF_BUFFER_ADD( msg, modulename );
1876 OSRF_BUFFER_ADD( msg,
1877 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1879 char* m = buffer_release( msg );
1880 osrfAppSessionStatus( ctx->session,
1881 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1884 cached_root_id[ 0 ] = '\0';
1888 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1889 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1891 strcpy( cached_root_id, root_org_unit_id );
1892 jsonObjectFree( result );
1893 return cached_root_id;
1897 @brief Create a JSON_HASH with a single key/value pair.
1898 @param key The key of the key/value pair.
1899 @param value the value of the key/value pair.
1900 @return Pointer to a newly created jsonObject of type JSON_HASH.
1902 The value of the key/value is either a string or (if @a value is NULL) a null.
1904 static jsonObject* single_hash( const char* key, const char* value ) {
1906 if( ! key ) key = "";
1908 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1909 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1914 int doCreate( osrfMethodContext* ctx ) {
1915 if(osrfMethodVerifyContext( ctx )) {
1916 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1921 timeout_needs_resetting = 1;
1923 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1924 jsonObject* target = NULL;
1925 jsonObject* options = NULL;
1927 if( enforce_pcrud ) {
1928 target = jsonObjectGetIndex( ctx->params, 1 );
1929 options = jsonObjectGetIndex( ctx->params, 2 );
1931 target = jsonObjectGetIndex( ctx->params, 0 );
1932 options = jsonObjectGetIndex( ctx->params, 1 );
1935 if( !verifyObjectClass( ctx, target )) {
1936 osrfAppRespondComplete( ctx, NULL );
1940 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1942 const char* trans_id = getXactId( ctx );
1944 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1946 osrfAppSessionStatus(
1948 OSRF_STATUS_BADREQUEST,
1949 "osrfMethodException",
1951 "No active transaction -- required for CREATE"
1953 osrfAppRespondComplete( ctx, NULL );
1957 // The following test is harmless but redundant. If a class is
1958 // readonly, we don't register a create method for it.
1959 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1960 osrfAppSessionStatus(
1962 OSRF_STATUS_BADREQUEST,
1963 "osrfMethodException",
1965 "Cannot INSERT readonly class"
1967 osrfAppRespondComplete( ctx, NULL );
1971 // Set the last_xact_id
1972 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1974 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1975 trans_id, target->classname, index);
1976 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1979 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1981 dbhandle = writehandle;
1983 osrfHash* fields = osrfHashGet( meta, "fields" );
1984 char* pkey = osrfHashGet( meta, "primarykey" );
1985 char* seq = osrfHashGet( meta, "sequence" );
1987 growing_buffer* table_buf = buffer_init( 128 );
1988 growing_buffer* col_buf = buffer_init( 128 );
1989 growing_buffer* val_buf = buffer_init( 128 );
1991 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1992 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1993 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1994 buffer_add( val_buf,"VALUES (" );
1998 osrfHash* field = NULL;
1999 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2000 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2002 const char* field_name = osrfHashIteratorKey( field_itr );
2004 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2007 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2010 if( field_object && field_object->classname ) {
2011 value = oilsFMGetString(
2013 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2015 } else if( field_object && JSON_BOOL == field_object->type ) {
2016 if( jsonBoolIsTrue( field_object ) )
2017 value = strdup( "t" );
2019 value = strdup( "f" );
2021 value = jsonObjectToSimpleString( field_object );
2027 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2028 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2031 buffer_add( col_buf, field_name );
2033 if( !field_object || field_object->type == JSON_NULL ) {
2034 buffer_add( val_buf, "DEFAULT" );
2036 } else if( !strcmp( get_primitive( field ), "number" )) {
2037 const char* numtype = get_datatype( field );
2038 if( !strcmp( numtype, "INT8" )) {
2039 buffer_fadd( val_buf, "%lld", atoll( value ));
2041 } else if( !strcmp( numtype, "INT" )) {
2042 buffer_fadd( val_buf, "%d", atoi( value ));
2044 } else if( !strcmp( numtype, "NUMERIC" )) {
2045 buffer_fadd( val_buf, "%f", atof( value ));
2048 if( dbi_conn_quote_string( writehandle, &value )) {
2049 OSRF_BUFFER_ADD( val_buf, value );
2052 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2053 osrfAppSessionStatus(
2055 OSRF_STATUS_INTERNALSERVERERROR,
2056 "osrfMethodException",
2058 "Error quoting string -- please see the error log for more details"
2061 buffer_free( table_buf );
2062 buffer_free( col_buf );
2063 buffer_free( val_buf );
2064 osrfAppRespondComplete( ctx, NULL );
2072 osrfHashIteratorFree( field_itr );
2074 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2075 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2077 char* table_str = buffer_release( table_buf );
2078 char* col_str = buffer_release( col_buf );
2079 char* val_str = buffer_release( val_buf );
2080 growing_buffer* sql = buffer_init( 128 );
2081 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2086 char* query = buffer_release( sql );
2088 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2090 jsonObject* obj = NULL;
2093 dbi_result result = dbi_conn_query( writehandle, query );
2095 obj = jsonNewObject( NULL );
2097 int errnum = dbi_conn_error( writehandle, &msg );
2100 "%s ERROR inserting %s object using query [%s]: %d %s",
2102 osrfHashGet(meta, "fieldmapper"),
2105 msg ? msg : "(No description available)"
2107 osrfAppSessionStatus(
2109 OSRF_STATUS_INTERNALSERVERERROR,
2110 "osrfMethodException",
2112 "INSERT error -- please see the error log for more details"
2114 if( !oilsIsDBConnected( writehandle ))
2115 osrfAppSessionPanic( ctx->session );
2118 dbi_result_free( result );
2120 char* id = oilsFMGetString( target, pkey );
2122 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2123 growing_buffer* _id = buffer_init( 10 );
2124 buffer_fadd( _id, "%lld", new_id );
2125 id = buffer_release( _id );
2128 // Find quietness specification, if present
2129 const char* quiet_str = NULL;
2131 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2133 quiet_str = jsonObjectGetString( quiet_obj );
2136 if( str_is_true( quiet_str )) { // if quietness is specified
2137 obj = jsonNewObject( id );
2141 // Fetch the row that we just inserted, so that we can return it to the client
2142 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2143 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2146 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2150 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2152 jsonObjectFree( list );
2153 jsonObjectFree( where_clause );
2160 osrfAppRespondComplete( ctx, obj );
2161 jsonObjectFree( obj );
2166 @brief Implement the retrieve method.
2167 @param ctx Pointer to the method context.
2168 @param err Pointer through which to return an error code.
2169 @return If successful, a pointer to the result to be returned to the client;
2172 From the method's class, fetch a row with a specified value in the primary key. This
2173 method relies on the database design convention that a primary key consists of a single
2177 - authkey (PCRUD only)
2178 - value of the primary key for the desired row, for building the WHERE clause
2179 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2181 Return to client: One row from the query.
2183 int doRetrieve( osrfMethodContext* ctx ) {
2184 if(osrfMethodVerifyContext( ctx )) {
2185 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2190 timeout_needs_resetting = 1;
2195 if( enforce_pcrud ) {
2200 // Get the class metadata
2201 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2203 // Get the value of the primary key, from a method parameter
2204 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2208 "%s retrieving %s object with primary key value of %s",
2210 osrfHashGet( class_def, "fieldmapper" ),
2211 jsonObjectGetString( id_obj )
2214 // Build a WHERE clause based on the key value
2215 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2218 osrfHashGet( class_def, "primarykey" ), // name of key column
2219 jsonObjectClone( id_obj ) // value of key column
2222 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2226 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2228 jsonObjectFree( where_clause );
2230 osrfAppRespondComplete( ctx, NULL );
2234 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2235 jsonObjectFree( list );
2237 if( enforce_pcrud ) {
2238 if(!verifyObjectPCRUD( ctx, obj )) {
2239 jsonObjectFree( obj );
2241 growing_buffer* msg = buffer_init( 128 );
2242 OSRF_BUFFER_ADD( msg, modulename );
2243 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2245 char* m = buffer_release( msg );
2246 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2250 osrfAppRespondComplete( ctx, NULL );
2255 osrfAppRespondComplete( ctx, obj );
2256 jsonObjectFree( obj );
2261 @brief Translate a numeric value to a string representation for the database.
2262 @param field Pointer to the IDL field definition.
2263 @param value Pointer to a jsonObject holding the value of a field.
2264 @return Pointer to a newly allocated string.
2266 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2267 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2268 or (what is worse) valid SQL that is wrong.
2270 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2272 The calling code is responsible for freeing the resulting string by calling free().
2274 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2275 growing_buffer* val_buf = buffer_init( 32 );
2276 const char* numtype = get_datatype( field );
2278 // For historical reasons the following contains cruft that could be cleaned up.
2279 if( !strncmp( numtype, "INT", 3 ) ) {
2280 if( value->type == JSON_NUMBER )
2281 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2282 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2284 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2287 } else if( !strcmp( numtype, "NUMERIC" )) {
2288 if( value->type == JSON_NUMBER )
2289 buffer_fadd( val_buf, jsonObjectGetString( value ));
2291 buffer_fadd( val_buf, jsonObjectGetString( value ));
2295 // Presumably this was really intended to be a string, so quote it
2296 char* str = jsonObjectToSimpleString( value );
2297 if( dbi_conn_quote_string( dbhandle, &str )) {
2298 OSRF_BUFFER_ADD( val_buf, str );
2301 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2303 buffer_free( val_buf );
2308 return buffer_release( val_buf );
2311 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2312 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2313 growing_buffer* sql_buf = buffer_init( 32 );
2319 osrfHashGet( field, "name" )
2323 buffer_add( sql_buf, "IN (" );
2324 } else if( !strcasecmp( op,"not in" )) {
2325 buffer_add( sql_buf, "NOT IN (" );
2327 buffer_add( sql_buf, "IN (" );
2330 if( node->type == JSON_HASH ) {
2331 // subquery predicate
2332 char* subpred = buildQuery( ctx, node, SUBSELECT );
2334 buffer_free( sql_buf );
2338 buffer_add( sql_buf, subpred );
2341 } else if( node->type == JSON_ARRAY ) {
2342 // literal value list
2343 int in_item_index = 0;
2344 int in_item_first = 1;
2345 const jsonObject* in_item;
2346 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2351 buffer_add( sql_buf, ", " );
2354 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2355 osrfLogError( OSRF_LOG_MARK,
2356 "%s: Expected string or number within IN list; found %s",
2357 modulename, json_type( in_item->type ) );
2358 buffer_free( sql_buf );
2362 // Append the literal value -- quoted if not a number
2363 if( JSON_NUMBER == in_item->type ) {
2364 char* val = jsonNumberToDBString( field, in_item );
2365 OSRF_BUFFER_ADD( sql_buf, val );
2368 } else if( !strcmp( get_primitive( field ), "number" )) {
2369 char* val = jsonNumberToDBString( field, in_item );
2370 OSRF_BUFFER_ADD( sql_buf, val );
2374 char* key_string = jsonObjectToSimpleString( in_item );
2375 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2376 OSRF_BUFFER_ADD( sql_buf, key_string );
2379 osrfLogError( OSRF_LOG_MARK,
2380 "%s: Error quoting key string [%s]", modulename, key_string );
2382 buffer_free( sql_buf );
2388 if( in_item_first ) {
2389 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2390 buffer_free( sql_buf );
2394 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2395 modulename, json_type( node->type ));
2396 buffer_free( sql_buf );
2400 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2402 return buffer_release( sql_buf );
2405 // Receive a JSON_ARRAY representing a function call. The first
2406 // entry in the array is the function name. The rest are parameters.
2407 static char* searchValueTransform( const jsonObject* array ) {
2409 if( array->size < 1 ) {
2410 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2414 // Get the function name
2415 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2416 if( func_item->type != JSON_STRING ) {
2417 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2418 modulename, json_type( func_item->type ));
2422 growing_buffer* sql_buf = buffer_init( 32 );
2424 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2425 OSRF_BUFFER_ADD( sql_buf, "( " );
2427 // Get the parameters
2428 int func_item_index = 1; // We already grabbed the zeroth entry
2429 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2431 // Add a separator comma, if we need one
2432 if( func_item_index > 2 )
2433 buffer_add( sql_buf, ", " );
2435 // Add the current parameter
2436 if( func_item->type == JSON_NULL ) {
2437 buffer_add( sql_buf, "NULL" );
2439 char* val = jsonObjectToSimpleString( func_item );
2440 if( dbi_conn_quote_string( dbhandle, &val )) {
2441 OSRF_BUFFER_ADD( sql_buf, val );
2444 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2446 buffer_free( sql_buf );
2453 buffer_add( sql_buf, " )" );
2455 return buffer_release( sql_buf );
2458 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2459 const jsonObject* node, const char* op ) {
2461 if( ! is_good_operator( op ) ) {
2462 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2466 char* val = searchValueTransform( node );
2470 growing_buffer* sql_buf = buffer_init( 32 );
2475 osrfHashGet( field, "name" ),
2482 return buffer_release( sql_buf );
2485 // class_alias is a class name or other table alias
2486 // field is a field definition as stored in the IDL
2487 // node comes from the method parameter, and may represent an entry in the SELECT list
2488 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2489 const jsonObject* node ) {
2490 growing_buffer* sql_buf = buffer_init( 32 );
2492 const char* field_transform = jsonObjectGetString(
2493 jsonObjectGetKeyConst( node, "transform" ) );
2494 const char* transform_subcolumn = jsonObjectGetString(
2495 jsonObjectGetKeyConst( node, "result_field" ) );
2497 if( transform_subcolumn ) {
2498 if( ! is_identifier( transform_subcolumn ) ) {
2499 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2500 modulename, transform_subcolumn );
2501 buffer_free( sql_buf );
2504 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2507 if( field_transform ) {
2509 if( ! is_identifier( field_transform ) ) {
2510 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2511 modulename, field_transform );
2512 buffer_free( sql_buf );
2516 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2517 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2518 field_transform, class_alias, osrfHashGet( field, "name" ));
2520 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2521 field_transform, class_alias, osrfHashGet( field, "name" ));
2524 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2527 if( array->type != JSON_ARRAY ) {
2528 osrfLogError( OSRF_LOG_MARK,
2529 "%s: Expected JSON_ARRAY for function params; found %s",
2530 modulename, json_type( array->type ) );
2531 buffer_free( sql_buf );
2534 int func_item_index = 0;
2535 jsonObject* func_item;
2536 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2538 char* val = jsonObjectToSimpleString( func_item );
2541 buffer_add( sql_buf, ",NULL" );
2542 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2543 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2544 OSRF_BUFFER_ADD( sql_buf, val );
2546 osrfLogError( OSRF_LOG_MARK,
2547 "%s: Error quoting key string [%s]", modulename, val );
2549 buffer_free( sql_buf );
2556 buffer_add( sql_buf, " )" );
2559 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2562 if( transform_subcolumn )
2563 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2565 return buffer_release( sql_buf );
2568 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2569 const jsonObject* node, const char* op ) {
2571 if( ! is_good_operator( op ) ) {
2572 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2576 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2577 if( ! field_transform )
2580 int extra_parens = 0; // boolean
2582 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2584 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2586 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2588 free( field_transform );
2592 } else if( value_obj->type == JSON_ARRAY ) {
2593 value = searchValueTransform( value_obj );
2595 osrfLogError( OSRF_LOG_MARK,
2596 "%s: Error building value transform for field transform", modulename );
2597 free( field_transform );
2600 } else if( value_obj->type == JSON_HASH ) {
2601 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2603 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2605 free( field_transform );
2609 } else if( value_obj->type == JSON_NUMBER ) {
2610 value = jsonNumberToDBString( field, value_obj );
2611 } else if( value_obj->type == JSON_NULL ) {
2612 osrfLogError( OSRF_LOG_MARK,
2613 "%s: Error building predicate for field transform: null value", modulename );
2614 free( field_transform );
2616 } else if( value_obj->type == JSON_BOOL ) {
2617 osrfLogError( OSRF_LOG_MARK,
2618 "%s: Error building predicate for field transform: boolean value", modulename );
2619 free( field_transform );
2622 if( !strcmp( get_primitive( field ), "number") ) {
2623 value = jsonNumberToDBString( field, value_obj );
2625 value = jsonObjectToSimpleString( value_obj );
2626 if( !dbi_conn_quote_string( dbhandle, &value )) {
2627 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2628 modulename, value );
2630 free( field_transform );
2636 const char* left_parens = "";
2637 const char* right_parens = "";
2639 if( extra_parens ) {
2644 growing_buffer* sql_buf = buffer_init( 32 );
2648 "%s%s %s %s %s %s%s",
2659 free( field_transform );
2661 return buffer_release( sql_buf );
2664 static char* searchSimplePredicate( const char* op, const char* class_alias,
2665 osrfHash* field, const jsonObject* node ) {
2667 if( ! is_good_operator( op ) ) {
2668 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2674 // Get the value to which we are comparing the specified column
2675 if( node->type != JSON_NULL ) {
2676 if( node->type == JSON_NUMBER ) {
2677 val = jsonNumberToDBString( field, node );
2678 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2679 val = jsonNumberToDBString( field, node );
2681 val = jsonObjectToSimpleString( node );
2686 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2687 // Value is not numeric; enclose it in quotes
2688 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2689 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2696 // Compare to a null value
2697 val = strdup( "NULL" );
2698 if( strcmp( op, "=" ))
2704 growing_buffer* sql_buf = buffer_init( 32 );
2705 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2706 char* pred = buffer_release( sql_buf );
2713 static char* searchBETWEENPredicate( const char* class_alias,
2714 osrfHash* field, const jsonObject* node ) {
2716 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2717 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2719 if( NULL == y_node ) {
2720 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2723 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2724 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2731 if( !strcmp( get_primitive( field ), "number") ) {
2732 x_string = jsonNumberToDBString( field, x_node );
2733 y_string = jsonNumberToDBString( field, y_node );
2736 x_string = jsonObjectToSimpleString( x_node );
2737 y_string = jsonObjectToSimpleString( y_node );
2738 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2739 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2740 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2741 modulename, x_string, y_string );
2748 growing_buffer* sql_buf = buffer_init( 32 );
2749 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2750 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2754 return buffer_release( sql_buf );
2757 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2758 jsonObject* node, osrfMethodContext* ctx ) {
2761 if( node->type == JSON_ARRAY ) { // equality IN search
2762 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2763 } else if( node->type == JSON_HASH ) { // other search
2764 jsonIterator* pred_itr = jsonNewIterator( node );
2765 if( !jsonIteratorHasNext( pred_itr ) ) {
2766 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2767 modulename, osrfHashGet(field, "name" ));
2769 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2771 // Verify that there are no additional predicates
2772 if( jsonIteratorHasNext( pred_itr ) ) {
2773 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2774 modulename, osrfHashGet(field, "name" ));
2775 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2776 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2777 else if( !(strcasecmp( pred_itr->key,"in" ))
2778 || !(strcasecmp( pred_itr->key,"not in" )) )
2779 pred = searchINPredicate(
2780 class_info->alias, field, pred_node, pred_itr->key, ctx );
2781 else if( pred_node->type == JSON_ARRAY )
2782 pred = searchFunctionPredicate(
2783 class_info->alias, field, pred_node, pred_itr->key );
2784 else if( pred_node->type == JSON_HASH )
2785 pred = searchFieldTransformPredicate(
2786 class_info, field, pred_node, pred_itr->key );
2788 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2790 jsonIteratorFree( pred_itr );
2792 } else if( node->type == JSON_NULL ) { // IS NULL search
2793 growing_buffer* _p = buffer_init( 64 );
2796 "\"%s\".%s IS NULL",
2797 class_info->class_name,
2798 osrfHashGet( field, "name" )
2800 pred = buffer_release( _p );
2801 } else { // equality search
2802 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2821 field : call_number,
2837 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2839 const jsonObject* working_hash;
2840 jsonObject* freeable_hash = NULL;
2842 if( join_hash->type == JSON_HASH ) {
2843 working_hash = join_hash;
2844 } else if( join_hash->type == JSON_STRING ) {
2845 // turn it into a JSON_HASH by creating a wrapper
2846 // around a copy of the original
2847 const char* _tmp = jsonObjectGetString( join_hash );
2848 freeable_hash = jsonNewObjectType( JSON_HASH );
2849 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2850 working_hash = freeable_hash;
2854 "%s: JOIN failed; expected JSON object type not found",
2860 growing_buffer* join_buf = buffer_init( 128 );
2861 const char* leftclass = left_info->class_name;
2863 jsonObject* snode = NULL;
2864 jsonIterator* search_itr = jsonNewIterator( working_hash );
2866 while ( (snode = jsonIteratorNext( search_itr )) ) {
2867 const char* right_alias = search_itr->key;
2869 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2871 class = right_alias;
2873 const ClassInfo* right_info = add_joined_class( right_alias, class );
2877 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2881 jsonIteratorFree( search_itr );
2882 buffer_free( join_buf );
2884 jsonObjectFree( freeable_hash );
2887 osrfHash* links = right_info->links;
2888 const char* table = right_info->source_def;
2890 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2891 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2893 if( field && !fkey ) {
2894 // Look up the corresponding join column in the IDL.
2895 // The link must be defined in the child table,
2896 // and point to the right parent table.
2897 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2898 const char* reltype = NULL;
2899 const char* other_class = NULL;
2900 reltype = osrfHashGet( idl_link, "reltype" );
2901 if( reltype && strcmp( reltype, "has_many" ) )
2902 other_class = osrfHashGet( idl_link, "class" );
2903 if( other_class && !strcmp( other_class, leftclass ) )
2904 fkey = osrfHashGet( idl_link, "key" );
2908 "%s: JOIN failed. No link defined from %s.%s to %s",
2914 buffer_free( join_buf );
2916 jsonObjectFree( freeable_hash );
2917 jsonIteratorFree( search_itr );
2921 } else if( !field && fkey ) {
2922 // Look up the corresponding join column in the IDL.
2923 // The link must be defined in the child table,
2924 // and point to the right parent table.
2925 osrfHash* left_links = left_info->links;
2926 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2927 const char* reltype = NULL;
2928 const char* other_class = NULL;
2929 reltype = osrfHashGet( idl_link, "reltype" );
2930 if( reltype && strcmp( reltype, "has_many" ) )
2931 other_class = osrfHashGet( idl_link, "class" );
2932 if( other_class && !strcmp( other_class, class ) )
2933 field = osrfHashGet( idl_link, "key" );
2937 "%s: JOIN failed. No link defined from %s.%s to %s",
2943 buffer_free( join_buf );
2945 jsonObjectFree( freeable_hash );
2946 jsonIteratorFree( search_itr );
2950 } else if( !field && !fkey ) {
2951 osrfHash* left_links = left_info->links;
2953 // For each link defined for the left class:
2954 // see if the link references the joined class
2955 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2956 osrfHash* curr_link = NULL;
2957 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2958 const char* other_class = osrfHashGet( curr_link, "class" );
2959 if( other_class && !strcmp( other_class, class ) ) {
2961 // In the IDL, the parent class doesn't always know then names of the child
2962 // columns that are pointing to it, so don't use that end of the link
2963 const char* reltype = osrfHashGet( curr_link, "reltype" );
2964 if( reltype && strcmp( reltype, "has_many" ) ) {
2965 // Found a link between the classes
2966 fkey = osrfHashIteratorKey( itr );
2967 field = osrfHashGet( curr_link, "key" );
2972 osrfHashIteratorFree( itr );
2974 if( !field || !fkey ) {
2975 // Do another such search, with the classes reversed
2977 // For each link defined for the joined class:
2978 // see if the link references the left class
2979 osrfHashIterator* itr = osrfNewHashIterator( links );
2980 osrfHash* curr_link = NULL;
2981 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2982 const char* other_class = osrfHashGet( curr_link, "class" );
2983 if( other_class && !strcmp( other_class, leftclass ) ) {
2985 // In the IDL, the parent class doesn't know then names of the child
2986 // columns that are pointing to it, so don't use that end of the link
2987 const char* reltype = osrfHashGet( curr_link, "reltype" );
2988 if( reltype && strcmp( reltype, "has_many" ) ) {
2989 // Found a link between the classes
2990 field = osrfHashIteratorKey( itr );
2991 fkey = osrfHashGet( curr_link, "key" );
2996 osrfHashIteratorFree( itr );
2999 if( !field || !fkey ) {
3002 "%s: JOIN failed. No link defined between %s and %s",
3007 buffer_free( join_buf );
3009 jsonObjectFree( freeable_hash );
3010 jsonIteratorFree( search_itr );
3015 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3017 if( !strcasecmp( type,"left" )) {
3018 buffer_add( join_buf, " LEFT JOIN" );
3019 } else if( !strcasecmp( type,"right" )) {
3020 buffer_add( join_buf, " RIGHT JOIN" );
3021 } else if( !strcasecmp( type,"full" )) {
3022 buffer_add( join_buf, " FULL JOIN" );
3024 buffer_add( join_buf, " INNER JOIN" );
3027 buffer_add( join_buf, " INNER JOIN" );
3030 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3031 table, right_alias, right_alias, field, left_info->alias, fkey );
3033 // Add any other join conditions as specified by "filter"
3034 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3036 const char* filter_op = jsonObjectGetString(
3037 jsonObjectGetKeyConst( snode, "filter_op" ) );
3038 if( filter_op && !strcasecmp( "or",filter_op )) {
3039 buffer_add( join_buf, " OR " );
3041 buffer_add( join_buf, " AND " );
3044 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3046 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3047 OSRF_BUFFER_ADD( join_buf, jpred );
3052 "%s: JOIN failed. Invalid conditional expression.",
3055 jsonIteratorFree( search_itr );
3056 buffer_free( join_buf );
3058 jsonObjectFree( freeable_hash );
3063 buffer_add( join_buf, " ) " );
3065 // Recursively add a nested join, if one is present
3066 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3068 char* jpred = searchJOIN( join_filter, right_info );
3070 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3071 OSRF_BUFFER_ADD( join_buf, jpred );
3074 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3075 jsonIteratorFree( search_itr );
3076 buffer_free( join_buf );
3078 jsonObjectFree( freeable_hash );
3085 jsonObjectFree( freeable_hash );
3086 jsonIteratorFree( search_itr );
3088 return buffer_release( join_buf );
3093 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3094 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3095 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3097 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3099 search_hash is the JSON expression of the conditions.
3100 meta is the class definition from the IDL, for the relevant table.
3101 opjoin_type indicates whether multiple conditions, if present, should be
3102 connected by AND or OR.
3103 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3104 to pass it to other functions -- and all they do with it is to use the session
3105 and request members to send error messages back to the client.
3109 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3110 int opjoin_type, osrfMethodContext* ctx ) {
3114 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3115 "opjoin_type = %d, ctx addr = %p",
3118 class_info->class_def,
3123 growing_buffer* sql_buf = buffer_init( 128 );
3125 jsonObject* node = NULL;
3128 if( search_hash->type == JSON_ARRAY ) {
3129 if( 0 == search_hash->size ) {
3132 "%s: Invalid predicate structure: empty JSON array",
3135 buffer_free( sql_buf );
3139 unsigned long i = 0;
3140 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3144 if( opjoin_type == OR_OP_JOIN )
3145 buffer_add( sql_buf, " OR " );
3147 buffer_add( sql_buf, " AND " );
3150 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3152 buffer_free( sql_buf );
3156 buffer_fadd( sql_buf, "( %s )", subpred );
3160 } else if( search_hash->type == JSON_HASH ) {
3161 osrfLogDebug( OSRF_LOG_MARK,
3162 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3163 jsonIterator* search_itr = jsonNewIterator( search_hash );
3164 if( !jsonIteratorHasNext( search_itr ) ) {
3167 "%s: Invalid predicate structure: empty JSON object",
3170 jsonIteratorFree( search_itr );
3171 buffer_free( sql_buf );
3175 while( (node = jsonIteratorNext( search_itr )) ) {
3180 if( opjoin_type == OR_OP_JOIN )
3181 buffer_add( sql_buf, " OR " );
3183 buffer_add( sql_buf, " AND " );
3186 if( '+' == search_itr->key[ 0 ] ) {
3188 // This plus sign prefixes a class name or other table alias;
3189 // make sure the table alias is in scope
3190 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3191 if( ! alias_info ) {
3194 "%s: Invalid table alias \"%s\" in WHERE clause",
3198 jsonIteratorFree( search_itr );
3199 buffer_free( sql_buf );
3203 if( node->type == JSON_STRING ) {
3204 // It's the name of a column; make sure it belongs to the class
3205 const char* fieldname = jsonObjectGetString( node );
3206 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3209 "%s: Invalid column name \"%s\" in WHERE clause "
3210 "for table alias \"%s\"",
3215 jsonIteratorFree( search_itr );
3216 buffer_free( sql_buf );
3220 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3222 // It's something more complicated
3223 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3225 jsonIteratorFree( search_itr );
3226 buffer_free( sql_buf );
3230 buffer_fadd( sql_buf, "( %s )", subpred );
3233 } else if( '-' == search_itr->key[ 0 ] ) {
3234 if( !strcasecmp( "-or", search_itr->key )) {
3235 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3237 jsonIteratorFree( search_itr );
3238 buffer_free( sql_buf );
3242 buffer_fadd( sql_buf, "( %s )", subpred );
3244 } else if( !strcasecmp( "-and", search_itr->key )) {
3245 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3247 jsonIteratorFree( search_itr );
3248 buffer_free( sql_buf );
3252 buffer_fadd( sql_buf, "( %s )", subpred );
3254 } else if( !strcasecmp("-not",search_itr->key) ) {
3255 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3257 jsonIteratorFree( search_itr );
3258 buffer_free( sql_buf );
3262 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3264 } else if( !strcasecmp( "-exists", search_itr->key )) {
3265 char* subpred = buildQuery( ctx, node, SUBSELECT );
3267 jsonIteratorFree( search_itr );
3268 buffer_free( sql_buf );
3272 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3274 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3275 char* subpred = buildQuery( ctx, node, SUBSELECT );
3277 jsonIteratorFree( search_itr );
3278 buffer_free( sql_buf );
3282 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3284 } else { // Invalid "minus" operator
3287 "%s: Invalid operator \"%s\" in WHERE clause",
3291 jsonIteratorFree( search_itr );
3292 buffer_free( sql_buf );
3298 const char* class = class_info->class_name;
3299 osrfHash* fields = class_info->fields;
3300 osrfHash* field = osrfHashGet( fields, search_itr->key );
3303 const char* table = class_info->source_def;
3306 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3309 table ? table : "?",
3312 jsonIteratorFree( search_itr );
3313 buffer_free( sql_buf );
3317 char* subpred = searchPredicate( class_info, field, node, ctx );
3319 buffer_free( sql_buf );
3320 jsonIteratorFree( search_itr );
3324 buffer_add( sql_buf, subpred );
3328 jsonIteratorFree( search_itr );
3331 // ERROR ... only hash and array allowed at this level
3332 char* predicate_string = jsonObjectToJSON( search_hash );
3335 "%s: Invalid predicate structure: %s",
3339 buffer_free( sql_buf );
3340 free( predicate_string );
3344 return buffer_release( sql_buf );
3347 /* Build a JSON_ARRAY of field names for a given table alias
3349 static jsonObject* defaultSelectList( const char* table_alias ) {
3354 ClassInfo* class_info = search_all_alias( table_alias );
3355 if( ! class_info ) {
3358 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3365 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3366 osrfHash* field_def = NULL;
3367 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3368 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3369 const char* field_name = osrfHashIteratorKey( field_itr );
3370 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3371 jsonObjectPush( array, jsonNewObject( field_name ) );
3374 osrfHashIteratorFree( field_itr );
3379 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3380 // The jsonObject must be a JSON_HASH with an single entry for "union",
3381 // "intersect", or "except". The data associated with this key must be an
3382 // array of hashes, each hash being a query.
3383 // Also allowed but currently ignored: entries for "order_by" and "alias".
3384 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3386 if( ! combo || combo->type != JSON_HASH )
3387 return NULL; // should be impossible; validated by caller
3389 const jsonObject* query_array = NULL; // array of subordinate queries
3390 const char* op = NULL; // name of operator, e.g. UNION
3391 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3392 int op_count = 0; // for detecting conflicting operators
3393 int excepting = 0; // boolean
3394 int all = 0; // boolean
3395 jsonObject* order_obj = NULL;
3397 // Identify the elements in the hash
3398 jsonIterator* query_itr = jsonNewIterator( combo );
3399 jsonObject* curr_obj = NULL;
3400 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3401 if( ! strcmp( "union", query_itr->key ) ) {
3404 query_array = curr_obj;
3405 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3408 query_array = curr_obj;
3409 } else if( ! strcmp( "except", query_itr->key ) ) {
3413 query_array = curr_obj;
3414 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3417 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3420 order_obj = curr_obj;
3421 } else if( ! strcmp( "alias", query_itr->key ) ) {
3422 if( curr_obj->type != JSON_STRING ) {
3423 jsonIteratorFree( query_itr );
3426 alias = jsonObjectGetString( curr_obj );
3427 } else if( ! strcmp( "all", query_itr->key ) ) {
3428 if( obj_is_true( curr_obj ) )
3432 osrfAppSessionStatus(
3434 OSRF_STATUS_INTERNALSERVERERROR,
3435 "osrfMethodException",
3437 "Malformed query; unexpected entry in query object"
3441 "%s: Unexpected entry for \"%s\" in%squery",
3446 jsonIteratorFree( query_itr );
3450 jsonIteratorFree( query_itr );
3452 // More sanity checks
3453 if( ! query_array ) {
3455 osrfAppSessionStatus(
3457 OSRF_STATUS_INTERNALSERVERERROR,
3458 "osrfMethodException",
3460 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3464 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3467 return NULL; // should be impossible...
3468 } else if( op_count > 1 ) {
3470 osrfAppSessionStatus(
3472 OSRF_STATUS_INTERNALSERVERERROR,
3473 "osrfMethodException",
3475 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3479 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3483 } if( query_array->type != JSON_ARRAY ) {
3485 osrfAppSessionStatus(
3487 OSRF_STATUS_INTERNALSERVERERROR,
3488 "osrfMethodException",
3490 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3494 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3497 json_type( query_array->type )
3500 } if( query_array->size < 2 ) {
3502 osrfAppSessionStatus(
3504 OSRF_STATUS_INTERNALSERVERERROR,
3505 "osrfMethodException",
3507 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3511 "%s:%srequires multiple queries as operands",
3516 } else if( excepting && query_array->size > 2 ) {
3518 osrfAppSessionStatus(
3520 OSRF_STATUS_INTERNALSERVERERROR,
3521 "osrfMethodException",
3523 "EXCEPT operator has too many queries as operands"
3527 "%s:EXCEPT operator has too many queries as operands",
3531 } else if( order_obj && ! alias ) {
3533 osrfAppSessionStatus(
3535 OSRF_STATUS_INTERNALSERVERERROR,
3536 "osrfMethodException",
3538 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3542 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3548 // So far so good. Now build the SQL.
3549 growing_buffer* sql = buffer_init( 256 );
3551 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3552 // Add a layer of parentheses
3553 if( flags & SUBCOMBO )
3554 OSRF_BUFFER_ADD( sql, "( " );
3556 // Traverse the query array. Each entry should be a hash.
3557 int first = 1; // boolean
3559 jsonObject* query = NULL;
3560 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3561 if( query->type != JSON_HASH ) {
3563 osrfAppSessionStatus(
3565 OSRF_STATUS_INTERNALSERVERERROR,
3566 "osrfMethodException",
3568 "Malformed query under UNION, INTERSECT or EXCEPT"
3572 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3575 json_type( query->type )
3584 OSRF_BUFFER_ADD( sql, op );
3586 OSRF_BUFFER_ADD( sql, "ALL " );
3589 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3593 "%s: Error building query under%s",
3601 OSRF_BUFFER_ADD( sql, query_str );
3604 if( flags & SUBCOMBO )
3605 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3607 if( !(flags & SUBSELECT) )
3608 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3610 return buffer_release( sql );
3613 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3614 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3615 // or "except" to indicate the type of query.
3616 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3620 osrfAppSessionStatus(
3622 OSRF_STATUS_INTERNALSERVERERROR,
3623 "osrfMethodException",
3625 "Malformed query; no query object"
3627 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3629 } else if( query->type != JSON_HASH ) {
3631 osrfAppSessionStatus(
3633 OSRF_STATUS_INTERNALSERVERERROR,
3634 "osrfMethodException",
3636 "Malformed query object"
3640 "%s: Query object is %s instead of JSON_HASH",
3642 json_type( query->type )
3647 // Determine what kind of query it purports to be, and dispatch accordingly.
3648 if( jsonObjectGetKeyConst( query, "union" ) ||
3649 jsonObjectGetKeyConst( query, "intersect" ) ||
3650 jsonObjectGetKeyConst( query, "except" )) {
3651 return doCombo( ctx, query, flags );
3653 // It is presumably a SELECT query
3655 // Push a node onto the stack for the current query. Every level of
3656 // subquery gets its own QueryFrame on the Stack.
3659 // Build an SQL SELECT statement
3662 jsonObjectGetKey( query, "select" ),
3663 jsonObjectGetKeyConst( query, "from" ),
3664 jsonObjectGetKeyConst( query, "where" ),
3665 jsonObjectGetKeyConst( query, "having" ),
3666 jsonObjectGetKeyConst( query, "order_by" ),
3667 jsonObjectGetKeyConst( query, "limit" ),
3668 jsonObjectGetKeyConst( query, "offset" ),
3677 /* method context */ osrfMethodContext* ctx,
3679 /* SELECT */ jsonObject* selhash,
3680 /* FROM */ const jsonObject* join_hash,
3681 /* WHERE */ const jsonObject* search_hash,
3682 /* HAVING */ const jsonObject* having_hash,
3683 /* ORDER BY */ const jsonObject* order_hash,
3684 /* LIMIT */ const jsonObject* limit,
3685 /* OFFSET */ const jsonObject* offset,
3686 /* flags */ int flags
3688 const char* locale = osrf_message_get_last_locale();
3690 // general tmp objects
3691 const jsonObject* tmp_const;
3692 jsonObject* selclass = NULL;
3693 jsonObject* snode = NULL;
3694 jsonObject* onode = NULL;
3696 char* string = NULL;
3697 int from_function = 0;
3702 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3704 // punt if there's no FROM clause
3705 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3708 "%s: FROM clause is missing or empty",
3712 osrfAppSessionStatus(
3714 OSRF_STATUS_INTERNALSERVERERROR,
3715 "osrfMethodException",
3717 "FROM clause is missing or empty in JSON query"
3722 // the core search class
3723 const char* core_class = NULL;
3725 // get the core class -- the only key of the top level FROM clause, or a string
3726 if( join_hash->type == JSON_HASH ) {
3727 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3728 snode = jsonIteratorNext( tmp_itr );
3730 // Populate the current QueryFrame with information
3731 // about the core class
3732 if( add_query_core( NULL, tmp_itr->key ) ) {
3734 osrfAppSessionStatus(
3736 OSRF_STATUS_INTERNALSERVERERROR,
3737 "osrfMethodException",
3739 "Unable to look up core class"
3743 core_class = curr_query->core.class_name;
3746 jsonObject* extra = jsonIteratorNext( tmp_itr );
3748 jsonIteratorFree( tmp_itr );
3751 // There shouldn't be more than one entry in join_hash
3755 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3759 osrfAppSessionStatus(
3761 OSRF_STATUS_INTERNALSERVERERROR,
3762 "osrfMethodException",
3764 "Malformed FROM clause in JSON query"
3766 return NULL; // Malformed join_hash; extra entry
3768 } else if( join_hash->type == JSON_ARRAY ) {
3769 // We're selecting from a function, not from a table
3771 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3774 } else if( join_hash->type == JSON_STRING ) {
3775 // Populate the current QueryFrame with information
3776 // about the core class
3777 core_class = jsonObjectGetString( join_hash );
3779 if( add_query_core( NULL, core_class ) ) {
3781 osrfAppSessionStatus(
3783 OSRF_STATUS_INTERNALSERVERERROR,
3784 "osrfMethodException",
3786 "Unable to look up core class"
3794 "%s: FROM clause is unexpected JSON type: %s",
3796 json_type( join_hash->type )
3799 osrfAppSessionStatus(
3801 OSRF_STATUS_INTERNALSERVERERROR,
3802 "osrfMethodException",
3804 "Ill-formed FROM clause in JSON query"
3809 // Build the join clause, if any, while filling out the list
3810 // of joined classes in the current QueryFrame.
3811 char* join_clause = NULL;
3812 if( join_hash && ! from_function ) {
3814 join_clause = searchJOIN( join_hash, &curr_query->core );
3815 if( ! join_clause ) {
3817 osrfAppSessionStatus(
3819 OSRF_STATUS_INTERNALSERVERERROR,
3820 "osrfMethodException",
3822 "Unable to construct JOIN clause(s)"
3828 // For in case we don't get a select list
3829 jsonObject* defaultselhash = NULL;
3831 // if there is no select list, build a default select list ...
3832 if( !selhash && !from_function ) {
3833 jsonObject* default_list = defaultSelectList( core_class );
3834 if( ! default_list ) {
3836 osrfAppSessionStatus(
3838 OSRF_STATUS_INTERNALSERVERERROR,
3839 "osrfMethodException",
3841 "Unable to build default SELECT clause in JSON query"
3843 free( join_clause );
3848 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3849 jsonObjectSetKey( selhash, core_class, default_list );
3852 // The SELECT clause can be encoded only by a hash
3853 if( !from_function && selhash->type != JSON_HASH ) {
3856 "%s: Expected JSON_HASH for SELECT clause; found %s",
3858 json_type( selhash->type )
3862 osrfAppSessionStatus(
3864 OSRF_STATUS_INTERNALSERVERERROR,
3865 "osrfMethodException",
3867 "Malformed SELECT clause in JSON query"
3869 free( join_clause );
3873 // If you see a null or wild card specifier for the core class, or an
3874 // empty array, replace it with a default SELECT list
3875 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3877 int default_needed = 0; // boolean
3878 if( JSON_STRING == tmp_const->type
3879 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3881 else if( JSON_NULL == tmp_const->type )
3884 if( default_needed ) {
3885 // Build a default SELECT list
3886 jsonObject* default_list = defaultSelectList( core_class );
3887 if( ! default_list ) {
3889 osrfAppSessionStatus(
3891 OSRF_STATUS_INTERNALSERVERERROR,
3892 "osrfMethodException",
3894 "Can't build default SELECT clause in JSON query"
3896 free( join_clause );
3901 jsonObjectSetKey( selhash, core_class, default_list );
3905 // temp buffers for the SELECT list and GROUP BY clause
3906 growing_buffer* select_buf = buffer_init( 128 );
3907 growing_buffer* group_buf = buffer_init( 128 );
3909 int aggregate_found = 0; // boolean
3911 // Build a select list
3912 if( from_function ) // From a function we select everything
3913 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3916 // Build the SELECT list as SQL
3920 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3921 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3923 const char* cname = selclass_itr->key;
3925 // Make sure the target relation is in the FROM clause.
3927 // At this point join_hash is a step down from the join_hash we
3928 // received as a parameter. If the original was a JSON_STRING,
3929 // then json_hash is now NULL. If the original was a JSON_HASH,
3930 // then json_hash is now the first (and only) entry in it,
3931 // denoting the core class. We've already excluded the
3932 // possibility that the original was a JSON_ARRAY, because in
3933 // that case from_function would be non-NULL, and we wouldn't
3936 // If the current table alias isn't in scope, bail out
3937 ClassInfo* class_info = search_alias( cname );
3938 if( ! class_info ) {
3941 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3946 osrfAppSessionStatus(
3948 OSRF_STATUS_INTERNALSERVERERROR,
3949 "osrfMethodException",
3951 "Selected class not in FROM clause in JSON query"
3953 jsonIteratorFree( selclass_itr );
3954 buffer_free( select_buf );
3955 buffer_free( group_buf );
3956 if( defaultselhash )
3957 jsonObjectFree( defaultselhash );
3958 free( join_clause );
3962 if( selclass->type != JSON_ARRAY ) {
3965 "%s: Malformed SELECT list for class \"%s\"; not an array",
3970 osrfAppSessionStatus(
3972 OSRF_STATUS_INTERNALSERVERERROR,
3973 "osrfMethodException",
3975 "Selected class not in FROM clause in JSON query"
3978 jsonIteratorFree( selclass_itr );
3979 buffer_free( select_buf );
3980 buffer_free( group_buf );
3981 if( defaultselhash )
3982 jsonObjectFree( defaultselhash );
3983 free( join_clause );
3987 // Look up some attributes of the current class
3988 osrfHash* idlClass = class_info->class_def;
3989 osrfHash* class_field_set = class_info->fields;
3990 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3991 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3993 if( 0 == selclass->size ) {
3996 "%s: No columns selected from \"%s\"",
4002 // stitch together the column list for the current table alias...
4003 unsigned long field_idx = 0;
4004 jsonObject* selfield = NULL;
4005 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4007 // If we need a separator comma, add one
4011 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4014 // if the field specification is a string, add it to the list
4015 if( selfield->type == JSON_STRING ) {
4017 // Look up the field in the IDL
4018 const char* col_name = jsonObjectGetString( selfield );
4019 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4021 // No such field in current class
4024 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4030 osrfAppSessionStatus(
4032 OSRF_STATUS_INTERNALSERVERERROR,
4033 "osrfMethodException",
4035 "Selected column not defined in JSON query"
4037 jsonIteratorFree( selclass_itr );
4038 buffer_free( select_buf );
4039 buffer_free( group_buf );
4040 if( defaultselhash )
4041 jsonObjectFree( defaultselhash );
4042 free( join_clause );
4044 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4045 // Virtual field not allowed
4048 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4054 osrfAppSessionStatus(
4056 OSRF_STATUS_INTERNALSERVERERROR,
4057 "osrfMethodException",
4059 "Selected column may not be virtual in JSON query"
4061 jsonIteratorFree( selclass_itr );
4062 buffer_free( select_buf );
4063 buffer_free( group_buf );
4064 if( defaultselhash )
4065 jsonObjectFree( defaultselhash );
4066 free( join_clause );
4072 if( flags & DISABLE_I18N )
4075 i18n = osrfHashGet( field_def, "i18n" );
4077 if( str_is_true( i18n ) ) {
4078 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4079 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4080 class_tname, cname, col_name, class_pkey,
4081 cname, class_pkey, locale, col_name );
4083 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4084 cname, col_name, col_name );
4087 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4088 cname, col_name, col_name );
4091 // ... but it could be an object, in which case we check for a Field Transform
4092 } else if( selfield->type == JSON_HASH ) {
4094 const char* col_name = jsonObjectGetString(
4095 jsonObjectGetKeyConst( selfield, "column" ) );
4097 // Get the field definition from the IDL
4098 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4100 // No such field in current class
4103 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4109 osrfAppSessionStatus(
4111 OSRF_STATUS_INTERNALSERVERERROR,
4112 "osrfMethodException",
4114 "Selected column is not defined in JSON query"
4116 jsonIteratorFree( selclass_itr );
4117 buffer_free( select_buf );
4118 buffer_free( group_buf );
4119 if( defaultselhash )
4120 jsonObjectFree( defaultselhash );
4121 free( join_clause );
4123 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4124 // No such field in current class
4127 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4133 osrfAppSessionStatus(
4135 OSRF_STATUS_INTERNALSERVERERROR,
4136 "osrfMethodException",
4138 "Selected column is virtual 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 );
4149 // Decide what to use as a column alias
4151 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4152 _alias = jsonObjectGetString( tmp_const );
4153 } else { // Use field name as the alias
4157 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4158 char* transform_str = searchFieldTransform(
4159 class_info->alias, field_def, selfield );
4160 if( transform_str ) {
4161 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4162 free( transform_str );
4165 osrfAppSessionStatus(
4167 OSRF_STATUS_INTERNALSERVERERROR,
4168 "osrfMethodException",
4170 "Unable to generate transform function in JSON query"
4172 jsonIteratorFree( selclass_itr );
4173 buffer_free( select_buf );
4174 buffer_free( group_buf );
4175 if( defaultselhash )
4176 jsonObjectFree( defaultselhash );
4177 free( join_clause );
4184 if( flags & DISABLE_I18N )
4187 i18n = osrfHashGet( field_def, "i18n" );
4189 if( str_is_true( i18n ) ) {
4190 buffer_fadd( select_buf,
4191 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4192 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4193 class_tname, cname, col_name, class_pkey, cname,
4194 class_pkey, locale, _alias );
4196 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4197 cname, col_name, _alias );
4200 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4201 cname, col_name, _alias );
4208 "%s: Selected item is unexpected JSON type: %s",
4210 json_type( selfield->type )
4213 osrfAppSessionStatus(
4215 OSRF_STATUS_INTERNALSERVERERROR,
4216 "osrfMethodException",
4218 "Ill-formed SELECT item in JSON query"
4220 jsonIteratorFree( selclass_itr );
4221 buffer_free( select_buf );
4222 buffer_free( group_buf );
4223 if( defaultselhash )
4224 jsonObjectFree( defaultselhash );
4225 free( join_clause );
4229 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4230 if( obj_is_true( agg_obj ) )
4231 aggregate_found = 1;
4233 // Append a comma (except for the first one)
4234 // and add the column to a GROUP BY clause
4238 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4240 buffer_fadd( group_buf, " %d", sel_pos );
4244 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4246 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4247 if ( ! obj_is_true( aggregate_obj ) ) {
4251 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4254 buffer_fadd(group_buf, " %d", sel_pos);
4257 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4261 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4264 _column = searchFieldTransform(class_info->alias, field, selfield);
4265 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4266 OSRF_BUFFER_ADD(group_buf, _column);
4267 _column = searchFieldTransform(class_info->alias, field, selfield);
4274 } // end while -- iterating across SELECT columns
4276 } // end while -- iterating across classes
4278 jsonIteratorFree( selclass_itr );
4282 char* col_list = buffer_release( select_buf );
4284 // Make sure the SELECT list isn't empty. This can happen, for example,
4285 // if we try to build a default SELECT clause from a non-core table.
4288 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4290 osrfAppSessionStatus(
4292 OSRF_STATUS_INTERNALSERVERERROR,
4293 "osrfMethodException",
4295 "SELECT list is empty"
4298 buffer_free( group_buf );
4299 if( defaultselhash )
4300 jsonObjectFree( defaultselhash );
4301 free( join_clause );
4307 table = searchValueTransform( join_hash );
4309 table = strdup( curr_query->core.source_def );
4313 osrfAppSessionStatus(
4315 OSRF_STATUS_INTERNALSERVERERROR,
4316 "osrfMethodException",
4318 "Unable to identify table for core class"
4321 buffer_free( group_buf );
4322 if( defaultselhash )
4323 jsonObjectFree( defaultselhash );
4324 free( join_clause );
4328 // Put it all together
4329 growing_buffer* sql_buf = buffer_init( 128 );
4330 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4334 // Append the join clause, if any
4336 buffer_add(sql_buf, join_clause );
4337 free( join_clause );
4340 char* order_by_list = NULL;
4341 char* having_buf = NULL;
4343 if( !from_function ) {
4345 // Build a WHERE clause, if there is one
4347 buffer_add( sql_buf, " WHERE " );
4349 // and it's on the WHERE clause
4350 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4353 osrfAppSessionStatus(
4355 OSRF_STATUS_INTERNALSERVERERROR,
4356 "osrfMethodException",
4358 "Severe query error in WHERE predicate -- see error log for more details"
4361 buffer_free( group_buf );
4362 buffer_free( sql_buf );
4363 if( defaultselhash )
4364 jsonObjectFree( defaultselhash );
4368 buffer_add( sql_buf, pred );
4372 // Build a HAVING clause, if there is one
4375 // and it's on the the WHERE clause
4376 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4378 if( ! having_buf ) {
4380 osrfAppSessionStatus(
4382 OSRF_STATUS_INTERNALSERVERERROR,
4383 "osrfMethodException",
4385 "Severe query error in HAVING predicate -- see error log for more details"
4388 buffer_free( group_buf );
4389 buffer_free( sql_buf );
4390 if( defaultselhash )
4391 jsonObjectFree( defaultselhash );
4396 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4398 // Build an ORDER BY clause, if there is one
4399 if( NULL == order_hash )
4400 ; // No ORDER BY? do nothing
4401 else if( JSON_ARRAY == order_hash->type ) {
4402 // Array of field specifications, each specification being a
4403 // hash to define the class, field, and other details
4405 jsonObject* order_spec;
4406 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4408 if( JSON_HASH != order_spec->type ) {
4409 osrfLogError( OSRF_LOG_MARK,
4410 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4411 modulename, json_type( order_spec->type ) );
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 const char* class_alias =
4430 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4432 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4435 OSRF_BUFFER_ADD( order_buf, ", " );
4437 order_buf = buffer_init( 128 );
4439 if( !field || !class_alias ) {
4440 osrfLogError( OSRF_LOG_MARK,
4441 "%s: Missing class or field name in field specification "
4442 "of ORDER BY clause",
4445 osrfAppSessionStatus(
4447 OSRF_STATUS_INTERNALSERVERERROR,
4448 "osrfMethodException",
4450 "Malformed ORDER BY clause -- see error log for more details"
4452 buffer_free( order_buf );
4454 buffer_free( group_buf );
4455 buffer_free( sql_buf );
4456 if( defaultselhash )
4457 jsonObjectFree( defaultselhash );
4461 ClassInfo* order_class_info = search_alias( class_alias );
4462 if( ! order_class_info ) {
4463 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4464 "not in FROM clause", modulename, class_alias );
4466 osrfAppSessionStatus(
4468 OSRF_STATUS_INTERNALSERVERERROR,
4469 "osrfMethodException",
4471 "Invalid class referenced in ORDER BY clause -- "
4472 "see error log for more details"
4475 buffer_free( group_buf );
4476 buffer_free( sql_buf );
4477 if( defaultselhash )
4478 jsonObjectFree( defaultselhash );
4482 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4484 osrfLogError( OSRF_LOG_MARK,
4485 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4486 modulename, class_alias, field );
4488 osrfAppSessionStatus(
4490 OSRF_STATUS_INTERNALSERVERERROR,
4491 "osrfMethodException",
4493 "Invalid field referenced in ORDER BY clause -- "
4494 "see error log for more details"
4497 buffer_free( group_buf );
4498 buffer_free( sql_buf );
4499 if( defaultselhash )
4500 jsonObjectFree( defaultselhash );
4502 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4503 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4504 modulename, field );
4506 osrfAppSessionStatus(
4508 OSRF_STATUS_INTERNALSERVERERROR,
4509 "osrfMethodException",
4511 "Virtual field in ORDER BY clause -- see error log for more details"
4513 buffer_free( order_buf );
4515 buffer_free( group_buf );
4516 buffer_free( sql_buf );
4517 if( defaultselhash )
4518 jsonObjectFree( defaultselhash );
4522 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4523 char* transform_str = searchFieldTransform(
4524 class_alias, field_def, order_spec );
4525 if( ! transform_str ) {
4527 osrfAppSessionStatus(
4529 OSRF_STATUS_INTERNALSERVERERROR,
4530 "osrfMethodException",
4532 "Severe query error in ORDER BY clause -- "
4533 "see error log for more details"
4535 buffer_free( order_buf );
4537 buffer_free( group_buf );
4538 buffer_free( sql_buf );
4539 if( defaultselhash )
4540 jsonObjectFree( defaultselhash );
4544 OSRF_BUFFER_ADD( order_buf, transform_str );
4545 free( transform_str );
4548 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4550 const char* direction =
4551 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4553 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4554 OSRF_BUFFER_ADD( order_buf, " DESC" );
4556 OSRF_BUFFER_ADD( order_buf, " ASC" );
4559 } else if( JSON_HASH == order_hash->type ) {
4560 // This hash is keyed on class alias. Each class has either
4561 // an array of field names or a hash keyed on field name.
4562 jsonIterator* class_itr = jsonNewIterator( order_hash );
4563 while( (snode = jsonIteratorNext( class_itr )) ) {
4565 ClassInfo* order_class_info = search_alias( class_itr->key );
4566 if( ! order_class_info ) {
4567 osrfLogError( OSRF_LOG_MARK,
4568 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4569 modulename, class_itr->key );
4571 osrfAppSessionStatus(
4573 OSRF_STATUS_INTERNALSERVERERROR,
4574 "osrfMethodException",
4576 "Invalid class referenced in ORDER BY clause -- "
4577 "see error log for more details"
4579 jsonIteratorFree( class_itr );
4580 buffer_free( order_buf );
4582 buffer_free( group_buf );
4583 buffer_free( sql_buf );
4584 if( defaultselhash )
4585 jsonObjectFree( defaultselhash );
4589 osrfHash* field_list_def = order_class_info->fields;
4591 if( snode->type == JSON_HASH ) {
4593 // Hash is keyed on field names from the current class. For each field
4594 // there is another layer of hash to define the sorting details, if any,
4595 // or a string to indicate direction of sorting.
4596 jsonIterator* order_itr = jsonNewIterator( snode );
4597 while( (onode = jsonIteratorNext( order_itr )) ) {
4599 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4601 osrfLogError( OSRF_LOG_MARK,
4602 "%s: Invalid field \"%s\" in ORDER BY clause",
4603 modulename, order_itr->key );
4605 osrfAppSessionStatus(
4607 OSRF_STATUS_INTERNALSERVERERROR,
4608 "osrfMethodException",
4610 "Invalid field in ORDER BY clause -- "
4611 "see error log for more details"
4613 jsonIteratorFree( order_itr );
4614 jsonIteratorFree( class_itr );
4615 buffer_free( order_buf );
4617 buffer_free( group_buf );
4618 buffer_free( sql_buf );
4619 if( defaultselhash )
4620 jsonObjectFree( defaultselhash );
4622 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4623 osrfLogError( OSRF_LOG_MARK,
4624 "%s: Virtual field \"%s\" in ORDER BY clause",
4625 modulename, order_itr->key );
4627 osrfAppSessionStatus(
4629 OSRF_STATUS_INTERNALSERVERERROR,
4630 "osrfMethodException",
4632 "Virtual field in ORDER BY clause -- "
4633 "see error log for more details"
4635 jsonIteratorFree( order_itr );
4636 jsonIteratorFree( class_itr );
4637 buffer_free( order_buf );
4639 buffer_free( group_buf );
4640 buffer_free( sql_buf );
4641 if( defaultselhash )
4642 jsonObjectFree( defaultselhash );
4646 const char* direction = NULL;
4647 if( onode->type == JSON_HASH ) {
4648 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4649 string = searchFieldTransform(
4651 osrfHashGet( field_list_def, order_itr->key ),
4655 if( ctx ) osrfAppSessionStatus(
4657 OSRF_STATUS_INTERNALSERVERERROR,
4658 "osrfMethodException",
4660 "Severe query error in ORDER BY clause -- "
4661 "see error log for more details"
4663 jsonIteratorFree( order_itr );
4664 jsonIteratorFree( class_itr );
4666 buffer_free( group_buf );
4667 buffer_free( order_buf);
4668 buffer_free( sql_buf );
4669 if( defaultselhash )
4670 jsonObjectFree( defaultselhash );
4674 growing_buffer* field_buf = buffer_init( 16 );
4675 buffer_fadd( field_buf, "\"%s\".%s",
4676 class_itr->key, order_itr->key );
4677 string = buffer_release( field_buf );
4680 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4681 const char* dir = jsonObjectGetString( tmp_const );
4682 if(!strncasecmp( dir, "d", 1 )) {
4683 direction = " DESC";
4689 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4690 osrfLogError( OSRF_LOG_MARK,
4691 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4692 modulename, json_type( onode->type ) );
4694 osrfAppSessionStatus(
4696 OSRF_STATUS_INTERNALSERVERERROR,
4697 "osrfMethodException",
4699 "Malformed ORDER BY clause -- see error log for more details"
4701 jsonIteratorFree( order_itr );
4702 jsonIteratorFree( class_itr );
4704 buffer_free( group_buf );
4705 buffer_free( order_buf );
4706 buffer_free( sql_buf );
4707 if( defaultselhash )
4708 jsonObjectFree( defaultselhash );
4712 string = strdup( order_itr->key );
4713 const char* dir = jsonObjectGetString( onode );
4714 if( !strncasecmp( dir, "d", 1 )) {
4715 direction = " DESC";
4722 OSRF_BUFFER_ADD( order_buf, ", " );
4724 order_buf = buffer_init( 128 );
4726 OSRF_BUFFER_ADD( order_buf, string );
4730 OSRF_BUFFER_ADD( order_buf, direction );
4734 jsonIteratorFree( order_itr );
4736 } else if( snode->type == JSON_ARRAY ) {
4738 // Array is a list of fields from the current class
4739 unsigned long order_idx = 0;
4740 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4742 const char* _f = jsonObjectGetString( onode );
4744 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4746 osrfLogError( OSRF_LOG_MARK,
4747 "%s: Invalid field \"%s\" in ORDER BY clause",
4750 osrfAppSessionStatus(
4752 OSRF_STATUS_INTERNALSERVERERROR,
4753 "osrfMethodException",
4755 "Invalid field in ORDER BY clause -- "
4756 "see error log for more details"
4758 jsonIteratorFree( class_itr );
4759 buffer_free( order_buf );
4761 buffer_free( group_buf );
4762 buffer_free( sql_buf );
4763 if( defaultselhash )
4764 jsonObjectFree( defaultselhash );
4766 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4767 osrfLogError( OSRF_LOG_MARK,
4768 "%s: Virtual field \"%s\" in ORDER BY clause",
4771 osrfAppSessionStatus(
4773 OSRF_STATUS_INTERNALSERVERERROR,
4774 "osrfMethodException",
4776 "Virtual field in ORDER BY clause -- "
4777 "see error log for more details"
4779 jsonIteratorFree( class_itr );
4780 buffer_free( order_buf );
4782 buffer_free( group_buf );
4783 buffer_free( sql_buf );
4784 if( defaultselhash )
4785 jsonObjectFree( defaultselhash );
4790 OSRF_BUFFER_ADD( order_buf, ", " );
4792 order_buf = buffer_init( 128 );
4794 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4798 // IT'S THE OOOOOOOOOOOLD STYLE!
4800 osrfLogError( OSRF_LOG_MARK,
4801 "%s: Possible SQL injection attempt; direct order by is not allowed",
4804 osrfAppSessionStatus(
4806 OSRF_STATUS_INTERNALSERVERERROR,
4807 "osrfMethodException",
4809 "Severe query error -- see error log for more details"
4814 buffer_free( group_buf );
4815 buffer_free( order_buf );
4816 buffer_free( sql_buf );
4817 if( defaultselhash )
4818 jsonObjectFree( defaultselhash );
4819 jsonIteratorFree( class_itr );
4823 jsonIteratorFree( class_itr );
4825 osrfLogError( OSRF_LOG_MARK,
4826 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4827 modulename, json_type( order_hash->type ) );
4829 osrfAppSessionStatus(
4831 OSRF_STATUS_INTERNALSERVERERROR,
4832 "osrfMethodException",
4834 "Malformed ORDER BY clause -- see error log for more details"
4836 buffer_free( order_buf );
4838 buffer_free( group_buf );
4839 buffer_free( sql_buf );
4840 if( defaultselhash )
4841 jsonObjectFree( defaultselhash );
4846 order_by_list = buffer_release( order_buf );
4850 string = buffer_release( group_buf );
4852 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4853 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4854 OSRF_BUFFER_ADD( sql_buf, string );
4859 if( having_buf && *having_buf ) {
4860 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4861 OSRF_BUFFER_ADD( sql_buf, having_buf );
4865 if( order_by_list ) {
4867 if( *order_by_list ) {
4868 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4869 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4872 free( order_by_list );
4876 const char* str = jsonObjectGetString( limit );
4877 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4881 const char* str = jsonObjectGetString( offset );
4882 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4885 if( !(flags & SUBSELECT) )
4886 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4888 if( defaultselhash )
4889 jsonObjectFree( defaultselhash );
4891 return buffer_release( sql_buf );
4893 } // end of SELECT()
4896 @brief Build a SELECT statement.
4897 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
4898 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
4899 @param meta Pointer to the class metadata for the core class.
4900 @param ctx Pointer to the method context.
4901 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
4903 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
4904 "order_by", "limit", and "offset".
4906 The SELECT statements built here are distinct from those built for the json_query method.
4908 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
4909 osrfHash* meta, osrfMethodContext* ctx ) {
4911 const char* locale = osrf_message_get_last_locale();
4913 osrfHash* fields = osrfHashGet( meta, "fields" );
4914 const char* core_class = osrfHashGet( meta, "classname" );
4916 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
4918 jsonObject* selhash = NULL;
4919 jsonObject* defaultselhash = NULL;
4921 growing_buffer* sql_buf = buffer_init( 128 );
4922 growing_buffer* select_buf = buffer_init( 128 );
4924 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
4925 defaultselhash = jsonNewObjectType( JSON_HASH );
4926 selhash = defaultselhash;
4929 // If there's no SELECT list for the core class, build one
4930 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4931 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4933 // Add every non-virtual field to the field list
4934 osrfHash* field_def = NULL;
4935 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4936 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4937 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4938 const char* field = osrfHashIteratorKey( field_itr );
4939 jsonObjectPush( field_list, jsonNewObject( field ) );
4942 osrfHashIteratorFree( field_itr );
4943 jsonObjectSetKey( selhash, core_class, field_list );
4946 // Build a list of columns for the SELECT clause
4948 const jsonObject* snode = NULL;
4949 jsonIterator* class_itr = jsonNewIterator( selhash );
4950 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
4952 // If the class isn't in the IDL, ignore it
4953 const char* cname = class_itr->key;
4954 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4958 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
4959 if( strcmp( core_class, class_itr->key )) {
4963 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4964 if( !found->size ) {
4965 jsonObjectFree( found );
4969 jsonObjectFree( found );
4972 const jsonObject* node = NULL;
4973 jsonIterator* select_itr = jsonNewIterator( snode );
4974 while( (node = jsonIteratorNext( select_itr )) ) {
4975 const char* item_str = jsonObjectGetString( node );
4976 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4977 char* fname = osrfHashGet( field, "name" );
4985 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4990 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
4991 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4994 i18n = osrfHashGet( field, "i18n" );
4996 if( str_is_true( i18n ) ) {
4997 char* pkey = osrfHashGet( idlClass, "primarykey" );
4998 char* tname = osrfHashGet( idlClass, "tablename" );
5000 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5001 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5002 tname, cname, fname, pkey, cname, pkey, locale, fname );
5004 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5007 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5011 jsonIteratorFree( select_itr );
5014 jsonIteratorFree( class_itr );
5016 char* col_list = buffer_release( select_buf );
5017 char* table = oilsGetRelation( meta );
5019 table = strdup( "(null)" );
5021 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5025 // Clear the query stack (as a fail-safe precaution against possible
5026 // leftover garbage); then push the first query frame onto the stack.
5027 clear_query_stack();
5029 if( add_query_core( NULL, core_class ) ) {
5031 osrfAppSessionStatus(
5033 OSRF_STATUS_INTERNALSERVERERROR,
5034 "osrfMethodException",
5036 "Unable to build query frame for core class"
5038 buffer_free( sql_buf );
5039 if( defaultselhash )
5040 jsonObjectFree( defaultselhash );
5044 // Add the JOIN clauses, if any
5046 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5047 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5048 OSRF_BUFFER_ADD( sql_buf, join_clause );
5049 free( join_clause );
5052 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5053 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5055 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5057 // Add the conditions in the WHERE clause
5058 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5060 osrfAppSessionStatus(
5062 OSRF_STATUS_INTERNALSERVERERROR,
5063 "osrfMethodException",
5065 "Severe query error -- see error log for more details"
5067 buffer_free( sql_buf );
5068 if( defaultselhash )
5069 jsonObjectFree( defaultselhash );
5070 clear_query_stack();
5073 buffer_add( sql_buf, pred );
5077 // Add the ORDER BY and/or LIMIT clauses, if present
5078 if( rest_of_query ) {
5079 const jsonObject* order_by = NULL;
5080 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5082 growing_buffer* order_buf = buffer_init( 128 );
5084 if( JSON_HASH != order_by->type )
5085 osrfLogWarning( OSRF_LOG_MARK,
5086 "\"order_by\" object in a query is not a JSON_HASH; no ORDER BY generated" );
5088 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5089 // and build a list of ORDER BY expressions.
5091 jsonIterator* class_itr = jsonNewIterator( order_by );
5092 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5094 if( !jsonObjectGetKeyConst( selhash, class_itr->key ))
5095 continue; // class not referenced by SELECT clause? Ignore it.
5097 if( snode->type == JSON_HASH ) {
5099 // If the data for the current class is a JSON_HASH, then it is
5100 // keyed on field name.
5102 const jsonObject* onode = NULL;
5103 jsonIterator* order_itr = jsonNewIterator( snode );
5104 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5106 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5107 class_itr->key, order_itr->key );
5109 continue; // Field not defined in IDL? Ignore it.
5111 char* field_str = NULL;
5112 char* direction = NULL;
5113 if( onode->type == JSON_HASH ) {
5114 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5115 field_str = searchFieldTransform( class_itr->key, field_def, onode);
5117 osrfAppSessionStatus(
5119 OSRF_STATUS_INTERNALSERVERERROR,
5120 "osrfMethodException",
5122 "Severe query error in ORDER BY clause -- "
5123 "see error log for more details"
5125 jsonIteratorFree( order_itr );
5126 jsonIteratorFree( class_itr );
5127 buffer_free( order_buf );
5128 buffer_free( sql_buf );
5129 if( defaultselhash )
5130 jsonObjectFree( defaultselhash );
5131 clear_query_stack();
5135 growing_buffer* field_buf = buffer_init( 16 );
5136 buffer_fadd( field_buf, "\"%s\".%s",
5137 class_itr->key, order_itr->key );
5138 field_str = buffer_release( field_buf );
5141 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5142 const char* dir = jsonObjectGetString( order_by );
5143 if(!strncasecmp( dir, "d", 1 )) {
5144 direction = " DESC";
5148 field_str = strdup( order_itr->key );
5149 const char* dir = jsonObjectGetString( onode );
5150 if( !strncasecmp( dir, "d", 1 )) {
5151 direction = " DESC";
5160 buffer_add( order_buf, ", " );
5163 buffer_add( order_buf, field_str );
5167 buffer_add( order_buf, direction );
5171 jsonIteratorFree( order_itr );
5174 // The data for the current class is not a JSON_HASH, so we expect
5175 // it to be a JSON_STRING with a single field name.
5176 const char* str = jsonObjectGetString( snode );
5177 buffer_add( order_buf, str );
5181 } // end while; looping over order_by expressions
5183 jsonIteratorFree( class_itr );
5185 char* order_by_list = buffer_release( order_buf );
5187 if( *order_by_list ) {
5188 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5189 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5192 free( order_by_list );
5195 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5197 const char* str = jsonObjectGetString( limit );
5205 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5207 const char* str = jsonObjectGetString( offset );
5216 if( defaultselhash )
5217 jsonObjectFree( defaultselhash );
5218 clear_query_stack();
5220 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5221 return buffer_release( sql_buf );
5224 int doJSONSearch ( osrfMethodContext* ctx ) {
5225 if(osrfMethodVerifyContext( ctx )) {
5226 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5230 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5234 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5238 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5239 flags |= SELECT_DISTINCT;
5241 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5242 flags |= DISABLE_I18N;
5244 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5245 clear_query_stack(); // a possibly needless precaution
5246 char* sql = buildQuery( ctx, hash, flags );
5247 clear_query_stack();
5254 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5257 dbhandle = writehandle;
5259 dbi_result result = dbi_conn_query( dbhandle, sql );
5262 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5264 if( dbi_result_first_row( result )) {
5265 /* JSONify the result */
5266 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5269 jsonObject* return_val = oilsMakeJSONFromResult( result );
5270 osrfAppRespond( ctx, return_val );
5271 jsonObjectFree( return_val );
5272 } while( dbi_result_next_row( result ));
5275 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5278 osrfAppRespondComplete( ctx, NULL );
5280 /* clean up the query */
5281 dbi_result_free( result );
5286 int errnum = dbi_conn_error( dbhandle, &msg );
5287 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5288 modulename, sql, errnum, msg ? msg : "(No description available)" );
5289 osrfAppSessionStatus(
5291 OSRF_STATUS_INTERNALSERVERERROR,
5292 "osrfMethodException",
5294 "Severe query error -- see error log for more details"
5296 if( !oilsIsDBConnected( dbhandle ))
5297 osrfAppSessionPanic( ctx->session );
5304 // The last parameter, err, is used to report an error condition by updating an int owned by
5305 // the calling code.
5307 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5308 // It is the responsibility of the calling code to initialize *err before the
5309 // call, so that it will be able to make sense of the result.
5311 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5312 // redundant anyway.
5313 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5314 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5317 dbhandle = writehandle;
5319 char* core_class = osrfHashGet( class_meta, "classname" );
5320 char* pkey = osrfHashGet( class_meta, "primarykey" );
5322 const jsonObject* _tmp;
5324 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5326 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5331 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5333 dbi_result result = dbi_conn_query( dbhandle, sql );
5334 if( NULL == result ) {
5336 int errnum = dbi_conn_error( dbhandle, &msg );
5337 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5338 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5339 msg ? msg : "(No description available)" );
5340 if( !oilsIsDBConnected( dbhandle ))
5341 osrfAppSessionPanic( ctx->session );
5342 osrfAppSessionStatus(
5344 OSRF_STATUS_INTERNALSERVERERROR,
5345 "osrfMethodException",
5347 "Severe query error -- see error log for more details"
5354 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5357 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5358 jsonObject* row_obj = NULL;
5360 if( dbi_result_first_row( result )) {
5362 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5363 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5364 // eliminate the duplicates.
5365 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5366 osrfHash* dedup = osrfNewHash();
5368 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5369 char* pkey_val = oilsFMGetString( row_obj, pkey );
5370 if( osrfHashGet( dedup, pkey_val ) ) {
5371 jsonObjectFree( row_obj );
5374 osrfHashSet( dedup, pkey_val, pkey_val );
5375 jsonObjectPush( res_list, row_obj );
5377 } while( dbi_result_next_row( result ));
5378 osrfHashFree( dedup );
5381 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5385 /* clean up the query */
5386 dbi_result_free( result );
5389 // If we're asked to flesh, and there's anything to flesh, then flesh it
5390 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5391 // something that he has no permission to look at).
5392 if( res_list->size && query_hash && ! enforce_pcrud ) {
5393 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5395 // Get the flesh depth
5396 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5397 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5398 flesh_depth = max_flesh_depth;
5400 // We need a non-zero flesh depth, and a list of fields to flesh
5401 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5402 if( temp_blob && flesh_depth > 0 ) {
5404 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5405 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5407 osrfStringArray* link_fields = NULL;
5408 osrfHash* links = osrfHashGet( class_meta, "links" );
5410 // Make an osrfStringArray of the names of fields to be fleshed
5411 if( flesh_fields ) {
5412 if( flesh_fields->size == 1 ) {
5413 const char* _t = jsonObjectGetString(
5414 jsonObjectGetIndex( flesh_fields, 0 ) );
5415 if( !strcmp( _t, "*" ))
5416 link_fields = osrfHashKeys( links );
5419 if( !link_fields ) {
5421 link_fields = osrfNewStringArray( 1 );
5422 jsonIterator* _i = jsonNewIterator( flesh_fields );
5423 while ((_f = jsonIteratorNext( _i ))) {
5424 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5426 jsonIteratorFree( _i );
5430 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5432 // Iterate over the JSON_ARRAY of rows
5434 unsigned long res_idx = 0;
5435 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5438 const char* link_field;
5440 // Iterate over the list of fleshable fields
5441 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5443 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5445 osrfHash* kid_link = osrfHashGet( links, link_field );
5447 continue; // Not a link field; skip it
5449 osrfHash* field = osrfHashGet( fields, link_field );
5451 continue; // Not a field at all; skip it (IDL is ill-formed)
5453 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5454 osrfHashGet( kid_link, "class" ));
5456 continue; // The class it links to doesn't exist; skip it
5458 const char* reltype = osrfHashGet( kid_link, "reltype" );
5460 continue; // No reltype; skip it (IDL is ill-formed)
5462 osrfHash* value_field = field;
5464 if( !strcmp( reltype, "has_many" )
5465 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5466 value_field = osrfHashGet(
5467 fields, osrfHashGet( class_meta, "primarykey" ) );
5470 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5472 if( link_map->size > 0 ) {
5473 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5476 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5481 osrfHashGet( kid_link, "class" ),
5488 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5489 osrfHashGet( kid_link, "field" ),
5490 osrfHashGet( kid_link, "class" ),
5491 osrfHashGet( kid_link, "key" ),
5492 osrfHashGet( kid_link, "reltype" )
5495 const char* search_key = jsonObjectGetString(
5496 jsonObjectGetIndex( cur,
5497 atoi( osrfHashGet( value_field, "array_position" ) )
5502 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5506 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5508 // construct WHERE clause
5509 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5512 osrfHashGet( kid_link, "key" ),
5513 jsonNewObject( search_key )
5516 // construct the rest of the query, mostly
5517 // by copying pieces of the previous level of query
5518 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5519 jsonObjectSetKey( rest_of_query, "flesh",
5520 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5524 jsonObjectSetKey( rest_of_query, "flesh_fields",
5525 jsonObjectClone( flesh_blob ));
5527 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5528 jsonObjectSetKey( rest_of_query, "order_by",
5529 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5533 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5534 jsonObjectSetKey( rest_of_query, "select",
5535 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5539 // do the query, recursively, to expand the fleshable field
5540 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5541 where_clause, rest_of_query, err );
5543 jsonObjectFree( where_clause );
5544 jsonObjectFree( rest_of_query );
5547 osrfStringArrayFree( link_fields );
5548 jsonObjectFree( res_list );
5549 jsonObjectFree( flesh_blob );
5553 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5554 osrfHashGet( kid_link, "class" ), kids->size );
5556 // Traverse the result set
5557 jsonObject* X = NULL;
5558 if( link_map->size > 0 && kids->size > 0 ) {
5560 kids = jsonNewObjectType( JSON_ARRAY );
5562 jsonObject* _k_node;
5563 unsigned long res_idx = 0;
5564 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5570 (unsigned long) atoi(
5576 osrfHashGet( kid_link, "class" )
5580 osrfStringArrayGetString( link_map, 0 )
5588 } // end while loop traversing X
5591 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5592 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5593 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5594 osrfHashGet( kid_link, "field" ));
5597 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5598 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5602 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5604 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5605 osrfHashGet( kid_link, "field" ) );
5608 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5609 jsonObjectClone( kids )
5614 jsonObjectFree( kids );
5618 jsonObjectFree( kids );
5620 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5621 osrfHashGet( kid_link, "field" ) );
5622 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5624 } // end while loop traversing list of fleshable fields
5625 } // end while loop traversing res_list
5626 jsonObjectFree( flesh_blob );
5627 osrfStringArrayFree( link_fields );
5636 int doUpdate( osrfMethodContext* ctx ) {
5637 if( osrfMethodVerifyContext( ctx )) {
5638 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5643 timeout_needs_resetting = 1;
5645 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5647 jsonObject* target = NULL;
5649 target = jsonObjectGetIndex( ctx->params, 1 );
5651 target = jsonObjectGetIndex( ctx->params, 0 );
5653 if(!verifyObjectClass( ctx, target )) {
5654 osrfAppRespondComplete( ctx, NULL );
5658 if( getXactId( ctx ) == NULL ) {
5659 osrfAppSessionStatus(
5661 OSRF_STATUS_BADREQUEST,
5662 "osrfMethodException",
5664 "No active transaction -- required for UPDATE"
5666 osrfAppRespondComplete( ctx, NULL );
5670 // The following test is harmless but redundant. If a class is
5671 // readonly, we don't register an update method for it.
5672 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5673 osrfAppSessionStatus(
5675 OSRF_STATUS_BADREQUEST,
5676 "osrfMethodException",
5678 "Cannot UPDATE readonly class"
5680 osrfAppRespondComplete( ctx, NULL );
5684 const char* trans_id = getXactId( ctx );
5686 // Set the last_xact_id
5687 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5689 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5690 trans_id, target->classname, index );
5691 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5694 char* pkey = osrfHashGet( meta, "primarykey" );
5695 osrfHash* fields = osrfHashGet( meta, "fields" );
5697 char* id = oilsFMGetString( target, pkey );
5701 "%s updating %s object with %s = %s",
5703 osrfHashGet( meta, "fieldmapper" ),
5708 dbhandle = writehandle;
5709 growing_buffer* sql = buffer_init( 128 );
5710 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5713 osrfHash* field_def = NULL;
5714 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5715 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5717 // Skip virtual fields, and the primary key
5718 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5721 const char* field_name = osrfHashIteratorKey( field_itr );
5722 if( ! strcmp( field_name, pkey ) )
5725 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5727 int value_is_numeric = 0; // boolean
5729 if( field_object && field_object->classname ) {
5730 value = oilsFMGetString(
5732 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5734 } else if( field_object && JSON_BOOL == field_object->type ) {
5735 if( jsonBoolIsTrue( field_object ) )
5736 value = strdup( "t" );
5738 value = strdup( "f" );
5740 value = jsonObjectToSimpleString( field_object );
5741 if( field_object && JSON_NUMBER == field_object->type )
5742 value_is_numeric = 1;
5745 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5746 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5748 if( !field_object || field_object->type == JSON_NULL ) {
5749 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5750 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5754 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5755 buffer_fadd( sql, " %s = NULL", field_name );
5758 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5762 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5764 const char* numtype = get_datatype( field_def );
5765 if( !strncmp( numtype, "INT", 3 ) ) {
5766 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5767 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5768 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5770 // Must really be intended as a string, so quote it
5771 if( dbi_conn_quote_string( dbhandle, &value )) {
5772 buffer_fadd( sql, " %s = %s", field_name, value );
5774 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5775 modulename, value );
5776 osrfAppSessionStatus(
5778 OSRF_STATUS_INTERNALSERVERERROR,
5779 "osrfMethodException",
5781 "Error quoting string -- please see the error log for more details"
5785 osrfHashIteratorFree( field_itr );
5787 osrfAppRespondComplete( ctx, NULL );
5792 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5795 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5799 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5800 buffer_fadd( sql, " %s = %s", field_name, value );
5802 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5803 osrfAppSessionStatus(
5805 OSRF_STATUS_INTERNALSERVERERROR,
5806 "osrfMethodException",
5808 "Error quoting string -- please see the error log for more details"
5812 osrfHashIteratorFree( field_itr );
5814 osrfAppRespondComplete( ctx, NULL );
5823 osrfHashIteratorFree( field_itr );
5825 jsonObject* obj = jsonNewObject( id );
5827 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5828 dbi_conn_quote_string( dbhandle, &id );
5830 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5832 char* query = buffer_release( sql );
5833 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5835 dbi_result result = dbi_conn_query( dbhandle, query );
5840 jsonObjectFree( obj );
5841 obj = jsonNewObject( NULL );
5843 int errnum = dbi_conn_error( dbhandle, &msg );
5846 "%s ERROR updating %s object with %s = %s: %d %s",
5848 osrfHashGet( meta, "fieldmapper" ),
5852 msg ? msg : "(No description available)"
5854 osrfAppSessionStatus(
5856 OSRF_STATUS_INTERNALSERVERERROR,
5857 "osrfMethodException",
5859 "Error in updating a row -- please see the error log for more details"
5861 if( !oilsIsDBConnected( dbhandle ))
5862 osrfAppSessionPanic( ctx->session );
5865 dbi_result_free( result );
5868 osrfAppRespondComplete( ctx, obj );
5869 jsonObjectFree( obj );
5873 int doDelete( osrfMethodContext* ctx ) {
5874 if( osrfMethodVerifyContext( ctx )) {
5875 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5880 timeout_needs_resetting = 1;
5882 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5884 if( getXactId( ctx ) == NULL ) {
5885 osrfAppSessionStatus(
5887 OSRF_STATUS_BADREQUEST,
5888 "osrfMethodException",
5890 "No active transaction -- required for DELETE"
5892 osrfAppRespondComplete( ctx, NULL );
5896 // The following test is harmless but redundant. If a class is
5897 // readonly, we don't register a delete method for it.
5898 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5899 osrfAppSessionStatus(
5901 OSRF_STATUS_BADREQUEST,
5902 "osrfMethodException",
5904 "Cannot DELETE readonly class"
5906 osrfAppRespondComplete( ctx, NULL );
5910 dbhandle = writehandle;
5912 char* pkey = osrfHashGet( meta, "primarykey" );
5919 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5920 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5921 osrfAppRespondComplete( ctx, NULL );
5925 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5927 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5928 osrfAppRespondComplete( ctx, NULL );
5931 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5936 "%s deleting %s object with %s = %s",
5938 osrfHashGet( meta, "fieldmapper" ),
5943 jsonObject* obj = jsonNewObject( id );
5945 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5946 dbi_conn_quote_string( writehandle, &id );
5948 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5949 osrfHashGet( meta, "tablename" ), pkey, id );
5954 jsonObjectFree( obj );
5955 obj = jsonNewObject( NULL );
5957 int errnum = dbi_conn_error( writehandle, &msg );
5960 "%s ERROR deleting %s object with %s = %s: %d %s",
5962 osrfHashGet( meta, "fieldmapper" ),
5966 msg ? msg : "(No description available)"
5968 osrfAppSessionStatus(
5970 OSRF_STATUS_INTERNALSERVERERROR,
5971 "osrfMethodException",
5973 "Error in deleting a row -- please see the error log for more details"
5975 if( !oilsIsDBConnected( writehandle ))
5976 osrfAppSessionPanic( ctx->session );
5978 dbi_result_free( result );
5982 osrfAppRespondComplete( ctx, obj );
5983 jsonObjectFree( obj );
5988 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5989 @param result An iterator for a result set; we only look at the current row.
5990 @param @meta Pointer to the class metadata for the core class.
5991 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5993 If a column is not defined in the IDL, or if it has no array_position defined for it in
5994 the IDL, or if it is defined as virtual, ignore it.
5996 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5997 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5998 array_position in the IDL.
6000 A field defined in the IDL but not represented in the returned row will leave a hole
6001 in the JSON_ARRAY. In effect it will be treated as a null value.
6003 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6004 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6005 classname corresponding to the @a meta argument.
6007 The calling code is responsible for freeing the the resulting jsonObject by calling
6010 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6011 if( !( result && meta )) return NULL;
6013 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6014 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6015 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6017 osrfHash* fields = osrfHashGet( meta, "fields" );
6019 int columnIndex = 1;
6020 const char* columnName;
6022 /* cycle through the columns in the row returned from the database */
6023 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6025 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6027 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6029 /* determine the field type and storage attributes */
6030 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6031 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6033 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6034 // or if it has no sequence number there, or if it's virtual, skip it.
6035 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6038 if( str_is_true( osrfHashGet( _f, "virtual" )))
6039 continue; // skip this column: IDL says it's virtual
6041 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6042 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6043 continue; // since we assign sequence numbers dynamically as we load the IDL.
6045 fmIndex = atoi( pos );
6046 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6048 continue; // This field is not defined in the IDL
6051 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6052 // sequence number from the IDL (which is likely to be different from the sequence
6053 // of columns in the SELECT clause).
6054 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6055 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6060 case DBI_TYPE_INTEGER :
6062 if( attr & DBI_INTEGER_SIZE8 )
6063 jsonObjectSetIndex( object, fmIndex,
6064 jsonNewNumberObject(
6065 dbi_result_get_longlong_idx( result, columnIndex )));
6067 jsonObjectSetIndex( object, fmIndex,
6068 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6072 case DBI_TYPE_DECIMAL :
6073 jsonObjectSetIndex( object, fmIndex,
6074 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6077 case DBI_TYPE_STRING :
6082 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6087 case DBI_TYPE_DATETIME : {
6089 char dt_string[ 256 ] = "";
6092 // Fetch the date column as a time_t
6093 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6095 // Translate the time_t to a human-readable string
6096 if( !( attr & DBI_DATETIME_DATE )) {
6097 gmtime_r( &_tmp_dt, &gmdt );
6098 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6099 } else if( !( attr & DBI_DATETIME_TIME )) {
6100 localtime_r( &_tmp_dt, &gmdt );
6101 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6103 localtime_r( &_tmp_dt, &gmdt );
6104 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6107 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6111 case DBI_TYPE_BINARY :
6112 osrfLogError( OSRF_LOG_MARK,
6113 "Can't do binary at column %s : index %d", columnName, columnIndex );
6122 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6123 if( !result ) return NULL;
6125 jsonObject* object = jsonNewObject( NULL );
6128 char dt_string[ 256 ];
6132 int columnIndex = 1;
6134 unsigned short type;
6135 const char* columnName;
6137 /* cycle through the column list */
6138 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6140 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6142 fmIndex = -1; // reset the position
6144 /* determine the field type and storage attributes */
6145 type = dbi_result_get_field_type_idx( result, columnIndex );
6146 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6148 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6149 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6154 case DBI_TYPE_INTEGER :
6156 if( attr & DBI_INTEGER_SIZE8 )
6157 jsonObjectSetKey( object, columnName,
6158 jsonNewNumberObject( dbi_result_get_longlong_idx(
6159 result, columnIndex )) );
6161 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6162 dbi_result_get_int_idx( result, columnIndex )) );
6165 case DBI_TYPE_DECIMAL :
6166 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6167 dbi_result_get_double_idx( result, columnIndex )) );
6170 case DBI_TYPE_STRING :
6171 jsonObjectSetKey( object, columnName,
6172 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6175 case DBI_TYPE_DATETIME :
6177 memset( dt_string, '\0', sizeof( dt_string ));
6178 memset( &gmdt, '\0', sizeof( gmdt ));
6180 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6182 if( !( attr & DBI_DATETIME_DATE )) {
6183 gmtime_r( &_tmp_dt, &gmdt );
6184 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6185 } else if( !( attr & DBI_DATETIME_TIME )) {
6186 localtime_r( &_tmp_dt, &gmdt );
6187 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6189 localtime_r( &_tmp_dt, &gmdt );
6190 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6193 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6196 case DBI_TYPE_BINARY :
6197 osrfLogError( OSRF_LOG_MARK,
6198 "Can't do binary at column %s : index %d", columnName, columnIndex );
6202 } // end while loop traversing result
6207 // Interpret a string as true or false
6208 int str_is_true( const char* str ) {
6209 if( NULL == str || strcasecmp( str, "true" ) )
6215 // Interpret a jsonObject as true or false
6216 static int obj_is_true( const jsonObject* obj ) {
6219 else switch( obj->type )
6227 if( strcasecmp( obj->value.s, "true" ) )
6231 case JSON_NUMBER : // Support 1/0 for perl's sake
6232 if( jsonObjectGetNumber( obj ) == 1.0 )
6241 // Translate a numeric code into a text string identifying a type of
6242 // jsonObject. To be used for building error messages.
6243 static const char* json_type( int code ) {
6249 return "JSON_ARRAY";
6251 return "JSON_STRING";
6253 return "JSON_NUMBER";
6259 return "(unrecognized)";
6263 // Extract the "primitive" attribute from an IDL field definition.
6264 // If we haven't initialized the app, then we must be running in
6265 // some kind of testbed. In that case, default to "string".
6266 static const char* get_primitive( osrfHash* field ) {
6267 const char* s = osrfHashGet( field, "primitive" );
6269 if( child_initialized )
6272 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6274 osrfHashGet( field, "name" )
6282 // Extract the "datatype" attribute from an IDL field definition.
6283 // If we haven't initialized the app, then we must be running in
6284 // some kind of testbed. In that case, default to to NUMERIC,
6285 // since we look at the datatype only for numbers.
6286 static const char* get_datatype( osrfHash* field ) {
6287 const char* s = osrfHashGet( field, "datatype" );
6289 if( child_initialized )
6292 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6294 osrfHashGet( field, "name" )
6303 @brief Determine whether a string is potentially a valid SQL identifier.
6304 @param s The identifier to be tested.
6305 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6307 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6308 need to follow all the rules exactly, such as requiring that the first character not
6311 We allow leading and trailing white space. In between, we do not allow punctuation
6312 (except for underscores and dollar signs), control characters, or embedded white space.
6314 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6315 for the foreseeable future such quoted identifiers are not likely to be an issue.
6317 int is_identifier( const char* s) {
6321 // Skip leading white space
6322 while( isspace( (unsigned char) *s ) )
6326 return 0; // Nothing but white space? Not okay.
6328 // Check each character until we reach white space or
6329 // end-of-string. Letters, digits, underscores, and
6330 // dollar signs are okay. With the exception of periods
6331 // (as in schema.identifier), control characters and other
6332 // punctuation characters are not okay. Anything else
6333 // is okay -- it could for example be part of a multibyte
6334 // UTF8 character such as a letter with diacritical marks,
6335 // and those are allowed.
6337 if( isalnum( (unsigned char) *s )
6341 ; // Fine; keep going
6342 else if( ispunct( (unsigned char) *s )
6343 || iscntrl( (unsigned char) *s ) )
6346 } while( *s && ! isspace( (unsigned char) *s ) );
6348 // If we found any white space in the above loop,
6349 // the rest had better be all white space.
6351 while( isspace( (unsigned char) *s ) )
6355 return 0; // White space was embedded within non-white space
6361 @brief Determine whether to accept a character string as a comparison operator.
6362 @param op The candidate comparison operator.
6363 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6365 We don't validate the operator for real. We just make sure that it doesn't contain
6366 any semicolons or white space (with special exceptions for a few specific operators).
6367 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6368 space but it's still not a valid operator, then the database will complain.
6370 Another approach would be to compare the string against a short list of approved operators.
6371 We don't do that because we want to allow custom operators like ">100*", which at this
6372 writing would be difficult or impossible to express otherwise in a JSON query.
6374 int is_good_operator( const char* op ) {
6375 if( !op ) return 0; // Sanity check
6379 if( isspace( (unsigned char) *s ) ) {
6380 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6381 // and IS NOT DISTINCT FROM.
6382 if( !strcasecmp( op, "similar to" ) )
6384 else if( !strcasecmp( op, "is distinct from" ) )
6386 else if( !strcasecmp( op, "is not distinct from" ) )
6391 else if( ';' == *s )
6399 @name Query Frame Management
6401 The following machinery supports a stack of query frames for use by SELECT().
6403 A query frame caches information about one level of a SELECT query. When we enter
6404 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6406 The query frame stores information about the core class, and about any joined classes
6409 The main purpose is to map table aliases to classes and tables, so that a query can
6410 join to the same table more than once. A secondary goal is to reduce the number of
6411 lookups in the IDL by caching the results.
6415 #define STATIC_CLASS_INFO_COUNT 3
6417 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6420 @brief Allocate a ClassInfo as raw memory.
6421 @return Pointer to the newly allocated ClassInfo.
6423 Except for the in_use flag, which is used only by the allocation and deallocation
6424 logic, we don't initialize the ClassInfo here.
6426 static ClassInfo* allocate_class_info( void ) {
6427 // In order to reduce the number of mallocs and frees, we return a static
6428 // instance of ClassInfo, if we can find one that we're not already using.
6429 // We rely on the fact that the compiler will implicitly initialize the
6430 // static instances so that in_use == 0.
6433 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6434 if( ! static_class_info[ i ].in_use ) {
6435 static_class_info[ i ].in_use = 1;
6436 return static_class_info + i;
6440 // The static ones are all in use. Malloc one.
6442 return safe_malloc( sizeof( ClassInfo ) );
6446 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6447 @param info Pointer to the ClassInfo to be cleared.
6449 static void clear_class_info( ClassInfo* info ) {
6454 // Free any malloc'd strings
6456 if( info->alias != info->alias_store )
6457 free( info->alias );
6459 if( info->class_name != info->class_name_store )
6460 free( info->class_name );
6462 free( info->source_def );
6464 info->alias = info->class_name = info->source_def = NULL;
6469 @brief Free a ClassInfo and everything it owns.
6470 @param info Pointer to the ClassInfo to be freed.
6472 static void free_class_info( ClassInfo* info ) {
6477 clear_class_info( info );
6479 // If it's one of the static instances, just mark it as not in use
6482 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6483 if( info == static_class_info + i ) {
6484 static_class_info[ i ].in_use = 0;
6489 // Otherwise it must have been malloc'd, so free it
6495 @brief Populate an already-allocated ClassInfo.
6496 @param info Pointer to the ClassInfo to be populated.
6497 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6499 @param class Name of the class.
6500 @return Zero if successful, or 1 if not.
6502 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6503 the relevant portions of the IDL for the specified class.
6505 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6508 osrfLogError( OSRF_LOG_MARK,
6509 "%s ERROR: No ClassInfo available to populate", modulename );
6510 info->alias = info->class_name = info->source_def = NULL;
6511 info->class_def = info->fields = info->links = NULL;
6516 osrfLogError( OSRF_LOG_MARK,
6517 "%s ERROR: No class name provided for lookup", modulename );
6518 info->alias = info->class_name = info->source_def = NULL;
6519 info->class_def = info->fields = info->links = NULL;
6523 // Alias defaults to class name if not supplied
6524 if( ! alias || ! alias[ 0 ] )
6527 // Look up class info in the IDL
6528 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6530 osrfLogError( OSRF_LOG_MARK,
6531 "%s ERROR: Class %s not defined in IDL", modulename, class );
6532 info->alias = info->class_name = info->source_def = NULL;
6533 info->class_def = info->fields = info->links = NULL;
6535 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6536 osrfLogError( OSRF_LOG_MARK,
6537 "%s ERROR: Class %s is defined as virtual", modulename, class );
6538 info->alias = info->class_name = info->source_def = NULL;
6539 info->class_def = info->fields = info->links = NULL;
6543 osrfHash* links = osrfHashGet( class_def, "links" );
6545 osrfLogError( OSRF_LOG_MARK,
6546 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6547 info->alias = info->class_name = info->source_def = NULL;
6548 info->class_def = info->fields = info->links = NULL;
6552 osrfHash* fields = osrfHashGet( class_def, "fields" );
6554 osrfLogError( OSRF_LOG_MARK,
6555 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6556 info->alias = info->class_name = info->source_def = NULL;
6557 info->class_def = info->fields = info->links = NULL;
6561 char* source_def = oilsGetRelation( class_def );
6565 // We got everything we need, so populate the ClassInfo
6566 if( strlen( alias ) > ALIAS_STORE_SIZE )
6567 info->alias = strdup( alias );
6569 strcpy( info->alias_store, alias );
6570 info->alias = info->alias_store;
6573 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6574 info->class_name = strdup( class );
6576 strcpy( info->class_name_store, class );
6577 info->class_name = info->class_name_store;
6580 info->source_def = source_def;
6582 info->class_def = class_def;
6583 info->links = links;
6584 info->fields = fields;
6589 #define STATIC_FRAME_COUNT 3
6591 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6594 @brief Allocate a QueryFrame as raw memory.
6595 @return Pointer to the newly allocated QueryFrame.
6597 Except for the in_use flag, which is used only by the allocation and deallocation
6598 logic, we don't initialize the QueryFrame here.
6600 static QueryFrame* allocate_frame( void ) {
6601 // In order to reduce the number of mallocs and frees, we return a static
6602 // instance of QueryFrame, if we can find one that we're not already using.
6603 // We rely on the fact that the compiler will implicitly initialize the
6604 // static instances so that in_use == 0.
6607 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6608 if( ! static_frame[ i ].in_use ) {
6609 static_frame[ i ].in_use = 1;
6610 return static_frame + i;
6614 // The static ones are all in use. Malloc one.
6616 return safe_malloc( sizeof( QueryFrame ) );
6620 @brief Free a QueryFrame, and all the memory it owns.
6621 @param frame Pointer to the QueryFrame to be freed.
6623 static void free_query_frame( QueryFrame* frame ) {
6628 clear_class_info( &frame->core );
6630 // Free the join list
6632 ClassInfo* info = frame->join_list;
6635 free_class_info( info );
6639 frame->join_list = NULL;
6642 // If the frame is a static instance, just mark it as unused
6644 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6645 if( frame == static_frame + i ) {
6646 static_frame[ i ].in_use = 0;
6651 // Otherwise it must have been malloc'd, so free it
6657 @brief Search a given QueryFrame for a specified alias.
6658 @param frame Pointer to the QueryFrame to be searched.
6659 @param target The alias for which to search.
6660 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6662 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6663 if( ! frame || ! target ) {
6667 ClassInfo* found_class = NULL;
6669 if( !strcmp( target, frame->core.alias ) )
6670 return &(frame->core);
6672 ClassInfo* curr_class = frame->join_list;
6673 while( curr_class ) {
6674 if( strcmp( target, curr_class->alias ) )
6675 curr_class = curr_class->next;
6677 found_class = curr_class;
6687 @brief Push a new (blank) QueryFrame onto the stack.
6689 static void push_query_frame( void ) {
6690 QueryFrame* frame = allocate_frame();
6691 frame->join_list = NULL;
6692 frame->next = curr_query;
6694 // Initialize the ClassInfo for the core class
6695 ClassInfo* core = &frame->core;
6696 core->alias = core->class_name = core->source_def = NULL;
6697 core->class_def = core->fields = core->links = NULL;
6703 @brief Pop a QueryFrame off the stack and destroy it.
6705 static void pop_query_frame( void ) {
6710 QueryFrame* popped = curr_query;
6711 curr_query = popped->next;
6713 free_query_frame( popped );
6717 @brief Populate the ClassInfo for the core class.
6718 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6719 class name as an alias.
6720 @param class_name Name of the core class.
6721 @return Zero if successful, or 1 if not.
6723 Populate the ClassInfo of the core class with copies of the alias and class name, and
6724 with pointers to the relevant portions of the IDL for the core class.
6726 static int add_query_core( const char* alias, const char* class_name ) {
6729 if( ! curr_query ) {
6730 osrfLogError( OSRF_LOG_MARK,
6731 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6733 } else if( curr_query->core.alias ) {
6734 osrfLogError( OSRF_LOG_MARK,
6735 "%s ERROR: Core class %s already populated as %s",
6736 modulename, curr_query->core.class_name, curr_query->core.alias );
6740 build_class_info( &curr_query->core, alias, class_name );
6741 if( curr_query->core.alias )
6744 osrfLogError( OSRF_LOG_MARK,
6745 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6751 @brief Search the current QueryFrame for a specified alias.
6752 @param target The alias for which to search.
6753 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6755 static inline ClassInfo* search_alias( const char* target ) {
6756 return search_alias_in_frame( curr_query, target );
6760 @brief Search all levels of query for a specified alias, starting with the current query.
6761 @param target The alias for which to search.
6762 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6764 static ClassInfo* search_all_alias( const char* target ) {
6765 ClassInfo* found_class = NULL;
6766 QueryFrame* curr_frame = curr_query;
6768 while( curr_frame ) {
6769 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6772 curr_frame = curr_frame->next;
6779 @brief Add a class to the list of classes joined to the current query.
6780 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6781 the class name as an alias.
6782 @param classname The name of the class to be added.
6783 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6785 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6787 if( ! classname || ! *classname ) { // sanity check
6788 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6795 const ClassInfo* conflict = search_alias( alias );
6797 osrfLogError( OSRF_LOG_MARK,
6798 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6799 modulename, alias, conflict->class_name );
6803 ClassInfo* info = allocate_class_info();
6805 if( build_class_info( info, alias, classname ) ) {
6806 free_class_info( info );
6810 // Add the new ClassInfo to the join list of the current QueryFrame
6811 info->next = curr_query->join_list;
6812 curr_query->join_list = info;
6818 @brief Destroy all nodes on the query stack.
6820 static void clear_query_stack( void ) {