tripal_core.api.inc 136 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726
  1. <?php
  2. require_once "tripal_core.schema_v1.2.api.inc";
  3. require_once "tripal_core.schema_v1.11.api.inc";
  4. /**
  5. * @file
  6. * The Tripal Core API
  7. *
  8. * This file provides the API needed for all other Tripal and Tripal dependent
  9. * modules.
  10. *
  11. * @defgroup tripal_api Tripal API
  12. * @{
  13. * Provides an application programming interface (API) for Tripal
  14. *
  15. * The Tripal API currently provides generic insert/update/select functions for all chado content as
  16. * well as some module specific functions that insert/update/delete/select specific chado content.
  17. *
  18. * This API is currently in its infancy and some necessary functions might be missing. If you find
  19. * a missing function that you think should be included go to the sourceforge feature request
  20. * page and request it's inclusion in the API. Such feature requests with a working function
  21. * definition will be given priority.
  22. * @}
  23. *
  24. * @defgroup tripal_chado_api Core Module Chado API
  25. * @{
  26. * Provides an application programming interface (API) to manage data withing the Chado database.
  27. * This includes functions for selecting, inserting, updating and deleting records
  28. * in Chado tables. The functions will ensure proper integrity contraints are met
  29. * for inserts and updates.
  30. *
  31. * Also, a set of functions is provided for creating template variables. First,
  32. * is the tripal_core_generate_chado_vars which is used to select one ore more
  33. * records from a table and return an array with foreign key relationships fully
  34. * populated. For example, if selecting a feature, the organism_id and type_id
  35. * would be present in the returned array as a nested array with their respective
  36. * foreign keys also nested. The only fields that are not included are text
  37. * fields (which may be very large) or many-to-many foreign key relationships.
  38. * However, these fields and relationships can be expanded using the
  39. * tripal_core_expand_chado_vars.
  40. *
  41. * When a row from a chado table is selected using these two functions, it provides
  42. * a way for users who want to cutomize Drupal template files to access all data
  43. * associate with a specific record.
  44. *
  45. * Finally, the property tables in Chado generally follow the same format. Therefore
  46. * there is a set of functions for inserting, updating and deleting properties for
  47. * any table. This provides quick lookup of properties (provided the CV term is
  48. * known).
  49. *
  50. * @}
  51. * @ingroup tripal_api
  52. *
  53. * @defgroup tripal_files_api Core Module Files API
  54. * @{
  55. * Provides an application programming interface (API) for managing files within
  56. * the Tripal data directory structure.
  57. *
  58. * @}
  59. * @ingroup tripal_api
  60. /**
  61. * Provides a generic routine for inserting into any Chado table
  62. *
  63. * Use this function to insert a record into any Chado table. The first
  64. * argument specifies the table for inserting and the second is an array
  65. * of values to be inserted. The array is mutli-dimensional such that
  66. * foreign key lookup values can be specified.
  67. *
  68. * @param $table
  69. * The name of the chado table for inserting
  70. * @param $values
  71. * An associative array containing the values for inserting.
  72. * @param $options
  73. * An array of options such as:
  74. * - statement_name: the name of the prepared statement to use. If the statement
  75. * has not yet been prepared it will be prepared automatically. On subsequent
  76. * calls with the same statement_name only an execute on the previously
  77. * prepared statement will occur.
  78. * - is_prepared: TRUE or FALSE. Whether or not the statement is prepared. By
  79. * default if the statement is not prepared it will be automatically.
  80. * However to avoid this check, which requires a database query you can
  81. * set this value to true and the check will not be performed.
  82. * - skip_validation: TRUE or FALSE. If TRUE will skip all the validation steps and
  83. * just try to insert as is. This is much faster but results in unhandled
  84. * non user-friendly errors if the insert fails.
  85. * - return_record: by default, the function will return the record but with
  86. * the primary keys added after insertion. To simply return TRUE on success
  87. * set this option to FALSE
  88. *
  89. * @return
  90. * On success this function returns the inserted record with the new primary keys
  91. * add to the returned array. On failure, it returns FALSE.
  92. *
  93. * Example usage:
  94. * @code
  95. * $values = array(
  96. * 'organism_id' => array(
  97. * 'genus' => 'Citrus',
  98. * 'species' => 'sinensis',
  99. * ),
  100. * 'name' => 'orange1.1g000034m.g',
  101. * 'uniquename' => 'orange1.1g000034m.g',
  102. * 'type_id' => array (
  103. * 'cv_id' => array (
  104. * 'name' => 'sequence',
  105. * ),
  106. * 'name' => 'gene',
  107. * 'is_obsolete' => 0
  108. * ),
  109. * );
  110. * $result = tripal_core_chado_insert('feature',$values);
  111. * @endcode
  112. * The above code inserts a record into the feature table. The $values array is
  113. * nested such that the organism is selected by way of the organism_id foreign
  114. * key constraint by specifying the genus and species. The cvterm is also
  115. * specified using its foreign key and the cv_id for the cvterm is nested as
  116. * well.
  117. *
  118. * @ingroup tripal_chado_api
  119. */
  120. function tripal_core_chado_insert($table, $values, $options = array()) {
  121. if (!is_array($values)) {
  122. watchdog('tripal_core', 'Cannot pass non array as values for inserting.', array(),
  123. WATCHDOG_ERROR);
  124. return FALSE;
  125. }
  126. if (count($values)==0) {
  127. watchdog('tripal_core', 'Cannot pass an empty array as values for inserting.', array(),
  128. WATCHDOG_ERROR);
  129. return FALSE;
  130. }
  131. // set defaults for options. If we don't set defaults then
  132. // we get memory leaks when we try to access the elements
  133. if (!is_array($options)) {
  134. $options = array();
  135. }
  136. if (!array_key_exists('is_prepared', $options)) {
  137. $options['is_prepared'] = FALSE;
  138. }
  139. if (!array_key_exists('statement_name', $options)) {
  140. $options['statement_name'] = FALSE;
  141. }
  142. if (!array_key_exists('skip_validation', $options)) {
  143. $options['skip_validation'] = FALSE;
  144. }
  145. if (!array_key_exists('return_record', $options)) {
  146. $options['return_record'] = TRUE;
  147. }
  148. $insert_values = array();
  149. // Determine plan of action
  150. if ($options['statement_name']) {
  151. // we have a prepared statment (or want to create one) so set $prepared = TRUE
  152. $prepared = TRUE;
  153. // we need to get a persistent connection. If one exists this function
  154. // will not recreate it, but if not it will create one and store it in
  155. // a Drupal variable for reuse later.
  156. $connection = tripal_db_persistent_chado();
  157. // if we cannot get a connection the abandon the prepared statement
  158. if (!$connection) {
  159. $prepared = FALSE;
  160. unset($options['statement_name']);
  161. }
  162. }
  163. else {
  164. //print "NO STATEMENT (insert): $table\n";
  165. //debug_print_backtrace();
  166. }
  167. if (array_key_exists('skip_validation', $options)) {
  168. $validate = !$options['skip_validation'];
  169. }
  170. else {
  171. $validate = TRUE;
  172. }
  173. // get the table description
  174. $table_desc = tripal_core_get_chado_table_schema($table);
  175. if (empty($table_desc)) {
  176. watchdog('tripal_core', 'tripal_core_chado_insert: There is no table description for !table_name', array('!table_name' => $table), WATCHDOG_WARNING);
  177. }
  178. // iterate through the values array and create a new 'insert_values' array
  179. // that has all the values needed for insert with all foreign relationsihps
  180. // resolved.
  181. foreach ($values as $field => $value) {
  182. // make sure the field is in the table description. If not then return an error
  183. // message
  184. if (!array_key_exists($field, $table_desc['fields'])) {
  185. watchdog('tripal_core', "tripal_core_chado_insert: The field '%field' does not exist ".
  186. "for the table '%table'. Cannot perform insert. Values: %array",
  187. array('%field' => $field, '%table' => $table, '%array' => print_r($values, 1)), WATCHDOG_ERROR);
  188. return FALSE;
  189. }
  190. if (is_array($value)) {
  191. $foreign_options = array();
  192. if ($options['statement_name']) {
  193. // add the fk relationship info to the prepared statement name so that
  194. // we can prepare the selects run by the recrusive tripal_core_chado_get_foreign_key
  195. // function.
  196. $fk_sname = "fk_" . $table . "_" . $field;
  197. foreach ($value as $k => $v) {
  198. $fk_sname .= substr($k, 0, 2);
  199. }
  200. $foreign_options['statement_name'] = $fk_sname;
  201. }
  202. // select the value from the foreign key relationship for this value
  203. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value, $foreign_options);
  204. if (sizeof($results) > 1) {
  205. watchdog('tripal_core', 'tripal_core_chado_insert: Too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)), WATCHDOG_ERROR);
  206. }
  207. elseif (sizeof($results) < 1) {
  208. //watchdog('tripal_core', 'tripal_core_chado_insert: no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)), WATCHDOG_ERROR);
  209. }
  210. else {
  211. $insert_values[$field] = $results[0];
  212. }
  213. }
  214. else {
  215. $insert_values[$field] = $value;
  216. }
  217. }
  218. if ($validate) {
  219. // check for violation of any unique constraints
  220. $ukeys = array();
  221. if (array_key_exists('unique keys', $table_desc)) {
  222. $ukeys = $table_desc['unique keys'];
  223. }
  224. $ukselect_cols = array();
  225. $ukselect_vals = array();
  226. if ($ukeys) {
  227. foreach ($ukeys as $name => $fields) {
  228. foreach ($fields as $index => $field) {
  229. // build the arrays for performing a select that will check the contraint
  230. $ukselect_cols[] = $field;
  231. if (!array_key_exists($field, $insert_values)) {
  232. if (array_key_exists('default', $table_desc['fields'][$field])) {
  233. $ukselect_vals[$field] = $table_desc['fields'][$field]['default'];
  234. }
  235. }
  236. else {
  237. $ukselect_vals[$field] = $insert_values[$field];
  238. }
  239. }
  240. // now check the constraint
  241. $coptions = array();
  242. if ($options['statement_name']) {
  243. $coptions = array('statement_name' => 'uqsel_' . $table . '_' . $name);
  244. }
  245. if (tripal_core_chado_select($table, $ukselect_cols, $ukselect_vals, $coptions)) {
  246. watchdog('tripal_core', "tripal_core_chado_insert: Cannot insert duplicate record into $table table: " .
  247. print_r($values, 1), array(), 'WATCHDOG_ERROR');
  248. return FALSE;
  249. }
  250. }
  251. }
  252. // if trying to insert a field that is the primary key, make sure it also is unique
  253. if (array_key_exists('primary key', $table_desc)) {
  254. $pkey = $table_desc['primary key'][0];
  255. if (array_key_exists($pkey, $insert_values)) {
  256. $coptions = array('statement_name' => 'pqsel_' . $table . '_' . $pkey);
  257. if (tripal_core_chado_select($table, array($pkey), array($pkey => $insert_values[$pkey]), $coptions)) {
  258. watchdog('tripal_core', "tripal_core_chado_insert: Cannot insert duplicate primary key into $table table: " . print_r($values, 1), array(), 'WATCHDOG_ERROR');
  259. return FALSE;
  260. }
  261. }
  262. }
  263. // make sure required fields have a value
  264. if (!is_array($table_desc['fields'])) {
  265. $table_desc['fields'] = array();
  266. watchdog('tripal_core', "tripal_core_chado_insert: %table missing fields: \n %schema",
  267. array('%table' => $table, '%schema' => print_r($table_desc, 1)), WATCHDOG_WARNING);
  268. }
  269. foreach ($table_desc['fields'] as $field => $def) {
  270. // a field is considered missing if it cannot be NULL and there is no default
  271. // value for it or it is of type 'serial'
  272. if (array_key_exists('NOT NULL', $def) and
  273. !array_key_exists($field, $insert_values) and
  274. !array_key_exists('default', $def) and
  275. strcmp($def['type'], serial) != 0) {
  276. watchdog('tripal_core', "tripal_core_chado_insert: Field $table.$field cannot be NULL: " .
  277. print_r($values, 1), array(), 'WATCHDOG_ERROR');
  278. return FALSE;
  279. }
  280. }
  281. } //end of validation
  282. // Now build the insert SQL statement
  283. $ifields = array(); // contains the names of the fields
  284. $ivalues = array(); // contains the values of the fields
  285. $itypes = array(); // contains %d/%s placeholders for the sql query
  286. $iplaceholders = array(); // contains $1/$2 placeholders for the prepare query
  287. $idatatypes = array(); // contains the data type of the fields (int, text, etc.)
  288. $i = 1;
  289. foreach ($insert_values as $field => $value) {
  290. $ifields[] = $field;
  291. $ivalues[] = $value;
  292. $iplaceholders[] = '$' . $i;
  293. $i++;
  294. if (strcmp($value, '__NULL__')==0) {
  295. $itypes[] = "NULL";
  296. $idatatypes[] = "NULL";
  297. }
  298. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'serial')==0 OR
  299. strcasecmp($table_desc['fields'][$field]['type'], 'int')==0 OR
  300. strcasecmp($table_desc['fields'][$field]['type'], 'integer')==0) {
  301. $itypes[] = "%d";
  302. $idatatypes[] = 'int';
  303. }
  304. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
  305. $itypes[] = "%s";
  306. $idatatypes[] = 'bool';
  307. }
  308. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'float')==0) {
  309. $itypes[] = "%s";
  310. $idatatypes[] = 'numeric';
  311. }
  312. else {
  313. $itypes[] = "'%s'";
  314. $idatatypes[] = 'text';
  315. }
  316. }
  317. // create the SQL
  318. $sql = "INSERT INTO {$table} (" . implode(", ", $ifields) . ") VALUES (" . implode(", ", $itypes) . ")";
  319. // if this is a prepared statement then execute it
  320. if ($prepared) {
  321. // if this is the first time we've run this query
  322. // then we need to do the prepare, otherwise just execute
  323. if ($options['is_prepared'] != TRUE) {
  324. // prepare the statement
  325. $psql = "PREPARE " . $options['statement_name'] . " (" . implode(', ', $idatatypes) . ") AS INSERT INTO {$table} (" . implode(", ", $ifields) . ") VALUES (" . implode(", ", $iplaceholders) . ")";
  326. $status = tripal_core_chado_prepare($options['statement_name'], $psql, $idatatypes);
  327. if (!$status) {
  328. watchdog('tripal_core', "tripal_core_chado_insert: not able to prepare '%name' statement for: %sql", array('%name' => $options['statement_name'], '%sql' => $sql), WATCHDOG_ERROR);
  329. return FALSE;
  330. }
  331. }
  332. $sql = "EXECUTE " . $options['statement_name'] . "(" . implode(", ", $itypes) . ")";
  333. $result = tripal_core_chado_execute_prepared($options['statement_name'], $sql, $ivalues);
  334. }
  335. // if it's not a prepared statement then insert normally
  336. else {
  337. $result = chado_query($sql, $ivalues);
  338. }
  339. // if we have a result then add primary keys to return array
  340. if ($options['return_record'] == TRUE and $result) {
  341. if (array_key_exists('primary key', $table_desc) and is_array($table_desc['primary key'])) {
  342. foreach ($table_desc['primary key'] as $field) {
  343. $sql = '';
  344. $psql = "PREPARE currval_" . $table . "_" . $field . " AS SELECT CURRVAL('" . $table . "_" . $field . "_seq')";
  345. $is_prepared = tripal_core_chado_prepare("currval_" . $table . "_" . $field, $psql, array());
  346. $value = '';
  347. if ($is_prepared) {
  348. $value = db_result(chado_query("EXECUTE currval_". $table . "_" . $field));
  349. if (!$value) {
  350. watchdog('tripal_core', "tripal_core_chado_insert: not able to retrieve primary key after insert: %sql",
  351. array('%sql' => $psql), WATCHDOG_ERROR);
  352. return FALSE;
  353. }
  354. }
  355. else {
  356. $sql = "SELECT CURRVAL('" . $table . "_" . $field . "_seq')";
  357. $value = db_result(chado_query($sql));
  358. if (!$value) {
  359. watchdog('tripal_core', "tripal_core_chado_insert: not able to retrieve primary key after insert: %sql",
  360. array('%sql' => $sql), WATCHDOG_ERROR);
  361. return FALSE;
  362. }
  363. }
  364. $values[$field] = $value;
  365. }
  366. }
  367. return $values;
  368. }
  369. elseif ($options['return_record'] == FALSE and $result) {
  370. return TRUE;
  371. }
  372. else {
  373. watchdog('tripal_core', "tripal_core_chado_insert: Cannot insert record into $table table: " . print_r($values, 1), array(), 'WATCHDOG_ERROR');
  374. return FALSE;
  375. }
  376. return FALSE;
  377. }
  378. /**
  379. * Provides a generic routine for updating into any Chado table
  380. *
  381. * Use this function to update a record in any Chado table. The first
  382. * argument specifies the table for inserting, the second is an array
  383. * of values to matched for locating the record for updating, and the third
  384. * argument give the values to update. The arrays are mutli-dimensional such
  385. * that foreign key lookup values can be specified.
  386. *
  387. * @param $table
  388. * The name of the chado table for inserting
  389. * @param $match
  390. * An associative array containing the values for locating a record to update.
  391. * @param $values
  392. * An associative array containing the values for updating.
  393. * @param $options
  394. * An array of options such as:
  395. * - statement_name: the name of the prepared statement to use. If the statement
  396. * has not yet been prepared it will be prepared automatically. On subsequent
  397. * calls with the same statement_name only an execute on the previously
  398. * prepared statement will occur.
  399. * - is_prepared: TRUE or FALSE. Whether or not the statement is prepared. By
  400. * default if the statement is not prepared it will be automatically.
  401. * However to avoid this check, which requires a database query you can
  402. * set this value to true and the check will not be performed.
  403. * - return_record: by default, the function will return the TRUE if the record
  404. * was succesfully updated. However, set this option to TRUE to return the
  405. * record that was updated. The returned record will have the fields provided
  406. * but the primary key (if available for the table) will be added to the record.
  407. * @return
  408. * On success this function returns TRUE. On failure, it returns FALSE.
  409. *
  410. * Example usage:
  411. * @code
  412. $umatch = array(
  413. 'organism_id' => array(
  414. 'genus' => 'Citrus',
  415. 'species' => 'sinensis',
  416. ),
  417. 'uniquename' => 'orange1.1g000034m.g7',
  418. 'type_id' => array (
  419. 'cv_id' => array (
  420. 'name' => 'sequence',
  421. ),
  422. 'name' => 'gene',
  423. 'is_obsolete' => 0
  424. ),
  425. );
  426. $uvalues = array(
  427. 'name' => 'orange1.1g000034m.g',
  428. 'type_id' => array (
  429. 'cv_id' => array (
  430. 'name' => 'sequence',
  431. ),
  432. 'name' => 'mRNA',
  433. 'is_obsolete' => 0
  434. ),
  435. );
  436. * $result = tripal_core_chado_update('feature',$umatch,$uvalues);
  437. * @endcode
  438. * The above code species that a feature with a given uniquename, organism_id,
  439. * and type_id (the unique constraint for the feature table) will be updated.
  440. * The organism_id is specified as a nested array that uses the organism_id
  441. * foreign key constraint to lookup the specified values to find the exact
  442. * organism_id. The same nested struture is also used for specifying the
  443. * values to update. The function will find the record that matches the
  444. * columns specified and update the record with the avlues in the $uvalues array.
  445. *
  446. * @ingroup tripal_chado_api
  447. */
  448. function tripal_core_chado_update($table, $match, $values, $options = NULL) {
  449. if (!is_array($values)) {
  450. watchdog('tripal_core', 'Cannot pass non array as values for updating.', array(),
  451. WATCHDOG_ERROR);
  452. return FALSE;
  453. }
  454. if (count($values)==0) {
  455. watchdog('tripal_core', 'Cannot pass an empty array as values for updating.', array(),
  456. WATCHDOG_ERROR);
  457. return FALSE;
  458. }
  459. if (!is_array($match)) {
  460. watchdog('tripal_core', 'Cannot pass non array as values for matching.', array(),
  461. WATCHDOG_ERROR);
  462. return FALSE;
  463. }
  464. if (count($match)==0) {
  465. watchdog('tripal_core', 'Cannot pass an empty array as values for matching.', array(),
  466. WATCHDOG_ERROR);
  467. return FALSE;
  468. }
  469. // set defaults for options. If we don't set defaults then
  470. // we get memory leaks when we try to access the elements
  471. if (!is_array($options)) {
  472. $options = array();
  473. }
  474. if (!array_key_exists('is_prepared', $options)) {
  475. $options['is_prepared'] = FALSE;
  476. }
  477. if (!array_key_exists('statement_name', $options)) {
  478. $options['statement_name'] = FALSE;
  479. }
  480. if (!array_key_exists('return_record', $options)) {
  481. $options['return_record'] = FALSE;
  482. }
  483. $update_values = array(); // contains the values to be updated
  484. $update_matches = array(); // contains the values for the where clause
  485. // Determine plan of action
  486. if ($options['statement_name']) {
  487. // we have a prepared statment (or want to create one) so set $prepared = TRUE
  488. $prepared = TRUE;
  489. // we need to get a persistent connection. If one exists this function
  490. // will not recreate it, but if not it will create one and store it in
  491. // a Drupal variable for reuse later.
  492. $connection = tripal_db_persistent_chado();
  493. // if we cannot get a connection the abandon the prepared statement
  494. if (!$connection ) {
  495. $prepared = FALSE;
  496. unset($options['statement_name']);
  497. }
  498. }
  499. else {
  500. //print "NO STATEMENT (update): $table\n";
  501. //debug_print_backtrace();
  502. }
  503. // get the table description
  504. $table_desc = tripal_core_get_chado_table_schema($table);
  505. // if the user wants us to return the record then we need to get the
  506. // unique primary key if one exists. That way we can add it to the
  507. // values that get returned at the end of the function
  508. $pkeys = array();
  509. if ($options['return_record'] == TRUE) {
  510. if (array_key_exists('primary key', $table_desc) and is_array($table_desc['primary key'])) {
  511. $columns = array();
  512. $stmt_suffix = '';
  513. foreach ($table_desc['primary key'] as $field) {
  514. $columns[] = $field;
  515. $stmt_suffix .= substr($field, 0, 2);
  516. }
  517. $options2 = array('statement_name' => 'sel_' . $table . '_' . $stmt_suffix);
  518. $results = tripal_core_chado_select($table, $columns, $match, $options2);
  519. if (count($results) > 0) {
  520. foreach ($results as $index => $pkey) {
  521. $pkeys[] = $pkey;
  522. }
  523. }
  524. }
  525. }
  526. // get the values needed for matching in the SQL statement
  527. foreach ($match as $field => $value) {
  528. if (is_array($value)) {
  529. $foreign_options = array();
  530. if ($options['statement_name']) {
  531. // add the fk relationship info to the prepared statement name so that
  532. // we can prepare the selects run by the recrusive tripal_core_chado_get_foreign_key
  533. // function.
  534. $fk_sname = "fk_" . $table . "_" . $field;
  535. foreach ($value as $k => $v) {
  536. $fk_sname .= substr($k, 0, 2);
  537. }
  538. $foreign_options['statement_name'] = $fk_sname;
  539. }
  540. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value, $foreign_options);
  541. if (sizeof($results) > 1) {
  542. watchdog('tripal_core', 'tripal_core_chado_update: When trying to find record to update, too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)), WATCHDOG_ERROR);
  543. }
  544. elseif (sizeof($results) < 1) {
  545. //watchdog('tripal_core', 'tripal_core_chado_update: When trying to find record to update, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)), WATCHDOG_ERROR);
  546. }
  547. else {
  548. $update_matches[$field] = $results[0];
  549. }
  550. }
  551. else {
  552. $update_matches[$field] = $value;
  553. }
  554. }
  555. // get the values used for updating
  556. foreach ($values as $field => $value) {
  557. if (is_array($value)) {
  558. $foreign_options = array();
  559. // select the value from the foreign key relationship for this value
  560. if ($options['statement_name']) {
  561. // add the fk relationship info to the prepared statement name so that
  562. // we can prepare the selects run by the recrusive tripal_core_chado_get_foreign_key
  563. // function.
  564. $fk_sname = "fk_" . $table . "_" . $field;
  565. foreach ($value as $k => $v) {
  566. $fk_sname .= substr($k, 0, 2);
  567. }
  568. $foreign_options['statement_name'] = $fk_sname;
  569. }
  570. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value, $foreign_options);
  571. if (sizeof($results) > 1) {
  572. watchdog('tripal_core', 'tripal_core_chado_update: When trying to find update values, too many records match the criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value, TRUE)), WATCHDOG_ERROR);
  573. }
  574. elseif (sizeof($results) < 1) {
  575. //watchdog('tripal_core', 'tripal_core_chado_update: When trying to find update values, no record matches criteria supplied for !foreign_key foreign key constraint (!criteria)', array('!foreign_key' => $field, '!criteria' => print_r($value,TRUE)), WATCHDOG_ERROR);
  576. }
  577. else {
  578. $update_values[$field] = $results[0];
  579. }
  580. }
  581. else {
  582. $update_values[$field] = $value;
  583. }
  584. }
  585. // now build the SQL statement
  586. $sql = "UPDATE {$table} SET ";
  587. $psql = "UPDATE {$table} SET ";
  588. $uargs = array();
  589. $idatatypes = array();
  590. $pvalues = array();
  591. $ivalues = array();
  592. $i = 1;
  593. foreach ($update_values as $field => $value) {
  594. if (strcasecmp($table_desc['fields'][$field]['type'], 'serial')==0 OR
  595. strcasecmp($table_desc['fields'][$field]['type'], 'int')==0 OR
  596. strcasecmp($table_desc['fields'][$field]['type'], 'integer')==0) {
  597. if (strcmp($value, '__NULL__') == 0) {
  598. $sql .= " $field = %s, ";
  599. $ivalues[] = 'NULL';
  600. $pvalues[] = '%s';
  601. $uargs[] = 'NULL';
  602. }
  603. else {
  604. $sql .= " $field = %d, ";
  605. $ivalues[] = $value;
  606. $pvalues[] = '%d';
  607. $uargs[] = $value;
  608. }
  609. $idatatypes[] = 'int';
  610. }
  611. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
  612. $sql .= " $field = %s, ";
  613. $pvalues[] = '%s';
  614. if (strcmp($value, '__NULL__')==0) {
  615. $ivalues[] = 'NULL';
  616. $uargs[] = 'NULL';
  617. }
  618. else {
  619. $ivalues[] = $value;
  620. $uargs[] = $value;
  621. }
  622. $idatatypes[] = 'bool';
  623. }
  624. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'float')==0) {
  625. $sql .= " $field = %s, ";
  626. $pvalues[] = '%s';
  627. if (strcmp($value, '__NULL__')==0) {
  628. $ivalues[] = 'NULL';
  629. $uargs[] = 'NULL';
  630. }
  631. else {
  632. $ivalues[] = $value;
  633. $uargs[] = $value;
  634. }
  635. $idatatypes[] = 'numeric';
  636. }
  637. else {
  638. if (strcmp($value, '__NULL__') == 0) {
  639. $sql .= " $field = %s, ";
  640. $ivalues[] = 'NULL';
  641. $uargs[] = 'NULL';
  642. $pvalues[] = '%s';
  643. }
  644. else {
  645. $sql .= " $field = '%s', ";
  646. $ivalues[] = $value;
  647. $uargs[] = $value;
  648. $pvalues[] = "'%s'";
  649. }
  650. $idatatypes[] = 'text';
  651. }
  652. $psql .= "$field = \$" . $i . ", ";
  653. $i++;
  654. }
  655. $sql = drupal_substr($sql, 0, -2); // get rid of the trailing comma & space
  656. $psql = drupal_substr($psql, 0, -2); // get rid of the trailing comma & space
  657. $sql .= " WHERE ";
  658. $psql .= " WHERE ";
  659. foreach ($update_matches as $field => $value) {
  660. if (strcasecmp($table_desc['fields'][$field]['type'], 'serial')==0 OR
  661. strcasecmp($table_desc['fields'][$field]['type'], 'int')==0 OR
  662. strcasecmp($table_desc['fields'][$field]['type'], 'integer')==0) {
  663. if (strcmp($value, '__NULL__')==0) {
  664. $sql .= " $field = %s AND ";
  665. $ivalues[] = 'NULL';
  666. $uargs[] = 'NULL';
  667. $pvalues[] = '%s';
  668. }
  669. else {
  670. $sql .= " $field = %d AND ";
  671. $ivalues[] = $value;
  672. $uargs[] = $value;
  673. $pvalues[] = '%s';
  674. }
  675. $idatatypes[] = 'int';
  676. }
  677. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
  678. $sql .= " $field = %s AND ";
  679. $pvalues[] = '%s';
  680. if (strcmp($value, '__NULL__')==0) {
  681. $ivalues[] = 'NULL';
  682. $uargs[] = 'NULL';
  683. }
  684. else {
  685. $ivalues[] = $value;
  686. $uargs[] = $value;
  687. }
  688. $idatatypes[] = 'bool';
  689. }
  690. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'float')==0) {
  691. $sql .= " $field = %s AND ";
  692. $pvalues[] = '%s';
  693. if (strcmp($value, '__NULL__')==0) {
  694. $ivalues[] = 'NULL';
  695. $uargs[] = 'NULL';
  696. }
  697. else {
  698. $ivalues[] = $value;
  699. $uargs[] = $value;
  700. }
  701. $idatatypes[] = 'numeric';
  702. }
  703. else {
  704. if (strcmp($value, '__NULL__')==0) {
  705. $sql .= " $field = %s AND ";
  706. $ivalues[] = 'NULL';
  707. $uargs[] = 'NULL';
  708. $pvalues[] = '%s';
  709. }
  710. else {
  711. $sql .= " $field = '%s' AND ";
  712. $ivalues[] = $value;
  713. $uargs[] = $value;
  714. $pvalues[] = "'%s'";
  715. }
  716. $idatatypes[] = 'text';
  717. }
  718. $psql .= "$field = \$" . $i . " AND ";
  719. $i++;
  720. }
  721. $sql = drupal_substr($sql, 0, -4); // get rid of the trailing 'AND'
  722. $psql = drupal_substr($psql, 0, -4); // get rid of the trailing 'AND'
  723. // finish constructing the prepared SQL statement
  724. $psql = "PREPARE " . $options['statement_name'] . " (" . implode(', ', $idatatypes) . ") AS " . $psql;
  725. // finally perform the update. If successful, return the updated record
  726. if ($prepared) {
  727. // if this is the first time we've run this query
  728. // then we need to do the prepare, otherwise just execute
  729. if ($options['is_prepared'] != TRUE and !tripal_core_is_sql_prepared($options['statement_name'])) {
  730. $status = chado_query($psql);
  731. if (!$status) {
  732. watchdog('tripal_core', "tripal_core_chado_update: not able to prepare '%name' statement for: %sql",
  733. array('%name' => $options['statement_name'], '%sql' => $sql), WATCHDOG_ERROR);
  734. return FALSE;
  735. }
  736. }
  737. $sql = "EXECUTE " . $options['statement_name'] . "(" . implode(", ", $pvalues) . ")";
  738. $result = chado_query($sql, $ivalues);
  739. }
  740. // if it's not a prepared statement then insert normally
  741. else {
  742. $result = chado_query($sql, $uargs);
  743. }
  744. // if we have a result then add primary keys to return array
  745. if ($options['return_record'] == TRUE and $result) {
  746. // only if we have a single result do we want to add the primary keys to the values
  747. // array. If the update matched many records we can't add the pkeys
  748. if (count($pkeys) == 1) {
  749. foreach ($pkeys as $index => $pkey) {
  750. foreach ($pkey as $field => $fvalue) {
  751. $values[$field] = $fvalue;
  752. }
  753. }
  754. }
  755. return $values;
  756. }
  757. elseif ($options['return_record'] == FALSE and $result) {
  758. return TRUE;
  759. }
  760. else {
  761. watchdog('tripal_core', "Cannot update record in $table table. \nMatch:" . print_r($match, 1) . "\nValues: ". print_r($values, 1), array(), 'WATCHDOG_ERROR');
  762. return FALSE;
  763. }
  764. return FALSE;
  765. }
  766. /**
  767. * Provides a generic function for deleting a record(s) from any chado table
  768. *
  769. * Use this function to delete a record(s) in any Chado table. The first
  770. * argument specifies the table to delete from and the second is an array
  771. * of values to match for locating the record(s) to be deleted. The arrays
  772. * are mutli-dimensional such that foreign key lookup values can be specified.
  773. *
  774. * @param $table
  775. * The name of the chado table for inserting
  776. * @param $match
  777. * An associative array containing the values for locating a record to update.
  778. * @param $options
  779. * An array of options such as:
  780. * - statement_name: the name of the prepared statement to use. If the statement
  781. * has not yet been prepared it will be prepared automatically. On subsequent
  782. * calls with the same statement_name only an execute on the previously
  783. * prepared statement will occur.
  784. * - is_prepared: TRUE or FALSE. Whether or not the statement is prepared. By
  785. * default if the statement is not prepared it will be automatically.
  786. * However to avoid this check, which requires a database query you can
  787. * set this value to true and the check will not be performed.
  788. * @return
  789. * On success this function returns TRUE. On failure, it returns FALSE.
  790. *
  791. * Example usage:
  792. * @code
  793. $umatch = array(
  794. 'organism_id' => array(
  795. 'genus' => 'Citrus',
  796. 'species' => 'sinensis',
  797. ),
  798. 'uniquename' => 'orange1.1g000034m.g7',
  799. 'type_id' => array (
  800. 'cv_id' => array (
  801. 'name' => 'sequence',
  802. ),
  803. 'name' => 'gene',
  804. 'is_obsolete' => 0
  805. ),
  806. );
  807. $uvalues = array(
  808. 'name' => 'orange1.1g000034m.g',
  809. 'type_id' => array (
  810. 'cv_id' => array (
  811. 'name' => 'sequence',
  812. ),
  813. 'name' => 'mRNA',
  814. 'is_obsolete' => 0
  815. ),
  816. );
  817. * $result = tripal_core_chado_update('feature',$umatch,$uvalues);
  818. * @endcode
  819. * The above code species that a feature with a given uniquename, organism_id,
  820. * and type_id (the unique constraint for the feature table) will be deleted.
  821. * The organism_id is specified as a nested array that uses the organism_id
  822. * foreign key constraint to lookup the specified values to find the exact
  823. * organism_id. The same nested struture is also used for specifying the
  824. * values to update. The function will find all records that match the
  825. * columns specified and delete them.
  826. *
  827. * @ingroup tripal_chado_api
  828. */
  829. function tripal_core_chado_delete($table, $match, $options = NULL) {
  830. if (!is_array($match)) {
  831. watchdog('tripal_core', 'Cannot pass non array as values for matching.', array(),
  832. WATCHDOG_ERROR);
  833. return FALSE;
  834. }
  835. if (count($match)==0) {
  836. watchdog('tripal_core', 'Cannot pass an empty array as values for matching.', array(),
  837. WATCHDOG_ERROR);
  838. return FALSE;
  839. }
  840. // set defaults for options. If we don't set defaults then
  841. // we get memory leaks when we try to access the elements
  842. if (!is_array($options)) {
  843. $options = array();
  844. }
  845. if (!array_key_exists('is_prepared', $options)) {
  846. $options['is_prepared'] = FALSE;
  847. }
  848. if (!array_key_exists('statement_name', $options)) {
  849. $options['statement_name'] = FALSE;
  850. }
  851. // Determine plan of action
  852. if ($options['statement_name']) {
  853. // we have a prepared statment (or want to create one) so set $prepared = TRUE
  854. $prepared = TRUE;
  855. // we need to get a persistent connection. If one exists this function
  856. // will not recreate it, but if not it will create one and store it in
  857. // a Drupal variable for reuse later.
  858. $connection = tripal_db_persistent_chado();
  859. // if we cannot get a connection the abandon the prepared statement
  860. if (!$connection ) {
  861. $prepared = FALSE;
  862. unset($options['statement_name']);
  863. }
  864. }
  865. else {
  866. //print "NO STATEMENT (update): $table\n";
  867. //debug_print_backtrace();
  868. }
  869. $delete_matches = array(); // contains the values for the where clause
  870. // get the table description
  871. $table_desc = tripal_core_get_chado_table_schema($table);
  872. $fields = $table_desc['fields'];
  873. // get the values needed for matching in the SQL statement
  874. foreach ($match as $field => $value) {
  875. if (is_array($value)) {
  876. // if the user has specified an array of values to delete rather than
  877. // FK relationships the keep those in our match
  878. if (array_values($value) === $value) {
  879. $delete_matches[$field] = $value;
  880. }
  881. else {
  882. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value);
  883. if (sizeof($results) > 1) {
  884. 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);
  885. }
  886. elseif (sizeof($results) < 1) {
  887. //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);
  888. }
  889. else {
  890. $delete_matches[$field] = $results[0];
  891. }
  892. }
  893. }
  894. else {
  895. $delete_matches[$field] = $value;
  896. }
  897. }
  898. // now build the SQL statement
  899. $sql = "DELETE FROM {$table} WHERE ";
  900. $psql = $sql;
  901. $uargs = array();
  902. $idatatypes = array();
  903. $pvalues = array();
  904. $ivalues = array();
  905. $dargs = array();
  906. $void_prepared = 0;
  907. $i = 1;
  908. foreach ($delete_matches as $field => $value) {
  909. // if we have an array values then this is an "IN" clasue.
  910. // we cannot use prepared statements with these
  911. if (count($value) > 1) {
  912. $sql .= "$field IN (" . db_placeholders($value, 'varchar') . ") AND ";
  913. foreach ($value as $v) {
  914. $dargs[] = $v;
  915. }
  916. $void_prepared = 1;
  917. continue;
  918. }
  919. if (strcasecmp($table_desc['fields'][$field]['type'], 'serial') == 0 OR
  920. strcasecmp($table_desc['fields'][$field]['type'], 'int') == 0 OR
  921. strcasecmp($table_desc['fields'][$field]['type'], 'integer') == 0) {
  922. if (strcmp($value, '__NULL__') == 0) {
  923. $sql .= " $field = NULL AND ";
  924. $ivalues[] = 'NULL';
  925. $pvalues[] = '%s';
  926. $uargs[] = 'NULL';
  927. }
  928. else {
  929. $sql .= " $field = %d AND ";
  930. $ivalues[] = $value;
  931. $pvalues[] = '%d';
  932. $uargs[] = $value;
  933. }
  934. $idatatypes[] = 'int';
  935. }
  936. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
  937. $sql .= " $field = %s AND ";
  938. $pvalues[] = '%s';
  939. if (strcmp($value, '__NULL__')==0) {
  940. $ivalues[] = 'NULL';
  941. $uargs[] = 'NULL';
  942. }
  943. else {
  944. $ivalues[] = $value;
  945. $uargs[] = $value;
  946. }
  947. $idatatypes[] = 'bool';
  948. }
  949. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'float')==0) {
  950. $sql .= " $field = %s AND ";
  951. $pvalues[] = '%s';
  952. if (strcmp($value, '__NULL__')==0) {
  953. $ivalues[] = 'NULL';
  954. $uargs[] = 'NULL';
  955. }
  956. else {
  957. $ivalues[] = $value;
  958. $uargs[] = $value;
  959. }
  960. $idatatypes[] = 'numeric';
  961. }
  962. else {
  963. if (strcmp($value, '__NULL__')==0) {
  964. $sql .= " $field = %s AND ";
  965. $ivalues[] = 'NULL';
  966. $uargs[] = 'NULL';
  967. $pvalues[] = '%s';
  968. }
  969. else {
  970. $sql .= " $field = '%s' AND ";
  971. $ivalues[] = $value;
  972. $uargs[] = $value;
  973. $pvalues[] = "'%s'";
  974. }
  975. $idatatypes[] = 'text';
  976. }
  977. array_push($dargs, $value);
  978. $psql .= "$field = \$" . $i . " AND ";
  979. $i++;
  980. }
  981. $sql = drupal_substr($sql, 0, -4); // get rid of the trailing 'AND'
  982. $psql = drupal_substr($psql, 0, -4); // get rid of the trailing 'AND'
  983. // finish constructing the prepared SQL statement
  984. $psql = "PREPARE " . $options['statement_name'] . " (" . implode(', ', $idatatypes) . ") AS " . $psql;
  985. // finally perform the update. If successful, return the updated record
  986. if ($prepared and !$void_prepared) {
  987. // if this is the first time we've run this query
  988. // then we need to do the prepare, otherwise just execute
  989. if ($options['is_prepared'] != TRUE and
  990. !tripal_core_is_sql_prepared($options['statement_name'])) {
  991. $status = chado_query($psql);
  992. if (!$status) {
  993. watchdog('tripal_core', "tripal_core_chado_delete: not able to prepare '%name' statement for: %sql", array('%name' => $options['statement_name'], '%sql' => $sql), WATCHDOG_ERROR);
  994. return FALSE;
  995. }
  996. }
  997. $sql = "EXECUTE " . $options['statement_name'] . "(" . implode(", ", $pvalues) . ")";
  998. $resource = chado_query($sql, $ivalues);
  999. }
  1000. // if it's not a prepared statement then insert normally
  1001. else {
  1002. $resource = chado_query($sql, $uargs);
  1003. }
  1004. // finally perform the delete. If successful, return the updated record
  1005. $result = chado_query($sql, $dargs);
  1006. if ($result) {
  1007. return TRUE;
  1008. }
  1009. else {
  1010. watchdog('tripal_core', "Cannot delete record in $table table. Match:" . print_r($match, 1) . ". Values: ". print_r($values, 1), array(), 'WATCHDOG_ERROR');
  1011. return FALSE;
  1012. }
  1013. return FALSE;
  1014. }
  1015. /**
  1016. * Provides a generic routine for selecting data from a Chado table
  1017. *
  1018. * Use this function to perform a simple select from any Chado table.
  1019. *
  1020. * @param $table
  1021. * The name of the chado table for inserting
  1022. * @param $columns
  1023. * An array of column names
  1024. * @param $values
  1025. * An associative array containing the values for filtering the results. In the
  1026. * case where multiple values for the same time are to be selected an additional
  1027. * entry for the field should appear for each value
  1028. * @param $options
  1029. * An associative array of additional options where the key is the option
  1030. * and the value is the value of that option.
  1031. *
  1032. * Additional Options Include:
  1033. * - has_record
  1034. * Set this argument to 'TRUE' to have this function return a numeric
  1035. * value for the number of recrods rather than the array of records. this
  1036. * can be useful in 'if' statements to check the presence of particula records.
  1037. * - return_sql
  1038. * Set this to 'TRUE' to have this function return an array where the first
  1039. * element is the sql that would have been run and the second is an array of
  1040. * arguments.
  1041. * - case_insensitive_columns
  1042. * An array of columns to do a case insensitive search on.
  1043. * - regex_columns
  1044. * An array of columns where the value passed in should be treated as a regular expression
  1045. * - order_by
  1046. * An associative array containing the column names of the table as keys
  1047. * and the type of sort (i.e. ASC, DESC) as the values. The results in the
  1048. * query will be sorted by the key values in the direction listed by the value
  1049. * - statement_name: the name of the prepared statement to use. If the statement
  1050. * has not yet been prepared it will be prepared automatically. On subsequent
  1051. * calls with the same statement_name only an execute on the previously
  1052. * prepared statement will occur.
  1053. * - is_prepared: TRUE or FALSE. Whether or not the statement is prepared. By
  1054. * default if the statement is not prepared it will be automatically.
  1055. * However to avoid this check, which requires a database query you can
  1056. * set this value to true and the check will not be performed.
  1057. * - is_duplicate: TRUE or FALSE. Checks the values submited to see if
  1058. * they violate any of the unique constraints. If so, the record
  1059. * is returned, if not, FALSE is returned.
  1060. *
  1061. *
  1062. * @return
  1063. * A database query result resource, FALSE if the query was not executed
  1064. * correctly, an empty array if no records were matched, or the number of records
  1065. * in the dataset if $has_record is set.
  1066. * If the option 'is_duplicate' is provided and the record is a duplicate it
  1067. * will return the duplicated record. If the 'has_record' option is provided
  1068. * a value of TRUE will be returned if a record exists and FALSE will bee
  1069. * returned if there are not records.
  1070. *
  1071. * Example usage:
  1072. * @code
  1073. * $columns = array('feature_id', 'name');
  1074. * $values = array(
  1075. * 'organism_id' => array(
  1076. * 'genus' => 'Citrus',
  1077. * 'species' => array('sinensis', 'clementina'),
  1078. * ),
  1079. * 'uniquename' => 'orange1.1g000034m.g',
  1080. * 'type_id' => array (
  1081. * 'cv_id' => array (
  1082. * 'name' => 'sequence',
  1083. * ),
  1084. * 'name' => 'gene',
  1085. * 'is_obsolete' => 0
  1086. * ),
  1087. * );
  1088. * $options = array(
  1089. * 'statement_name' => 'sel_feature_genus_species_cvname'
  1090. * 'order_by' => array(
  1091. * 'name' => 'ASC'
  1092. * ),
  1093. * );
  1094. * $result = tripal_core_chado_select('feature',$columns,$values,$options);
  1095. * @endcode
  1096. * The above code selects a record from the feature table using the three fields
  1097. * that uniquely identify a feature. The $columns array simply lists the columns
  1098. * to select. The $values array is nested such that the organism is identified by
  1099. * way of the organism_id foreign key constraint by specifying the genus and
  1100. * species. The cvterm is also specified using its foreign key and the cv_id
  1101. * for the cvterm is nested as well. In the example above, two different species
  1102. * are allowed to match
  1103. *
  1104. * @ingroup tripal_chado_api
  1105. */
  1106. function tripal_core_chado_select($table, $columns, $values, $options = NULL) {
  1107. if (!is_array($values)) {
  1108. watchdog('tripal_core', 'Cannot pass non array as values for selecting.', array(),
  1109. WATCHDOG_ERROR);
  1110. return FALSE;
  1111. }
  1112. if (count($values)==0) {
  1113. watchdog('tripal_core', 'Cannot pass an empty array as values for selecting.', array(),
  1114. WATCHDOG_ERROR);
  1115. return FALSE;
  1116. }
  1117. if (!is_array($columns)) {
  1118. watchdog('tripal_core', 'Cannot pass non array as columns for selecting.', array(),
  1119. WATCHDOG_ERROR);
  1120. return FALSE;
  1121. }
  1122. if (count($columns)==0) {
  1123. watchdog('tripal_core', 'Cannot pass an empty array as columns for selecting.', array(),
  1124. WATCHDOG_ERROR);
  1125. return FALSE;
  1126. }
  1127. // set defaults for options. If we don't set defaults then
  1128. // we get memory leaks when we try to access the elements
  1129. if (!is_array($options)) {
  1130. $options = array();
  1131. }
  1132. if (!array_key_exists('case_insensitive_columns', $options)) {
  1133. $options['case_insensitive_columns'] = array();
  1134. }
  1135. if (!array_key_exists('regex_columns', $options)) {
  1136. $options['regex_columns'] = array();
  1137. }
  1138. if (!array_key_exists('order_by', $options)) {
  1139. $options['order_by'] = array();
  1140. }
  1141. if (!array_key_exists('is_prepared', $options)) {
  1142. $options['is_prepared'] = FALSE;
  1143. }
  1144. if (!array_key_exists('return_sql', $options)) {
  1145. $options['return_sql'] = FALSE;
  1146. }
  1147. if (!array_key_exists('has_record', $options)) {
  1148. $options['has_record'] = FALSE;
  1149. }
  1150. if (!array_key_exists('statement_name', $options)) {
  1151. $options['statement_name'] = FALSE;
  1152. }
  1153. if (!array_key_exists('is_duplicate', $options)) {
  1154. $options['is_duplicate'] = FALSE;
  1155. }
  1156. // if this is a prepared statement check to see if it has already been prepared
  1157. $prepared = FALSE;
  1158. if ($options['statement_name']) {
  1159. $prepared = TRUE;
  1160. // we need to get a persistent connection. If one exists this function
  1161. // will not recreate it, but if not it will create one and store it in
  1162. // a Drupal variable for reuse later.
  1163. $connection = tripal_db_persistent_chado();
  1164. // if we cannot get a connection the abandon the prepared statement
  1165. if (!$connection) {
  1166. $prepared = FALSE;
  1167. unset($options['statement_name']);
  1168. }
  1169. }
  1170. else {
  1171. //print "NO STATEMENT (select): $table\n";
  1172. //debug_print_backtrace();
  1173. }
  1174. // check that our columns and values arguments are proper arrays
  1175. if (!is_array($columns)) {
  1176. watchdog('tripal_core', 'the $columns argument for tripal_core_chado_select must be an array.');
  1177. return FALSE;
  1178. }
  1179. if (!is_array($values)) {
  1180. watchdog('tripal_core', 'the $values argument for tripal_core_chado_select must be an array.');
  1181. return FALSE;
  1182. }
  1183. // get the table description
  1184. $table_desc = tripal_core_get_chado_table_schema($table);
  1185. $select = '';
  1186. $from = '';
  1187. $where = '';
  1188. $args = array();
  1189. // if the 'use_unique' option is turned on then we want
  1190. // to remove all but unique keys
  1191. if ($options['is_duplicate'] and array_key_exists('unique keys', $table_desc)) {
  1192. $ukeys = $table_desc['unique keys'];
  1193. $has_results = 0;
  1194. // iterate through the unique constraints and reset the values and columns
  1195. // arrays to only include these fields
  1196. foreach ($ukeys as $cname => $fields) {
  1197. if ($has_results) {
  1198. continue;
  1199. }
  1200. $new_values = array();
  1201. $new_columns = array();
  1202. $new_options = array();
  1203. $uq_sname = "uq_" . $table . "_";
  1204. $has_pkey = 0;
  1205. // include the primary key in the results returned
  1206. if (array_key_exists('primary key', $table_desc)) {
  1207. $has_pkey = 1;
  1208. $pkeys = $table_desc['primary key'];
  1209. foreach ($pkeys as $index => $key) {
  1210. array_push($new_columns, $key);
  1211. }
  1212. }
  1213. // recreate the $values and $columns arrays
  1214. foreach ($fields as $field) {
  1215. if (array_key_exists($field, $values)) {
  1216. $new_values[$field] = $values[$field];
  1217. $uq_sname .= substr($field, 0, 2);
  1218. // if there is no primary key then use the unique contraint fields
  1219. if (!$has_pkey) {
  1220. array_push($new_columns, $field);
  1221. }
  1222. }
  1223. // if the field doesn't exist in the values array then
  1224. // substitute any default values
  1225. elseif (array_key_exists('default', $table_desc['fields'][$field])) {
  1226. $new_values[$field] = $table_desc['fields'][$field]['default'];
  1227. $uq_sname .= substr($field, 0, 2);
  1228. if (!$has_pkey) {
  1229. array_push($new_columns, $field);
  1230. }
  1231. }
  1232. // if there is no value (default or otherwise) check if this field is
  1233. // allowed to be null
  1234. elseif (!$table_desc['fields'][$field]['not null']) {
  1235. $new_values[$field] = NULL;
  1236. $uq_sname .= "n".substr($field, 0, 2);
  1237. if (!$has_pkey) {
  1238. array_push($new_columns, $field);
  1239. }
  1240. }
  1241. // if the array key doesn't exist in the values given by the caller
  1242. // and there is no default value then we cannot check if the record
  1243. // is a duplicate so return FALSE
  1244. else {
  1245. watchdog('tripal_core', "tripal_core_chado_select: There is no value for %field"
  1246. ." thus we cannot check if this record is unique",
  1247. array('%field' => $field), WATCHDOG_ERROR);
  1248. return FALSE;
  1249. }
  1250. }
  1251. $new_options['statement_name'] = $uq_sname;
  1252. $results = tripal_core_chado_select($table, $new_columns, $new_values, $new_options);
  1253. // if we have a duplicate record then return the results
  1254. if (count($results) > 0) {
  1255. $has_results = 1;
  1256. }
  1257. unset($new_columns);
  1258. unset($new_values);
  1259. unset($new_options);
  1260. }
  1261. if ($options['has_record'] and $has_results) {
  1262. return TRUE;
  1263. }
  1264. else {
  1265. return $results;
  1266. }
  1267. }
  1268. foreach ($values as $field => $value) {
  1269. // make sure the field is in the table description. If not then return an error
  1270. // message
  1271. if (!array_key_exists($field, $table_desc['fields'])) {
  1272. watchdog('tripal_core', "tripal_core_chado_select: The field '%field' does not exist ".
  1273. "for the table '%table'. Cannot perform query. Values: %array",
  1274. array('%field' => $field, '%table' => $table, '%array' => print_r($values, 1)), WATCHDOG_ERROR);
  1275. return array();
  1276. }
  1277. $select[] = $field;
  1278. if (is_array($value)) {
  1279. // if the user has specified multiple values for matching then this we
  1280. // want to catch that and save them in our $where array, otherwise
  1281. // we'll descend for a foreign key relationship
  1282. if (array_values($value) === $value) {
  1283. $where[$field] = $value;
  1284. }
  1285. else {
  1286. // select the value from the foreign key relationship for this value
  1287. $foreign_options = array(
  1288. 'regex_columns' => $options['regex_columns'],
  1289. 'case_insensitive_columns' => $options['case_insensitive_columns']
  1290. );
  1291. if (array_key_exists('statement_name', $options) and $options['statement_name']) {
  1292. // add the fk relationship info to the prepared statement name so that
  1293. // we can prepare the selects run by the recrusive tripal_core_chado_get_foreign_key
  1294. // function. we need the statement name to be unique so take the first two characters of each column
  1295. $fk_sname = "fk_" . $table . "_" . $field;
  1296. foreach ($value as $k => $v) {
  1297. $fk_sname .= substr($k, 0, 2);
  1298. }
  1299. $foreign_options['statement_name'] = $fk_sname;
  1300. }
  1301. $results = tripal_core_chado_get_foreign_key($table_desc, $field, $value, $foreign_options);
  1302. if (!$results or count($results)==0) {
  1303. // foreign key records are required
  1304. // thus if none matched then return FALSE and alert the admin through watchdog
  1305. /*watchdog('tripal_core',
  1306. 'tripal_core_chado_select: no record in the table referenced by the foreign key (!field) exists. tripal_core_chado_select table=!table, columns=!columns, values=!values',
  1307. array('!table' => $table,
  1308. '!columns' => '<pre>' . print_r($columns, TRUE) . '</pre>',
  1309. '!values' => '<pre>' . print_r($values, TRUE) . '</pre>',
  1310. '!field' => $field,
  1311. ),
  1312. WATCHDOG_WARNING);*/
  1313. return array();
  1314. }
  1315. else {
  1316. $where[$field] = $results;
  1317. }
  1318. }
  1319. }
  1320. else {
  1321. // need to catch a 0 and make int if integer field
  1322. // but we don't want to catch a NULL
  1323. if ($value === NULL) {
  1324. $where[$field] = NULL;
  1325. }
  1326. elseif ($table_desc['fields'][$field]['type'] == 'int') {
  1327. $where[$field][] = (int) $value;
  1328. }
  1329. else {
  1330. $where[$field][] = $value;
  1331. }
  1332. }
  1333. }
  1334. // now build the SQL and prepared SQL statements. We may not use
  1335. // the prepared statement if it wasn't requested in the options or if the
  1336. // argument in a where statement has multiple values.
  1337. if (empty($where)) {
  1338. // sometimes want to select everything
  1339. $sql = "SELECT " . implode(', ', $columns) . " ";
  1340. $sql .= "FROM {$table} ";
  1341. // we don't prepare a statement if there is no where clause
  1342. $prepared = FALSE;
  1343. }
  1344. else {
  1345. $sql = "SELECT " . implode(', ', $columns) . " ";
  1346. $sql .= "FROM {$table} ";
  1347. $sql .= "WHERE ";
  1348. $psql = $sql; // prepared SQL statement;
  1349. $i = 1;
  1350. $pvalues = array();
  1351. $itypes = array();
  1352. foreach ($where as $field => $value) {
  1353. // if we have multiple values returned then we need an 'IN' statement
  1354. // in our where statement
  1355. if (count($value) > 1) {
  1356. $sql .= "$field IN (" . db_placeholders($value, 'varchar') . ") AND ";
  1357. foreach ($value as $v) {
  1358. $args[] = $v;
  1359. // we can't do a prepared statement with an 'IN' statement in a
  1360. // where clause because we can't guarantee we'll always have the
  1361. // same number of elements.
  1362. $prepared = FALSE;
  1363. }
  1364. }
  1365. // if we have a null value then we need an IS NULL in our where statement
  1366. elseif ($value === NULL) {
  1367. $sql .= "$field IS NULL AND ";
  1368. $psql .= "$field IS NULL AND ";
  1369. // Need to remove one from the argument count b/c nulls don't add an argument
  1370. $i--;
  1371. }
  1372. // if we have a single value then we need an = in our where statement
  1373. else {
  1374. $operator = '=';
  1375. if (in_array($field, $options['regex_columns'])) {
  1376. $operator = '~*';
  1377. }
  1378. // get the types for the prepared statement. First check if the type
  1379. // is an integer
  1380. if (strcasecmp($table_desc['fields'][$field]['type'], 'serial')==0 OR
  1381. strcasecmp($table_desc['fields'][$field]['type'], 'int')==0 OR
  1382. strcasecmp($table_desc['fields'][$field]['type'], 'integer')==0) {
  1383. $sql .= "$field $operator %d AND ";
  1384. $psql .= "$field $operator \$" . $i . " AND ";
  1385. $args[] = $value[0];
  1386. // set the variables needed for the prepared statement
  1387. $idatatypes[] = 'int';
  1388. $itypes[] = '%d';
  1389. $pvalues[] = $value[0];
  1390. }
  1391. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
  1392. $sql .= "$field $operator %s AND ";
  1393. $psql .= "$field $operator \$" . $i . " AND ";
  1394. $args[] = $value[0];
  1395. // set the variables needed for the prepared statement
  1396. $idatatypes[] = 'bool';
  1397. $itypes[] = '%d';
  1398. $pvalues[] = $value[0];
  1399. }
  1400. elseif (strcasecmp($table_desc['fields'][$field]['type'], 'float')==0) {
  1401. $sql .= "$field $operator %s AND ";
  1402. $psql .= "$field $operator \$" . $i . " AND ";
  1403. $args[] = $value[0];
  1404. // set the variables needed for the prepared statement
  1405. $idatatypes[] = 'numeric';
  1406. $itypes[] = '%f';
  1407. $pvalues[] = $value[0];
  1408. }
  1409. // else the type is a text
  1410. else {
  1411. if (in_array($field, $options['case_insensitive_columns'])) {
  1412. $sql .= "lower($field) $operator lower('%s') AND ";
  1413. $psql .= "lower($field) $operator lower('\$" . $i . "') AND ";
  1414. $args[] = $value;
  1415. }
  1416. else {
  1417. $sql .= "$field $operator '%s' AND ";
  1418. $psql .= "$field $operator \$" . $i . " AND ";
  1419. $args[] = $value[0];
  1420. }
  1421. // set the variables needed for the prepared statement
  1422. $idatatypes[] = 'text';
  1423. $itypes[] = "'%s'";
  1424. $pvalues[] = $value[0];
  1425. }
  1426. }
  1427. $i++;
  1428. } // end foreach item in where clause
  1429. $sql = drupal_substr($sql, 0, -4); // get rid of the trailing 'AND '
  1430. $psql = drupal_substr($psql, 0, -4); // get rid of the trailing 'AND '
  1431. } // end if(empty($where)){ } else {
  1432. // finally add any ordering of the results to the SQL statement
  1433. if (count($options['order_by']) > 0) {
  1434. $sql .= " ORDER BY ";
  1435. $psql .= " ORDER BY ";
  1436. foreach ($options['order_by'] as $field => $dir) {
  1437. $sql .= "$field $dir, ";
  1438. $psql .= "$field $dir, ";
  1439. }
  1440. $sql = drupal_substr($sql, 0, -2); // get rid of the trailing ', '
  1441. $psql = drupal_substr($psql, 0, -2); // get rid of the trailing ', '
  1442. }
  1443. // finish constructing the prepared SQL statement
  1444. if ($options['statement_name']) {
  1445. $psql = "PREPARE " . $options['statement_name'] . " (" . implode(', ', $idatatypes) . ") AS " . $psql;
  1446. }
  1447. // if the caller has requested the SQL rather than the results...
  1448. // which happens in the case of wanting to use the Drupal pager, then do so
  1449. if ($options['return_sql'] == TRUE) {
  1450. return array('sql' => $sql, 'args' => $args);
  1451. }
  1452. // prepare the statement
  1453. if ($prepared) {
  1454. // if this is the first time we've run this query
  1455. // then we need to do the prepare, otherwise just execute
  1456. if ($options['is_prepared'] != TRUE) {
  1457. $status = tripal_core_chado_prepare($options['statement_name'], $psql, $idatatypes);
  1458. if (!$status) {
  1459. return FALSE;
  1460. }
  1461. }
  1462. $sql = "EXECUTE " . $options['statement_name'] . "(" . implode(", ", $itypes) . ")";
  1463. // WARNING: This call creates a memory leak: if you remove the $pvalues it doesn't
  1464. // do this. Got to find out what's causing this.
  1465. $resource = tripal_core_chado_execute_prepared($options['statement_name'], $sql, $pvalues);
  1466. }
  1467. else {
  1468. $resource = chado_query($sql, $args);
  1469. }
  1470. // format results into an array
  1471. $results = array();
  1472. while ($r = db_fetch_object($resource)) {
  1473. $results[] = $r;
  1474. }
  1475. if ($options['has_record']) {
  1476. return count($results);
  1477. }
  1478. return $results;
  1479. }
  1480. /**
  1481. * Gets the value of a foreign key relationship
  1482. *
  1483. * This function is used by tripal_core_chado_select, tripal_core_chado_insert,
  1484. * and tripal_core_chado_update to iterate through the associate array of
  1485. * values that gets passed to each of those routines. The values array
  1486. * is nested where foreign key contraints are used to specify a value that. See
  1487. * documentation for any of those functions for further information.
  1488. *
  1489. * @param $table_desc
  1490. * A table description for the table with the foreign key relationship to be identified generated by
  1491. * hook_chado_<table name>_schema()
  1492. * @param $field
  1493. * The field in the table that is the foreign key.
  1494. * @param $values
  1495. * An associative array containing the values
  1496. * @param $options
  1497. * An associative array of additional options where the key is the option
  1498. * and the value is the value of that option. These options are passed on to tripal_core_chado_select.
  1499. *
  1500. * Additional Options Include:
  1501. * - case_insensitive_columns
  1502. * An array of columns to do a case insensitive search on.
  1503. * - regex_columns
  1504. * An array of columns where the value passed in should be treated as a regular expression
  1505. *
  1506. * @return
  1507. * A string containg the results of the foreign key lookup, or FALSE if failed.
  1508. *
  1509. * Example usage:
  1510. * @code
  1511. *
  1512. * $values = array(
  1513. * 'genus' => 'Citrus',
  1514. * 'species' => 'sinensis',
  1515. * );
  1516. * $value = tripal_core_chado_get_foreign_key('feature', 'organism_id',$values);
  1517. *
  1518. * @endcode
  1519. * The above code selects a record from the feature table using the three fields
  1520. * that uniquely identify a feature. The $columns array simply lists the columns
  1521. * to select. The $values array is nested such that the organism is identified by
  1522. * way of the organism_id foreign key constraint by specifying the genus and
  1523. * species. The cvterm is also specified using its foreign key and the cv_id
  1524. * for the cvterm is nested as well.
  1525. *
  1526. * @ingroup tripal_chado_api
  1527. */
  1528. function tripal_core_chado_get_foreign_key($table_desc, $field, $values, $options = NULL) {
  1529. // set defaults for options. If we don't set defaults then
  1530. // we get memory leaks when we try to access the elements
  1531. if (!is_array($options)) {
  1532. $options = array();
  1533. }
  1534. if (!array_key_exists('case_insensitive_columns', $options)) {
  1535. $options['case_insensitive_columns'] = array();
  1536. }
  1537. if (!array_key_exists('regex_columns', $options)) {
  1538. $options['regex_columns'] = array();
  1539. }
  1540. // get the list of foreign keys for this table description and
  1541. // iterate through those until we find the one we're looking for
  1542. $fkeys = '';
  1543. if (array_key_exists('foreign keys', $table_desc)) {
  1544. $fkeys = $table_desc['foreign keys'];
  1545. }
  1546. if ($fkeys) {
  1547. foreach ($fkeys as $name => $def) {
  1548. if (is_array($def['table'])) {
  1549. //foreign key was described 2X
  1550. $message = "The foreign key " . $name . " was defined twice. Please check modules "
  1551. ."to determine if hook_chado_schema_<version>_" . $table_desc['table'] . "() was "
  1552. ."implemented and defined this foreign key when it wasn't supposed to. Modules "
  1553. ."this hook was implemented in: " . implode(', ',
  1554. module_implements("chado_" . $table_desc['table'] . "_schema")) . ".";
  1555. watchdog('tripal_core', $message);
  1556. drupal_set_message(check_plain($message), 'error');
  1557. continue;
  1558. }
  1559. $table = $def['table'];
  1560. $columns = $def['columns'];
  1561. // iterate through the columns of the foreign key relationship
  1562. foreach ($columns as $left => $right) {
  1563. // does the left column in the relationship match our field?
  1564. if (strcmp($field, $left) == 0) {
  1565. // the column name of the foreign key matches the field we want
  1566. // so this is the right relationship. Now we want to select
  1567. $select_cols = array($right);
  1568. $result = tripal_core_chado_select($table, $select_cols, $values, $options);
  1569. $fields = array();
  1570. if ($result and count($result) > 0) {
  1571. foreach ($result as $obj) {
  1572. $fields[] = $obj->$right;
  1573. }
  1574. return $fields;
  1575. }
  1576. }
  1577. }
  1578. }
  1579. }
  1580. else {
  1581. // TODO: what do we do if we get to this point and we have a fk
  1582. // relationship expected but we don't have any definition for one in the
  1583. // table schema??
  1584. $version = tripal_core_get_chado_version(TRUE);
  1585. $message = t("There is no foreign key relationship defined for " . $field . ".
  1586. To define a foreign key relationship, determine the table this foreign
  1587. key referrs to (<foreign table>) and then implement
  1588. hook_chado_chado_schema_v<version>_<foreign table>(). See
  1589. tripal_feature_chado_v1_2_schema_feature for an example. Chado version: $version");
  1590. watchdog('tripal_core', $message);
  1591. drupal_set_message(check_plain($message), 'error');
  1592. }
  1593. return array();
  1594. }
  1595. /**
  1596. * Generates an object containing the full details of a record(s) in chado.
  1597. *
  1598. * This differs from the objects returned by tripal_core_chado_select in so far as all foreign key
  1599. * relationships have been followed meaning you have more complete details. Thus this function
  1600. * should be used whenever you need a full variable and tripal_core_chado_select should be used if
  1601. * you only case about a few columns.
  1602. *
  1603. * @param $table
  1604. * The name of the base table to generate a variable for
  1605. * @param $values
  1606. * A select values array that selects the records you want from the base table
  1607. * (this has the same form as tripal_core_chado_select)
  1608. * @param $base_options
  1609. * An array containing options for the base table. For example, an
  1610. * option of 'order_by' may be used to sort results in the base table
  1611. * if more than one are returned. The options must be compatible with
  1612. * the options accepted by the tripal_core_chado_select() function.
  1613. * Additionally, These options are available for this function:
  1614. * -return_array:
  1615. * can be provided to force the function to always return an array. Default
  1616. * behavior is to return a single record if only one record exists or to return
  1617. * an array if multiple records exist.
  1618. * - include_fk:
  1619. * an array of FK relationships to follow. By default, the
  1620. * tripal_core_chado_select function will follow all FK relationships but this
  1621. * may generate more queries then is desired slowing down this function call when
  1622. * there are lots of FK relationships to follow. Provide an array specifying the
  1623. * fields to include. For example, if expanding a property table (e.g. featureprop)
  1624. * and you want the CV and accession but do not want the DB the following
  1625. * array would work:
  1626. *
  1627. * $table_options = array(
  1628. * 'include_fk' => array(
  1629. * 'type_id' => array(
  1630. * 'cv_id' => 1,
  1631. * 'dbxref_id' => 1,
  1632. * )
  1633. * )
  1634. * );
  1635. *
  1636. * The above array will expand the 'type_id' of the property table but only
  1637. * further expand the cv_id and the dbxref_id and will go no further.
  1638. * @return
  1639. * Either an object (if only one record was selected from the base table)
  1640. * or an array of objects (if more than one record was selected from the base table).
  1641. * If the option 'return_array' is provided the function always returns an array.
  1642. *
  1643. * Example Usage:
  1644. * @code
  1645. $values = array(
  1646. 'name' => 'Medtr4g030710'
  1647. );
  1648. $features = tripal_core_generate_chado_var('feature', $values);
  1649. * @endcode
  1650. * This will return an object if there is only one feature with the name Medtr4g030710 or it will
  1651. * return an array of feature objects if more than one feature has that name.
  1652. *
  1653. * Note to Module Designers: Fields can be excluded by default from these objects by implementing
  1654. * one of the following hooks:
  1655. * - hook_exclude_field_from_tablename_by_default (where tablename is the name of the table):
  1656. * This hook allows you to add fields to be excluded on a per table basis. Simply implement
  1657. * this hook to return an array of fields to be excluded. For example:
  1658. * @code
  1659. mymodule_exclude_field_from_feature_by_default() {
  1660. return array('residues' => TRUE);
  1661. }
  1662. * @endcode
  1663. * will ensure that feature.residues is ecluded from a feature object by default.
  1664. * - hook_exclude_type_by_default:
  1665. * This hook allows you to exclude fields from all tables that are of a given postgresql field
  1666. * type. Simply implement this hook to return an array of postgresql types mapped to criteria.
  1667. * Then all fields of that type where the criteria supplied returns TRUE will be excluded from
  1668. * any table. Tokens available in criteria are &gt;field_value&lt; and &gt;field_name&lt; . For example:
  1669. * @code
  1670. mymodule_exclude_type_by_default() {
  1671. return array('text' => 'length(&gt;field_value&lt; ) > 50');
  1672. }
  1673. * @endcode
  1674. * 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.
  1675. *
  1676. * @ingroup tripal_chado_api
  1677. */
  1678. function tripal_core_generate_chado_var($table, $values, $base_options = array()) {
  1679. $all = new stdClass();
  1680. $return_array = 0;
  1681. if (array_key_exists('return_array', $base_options)) {
  1682. $return_array = 1;
  1683. }
  1684. $include_fk = 0;
  1685. if (array_key_exists('include_fk', $base_options)) {
  1686. $include_fk = $base_options['include_fk'];
  1687. }
  1688. // get description for the current table----------------------------------------------------------
  1689. $table_desc = tripal_core_get_chado_table_schema($table);
  1690. if (!$table_desc or count($table_desc) == 0) {
  1691. watchdog('tripal_core', "tripal_core_generate_chado_var: The table '%table' has not been defined. ".
  1692. "and cannot be expanded. If this is a custom table, please add it using the Tripal ".
  1693. "custom table interface.", array('%table' => $table), WATCHDOG_ERROR);
  1694. if ($return_array) {
  1695. return array();
  1696. }
  1697. return FALSE;
  1698. }
  1699. $table_primary_key = $table_desc['primary key'][0];
  1700. $table_columns = array_keys($table_desc['fields']);
  1701. // Expandable fields without value needed for criteria--------------------------------------------
  1702. $all->expandable_fields = array();
  1703. if ($table_desc['referring_tables']) {
  1704. $all->expandable_tables = $table_desc['referring_tables'];
  1705. }
  1706. else {
  1707. $all->expandable_tables = array();
  1708. }
  1709. $all->expandable_nodes = array();
  1710. // Get fields to be removed by name.................................
  1711. $fields_to_remove = module_invoke_all('exclude_field_from_' . $table . '_by_default');
  1712. foreach ($fields_to_remove as $field_name => $criteria) {
  1713. //replace &gt;field_name&lt; with the current field name &
  1714. $criteria = preg_replace('/&gt;field_name&lt; /', addslashes($field_name), $criteria);
  1715. // if field_value needed we can't deal with this field yet
  1716. if (preg_match('/&gt;field_value&lt; /', $criteria)) {
  1717. break;
  1718. }
  1719. //if criteria then remove from query
  1720. // @coder-ignore: only module designers can populate $criteria -not security risk
  1721. $success = drupal_eval('<?php return ' . $criteria . '; ?>');
  1722. // watchdog('tripal_core',
  1723. // 'Evaluating criteria (%criteria) for field %field in tripal_core_generate_chado_var for %table evaluated to %success',
  1724. // array('%table' => $table, '%criteria'=>$criteria, '%field' => $field_name, '%success'=>$success),
  1725. // WATCHDOG_NOTICE
  1726. // );
  1727. if ($success) {
  1728. unset($table_columns[array_search($field_name, $table_columns)]);
  1729. unset($fields_to_remove[$field_name]);
  1730. $all->expandable_fields[] = $table . '.' . $field_name;
  1731. }
  1732. }
  1733. //Get fields to be removed by type................................
  1734. $types_to_remove = module_invoke_all('exclude_type_by_default');
  1735. $field_types = array();
  1736. foreach ($table_desc['fields'] as $field_name => $field_array) {
  1737. $field_types[$field_array['type']][] = $field_name;
  1738. }
  1739. foreach ($types_to_remove as $field_type => $criteria) {
  1740. // if there are fields of that type to remove
  1741. if (is_array($field_types[$field_type])) {
  1742. //replace &gt;field_name&lt; with the current field name &
  1743. $criteria = preg_replace('/&gt;field_name&lt; /', addslashes($field_name), $criteria);
  1744. foreach ($field_types[$field_type] as $field_name) {
  1745. // if field_value needed we can't deal with this field yet
  1746. if (preg_match('/&gt;field_value&lt; /', $criteria)) {
  1747. $fields_to_remove[$field_name] = $criteria;
  1748. continue;
  1749. }
  1750. // if field_value needed we can't deal with this field yet
  1751. if (preg_match('/&gt;field_value&lt; /', $criteria)) {
  1752. break;
  1753. }
  1754. //if criteria then remove from query
  1755. // @coder-ignore: only module designers can populate $criteria -not security risk
  1756. $success = drupal_eval('<?php return ' . $criteria . '; ?>');
  1757. // watchdog('tripal_core',
  1758. // 'Evaluating criteria (%criteria) for field %field of $type in tripal_core_generate_chado_var for %table evaluated to %success',
  1759. // array('%table'=>$table, '%criteria'=>$criteria, '%field'=>$field_name, '%type'=>$field_type, '%success'=>$success),
  1760. // WATCHDOG_NOTICE
  1761. // );
  1762. if ($success) {
  1763. unset($table_columns[array_search($field_name, $table_columns)]);
  1764. $all->expandable_fields[] = $table . '.' . $field_name;
  1765. }
  1766. } //end of foreach field of that type
  1767. }
  1768. } //end of foreach type to be removed
  1769. // get the values for the record in the current table---------------------------------------------
  1770. $results = tripal_core_chado_select($table, $table_columns, $values, $base_options);
  1771. if ($results) {
  1772. foreach ($results as $key => $object) {
  1773. // Add empty expandable_x arrays
  1774. $object->expandable_fields = $all->expandable_fields;
  1775. $object->expandable_tables = $all->expandable_tables;
  1776. $object->expandable_nodes = $all->expandable_nodes;
  1777. // add curent table
  1778. $object->tablename = $table;
  1779. // check if the current table maps to a node type-----------------------------------------------
  1780. // if this table is connected to a node there will be a chado_tablename table in drupal
  1781. if (db_table_exists('chado_' . $table)) {
  1782. // that has a foreign key to this one ($table_desc['primary key'][0]
  1783. // and to the node table (nid)
  1784. $sql = "SELECT %s, nid FROM {chado_%s} WHERE %s=%d";
  1785. $mapping = db_fetch_object(db_query($sql, $table_primary_key, $table,
  1786. $table_primary_key, $object->{$table_primary_key}));
  1787. if ($mapping->{$table_primary_key}) {
  1788. $object->nid = $mapping->nid;
  1789. $object->expandable_nodes[] = $table;
  1790. }
  1791. }
  1792. // remove any fields where criteria need to be evalulated---------------------------------------
  1793. foreach ($fields_to_remove as $field_name => $criteria) {
  1794. if (!isset($object->{$field_name})) {
  1795. break;
  1796. }
  1797. $criteria = preg_replace('/&gt;field_value&lt; /', addslashes($object->{$field_name}), $criteria);
  1798. //if criteria then remove from query
  1799. // @coder-ignore: only module designers can populate $criteria -not security risk
  1800. $success = drupal_eval('<?php return ' . $criteria . '; ?>');
  1801. // watchdog('tripal_core',
  1802. // 'Evaluating criteria (%criteria) for field %field in tripal_core_generate_chado_var for %table evaluated to %success',
  1803. // array('%table' => $table, '%criteria'=>$criteria, '%field' => $field_name, '%success'=>$success),
  1804. // WATCHDOG_NOTICE
  1805. // );
  1806. if ($success) {
  1807. unset($object->{$field_name});
  1808. $object->expandable_fields[] = $table . '.' . $field_name;
  1809. }
  1810. }
  1811. // recursively follow foreign key relationships nesting objects as we go------------------------
  1812. if ($table_desc['foreign keys']) {
  1813. foreach ($table_desc['foreign keys'] as $foreign_key_array) {
  1814. $foreign_table = $foreign_key_array['table'];
  1815. foreach ($foreign_key_array['columns'] as $foreign_key => $primary_key) {
  1816. // Note: Foreign key is the field in the current table whereas primary_key is the field in
  1817. // the table referenced by the foreign key
  1818. //Dont do anything if the foreign key is empty
  1819. if (empty($object->{$foreign_key})) {
  1820. continue;
  1821. }
  1822. if ($include_fk) {
  1823. // don't recurse if the callee has supplied an $fk_include list and this
  1824. // FK table is not in the list.
  1825. if (is_array($include_fk) and !array_key_exists($foreign_key, $include_fk)) {
  1826. continue;
  1827. }
  1828. // if we have the option but it is not an array then we don't recurse any furutehr
  1829. if (!is_array($include_fk)) {
  1830. continue;
  1831. }
  1832. }
  1833. // get the record from the foreign table
  1834. $foreign_values = array($primary_key => $object->{$foreign_key});
  1835. $options = array();
  1836. if (is_array($include_fk)) {
  1837. $options['include_fk'] = $include_fk[$foreign_key];
  1838. }
  1839. $foreign_object = tripal_core_generate_chado_var($foreign_table, $foreign_values, $options);
  1840. // add the foreign record to the current object in a nested manner
  1841. $object->{$foreign_key} = $foreign_object;
  1842. // Flatten expandable_x arrays so only in the bottom object
  1843. if (is_array($object->{$foreign_key}->expandable_fields)) {
  1844. $object->expandable_fields = array_merge(
  1845. $object->expandable_fields,
  1846. $object->{$foreign_key}->expandable_fields
  1847. );
  1848. unset($object->{$foreign_key}->expandable_fields);
  1849. }
  1850. if (is_array($object->{$foreign_key}->expandable_tables)) {
  1851. $object->expandable_tables = array_merge(
  1852. $object->expandable_tables,
  1853. $object->{$foreign_key}->expandable_tables
  1854. );
  1855. unset($object->{$foreign_key}->expandable_tables);
  1856. }
  1857. if (is_array($object->{$foreign_key}->expandable_nodes)) {
  1858. $object->expandable_nodes = array_merge(
  1859. $object->expandable_nodes,
  1860. $object->{$foreign_key}->expandable_nodes
  1861. );
  1862. unset($object->{$foreign_key}->expandable_nodes);
  1863. }
  1864. }
  1865. }
  1866. $results[$key] = $object;
  1867. }
  1868. }
  1869. }
  1870. // check only one result returned
  1871. if (!$return_array) {
  1872. if (sizeof($results) == 1) {
  1873. // add results to object
  1874. return $results[0];
  1875. }
  1876. elseif (!empty($results)) {
  1877. return $results;
  1878. }
  1879. else {
  1880. // no results returned
  1881. }
  1882. }
  1883. // the caller has requested results are always returned as
  1884. // an array
  1885. else {
  1886. if (!$results) {
  1887. return array();
  1888. }
  1889. else {
  1890. return $results;
  1891. }
  1892. }
  1893. }
  1894. /**
  1895. * Retrieves fields/tables/nodes that were excluded by default from a variable and adds them
  1896. *
  1897. * This function exists to allow tripal_core_generate_chado_var() to excldue some
  1898. * fields/tables/nodes from the default form of a variable without making it extremely difficult for
  1899. * the tripal admin to get at these variables if he/she wants them.
  1900. *
  1901. * @param $object
  1902. * This must be an object generated using tripal_core_generate_chado_var()
  1903. * @param $type
  1904. * Must be one of 'field', 'table', 'node'. Indicates what is being expanded.
  1905. * @param $to_expand
  1906. * The name of the field/table/node to be expanded
  1907. * @param $table_options
  1908. * - order_by:
  1909. * An array containing options for the base table. For example, an
  1910. * option of 'order_by' may be used to sort results in the base table
  1911. * if more than one are returned. The options must be compatible with
  1912. * the options accepted by the tripal_core_chado_select() function.
  1913. * - return_array:
  1914. * Additionally, The option 'return_array' can be provided to force
  1915. * the function to expand tables as an array. Default behavior is to expand
  1916. * a table as single record if only one record exists or to expand as an array if
  1917. * multiple records exist.
  1918. * - include_fk:
  1919. * an array of FK relationships to follow. By default, the
  1920. * tripal_core_chado_select function will follow all FK relationships but this
  1921. * may generate more queries then is desired slowing down this function call when
  1922. * there are lots of FK relationships to follow. Provide an array specifying the
  1923. * fields to include. For example, if expanding a property table (e.g. featureprop)
  1924. * and you want the CV and accession but do not want the DB the following
  1925. * array would work:
  1926. * $table_options = array(
  1927. * 'include_fk' => array(
  1928. * 'type_id' => array(
  1929. * 'cv_id' => 1,
  1930. * 'dbxref_id' => 1,
  1931. * )
  1932. * )
  1933. * );
  1934. *
  1935. * The above array will expand the 'type_id' of the property table but only
  1936. * further expand the cv_id and the dbxref_id and will go no further.
  1937. * @return
  1938. * A chado object supplemented with the field/table/node requested to be expanded.
  1939. * If the type is a table and it has already been expanded no changes is made to the
  1940. * returned object
  1941. *
  1942. * Example Usage:
  1943. * @code
  1944. // Get a chado object to be expanded
  1945. $values = array(
  1946. 'name' => 'Medtr4g030710'
  1947. );
  1948. $features = tripal_core_generate_chado_var('feature', $values);
  1949. // Expand the organism node
  1950. $feature = tripal_core_expand_chado_vars($feature, 'node', 'organism');
  1951. // Expand the feature.residues field
  1952. $feature = tripal_core_expand_chado_vars($feature, 'field', 'feature.residues');
  1953. // Expand the feature properties (featureprop table)
  1954. $feature = tripal_core_expand_chado_vars($feature, 'table', 'featureprop');
  1955. * @endcode
  1956. *
  1957. * @ingroup tripal_chado_api
  1958. */
  1959. function tripal_core_expand_chado_vars($object, $type, $to_expand, $table_options = array()) {
  1960. $base_table = $object->tablename;
  1961. // check to see if we are expanding an array of objects
  1962. if (is_array($object)) {
  1963. foreach ($object as $index => $o) {
  1964. $object[$index] = tripal_core_expand_chado_vars($o, $type, $to_expand);
  1965. }
  1966. return $object;
  1967. }
  1968. switch ($type) {
  1969. case "field": //--------------------------------------------------------------------------------
  1970. if (preg_match('/(\w+)\.(\w+)/', $to_expand, $matches)) {
  1971. $tablename = $matches[1];
  1972. $fieldname = $matches[2];
  1973. $table_desc = tripal_core_get_chado_table_schema($tablename);
  1974. $values = array();
  1975. foreach ($table_desc['primary key'] as $key) {
  1976. $values[$key] = $object->{$key};
  1977. }
  1978. if ($base_table == $tablename) {
  1979. //get the field
  1980. $results = tripal_core_chado_select($tablename, array($fieldname), $values);
  1981. $object->{$fieldname} = $results[0]->{$fieldname};
  1982. $object->expanded = $to_expand;
  1983. }
  1984. else {
  1985. //We need to recurse -the field is in a nested object
  1986. foreach ((array) $object as $field_name => $field_value) {
  1987. if (is_object($field_value)) {
  1988. $object->{$field_name} = tripal_core_expand_chado_vars(
  1989. $field_value,
  1990. 'field',
  1991. $to_expand
  1992. );
  1993. }
  1994. } //end of for each field in the current object
  1995. }
  1996. }
  1997. else {
  1998. watchdog('tripal_core', 'tripal_core_expand_chado_vars: Field (%field) not in the right format. ".
  1999. "It should be <tablename>.<fieldname>', WATCHDOG_ERROR);
  2000. }
  2001. break;
  2002. case "table": //--------------------------------------------------------------------------------
  2003. $foreign_table = $to_expand;
  2004. // don't expand the table it already is expanded
  2005. if (array_key_exists($foreign_table, $object)) {
  2006. return $object;
  2007. }
  2008. $foreign_table_desc = tripal_core_get_chado_table_schema($foreign_table);
  2009. // If it's connected to the base table
  2010. if ($foreign_table_desc['foreign keys'][$base_table]) {
  2011. foreach ($foreign_table_desc['foreign keys'][$base_table]['columns'] as $left => $right) {
  2012. if (!$object->{$right}) {
  2013. continue;
  2014. }
  2015. if (is_array($values)) {
  2016. $values = array_merge($values, array($left => $object->{$right}) );
  2017. }
  2018. else {
  2019. $values = array($left => $object->{$right});
  2020. }
  2021. // if a prepared statement is provide then generate a new name
  2022. $new_options = $table_options;
  2023. if (array_key_exists('statement_name', $table_options)) {
  2024. $new_options['statement_name'] = "exp_" . $foreign_table . "_" . substr($left, 0, 2) . substr($right, 0, 2);
  2025. }
  2026. $foreign_object = tripal_core_generate_chado_var($foreign_table, array($left => $object->{$right}), $new_options);
  2027. if ($foreign_object) {
  2028. // in the case where the foreign key relationships exists more
  2029. // than once with the same table we want to alter the
  2030. // array structure
  2031. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) {
  2032. if (!is_object($object->{$foreign_table})) {
  2033. $object->{$foreign_table} = new stdClass();
  2034. }
  2035. $object->{$foreign_table}->{$left} = $foreign_object;
  2036. $object->expanded = $to_expand;
  2037. }
  2038. else {
  2039. $object->{$foreign_table} = $foreign_object;
  2040. $object->expanded = $to_expand;
  2041. }
  2042. }
  2043. // if the object returned is NULL then handle that
  2044. else {
  2045. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) {
  2046. $object->{$foreign_table}->{$left} = NULL;
  2047. }
  2048. else {
  2049. $object->{$foreign_table} = NULL;
  2050. }
  2051. }
  2052. }
  2053. }
  2054. else {
  2055. // We need to recurse -the table has a relationship to one of the nested objects
  2056. $did_expansion = 0;
  2057. foreach ((array) $object as $field_name => $field_value) {
  2058. // if we have a nested object ->expand the table in it
  2059. if (is_object($field_value)) {
  2060. $did_expansion = 1;
  2061. $object->{$field_name} = tripal_core_expand_chado_vars($field_value, 'table', $foreign_table);
  2062. }
  2063. }
  2064. // if we did not expand this table we should return a message that the foreign table
  2065. // could not be expanded
  2066. if (!$did_expansion) {
  2067. watchdog('tripal_core', 'tripal_core_expand_chado_vars: Could not expand table, %table. It is ',
  2068. 'not in a foreign key relationship with the base object nor with any other expanded table. ' .
  2069. 'Check the table definition to ensure that a proper foreign key relationship is present.',
  2070. array('%table' => $foreign_table), WATCHDOG_ERROR);
  2071. }
  2072. }
  2073. break;
  2074. case "node": //---------------------------------------------------------------------------------
  2075. //if the node to be expanded is for our base table, then just expand it
  2076. if ($object->tablename == $to_expand) {
  2077. $node = node_load($object->nid);
  2078. if ($node) {
  2079. $object->expanded = $to_expand;
  2080. $node->expandable_fields = $object->expandable_fields;
  2081. unset($object->expandable_fields);
  2082. $node->expandable_tables = $object->expandable_tables;
  2083. unset($object->expandable_tables);
  2084. $node->expandable_nodes = $object->expandable_nodes;
  2085. unset($object->expandable_nodes);
  2086. $node->{$base_table} = $object;
  2087. $object = $node;
  2088. }
  2089. else {
  2090. watchdog('tripal_core', 'tripal_core_expand_chado_vars: No node matches the nid (%nid) supplied.',
  2091. array('%nid' => $object->nid), WATCHDOG_ERROR);
  2092. } //end of if node
  2093. }
  2094. else {
  2095. //We need to recurse -the node to expand is one of the nested objects
  2096. foreach ((array) $object as $field_name => $field_value) {
  2097. if (is_object($field_value)) {
  2098. $object->{$field_name} = tripal_core_expand_chado_vars(
  2099. $field_value,
  2100. 'node',
  2101. $to_expand
  2102. );
  2103. }
  2104. } //end of for each field in the current object
  2105. }
  2106. break;
  2107. default:
  2108. watchdog('tripal_core', 'tripal_core_expand_chado_vars: Unrecognized type (%type). Should be one of "field", "table", "node".',
  2109. array('%type' => $type), WATCHDOG_ERROR);
  2110. return FALSE;
  2111. }
  2112. //move extended array downwards-------------------------------------------------------------------
  2113. if (!$object->expanded) {
  2114. //if there's no extended field then go hunting for it
  2115. foreach ( (array)$object as $field_name => $field_value) {
  2116. if (is_object($field_value)) {
  2117. if (isset($field_value->expanded)) {
  2118. $object->expanded = $field_value->expanded;
  2119. unset($field_value->expanded);
  2120. }
  2121. }
  2122. }
  2123. }
  2124. //try again becasue now we might have moved it down
  2125. if ($object->expanded) {
  2126. $expandable_name = 'expandable_' . $type . 's';
  2127. if ($object->{$expandable_name}) {
  2128. $key_to_remove = array_search($object->expanded, $object->{$expandable_name});
  2129. unset($object->{$expandable_name}[$key_to_remove]);
  2130. unset($object->expanded);
  2131. }
  2132. else {
  2133. // if there is an expandable array then we've reached the base object
  2134. // if we get here and don't have anything expanded then something went wrong
  2135. // watchdog(
  2136. // 'tripal_core',
  2137. // 'tripal_core_expand_chado_vars: Unable to expand the %type %to_expand',
  2138. // array('%type'=>$type, '%to_expand'=>$to_expand),
  2139. // WATCHDOG_ERROR
  2140. // );
  2141. } //end of it we've reached the base object
  2142. }
  2143. return $object;
  2144. }
  2145. /**
  2146. * Implements hook_exclude_type_by_default()
  2147. *
  2148. * This hooks allows fields of a specified type that match a specified criteria to be excluded by
  2149. * default from any table when tripal_core_generate_chado_var() is called. Keep in mind that if
  2150. * fields are excluded by default they can always be expanded at a later date using
  2151. * tripal_core_expand_chado_vars().
  2152. *
  2153. * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using
  2154. * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are
  2155. * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can
  2156. * contain the following tokens:
  2157. * - &gt;field_name&lt;
  2158. * Replaced by the name of the field to be excluded
  2159. * - &gt;field_value&lt;
  2160. * Replaced by the value of the field in the current record
  2161. * Also keep in mind that if your criteria doesn't contain the &gt;field_value&lt; token then it will be
  2162. * evaluated before the query is executed and if the field is excluded it won't be included in the
  2163. * query.
  2164. *
  2165. * @return
  2166. * An array of type => criteria where the type is excluded if the criteria evaluates to TRUE
  2167. *
  2168. * @ingroup tripal_chado_api
  2169. */
  2170. function tripal_core_exclude_type_by_default() {
  2171. return array('text' => 'strlen("&gt;field_value&lt; ") > 100');
  2172. }
  2173. /**
  2174. * Implements hook_exclude_field_from_<tablename>_by_default()
  2175. *
  2176. * This hooks allows fields from a specified table that match a specified criteria to be excluded by
  2177. * default from any table when tripal_core_generate_chado_var() is called. Keep in mind that if
  2178. * fields are excluded by default they can always be expanded at a later date using
  2179. * tripal_core_expand_chado_vars().
  2180. *
  2181. * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using
  2182. * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are
  2183. * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can
  2184. * contain the following tokens:
  2185. * - &gt;field_name&lt;
  2186. * Replaced by the name of the field to be excluded
  2187. * - &gt;field_value&lt;
  2188. * Replaced by the value of the field in the current record
  2189. * Also keep in mind that if your criteria doesn't contain the &gt;field_value&lt; token then it will be
  2190. * evaluated before the query is executed and if the field is excluded it won't be included in the
  2191. * query.
  2192. *
  2193. * @return
  2194. * An array of type => criteria where the type is excluded if the criteria evaluates to TRUE
  2195. *
  2196. * @ingroup tripal_chado_api
  2197. */
  2198. function tripal_core_exclude_field_from_feature_by_default() {
  2199. return array();
  2200. }
  2201. /**
  2202. * Use this function instead of db_query() to avoid switching databases
  2203. * when making query to the chado database
  2204. *
  2205. * Will use a chado persistent connection if it already exists
  2206. *
  2207. * @param $sql
  2208. * The sql statement to execute
  2209. *
  2210. * @returns
  2211. * A database query result resource or FALSE if the query was not
  2212. * executed correctly
  2213. */
  2214. function chado_query($sql) {
  2215. global $persistent_chado;
  2216. $is_local = tripal_core_is_chado_local();
  2217. $args = func_get_args();
  2218. array_shift($args); // remove the $sql from the argument list
  2219. $sql = db_prefix_tables($sql);
  2220. if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
  2221. $args = $args[0];
  2222. }
  2223. // run the Drupal command to clean up the SQL
  2224. _db_query_callback($args, TRUE);
  2225. $sql = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $sql);
  2226. // add the chado schema to the table names if Chado is local to the Drupal database
  2227. if ($is_local) {
  2228. // TODO: this regular expression really needs to be removed as there are too many
  2229. // cases where it could break. Instead we need to surround tables with brackets
  2230. // like Drupal tables are and then find those and prefix those with chado.
  2231. $sql = preg_replace('/\n/', '', $sql); // remove carriage returns
  2232. // in the statement below we want to add 'chado.' to the beginning of each table
  2233. // we use the FROM keyword to look for tables, but FROM is also used in the
  2234. // 'substring' function of postgres. But since table names can't start with
  2235. // a number we exclude words numeric values. We also exclude tables that
  2236. // already have a schema prefix.
  2237. $sql = preg_replace('/FROM\s+([^0123456789\(][^\.]*?)(\s|$)/i', 'FROM chado.\1 ', $sql);
  2238. $sql = preg_replace('/INNER\s+JOIN\s+([^\.]*?)\s/i', 'INNER JOIN chado.\1 ', $sql);
  2239. }
  2240. //print "$sql\n";
  2241. // If the query is not a select then we still need to change the search_path
  2242. if (!preg_match('/^SELECT/i',$sql)) {
  2243. $change_path = TRUE;
  2244. }
  2245. // Execute the query on the chado database/schema
  2246. // Use the persistent chado connection if it already exists
  2247. if ($persistent_chado) {
  2248. $query = $sql;
  2249. // Duplicate the _db_query code in order to ensure that the drupal
  2250. // $active_db variable is not used in the pg_query command
  2251. // thus changed $active_db to $persistent_chado
  2252. // START COPY FROM _db_query in database.pgsql.inc
  2253. if (variable_get('dev_query', 0)) {
  2254. list($usec, $sec) = explode(' ', microtime());
  2255. $timer = (float) $usec + (float) $sec;
  2256. }
  2257. // if we're local we can just run the query
  2258. if ($is_local) {
  2259. if ($change_path) {
  2260. $previous_db = tripal_db_set_active('chado');
  2261. }
  2262. $last_result = pg_query($persistent_chado, $query);
  2263. if ($change_path) {
  2264. tripal_db_set_active($previous_db);
  2265. }
  2266. }
  2267. else {
  2268. $previous_db = tripal_db_set_active('chado');
  2269. $last_result = pg_query($persistent_chado, $query);
  2270. tripal_db_set_active($previous_db);
  2271. }
  2272. if (variable_get('dev_query', 0)) {
  2273. $bt = debug_backtrace();
  2274. $query = $bt[2]['function'] . "\n" . $query;
  2275. list($usec, $sec) = explode(' ', microtime());
  2276. $stop = (float) $usec + (float) $sec;
  2277. $diff = $stop - $timer;
  2278. $queries[] = array($query, $diff);
  2279. }
  2280. if ($last_result !== FALSE) {
  2281. return $last_result;
  2282. }
  2283. else {
  2284. // Indicate to drupal_error_handler that this is a database error.
  2285. ${DB_ERROR} = TRUE;
  2286. trigger_error(check_plain(pg_last_error($persistent_chado) . "\nquery: " . $query), E_USER_WARNING);
  2287. return FALSE;
  2288. }
  2289. // END COPY FROM _db_query in database.pgsql.inc
  2290. }
  2291. else {
  2292. // before running the query we want to prefix the table names with
  2293. // the chado schema. Previously use had to make changes to the
  2294. // search_path but that caused a lot of database calls and wasted
  2295. // resources during long jobs.
  2296. if ($is_local) {
  2297. if ($change_path) {
  2298. $previous_db = tripal_db_set_active('chado');
  2299. }
  2300. $results = _db_query($sql);
  2301. if ($change_path) {
  2302. tripal_db_set_active($previous_db);
  2303. }
  2304. }
  2305. else {
  2306. $previous_db = tripal_db_set_active('chado') ;
  2307. $results = _db_query($sql);
  2308. tripal_db_set_active($previous_db);
  2309. }
  2310. }
  2311. return $results;
  2312. }
  2313. /**
  2314. * Get chado id for a node. E.g, if you want to get 'analysis_id' from the
  2315. * 'analysis' table for a synced 'chado_analysis' node, use:
  2316. * $analysis_id = chado_get_id_for_node ('analysis', $node)
  2317. * Likewise,
  2318. * $organism_id = chado_get_id_for_node ('organism', $node)
  2319. * $feature_id = chado_get_id_for_node ('feature', $node)
  2320. */
  2321. function chado_get_id_for_node($table, $node) {
  2322. return db_result(db_query("SELECT %s_id FROM {chado_%s} WHERE nid = %d", $table, $table, $node->nid));
  2323. }
  2324. /**
  2325. * Get node id for a chado feature/organism/analysis. E.g, if you want to
  2326. * get the node id for an analysis, use:
  2327. * $nid = chado_get_node_id ('analysis', $analysis_id)
  2328. * Likewise,
  2329. * $nid = chado_get_node_id ('organism', $organism_id)
  2330. * $nid = chado_get_node_id ('feature', $feature_id)
  2331. */
  2332. function chado_get_node_id($table, $id) {
  2333. return db_result(db_query("SELECT nid FROM {chado_%s} WHERE %s_id = %d", $table, $table, $id));
  2334. }
  2335. /**
  2336. * Retrieve a property for a given base table record
  2337. *
  2338. * @param $basetable
  2339. * The base table for which the property should be retrieved. Thus to retrieve a property
  2340. * for a feature the basetable=feature and property is retrieved from featureprop
  2341. * @param $record_id
  2342. * The foriegn key field of the base table. This should be in integer.
  2343. * @param $property
  2344. * The cvterm name describing the type of properties to be retrieved
  2345. * @param $cv_name
  2346. * The name of the cv that the above cvterm is part of
  2347. *
  2348. * @return
  2349. * An array in the same format as that generated by the function
  2350. * tripal_core_generate_chado_var(). If only one record is returned it
  2351. * is a single object. If more than one record is returned then it is an array
  2352. * of objects
  2353. *
  2354. * @ingroup tripal_chado_api
  2355. */
  2356. function tripal_core_get_property($basetable, $record_id, $property, $cv_name) {
  2357. // get the foreign key for this property table
  2358. $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
  2359. $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
  2360. // construct the array of values to be selected
  2361. $values = array(
  2362. $fkcol => $record_id,
  2363. 'type_id' => array(
  2364. 'cv_id' => array(
  2365. 'name' => $cv_name,
  2366. ),
  2367. 'name' => $property,
  2368. 'is_obsolete' => 0
  2369. ),
  2370. );
  2371. $results = tripal_core_generate_chado_var($basetable . 'prop', $values);
  2372. $results = tripal_core_expand_chado_vars($results, 'field', $basetable . 'prop.value');
  2373. return $results;
  2374. }
  2375. /**
  2376. * Insert a property for a given base table. By default if the property already
  2377. * exists a new property is added with the next available rank. If
  2378. * $update_if_present argument is specified then the record will be updated if it
  2379. * exists rather than adding a new property.
  2380. *
  2381. * @param $basetable
  2382. * The base table for which the property should be inserted. Thus to insert a property
  2383. * for a feature the basetable=feature and property is inserted into featureprop
  2384. * @param $record_id
  2385. * The foriegn key field of the base table. This should be in integer.
  2386. * @param $property
  2387. * The cvterm name describing the type of properties to be inserted
  2388. * @param $cv_name
  2389. * The name of the cv that the above cvterm is part of
  2390. * @param $value
  2391. * The value of the property to be inserted (can be empty)
  2392. * @param $update_if_present
  2393. * A boolean indicating whether an existing record should be updated. If the
  2394. * property already exists and this value is not specified or is zero then
  2395. * a new property will be added with the next largest rank.
  2396. *
  2397. * @return
  2398. * Return True on Insert/Update and False otherwise
  2399. *
  2400. * @ingroup tripal_chado_api
  2401. */
  2402. function tripal_core_insert_property($basetable, $record_id, $property,
  2403. $cv_name, $value, $update_if_present = 0) {
  2404. // first see if the property already exists, if the user want's to update
  2405. // then we can do that, but otherwise we want to increment the rank and
  2406. // insert
  2407. $props = tripal_core_get_property($basetable, $record_id, $property, $cv_name);
  2408. if (!is_array($props)) {
  2409. $props = array($props);
  2410. }
  2411. $rank = 0;
  2412. if (count($props) > 0) {
  2413. if ($update_if_present) {
  2414. return tripal_core_update_property($basetable, $record_id, $property, $cv_name, $value);
  2415. }
  2416. else {
  2417. // iterate through the properties returned and check to see if the
  2418. // property with this value already exists if not, get the largest rank
  2419. // and insert the same property but with this new value
  2420. foreach ($props as $p) {
  2421. if ($p->rank > $rank) {
  2422. $rank = $p->rank;
  2423. }
  2424. if (strcmp($p->value, $value) == 0) {
  2425. return TRUE;
  2426. }
  2427. }
  2428. // now add 1 to the rank
  2429. $rank++;
  2430. }
  2431. }
  2432. else {
  2433. watchdog('tripal_core', "Cannot find property '!prop_name'.",
  2434. array('!prop_name' => $property), WATCHDOG_ERROR);
  2435. }
  2436. // get the foreign key for this property table
  2437. $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
  2438. $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
  2439. // construct the array of values to be inserted
  2440. $values = array(
  2441. $fkcol => $record_id,
  2442. 'type_id' => array(
  2443. 'cv_id' => array(
  2444. 'name' => $cv_name,
  2445. ),
  2446. 'name' => $property,
  2447. 'is_obsolete' => 0
  2448. ),
  2449. 'value' => $value,
  2450. 'rank' => $rank,
  2451. );
  2452. return tripal_core_chado_insert($basetable . 'prop', $values);
  2453. }
  2454. /**
  2455. * Update a property for a given base table record and property name. This
  2456. * function should be used only if one record of the property will be present.
  2457. * If the property name can have multiple entries (with increasing rank) then
  2458. * use the function named tripal_core_update_property_by_id
  2459. *
  2460. * @param $basetable
  2461. * The base table for which the property should be updated. The property table
  2462. * is constructed using a combination of the base table name and the suffix
  2463. * 'prop' (e.g. basetable = feature then property tabie is featureprop).
  2464. * @param $record_id
  2465. * The foreign key of the basetable to update a property for. This should be in integer.
  2466. * For example, if the basetable is 'feature' then the $record_id should be the feature_id
  2467. * @param $property
  2468. * The cvterm name of property to be updated
  2469. * @param $cv_name
  2470. * The name of the cv that the above cvterm is part of
  2471. * @param $value
  2472. * The value of the property to be inserted (can be empty)
  2473. * @param $insert_if_missing
  2474. * A boolean indicating whether a record should be inserted if one doesn't exist to update
  2475. *
  2476. * Note: The property to be updated is select via the unique combination of $record_id and
  2477. * $property and then it is updated with the supplied value
  2478. *
  2479. * @return
  2480. * Return True on Update/Insert and False otherwise
  2481. *
  2482. * @ingroup tripal_chado_api
  2483. */
  2484. function tripal_core_update_property($basetable, $record_id, $property,
  2485. $cv_name, $value, $insert_if_missing = 0) {
  2486. // first see if the property is missing (we can't update a missing property
  2487. $prop = tripal_core_get_property($basetable, $record_id, $property, $cv_name);
  2488. if (count($prop)==0) {
  2489. if ($insert_if_missing) {
  2490. return tripal_core_insert_property($basetable, $record_id, $property, $cv_name, $value);
  2491. }
  2492. else {
  2493. return FALSE;
  2494. }
  2495. }
  2496. // get the foreign key for this property table
  2497. $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
  2498. $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
  2499. // construct the array that will match the exact record to update
  2500. $match = array(
  2501. $fkcol => $record_id,
  2502. 'type_id' => array(
  2503. 'cv_id' => array(
  2504. 'name' => $cv_name,
  2505. ),
  2506. 'name' => $property,
  2507. ),
  2508. );
  2509. // construct the array of values to be updated
  2510. $values = array(
  2511. 'value' => $value,
  2512. );
  2513. return tripal_core_chado_update($basetable . 'prop', $match, $values);
  2514. }
  2515. /**
  2516. * Update a property for a given base table record. This function should be
  2517. * used if multiple records of the same property will be present. Also, use this
  2518. * function to change the property name of an existing property.
  2519. *
  2520. * @param $basetable
  2521. * The base table for which the property should be updated. The property table
  2522. * is constructed using a combination of the base table name and the suffix
  2523. * 'prop' (e.g. basetable = feature then property tabie is featureprop).
  2524. * @param $record_id
  2525. * The primary key of the base table. This should be in integer.
  2526. * For example, if the basetable is 'feature' then the $record_id should be the featureprop_id
  2527. * @param $property
  2528. * The cvterm name of property to be updated
  2529. * @param $cv_name
  2530. * The name of the cv that the above cvterm is part of
  2531. * @param $value
  2532. * The value of the property to be inserted (can be empty)
  2533. *
  2534. * @return
  2535. * Return True on Update/Insert and False otherwise
  2536. *
  2537. * @ingroup tripal_chado_api
  2538. */
  2539. function tripal_core_update_property_by_id($basetable, $record_id, $property,
  2540. $cv_name, $value) {
  2541. // get the primary key for this property table
  2542. $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
  2543. $pkcol = $table_desc['primary key'][0];
  2544. // construct the array that will match the exact record to update
  2545. $match = array(
  2546. $pkcol => $record_id,
  2547. );
  2548. // construct the array of values to be updated
  2549. $values = array(
  2550. 'type_id' => array(
  2551. 'cv_id' => array(
  2552. 'name' => $cv_name,
  2553. ),
  2554. 'name' => $property,
  2555. ),
  2556. 'value' => $value,
  2557. );
  2558. return tripal_core_chado_update($basetable . 'prop', $match, $values);
  2559. }
  2560. /**
  2561. * Deletes a property for a given base table record using the property name
  2562. *
  2563. * @param $basetable
  2564. * The base table for which the property should be deleted. Thus to deleted a property
  2565. * for a feature the basetable=feature and property is deleted from featureprop
  2566. * @param $record_id
  2567. * The primary key of the basetable to delete a property for. This should be in integer.
  2568. * @param $property
  2569. * The cvterm name describing the type of property to be deleted
  2570. * @param $cv_name
  2571. * The name of the cv that the above cvterm is part of
  2572. *
  2573. * Note: The property to be deleted is select via the unique combination of $record_id and $property
  2574. *
  2575. * @return
  2576. * Return True on Delete and False otherwise
  2577. *
  2578. * @ingroup tripal_chado_api
  2579. */
  2580. function tripal_core_delete_property($basetable, $record_id, $property, $cv_name) {
  2581. // get the foreign key for this property table
  2582. $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
  2583. $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
  2584. // construct the array that will match the exact record to update
  2585. $match = array(
  2586. $fkcol => $record_id,
  2587. 'type_id' => array(
  2588. 'cv_id' => array(
  2589. 'name' => $cv_name,
  2590. ),
  2591. 'name' => $property,
  2592. ),
  2593. );
  2594. return tripal_core_chado_delete($basetable . 'prop', $match);
  2595. }
  2596. /**
  2597. * Deletes a property using the property ID
  2598. *
  2599. * @param $basetable
  2600. * The base table for which the property should be deleted. Thus to deleted a property
  2601. * for a feature the basetable=feature and property is deleted from featureprop
  2602. * @param $record_id
  2603. * The primary key of the basetable to delete a property for. This should be in integer.
  2604. *
  2605. * @return
  2606. * Return True on Delete and False otherwise
  2607. *
  2608. * @ingroup tripal_chado_api
  2609. */
  2610. function tripal_core_delete_property_by_id($basetable, $record_id) {
  2611. // get the foreign key for this property table
  2612. $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
  2613. $pkcol = $table_desc['primary key'][0];
  2614. // construct the array that will match the exact record to update
  2615. $match = array(
  2616. $pkcol => $record_id,
  2617. );
  2618. return tripal_core_chado_delete($basetable . 'prop', $match);
  2619. }
  2620. /**
  2621. * This function is typically used in the '.install' file for a Tripal module
  2622. * Each module should call this function during installation to create
  2623. * the module data directory which is sites/default/files/tripal/[module_name]
  2624. * for default Drupal settings. This directory can then be used by the module
  2625. * for storing files.
  2626. *
  2627. * @param $module_name
  2628. * the name of the module being installed.
  2629. *
  2630. * @returns
  2631. * nothing
  2632. *
  2633. * @ingroup tripal_files_api
  2634. */
  2635. function tripal_create_moddir($module_name) {
  2636. // make the data directory for this module
  2637. $data_dir = file_directory_path() . "/tripal/$module_name";
  2638. if (!file_check_directory($data_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
  2639. $message = "Cannot create directory $data_dir. This module may not ".
  2640. "behave correctly without this directory. Please create ".
  2641. "the directory manually or fix the problem and reinstall.";
  2642. drupal_set_message(check_plain(t($message)), 'error');
  2643. watchdog('tripal_core', $message, array(), WATCHDOG_ERROR);
  2644. }
  2645. }
  2646. /**
  2647. * Each Tripal module has a unique data directory which was creatd using the
  2648. * tripal_create_moddir function during installation. This function
  2649. * retrieves the directory path.
  2650. *
  2651. * @param $module_name
  2652. * The name of the module
  2653. *
  2654. * @returns
  2655. * The path within the Drupal installation where the data directory resides
  2656. * @ingroup tripal_files_api
  2657. */
  2658. function tripal_get_moddir($module_name) {
  2659. $data_dir = file_directory_path() . "/tripal/$module_name";
  2660. return $data_dir;
  2661. }
  2662. /**
  2663. * Set the Tripal Database
  2664. *
  2665. * The tripal_db_set_active function is used to prevent namespace collisions
  2666. * when chado and drupal are installed in the same database but in different
  2667. * schemas. It is also used for backwards compatibility with older versions
  2668. * of tripal or in cases where chado is located outside of the Drupal database.
  2669. *
  2670. * @ingroup tripal_chado_api
  2671. */
  2672. function tripal_db_set_active($dbname = 'default') {
  2673. global $db_url, $db_type, $active_db;
  2674. $chado_exists = 0;
  2675. // only postgres can support search paths. So if this is MysQL then
  2676. // just run the normal tripal_db_set_active function.
  2677. if (strcmp($db_type, 'pgsql')==0) {
  2678. // if the 'chado' database is in the $db_url variable then chado is
  2679. // not in the same Drupal database, so we don't need to set any
  2680. // search_path and can just change the database
  2681. if (is_array($db_url)) {
  2682. if (isset($db_url[$dbname])) {
  2683. return db_set_active($dbname);
  2684. }
  2685. }
  2686. // if this is the default database then set the search path and return
  2687. if (strcmp($dbname, 'default')==0) {
  2688. tripal_db_set_default_search_path();
  2689. return db_set_active($dbname);
  2690. }
  2691. // if the user requests a database other than the default
  2692. // then we need to try and set the chado search path. This
  2693. // only works if Chado is local to the Drpual database. If it
  2694. // fails then do nothing.
  2695. else {
  2696. if (tripal_db_set_chado_search_path($dbname)) {
  2697. // if the chado schema is local to Drupal then
  2698. // just return the active database.
  2699. return 'default';
  2700. }
  2701. else {
  2702. watchdog('tripal_core', "Cannot set 'search_path' variable for Postgres to %dbname",
  2703. array('%dbname' => $dbname), WATCHDOG_ERROR);
  2704. }
  2705. }
  2706. }
  2707. // a non postgres database
  2708. else {
  2709. return db_set_active($dbname);
  2710. }
  2711. }
  2712. /**
  2713. * Gets the current search_path for PostgreSQL
  2714. *
  2715. * @ingroup tripal_chado_api
  2716. */
  2717. function tripal_db_get_search_path() {
  2718. $path = db_fetch_object(db_query("show search_path"));
  2719. return $path->search_path;
  2720. }
  2721. /**
  2722. * Set the chado search_path for PostgreSQL
  2723. *
  2724. * Sets the database search_path for postgreSQL to the
  2725. * chado schema.
  2726. *
  2727. * @ingroup tripal_chado_api
  2728. */
  2729. function tripal_db_set_chado_search_path($dbname) {
  2730. // check to make sure the chado schema exists
  2731. $chado_exists = variable_get('chado_schema_exists', FALSE);
  2732. if (!$chado_exists) {
  2733. $chado_exists = tripal_core_chado_schema_exists();
  2734. }
  2735. // here we make the assumption that the default database schema is
  2736. // 'public'. This will most likely always be the case but if not,
  2737. // then this code will break
  2738. if ($chado_exists) {
  2739. db_query('set search_path to %s', "$dbname,public");
  2740. return TRUE;
  2741. }
  2742. else {
  2743. return FALSE;
  2744. }
  2745. }
  2746. /**
  2747. * Set the default search_path for PostgreSQL
  2748. *
  2749. * Sets the database search_path for postgreSQL to the
  2750. * default schema.
  2751. *
  2752. * @ingroup tripal_chado_api
  2753. */
  2754. function tripal_db_set_default_search_path() {
  2755. // we make the assumption that the default schema is 'public'.
  2756. $chado_exists = variable_get('chado_schema_exists', FALSE);
  2757. if ($chado_exists) {
  2758. db_query('set search_path to %s', 'public,chado');
  2759. }
  2760. else {
  2761. db_query('set search_path to %s', 'public');
  2762. }
  2763. }
  2764. /**
  2765. * Indicates if the SQL statement is prepapred
  2766. *
  2767. * @param $statement_name
  2768. * The name of the statement to check if it is prepared.
  2769. *
  2770. * @return
  2771. * TRUE if the statement is preapred, FALSE otherwise
  2772. */
  2773. function tripal_core_is_sql_prepared($statement_name) {
  2774. global $prepared_statements;
  2775. if (!is_array($prepared_statements)) {
  2776. watchdog('tripal_core', "tripal_core_is_sql_prepared: argument must be an array", array(), WATCHDOG_ERROR);
  2777. return FALSE;
  2778. }
  2779. // check to see if the statement is prepared already
  2780. if (in_array($statement_name, $prepared_statements)) {
  2781. return TRUE;
  2782. }
  2783. // @coder-ignore: acting on postgres tables rather then drupal schema therefore, table prefixing does not apply
  2784. $sql = "SELECT name FROM pg_prepared_statements WHERE name = '%s'";
  2785. // do not use 'chado_query' here or it causes memory-leaks
  2786. $result = db_fetch_object(db_query($sql, $statement_name));
  2787. if ($result) {
  2788. return TRUE;
  2789. }
  2790. return FALSE;
  2791. }
  2792. /**
  2793. * Prepare a chado query
  2794. *
  2795. * @param $statement_name
  2796. * The name of the prepared statement
  2797. * @param $psql
  2798. * The SQL statement to be executed via chado_query.
  2799. * Should be of the form PREPARE <statement name> AS <SQL Statement to be prepared>
  2800. * @param $args
  2801. * An array of arguements required to execute the prepared statement. The keys of
  2802. * the array should correspond to the $\d in the prepare statement and the value should
  2803. * be the type of value needed (ie: text, int, etc.)
  2804. */
  2805. function tripal_core_chado_prepare($statement_name, $psql, $args) {
  2806. global $persistent_chado;
  2807. global $prepared_statements;
  2808. if (!$persistent_chado) {
  2809. watchdog('tripal_core', "chado_prepare: not able to prepare '%name' statement as no persistent connection is available", array('%name' => $statement_name, '%sql' => $psql), WATCHDOG_ERROR);
  2810. return FALSE;
  2811. }
  2812. // Check to see if this statement was already prepared
  2813. if (tripal_core_is_sql_prepared($statement_name)) {
  2814. // check that the arguments are the same
  2815. $prepared_args = $prepared_statements[$statement_name]['prepared_args'];
  2816. $prepared_sql = $prepared_statements[$statement_name]['prepared_sql'];
  2817. if ($prepared_args == $args) {
  2818. // This statement is already prepared
  2819. return TRUE;
  2820. }
  2821. else {
  2822. // Although a statement with this name is already prepared it is not the same!
  2823. watchdog('tripal_core', "chado_prepare: '%name' statement already prepared with different arguments! ".
  2824. "You want to prepare \n%sql\n with \n%values\n and the existing statement is \n%esql\n with \n%existing",
  2825. array('%name' => $statement_name, '%sql' => $psql, '%values' => print_r($args, TRUE), '%esql' => $prepared_sql,
  2826. '%existing' => print_r($prepared_args, TRUE)), WATCHDOG_ERROR);
  2827. return FALSE;
  2828. }
  2829. }
  2830. $status = chado_query($psql);
  2831. if (!$status) {
  2832. watchdog('tripal_core', "chado_prepare: not able to prepare '%name' statement for: %sql", array('%name' => $statement_name, '%sql' => $psql), WATCHDOG_ERROR);
  2833. return FALSE;
  2834. }
  2835. else {
  2836. $prepared_statements[$statement_name] = array();
  2837. $prepared_statements[$statement_name]['prepared_args'] = $args;
  2838. $prepared_statements[$statement_name]['prepared_sql'] = $psql;
  2839. return TRUE;
  2840. }
  2841. }
  2842. /**
  2843. * Execute a prepared statement with the supplied values
  2844. *
  2845. * @param $statement_name
  2846. * The name of the prepared statement
  2847. * @param $sql
  2848. * The SQL to execute using chado query.
  2849. * Should be of the form EXECUTE <statement_name> (<arg1>,<arg2>...<argn>)
  2850. * @param $values
  2851. * An array of values in the execute sql statement
  2852. */
  2853. function tripal_core_chado_execute_prepared($statement_name, $sql, $values) {
  2854. global $prepared_statements;
  2855. if (!tripal_core_is_sql_prepared($statement_name)) {
  2856. watchdog('tripal_core', "tripal_core_chado_execute_prepared: Cannot execute an unprepared statement: '%name'", array('%name' => $statement_name), WATCHDOG_ERROR);
  2857. return FALSE;
  2858. }
  2859. // Before Executing, Ensure that all the values are supplied
  2860. $required_values = $prepared_statements[$statement_name]['prepared_args'];
  2861. if (!$required_values) {
  2862. watchdog('tripal_core', "tripal_core_chado_execute_prepared: missing prepare arguments for this statement: '%name'", array('%name' => $statement_name), WATCHDOG_ERROR);
  2863. return FALSE;
  2864. }
  2865. if (sizeof($required_values) == sizeof($values)) {
  2866. $error = FALSE;
  2867. foreach ($values as $k => $v) {
  2868. if (isset($required_values[$k])) {
  2869. switch ($required_values[$k]) {
  2870. case 'text':
  2871. $check = is_string($v);
  2872. if ($v != '' and !$check) {
  2873. watchdog('tripal_core', "chado_execute_prepared: wrong argument type supplied for '%name' statement, field %k. Expected %required but recieved '%value'",
  2874. array('%name' => $statement_name, '%k' => $k+1, '%required' => $required_values[$k], '%value' => print_r($v, TRUE)), WATCHDOG_ERROR);
  2875. return FALSE;
  2876. }
  2877. break;
  2878. case 'int':
  2879. $check = is_numeric($v);
  2880. if (!$check) {
  2881. watchdog('tripal_core', "chado_execute_prepared: wrong argument type supplied for '%name' statement, field %k. Expected %required but recieved '%value'",
  2882. array('%name' => $statement_name, '%k' => $k+1, '%required' => $required_values[$k], '%value' => print_r($v, TRUE)), WATCHDOG_ERROR);
  2883. return FALSE;
  2884. }
  2885. break;
  2886. case 'bool':
  2887. if ($v != 'TRUE' and $v != 'FALSE') {
  2888. watchdog('tripal_core', "chado_execute_prepared: wrong argument type supplied for '%name' statement, field %k. Expected %required but recieved '%value'",
  2889. array('%name' => $statement_name, '%k' => $k+1, '%required' => $required_values[$k], '%value' => print_r($v, TRUE)), WATCHDOG_ERROR);
  2890. return FALSE;
  2891. }
  2892. break;
  2893. case 'numeric':
  2894. $check = is_numeric($v);
  2895. if (!$check) {
  2896. watchdog('tripal_core', "chado_execute_prepared: wrong argument type supplied for '%name' statement, field %k. Expected %required but recieved '%value'",
  2897. array('%name' => $statement_name, '%k' => $k+1, '%required' => $required_values[$k], '%value' => print_r($v, TRUE)), WATCHDOG_ERROR);
  2898. return FALSE;
  2899. }
  2900. break;
  2901. default:
  2902. watchdog('tripal_core', "chado_execute_prepared: unsupported argument type (supplied for '%name' statement %type)",
  2903. array('%name' => $statement_name, '%type' => $required_values[$k]), WATCHDOG_WARNING);
  2904. break;
  2905. }
  2906. }
  2907. else {
  2908. watchdog('tripal_core', "chado_execute_prepared: wrong number of arguments supplied for '%name' statement. Expected %required but recieved %values",
  2909. array('%name' => $statement_name, '%required' => print_r($required_values, TRUE), '%values' => print_r($values, TRUE)), WATCHDOG_ERROR);
  2910. return FALSE;
  2911. }
  2912. }
  2913. // Since all values are supplied, execute
  2914. $resource = chado_query($sql, $values);
  2915. return $resource;
  2916. }
  2917. else {
  2918. watchdog('tripal_core', "chado_execute_prepared: wrong number of arguments supplied for '%name' statement. ' .
  2919. 'Expected %required but recieved %values. Statement: %statement.",
  2920. array('%name' => $statement_name, '%required' => print_r($required_values, TRUE),
  2921. '%values' => print_r($values, TRUE), '%statement' => $prepared_statements[$statement_name]['prepared_sql']), WATCHDOG_ERROR);
  2922. return FALSE;
  2923. }
  2924. }
  2925. /**
  2926. * Clears prepared statements to avoid conflicts
  2927. *
  2928. * If no statement_name_regex is supplied then it clears ALL prepared statements;
  2929. * Otherwise, it clears prepared statement names that match the regex provided
  2930. */
  2931. function tripal_core_chado_clear_prepared ($statement_name_regex = NULL) {
  2932. global $prepared_statements;
  2933. if ($statement_name_regex) {
  2934. $resource = chado_query("SELECT * FROM pg_catalog.pg_prepared_statements WHERE name~'%s'",$statement_name_regex);
  2935. while ($r = db_fetch_object($resource)) {
  2936. $k = array_search($r->name, $prepared_statements);
  2937. unset($prepared_statements[$k]);
  2938. chado_query('DEALLOCATE PREPARE %s',$r->name);
  2939. }
  2940. }
  2941. else {
  2942. $prepared_statements = array();
  2943. chado_query('DEALLOCATE PREPARE ALL');
  2944. }
  2945. }
  2946. /**
  2947. * Instantiate or Return a persistent chado connection. This should not be confused with
  2948. * PHP persistent connections. Here we use the drupal db_connect function to
  2949. *
  2950. * NOTE: cannot use $active_db since a new connection is created each time
  2951. * db_set_active() is called
  2952. *
  2953. * @return
  2954. * A postgresql connection object which can be used by pg_prepare, pg_execute, etc.
  2955. */
  2956. function tripal_db_persistent_chado() {
  2957. global $db_url;
  2958. global $persistent_chado;
  2959. // get connection if it already exists otherwise we need to set it
  2960. if ($persistent_chado) {
  2961. return $persistent_chado;
  2962. }
  2963. else {
  2964. if (is_array($db_url) && isset($db_url['chado'])) {
  2965. $connection = db_connect($db_url['chado']);
  2966. if (!$connection) {
  2967. watchdog('tripal_core', "Could not create persistant connection", array(), WATCHDOG_ERROR);
  2968. return FALSE;
  2969. }
  2970. $persistent_chado = $connection;
  2971. }
  2972. else {
  2973. if (is_array($db_url)) {
  2974. $connection = db_connect($db_url['default']);
  2975. }
  2976. else {
  2977. $connection = db_connect($db_url);
  2978. }
  2979. if (!$connection) {
  2980. $persistent_chado = NULL;
  2981. watchdog('tripal_core', "Could not create persistant connection", array(), WATCHDOG_ERROR);
  2982. return FALSE;
  2983. }
  2984. $persistent_chado = $connection;
  2985. }
  2986. return $connection;
  2987. }
  2988. return FALSE;
  2989. }
  2990. /**
  2991. * Release a persistent chado connection
  2992. */
  2993. function tripal_db_release_persistent_chado() {
  2994. $persistent_chado = NULL;
  2995. }
  2996. /**
  2997. * Start a transaction block. Ensures the use of a persistent chado connection
  2998. */
  2999. function tripal_db_start_transaction() {
  3000. $connection = tripal_db_persistent_chado();
  3001. if ($connection) {
  3002. chado_query("BEGIN");
  3003. return $connection;
  3004. }
  3005. return FALSE;
  3006. }
  3007. /**
  3008. * Set a savepoint to roll the current transaction back to if an error is encountered
  3009. */
  3010. function tripal_db_set_savepoint_transaction($savepoint, $release = FALSE) {
  3011. // Postgresql requires a savepoint of the same name to be unset before re-use
  3012. if ($release) {
  3013. chado_query("RELEASE SAVEPOINT %s", $savepoint);
  3014. }
  3015. chado_query("SAVEPOINT %s", $savepoint);
  3016. }
  3017. /**
  3018. * Commit changes made during the current transaction
  3019. */
  3020. function tripal_db_commit_transaction() {
  3021. chado_query("COMMIT");
  3022. }
  3023. /**
  3024. * Rollback changes.
  3025. *
  3026. * If $savepoint is NULL then rollback to the beginning of the transaction,
  3027. * Otherwise, rollback to the point at which the named $savepoint was created
  3028. *
  3029. * @param $savepoint
  3030. * The name of the saved point in the transaction to rollback to
  3031. */
  3032. function tripal_db_rollback_transaction($savepoint = NULL, $commit = TRUE) {
  3033. if ($savepoint) {
  3034. chado_query("ROLLBACK TO SAVEPOINT %s", $savepoint);
  3035. }
  3036. else {
  3037. chado_query("ROLLBACK");
  3038. }
  3039. if ($commit) {
  3040. tripal_db_commit_transaction();
  3041. }
  3042. }
  3043. /**
  3044. * Purpose: Get max rank for a given set of criteria
  3045. * This function was developed with the many property tables in chado in mind
  3046. *
  3047. * @param $tablename
  3048. * The name of the chado table you want to select the max rank from this table must contain a
  3049. * rank column of type integer
  3050. * @param $where_options
  3051. * where options should include the id and type for that table to correctly
  3052. * group a set of records together where the only difference are the value and rank
  3053. * @code
  3054. * array(
  3055. * <column_name> => array(
  3056. * 'type' => <type of column: INT/STRING>,
  3057. * 'value' => <the value you want to filter on>,
  3058. * 'exact' => <if TRUE use =; if FALSE use ~>,
  3059. * )
  3060. * )
  3061. * @endcode
  3062. * @return the maximum rank
  3063. *
  3064. * @ingroup tripal_chado_api
  3065. */
  3066. function tripal_get_max_chado_rank($tablename, $where_options) {
  3067. $where= array();
  3068. //generate the where clause from supplied options
  3069. // the key is the column name
  3070. foreach ($where_options as $key => $val_array) {
  3071. if (preg_match('/INT/', $val_array['type'])) {
  3072. $where[] = $key . "=" . $val_array['value'];
  3073. }
  3074. else {
  3075. if ($val_array['exact']) {
  3076. $operator='=';
  3077. }
  3078. else {
  3079. $operator='~';
  3080. }
  3081. $where[] = $key . $operator . "'" . $val_array['value'] . "'";
  3082. }
  3083. }
  3084. $previous_db = tripal_db_set_active('chado');
  3085. $result = db_fetch_object(db_query("SELECT max(rank) as max_rank, count(rank) as count FROM %s WHERE %s",
  3086. $tablename, implode(' AND ', $where)));
  3087. tripal_db_set_active($previous_db);
  3088. //drupal_set_message("Max Rank Query=SELECT max(rank) as max_rank, count(rank) as count FROM ".$tablename." WHERE ".implode(' AND ',$where));
  3089. if ($result->count > 0) {
  3090. return $result->max_rank;
  3091. }
  3092. else {
  3093. return -1;
  3094. }
  3095. }
  3096. /**
  3097. * Retrieves the schema in an array for the specified custom table.
  3098. *
  3099. * @param $table
  3100. * The name of the table to create.
  3101. *
  3102. * @return
  3103. * A Drupal-style Schema API array definition of the table. Returns
  3104. * FALSE on failure.
  3105. *
  3106. * @ingroup tripal_core_api
  3107. */
  3108. function tripal_get_chado_custom_schema($table) {
  3109. $sql = "SELECT schema FROM {tripal_custom_tables} WHERE table_name = '%s'";
  3110. $custom = db_fetch_object(db_query($sql, $table));
  3111. if (!$custom) {
  3112. return FALSE;
  3113. }
  3114. else {
  3115. return unserialize($custom->schema);
  3116. }
  3117. }
  3118. /**
  3119. * Check that the Chado schema exists
  3120. *
  3121. * @return
  3122. * TRUE/FALSE depending upon whether it exists
  3123. */
  3124. function tripal_core_chado_schema_exists() {
  3125. $exists = variable_get('chado_schema_exists', FALSE);
  3126. if (!$exists) {
  3127. // This is postgresql-specific code to check the existence of the chado schema
  3128. // @coder-ignore: acting on pg_catalog schema rather then drupal schema therefore, table prefixing does not apply
  3129. $sql = "SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname = 'chado'";
  3130. if (db_fetch_object(db_query($sql))) {
  3131. variable_set('chado_schema_exists', TRUE);
  3132. return TRUE;
  3133. }
  3134. else {
  3135. return FALSE;
  3136. }
  3137. }
  3138. return TRUE;
  3139. }
  3140. /**
  3141. * Check that any given schema exists
  3142. *
  3143. * @param $schema
  3144. * The name of the schema to check the existence of
  3145. *
  3146. * @return
  3147. * TRUE/FALSE depending upon whether or not the schema exists
  3148. *
  3149. * @ingroup tripal_chado_api
  3150. */
  3151. function tripal_core_schema_exists($schema) {
  3152. // check that the chado schema now exists
  3153. $sql = "SELECT nspname
  3154. FROM pg_namespace
  3155. WHERE has_schema_privilege(nspname, 'USAGE') and nspname = '%s'
  3156. ORDER BY nspname";
  3157. $name = db_fetch_object(db_query($sql, $schema));
  3158. if (strcmp($name->nspname, $schema) != 0) {
  3159. return FALSE;
  3160. }
  3161. return TRUE;
  3162. }
  3163. /**
  3164. * Retrieves the list tables in the Chado schema. By default it only retursn
  3165. * the default Chado tables, but may also return custom tables added to the
  3166. * Chado schema as well.
  3167. *
  3168. * @param $include_custom
  3169. * Optional. Set as TRUE to include any custom tables created in the
  3170. * Chado schema. Custom tables are added to Chado using the
  3171. * tripal_core_chado_create_table() function.
  3172. *
  3173. * @returns
  3174. * An associative array where the key and value pairs are the Chado table names.
  3175. *
  3176. * @ingroup tripal_core_api
  3177. */
  3178. function tripal_core_get_chado_tables($include_custom = NULL) {
  3179. // first get the chado version that is installed
  3180. $v = tripal_core_get_chado_version();
  3181. $tables = array();
  3182. if ($v == '1.2') {
  3183. $tables_v1_2 = tripal_core_chado_get_v1_2_tables();
  3184. foreach ($tables_v1_2 as $table) {
  3185. $tables[$table] = $table;
  3186. }
  3187. }
  3188. if ($v == '1.11' or $v == '1.11 or older') {
  3189. $tables_v1_11 = tripal_core_chado_get_v1_11_tables();
  3190. foreach ($tables_v1_11 as $table) {
  3191. $tables[$table] = $table;
  3192. }
  3193. }
  3194. // now add in the custom tables too if requested
  3195. if ($include_custom) {
  3196. $sql = "SELECT table_name FROM {tripal_custom_tables}";
  3197. $resource = db_query($sql);
  3198. while ($r = db_fetch_object($resource)) {
  3199. $tables[$r->table_name] = $r->table_name;
  3200. }
  3201. }
  3202. asort($tables);
  3203. return $tables;
  3204. }
  3205. /**
  3206. * Sets a Drupal variable with the current version of Chado. This variable
  3207. * can then be queried later using the tripal_core_get_chado_Version
  3208. *
  3209. * @returns
  3210. * The version of Chado
  3211. *
  3212. * @ingroup tripal_core_api
  3213. */
  3214. function tripal_core_set_chado_version() {
  3215. global $db_url;
  3216. // check that Chado is installed if not return 'uninstalled as the version'
  3217. $chado_exists = tripal_core_chado_schema_exists();
  3218. if (!$chado_exists) {
  3219. // if it's not in the drupal database check to see if it's specified in the $db_url
  3220. // in the settings.php
  3221. if (!is_array($db_url) or !array_key_exists('chado', $db_url)) {
  3222. // if it's not in the drupal database or specified in the $db_url then
  3223. // return uninstalled as the version
  3224. return 'not installed';
  3225. }
  3226. }
  3227. // if the table doesn't exist then we don't know what version but we know
  3228. // it must be 1.11 or older.
  3229. $previous_db = tripal_db_set_active('chado');
  3230. $prop_exists = db_table_exists('chadoprop');
  3231. tripal_db_set_active($previous_db);
  3232. if (!$prop_exists) {
  3233. return "1.11 or older";
  3234. }
  3235. // we can't use the Tripal API to query this table
  3236. // because the Tripal API depends on this function to
  3237. // tell it the version. So, we need a typical SQL statement
  3238. $sql = "SELECT value "
  3239. ."FROM chadoprop CP "
  3240. ." INNER JOIN cvterm CVT on CVT.cvterm_id = CP.type_id "
  3241. ." INNER JOIN cv CV on CVT.cv_id = CV.cv_id "
  3242. ."WHERE CV.name = 'chado_properties' and CVT.name = 'version'";
  3243. $previous_db = tripal_db_set_active('chado');
  3244. $v = db_fetch_object(db_query($sql));
  3245. tripal_db_set_active($previous_db);
  3246. // if we don't have a version in the chadoprop table then it must be
  3247. // v1.11 or older
  3248. if (!$v->value) {
  3249. variable_set('chado_version', "1.11 or older");
  3250. return "1.11 or older";
  3251. }
  3252. variable_set('chado_version', $v->value);
  3253. return $v->value;
  3254. }
  3255. /**
  3256. * Returns the version number of the currently installed Chado instance.
  3257. * It can return the real or effective version.
  3258. *
  3259. * @param $exact
  3260. * Set this argument to 1 to retrieve the exact version that is installed.
  3261. * Otherwise, this function will set the version to the nearest 'tenth'.
  3262. * Chado versioning numbers in the hundreds represent changes to the
  3263. * software and not the schema. Changes in the tenth's represent changes
  3264. * in the schema.
  3265. *
  3266. * @param $warn_if_unsupported
  3267. * If the currently installed version of Chado is not supported by Tripal
  3268. * the generatea a Drupal warning.
  3269. *
  3270. * @returns
  3271. * The version of Chado
  3272. *
  3273. * @ingroup tripal_core_api
  3274. */
  3275. function tripal_core_get_chado_version($exact = FALSE, $warn_if_unsupported = FALSE) {
  3276. // first get the chado version that is installed
  3277. $exact_version = variable_get('chado_version', '');
  3278. if (!$exact_version) {
  3279. $exact_version = tripal_core_set_chado_version();
  3280. }
  3281. // Tripal only supports v1.11 or newer.. really this is the same as v1.1
  3282. // but at the time the v1.11 schema API was written we didn't know that so
  3283. // we'll return the version 1.11 so the schema API will work.
  3284. if (strcmp($exact_version, '1.11 or older') == 0) {
  3285. $exact_version = "1.11";
  3286. if ($warn_if_unsupported) {
  3287. drupal_set_message(t("WARNING: Tripal does not fully support Chado version less than v1.11. If you are certain this is v1.11
  3288. or if Chado was installed using an earlier version of Tripal then all is well. If not please upgrade to v1.11 or later"),
  3289. 'warning');
  3290. }
  3291. }
  3292. // if not returing an exact version, return the version to the nearest 10th.
  3293. // return 1.2 for all versions of 1.2x
  3294. $effective_version = $exact_version;
  3295. if (preg_match('/^1\.2\d+$/', $effective_version)) {
  3296. $effective_version = "1.2";
  3297. }
  3298. if ($warn_if_unsupported and ($effective_version != 1.11 and $effective_version != 1.2 and $effective_version != 'not installed')) {
  3299. drupal_set_message(t("WARNING: The currently installed version of Chado, v$exact_version, is not fully compatible with Tripal."), 'warning');
  3300. }
  3301. // if the callee has requested the exact version then return it
  3302. if ($exact) {
  3303. return $exact_version;
  3304. }
  3305. return $effective_version;
  3306. }
  3307. /**
  3308. * Retrieves the chado tables Schema API array.
  3309. *
  3310. * @param $table
  3311. * The name of the table to retrieve. The function will use the appopriate
  3312. * Tripal chado schema API hooks (e.g. v1.11 or v1.2).
  3313. *
  3314. * @returns
  3315. * A Drupal Schema API array defining the table.
  3316. *
  3317. * @ingroup tripal_core_api
  3318. */
  3319. function tripal_core_get_chado_table_schema($table) {
  3320. // first get the chado version that is installed
  3321. $v = tripal_core_get_chado_version();
  3322. // get the table array from the proper chado schema
  3323. $v = preg_replace("/\./", "_", $v); // reformat version for hook name
  3324. $table_arr = module_invoke_all("chado_schema_v" . $v . "_" . $table);
  3325. // if the table_arr is empty then maybe this is a custom table
  3326. if (!is_array($table_arr) or count($table_arr) == 0) {
  3327. $table_arr = tripal_get_chado_custom_schema($table);
  3328. }
  3329. return $table_arr;
  3330. }
  3331. /**
  3332. * This function will delete Drupal nodes for any sync'ed table (e.g.
  3333. * feature, organism, analysis, stock, library) if the chado record has been
  3334. * deleted or the entry in the chado_[table] table has been removed.
  3335. *
  3336. * @param $table
  3337. * The name of the table that corresonds to the node type we want to clean up.
  3338. * @param $job_id
  3339. * This should be the job id from the Tripal jobs system. This function
  3340. * will update the job status using the provided job ID.
  3341. *
  3342. * @ingroup tripal_core_api
  3343. */
  3344. function tripal_core_clean_orphaned_nodes($table, $job_id) {
  3345. $count = 0;
  3346. // build the SQL statments needed to check if nodes point to valid analyses
  3347. $dsql = "SELECT * FROM {node} WHERE type = 'chado_%s' order by nid";
  3348. $nsql = "SELECT * FROM {node} WHERE nid = %d";
  3349. $csql = "SELECT * FROM {chado_%s} where nid = %d ";
  3350. $clsql= "SELECT * FROM {chado_%s}";
  3351. $lsql = "SELECT * FROM %s where %s_id = %d ";
  3352. // load into nodes array
  3353. print "Getting nodes\n";
  3354. $nodes = array();
  3355. $res = db_query($dsql, $table);
  3356. while ($node = db_fetch_object($res)) {
  3357. $nodes[$count] = $node;
  3358. $count++;
  3359. }
  3360. // load the chado_$table into an array
  3361. print "Getting chado_$table\n";
  3362. $cnodes = array();
  3363. $res = db_query($clsql, $table);
  3364. while ($node = db_fetch_object($res)) {
  3365. $cnodes[$count] = $node;
  3366. $count++;
  3367. }
  3368. $interval = intval($count * 0.01);
  3369. if ($interval < 1) {
  3370. $interval = 1;
  3371. }
  3372. // iterate through all of the chado_$table entries and remove those
  3373. // that don't have a node or don't have a $table record in chado.libary
  3374. print "Verifying all chado_$table Entries\n";
  3375. $deleted = 0;
  3376. foreach ($cnodes as $nid) {
  3377. // update the job status every 1% analyses
  3378. if ($job_id and $i % $interval == 0) {
  3379. tripal_job_set_progress($job_id, intval(($i / $count) * 100));
  3380. }
  3381. // see if the node exits, if not remove the entry from the chado_$table table
  3382. $node = db_fetch_object(db_query($nsql, $nid->nid));
  3383. if (!$node) {
  3384. $deleted++;
  3385. db_query("DELETE FROM {chado_%s} WHERE nid = %d", $table, $nid->nid);
  3386. $message = "chado_$table missing node.... DELETING: $nid->nid";
  3387. watchdog('tripal_core', $message, array(), WATCHDOG_WARNING);
  3388. }
  3389. // see if the record in chado exist, if not remove the entry from the chado_$table
  3390. $table_id = $table . "_id";
  3391. $record = db_fetch_object(chado_query($lsql, $table, $table, $nid->$table_id));
  3392. if (!$record) {
  3393. $deleted++;
  3394. db_query("DELETE FROM {chado_%s} WHERE %s_id = '%d'", $table, $table, $nid->$table_id);
  3395. $message = "chado_$table missing $table.... DELETING entry.";
  3396. watchdog('tripal_core', $message, array(), WATCHDOG_WARNING);
  3397. }
  3398. $i++;
  3399. }
  3400. print "\t$deleted chado_$table entries missing either a node or chado entry.\n";
  3401. // iterate through all of the nodes and delete those that don't
  3402. // have a corresponding entry in chado_$table
  3403. $deleted = 0;
  3404. foreach ($nodes as $node) {
  3405. // update the job status every 1% libraries
  3406. if ($job_id and $i % $interval == 0) {
  3407. tripal_job_set_progress($job_id, intval(($i / $count) * 100));
  3408. }
  3409. // check to see if the node has a corresponding entry
  3410. // in the chado_$table table. If not then delete the node.
  3411. $link = db_fetch_object(db_query($csql, $table, $node->nid));
  3412. if (!$link) {
  3413. if (node_access('delete', $node)) {
  3414. $deleted++;
  3415. $message = "Node missing in chado_$table table.... DELETING node $node->nid";
  3416. watchdog("tripal_core", $message, array(), WATCHDOG_WARNING);
  3417. node_delete($node->nid);
  3418. }
  3419. else {
  3420. $message = "Node missing in chado_$table table.... but cannot delete due to improper permissions (node $node->nid)";
  3421. watchdog("tripal_core", $message, array(), WATCHDOG_WARNING);
  3422. }
  3423. }
  3424. $i++;
  3425. }
  3426. print "\t$deleted nodes did not have corresponding chado_$table entries.\n";
  3427. return '';
  3428. }
  3429. /**
  3430. * Check whether chado is installed (either in the same or a different database)
  3431. *
  3432. * @return
  3433. * TRUE/FALSE depending upon whether chado is installed.
  3434. *
  3435. * @ingroup tripal_chado_api
  3436. */
  3437. function tripal_core_is_chado_installed() {
  3438. global $db_url, $db_type;
  3439. // first check if chado is in the db_url of the
  3440. // settings.php file
  3441. if (is_array($db_url)) {
  3442. if (isset($db_url['chado'])) {
  3443. return TRUE;
  3444. }
  3445. }
  3446. // check to make sure the chado schema exists
  3447. return tripal_core_chado_schema_exists();
  3448. }
  3449. /**
  3450. * Check whether chado is installed local to the Drupal database
  3451. * in its own Chado schema.
  3452. *
  3453. * @return
  3454. * TRUE/FALSE depending upon whether chado is local.
  3455. *
  3456. * @ingroup tripal_chado_api
  3457. */
  3458. function tripal_core_is_chado_local() {
  3459. global $db_url, $db_type;
  3460. $is_installed = tripal_core_is_chado_installed();
  3461. if ($is_installed) {
  3462. if (is_array($db_url)) {
  3463. if (isset($db_url['chado'])) {
  3464. return FALSE;
  3465. }
  3466. }
  3467. return TRUE;
  3468. }
  3469. return FALSE;
  3470. }
  3471. /**
  3472. * Determine whether a given chado table is directly linked to a node
  3473. *
  3474. * @param $chado_table
  3475. * The name of a chado table to check (ie: feature)
  3476. * @return
  3477. * TRUE if it is linked to a node and FALSE otherwise
  3478. */
  3479. function tripal_core_is_tripal_node_type($chado_table) {
  3480. $linking_table = 'chado_' . $chado_table;
  3481. if (db_table_exists($linking_table)) {
  3482. return TRUE;
  3483. }
  3484. else {
  3485. return FALSE;
  3486. }
  3487. }