views_handler_join_chado_aggregator.inc 9.7 KB

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