Add partial support for bind variables: load them from the
authorscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 19 May 2010 12:46:18 +0000 (12:46 +0000)
committerscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 19 May 2010 12:46:18 +0000 (12:46 +0000)
database, and use default values if available.  There is no
mechanism yet to override the defaults.

git-svn-id: svn://svn.open-ils.org/ILS/trunk@16450 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/include/openils/oils_buildq.h
Open-ILS/src/c-apps/buildSQL.c
Open-ILS/src/c-apps/oils_buildq.c
Open-ILS/src/c-apps/oils_qstore.c
Open-ILS/src/c-apps/oils_storedq.c

index 756b3f8..5ddc7b7 100644 (file)
@@ -21,6 +21,9 @@ typedef struct FromRelation_ FromRelation;
 struct SelectItem_;
 typedef struct SelectItem_ SelectItem;
 
+struct BindVar_;
+typedef struct BindVar_ BindVar;
+
 struct Expression_;
 typedef struct Expression_ Expression;
 
@@ -50,10 +53,15 @@ struct BuildSQLState_ {
        int error;                    /**< Boolean; true if an error has occurred */
        osrfStringArray* error_msgs;  /**< Descriptions of errors, if any */
        growing_buffer* sql;          /**< To hold the constructed query */
+       osrfHash* bindvar_list;       /**< List of bind variables used by this query, each with
+                                          a pointer to the corresponding BindVar. */
        IdNode* query_stack;          /**< For avoiding infinite recursion of nested queries */
        IdNode* expr_stack;           /**< For avoiding infinite recursion of nested expressions */
        IdNode* from_stack;           /**< For avoiding infinite recursion of from clauses */
        int indent;                   /**< For prettifying SQL output: level of indentation */
+       int defaults_usable;          /**< Boolean; if true, we can use unconfirmed default
+                                          values for bind variables */
+       int values_required;          /**< Boolean: if true, we need values for a bind variables */
 };
 
 typedef enum {
@@ -119,7 +127,25 @@ struct SelectItem_ {
 };
 
 typedef enum {
+       BIND_STR,
+       BIND_NUM,
+       BIND_STR_LIST,
+       BIND_NUM_LIST
+} BindVarType;
+
+struct BindVar_ {
+       BindVar*    next;
+       char*       name;
+       char*       label;
+       BindVarType type;
+       char*       description;
+       jsonObject* default_value;
+       jsonObject* actual_value;
+};
+
+typedef enum {
        EXP_BETWEEN,
+       EXP_BIND,
        EXP_BOOL,
        EXP_CASE,
        EXP_CAST,
@@ -153,6 +179,7 @@ struct Expression_ {
        StoredQ*    subquery;
        int         cast_type_id;
        int         negate;             // Boolean
+       BindVar*    bind;
 };
 
 struct QSeq_ {
index 5f50521..cc1c5b8 100644 (file)
@@ -9,6 +9,7 @@
 #include <dbi/dbi.h>
 #include "opensrf/utils.h"
 #include "opensrf/string_array.h"
+#include "opensrf/osrf_hash.h"
 #include "opensrf/osrf_application.h"
 #include "openils/oils_idl.h"
 #include "openils/oils_sql.h"
@@ -22,6 +23,8 @@ static void buildJoin( BuildSQLState* state, FromRelation* join );
 static void buildSelectList( BuildSQLState* state, SelectItem* item );
 static void buildOrderBy( BuildSQLState* state, OrderItem* ord_list );
 static void buildExpression( BuildSQLState* state, Expression* expr );
+static void buildBindVar( BuildSQLState* state, BindVar* bind );
+static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj );
 
 static void add_newline( BuildSQLState* state );
 static inline void incr_indent( BuildSQLState* state );
@@ -171,9 +174,9 @@ static void buildSelect( BuildSQLState* state, StoredQ* query ) {
                decr_indent( state );
        }
 
-       // To do: build GROUP BY clause, if there is one
+    // To do: build GROUP BY clause, if there is one
 
-       // Build HAVING clause, if there is one
+    // Build HAVING clause, if there is one
        if( query->having_clause ) {
                add_newline( state );
                buffer_add( state->sql, "HAVING" );
@@ -187,7 +190,7 @@ static void buildSelect( BuildSQLState* state, StoredQ* query ) {
                }
                decr_indent( state );
        }
-
+       
        // Build ORDER BY clause, if there is one
        if( query->order_by_list ) {
                buildOrderBy( state, query->order_by_list );
@@ -453,6 +456,14 @@ static void buildExpression( BuildSQLState* state, Expression* expr ) {
                        sqlAddMsg( state, "BETWEEN expressions not yet supported" );
                        state->error = 1;
                        break;
+               case EXP_BIND :
+                       if( !expr->bind ) {     // Sanity check
+                               osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                                       "Internal error: no variable for bind variable expression" ));
+                               state->error = 1;
+                       } else
+                               buildBindVar( state, expr->bind );
+                       break;
                case EXP_BOOL :
                        if( expr->negate )
                                buffer_add( state->sql, "NOT " );
@@ -471,6 +482,9 @@ static void buildExpression( BuildSQLState* state, Expression* expr ) {
                        state->error = 1;
                        break;
                case EXP_CAST :                   // Type cast
+                       if( expr->negate )
+                               buffer_add( state->sql, "NOT " );
+
                        sqlAddMsg( state, "Cast expressions not yet supported" );
                        state->error = 1;
                        break;
@@ -509,6 +523,9 @@ static void buildExpression( BuildSQLState* state, Expression* expr ) {
                        }
                        break;
                case EXP_FIELD :
+                       if( expr->negate )
+                               buffer_add( state->sql, "NOT " );
+
                        sqlAddMsg( state, "Field expressions not yet supported" );
                        state->error = 1;
                        break;
@@ -589,6 +606,7 @@ static void buildExpression( BuildSQLState* state, Expression* expr ) {
                                        "Internal error: No string value in string expression # %d", expr->id ));
                                        state->error = 1;
                        } else {
+                               // To do: escape special characters in the string
                                buffer_add_char( state->sql, '\'' );
                                buffer_add( state->sql, expr->literal );
                                buffer_add_char( state->sql, '\'' );
@@ -617,6 +635,141 @@ static void buildExpression( BuildSQLState* state, Expression* expr ) {
                buffer_add_char( state->sql, ')' );
 }
 
+/**
+       @brief Add the value of a bind variable to an SQL statement.
+       @param state Pointer to the query-building context.
+       @param bind Pointer to the bind variable whose value is to be added to the SQL.
+
+       The value may be a null, a scalar, or an array of nulls and/or scalars, depending on
+       the type of the bind variable.
+*/
+static void buildBindVar( BuildSQLState* state, BindVar* bind ) {
+
+       // Decide where to get the value, if any
+       const jsonObject* value = NULL;
+       if( bind->actual_value )
+               value = bind->actual_value;
+       else if( bind->default_value ) {
+               if( state->defaults_usable )
+                       value = bind->default_value;
+               else {
+                       sqlAddMsg( state, "No confirmed value available for bind variable \"%s\"",
+                               bind->name );
+                       state->error = 1;
+                       return;
+               }
+       } else if( state->values_required ) {
+               sqlAddMsg( state, "No value available for bind variable \"%s\"", bind->name );
+               state->error = 1;
+               return;
+       } else {
+               // No value available, and that's okay.  Emit the name of the bind variable.
+               buffer_add_char( state->sql, ':' );
+               buffer_add( state->sql, bind->name );
+               return;
+       }
+
+       // If we get to this point, we know that a value is available.  Carry on.
+
+       int numeric = 0;       // Boolean
+       if( BIND_NUM == bind->type || BIND_NUM_LIST == bind->type )
+               numeric = 1;
+
+       // Emit the value
+       switch( bind->type ) {
+               case BIND_STR :
+               case BIND_NUM :
+                       buildScalar( state, numeric, value );
+                       break;
+               case BIND_STR_LIST :
+               case BIND_NUM_LIST :
+                       if( JSON_ARRAY == value->type ) {
+                               // Iterate over array, emit each value
+                               int first = 1;   // Boolean
+                               unsigned long max = value->size;
+                               unsigned long i = 0;
+                               while( i < max ) {
+                                       if( first )
+                                               first = 0;
+                                       else
+                                               buffer_add( state->sql, ", " );
+
+                                       buildScalar( state, numeric, jsonObjectGetIndex( value, i ));
+                                       ++i;
+                               }
+                       } else {
+                               osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                                       "Invalid value for bind variable; expected a list of values" ));
+                               state->error = 1;
+                       }
+                       break;
+               default :
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Internal error: invalid type for bind variable" ));
+                       state->error = 1;
+                       break;
+       }
+
+       if( state->error )
+               osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
+                       "Unable to emit value of bind variable \"%s\"", bind->name ));
+}
+
+/**
+       @brief Add a number or quoted string to an SQL statement.
+       @param state Pointer to the query-building context.
+       @param numeric Boolean; true if the value is expected to be a number
+       @param obj Pointer to the jsonObject whose value is to be added to the SQL.
+*/
+static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj ) {
+       switch( obj->type ) {
+               case JSON_HASH :
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Internal error: hash value for bind variable" ));
+                       state->error = 1;
+                       break;
+               case JSON_ARRAY :
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Internal error: array value for bind variable" ));
+                       state->error = 1;
+                       break;
+               case JSON_STRING :
+                       if( numeric ) {
+                               sqlAddMsg( state,
+                                       "Invalid value for bind variable: expected a string, found a number" );
+                               state->error = 1;
+                       } else {
+                               // To do: escape special characters in the string
+                               buffer_add_char( state->sql, '\'' );
+                               buffer_add( state->sql, jsonObjectGetString( obj ));
+                               buffer_add_char( state->sql, '\'' );
+                       }
+                       break;
+               case JSON_NUMBER :
+                       if( numeric ) {
+                               buffer_add( state->sql, jsonObjectGetString( obj ));
+                       } else {
+                               sqlAddMsg( state,
+                                       "Invalid value for bind variable: expected a number, found a string" );
+                               state->error = 1;
+                       }
+                       break;
+               case JSON_NULL :
+                       buffer_add( state->sql, "NULL" );
+                       break;
+               case JSON_BOOL :
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Internal error: boolean value for bind variable" ));
+                       state->error = 1;
+                       break;
+               default :
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Internal error: corrupted value for bind variable" ));
+                       state->error = 1;
+                       break;
+       }
+}
+
 static void add_newline( BuildSQLState* state ) {
        buffer_add_char( state->sql, '\n' );
 
index f460c64..2f8fd23 100644 (file)
 */
 BuildSQLState* buildSQLStateNew( dbi_conn dbhandle ) {
 
-       BuildSQLState* state = safe_malloc( sizeof( BuildSQLState ) );
-       state->dbhandle    = dbhandle;
-       state->error       = 0;
-       state->error_msgs  = osrfNewStringArray( 16 );
-       state->sql         = buffer_init( 128 );
-       state->query_stack = NULL;
-       state->expr_stack  = NULL;
-       state->indent      = 0;
+       BuildSQLState* state   = safe_malloc( sizeof( BuildSQLState ) );
+       state->dbhandle        = dbhandle;
+       state->result          = NULL;
+       state->error           = 0;
+       state->error_msgs      = osrfNewStringArray( 16 );
+       state->sql             = buffer_init( 128 );
+       state->bindvar_list    = NULL;  // Don't build it until we need it
+       state->query_stack     = NULL;
+       state->expr_stack      = NULL;
+       state->indent          = 0;
+       state->defaults_usable = 0;
+       state->values_required = 0;
 
        return state;
 }
@@ -53,8 +57,14 @@ const char* sqlAddMsg( BuildSQLState* state, const char* msg, ... ) {
 void buildSQLStateFree( BuildSQLState* state ){
        
        if( state ) {
+               if( state->result ) {
+                       dbi_result_free( state->result );
+                       state->result = NULL;
+               }
                osrfStringArrayFree( state->error_msgs );
                buffer_free( state->sql );
+               if( state->bindvar_list )
+                       osrfHashFree( state->bindvar_list );
                while( state->query_stack )
                        pop_id( &state->query_stack );
                while( state->expr_stack )
index 395b16b..cf37a33 100644 (file)
@@ -77,6 +77,10 @@ int osrfAppInitialize() {
        if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
                return 1; /* return non-zero to indicate error */
 
+       // Set the SQL options.  Here the second and third parameters are irrelevant, but we need
+       // to set the module name for use in error messages.
+       oilsSetSQLOptions( modulename, 0, 100 );
+
        growing_buffer* method_name = buffer_init( 64 );
 
        OSRF_BUFFER_ADD( method_name, modulename );
@@ -188,6 +192,8 @@ int doPrepare( osrfMethodContext* ctx ) {
        osrfLogInfo( OSRF_LOG_MARK, "Loading query for id # %d", query_id );
 
        BuildSQLState* state = buildSQLStateNew( dbhandle );
+       state->defaults_usable = 1;
+       state->values_required = 0;
        StoredQ* query = getStoredQuery( state, query_id );
        if( state->error ) {
                osrfLogWarning( OSRF_LOG_MARK, "Unable to load stored query # %d", query_id );
index 8a6a516..6eac132 100644 (file)
@@ -9,6 +9,7 @@
 #include "opensrf/utils.h"
 #include "opensrf/log.h"
 #include "opensrf/string_array.h"
+#include "opensrf/osrf_hash.h"
 #include "openils/oils_buildq.h"
 
 #define PRINT if( verbose ) printf
@@ -36,6 +37,10 @@ static SelectItem* getSelectList( BuildSQLState* state, int query_id );
 static SelectItem* constructSelectItem( BuildSQLState* state, dbi_result result );
 static void selectListFree( SelectItem* sel );
 
+static BindVar* getBindVar( BuildSQLState* state, const char* name );
+static BindVar* constructBindVar( BuildSQLState* state, dbi_result result );
+static void bindVarFree( char* name, void* p );
+
 static Expression* getExpression( BuildSQLState* state, int id );
 static Expression* constructExpression( BuildSQLState* state, dbi_result result );
 static void expressionFree( Expression* exp );
@@ -52,6 +57,7 @@ static const IdNode* searchIdStack( const IdNode* stack, int id, const char* ali
 static StoredQ* free_storedq_list = NULL;
 static FromRelation* free_from_relation_list = NULL;
 static SelectItem* free_select_item_list = NULL;
+static BindVar* free_bindvar_list = NULL;
 static Expression* free_expression_list = NULL;
 static IdNode* free_id_node_list = NULL;
 static QSeq* free_qseq_list = NULL;
@@ -221,6 +227,7 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
                        freeQSeqList( child_list );
                        fromRelationFree( from_clause );
                        selectListFree( select_list );
+                       state->error = 1;
                        return NULL;
                }
        }
@@ -236,6 +243,7 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
                        freeQSeqList( child_list );
                        fromRelationFree( from_clause );
                        selectListFree( select_list );
+                       state->error = 1;
                        return NULL;
                }
        }
@@ -613,6 +621,7 @@ static FromRelation* constructFromRelation( BuildSQLState* state, dbi_result res
                        osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
                                "Unable to load ON condition for FROM relation # %d", id ));
                        joinListFree( join_list );
+                       state->error = 1;
                        return NULL;
                }
                else
@@ -810,6 +819,7 @@ static SelectItem* constructSelectItem( BuildSQLState* state, dbi_result result
        if( !expression ) {
                osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
                        "Unable to fetch expression for id = %d", expression_id ));
+               state->error = 1;
                return NULL;
        };
 
@@ -833,6 +843,13 @@ static SelectItem* constructSelectItem( BuildSQLState* state, dbi_result result
        return sel;
 }
 
+/**
+       @brief Free a list of SelectItems.
+       @param sel Pointer to the first item in the list to be freed.
+
+       Free the column alias and expression owned by each item.  Put the entire list into a free
+       list of SelectItems.
+*/
 static void selectListFree( SelectItem* sel ) {
        if( !sel )
                return;    // Nothing to free
@@ -855,6 +872,152 @@ static void selectListFree( SelectItem* sel ) {
        free_select_item_list = first;
 }
 
+/**
+       @brief Given the name of a bind variable, build a corresponding BindVar.
+       @param state Pointer to the query-building context.
+       @param name Name of the bind variable.
+       @return Pointer to the newly-built BindVar.
+
+       Since the same bind variable may appear multiple times, we load it only once for the
+       entire query, and reference the one copy wherever needed.
+*/
+static BindVar* getBindVar( BuildSQLState* state, const char* name ) {
+       BindVar* bind = NULL;
+       if( state->bindvar_list ) {
+               bind = osrfHashGet( state->bindvar_list, name );
+               if( bind )
+                       return bind;   // Already loaded it...
+       }
+
+       // Load a BindVar from the Database.
+       dbi_result result = dbi_conn_queryf( state->dbhandle,
+               "SELECT name, type, description, default_value, label "
+               "FROM query.bind_variable WHERE name = \'%s\';", name );
+       if( result ) {
+               if( dbi_result_first_row( result ) ) {
+                       bind = constructBindVar( state, result );
+                       if( bind ) {
+                               PRINT( "Got a bind variable for %s\n", name );
+                       } else 
+                               osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                                       "Unable to load bind variable \"%s\"", name ));
+               } else {
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "No bind variable found with name \"%s\"", name ));
+               }
+       } else {
+               const char* msg;
+               int errnum = dbi_conn_error( state->dbhandle, &msg );
+               osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                       "Unable to query query.bind_variable table for \"%s\": #%d %s",
+                       name, errnum, msg ? msg : "No description available" ));
+       }
+
+       if( bind ) {
+               // Add the new bind variable to the list
+               if( !state->bindvar_list ) {
+                       // Don't have a list yet?  Start one.
+                       state->bindvar_list = osrfNewHash();
+                       osrfHashSetCallback( state->bindvar_list, bindVarFree );
+               }
+               osrfHashSet( state->bindvar_list, bind, name );
+       } else
+               state->error = 1;
+
+       return bind;
+}
+
+/**
+       @brief Construct a BindVar to represent a bind variable.
+       @param Pointer to the query-building context.
+       @param result Database cursor positioned at a row in query.bind_variable.
+       @return Pointer to a newly constructed BindVar, if successful, or NULL if not.
+
+       The calling code is responsible for freeing the BindVar by calling bindVarFree().
+*/
+static BindVar* constructBindVar( BuildSQLState* state, dbi_result result ) {
+
+       const char* name = dbi_result_get_string_idx( result, 1 );
+
+       const char* type_str = dbi_result_get_string_idx( result, 2 );
+       BindVarType type;
+       if( !strcmp( type_str, "string" ))
+               type = BIND_STR;
+       else if( !strcmp( type_str, "number" ))
+               type = BIND_NUM;
+       else if( !strcmp( type_str, "string_list" ))
+               type = BIND_STR_LIST;
+       else if( !strcmp( type_str, "number_list" ))
+               type = BIND_NUM_LIST;;
+
+       const char* description = dbi_result_get_string_idx( result, 3 );
+
+       // The default value is encoded as JSON.  Translate it into a jsonObject.
+       const char* default_value_str = dbi_result_get_string_idx( result, 4 );
+       jsonObject* default_value = NULL;
+       if( default_value_str ) {
+               default_value = jsonParse( default_value_str );
+               if( !default_value ) {
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Unable to parse JSON string for default value of bind variable \"%s\"",
+                               name ));
+                       state->error = 1;
+                       return NULL;
+               }
+       }
+
+       const char* label = dbi_result_get_string_idx( result, 5 );
+
+       // Allocate a BindVar: from the free list if possible, from the heap if necessary
+       BindVar* bind = NULL;
+       if( free_bindvar_list ) {
+               bind = free_bindvar_list;
+               free_bindvar_list = free_bindvar_list->next;
+       } else
+               bind = safe_malloc( sizeof( BindVar ) );
+
+       bind->next = NULL;
+       bind->name = strdup( name );
+       bind->label = strdup( label );
+       bind->type = type;
+       bind->description = strdup( description );
+       bind->default_value = default_value;
+       bind->actual_value = NULL;
+
+       return bind;
+}
+
+/**
+       @brief Deallocate a BindVar.
+       @param key Pointer to the bind variable name (not used).
+       @param p Pointer to the BindVar to be deallocated, cast to a void pointer.
+
+       Free the strings and jsonObjects owned by the BindVar, and put the BindVar itself into a
+       free list.
+
+       This function is a callback installed in an osrfHash; hence the peculiar signature.
+*/
+static void bindVarFree( char* key, void* p ) {
+       if( p ) {
+               BindVar* bind = p;
+               free( bind->name );
+               free( bind->label );
+               free( bind->description );
+               if( bind->default_value ) {
+                       jsonObjectFree( bind->default_value );
+                       bind->default_value = NULL;
+               }
+               if( bind->actual_value ) {
+                       jsonObjectFree( bind->actual_value );
+                       bind->actual_value = NULL;
+               }
+
+               // Prepend to free list
+               bind->next = free_bindvar_list;
+               free_bindvar_list = bind;
+       }
+}
+
 static Expression* getExpression( BuildSQLState* state, int id ) {
        
        // Check the stack to see if the current expression is nested inside itself.  If it is,
@@ -868,10 +1031,11 @@ static Expression* getExpression( BuildSQLState* state, int id ) {
        } else
                push_id( &state->expr_stack, id, NULL );
 
-               Expression* exp = NULL;
+       Expression* exp = NULL;
        dbi_result result = dbi_conn_queryf( state->dbhandle,
                "SELECT id, type, parenthesize, parent_expr, seq_no, literal, table_alias, column_name, "
-               "left_operand, operator, right_operand, function_id, subquery, cast_type, negate "
+               "left_operand, operator, right_operand, function_id, subquery, cast_type, negate, "
+               "bind_variable "
                "FROM query.expression WHERE id = %d;", id );
        if( result ) {
                if( dbi_result_first_row( result ) ) {
@@ -914,6 +1078,8 @@ static Expression* constructExpression( BuildSQLState* state, dbi_result result
        ExprType type;
        if( !strcmp( type_str, "xbet" ))
                type = EXP_BETWEEN;
+       else if( !strcmp( type_str, "xbind" ))
+               type = EXP_BIND;
        else if( !strcmp( type_str, "xbool" ))
                type = EXP_BOOL;
        else if( !strcmp( type_str, "xcase" ))
@@ -989,10 +1155,12 @@ static Expression* constructExpression( BuildSQLState* state, dbi_result result
                cast_type_id = dbi_result_get_int_idx( result, 14 );
 
        int negate = oils_result_get_bool_idx( result, 15 );
+       const char* bind_variable = dbi_result_get_string_idx( result, 16 );
 
        Expression* left_operand = NULL;
        Expression* right_operand = NULL;
        StoredQ* subquery = NULL;
+       BindVar* bind = NULL;
 
        if( EXP_OPERATOR == type ) {
                // Load left and/or right operands
@@ -1092,6 +1260,25 @@ static Expression* constructExpression( BuildSQLState* state, dbi_result result
                        }
                        PRINT( "\tExpression is subquery %d\n", subquery_id );
                }
+       } else if( EXP_BIND == type ) {
+               if( bind_variable ) {
+                       // To do: Build a BindVar
+                       bind = getBindVar( state, bind_variable );
+                       if( ! bind ) {
+                               osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
+                                       "Unable to load bind variable \"%s\" for expression # %d",
+                                       bind_variable, id ));
+                               state->error = 1;
+                               return NULL;
+                       }
+                       PRINT( "\tBind variable is \"%s\"\n", bind_variable );
+               } else {
+                       osrfLogWarning( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "No variable specified for bind variable expression # %d",
+                       bind_variable, id ));
+                       state->error = 1;
+                       return NULL;
+               }
        }
 
        // Allocate an Expression: from the free list if possible, from the heap if necessary
@@ -1120,6 +1307,7 @@ static Expression* constructExpression( BuildSQLState* state, dbi_result result
        exp->subquery = subquery;
        exp->cast_type_id = subquery_id;
        exp->negate = negate;
+       exp->bind = bind;
 
        return exp;
 }
@@ -1152,7 +1340,10 @@ static void expressionFree( Expression* exp ) {
                        storedQFree( exp->subquery );
                        exp->subquery = NULL;
                }
+               // We don't free the bind member here because the Expression doesn't own it;
+               // the bindvar_list hash owns it, so that multiple Expressions can reference it.
 
+               // Prepend to the free list
                exp->next = free_expression_list;
                free_expression_list = exp;
        }
@@ -1225,6 +1416,7 @@ static OrderItem* constructOrderItem( BuildSQLState* state, dbi_result result )
        if( !expression ) {
                osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
                        "Unable to fetch ORDER BY expression for id = %d", expression_id ));
+               state->error = 1;
                return NULL;
        };
 
@@ -1470,6 +1662,14 @@ void storedQCleanup( void ) {
                free( ord );
                ord = free_order_item_list;
        }
+
+       // Free all the nodes in the bind variable free list
+       BindVar* bind = free_bindvar_list;
+       while( bind ) {
+               free_bindvar_list = bind->next;
+               free( bind );
+               bind = free_bindvar_list;
+       }
 }
 
 /**