custom_tables.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. <?php
  2. /**
  3. * @file
  4. * Contains functions for the Custom Tables API
  5. * @defgroup tripal_custom_tables_api Core Module Custom Tables API
  6. * @{
  7. * Provides an application programming interface (API) to manage custom tables in Chado.
  8. * @}
  9. * @ingroup tripal_api
  10. */
  11. /**
  12. * Edits a custom table in the chado database. It supports
  13. * using the Drupal Schema API array.
  14. *
  15. * @param $table_id
  16. * The table_id of the table to edit
  17. * @param $table_name
  18. * The name of the custom table
  19. * @param $schema
  20. * Use the Schema API array to define the custom table.
  21. * @param $skip_creation
  22. * Set as TRUE to skip dropping and re-creation of the table. This is
  23. * useful if the table was already created through another means and you
  24. * simply want to make Tripal aware of the table schema.
  25. *
  26. * @ingroup tripal_custom_tables_api
  27. */
  28. function tripal_core_edit_custom_table($table_id, $table_name, $schema, $skip_creation = 0) {
  29. // Create a new record
  30. $record = new stdClass();
  31. $record->table_id = $table_id;
  32. $record->table_name = $table_name;
  33. $record->schema = serialize($schema);
  34. // get the current custom table record
  35. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_id = %d";
  36. $custom_table = db_fetch_object(db_query($sql, $table_id));
  37. // drop the table from chado if it exists
  38. if(!$skip_creation){
  39. if (db_table_exists($custom_table->table_name)) {
  40. chado_query("DROP TABLE %s", $custom_table->table_name);
  41. drupal_set_message(t("Custom Table '%name' dropped", array('%name' => $custom_table->table_name)));
  42. }
  43. }
  44. // update the custom table record and re-create the table in Chado
  45. if (drupal_write_record('tripal_custom_tables', $record, 'table_id')) {
  46. // drop the table from chado if it exists
  47. if(!$skip_creation){
  48. if (db_table_exists($custom_table->table_name)) {
  49. chado_query("DROP TABLE %s", $custom_table->table_name);
  50. drupal_set_message(t("Custom Table '%name' dropped", array('%name' => $custom_table->table_name)));
  51. }
  52. // re-create the table
  53. if (!tripal_core_create_custom_table ($ret, $table_name, $schema)) {
  54. drupal_set_message(t("Could not create the custom table. Check Drupal error report logs."));
  55. }
  56. else {
  57. drupal_set_message(t("Custom table '%name' created", array('%name' => $table_name)));
  58. }
  59. }
  60. // TODO: add FK constraints
  61. }
  62. }
  63. /**
  64. * Add a new table to the Chado schema. This function is simply a wrapper for
  65. * the db_create_table() function of Drupal, but ensures the table is created
  66. * inside the Chado schema rather than the Drupal schema. If the table already
  67. * exists then it will be dropped and recreated using the schema provided.
  68. * However, it will only drop a table if it exsits in the tripal_custom_tables
  69. * table. This way the function cannot be used to accidentally alter existing
  70. * non custom tables. If $skip_creation is set then the table is simply
  71. * added to the tripal_custom_tables and no table is created in Chado.
  72. *
  73. * @param $ret
  74. * Array to which query results will be added.
  75. * @param $table
  76. * The name of the table to create.
  77. * @param $schema
  78. * A Drupal-style Schema API definition of the table
  79. * @param $skip_creation
  80. * Set as TRUE to skip dropping and re-creation of the table. This is
  81. * useful if the table was already created through another means and you
  82. * simply want to make Tripal aware of the table schema.
  83. *
  84. * @return
  85. * A database query result resource for the new table, or FALSE if table was not constructed.
  86. *
  87. * @ingroup tripal_custom_tables_api
  88. */
  89. function tripal_core_create_custom_table(&$ret, $table, $schema, $skip_creation = 0) {
  90. $ret = array();
  91. // see if the table entry already exists
  92. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_name = '%s'";
  93. $centry = db_fetch_object(db_query($sql, $table));
  94. // If the table exits in Chado but not in the tripal_custom_tables field
  95. // then call an error. if the table exits in the tripal_custom_tables but
  96. // not in Chado then create the table and replace the entry.
  97. $previous_db = tripal_db_set_active('chado'); // use chado database
  98. $exists = db_table_exists($table);
  99. tripal_db_set_active($previous_db); // now use drupal database
  100. if (!$exists) {
  101. $previous_db = tripal_db_set_active('chado'); // use chado database
  102. db_create_table($ret, $table, $schema);
  103. tripal_db_set_active($previous_db); // now use drupal database
  104. if (count($ret)==0) {
  105. watchdog('tripal_core', "Error adding custom table '!table_name'.",
  106. array('!table_name' => $table), WATCHDOG_ERROR);
  107. return FALSE;
  108. }
  109. }
  110. if ($exists and !$centry and !$skip_creation) {
  111. watchdog('tripal_core', "Could not add custom table '!table_name'. It ".
  112. "already exists but is not known to Tripal as being a custom table.",
  113. array('!table_name' => $table), WATCHDOG_WARNING);
  114. return FALSE;
  115. }
  116. if ($exists and $centry and !$skip_creation) {
  117. // drop the table we'll recreate it with the new schema
  118. $previous_db = tripal_db_set_active('chado'); // use chado database
  119. db_drop_table($ret, $table);
  120. db_create_table($ret, $table, $schema);
  121. tripal_db_set_active($previous_db); // now use drupal database
  122. }
  123. // add an entry in the tripal_custom_table
  124. $record = new stdClass();
  125. $record->table_name = $table;
  126. $record->schema = serialize($schema);
  127. // if an entry already exists then remove it
  128. if ($centry) {
  129. $sql = "DELETE FROM {tripal_custom_tables} WHERE table_name = '%s'";
  130. db_query($sql, $table);
  131. }
  132. $success = drupal_write_record('tripal_custom_tables', $record);
  133. if (!$success) {
  134. watchdog('tripal_core', "Error adding custom table %table_name.",
  135. array('%table_name' => $table), WATCHDOG_ERROR);
  136. drupal_set_message(t("Could not add custom table %table_name.
  137. Please check the schema array.", array('%table_name' => $table)), 'error');
  138. return FALSE;
  139. }
  140. // now add any foreign key constraints
  141. if(!$skip_creation and array_key_exists('foreign keys', $schema)){
  142. $fkeys = $schema['foreign keys'];
  143. foreach ($fkeys as $fktable => $fkdetails) {
  144. $relations = $fkdetails['columns'];
  145. foreach ($relations as $left => $right) {
  146. $sql = "ALTER TABLE $table ADD CONSTRAINT " .
  147. $table . "_" . $left . "_fkey FOREIGN KEY ($left) REFERENCES $fktable ($right) " .
  148. "ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED";
  149. if(!chado_query($sql)){
  150. watchdog('tripal_core', "Error, could not add foreign key contraint to custom table.",
  151. array('!table_name' => $table), WATCHDOG_ERROR);
  152. drupal_set_message(t("Could not add foreign key contraint to table %table_name.
  153. Please check the schema array and the report log for errors.",
  154. array('%table_name' => $table)), 'error');
  155. return FALSE;
  156. }
  157. }
  158. }
  159. }
  160. return TRUE;
  161. }
  162. /**
  163. * Retrieve the custom table id given the name
  164. *
  165. * @param $table_name
  166. * The name of the custom table
  167. *
  168. * @return
  169. * The unique identifier for the given table
  170. *
  171. * @ingroup tripal_custom_tables_api
  172. */
  173. function tripal_custom_tables_get_table_id($table_name) {
  174. $sql = "SELECT * FROM {tripal_custom_tables} ".
  175. "WHERE table_name = '%s'";
  176. if (db_table_exists('tripal_custom_tables')) {
  177. $custom_table = db_fetch_object(db_query($sql, $table_name));
  178. if ($custom_table) {
  179. return $custom_table->table_id;
  180. }
  181. }
  182. return FALSE;
  183. }
  184. /**
  185. * A template function which returns markup to display details for the custom table
  186. *
  187. * @param $table_id
  188. * The unique ID of the custom table
  189. *
  190. * @ingroup tripal_custom_tables_api
  191. */
  192. function tripal_custom_table_view($table_id) {
  193. // get this custom_table details
  194. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_id = %d";
  195. $custom_table = db_fetch_object(db_query($sql, $table_id));
  196. // create a table with each row containig stats for
  197. // an individual job in the results set.
  198. $return_url = url("admin/tripal/custom_tables/");
  199. $output .= "<p><a href=\"$return_url\">" . t("Return to list of custom tables") . "</a></p>";
  200. $output .= "<br />";
  201. $output .= "<p>Details for <b>$custom_table->table_name</b>:</p>";
  202. $output .= "<br />";
  203. $output .= "<table class=\"border-table\">";
  204. if ($custom_table->table_name) {
  205. $output .= " <tr>".
  206. " <th>Table Name</th>".
  207. " <td>$custom_table->table_name</td>".
  208. " </tr>";
  209. }
  210. if ($custom_table->schema) {
  211. $output .= " <tr>".
  212. " <th>Table Field Definitions</th>".
  213. " <td><pre>" . var_export(unserialize($custom_table->schema),1) . "</pre></td>".
  214. " </tr>";
  215. }
  216. // build the URLs using the url function so we can handle installations where
  217. // clean URLs are or are not used
  218. $delete_url = url("admin/tripal/custom_tables/action/delete/$custom_table->table_id");
  219. $edit_url = url("admin/tripal/custom_tables/edit/$custom_table->table_id");
  220. $output .= "<tr><th>Actions</th>".
  221. "<td>".
  222. " <a href='$edit_url'>Edit</a>, ".
  223. " <a href='$delete_url'>Delete</a></td></tr>";
  224. $output .= "</table>";
  225. return $output;
  226. }
  227. /**
  228. * A template function to render a listing of all Custom tables
  229. *
  230. * @ingroup tripal_custom_tables_api
  231. */
  232. function tripal_custom_tables_list() {
  233. $header = array('', 'Table Name', 'Description');
  234. $rows = array();
  235. $custom_tables = db_query("SELECT * FROM {tripal_custom_tables} ORDER BY table_name");
  236. while ($custom_table = db_fetch_object($custom_tables)) {
  237. $rows[] = array(
  238. l(t('View'), "admin/tripal/custom_tables/view/$custom_table->table_id") ." | ".
  239. l(t('Edit'), "admin/tripal/custom_tables/edit/$custom_table->table_id") ." | ".
  240. $custom_table->table_name,
  241. $custom_table->comment,
  242. l(t('Delete'), "admin/tripal/custom_tables/action/delete/$custom_table->table_id"),
  243. );
  244. }
  245. $rows[] = array(
  246. 'data' => array(
  247. array('data' => l(t('Create a new custom table.'), "admin/tripal/custom_tables/new"),
  248. 'colspan' => 6),
  249. )
  250. );
  251. $page = theme('table', $header, $rows);
  252. return $page;
  253. }
  254. /**
  255. * A Form to Create/Edit a Custom table
  256. *
  257. * @param $form_state
  258. * The current state of the form (Form API)
  259. * @param $table_id
  260. * The unique ID of the Custom table to Edit or NULL if creating a new table
  261. *
  262. * @return
  263. * A form array (Form API)
  264. *
  265. * @ingroup tripal_core
  266. */
  267. function tripal_custom_tables_form(&$form_state = NULL, $table_id = NULL) {
  268. if (!$table_id) {
  269. $action = 'Add';
  270. }
  271. else {
  272. $action = 'Edit';
  273. }
  274. // get this requested table
  275. if (strcmp($action, 'Edit')==0) {
  276. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_id = %d ";
  277. $custom_table = db_fetch_object(db_query($sql, $table_id));
  278. // set the default values. If there is a value set in the
  279. // form_state then let's use that, otherwise, we'll pull
  280. // the values from the database
  281. $default_schema = $form_state['values']['schema'];
  282. $default_skip = $form_state['values']['skip_creation'];
  283. if (!$default_table_name) {
  284. $default_table = $custom_table->table_name;
  285. }
  286. if (!$default_schema) {
  287. $default_schema = var_export(unserialize($custom_table->schema),1);
  288. }
  289. }
  290. // Build the form
  291. $form['action'] = array(
  292. '#type' => 'value',
  293. '#value' => $action
  294. );
  295. $form['table_id'] = array(
  296. '#type' => 'value',
  297. '#value' => $table_id
  298. );
  299. $form['instructions']= array(
  300. '#type' => 'markup',
  301. '#value' => t('At times it is necessary to add a custom table to the Chado schema.
  302. These are not offically sanctioned tables but may be necessary for local data requirements.
  303. Avoid creating custom tables when possible as other GMOD tools may not recognize these tables
  304. nor the data in them. Linker tables are often a good candidate for
  305. a custom table. For example a table to link stocks and libraries (e.g. library_stock). If the
  306. table already exists it will be dropped and re-added using the definition supplied below. All
  307. data in the table will be lost. However, If you
  308. are certain the schema definition you provide is correct for an existing table, select the checkbox
  309. below to skip creation of the table.
  310. '),
  311. );
  312. $form['skip_creation']= array(
  313. '#type' => 'checkbox',
  314. '#title' => t('Skip Table Creation'),
  315. '#description' => t('If your table already exists, check this box to prevent it from being dropped and re-created.'),
  316. '#default_value' => $default_skip,
  317. );
  318. $form['schema']= array(
  319. '#type' => 'textarea',
  320. '#title' => t('Schema Array'),
  321. '#description' => t('Please enter the Drupal Schema API compatible array that defines the table.'),
  322. '#required' => FALSE,
  323. '#default_value' => $default_schema,
  324. '#rows' => 25,
  325. );
  326. if ($action == 'Edit') {
  327. $value = 'Save';
  328. }
  329. if ($action == 'Add') {
  330. $value = 'Add';
  331. }
  332. $form['submit'] = array(
  333. '#type' => 'submit',
  334. '#value' => t($value),
  335. '#executes_submit_callback' => TRUE,
  336. );
  337. $form['#redirect'] = 'admin/tripal/custom_tables';
  338. $form['example']= array(
  339. '#type' => 'markup',
  340. '#value' => "<br>Example library_stock table: <pre>
  341. array (
  342. 'table' => 'library_stock',
  343. 'fields' => array (
  344. 'library_stock_id' => array(
  345. 'type' => serial,
  346. 'not null' => TRUE,
  347. ),
  348. 'library_id' => array(
  349. 'type' => 'int',
  350. 'not null' => TRUE,
  351. ),
  352. 'stock_id' => array(
  353. 'type' => 'int',
  354. 'not null' => TRUE,
  355. ),
  356. ),
  357. 'primary key' => array(
  358. 'library_stock_id'
  359. ),
  360. 'unique keys' => array(
  361. 'library_stock_c1' => array(
  362. 'library_id',
  363. 'stock_id'
  364. ),
  365. ),
  366. 'foreign keys' => array(
  367. 'library' => array(
  368. 'table' => 'library',
  369. 'columns' => array(
  370. 'library_id' => 'library_id',
  371. ),
  372. ),
  373. 'stock' => array(
  374. 'table' => 'stock',
  375. 'columns' => array(
  376. 'stock_id' => 'stock_id',
  377. ),
  378. ),
  379. ),
  380. )
  381. </pre>",
  382. );
  383. return $form;
  384. }
  385. /**
  386. * Validate the Create/Edit custom table form
  387. * Implements hook_form_validate().
  388. *
  389. * @ingroup tripal_core
  390. */
  391. function tripal_custom_tables_form_validate($form, &$form_state) {
  392. $action = $form_state['values']['action'];
  393. $table_id = $form_state['values']['table_id'];
  394. $schema = $form_state['values']['schema'];
  395. if (!$schema) {
  396. form_set_error($form_state['values']['schema'],
  397. t('Schema array field is required.'));
  398. }
  399. // make sure the array is valid
  400. $schema_array = array();
  401. if ($schema) {
  402. $success = eval("\$schema_array = $schema;");
  403. if ($success === FALSE) {
  404. $error = error_get_last();
  405. form_set_error($form_state['values']['schema'],
  406. t("The schema array is improperly formatted. Parse Error : " . $error["message"]));
  407. }
  408. if (is_array($schema_array) and !array_key_exists('table', $schema_array)) {
  409. form_set_error($form_state['values']['schema'],
  410. t("The schema array must have key named 'table'"));
  411. }
  412. // TODO: add in more validation checks of the array to help the user
  413. }
  414. }
  415. /**
  416. * Submit the Create/Edit Custom table form
  417. * Implements hook_form_submit().
  418. *
  419. * @ingroup tripal_core
  420. */
  421. function tripal_custom_tables_form_submit($form, &$form_state) {
  422. $ret = array();
  423. $action = $form_state['values']['action'];
  424. $table_id = $form_state['values']['table_id'];
  425. $schema = $form_state['values']['schema'];
  426. $skip_creation = $form_state['values']['skip_creation'];
  427. // conver the schema into a PHP array
  428. $schema_arr = array();
  429. eval("\$schema_arr = $schema;");
  430. if (strcmp($action, 'Edit') == 0) {
  431. tripal_core_edit_custom_table($table_id, $schema_arr['table'], $schema_arr, $skip_creation);
  432. }
  433. elseif (strcmp($action, 'Add') == 0) {
  434. tripal_core_create_custom_table($ret, $schema_arr['table'], $schema_arr, $skip_creation);
  435. }
  436. else {
  437. drupal_set_message(t("No action performed."));
  438. }
  439. return '';
  440. }
  441. /**
  442. * Does the specified action for the specified custom table
  443. *
  444. * @param $op
  445. * The action to be taken. Currenly only delete is available
  446. * @param $table_id
  447. * The unique ID of the custom table for the action to be performed on
  448. * @param $redirect
  449. * TRUE/FALSE depending on whether you want to redirect the user to admin/tripal/custom_tables
  450. *
  451. * @ingroup tripal_core
  452. */
  453. function tripal_custom_tables_action($op, $table_id, $redirect = FALSE) {
  454. global $user;
  455. $args = array("$table_id");
  456. if (!$table_id) {
  457. return '';
  458. }
  459. // get this table details
  460. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_id = %d";
  461. $custom_table = db_fetch_object(db_query($sql, $table_id));
  462. if ($op == 'delete') {
  463. // remove the entry from the tripal_custom tables table
  464. $sql = "DELETE FROM {tripal_custom_tables} ".
  465. "WHERE table_id = $table_id";
  466. db_query($sql);
  467. // drop the table from chado if it exists
  468. if (db_table_exists($custom_table->table_name)) {
  469. $success = chado_query("DROP TABLE %s", $custom_table->table_name);
  470. if($success){
  471. drupal_set_message(t("Custom Table '%name' dropped", array('%name' => $custom_table->table_name)));
  472. }
  473. }
  474. }
  475. // Redirect the user
  476. if ($redirect) {
  477. drupal_goto("admin/tripal/custom_tables");
  478. }
  479. }