Tripal 0.3b
tripal_core/tripal_core.api.inc
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * @file
00004  * The Tripal Core API
00005  *
00006  * This file provides the API needed for all other Tripal and Tripal dependent
00007  * modules.
00008  */
00009  
00010 /**
00011  * @defgroup tripal_api Tripal API
00012  * @{
00013  * Provides an application programming interface (API) for Tripal
00014  *
00015  * The Tripal API currently provides generic insert/update/select functions for all chado content as 
00016  * well as some module specific functions that insert/update/delete/select specific chado content. 
00017  *
00018  * This API is currently in its infancy and some necessary functions might be missing. If you find
00019  * a missing function that you think should be included go to the sourceforge feature request 
00020  * page and request it's inclusion in the API. Such feature requests with a working function 
00021  * definition will be given priority.
00022  * @}
00023  */
00024 
00025 require_once "tripal_core.schema.api.inc";
00026 
00027 /** 
00028  * @defgroup tripal_chado_api Core Module Chado API
00029  * @{
00030  * Provides an application programming interface (API) to manage data withing the Chado database.
00031  * This includes functions for selecting, inserting, updating and deleting records 
00032  * in Chado tables.  The functions will ensure proper integrity contraints are met
00033  * for inserts and updates.  
00034  *
00035  * Also, a set of functions is provided for creating template variables.  First,
00036  * is the tripal_core_generate_chado_vars which is used to select one ore more
00037  * records from a table and return an array with foreign key relationships fully
00038  * populated.  For example, if selecting a feature, the organism_id and type_id
00039  * would be present in the returned array as a nested array with their respective
00040  * foreign keys also nested.  The only fields that are not included are text
00041  * fields (which may be very large) or many-to-many foreign key relationships.  
00042  * However, these fields and relationships can be expanded using the 
00043  * tripal_core_expand_chado_vars.
00044  * 
00045  * When a row from a chado table is selected using these two functions, it provides
00046  * a way for users who want to cutomize Drupal template files to access all data
00047  * associate with a specific record.
00048  *
00049  * Finally, the property tables in Chado generally follow the same format.  Therefore
00050  * there is a set of functions for inserting, updating and deleting properties for
00051  * any table.  This provides quick lookup of properties (provided the CV term is
00052  * known).
00053  *
00054  * @}
00055  * @ingroup tripal_api
00056  */
00057 
00058 /** 
00059  * @defgroup tripal_files_api Core Module Files API
00060  * @{
00061  * Provides an application programming interface (API) for managing files within 
00062  * the Tripal data directory structure.
00063  *
00064  * @}
00065  * @ingroup tripal_api
00066  */
00067  
00068 /**
00069 * Provides a generic routine for inserting into any Chado table
00070 *
00071 * Use this function to insert a record into any Chado table.  The first
00072 * argument specifies the table for inserting and the second is an array
00073 * of values to be inserted.  The array is mutli-dimensional such that
00074 * foreign key lookup values can be specified.  
00075 *
00076 * @param $table
00077 *  The name of the chado table for inserting
00078 * @param $values
00079 *  An associative array containing the values for inserting.
00080 * 
00081 * @return
00082 *  On success this function returns TRUE. On failure, it returns FALSE.
00083 *
00084 * Example usage:
00085 * @code
00086 *   $values =  array(
00087 *     'organism_id' => array(
00088 *         'genus' => 'Citrus',
00089 *         'species' => 'sinensis',
00090 *      ),
00091 *     'name' => 'orange1.1g000034m.g',
00092 *     'uniquename' => 'orange1.1g000034m.g',
00093 *     'type_id' => array (
00094 *         'cv_id' => array (
00095 *            'name' => 'sequence',
00096 *         ),
00097 *         'name' => 'gene',
00098 *         'is_obsolete' => 0
00099 *      ),
00100 *   );
00101 *   $result = tripal_core_chado_insert('feature',$values);
00102 * @endcode
00103 * The above code inserts a record into the feature table.  The $values array is
00104 * nested such that the organism is selected by way of the organism_id foreign
00105 * key constraint by specifying the genus and species.  The cvterm is also
00106 * specified using its foreign key and the cv_id for the cvterm is nested as
00107 * well.
00108 *
00109 * @ingroup tripal_chado_api
00110 */
00111 function tripal_core_chado_insert($table,$values){
00112    $insert_values = array();
00113    
00114    // get the table description
00115    $table_desc = module_invoke_all('chado_'.$table.'_schema');
00116 
00117    // iterate through the values array and create a new 'insert_values' array
00118    // that has all the values needed for insert with all foreign relationsihps
00119    // resolved.
00120    foreach($values as $field => $value){
00121       if(is_array($value)){
00122          // select the value from the foreign key relationship for this value
00123          $results = tripal_core_chado_get_foreign_key($table_desc,$field,$value);
00124          if (sizeof($results) > 1) {
00125            watchdog('tripal_core', 'tripal_core_chado_insert: Too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);         
00126          } elseif (sizeof($results) < 1) {
00127            watchdog('tripal_core', 'tripal_core_chado_insert: no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);
00128          } else {
00129            $insert_values[$field] = $results[0];
00130          }
00131       }  
00132       else {
00133          $insert_values[$field] = $value;
00134       }
00135    }
00136 
00137    // check for violation of any unique constraints
00138    $ukeys = $table_desc['unique keys'];
00139    $ukselect_cols = array();
00140    $ukselect_vals = array();
00141    if ($ukeys) {
00142      foreach($ukeys as $name => $fields){
00143         foreach($fields as $index => $field){
00144            // build the arrays for performing a select that will check the contraint
00145            array_push($ukselect_cols,$field);
00146            $ukselect_vals[$field] = $insert_values[$field];
00147         }
00148         // now check the constraint
00149         if(tripal_core_chado_select($table,$ukselect_cols,$ukselect_vals)){
00150            watchdog('tripal_core',"tripal_core_chado_insert: Cannot insert duplicate record into $table table: " . print_r($values,1),array(),'WATCHDOG_ERROR');
00151            return false;
00152         }
00153      }
00154    }
00155 
00156    // if trying to insert a field that is the primary key, make sure it also is unique
00157    $pkey = $table_desc['primary key'][0];
00158    if($insert_values[$pkey]){
00159       if(tripal_core_chado_select($table,array($pkey),array($pkey => $insert_values[$pkey]))){
00160          watchdog('tripal_core',"tripal_core_chado_insert: Cannot insert duplicate primary key into $table table: " . print_r($values,1),array(),'WATCHDOG_ERROR');
00161          return false;
00162       }
00163    }
00164 
00165    // make sure required fields have a value
00166    $fields = $table_desc['fields'];
00167    foreach($fields as $field => $def){
00168       // a field is considered missing if it cannot be null and there is no default
00169       // value for it or it is of type 'serial'
00170       if($def['not null'] == 1 and !array_key_exists($field,$insert_values) and !isset($def['default']) and strcmp($def['type'],serial)!=0){
00171          watchdog('tripal_core',"tripal_core_chado_insert: Field $table.$field cannot be null: " . print_r($values,1),array(),'WATCHDOG_ERROR');
00172          return false;
00173       }
00174    }
00175 
00176    // Now build the insert SQL statement
00177    $ifields = array();
00178    $ivalues = array();
00179    $itypes = array();
00180    foreach ($insert_values as $field => $value){
00181       array_push($ifields,$field);
00182       array_push($ivalues,$value);
00183       if(strcmp($fields[$field]['type'],'serial')==0 or 
00184          strcmp($fields[$field]['type'],'int')==0){
00185          array_push($itypes,"%d");
00186       } else {
00187          array_push($itypes,"'%s'");
00188       }
00189    }
00190    $sql = "INSERT INTO {$table} (" . implode(", ",$ifields) . ") VALUES (". implode(", ",$itypes) .")";
00191 
00192    // finally perform the insert. 
00193    $previous_db = tripal_db_set_active('chado');  // use chado database
00194    $result = db_query($sql,$ivalues);
00195    tripal_db_set_active($previous_db);  // now use drupal database 
00196    if($result){
00197       // add primary keys to values before return
00198       $primary_key = array();
00199       foreach ($table_desc['primary key'] as $field) {
00200         $value = db_last_insert_id($table, $field);
00201         $values[$field] = $value;
00202       }
00203       return $values;
00204    } 
00205    else {
00206       watchdog('tripal_core',"tripal_core_chado_insert: Cannot insert record into $table table: " . print_r($values,1),array(),'WATCHDOG_ERROR');
00207       return false;
00208    }
00209    return false;
00210 }
00211 
00212 /**
00213  * Provides a generic function for deleting a record(s) from any chado table
00214  *
00215  * Use this function to delete a record(s) in any Chado table.  The first
00216  * argument specifies the table to delete from and the second is an array
00217  * of values to match for locating the record(s) to be deleted.  The arrays 
00218  * are mutli-dimensional such that foreign key lookup values can be specified.
00219  *
00220  * @param $table
00221  *  The name of the chado table for inserting
00222  * @param $match
00223  *  An associative array containing the values for locating a record to update.
00224  *
00225  * @return 
00226  *   On success this function returns TRUE. On failure, it returns FALSE.
00227  *
00228  * Example usage:
00229  * @code
00230    $umatch = array(
00231      'organism_id' => array(
00232          'genus' => 'Citrus',
00233          'species' => 'sinensis',
00234       ),
00235      'uniquename' => 'orange1.1g000034m.g7',
00236      'type_id' => array (
00237          'cv_id' => array (
00238             'name' => 'sequence',
00239          ),
00240          'name' => 'gene',
00241          'is_obsolete' => 0
00242       ),
00243    );
00244    $uvalues = array(
00245       'name' => 'orange1.1g000034m.g',
00246       'type_id' => array (
00247          'cv_id' => array (
00248             'name' => 'sequence',
00249          ),
00250          'name' => 'mRNA',
00251          'is_obsolete' => 0
00252       ),
00253    );
00254  *   $result = tripal_core_chado_update('feature',$umatch,$uvalues);
00255  * @endcode
00256  * The above code species that a feature with a given uniquename, organism_id,
00257  * and type_id (the unique constraint for the feature table) will be deleted.
00258  * The organism_id is specified as a nested array that uses the organism_id
00259  * foreign key constraint to lookup the specified values to find the exact 
00260  * organism_id. The same nested struture is also used for specifying the 
00261  * values to update.  The function will find all records that match the 
00262  * columns specified and delete them.
00263  *
00264  * @ingroup tripal_chado_api
00265  */
00266 function tripal_core_chado_delete($table,$match){
00267    $delete_matches = array();  // contains the values for the where clause
00268    
00269    // get the table description
00270    $table_desc = module_invoke_all('chado_'.$table.'_schema');
00271 
00272    // get the values needed for matching in the SQL statement
00273    foreach ($match as $field => $value){
00274       if(is_array($value)){
00275          $results = tripal_core_chado_get_foreign_key($table_desc,$field,$value);
00276          if (sizeof($results) > 1) {
00277            watchdog('tripal_core', 'tripal_core_chado_delete: When trying to find record to delete, too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);         
00278          } elseif (sizeof($results) < 1) {
00279            watchdog('tripal_core', 'tripal_core_chado_delete: When trying to find record to delete, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);
00280          } else {
00281            $delete_matches[$field] = $results[0];
00282          }
00283       }
00284       else {
00285          $delete_matches[$field] = $value;
00286       }
00287    }
00288    // now build the SQL statement
00289    $sql = "DELETE FROM {$table} WHERE ";
00290    $dargs = array();
00291    foreach($delete_matches as $field => $value){
00292       if(strcmp($fields[$field]['type'],'serial')==0 or 
00293          strcmp($fields[$field]['type'],'int')==0){
00294          $sql .= " $field = %d AND ";
00295       } else {
00296          $sql .= " $field = '%s' AND ";
00297       }
00298       array_push($dargs,$value);
00299    }
00300    $sql = substr($sql,0,-4);  // get rid of the trailing 'AND'
00301    
00302    // finally perform the delete.  If successful, return the updated record
00303    $previous_db = tripal_db_set_active('chado');  // use chado database
00304    $result = db_query($sql,$dargs);
00305    tripal_db_set_active($previous_db);  // now use drupal database 
00306    if($result){
00307       return true;
00308    } 
00309    else {
00310       watchdog('tripal_core',"Cannot delete record in $table table.  Match:" . print_r($match,1) . ". Values: ". print_r($values,1),array(),'WATCHDOG_ERROR');
00311       return false;
00312    }
00313    return false;
00314 }
00315 
00316 /**
00317 * Provides a generic routine for updating into any Chado table
00318 *
00319 * Use this function to update a record in any Chado table.  The first
00320 * argument specifies the table for inserting, the second is an array
00321 * of values to matched for locating the record for updating, and the third 
00322 * argument give the values to update.  The arrays are mutli-dimensional such 
00323 * that foreign key lookup values can be specified.  
00324 *
00325 * @param $table
00326 *  The name of the chado table for inserting
00327 * @param $match
00328 *  An associative array containing the values for locating a record to update.
00329 * @param $values
00330 *  An associative array containing the values for updating.
00331 *
00332 * @return
00333 *  On success this function returns TRUE. On failure, it returns FALSE.
00334 *
00335 * Example usage:
00336 * @code
00337    $umatch = array(
00338      'organism_id' => array(
00339          'genus' => 'Citrus',
00340          'species' => 'sinensis',
00341       ),
00342      'uniquename' => 'orange1.1g000034m.g7',
00343      'type_id' => array (
00344          'cv_id' => array (
00345             'name' => 'sequence',
00346          ),
00347          'name' => 'gene',
00348          'is_obsolete' => 0
00349       ),
00350    );
00351    $uvalues = array(
00352       'name' => 'orange1.1g000034m.g',
00353       'type_id' => array (
00354          'cv_id' => array (
00355             'name' => 'sequence',
00356          ),
00357          'name' => 'mRNA',
00358          'is_obsolete' => 0
00359       ),
00360    );
00361 *   $result = tripal_core_chado_update('feature',$umatch,$uvalues);
00362 * @endcode
00363 * The above code species that a feature with a given uniquename, organism_id,
00364 * and type_id (the unique constraint for the feature table) will be updated.
00365 * The organism_id is specified as a nested array that uses the organism_id
00366 * foreign key constraint to lookup the specified values to find the exact 
00367 * organism_id. The same nested struture is also used for specifying the 
00368 * values to update.  The function will find the record that matches the 
00369 * columns specified and update the record with the avlues in the $uvalues array.
00370 *
00371 * @ingroup tripal_chado_api
00372 */
00373 function tripal_core_chado_update($table,$match,$values){
00374    $update_values = array();   // contains the values to be updated
00375    $update_matches = array();  // contains the values for the where clause
00376    
00377    // get the table description
00378    $table_desc = module_invoke_all('chado_'.$table.'_schema');
00379 
00380    // get the values needed for matching in the SQL statement
00381    foreach ($match as $field => $value){
00382       if(is_array($value)){
00383          $results = tripal_core_chado_get_foreign_key($table_desc,$field,$value);
00384          if (sizeof($results) > 1) {
00385            watchdog('tripal_core', 'tripal_core_chado_update: When trying to find record to update, too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);         
00386          } elseif (sizeof($results) < 1) {
00387            watchdog('tripal_core', 'tripal_core_chado_update: When trying to find record to update, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);
00388          } else {
00389            $update_matches[$field] = $results[0];
00390          }
00391       }
00392       else {
00393          $update_matches[$field] = $value;
00394       }
00395    }
00396 
00397    // get the values used for updating
00398    foreach ($values as $field => $value){
00399       if(is_array($value)){
00400          $results = tripal_core_chado_get_foreign_key($table_desc,$field,$value);
00401          if (sizeof($results) > 1) {
00402            watchdog('tripal_core', 'tripal_core_chado_update: When trying to find update values, too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);         
00403          } elseif (sizeof($results) < 1) {
00404            watchdog('tripal_core', 'tripal_core_chado_update: When trying to find update values, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);
00405          } else {
00406            $update_values[$field] = $results[0];
00407          }         
00408       }
00409       else {
00410          $update_values[$field] = $value;
00411       }
00412    }
00413 
00414    // now build the SQL statement
00415    $sql = "UPDATE {$table} SET ";
00416    $fields = $table_desc['fields'];
00417    $uargs = array();
00418    foreach($update_values as $field => $value){
00419       if(strcmp($fields[$field]['type'],'serial')==0 or 
00420          strcmp($fields[$field]['type'],'int')==0){
00421          $sql .= " $field = %d, ";
00422       } else {
00423          $sql .= " $field = '%s', ";
00424       }
00425       array_push($uargs,$value);
00426    }
00427    $sql = substr($sql,0,-2);  // get rid of the trailing comma & space
00428    $sql .= " WHERE ";
00429    foreach($update_matches as $field => $value){
00430       if(strcmp($fields[$field]['type'],'serial')==0 or 
00431          strcmp($fields[$field]['type'],'int')==0){
00432          $sql .= " $field = %d AND ";
00433       } else {
00434          $sql .= " $field = '%s' AND ";
00435       }
00436       array_push($uargs,$value);
00437    }
00438    $sql = substr($sql,0,-4);  // get rid of the trailing 'AND'
00439    
00440    // finally perform the update.  If successful, return the updated record
00441    $previous_db = tripal_db_set_active('chado');  // use chado database
00442    $result = db_query($sql,$uargs);
00443    tripal_db_set_active($previous_db);  // now use drupal database 
00444    if($result){
00445       return true;
00446    } 
00447    else {
00448       watchdog('tripal_core',"Cannot update record in $table table.  Match:" . print_r($match,1) . ". Values: ". print_r($values,1),array(),'WATCHDOG_ERROR');
00449       return false;
00450    }
00451    return false;
00452 }
00453 
00454 /**
00455 * Provides a generic routine for selecting data from a Chado table
00456 *
00457 * Use this function to perform a simple select from any Chado table.  
00458 *
00459 * @param $table
00460 *  The name of the chado table for inserting
00461 * @param $columns
00462 *  An array of column names
00463 * @param $values
00464 *  An associative array containing the values for filtering the results. In the 
00465 *  case where multiple values for the same time are to be selected an additional
00466 *  entry for the field should appear for each value
00467 * @param $options
00468 *  An associative array of additional options where the key is the option 
00469 *  and the value is the value of that option.
00470 *
00471 * Additional Options Include:
00472 *  - has_record
00473 *     Set this argument to 'true' to have this function return a numeric 
00474 *     value for the number of recrods rather than the array of records.  this
00475 *     can be useful in 'if' statements to check the presence of particula records.
00476 *  - return_sql
00477 *     Set this to 'true' to have this function return an array where the first 
00478 *     element is the sql that would have been run and the second is an array of 
00479 *     arguments.
00480 *  - case_insensitive_columns
00481 *     An array of columns to do a case insensitive search on.
00482 *  - regex_columns
00483 *     An array of columns where the value passed in should be treated as a regular expression
00484 *  - order_by
00485 *     An associative array containing the column names of the table as keys
00486 *     and the type of sort (i.e. ASC, DESC) as the values.  The results in the
00487 *     query will be sorted by the key values in the direction listed by the value
00488 * 
00489 * @return
00490 *  A database query result resource, FALSE if the query was not executed 
00491 *  correctly, or the number of records in the dataset if $has_record is set.
00492 *
00493 * Example usage:
00494 * @code
00495 *   $columns = array('feature_id','name');
00496 *   $values =  array(
00497 *     'organism_id' => array(
00498 *         'genus' => 'Citrus',
00499 *         'species' => array('sinensis','clementina'),
00500 *      ),
00501 *     'uniquename' => 'orange1.1g000034m.g',
00502 *     'type_id' => array (
00503 *         'cv_id' => array (
00504 *            'name' => 'sequence',
00505 *         ),
00506 *         'name' => 'gene',
00507 *         'is_obsolete' => 0
00508 *      ),
00509 *   );
00510 *   $result = tripal_core_chado_select('feature',$columns,$values);
00511 * @endcode
00512 * The above code selects a record from the feature table using the three fields 
00513 * that uniquely identify a feature.  The $columns array simply lists the columns
00514 * to select. The $values array is nested such that the organism is identified by 
00515 * way of the organism_id foreign key constraint by specifying the genus and 
00516 * species.  The cvterm is also specified using its foreign key and the cv_id 
00517 * for the cvterm is nested as well.  In the example above, two different species
00518 * are allowed to match
00519 *
00520 * @ingroup tripal_chado_api
00521 */
00522 function tripal_core_chado_select($table,$columns,$values,$options = null){
00523     if (!is_array($options)) { $options = array(); }
00524     if (!$options['case_insensitive_columns']) { $options['case_insensitive_columns'] = array(); }
00525     if (!$options['regex_columns']) { $options['regex_columns'] = array(); }
00526     if (!$options['order_by']) { $options['order_by'] = array(); }
00527     
00528    if (!is_array($columns)){
00529       watchdog('tripal_core', 'the $columns argument for tripal_core_chado_select must be an array.');
00530       return false;
00531    }
00532 
00533    if (!is_array($values)){
00534       watchdog('tripal_core', 'the $values argument for tripal_core_chado_select must be an array.');
00535       return false;
00536    }
00537 
00538    // get the table description
00539    $table_desc = module_invoke_all('chado_'.$table.'_schema');
00540 
00541    $select = '';
00542    $from = ''; 
00543    $where = '';
00544    $args = array();
00545    foreach($values as $field => $value){
00546       $select[] = $field;
00547       if(is_array($value)){
00548          // if the user has specified multiple values for matching then this we
00549          // want to catch that and save them in our $where array, otherwise
00550          // we'll descend for a foreign key relationship
00551          if(array_values($value) === $value){
00552             $where[$field] = $value;
00553          } else {
00554             // select the value from the foreign key relationship for this value
00555             $foreign_options = array(
00556               'regex_columns' => $options['regex_columns'],
00557               'case_insensitive_columns' => $options['case_insensitive_columns']
00558             );
00559             $results = tripal_core_chado_get_foreign_key($table_desc,$field,$value, $foreign_options);
00560             if (sizeof($results) < 1) {
00561               // foreign key records are required
00562               // thus if none matched then return false and alert the admin through watchdog
00563               watchdog('tripal_core', 
00564                'tripal_core_chado_select: no record in the table referenced by the foreign key (!field)   exists. tripal_core_chado_select table=!table, columns=!columns, values=!values', 
00565                array('!table' => $table, 
00566                  '!columns' => '<pre>' . print_r($columns, TRUE) . '</pre>', 
00567                  '!values' => '<pre>' . print_r($values, TRUE) . '</pre>',
00568                  '!field' => $field,
00569                ), 
00570                WATCHDOG_WARNING);
00571               return false;           
00572             } else {
00573               $where[$field] = $results;
00574             }
00575          }
00576       } 
00577       else {
00578         //need to catch a 0 and make int if integer field
00579         if ($table_desc['fields'][$field]['type'] == 'int') {
00580           $where[$field][] = (int) $value;
00581         } else {
00582           $where[$field][] = $value;
00583         }
00584       }
00585    }
00586 
00587    // now build the SQL select statement
00588    if (empty($where)) {
00589      // sometimes want to select everything
00590      $sql  = "SELECT " . implode(',',$columns) . " ";
00591      $sql .= "FROM {$table} ";
00592    } else {
00593      $sql  = "SELECT " . implode(',',$columns) . " ";
00594      $sql .= "FROM {$table} ";
00595      $sql .= "WHERE ";
00596      foreach($where as $field => $value){
00597        if (count($value) > 1) {
00598          $sql .= "$field IN (".db_placeholders($value,'varchar').") AND ";
00599          foreach ($value as $v) { $args[] = $v; }
00600        } else {
00601          $operator = '=';
00602          if (in_array($field, $options['regex_columns'])) {
00603            $operator = '~*';
00604          }
00605          if (in_array($field, $options['case_insensitive_columns'])) {
00606            $sql .= "lower($field) $operator lower('%s') AND ";
00607            $args[] = $value[0];
00608          }  else {
00609            $sql .= "$field $operator '%s' AND ";
00610            $args[] = $value[0];
00611          }
00612        }
00613      }
00614      $sql = substr($sql,0,-4);  // get rid of the trailing 'AND '
00615    }
00616    // finally add any ordering of the results to the SQL statement
00617    if(count($options['order_by']) > 0){
00618       $sql .= " ORDER BY ";
00619       foreach($options['order_by'] as $field => $dir){
00620          $sql .= "$field $dir, ";
00621       }
00622       $sql = substr($sql,0,-2);  // get rid of the trailing ', '
00623    }
00624 
00625 
00626    // if the caller has requested the SQL rather than the results...
00627    // which happens in the case of wanting to use the Drupal pager, then do so
00628    if($options['return_sql']){
00629       return array('sql'=> $sql, 'args' => $args);
00630    }
00631 
00632    $previous_db = tripal_db_set_active('chado');  // use chado database
00633    $resource = db_query($sql,$args);
00634    tripal_db_set_active($previous_db);  // now use drupal database   
00635 
00636    $results = array();
00637    while ($r = db_fetch_object($resource)) {
00638      $results[] = $r;    
00639    }
00640    
00641    if(!$options['has_record']){
00642       return $results;
00643    } else{
00644       return count($results);
00645    }
00646 }
00647 
00648 /**
00649 * Gets the value of a foreign key relationship
00650 *
00651 * This function is used by tripal_core_chado_select, tripal_core_chado_insert,
00652 * and tripal_core_chado_update to iterate through the associate array of
00653 * values that gets passed to each of those routines.  The values array
00654 * is nested where foreign key contraints are used to specify a value that.  See
00655 * documentation for any of those functions for further information.
00656 *
00657 * @param $table_desc
00658 *  A table description for the table with the foreign key relationship to be identified generated by 
00659 *  hook_chado_<table name>_schema()
00660 * @param $field
00661 *  The field in the table that is the foreign key.
00662 * @param $values
00663 *  An associative array containing the values 
00664 * @param $options
00665 *  An associative array of additional options where the key is the option 
00666 *  and the value is the value of that option. These options are passed on to tripal_core_chado_select.
00667 *
00668 * Additional Options Include:
00669 *  - case_insensitive_columns
00670 *     An array of columns to do a case insensitive search on.
00671 *  - regex_columns
00672 *     An array of columns where the value passed in should be treated as a regular expression
00673 * 
00674 * @return
00675 *  A string containg the results of the foreign key lookup, or FALSE if failed.
00676 *
00677 * Example usage:
00678 * @code
00679 *
00680 *   $values = array(
00681 *     'genus' => 'Citrus',
00682 *     'species' => 'sinensis',
00683 *   );
00684 *   $value = tripal_core_chado_get_foreign_key('feature','organism_id',$values);
00685 *
00686 * @endcode
00687 * The above code selects a record from the feature table using the three fields 
00688 * that uniquely identify a feature.  The $columns array simply lists the columns
00689 * to select. The $values array is nested such that the organism is identified by 
00690 * way of the organism_id foreign key constraint by specifying the genus and 
00691 * species.  The cvterm is also specified using its foreign key and the cv_id 
00692 * for the cvterm is nested as well.
00693 *
00694 * @ingroup tripal_chado_api
00695 */
00696 function tripal_core_chado_get_foreign_key($table_desc,$field,$values, $options = null){
00697     if (!is_array($options)) { $options = array(); }
00698     if (!$options['case_insensitive_columns']) { $options['case_insensitive_columns'] = array(); }
00699     if (!$options['regex_columns']) { $options['regex_columns'] = array(); }
00700     
00701    // get the list of foreign keys for this table description and
00702    // iterate through those until we find the one we're looking for
00703    $fkeys = $table_desc['foreign keys'];
00704    if($fkeys){
00705       foreach($fkeys as $name => $def){
00706          if (is_array($def['table'])) {
00707            //foreign key was described 2X
00708            $message = "The foreign key ".$name." was defined twice. Please check modules to determine if hook_chado_".$table_desc['table']."_schema() was implemented and defined this foreign key when it wasn't supposed to. Modules this hook was implemented in: ".implode(', ', module_implements("chado_".$table_desc['table']."_schema")).".";
00709            watchdog('tripal_core', $message);
00710            drupal_set_message($message,'error');
00711            continue;
00712          }
00713          $table = $def['table'];
00714          $columns = $def['columns'];
00715          // iterate through the columns of the foreign key relationship
00716          foreach($columns as $left => $right){
00717             // does the left column in the relationship match our field?
00718             if(strcmp($field,$left)==0){
00719                // the column name of the foreign key matches the field we want 
00720                // so this is the right relationship.  Now we want to select
00721                $select_cols = array($right);
00722                $result = tripal_core_chado_select($table,$select_cols,$values, $options);
00723                $fields = array();
00724                foreach ($result as $obj) {
00725                  $fields[] = $obj->$right;
00726                }
00727                return $fields;
00728             }
00729          }
00730       } 
00731    } 
00732    else {
00733       // TODO: what do we do if we get to this point and we have a fk 
00734       // relationship expected but we don't have any definition for one in the
00735       // table schema??
00736       $message = "There is no foreign key relationship defined for ".$field.". 
00737          To define a foreign key relationship, determine the table this foreign 
00738          key referrs to (<foreign table>) and then implement 
00739          hook_chado_<foreign table>_schema(). See 
00740          tripal_feature_chado_feature_schema for an example.";
00741       watchdog('tripal_core', $message);
00742       drupal_set_message($message,'error');      
00743    }
00744    return false;
00745 }
00746 
00747 /** 
00748  * Generates an object containing the full details of a record(s) in chado. 
00749  *
00750  * This differs from the objects returned by tripal_core_chado_select in so far as all foreign key 
00751  * relationships have been followed meaning you have more complete details. Thus this function 
00752  * should be used whenever you need a full variable and tripal_core_chado_select should be used if 
00753  * you only case about a few columns.
00754  *
00755  * @param $table
00756  *   The name of the base table to generate a variable for
00757  * @param $values
00758  *   A select values array that selects the records you want from the base table
00759  *   (this has the same form as tripal_core_chado_select)
00760  * @param $base_options
00761  *   An array containing options for the base table.  For example, an
00762  *   option of 'order_by' may be used to sort results in the base table
00763  *   if more than one are returned.  The options must be compatible with
00764  *   the options accepted by the tripal_core_chado_select() function.
00765  * @return
00766  *   Either an object (if only one record was selected from the base table) 
00767  *   or an array of objects (if more than one record was selected from the base table).
00768  *
00769  * Example Usage:
00770  * @code
00771       $values = array(
00772         'name' => 'Medtr4g030710'
00773       );
00774       $features = tripal_core_generate_chado_var('feature', $values);
00775  * @endcode
00776  * This will return an object if there is only one feature with the name Medtr4g030710 or it will 
00777  * return an array of feature objects if more than one feature has that name.
00778  *
00779  * Note to Module Designers: Fields can be excluded by default from these objects by implementing 
00780  * one of the following hooks:
00781  *  - hook_exclude_field_from_tablename_by_default (where tablename is the name of the table):
00782  *      This hook allows you to add fields to be excluded on a per table basis. Simply implement 
00783  *      this hook to return an array of fields to be excluded. For example:  
00784  * @code
00785           mymodule_exclude_field_from_feature_by_default() {
00786             return array('residues' => TRUE);
00787           }
00788  * @endcode
00789  *      will ensure that feature.residues is ecluded from a feature object by default.
00790  *  - hook_exclude_type_by_default:
00791  *      This hook allows you to exclude fields from all tables that are of a given postgresql field 
00792  *      type. Simply implement this hook to return an array of postgresql types mapped to criteria.
00793  *      Then all fields of that type where the criteria supplied returns TRUE will be excluded from 
00794  *      any table. Tokens available in criteria are &gt;field_value&lt;  and &gt;field_name&lt; . For example:
00795  * @code
00796           mymodule_exclude_type_by_default() {
00797             return array('text' => 'length(&gt;field_value&lt; ) > 50');
00798           }
00799  * @endcode
00800  *      will exclude all text fields with a length > 50. Thus if $feature.residues is longer than 50 *      it will be excluded, otherwise it will be added.
00801  *
00802  * @ingroup tripal_chado_api
00803  */
00804  function tripal_core_generate_chado_var($table, $values, $base_options=array()) {
00805   
00806   // get description for the current table----------------------------------------------------------
00807   $table_desc = module_invoke_all('chado_'.$table.'_schema');
00808   $table_primary_key = $table_desc['primary key'][0];
00809   $table_columns = array_keys($table_desc['fields']);
00810 
00811   // Expandable fields without value needed for criteria--------------------------------------------
00812   $all->expandable_fields = array();
00813   if ($table_desc['referring_tables']) {
00814     $all->expandable_tables = $table_desc['referring_tables'];
00815   } else {
00816     $all->expandable_tables = array();
00817   }
00818   $all->expandable_nodes = array();
00819     
00820   // Get fields to be removed by name.................................
00821   $fields_to_remove = module_invoke_all('exclude_field_from_'.$table.'_by_default');
00822   foreach ($fields_to_remove as $field_name => $criteria) {
00823     //replace &gt;field_name&lt;  with the current field name & 
00824     $criteria = preg_replace('/&gt;field_name&lt; /', $field_name, $criteria);
00825 
00826     // if field_value needed we can't deal with this field yet
00827     if (preg_match('/&gt;field_value&lt; /', $criteria)) { break; }
00828 
00829     //if criteria then remove from query
00830     $success = drupal_eval('<?php return '.$criteria.'; ?>');
00831 //    watchdog('tripal_core', 
00832 //      'Evaluating criteria (%criteria) for field %field in tripal_core_generate_chado_var for %table evaluated to %success',
00833 //      array('%table' => $table, '%criteria'=>$criteria, '%field' => $field_name, '%success'=>$success),
00834 //      WATCHDOG_NOTICE
00835 //    );
00836     if ($success) {
00837       unset($table_columns[array_search($field_name, $table_columns)]);
00838       unset($fields_to_remove[$field_name]);
00839       $all->expandable_fields[] = $table . '.' . $field_name;
00840     }
00841   }
00842   
00843   //Get fields to be removed by type................................
00844   $types_to_remove = module_invoke_all('exclude_type_by_default');
00845   $field_types = array();
00846   foreach ($table_desc['fields'] as $field_name => $field_array) {
00847     $field_types[$field_array['type']][] = $field_name; 
00848   }
00849   foreach ($types_to_remove as $field_type => $criteria) {
00850     // if there are fields of that type to remove
00851     if (is_array($field_types[$field_type])) {
00852       //replace &gt;field_name&lt;  with the current field name & 
00853       $criteria = preg_replace('/&gt;field_name&lt; /', $field_name, $criteria);
00854       
00855       foreach ($field_types[$field_type] as $field_name) {
00856         // if field_value needed we can't deal with this field yet
00857         if (preg_match('/&gt;field_value&lt; /', $criteria)) { 
00858           $fields_to_remove[$field_name] = $criteria;
00859           continue; 
00860         }
00861 
00862         // if field_value needed we can't deal with this field yet
00863         if (preg_match('/&gt;field_value&lt; /', $criteria)) { break; }
00864 
00865         //if criteria then remove from query
00866         $success = drupal_eval('<?php return '.$criteria.'; ?>');
00867 //        watchdog('tripal_core', 
00868 //          'Evaluating criteria (%criteria) for field %field of $type in tripal_core_generate_chado_var for %table evaluated to %success',
00869 //          array('%table'=>$table, '%criteria'=>$criteria, '%field'=>$field_name, '%type'=>$field_type, '%success'=>$success),
00870 //          WATCHDOG_NOTICE
00871 //        );
00872         if ($success) {
00873           unset($table_columns[array_search($field_name, $table_columns)]);
00874           $all->expandable_fields[] = $table . '.' . $field_name;
00875         }
00876       } //end of foreach field of that type
00877     }
00878   } //end of foreach type to be removed
00879   
00880   // get the values for the record in the current table---------------------------------------------
00881   $results = tripal_core_chado_select($table, $table_columns, $values,$base_options);   
00882   
00883   if($results){
00884      foreach ($results as $key => $object) {
00885        // Add empty expandable_x arrays
00886        $object->expandable_fields = $all->expandable_fields;
00887        $object->expandable_tables = $all->expandable_tables;
00888        $object->expandable_nodes = $all->expandable_nodes;
00889        
00890        // add curent table
00891        $object->tablename = $table;
00892        
00893        // check if the current table maps to a node type-----------------------------------------------
00894        // if this table is connected to a node there will be a chado_tablename table in drupal
00895        if (db_table_exists('chado_'.$table)) {
00896          // that has a foreign key to this one ($table_desc['primary key'][0] 
00897          // and to the node table (nid)
00898          $sql = "SELECT %s, nid FROM chado_%s WHERE %s=%d";
00899          $mapping = db_fetch_object(db_query(
00900            $sql,
00901            $table_primary_key,
00902            $table,
00903            $table_primary_key, 
00904            $object->{$table_primary_key}
00905          ));
00906          if ($mapping->{$table_primary_key}) {
00907            $object->nid = $mapping->nid;
00908            $object->expandable_nodes[] = $table;
00909          }
00910        }
00911 
00912        // remove any fields where criteria need to be evalulated---------------------------------------
00913        foreach ($fields_to_remove as $field_name => $criteria) {
00914          if (!isset($object->{$field_name})) { break; }
00915          $criteria = preg_replace('/&gt;field_value&lt; /', $object->{$field_name}, $criteria);
00916          //if criteria then remove from query
00917          $success = drupal_eval('<?php return '.$criteria.'; ?>');
00918    //      watchdog('tripal_core', 
00919    //        'Evaluating criteria (%criteria) for field %field in tripal_core_generate_chado_var for   %table evaluated to %success',
00920    //        array('%table' => $table, '%criteria'=>$criteria, '%field' => $field_name, '%success'=>$success),
00921    //        WATCHDOG_NOTICE
00922    //      );
00923          if ($success) {
00924            unset($object->{$field_name});
00925            $object->expandable_fields[] = $table . '.' . $field_name;
00926          }      
00927        }
00928        
00929        // recursively follow foreign key relationships nesting objects as we go------------------------
00930        if ($table_desc['foreign keys']) {
00931          foreach ($table_desc['foreign keys'] as $foreign_key_array) {
00932            $foreign_table = $foreign_key_array['table'];
00933            foreach ($foreign_key_array['columns'] as $foreign_key => $primary_key) {
00934              // Note: Foreign key is the field in the current table whereas primary_key is the field in 
00935              // the table referenced by the foreign key
00936              
00937              //Dont do anything if the foreign key is empty
00938              if (empty($object->{$foreign_key})) {
00939                break;
00940              }
00941              
00942              // get the record from the foreign table
00943              $foreign_values = array($primary_key => $object->{$foreign_key});
00944              $foreign_object = tripal_core_generate_chado_var($foreign_table, $foreign_values);
00945        
00946              // add the foreign record to the current object in a nested manner
00947              $object->{$foreign_key} = $foreign_object;
00948              
00949              // Flatten expandable_x arrays so only in the bottom object
00950              if (is_array($object->{$foreign_key}->expandable_fields)) {
00951                $object->expandable_fields = array_merge(
00952                  $object->expandable_fields, 
00953                  $object->{$foreign_key}->expandable_fields
00954                );
00955                unset($object->{$foreign_key}->expandable_fields);
00956              }
00957              if (is_array($object->{$foreign_key}->expandable_tables)) {
00958                $object->expandable_tables = array_merge(
00959                  $object->expandable_tables, 
00960                  $object->{$foreign_key}->expandable_tables
00961                );
00962                unset($object->{$foreign_key}->expandable_tables);
00963              }
00964              if (is_array($object->{$foreign_key}->expandable_nodes)) {
00965                $object->expandable_nodes = array_merge(
00966                  $object->expandable_nodes, 
00967                  $object->{$foreign_key}->expandable_nodes
00968                );
00969                unset($object->{$foreign_key}->expandable_nodes);
00970              }
00971            }
00972          }    
00973          
00974          $results[$key] = $object;
00975        }
00976      }
00977   }
00978   
00979     // check only one result returned
00980   if (sizeof($results) == 1) {
00981     // add results to object
00982     return $results[0];
00983   } elseif (!empty($results)) {
00984     return $results;
00985   } else {
00986     // no results returned
00987   } 
00988   
00989 }
00990 
00991 /**
00992  * Retrieves fields/tables/nodes that were excluded by default from a variable and adds them
00993  *
00994  * This function exists to allow tripal_core_generate_chado_var() to excldue some 
00995  * fields/tables/nodes from the default form of a variable without making it extremely difficult for 
00996  * the tripal admin to get at these variables if he/she wants them.
00997  *
00998  * @param $object
00999  *   This must be an object generated using tripal_core_generate_chado_var()
01000  * @param $type
01001  *   Must be one of 'field', 'table', 'node'. Indicates what is being expanded.
01002  * @param $to_expand
01003  *   The name of the field/table/node to be expanded
01004  * @param $table_options
01005  *   An array containing options for the base table.  For example, an
01006  *   option of 'order_by' may be used to sort results in the base table
01007  *   if more than one are returned.  The options must be compatible with
01008  *   the options accepted by the tripal_core_chado_select() function.
01009  * @return
01010  *   A chado object supplemented with the field/table/node requested to be expanded
01011  *
01012  * Example Usage:
01013  * @code
01014       // Get a chado object to be expanded
01015       $values = array(
01016         'name' => 'Medtr4g030710'
01017       );
01018       $features = tripal_core_generate_chado_var('feature', $values);
01019       
01020       // Expand the organism node
01021       $feature = tripal_core_expand_chado_vars($feature, 'node', 'organism');
01022       
01023       // Expand the feature.residues field
01024       $feature = tripal_core_expand_chado_vars($feature, 'field', 'feature.residues');
01025       
01026       // Expand the feature properties (featureprop table)
01027       $feature = tripal_core_expand_chado_vars($feature, 'table', 'featureprop');
01028  * @endcode
01029  *
01030  * @ingroup tripal_chado_api
01031  */
01032 function tripal_core_expand_chado_vars ($object, $type, $to_expand,$table_options = array()) {
01033   $base_table = $object->tablename;
01034   
01035   // check to see if they are expanding an array of objects
01036   if (is_array($object)) {
01037     foreach ($object as $index => $o) {
01038       $object[$index] = tripal_core_expand_chado_vars($o,$type,$to_expand);
01039     }
01040     return $object;
01041   }
01042   
01043   
01044   switch ($type) {
01045     case "field": //--------------------------------------------------------------------------------
01046       if (preg_match('/(\w+)\.(\w+)/', $to_expand, $matches)) {
01047         $tablename = $matches[1];
01048         $fieldname = $matches[2];
01049         $table_desc = module_invoke_all('chado_'.$tablename.'_schema');
01050 
01051         $values = array();
01052         foreach($table_desc['primary key'] as $key) {
01053           $values[$key] = $object->{$key};
01054         }
01055         
01056         if ($base_table == $tablename) {
01057           //get the field
01058           $results = tripal_core_chado_select(
01059             $tablename, 
01060             array($fieldname), 
01061             $values
01062           );
01063           $object->{$fieldname} = $results[0]->{$fieldname};
01064           $object->expanded = $to_expand;
01065         } else {
01066           //We need to recurse -the field is in a nested object
01067           foreach ((array) $object as $field_name => $field_value) {
01068             if (is_object($field_value)) {
01069               $object->{$field_name} = tripal_core_expand_chado_vars(
01070                 $field_value,
01071                 'field',
01072                 $to_expand
01073               );
01074             }
01075           } //end of for each field in the current object          
01076         }
01077       } else {
01078         watchdog(
01079           'tripal_core',
01080           'tripal_core_expand_chado_vars: Field (%field) not in the right format. It should be <tablename>.<fieldname>',
01081           WATCHDOG_ERROR
01082         );
01083       }
01084     
01085     break;
01086     case "table": //--------------------------------------------------------------------------------
01087       $foreign_table = $to_expand;
01088       $foreign_table_desc = module_invoke_all('chado_'.$foreign_table.'_schema');
01089       
01090       // If it's connected to the base table
01091       if ($foreign_table_desc['foreign keys'][$base_table]) {
01092         foreach ($foreign_table_desc['foreign keys'][$base_table]['columns'] as $left => $right) {
01093           if (!$object->{$right}) { break; }
01094           
01095           if (is_array($values)) { 
01096             $values = array_merge($values, array($left => $object->{$right}) );
01097           } else {
01098             $values = array($left => $object->{$right});
01099           }
01100           $foreign_object = tripal_core_generate_chado_var(
01101             $foreign_table,
01102             array($left => $object->{$right}),
01103             $table_options
01104           );
01105   
01106           if ($foreign_object) {
01107             // in the case where the a foreign key relationships exists more 
01108             // than once with the same table we want to alter the 
01109             // array structure
01110             if(count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1){
01111                $object->{$foreign_table}->{$left} = $foreign_object;
01112                $object->expanded = $to_expand;
01113             } else {
01114                $object->{$foreign_table} = $foreign_object;
01115                $object->expanded = $to_expand;
01116             }
01117           }
01118         }
01119       } else {
01120         //We need to recurse -the table has a relationship to one of the nested objects
01121         foreach ((array) $object as $field_name => $field_value) {
01122           // if we have a nested object ->expand the table in it
01123           if (is_object($field_value)) {
01124             $object->{$field_name} = tripal_core_expand_chado_vars(
01125               $field_value,
01126               'table',
01127               $foreign_table
01128             );
01129           }
01130         }
01131         
01132       }
01133       
01134     break;
01135     case "node": //---------------------------------------------------------------------------------
01136       //if the node to be expanded is for our base table, then just expand it
01137       if ($object->tablename == $to_expand) {
01138         $node = node_load($object->nid);
01139         if ($node) {
01140           $object->expanded = $to_expand;
01141           $node->expandable_fields = $object->expandable_fields;
01142           unset($object->expandable_fields);
01143           $node->expandable_tables = $object->expandable_tables;
01144           unset($object->expandable_tables);
01145           $node->expandable_nodes = $object->expandable_nodes;
01146           unset($object->expandable_nodes);
01147           $node->{$base_table} = $object;
01148           $object = $node;
01149         } else {
01150           watchdog(
01151             'tripal_core',
01152             'tripal_core_expand_chado_vars: No node matches the nid (%nid) supplied.',
01153             array('%nid'=>$object->nid),
01154             WATCHDOG_ERROR
01155           );
01156         } //end of if node
01157       } else {
01158         //We need to recurse -the node to expand is one of the nested objects
01159         foreach ((array) $object as $field_name => $field_value) {
01160           if (is_object($field_value)) {
01161             $object->{$field_name} = tripal_core_expand_chado_vars(
01162               $field_value,
01163               'node',
01164               $to_expand
01165             );
01166           }
01167         } //end of for each field in the current object
01168       }
01169       
01170     break;
01171     default:
01172      watchdog('tripal_core',
01173       'tripal_core_expand_chado_vars: Unrecognized type (%type). Should be one of "field", "table", "node".',
01174       array('%type'=>$type),
01175       WATCHDOG_ERROR
01176     );
01177     return FALSE;
01178   }
01179 
01180   //move extended array downwards-------------------------------------------------------------------
01181   if (!$object->expanded) {
01182     //if there's no extended field then go hunting for it
01183     foreach ( (array)$object as $field_name => $field_value) {
01184       if (is_object($field_value)) {
01185         if (isset($field_value->expanded)) {
01186           $object->expanded = $field_value->expanded;
01187           unset($field_value->expanded);
01188         }
01189       }
01190     }  
01191   }
01192   //try again becasue now we might have moved it down
01193   if ($object->expanded) {
01194     $expandable_name = 'expandable_'.$type.'s';
01195     if ($object->{$expandable_name}) {
01196       $key_to_remove = array_search($object->expanded, $object->{$expandable_name});
01197       unset($object->{$expandable_name}[$key_to_remove]);
01198       unset($object->expanded);
01199     } else {
01200       // if there is an expandable array then we've reached the base object
01201       // if we get here and don't have anything expanded then something went wrong
01202 //      watchdog(
01203 //        'tripal_core',
01204 //        'tripal_core_expand_chado_vars: Unable to expand the %type %to_expand',
01205 //        array('%type'=>$type, '%to_expand'=>$to_expand),
01206 //        WATCHDOG_ERROR
01207 //      );
01208     } //end of it we've reached the base object
01209   }
01210   
01211   return $object;
01212 }
01213 
01214 /**
01215  * Implements hook_exclude_type_by_default()
01216  *
01217  * This hooks allows fields of a specified type that match a specified criteria to be excluded by 
01218  * default from any table when tripal_core_generate_chado_var() is called. Keep in mind that if
01219  * fields are excluded by default they can always be expanded at a later date using 
01220  * tripal_core_expand_chado_vars(). 
01221  *
01222  * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using 
01223  * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are 
01224  * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can 
01225  * contain the following tokens:
01226  *   - &gt;field_name&lt; 
01227  *       Replaced by the name of the field to be excluded
01228  *   - &gt;field_value&lt; 
01229  *       Replaced by the value of the field in the current record
01230  * Also keep in mind that if your criteria doesn't contain the &gt;field_value&lt;  token then it will be 
01231  * evaluated before the query is executed and if the field is excluded it won't be included in the 
01232  * query.
01233  *
01234  * @return
01235  *   An array of type => criteria where the type is excluded if the criteria evaluates to TRUE
01236  *
01237  * @ingroup tripal_chado_api
01238  */
01239 function tripal_core_exclude_type_by_default() {
01240   return array('text' => "strlen('&gt;field_value&lt; ') > 100");
01241 }
01242 
01243 /**
01244  * Implements hook_exclude_field_from_<tablename>_by_default()
01245  *
01246  * This hooks allows fields from a specified table that match a specified criteria to be excluded by 
01247  * default from any table when tripal_core_generate_chado_var() is called. Keep in mind that if
01248  * fields are excluded by default they can always be expanded at a later date using 
01249  * tripal_core_expand_chado_vars(). 
01250  *
01251  * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using 
01252  * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are 
01253  * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can 
01254  * contain the following tokens:
01255  *   - &gt;field_name&lt; 
01256  *       Replaced by the name of the field to be excluded
01257  *   - &gt;field_value&lt; 
01258  *       Replaced by the value of the field in the current record
01259  * Also keep in mind that if your criteria doesn't contain the &gt;field_value&lt;  token then it will be 
01260  * evaluated before the query is executed and if the field is excluded it won't be included in the 
01261  * query.
01262  *
01263  * @return
01264  *   An array of type => criteria where the type is excluded if the criteria evaluates to TRUE
01265  *
01266  * @ingroup tripal_chado_api
01267  */
01268 function tripal_core_exclude_field_from_feature_by_default() {
01269   return array();
01270 }
01271 
01272 /**
01273  *  Use this function instead of db_query() to avoid switching databases
01274  *  when making query to the chado database
01275  */
01276 function chado_query($sql) {
01277   $args = func_get_args();
01278   array_shift($args);
01279   $sql = db_prefix_tables($sql);
01280   if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
01281     $args = $args[0];
01282   }
01283   _db_query_callback($args, TRUE);
01284   $sql = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $sql);
01285   $previous_db = db_set_active('chado');
01286   $results = _db_query($sql);
01287   db_set_active($previous_db);
01288   return $results;
01289 }
01290 
01291 /**
01292  * Get chado id for a node. E.g, if you want to get 'analysis_id' from the 
01293  * 'analysis' table for a synced 'chado_analysis' node, use:
01294  * $analysis_id = chado_get_id_for_node ('analysis', $node)
01295  * Likewise,
01296  * $organism_id = chado_get_id_for_node ('organism', $node)
01297  * $feature_id = chado_get_id_for_node ('feature', $node)
01298  */
01299 function chado_get_id_for_node ($table, $node) {
01300    return db_result(db_query("SELECT $table"."_id FROM {chado_".$table."} WHERE nid = $node->nid"));
01301 }
01302 
01303 /**
01304  *  Get node id for a chado feature/organism/analysis. E.g, if you want to
01305  *  get the node id for an analysis, use:
01306  *  $nid = chado_get_node_id ('analysis', $analysis_id)
01307  *  Likewise,
01308  *  $nid = chado_get_node_id ('organism', $organism_id)
01309  *  $nid = chado_get_node_id ('feature', $feature_id) 
01310  */
01311 function chado_get_node_id ($table, $id) {
01312    return db_result(db_query("SELECT nid FROM {chado_".$table."} WHERE $table"."_id = $id"));
01313 }
01314 
01315 /**
01316  * Retrieve a property for a given base table record
01317  *
01318  * @param $basetable
01319  *   The base table for which the property should be retrieved. Thus to retrieve a property
01320  *   for a feature the basetable=feature and property is retrieved from featureprop
01321  * @param $record_id
01322  *   The primary key of the basetable to retrieve properties for. This should be in integer.
01323  * @param $property
01324  *   The cvterm name describing the type of properties to be retrieved
01325  * @param $cv_name
01326  *   The name of the cv that the above cvterm is part of
01327  *
01328  * @return
01329  *   A chado variable with the specified properties expanded
01330  *
01331  * @ingroup tripal_chado_api
01332  */
01333 function tripal_core_get_property($basetable, $record_id, $property, $cv_name){
01334 
01335    // get the foreign key for this property table
01336    $table_desc = module_invoke_all('chado_'.$basetable.'prop_schema');
01337    $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
01338 
01339    // construct the array of values to be inserted  
01340    $values = array (
01341       $fkcol => $record_id,
01342       'type_id' => array ( 
01343          'cv_id' => array (
01344             'name' => $cv_name,
01345          ),
01346          'name' => $property,
01347          'is_obsolete' => 0
01348       ),
01349    );
01350    $results = tripal_core_generate_chado_var($basetable.'prop',$values);
01351    $results = tripal_core_expand_chado_vars($results,'field',$basetable.'prop.value');
01352    return $results;
01353 }
01354 /**
01355  * Insert a property for a given basetable record
01356  *
01357  * @param $basetable
01358  *   The base table for which the property should be inserted. Thus to insert a property
01359  *   for a feature the basetable=feature and property is inserted into featureprop
01360  * @param $record_id
01361  *   The primary key of the basetable to insert a property for. This should be in integer.
01362  * @param $property
01363  *   The cvterm name describing the type of properties to be inserted
01364  * @param $cv_name
01365  *   The name of the cv that the above cvterm is part of
01366  * @param $value
01367  *   The value of the property to be inserted (can be empty)
01368  * @param $update_if_present
01369  *   A boolean indicating whether an existing record should be updated or an error thrown
01370  *
01371  * @return
01372  *   Return True on Insert/Update and False otherwise
01373  *
01374  * @ingroup tripal_chado_api
01375  */
01376 function tripal_core_insert_property($basetable, $record_id, $property, 
01377    $cv_name, $value, $update_if_present = 0)
01378 {
01379    // first see if the property already exists, if so we can't insert
01380    $prop = tripal_core_get_property($basetable,$record_id,$property,$cv_name);
01381    if(count($prop)>0){ 
01382       if($update_if_present){
01383         return tripal_core_update_property($basetable,$record_id,$property,$cv_name,$value) ;
01384       } else {
01385         return FALSE;
01386       }
01387    }
01388 
01389    // get the foreign key for this property table
01390    $table_desc = module_invoke_all('chado_'.$basetable.'prop_schema');
01391    $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
01392 
01393    // construct the array of values to be inserted  
01394    $values = array (
01395       $fkcol => $record_id,
01396       'type_id' => array ( 
01397          'cv_id' => array (
01398             'name' => $cv_name,
01399          ),
01400          'name' => $property,
01401          'is_obsolete' => 0
01402       ),
01403       'value' => $value, 
01404       'rank' => 0,
01405    );
01406    return tripal_core_chado_insert($basetable.'prop',$values);
01407 }
01408 
01409 /**
01410  * Update a property for a given basetable record
01411  *
01412  * @param $basetable
01413  *   The base table for which the property should be updated. Thus to update a property
01414  *   for a feature the basetable=feature and property is updated in featureprop
01415  * @param $record_id
01416  *   The primary key of the basetable to update a property for. This should be in integer.
01417  * @param $property
01418  *   The cvterm name describing the type of property to be updated
01419  * @param $cv_name
01420  *   The name of the cv that the above cvterm is part of
01421  * @param $value
01422  *   The value of the property to be inserted (can be empty)
01423  * @param $insert_if_missing
01424  *   A boolean indicating whether a record should be inserted if one doesn't exist to update
01425  *
01426  * Note: The property to be updated is select via theu nique combination of $record_id and
01427  * $property and then it is updated with the supplied value
01428  *
01429  * @return
01430  *   Return True on Update/Insert and False otherwise
01431  *
01432  * @ingroup tripal_chado_api
01433  */
01434 function tripal_core_update_property($basetable, $record_id,$property,$cv_name,
01435    $value,$insert_if_missing = 0)
01436 {
01437 
01438    // first see if the property is missing (we can't update a missing property
01439    $prop = tripal_core_get_property($basetable,$record_id,$property,$cv_name);
01440    if(count($prop)==0){
01441       if($insert_if_missing){
01442         return tripal_core_insert_property($basetable,$record_id,$property,$cv_name,$value);
01443       } else {
01444         return FALSE;
01445       }
01446    }
01447 
01448    // get the foreign key for this property table
01449    $table_desc = module_invoke_all('chado_'.$basetable.'prop_schema');
01450    $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
01451 
01452    // construct the array that will match the exact record to update
01453    $match = array (
01454       $fkcol => $record_id,
01455       'type_id' => array ( 
01456          'cv_id' => array (
01457             'name' => $cv_name,
01458          ),
01459          'name' => $property,
01460       ),
01461    );
01462    // construct the array of values to be updated
01463    $values = array (      
01464       'value' => $value, 
01465    );
01466    return tripal_core_chado_update($basetable.'prop',$match,$values);
01467 }
01468 
01469 /**
01470  * Deletes a property for a given basetable record
01471  *
01472  * @param $basetable
01473  *   The base table for which the property should be deleted. Thus to deleted a property
01474  *   for a feature the basetable=feature and property is deleted from featureprop
01475  * @param $record_id
01476  *   The primary key of the basetable to delete a property for. This should be in integer.
01477  * @param $property
01478  *   The cvterm name describing the type of property to be deleted
01479  * @param $cv_name
01480  *   The name of the cv that the above cvterm is part of
01481  *
01482  * Note: The property to be deleted is select via theu nique combination of $record_id and $property 
01483  *
01484  * @return
01485  *   Return True on Delete and False otherwise
01486  *
01487  * @ingroup tripal_chado_api
01488  */
01489 function tripal_core_delete_property($basetable, $record_id,$property,$cv_name){
01490    // get the foreign key for this property table
01491    $table_desc = module_invoke_all('chado_'.$basetable.'prop_schema');
01492    $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
01493 
01494    // construct the array that will match the exact record to update
01495    $match = array (
01496       $fkcol => $record_id,
01497       'type_id' => array ( 
01498          'cv_id' => array (
01499             'name' => $cv_name,
01500          ),
01501          'name' => $property,
01502       ),
01503    );
01504    return tripal_core_chado_delete($basetable.'prop',$match);
01505 }
01506 
01507 /**
01508  * This function is typically used in the '.install' file for a Tripal module
01509  * Each module should call this function during installation to create
01510  * the module data directory which is sites/default/files/tripal/[module_name] 
01511  * for default Drupal settings.  This directory can then be used by the module 
01512  * for storing files.
01513  *
01514  * @param $module_name
01515  *   the name of the module being installed.
01516  *
01517  * @returns
01518  *   nothing
01519  *
01520  * @ingroup tripal_files_api
01521  */
01522 function tripal_create_moddir($module_name){
01523    // make the data directory for this module
01524    $data_dir = file_directory_path() . "/tripal/$module_name";
01525    if(!file_check_directory($data_dir,FILE_CREATE_DIRECTORY|FILE_MODIFY_PERMISSIONS)){
01526       $message = "Cannot create directory $data_dir. This module may not ".
01527                  "behave correctly without this directory.  Please  create ".
01528                  "the directory manually or fix the problem and reinstall.";
01529       drupal_set_message($message,'error');      
01530       watchdog('tripal_core',$message,array(),WATCHDOG_ERROR);
01531    }
01532 }
01533 
01534 /**
01535  * Each Tripal module has a unique data directory which was creatd using the
01536  * tripal_create_moddir function during installation.  This function 
01537  * retrieves the directory path.
01538  *
01539  * @param $module_name
01540  *   The name of the module
01541  *
01542  * @returns
01543  *   The path within the Drupal installation where the data directory resides
01544  * @ingroup tripal_files_api
01545  */
01546 function tripal_get_moddir($module_name){
01547    $data_dir = file_directory_path() . "/tripal/$module_name";
01548    return $data_dir;
01549 }
01550 /**
01551  * Set the Tripal Database
01552  *
01553  * The tripal_db_set_active function is used to prevent namespace collisions
01554  * when chado and drupal are installed in the same database but in different
01555  * schemas.  It is also used for backwards compatibility with older versions
01556  * of tripal or in cases where chado is located outside of the Drupal database.
01557  *
01558  * @ingroup tripal_chado_api
01559  */
01560 function tripal_db_set_active($dbname){
01561    global $db_url, $db_type;
01562    $chado_exists = 0;
01563 
01564    // only postgres can support search paths.  So if this is MysQL then
01565    // just run the normal tripal_db_set_active function.
01566    if(strcmp($db_type,'pgsql')==0){
01567 
01568       // if the 'chado' database is in the $db_url variable then chado is 
01569       // not in the same Drupal database
01570       if(is_array($db_url)){ 
01571          if(isset($db_url[$dbname])){
01572             return db_set_active($dbname);
01573          } 
01574       }
01575 
01576       // check to make sure the chado schema exists
01577       $sql = "select nspname from pg_catalog.pg_namespace where nspname = 'chado'";
01578       if(db_fetch_object(db_query($sql))){
01579          $chado_exists = 1;
01580       }
01581 
01582       // here we make the assumption that the default database schema is
01583       // 'public'.  This will most likely always be the case but if not,
01584       // then this code will break
01585       if($chado_exists && strcmp($dbname,'chado')==0){
01586          db_query("set search_path to %s",'chado,public');  
01587          return 'public,chado';
01588       } 
01589       elseif($chado_exists) {
01590          db_query("set search_path to %s",'public,chado');  
01591          return 'chado,public';
01592       }
01593       else {
01594          return db_set_active($dbname);
01595       }
01596    }
01597    else return db_set_active($dbname);
01598 }
01599 /**
01600  * Purpose: Get max rank for a given set of criteria
01601  *   This function was developed with the many property tables in chado in mind
01602  *
01603  * @param $tablename
01604  *    The name of the chado table you want to select the max rank from this table must contain a 
01605  *    rank column of type integer
01606  * @param $where_options
01607  *   where options should include the id and type for that table to correctly
01608  *     group a set of records together where the only difference are the value and rank
01609  * @code 
01610  *  array(
01611  *     <column_name> => array(
01612  *        'type' => <type of column: INT/STRING>,
01613  *       'value' => <the value you want to filter on>,
01614  *      'exact' => <if TRUE use =; if FALSE use ~>,
01615  *    )
01616  *  )
01617  * @endcode
01618  * @return the maximum rank
01619  *
01620  * @ingroup tripal_chado_api
01621  */
01622 function tripal_get_max_chado_rank ($tablename, $where_options) {
01623 
01624   $where= array();
01625   //generate the where clause from supplied options
01626   // the key is the column name
01627   foreach ($where_options as $key => $val_array) {
01628     if (preg_match('/INT/', $val_array['type'])) {
01629       $where[] = $key."=".$val_array['value'];
01630     } else {
01631       if ($val_array['exact']) { $operator='='; }
01632       else { $operator='~'; }
01633       $where[] = $key.$operator."'".$val_array['value']."'";
01634     }
01635   }
01636   
01637   $previous_db = tripal_db_set_active('chado');
01638   $result = db_fetch_object(db_query(
01639     "SELECT max(rank) as max_rank, count(rank) as count FROM %s WHERE %s",
01640     $tablename,
01641     implode(' AND ',$where)
01642   ));
01643   tripal_db_set_active($previous_db);
01644   //drupal_set_message("Max Rank Query=SELECT max(rank) as max_rank, count(rank) as count FROM ".$tablename." WHERE ".implode(' AND ',$where));
01645   if ($result->count > 0) {
01646     return $result->max_rank;
01647   } else {
01648     return -1;
01649   }
01650 }
 All Classes Files Functions Variables