custom_tables.php 19 KB

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