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 osrfLogWarn( OSRF_LOG_MARK,
4420 "%s: Invalid class \"%s\" referenced in ORDER BY clause, skipping it",
4421 modulename, class_itr->key );
4425 osrfHash* field_list_def = order_class_info->fields;
4427 if( snode->type == JSON_HASH ) {
4429 // Hash is keyed on field names from the current class. For each field
4430 // there is another layer of hash to define the sorting details, if any,
4431 // or a string to indicate direction of sorting.
4432 jsonIterator* order_itr = jsonNewIterator( snode );
4433 while( (onode = jsonIteratorNext( order_itr )) ) {
4435 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4437 osrfLogError( OSRF_LOG_MARK,
4438 "%s: Invalid field \"%s\" in ORDER BY clause",
4439 modulename, order_itr->key );
4441 osrfAppSessionStatus(
4443 OSRF_STATUS_INTERNALSERVERERROR,
4444 "osrfMethodException",
4446 "Invalid field in ORDER BY clause -- "
4447 "see error log for more details"
4449 jsonIteratorFree( order_itr );
4450 jsonIteratorFree( class_itr );
4451 buffer_free( order_buf );
4453 buffer_free( group_buf );
4454 buffer_free( sql_buf );
4455 if( defaultselhash )
4456 jsonObjectFree( defaultselhash );
4458 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4459 osrfLogError( OSRF_LOG_MARK,
4460 "%s: Virtual field \"%s\" in ORDER BY clause",
4461 modulename, order_itr->key );
4463 osrfAppSessionStatus(
4465 OSRF_STATUS_INTERNALSERVERERROR,
4466 "osrfMethodException",
4468 "Virtual field in ORDER BY clause -- "
4469 "see error log for more details"
4471 jsonIteratorFree( order_itr );
4472 jsonIteratorFree( class_itr );
4473 buffer_free( order_buf );
4475 buffer_free( group_buf );
4476 buffer_free( sql_buf );
4477 if( defaultselhash )
4478 jsonObjectFree( defaultselhash );
4482 const char* direction = NULL;
4483 if( onode->type == JSON_HASH ) {
4484 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4485 string = searchFieldTransform(
4487 osrfHashGet( field_list_def, order_itr->key ),
4491 if( ctx ) osrfAppSessionStatus(
4493 OSRF_STATUS_INTERNALSERVERERROR,
4494 "osrfMethodException",
4496 "Severe query error in ORDER BY clause -- "
4497 "see error log for more details"
4499 jsonIteratorFree( order_itr );
4500 jsonIteratorFree( class_itr );
4502 buffer_free( group_buf );
4503 buffer_free( order_buf);
4504 buffer_free( sql_buf );
4505 if( defaultselhash )
4506 jsonObjectFree( defaultselhash );
4510 growing_buffer* field_buf = buffer_init( 16 );
4511 buffer_fadd( field_buf, "\"%s\".%s",
4512 class_itr->key, order_itr->key );
4513 string = buffer_release( field_buf );
4516 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4517 const char* dir = jsonObjectGetString( tmp_const );
4518 if(!strncasecmp( dir, "d", 1 )) {
4519 direction = " DESC";
4525 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4526 osrfLogError( OSRF_LOG_MARK,
4527 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4528 modulename, json_type( onode->type ) );
4530 osrfAppSessionStatus(
4532 OSRF_STATUS_INTERNALSERVERERROR,
4533 "osrfMethodException",
4535 "Malformed ORDER BY clause -- see error log for more details"
4537 jsonIteratorFree( order_itr );
4538 jsonIteratorFree( class_itr );
4540 buffer_free( group_buf );
4541 buffer_free( order_buf );
4542 buffer_free( sql_buf );
4543 if( defaultselhash )
4544 jsonObjectFree( defaultselhash );
4548 string = strdup( order_itr->key );
4549 const char* dir = jsonObjectGetString( onode );
4550 if( !strncasecmp( dir, "d", 1 )) {
4551 direction = " DESC";
4558 OSRF_BUFFER_ADD( order_buf, ", " );
4560 order_buf = buffer_init( 128 );
4562 OSRF_BUFFER_ADD( order_buf, string );
4566 OSRF_BUFFER_ADD( order_buf, direction );
4570 jsonIteratorFree( order_itr );
4572 } else if( snode->type == JSON_ARRAY ) {
4574 // Array is a list of fields from the current class
4575 unsigned long order_idx = 0;
4576 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4578 const char* _f = jsonObjectGetString( onode );
4580 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4582 osrfLogError( OSRF_LOG_MARK,
4583 "%s: Invalid field \"%s\" in ORDER BY clause",
4586 osrfAppSessionStatus(
4588 OSRF_STATUS_INTERNALSERVERERROR,
4589 "osrfMethodException",
4591 "Invalid field in ORDER BY clause -- "
4592 "see error log for more details"
4594 jsonIteratorFree( class_itr );
4595 buffer_free( order_buf );
4597 buffer_free( group_buf );
4598 buffer_free( sql_buf );
4599 if( defaultselhash )
4600 jsonObjectFree( defaultselhash );
4602 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4603 osrfLogError( OSRF_LOG_MARK,
4604 "%s: Virtual field \"%s\" in ORDER BY clause",
4607 osrfAppSessionStatus(
4609 OSRF_STATUS_INTERNALSERVERERROR,
4610 "osrfMethodException",
4612 "Virtual field in ORDER BY clause -- "
4613 "see error log for more details"
4615 jsonIteratorFree( class_itr );
4616 buffer_free( order_buf );
4618 buffer_free( group_buf );
4619 buffer_free( sql_buf );
4620 if( defaultselhash )
4621 jsonObjectFree( defaultselhash );
4626 OSRF_BUFFER_ADD( order_buf, ", " );
4628 order_buf = buffer_init( 128 );
4630 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4634 // IT'S THE OOOOOOOOOOOLD STYLE!
4636 osrfLogError( OSRF_LOG_MARK,
4637 "%s: Possible SQL injection attempt; direct order by is not allowed",
4640 osrfAppSessionStatus(
4642 OSRF_STATUS_INTERNALSERVERERROR,
4643 "osrfMethodException",
4645 "Severe query error -- see error log for more details"
4650 buffer_free( group_buf );
4651 buffer_free( order_buf );
4652 buffer_free( sql_buf );
4653 if( defaultselhash )
4654 jsonObjectFree( defaultselhash );
4655 jsonIteratorFree( class_itr );
4659 jsonIteratorFree( class_itr );
4661 order_by_list = buffer_release( order_buf );
4663 osrfLogError( OSRF_LOG_MARK,
4664 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4665 modulename, json_type( order_hash->type ) );
4667 osrfAppSessionStatus(
4669 OSRF_STATUS_INTERNALSERVERERROR,
4670 "osrfMethodException",
4672 "Malformed ORDER BY clause -- see error log for more details"
4675 buffer_free( group_buf );
4676 buffer_free( sql_buf );
4677 if( defaultselhash )
4678 jsonObjectFree( defaultselhash );
4683 string = buffer_release( group_buf );
4685 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4686 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4687 OSRF_BUFFER_ADD( sql_buf, string );
4692 if( having_buf && *having_buf ) {
4693 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4694 OSRF_BUFFER_ADD( sql_buf, having_buf );
4698 if( order_by_list ) {
4700 if( *order_by_list ) {
4701 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4702 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4705 free( order_by_list );
4709 const char* str = jsonObjectGetString( limit );
4710 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4714 const char* str = jsonObjectGetString( offset );
4715 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4718 if( !(flags & SUBSELECT) )
4719 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4721 if( defaultselhash )
4722 jsonObjectFree( defaultselhash );
4724 return buffer_release( sql_buf );
4726 } // end of SELECT()
4729 @brief Build a list of ORDER BY expressions.
4730 @param ctx Pointer to the method context.
4731 @param order_array Pointer to a JSON_ARRAY of field specifications.
4732 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4733 Each expression may be either a column reference or a function call whose first parameter
4734 is a column reference.
4736 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4737 It may optionally include entries for "direction" and/or "transform".
4739 The calling code is responsible for freeing the returned string.
4741 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4742 if( ! order_array ) {
4743 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4746 osrfAppSessionStatus(
4748 OSRF_STATUS_INTERNALSERVERERROR,
4749 "osrfMethodException",
4751 "Logic error: ORDER BY clause expected, not found; "
4752 "see error log for more details"
4755 } else if( order_array->type != JSON_ARRAY ) {
4756 osrfLogError( OSRF_LOG_MARK,
4757 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4759 osrfAppSessionStatus(
4761 OSRF_STATUS_INTERNALSERVERERROR,
4762 "osrfMethodException",
4764 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4768 growing_buffer* order_buf = buffer_init( 128 );
4769 int first = 1; // boolean
4771 jsonObject* order_spec;
4772 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4774 if( JSON_HASH != order_spec->type ) {
4775 osrfLogError( OSRF_LOG_MARK,
4776 "%s: Malformed field specification in ORDER BY clause; "
4777 "expected JSON_HASH, found %s",
4778 modulename, json_type( order_spec->type ) );
4780 osrfAppSessionStatus(
4782 OSRF_STATUS_INTERNALSERVERERROR,
4783 "osrfMethodException",
4785 "Malformed ORDER BY clause -- see error log for more details"
4787 buffer_free( order_buf );
4791 const char* class_alias =
4792 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4794 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4796 if( !field || !class_alias ) {
4797 osrfLogError( OSRF_LOG_MARK,
4798 "%s: Missing class or field name in field specification of ORDER BY clause",
4801 osrfAppSessionStatus(
4803 OSRF_STATUS_INTERNALSERVERERROR,
4804 "osrfMethodException",
4806 "Malformed ORDER BY clause -- see error log for more details"
4808 buffer_free( order_buf );
4812 const ClassInfo* order_class_info = search_alias( class_alias );
4813 if( ! order_class_info ) {
4814 osrfLogWarn( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4815 "not in FROM clause, skipping it", modulename, class_alias );
4819 // Add a separating comma, except at the beginning
4823 OSRF_BUFFER_ADD( order_buf, ", " );
4825 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4827 osrfLogError( OSRF_LOG_MARK,
4828 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4829 modulename, class_alias, field );
4831 osrfAppSessionStatus(
4833 OSRF_STATUS_INTERNALSERVERERROR,
4834 "osrfMethodException",
4836 "Invalid field referenced in ORDER BY clause -- "
4837 "see error log for more details"
4841 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4842 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4843 modulename, field );
4845 osrfAppSessionStatus(
4847 OSRF_STATUS_INTERNALSERVERERROR,
4848 "osrfMethodException",
4850 "Virtual field in ORDER BY clause -- see error log for more details"
4852 buffer_free( order_buf );
4856 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
4857 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4858 if( ! transform_str ) {
4860 osrfAppSessionStatus(
4862 OSRF_STATUS_INTERNALSERVERERROR,
4863 "osrfMethodException",
4865 "Severe query error in ORDER BY clause -- "
4866 "see error log for more details"
4868 buffer_free( order_buf );
4872 OSRF_BUFFER_ADD( order_buf, transform_str );
4873 free( transform_str );
4876 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4878 const char* direction =
4879 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4881 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4882 OSRF_BUFFER_ADD( order_buf, " DESC" );
4884 OSRF_BUFFER_ADD( order_buf, " ASC" );
4888 return buffer_release( order_buf );
4892 @brief Build a SELECT statement.
4893 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
4894 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
4895 @param meta Pointer to the class metadata for the core class.
4896 @param ctx Pointer to the method context.
4897 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
4899 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
4900 "order_by", "limit", and "offset".
4902 The SELECT statements built here are distinct from those built for the json_query method.
4904 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
4905 osrfHash* meta, osrfMethodContext* ctx ) {
4907 const char* locale = osrf_message_get_last_locale();
4909 osrfHash* fields = osrfHashGet( meta, "fields" );
4910 const char* core_class = osrfHashGet( meta, "classname" );
4912 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
4914 jsonObject* selhash = NULL;
4915 jsonObject* defaultselhash = NULL;
4917 growing_buffer* sql_buf = buffer_init( 128 );
4918 growing_buffer* select_buf = buffer_init( 128 );
4920 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
4921 defaultselhash = jsonNewObjectType( JSON_HASH );
4922 selhash = defaultselhash;
4925 // If there's no SELECT list for the core class, build one
4926 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4927 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4929 // Add every non-virtual field to the field list
4930 osrfHash* field_def = NULL;
4931 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4932 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4933 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4934 const char* field = osrfHashIteratorKey( field_itr );
4935 jsonObjectPush( field_list, jsonNewObject( field ) );
4938 osrfHashIteratorFree( field_itr );
4939 jsonObjectSetKey( selhash, core_class, field_list );
4942 // Build a list of columns for the SELECT clause
4944 const jsonObject* snode = NULL;
4945 jsonIterator* class_itr = jsonNewIterator( selhash );
4946 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
4948 // If the class isn't in the IDL, ignore it
4949 const char* cname = class_itr->key;
4950 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4954 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
4955 if( strcmp( core_class, class_itr->key )) {
4959 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4960 if( !found->size ) {
4961 jsonObjectFree( found );
4965 jsonObjectFree( found );
4968 const jsonObject* node = NULL;
4969 jsonIterator* select_itr = jsonNewIterator( snode );
4970 while( (node = jsonIteratorNext( select_itr )) ) {
4971 const char* item_str = jsonObjectGetString( node );
4972 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4973 char* fname = osrfHashGet( field, "name" );
4981 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4986 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
4987 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4990 i18n = osrfHashGet( field, "i18n" );
4992 if( str_is_true( i18n ) ) {
4993 char* pkey = osrfHashGet( idlClass, "primarykey" );
4994 char* tname = osrfHashGet( idlClass, "tablename" );
4996 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4997 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4998 tname, cname, fname, pkey, cname, pkey, locale, fname );
5000 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5003 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5007 jsonIteratorFree( select_itr );
5010 jsonIteratorFree( class_itr );
5012 char* col_list = buffer_release( select_buf );
5013 char* table = oilsGetRelation( meta );
5015 table = strdup( "(null)" );
5017 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5021 // Clear the query stack (as a fail-safe precaution against possible
5022 // leftover garbage); then push the first query frame onto the stack.
5023 clear_query_stack();
5025 if( add_query_core( NULL, core_class ) ) {
5027 osrfAppSessionStatus(
5029 OSRF_STATUS_INTERNALSERVERERROR,
5030 "osrfMethodException",
5032 "Unable to build query frame for core class"
5034 buffer_free( sql_buf );
5035 if( defaultselhash )
5036 jsonObjectFree( defaultselhash );
5040 // Add the JOIN clauses, if any
5042 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5043 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5044 OSRF_BUFFER_ADD( sql_buf, join_clause );
5045 free( join_clause );
5048 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5049 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5051 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5053 // Add the conditions in the WHERE clause
5054 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5056 osrfAppSessionStatus(
5058 OSRF_STATUS_INTERNALSERVERERROR,
5059 "osrfMethodException",
5061 "Severe query error -- see error log for more details"
5063 buffer_free( sql_buf );
5064 if( defaultselhash )
5065 jsonObjectFree( defaultselhash );
5066 clear_query_stack();
5069 buffer_add( sql_buf, pred );
5073 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5074 if( rest_of_query ) {
5075 const jsonObject* order_by = NULL;
5076 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5078 char* order_by_list = NULL;
5080 if( JSON_ARRAY == order_by->type ) {
5081 order_by_list = buildOrderByFromArray( ctx, order_by );
5082 if( !order_by_list ) {
5083 buffer_free( sql_buf );
5084 if( defaultselhash )
5085 jsonObjectFree( defaultselhash );
5086 clear_query_stack();
5089 } else if( JSON_HASH == order_by->type ) {
5090 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5091 // and build a list of ORDER BY expressions.
5092 growing_buffer* order_buf = buffer_init( 128 );
5094 jsonIterator* class_itr = jsonNewIterator( order_by );
5095 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5097 if( !jsonObjectGetKeyConst( selhash, class_itr->key ))
5098 continue; // class not referenced by SELECT clause? Ignore it.
5100 if( snode->type == JSON_HASH ) {
5102 // If the data for the current class is a JSON_HASH, then it is
5103 // keyed on field name.
5105 const jsonObject* onode = NULL;
5106 jsonIterator* order_itr = jsonNewIterator( snode );
5107 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5109 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5110 class_itr->key, order_itr->key );
5112 continue; // Field not defined in IDL? Ignore it.
5114 char* field_str = NULL;
5115 char* direction = NULL;
5116 if( onode->type == JSON_HASH ) {
5117 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5118 field_str = searchFieldTransform(
5119 class_itr->key, field_def, onode );
5121 osrfAppSessionStatus(
5123 OSRF_STATUS_INTERNALSERVERERROR,
5124 "osrfMethodException",
5126 "Severe query error in ORDER BY clause -- "
5127 "see error log for more details"
5129 jsonIteratorFree( order_itr );
5130 jsonIteratorFree( class_itr );
5131 buffer_free( order_buf );
5132 buffer_free( sql_buf );
5133 if( defaultselhash )
5134 jsonObjectFree( defaultselhash );
5135 clear_query_stack();
5139 growing_buffer* field_buf = buffer_init( 16 );
5140 buffer_fadd( field_buf, "\"%s\".%s",
5141 class_itr->key, order_itr->key );
5142 field_str = buffer_release( field_buf );
5145 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5146 const char* dir = jsonObjectGetString( order_by );
5147 if(!strncasecmp( dir, "d", 1 )) {
5148 direction = " DESC";
5152 field_str = strdup( order_itr->key );
5153 const char* dir = jsonObjectGetString( onode );
5154 if( !strncasecmp( dir, "d", 1 )) {
5155 direction = " DESC";
5164 buffer_add( order_buf, ", " );
5167 buffer_add( order_buf, field_str );
5171 buffer_add( order_buf, direction );
5173 } // end while; looping over ORDER BY expressions
5175 jsonIteratorFree( order_itr );
5178 // The data for the current class is not a JSON_HASH, so we expect
5179 // it to be a JSON_STRING with a single field name.
5180 const char* str = jsonObjectGetString( snode );
5181 buffer_add( order_buf, str );
5185 } // end while; looping over order_by classes
5187 jsonIteratorFree( class_itr );
5188 order_by_list = buffer_release( order_buf );
5191 osrfLogWarning( OSRF_LOG_MARK,
5192 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5193 "no ORDER BY generated" );
5196 if( order_by_list && *order_by_list ) {
5197 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5198 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5201 free( order_by_list );
5204 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5206 const char* str = jsonObjectGetString( limit );
5214 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5216 const char* str = jsonObjectGetString( offset );
5225 if( defaultselhash )
5226 jsonObjectFree( defaultselhash );
5227 clear_query_stack();
5229 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5230 return buffer_release( sql_buf );
5233 int doJSONSearch ( osrfMethodContext* ctx ) {
5234 if(osrfMethodVerifyContext( ctx )) {
5235 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5239 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5243 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5247 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5248 flags |= SELECT_DISTINCT;
5250 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5251 flags |= DISABLE_I18N;
5253 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5254 clear_query_stack(); // a possibly needless precaution
5255 char* sql = buildQuery( ctx, hash, flags );
5256 clear_query_stack();
5263 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5266 dbhandle = writehandle;
5268 dbi_result result = dbi_conn_query( dbhandle, sql );
5271 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5273 if( dbi_result_first_row( result )) {
5274 /* JSONify the result */
5275 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5278 jsonObject* return_val = oilsMakeJSONFromResult( result );
5279 osrfAppRespond( ctx, return_val );
5280 jsonObjectFree( return_val );
5281 } while( dbi_result_next_row( result ));
5284 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5287 osrfAppRespondComplete( ctx, NULL );
5289 /* clean up the query */
5290 dbi_result_free( result );
5295 int errnum = dbi_conn_error( dbhandle, &msg );
5296 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5297 modulename, sql, errnum, msg ? msg : "(No description available)" );
5298 osrfAppSessionStatus(
5300 OSRF_STATUS_INTERNALSERVERERROR,
5301 "osrfMethodException",
5303 "Severe query error -- see error log for more details"
5305 if( !oilsIsDBConnected( dbhandle ))
5306 osrfAppSessionPanic( ctx->session );
5313 // The last parameter, err, is used to report an error condition by updating an int owned by
5314 // the calling code.
5316 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5317 // It is the responsibility of the calling code to initialize *err before the
5318 // call, so that it will be able to make sense of the result.
5320 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5321 // redundant anyway.
5322 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5323 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5326 dbhandle = writehandle;
5328 char* core_class = osrfHashGet( class_meta, "classname" );
5329 char* pkey = osrfHashGet( class_meta, "primarykey" );
5331 const jsonObject* _tmp;
5333 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5335 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5340 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5342 dbi_result result = dbi_conn_query( dbhandle, sql );
5343 if( NULL == result ) {
5345 int errnum = dbi_conn_error( dbhandle, &msg );
5346 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5347 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5348 msg ? msg : "(No description available)" );
5349 if( !oilsIsDBConnected( dbhandle ))
5350 osrfAppSessionPanic( ctx->session );
5351 osrfAppSessionStatus(
5353 OSRF_STATUS_INTERNALSERVERERROR,
5354 "osrfMethodException",
5356 "Severe query error -- see error log for more details"
5363 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5366 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5367 jsonObject* row_obj = NULL;
5369 if( dbi_result_first_row( result )) {
5371 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5372 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5373 // eliminate the duplicates.
5374 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5375 osrfHash* dedup = osrfNewHash();
5377 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5378 char* pkey_val = oilsFMGetString( row_obj, pkey );
5379 if( osrfHashGet( dedup, pkey_val ) ) {
5380 jsonObjectFree( row_obj );
5383 osrfHashSet( dedup, pkey_val, pkey_val );
5384 jsonObjectPush( res_list, row_obj );
5386 } while( dbi_result_next_row( result ));
5387 osrfHashFree( dedup );
5390 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5394 /* clean up the query */
5395 dbi_result_free( result );
5398 // If we're asked to flesh, and there's anything to flesh, then flesh it
5399 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5400 // something that he has no permission to look at).
5401 if( res_list->size && query_hash && ! enforce_pcrud ) {
5402 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5404 // Get the flesh depth
5405 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5406 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5407 flesh_depth = max_flesh_depth;
5409 // We need a non-zero flesh depth, and a list of fields to flesh
5410 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5411 if( temp_blob && flesh_depth > 0 ) {
5413 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5414 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5416 osrfStringArray* link_fields = NULL;
5417 osrfHash* links = osrfHashGet( class_meta, "links" );
5419 // Make an osrfStringArray of the names of fields to be fleshed
5420 if( flesh_fields ) {
5421 if( flesh_fields->size == 1 ) {
5422 const char* _t = jsonObjectGetString(
5423 jsonObjectGetIndex( flesh_fields, 0 ) );
5424 if( !strcmp( _t, "*" ))
5425 link_fields = osrfHashKeys( links );
5428 if( !link_fields ) {
5430 link_fields = osrfNewStringArray( 1 );
5431 jsonIterator* _i = jsonNewIterator( flesh_fields );
5432 while ((_f = jsonIteratorNext( _i ))) {
5433 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5435 jsonIteratorFree( _i );
5439 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5441 // Iterate over the JSON_ARRAY of rows
5443 unsigned long res_idx = 0;
5444 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5447 const char* link_field;
5449 // Iterate over the list of fleshable fields
5450 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5452 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5454 osrfHash* kid_link = osrfHashGet( links, link_field );
5456 continue; // Not a link field; skip it
5458 osrfHash* field = osrfHashGet( fields, link_field );
5460 continue; // Not a field at all; skip it (IDL is ill-formed)
5462 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5463 osrfHashGet( kid_link, "class" ));
5465 continue; // The class it links to doesn't exist; skip it
5467 const char* reltype = osrfHashGet( kid_link, "reltype" );
5469 continue; // No reltype; skip it (IDL is ill-formed)
5471 osrfHash* value_field = field;
5473 if( !strcmp( reltype, "has_many" )
5474 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5475 value_field = osrfHashGet(
5476 fields, osrfHashGet( class_meta, "primarykey" ) );
5479 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5481 if( link_map->size > 0 ) {
5482 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5485 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5490 osrfHashGet( kid_link, "class" ),
5497 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5498 osrfHashGet( kid_link, "field" ),
5499 osrfHashGet( kid_link, "class" ),
5500 osrfHashGet( kid_link, "key" ),
5501 osrfHashGet( kid_link, "reltype" )
5504 const char* search_key = jsonObjectGetString(
5505 jsonObjectGetIndex( cur,
5506 atoi( osrfHashGet( value_field, "array_position" ) )
5511 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5515 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5517 // construct WHERE clause
5518 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5521 osrfHashGet( kid_link, "key" ),
5522 jsonNewObject( search_key )
5525 // construct the rest of the query, mostly
5526 // by copying pieces of the previous level of query
5527 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5528 jsonObjectSetKey( rest_of_query, "flesh",
5529 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5533 jsonObjectSetKey( rest_of_query, "flesh_fields",
5534 jsonObjectClone( flesh_blob ));
5536 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5537 jsonObjectSetKey( rest_of_query, "order_by",
5538 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5542 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5543 jsonObjectSetKey( rest_of_query, "select",
5544 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5548 // do the query, recursively, to expand the fleshable field
5549 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5550 where_clause, rest_of_query, err );
5552 jsonObjectFree( where_clause );
5553 jsonObjectFree( rest_of_query );
5556 osrfStringArrayFree( link_fields );
5557 jsonObjectFree( res_list );
5558 jsonObjectFree( flesh_blob );
5562 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5563 osrfHashGet( kid_link, "class" ), kids->size );
5565 // Traverse the result set
5566 jsonObject* X = NULL;
5567 if( link_map->size > 0 && kids->size > 0 ) {
5569 kids = jsonNewObjectType( JSON_ARRAY );
5571 jsonObject* _k_node;
5572 unsigned long res_idx = 0;
5573 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5579 (unsigned long) atoi(
5585 osrfHashGet( kid_link, "class" )
5589 osrfStringArrayGetString( link_map, 0 )
5597 } // end while loop traversing X
5600 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5601 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5602 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5603 osrfHashGet( kid_link, "field" ));
5606 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5607 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5611 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5613 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5614 osrfHashGet( kid_link, "field" ) );
5617 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5618 jsonObjectClone( kids )
5623 jsonObjectFree( kids );
5627 jsonObjectFree( kids );
5629 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5630 osrfHashGet( kid_link, "field" ) );
5631 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5633 } // end while loop traversing list of fleshable fields
5634 } // end while loop traversing res_list
5635 jsonObjectFree( flesh_blob );
5636 osrfStringArrayFree( link_fields );
5645 int doUpdate( osrfMethodContext* ctx ) {
5646 if( osrfMethodVerifyContext( ctx )) {
5647 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5652 timeout_needs_resetting = 1;
5654 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5656 jsonObject* target = NULL;
5658 target = jsonObjectGetIndex( ctx->params, 1 );
5660 target = jsonObjectGetIndex( ctx->params, 0 );
5662 if(!verifyObjectClass( ctx, target )) {
5663 osrfAppRespondComplete( ctx, NULL );
5667 if( getXactId( ctx ) == NULL ) {
5668 osrfAppSessionStatus(
5670 OSRF_STATUS_BADREQUEST,
5671 "osrfMethodException",
5673 "No active transaction -- required for UPDATE"
5675 osrfAppRespondComplete( ctx, NULL );
5679 // The following test is harmless but redundant. If a class is
5680 // readonly, we don't register an update method for it.
5681 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5682 osrfAppSessionStatus(
5684 OSRF_STATUS_BADREQUEST,
5685 "osrfMethodException",
5687 "Cannot UPDATE readonly class"
5689 osrfAppRespondComplete( ctx, NULL );
5693 const char* trans_id = getXactId( ctx );
5695 // Set the last_xact_id
5696 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5698 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5699 trans_id, target->classname, index );
5700 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5703 char* pkey = osrfHashGet( meta, "primarykey" );
5704 osrfHash* fields = osrfHashGet( meta, "fields" );
5706 char* id = oilsFMGetString( target, pkey );
5710 "%s updating %s object with %s = %s",
5712 osrfHashGet( meta, "fieldmapper" ),
5717 dbhandle = writehandle;
5718 growing_buffer* sql = buffer_init( 128 );
5719 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5722 osrfHash* field_def = NULL;
5723 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5724 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5726 // Skip virtual fields, and the primary key
5727 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5730 const char* field_name = osrfHashIteratorKey( field_itr );
5731 if( ! strcmp( field_name, pkey ) )
5734 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5736 int value_is_numeric = 0; // boolean
5738 if( field_object && field_object->classname ) {
5739 value = oilsFMGetString(
5741 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5743 } else if( field_object && JSON_BOOL == field_object->type ) {
5744 if( jsonBoolIsTrue( field_object ) )
5745 value = strdup( "t" );
5747 value = strdup( "f" );
5749 value = jsonObjectToSimpleString( field_object );
5750 if( field_object && JSON_NUMBER == field_object->type )
5751 value_is_numeric = 1;
5754 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5755 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5757 if( !field_object || field_object->type == JSON_NULL ) {
5758 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5759 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5763 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5764 buffer_fadd( sql, " %s = NULL", field_name );
5767 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5771 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5773 const char* numtype = get_datatype( field_def );
5774 if( !strncmp( numtype, "INT", 3 ) ) {
5775 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5776 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5777 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5779 // Must really be intended as a string, so quote it
5780 if( dbi_conn_quote_string( dbhandle, &value )) {
5781 buffer_fadd( sql, " %s = %s", field_name, value );
5783 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5784 modulename, value );
5785 osrfAppSessionStatus(
5787 OSRF_STATUS_INTERNALSERVERERROR,
5788 "osrfMethodException",
5790 "Error quoting string -- please see the error log for more details"
5794 osrfHashIteratorFree( field_itr );
5796 osrfAppRespondComplete( ctx, NULL );
5801 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5804 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5808 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5809 buffer_fadd( sql, " %s = %s", field_name, value );
5811 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5812 osrfAppSessionStatus(
5814 OSRF_STATUS_INTERNALSERVERERROR,
5815 "osrfMethodException",
5817 "Error quoting string -- please see the error log for more details"
5821 osrfHashIteratorFree( field_itr );
5823 osrfAppRespondComplete( ctx, NULL );
5832 osrfHashIteratorFree( field_itr );
5834 jsonObject* obj = jsonNewObject( id );
5836 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5837 dbi_conn_quote_string( dbhandle, &id );
5839 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5841 char* query = buffer_release( sql );
5842 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5844 dbi_result result = dbi_conn_query( dbhandle, query );
5849 jsonObjectFree( obj );
5850 obj = jsonNewObject( NULL );
5852 int errnum = dbi_conn_error( dbhandle, &msg );
5855 "%s ERROR updating %s object with %s = %s: %d %s",
5857 osrfHashGet( meta, "fieldmapper" ),
5861 msg ? msg : "(No description available)"
5863 osrfAppSessionStatus(
5865 OSRF_STATUS_INTERNALSERVERERROR,
5866 "osrfMethodException",
5868 "Error in updating a row -- please see the error log for more details"
5870 if( !oilsIsDBConnected( dbhandle ))
5871 osrfAppSessionPanic( ctx->session );
5874 dbi_result_free( result );
5877 osrfAppRespondComplete( ctx, obj );
5878 jsonObjectFree( obj );
5882 int doDelete( osrfMethodContext* ctx ) {
5883 if( osrfMethodVerifyContext( ctx )) {
5884 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5889 timeout_needs_resetting = 1;
5891 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5893 if( getXactId( ctx ) == NULL ) {
5894 osrfAppSessionStatus(
5896 OSRF_STATUS_BADREQUEST,
5897 "osrfMethodException",
5899 "No active transaction -- required for DELETE"
5901 osrfAppRespondComplete( ctx, NULL );
5905 // The following test is harmless but redundant. If a class is
5906 // readonly, we don't register a delete method for it.
5907 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5908 osrfAppSessionStatus(
5910 OSRF_STATUS_BADREQUEST,
5911 "osrfMethodException",
5913 "Cannot DELETE readonly class"
5915 osrfAppRespondComplete( ctx, NULL );
5919 dbhandle = writehandle;
5921 char* pkey = osrfHashGet( meta, "primarykey" );
5928 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5929 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5930 osrfAppRespondComplete( ctx, NULL );
5934 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5936 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5937 osrfAppRespondComplete( ctx, NULL );
5940 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5945 "%s deleting %s object with %s = %s",
5947 osrfHashGet( meta, "fieldmapper" ),
5952 jsonObject* obj = jsonNewObject( id );
5954 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5955 dbi_conn_quote_string( writehandle, &id );
5957 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5958 osrfHashGet( meta, "tablename" ), pkey, id );
5963 jsonObjectFree( obj );
5964 obj = jsonNewObject( NULL );
5966 int errnum = dbi_conn_error( writehandle, &msg );
5969 "%s ERROR deleting %s object with %s = %s: %d %s",
5971 osrfHashGet( meta, "fieldmapper" ),
5975 msg ? msg : "(No description available)"
5977 osrfAppSessionStatus(
5979 OSRF_STATUS_INTERNALSERVERERROR,
5980 "osrfMethodException",
5982 "Error in deleting a row -- please see the error log for more details"
5984 if( !oilsIsDBConnected( writehandle ))
5985 osrfAppSessionPanic( ctx->session );
5987 dbi_result_free( result );
5991 osrfAppRespondComplete( ctx, obj );
5992 jsonObjectFree( obj );
5997 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5998 @param result An iterator for a result set; we only look at the current row.
5999 @param @meta Pointer to the class metadata for the core class.
6000 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6002 If a column is not defined in the IDL, or if it has no array_position defined for it in
6003 the IDL, or if it is defined as virtual, ignore it.
6005 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6006 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6007 array_position in the IDL.
6009 A field defined in the IDL but not represented in the returned row will leave a hole
6010 in the JSON_ARRAY. In effect it will be treated as a null value.
6012 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6013 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6014 classname corresponding to the @a meta argument.
6016 The calling code is responsible for freeing the the resulting jsonObject by calling
6019 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6020 if( !( result && meta )) return NULL;
6022 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6023 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6024 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6026 osrfHash* fields = osrfHashGet( meta, "fields" );
6028 int columnIndex = 1;
6029 const char* columnName;
6031 /* cycle through the columns in the row returned from the database */
6032 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6034 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6036 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6038 /* determine the field type and storage attributes */
6039 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6040 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6042 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6043 // or if it has no sequence number there, or if it's virtual, skip it.
6044 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6047 if( str_is_true( osrfHashGet( _f, "virtual" )))
6048 continue; // skip this column: IDL says it's virtual
6050 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6051 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6052 continue; // since we assign sequence numbers dynamically as we load the IDL.
6054 fmIndex = atoi( pos );
6055 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6057 continue; // This field is not defined in the IDL
6060 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6061 // sequence number from the IDL (which is likely to be different from the sequence
6062 // of columns in the SELECT clause).
6063 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6064 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6069 case DBI_TYPE_INTEGER :
6071 if( attr & DBI_INTEGER_SIZE8 )
6072 jsonObjectSetIndex( object, fmIndex,
6073 jsonNewNumberObject(
6074 dbi_result_get_longlong_idx( result, columnIndex )));
6076 jsonObjectSetIndex( object, fmIndex,
6077 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6081 case DBI_TYPE_DECIMAL :
6082 jsonObjectSetIndex( object, fmIndex,
6083 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6086 case DBI_TYPE_STRING :
6091 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6096 case DBI_TYPE_DATETIME : {
6098 char dt_string[ 256 ] = "";
6101 // Fetch the date column as a time_t
6102 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6104 // Translate the time_t to a human-readable string
6105 if( !( attr & DBI_DATETIME_DATE )) {
6106 gmtime_r( &_tmp_dt, &gmdt );
6107 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6108 } else if( !( attr & DBI_DATETIME_TIME )) {
6109 localtime_r( &_tmp_dt, &gmdt );
6110 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6112 localtime_r( &_tmp_dt, &gmdt );
6113 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6116 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6120 case DBI_TYPE_BINARY :
6121 osrfLogError( OSRF_LOG_MARK,
6122 "Can't do binary at column %s : index %d", columnName, columnIndex );
6131 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6132 if( !result ) return NULL;
6134 jsonObject* object = jsonNewObject( NULL );
6137 char dt_string[ 256 ];
6141 int columnIndex = 1;
6143 unsigned short type;
6144 const char* columnName;
6146 /* cycle through the column list */
6147 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6149 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6151 fmIndex = -1; // reset the position
6153 /* determine the field type and storage attributes */
6154 type = dbi_result_get_field_type_idx( result, columnIndex );
6155 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6157 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6158 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6163 case DBI_TYPE_INTEGER :
6165 if( attr & DBI_INTEGER_SIZE8 )
6166 jsonObjectSetKey( object, columnName,
6167 jsonNewNumberObject( dbi_result_get_longlong_idx(
6168 result, columnIndex )) );
6170 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6171 dbi_result_get_int_idx( result, columnIndex )) );
6174 case DBI_TYPE_DECIMAL :
6175 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6176 dbi_result_get_double_idx( result, columnIndex )) );
6179 case DBI_TYPE_STRING :
6180 jsonObjectSetKey( object, columnName,
6181 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6184 case DBI_TYPE_DATETIME :
6186 memset( dt_string, '\0', sizeof( dt_string ));
6187 memset( &gmdt, '\0', sizeof( gmdt ));
6189 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6191 if( !( attr & DBI_DATETIME_DATE )) {
6192 gmtime_r( &_tmp_dt, &gmdt );
6193 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6194 } else if( !( attr & DBI_DATETIME_TIME )) {
6195 localtime_r( &_tmp_dt, &gmdt );
6196 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6198 localtime_r( &_tmp_dt, &gmdt );
6199 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6202 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6205 case DBI_TYPE_BINARY :
6206 osrfLogError( OSRF_LOG_MARK,
6207 "Can't do binary at column %s : index %d", columnName, columnIndex );
6211 } // end while loop traversing result
6216 // Interpret a string as true or false
6217 int str_is_true( const char* str ) {
6218 if( NULL == str || strcasecmp( str, "true" ) )
6224 // Interpret a jsonObject as true or false
6225 static int obj_is_true( const jsonObject* obj ) {
6228 else switch( obj->type )
6236 if( strcasecmp( obj->value.s, "true" ) )
6240 case JSON_NUMBER : // Support 1/0 for perl's sake
6241 if( jsonObjectGetNumber( obj ) == 1.0 )
6250 // Translate a numeric code into a text string identifying a type of
6251 // jsonObject. To be used for building error messages.
6252 static const char* json_type( int code ) {
6258 return "JSON_ARRAY";
6260 return "JSON_STRING";
6262 return "JSON_NUMBER";
6268 return "(unrecognized)";
6272 // Extract the "primitive" attribute from an IDL field definition.
6273 // If we haven't initialized the app, then we must be running in
6274 // some kind of testbed. In that case, default to "string".
6275 static const char* get_primitive( osrfHash* field ) {
6276 const char* s = osrfHashGet( field, "primitive" );
6278 if( child_initialized )
6281 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6283 osrfHashGet( field, "name" )
6291 // Extract the "datatype" attribute from an IDL field definition.
6292 // If we haven't initialized the app, then we must be running in
6293 // some kind of testbed. In that case, default to to NUMERIC,
6294 // since we look at the datatype only for numbers.
6295 static const char* get_datatype( osrfHash* field ) {
6296 const char* s = osrfHashGet( field, "datatype" );
6298 if( child_initialized )
6301 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6303 osrfHashGet( field, "name" )
6312 @brief Determine whether a string is potentially a valid SQL identifier.
6313 @param s The identifier to be tested.
6314 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6316 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6317 need to follow all the rules exactly, such as requiring that the first character not
6320 We allow leading and trailing white space. In between, we do not allow punctuation
6321 (except for underscores and dollar signs), control characters, or embedded white space.
6323 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6324 for the foreseeable future such quoted identifiers are not likely to be an issue.
6326 int is_identifier( const char* s) {
6330 // Skip leading white space
6331 while( isspace( (unsigned char) *s ) )
6335 return 0; // Nothing but white space? Not okay.
6337 // Check each character until we reach white space or
6338 // end-of-string. Letters, digits, underscores, and
6339 // dollar signs are okay. With the exception of periods
6340 // (as in schema.identifier), control characters and other
6341 // punctuation characters are not okay. Anything else
6342 // is okay -- it could for example be part of a multibyte
6343 // UTF8 character such as a letter with diacritical marks,
6344 // and those are allowed.
6346 if( isalnum( (unsigned char) *s )
6350 ; // Fine; keep going
6351 else if( ispunct( (unsigned char) *s )
6352 || iscntrl( (unsigned char) *s ) )
6355 } while( *s && ! isspace( (unsigned char) *s ) );
6357 // If we found any white space in the above loop,
6358 // the rest had better be all white space.
6360 while( isspace( (unsigned char) *s ) )
6364 return 0; // White space was embedded within non-white space
6370 @brief Determine whether to accept a character string as a comparison operator.
6371 @param op The candidate comparison operator.
6372 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6374 We don't validate the operator for real. We just make sure that it doesn't contain
6375 any semicolons or white space (with special exceptions for a few specific operators).
6376 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6377 space but it's still not a valid operator, then the database will complain.
6379 Another approach would be to compare the string against a short list of approved operators.
6380 We don't do that because we want to allow custom operators like ">100*", which at this
6381 writing would be difficult or impossible to express otherwise in a JSON query.
6383 int is_good_operator( const char* op ) {
6384 if( !op ) return 0; // Sanity check
6388 if( isspace( (unsigned char) *s ) ) {
6389 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6390 // and IS NOT DISTINCT FROM.
6391 if( !strcasecmp( op, "similar to" ) )
6393 else if( !strcasecmp( op, "is distinct from" ) )
6395 else if( !strcasecmp( op, "is not distinct from" ) )
6400 else if( ';' == *s )
6408 @name Query Frame Management
6410 The following machinery supports a stack of query frames for use by SELECT().
6412 A query frame caches information about one level of a SELECT query. When we enter
6413 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6415 The query frame stores information about the core class, and about any joined classes
6418 The main purpose is to map table aliases to classes and tables, so that a query can
6419 join to the same table more than once. A secondary goal is to reduce the number of
6420 lookups in the IDL by caching the results.
6424 #define STATIC_CLASS_INFO_COUNT 3
6426 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6429 @brief Allocate a ClassInfo as raw memory.
6430 @return Pointer to the newly allocated ClassInfo.
6432 Except for the in_use flag, which is used only by the allocation and deallocation
6433 logic, we don't initialize the ClassInfo here.
6435 static ClassInfo* allocate_class_info( void ) {
6436 // In order to reduce the number of mallocs and frees, we return a static
6437 // instance of ClassInfo, if we can find one that we're not already using.
6438 // We rely on the fact that the compiler will implicitly initialize the
6439 // static instances so that in_use == 0.
6442 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6443 if( ! static_class_info[ i ].in_use ) {
6444 static_class_info[ i ].in_use = 1;
6445 return static_class_info + i;
6449 // The static ones are all in use. Malloc one.
6451 return safe_malloc( sizeof( ClassInfo ) );
6455 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6456 @param info Pointer to the ClassInfo to be cleared.
6458 static void clear_class_info( ClassInfo* info ) {
6463 // Free any malloc'd strings
6465 if( info->alias != info->alias_store )
6466 free( info->alias );
6468 if( info->class_name != info->class_name_store )
6469 free( info->class_name );
6471 free( info->source_def );
6473 info->alias = info->class_name = info->source_def = NULL;
6478 @brief Free a ClassInfo and everything it owns.
6479 @param info Pointer to the ClassInfo to be freed.
6481 static void free_class_info( ClassInfo* info ) {
6486 clear_class_info( info );
6488 // If it's one of the static instances, just mark it as not in use
6491 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6492 if( info == static_class_info + i ) {
6493 static_class_info[ i ].in_use = 0;
6498 // Otherwise it must have been malloc'd, so free it
6504 @brief Populate an already-allocated ClassInfo.
6505 @param info Pointer to the ClassInfo to be populated.
6506 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6508 @param class Name of the class.
6509 @return Zero if successful, or 1 if not.
6511 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6512 the relevant portions of the IDL for the specified class.
6514 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6517 osrfLogError( OSRF_LOG_MARK,
6518 "%s ERROR: No ClassInfo available to populate", modulename );
6519 info->alias = info->class_name = info->source_def = NULL;
6520 info->class_def = info->fields = info->links = NULL;
6525 osrfLogError( OSRF_LOG_MARK,
6526 "%s ERROR: No class name provided for lookup", modulename );
6527 info->alias = info->class_name = info->source_def = NULL;
6528 info->class_def = info->fields = info->links = NULL;
6532 // Alias defaults to class name if not supplied
6533 if( ! alias || ! alias[ 0 ] )
6536 // Look up class info in the IDL
6537 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6539 osrfLogError( OSRF_LOG_MARK,
6540 "%s ERROR: Class %s not defined in IDL", modulename, class );
6541 info->alias = info->class_name = info->source_def = NULL;
6542 info->class_def = info->fields = info->links = NULL;
6544 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6545 osrfLogError( OSRF_LOG_MARK,
6546 "%s ERROR: Class %s is defined as virtual", modulename, class );
6547 info->alias = info->class_name = info->source_def = NULL;
6548 info->class_def = info->fields = info->links = NULL;
6552 osrfHash* links = osrfHashGet( class_def, "links" );
6554 osrfLogError( OSRF_LOG_MARK,
6555 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6556 info->alias = info->class_name = info->source_def = NULL;
6557 info->class_def = info->fields = info->links = NULL;
6561 osrfHash* fields = osrfHashGet( class_def, "fields" );
6563 osrfLogError( OSRF_LOG_MARK,
6564 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6565 info->alias = info->class_name = info->source_def = NULL;
6566 info->class_def = info->fields = info->links = NULL;
6570 char* source_def = oilsGetRelation( class_def );
6574 // We got everything we need, so populate the ClassInfo
6575 if( strlen( alias ) > ALIAS_STORE_SIZE )
6576 info->alias = strdup( alias );
6578 strcpy( info->alias_store, alias );
6579 info->alias = info->alias_store;
6582 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6583 info->class_name = strdup( class );
6585 strcpy( info->class_name_store, class );
6586 info->class_name = info->class_name_store;
6589 info->source_def = source_def;
6591 info->class_def = class_def;
6592 info->links = links;
6593 info->fields = fields;
6598 #define STATIC_FRAME_COUNT 3
6600 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6603 @brief Allocate a QueryFrame as raw memory.
6604 @return Pointer to the newly allocated QueryFrame.
6606 Except for the in_use flag, which is used only by the allocation and deallocation
6607 logic, we don't initialize the QueryFrame here.
6609 static QueryFrame* allocate_frame( void ) {
6610 // In order to reduce the number of mallocs and frees, we return a static
6611 // instance of QueryFrame, if we can find one that we're not already using.
6612 // We rely on the fact that the compiler will implicitly initialize the
6613 // static instances so that in_use == 0.
6616 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6617 if( ! static_frame[ i ].in_use ) {
6618 static_frame[ i ].in_use = 1;
6619 return static_frame + i;
6623 // The static ones are all in use. Malloc one.
6625 return safe_malloc( sizeof( QueryFrame ) );
6629 @brief Free a QueryFrame, and all the memory it owns.
6630 @param frame Pointer to the QueryFrame to be freed.
6632 static void free_query_frame( QueryFrame* frame ) {
6637 clear_class_info( &frame->core );
6639 // Free the join list
6641 ClassInfo* info = frame->join_list;
6644 free_class_info( info );
6648 frame->join_list = NULL;
6651 // If the frame is a static instance, just mark it as unused
6653 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6654 if( frame == static_frame + i ) {
6655 static_frame[ i ].in_use = 0;
6660 // Otherwise it must have been malloc'd, so free it
6666 @brief Search a given QueryFrame for a specified alias.
6667 @param frame Pointer to the QueryFrame to be searched.
6668 @param target The alias for which to search.
6669 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6671 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6672 if( ! frame || ! target ) {
6676 ClassInfo* found_class = NULL;
6678 if( !strcmp( target, frame->core.alias ) )
6679 return &(frame->core);
6681 ClassInfo* curr_class = frame->join_list;
6682 while( curr_class ) {
6683 if( strcmp( target, curr_class->alias ) )
6684 curr_class = curr_class->next;
6686 found_class = curr_class;
6696 @brief Push a new (blank) QueryFrame onto the stack.
6698 static void push_query_frame( void ) {
6699 QueryFrame* frame = allocate_frame();
6700 frame->join_list = NULL;
6701 frame->next = curr_query;
6703 // Initialize the ClassInfo for the core class
6704 ClassInfo* core = &frame->core;
6705 core->alias = core->class_name = core->source_def = NULL;
6706 core->class_def = core->fields = core->links = NULL;
6712 @brief Pop a QueryFrame off the stack and destroy it.
6714 static void pop_query_frame( void ) {
6719 QueryFrame* popped = curr_query;
6720 curr_query = popped->next;
6722 free_query_frame( popped );
6726 @brief Populate the ClassInfo for the core class.
6727 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6728 class name as an alias.
6729 @param class_name Name of the core class.
6730 @return Zero if successful, or 1 if not.
6732 Populate the ClassInfo of the core class with copies of the alias and class name, and
6733 with pointers to the relevant portions of the IDL for the core class.
6735 static int add_query_core( const char* alias, const char* class_name ) {
6738 if( ! curr_query ) {
6739 osrfLogError( OSRF_LOG_MARK,
6740 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6742 } else if( curr_query->core.alias ) {
6743 osrfLogError( OSRF_LOG_MARK,
6744 "%s ERROR: Core class %s already populated as %s",
6745 modulename, curr_query->core.class_name, curr_query->core.alias );
6749 build_class_info( &curr_query->core, alias, class_name );
6750 if( curr_query->core.alias )
6753 osrfLogError( OSRF_LOG_MARK,
6754 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6760 @brief Search the current QueryFrame for a specified alias.
6761 @param target The alias for which to search.
6762 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6764 static inline ClassInfo* search_alias( const char* target ) {
6765 return search_alias_in_frame( curr_query, target );
6769 @brief Search all levels of query for a specified alias, starting with the current query.
6770 @param target The alias for which to search.
6771 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6773 static ClassInfo* search_all_alias( const char* target ) {
6774 ClassInfo* found_class = NULL;
6775 QueryFrame* curr_frame = curr_query;
6777 while( curr_frame ) {
6778 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6781 curr_frame = curr_frame->next;
6788 @brief Add a class to the list of classes joined to the current query.
6789 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6790 the class name as an alias.
6791 @param classname The name of the class to be added.
6792 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6794 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6796 if( ! classname || ! *classname ) { // sanity check
6797 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6804 const ClassInfo* conflict = search_alias( alias );
6806 osrfLogError( OSRF_LOG_MARK,
6807 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6808 modulename, alias, conflict->class_name );
6812 ClassInfo* info = allocate_class_info();
6814 if( build_class_info( info, alias, classname ) ) {
6815 free_class_info( info );
6819 // Add the new ClassInfo to the join list of the current QueryFrame
6820 info->next = curr_query->join_list;
6821 curr_query->join_list = info;
6827 @brief Destroy all nodes on the query stack.
6829 static void clear_query_stack( void ) {