tripal_core_chado.api.inc 92 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563
  1. <?php
  2. require_once "tripal_core.schema_v1.2.api.inc";
  3. require_once "tripal_core.schema_v1.11.api.inc";
  4. // Globals used by Tripals Error catching functions
  5. // Should match those defined by watchdog
  6. define('TRIPAL_CRITICAL',2);
  7. define('TRIPAL_ERROR',3);
  8. define('TRIPAL_WARNING',4);
  9. define('TRIPAL_NOTICE',5);
  10. define('TRIPAL_INFO',6);
  11. define('TRIPAL_DEBUG',7);
  12. /**
  13. * This function is used to set the global Chado variables
  14. */
  15. function tripal_core_set_globals() {
  16. // these global variables are meant to be accessed by all Tripal
  17. // modules to find the chado version installed and if Chado is local.
  18. // these variables are stored as globals rather than using the drupal_set_variable
  19. // functions because the Drupal functions make databaes queries and for long
  20. // running loaders we don't want those queries repeatedly.
  21. $GLOBALS["chado_is_installed"] = tripal_core_is_chado_installed();
  22. if ($GLOBALS["chado_is_installed"]) {
  23. $GLOBALS["chado_is_local"] = tripal_core_is_chado_local();
  24. $GLOBALS["chado_version"] = tripal_core_get_chado_version();
  25. $GLOBALS["exact_chado_version"] = tripal_core_get_chado_version(TRUE);
  26. }
  27. }
  28. /**
  29. * Provide better error notice for Tripal
  30. * @param $type
  31. * The catagory to which this message belongs. Can be any string, but the general
  32. * practice is to use the name of the module.
  33. * @param $message
  34. * The message to store in the log. Keep $message translatable by not concatenating
  35. * dynamic values into it! Variables in the message should be added by using placeholder
  36. * strings alongside the variables argument to declare the value of the placeholders.
  37. * See t() for documentation on how $message and $variables interact.
  38. * @param $variables
  39. * Array of variables to replace in the message on display or NULL if message is
  40. * already translated or not possible to translate.
  41. * @param $severity
  42. * The severity of the message; one of the following values:
  43. * - TRIPAL_CRITICAL: Critical conditions.
  44. * - TRIPAL_ERROR: Error conditions.
  45. * - TRIPAL_WARNING: Warning conditions.
  46. * - TRIPAL_NOTICE: (default) Normal but significant conditions.
  47. * - TRIPAL_INFO: Informational messages.
  48. * - TRIPAL_DEBUG: Debug-level messages.
  49. * @param $options
  50. * An array of options. Some available options include:
  51. * - print: prints the error message to the screen. Useful when display is the command-line
  52. */
  53. function tripal_core_report_error($type, $severity, $message, $variables = array(), $options = array()) {
  54. // Get human-readable severity string
  55. $severity_string = '';
  56. switch ($severity) {
  57. case TRIPAL_CRITICAL:
  58. $severity_string = 'CRITICAL';
  59. break;
  60. case TRIPAL_ERROR:
  61. $severity_string = 'ERROR';
  62. break;
  63. case TRIPAL_WARNING:
  64. $severity_string = 'WARNING';
  65. break;
  66. case TRIPAL_NOTICE:
  67. $severity_string = 'NOTICE';
  68. break;
  69. case TRIPAL_INFO:
  70. $severity_string = 'INFO';
  71. break;
  72. case TRIPAL_DEBUG:
  73. $severity_string = 'DEBUG';
  74. break;
  75. }
  76. // Send to watchdog
  77. try {
  78. watchdog($type, $message, $variables, $severity);
  79. }
  80. catch (Exception $e) {
  81. print "CRITICAL (TRIPAL_CORE): Unable to register error message with watchdog";
  82. $options['print'] = TRUE;
  83. }
  84. // If print option supplied then print directly to the screen
  85. if (isset($options['print'])) {
  86. if (sizeof($variables) > 0) {
  87. $message = str_replace(array_keys($variables), $variables, $message);
  88. }
  89. print $severity_string . ' (' . strtoupper($type) . '):' . $message . "\n";
  90. }
  91. }
  92. /**
  93. * @file
  94. * The Tripal Core API
  95. *
  96. * This file provides the API needed for all other Tripal and Tripal dependent
  97. * modules.
  98. *
  99. *
  100. * @defgroup tripal_chado_api Chado API
  101. * @ingroup tripal_core_api
  102. * @{
  103. * Provides an application programming interface (API) to manage data withing the Chado database.
  104. * This includes functions for selecting, inserting, updating and deleting records
  105. * in Chado tables. The functions will ensure proper integrity contraints are met
  106. * for inserts and updates.
  107. *
  108. * Also, a set of functions is provided for creating template variables. First,
  109. * is the tripal_core_generate_chado_vars which is used to select one ore more
  110. * records from a table and return an array with foreign key relationships fully
  111. * populated. For example, if selecting a feature, the organism_id and type_id
  112. * would be present in the returned array as a nested array with their respective
  113. * foreign keys also nested. The only fields that are not included are text
  114. * fields (which may be very large) or many-to-many foreign key relationships.
  115. * However, these fields and relationships can be expanded using the
  116. * tripal_core_expand_chado_vars.
  117. *
  118. * When a row from a chado table is selected using these two functions, it provides
  119. * a way for users who want to cutomize Drupal template files to access all data
  120. * associate with a specific record.
  121. *
  122. * Finally, the property tables in Chado generally follow the same format. Therefore
  123. * there is a set of functions for inserting, updating and deleting properties for
  124. * any table. This provides quick lookup of properties (provided the CV term is
  125. * known).
  126. *
  127. * @}
  128. *
  129. */
  130. /**
  131. * Provides a generic routine for inserting into any Chado table
  132. *
  133. * Use this function to insert a record into any Chado table. The first
  134. * argument specifies the table for inserting and the second is an array
  135. * of values to be inserted. The array is mutli-dimensional such that
  136. * foreign key lookup values can be specified.
  137. *
  138. * @param $table
  139. * The name of the chado table for inserting
  140. * @param $values
  141. * An associative array containing the values for inserting.
  142. * @param $options
  143. * An array of options such as:
  144. * - skip_validation: TRUE or FALSE. If TRUE will skip all the validation steps and
  145. * just try to insert as is. This is much faster but results in unhandled
  146. * non user-friendly errors if the insert fails.
  147. * - return_record: by default, the function will return the record but with
  148. * the primary keys added after insertion. To simply return TRUE on success
  149. * set this option to FALSE
  150. *
  151. * @return
  152. * On success this function returns the inserted record with the new primary keys
  153. * added to the returned array. On failure, it returns FALSE.
  154. *
  155. * Example usage:
  156. * @code
  157. * $values = array(
  158. * 'organism_id' => array(
  159. * 'genus' => 'Citrus',
  160. * 'species' => 'sinensis',
  161. * ),
  162. * 'name' => 'orange1.1g000034m.g',
  163. * 'uniquename' => 'orange1.1g000034m.g',
  164. * 'type_id' => array (
  165. * 'cv_id' => array (
  166. * 'name' => 'sequence',
  167. * ),
  168. * 'name' => 'gene',
  169. * 'is_obsolete' => 0
  170. * ),
  171. * );
  172. * $result = tripal_core_chado_insert('feature',$values);
  173. * @endcode
  174. * The above code inserts a record into the feature table. The $values array is
  175. * nested such that the organism is selected by way of the organism_id foreign
  176. * key constraint by specifying the genus and species. The cvterm is also
  177. * specified using its foreign key and the cv_id for the cvterm is nested as
  178. * well.
  179. *
  180. * @ingroup tripal_chado_api
  181. */
  182. function tripal_core_chado_insert($table, $values, $options = array()) {
  183. $print_errors = (isset($options['print_errors'])) ? $options['print_errors'] : FALSE;
  184. if (!is_array($values)) {
  185. tripal_core_report_error(
  186. 'tripal_core',
  187. TRIPAL_ERROR,
  188. 'Cannot pass non array as values for inserting.',
  189. array(),
  190. array('print' => $print_errors)
  191. );
  192. return FALSE;
  193. }
  194. if (count($values)==0) {
  195. tripal_core_report_error(
  196. 'tripal_core',
  197. TRIPAL_ERROR,
  198. 'Cannot pass an empty array as values for inserting.',
  199. array(),
  200. array('print' => $print_errors)
  201. );
  202. return FALSE;
  203. }
  204. // set defaults for options. If we don't set defaults then
  205. // we get memory leaks when we try to access the elements
  206. if (!is_array($options)) {
  207. $options = array();
  208. }
  209. if (!array_key_exists('skip_validation', $options)) {
  210. $options['skip_validation'] = FALSE;
  211. }
  212. if (!array_key_exists('return_record', $options)) {
  213. $options['return_record'] = TRUE;
  214. }
  215. $insert_values = array();
  216. if (array_key_exists('skip_validation', $options)) {
  217. $validate = !$options['skip_validation'];
  218. }
  219. else {
  220. $validate = TRUE;
  221. }
  222. // get the table description
  223. $table_desc = tripal_core_get_chado_table_schema($table);
  224. if (empty($table_desc)) {
  225. tripal_core_report_error('tripal_core', TRIPAL_WARNING,
  226. 'tripal_core_chado_insert; There is no table description for !table_name',
  227. array('!table_name' => $table), array('print' => $print_errors)
  228. );
  229. }
  230. // iterate through the values array and create a new 'insert_values' array
  231. // that has all the values needed for insert with all foreign relationsihps
  232. // resolved.
  233. foreach ($values as $field => $value) {
  234. // make sure the field is in the table description. If not then return an error
  235. // message
  236. if (!array_key_exists($field, $table_desc['fields'])) {
  237. tripal_core_report_error(
  238. 'tripal_core',
  239. TRIPAL_ERROR,
  240. "tripal_core_chado_insert; The field '%field' does not exist " .
  241. "for the table '%table'. Cannot perform insert. Values: %array",
  242. array('%field' => $field, '%table' => $table, '%array' => print_r($values, 1)),
  243. array('print' => $print_errors)
  244. );
  245. return FALSE;
  246. }
  247. if (is_array($value)) {
  248. // select the value from the foreign key relationship for this value
  249. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value);
  250. if (sizeof($results) > 1) {
  251. tripal_core_report_error(
  252. 'tripal_core',
  253. TRIPAL_ERROR,
  254. 'tripal_core_chado_insert: Too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)',
  255. array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)),
  256. array('print' => $print_errors)
  257. );
  258. }
  259. elseif (sizeof($results) < 1) {
  260. tripal_core_report_error(
  261. 'tripal_core',
  262. TRIPAL_DEBUG,
  263. 'tripal_core_chado_insert: no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)',
  264. array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)),
  265. array('print' => $print_errors)
  266. );
  267. }
  268. else {
  269. $insert_values[$field] = $results[0];
  270. }
  271. }
  272. else {
  273. $insert_values[$field] = $value;
  274. }
  275. }
  276. if ($validate) {
  277. // check for violation of any unique constraints
  278. $ukeys = array();
  279. if (array_key_exists('unique keys', $table_desc)) {
  280. $ukeys = $table_desc['unique keys'];
  281. }
  282. $ukselect_cols = array();
  283. $ukselect_vals = array();
  284. if ($ukeys) {
  285. foreach ($ukeys as $name => $fields) {
  286. foreach ($fields as $index => $field) {
  287. // build the arrays for performing a select that will check the contraint
  288. $ukselect_cols[] = $field;
  289. if (!array_key_exists($field, $insert_values)) {
  290. if (array_key_exists('default', $table_desc['fields'][$field])) {
  291. $ukselect_vals[$field] = $table_desc['fields'][$field]['default'];
  292. }
  293. }
  294. else {
  295. $ukselect_vals[$field] = $insert_values[$field];
  296. }
  297. }
  298. // now check the constraint
  299. if (tripal_core_chado_select($table, $ukselect_cols, $ukselect_vals)) {
  300. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  301. "tripal_core_chado_insert; Cannot insert duplicate record into $table table: !values",
  302. array('!values' => print_r($values, TRUE)), array('print' => $print_errors)
  303. );
  304. return FALSE;
  305. }
  306. }
  307. }
  308. // if trying to insert a field that is the primary key, make sure it also is unique
  309. if (array_key_exists('primary key', $table_desc)) {
  310. $pkey = $table_desc['primary key'][0];
  311. if (array_key_exists($pkey, $insert_values)) {
  312. $coptions = array();
  313. if (tripal_core_chado_select($table, array($pkey), array($pkey => $insert_values[$pkey]), $coptions)) {
  314. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  315. 'tripal_core_chado_insert; Cannot insert duplicate primary key into !table table: !values',
  316. array('!table' => $table, '!values' => print_r($values, TRUE)),
  317. array('print' => $print_errors)
  318. );
  319. return FALSE;
  320. }
  321. }
  322. }
  323. // make sure required fields have a value
  324. if (!is_array($table_desc['fields'])) {
  325. $table_desc['fields'] = array();
  326. tripal_core_report_error(
  327. 'tripal_core',
  328. TRIPAL_WARNING,
  329. "tripal_core_chado_insert; %table missing fields: \n %schema",
  330. array('%table' => $table, '%schema' => print_r($table_desc, 1)),
  331. array('print' => $print_errors)
  332. );
  333. }
  334. foreach ($table_desc['fields'] as $field => $def) {
  335. // a field is considered missing if it cannot be NULL and there is no default
  336. // value for it or it is of type 'serial'
  337. if (array_key_exists('NOT NULL', $def) and
  338. !array_key_exists($field, $insert_values) and
  339. !array_key_exists('default', $def) and
  340. strcmp($def['type'], serial) != 0) {
  341. tripal_core_report_error(
  342. 'tripal_core',
  343. TRIPAL_ERROR,
  344. "tripal_core_chado_insert; Field %table.%field cannot be NULL: %values",
  345. array('%table' => $table, '%field' => $field, '%values' => print_r($values, 1)),
  346. array('print' => $print_errors)
  347. );
  348. return FALSE;
  349. }
  350. }
  351. } //end of validation
  352. // Now build the insert SQL statement
  353. $ifields = array(); // contains the names of the fields
  354. $itypes = array(); // contains placeholders for the sql query
  355. $ivalues = array(); // contains the values of the fields
  356. $i = 1;
  357. foreach ($insert_values as $field => $value) {
  358. $ifields[] = $field;
  359. $ivalues[":$field"] = $value;
  360. $i++;
  361. if (strcmp($value, '__NULL__')==0) {
  362. $itypes[] = "NULL";
  363. }
  364. else {
  365. $itypes[] = ":$field";
  366. }
  367. }
  368. // create the SQL
  369. $sql = 'INSERT INTO {' . $table . '} (' . implode(", ", $ifields) . ") VALUES (" . implode(", ", $itypes) . ")";
  370. $result = chado_query($sql, $ivalues);
  371. // if we have a result then add primary keys to return array
  372. if ($options['return_record'] == TRUE and $result) {
  373. if (array_key_exists('primary key', $table_desc) and is_array($table_desc['primary key'])) {
  374. foreach ($table_desc['primary key'] as $field) {
  375. $sql = "SELECT CURRVAL('{" . $table . "_" . $field . "_seq}')";
  376. $results = chado_query($sql);
  377. $value = $results->fetchField();
  378. if (!$value) {
  379. tripal_core_report_error(
  380. 'tripal_core',
  381. TRIPAL_ERROR,
  382. "tripal_core_chado_insert; not able to retrieve primary key after insert: %sql",
  383. array('%sql' => $sql),
  384. array('print' => $print_errors)
  385. );
  386. return FALSE;
  387. }
  388. $values[$field] = $value;
  389. }
  390. }
  391. return $values;
  392. }
  393. elseif ($options['return_record'] == FALSE and $result) {
  394. return TRUE;
  395. }
  396. else {
  397. tripal_core_report_error(
  398. 'tripal_core',
  399. TRIPAL_ERROR,
  400. 'tripal_core_chado_insert; Cannot insert record into "%table": %values',
  401. array('%table' => $table, '%values' => print_r($values, 1)),
  402. array('print' => $print_errors)
  403. );
  404. return FALSE;
  405. }
  406. return FALSE;
  407. }
  408. /**
  409. * Provides a generic routine for updating into any Chado table
  410. *
  411. * Use this function to update a record in any Chado table. The first
  412. * argument specifies the table for inserting, the second is an array
  413. * of values to matched for locating the record for updating, and the third
  414. * argument give the values to update. The arrays are mutli-dimensional such
  415. * that foreign key lookup values can be specified.
  416. *
  417. * @param $table
  418. * The name of the chado table for inserting
  419. * @param $match
  420. * An associative array containing the values for locating a record to update.
  421. * @param $values
  422. * An associative array containing the values for updating.
  423. * @param $options
  424. * An array of options such as:
  425. * - return_record: by default, the function will return the TRUE if the record
  426. * was succesfully updated. However, set this option to TRUE to return the
  427. * record that was updated. The returned record will have the fields provided
  428. * but the primary key (if available for the table) will be added to the record.
  429. * @return
  430. * On success this function returns TRUE. On failure, it returns FALSE.
  431. *
  432. * Example usage:
  433. * @code
  434. $umatch = array(
  435. 'organism_id' => array(
  436. 'genus' => 'Citrus',
  437. 'species' => 'sinensis',
  438. ),
  439. 'uniquename' => 'orange1.1g000034m.g7',
  440. 'type_id' => array (
  441. 'cv_id' => array (
  442. 'name' => 'sequence',
  443. ),
  444. 'name' => 'gene',
  445. 'is_obsolete' => 0
  446. ),
  447. );
  448. $uvalues = array(
  449. 'name' => 'orange1.1g000034m.g',
  450. 'type_id' => array (
  451. 'cv_id' => array (
  452. 'name' => 'sequence',
  453. ),
  454. 'name' => 'mRNA',
  455. 'is_obsolete' => 0
  456. ),
  457. );
  458. * $result = tripal_core_chado_update('feature',$umatch,$uvalues);
  459. * @endcode
  460. * The above code species that a feature with a given uniquename, organism_id,
  461. * and type_id (the unique constraint for the feature table) will be updated.
  462. * The organism_id is specified as a nested array that uses the organism_id
  463. * foreign key constraint to lookup the specified values to find the exact
  464. * organism_id. The same nested struture is also used for specifying the
  465. * values to update. The function will find the record that matches the
  466. * columns specified and update the record with the avlues in the $uvalues array.
  467. *
  468. * @ingroup tripal_chado_api
  469. */
  470. function tripal_core_chado_update($table, $match, $values, $options = NULL) {
  471. $print_errors = (isset($options['print_errors'])) ? $options['print_errors'] : FALSE;
  472. if (!is_array($values)) {
  473. tripal_core_report_error(
  474. 'tripal_core',
  475. TRIPAL_ERROR,
  476. 'Cannot pass non array as values for updating.',
  477. array(),
  478. array('print' => $print_errors)
  479. );
  480. return FALSE;
  481. }
  482. if (count($values)==0) {
  483. tripal_core_report_error(
  484. 'tripal_core',
  485. TRIPAL_ERROR,
  486. 'Cannot pass an empty array as values for updating.',
  487. array(),
  488. array('print' => $print_errors)
  489. );
  490. return FALSE;
  491. }
  492. if (!is_array($match)) {
  493. tripal_core_report_error(
  494. 'tripal_core',
  495. TRIPAL_ERROR,
  496. 'Cannot pass non array as values for matching.',
  497. array(),
  498. array('print' => $print_errors)
  499. );
  500. return FALSE;
  501. }
  502. if (count($match)==0) {
  503. tripal_core_report_error(
  504. 'tripal_core',
  505. TRIPAL_ERROR,
  506. 'Cannot pass an empty array as values for matching.',
  507. array(),
  508. array('print' => $print_errors)
  509. );
  510. return FALSE;
  511. }
  512. // set defaults for options. If we don't set defaults then
  513. // we get memory leaks when we try to access the elements
  514. if (!is_array($options)) {
  515. $options = array();
  516. }
  517. if (!array_key_exists('return_record', $options)) {
  518. $options['return_record'] = FALSE;
  519. }
  520. $update_values = array(); // contains the values to be updated
  521. $update_matches = array(); // contains the values for the where clause
  522. // get the table description
  523. $table_desc = tripal_core_get_chado_table_schema($table);
  524. // if the user wants us to return the record then we need to get the
  525. // unique primary key if one exists. That way we can add it to the
  526. // values that get returned at the end of the function
  527. $pkeys = array();
  528. if ($options['return_record'] == TRUE) {
  529. if (array_key_exists('primary key', $table_desc) and is_array($table_desc['primary key'])) {
  530. $columns = array();
  531. $stmt_suffix = '';
  532. foreach ($table_desc['primary key'] as $field) {
  533. $columns[] = $field;
  534. $stmt_suffix .= substr($field, 0, 2);
  535. }
  536. $options2 = array();
  537. $results = tripal_core_chado_select($table, $columns, $match, $options2);
  538. if (count($results) > 0) {
  539. foreach ($results as $index => $pkey) {
  540. $pkeys[] = $pkey;
  541. }
  542. }
  543. }
  544. }
  545. // get the values needed for matching in the SQL statement
  546. foreach ($match as $field => $value) {
  547. if (is_array($value)) {
  548. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value);
  549. if (sizeof($results) > 1) {
  550. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  551. '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)',
  552. array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)),
  553. array('print' => $print_errors)
  554. );
  555. }
  556. elseif (sizeof($results) < 1) {
  557. tripal_core_report_error('tripal_core',TRIPAL_DEBUG,
  558. 'tripal_core_chado_update: When trying to find record to update, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)',
  559. array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)),
  560. array('print' => $print_errors)
  561. );
  562. }
  563. else {
  564. $update_matches[$field] = $results[0];
  565. }
  566. }
  567. else {
  568. $update_matches[$field] = $value;
  569. }
  570. }
  571. // get the values used for updating
  572. foreach ($values as $field => $value) {
  573. if (is_array($value)) {
  574. $foreign_options = array();
  575. // select the value from the foreign key relationship for this value
  576. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value, $foreign_options);
  577. if (sizeof($results) > 1) {
  578. tripal_core_report_error(
  579. 'tripal_core',
  580. TRIPAL_ERROR,
  581. 'tripal_core_chado_update: When trying to find update values, too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)',
  582. array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)),
  583. array('print' => $print_errors)
  584. );
  585. }
  586. elseif (sizeof($results) < 1) {
  587. tripal_core_report_error(
  588. 'tripal_core',
  589. TRIPAL_DEBUG,
  590. 'tripal_core_chado_update: When trying to find update values, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)',
  591. array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)),
  592. array('print' => $print_errors)
  593. );
  594. }
  595. else {
  596. $update_values[$field] = $results[0];
  597. }
  598. }
  599. else {
  600. $update_values[$field] = $value;
  601. }
  602. }
  603. // now build the SQL statement
  604. $sql = 'UPDATE {' . $table . '} SET ';
  605. $args = array(); // arguments passed to chado_query
  606. foreach ($update_values as $field => $value) {
  607. if (strcmp($value, '__NULL__') == 0) {
  608. $sql .= " $field = NULL, ";
  609. }
  610. else {
  611. $sql .= " $field = :$field, ";
  612. $args[":$field"] = $value;
  613. }
  614. }
  615. $sql = drupal_substr($sql, 0, -2); // get rid of the trailing comma & space
  616. $sql .= " WHERE ";
  617. foreach ($update_matches as $field => $value) {
  618. if (strcmp($value, '__NULL__')==0) {
  619. $sql .= " $field = NULL AND ";
  620. }
  621. else {
  622. $sql .= " $field = :$field AND ";
  623. $args[":$field"] = $value;
  624. }
  625. }
  626. $sql = drupal_substr($sql, 0, -4); // get rid of the trailing 'AND'
  627. $result = chado_query($sql, $args);
  628. // if we have a result then add primary keys to return array
  629. if ($options['return_record'] == TRUE and $result) {
  630. // only if we have a single result do we want to add the primary keys to the values
  631. // array. If the update matched many records we can't add the pkeys
  632. if (count($pkeys) == 1) {
  633. foreach ($pkeys as $index => $pkey) {
  634. foreach ($pkey as $field => $fvalue) {
  635. $values[$field] = $fvalue;
  636. }
  637. }
  638. }
  639. return $values;
  640. }
  641. elseif ($options['return_record'] == FALSE and $result) {
  642. return TRUE;
  643. }
  644. else {
  645. tripal_core_report_error(
  646. 'tripal_core',
  647. TRIPAL_ERROR,
  648. "tripal_core_chado_update: Cannot update record in %table table. \nMatch: %match \nValues: %values",
  649. array('%table' => table, '%match' => print_r($match,TRUE), '%values' => print_r($values, 1)),
  650. array('print' => $print_errors)
  651. );
  652. return FALSE;
  653. }
  654. return FALSE;
  655. }
  656. /**
  657. * Provides a generic function for deleting a record(s) from any chado table
  658. *
  659. * Use this function to delete a record(s) in any Chado table. The first
  660. * argument specifies the table to delete from and the second is an array
  661. * of values to match for locating the record(s) to be deleted. The arrays
  662. * are mutli-dimensional such that foreign key lookup values can be specified.
  663. *
  664. * @param $table
  665. * The name of the chado table for inserting
  666. * @param $match
  667. * An associative array containing the values for locating a record to update.
  668. * @param $options
  669. * Currently there are no options
  670. * @return
  671. * On success this function returns TRUE. On failure, it returns FALSE.
  672. *
  673. * Example usage:
  674. * @code
  675. $umatch = array(
  676. 'organism_id' => array(
  677. 'genus' => 'Citrus',
  678. 'species' => 'sinensis',
  679. ),
  680. 'uniquename' => 'orange1.1g000034m.g7',
  681. 'type_id' => array (
  682. 'cv_id' => array (
  683. 'name' => 'sequence',
  684. ),
  685. 'name' => 'gene',
  686. 'is_obsolete' => 0
  687. ),
  688. );
  689. $uvalues = array(
  690. 'name' => 'orange1.1g000034m.g',
  691. 'type_id' => array (
  692. 'cv_id' => array (
  693. 'name' => 'sequence',
  694. ),
  695. 'name' => 'mRNA',
  696. 'is_obsolete' => 0
  697. ),
  698. );
  699. * $result = tripal_core_chado_update('feature', $umatch, $uvalues);
  700. * @endcode
  701. * The above code species that a feature with a given uniquename, organism_id,
  702. * and type_id (the unique constraint for the feature table) will be deleted.
  703. * The organism_id is specified as a nested array that uses the organism_id
  704. * foreign key constraint to lookup the specified values to find the exact
  705. * organism_id. The same nested struture is also used for specifying the
  706. * values to update. The function will find all records that match the
  707. * columns specified and delete them.
  708. *
  709. * @ingroup tripal_chado_api
  710. */
  711. function tripal_core_chado_delete($table, $match, $options = NULL) {
  712. if (!is_array($match)) {
  713. watchdog('tripal_core', 'Cannot pass non array as values for matching.', array(),
  714. WATCHDOG_ERROR);
  715. return FALSE;
  716. }
  717. if (count($match)==0) {
  718. watchdog('tripal_core', 'Cannot pass an empty array as values for matching.', array(),
  719. WATCHDOG_ERROR);
  720. return FALSE;
  721. }
  722. // set defaults for options. If we don't set defaults then
  723. // we get memory leaks when we try to access the elements
  724. if (!is_array($options)) {
  725. $options = array();
  726. }
  727. $delete_matches = array(); // contains the values for the where clause
  728. // get the table description
  729. $table_desc = tripal_core_get_chado_table_schema($table);
  730. $fields = $table_desc['fields'];
  731. // get the values needed for matching in the SQL statement
  732. foreach ($match as $field => $value) {
  733. if (is_array($value)) {
  734. // if the user has specified an array of values to delete rather than
  735. // FK relationships the keep those in our match
  736. if (array_values($value) === $value) {
  737. $delete_matches[$field] = $value;
  738. }
  739. else {
  740. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value);
  741. if (sizeof($results) > 1) {
  742. 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);
  743. }
  744. elseif (sizeof($results) < 1) {
  745. //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);
  746. }
  747. else {
  748. $delete_matches[$field] = $results[0];
  749. }
  750. }
  751. }
  752. else {
  753. $delete_matches[$field] = $value;
  754. }
  755. }
  756. // now build the SQL statement
  757. $sql = 'DELETE FROM {' . $table . '} WHERE ';
  758. $args = array();
  759. foreach ($delete_matches as $field => $value) {
  760. // if we have an array values then this is an "IN" clasue.
  761. // we cannot use prepared statements with these
  762. if (count($value) > 1) {
  763. $sql .= "$field IN (";
  764. $index = 0;
  765. foreach ($value as $v) {
  766. $sql .= ":$field" . $index . ", ";
  767. $args[":$field" . $index] = $v;
  768. $index++;
  769. }
  770. $sql = drupal_substr($sql, 0, -2); // get rid of trailing ', '
  771. $sql .= ") AND ";
  772. }
  773. else {
  774. if (strcmp($value, '__NULL__') == 0) {
  775. $sql .= " $field = NULL AND ";
  776. }
  777. else {
  778. $sql .= " $field = :$field AND ";
  779. $args[":$field"] = $value;
  780. }
  781. }
  782. }
  783. $sql = drupal_substr($sql, 0, -4); // get rid of the trailing 'AND'
  784. // finally perform the delete. If successful, return the updated record
  785. $result = chado_query($sql, $args);
  786. if ($result) {
  787. return TRUE;
  788. }
  789. else {
  790. watchdog('tripal_core', "Cannot delete record in $table table. Match:" . print_r($match, 1) . ". Values: " . print_r($values, 1), array(), 'WATCHDOG_ERROR');
  791. return FALSE;
  792. }
  793. return FALSE;
  794. }
  795. /**
  796. * Provides a generic routine for selecting data from a Chado table
  797. *
  798. * Use this function to perform a simple select from any Chado table.
  799. *
  800. * @param $table
  801. * The name of the chado table for inserting
  802. * @param $columns
  803. * An array of column names
  804. * @param $values
  805. * An associative array containing the values for filtering the results. In the
  806. * case where multiple values for the same time are to be selected an additional
  807. * entry for the field should appear for each value
  808. * @param $options
  809. * An associative array of additional options where the key is the option
  810. * and the value is the value of that option.
  811. *
  812. * Additional Options Include:
  813. * - has_record
  814. * Set this argument to 'TRUE' to have this function return a numeric
  815. * value for the number of recrods rather than the array of records. this
  816. * can be useful in 'if' statements to check the presence of particula records.
  817. * - return_sql
  818. * Set this to 'TRUE' to have this function return an array where the first
  819. * element is the sql that would have been run and the second is an array of
  820. * arguments.
  821. * - case_insensitive_columns
  822. * An array of columns to do a case insensitive search on.
  823. * - regex_columns
  824. * An array of columns where the value passed in should be treated as a regular expression
  825. * - order_by
  826. * An associative array containing the column names of the table as keys
  827. * and the type of sort (i.e. ASC, DESC) as the values. The results in the
  828. * query will be sorted by the key values in the direction listed by the value
  829. * - is_duplicate: TRUE or FALSE. Checks the values submited to see if
  830. * they violate any of the unique constraints. If so, the record
  831. * is returned, if not, FALSE is returned.
  832. * - pager: Use this option if it is desired to return only a subset of results
  833. * so that they may be shown with in a Drupal-style pager. This should be
  834. * an array with two keys: 'limit' and 'element'. The value of 'limit'
  835. * should specify the number of records to return and 'element' is a
  836. * unique integer to differentiate between pagers when more than one
  837. * appear on a page. The 'element' should start with zero and increment by
  838. * one for each pager.
  839. *
  840. * @return
  841. * An array of results, FALSE if the query was not executed
  842. * correctly, an empty array if no records were matched, or the number of records
  843. * in the dataset if $has_record is set.
  844. * If the option 'is_duplicate' is provided and the record is a duplicate it
  845. * will return the duplicated record. If the 'has_record' option is provided
  846. * a value of TRUE will be returned if a record exists and FALSE will bee
  847. * returned if there are not records.
  848. *
  849. * Example usage:
  850. * @code
  851. * $columns = array('feature_id', 'name');
  852. * $values = array(
  853. * 'organism_id' => array(
  854. * 'genus' => 'Citrus',
  855. * 'species' => array('sinensis', 'clementina'),
  856. * ),
  857. * 'uniquename' => 'orange1.1g000034m.g',
  858. * 'type_id' => array (
  859. * 'cv_id' => array (
  860. * 'name' => 'sequence',
  861. * ),
  862. * 'name' => 'gene',
  863. * 'is_obsolete' => 0
  864. * ),
  865. * );
  866. * $options = array(
  867. * 'order_by' => array(
  868. * 'name' => 'ASC'
  869. * ),
  870. * );
  871. * $result = tripal_core_chado_select('feature',$columns,$values,$options);
  872. * @endcode
  873. * The above code selects a record from the feature table using the three fields
  874. * that uniquely identify a feature. The $columns array simply lists the columns
  875. * to select. The $values array is nested such that the organism is identified by
  876. * way of the organism_id foreign key constraint by specifying the genus and
  877. * species. The cvterm is also specified using its foreign key and the cv_id
  878. * for the cvterm is nested as well. In the example above, two different species
  879. * are allowed to match
  880. *
  881. * @ingroup tripal_chado_api
  882. */
  883. function tripal_core_chado_select($table, $columns, $values, $options = NULL) {
  884. $print_errors = (isset($options['print_errors'])) ? $options['print_errors'] : FALSE;
  885. if (!is_array($values)) {
  886. tripal_core_report_error('tripal_core', TRIPAL_ERROR, 'Cannot pass non array as values for selecting.',
  887. array(), array('print' => $print_errors)
  888. );
  889. return FALSE;
  890. }
  891. if (!is_array($columns)) {
  892. tripal_core_report_error('tripal_core', TRIPAL_ERROR, 'Cannot pass non array as columns for selecting.',
  893. array(), array('print' => $print_errors)
  894. );
  895. return FALSE;
  896. }
  897. if (count($columns)==0) {
  898. tripal_core_report_error('tripal_core', TRIPAL_ERROR, 'Cannot pass an empty array as columns for selecting.',
  899. array(), array('print' => $print_errors)
  900. );
  901. return FALSE;
  902. }
  903. // set defaults for options. If we don't set defaults then
  904. // we get memory leaks when we try to access the elements
  905. if (!is_array($options)) {
  906. $options = array();
  907. }
  908. if (!array_key_exists('case_insensitive_columns', $options)) {
  909. $options['case_insensitive_columns'] = array();
  910. }
  911. if (!array_key_exists('regex_columns', $options)) {
  912. $options['regex_columns'] = array();
  913. }
  914. if (!array_key_exists('order_by', $options)) {
  915. $options['order_by'] = array();
  916. }
  917. if (!array_key_exists('return_sql', $options)) {
  918. $options['return_sql'] = FALSE;
  919. }
  920. if (!array_key_exists('has_record', $options)) {
  921. $options['has_record'] = FALSE;
  922. }
  923. if (!array_key_exists('is_duplicate', $options)) {
  924. $options['is_duplicate'] = FALSE;
  925. }
  926. $pager = array();
  927. if (array_key_exists('pager', $options)) {
  928. $pager = $options['pager'];
  929. }
  930. // check that our columns and values arguments are proper arrays
  931. if (!is_array($columns)) {
  932. tripal_core_report_error(
  933. 'tripal_core',
  934. TRIPAL_ERROR,
  935. 'tripal_core_chado_select; the $columns argument must be an array. Columns:%columns',
  936. array('%columns' => print_r($columns, TRUE)),
  937. array('print' => $print_errors)
  938. );
  939. return FALSE;
  940. }
  941. if (!is_array($values)) {
  942. tripal_core_report_error(
  943. 'tripal_core',
  944. TRIPAL_ERROR,
  945. 'tripal_core_chado_select; the $values argument must be an array. Values:%values',
  946. array('%values' => print_r($values, TRUE)),
  947. array('print' => $print_errors)
  948. );
  949. return FALSE;
  950. }
  951. // get the table description
  952. $table_desc = tripal_core_get_chado_table_schema($table);
  953. $select = '';
  954. $from = '';
  955. $where = array();
  956. $args = array();
  957. // if the 'use_unique' option is turned on then we want
  958. // to remove all but unique keys
  959. if ($options['is_duplicate'] and array_key_exists('unique keys', $table_desc)) {
  960. $ukeys = $table_desc['unique keys'];
  961. $has_results = 0;
  962. // iterate through the unique constraints and reset the values and columns
  963. // arrays to only include these fields
  964. foreach ($ukeys as $cname => $fields) {
  965. if ($has_results) {
  966. continue;
  967. }
  968. $new_values = array();
  969. $new_columns = array();
  970. $new_options = array();
  971. $uq_sname = "uq_" . $table . "_";
  972. $has_pkey = 0;
  973. // include the primary key in the results returned
  974. if (array_key_exists('primary key', $table_desc)) {
  975. $has_pkey = 1;
  976. $pkeys = $table_desc['primary key'];
  977. foreach ($pkeys as $index => $key) {
  978. array_push($new_columns, $key);
  979. }
  980. }
  981. // recreate the $values and $columns arrays
  982. foreach ($fields as $field) {
  983. if (array_key_exists($field, $values)) {
  984. $new_values[$field] = $values[$field];
  985. $uq_sname .= substr($field, 0, 2);
  986. // if there is no primary key then use the unique contraint fields
  987. if (!$has_pkey) {
  988. array_push($new_columns, $field);
  989. }
  990. }
  991. // if the field doesn't exist in the values array then
  992. // substitute any default values
  993. elseif (array_key_exists('default', $table_desc['fields'][$field])) {
  994. $new_values[$field] = $table_desc['fields'][$field]['default'];
  995. $uq_sname .= substr($field, 0, 2);
  996. if (!$has_pkey) {
  997. array_push($new_columns, $field);
  998. }
  999. }
  1000. // if there is no value (default or otherwise) check if this field is
  1001. // allowed to be null
  1002. elseif (!$table_desc['fields'][$field]['not null']) {
  1003. $new_values[$field] = NULL;
  1004. $uq_sname .= "n" . substr($field, 0, 2);
  1005. if (!$has_pkey) {
  1006. array_push($new_columns, $field);
  1007. }
  1008. }
  1009. // if the array key doesn't exist in the values given by the caller
  1010. // and there is no default value then we cannot check if the record
  1011. // is a duplicate so return FALSE
  1012. else {
  1013. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  1014. 'tripal_core_chado_select: There is no value for %field thus we cannot check if this record is unique',
  1015. array('%field' => $field), array('print' => $print_errors));
  1016. return FALSE;
  1017. }
  1018. }
  1019. $results = tripal_core_chado_select($table, $new_columns, $new_values, $new_options);
  1020. // if we have a duplicate record then return the results
  1021. if (count($results) > 0) {
  1022. $has_results = 1;
  1023. }
  1024. unset($new_columns);
  1025. unset($new_values);
  1026. unset($new_options);
  1027. }
  1028. if ($options['has_record'] and $has_results) {
  1029. return TRUE;
  1030. }
  1031. else {
  1032. return $results;
  1033. }
  1034. }
  1035. foreach ($values as $field => $value) {
  1036. // make sure the field is in the table description. If not then return an error
  1037. // message
  1038. if (!array_key_exists($field, $table_desc['fields'])) {
  1039. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  1040. 'tripal_core_chado_select: The field "%field" does not exist for the table "%table". Cannot perform query. Values: %array',
  1041. array('%field' => $field, '%table' => $table, '%array' => print_r($values, 1)),
  1042. array('print' => $print_errors)
  1043. );
  1044. return array();
  1045. }
  1046. $select[] = $field;
  1047. if (is_array($value)) {
  1048. // if the user has specified multiple values for matching then this we
  1049. // want to catch that and save them in our $where array, otherwise
  1050. // we'll descend for a foreign key relationship
  1051. if (array_values($value) === $value) {
  1052. $where[$field] = $value;
  1053. }
  1054. else {
  1055. // select the value from the foreign key relationship for this value
  1056. $foreign_options = array(
  1057. 'regex_columns' => $options['regex_columns'],
  1058. );
  1059. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value, $foreign_options);
  1060. if (!$results or count($results)==0) {
  1061. return array();
  1062. }
  1063. else {
  1064. $where[$field] = $results;
  1065. }
  1066. }
  1067. }
  1068. else {
  1069. // need to catch a 0 and make int if integer field
  1070. // but we don't want to catch a NULL
  1071. if ($value === NULL) {
  1072. $where[$field] = NULL;
  1073. }
  1074. elseif ($table_desc['fields'][$field]['type'] == 'int') {
  1075. $where[$field][] = (int) $value;
  1076. }
  1077. else {
  1078. $where[$field][] = $value;
  1079. }
  1080. }
  1081. }
  1082. // now build the SQL and prepared SQL statements. We may not use
  1083. // the prepared statement if it wasn't requested in the options or if the
  1084. // argument in a where statement has multiple values.
  1085. if (empty($where)) {
  1086. // sometimes want to select everything
  1087. $sql = "SELECT " . implode(', ', $columns) . " ";
  1088. $sql .= 'FROM {' . $table . '} ';
  1089. // we don't prepare a statement if there is no where clause
  1090. $prepared = FALSE;
  1091. }
  1092. else {
  1093. $sql = "SELECT " . implode(', ', $columns) . " ";
  1094. $sql .= 'FROM {' . $table . '} ';
  1095. // if $values is empty then we want all results so no where clause
  1096. if (!empty($values)) {
  1097. $sql .= "WHERE ";
  1098. }
  1099. foreach ($where as $field => $value) {
  1100. // if we have multiple values returned then we need an 'IN' statement
  1101. // in our where statement
  1102. if (count($value) > 1) {
  1103. $sql .= "$field IN (";
  1104. $index = 0;
  1105. foreach ($value as $v) {
  1106. $sql .= ":$field" . $index . ', ';
  1107. $args[":$field" . $index] = $v;
  1108. $index++;
  1109. }
  1110. $sql = drupal_substr($sql, 0, -2); // remove trailing ', '
  1111. $sql .= ") AND ";
  1112. }
  1113. // if we have a null value then we need an IS NULL in our where statement
  1114. elseif ($value === NULL) {
  1115. $sql .= "$field IS NULL AND ";
  1116. // Need to remove one from the argument count b/c nulls don't add an argument
  1117. }
  1118. // if we have a single value then we need an = in our where statement
  1119. else {
  1120. $operator = '=';
  1121. if (in_array($field, $options['regex_columns'])) {
  1122. $operator = '~*';
  1123. }
  1124. if (in_array($field, $options['case_insensitive_columns'])) {
  1125. $sql .= "lower($field) $operator lower(:$field) AND ";
  1126. $args[":$field"] = $value[0];
  1127. }
  1128. else {
  1129. $sql .= "$field $operator :$field AND ";
  1130. $args[":$field"] = $value[0];
  1131. }
  1132. }
  1133. } // end foreach item in where clause
  1134. $sql = drupal_substr($sql, 0, -4); // get rid of the trailing 'AND '
  1135. } // end if (empty($where)){ } else {
  1136. // finally add any ordering of the results to the SQL statement
  1137. if (count($options['order_by']) > 0) {
  1138. $sql .= " ORDER BY ";
  1139. foreach ($options['order_by'] as $field => $dir) {
  1140. $sql .= "$field $dir, ";
  1141. }
  1142. $sql = drupal_substr($sql, 0, -2); // get rid of the trailing ', '
  1143. }
  1144. // if the caller has requested the SQL rather than the results then do so
  1145. if ($options['return_sql'] == TRUE) {
  1146. return array('sql' => $sql, 'args' => $args);
  1147. }
  1148. if (array_key_exists('limit', $pager)) {
  1149. $total_records = 0;
  1150. $resource = chado_pager_query($sql, $args, $pager['limit'], $pager['element'], NULL, $total_records);
  1151. }
  1152. else {
  1153. $resource = chado_query($sql, $args);
  1154. }
  1155. // format results into an array
  1156. $results = array();
  1157. foreach ($resource as $r) {
  1158. $results[] = $r;
  1159. }
  1160. if ($options['has_record']) {
  1161. return count($results);
  1162. }
  1163. return $results;
  1164. }
  1165. /**
  1166. * Gets the value of a foreign key relationship
  1167. *
  1168. * This function is used by tripal_core_chado_select, tripal_core_chado_insert,
  1169. * and tripal_core_chado_update to iterate through the associate array of
  1170. * values that gets passed to each of those routines. The values array
  1171. * is nested where foreign key contraints are used to specify a value that. See
  1172. * documentation for any of those functions for further information.
  1173. *
  1174. * @param $table_desc
  1175. * A table description for the table with the foreign key relationship to be identified generated by
  1176. * hook_chado_<table name>_schema()
  1177. * @param $field
  1178. * The field in the table that is the foreign key.
  1179. * @param $values
  1180. * An associative array containing the values
  1181. * @param $options
  1182. * An associative array of additional options where the key is the option
  1183. * and the value is the value of that option. These options are passed on to tripal_core_chado_select.
  1184. *
  1185. * Additional Options Include:
  1186. * - case_insensitive_columns
  1187. * An array of columns to do a case insensitive search on.
  1188. * - regex_columns
  1189. * An array of columns where the value passed in should be treated as a regular expression
  1190. *
  1191. * @return
  1192. * A string containg the results of the foreign key lookup, or FALSE if failed.
  1193. *
  1194. * Example usage:
  1195. * @code
  1196. *
  1197. * $values = array(
  1198. * 'genus' => 'Citrus',
  1199. * 'species' => 'sinensis',
  1200. * );
  1201. * $value = tripal_core_chado_get_foreign_key('feature', 'organism_id',$values);
  1202. *
  1203. * @endcode
  1204. * The above code selects a record from the feature table using the three fields
  1205. * that uniquely identify a feature. The $columns array simply lists the columns
  1206. * to select. The $values array is nested such that the organism is identified by
  1207. * way of the organism_id foreign key constraint by specifying the genus and
  1208. * species. The cvterm is also specified using its foreign key and the cv_id
  1209. * for the cvterm is nested as well.
  1210. *
  1211. * @ingroup tripal_chado_api
  1212. */
  1213. function tripal_core_chado_get_foreign_key($table_desc, $field, $values, $options = NULL) {
  1214. // set defaults for options. If we don't set defaults then
  1215. // we get memory leaks when we try to access the elements
  1216. if (!is_array($options)) {
  1217. $options = array();
  1218. }
  1219. if (!array_key_exists('case_insensitive_columns', $options)) {
  1220. $options['case_insensitive_columns'] = array();
  1221. }
  1222. if (!array_key_exists('regex_columns', $options)) {
  1223. $options['regex_columns'] = array();
  1224. }
  1225. // get the list of foreign keys for this table description and
  1226. // iterate through those until we find the one we're looking for
  1227. $fkeys = '';
  1228. if (array_key_exists('foreign keys', $table_desc)) {
  1229. $fkeys = $table_desc['foreign keys'];
  1230. }
  1231. if ($fkeys) {
  1232. foreach ($fkeys as $name => $def) {
  1233. if (is_array($def['table'])) {
  1234. //foreign key was described 2X
  1235. $message = "The foreign key " . $name . " was defined twice. Please check modules "
  1236. . "to determine if hook_chado_schema_<version>_" . $table_desc['table'] . "() was "
  1237. . "implemented and defined this foreign key when it wasn't supposed to. Modules "
  1238. . "this hook was implemented in: " . implode(', ',
  1239. module_implements("chado_" . $table_desc['table'] . "_schema")) . ".";
  1240. watchdog('tripal_core', $message);
  1241. drupal_set_message(check_plain($message), 'error');
  1242. continue;
  1243. }
  1244. $table = $def['table'];
  1245. $columns = $def['columns'];
  1246. // iterate through the columns of the foreign key relationship
  1247. foreach ($columns as $left => $right) {
  1248. // does the left column in the relationship match our field?
  1249. if (strcmp($field, $left) == 0) {
  1250. // the column name of the foreign key matches the field we want
  1251. // so this is the right relationship. Now we want to select
  1252. $select_cols = array($right);
  1253. $result = tripal_core_chado_select($table, $select_cols, $values, $options);
  1254. $fields = array();
  1255. if ($result and count($result) > 0) {
  1256. foreach ($result as $obj) {
  1257. $fields[] = $obj->$right;
  1258. }
  1259. return $fields;
  1260. }
  1261. }
  1262. }
  1263. }
  1264. }
  1265. else {
  1266. // TODO: what do we do if we get to this point and we have a fk
  1267. // relationship expected but we don't have any definition for one in the
  1268. // table schema??
  1269. $version = $GLOBALS["chado_version"];
  1270. $message = t("There is no foreign key relationship defined for " . $field . " .
  1271. To define a foreign key relationship, determine the table this foreign
  1272. key referrs to (<foreign table>) and then implement
  1273. hook_chado_chado_schema_v<version>_<foreign table>(). See
  1274. tripal_feature_chado_v1_2_schema_feature for an example. Chado version: $version");
  1275. watchdog('tripal_core', $message);
  1276. drupal_set_message(check_plain($message), 'error');
  1277. }
  1278. return array();
  1279. }
  1280. /**
  1281. * Generates an object containing the full details of a record(s) in chado.
  1282. *
  1283. * This differs from the objects returned by tripal_core_chado_select in so far as all foreign key
  1284. * relationships have been followed meaning you have more complete details. Thus this function
  1285. * should be used whenever you need a full variable and tripal_core_chado_select should be used if
  1286. * you only case about a few columns.
  1287. *
  1288. * @param $table
  1289. * The name of the base table to generate a variable for
  1290. * @param $values
  1291. * A select values array that selects the records you want from the base table
  1292. * (this has the same form as tripal_core_chado_select)
  1293. * @param $base_options
  1294. * An array containing options for the base table. For example, an
  1295. * option of 'order_by' may be used to sort results in the base table
  1296. * if more than one are returned. The options must be compatible with
  1297. * the options accepted by the tripal_core_chado_select() function.
  1298. * Additionally, These options are available for this function:
  1299. * -return_array:
  1300. * can be provided to force the function to always return an array. Default
  1301. * behavior is to return a single record if only one record exists or to return
  1302. * an array if multiple records exist.
  1303. * - include_fk:
  1304. * an array of FK relationships to follow. By default, the
  1305. * tripal_core_chado_select function will follow all FK relationships but this
  1306. * may generate more queries then is desired slowing down this function call when
  1307. * there are lots of FK relationships to follow. Provide an array specifying the
  1308. * fields to include. For example, if expanding a property table (e.g. featureprop)
  1309. * and you want the CV and accession but do not want the DB the following
  1310. * array would work:
  1311. *
  1312. * $table_options = array(
  1313. * 'include_fk' => array(
  1314. * 'type_id' => array(
  1315. * 'cv_id' => 1,
  1316. * 'dbxref_id' => 1,
  1317. * )
  1318. * )
  1319. * );
  1320. *
  1321. * The above array will expand the 'type_id' of the property table but only
  1322. * further expand the cv_id and the dbxref_id and will go no further.
  1323. * - pager:
  1324. * Use this option if it is desired to return only a subset of results
  1325. * so that they may be shown within a Drupal-style pager. This should be
  1326. * an array with two keys: 'limit' and 'element'. The value of 'limit'
  1327. * should specify the number of records to return and 'element' is a
  1328. * unique integer to differentiate between pagers when more than one
  1329. * appear on a page. The 'element' should start with zero and increment by
  1330. * one for each pager. This only works when type is a 'table'.
  1331. * @return
  1332. * Either an object (if only one record was selected from the base table)
  1333. * or an array of objects (if more than one record was selected from the base table).
  1334. * If the option 'return_array' is provided the function always returns an array.
  1335. *
  1336. * Example Usage:
  1337. * @code
  1338. $values = array(
  1339. 'name' => 'Medtr4g030710'
  1340. );
  1341. $features = tripal_core_generate_chado_var('feature', $values);
  1342. * @endcode
  1343. * This will return an object if there is only one feature with the name Medtr4g030710 or it will
  1344. * return an array of feature objects if more than one feature has that name.
  1345. *
  1346. * Note to Module Designers: Fields can be excluded by default from these objects by implementing
  1347. * one of the following hooks:
  1348. * - hook_exclude_field_from_tablename_by_default (where tablename is the name of the table):
  1349. * This hook allows you to add fields to be excluded on a per table basis. Simply implement
  1350. * this hook to return an array of fields to be excluded. For example:
  1351. * @code
  1352. mymodule_exclude_field_from_feature_by_default() {
  1353. return array('residues' => TRUE);
  1354. }
  1355. * @endcode
  1356. * will ensure that feature.residues is ecluded from a feature object by default.
  1357. * - hook_exclude_type_by_default:
  1358. * This hook allows you to exclude fields from all tables that are of a given postgresql field
  1359. * type. Simply implement this hook to return an array of postgresql types mapped to criteria.
  1360. * Then all fields of that type where the criteria supplied returns TRUE will be excluded from
  1361. * any table. Tokens available in criteria are &gt;field_value&lt; and &gt;field_name&lt; . For example:
  1362. * @code
  1363. mymodule_exclude_type_by_default() {
  1364. return array('text' => 'length(&gt;field_value&lt; ) > 50');
  1365. }
  1366. * @endcode
  1367. * 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.
  1368. *
  1369. * @ingroup tripal_chado_api
  1370. */
  1371. function tripal_core_generate_chado_var($table, $values, $base_options = array()) {
  1372. $all = new stdClass();
  1373. $return_array = 0;
  1374. if (array_key_exists('return_array', $base_options)) {
  1375. $return_array = 1;
  1376. }
  1377. $include_fk = 0;
  1378. if (array_key_exists('include_fk', $base_options)) {
  1379. $include_fk = $base_options['include_fk'];
  1380. }
  1381. $pager = array();
  1382. if (array_key_exists('pager', $base_options)) {
  1383. $pager = $base_options['pager'];
  1384. }
  1385. // get description for the current table----------------------------------------------------------
  1386. $table_desc = tripal_core_get_chado_table_schema($table);
  1387. if (!$table_desc or count($table_desc) == 0) {
  1388. watchdog('tripal_core', "tripal_core_generate_chado_var: The table '%table' has not been defined. " .
  1389. "and cannot be expanded. If this is a custom table, please add it using the Tripal " .
  1390. "custom table interface.", array('%table' => $table), WATCHDOG_ERROR);
  1391. if ($return_array) {
  1392. return array();
  1393. }
  1394. return FALSE;
  1395. }
  1396. $table_primary_key = $table_desc['primary key'][0];
  1397. $table_columns = array_keys($table_desc['fields']);
  1398. // Expandable fields without value needed for criteria--------------------------------------------
  1399. $all->expandable_fields = array();
  1400. if (array_key_exists('referring_tables', $table_desc) and $table_desc['referring_tables']) {
  1401. $all->expandable_tables = $table_desc['referring_tables'];
  1402. }
  1403. else {
  1404. $all->expandable_tables = array();
  1405. }
  1406. $all->expandable_nodes = array();
  1407. /*
  1408. // Get fields to be removed by name.................................
  1409. $fields_to_remove = module_invoke_all('exclude_field_from_' . $table . '_by_default');
  1410. foreach ($fields_to_remove as $field_name => $criteria) {
  1411. //replace &gt;field_name&lt; with the current field name &
  1412. $criteria = preg_replace('/&gt;field_name&lt; /', addslashes($field_name), $criteria);
  1413. // if field_value needed we can't deal with this field yet
  1414. if (preg_match('/&gt;field_value&lt; /', $criteria)) {
  1415. break;
  1416. }
  1417. //if criteria then remove from query
  1418. // @coder-ignore: only module designers can populate $criteria -not security risk
  1419. $success = php_eval('<?php return ' . $criteria . '; ?>');
  1420. if ($success) {
  1421. unset($table_columns[array_search($field_name, $table_columns)]);
  1422. unset($fields_to_remove[$field_name]);
  1423. $all->expandable_fields[] = $table . '.' . $field_name;
  1424. }
  1425. }
  1426. //Get fields to be removed by type................................
  1427. $types_to_remove = module_invoke_all('exclude_type_by_default');
  1428. $field_types = array();
  1429. foreach ($table_desc['fields'] as $field_name => $field_array) {
  1430. $field_types[$field_array['type']][] = $field_name;
  1431. }
  1432. foreach ($types_to_remove as $field_type => $criteria) {
  1433. // if there are fields of that type to remove
  1434. if (is_array($field_types[$field_type])) {
  1435. //replace &gt;field_name&lt; with the current field name &
  1436. $criteria = preg_replace('/&gt;field_name&lt; /', addslashes($field_name), $criteria);
  1437. foreach ($field_types[$field_type] as $field_name) {
  1438. // if field_value needed we can't deal with this field yet
  1439. if (preg_match('/&gt;field_value&lt; /', $criteria)) {
  1440. $fields_to_remove[$field_name] = $criteria;
  1441. continue;
  1442. }
  1443. // if field_value needed we can't deal with this field yet
  1444. if (preg_match('/&gt;field_value&lt; /', $criteria)) {
  1445. break;
  1446. }
  1447. //if criteria then remove from query
  1448. // @coder-ignore: only module designers can populate $criteria -not security risk
  1449. $success = php_eval('<?php return ' . $criteria . '; ?>');
  1450. if ($success) {
  1451. unset($table_columns[array_search($field_name, $table_columns)]);
  1452. $all->expandable_fields[] = $table . '.' . $field_name;
  1453. }
  1454. } //end of foreach field of that type
  1455. }
  1456. } //end of foreach type to be removed
  1457. */
  1458. // get the values for the record in the current table---------------------------------------------
  1459. $results = tripal_core_chado_select($table, $table_columns, $values, $base_options);
  1460. if ($results) {
  1461. foreach ($results as $key => $object) {
  1462. // Add empty expandable_x arrays
  1463. $object->expandable_fields = $all->expandable_fields;
  1464. $object->expandable_tables = $all->expandable_tables;
  1465. $object->expandable_nodes = $all->expandable_nodes;
  1466. // add curent table
  1467. $object->tablename = $table;
  1468. // check if the current table maps to a node type-----------------------------------------------
  1469. // if this table is connected to a node there will be a chado_tablename table in drupal
  1470. if (db_table_exists('chado_' . $table)) {
  1471. // that has a foreign key to this one ($table_desc['primary key'][0]
  1472. // and to the node table (nid)
  1473. $sql = "
  1474. SELECT $table_primary_key, nid
  1475. FROM {chado_$table}
  1476. WHERE $table_primary_key = :$table_primary_key
  1477. ";
  1478. $mapping = db_query($sql, array(":$table_primary_key" => $object->{$table_primary_key}))->fetchObject();
  1479. if ($mapping and $mapping->$table_primary_key) {
  1480. $object->nid = $mapping->nid;
  1481. $object->expandable_nodes[] = $table;
  1482. }
  1483. }
  1484. // remove any fields where criteria needs to be evalulated---------------------------------------
  1485. /* foreach ($fields_to_remove as $field_name => $criteria) {
  1486. if (!isset($object->{$field_name})) {
  1487. break;
  1488. }
  1489. $criteria = preg_replace('/&gt;field_value&lt; /', addslashes($object->{$field_name}), $criteria);
  1490. $success = php_eval('<?php return ' . $criteria . '; ?>');
  1491. if ($success) {
  1492. unset($object->{$field_name});
  1493. $object->expandable_fields[] = $table . '.' . $field_name;
  1494. }
  1495. }
  1496. */
  1497. // recursively follow foreign key relationships nesting objects as we go------------------------
  1498. if ($table_desc['foreign keys']) {
  1499. foreach ($table_desc['foreign keys'] as $foreign_key_array) {
  1500. $foreign_table = $foreign_key_array['table'];
  1501. foreach ($foreign_key_array['columns'] as $foreign_key => $primary_key) {
  1502. // Note: Foreign key is the field in the current table whereas primary_key is the field in
  1503. // the table referenced by the foreign key
  1504. //Dont do anything if the foreign key is empty
  1505. if (empty($object->{$foreign_key})) {
  1506. continue;
  1507. }
  1508. if ($include_fk) {
  1509. // don't recurse if the callee has supplied an $fk_include list and this
  1510. // FK table is not in the list.
  1511. if (is_array($include_fk) and !array_key_exists($foreign_key, $include_fk)) {
  1512. continue;
  1513. }
  1514. // if we have the option but it is not an array then we don't recurse any furutehr
  1515. if (!is_array($include_fk)) {
  1516. continue;
  1517. }
  1518. }
  1519. // get the record from the foreign table
  1520. $foreign_values = array($primary_key => $object->{$foreign_key});
  1521. $options = array();
  1522. if (is_array($include_fk)) {
  1523. $options['include_fk'] = $include_fk[$foreign_key];
  1524. }
  1525. $foreign_object = tripal_core_generate_chado_var($foreign_table, $foreign_values, $options);
  1526. // add the foreign record to the current object in a nested manner
  1527. $object->{$foreign_key} = $foreign_object;
  1528. // Flatten expandable_x arrays so only in the bottom object
  1529. if (property_exists($object->{$foreign_key}, 'expandable_fields') and
  1530. is_array($object->{$foreign_key}->expandable_fields)) {
  1531. $object->expandable_fields = array_merge(
  1532. $object->expandable_fields,
  1533. $object->{$foreign_key}->expandable_fields
  1534. );
  1535. unset($object->{$foreign_key}->expandable_fields);
  1536. }
  1537. if (property_exists($object->{$foreign_key}, 'expandable_tables') and
  1538. is_array($object->{$foreign_key}->expandable_tables)) {
  1539. $object->expandable_tables = array_merge(
  1540. $object->expandable_tables,
  1541. $object->{$foreign_key}->expandable_tables
  1542. );
  1543. unset($object->{$foreign_key}->expandable_tables);
  1544. }
  1545. if (property_exists($object->{$foreign_key}, 'expandable_nodes') and
  1546. is_array($object->{$foreign_key}->expandable_nodes)) {
  1547. $object->expandable_nodes = array_merge(
  1548. $object->expandable_nodes,
  1549. $object->{$foreign_key}->expandable_nodes
  1550. );
  1551. unset($object->{$foreign_key}->expandable_nodes);
  1552. }
  1553. }
  1554. }
  1555. $results[$key] = $object;
  1556. }
  1557. }
  1558. }
  1559. // convert the results into an array
  1560. $results_arr = array();
  1561. foreach ($results as $record) {
  1562. $results_arr[] = $record;
  1563. }
  1564. // check only one result returned
  1565. if (!$return_array) {
  1566. if (sizeof($results_arr) == 1) {
  1567. // add results to object
  1568. return $results_arr[0];
  1569. }
  1570. elseif (!empty($results_arr)) {
  1571. return $results_arr;
  1572. }
  1573. else {
  1574. // no results returned
  1575. }
  1576. }
  1577. // the caller has requested results are always returned as
  1578. // an array
  1579. else {
  1580. if (!$results_arr) {
  1581. return array();
  1582. }
  1583. else {
  1584. return $results_arr;
  1585. }
  1586. }
  1587. }
  1588. /**
  1589. * Retrieves fields/tables/nodes that were excluded by default from a variable and adds them
  1590. *
  1591. * This function exists to allow tripal_core_generate_chado_var() to excldue some
  1592. * fields/tables/nodes from the default form of a variable without making it extremely difficult for
  1593. * the tripal admin to get at these variables if he/she wants them.
  1594. *
  1595. * @param $object
  1596. * This must be an object generated using tripal_core_generate_chado_var()
  1597. * @param $type
  1598. * Must be one of 'field', 'table', 'node'. Indicates what is being expanded.
  1599. * @param $to_expand
  1600. * The name of the field/table/node to be expanded
  1601. * @param $table_options
  1602. * - order_by:
  1603. * An array containing options for the base table. For example, an
  1604. * option of 'order_by' may be used to sort results in the base table
  1605. * if more than one are returned. The options must be compatible with
  1606. * the options accepted by the tripal_core_chado_select() function.
  1607. * - return_array:
  1608. * Additionally, The option 'return_array' can be provided to force
  1609. * the function to expand tables as an array. Default behavior is to expand
  1610. * a table as single record if only one record exists or to expand as an array if
  1611. * multiple records exist.
  1612. * - include_fk:
  1613. * an array of FK relationships to follow. By default, the
  1614. * tripal_core_expand_chado_vars function will follow all FK relationships but this
  1615. * may generate more queries then is desired slowing down this function call when
  1616. * there are lots of FK relationships to follow. Provide an array specifying the
  1617. * fields to include. For example, if expanding a property table (e.g. featureprop)
  1618. * and you want the CV and accession but do not want the DB the following
  1619. * array would work:
  1620. * $table_options = array(
  1621. * 'include_fk' => array(
  1622. * 'type_id' => array(
  1623. * 'cv_id' => 1,
  1624. * 'dbxref_id' => 1,
  1625. * )
  1626. * )
  1627. * );
  1628. *
  1629. * The above array will expand the 'type_id' of the property table but only
  1630. * further expand the cv_id and the dbxref_id and will go no further.
  1631. * - pager:
  1632. * Use this option if it is desired to return only a subset of results
  1633. * so that they may be shown within a Drupal-style pager. This should be
  1634. * an array with two keys: 'limit' and 'element'. The value of 'limit'
  1635. * should specify the number of records to return and 'element' is a
  1636. * unique integer to differentiate between pagers when more than one
  1637. * appear on a page. The 'element' should start with zero and increment by
  1638. * one for each pager. This only works when type is a 'table'.
  1639. * @return
  1640. * A chado object supplemented with the field/table/node requested to be expanded.
  1641. * If the type is a table and it has already been expanded no changes is made to the
  1642. * returned object
  1643. *
  1644. * Example Usage:
  1645. * @code
  1646. // Get a chado object to be expanded
  1647. $values = array(
  1648. 'name' => 'Medtr4g030710'
  1649. );
  1650. $features = tripal_core_generate_chado_var('feature', $values);
  1651. // Expand the organism node
  1652. $feature = tripal_core_expand_chado_vars($feature, 'node', 'organism');
  1653. // Expand the feature.residues field
  1654. $feature = tripal_core_expand_chado_vars($feature, 'field', 'feature.residues');
  1655. // Expand the feature properties (featureprop table)
  1656. $feature = tripal_core_expand_chado_vars($feature, 'table', 'featureprop');
  1657. * @endcode
  1658. *
  1659. * @ingroup tripal_chado_api
  1660. */
  1661. function tripal_core_expand_chado_vars($object, $type, $to_expand, $table_options = array()) {
  1662. // make sure we have a value
  1663. if (!$object) {
  1664. watchdog('tripal_core', 'Cannot pass non array as argument, $object, to tripal_core_expand_chado_vars function.', array(), WATCHDOG_ERROR);
  1665. return $object;
  1666. }
  1667. // check to see if we are expanding an array of objects
  1668. if (is_array($object)) {
  1669. foreach ($object as $index => $o) {
  1670. $object[$index] = tripal_core_expand_chado_vars($o, $type, $to_expand);
  1671. }
  1672. return $object;
  1673. }
  1674. // get the base table name
  1675. $base_table = $object->tablename;
  1676. switch ($type) {
  1677. case "field": //--------------------------------------------------------------------------------
  1678. if (preg_match('/(\w+)\.(\w+)/', $to_expand, $matches)) {
  1679. $tablename = $matches[1];
  1680. $fieldname = $matches[2];
  1681. $table_desc = tripal_core_get_chado_table_schema($tablename);
  1682. $values = array();
  1683. foreach ($table_desc['primary key'] as $key) {
  1684. $values[$key] = $object->{$key};
  1685. }
  1686. if ($base_table == $tablename) {
  1687. //get the field
  1688. $results = tripal_core_chado_select($tablename, array($fieldname), $values);
  1689. $object->{$fieldname} = $results[0]->{$fieldname};
  1690. $object->expanded = $to_expand;
  1691. }
  1692. else {
  1693. //We need to recurse -the field is in a nested object
  1694. foreach ((array) $object as $field_name => $field_value) {
  1695. if (is_object($field_value)) {
  1696. $object->{$field_name} = tripal_core_expand_chado_vars(
  1697. $field_value,
  1698. 'field',
  1699. $to_expand
  1700. );
  1701. }
  1702. } //end of for each field in the current object
  1703. }
  1704. }
  1705. else {
  1706. watchdog('tripal_core', 'tripal_core_expand_chado_vars: Field (%field) not in the right format. " .
  1707. "It should be <tablename>.<fieldname>', WATCHDOG_ERROR);
  1708. }
  1709. break;
  1710. case "table": //--------------------------------------------------------------------------------
  1711. $foreign_table = $to_expand;
  1712. // don't expand the table it already is expanded
  1713. if (array_key_exists($foreign_table, $object)) {
  1714. return $object;
  1715. }
  1716. $foreign_table_desc = tripal_core_get_chado_table_schema($foreign_table);
  1717. // If it's connected to the base table via a FK constraint
  1718. if (array_key_exists($base_table, $foreign_table_desc['foreign keys'])) {
  1719. foreach ($foreign_table_desc['foreign keys'][$base_table]['columns'] as $left => $right) {
  1720. // if the FK value in the base table is not there then we can't expand it, so just skip it.
  1721. if (!$object->{$right}) {
  1722. continue;
  1723. }
  1724. // generate a new object for this table using the FK values in the base table.
  1725. $new_options = $table_options;
  1726. $foreign_object = tripal_core_generate_chado_var($foreign_table, array($left => $object->{$right}), $new_options);
  1727. // if the generation of the object was successful, update the base object to include it.
  1728. if ($foreign_object) {
  1729. // in the case where the foreign key relationship exists more
  1730. // than once with the same table we want to alter the array structure to
  1731. // include the field name.
  1732. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) {
  1733. if (!property_exists($object, $foreign_table)) {
  1734. $object->{$foreign_table} = new stdClass();
  1735. }
  1736. $object->{$foreign_table}->{$left} = $foreign_object;
  1737. $object->expanded = $to_expand;
  1738. }
  1739. else {
  1740. if (!property_exists($object, $foreign_table)) {
  1741. $object->{$foreign_table} = new stdClass();
  1742. }
  1743. $object->{$foreign_table} = $foreign_object;
  1744. $object->expanded = $to_expand;
  1745. }
  1746. }
  1747. // if the object returned is NULL then handle that
  1748. else {
  1749. // in the case where the foreign key relationship exists more
  1750. // than once with the same table we want to alter the array structure to
  1751. // include the field name.
  1752. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) {
  1753. if (!property_exists($object, $foreign_table)) {
  1754. $object->{$foreign_table} = new stdClass();
  1755. }
  1756. $object->{$foreign_table}->{$left} = NULL;
  1757. }
  1758. else {
  1759. $object->{$foreign_table} = NULL;
  1760. }
  1761. }
  1762. }
  1763. }
  1764. // if the foreign table is not connected to the base table through a FK constraint
  1765. else {
  1766. // We need to recurse -the table has a relationship to one of the nested objects
  1767. $did_expansion = 0;
  1768. foreach ((array) $object as $field_name => $field_value) {
  1769. // if we have a nested object ->expand the table in it
  1770. // check to see if the $field_name is a valid chado table, we don't need
  1771. // to call tripal_core_expand_chado_vars on fields that aren't tables
  1772. $check = tripal_core_get_chado_table_schema($field_name);
  1773. if ($check) {
  1774. $did_expansion = 1;
  1775. $object->{$field_name} = tripal_core_expand_chado_vars($field_value, 'table', $foreign_table);
  1776. }
  1777. }
  1778. // if we did not expand this table we should return a message that the foreign table
  1779. // could not be expanded
  1780. if (!$did_expansion) {
  1781. watchdog('tripal_core', 'tripal_core_expand_chado_vars: Could not expand table, %table. It is ' .
  1782. 'not in a foreign key relationship with the base object nor with any other expanded table. ' .
  1783. 'Check the table definition to ensure that a proper foreign key relationship is present.',
  1784. array('%table' => $foreign_table), WATCHDOG_ERROR);
  1785. }
  1786. }
  1787. break;
  1788. case "node": //---------------------------------------------------------------------------------
  1789. //if the node to be expanded is for our base table, then just expand it
  1790. if ($object->tablename == $to_expand) {
  1791. $node = node_load($object->nid);
  1792. if ($node) {
  1793. $object->expanded = $to_expand;
  1794. $node->expandable_fields = $object->expandable_fields;
  1795. unset($object->expandable_fields);
  1796. $node->expandable_tables = $object->expandable_tables;
  1797. unset($object->expandable_tables);
  1798. $node->expandable_nodes = $object->expandable_nodes;
  1799. unset($object->expandable_nodes);
  1800. $node->{$base_table} = $object;
  1801. $object = $node;
  1802. }
  1803. else {
  1804. watchdog('tripal_core', 'tripal_core_expand_chado_vars: No node matches the nid (%nid) supplied.',
  1805. array('%nid' => $object->nid), WATCHDOG_ERROR);
  1806. } //end of if node
  1807. }
  1808. else {
  1809. //We need to recurse -the node to expand is one of the nested objects
  1810. foreach ((array) $object as $field_name => $field_value) {
  1811. if (is_object($field_value)) {
  1812. $object->{$field_name} = tripal_core_expand_chado_vars(
  1813. $field_value,
  1814. 'node',
  1815. $to_expand
  1816. );
  1817. }
  1818. } //end of for each field in the current object
  1819. }
  1820. break;
  1821. default:
  1822. watchdog('tripal_core', 'tripal_core_expand_chado_vars: Unrecognized type (%type). Should be one of "field", "table", "node".',
  1823. array('%type' => $type), WATCHDOG_ERROR);
  1824. return FALSE;
  1825. }
  1826. // move extended array downwards
  1827. if (!property_exists($object, 'expanded')) {
  1828. //if there's no extended field then go hunting for it
  1829. foreach ( (array)$object as $field_name => $field_value) {
  1830. if (is_object($field_value)) {
  1831. if (isset($field_value->expanded)) {
  1832. $object->expanded = $field_value->expanded;
  1833. unset($field_value->expanded);
  1834. }
  1835. }
  1836. }
  1837. }
  1838. //try again becasue now we might have moved it down
  1839. if (property_exists($object, 'expanded')) {
  1840. $expandable_name = 'expandable_' . $type . 's';
  1841. if (property_exists($object, $expandable_name) and $object->{$expandable_name}) {
  1842. $key_to_remove = array_search($object->expanded, $object->{$expandable_name});
  1843. unset($object->{$expandable_name}[$key_to_remove]);
  1844. unset($object->expanded);
  1845. }
  1846. else {
  1847. // if there is an expandable array then we've reached the base object
  1848. // if we get here and don't have anything expanded then something went wrong
  1849. // watchdog(
  1850. // 'tripal_core',
  1851. // 'tripal_core_expand_chado_vars: Unable to expand the %type %to_expand',
  1852. // array('%type'=>$type, '%to_expand'=>$to_expand),
  1853. // WATCHDOG_ERROR
  1854. // );
  1855. } //end of it we've reached the base object
  1856. }
  1857. return $object;
  1858. }
  1859. /**
  1860. * Implements hook_exclude_type_by_default()
  1861. *
  1862. * This hooks allows fields of a specified type that match a specified criteria to be excluded by
  1863. * default from any table when tripal_core_generate_chado_var() is called. Keep in mind that if
  1864. * fields are excluded by default they can always be expanded at a later date using
  1865. * tripal_core_expand_chado_vars().
  1866. *
  1867. * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using
  1868. * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are
  1869. * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can
  1870. * contain the following tokens:
  1871. * - &gt;field_name&lt;
  1872. * Replaced by the name of the field to be excluded
  1873. * - &gt;field_value&lt;
  1874. * Replaced by the value of the field in the current record
  1875. * Also keep in mind that if your criteria doesn't contain the &gt;field_value&lt; token then it will be
  1876. * evaluated before the query is executed and if the field is excluded it won't be included in the
  1877. * query.
  1878. *
  1879. * @return
  1880. * An array of type => criteria where the type is excluded if the criteria evaluates to TRUE
  1881. *
  1882. * @ingroup tripal_chado_api
  1883. */
  1884. function tripal_core_exclude_type_by_default() {
  1885. return array('text' => 'strlen("&gt;field_value&lt; ") > 100');
  1886. }
  1887. /**
  1888. * Implements hook_exclude_field_from_<tablename>_by_default()
  1889. *
  1890. * This hooks allows fields from a specified table that match a specified criteria to be excluded by
  1891. * default from any table when tripal_core_generate_chado_var() is called. Keep in mind that if
  1892. * fields are excluded by default they can always be expanded at a later date using
  1893. * tripal_core_expand_chado_vars().
  1894. *
  1895. * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using
  1896. * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are
  1897. * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can
  1898. * contain the following tokens:
  1899. * - &gt;field_name&lt;
  1900. * Replaced by the name of the field to be excluded
  1901. * - &gt;field_value&lt;
  1902. * Replaced by the value of the field in the current record
  1903. * Also keep in mind that if your criteria doesn't contain the &gt;field_value&lt; token then it will be
  1904. * evaluated before the query is executed and if the field is excluded it won't be included in the
  1905. * query.
  1906. *
  1907. * @return
  1908. * An array of type => criteria where the type is excluded if the criteria evaluates to TRUE
  1909. *
  1910. * @ingroup tripal_chado_api
  1911. */
  1912. function tripal_core_exclude_field_from_feature_by_default() {
  1913. return array();
  1914. }
  1915. /**
  1916. * Use this function instead of pager_query() when selecting a
  1917. * subset of records from a Chado table.
  1918. *
  1919. * @param $query
  1920. * The SQL statement to execute, this is followed by a variable number of args
  1921. * used as substitution values in the SQL statement.
  1922. * @param $args
  1923. * The array of arguments for the query. They keys are the placeholders
  1924. * @param $limit
  1925. * The number of query results to display per page.
  1926. * @param $element
  1927. * An optional integer to distinguish between multiple pagers on one page.
  1928. * @param $count_query
  1929. * An SQL query used to count matching records.
  1930. *
  1931. * @returns
  1932. * A database query result resource or FALSE if the query was not
  1933. * executed correctly
  1934. *
  1935. * @ingroup tripal_chado_api
  1936. */
  1937. function chado_pager_query($query, $args, $limit, $element, $count_query = '') {
  1938. // get the page and offset for the pager
  1939. $page = isset($_GET['page']) ? $_GET['page'] : '0';
  1940. $offset = $limit * $page;
  1941. // Construct a count query if none was given.
  1942. if (!isset($count_query)) {
  1943. $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'),
  1944. array('SELECT COUNT(*) FROM ', ''), $query);
  1945. }
  1946. // We calculate the total of pages as ceil(items / limit).
  1947. $results = chado_query($count_query, $args);
  1948. if (!$results) {
  1949. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  1950. "chado_pager_query(): Query failed: %cq", array('%cq' => $count_query));
  1951. return;
  1952. }
  1953. $total_records = $results->fetchField();
  1954. // set a session variable for storing the total number of records
  1955. $_SESSION['chado_pager'][$element]['total_records'] = $total_records;
  1956. pager_default_initialize($total_records, $limit, $element);
  1957. $query .= ' LIMIT ' . (int) $limit . ' OFFSET ' . (int) $offset;
  1958. $results = chado_query($query, $args);
  1959. return $results;
  1960. }
  1961. /**
  1962. * Use this function instead of db_query() to avoid switching databases
  1963. * when making query to the chado database
  1964. *
  1965. * Will use a chado persistent connection if it already exists
  1966. *
  1967. * @param $sql
  1968. * The sql statement to execute
  1969. *
  1970. * @param $args
  1971. * The array of arguments, with the same structure as passed to
  1972. * the db_query() function of Drupal.
  1973. *
  1974. * @return
  1975. * DatabaseStatementInterface A prepared statement object, already executed.
  1976. *
  1977. * @ingroup tripal_chado_api
  1978. */
  1979. function chado_query($sql, $args = array()) {
  1980. $is_local = $GLOBALS["chado_is_local"];
  1981. // Args should be an array
  1982. if (!is_array($args)) {
  1983. tripal_core_report_error('tripal_core', TRIPAL_ERROR,
  1984. 'chado_query; Need to pass an array to chado_query, "%value" passed instead. Query: %query',
  1985. array('%value' => $args, '%query' => $sql)
  1986. );
  1987. $args = array($args);
  1988. return FALSE;
  1989. }
  1990. // if Chado is local to the database then prefix the Chado table
  1991. // names with 'chado'.
  1992. if ($is_local) {
  1993. $sql = preg_replace('/\n/', '', $sql); // remove carriage returns
  1994. $sql = preg_replace('/\{(.*?)\}/', 'chado.$1', $sql);
  1995. // the featureloc table has some indexes that use function that call other functions
  1996. // and those calls do not reference a schema, therefore, any tables with featureloc
  1997. // must automaticaly have the chado schema set as active to find
  1998. if(preg_match('/chado.featureloc/i', $sql)) {
  1999. $previous_db = tripal_db_set_active('chado') ;
  2000. $results = db_query($sql, $args);
  2001. tripal_db_set_active($previous_db);
  2002. }
  2003. // for all other tables we should have everything in scope so just run the query
  2004. else {
  2005. $results = db_query($sql, $args);
  2006. }
  2007. }
  2008. // if Chado is not local to the Drupal database then we have to
  2009. // switch to another database
  2010. else {
  2011. $previous_db = tripal_db_set_active('chado') ;
  2012. $results = db_query($sql, $args);
  2013. tripal_db_set_active($previous_db);
  2014. }
  2015. return $results;
  2016. }
  2017. /**
  2018. * Get chado id for a node. E.g, if you want to get 'analysis_id' from the
  2019. * 'analysis' table for a synced 'chado_analysis' node, (the same for
  2020. * organisms and features):
  2021. * $analysis_id = chado_get_id_for_node ('analysis', $node->nid)
  2022. * $organism_id = chado_get_id_for_node ('organism', $node->nid)
  2023. * $feature_id = chado_get_id_for_node ('feature', $node->nid)
  2024. *
  2025. * @param $table
  2026. * @param $nid
  2027. *
  2028. * @ingroup tripal_chado_api
  2029. */
  2030. function chado_get_id_for_node($table, $nid) {
  2031. $sql = "SELECT " . $table . "_id as id FROM {chado_$table} WHERE nid = :nid";
  2032. return db_query($sql, array(':nid' => $nid))->fetchField();
  2033. }
  2034. /**
  2035. * Get node id for a chado feature/organism/analysis. E.g, if you want to
  2036. * get the node id for an analysis, use:
  2037. * $nid = chado_get_node_id ('analysis', $analysis_id)
  2038. * Likewise,
  2039. * $nid = chado_get_node_id ('organism', $organism_id)
  2040. * $nid = chado_get_node_id ('feature', $feature_id)
  2041. *
  2042. * @ingroup tripal_chado_api
  2043. */
  2044. function chado_get_node_id($table, $id) {
  2045. $sql = "SELECT nid FROM {chado_$table} WHERE " . $table . "_id = :" . $table . "_id";
  2046. return db_query($sql, array(":" . $table . "_id" => $id))->fetchField();
  2047. }
  2048. /**
  2049. * Retrieves the schema in an array for the specified custom table.
  2050. *
  2051. * @param $table
  2052. * The name of the table to create.
  2053. *
  2054. * @return
  2055. * A Drupal-style Schema API array definition of the table. Returns
  2056. * FALSE on failure.
  2057. *
  2058. * @ingroup tripal_core_api
  2059. */
  2060. function tripal_get_chado_custom_schema($table) {
  2061. $sql = "SELECT schema FROM {tripal_custom_tables} WHERE table_name = :table_name";
  2062. $results = db_query($sql, array(':table_name' => $table));
  2063. $custom = $results->fetchObject();
  2064. if (!$custom) {
  2065. return FALSE;
  2066. }
  2067. else {
  2068. return unserialize($custom->schema);
  2069. }
  2070. }
  2071. /**
  2072. * Check that any given Chado table exists. This function
  2073. * is necessary because Drupa's db_table_exists function
  2074. * hardcodes the 'public'
  2075. *
  2076. * @return
  2077. * TRUE/FALSE depending upon whether it exists
  2078. */
  2079. function chado_table_exists($table) {
  2080. global $databases;
  2081. $default_db = $databases['default']['default']['database'];
  2082. $sql = "
  2083. SELECT 1
  2084. FROM information_schema.tables
  2085. WHERE
  2086. table_name = :table_name AND
  2087. table_schema = 'chado' AND
  2088. table_catalog = '$default_db'
  2089. ";
  2090. $results = db_query($sql, array(':table_name' => $table));
  2091. $exists = $results->fetchObject();
  2092. if (!$exists) {
  2093. return FALSE;
  2094. }
  2095. return TRUE;
  2096. }
  2097. /**
  2098. * Check that any given schema exists
  2099. *
  2100. * @param $schema
  2101. * The name of the schema to check the existence of
  2102. *
  2103. * @return
  2104. * TRUE/FALSE depending upon whether or not the schema exists
  2105. *
  2106. * @ingroup tripal_chado_api
  2107. */
  2108. function tripal_core_schema_exists($schema) {
  2109. // check that the chado schema now exists
  2110. $sql = "
  2111. SELECT nspname
  2112. FROM pg_namespace
  2113. WHERE
  2114. has_schema_privilege(nspname, 'USAGE') AND
  2115. nspname = :nspname
  2116. ORDER BY nspname
  2117. ";
  2118. $results = db_query($sql, array(':nspname' => $schema));
  2119. $name = $results->fetchObject();
  2120. if (strcmp($name->nspname, $schema) != 0) {
  2121. return FALSE;
  2122. }
  2123. return TRUE;
  2124. }
  2125. /**
  2126. * Retrieves the list tables in the Chado schema. By default it only retursn
  2127. * the default Chado tables, but may also return custom tables added to the
  2128. * Chado schema as well.
  2129. *
  2130. * @param $include_custom
  2131. * Optional. Set as TRUE to include any custom tables created in the
  2132. * Chado schema. Custom tables are added to Chado using the
  2133. * tripal_core_chado_create_table() function.
  2134. *
  2135. * @returns
  2136. * An associative array where the key and value pairs are the Chado table names.
  2137. *
  2138. * @ingroup tripal_core_api
  2139. */
  2140. function tripal_core_get_chado_tables($include_custom = NULL) {
  2141. // first get the chado version that is installed
  2142. $v = $GLOBALS["chado_version"];
  2143. $tables = array();
  2144. if ($v == '1.2') {
  2145. $tables_v1_2 = tripal_core_chado_get_v1_2_tables();
  2146. foreach ($tables_v1_2 as $table) {
  2147. $tables[$table] = $table;
  2148. }
  2149. }
  2150. if ($v == '1.11' or $v == '1.11 or older') {
  2151. $tables_v1_11 = tripal_core_chado_get_v1_11_tables();
  2152. foreach ($tables_v1_11 as $table) {
  2153. $tables[$table] = $table;
  2154. }
  2155. }
  2156. // now add in the custom tables too if requested
  2157. if ($include_custom) {
  2158. $sql = "SELECT table_name FROM {tripal_custom_tables}";
  2159. $resource = db_query($sql);
  2160. foreach ($resource as $r) {
  2161. $tables[$r->table_name] = $r->table_name;
  2162. }
  2163. }
  2164. asort($tables);
  2165. return $tables;
  2166. }
  2167. /**
  2168. * Returns the version number of the currently installed Chado instance.
  2169. * It can return the real or effective version. Note, this function
  2170. * is executed in the hook_init() of the tripal_core module which then
  2171. * sets the $GLOBAL['exact_chado_version'] and $GLOBAL['chado_version']
  2172. * variable. You can access these variables rather than calling this function.
  2173. *
  2174. * @param $exact
  2175. * Set this argument to 1 to retrieve the exact version that is installed.
  2176. * Otherwise, this function will set the version to the nearest 'tenth'.
  2177. * Chado versioning numbers in the hundreds represent changes to the
  2178. * software and not the schema. Changes in the tenth's represent changes
  2179. * in the schema.
  2180. *
  2181. * @param $warn_if_unsupported
  2182. * If the currently installed version of Chado is not supported by Tripal
  2183. * this generates a Drupal warning.
  2184. *
  2185. * @returns
  2186. * The version of Chado
  2187. *
  2188. * @ingroup tripal_core_api
  2189. */
  2190. function tripal_core_get_chado_version($exact = FALSE, $warn_if_unsupported = FALSE) {
  2191. global $databases;
  2192. $version = '';
  2193. $is_local = 0;
  2194. // check that Chado is installed if not return 'uninstalled as the version'
  2195. $chado_exists = tripal_core_chado_schema_exists();
  2196. if (!$chado_exists) {
  2197. // if it's not in the drupal database check to see if it's specified in the $db_url
  2198. // in the settings.php
  2199. if (!array_key_exists('chado', $databases)) {
  2200. // if it's not in the drupal database or specified in the $db_url then
  2201. // return uninstalled as the version
  2202. return 'not installed';
  2203. }
  2204. $is_local = 0;
  2205. $previous_db = tripal_db_set_active('chado');
  2206. $prop_exists = db_table_exists('chadoprop');
  2207. tripal_db_set_active($previous_db);
  2208. }
  2209. else {
  2210. $is_local = 1;
  2211. $prop_exists = db_table_exists('chado.chadoprop');
  2212. }
  2213. // if the table doesn't exist then we don't know what version but we know
  2214. // it must be 1.11 or older.
  2215. if (!$prop_exists) {
  2216. $version = "1.11 or older";
  2217. }
  2218. else {
  2219. $sql = "
  2220. SELECT value
  2221. FROM {chadoprop} CP
  2222. INNER JOIN {cvterm} CVT on CVT.cvterm_id = CP.type_id
  2223. INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
  2224. WHERE CV.name = 'chado_properties' and CVT.name = 'version'
  2225. ";
  2226. if (!$is_local) {
  2227. $previous_db = tripal_db_set_active('chado');
  2228. $results = db_query($sql);
  2229. tripal_db_set_active($previous_db);
  2230. }
  2231. else {
  2232. $results = chado_query($sql);
  2233. }
  2234. $v = $results->fetchObject();
  2235. // if we don't have a version in the chadoprop table then it must be
  2236. // v1.11 or older
  2237. if (!$v) {
  2238. $version = "1.11 or older";
  2239. }
  2240. $version = $v->value;
  2241. }
  2242. // next get the exact Chado version that is installed
  2243. $exact_version = $version;
  2244. // Tripal only supports v1.11 or newer.. really this is the same as v1.1
  2245. // but at the time the v1.11 schema API was written we didn't know that so
  2246. // we'll return the version 1.11 so the schema API will work.
  2247. if (strcmp($exact_version, '1.11 or older') == 0) {
  2248. $exact_version = "1.11";
  2249. if ($warn_if_unsupported) {
  2250. drupal_set_message(t("WARNING: Tripal does not fully support Chado version less than v1.11. If you are certain this is v1.11
  2251. or if Chado was installed using an earlier version of Tripal then all is well. If not please upgrade to v1.11 or later"),
  2252. 'warning');
  2253. }
  2254. }
  2255. // if not returing an exact version, return the version to the nearest 10th.
  2256. // return 1.2 for all versions of 1.2x
  2257. $effective_version = $exact_version;
  2258. if (preg_match('/^1\.2\d+$/', $effective_version)) {
  2259. $effective_version = "1.2";
  2260. }
  2261. if ($warn_if_unsupported and ($effective_version != 1.11 and $effective_version != 1.2 and $effective_version != 'not installed')) {
  2262. drupal_set_message(t("WARNING: The currently installed version of Chado, v$exact_version, is not fully compatible with Tripal."), 'warning');
  2263. }
  2264. // if the callee has requested the exact version then return it
  2265. if ($exact) {
  2266. return $exact_version;
  2267. }
  2268. return $effective_version;
  2269. }
  2270. /**
  2271. * Retrieves the chado tables Schema API array.
  2272. *
  2273. * @param $table
  2274. * The name of the table to retrieve. The function will use the appopriate
  2275. * Tripal chado schema API hooks (e.g. v1.11 or v1.2).
  2276. *
  2277. * @returns
  2278. * A Drupal Schema API array defining the table.
  2279. *
  2280. * @ingroup tripal_core_api
  2281. */
  2282. function tripal_core_get_chado_table_schema($table) {
  2283. // first get the chado version that is installed
  2284. $v = $GLOBALS["chado_version"];
  2285. // get the table array from the proper chado schema
  2286. $v = preg_replace("/\./", "_", $v); // reformat version for hook name
  2287. $table_arr = module_invoke_all("chado_schema_v" . $v . "_" . $table);
  2288. // if the table_arr is empty then maybe this is a custom table
  2289. if (!is_array($table_arr) or count($table_arr) == 0) {
  2290. $table_arr = tripal_get_chado_custom_schema($table);
  2291. }
  2292. return $table_arr;
  2293. }
  2294. /**
  2295. * Check that the Chado schema exists within the local database
  2296. *
  2297. * @return
  2298. * TRUE/FALSE depending upon whether it exists
  2299. */
  2300. function tripal_core_chado_schema_exists() {
  2301. // This is postgresql-specific code to check the existence of the chado schema
  2302. // @coder-ignore: acting on pg_catalog schema rather then drupal schema therefore, table prefixing does not apply
  2303. $sql = "
  2304. SELECT nspname
  2305. FROM pg_namespace
  2306. WHERE
  2307. has_schema_privilege(nspname, 'USAGE') AND
  2308. nspname = 'chado'
  2309. ";
  2310. $results = db_query($sql);
  2311. $name = $results->fetchObject();
  2312. if ($name) {
  2313. variable_set('chado_schema_exists', FALSE);
  2314. return TRUE;
  2315. }
  2316. else {
  2317. variable_set('chado_schema_exists', TRUE);
  2318. return FALSE;
  2319. }
  2320. }
  2321. /**
  2322. * Check whether chado is installed (either in the same or a different database)
  2323. *
  2324. * @return
  2325. * TRUE/FALSE depending upon whether chado is installed.
  2326. *
  2327. * @ingroup tripal_chado_api
  2328. */
  2329. function tripal_core_is_chado_installed() {
  2330. global $databases;
  2331. // first check if chado is in the $databases variable of the settings.php file
  2332. if (array_key_exists('chado', $databases)) {
  2333. return TRUE;
  2334. }
  2335. // check to make sure the chado schema exists
  2336. return tripal_core_chado_schema_exists();
  2337. }
  2338. /**
  2339. * Check whether chado is installed local to the Drupal database
  2340. * in its own Chado schema.
  2341. *
  2342. * @return
  2343. * TRUE/FALSE depending upon whether chado is local.
  2344. *
  2345. * @ingroup tripal_chado_api
  2346. */
  2347. function tripal_core_is_chado_local() {
  2348. global $databases, $db_type;
  2349. // first check if chado is in the $databases variable of the settings.php file
  2350. if (array_key_exists('chado', $databases)) {
  2351. return FALSE;
  2352. }
  2353. // check to make sure the chado schema exists
  2354. return tripal_core_chado_schema_exists();
  2355. }
  2356. /**
  2357. * Determine whether a given chado table is directly linked to a node
  2358. *
  2359. * @param $chado_table
  2360. * The name of a chado table to check (ie: feature)
  2361. * @return
  2362. * TRUE if it is linked to a node and FALSE otherwise
  2363. */
  2364. function tripal_core_is_tripal_node_type($chado_table) {
  2365. $linking_table = 'chado_' . $chado_table;
  2366. if (db_table_exists($linking_table)) {
  2367. return TRUE;
  2368. }
  2369. else {
  2370. return FALSE;
  2371. }
  2372. }
  2373. /**
  2374. * Set the Tripal Database
  2375. *
  2376. * The tripal_db_set_active function is used to prevent namespace collisions
  2377. * when chado and drupal are installed in the same database but in different
  2378. * schemas. It is also used for backwards compatibility with older versions
  2379. * of tripal or in cases where chado is located outside of the Drupal database.
  2380. * or when using Drupal functions such as db_table_exists()
  2381. *
  2382. * @ingroup tripal_chado_api
  2383. */
  2384. function tripal_db_set_active($dbname = 'default') {
  2385. global $databases, $active_db;
  2386. if ($dbname ) {
  2387. if ($dbname == 'chado') {
  2388. db_query('set search_path to chado,public');
  2389. return 'default';
  2390. }
  2391. else {
  2392. db_query('set search_path to public');
  2393. return 'chado';
  2394. }
  2395. }
  2396. // if the 'chado' database is in the $db_url variable then chado is
  2397. // not in the same Drupal database, so we don't need to set any
  2398. // search_path and can just change the database
  2399. elseif (array_key_exists($dbname, $databases)) {
  2400. return db_set_active($dbname);
  2401. }
  2402. }