custom_tables.php 17 KB

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