tripal_chado.custom_tables.api.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. <?php
  2. /**
  3. * @file
  4. * Provides an API to manage custom tables in Chado.
  5. */
  6. /**
  7. * @defgroup tripal_custom_tables_api Chado Custom Tables
  8. * @ingroup tripal_chado_api
  9. * @{
  10. * Provides an API to manage custom tables in Chado.
  11. * @}
  12. */
  13. /**
  14. * Edits a custom table in the chado database. It supports
  15. * using the Drupal Schema API array.
  16. *
  17. * @param $table_id
  18. * The table_id of the table to edit.
  19. * @param $table_name
  20. * The name of the custom table.
  21. * @param $schema
  22. * Use the Schema API array to define the custom table.
  23. * @param $skip_if_exists
  24. * Set as TRUE to skip dropping and re-creation of the table. This is
  25. * useful if the table was already created through another means and you
  26. * simply want to make Tripal aware of the table schema.
  27. * @param $is_base
  28. * Set as TRUE if the table should be considered a base table. Base tables
  29. * are those which can be used to create content types and publish records.
  30. * Default is FALSE.
  31. * @ingroup tripal_custom_tables_api
  32. */
  33. function chado_edit_custom_table($table_id, $table_name, $schema,
  34. $skip_if_exists = 1, $is_base = FALSE) {
  35. $transaction = db_transaction();
  36. try {
  37. // Create a new record.
  38. $record = new stdClass();
  39. $record->table_id = $table_id;
  40. $record->table_name = $table_name;
  41. $record->schema = serialize($schema);
  42. $record->is_base = $is_base;
  43. // Get the current custom table record.
  44. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_id = :table_id";
  45. $results = db_query($sql, [':table_id' => $table_id]);
  46. $custom_table = $results->fetchObject();
  47. // If this is a materialized view then don't allow editing with this function.
  48. if ($custom_table->mview_id) {
  49. tripal_report_error('tripal_chado', TRIPAL_ERROR, "Please use the chado_edit_mview() function to edit this custom table as it is a materialized view.", []);
  50. drupal_set_message("This custom table is a materialized view. Please use the " . l('Materialized View', 'admin/tripal/storage/chado/mviews') . " interface to edit it.", 'error');
  51. return FALSE;
  52. }
  53. // If the user changed the table name, we want to drop the old one and force
  54. // creation of the new one.
  55. if ($custom_table->table_name != $table_name) {
  56. chado_query("DROP TABLE %s", $custom_table->table_name);
  57. $skip_if_exists = 0; // we want to create the table
  58. }
  59. // If skip creation is not set, then drop the table from chado if it exists.
  60. if (!$skip_if_exists) {
  61. if (db_table_exists($custom_table->table_name)) {
  62. chado_query("DROP TABLE %s", $custom_table->table_name);
  63. drupal_set_message(t("Custom Table " . $custom_table->table_name . " dropped"));
  64. }
  65. }
  66. // Update the custom table record and run the create custom table function.
  67. drupal_write_record('tripal_custom_tables', $record, 'table_id');
  68. $success = chado_create_custom_table($table_name, $schema, $skip_if_exists, NULL, FALSE, $is_base);
  69. // Re-add the custom table to the semantic web interface to pick up any
  70. // changes in fields.
  71. chado_add_semweb_table($table_name);
  72. }
  73. catch (Exception $e) {
  74. $transaction->rollback();
  75. watchdog_exception('tripal_chado', $e);
  76. $error = _drupal_decode_exception($e);
  77. drupal_set_message(t("Could not update custom table '%table_name': %message.",
  78. ['%table_name' => $table, '%message' => $error['!message']]), 'error');
  79. return FALSE;
  80. }
  81. return TRUE;
  82. }
  83. /**
  84. * Add a new table to the Chado schema.
  85. *
  86. * This function is simply a wrapper for the db_create_table() function of
  87. * Drupal, but ensures the table is created
  88. * inside the Chado schema rather than the Drupal schema. If the table already
  89. * exists then it will be dropped and recreated using the schema provided.
  90. * However, it will only drop a table if it exsits in the tripal_custom_tables
  91. * table. This way the function cannot be used to accidentally alter existing
  92. * non custom tables. If $skip_if_exists is set then the table is simply
  93. * added to the tripal_custom_tables and no table is created in Chado.
  94. *
  95. * If you are creating a materialized view do not use this function, but rather
  96. * use the chado_add_mview(). A materialized view is also considered a custom
  97. * table and an entry for it will be added to both the tripal_mviews and
  98. * tripal_custom_tables tables, but only if the chado_add_mview() function is
  99. * used. The optional $mview_id parameters in this function is intended
  100. * for use by the chado_add_mview() function when it calls this function
  101. * to create the table.
  102. *
  103. * @param $table
  104. * The name of the table to create.
  105. * @param $schema
  106. * A Drupal-style Schema API definition of the table.
  107. * @param $skip_if_exists
  108. * Set as TRUE to skip dropping and re-creation of the table if it already
  109. * exists. This is useful if the table was already created through another
  110. * means and you simply want to make Tripal aware of the table schema. If the
  111. * table does not exist it will be created.
  112. * @param $mview_id
  113. * Optional. If this custom table is also a materialized view then provide
  114. * it's mview_id. This paramter is intended only when this function
  115. * is called by the chado_add_mview() function. When creating a custom
  116. * table you shouldn't need to use this parameter.
  117. * @param $redirect
  118. * Optional (default: TRUE). By default this function redirects back to
  119. * admin pages. However, when called by Drush we don't want to redirect. This
  120. * parameter allows this to be used as a true API function.
  121. * @param $is_base
  122. * Optional (default: 0). Indicates if this custom view is a base table.
  123. * Base tables can be used to create content types and publish records.
  124. *
  125. * @return
  126. * TRUE on success, FALSE on failure.
  127. *
  128. * @ingroup tripal_custom_tables_api
  129. */
  130. function chado_create_custom_table($table, $schema, $skip_if_exists = TRUE,
  131. $mview_id = NULL, $redirect = TRUE, $is_base = 0) {
  132. if (!$table) {
  133. tripal_report_error('trp_ctables', TRIPAL_ERROR,
  134. 'Please provide a value for the $table argument to the chado_create_custom_table() function');
  135. return FALSE;
  136. }
  137. if (!$schema) {
  138. tripal_report_error('trp_ctables', TRIPAL_ERROR,
  139. 'Please provide a value for the $schema argument to the chado_create_custom_table() function');
  140. return FALSE;
  141. }
  142. if ($schema and !is_array($schema)) {
  143. tripal_report_error('trp_ctables', TRIPAL_ERROR,
  144. 'Please provide an array for the $schema argument to the chado_create_custom_table() function');
  145. return FALSE;
  146. }
  147. // TODO: make sure the schema is valid by adding extra checks.
  148. global $databases;
  149. $created = 0;
  150. $recreated = 0;
  151. $chado_schema = chado_get_schema_name('chado');
  152. $chado_dot = $chado_schema . '.';
  153. $transaction = db_transaction();
  154. try {
  155. // See if the table entry already exists in the tripal_custom_tables table.
  156. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_name = :table_name";
  157. $results = db_query($sql, [':table_name' => $table]);
  158. $centry = $results->fetchObject();
  159. // Check to see if the table already exists in the chado schema.
  160. $exists = chado_table_exists($table);
  161. // If the table does not exist then create it.
  162. if (!$exists) {
  163. $ret = db_create_table($chado_dot . $table, $schema);
  164. $created = 1;
  165. }
  166. // If the table exists in Chado and in our custom table and
  167. // skip creation is turned off then drop and re-create the table.
  168. if ($exists and is_object($centry) and !$skip_if_exists) {
  169. // Drop the table we'll recreate it with the new schema.
  170. chado_query('DROP TABLE {' . $table . '}');
  171. // Remove any 'referring_tables' from the array as the
  172. // db_create_table doesn't use that.
  173. $new_schema = $schema;
  174. if (array_key_exists('referring_tables', $new_schema)) {
  175. unset($new_schema['referring_tables']);
  176. }
  177. db_create_table($chado_dot . $table, $new_schema);
  178. $recreated = 1;
  179. }
  180. // Add an entry in the tripal_custom_tables.
  181. $record = new stdClass();
  182. $record->table_name = $table;
  183. $record->schema = serialize($schema);
  184. if ($mview_id) {
  185. $record->mview_id = $mview_id;
  186. }
  187. $record->is_base = $is_base;
  188. // If an entry already exists then remove it.
  189. if ($centry) {
  190. $sql = "DELETE FROM {tripal_custom_tables} WHERE table_name = :table_name";
  191. db_query($sql, [':table_name' => $table]);
  192. }
  193. $success = drupal_write_record('tripal_custom_tables', $record);
  194. // Now add any foreign key constraints.
  195. if (($created or !$skip_if_exists) and array_key_exists('foreign keys', $schema)) {
  196. // Iterate through the foreign keys and add each one.
  197. $fkeys = $schema['foreign keys'];
  198. foreach ($fkeys as $fktable => $fkdetails) {
  199. $relations = $fkdetails['columns'];
  200. foreach ($relations as $left => $right) {
  201. $sql = '
  202. ALTER TABLE {' . $table . '}
  203. ADD CONSTRAINT ' . $table . '_' . $left . '_fkey FOREIGN KEY (' . $left . ')
  204. REFERENCES {' . $fktable . '} (' . $right . ')
  205. ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
  206. ';
  207. chado_query($sql);
  208. }
  209. }
  210. }
  211. // Add the custom table to the semantic web interface.
  212. chado_add_semweb_table($table);
  213. }
  214. catch (Exception $e) {
  215. $transaction->rollback();
  216. $error = $e->getMessage();
  217. watchdog_exception('tripal_chado', $e);
  218. drupal_set_message(t("Could not add custom table '%table_name': %message.",
  219. ['%table_name' => $table, '%message' => $error]), 'error');
  220. return FALSE;
  221. }
  222. if ($created) {
  223. drupal_set_message("Custom table, '" . $table . "' , created successfully.", 'status');
  224. }
  225. elseif ($recreated) {
  226. drupal_set_message("Custom table, '" . $table . "' , re-created successfully.", 'status');
  227. }
  228. else {
  229. drupal_set_message("Custom table, '" . $table . "' , already exists. Table structure not changed, but definition array has been saved.", 'status');
  230. }
  231. // Only redirect if asked to. This allows us to not try to redirect when this
  232. // function is called by Drush.
  233. if ($redirect) {
  234. if ($mview_id) {
  235. drupal_goto('admin/tripal/storage/chado/mviews/');
  236. }
  237. else {
  238. drupal_goto('admin/tripal/storage/chado/custom_tables');
  239. }
  240. }
  241. return TRUE;
  242. }
  243. /**
  244. * This function is used to validate a Drupal Schema API array prior to
  245. * passing it ot the chado_create_custom_table_schema(). This function
  246. * can be used in a form validate function or whenver a schema is provided by
  247. * a user and needs validation.
  248. *
  249. * @param $schema_array
  250. * the Drupal Schema API compatible array.
  251. *
  252. * @return
  253. * An empty string for success or a message string for failure.
  254. *
  255. * @ingroup tripal_custom_tables_api
  256. */
  257. function chado_validate_custom_table_schema($schema_array) {
  258. if (is_array($schema_array) and !array_key_exists('table', $schema_array)) {
  259. return "The schema array must have key named 'table'";
  260. }
  261. if (preg_match('/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]/', $schema_array['table'])) {
  262. return "Postgres will automatically change the table name to lower-case. To prevent unwanted side-effects, please rename the table with all lower-case characters.";
  263. }
  264. // Check index length.
  265. if (array_key_exists('indexes', $schema_array)) {
  266. foreach ($schema_array['indexes'] as $index_name => $details) {
  267. if (strlen($schema_array['table'] . '_' . $index_name) > 60) {
  268. return "One ore more index names appear to be too long. For example: '" . $schema_array['table'] . '_' . $index_name .
  269. ".' Index names are created by concatenating the table name with the index name provided " .
  270. "in the 'indexes' array of the schema. Please alter any indexes that when combined with the table name are " .
  271. "longer than 60 characters.";
  272. }
  273. }
  274. }
  275. // Check unique key length.
  276. if (array_key_exists('unique keys', $schema_array)) {
  277. foreach ($schema_array['unique keys'] as $index_name => $details) {
  278. if (strlen($schema_array['table'] . '_' . $index_name) > 60) {
  279. return "One ore more unique key names appear to be too long. For example: '" . $schema_array['table'] . '_' . $index_name .
  280. ".' Unique key names are created by concatenating the table name with the key name provided " .
  281. "in the 'unique keys' array of the schema. Please alter any unique keys that when combined with the table name are " .
  282. "longer than 60 characters.";
  283. }
  284. }
  285. }
  286. }
  287. /**
  288. * Retrieve the custom table id given the name.
  289. *
  290. * @param $table_name
  291. * The name of the custom table.
  292. *
  293. * @return
  294. * The unique identifier for the given table.
  295. *
  296. * @ingroup tripal_custom_tables_api
  297. */
  298. function chado_get_custom_table_id($table_name) {
  299. if (db_table_exists('tripal_custom_tables')) {
  300. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_name = :table_name";
  301. $results = db_query($sql, [':table_name' => $table_name]);
  302. $custom_table = $results->fetchObject();
  303. if ($custom_table) {
  304. return $custom_table->table_id;
  305. }
  306. }
  307. return FALSE;
  308. }
  309. /**
  310. * Retrieves the list of custom tables that are base tables.
  311. *
  312. * @return
  313. * An associative array where the key and value pairs are the table names.
  314. *
  315. * @ingroup tripal_custom_tables_api
  316. */
  317. function chado_get_base_custom_tables() {
  318. $num_tables = 0;
  319. $sql = "SELECT table_name FROM {tripal_custom_tables} WHERE is_base = 1";
  320. $resource = db_query($sql);
  321. $tables = [];
  322. foreach ($resource as $r) {
  323. $tables[$r->table_name] = $r->table_name;
  324. $num_tables++;
  325. }
  326. if ($num_tables > 0) {
  327. asort($tables);
  328. }
  329. return $tables;
  330. }
  331. /**
  332. * Retrieves the list of custom tables in this site.
  333. *
  334. * @return
  335. * An associative array where the key and value pairs are the table names.
  336. *
  337. * @ingroup tripal_custom_tables_api
  338. */
  339. function chado_get_custom_table_names($include_mview = TRUE) {
  340. $sql = "SELECT table_name FROM {tripal_custom_tables}";
  341. if (!$include_mview) {
  342. $sql .= " WHERE mview_id IS NULL";
  343. }
  344. $resource = db_query($sql);
  345. foreach ($resource as $r) {
  346. $tables[$r->table_name] = $r->table_name;
  347. }
  348. asort($tables);
  349. return $tables;
  350. }
  351. /**
  352. * Deletes the specified custom table.
  353. *
  354. * @param $table_id
  355. * The unique ID of the custom table for the action to be performed on.
  356. *
  357. * @ingroup tripal_custom_tables_api
  358. */
  359. function chado_delete_custom_table($table_id) {
  360. global $user;
  361. $args = ["$table_id"];
  362. if (!$table_id) {
  363. return '';
  364. }
  365. // Get this table details.
  366. $sql = "SELECT * FROM {tripal_custom_tables} WHERE table_id = :table_id";
  367. $results = db_query($sql, [':table_id' => $table_id]);
  368. $custom_table = $results->fetchObject();
  369. // If this is a materialized view then don't allow deletion with this function.
  370. if ($custom_table->mview_id) {
  371. tripal_report_error('tripal_chado', TRIPAL_ERROR, "Please use the chado_delete_mview() function to delete this custom table as it is a materialized view. Table not deleted.", []);
  372. drupal_set_message("This custom table is a materialized view. Please use the " . l('Materialized View', 'admin/tripal/storage/chado/mviews') . " interface to delete it.", 'error');
  373. return FALSE;
  374. }
  375. // Remove the entry from the tripal_custom tables table.
  376. $sql = "DELETE FROM {tripal_custom_tables} WHERE table_id = $table_id";
  377. $success = db_query($sql);
  378. if ($success) {
  379. drupal_set_message(t("Custom Table '%name' removed", ['%name' => $custom_table->table_name]));
  380. }
  381. // Drop the table from chado if it exists.
  382. if (chado_table_exists($custom_table->table_name)) {
  383. $success = chado_query("DROP TABLE {" . $custom_table->table_name . "}");
  384. if ($success) {
  385. drupal_set_message(t("Custom Table '%name' dropped", ['%name' => $custom_table->table_name]));
  386. }
  387. else {
  388. tripal_report_error('tripal_chado', TRIPAL_ERROR, "Cannot drop the custom table: %name", ['%name' => $custom_table->table_name]);
  389. drupal_set_message(t("Cannot drop the custom table: '%name'", ['%name' => $custom_table->table_name]));
  390. }
  391. }
  392. }