tripal_core.mviews.inc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. <?php
  2. /**
  3. * @file
  4. * Contains functions for viewing and editing of Materialized Views
  5. * on a Tripal website.
  6. */
  7. /**
  8. * @defgroup tripal_mviews Tripal Materialized Views
  9. * @ingroup tripal_core
  10. * @{
  11. * Contains functions for viewing and editing of Materialized Views
  12. * on a Tripal website.
  13. * @}
  14. */
  15. /**
  16. * Provides a landing page for tripal jobs admin
  17. *
  18. * @ingroup tripal_mviews
  19. */
  20. function tripal_mview_admin_view() {
  21. $output = '';
  22. // set the breadcrumb
  23. $breadcrumb = array();
  24. $breadcrumb[] = l('Home', '<front>');
  25. $breadcrumb[] = l('Administration', 'admin');
  26. $breadcrumb[] = l('Tripal', 'admin/tripal');
  27. $breadcrumb[] = l('Chado Schema', 'admin/tripal/schema');
  28. $breadcrumb[] = l('Materialized Views', 'admin/tripal/schema/mviews');
  29. drupal_set_breadcrumb($breadcrumb);
  30. // Add the view
  31. $view = views_embed_view('tripal_core_admin_mviews','default');
  32. if (isset($view)) {
  33. $output .= $view;
  34. }
  35. else {
  36. $output .= '<p>The Tripal Materalized View management system uses primarily views to provide an '
  37. . 'administrative interface. Currently one or more views needed for this '
  38. . 'administrative interface are disabled. <strong>Click each of the following links to '
  39. . 'enable the pertinent views</strong>:</p>';
  40. $output .= '<ul>';
  41. $output .= '<li>'.l('MViews View', 'admin/tripal/schema/mviews/views/mviews/enable').'</li>';
  42. $output .= '</ul>';
  43. }
  44. return $output;
  45. }
  46. /**
  47. * A template function which returns markup to display details for the current materialized view
  48. *
  49. * @param $mview_id
  50. * The unique ID of the materialized view to render
  51. *
  52. * @ingroup tripal_mviews
  53. */
  54. function tripal_mview_report($mview_id) {
  55. // get this mview details
  56. $sql = "SELECT * FROM {tripal_mviews} WHERE mview_id = :mview_id";
  57. $results = db_query($sql, array(':mview_id' => $mview_id));
  58. $mview = $results->fetchObject();
  59. $rows = array();
  60. // create a table with each row containig stats for
  61. // an individual job in the results set.
  62. $output = "<p>" . l("Return to table of materialized views", "admin/tripal/schema/mviews/") . "</p>";
  63. $output .= "<p>Details for <b>$mview->name</b>:</p>";
  64. // build the URLs using the url function so we can handle installations where
  65. // clean URLs are or are not used
  66. $update_url = url("admin/tripal/schema/mviews/action/update/$mview->mview_id");
  67. $delete_url = url("admin/tripal/schema/mviews/action/delete/$mview->mview_id");
  68. $edit_url = url("admin/tripal/schema/mviews/edit/$mview->mview_id");
  69. $rows[] = array('Actions', "<a href='$update_url'>Populate</a>, <a href='$edit_url'>Edit</a>, <a href='$delete_url'>Delete</a>");
  70. if ($mview->last_update > 0) {
  71. $update = format_date($mview->last_update);
  72. }
  73. else {
  74. $update = 'Not yet populated';
  75. }
  76. $rows[] = array('Last Update', $update);
  77. if ($mview->name) {
  78. $rows[] = array('View Name', $mview->name);
  79. }
  80. if ($mview->modulename) {
  81. $rows[] = array('Module Name', $mview->modulename);
  82. }
  83. if ($mview->mv_table) {
  84. $rows[] = array('Table Name', $mview->mv_table);
  85. }
  86. if ($mview->mv_specs) {
  87. $rows[] = array('Table Field Definitions', $mview->mv_specs);
  88. }
  89. if ($mview->query) {
  90. $rows[] = array('Query', "<textarea rows=\"15\" cols=\"120\" style=\"font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;\">" . $mview->query . "</textarea>");
  91. }
  92. if ($mview->indexed) {
  93. $rows[] = array('Indexed Fields', $mview->indexed);
  94. }
  95. if ($mview->special_index) {
  96. $rows[] = array('Special Indexed Fields', $mview->special_index);
  97. }
  98. if ($mview->mv_schema) {
  99. $rows[] = array('Drupal Schema API Definition', "<textarea rows=\"20\" cols=\"120\" style=\"font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;\">" . $mview->mv_schema . "</textarea>");
  100. }
  101. $header = array('Detail', 'Value');
  102. $table = array(
  103. 'header' => $header,
  104. 'rows' => $rows,
  105. 'attributes' => array('class' => 'tripal-data-table'),
  106. 'sticky' => FALSE,
  107. 'caption' => '',
  108. 'colgroups' => array(),
  109. 'empty' => 'There are no materialized views',
  110. );
  111. $table = theme_table($table);
  112. $output .= $table;
  113. return $output;
  114. }
  115. /**
  116. * A template function to render a listing of all Materialized Views
  117. *
  118. * @ingroup tripal_mviews
  119. */
  120. function tripal_mviews_report() {
  121. $header = array('', 'MView Name', 'Last Update', 'Status', 'Description', '');
  122. $rows = array();
  123. $mviews = db_query("SELECT * FROM {tripal_mviews} ORDER BY name");
  124. foreach ($mviews as $mview) {
  125. if ($mview->last_update > 0) {
  126. $update = format_date($mview->last_update);
  127. }
  128. else {
  129. $update = 'Not yet populated';
  130. }
  131. $rows[] = array(
  132. l(t('View'), "admin/tripal/schema/mviews/report/$mview->mview_id") . " | " .
  133. l(t('Edit'), "admin/tripal/schema/mviews/edit/$mview->mview_id") . " | " .
  134. l(t('Populate'), "admin/tripal/schema/mviews/action/update/$mview->mview_id"),
  135. $mview->name,
  136. $update,
  137. $mview->status,
  138. $mview->comment,
  139. l(t('Delete'), "admin/tripal/schema/mviews/action/delete/$mview->mview_id"),
  140. );
  141. }
  142. $rows[] = array(
  143. 'data' => array(
  144. array('data' => l(t('Create a new materialized view.'), "admin/tripal/schema/mviews/new"),
  145. 'colspan' => 6),
  146. )
  147. );
  148. $page = '</p>' . t("Materialized Views (MViews) are custom tables populated with a defined SQL statement.
  149. Because Chado is highly normalized and highly constrained it serves as a wonderful
  150. data storage platform, but unfortunately some queries may be slow. MViews alleviate slowness by aggregating data
  151. into tables that are more easy to query. Use MViews to create tables for custom search pages or custom Tripal
  152. module development.") . '</p>';
  153. $page .= '<p><b>' . t("MViews behaves in the following way:") . '</b><ul>' .
  154. '<li>' . t("The SQL statement defined for an MVIEW will be used to populate the table") . '</li>' .
  155. '<li>' . t("Altering the table structure of an MView will cause the MView table to be dropped and recreated. All records in the MView will be lost.") . '</li>' .
  156. '<li>' . t("Altering the query of an existing view will not change the MView table. No records will be lost. ") . '</li>' .
  157. '<li>' . t("Repopulating an MView that is already populated will result in replacement of all records.") . '</li>' .
  158. '<li>' . t("A database transaction will be used when populating MViews. Therefore replacement of records does not occur until the query completes. Any search forms or pages dependent on the MView will continue to function.") . '</li>' .
  159. '</ul></p>';
  160. $page .= '<b>' . t("Existing MViews") . '</b>';
  161. $table = array(
  162. 'header' => $header,
  163. 'rows' => $rows,
  164. 'attributes' => array('class' => 'tripal-data-table'),
  165. 'sticky' => FALSE,
  166. 'caption' => '',
  167. 'colgroups' => array(),
  168. 'empty' => 'There are no materialized views',
  169. );
  170. $page .= theme_table($table);
  171. return $page;
  172. }
  173. /**
  174. * A Form to Create/Edit a Materialized View
  175. *
  176. * @param $form_state
  177. * The current state of the form (Form API)
  178. * @param $mview_id
  179. * The unique ID of the Materialized View to Edit or NULL if creating a new materialized view
  180. *
  181. * @return
  182. * A form array (Form API)
  183. *
  184. * @ingroup tripal_mviews
  185. */
  186. function tripal_mviews_form($form, &$form_state = NULL, $mview_id = NULL) {
  187. if (!$mview_id) {
  188. $action = 'Add';
  189. }
  190. else {
  191. $action = 'Edit';
  192. }
  193. // set defaults for collapsed fieldsets
  194. $schema_collapsed = 0;
  195. $traditional_collapsed = 1;
  196. $default_name = '';
  197. $default_mv_table = '';
  198. $default_mv_specs = '';
  199. $default_indexed = '';
  200. $default_mvquery = '';
  201. $default_special_index = '';
  202. $default_comment = '';
  203. $default_modulename = '';
  204. $default_schema = '';
  205. // if the view is the older style legacy view then this value get's set to 1
  206. $is_legacy = 0;
  207. // get this requested view
  208. if (strcmp($action, 'Edit') == 0 ) {
  209. $sql = "SELECT * FROM {tripal_mviews} WHERE mview_id = :mview_id ";
  210. $mview = db_query($sql, array(':mview_id' => $mview_id))->fetchObject();
  211. // set the default values. If there is a value set in the
  212. // form_state then let's use that, otherwise, we'll pull
  213. // the values from the database
  214. if (array_key_exists('values', $form_state)) {
  215. $default_name = $form_state['values']['name'];
  216. $default_mv_table = $form_state['values']['mv_table'];
  217. $default_mv_specs = $form_state['values']['mv_specs'];
  218. $default_indexed = $form_state['values']['indexed'];
  219. $default_mvquery = $form_state['values']['mvquery'];
  220. $default_special_index = $form_state['values']['special_index'];
  221. $default_comment = $form_state['values']['comment'];
  222. $default_modulename = $form_state['values']['modulename'];
  223. }
  224. if (!$default_name) {
  225. $default_name = $mview->name;
  226. }
  227. if (!$default_mv_table) {
  228. $default_mv_table = $mview->mv_table;
  229. }
  230. if (!$default_mv_specs) {
  231. $default_mv_specs = $mview->mv_specs;
  232. }
  233. if (!$default_indexed) {
  234. $default_indexed = $mview->indexed;
  235. }
  236. if (!$default_mvquery) {
  237. $default_mvquery = $mview->query;
  238. }
  239. if (!$default_special_index) {
  240. $default_special_index = $mview->special_index;
  241. }
  242. if (!$default_comment) {
  243. $default_comment = $mview->comment;
  244. }
  245. if (!$default_schema) {
  246. $default_schema = $mview->mv_schema;
  247. }
  248. if (!$default_modulename) {
  249. $default_modulename = $mview->modulename ? $mview->modulename : 'tripal_core';
  250. }
  251. if ($mview->mv_specs) {
  252. $is_legacy = 1;
  253. }
  254. // the mv_table column of the tripal_mviews table always has the table
  255. // name even if it is a custom table. However, for the sake of the form,
  256. // we do not want this to show up as the mv_table is needed for the
  257. // traditional style input. We'll blank it out if we have a custom
  258. // table and it will get reset in the submit function using the
  259. // 'table' value from the schema array
  260. if ($default_schema) {
  261. $default_mv_table = '';
  262. }
  263. // set which fieldset is collapsed
  264. if (!$default_schema) {
  265. $schema_collapsed = 1;
  266. $traditional_collapsed = 0;
  267. }
  268. }
  269. // Build the form
  270. $form['action'] = array(
  271. '#type' => 'value',
  272. '#value' => $action
  273. );
  274. $form['is_legacy'] = array(
  275. '#type' => 'value',
  276. '#value' => $is_legacy
  277. );
  278. $form['mview_id'] = array(
  279. '#type' => 'value',
  280. '#value' => $mview_id
  281. );
  282. $form['modulename'] = array(
  283. '#type' => 'value',
  284. '#value' => $default_modulename,
  285. );
  286. $form['return_link'] = array(
  287. '#type' => 'item',
  288. '#description' => l("Return to table of materialized views", "admin/tripal/schema/mviews/"),
  289. );
  290. $form['name']= array(
  291. '#type' => 'textfield',
  292. '#title' => t('View Name'),
  293. '#description' => t('Please enter the name for this materialized view.'),
  294. '#required' => TRUE,
  295. '#default_value' => $default_name,
  296. );
  297. $form['comment']= array(
  298. '#type' => 'textarea',
  299. '#title' => t('MView Description'),
  300. '#description' => t('Optional. Please provide a description of the purpose for this materialized vieww.'),
  301. '#required' => FALSE,
  302. '#default_value' => $default_comment,
  303. );
  304. // add a fieldset for the Drupal Schema API
  305. $form['schema'] = array(
  306. '#type' => 'fieldset',
  307. '#title' => 'Table Schema',
  308. '#description' => t('Use a ' . l("Drupal Schema API", "https://api.drupal.org/api/drupal/includes!database!schema.inc/group/schemaapi/7", array('attributes' => array('target' => '_blank'))) .
  309. ' array to describe the table. See the bottom of this page for an example.'),
  310. '#collapsible' => 1,
  311. '#collapsed' => $schema_collapsed ,
  312. );
  313. $form['schema']['schema']= array(
  314. '#type' => 'textarea',
  315. '#title' => t('Schema Array'),
  316. '#description' => t('Please enter the ' . l("Drupal Schema API", "https://api.drupal.org/api/drupal/includes!database!schema.inc/group/schemaapi/7", array('attributes' => array('target' => '_blank'))) .
  317. ' compatible array that defines the table. There must also be a "table" key with the name of the table as the value. See the example at the bottom of this page.'),
  318. '#required' => FALSE,
  319. '#default_value' => $default_schema,
  320. '#rows' => 25,
  321. '#attributes' => array(
  322. 'style' => "font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;",
  323. ),
  324. );
  325. // only let folks edit legacy MViews, not create new ones
  326. if ($is_legacy) {
  327. // add a fieldset for the Original Table Description fields
  328. $form['traditional'] = array(
  329. '#type' => 'fieldset',
  330. '#title' => 'Legacy MViews Setup',
  331. '#description' => t('Traditionally MViews were created by specifying PostgreSQL style ' .
  332. 'column types. This method can be used but is deprecated in favor of the ' .
  333. 'newer Drupal schema API method provided above. In rare cases where the Drupal Schema API ' .
  334. 'does not support a desired data type the Legacy Mviews should be used'),
  335. '#collapsible' => 1,
  336. '#collapsed' => $traditional_collapsed,
  337. );
  338. $form['traditional']['mv_table']= array(
  339. '#type' => 'textfield',
  340. '#title' => t('Table Name'),
  341. '#description' => t('Please enter the table name that this view will generate in the database. You can use the schema and table name for querying the view'),
  342. '#required' => FALSE,
  343. '#default_value' => $default_mv_table,
  344. );
  345. $form['traditional']['mv_specs']= array(
  346. '#type' => 'textarea',
  347. '#title' => t('Table Definition'),
  348. '#description' => t('Please enter the field definitions for this view. Each field should be separated by a comma or enter each field definition on each line.'),
  349. '#required' => FALSE,
  350. '#default_value' => $default_mv_specs,
  351. );
  352. $form['traditional']['indexed']= array(
  353. '#type' => 'textarea',
  354. '#title' => t('Indexed Fields'),
  355. '#description' => t('Please enter the field names (as provided in the table definition above) that will be indexed for this view. Separate by a comma or enter each field on a new line.'),
  356. '#required' => FALSE,
  357. '#default_value' => $default_indexed,
  358. );
  359. /**
  360. $form['traditional']['special_index']= array(
  361. '#type' => 'textarea',
  362. '#title' => t('View Name'),
  363. '#description' => t('Please enter the name for this materialized view.'),
  364. '#required' => TRUE,
  365. '#default_value' => $default_special_index,
  366. );
  367. */
  368. }
  369. $form['mvquery']= array(
  370. '#type' => 'textarea',
  371. '#title' => t('Query'),
  372. '#description' => t('Please enter the SQL statement used to populate the table.'),
  373. '#required' => TRUE,
  374. '#default_value' => $default_mvquery,
  375. '#rows' => 25,
  376. '#attributes' => array(
  377. 'style' => "font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;",
  378. ),
  379. );
  380. if ($action == 'Edit') {
  381. $value = 'Save';
  382. }
  383. if ($action == 'Add') {
  384. $value = 'Add';
  385. }
  386. $form['submit'] = array(
  387. '#type' => 'submit',
  388. '#value' => t($value),
  389. '#executes_submit_callback' => TRUE,
  390. );
  391. $form['#redirect'] = 'admin/tripal/schema/mviews';
  392. $form['example']= array(
  393. '#type' => 'item',
  394. '#description' => "<br>An example Schema API definition for a materialized view: <pre>
  395. array (
  396. 'description' => 'Stores the type and number of features per organism',
  397. 'table' => 'organism_feature_count',
  398. 'fields' => array (
  399. 'organism_id' => array (
  400. 'type' => 'int',
  401. 'not null' => true,
  402. ),
  403. 'genus' => array (
  404. 'type' => 'varchar',
  405. 'length' => '255',
  406. 'not null' => true,
  407. ),
  408. 'species' => array (
  409. 'type' => 'varchar',
  410. 'length' => '255',
  411. 'not null' => true,
  412. ),
  413. 'common_name' => array (
  414. 'type' => 'varchar',
  415. 'length' => '255',
  416. 'not null' => false,
  417. ),
  418. 'num_features' => array (
  419. 'type' => 'int',
  420. 'not null' => true,
  421. ),
  422. 'cvterm_id' => array (
  423. 'type' => 'int',
  424. 'not null' => true,
  425. ),
  426. 'feature_type' => array (
  427. 'type' => 'varchar',
  428. 'length' => '255',
  429. 'not null' => true,
  430. ),
  431. ),
  432. 'indexes' => array (
  433. 'organism_feature_count_idx1' => array ('organism_id'),
  434. 'organism_feature_count_idx2' => array ('cvterm_id'),
  435. 'organism_feature_count_idx3' => array ('feature_type'),
  436. ),
  437. )
  438. </pre>"
  439. );
  440. return $form;
  441. }
  442. /**
  443. * Validate the Create/Edit Materialized View Form
  444. * Implements hook_form_validate().
  445. *
  446. * @ingroup tripal_mviews
  447. */
  448. function tripal_mviews_form_validate($form, &$form_state) {
  449. $action = $form_state['values']['action'];
  450. $mview_id = $form_state['values']['mview_id'];
  451. $name = $form_state['values']['name'];
  452. $is_legacy = $form_state['values']['is_legacy'];
  453. $query = $form_state['values']['mvquery'];
  454. // if this is a legacy materialized view (no longer supported in Tripal v2.0
  455. // but code left just in case)
  456. if ($is_legacy) {
  457. $mv_table = $form_state['values']['mv_table'];
  458. $mv_specs = $form_state['values']['mv_specs'];
  459. $indexed = $form_state['values']['indexed'];
  460. $special_index = '';//$form_state['values']['special_index'];
  461. }
  462. else {
  463. $mv_table = '';
  464. $mv_specs = '';
  465. $indexed = '';
  466. $special_index = '';
  467. }
  468. $comment = $form_state['values']['comment'];
  469. $schema = $form_state['values']['schema'];
  470. // if both the schema and the older fields for the legacy view are populated then
  471. // this is an error and we need to let the user know.
  472. if ($schema and ($mv_table or $mv_specs or $indexed or $special_index)) {
  473. form_set_error($form_state['values']['schema'],
  474. t('You can create an MView using the Drupal Schema API method or the ' .
  475. 'traditional method but not both.'));
  476. }
  477. // if we don't have a schema and are missing fields for the legacy views then
  478. // inform the user.
  479. if (!$schema) {
  480. if (!$mv_specs) {
  481. form_set_error($form_state['values']['mv_specs'],
  482. t('The Table Definition field is required.'));
  483. }
  484. if (!$mv_table) {
  485. form_set_error($form_state['values']['mv_table'],
  486. t('The Table Name field is required.'));
  487. }
  488. }
  489. // make sure the array is valid
  490. if ($schema) {
  491. $success = eval("\$schema_array = $schema;");
  492. if ($success === FALSE) {
  493. $error = error_get_last();
  494. form_set_error($form_state['values']['schema'],
  495. t("The schema array is improperly formatted. Parse Error : " . $error["message"]));
  496. }
  497. if (!array_key_exists('table', $schema_array)) {
  498. form_set_error($form_state['values']['schema'],
  499. t("The schema array must have key named 'table'"));
  500. }
  501. // TODO: add in more validation checks of the array to help the user
  502. }
  503. }
  504. /**
  505. * Submit the Create/Edit Materialized View Form
  506. * Implements hook_form_submit().
  507. *
  508. * @ingroup tripal_mviews
  509. */
  510. function tripal_mviews_form_submit($form, &$form_state) {
  511. $ret = array();
  512. $action = $form_state['values']['action'];
  513. $mview_id = $form_state['values']['mview_id'];
  514. $name = $form_state['values']['name'];
  515. $is_legacy = $form_state['values']['is_legacy'];
  516. $query = $form_state['values']['mvquery'];
  517. $comment = $form_state['values']['comment'];
  518. $schema = $form_state['values']['schema'];
  519. $modulename = $form_state['values']['modulename'];
  520. $mv_table = '';
  521. $mv_specs = '';
  522. $indexed = '';
  523. $special_index = '';
  524. // if this is a legacy materialized view (no longer supported in Tripal v2.0
  525. // but code left just in case)
  526. if ($is_legacy) {
  527. $mv_table = $form_state['values']['mv_table'];
  528. $mv_specs = $form_state['values']['mv_specs'];
  529. $indexed = $form_state['values']['indexed'];
  530. $special_index = '';//$form_state['values']['special_index'];
  531. }
  532. if (!$modulename) {
  533. $modulename = 'tripal_core';
  534. }
  535. // if this is an edit action
  536. if (strcmp($action, 'Edit') == 0) {
  537. tripal_edit_mview($mview_id, $name, $modulename, $mv_table, $mv_specs,
  538. $indexed, $query, $special_index, $comment, $schema);
  539. }
  540. // else an add action
  541. elseif (strcmp($action, 'Add') == 0) {
  542. // convert the schema into a PHP array
  543. $schema_arr = array();
  544. eval("\$schema_arr = $schema;");
  545. tripal_add_mview($name, $modulename, $schema_arr, $query, $comment);
  546. }
  547. else {
  548. drupal_set_message(t("No action performed."));
  549. }
  550. return '';
  551. }