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 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 void userDataFree( void* );
104 static void sessionDataFree( char*, void* );
105 static int obj_is_true( const jsonObject* obj );
106 static const char* json_type( int code );
107 static const char* get_primitive( osrfHash* field );
108 static const char* get_datatype( osrfHash* field );
109 static void pop_query_frame( void );
110 static void push_query_frame( void );
111 static int add_query_core( const char* alias, const char* class_name );
112 static inline ClassInfo* search_alias( const char* target );
113 static ClassInfo* search_all_alias( const char* target );
114 static ClassInfo* add_joined_class( const char* alias, const char* classname );
115 static void clear_query_stack( void );
117 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
118 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
119 static const char* org_tree_root( osrfMethodContext* ctx );
120 static jsonObject* single_hash( const char* key, const char* value );
122 static int child_initialized = 0; /* boolean */
124 static dbi_conn writehandle; /* our MASTER db connection */
125 static dbi_conn dbhandle; /* our CURRENT db connection */
126 //static osrfHash * readHandles;
128 // The following points to the top of a stack of QueryFrames. It's a little
129 // confusing because the top level of the query is at the bottom of the stack.
130 static QueryFrame* curr_query = NULL;
132 static dbi_conn writehandle; /* our MASTER db connection */
133 static dbi_conn dbhandle; /* our CURRENT db connection */
134 //static osrfHash * readHandles;
136 static int max_flesh_depth = 100;
138 static int enforce_pcrud = 0; // Boolean
139 static char* modulename = NULL;
142 @brief Connect to the database.
143 @return A database connection if successful, or NULL if not.
145 dbi_conn oilsConnectDB( const char* mod_name ) {
147 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
148 if( dbi_initialize( NULL ) == -1 ) {
149 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
152 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
154 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
155 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
156 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
157 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
158 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
159 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
161 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
162 dbi_conn handle = dbi_conn_new( driver );
165 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
168 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
170 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
171 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
173 if( host ) dbi_conn_set_option( handle, "host", host );
174 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
175 if( user ) dbi_conn_set_option( handle, "username", user );
176 if( pw ) dbi_conn_set_option( handle, "password", pw );
177 if( db ) dbi_conn_set_option( handle, "dbname", db );
185 if( dbi_conn_connect( handle ) < 0 ) {
187 if( dbi_conn_connect( handle ) < 0 ) {
189 dbi_conn_error( handle, &msg );
190 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
191 msg ? msg : "(No description available)" );
196 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
202 @brief Select some options.
203 @param module_name: Name of the server.
204 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
206 This source file is used (at this writing) to implement three different servers:
207 - open-ils.reporter-store
211 These servers behave mostly the same, but they implement different combinations of
212 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
214 Here we use the server name in messages to identify which kind of server issued them.
215 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
217 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
219 module_name = "open-ils.cstore"; // bulletproofing with a default
224 modulename = strdup( module_name );
225 enforce_pcrud = do_pcrud;
226 max_flesh_depth = flesh_depth;
230 @brief Install a database connection.
231 @param conn Pointer to a database connection.
233 In some contexts, @a conn may merely provide a driver so that we can process strings
234 properly, without providing an open database connection.
236 void oilsSetDBConnection( dbi_conn conn ) {
237 dbhandle = writehandle = conn;
241 @brief Determine whether a database connection is alive.
242 @param handle Handle for a database connection.
243 @return 1 if the connection is alive, or zero if it isn't.
245 int oilsIsDBConnected( dbi_conn handle ) {
246 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
247 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
249 dbi_result_free( result );
252 // This is a terrible, horrible, no good, very bad kludge.
253 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
254 // but because (due to a previous error) the database is ignoring all commands,
255 // even innocuous SELECTs, until the current transaction is rolled back. The only
256 // known way to detect this condition via the dbi library is by looking at the error
257 // message. This approach will break if the language or wording of the message ever
259 // Note: the dbi_conn_ping function purports to determine whether the doatabase
260 // connection is live, but at this writing this function is unreliable and useless.
261 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
262 "ignored until end of transaction block\n";
264 dbi_conn_error( handle, &msg );
265 if( strcmp( msg, ok_msg )) {
266 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
269 return 1; // ignoring SELECT due to previous error; that's okay
274 @brief Get a table name, view name, or subquery for use in a FROM clause.
275 @param class Pointer to the IDL class entry.
276 @return A table name, a view name, or a subquery in parentheses.
278 In some cases the IDL defines a class, not with a table name or a view name, but with
279 a SELECT statement, which may be used as a subquery.
281 char* oilsGetRelation( osrfHash* classdef ) {
283 char* source_def = NULL;
284 const char* tabledef = osrfHashGet( classdef, "tablename" );
287 source_def = strdup( tabledef ); // Return the name of a table or view
289 tabledef = osrfHashGet( classdef, "source_definition" );
291 // Return a subquery, enclosed in parentheses
292 source_def = safe_malloc( strlen( tabledef ) + 3 );
293 source_def[ 0 ] = '(';
294 strcpy( source_def + 1, tabledef );
295 strcat( source_def, ")" );
297 // Not found: return an error
298 const char* classname = osrfHashGet( classdef, "classname" );
303 "%s ERROR No tablename or source_definition for class \"%s\"",
314 @brief Add datatypes from the database to the fields in the IDL.
315 @param handle Handle for a database connection
316 @return Zero if successful, or 1 upon error.
318 For each relevant class in the IDL: ask the database for the datatype of every field.
319 In particular, determine which fields are text fields and which fields are numeric
320 fields, so that we know whether to enclose their values in quotes.
322 int oilsExtendIDL( dbi_conn handle ) {
323 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
324 osrfHash* class = NULL;
325 growing_buffer* query_buf = buffer_init( 64 );
326 int results_found = 0; // boolean
328 // For each class in the IDL...
329 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
330 const char* classname = osrfHashIteratorKey( class_itr );
331 osrfHash* fields = osrfHashGet( class, "fields" );
333 // If the class is virtual, ignore it
334 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
335 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
339 char* tabledef = oilsGetRelation( class );
341 continue; // No such relation -- a query of it would be doomed to failure
343 buffer_reset( query_buf );
344 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
348 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
349 modulename, OSRF_BUFFER_C_STR( query_buf ) );
351 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
356 const char* columnName;
357 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
359 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
362 /* fetch the fieldmapper index */
363 osrfHash* _f = osrfHashGet(fields, columnName);
366 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
368 /* determine the field type and storage attributes */
370 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
372 case DBI_TYPE_INTEGER : {
374 if( !osrfHashGet(_f, "primitive") )
375 osrfHashSet(_f, "number", "primitive");
377 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
378 if( attr & DBI_INTEGER_SIZE8 )
379 osrfHashSet( _f, "INT8", "datatype" );
381 osrfHashSet( _f, "INT", "datatype" );
384 case DBI_TYPE_DECIMAL :
385 if( !osrfHashGet( _f, "primitive" ))
386 osrfHashSet( _f, "number", "primitive" );
388 osrfHashSet( _f, "NUMERIC", "datatype" );
391 case DBI_TYPE_STRING :
392 if( !osrfHashGet( _f, "primitive" ))
393 osrfHashSet( _f, "string", "primitive" );
395 osrfHashSet( _f,"TEXT", "datatype" );
398 case DBI_TYPE_DATETIME :
399 if( !osrfHashGet( _f, "primitive" ))
400 osrfHashSet( _f, "string", "primitive" );
402 osrfHashSet( _f, "TIMESTAMP", "datatype" );
405 case DBI_TYPE_BINARY :
406 if( !osrfHashGet( _f, "primitive" ))
407 osrfHashSet( _f, "string", "primitive" );
409 osrfHashSet( _f, "BYTEA", "datatype" );
414 "Setting [%s] to primitive [%s] and datatype [%s]...",
416 osrfHashGet( _f, "primitive" ),
417 osrfHashGet( _f, "datatype" )
421 } // end while loop for traversing columns of result
422 dbi_result_free( result );
425 int errnum = dbi_conn_error( handle, &msg );
426 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
427 errnum, msg ? msg : "(No description available)" );
428 // We don't check the database connection here. It's routine to get failures at
429 // this point; we routinely try to query tables that don't exist, because they
430 // are defined in the IDL but not in the database.
432 } // end for each class in IDL
434 buffer_free( query_buf );
435 osrfHashIteratorFree( class_itr );
436 child_initialized = 1;
438 if( !results_found ) {
439 osrfLogError( OSRF_LOG_MARK,
440 "No results found for any class -- bad database connection?" );
442 } else if( ! oilsIsDBConnected( handle )) {
443 osrfLogError( OSRF_LOG_MARK,
444 "Unable to extend IDL: database connection isn't working" );
452 @brief Free an osrfHash that stores a transaction ID.
453 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
455 This function is a callback, to be called by the application session when it ends.
456 The application session stores the osrfHash via an opaque pointer.
458 If the osrfHash contains an entry for the key "xact_id", it means that an
459 uncommitted transaction is pending. Roll it back.
461 void userDataFree( void* blob ) {
462 osrfHash* hash = (osrfHash*) blob;
463 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
464 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
466 int errnum = dbi_conn_error( writehandle, &msg );
467 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
468 errnum, msg ? msg : "(No description available)" );
472 osrfHashFree( hash );
476 @name Managing session data
477 @brief Maintain data stored via the userData pointer of the application session.
479 Currently, session-level data is stored in an osrfHash. Other arrangements are
480 possible, and some would be more efficient. The application session calls a
481 callback function to free userData before terminating.
483 Currently, the only data we store at the session level is the transaction id. By this
484 means we can ensure that any pending transactions are rolled back before the application
490 @brief Free an item in the application session's userData.
491 @param key The name of a key for an osrfHash.
492 @param item An opaque pointer to the item associated with the key.
494 We store an osrfHash as userData with the application session, and arrange (by
495 installing userDataFree() as a different callback) for the session to free that
496 osrfHash before terminating.
498 This function is a callback for freeing items in the osrfHash. Currently we store
500 - Transaction id of a pending transaction; a character string. Key: "xact_id".
501 - Authkey; a character string. Key: "authkey".
502 - User object from the authentication server; a jsonObject. Key: "user_login".
504 If we ever store anything else in userData, we will need to revisit this function so
505 that it will free whatever else needs freeing.
507 static void sessionDataFree( char* key, void* item ) {
508 if( !strcmp( key, "xact_id" )
509 || !strcmp( key, "authkey" ) ) {
511 } else if( !strcmp( key, "user_login" ) )
512 jsonObjectFree( (jsonObject*) item );
516 @brief Save a transaction id.
517 @param ctx Pointer to the method context.
519 Save the session_id of the current application session as a transaction id.
521 static void setXactId( osrfMethodContext* ctx ) {
522 if( ctx && ctx->session ) {
523 osrfAppSession* session = ctx->session;
525 osrfHash* cache = session->userData;
527 // If the session doesn't already have a hash, create one. Make sure
528 // that the application session frees the hash when it terminates.
529 if( NULL == cache ) {
530 session->userData = cache = osrfNewHash();
531 osrfHashSetCallback( cache, &sessionDataFree );
532 ctx->session->userDataFree = &userDataFree;
535 // Save the transaction id in the hash, with the key "xact_id"
536 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
541 @brief Get the transaction ID for the current transaction, if any.
542 @param ctx Pointer to the method context.
543 @return Pointer to the transaction ID.
545 The return value points to an internal buffer, and will become invalid upon issuing
546 a commit or rollback.
548 static inline const char* getXactId( osrfMethodContext* ctx ) {
549 if( ctx && ctx->session && ctx->session->userData )
550 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
556 @brief Clear the current transaction id.
557 @param ctx Pointer to the method context.
559 static inline void clearXactId( osrfMethodContext* ctx ) {
560 if( ctx && ctx->session && ctx->session->userData )
561 osrfHashRemove( ctx->session->userData, "xact_id" );
566 @brief Save the user's login in the userData for the current application session.
567 @param ctx Pointer to the method context.
568 @param user_login Pointer to the user login object to be cached (we cache the original,
571 If @a user_login is NULL, remove the user login if one is already cached.
573 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
574 if( ctx && ctx->session ) {
575 osrfAppSession* session = ctx->session;
577 osrfHash* cache = session->userData;
579 // If the session doesn't already have a hash, create one. Make sure
580 // that the application session frees the hash when it terminates.
581 if( NULL == cache ) {
582 session->userData = cache = osrfNewHash();
583 osrfHashSetCallback( cache, &sessionDataFree );
584 ctx->session->userDataFree = &userDataFree;
588 osrfHashSet( cache, user_login, "user_login" );
590 osrfHashRemove( cache, "user_login" );
595 @brief Get the user login object for the current application session, if any.
596 @param ctx Pointer to the method context.
597 @return Pointer to the user login object if found; otherwise NULL.
599 The user login object was returned from the authentication server, and then cached so
600 we don't have to call the authentication server again for the same user.
602 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
603 if( ctx && ctx->session && ctx->session->userData )
604 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
610 @brief Save a copy of an authkey in the userData of the current application session.
611 @param ctx Pointer to the method context.
612 @param authkey The authkey to be saved.
614 If @a authkey is NULL, remove the authkey if one is already cached.
616 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
617 if( ctx && ctx->session && authkey ) {
618 osrfAppSession* session = ctx->session;
619 osrfHash* cache = session->userData;
621 // If the session doesn't already have a hash, create one. Make sure
622 // that the application session frees the hash when it terminates.
623 if( NULL == cache ) {
624 session->userData = cache = osrfNewHash();
625 osrfHashSetCallback( cache, &sessionDataFree );
626 ctx->session->userDataFree = &userDataFree;
629 // Save the transaction id in the hash, with the key "xact_id"
630 if( authkey && *authkey )
631 osrfHashSet( cache, strdup( authkey ), "authkey" );
633 osrfHashRemove( cache, "authkey" );
638 @brief Reset the login timeout.
639 @param authkey The authentication key for the current login session.
640 @param now The current time.
641 @return Zero if successful, or 1 if not.
643 Tell the authentication server to reset the timeout so that the login session won't
644 expire for a while longer.
646 We could dispense with the @a now parameter by calling time(). But we just called
647 time() in order to decide whether to reset the timeout, so we might as well reuse
648 the result instead of calling time() again.
650 static int reset_timeout( const char* authkey, time_t now ) {
651 jsonObject* auth_object = jsonNewObject( authkey );
653 // Ask the authentication server to reset the timeout. It returns an event
654 // indicating success or failure.
655 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
656 "open-ils.auth.session.reset_timeout", auth_object );
657 jsonObjectFree( auth_object );
659 if( !result || result->type != JSON_HASH ) {
660 osrfLogError( OSRF_LOG_MARK,
661 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
662 jsonObjectFree( result );
663 return 1; // Not the right sort of object returned
666 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
667 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
668 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
669 jsonObjectFree( result );
670 return 1; // Return code from method not available
673 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
674 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
676 desc = "(No reason available)"; // failsafe; shouldn't happen
677 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
678 jsonObjectFree( result );
682 // Revise our local proxy for the timeout deadline
683 // by a smallish fraction of the timeout interval
684 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
686 timeout = "1"; // failsafe; shouldn't happen
687 time_next_reset = now + atoi( timeout ) / 15;
689 jsonObjectFree( result );
690 return 0; // Successfully reset timeout
694 @brief Get the authkey string for the current application session, if any.
695 @param ctx Pointer to the method context.
696 @return Pointer to the cached authkey if found; otherwise NULL.
698 If present, the authkey string was cached from a previous method call.
700 static const char* getAuthkey( osrfMethodContext* ctx ) {
701 if( ctx && ctx->session && ctx->session->userData ) {
702 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
704 // Possibly reset the authentication timeout to keep the login alive. We do so
705 // no more than once per method call, and not at all if it has been only a short
706 // time since the last reset.
708 // Here we reset explicitly, if at all. We also implicitly reset the timeout
709 // whenever we call the "open-ils.auth.session.retrieve" method.
710 if( timeout_needs_resetting ) {
711 time_t now = time( NULL );
712 if( now >= time_next_reset && reset_timeout( authkey, now ) )
713 authkey = NULL; // timeout has apparently expired already
716 timeout_needs_resetting = 0;
724 @brief Implement the transaction.begin method.
725 @param ctx Pointer to the method context.
726 @return Zero if successful, or -1 upon error.
728 Start a transaction. Save a transaction ID for future reference.
731 - authkey (PCRUD only)
733 Return to client: Transaction ID
735 int beginTransaction( osrfMethodContext* ctx ) {
736 if(osrfMethodVerifyContext( ctx )) {
737 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
741 if( enforce_pcrud ) {
742 timeout_needs_resetting = 1;
743 const jsonObject* user = verifyUserPCRUD( ctx );
748 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
751 int errnum = dbi_conn_error( writehandle, &msg );
752 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
753 modulename, errnum, msg ? msg : "(No description available)" );
754 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
755 "osrfMethodException", ctx->request, "Error starting transaction" );
756 if( !oilsIsDBConnected( writehandle ))
757 osrfAppSessionPanic( ctx->session );
760 dbi_result_free( result );
762 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
763 osrfAppRespondComplete( ctx, ret );
764 jsonObjectFree( ret );
770 @brief Implement the savepoint.set method.
771 @param ctx Pointer to the method context.
772 @return Zero if successful, or -1 if not.
774 Issue a SAVEPOINT to the database server.
777 - authkey (PCRUD only)
780 Return to client: Savepoint name
782 int setSavepoint( osrfMethodContext* ctx ) {
783 if(osrfMethodVerifyContext( ctx )) {
784 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
789 if( enforce_pcrud ) {
791 timeout_needs_resetting = 1;
792 const jsonObject* user = verifyUserPCRUD( ctx );
797 // Verify that a transaction is pending
798 const char* trans_id = getXactId( ctx );
799 if( NULL == trans_id ) {
800 osrfAppSessionStatus(
802 OSRF_STATUS_INTERNALSERVERERROR,
803 "osrfMethodException",
805 "No active transaction -- required for savepoints"
810 // Get the savepoint name from the method params
811 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
813 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
816 int errnum = dbi_conn_error( writehandle, &msg );
819 "%s: Error creating savepoint %s in transaction %s: %d %s",
824 msg ? msg : "(No description available)"
826 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
827 "osrfMethodException", ctx->request, "Error creating savepoint" );
828 if( !oilsIsDBConnected( writehandle ))
829 osrfAppSessionPanic( ctx->session );
832 dbi_result_free( result );
833 jsonObject* ret = jsonNewObject( spName );
834 osrfAppRespondComplete( ctx, ret );
835 jsonObjectFree( ret );
841 @brief Implement the savepoint.release method.
842 @param ctx Pointer to the method context.
843 @return Zero if successful, or -1 if not.
845 Issue a RELEASE SAVEPOINT to the database server.
848 - authkey (PCRUD only)
851 Return to client: Savepoint name
853 int releaseSavepoint( osrfMethodContext* ctx ) {
854 if(osrfMethodVerifyContext( ctx )) {
855 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
860 if( enforce_pcrud ) {
862 timeout_needs_resetting = 1;
863 const jsonObject* user = verifyUserPCRUD( ctx );
868 // Verify that a transaction is pending
869 const char* trans_id = getXactId( ctx );
870 if( NULL == trans_id ) {
871 osrfAppSessionStatus(
873 OSRF_STATUS_INTERNALSERVERERROR,
874 "osrfMethodException",
876 "No active transaction -- required for savepoints"
881 // Get the savepoint name from the method params
882 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
884 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
887 int errnum = dbi_conn_error( writehandle, &msg );
890 "%s: Error releasing savepoint %s in transaction %s: %d %s",
895 msg ? msg : "(No description available)"
897 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
898 "osrfMethodException", ctx->request, "Error releasing savepoint" );
899 if( !oilsIsDBConnected( writehandle ))
900 osrfAppSessionPanic( ctx->session );
903 dbi_result_free( result );
904 jsonObject* ret = jsonNewObject( spName );
905 osrfAppRespondComplete( ctx, ret );
906 jsonObjectFree( ret );
912 @brief Implement the savepoint.rollback method.
913 @param ctx Pointer to the method context.
914 @return Zero if successful, or -1 if not.
916 Issue a ROLLBACK TO SAVEPOINT to the database server.
919 - authkey (PCRUD only)
922 Return to client: Savepoint name
924 int rollbackSavepoint( osrfMethodContext* ctx ) {
925 if(osrfMethodVerifyContext( ctx )) {
926 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
931 if( enforce_pcrud ) {
933 timeout_needs_resetting = 1;
934 const jsonObject* user = verifyUserPCRUD( ctx );
939 // Verify that a transaction is pending
940 const char* trans_id = getXactId( ctx );
941 if( NULL == trans_id ) {
942 osrfAppSessionStatus(
944 OSRF_STATUS_INTERNALSERVERERROR,
945 "osrfMethodException",
947 "No active transaction -- required for savepoints"
952 // Get the savepoint name from the method params
953 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
955 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
958 int errnum = dbi_conn_error( writehandle, &msg );
961 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
966 msg ? msg : "(No description available)"
968 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
969 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
970 if( !oilsIsDBConnected( writehandle ))
971 osrfAppSessionPanic( ctx->session );
974 dbi_result_free( result );
975 jsonObject* ret = jsonNewObject( spName );
976 osrfAppRespondComplete( ctx, ret );
977 jsonObjectFree( ret );
983 @brief Implement the transaction.commit method.
984 @param ctx Pointer to the method context.
985 @return Zero if successful, or -1 if not.
987 Issue a COMMIT to the database server.
990 - authkey (PCRUD only)
992 Return to client: Transaction ID.
994 int commitTransaction( osrfMethodContext* ctx ) {
995 if(osrfMethodVerifyContext( ctx )) {
996 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1000 if( enforce_pcrud ) {
1001 timeout_needs_resetting = 1;
1002 const jsonObject* user = verifyUserPCRUD( ctx );
1007 // Verify that a transaction is pending
1008 const char* trans_id = getXactId( ctx );
1009 if( NULL == trans_id ) {
1010 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1011 "osrfMethodException", ctx->request, "No active transaction to commit" );
1015 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1018 int errnum = dbi_conn_error( writehandle, &msg );
1019 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1020 modulename, errnum, msg ? msg : "(No description available)" );
1021 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1022 "osrfMethodException", ctx->request, "Error committing transaction" );
1023 if( !oilsIsDBConnected( writehandle ))
1024 osrfAppSessionPanic( ctx->session );
1027 dbi_result_free( result );
1028 jsonObject* ret = jsonNewObject( trans_id );
1029 osrfAppRespondComplete( ctx, ret );
1030 jsonObjectFree( ret );
1037 @brief Implement the transaction.rollback method.
1038 @param ctx Pointer to the method context.
1039 @return Zero if successful, or -1 if not.
1041 Issue a ROLLBACK to the database server.
1044 - authkey (PCRUD only)
1046 Return to client: Transaction ID
1048 int rollbackTransaction( osrfMethodContext* ctx ) {
1049 if( osrfMethodVerifyContext( ctx )) {
1050 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1054 if( enforce_pcrud ) {
1055 timeout_needs_resetting = 1;
1056 const jsonObject* user = verifyUserPCRUD( ctx );
1061 // Verify that a transaction is pending
1062 const char* trans_id = getXactId( ctx );
1063 if( NULL == trans_id ) {
1064 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1065 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1069 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1072 int errnum = dbi_conn_error( writehandle, &msg );
1073 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1074 modulename, errnum, msg ? msg : "(No description available)" );
1075 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1076 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1077 if( !oilsIsDBConnected( writehandle ))
1078 osrfAppSessionPanic( ctx->session );
1081 dbi_result_free( result );
1082 jsonObject* ret = jsonNewObject( trans_id );
1083 osrfAppRespondComplete( ctx, ret );
1084 jsonObjectFree( ret );
1091 @brief Implement the "search" method.
1092 @param ctx Pointer to the method context.
1093 @return Zero if successful, or -1 if not.
1096 - authkey (PCRUD only)
1097 - WHERE clause, as jsonObject
1098 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1100 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1101 Optionally flesh linked fields.
1103 int doSearch( osrfMethodContext* ctx ) {
1104 if( osrfMethodVerifyContext( ctx )) {
1105 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1110 timeout_needs_resetting = 1;
1112 jsonObject* where_clause;
1113 jsonObject* rest_of_query;
1115 if( enforce_pcrud ) {
1116 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1117 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1119 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1120 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1123 // Get the class metadata
1124 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1125 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1129 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1131 osrfAppRespondComplete( ctx, NULL );
1135 // Return each row to the client (except that some may be suppressed by PCRUD)
1136 jsonObject* cur = 0;
1137 unsigned long res_idx = 0;
1138 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1139 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1141 osrfAppRespond( ctx, cur );
1143 jsonObjectFree( obj );
1145 osrfAppRespondComplete( ctx, NULL );
1150 @brief Implement the "id_list" method.
1151 @param ctx Pointer to the method context.
1152 @param err Pointer through which to return an error code.
1153 @return Zero if successful, or -1 if not.
1156 - authkey (PCRUD only)
1157 - WHERE clause, as jsonObject
1158 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1160 Return to client: The primary key values for all rows of the relevant class that
1161 satisfy a specified WHERE clause.
1163 This method relies on the assumption that every class has a primary key consisting of
1166 int doIdList( osrfMethodContext* ctx ) {
1167 if( osrfMethodVerifyContext( ctx )) {
1168 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1173 timeout_needs_resetting = 1;
1175 jsonObject* where_clause;
1176 jsonObject* rest_of_query;
1178 // We use the where clause without change. But we need to massage the rest of the
1179 // query, so we work with a copy of it instead of modifying the original.
1181 if( enforce_pcrud ) {
1182 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1183 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1185 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1186 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1189 // Eliminate certain SQL clauses, if present.
1190 if( rest_of_query ) {
1191 jsonObjectRemoveKey( rest_of_query, "select" );
1192 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1193 jsonObjectRemoveKey( rest_of_query, "flesh" );
1194 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1196 rest_of_query = jsonNewObjectType( JSON_HASH );
1199 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1201 // Get the class metadata
1202 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1203 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1205 // Build a SELECT list containing just the primary key,
1206 // i.e. like { "classname":["keyname"] }
1207 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1209 // Load array with name of primary key
1210 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1211 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1212 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1214 jsonObjectSetKey( rest_of_query, "select", select_clause );
1219 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1221 jsonObjectFree( rest_of_query );
1223 osrfAppRespondComplete( ctx, NULL );
1227 // Return each primary key value to the client
1229 unsigned long res_idx = 0;
1230 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1231 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1232 continue; // Suppress due to lack of permission
1234 osrfAppRespond( ctx,
1235 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1238 jsonObjectFree( obj );
1239 osrfAppRespondComplete( ctx, NULL );
1244 @brief Verify that we have a valid class reference.
1245 @param ctx Pointer to the method context.
1246 @param param Pointer to the method parameters.
1247 @return 1 if the class reference is valid, or zero if it isn't.
1249 The class of the method params must match the class to which the method id devoted.
1250 For PCRUD there are additional restrictions.
1252 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1254 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1255 osrfHash* class = osrfHashGet( method_meta, "class" );
1257 // Compare the method's class to the parameters' class
1258 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1260 // Oops -- they don't match. Complain.
1261 growing_buffer* msg = buffer_init( 128 );
1264 "%s: %s method for type %s was passed a %s",
1266 osrfHashGet( method_meta, "methodtype" ),
1267 osrfHashGet( class, "classname" ),
1268 param->classname ? param->classname : "(null)"
1271 char* m = buffer_release( msg );
1272 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1280 return verifyObjectPCRUD( ctx, param );
1286 @brief (PCRUD only) Verify that the user is properly logged in.
1287 @param ctx Pointer to the method context.
1288 @return If the user is logged in, a pointer to the user object from the authentication
1289 server; otherwise NULL.
1291 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1293 // Get the authkey (the first method parameter)
1294 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1296 // See if we have the same authkey, and a user object,
1297 // locally cached from a previous call
1298 const char* cached_authkey = getAuthkey( ctx );
1299 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1300 const jsonObject* cached_user = getUserLogin( ctx );
1305 // We have no matching authentication data in the cache. Authenticate from scratch.
1306 jsonObject* auth_object = jsonNewObject( auth );
1308 // Fetch the user object from the authentication server
1309 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1311 jsonObjectFree( auth_object );
1313 if( !user->classname || strcmp(user->classname, "au" )) {
1315 growing_buffer* msg = buffer_init( 128 );
1318 "%s: permacrud received a bad auth token: %s",
1323 char* m = buffer_release( msg );
1324 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1328 jsonObjectFree( user );
1332 setUserLogin( ctx, user );
1333 setAuthkey( ctx, auth );
1335 // Allow ourselves up to a second before we have to reset the login timeout.
1336 // It would be nice to use some fraction of the timeout interval enforced by the
1337 // authentication server, but that value is not readily available at this point.
1338 // Instead, we use a conservative default interval.
1339 time_next_reset = time( NULL ) + 1;
1345 @brief For PCRUD: Determine whether the current user may access the current row.
1346 @param ctx Pointer to the method context.
1347 @param obj Pointer to the row being potentially accessed.
1348 @return 1 if access is permitted, or 0 if it isn't.
1350 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1352 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1354 dbhandle = writehandle;
1356 // Figure out what class and method are involved
1357 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1358 osrfHash* class = osrfHashGet( method_metadata, "class" );
1359 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1361 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1362 // contexts we will do another lookup of the current row, even if we already have a
1363 // previously fetched row image, because the row image in hand may not include the
1364 // foreign key(s) that we need.
1366 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1367 // but they aren't implemented yet.
1370 if( *method_type == 's' || *method_type == 'i' ) {
1371 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1373 } else if( *method_type == 'u' || *method_type == 'd' ) {
1374 fetch = 1; // MUST go to the db for the object for update and delete
1377 // Get the appropriate permacrud entry from the IDL, depending on method type
1378 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1380 // No permacrud for this method type on this class
1382 growing_buffer* msg = buffer_init( 128 );
1385 "%s: %s on class %s has no permacrud IDL entry",
1387 osrfHashGet( method_metadata, "methodtype" ),
1388 osrfHashGet( class, "classname" )
1391 char* m = buffer_release( msg );
1392 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1393 "osrfMethodException", ctx->request, m );
1400 // Get the user id, and make sure the user is logged in
1401 const jsonObject* user = verifyUserPCRUD( ctx );
1403 return 0; // Not logged in? No access.
1405 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1407 // Get a list of permissions from the permacrud entry.
1408 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1409 if( permission->size == 0 ) {
1410 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1414 // Build a list of org units that own the row. This is fairly convoluted because there
1415 // are several different ways that an org unit may own the row, as defined by the
1418 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1419 // identifying an owning org_unit..
1420 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1422 // Foreign context adds a layer of indirection. The row points to some other row that
1423 // an org unit may own. The "jump" attribute, if present, adds another layer of
1425 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1427 // The following string array stores the list of org units. (We don't have a thingie
1428 // for storing lists of integers, so we fake it with a list of strings.)
1429 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1432 const char* pkey_value = NULL;
1433 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1434 // If the global_required attribute is present and true, then the only owning
1435 // org unit is the root org unit, i.e. the one with no parent.
1436 osrfLogDebug( OSRF_LOG_MARK,
1437 "global-level permissions required, fetching top of the org tree" );
1439 // check for perm at top of org tree
1440 const char* org_tree_root_id = org_tree_root( ctx );
1441 if( org_tree_root_id ) {
1442 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1443 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1445 osrfStringArrayFree( context_org_array );
1450 // If the global_required attribute is absent or false, then we look for
1451 // local and/or foreign context. In order to find the relevant foreign
1452 // keys, we must either read the relevant row from the database, or look at
1453 // the image of the row that we already have in memory.
1455 // Even if we have an image of the row in memory, that image may not include the
1456 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1457 // of the row to make sure that we have what we need.
1459 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1460 "fetching context org ids" );
1461 const char* pkey = osrfHashGet( class, "primarykey" );
1462 jsonObject *param = NULL;
1465 // There is no primary key, so we can't do a fresh lookup. Use the row
1466 // image that we already have. If it doesn't have everything we need, too bad.
1468 param = jsonObjectClone( obj );
1469 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1470 } else if( obj->classname ) {
1471 pkey_value = oilsFMGetStringConst( obj, pkey );
1473 param = jsonObjectClone( obj );
1474 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1477 pkey_value = jsonObjectGetString( obj );
1479 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1480 "of %s and retrieving from the database", pkey_value );
1484 // Fetch the row so that we can look at the foreign key(s)
1485 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1486 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1487 jsonObjectFree( _tmp_params );
1489 param = jsonObjectExtractIndex( _list, 0 );
1490 jsonObjectFree( _list );
1494 // The row doesn't exist. Complain, and deny access.
1495 osrfLogDebug( OSRF_LOG_MARK,
1496 "Object not found in the database with primary key %s of %s",
1499 growing_buffer* msg = buffer_init( 128 );
1502 "%s: no object found with primary key %s of %s",
1508 char* m = buffer_release( msg );
1509 osrfAppSessionStatus(
1511 OSRF_STATUS_INTERNALSERVERERROR,
1512 "osrfMethodException",
1521 if( local_context && local_context->size > 0 ) {
1522 // The IDL provides a list of column names for the foreign keys denoting
1523 // local context, i.e. columns identifying owing org units directly. Look up
1524 // the value of each one, and if it isn't null, add it to the list of org units.
1525 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1526 local_context->size );
1528 const char* lcontext = NULL;
1529 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1530 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1531 if( fkey_value ) { // if not null
1532 osrfStringArrayAdd( context_org_array, fkey_value );
1535 "adding class-local field %s (value: %s) to the context org list",
1537 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1543 if( foreign_context ) {
1544 unsigned long class_count = osrfHashGetCount( foreign_context );
1545 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1547 if( class_count > 0 ) {
1549 // The IDL provides a list of foreign key columns pointing to rows that
1550 // an org unit may own. Follow each link, identify the owning org unit,
1551 // and add it to the list.
1552 osrfHash* fcontext = NULL;
1553 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1554 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1555 // For each class to which a foreign key points:
1556 const char* class_name = osrfHashIteratorKey( class_itr );
1557 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1561 "%d foreign context fields(s) specified for class %s",
1562 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1566 // Get the name of the key field in the foreign table
1567 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1569 // Get the value of the foreign key pointing to the foreign table
1570 char* foreign_pkey_value =
1571 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1572 if( !foreign_pkey_value )
1573 continue; // Foreign key value is null; skip it
1575 // Look up the row to which the foreign key points
1576 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1577 jsonObject* _list = doFieldmapperSearch(
1578 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1580 jsonObject* _fparam = NULL;
1581 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1582 _fparam = jsonObjectExtractIndex( _list, 0 );
1584 jsonObjectFree( _tmp_params );
1585 jsonObjectFree( _list );
1587 // At this point _fparam either points to the row identified by the
1588 // foreign key, or it's NULL (no such row found).
1590 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1592 const char* bad_class = NULL; // For noting failed lookups
1594 bad_class = class_name; // Referenced row not found
1595 else if( jump_list ) {
1596 // Follow a chain of rows, linked by foreign keys, to find an owner
1597 const char* flink = NULL;
1599 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1600 // For each entry in the jump list. Each entry (i.e. flink) is
1601 // the name of a foreign key column in the current row.
1603 // From the IDL, get the linkage information for the next jump
1604 osrfHash* foreign_link_hash =
1605 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1607 // Get the class metadata for the class
1608 // to which the foreign key points
1609 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1610 osrfHashGet( foreign_link_hash, "class" ));
1612 // Get the name of the referenced key of that class
1613 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1615 // Get the value of the foreign key pointing to that class
1616 free( foreign_pkey_value );
1617 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1618 if( !foreign_pkey_value )
1619 break; // Foreign key is null; quit looking
1621 // Build a WHERE clause for the lookup
1622 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1625 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1626 _tmp_params, NULL, &err );
1628 // Get the resulting row
1629 jsonObjectFree( _fparam );
1630 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1631 _fparam = jsonObjectExtractIndex( _list, 0 );
1633 // Referenced row not found
1635 bad_class = osrfHashGet( foreign_link_hash, "class" );
1638 jsonObjectFree( _tmp_params );
1639 jsonObjectFree( _list );
1645 // We had a foreign key pointing to such-and-such a row, but then
1646 // we couldn't fetch that row. The data in the database are in an
1647 // inconsistent state; the database itself may even be corrupted.
1648 growing_buffer* msg = buffer_init( 128 );
1651 "%s: no object of class %s found with primary key %s of %s",
1655 foreign_pkey_value ? foreign_pkey_value : "(null)"
1658 char* m = buffer_release( msg );
1659 osrfAppSessionStatus(
1661 OSRF_STATUS_INTERNALSERVERERROR,
1662 "osrfMethodException",
1668 osrfHashIteratorFree( class_itr );
1669 free( foreign_pkey_value );
1670 jsonObjectFree( param );
1675 free( foreign_pkey_value );
1678 // Examine each context column of the foreign row,
1679 // and add its value to the list of org units.
1681 const char* foreign_field = NULL;
1682 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1683 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1684 osrfStringArrayAdd( context_org_array,
1685 oilsFMGetStringConst( _fparam, foreign_field ));
1686 osrfLogDebug( OSRF_LOG_MARK,
1687 "adding foreign class %s field %s (value: %s) "
1688 "to the context org list",
1691 osrfStringArrayGetString(
1692 context_org_array, context_org_array->size - 1 )
1696 jsonObjectFree( _fparam );
1700 osrfHashIteratorFree( class_itr );
1704 jsonObjectFree( param );
1707 const char* context_org = NULL;
1708 const char* perm = NULL;
1711 // For every combination of permission and context org unit: call a stored procedure
1712 // to determine if the user has this permission in the context of this org unit.
1713 // If the answer is yes at any point, then we're done, and the user has permission.
1714 // In other words permissions are additive.
1716 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1718 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1724 "Checking object permission [%s] for user %d "
1725 "on object %s (class %s) at org %d",
1729 osrfHashGet( class, "classname" ),
1733 result = dbi_conn_queryf(
1735 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1738 osrfHashGet( class, "classname" ),
1746 "Received a result for object permission [%s] "
1747 "for user %d on object %s (class %s) at org %d",
1751 osrfHashGet( class, "classname" ),
1755 if( dbi_result_first_row( result )) {
1756 jsonObject* return_val = oilsMakeJSONFromResult( result );
1757 const char* has_perm = jsonObjectGetString(
1758 jsonObjectGetKeyConst( return_val, "has_perm" ));
1762 "Status of object permission [%s] for user %d "
1763 "on object %s (class %s) at org %d is %s",
1767 osrfHashGet(class, "classname"),
1772 if( *has_perm == 't' )
1774 jsonObjectFree( return_val );
1777 dbi_result_free( result );
1782 int errnum = dbi_conn_error( writehandle, &msg );
1783 osrfLogWarning( OSRF_LOG_MARK,
1784 "Unable to call check object permissions: %d, %s",
1785 errnum, msg ? msg : "(No description available)" );
1786 if( !oilsIsDBConnected( writehandle ))
1787 osrfAppSessionPanic( ctx->session );
1791 osrfLogDebug( OSRF_LOG_MARK,
1792 "Checking non-object permission [%s] for user %d at org %d",
1793 perm, userid, atoi(context_org) );
1794 result = dbi_conn_queryf(
1796 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1803 osrfLogDebug( OSRF_LOG_MARK,
1804 "Received a result for permission [%s] for user %d at org %d",
1805 perm, userid, atoi( context_org ));
1806 if( dbi_result_first_row( result )) {
1807 jsonObject* return_val = oilsMakeJSONFromResult( result );
1808 const char* has_perm = jsonObjectGetString(
1809 jsonObjectGetKeyConst( return_val, "has_perm" ));
1810 osrfLogDebug( OSRF_LOG_MARK,
1811 "Status of permission [%s] for user %d at org %d is [%s]",
1812 perm, userid, atoi( context_org ), has_perm );
1813 if( *has_perm == 't' )
1815 jsonObjectFree( return_val );
1818 dbi_result_free( result );
1823 int errnum = dbi_conn_error( writehandle, &msg );
1824 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1825 errnum, msg ? msg : "(No description available)" );
1826 if( !oilsIsDBConnected( writehandle ))
1827 osrfAppSessionPanic( ctx->session );
1835 osrfStringArrayFree( context_org_array );
1841 @brief Look up the root of the org_unit tree.
1842 @param ctx Pointer to the method context.
1843 @return The id of the root org unit, as a character string.
1845 Query actor.org_unit where parent_ou is null, and return the id as a string.
1847 This function assumes that there is only one root org unit, i.e. that we
1848 have a single tree, not a forest.
1850 The calling code is responsible for freeing the returned string.
1852 static const char* org_tree_root( osrfMethodContext* ctx ) {
1854 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1855 static time_t last_lookup_time = 0;
1856 time_t current_time = time( NULL );
1858 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1859 // We successfully looked this up less than an hour ago.
1860 // It's not likely to have changed since then.
1861 return strdup( cached_root_id );
1863 last_lookup_time = current_time;
1866 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1867 jsonObject* result = doFieldmapperSearch(
1868 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1869 jsonObjectFree( where_clause );
1871 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1874 jsonObjectFree( result );
1876 growing_buffer* msg = buffer_init( 128 );
1877 OSRF_BUFFER_ADD( msg, modulename );
1878 OSRF_BUFFER_ADD( msg,
1879 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1881 char* m = buffer_release( msg );
1882 osrfAppSessionStatus( ctx->session,
1883 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1886 cached_root_id[ 0 ] = '\0';
1890 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1891 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1893 strcpy( cached_root_id, root_org_unit_id );
1894 jsonObjectFree( result );
1895 return cached_root_id;
1899 @brief Create a JSON_HASH with a single key/value pair.
1900 @param key The key of the key/value pair.
1901 @param value the value of the key/value pair.
1902 @return Pointer to a newly created jsonObject of type JSON_HASH.
1904 The value of the key/value is either a string or (if @a value is NULL) a null.
1906 static jsonObject* single_hash( const char* key, const char* value ) {
1908 if( ! key ) key = "";
1910 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1911 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1916 int doCreate( osrfMethodContext* ctx ) {
1917 if(osrfMethodVerifyContext( ctx )) {
1918 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1923 timeout_needs_resetting = 1;
1925 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1926 jsonObject* target = NULL;
1927 jsonObject* options = NULL;
1929 if( enforce_pcrud ) {
1930 target = jsonObjectGetIndex( ctx->params, 1 );
1931 options = jsonObjectGetIndex( ctx->params, 2 );
1933 target = jsonObjectGetIndex( ctx->params, 0 );
1934 options = jsonObjectGetIndex( ctx->params, 1 );
1937 if( !verifyObjectClass( ctx, target )) {
1938 osrfAppRespondComplete( ctx, NULL );
1942 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1944 const char* trans_id = getXactId( ctx );
1946 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1948 osrfAppSessionStatus(
1950 OSRF_STATUS_BADREQUEST,
1951 "osrfMethodException",
1953 "No active transaction -- required for CREATE"
1955 osrfAppRespondComplete( ctx, NULL );
1959 // The following test is harmless but redundant. If a class is
1960 // readonly, we don't register a create method for it.
1961 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1962 osrfAppSessionStatus(
1964 OSRF_STATUS_BADREQUEST,
1965 "osrfMethodException",
1967 "Cannot INSERT readonly class"
1969 osrfAppRespondComplete( ctx, NULL );
1973 // Set the last_xact_id
1974 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1976 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1977 trans_id, target->classname, index);
1978 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1981 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1983 dbhandle = writehandle;
1985 osrfHash* fields = osrfHashGet( meta, "fields" );
1986 char* pkey = osrfHashGet( meta, "primarykey" );
1987 char* seq = osrfHashGet( meta, "sequence" );
1989 growing_buffer* table_buf = buffer_init( 128 );
1990 growing_buffer* col_buf = buffer_init( 128 );
1991 growing_buffer* val_buf = buffer_init( 128 );
1993 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1994 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1995 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1996 buffer_add( val_buf,"VALUES (" );
2000 osrfHash* field = NULL;
2001 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2002 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2004 const char* field_name = osrfHashIteratorKey( field_itr );
2006 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2009 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2012 if( field_object && field_object->classname ) {
2013 value = oilsFMGetString(
2015 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2017 } else if( field_object && JSON_BOOL == field_object->type ) {
2018 if( jsonBoolIsTrue( field_object ) )
2019 value = strdup( "t" );
2021 value = strdup( "f" );
2023 value = jsonObjectToSimpleString( field_object );
2029 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2030 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2033 buffer_add( col_buf, field_name );
2035 if( !field_object || field_object->type == JSON_NULL ) {
2036 buffer_add( val_buf, "DEFAULT" );
2038 } else if( !strcmp( get_primitive( field ), "number" )) {
2039 const char* numtype = get_datatype( field );
2040 if( !strcmp( numtype, "INT8" )) {
2041 buffer_fadd( val_buf, "%lld", atoll( value ));
2043 } else if( !strcmp( numtype, "INT" )) {
2044 buffer_fadd( val_buf, "%d", atoi( value ));
2046 } else if( !strcmp( numtype, "NUMERIC" )) {
2047 buffer_fadd( val_buf, "%f", atof( value ));
2050 if( dbi_conn_quote_string( writehandle, &value )) {
2051 OSRF_BUFFER_ADD( val_buf, value );
2054 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2055 osrfAppSessionStatus(
2057 OSRF_STATUS_INTERNALSERVERERROR,
2058 "osrfMethodException",
2060 "Error quoting string -- please see the error log for more details"
2063 buffer_free( table_buf );
2064 buffer_free( col_buf );
2065 buffer_free( val_buf );
2066 osrfAppRespondComplete( ctx, NULL );
2074 osrfHashIteratorFree( field_itr );
2076 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2077 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2079 char* table_str = buffer_release( table_buf );
2080 char* col_str = buffer_release( col_buf );
2081 char* val_str = buffer_release( val_buf );
2082 growing_buffer* sql = buffer_init( 128 );
2083 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2088 char* query = buffer_release( sql );
2090 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2092 jsonObject* obj = NULL;
2095 dbi_result result = dbi_conn_query( writehandle, query );
2097 obj = jsonNewObject( NULL );
2099 int errnum = dbi_conn_error( writehandle, &msg );
2102 "%s ERROR inserting %s object using query [%s]: %d %s",
2104 osrfHashGet(meta, "fieldmapper"),
2107 msg ? msg : "(No description available)"
2109 osrfAppSessionStatus(
2111 OSRF_STATUS_INTERNALSERVERERROR,
2112 "osrfMethodException",
2114 "INSERT error -- please see the error log for more details"
2116 if( !oilsIsDBConnected( writehandle ))
2117 osrfAppSessionPanic( ctx->session );
2120 dbi_result_free( result );
2122 char* id = oilsFMGetString( target, pkey );
2124 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2125 growing_buffer* _id = buffer_init( 10 );
2126 buffer_fadd( _id, "%lld", new_id );
2127 id = buffer_release( _id );
2130 // Find quietness specification, if present
2131 const char* quiet_str = NULL;
2133 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2135 quiet_str = jsonObjectGetString( quiet_obj );
2138 if( str_is_true( quiet_str )) { // if quietness is specified
2139 obj = jsonNewObject( id );
2143 // Fetch the row that we just inserted, so that we can return it to the client
2144 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2145 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2148 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2152 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2154 jsonObjectFree( list );
2155 jsonObjectFree( where_clause );
2162 osrfAppRespondComplete( ctx, obj );
2163 jsonObjectFree( obj );
2168 @brief Implement the retrieve method.
2169 @param ctx Pointer to the method context.
2170 @param err Pointer through which to return an error code.
2171 @return If successful, a pointer to the result to be returned to the client;
2174 From the method's class, fetch a row with a specified value in the primary key. This
2175 method relies on the database design convention that a primary key consists of a single
2179 - authkey (PCRUD only)
2180 - value of the primary key for the desired row, for building the WHERE clause
2181 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2183 Return to client: One row from the query.
2185 int doRetrieve( osrfMethodContext* ctx ) {
2186 if(osrfMethodVerifyContext( ctx )) {
2187 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2192 timeout_needs_resetting = 1;
2197 if( enforce_pcrud ) {
2202 // Get the class metadata
2203 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2205 // Get the value of the primary key, from a method parameter
2206 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2210 "%s retrieving %s object with primary key value of %s",
2212 osrfHashGet( class_def, "fieldmapper" ),
2213 jsonObjectGetString( id_obj )
2216 // Build a WHERE clause based on the key value
2217 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2220 osrfHashGet( class_def, "primarykey" ), // name of key column
2221 jsonObjectClone( id_obj ) // value of key column
2224 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2228 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2230 jsonObjectFree( where_clause );
2232 osrfAppRespondComplete( ctx, NULL );
2236 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2237 jsonObjectFree( list );
2239 if( enforce_pcrud ) {
2240 if(!verifyObjectPCRUD( ctx, obj )) {
2241 jsonObjectFree( obj );
2243 growing_buffer* msg = buffer_init( 128 );
2244 OSRF_BUFFER_ADD( msg, modulename );
2245 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2247 char* m = buffer_release( msg );
2248 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2252 osrfAppRespondComplete( ctx, NULL );
2257 osrfAppRespondComplete( ctx, obj );
2258 jsonObjectFree( obj );
2263 @brief Translate a numeric value to a string representation for the database.
2264 @param field Pointer to the IDL field definition.
2265 @param value Pointer to a jsonObject holding the value of a field.
2266 @return Pointer to a newly allocated string.
2268 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2269 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2270 or (what is worse) valid SQL that is wrong.
2272 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2274 The calling code is responsible for freeing the resulting string by calling free().
2276 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2277 growing_buffer* val_buf = buffer_init( 32 );
2278 const char* numtype = get_datatype( field );
2280 // For historical reasons the following contains cruft that could be cleaned up.
2281 if( !strncmp( numtype, "INT", 3 ) ) {
2282 if( value->type == JSON_NUMBER )
2283 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2284 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2286 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2289 } else if( !strcmp( numtype, "NUMERIC" )) {
2290 if( value->type == JSON_NUMBER )
2291 buffer_fadd( val_buf, jsonObjectGetString( value ));
2293 buffer_fadd( val_buf, jsonObjectGetString( value ));
2297 // Presumably this was really intended to be a string, so quote it
2298 char* str = jsonObjectToSimpleString( value );
2299 if( dbi_conn_quote_string( dbhandle, &str )) {
2300 OSRF_BUFFER_ADD( val_buf, str );
2303 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2305 buffer_free( val_buf );
2310 return buffer_release( val_buf );
2313 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2314 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2315 growing_buffer* sql_buf = buffer_init( 32 );
2321 osrfHashGet( field, "name" )
2325 buffer_add( sql_buf, "IN (" );
2326 } else if( !strcasecmp( op,"not in" )) {
2327 buffer_add( sql_buf, "NOT IN (" );
2329 buffer_add( sql_buf, "IN (" );
2332 if( node->type == JSON_HASH ) {
2333 // subquery predicate
2334 char* subpred = buildQuery( ctx, node, SUBSELECT );
2336 buffer_free( sql_buf );
2340 buffer_add( sql_buf, subpred );
2343 } else if( node->type == JSON_ARRAY ) {
2344 // literal value list
2345 int in_item_index = 0;
2346 int in_item_first = 1;
2347 const jsonObject* in_item;
2348 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2353 buffer_add( sql_buf, ", " );
2356 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2357 osrfLogError( OSRF_LOG_MARK,
2358 "%s: Expected string or number within IN list; found %s",
2359 modulename, json_type( in_item->type ) );
2360 buffer_free( sql_buf );
2364 // Append the literal value -- quoted if not a number
2365 if( JSON_NUMBER == in_item->type ) {
2366 char* val = jsonNumberToDBString( field, in_item );
2367 OSRF_BUFFER_ADD( sql_buf, val );
2370 } else if( !strcmp( get_primitive( field ), "number" )) {
2371 char* val = jsonNumberToDBString( field, in_item );
2372 OSRF_BUFFER_ADD( sql_buf, val );
2376 char* key_string = jsonObjectToSimpleString( in_item );
2377 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2378 OSRF_BUFFER_ADD( sql_buf, key_string );
2381 osrfLogError( OSRF_LOG_MARK,
2382 "%s: Error quoting key string [%s]", modulename, key_string );
2384 buffer_free( sql_buf );
2390 if( in_item_first ) {
2391 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2392 buffer_free( sql_buf );
2396 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2397 modulename, json_type( node->type ));
2398 buffer_free( sql_buf );
2402 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2404 return buffer_release( sql_buf );
2407 // Receive a JSON_ARRAY representing a function call. The first
2408 // entry in the array is the function name. The rest are parameters.
2409 static char* searchValueTransform( const jsonObject* array ) {
2411 if( array->size < 1 ) {
2412 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2416 // Get the function name
2417 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2418 if( func_item->type != JSON_STRING ) {
2419 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2420 modulename, json_type( func_item->type ));
2424 growing_buffer* sql_buf = buffer_init( 32 );
2426 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2427 OSRF_BUFFER_ADD( sql_buf, "( " );
2429 // Get the parameters
2430 int func_item_index = 1; // We already grabbed the zeroth entry
2431 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2433 // Add a separator comma, if we need one
2434 if( func_item_index > 2 )
2435 buffer_add( sql_buf, ", " );
2437 // Add the current parameter
2438 if( func_item->type == JSON_NULL ) {
2439 buffer_add( sql_buf, "NULL" );
2441 char* val = jsonObjectToSimpleString( func_item );
2442 if( dbi_conn_quote_string( dbhandle, &val )) {
2443 OSRF_BUFFER_ADD( sql_buf, val );
2446 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2448 buffer_free( sql_buf );
2455 buffer_add( sql_buf, " )" );
2457 return buffer_release( sql_buf );
2460 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2461 const jsonObject* node, const char* op ) {
2463 if( ! is_good_operator( op ) ) {
2464 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2468 char* val = searchValueTransform( node );
2472 growing_buffer* sql_buf = buffer_init( 32 );
2477 osrfHashGet( field, "name" ),
2484 return buffer_release( sql_buf );
2487 // class_alias is a class name or other table alias
2488 // field is a field definition as stored in the IDL
2489 // node comes from the method parameter, and may represent an entry in the SELECT list
2490 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2491 const jsonObject* node ) {
2492 growing_buffer* sql_buf = buffer_init( 32 );
2494 const char* field_transform = jsonObjectGetString(
2495 jsonObjectGetKeyConst( node, "transform" ) );
2496 const char* transform_subcolumn = jsonObjectGetString(
2497 jsonObjectGetKeyConst( node, "result_field" ) );
2499 if( transform_subcolumn ) {
2500 if( ! is_identifier( transform_subcolumn ) ) {
2501 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2502 modulename, transform_subcolumn );
2503 buffer_free( sql_buf );
2506 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2509 if( field_transform ) {
2511 if( ! is_identifier( field_transform ) ) {
2512 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2513 modulename, field_transform );
2514 buffer_free( sql_buf );
2518 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2519 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2520 field_transform, class_alias, osrfHashGet( field, "name" ));
2522 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2523 field_transform, class_alias, osrfHashGet( field, "name" ));
2526 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2529 if( array->type != JSON_ARRAY ) {
2530 osrfLogError( OSRF_LOG_MARK,
2531 "%s: Expected JSON_ARRAY for function params; found %s",
2532 modulename, json_type( array->type ) );
2533 buffer_free( sql_buf );
2536 int func_item_index = 0;
2537 jsonObject* func_item;
2538 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2540 char* val = jsonObjectToSimpleString( func_item );
2543 buffer_add( sql_buf, ",NULL" );
2544 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2545 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2546 OSRF_BUFFER_ADD( sql_buf, val );
2548 osrfLogError( OSRF_LOG_MARK,
2549 "%s: Error quoting key string [%s]", modulename, val );
2551 buffer_free( sql_buf );
2558 buffer_add( sql_buf, " )" );
2561 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2564 if( transform_subcolumn )
2565 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2567 return buffer_release( sql_buf );
2570 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2571 const jsonObject* node, const char* op ) {
2573 if( ! is_good_operator( op ) ) {
2574 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2578 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2579 if( ! field_transform )
2582 int extra_parens = 0; // boolean
2584 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2586 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2588 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2590 free( field_transform );
2594 } else if( value_obj->type == JSON_ARRAY ) {
2595 value = searchValueTransform( value_obj );
2597 osrfLogError( OSRF_LOG_MARK,
2598 "%s: Error building value transform for field transform", modulename );
2599 free( field_transform );
2602 } else if( value_obj->type == JSON_HASH ) {
2603 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2605 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2607 free( field_transform );
2611 } else if( value_obj->type == JSON_NUMBER ) {
2612 value = jsonNumberToDBString( field, value_obj );
2613 } else if( value_obj->type == JSON_NULL ) {
2614 osrfLogError( OSRF_LOG_MARK,
2615 "%s: Error building predicate for field transform: null value", modulename );
2616 free( field_transform );
2618 } else if( value_obj->type == JSON_BOOL ) {
2619 osrfLogError( OSRF_LOG_MARK,
2620 "%s: Error building predicate for field transform: boolean value", modulename );
2621 free( field_transform );
2624 if( !strcmp( get_primitive( field ), "number") ) {
2625 value = jsonNumberToDBString( field, value_obj );
2627 value = jsonObjectToSimpleString( value_obj );
2628 if( !dbi_conn_quote_string( dbhandle, &value )) {
2629 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2630 modulename, value );
2632 free( field_transform );
2638 const char* left_parens = "";
2639 const char* right_parens = "";
2641 if( extra_parens ) {
2646 growing_buffer* sql_buf = buffer_init( 32 );
2650 "%s%s %s %s %s %s%s",
2661 free( field_transform );
2663 return buffer_release( sql_buf );
2666 static char* searchSimplePredicate( const char* op, const char* class_alias,
2667 osrfHash* field, const jsonObject* node ) {
2669 if( ! is_good_operator( op ) ) {
2670 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2676 // Get the value to which we are comparing the specified column
2677 if( node->type != JSON_NULL ) {
2678 if( node->type == JSON_NUMBER ) {
2679 val = jsonNumberToDBString( field, node );
2680 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2681 val = jsonNumberToDBString( field, node );
2683 val = jsonObjectToSimpleString( node );
2688 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2689 // Value is not numeric; enclose it in quotes
2690 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2691 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2698 // Compare to a null value
2699 val = strdup( "NULL" );
2700 if( strcmp( op, "=" ))
2706 growing_buffer* sql_buf = buffer_init( 32 );
2707 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2708 char* pred = buffer_release( sql_buf );
2715 static char* searchBETWEENPredicate( const char* class_alias,
2716 osrfHash* field, const jsonObject* node ) {
2718 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2719 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2721 if( NULL == y_node ) {
2722 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2725 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2726 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2733 if( !strcmp( get_primitive( field ), "number") ) {
2734 x_string = jsonNumberToDBString( field, x_node );
2735 y_string = jsonNumberToDBString( field, y_node );
2738 x_string = jsonObjectToSimpleString( x_node );
2739 y_string = jsonObjectToSimpleString( y_node );
2740 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2741 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2742 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2743 modulename, x_string, y_string );
2750 growing_buffer* sql_buf = buffer_init( 32 );
2751 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2752 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2756 return buffer_release( sql_buf );
2759 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2760 jsonObject* node, osrfMethodContext* ctx ) {
2763 if( node->type == JSON_ARRAY ) { // equality IN search
2764 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2765 } else if( node->type == JSON_HASH ) { // other search
2766 jsonIterator* pred_itr = jsonNewIterator( node );
2767 if( !jsonIteratorHasNext( pred_itr ) ) {
2768 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2769 modulename, osrfHashGet(field, "name" ));
2771 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2773 // Verify that there are no additional predicates
2774 if( jsonIteratorHasNext( pred_itr ) ) {
2775 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2776 modulename, osrfHashGet(field, "name" ));
2777 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2778 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2779 else if( !(strcasecmp( pred_itr->key,"in" ))
2780 || !(strcasecmp( pred_itr->key,"not in" )) )
2781 pred = searchINPredicate(
2782 class_info->alias, field, pred_node, pred_itr->key, ctx );
2783 else if( pred_node->type == JSON_ARRAY )
2784 pred = searchFunctionPredicate(
2785 class_info->alias, field, pred_node, pred_itr->key );
2786 else if( pred_node->type == JSON_HASH )
2787 pred = searchFieldTransformPredicate(
2788 class_info, field, pred_node, pred_itr->key );
2790 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2792 jsonIteratorFree( pred_itr );
2794 } else if( node->type == JSON_NULL ) { // IS NULL search
2795 growing_buffer* _p = buffer_init( 64 );
2798 "\"%s\".%s IS NULL",
2799 class_info->class_name,
2800 osrfHashGet( field, "name" )
2802 pred = buffer_release( _p );
2803 } else { // equality search
2804 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2823 field : call_number,
2839 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2841 const jsonObject* working_hash;
2842 jsonObject* freeable_hash = NULL;
2844 if( join_hash->type == JSON_HASH ) {
2845 working_hash = join_hash;
2846 } else if( join_hash->type == JSON_STRING ) {
2847 // turn it into a JSON_HASH by creating a wrapper
2848 // around a copy of the original
2849 const char* _tmp = jsonObjectGetString( join_hash );
2850 freeable_hash = jsonNewObjectType( JSON_HASH );
2851 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2852 working_hash = freeable_hash;
2856 "%s: JOIN failed; expected JSON object type not found",
2862 growing_buffer* join_buf = buffer_init( 128 );
2863 const char* leftclass = left_info->class_name;
2865 jsonObject* snode = NULL;
2866 jsonIterator* search_itr = jsonNewIterator( working_hash );
2868 while ( (snode = jsonIteratorNext( search_itr )) ) {
2869 const char* right_alias = search_itr->key;
2871 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2873 class = right_alias;
2875 const ClassInfo* right_info = add_joined_class( right_alias, class );
2879 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2883 jsonIteratorFree( search_itr );
2884 buffer_free( join_buf );
2886 jsonObjectFree( freeable_hash );
2889 osrfHash* links = right_info->links;
2890 const char* table = right_info->source_def;
2892 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2893 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2895 if( field && !fkey ) {
2896 // Look up the corresponding join column in the IDL.
2897 // The link must be defined in the child table,
2898 // and point to the right parent table.
2899 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2900 const char* reltype = NULL;
2901 const char* other_class = NULL;
2902 reltype = osrfHashGet( idl_link, "reltype" );
2903 if( reltype && strcmp( reltype, "has_many" ) )
2904 other_class = osrfHashGet( idl_link, "class" );
2905 if( other_class && !strcmp( other_class, leftclass ) )
2906 fkey = osrfHashGet( idl_link, "key" );
2910 "%s: JOIN failed. No link defined from %s.%s to %s",
2916 buffer_free( join_buf );
2918 jsonObjectFree( freeable_hash );
2919 jsonIteratorFree( search_itr );
2923 } else if( !field && fkey ) {
2924 // Look up the corresponding join column in the IDL.
2925 // The link must be defined in the child table,
2926 // and point to the right parent table.
2927 osrfHash* left_links = left_info->links;
2928 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2929 const char* reltype = NULL;
2930 const char* other_class = NULL;
2931 reltype = osrfHashGet( idl_link, "reltype" );
2932 if( reltype && strcmp( reltype, "has_many" ) )
2933 other_class = osrfHashGet( idl_link, "class" );
2934 if( other_class && !strcmp( other_class, class ) )
2935 field = osrfHashGet( idl_link, "key" );
2939 "%s: JOIN failed. No link defined from %s.%s to %s",
2945 buffer_free( join_buf );
2947 jsonObjectFree( freeable_hash );
2948 jsonIteratorFree( search_itr );
2952 } else if( !field && !fkey ) {
2953 osrfHash* left_links = left_info->links;
2955 // For each link defined for the left class:
2956 // see if the link references the joined class
2957 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2958 osrfHash* curr_link = NULL;
2959 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2960 const char* other_class = osrfHashGet( curr_link, "class" );
2961 if( other_class && !strcmp( other_class, class ) ) {
2963 // In the IDL, the parent class doesn't always know then names of the child
2964 // columns that are pointing to it, so don't use that end of the link
2965 const char* reltype = osrfHashGet( curr_link, "reltype" );
2966 if( reltype && strcmp( reltype, "has_many" ) ) {
2967 // Found a link between the classes
2968 fkey = osrfHashIteratorKey( itr );
2969 field = osrfHashGet( curr_link, "key" );
2974 osrfHashIteratorFree( itr );
2976 if( !field || !fkey ) {
2977 // Do another such search, with the classes reversed
2979 // For each link defined for the joined class:
2980 // see if the link references the left class
2981 osrfHashIterator* itr = osrfNewHashIterator( links );
2982 osrfHash* curr_link = NULL;
2983 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2984 const char* other_class = osrfHashGet( curr_link, "class" );
2985 if( other_class && !strcmp( other_class, leftclass ) ) {
2987 // In the IDL, the parent class doesn't know then names of the child
2988 // columns that are pointing to it, so don't use that end of the link
2989 const char* reltype = osrfHashGet( curr_link, "reltype" );
2990 if( reltype && strcmp( reltype, "has_many" ) ) {
2991 // Found a link between the classes
2992 field = osrfHashIteratorKey( itr );
2993 fkey = osrfHashGet( curr_link, "key" );
2998 osrfHashIteratorFree( itr );
3001 if( !field || !fkey ) {
3004 "%s: JOIN failed. No link defined between %s and %s",
3009 buffer_free( join_buf );
3011 jsonObjectFree( freeable_hash );
3012 jsonIteratorFree( search_itr );
3017 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3019 if( !strcasecmp( type,"left" )) {
3020 buffer_add( join_buf, " LEFT JOIN" );
3021 } else if( !strcasecmp( type,"right" )) {
3022 buffer_add( join_buf, " RIGHT JOIN" );
3023 } else if( !strcasecmp( type,"full" )) {
3024 buffer_add( join_buf, " FULL JOIN" );
3026 buffer_add( join_buf, " INNER JOIN" );
3029 buffer_add( join_buf, " INNER JOIN" );
3032 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3033 table, right_alias, right_alias, field, left_info->alias, fkey );
3035 // Add any other join conditions as specified by "filter"
3036 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3038 const char* filter_op = jsonObjectGetString(
3039 jsonObjectGetKeyConst( snode, "filter_op" ) );
3040 if( filter_op && !strcasecmp( "or",filter_op )) {
3041 buffer_add( join_buf, " OR " );
3043 buffer_add( join_buf, " AND " );
3046 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3048 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3049 OSRF_BUFFER_ADD( join_buf, jpred );
3054 "%s: JOIN failed. Invalid conditional expression.",
3057 jsonIteratorFree( search_itr );
3058 buffer_free( join_buf );
3060 jsonObjectFree( freeable_hash );
3065 buffer_add( join_buf, " ) " );
3067 // Recursively add a nested join, if one is present
3068 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3070 char* jpred = searchJOIN( join_filter, right_info );
3072 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3073 OSRF_BUFFER_ADD( join_buf, jpred );
3076 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3077 jsonIteratorFree( search_itr );
3078 buffer_free( join_buf );
3080 jsonObjectFree( freeable_hash );
3087 jsonObjectFree( freeable_hash );
3088 jsonIteratorFree( search_itr );
3090 return buffer_release( join_buf );
3095 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3096 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3097 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3099 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3101 search_hash is the JSON expression of the conditions.
3102 meta is the class definition from the IDL, for the relevant table.
3103 opjoin_type indicates whether multiple conditions, if present, should be
3104 connected by AND or OR.
3105 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3106 to pass it to other functions -- and all they do with it is to use the session
3107 and request members to send error messages back to the client.
3111 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3112 int opjoin_type, osrfMethodContext* ctx ) {
3116 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3117 "opjoin_type = %d, ctx addr = %p",
3120 class_info->class_def,
3125 growing_buffer* sql_buf = buffer_init( 128 );
3127 jsonObject* node = NULL;
3130 if( search_hash->type == JSON_ARRAY ) {
3131 if( 0 == search_hash->size ) {
3134 "%s: Invalid predicate structure: empty JSON array",
3137 buffer_free( sql_buf );
3141 unsigned long i = 0;
3142 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3146 if( opjoin_type == OR_OP_JOIN )
3147 buffer_add( sql_buf, " OR " );
3149 buffer_add( sql_buf, " AND " );
3152 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3154 buffer_free( sql_buf );
3158 buffer_fadd( sql_buf, "( %s )", subpred );
3162 } else if( search_hash->type == JSON_HASH ) {
3163 osrfLogDebug( OSRF_LOG_MARK,
3164 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3165 jsonIterator* search_itr = jsonNewIterator( search_hash );
3166 if( !jsonIteratorHasNext( search_itr ) ) {
3169 "%s: Invalid predicate structure: empty JSON object",
3172 jsonIteratorFree( search_itr );
3173 buffer_free( sql_buf );
3177 while( (node = jsonIteratorNext( search_itr )) ) {
3182 if( opjoin_type == OR_OP_JOIN )
3183 buffer_add( sql_buf, " OR " );
3185 buffer_add( sql_buf, " AND " );
3188 if( '+' == search_itr->key[ 0 ] ) {
3190 // This plus sign prefixes a class name or other table alias;
3191 // make sure the table alias is in scope
3192 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3193 if( ! alias_info ) {
3196 "%s: Invalid table alias \"%s\" in WHERE clause",
3200 jsonIteratorFree( search_itr );
3201 buffer_free( sql_buf );
3205 if( node->type == JSON_STRING ) {
3206 // It's the name of a column; make sure it belongs to the class
3207 const char* fieldname = jsonObjectGetString( node );
3208 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3211 "%s: Invalid column name \"%s\" in WHERE clause "
3212 "for table alias \"%s\"",
3217 jsonIteratorFree( search_itr );
3218 buffer_free( sql_buf );
3222 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3224 // It's something more complicated
3225 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3227 jsonIteratorFree( search_itr );
3228 buffer_free( sql_buf );
3232 buffer_fadd( sql_buf, "( %s )", subpred );
3235 } else if( '-' == search_itr->key[ 0 ] ) {
3236 if( !strcasecmp( "-or", search_itr->key )) {
3237 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3239 jsonIteratorFree( search_itr );
3240 buffer_free( sql_buf );
3244 buffer_fadd( sql_buf, "( %s )", subpred );
3246 } else if( !strcasecmp( "-and", search_itr->key )) {
3247 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3249 jsonIteratorFree( search_itr );
3250 buffer_free( sql_buf );
3254 buffer_fadd( sql_buf, "( %s )", subpred );
3256 } else if( !strcasecmp("-not",search_itr->key) ) {
3257 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3259 jsonIteratorFree( search_itr );
3260 buffer_free( sql_buf );
3264 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3266 } else if( !strcasecmp( "-exists", search_itr->key )) {
3267 char* subpred = buildQuery( ctx, node, SUBSELECT );
3269 jsonIteratorFree( search_itr );
3270 buffer_free( sql_buf );
3274 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3276 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3277 char* subpred = buildQuery( ctx, node, SUBSELECT );
3279 jsonIteratorFree( search_itr );
3280 buffer_free( sql_buf );
3284 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3286 } else { // Invalid "minus" operator
3289 "%s: Invalid operator \"%s\" in WHERE clause",
3293 jsonIteratorFree( search_itr );
3294 buffer_free( sql_buf );
3300 const char* class = class_info->class_name;
3301 osrfHash* fields = class_info->fields;
3302 osrfHash* field = osrfHashGet( fields, search_itr->key );
3305 const char* table = class_info->source_def;
3308 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3311 table ? table : "?",
3314 jsonIteratorFree( search_itr );
3315 buffer_free( sql_buf );
3319 char* subpred = searchPredicate( class_info, field, node, ctx );
3321 buffer_free( sql_buf );
3322 jsonIteratorFree( search_itr );
3326 buffer_add( sql_buf, subpred );
3330 jsonIteratorFree( search_itr );
3333 // ERROR ... only hash and array allowed at this level
3334 char* predicate_string = jsonObjectToJSON( search_hash );
3337 "%s: Invalid predicate structure: %s",
3341 buffer_free( sql_buf );
3342 free( predicate_string );
3346 return buffer_release( sql_buf );
3349 /* Build a JSON_ARRAY of field names for a given table alias
3351 static jsonObject* defaultSelectList( const char* table_alias ) {
3356 ClassInfo* class_info = search_all_alias( table_alias );
3357 if( ! class_info ) {
3360 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3367 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3368 osrfHash* field_def = NULL;
3369 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3370 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3371 const char* field_name = osrfHashIteratorKey( field_itr );
3372 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3373 jsonObjectPush( array, jsonNewObject( field_name ) );
3376 osrfHashIteratorFree( field_itr );
3381 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3382 // The jsonObject must be a JSON_HASH with an single entry for "union",
3383 // "intersect", or "except". The data associated with this key must be an
3384 // array of hashes, each hash being a query.
3385 // Also allowed but currently ignored: entries for "order_by" and "alias".
3386 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3388 if( ! combo || combo->type != JSON_HASH )
3389 return NULL; // should be impossible; validated by caller
3391 const jsonObject* query_array = NULL; // array of subordinate queries
3392 const char* op = NULL; // name of operator, e.g. UNION
3393 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3394 int op_count = 0; // for detecting conflicting operators
3395 int excepting = 0; // boolean
3396 int all = 0; // boolean
3397 jsonObject* order_obj = NULL;
3399 // Identify the elements in the hash
3400 jsonIterator* query_itr = jsonNewIterator( combo );
3401 jsonObject* curr_obj = NULL;
3402 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3403 if( ! strcmp( "union", query_itr->key ) ) {
3406 query_array = curr_obj;
3407 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3410 query_array = curr_obj;
3411 } else if( ! strcmp( "except", query_itr->key ) ) {
3415 query_array = curr_obj;
3416 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3419 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3422 order_obj = curr_obj;
3423 } else if( ! strcmp( "alias", query_itr->key ) ) {
3424 if( curr_obj->type != JSON_STRING ) {
3425 jsonIteratorFree( query_itr );
3428 alias = jsonObjectGetString( curr_obj );
3429 } else if( ! strcmp( "all", query_itr->key ) ) {
3430 if( obj_is_true( curr_obj ) )
3434 osrfAppSessionStatus(
3436 OSRF_STATUS_INTERNALSERVERERROR,
3437 "osrfMethodException",
3439 "Malformed query; unexpected entry in query object"
3443 "%s: Unexpected entry for \"%s\" in%squery",
3448 jsonIteratorFree( query_itr );
3452 jsonIteratorFree( query_itr );
3454 // More sanity checks
3455 if( ! query_array ) {
3457 osrfAppSessionStatus(
3459 OSRF_STATUS_INTERNALSERVERERROR,
3460 "osrfMethodException",
3462 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3466 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3469 return NULL; // should be impossible...
3470 } else if( op_count > 1 ) {
3472 osrfAppSessionStatus(
3474 OSRF_STATUS_INTERNALSERVERERROR,
3475 "osrfMethodException",
3477 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3481 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3485 } if( query_array->type != JSON_ARRAY ) {
3487 osrfAppSessionStatus(
3489 OSRF_STATUS_INTERNALSERVERERROR,
3490 "osrfMethodException",
3492 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3496 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3499 json_type( query_array->type )
3502 } if( query_array->size < 2 ) {
3504 osrfAppSessionStatus(
3506 OSRF_STATUS_INTERNALSERVERERROR,
3507 "osrfMethodException",
3509 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3513 "%s:%srequires multiple queries as operands",
3518 } else if( excepting && query_array->size > 2 ) {
3520 osrfAppSessionStatus(
3522 OSRF_STATUS_INTERNALSERVERERROR,
3523 "osrfMethodException",
3525 "EXCEPT operator has too many queries as operands"
3529 "%s:EXCEPT operator has too many queries as operands",
3533 } else if( order_obj && ! alias ) {
3535 osrfAppSessionStatus(
3537 OSRF_STATUS_INTERNALSERVERERROR,
3538 "osrfMethodException",
3540 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3544 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3550 // So far so good. Now build the SQL.
3551 growing_buffer* sql = buffer_init( 256 );
3553 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3554 // Add a layer of parentheses
3555 if( flags & SUBCOMBO )
3556 OSRF_BUFFER_ADD( sql, "( " );
3558 // Traverse the query array. Each entry should be a hash.
3559 int first = 1; // boolean
3561 jsonObject* query = NULL;
3562 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3563 if( query->type != JSON_HASH ) {
3565 osrfAppSessionStatus(
3567 OSRF_STATUS_INTERNALSERVERERROR,
3568 "osrfMethodException",
3570 "Malformed query under UNION, INTERSECT or EXCEPT"
3574 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3577 json_type( query->type )
3586 OSRF_BUFFER_ADD( sql, op );
3588 OSRF_BUFFER_ADD( sql, "ALL " );
3591 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3595 "%s: Error building query under%s",
3603 OSRF_BUFFER_ADD( sql, query_str );
3606 if( flags & SUBCOMBO )
3607 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3609 if( !(flags & SUBSELECT) )
3610 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3612 return buffer_release( sql );
3615 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3616 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3617 // or "except" to indicate the type of query.
3618 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3622 osrfAppSessionStatus(
3624 OSRF_STATUS_INTERNALSERVERERROR,
3625 "osrfMethodException",
3627 "Malformed query; no query object"
3629 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3631 } else if( query->type != JSON_HASH ) {
3633 osrfAppSessionStatus(
3635 OSRF_STATUS_INTERNALSERVERERROR,
3636 "osrfMethodException",
3638 "Malformed query object"
3642 "%s: Query object is %s instead of JSON_HASH",
3644 json_type( query->type )
3649 // Determine what kind of query it purports to be, and dispatch accordingly.
3650 if( jsonObjectGetKeyConst( query, "union" ) ||
3651 jsonObjectGetKeyConst( query, "intersect" ) ||
3652 jsonObjectGetKeyConst( query, "except" )) {
3653 return doCombo( ctx, query, flags );
3655 // It is presumably a SELECT query
3657 // Push a node onto the stack for the current query. Every level of
3658 // subquery gets its own QueryFrame on the Stack.
3661 // Build an SQL SELECT statement
3664 jsonObjectGetKey( query, "select" ),
3665 jsonObjectGetKeyConst( query, "from" ),
3666 jsonObjectGetKeyConst( query, "where" ),
3667 jsonObjectGetKeyConst( query, "having" ),
3668 jsonObjectGetKeyConst( query, "order_by" ),
3669 jsonObjectGetKeyConst( query, "limit" ),
3670 jsonObjectGetKeyConst( query, "offset" ),
3679 /* method context */ osrfMethodContext* ctx,
3681 /* SELECT */ jsonObject* selhash,
3682 /* FROM */ const jsonObject* join_hash,
3683 /* WHERE */ const jsonObject* search_hash,
3684 /* HAVING */ const jsonObject* having_hash,
3685 /* ORDER BY */ const jsonObject* order_hash,
3686 /* LIMIT */ const jsonObject* limit,
3687 /* OFFSET */ const jsonObject* offset,
3688 /* flags */ int flags
3690 const char* locale = osrf_message_get_last_locale();
3692 // general tmp objects
3693 const jsonObject* tmp_const;
3694 jsonObject* selclass = NULL;
3695 jsonObject* snode = NULL;
3696 jsonObject* onode = NULL;
3698 char* string = NULL;
3699 int from_function = 0;
3704 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3706 // punt if there's no FROM clause
3707 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3710 "%s: FROM clause is missing or empty",
3714 osrfAppSessionStatus(
3716 OSRF_STATUS_INTERNALSERVERERROR,
3717 "osrfMethodException",
3719 "FROM clause is missing or empty in JSON query"
3724 // the core search class
3725 const char* core_class = NULL;
3727 // get the core class -- the only key of the top level FROM clause, or a string
3728 if( join_hash->type == JSON_HASH ) {
3729 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3730 snode = jsonIteratorNext( tmp_itr );
3732 // Populate the current QueryFrame with information
3733 // about the core class
3734 if( add_query_core( NULL, tmp_itr->key ) ) {
3736 osrfAppSessionStatus(
3738 OSRF_STATUS_INTERNALSERVERERROR,
3739 "osrfMethodException",
3741 "Unable to look up core class"
3745 core_class = curr_query->core.class_name;
3748 jsonObject* extra = jsonIteratorNext( tmp_itr );
3750 jsonIteratorFree( tmp_itr );
3753 // There shouldn't be more than one entry in join_hash
3757 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3761 osrfAppSessionStatus(
3763 OSRF_STATUS_INTERNALSERVERERROR,
3764 "osrfMethodException",
3766 "Malformed FROM clause in JSON query"
3768 return NULL; // Malformed join_hash; extra entry
3770 } else if( join_hash->type == JSON_ARRAY ) {
3771 // We're selecting from a function, not from a table
3773 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3776 } else if( join_hash->type == JSON_STRING ) {
3777 // Populate the current QueryFrame with information
3778 // about the core class
3779 core_class = jsonObjectGetString( join_hash );
3781 if( add_query_core( NULL, core_class ) ) {
3783 osrfAppSessionStatus(
3785 OSRF_STATUS_INTERNALSERVERERROR,
3786 "osrfMethodException",
3788 "Unable to look up core class"
3796 "%s: FROM clause is unexpected JSON type: %s",
3798 json_type( join_hash->type )
3801 osrfAppSessionStatus(
3803 OSRF_STATUS_INTERNALSERVERERROR,
3804 "osrfMethodException",
3806 "Ill-formed FROM clause in JSON query"
3811 // Build the join clause, if any, while filling out the list
3812 // of joined classes in the current QueryFrame.
3813 char* join_clause = NULL;
3814 if( join_hash && ! from_function ) {
3816 join_clause = searchJOIN( join_hash, &curr_query->core );
3817 if( ! join_clause ) {
3819 osrfAppSessionStatus(
3821 OSRF_STATUS_INTERNALSERVERERROR,
3822 "osrfMethodException",
3824 "Unable to construct JOIN clause(s)"
3830 // For in case we don't get a select list
3831 jsonObject* defaultselhash = NULL;
3833 // if there is no select list, build a default select list ...
3834 if( !selhash && !from_function ) {
3835 jsonObject* default_list = defaultSelectList( core_class );
3836 if( ! default_list ) {
3838 osrfAppSessionStatus(
3840 OSRF_STATUS_INTERNALSERVERERROR,
3841 "osrfMethodException",
3843 "Unable to build default SELECT clause in JSON query"
3845 free( join_clause );
3850 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3851 jsonObjectSetKey( selhash, core_class, default_list );
3854 // The SELECT clause can be encoded only by a hash
3855 if( !from_function && selhash->type != JSON_HASH ) {
3858 "%s: Expected JSON_HASH for SELECT clause; found %s",
3860 json_type( selhash->type )
3864 osrfAppSessionStatus(
3866 OSRF_STATUS_INTERNALSERVERERROR,
3867 "osrfMethodException",
3869 "Malformed SELECT clause in JSON query"
3871 free( join_clause );
3875 // If you see a null or wild card specifier for the core class, or an
3876 // empty array, replace it with a default SELECT list
3877 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3879 int default_needed = 0; // boolean
3880 if( JSON_STRING == tmp_const->type
3881 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3883 else if( JSON_NULL == tmp_const->type )
3886 if( default_needed ) {
3887 // Build a default SELECT list
3888 jsonObject* default_list = defaultSelectList( core_class );
3889 if( ! default_list ) {
3891 osrfAppSessionStatus(
3893 OSRF_STATUS_INTERNALSERVERERROR,
3894 "osrfMethodException",
3896 "Can't build default SELECT clause in JSON query"
3898 free( join_clause );
3903 jsonObjectSetKey( selhash, core_class, default_list );
3907 // temp buffers for the SELECT list and GROUP BY clause
3908 growing_buffer* select_buf = buffer_init( 128 );
3909 growing_buffer* group_buf = buffer_init( 128 );
3911 int aggregate_found = 0; // boolean
3913 // Build a select list
3914 if( from_function ) // From a function we select everything
3915 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3918 // Build the SELECT list as SQL
3922 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3923 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3925 const char* cname = selclass_itr->key;
3927 // Make sure the target relation is in the FROM clause.
3929 // At this point join_hash is a step down from the join_hash we
3930 // received as a parameter. If the original was a JSON_STRING,
3931 // then json_hash is now NULL. If the original was a JSON_HASH,
3932 // then json_hash is now the first (and only) entry in it,
3933 // denoting the core class. We've already excluded the
3934 // possibility that the original was a JSON_ARRAY, because in
3935 // that case from_function would be non-NULL, and we wouldn't
3938 // If the current table alias isn't in scope, bail out
3939 ClassInfo* class_info = search_alias( cname );
3940 if( ! class_info ) {
3943 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3948 osrfAppSessionStatus(
3950 OSRF_STATUS_INTERNALSERVERERROR,
3951 "osrfMethodException",
3953 "Selected class not in FROM clause in JSON query"
3955 jsonIteratorFree( selclass_itr );
3956 buffer_free( select_buf );
3957 buffer_free( group_buf );
3958 if( defaultselhash )
3959 jsonObjectFree( defaultselhash );
3960 free( join_clause );
3964 if( selclass->type != JSON_ARRAY ) {
3967 "%s: Malformed SELECT list for class \"%s\"; not an array",
3972 osrfAppSessionStatus(
3974 OSRF_STATUS_INTERNALSERVERERROR,
3975 "osrfMethodException",
3977 "Selected class not in FROM clause in JSON query"
3980 jsonIteratorFree( selclass_itr );
3981 buffer_free( select_buf );
3982 buffer_free( group_buf );
3983 if( defaultselhash )
3984 jsonObjectFree( defaultselhash );
3985 free( join_clause );
3989 // Look up some attributes of the current class
3990 osrfHash* idlClass = class_info->class_def;
3991 osrfHash* class_field_set = class_info->fields;
3992 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3993 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3995 if( 0 == selclass->size ) {
3998 "%s: No columns selected from \"%s\"",
4004 // stitch together the column list for the current table alias...
4005 unsigned long field_idx = 0;
4006 jsonObject* selfield = NULL;
4007 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4009 // If we need a separator comma, add one
4013 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4016 // if the field specification is a string, add it to the list
4017 if( selfield->type == JSON_STRING ) {
4019 // Look up the field in the IDL
4020 const char* col_name = jsonObjectGetString( selfield );
4021 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4023 // No such field in current class
4026 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4032 osrfAppSessionStatus(
4034 OSRF_STATUS_INTERNALSERVERERROR,
4035 "osrfMethodException",
4037 "Selected column not defined in JSON query"
4039 jsonIteratorFree( selclass_itr );
4040 buffer_free( select_buf );
4041 buffer_free( group_buf );
4042 if( defaultselhash )
4043 jsonObjectFree( defaultselhash );
4044 free( join_clause );
4046 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4047 // Virtual field not allowed
4050 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4056 osrfAppSessionStatus(
4058 OSRF_STATUS_INTERNALSERVERERROR,
4059 "osrfMethodException",
4061 "Selected column may not be virtual in JSON query"
4063 jsonIteratorFree( selclass_itr );
4064 buffer_free( select_buf );
4065 buffer_free( group_buf );
4066 if( defaultselhash )
4067 jsonObjectFree( defaultselhash );
4068 free( join_clause );
4074 if( flags & DISABLE_I18N )
4077 i18n = osrfHashGet( field_def, "i18n" );
4079 if( str_is_true( i18n ) ) {
4080 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4081 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4082 class_tname, cname, col_name, class_pkey,
4083 cname, class_pkey, locale, col_name );
4085 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4086 cname, col_name, col_name );
4089 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4090 cname, col_name, col_name );
4093 // ... but it could be an object, in which case we check for a Field Transform
4094 } else if( selfield->type == JSON_HASH ) {
4096 const char* col_name = jsonObjectGetString(
4097 jsonObjectGetKeyConst( selfield, "column" ) );
4099 // Get the field definition from the IDL
4100 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4102 // No such field in current class
4105 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4111 osrfAppSessionStatus(
4113 OSRF_STATUS_INTERNALSERVERERROR,
4114 "osrfMethodException",
4116 "Selected column is not defined in JSON query"
4118 jsonIteratorFree( selclass_itr );
4119 buffer_free( select_buf );
4120 buffer_free( group_buf );
4121 if( defaultselhash )
4122 jsonObjectFree( defaultselhash );
4123 free( join_clause );
4125 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4126 // No such field in current class
4129 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4135 osrfAppSessionStatus(
4137 OSRF_STATUS_INTERNALSERVERERROR,
4138 "osrfMethodException",
4140 "Selected column is virtual in JSON query"
4142 jsonIteratorFree( selclass_itr );
4143 buffer_free( select_buf );
4144 buffer_free( group_buf );
4145 if( defaultselhash )
4146 jsonObjectFree( defaultselhash );
4147 free( join_clause );
4151 // Decide what to use as a column alias
4153 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4154 _alias = jsonObjectGetString( tmp_const );
4155 } else { // Use field name as the alias
4159 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4160 char* transform_str = searchFieldTransform(
4161 class_info->alias, field_def, selfield );
4162 if( transform_str ) {
4163 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4164 free( transform_str );
4167 osrfAppSessionStatus(
4169 OSRF_STATUS_INTERNALSERVERERROR,
4170 "osrfMethodException",
4172 "Unable to generate transform function in JSON query"
4174 jsonIteratorFree( selclass_itr );
4175 buffer_free( select_buf );
4176 buffer_free( group_buf );
4177 if( defaultselhash )
4178 jsonObjectFree( defaultselhash );
4179 free( join_clause );
4186 if( flags & DISABLE_I18N )
4189 i18n = osrfHashGet( field_def, "i18n" );
4191 if( str_is_true( i18n ) ) {
4192 buffer_fadd( select_buf,
4193 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4194 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4195 class_tname, cname, col_name, class_pkey, cname,
4196 class_pkey, locale, _alias );
4198 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4199 cname, col_name, _alias );
4202 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4203 cname, col_name, _alias );
4210 "%s: Selected item is unexpected JSON type: %s",
4212 json_type( selfield->type )
4215 osrfAppSessionStatus(
4217 OSRF_STATUS_INTERNALSERVERERROR,
4218 "osrfMethodException",
4220 "Ill-formed SELECT item in JSON query"
4222 jsonIteratorFree( selclass_itr );
4223 buffer_free( select_buf );
4224 buffer_free( group_buf );
4225 if( defaultselhash )
4226 jsonObjectFree( defaultselhash );
4227 free( join_clause );
4231 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4232 if( obj_is_true( agg_obj ) )
4233 aggregate_found = 1;
4235 // Append a comma (except for the first one)
4236 // and add the column to a GROUP BY clause
4240 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4242 buffer_fadd( group_buf, " %d", sel_pos );
4246 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4248 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4249 if ( ! obj_is_true( aggregate_obj ) ) {
4253 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4256 buffer_fadd(group_buf, " %d", sel_pos);
4259 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4263 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4266 _column = searchFieldTransform(class_info->alias, field, selfield);
4267 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4268 OSRF_BUFFER_ADD(group_buf, _column);
4269 _column = searchFieldTransform(class_info->alias, field, selfield);
4276 } // end while -- iterating across SELECT columns
4278 } // end while -- iterating across classes
4280 jsonIteratorFree( selclass_itr );
4283 char* col_list = buffer_release( select_buf );
4285 // Make sure the SELECT list isn't empty. This can happen, for example,
4286 // if we try to build a default SELECT clause from a non-core table.
4289 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4291 osrfAppSessionStatus(
4293 OSRF_STATUS_INTERNALSERVERERROR,
4294 "osrfMethodException",
4296 "SELECT list is empty"
4299 buffer_free( group_buf );
4300 if( defaultselhash )
4301 jsonObjectFree( defaultselhash );
4302 free( join_clause );
4308 table = searchValueTransform( join_hash );
4310 table = strdup( curr_query->core.source_def );
4314 osrfAppSessionStatus(
4316 OSRF_STATUS_INTERNALSERVERERROR,
4317 "osrfMethodException",
4319 "Unable to identify table for core class"
4322 buffer_free( group_buf );
4323 if( defaultselhash )
4324 jsonObjectFree( defaultselhash );
4325 free( join_clause );
4329 // Put it all together
4330 growing_buffer* sql_buf = buffer_init( 128 );
4331 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4335 // Append the join clause, if any
4337 buffer_add(sql_buf, join_clause );
4338 free( join_clause );
4341 char* order_by_list = NULL;
4342 char* having_buf = NULL;
4344 if( !from_function ) {
4346 // Build a WHERE clause, if there is one
4348 buffer_add( sql_buf, " WHERE " );
4350 // and it's on the WHERE clause
4351 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4354 osrfAppSessionStatus(
4356 OSRF_STATUS_INTERNALSERVERERROR,
4357 "osrfMethodException",
4359 "Severe query error in WHERE predicate -- see error log for more details"
4362 buffer_free( group_buf );
4363 buffer_free( sql_buf );
4364 if( defaultselhash )
4365 jsonObjectFree( defaultselhash );
4369 buffer_add( sql_buf, pred );
4373 // Build a HAVING clause, if there is one
4376 // and it's on the the WHERE clause
4377 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4379 if( ! having_buf ) {
4381 osrfAppSessionStatus(
4383 OSRF_STATUS_INTERNALSERVERERROR,
4384 "osrfMethodException",
4386 "Severe query error in HAVING predicate -- see error log for more details"
4389 buffer_free( group_buf );
4390 buffer_free( sql_buf );
4391 if( defaultselhash )
4392 jsonObjectFree( defaultselhash );
4397 // Build an ORDER BY clause, if there is one
4398 if( NULL == order_hash )
4399 ; // No ORDER BY? do nothing
4400 else if( JSON_ARRAY == order_hash->type ) {
4401 order_by_list = buildOrderByFromArray( ctx, order_hash );
4402 if( !order_by_list ) {
4404 buffer_free( group_buf );
4405 buffer_free( sql_buf );
4406 if( defaultselhash )
4407 jsonObjectFree( defaultselhash );
4410 } else if( JSON_HASH == order_hash->type ) {
4411 // This hash is keyed on class alias. Each class has either
4412 // an array of field names or a hash keyed on field name.
4413 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4414 jsonIterator* class_itr = jsonNewIterator( order_hash );
4415 while( (snode = jsonIteratorNext( class_itr )) ) {
4417 ClassInfo* order_class_info = search_alias( class_itr->key );
4418 if( ! order_class_info ) {
4419 osrfLogError( OSRF_LOG_MARK,
4420 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4421 modulename, class_itr->key );
4423 osrfAppSessionStatus(
4425 OSRF_STATUS_INTERNALSERVERERROR,
4426 "osrfMethodException",
4428 "Invalid class referenced in ORDER BY clause -- "
4429 "see error log for more details"
4431 jsonIteratorFree( class_itr );
4432 buffer_free( order_buf );
4434 buffer_free( group_buf );
4435 buffer_free( sql_buf );
4436 if( defaultselhash )
4437 jsonObjectFree( defaultselhash );
4441 osrfHash* field_list_def = order_class_info->fields;
4443 if( snode->type == JSON_HASH ) {
4445 // Hash is keyed on field names from the current class. For each field
4446 // there is another layer of hash to define the sorting details, if any,
4447 // or a string to indicate direction of sorting.
4448 jsonIterator* order_itr = jsonNewIterator( snode );
4449 while( (onode = jsonIteratorNext( order_itr )) ) {
4451 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4453 osrfLogError( OSRF_LOG_MARK,
4454 "%s: Invalid field \"%s\" in ORDER BY clause",
4455 modulename, order_itr->key );
4457 osrfAppSessionStatus(
4459 OSRF_STATUS_INTERNALSERVERERROR,
4460 "osrfMethodException",
4462 "Invalid field in ORDER BY clause -- "
4463 "see error log for more details"
4465 jsonIteratorFree( order_itr );
4466 jsonIteratorFree( class_itr );
4467 buffer_free( order_buf );
4469 buffer_free( group_buf );
4470 buffer_free( sql_buf );
4471 if( defaultselhash )
4472 jsonObjectFree( defaultselhash );
4474 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4475 osrfLogError( OSRF_LOG_MARK,
4476 "%s: Virtual field \"%s\" in ORDER BY clause",
4477 modulename, order_itr->key );
4479 osrfAppSessionStatus(
4481 OSRF_STATUS_INTERNALSERVERERROR,
4482 "osrfMethodException",
4484 "Virtual field in ORDER BY clause -- "
4485 "see error log for more details"
4487 jsonIteratorFree( order_itr );
4488 jsonIteratorFree( class_itr );
4489 buffer_free( order_buf );
4491 buffer_free( group_buf );
4492 buffer_free( sql_buf );
4493 if( defaultselhash )
4494 jsonObjectFree( defaultselhash );
4498 const char* direction = NULL;
4499 if( onode->type == JSON_HASH ) {
4500 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4501 string = searchFieldTransform(
4503 osrfHashGet( field_list_def, order_itr->key ),
4507 if( ctx ) osrfAppSessionStatus(
4509 OSRF_STATUS_INTERNALSERVERERROR,
4510 "osrfMethodException",
4512 "Severe query error in ORDER BY clause -- "
4513 "see error log for more details"
4515 jsonIteratorFree( order_itr );
4516 jsonIteratorFree( class_itr );
4518 buffer_free( group_buf );
4519 buffer_free( order_buf);
4520 buffer_free( sql_buf );
4521 if( defaultselhash )
4522 jsonObjectFree( defaultselhash );
4526 growing_buffer* field_buf = buffer_init( 16 );
4527 buffer_fadd( field_buf, "\"%s\".%s",
4528 class_itr->key, order_itr->key );
4529 string = buffer_release( field_buf );
4532 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4533 const char* dir = jsonObjectGetString( tmp_const );
4534 if(!strncasecmp( dir, "d", 1 )) {
4535 direction = " DESC";
4541 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4542 osrfLogError( OSRF_LOG_MARK,
4543 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4544 modulename, json_type( onode->type ) );
4546 osrfAppSessionStatus(
4548 OSRF_STATUS_INTERNALSERVERERROR,
4549 "osrfMethodException",
4551 "Malformed ORDER BY clause -- see error log for more details"
4553 jsonIteratorFree( order_itr );
4554 jsonIteratorFree( class_itr );
4556 buffer_free( group_buf );
4557 buffer_free( order_buf );
4558 buffer_free( sql_buf );
4559 if( defaultselhash )
4560 jsonObjectFree( defaultselhash );
4564 string = strdup( order_itr->key );
4565 const char* dir = jsonObjectGetString( onode );
4566 if( !strncasecmp( dir, "d", 1 )) {
4567 direction = " DESC";
4574 OSRF_BUFFER_ADD( order_buf, ", " );
4576 order_buf = buffer_init( 128 );
4578 OSRF_BUFFER_ADD( order_buf, string );
4582 OSRF_BUFFER_ADD( order_buf, direction );
4586 jsonIteratorFree( order_itr );
4588 } else if( snode->type == JSON_ARRAY ) {
4590 // Array is a list of fields from the current class
4591 unsigned long order_idx = 0;
4592 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4594 const char* _f = jsonObjectGetString( onode );
4596 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4598 osrfLogError( OSRF_LOG_MARK,
4599 "%s: Invalid field \"%s\" in ORDER BY clause",
4602 osrfAppSessionStatus(
4604 OSRF_STATUS_INTERNALSERVERERROR,
4605 "osrfMethodException",
4607 "Invalid field in ORDER BY clause -- "
4608 "see error log for more details"
4610 jsonIteratorFree( class_itr );
4611 buffer_free( order_buf );
4613 buffer_free( group_buf );
4614 buffer_free( sql_buf );
4615 if( defaultselhash )
4616 jsonObjectFree( defaultselhash );
4618 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4619 osrfLogError( OSRF_LOG_MARK,
4620 "%s: Virtual field \"%s\" in ORDER BY clause",
4623 osrfAppSessionStatus(
4625 OSRF_STATUS_INTERNALSERVERERROR,
4626 "osrfMethodException",
4628 "Virtual field in ORDER BY clause -- "
4629 "see error log for more details"
4631 jsonIteratorFree( class_itr );
4632 buffer_free( order_buf );
4634 buffer_free( group_buf );
4635 buffer_free( sql_buf );
4636 if( defaultselhash )
4637 jsonObjectFree( defaultselhash );
4642 OSRF_BUFFER_ADD( order_buf, ", " );
4644 order_buf = buffer_init( 128 );
4646 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4650 // IT'S THE OOOOOOOOOOOLD STYLE!
4652 osrfLogError( OSRF_LOG_MARK,
4653 "%s: Possible SQL injection attempt; direct order by is not allowed",
4656 osrfAppSessionStatus(
4658 OSRF_STATUS_INTERNALSERVERERROR,
4659 "osrfMethodException",
4661 "Severe query error -- see error log for more details"
4666 buffer_free( group_buf );
4667 buffer_free( order_buf );
4668 buffer_free( sql_buf );
4669 if( defaultselhash )
4670 jsonObjectFree( defaultselhash );
4671 jsonIteratorFree( class_itr );
4675 jsonIteratorFree( class_itr );
4677 order_by_list = buffer_release( order_buf );
4679 osrfLogError( OSRF_LOG_MARK,
4680 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4681 modulename, json_type( order_hash->type ) );
4683 osrfAppSessionStatus(
4685 OSRF_STATUS_INTERNALSERVERERROR,
4686 "osrfMethodException",
4688 "Malformed ORDER BY clause -- see error log for more details"
4691 buffer_free( group_buf );
4692 buffer_free( sql_buf );
4693 if( defaultselhash )
4694 jsonObjectFree( defaultselhash );
4699 string = buffer_release( group_buf );
4701 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4702 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4703 OSRF_BUFFER_ADD( sql_buf, string );
4708 if( having_buf && *having_buf ) {
4709 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4710 OSRF_BUFFER_ADD( sql_buf, having_buf );
4714 if( order_by_list ) {
4716 if( *order_by_list ) {
4717 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4718 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4721 free( order_by_list );
4725 const char* str = jsonObjectGetString( limit );
4726 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4730 const char* str = jsonObjectGetString( offset );
4731 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4734 if( !(flags & SUBSELECT) )
4735 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4737 if( defaultselhash )
4738 jsonObjectFree( defaultselhash );
4740 return buffer_release( sql_buf );
4742 } // end of SELECT()
4745 @brief Build a list of ORDER BY expressions.
4746 @param ctx Pointer to the method context.
4747 @param order_array Pointer to a JSON_ARRAY of field specifications.
4748 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4749 Each expression may be either a column reference or a function call whose first parameter
4750 is a column reference.
4752 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4753 It may optionally include entries for "direction" and/or "transform".
4755 The calling code is responsible for freeing the returned string.
4757 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4758 if( ! order_array ) {
4759 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4762 osrfAppSessionStatus(
4764 OSRF_STATUS_INTERNALSERVERERROR,
4765 "osrfMethodException",
4767 "Logic error: ORDER BY clause expected, not found; "
4768 "see error log for more details"
4771 } else if( order_array->type != JSON_ARRAY ) {
4772 osrfLogError( OSRF_LOG_MARK,
4773 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4775 osrfAppSessionStatus(
4777 OSRF_STATUS_INTERNALSERVERERROR,
4778 "osrfMethodException",
4780 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4784 growing_buffer* order_buf = buffer_init( 128 );
4785 int first = 1; // boolean
4787 jsonObject* order_spec;
4788 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4790 if( JSON_HASH != order_spec->type ) {
4791 osrfLogError( OSRF_LOG_MARK,
4792 "%s: Malformed field specification in ORDER BY clause; "
4793 "expected JSON_HASH, found %s",
4794 modulename, json_type( order_spec->type ) );
4796 osrfAppSessionStatus(
4798 OSRF_STATUS_INTERNALSERVERERROR,
4799 "osrfMethodException",
4801 "Malformed ORDER BY clause -- see error log for more details"
4803 buffer_free( order_buf );
4807 const char* class_alias =
4808 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4810 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4812 if( !field || !class_alias ) {
4813 osrfLogError( OSRF_LOG_MARK,
4814 "%s: Missing class or field name in field specification of ORDER BY clause",
4817 osrfAppSessionStatus(
4819 OSRF_STATUS_INTERNALSERVERERROR,
4820 "osrfMethodException",
4822 "Malformed ORDER BY clause -- see error log for more details"
4824 buffer_free( order_buf );
4828 const ClassInfo* order_class_info = search_alias( class_alias );
4829 if( ! order_class_info ) {
4830 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4831 "not in FROM clause, skipping it", modulename, class_alias );
4835 // Add a separating comma, except at the beginning
4839 OSRF_BUFFER_ADD( order_buf, ", " );
4841 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4843 osrfLogError( OSRF_LOG_MARK,
4844 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4845 modulename, class_alias, field );
4847 osrfAppSessionStatus(
4849 OSRF_STATUS_INTERNALSERVERERROR,
4850 "osrfMethodException",
4852 "Invalid field referenced in ORDER BY clause -- "
4853 "see error log for more details"
4857 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4858 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4859 modulename, field );
4861 osrfAppSessionStatus(
4863 OSRF_STATUS_INTERNALSERVERERROR,
4864 "osrfMethodException",
4866 "Virtual field in ORDER BY clause -- see error log for more details"
4868 buffer_free( order_buf );
4872 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
4873 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4874 if( ! transform_str ) {
4876 osrfAppSessionStatus(
4878 OSRF_STATUS_INTERNALSERVERERROR,
4879 "osrfMethodException",
4881 "Severe query error in ORDER BY clause -- "
4882 "see error log for more details"
4884 buffer_free( order_buf );
4888 OSRF_BUFFER_ADD( order_buf, transform_str );
4889 free( transform_str );
4892 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4894 const char* direction =
4895 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4897 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4898 OSRF_BUFFER_ADD( order_buf, " DESC" );
4900 OSRF_BUFFER_ADD( order_buf, " ASC" );
4904 return buffer_release( order_buf );
4908 @brief Build a SELECT statement.
4909 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
4910 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
4911 @param meta Pointer to the class metadata for the core class.
4912 @param ctx Pointer to the method context.
4913 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
4915 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
4916 "order_by", "limit", and "offset".
4918 The SELECT statements built here are distinct from those built for the json_query method.
4920 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
4921 osrfHash* meta, osrfMethodContext* ctx ) {
4923 const char* locale = osrf_message_get_last_locale();
4925 osrfHash* fields = osrfHashGet( meta, "fields" );
4926 const char* core_class = osrfHashGet( meta, "classname" );
4928 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
4930 jsonObject* selhash = NULL;
4931 jsonObject* defaultselhash = NULL;
4933 growing_buffer* sql_buf = buffer_init( 128 );
4934 growing_buffer* select_buf = buffer_init( 128 );
4936 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
4937 defaultselhash = jsonNewObjectType( JSON_HASH );
4938 selhash = defaultselhash;
4941 // If there's no SELECT list for the core class, build one
4942 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4943 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4945 // Add every non-virtual field to the field list
4946 osrfHash* field_def = NULL;
4947 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4948 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4949 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4950 const char* field = osrfHashIteratorKey( field_itr );
4951 jsonObjectPush( field_list, jsonNewObject( field ) );
4954 osrfHashIteratorFree( field_itr );
4955 jsonObjectSetKey( selhash, core_class, field_list );
4958 // Build a list of columns for the SELECT clause
4960 const jsonObject* snode = NULL;
4961 jsonIterator* class_itr = jsonNewIterator( selhash );
4962 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
4964 // If the class isn't in the IDL, ignore it
4965 const char* cname = class_itr->key;
4966 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4970 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
4971 if( strcmp( core_class, class_itr->key )) {
4975 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4976 if( !found->size ) {
4977 jsonObjectFree( found );
4981 jsonObjectFree( found );
4984 const jsonObject* node = NULL;
4985 jsonIterator* select_itr = jsonNewIterator( snode );
4986 while( (node = jsonIteratorNext( select_itr )) ) {
4987 const char* item_str = jsonObjectGetString( node );
4988 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4989 char* fname = osrfHashGet( field, "name" );
4997 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5002 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5003 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5006 i18n = osrfHashGet( field, "i18n" );
5008 if( str_is_true( i18n ) ) {
5009 char* pkey = osrfHashGet( idlClass, "primarykey" );
5010 char* tname = osrfHashGet( idlClass, "tablename" );
5012 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5013 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5014 tname, cname, fname, pkey, cname, pkey, locale, fname );
5016 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5019 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5023 jsonIteratorFree( select_itr );
5026 jsonIteratorFree( class_itr );
5028 char* col_list = buffer_release( select_buf );
5029 char* table = oilsGetRelation( meta );
5031 table = strdup( "(null)" );
5033 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5037 // Clear the query stack (as a fail-safe precaution against possible
5038 // leftover garbage); then push the first query frame onto the stack.
5039 clear_query_stack();
5041 if( add_query_core( NULL, core_class ) ) {
5043 osrfAppSessionStatus(
5045 OSRF_STATUS_INTERNALSERVERERROR,
5046 "osrfMethodException",
5048 "Unable to build query frame for core class"
5050 buffer_free( sql_buf );
5051 if( defaultselhash )
5052 jsonObjectFree( defaultselhash );
5056 // Add the JOIN clauses, if any
5058 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5059 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5060 OSRF_BUFFER_ADD( sql_buf, join_clause );
5061 free( join_clause );
5064 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5065 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5067 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5069 // Add the conditions in the WHERE clause
5070 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5072 osrfAppSessionStatus(
5074 OSRF_STATUS_INTERNALSERVERERROR,
5075 "osrfMethodException",
5077 "Severe query error -- see error log for more details"
5079 buffer_free( sql_buf );
5080 if( defaultselhash )
5081 jsonObjectFree( defaultselhash );
5082 clear_query_stack();
5085 buffer_add( sql_buf, pred );
5089 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5090 if( rest_of_query ) {
5091 const jsonObject* order_by = NULL;
5092 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5094 char* order_by_list = NULL;
5096 if( JSON_ARRAY == order_by->type ) {
5097 order_by_list = buildOrderByFromArray( ctx, order_by );
5098 if( !order_by_list ) {
5099 buffer_free( sql_buf );
5100 if( defaultselhash )
5101 jsonObjectFree( defaultselhash );
5102 clear_query_stack();
5105 } else if( JSON_HASH == order_by->type ) {
5106 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5107 // and build a list of ORDER BY expressions.
5108 growing_buffer* order_buf = buffer_init( 128 );
5110 jsonIterator* class_itr = jsonNewIterator( order_by );
5111 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5113 ClassInfo* order_class_info = search_alias( class_itr->key );
5114 if( ! order_class_info )
5115 continue; // class not referenced by FROM clause? Ignore it.
5117 if( JSON_HASH == snode->type ) {
5119 // If the data for the current class is a JSON_HASH, then it is
5120 // keyed on field name.
5122 const jsonObject* onode = NULL;
5123 jsonIterator* order_itr = jsonNewIterator( snode );
5124 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5126 osrfHash* field_def = osrfHashGet(
5127 order_class_info->fields, order_itr->key );
5129 continue; // Field not defined in IDL? Ignore it.
5130 if( str_is_true( osrfHashGet( field_def, "virtual")))
5131 continue; // Field is virtual? Ignore it.
5133 char* field_str = NULL;
5134 char* direction = NULL;
5135 if( onode->type == JSON_HASH ) {
5136 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5137 field_str = searchFieldTransform(
5138 class_itr->key, field_def, onode );
5140 osrfAppSessionStatus(
5142 OSRF_STATUS_INTERNALSERVERERROR,
5143 "osrfMethodException",
5145 "Severe query error in ORDER BY clause -- "
5146 "see error log for more details"
5148 jsonIteratorFree( order_itr );
5149 jsonIteratorFree( class_itr );
5150 buffer_free( order_buf );
5151 buffer_free( sql_buf );
5152 if( defaultselhash )
5153 jsonObjectFree( defaultselhash );
5154 clear_query_stack();
5158 growing_buffer* field_buf = buffer_init( 16 );
5159 buffer_fadd( field_buf, "\"%s\".%s",
5160 class_itr->key, order_itr->key );
5161 field_str = buffer_release( field_buf );
5164 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5165 const char* dir = jsonObjectGetString( order_by );
5166 if(!strncasecmp( dir, "d", 1 )) {
5167 direction = " DESC";
5171 field_str = strdup( order_itr->key );
5172 const char* dir = jsonObjectGetString( onode );
5173 if( !strncasecmp( dir, "d", 1 )) {
5174 direction = " DESC";
5183 buffer_add( order_buf, ", " );
5186 buffer_add( order_buf, field_str );
5190 buffer_add( order_buf, direction );
5192 } // end while; looping over ORDER BY expressions
5194 jsonIteratorFree( order_itr );
5196 } else if( JSON_STRING == snode->type ) {
5197 // We expect a comma-separated list of sort fields.
5198 const char* str = jsonObjectGetString( snode );
5199 if( strchr( str, ';' )) {
5200 // No semicolons allowed. It is theoretically possible for a
5201 // legitimate semicolon to occur within quotes, but it's not likely
5202 // to occur in practice in the context of an ORDER BY list.
5203 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5204 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5206 osrfAppSessionStatus(
5208 OSRF_STATUS_INTERNALSERVERERROR,
5209 "osrfMethodException",
5211 "Possible attempt at SOL injection -- "
5212 "semicolon found in ORDER BY list"
5215 jsonIteratorFree( class_itr );
5216 buffer_free( order_buf );
5217 buffer_free( sql_buf );
5218 if( defaultselhash )
5219 jsonObjectFree( defaultselhash );
5220 clear_query_stack();
5223 buffer_add( order_buf, str );
5227 } // end while; looping over order_by classes
5229 jsonIteratorFree( class_itr );
5230 order_by_list = buffer_release( order_buf );
5233 osrfLogWarning( OSRF_LOG_MARK,
5234 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5235 "no ORDER BY generated" );
5238 if( order_by_list && *order_by_list ) {
5239 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5240 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5243 free( order_by_list );
5246 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5248 const char* str = jsonObjectGetString( limit );
5256 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5258 const char* str = jsonObjectGetString( offset );
5267 if( defaultselhash )
5268 jsonObjectFree( defaultselhash );
5269 clear_query_stack();
5271 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5272 return buffer_release( sql_buf );
5275 int doJSONSearch ( osrfMethodContext* ctx ) {
5276 if(osrfMethodVerifyContext( ctx )) {
5277 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5281 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5285 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5289 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5290 flags |= SELECT_DISTINCT;
5292 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5293 flags |= DISABLE_I18N;
5295 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5296 clear_query_stack(); // a possibly needless precaution
5297 char* sql = buildQuery( ctx, hash, flags );
5298 clear_query_stack();
5305 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5308 dbhandle = writehandle;
5310 dbi_result result = dbi_conn_query( dbhandle, sql );
5313 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5315 if( dbi_result_first_row( result )) {
5316 /* JSONify the result */
5317 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5320 jsonObject* return_val = oilsMakeJSONFromResult( result );
5321 osrfAppRespond( ctx, return_val );
5322 jsonObjectFree( return_val );
5323 } while( dbi_result_next_row( result ));
5326 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5329 osrfAppRespondComplete( ctx, NULL );
5331 /* clean up the query */
5332 dbi_result_free( result );
5337 int errnum = dbi_conn_error( dbhandle, &msg );
5338 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5339 modulename, sql, errnum, msg ? msg : "(No description available)" );
5340 osrfAppSessionStatus(
5342 OSRF_STATUS_INTERNALSERVERERROR,
5343 "osrfMethodException",
5345 "Severe query error -- see error log for more details"
5347 if( !oilsIsDBConnected( dbhandle ))
5348 osrfAppSessionPanic( ctx->session );
5355 // The last parameter, err, is used to report an error condition by updating an int owned by
5356 // the calling code.
5358 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5359 // It is the responsibility of the calling code to initialize *err before the
5360 // call, so that it will be able to make sense of the result.
5362 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5363 // redundant anyway.
5364 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5365 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5368 dbhandle = writehandle;
5370 char* core_class = osrfHashGet( class_meta, "classname" );
5371 char* pkey = osrfHashGet( class_meta, "primarykey" );
5373 const jsonObject* _tmp;
5375 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5377 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5382 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5384 dbi_result result = dbi_conn_query( dbhandle, sql );
5385 if( NULL == result ) {
5387 int errnum = dbi_conn_error( dbhandle, &msg );
5388 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5389 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5390 msg ? msg : "(No description available)" );
5391 if( !oilsIsDBConnected( dbhandle ))
5392 osrfAppSessionPanic( ctx->session );
5393 osrfAppSessionStatus(
5395 OSRF_STATUS_INTERNALSERVERERROR,
5396 "osrfMethodException",
5398 "Severe query error -- see error log for more details"
5405 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5408 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5409 jsonObject* row_obj = NULL;
5411 if( dbi_result_first_row( result )) {
5413 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5414 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5415 // eliminate the duplicates.
5416 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5417 osrfHash* dedup = osrfNewHash();
5419 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5420 char* pkey_val = oilsFMGetString( row_obj, pkey );
5421 if( osrfHashGet( dedup, pkey_val ) ) {
5422 jsonObjectFree( row_obj );
5425 osrfHashSet( dedup, pkey_val, pkey_val );
5426 jsonObjectPush( res_list, row_obj );
5428 } while( dbi_result_next_row( result ));
5429 osrfHashFree( dedup );
5432 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5436 /* clean up the query */
5437 dbi_result_free( result );
5440 // If we're asked to flesh, and there's anything to flesh, then flesh it
5441 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5442 // something that he has no permission to look at).
5443 if( res_list->size && query_hash && ! enforce_pcrud ) {
5444 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5446 // Get the flesh depth
5447 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5448 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5449 flesh_depth = max_flesh_depth;
5451 // We need a non-zero flesh depth, and a list of fields to flesh
5452 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5453 if( temp_blob && flesh_depth > 0 ) {
5455 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5456 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5458 osrfStringArray* link_fields = NULL;
5459 osrfHash* links = osrfHashGet( class_meta, "links" );
5461 // Make an osrfStringArray of the names of fields to be fleshed
5462 if( flesh_fields ) {
5463 if( flesh_fields->size == 1 ) {
5464 const char* _t = jsonObjectGetString(
5465 jsonObjectGetIndex( flesh_fields, 0 ) );
5466 if( !strcmp( _t, "*" ))
5467 link_fields = osrfHashKeys( links );
5470 if( !link_fields ) {
5472 link_fields = osrfNewStringArray( 1 );
5473 jsonIterator* _i = jsonNewIterator( flesh_fields );
5474 while ((_f = jsonIteratorNext( _i ))) {
5475 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5477 jsonIteratorFree( _i );
5481 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5483 // Iterate over the JSON_ARRAY of rows
5485 unsigned long res_idx = 0;
5486 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5489 const char* link_field;
5491 // Iterate over the list of fleshable fields
5492 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5494 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5496 osrfHash* kid_link = osrfHashGet( links, link_field );
5498 continue; // Not a link field; skip it
5500 osrfHash* field = osrfHashGet( fields, link_field );
5502 continue; // Not a field at all; skip it (IDL is ill-formed)
5504 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5505 osrfHashGet( kid_link, "class" ));
5507 continue; // The class it links to doesn't exist; skip it
5509 const char* reltype = osrfHashGet( kid_link, "reltype" );
5511 continue; // No reltype; skip it (IDL is ill-formed)
5513 osrfHash* value_field = field;
5515 if( !strcmp( reltype, "has_many" )
5516 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5517 value_field = osrfHashGet(
5518 fields, osrfHashGet( class_meta, "primarykey" ) );
5521 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5523 if( link_map->size > 0 ) {
5524 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5527 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5532 osrfHashGet( kid_link, "class" ),
5539 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5540 osrfHashGet( kid_link, "field" ),
5541 osrfHashGet( kid_link, "class" ),
5542 osrfHashGet( kid_link, "key" ),
5543 osrfHashGet( kid_link, "reltype" )
5546 const char* search_key = jsonObjectGetString(
5547 jsonObjectGetIndex( cur,
5548 atoi( osrfHashGet( value_field, "array_position" ) )
5553 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5557 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5559 // construct WHERE clause
5560 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5563 osrfHashGet( kid_link, "key" ),
5564 jsonNewObject( search_key )
5567 // construct the rest of the query, mostly
5568 // by copying pieces of the previous level of query
5569 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5570 jsonObjectSetKey( rest_of_query, "flesh",
5571 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5575 jsonObjectSetKey( rest_of_query, "flesh_fields",
5576 jsonObjectClone( flesh_blob ));
5578 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5579 jsonObjectSetKey( rest_of_query, "order_by",
5580 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5584 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5585 jsonObjectSetKey( rest_of_query, "select",
5586 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5590 // do the query, recursively, to expand the fleshable field
5591 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5592 where_clause, rest_of_query, err );
5594 jsonObjectFree( where_clause );
5595 jsonObjectFree( rest_of_query );
5598 osrfStringArrayFree( link_fields );
5599 jsonObjectFree( res_list );
5600 jsonObjectFree( flesh_blob );
5604 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5605 osrfHashGet( kid_link, "class" ), kids->size );
5607 // Traverse the result set
5608 jsonObject* X = NULL;
5609 if( link_map->size > 0 && kids->size > 0 ) {
5611 kids = jsonNewObjectType( JSON_ARRAY );
5613 jsonObject* _k_node;
5614 unsigned long res_idx = 0;
5615 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5621 (unsigned long) atoi(
5627 osrfHashGet( kid_link, "class" )
5631 osrfStringArrayGetString( link_map, 0 )
5639 } // end while loop traversing X
5642 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5643 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5644 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5645 osrfHashGet( kid_link, "field" ));
5648 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5649 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5653 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5655 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5656 osrfHashGet( kid_link, "field" ) );
5659 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5660 jsonObjectClone( kids )
5665 jsonObjectFree( kids );
5669 jsonObjectFree( kids );
5671 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5672 osrfHashGet( kid_link, "field" ) );
5673 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5675 } // end while loop traversing list of fleshable fields
5676 } // end while loop traversing res_list
5677 jsonObjectFree( flesh_blob );
5678 osrfStringArrayFree( link_fields );
5687 int doUpdate( osrfMethodContext* ctx ) {
5688 if( osrfMethodVerifyContext( ctx )) {
5689 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5694 timeout_needs_resetting = 1;
5696 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5698 jsonObject* target = NULL;
5700 target = jsonObjectGetIndex( ctx->params, 1 );
5702 target = jsonObjectGetIndex( ctx->params, 0 );
5704 if(!verifyObjectClass( ctx, target )) {
5705 osrfAppRespondComplete( ctx, NULL );
5709 if( getXactId( ctx ) == NULL ) {
5710 osrfAppSessionStatus(
5712 OSRF_STATUS_BADREQUEST,
5713 "osrfMethodException",
5715 "No active transaction -- required for UPDATE"
5717 osrfAppRespondComplete( ctx, NULL );
5721 // The following test is harmless but redundant. If a class is
5722 // readonly, we don't register an update method for it.
5723 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5724 osrfAppSessionStatus(
5726 OSRF_STATUS_BADREQUEST,
5727 "osrfMethodException",
5729 "Cannot UPDATE readonly class"
5731 osrfAppRespondComplete( ctx, NULL );
5735 const char* trans_id = getXactId( ctx );
5737 // Set the last_xact_id
5738 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5740 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5741 trans_id, target->classname, index );
5742 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5745 char* pkey = osrfHashGet( meta, "primarykey" );
5746 osrfHash* fields = osrfHashGet( meta, "fields" );
5748 char* id = oilsFMGetString( target, pkey );
5752 "%s updating %s object with %s = %s",
5754 osrfHashGet( meta, "fieldmapper" ),
5759 dbhandle = writehandle;
5760 growing_buffer* sql = buffer_init( 128 );
5761 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5764 osrfHash* field_def = NULL;
5765 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5766 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5768 // Skip virtual fields, and the primary key
5769 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5772 const char* field_name = osrfHashIteratorKey( field_itr );
5773 if( ! strcmp( field_name, pkey ) )
5776 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5778 int value_is_numeric = 0; // boolean
5780 if( field_object && field_object->classname ) {
5781 value = oilsFMGetString(
5783 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5785 } else if( field_object && JSON_BOOL == field_object->type ) {
5786 if( jsonBoolIsTrue( field_object ) )
5787 value = strdup( "t" );
5789 value = strdup( "f" );
5791 value = jsonObjectToSimpleString( field_object );
5792 if( field_object && JSON_NUMBER == field_object->type )
5793 value_is_numeric = 1;
5796 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5797 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5799 if( !field_object || field_object->type == JSON_NULL ) {
5800 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5801 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5805 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5806 buffer_fadd( sql, " %s = NULL", field_name );
5809 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5813 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5815 const char* numtype = get_datatype( field_def );
5816 if( !strncmp( numtype, "INT", 3 ) ) {
5817 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5818 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5819 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5821 // Must really be intended as a string, so quote it
5822 if( dbi_conn_quote_string( dbhandle, &value )) {
5823 buffer_fadd( sql, " %s = %s", field_name, value );
5825 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5826 modulename, value );
5827 osrfAppSessionStatus(
5829 OSRF_STATUS_INTERNALSERVERERROR,
5830 "osrfMethodException",
5832 "Error quoting string -- please see the error log for more details"
5836 osrfHashIteratorFree( field_itr );
5838 osrfAppRespondComplete( ctx, NULL );
5843 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5846 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5850 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5851 buffer_fadd( sql, " %s = %s", field_name, value );
5853 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5854 osrfAppSessionStatus(
5856 OSRF_STATUS_INTERNALSERVERERROR,
5857 "osrfMethodException",
5859 "Error quoting string -- please see the error log for more details"
5863 osrfHashIteratorFree( field_itr );
5865 osrfAppRespondComplete( ctx, NULL );
5874 osrfHashIteratorFree( field_itr );
5876 jsonObject* obj = jsonNewObject( id );
5878 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5879 dbi_conn_quote_string( dbhandle, &id );
5881 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5883 char* query = buffer_release( sql );
5884 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5886 dbi_result result = dbi_conn_query( dbhandle, query );
5891 jsonObjectFree( obj );
5892 obj = jsonNewObject( NULL );
5894 int errnum = dbi_conn_error( dbhandle, &msg );
5897 "%s ERROR updating %s object with %s = %s: %d %s",
5899 osrfHashGet( meta, "fieldmapper" ),
5903 msg ? msg : "(No description available)"
5905 osrfAppSessionStatus(
5907 OSRF_STATUS_INTERNALSERVERERROR,
5908 "osrfMethodException",
5910 "Error in updating a row -- please see the error log for more details"
5912 if( !oilsIsDBConnected( dbhandle ))
5913 osrfAppSessionPanic( ctx->session );
5916 dbi_result_free( result );
5919 osrfAppRespondComplete( ctx, obj );
5920 jsonObjectFree( obj );
5924 int doDelete( osrfMethodContext* ctx ) {
5925 if( osrfMethodVerifyContext( ctx )) {
5926 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5931 timeout_needs_resetting = 1;
5933 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5935 if( getXactId( ctx ) == NULL ) {
5936 osrfAppSessionStatus(
5938 OSRF_STATUS_BADREQUEST,
5939 "osrfMethodException",
5941 "No active transaction -- required for DELETE"
5943 osrfAppRespondComplete( ctx, NULL );
5947 // The following test is harmless but redundant. If a class is
5948 // readonly, we don't register a delete method for it.
5949 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5950 osrfAppSessionStatus(
5952 OSRF_STATUS_BADREQUEST,
5953 "osrfMethodException",
5955 "Cannot DELETE readonly class"
5957 osrfAppRespondComplete( ctx, NULL );
5961 dbhandle = writehandle;
5963 char* pkey = osrfHashGet( meta, "primarykey" );
5970 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5971 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5972 osrfAppRespondComplete( ctx, NULL );
5976 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5978 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5979 osrfAppRespondComplete( ctx, NULL );
5982 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5987 "%s deleting %s object with %s = %s",
5989 osrfHashGet( meta, "fieldmapper" ),
5994 jsonObject* obj = jsonNewObject( id );
5996 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5997 dbi_conn_quote_string( writehandle, &id );
5999 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6000 osrfHashGet( meta, "tablename" ), pkey, id );
6005 jsonObjectFree( obj );
6006 obj = jsonNewObject( NULL );
6008 int errnum = dbi_conn_error( writehandle, &msg );
6011 "%s ERROR deleting %s object with %s = %s: %d %s",
6013 osrfHashGet( meta, "fieldmapper" ),
6017 msg ? msg : "(No description available)"
6019 osrfAppSessionStatus(
6021 OSRF_STATUS_INTERNALSERVERERROR,
6022 "osrfMethodException",
6024 "Error in deleting a row -- please see the error log for more details"
6026 if( !oilsIsDBConnected( writehandle ))
6027 osrfAppSessionPanic( ctx->session );
6029 dbi_result_free( result );
6033 osrfAppRespondComplete( ctx, obj );
6034 jsonObjectFree( obj );
6039 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6040 @param result An iterator for a result set; we only look at the current row.
6041 @param @meta Pointer to the class metadata for the core class.
6042 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6044 If a column is not defined in the IDL, or if it has no array_position defined for it in
6045 the IDL, or if it is defined as virtual, ignore it.
6047 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6048 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6049 array_position in the IDL.
6051 A field defined in the IDL but not represented in the returned row will leave a hole
6052 in the JSON_ARRAY. In effect it will be treated as a null value.
6054 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6055 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6056 classname corresponding to the @a meta argument.
6058 The calling code is responsible for freeing the the resulting jsonObject by calling
6061 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6062 if( !( result && meta )) return NULL;
6064 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6065 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6066 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6068 osrfHash* fields = osrfHashGet( meta, "fields" );
6070 int columnIndex = 1;
6071 const char* columnName;
6073 /* cycle through the columns in the row returned from the database */
6074 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6076 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6078 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6080 /* determine the field type and storage attributes */
6081 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6082 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6084 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6085 // or if it has no sequence number there, or if it's virtual, skip it.
6086 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6089 if( str_is_true( osrfHashGet( _f, "virtual" )))
6090 continue; // skip this column: IDL says it's virtual
6092 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6093 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6094 continue; // since we assign sequence numbers dynamically as we load the IDL.
6096 fmIndex = atoi( pos );
6097 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6099 continue; // This field is not defined in the IDL
6102 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6103 // sequence number from the IDL (which is likely to be different from the sequence
6104 // of columns in the SELECT clause).
6105 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6106 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6111 case DBI_TYPE_INTEGER :
6113 if( attr & DBI_INTEGER_SIZE8 )
6114 jsonObjectSetIndex( object, fmIndex,
6115 jsonNewNumberObject(
6116 dbi_result_get_longlong_idx( result, columnIndex )));
6118 jsonObjectSetIndex( object, fmIndex,
6119 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6123 case DBI_TYPE_DECIMAL :
6124 jsonObjectSetIndex( object, fmIndex,
6125 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6128 case DBI_TYPE_STRING :
6133 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6138 case DBI_TYPE_DATETIME : {
6140 char dt_string[ 256 ] = "";
6143 // Fetch the date column as a time_t
6144 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6146 // Translate the time_t to a human-readable string
6147 if( !( attr & DBI_DATETIME_DATE )) {
6148 gmtime_r( &_tmp_dt, &gmdt );
6149 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6150 } else if( !( attr & DBI_DATETIME_TIME )) {
6151 localtime_r( &_tmp_dt, &gmdt );
6152 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6154 localtime_r( &_tmp_dt, &gmdt );
6155 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6158 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6162 case DBI_TYPE_BINARY :
6163 osrfLogError( OSRF_LOG_MARK,
6164 "Can't do binary at column %s : index %d", columnName, columnIndex );
6173 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6174 if( !result ) return NULL;
6176 jsonObject* object = jsonNewObject( NULL );
6179 char dt_string[ 256 ];
6183 int columnIndex = 1;
6185 unsigned short type;
6186 const char* columnName;
6188 /* cycle through the column list */
6189 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6191 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6193 fmIndex = -1; // reset the position
6195 /* determine the field type and storage attributes */
6196 type = dbi_result_get_field_type_idx( result, columnIndex );
6197 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6199 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6200 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6205 case DBI_TYPE_INTEGER :
6207 if( attr & DBI_INTEGER_SIZE8 )
6208 jsonObjectSetKey( object, columnName,
6209 jsonNewNumberObject( dbi_result_get_longlong_idx(
6210 result, columnIndex )) );
6212 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6213 dbi_result_get_int_idx( result, columnIndex )) );
6216 case DBI_TYPE_DECIMAL :
6217 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6218 dbi_result_get_double_idx( result, columnIndex )) );
6221 case DBI_TYPE_STRING :
6222 jsonObjectSetKey( object, columnName,
6223 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6226 case DBI_TYPE_DATETIME :
6228 memset( dt_string, '\0', sizeof( dt_string ));
6229 memset( &gmdt, '\0', sizeof( gmdt ));
6231 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6233 if( !( attr & DBI_DATETIME_DATE )) {
6234 gmtime_r( &_tmp_dt, &gmdt );
6235 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6236 } else if( !( attr & DBI_DATETIME_TIME )) {
6237 localtime_r( &_tmp_dt, &gmdt );
6238 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6240 localtime_r( &_tmp_dt, &gmdt );
6241 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6244 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6247 case DBI_TYPE_BINARY :
6248 osrfLogError( OSRF_LOG_MARK,
6249 "Can't do binary at column %s : index %d", columnName, columnIndex );
6253 } // end while loop traversing result
6258 // Interpret a string as true or false
6259 int str_is_true( const char* str ) {
6260 if( NULL == str || strcasecmp( str, "true" ) )
6266 // Interpret a jsonObject as true or false
6267 static int obj_is_true( const jsonObject* obj ) {
6270 else switch( obj->type )
6278 if( strcasecmp( obj->value.s, "true" ) )
6282 case JSON_NUMBER : // Support 1/0 for perl's sake
6283 if( jsonObjectGetNumber( obj ) == 1.0 )
6292 // Translate a numeric code into a text string identifying a type of
6293 // jsonObject. To be used for building error messages.
6294 static const char* json_type( int code ) {
6300 return "JSON_ARRAY";
6302 return "JSON_STRING";
6304 return "JSON_NUMBER";
6310 return "(unrecognized)";
6314 // Extract the "primitive" attribute from an IDL field definition.
6315 // If we haven't initialized the app, then we must be running in
6316 // some kind of testbed. In that case, default to "string".
6317 static const char* get_primitive( osrfHash* field ) {
6318 const char* s = osrfHashGet( field, "primitive" );
6320 if( child_initialized )
6323 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6325 osrfHashGet( field, "name" )
6333 // Extract the "datatype" attribute from an IDL field definition.
6334 // If we haven't initialized the app, then we must be running in
6335 // some kind of testbed. In that case, default to to NUMERIC,
6336 // since we look at the datatype only for numbers.
6337 static const char* get_datatype( osrfHash* field ) {
6338 const char* s = osrfHashGet( field, "datatype" );
6340 if( child_initialized )
6343 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6345 osrfHashGet( field, "name" )
6354 @brief Determine whether a string is potentially a valid SQL identifier.
6355 @param s The identifier to be tested.
6356 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6358 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6359 need to follow all the rules exactly, such as requiring that the first character not
6362 We allow leading and trailing white space. In between, we do not allow punctuation
6363 (except for underscores and dollar signs), control characters, or embedded white space.
6365 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6366 for the foreseeable future such quoted identifiers are not likely to be an issue.
6368 int is_identifier( const char* s) {
6372 // Skip leading white space
6373 while( isspace( (unsigned char) *s ) )
6377 return 0; // Nothing but white space? Not okay.
6379 // Check each character until we reach white space or
6380 // end-of-string. Letters, digits, underscores, and
6381 // dollar signs are okay. With the exception of periods
6382 // (as in schema.identifier), control characters and other
6383 // punctuation characters are not okay. Anything else
6384 // is okay -- it could for example be part of a multibyte
6385 // UTF8 character such as a letter with diacritical marks,
6386 // and those are allowed.
6388 if( isalnum( (unsigned char) *s )
6392 ; // Fine; keep going
6393 else if( ispunct( (unsigned char) *s )
6394 || iscntrl( (unsigned char) *s ) )
6397 } while( *s && ! isspace( (unsigned char) *s ) );
6399 // If we found any white space in the above loop,
6400 // the rest had better be all white space.
6402 while( isspace( (unsigned char) *s ) )
6406 return 0; // White space was embedded within non-white space
6412 @brief Determine whether to accept a character string as a comparison operator.
6413 @param op The candidate comparison operator.
6414 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6416 We don't validate the operator for real. We just make sure that it doesn't contain
6417 any semicolons or white space (with special exceptions for a few specific operators).
6418 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6419 space but it's still not a valid operator, then the database will complain.
6421 Another approach would be to compare the string against a short list of approved operators.
6422 We don't do that because we want to allow custom operators like ">100*", which at this
6423 writing would be difficult or impossible to express otherwise in a JSON query.
6425 int is_good_operator( const char* op ) {
6426 if( !op ) return 0; // Sanity check
6430 if( isspace( (unsigned char) *s ) ) {
6431 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6432 // and IS NOT DISTINCT FROM.
6433 if( !strcasecmp( op, "similar to" ) )
6435 else if( !strcasecmp( op, "is distinct from" ) )
6437 else if( !strcasecmp( op, "is not distinct from" ) )
6442 else if( ';' == *s )
6450 @name Query Frame Management
6452 The following machinery supports a stack of query frames for use by SELECT().
6454 A query frame caches information about one level of a SELECT query. When we enter
6455 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6457 The query frame stores information about the core class, and about any joined classes
6460 The main purpose is to map table aliases to classes and tables, so that a query can
6461 join to the same table more than once. A secondary goal is to reduce the number of
6462 lookups in the IDL by caching the results.
6466 #define STATIC_CLASS_INFO_COUNT 3
6468 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6471 @brief Allocate a ClassInfo as raw memory.
6472 @return Pointer to the newly allocated ClassInfo.
6474 Except for the in_use flag, which is used only by the allocation and deallocation
6475 logic, we don't initialize the ClassInfo here.
6477 static ClassInfo* allocate_class_info( void ) {
6478 // In order to reduce the number of mallocs and frees, we return a static
6479 // instance of ClassInfo, if we can find one that we're not already using.
6480 // We rely on the fact that the compiler will implicitly initialize the
6481 // static instances so that in_use == 0.
6484 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6485 if( ! static_class_info[ i ].in_use ) {
6486 static_class_info[ i ].in_use = 1;
6487 return static_class_info + i;
6491 // The static ones are all in use. Malloc one.
6493 return safe_malloc( sizeof( ClassInfo ) );
6497 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6498 @param info Pointer to the ClassInfo to be cleared.
6500 static void clear_class_info( ClassInfo* info ) {
6505 // Free any malloc'd strings
6507 if( info->alias != info->alias_store )
6508 free( info->alias );
6510 if( info->class_name != info->class_name_store )
6511 free( info->class_name );
6513 free( info->source_def );
6515 info->alias = info->class_name = info->source_def = NULL;
6520 @brief Free a ClassInfo and everything it owns.
6521 @param info Pointer to the ClassInfo to be freed.
6523 static void free_class_info( ClassInfo* info ) {
6528 clear_class_info( info );
6530 // If it's one of the static instances, just mark it as not in use
6533 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6534 if( info == static_class_info + i ) {
6535 static_class_info[ i ].in_use = 0;
6540 // Otherwise it must have been malloc'd, so free it
6546 @brief Populate an already-allocated ClassInfo.
6547 @param info Pointer to the ClassInfo to be populated.
6548 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6550 @param class Name of the class.
6551 @return Zero if successful, or 1 if not.
6553 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6554 the relevant portions of the IDL for the specified class.
6556 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6559 osrfLogError( OSRF_LOG_MARK,
6560 "%s ERROR: No ClassInfo available to populate", modulename );
6561 info->alias = info->class_name = info->source_def = NULL;
6562 info->class_def = info->fields = info->links = NULL;
6567 osrfLogError( OSRF_LOG_MARK,
6568 "%s ERROR: No class name provided for lookup", modulename );
6569 info->alias = info->class_name = info->source_def = NULL;
6570 info->class_def = info->fields = info->links = NULL;
6574 // Alias defaults to class name if not supplied
6575 if( ! alias || ! alias[ 0 ] )
6578 // Look up class info in the IDL
6579 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6581 osrfLogError( OSRF_LOG_MARK,
6582 "%s ERROR: Class %s not defined in IDL", modulename, class );
6583 info->alias = info->class_name = info->source_def = NULL;
6584 info->class_def = info->fields = info->links = NULL;
6586 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6587 osrfLogError( OSRF_LOG_MARK,
6588 "%s ERROR: Class %s is defined as virtual", modulename, class );
6589 info->alias = info->class_name = info->source_def = NULL;
6590 info->class_def = info->fields = info->links = NULL;
6594 osrfHash* links = osrfHashGet( class_def, "links" );
6596 osrfLogError( OSRF_LOG_MARK,
6597 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6598 info->alias = info->class_name = info->source_def = NULL;
6599 info->class_def = info->fields = info->links = NULL;
6603 osrfHash* fields = osrfHashGet( class_def, "fields" );
6605 osrfLogError( OSRF_LOG_MARK,
6606 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6607 info->alias = info->class_name = info->source_def = NULL;
6608 info->class_def = info->fields = info->links = NULL;
6612 char* source_def = oilsGetRelation( class_def );
6616 // We got everything we need, so populate the ClassInfo
6617 if( strlen( alias ) > ALIAS_STORE_SIZE )
6618 info->alias = strdup( alias );
6620 strcpy( info->alias_store, alias );
6621 info->alias = info->alias_store;
6624 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6625 info->class_name = strdup( class );
6627 strcpy( info->class_name_store, class );
6628 info->class_name = info->class_name_store;
6631 info->source_def = source_def;
6633 info->class_def = class_def;
6634 info->links = links;
6635 info->fields = fields;
6640 #define STATIC_FRAME_COUNT 3
6642 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6645 @brief Allocate a QueryFrame as raw memory.
6646 @return Pointer to the newly allocated QueryFrame.
6648 Except for the in_use flag, which is used only by the allocation and deallocation
6649 logic, we don't initialize the QueryFrame here.
6651 static QueryFrame* allocate_frame( void ) {
6652 // In order to reduce the number of mallocs and frees, we return a static
6653 // instance of QueryFrame, if we can find one that we're not already using.
6654 // We rely on the fact that the compiler will implicitly initialize the
6655 // static instances so that in_use == 0.
6658 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6659 if( ! static_frame[ i ].in_use ) {
6660 static_frame[ i ].in_use = 1;
6661 return static_frame + i;
6665 // The static ones are all in use. Malloc one.
6667 return safe_malloc( sizeof( QueryFrame ) );
6671 @brief Free a QueryFrame, and all the memory it owns.
6672 @param frame Pointer to the QueryFrame to be freed.
6674 static void free_query_frame( QueryFrame* frame ) {
6679 clear_class_info( &frame->core );
6681 // Free the join list
6683 ClassInfo* info = frame->join_list;
6686 free_class_info( info );
6690 frame->join_list = NULL;
6693 // If the frame is a static instance, just mark it as unused
6695 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6696 if( frame == static_frame + i ) {
6697 static_frame[ i ].in_use = 0;
6702 // Otherwise it must have been malloc'd, so free it
6708 @brief Search a given QueryFrame for a specified alias.
6709 @param frame Pointer to the QueryFrame to be searched.
6710 @param target The alias for which to search.
6711 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6713 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6714 if( ! frame || ! target ) {
6718 ClassInfo* found_class = NULL;
6720 if( !strcmp( target, frame->core.alias ) )
6721 return &(frame->core);
6723 ClassInfo* curr_class = frame->join_list;
6724 while( curr_class ) {
6725 if( strcmp( target, curr_class->alias ) )
6726 curr_class = curr_class->next;
6728 found_class = curr_class;
6738 @brief Push a new (blank) QueryFrame onto the stack.
6740 static void push_query_frame( void ) {
6741 QueryFrame* frame = allocate_frame();
6742 frame->join_list = NULL;
6743 frame->next = curr_query;
6745 // Initialize the ClassInfo for the core class
6746 ClassInfo* core = &frame->core;
6747 core->alias = core->class_name = core->source_def = NULL;
6748 core->class_def = core->fields = core->links = NULL;
6754 @brief Pop a QueryFrame off the stack and destroy it.
6756 static void pop_query_frame( void ) {
6761 QueryFrame* popped = curr_query;
6762 curr_query = popped->next;
6764 free_query_frame( popped );
6768 @brief Populate the ClassInfo for the core class.
6769 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6770 class name as an alias.
6771 @param class_name Name of the core class.
6772 @return Zero if successful, or 1 if not.
6774 Populate the ClassInfo of the core class with copies of the alias and class name, and
6775 with pointers to the relevant portions of the IDL for the core class.
6777 static int add_query_core( const char* alias, const char* class_name ) {
6780 if( ! curr_query ) {
6781 osrfLogError( OSRF_LOG_MARK,
6782 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6784 } else if( curr_query->core.alias ) {
6785 osrfLogError( OSRF_LOG_MARK,
6786 "%s ERROR: Core class %s already populated as %s",
6787 modulename, curr_query->core.class_name, curr_query->core.alias );
6791 build_class_info( &curr_query->core, alias, class_name );
6792 if( curr_query->core.alias )
6795 osrfLogError( OSRF_LOG_MARK,
6796 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6802 @brief Search the current QueryFrame for a specified alias.
6803 @param target The alias for which to search.
6804 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6806 static inline ClassInfo* search_alias( const char* target ) {
6807 return search_alias_in_frame( curr_query, target );
6811 @brief Search all levels of query for a specified alias, starting with the current query.
6812 @param target The alias for which to search.
6813 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6815 static ClassInfo* search_all_alias( const char* target ) {
6816 ClassInfo* found_class = NULL;
6817 QueryFrame* curr_frame = curr_query;
6819 while( curr_frame ) {
6820 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6823 curr_frame = curr_frame->next;
6830 @brief Add a class to the list of classes joined to the current query.
6831 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6832 the class name as an alias.
6833 @param classname The name of the class to be added.
6834 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6836 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6838 if( ! classname || ! *classname ) { // sanity check
6839 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6846 const ClassInfo* conflict = search_alias( alias );
6848 osrfLogError( OSRF_LOG_MARK,
6849 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6850 modulename, alias, conflict->class_name );
6854 ClassInfo* info = allocate_class_info();
6856 if( build_class_info( info, alias, classname ) ) {
6857 free_class_info( info );
6861 // Add the new ClassInfo to the join list of the current QueryFrame
6862 info->next = curr_query->join_list;
6863 curr_query->join_list = info;
6869 @brief Destroy all nodes on the query stack.
6871 static void clear_query_stack( void ) {