views_handler_join_chado_aggregator.inc 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <?php
  2. /**
  3. * @file
  4. * Handler to allow joins between records via a linking table
  5. *
  6. * Example Usage:
  7. * To join the analysisprop table to the analysis table,
  8. * Use the following code in the analysisprop hook_views_data:
  9. * @code
  10. $data['analysisprop']['table']['join']['analysis'] = array(
  11. 'left_field' => 'analysis_id',
  12. 'field' => 'analysis_id',
  13. 'handler' => 'views_handler_join_chado_aggregator',
  14. 'pre-aggregated' => TRUE | FALSE //whether the table is already aggregated (contains arrays)
  15. 'table_aggregated' => CURRENT | LEFT //the table which has many records for each record in the other
  16. );
  17. * @endcode
  18. */
  19. class views_handler_join_chado_aggregator extends views_join {
  20. // PHP 4 doesn't call constructors of the base class automatically from a
  21. // constructor of a derived class. It is your responsibility to propagate
  22. // the call to constructors upstream where appropriate.
  23. function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT', $added = NULL) {
  24. parent::construct($table, $left_table, $left_field, $field, $extra, $type);
  25. // Determine the postgresql version
  26. $postgresql_version = pg_version();
  27. $this->postgresql_version = $postgresql_version['client'];
  28. // If version is 9.0+ then indicate
  29. // Needed to apply sorting for aggregated fields
  30. if (intval($postgresql_version['client']) >= 9) {
  31. $this->postgresql_9up = TRUE;
  32. }
  33. }
  34. /**
  35. * Creates SQL including aggregation query used in join
  36. */
  37. function join($table, &$query) {
  38. $output = array();
  39. // Create the table SQL (used in join) -------
  40. // query creating one-to-one table using array_agg
  41. // Only aggregate each field if it the join table hadn't been pre-aggregated
  42. // Example where it might be pre-aggregated: Materialized view
  43. if (!$this->definition['pre-aggregated']) {
  44. // Determine Order BY's for aggregates
  45. $order_by = array();
  46. if (!is_array($this->sort)) {
  47. $this->sort = array();
  48. }
  49. foreach ($this->sort as $s) {
  50. $order_by[] = $s['table'] . '.' . $s['field'] . ' ' . $s['order'];
  51. }
  52. // get table description (if defined via schema api)
  53. $table_desc = module_invoke_all('chado_' . $this->definition['table'] . '_schema');
  54. $select_fields[ $this->definition['table'] ] = $table_desc['fields'];
  55. if (!empty($table_desc)) {
  56. // Add joins to tables with a foreign key in this table
  57. // (ie: add join to cvterm if this table has a type_id
  58. $joins = array();
  59. foreach ($table_desc['foreign keys'] as $defn) {
  60. if ($defn['table'] != $this->left_table) {
  61. foreach ( $defn['columns'] as $left => $right) {
  62. $left = $this->definition['table'] . '.' . $left;
  63. $right = $defn['table'] . '.' . $right;
  64. $joins[] = "LEFT JOIN $defn[table] $defn[table] ON $left=$right";
  65. }
  66. // Fields to be selected from joined table
  67. $join_table = module_invoke_all('chado_' . $defn['table'] . '_schema');
  68. $select_fields[ $defn['table'] ] = $join_table['fields'];
  69. }
  70. }
  71. // Fields to be selected
  72. foreach ($select_fields as $table => $table_fields) {
  73. foreach ($table_fields as $fname => $f) {
  74. $alias = '';
  75. if ($table != $this->definition['table']) {
  76. $alias = $table . '_';
  77. }
  78. if ($fname != $this->definition['field']) {
  79. // Add sort to aggregate field if postgreSQL 9.0+
  80. if ($this->postgresql_9up && !empty($order_by)) {
  81. $fields[] = 'array_agg(' . $table . '.' . $fname . ' ORDER BY ' . implode(',', $order_by) . ') as ' . $alias . $fname;
  82. }
  83. else {
  84. $fields[] = 'array_agg(' . $table . '.' . $fname . ') as '. $alias . $fname;
  85. }
  86. $composite_field_parts[] = "'" . $alias . $fname . "::' ||" . $table . '.' . $fname;
  87. }
  88. else {
  89. $fields[] = $fname;
  90. $composite_field_parts[] = "'" . $alias . $fname . "::' ||" . $table . '.' . $fname;
  91. }
  92. }
  93. }
  94. // There is no definition in schema api
  95. // then use postgresql select
  96. }
  97. else {
  98. // No known foreign key reelationships
  99. $joins = array();
  100. // Fields to be selected
  101. $sql = "SELECT
  102. attname as column,
  103. format_type(atttypid, atttypmod) as datatype
  104. FROM pg_attribute, pg_type
  105. WHERE typname='nd_genotype_experiment'
  106. AND attrelid=typrelid
  107. AND attname NOT IN ('cmin','cmax','ctid','oid','tableoid','xmin','xmax')";
  108. $previous_db = tripal_db_set_active('chado');
  109. $resource = db_query($sql);
  110. tripal_db_set_active($previous_db);
  111. while ($r = db_fetch_object($resource)) {
  112. $table = $this->definition['table'];
  113. $alias = ''; //no alias needed if table is current table (only option if no schema api definition)
  114. $fname = $r->column;
  115. if ($fname != $this->definition['field']) {
  116. // Add sort to aggregate field if postgreSQL 9.0+
  117. if ($this->postgresql_9up && !empty($order_by)) {
  118. $fields[] = 'array_agg(' . $table . '.' . $fname . ' ORDER BY ' . implode(',', $order_by) . ') as ' . $alias . $fname;
  119. }
  120. else {
  121. $fields[] = 'array_agg(' . $table . '.' . $fname . ') as ' . $alias . $fname;
  122. }
  123. $composite_field_parts[] = "'" . $alias . $fname . "::' ||" . $table . '.' . $fname;
  124. }
  125. else {
  126. $fields[] = $fname;
  127. $composite_field_parts[] = "'" . $alias . $fname . "::' ||" . $table . '.' . $fname;
  128. }
  129. }
  130. }
  131. // composite field
  132. // (combines all other fields before aggregating)
  133. // Add sort to aggregate field if postgreSQL 9.0+
  134. if ($this->postgresql_9up && !empty($order_by)) {
  135. $composite_field = "array_agg('{'||" . implode(" || ',' || ", $composite_field_parts) . "||'}' ORDER BY " . implode(',', $order_by) . ") as all";
  136. }
  137. else {
  138. $composite_field = "array_agg('{'||" . implode(" || ',' || ", $composite_field_parts) . "||'}') as all";
  139. }
  140. $fields[] = $composite_field;
  141. // SQL to use in the join
  142. $sql = 'SELECT ' . implode(', ', $fields)
  143. .' FROM ' . $this->definition['table']
  144. .' ' . implode(' ', $joins);
  145. if (!empty($this->filter)) {
  146. $sql .= ' WHERE ' . implode(', ', $this->filter);
  147. }
  148. $sql .= ' GROUP BY ' . $this->definition['field'];
  149. // Create the join (full SQL) ----------------
  150. $output[] = $this->create_single_join(
  151. $query,
  152. array(
  153. 'table' => $this->definition['table'],
  154. 'field' => $this->definition['field'],
  155. 'table_sql' => $sql,
  156. 'is_drupal' => FALSE,
  157. ),
  158. array(
  159. 'table' => $this->definition['left_table'],
  160. 'field' => $this->definition['left_field'],
  161. ),
  162. 'LEFT'
  163. );
  164. // Otherwise the table has been pre-aggregated
  165. // Then only need to do a regular join with any in where
  166. }
  167. else {
  168. // Create the join
  169. $current_table_spec = array(
  170. 'table' => $this->definition['table'],
  171. 'field' => $this->definition['field'],
  172. 'is_drupal' => FALSE,
  173. );
  174. $left_table_spec = array(
  175. 'table' => $this->definition['left_table'],
  176. 'field' => $this->definition['left_field'],
  177. );
  178. switch ($this->definition['table_aggregated']) {
  179. default:
  180. case 'CURRENT':
  181. $current_table_spec['pre-aggregated'] = TRUE;
  182. break;
  183. case 'LEFT':
  184. $left_table_spec['pre-aggregated'] = TRUE;
  185. break;
  186. }
  187. $output[] = $this->create_single_join(
  188. $query,
  189. $current_table_spec,
  190. $left_table_spec,
  191. 'LEFT'
  192. );
  193. }
  194. return implode("\n", $output);
  195. }
  196. /**
  197. * Creates SQL for a single join based on parameters
  198. * Join will be: <type> JOIN (<query creating one-to-one table using array_agg>) <table alias>
  199. * ON <qualified left field>=<qualified right field>
  200. */
  201. function create_single_join(&$query, $right_spec, $left_spec, $join_type) {
  202. if ($right_spec['table']) {
  203. $right = $query->get_table_info($right_spec['table']);
  204. if (!$right['alias']) {
  205. $right['alias'] = $right_spec['table'];
  206. }
  207. $right_field = "$right[alias].$right_spec[field]";
  208. // Add any() around field if already aggregated
  209. if ($right_spec['pre-aggregated']) {
  210. $right_field = "any(" . $right_field . ")";
  211. }
  212. // Add drupal { } around table
  213. if ($right_spec['is_drupal']) {
  214. $right_table = '{' . $right_spec['table'] . '}';
  215. }
  216. else {
  217. $right_table = $right_spec['table'];
  218. }
  219. }
  220. if ($left_spec['table']) {
  221. $left = $query->get_table_info($left_spec['table']);
  222. if (!$left['alias']) {
  223. $left['alias'] = $left_spec['table'];
  224. }
  225. $left_field = "$left[alias].$left_spec[field]";
  226. }
  227. else {
  228. // This can be used if left_field is a formula or something. It should be used only *very* rarely.
  229. $left_field = $this->left_spec['field'];
  230. }
  231. // Add any() around field if already aggregated
  232. if ($left_spec['pre-aggregated']) {
  233. $left_field = "any(" . $left_field . ")";
  234. }
  235. // Concatenate parts together to form join sql
  236. if (!empty($right_spec['table_sql'])) {
  237. $output = " $join_type JOIN ($right_spec[table_sql]) $right[alias] ON $left_field = $right_field";
  238. }
  239. else {
  240. $output = " $join_type JOIN $right_spec[table] $right[alias] ON $left_field = $right_field";
  241. }
  242. return $output;
  243. }
  244. }