Browse Source

Fixed term/vocab pages to include more detail. Working on cvtermpath fix

Stephen Ficklin 6 years ago
parent
commit
e4676b1a00

+ 0 - 2
tripal/api/tripal.terms.api.inc

@@ -155,7 +155,6 @@ function hook_vocab_get_term($vocabulary, $accession) {
 function hook_vocab_get_terms($vocabulary, $limit = 25, $element = 0) {
   // See the tripal_chado_vocab_get_terms() function for an example.
 }
-
 /**
  * Hook used by the default term storage backend to provide children for a term.
  *
@@ -423,7 +422,6 @@ function tripal_get_vocabulary_root_terms($vocabulary) {
     }
   }
 }
-
 /**
  * Retrieves the immediate children of the given term.
  *

+ 3 - 2
tripal/includes/TripalJob.inc

@@ -509,8 +509,9 @@ class TripalJob {
     if ($diff >= $this->interval) {
       $duration = (time() - $this->progress_start_time) / 60;
       $duration = sprintf("%.2f", $duration);
-      $memory = number_format(memory_get_usage());
-      print "Percent complete: " . $percent . "%. Memory: " . $memory . " bytes. Duration: " . $duration . " mins\r";
+      $memory = memory_get_usage();
+      $fmemory = number_format($memory);
+      print "Percent complete: " . $percent . "%. Memory: " . $fmemory . " bytes. Duration: " . $duration . " mins\r";
       $this->prev_update = $diff;
       $this->setProgress($percent);
     }

+ 140 - 107
tripal/includes/tripal.term_lookup.inc

@@ -59,6 +59,8 @@ function tripal_vocabulary_lookup_vocab_page($vocabulary) {
   drupal_set_breadcrumb($breadcrumb);
 
   $vocab = tripal_get_vocabulary_details($vocabulary);
+  $vocab_table = tripal_vocabulary_get_vocab_details($vocab);
+  
   if ($vocab['description']) {
     drupal_set_title($vocabulary . ': ' . $vocab['description']);
   }
@@ -71,58 +73,7 @@ function tripal_vocabulary_lookup_vocab_page($vocabulary) {
     drupal_set_message('The vocabulary cannot be found on this site', 'error');
     return '';
   }
-
-  $headers = array();
-  $rows = array();
-  $vocab_name = $vocab['name'];
-  $short_name = $vocab['short_name'];
-  if ($vocab['url']) {
-    $short_name = l($vocab['short_name'], $vocab['url'], array('attributes' => array('target' => '_blank')));
-  }
-  $vocab_desc = $vocab['description'];
-  $rows[] = array(
-    array(
-      'data' => 'Short Name',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    $short_name,
-  );
-  $rows[] = array(
-    array(
-      'data' => 'Vocabulary Name(s)',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    $vocab_name,
-  );
-  $rows[] = array(
-    array(
-      'data' => 'Description',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    $vocab_desc,
-  );
-  $rows[] = array(
-    array(
-      'data' => 'Number of Terms Loaded on This Site',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    number_format($vocab['num_terms']),
-  );
-
-  $table = array(
-    'header' => $headers,
-    'rows' => $rows,
-    'attributes' => array(),
-    'sticky' => FALSE,
-    'caption' => '',
-    'colgroups' => array(),
-    'empty' => '',
-  );
-
+  
   $has_root = TRUE;
   $root_terms = tripal_get_vocabulary_root_terms($vocabulary);
   // If this vocabulary doesn't have root terms then it's either not an
@@ -156,7 +107,9 @@ function tripal_vocabulary_lookup_vocab_page($vocabulary) {
     'vocab_table' => array(
       '#type' => 'item',
       '#title' => 'Details',
-      '#markup' => '<p>A vocabulary is always identified by its short name and sometimes it may offer multiple sub-vocabularies with different names. Both are listed below.</p>' . theme_table($table),
+      '#markup' => '<p>A vocabulary is always identified by its short name ' . 
+        'and sometimes it may offer multiple sub-vocabularies with different ' . 
+        'names. Both are listed below.</p>' . $vocab_table,
     ),
     'vocab_browser' => array(
       '#type' => 'item',
@@ -178,6 +131,69 @@ function tripal_vocabulary_lookup_vocab_page($vocabulary) {
   return $content;
 }
 
+/**
+ * Generates a table view of the vocabulary.
+ * 
+ * @param $vocab
+ *   The vocabulary array.
+ * @return 
+ *    An HTML rendered table describing the vocabulary.
+ */
+function tripal_vocabulary_get_vocab_details($vocab) {
+  $headers = array();
+  $rows = array();
+  $vocab_name = $vocab['name'];
+  $short_name = $vocab['short_name'];
+  if ($vocab['url']) {
+    $short_name = l($vocab['short_name'], $vocab['url'], array('attributes' => array('target' => '_blank')));
+  }
+  $vocab_desc = $vocab['description'];
+  $rows[] = array(
+    array(
+      'data' => 'Short Name',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $short_name,
+  );
+  $rows[] = array(
+    array(
+      'data' => 'Vocabulary Name(s)',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $vocab_name,
+  );
+  $rows[] = array(
+    array(
+      'data' => 'Description',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $vocab_desc,
+  );
+  $rows[] = array(
+    array(
+      'data' => 'Number of Terms Loaded on This Site',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    number_format($vocab['num_terms']),
+  );
+  
+  $table = array(
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => array(),
+    'sticky' => FALSE,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => '',
+  );
+  
+  return theme_table($table);
+}
+
 /**
  * A helper function to format an array of terms into a list for the web page.
  *
@@ -203,9 +219,6 @@ function tripal_vocabulary_lookup_term_children_format($children) {
     if ($child['accession'] != $child['name']) {
       $items .= ' [' . $child['vocabulary']['short_name'] . ':' . $child['accession'] . '] ';
     }
-    if ($num_grand > 0) {
-      $items .= ' (' . $num_grand . ')';
-    }
     $items .= '</li>';
   }
   $items .= '</ul>';
@@ -257,9 +270,10 @@ function tripal_vocabulary_lookup_term_page($vocabulary, $accession) {
   $breadcrumb[] = l($vocabulary, 'cv/lookup/' . $vocabulary);
   drupal_set_breadcrumb($breadcrumb);
 
+  $vocab = tripal_get_vocabulary_details($vocabulary);
+  $vocab_table = tripal_vocabulary_get_vocab_details($vocab);
+  
   $term = tripal_get_term_details($vocabulary, $accession);
-  drupal_set_title($term['name']);
-
 
   // If we can't find the term then just return a message.
   if (!$term) {
@@ -272,8 +286,12 @@ function tripal_vocabulary_lookup_term_page($vocabulary, $accession) {
   $rows = array();
   $term_name = $term['name'];
   $accession = $term['vocabulary']['short_name'] . ':' . $term['accession'];
-  if ($term['url']) {
-    $term_name = l($term['name'], $term['url'], array('attributes' => array('target' => '_blank')));
+  drupal_set_title($accession);
+  
+  // Create a URL to point this term to it's source page, but only if the
+  // source is not this site.
+  if ($term['url'] and !preg_match('/cv\/lookup/', $term['url'])) {
+    $accession = l($accession, $term['url'], array('attributes' => array('target' => '_blank')));
   }
   $rows[] = array(
     array(
@@ -299,63 +317,78 @@ function tripal_vocabulary_lookup_term_page($vocabulary, $accession) {
     ),
     $term['definition'],
   );
-
-
-  $table = array(
-    'header' => $headers,
-    'rows' => $rows,
-    'attributes' => array(),
-    'sticky' => FALSE,
-    'caption' => 'Term Details',
-    'colgroups' => array(),
-    'empty' => '',
-  );
-  $content = theme_table($table);
-
-
-  $rows = array();
-  $vocab_name = $term['vocabulary']['name'];
-  if ($term['vocabulary']['url']) {
-    $vocab_name = l($term['vocabulary']['name'], $term['vocabulary']['url'], array('attributes' => array('target' => '_blank')));
+  
+  // Now iterate through any other columns in the term array and add those
+  // details.
+  foreach ($term as $key => $value) {
+    if (in_array($key, ['name', 'definition', 'vocabulary', 'accession', 'url'])) {
+      continue;
+    }
+    // Convert thisto an array so we can alter it.
+    if (!is_array($value)) {
+      $new_values[] = $value;
+      $value = $new_values;
+    }
+    // If this is a relationship key then let's try to rewrite the GO 
+    // term in the relationship as a link.
+    if ($key == 'relationship') {
+      foreach ($value as $index => $v) {
+        $matches = [];
+        if (preg_match('/^(.+)\s(.+?):(.+?)$/', $v, $matches)) {
+          $rel = $matches[1];
+          $voc = $matches[2];
+          $acc = $matches[3];
+          $v = $rel . ' ' . l($voc . ':' . $acc, 'cv/lookup/' . $voc . '/' . $acc, ['attributes' => ['target' => '_blank']]);
+          $t = tripal_get_term_details($voc, $acc);
+          if ($t) {
+            $v .= ' (' . $t['name'] . ')';
+          }
+          $value[$index] = $v;
+        }
+      }
+    }
+    if (count($value) > 1) {
+      $value_str = theme_item_list([
+        'items' => $value,
+        'type' => 'ul',
+        'attributes' => [],
+        'title' => '',
+      ]);
+    }
+    else {
+      $value_str = $value[0];
+    }
+    $rows[] = array(
+      array(
+        'data' => ucfirst($key),
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      $value_str,
+    );
   }
-  $short_name = $term['vocabulary']['short_name'];
-  $vocab_desc = $term['vocabulary']['description'];
-  $rows[] = array(
-    array(
-      'data' => 'Name',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    $vocab_name,
-  );
-  $rows[] = array(
-    array(
-      'data' => 'Short Name',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    $short_name,
-  );
-  $rows[] = array(
-    array(
-      'data' => 'Description',
-      'header' => TRUE,
-      'width' => '20%',
-    ),
-    $vocab_desc,
-  );
 
   $table = array(
     'header' => $headers,
     'rows' => $rows,
     'attributes' => array(),
     'sticky' => FALSE,
-    'caption' => 'Term Vocabulary details',
+    'caption' => '',
     'colgroups' => array(),
     'empty' => '',
   );
-  $content .=  theme_table($table);
-
+  $content['cvterm'] = [
+    '#type' => 'item',
+    '#title' => 'Term Details',
+    '#markup' => theme_table($table),
+  ];
+  
+  $content['vocabulary'] = [
+    '#type' => 'item',
+    '#title' => 'Vocabulary Details',
+    '#markup' => $vocab_table,
+  ];
+  
   drupal_add_js(array(
     'tripal' => array(
       'cv_lookup' => array(

+ 65 - 25
tripal_chado/api/ChadoRecord.inc

@@ -189,6 +189,7 @@ class ChadoRecord {
         }
         $this->record_id = $record_id;
         $this->values = $values;
+        $this->missing_required_col = [];
       }
       catch (Exception $e) {
         $message = t('ChadoRecord::_construct(). Could not find a record in table, !table, with the given !pkey: !record_id. ERROR: !error',
@@ -276,8 +277,12 @@ class ChadoRecord {
 
     // Additionally, make sure we have all the required values!
     if (!empty($this->missing_required_col)) {
-      $message = t('ChadoRecord::insert(). The columns named, "!columns", require a value for the table: "!table". You can set these values using ChadoRecord::setValues().',
-        ['!columns' => implode('", "', $this->missing_required_col), '!table' => $this->table_name]);
+      $message = t('ChadoRecord::insert(). The columns named, "!columns", ' . 
+        'require a value for the table: "!table". You can set these values ' .
+        'using ChadoRecord::setValues(). Current values: !values.',
+        ['!columns' => implode('", "', $this->missing_required_col), 
+         '!table' => $this->table_name,
+         '!values' => print_r($this->values, TRUE)]);
       throw new Exception($message);
     }
 
@@ -303,8 +308,11 @@ class ChadoRecord {
       $this->find();
     }
     catch (Exception $e) {
-      $message = t('ChadoRecord::insert(). Could not insert a record into the table, !table, with the following values: !values. ERROR: !error',
-        ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
+      $message = t('ChadoRecord::insert(). Could not insert a record into the ' . 
+        'table, !table, with the following values: !values. ERROR: !error',
+        ['!table' => $this->table_name, 
+         '!values' => print_r($this->values, TRUE), 
+         '!error' => $e->getMessage()]);
       throw new Exception($message);
     }
   }
@@ -323,22 +331,29 @@ class ChadoRecord {
 
     // Make sure we have values for this record before updating.
     if (empty($this->values)) {
-      $message = t('ChadoRecord::update(). Could not update a record into the table, !table, without any values.',
+      $message = t('ChadoRecord::update(). Could not update a record into the ' . 
+        'table, !table, without any values.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
 
     // Additionally, make sure we have all the required values!
     if (!empty($this->missing_required_col)) {
-      $message = t('ChadoRecord::update(). The columns named, "!columns", require a value for the table: "!table". You can set these values using ChadoRecord::setValues().',
-        ['!columns' => implode('", "', $this->missing_required_col), '!table' => $this->table_name]);
+      $message = t('ChadoRecord::update(). The columns named, "!columns", ' . 
+        'require a value for the table: "!table". You can set these values ' . 
+        'using ChadoRecord::setValues(). Current values: !values.',
+        ['!columns' => implode('", "', $this->missing_required_col), 
+         '!table' => $this->table_name,
+         '!values' => print_r($this->values, TRUE)]);
       throw new Exception($message);
     }
 
     // We have to have a record ID for the record to update.
     if (!$this->record_id) {
-      $message = t('ChadoRecord::update(). Could not update a record in the table, !table, without a record ID.',
-        ['!table' => $this->table_name]);
+      $message = t('ChadoRecord::update(). Could not update a record in the ' .
+        'table, !table, without a record ID. Current values: !values.',
+        ['!table' => $this->table_name,
+         '!values' => print_r($this->values, TRUE)]);
       throw new Exception($message);
     }
 
@@ -363,7 +378,9 @@ class ChadoRecord {
       chado_query($sql, $update_args);
     }
     catch (Exception $e) {
-      $message = t('ChadoRecord::update(). Could not update a record in the table, !table, with !record_id as the record ID and the following values: !values. ERROR: !error',
+      $message = t('ChadoRecord::update(). Could not update a record in the ' .
+        'table, !table, with !record_id as the record ID and the following ' .
+        'values: !values. ERROR: !error',
         ['!table' => $this->table_name,
          '!record_id' => $this->record_id,
          '!values' => print_r($this->values, TRUE),
@@ -431,8 +448,11 @@ class ChadoRecord {
         $this->values[$column] = $value;
       }
       else {
-        $message = t('ChadoRecord::setValues(). The column named, "!column", does not exist in table: "!table". Values: !values".',
-          ['!column' => $column, '!table' => $this->table_name, '!values' => print_r($values, TRUE)]);
+        $message = t('ChadoRecord::setValues(). The column named, "!column", ' . 
+          'does not exist in table: "!table". Values: !values".',
+          ['!column' => $column, 
+           '!table' => $this->table_name, 
+           '!values' => print_r($values, TRUE)]);
         throw new Exception($message);
       }
     }
@@ -460,8 +480,13 @@ class ChadoRecord {
     // Ensure that no values are arrays.
     foreach ($values as $column => $value) {
       if (is_array($value)) {
-        $message = t('ChadoRecord::setValues(). The column named, "!column", must be a single value but is currently: "!values". NOTE: we currently don\'t support expanding foreign key relationships or multiple values for a given column.',
-          ['!column' => $column, '!table' => $this->table_name, '!values' => implode('", "', $value)]);
+        $message = t('ChadoRecord::setValues(). The column named, "!column", ' . 
+          'must be a single value but is currently: "!values". NOTE: this function ' .
+          'currently does not support expanding foreign key relationships or ' .
+          'multiple values for a given column.',
+          ['!column' => $column, 
+           '!table' => $this->table_name, 
+           '!values' => implode('", "', $value)]);
         throw new Exception($message);
       }
     }
@@ -496,15 +521,19 @@ class ChadoRecord {
 
     // Make sure the column is valid.
     if (!in_array($column_name, $this->column_names)) {
-      $message = t('ChadoRecord::setValue(). The column named, "!column", does not exist in table: "!table".',
-        ['!column' => $column_name, '!table' => $this->table_name]);
+      $message = t('ChadoRecord::setValue(). The column named, "!column", does ' . 
+        'not exist in table: "!table".',
+        ['!column' => $column_name, 
+         '!table' => $this->table_name]);
       throw new Exception($message);
     }
 
     // Make sure that the value is not NULL if this is a required field.
     if (!in_array($column_name, $this->required_cols) and $value == '__NULL__') {
-      $message = t('ChadoRecord::setValue(). The column named, "!column", requires a value for the table: "!table".',
-        ['!column' => $column_name, '!table' => $this->table_name]);
+      $message = t('ChadoRecord::setValue(). The column named, "!column", ' . 
+        'requires a value for the table: "!table".',
+        ['!column' => $column_name, 
+         '!table' => $this->table_name]);
       throw new Exception($message);
     }
     
@@ -515,8 +544,13 @@ class ChadoRecord {
 
     // Ensure that no values are arrays.
     if (is_array($value)) {
-      $message = t('ChadoRecord::setValue(). The column named, "!column", must be a single value but is currently: "!values". NOTE: we currently don\'t support expanding foreign key relationships or multiple values for a given column.',
-        ['!column' => $column, '!table' => $this->table_name, '!values' => implode('", "', $value)]);
+      $message = t('ChadoRecord::setValue(). The column named, "!column", ' . 
+        'must be a single value but is currently: "!values". NOTE: this function ' . 
+        'currently does not support expanding foreign key relationships or ' .
+        'multiple values for a given column.',
+        ['!column' => $column, 
+         '!table' => $this->table_name, 
+         '!values' => implode('", "', $value)]);
       throw new Exception($message);
     }
 
@@ -533,8 +567,10 @@ class ChadoRecord {
 
     // Make sure the column is valid.
     if (!in_array($column_name, $this->column_names)) {
-      $message = t('ChadoRecord::getValue(). The column named, "!column", does not exist in table: "!table".',
-        ['!column' => $column_name, '!table' => $this->table_name]);
+      $message = t('ChadoRecord::getValue(). The column named, "!column", ' . 
+        'does not exist in table: "!table".',
+        ['!column' => $column_name, 
+         '!table' => $this->table_name]);
       throw new Exception($message);
     }
 
@@ -565,7 +601,8 @@ class ChadoRecord {
 
     // Make sure we have values for this record before searching.
     if (empty($this->values)) {
-      $message = t('ChadoRecord::find(). Could not find a record from the table, !table, without any values.',
+      $message = t('ChadoRecord::find(). Could not find a record from ' . 
+        'the table, !table, without any values.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
@@ -581,8 +618,11 @@ class ChadoRecord {
       $results = chado_query($sql, $select_args);
     }
     catch (Exception $e) {
-      $message = t('ChadoRecord::find(). Could not find a record in the table, !table, with the following values: !values. ERROR: !error',
-        ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
+      $message = t('ChadoRecord::find(). Could not find a record in the ' . 
+        'table, !table, with the following values: !values. ERROR: !error',
+        ['!table' => $this->table_name, 
+         '!values' => print_r($this->values, TRUE), 
+         '!error' => $e->getMessage()]);
       throw new Exception($message);
     }
 

+ 223 - 73
tripal_chado/api/modules/tripal_chado.cv.api.inc

@@ -413,25 +413,39 @@ function _chado_update_cvtermpath_add_constraints() {
     "FOREIGN KEY (type_id) REFERENCES cvterm(cvterm_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED";
   db_query($sql);
 }
+
 /**
- * Duplicate of fill_cvtermpath() stored procedure in Chado.
+ * Replacement for the fill_cvtermpath() stored procedure in Chado.
  *
- * Identifies all of the root terms of the controlled vocabulary. These
- * root terms are then processed by calling the
- * _chado_update_cvtermpath_root_loop() function on each one.
+ * Fills the cvtermpath table of Chado with relationships between every
+ * node in the ontology graph and all of it's descendents.  This was 
+ * previously performed using the fill_cvtermpath() stored procedure of Chado
+ * but that function cannot handle loops in the ontology graphs and results
+ * in stack depth errors in PostgreSQL.
  *
- * @param $cvid
+ * @param $cv_id
  *   The controlled vocabulary ID from the cv table of Chado (i.e. cv.cv_id).
- * @param $job_id
+ * @param $job
+ *   An instance of a TripalJob.
  *
  * @ingroup tripal_chado_cv_api
  */
-function chado_update_cvtermpath($cv_id, TripalJob $job = NULL) {
+function chado_update_cvtermpath($cv_id, $clear = FALSE, $job = NULL) {
+  
+  $cv = new ChadoRecord('cv', $cv_id);
+  print "Building cvterm paths for vocabulary: " . $cv->getValue('name') ."\n"; 
+  
+  if ($clear) {
+    print "Clearing the cvtermpath table for this vocabulary...\n";
+    chado_clear_cvtermpath($cv_id);  
+    print "Clearing completed.\n";
+  }
   
   // The cache is used to limit repetitive queries by storing known data.
   $cache = [
     'rels' => [],
     'processed' => [],
+    'is_a' => NULL,
   ];
   
   // TODO: there's a function to determine the current Chado instance.
@@ -446,9 +460,11 @@ function chado_update_cvtermpath($cv_id, TripalJob $job = NULL) {
     // Get the is_a term. The OBO importer adds this for evey vocabulary.
     $sql = "SELECT * FROM cvterm WHERE name = :is_a and cv_id = :cv_id";
     $args = [':is_a' => 'is_a', ':cv_id' => $cv_id];
-    $is_a = chado_query($sql, $args)->fetchObject();
+    $cache['$is_a'] = chado_query($sql, $args)->fetchObject();
     
-    // First cache all the relationships for this vocaublary.
+    // First cache all the relationships for this vocaublary so that we
+    // don't have to do repetitive queries to Chado.
+    print "Retrieving relationships...\n";
     $sql = "
       SELECT CVTR.subject_id, CVTR.type_id, CVTR.object_id, CVTS.name
       FROM {cvterm_relationship} CVTR
@@ -457,9 +473,17 @@ function chado_update_cvtermpath($cv_id, TripalJob $job = NULL) {
       WHERE CVTO.cv_id = :cv_id 
     ";
     $rels = chado_query($sql, [':cv_id' => $cv_id]);
+    $total_items;
     while ($rel = $rels->fetchObject()) {
       $cache['rels'][$rel->object_id][] = [$rel->subject_id, $rel->type_id, $rel->name];
     }
+    $total_items = count(array_keys($cache['rels']));
+    if ($job) {
+      $job->setTotalItems($total_items);
+      $job->logMessage('Note: Progress updates occur as each term is processed and ' . 
+        'some terms take longer than others.');
+      $job->setProgress(0);
+    }
     
     // Next get the tree roots. These are terms that are in relationships as
     // an object but never as a subject.
@@ -474,25 +498,24 @@ function chado_update_cvtermpath($cv_id, TripalJob $job = NULL) {
     $roots = chado_query($sql, [':cvid' => $cv_id]);
      
     // Iterate through the tree roots.
+    print "Processing terms...\n";
     while ($root = $roots->fetchObject()) {
       $root_id =  $root->cvterm_id;
       $root_name = $root->name;
-      $num_handled = 0;
       
-      // Add each root as a reference to itself in the cvtermpath table.
-      $cvtermpath = new ChadoRecord('cvtermpath');
-      $cvtermpath->setValues([
-        'type_id' => $is_a->cvterm_id,
-        'object_id' => $root_id,
-        'subject_id' => $root_id,
-        'cv_id' => $cv_id,
-        'pathdistance' => 1,
-      ]);
-      if (!$cvtermpath->find()) {
-        $cvtermpath->insert();
+      if ($job) {
+        $job->logMessage('Processing tree root: ' . $root_name . '...');
       }
       
-      _chado_update_cvtermpath_root($cv_id, $root_id, $root_name, $cache, $job, $num_handled);
+//       _chado_update_cvtermpath_traverse_tree($root_id, $cache, 0);
+//       return;
+      
+      // Now start descending through the tree and add the relationships
+      // to the cvtermpath table.
+      $num_handled = 0;
+      $depth = 0;
+      _chado_update_cvtermpath_root($cv_id, $root_id, $root_name, $cache, $job, 
+        $num_handled, $clear, $depth);
     }
     
     // Restore the table constraints and indexes.
@@ -505,15 +528,18 @@ function chado_update_cvtermpath($cv_id, TripalJob $job = NULL) {
   }
 }
 
-function _chado_update_cvtermpath_root($cv_id, $root_id, $root_name, &$cache, TripalJob $job, &$num_handled, $root_depth = 0) {
+/**
+ * 
+ */
+function _chado_update_cvtermpath_traverse_tree($root_id, &$cache, $depth) {
+  
+  $mem = memory_get_usage();
+  $memory = number_format($mem);
+  print $memory . " $depth\n";
   
   // Mark this node as having been processed as a root node.
   $cache['processed'][$root_id] = TRUE;
   
-  // An array to keep track of which terms have been visited when descending
-  // the tree. We'll use this to avoid loops.
-  $visited = [];
-  
   // Get this term's children and recurse.
   $children = $cache['rels'][$root_id];
   
@@ -522,33 +548,101 @@ function _chado_update_cvtermpath_root($cv_id, $root_id, $root_name, &$cache, Tr
     return;
   }
   
-  // Iterate through the children and descend the tree.
+  $next_depth = $depth +1;
   foreach ($children as $child) {
     $child_id = $child[0];
     $type_id = $child[1];
     $name = $child[2];
-    _chado_update_cvtermpath_item($cv_id, $root_id, $child_id, $type_id, $cache, $visited, 1);
+    
+    // Don't use a node as a root if we've already used it once before.
+    if (array_key_exists($child_id, $cache['processed'])) {
+      continue;
+    }
+    
+    // recurse.
+    _chado_update_cvtermpath_traverse_tree($child_id, $cache, $next_depth);
   }
+}
+
+/**
+ * Treats a term within the ontology as a root. 
+ * 
+ * In order to add all relationships between a term and it's descendents each
+ * term gets it's turn as a "root".  The first time this function is called
+ * it should be called with the actual root's of the ontology.  This function
+ * will then recursively treat each child term within the tree as a root in
+ * order to find all relationships.
+ * 
+ * @param $cv_id
+ *   The vocaulary Id
+ * @param $root_id
+ *   This root term's cvterm Id.
+ * @param $root_name
+ *   The name of this root term.
+ * @param $cache
+ *   The cache used for lookups.
+ * @param $job
+ *   The TripalJob instance.
+ * @param $num_handled
+ *   Used to keep track of the number of nodes that have been handled for
+ *   progress reporting.
+ * @param $clear
+ *   A flag indicating if the cvtermpath was cleared for this vocabulary
+ *   prior to processing the tree.
+ * @param $root_depth
+ *   The current depth in the tree of this term.
+ */
+function _chado_update_cvtermpath_root($cv_id, $root_id, $root_name, &$cache, 
+  $job, &$num_handled, $clear, $root_depth = 0) {
+      
+    
+  $mem = memory_get_usage();
+  $memory = number_format($mem);
+  print $memory . " $root_depth\n";
+    
+  // Mark this node as having been processed as a root node.
+  $cache['processed'][$root_id] = TRUE;
   
-  // Now that we've descended the tree we can calculate how many entries we
-  // will add to the cvterm table. We only want to do this with a root_depth
-  // level of 0 because this is the top level root term.
+  // For the actual tree roots we need to add a relatioship to themselves.
   if ($root_depth == 0) {
-    $num_records = 0;
-    foreach ($visited as $subject_id => $details) {
-      $depth = $details[4];
-      $num_records += $depth; 
-    }
-    print "Adding " . number_format($num_records) . " paths for root: '$root_name'\n";
-    if ($job) {
-      $job->setTotalItems($num_records);
-    }
+    $is_a = $cache['$is_a'];
+    $type_id = $is_a->cvterm_id;
+    $depth = 1;
+    _chado_update_cvtermpath_add_relationship($type_id, $root_id, $root_id, $cv_id, $depth, $clear);
   }
-
   
-  // Insert into the cvtermpath table.
-  _chado_update_cvtermpath_process_visited($visited, $job, $num_handled, 1);
+  // The $path variable contains only the current path on the descent. This
+  // is used for detecting loops in the graph. If we encounter a node a 
+  // second time while traversing a single path of the tree then we've hit
+  // a loop.
+  $path = [];
+  $path[] = $root_id;
+  
+  // Get this term's children and recurse.
+  $children = $cache['rels'][$root_id];
+  
+  // If there are no children do nothing.
+  if (!$children) {
+    return;
+  }
+    
+  // Set the job progress.
+  $num_handled++;
+  if ($job) {
+    //print "$root_name\n";
+    //$job->setItemsHandled($num_handled);
+  }
   
+  // Iterate through the children and descend the tree.
+   foreach ($children as $child) {
+    $child_id = $child[0];
+    $type_id = $child[1];
+    $name = $child[2];
+    $path[$child_id];
+    $next_depth = 1;
+    _chado_update_cvtermpath_item($cv_id, $root_id, $child_id, $type_id, 
+      $path, $cache, $clear, $next_depth);
+  } 
   
   // Next make each child of this node a root and recurse again.
   foreach ($children as $child) {
@@ -561,22 +655,63 @@ function _chado_update_cvtermpath_root($cv_id, $root_id, $root_name, &$cache, Tr
       continue;
     }
     
-    // Process this child as a root.
-    _chado_update_cvtermpath_root($cv_id, $child_id, $name, $cache, $job, $num_handled, $root_depth + 1);
+    // Process this child as a root. The path is used to catch for loops
+    // in the tree. If we encounter a node on the same path then we've 
+    // found a loop and we should stop.
+    $path[] = $name;
+    $next_depth = $root_depth + 1;
+    _chado_update_cvtermpath_root($cv_id, $child_id, $name, $cache, 
+      $job, $num_handled, $clear, $next_depth);
   }
 }  
-
-function _chado_update_cvtermpath_item($cv_id, $root_id, $cvterm_id, $type_id, &$cache, &$visited, $depth = 1) {
+/**
+ * Handles a single node in the tree.
+ * 
+ * This is a recursive function which calls itself as the tree is descended. It
+ * performs a depth-first search of the tree.
+ * 
+ * @param $cv_id
+ *   The vocaulary Id
+ * @param $root_id
+ *   This root term's cvterm Id.
+ * @param $cvterm_id
+ *   This term's cvterm Id.
+ * @param $type_id
+ *   The type relationship cvterm Id.
+ * @param $path
+ *   An array used for storing the current path down the tree. This is the
+ *   sequence of nodes visited to this point down a single branch.
+ * @param $cache
+ *   The cache used for lookups.
+ * @param $depth
+ *   The current depth in the tree. 
+ */
+function _chado_update_cvtermpath_item($cv_id, $root_id, $cvterm_id, $type_id, 
+  $path, &$cache, $clear, $depth = 1) {
   
-  // Have we visited this node before?  If so then this is a loop. We do not
-  // want to mark this node as having been visited before. Just return.
-  if (array_key_exists($cvterm_id, $visited)) {
+  //print implode('-', $path) . "\n";
+    
+  // Have we visited this node before while on this path then we won't
+  // descend further as this means we've hit a loop.
+  if (in_array($cvterm_id, $path)) {
+    foreach ($path as $id) {
+      $t = new ChadoRecord('cvterm', $id);
+     print $t->getValue('name') . '-';
+    }
+    $t = new ChadoRecord('cvterm', $cvterm_id);
+    print $t->getValue('name') . "\n";
+    print "LOOP!!!!!!!!!!!!!!!!!!!!!!\n";
     return;
   }
+
+  // Add this term to the path.
+  $path[] = $cvterm_id;
   
   // Indicate we have visited this node in the tree and store the cvterm
   // path details that we need for inserting into the cvtermpath table.
-  $visited[$cvterm_id] = [$type_id, $cvterm_id, $root_id, $cv_id, $depth + 1];
+  $next_depth = $depth + 1;
+//   _chado_update_cvtermpath_add_relationship($type_id, $cvterm_id, $root_id, 
+//     $cv_id, $next_depth, $clear);
   
   // Get this term's children and recurse.
   $children = $cache['rels'][$cvterm_id];
@@ -590,30 +725,45 @@ function _chado_update_cvtermpath_item($cv_id, $root_id, $cvterm_id, $type_id, &
   foreach ($children as $child) {
     $child_id = $child[0];
     $type_id = $child[1];
-    _chado_update_cvtermpath_item($cv_id, $root_id, $child_id, $type_id, $cache, $visited, $depth + 1);
+    _chado_update_cvtermpath_item($cv_id, $root_id, $child_id, $type_id, 
+      $path, $cache, $clear, $next_depth);
   }
 }
 
-function _chado_update_cvtermpath_process_visited($visited, TripalJob $job, &$num_handled, $depth  = 1) {
+/**
+ * Inserts values into the cvtermpath table.
+ * 
+ * After the entire tree below the current root term is traversed, this
+ * function is called and inserts all of the relationships that were found
+ * into the cvtermpath table. 
+ * 
+ * @param $visited
+ *   The array contaiing relationships for all visited nodes in the tree. These
+ *   elements will become the entries in the cvtermpath table.
+ * @param $job
+ *   The TripalJob instance.
+ * @param $clear
+ *   A flag indicating if the cvtermpath was cleared for this vocabulary
+ *   prior to processing the tree.
+ */
+function _chado_update_cvtermpath_add_relationship($type_id, $cvterm_id, 
+  $root_id, $cv_id, $depth, $clear) {
   
-  foreach ($visited as $subjectid_id => $details) {
-    $num_handled++;
-    if ($job) {
-      $job->setItemsHandled($num_handled);
-    }
-    list ($type_id, $subject_id, $object_id, $cv_id, $pathdistance) = $details;
-    $cvtermpath = new ChadoRecord('cvtermpath');
-    $cvtermpath->setValues([
-      'type_id' =>  $type_id,
-      'subject_id' => $subject_id,
-      'object_id' => $object_id,
-      'cv_id'  => $cv_id,
-      'pathdistance'  => $pathdistance,
-    ]);
-    if (!$cvtermpath->find()) {
-      $cvtermpath->insert();
-    }
-  }
+  $cvtermpath = new ChadoRecord('cvtermpath');
+  $cvtermpath->setValues([
+    'type_id' =>  $type_id,
+    'subject_id' => $cvterm_id,
+    'object_id' => $root_id,
+    'cv_id'  => $cv_id,
+    'pathdistance'  => $depth,
+  ]);
+//   if ($clear) {
+//     $cvtermpath->insert();
+//   }
+//   else 
+//    if (!$cvtermpath->find()) {
+    //$cvtermpath->insert();
+//  } 
 }
 
 /**
@@ -629,7 +779,7 @@ function _chado_update_cvtermpath_process_visited($visited, TripalJob $job, &$nu
  *
  * @ingroup tripal_chado_cv_api
  */
-function chado_update_cvtermpath_orig($cv_id, $job_id = NULL){
+function chado_update_cvtermpath_old($cv_id, $job_id = NULL){
   // TODO: there's a function to determine the current Chado instance.
   // we should use that.
   $prev_db = chado_set_active('chado');

+ 1 - 1
tripal_chado/api/tripal_chado.mviews.api.inc

@@ -386,7 +386,7 @@ function chado_delete_mview($mview_id) {
 }
 
 /**
- * Update a Materialized View.
+ * Populate a Materialized View.
  *
  * @param $mview_id
  *   The unique identifier for the materialized view to be updated.

+ 99 - 73
tripal_chado/includes/TripalImporter/OBOImporter.inc

@@ -437,10 +437,10 @@ class OBOImporter extends TripalImporter {
         $form_state['clicked_button']['#name'] == 'update_load_obo') {
       // Get the current record
       $vocab = db_select('tripal_cv_obo', 't')
-      ->fields('t', array('obo_id', 'name', 'path'))
-      ->condition('name', $uobo_name)
-      ->execute()
-      ->fetchObject();
+        ->fields('t', array('obo_id', 'name', 'path'))
+        ->condition('name', $uobo_name)
+        ->execute()
+        ->fetchObject();
       if ($vocab and $vocab->obo_id != $obo_id) {
         form_set_error('uobo_name', 'The vocabulary name must be different from existing vocabularies');
       }
@@ -461,10 +461,10 @@ class OBOImporter extends TripalImporter {
     if ($form_state['clicked_button']['#name'] == 'add_new_obo') {
       // Get the current record
       $vocab = db_select('tripal_cv_obo', 't')
-      ->fields('t', array('obo_id', 'name', 'path'))
-      ->condition('name', $obo_name)
-      ->execute()
-      ->fetchObject();
+        ->fields('t', array('obo_id', 'name', 'path'))
+        ->condition('name', $obo_name)
+        ->execute()
+        ->fetchObject();
       if ($vocab) {
         form_set_error('obo_name', 'The vocabulary name must be different from existing vocabularies');
       }
@@ -592,10 +592,11 @@ class OBOImporter extends TripalImporter {
       // First, if this is not a subset (e.g. GO Slims) then clear out the 
       // cvtermpath records first.
       if (!$this->is_subset) {
-        $sql = "DELETE FROM {cvtermpath} WHERE cv_id = :cv_id";
-        chado_query($sql, [':cv_id' => $cv_id]);
+        chado_update_cvtermpath($cv_id, FALSE, $this->job);
+      }
+      else {
+        chado_update_cvtermpath($cv_id, TRUE, $this->job);
       }
-      chado_update_cvtermpath($cv_id);
     }
   }
   /**
@@ -886,7 +887,9 @@ class OBOImporter extends TripalImporter {
     $this->default_namespace = $namespace;
     $this->default_db = $short_name;
     $this->addDB($this->default_db);
-    $this->addCV($this->default_namespace);
+    $cv = $this->addCV($this->default_namespace);
+    $this->obo_namespaces[$namespace] = $cv->cv_id;
+    
 
   }
   
@@ -1039,15 +1042,21 @@ class OBOImporter extends TripalImporter {
     // return it.   
     $this->logMessage("Found !term in EBI OLS.", ['!term' => $id]);
     $stanza = [];
-    $stanza['id'][] = $id;
-    $stanza['name'][] = $results['label'];
-    $stanza['def'][] = $results['def'];
-    $stanza['namespace'][] = $results['ontology_name'];
-    $stanza['is_obsolete'][] = $results['is_obsolete'];
-    $stanza['db_name'][] = $short_name;
-    $stanza['comment'][] = 'Term obtained using the EBI Ontology Lookup Service.';
-    if (array_key_exists('subset', $results)) {
-      $stanza['subset'][] =  $results['subset'];
+    $stanza['id'][0] = $id;
+    $stanza['name'][0] = $results['label'];
+    $stanza['def'][0] = $results['def'];
+    $stanza['namespace'][0] = $results['ontology_name'];
+    $stanza['is_obsolete'][0] = $results['is_obsolete'] ? 'true' : '';
+    $stanza['is_relationshiptype'][0] = '';
+    $stanza['db_name'][0] = $short_name;
+    $stanza['comment'][0] = 'Term obtained using the EBI Ontology Lookup Service.';
+    if (array_key_exists('in_subset', $results)) {
+      if (is_array($results['in_subset'])) {
+        $stanza['subset'] =  $results['in_subset'];
+      }
+      else {
+        $stanza['subset'][0] =  $results['in_subset'];
+      }
     }
     
     // If this term has been replaced then get the new term.
@@ -1130,14 +1139,20 @@ class OBOImporter extends TripalImporter {
     
     // Get the definition if there is one.
     $definition = '';
-    if (array_key_exists('definition', $stanza)) {
+    if (array_key_exists('def', $stanza)) {
       $definition = preg_replace('/^\"(.*)\"/', '\1', $stanza['def'][0]);
     }
     
     // Set the flag if this term is obsolete.
     $is_obsolete = 0;
     if (array_key_exists('is_obsolete', $stanza)) {
-      $is_obsolete = $stanza['is_obsolete'][0] == TRUE ? 1 : 0;
+      $is_obsolete = $stanza['is_obsolete'][0] == 'true' ? 1 : 0;
+    }
+    
+    // Set the flag if this is a relationsip type.
+    $is_relationshiptype = 0;
+    if (array_key_exists('is_relationshiptype', $stanza)) {
+      $is_relationshiptype = $stanza['is_relationshiptype'][0] == 'true' ? 1 : 0;
     }
     
     // Is this term borrowed from another ontology?  
@@ -1184,6 +1199,7 @@ class OBOImporter extends TripalImporter {
           $cvterm->setValue('name', $name);
           $cvterm->setValue('definition', $definition);
           $cvterm->setValue('is_obsolete', $is_obsolete);
+          $cvterm->setValue('is_relationshiptype', $is_relationshiptype);
           
           try {
             $cvterm->update();
@@ -1224,7 +1240,7 @@ class OBOImporter extends TripalImporter {
         'name' => $name,
         'definition' => $definition,
         'dbxref_id' => $dbxref->getID(),
-        'is_relationshiptype' => $is_relationship,
+        'is_relationshiptype' => $is_relationshiptype,
         'is_obsolete' => $is_obsolete,
       ]);
       
@@ -1277,10 +1293,20 @@ class OBOImporter extends TripalImporter {
     $name =  $stanza['name'][0];
     
     // First get the record for any potential conflicting term.
-    $check_cvterm = new ChadoRecord('cvterm');
-    $check_cvterm->setValues(['cv_id' => $cv->cv_id, 'name' => $name]);
-    if ($check_cvterm->find()) {
-                  
+    $sql = "
+      SELECT cvterm_id 
+      FROM {cvterm} 
+      WHERE name = :name and cv_id = :cv_id and dbxref_id != :dbxref_id
+    ";
+    $args = [
+      ':name' => $name,
+      ':cv_id' => $cv->cv_id,
+      ':dbxref_id' => $dbxref->getID(),
+    ];
+    $results = chado_query($sql, $args);
+    while ($conflict_id = $results->fetchField()) {
+      $check_cvterm = new ChadoRecord('cvterm', $conflict_id);
+                    
       // If the dbxref of this matched term is the same as the current term 
       // then it is the same term and there is no conflict.
       if ($dbxref->getID() == $check_cvterm->getValue('dbxref_id')) {
@@ -1291,12 +1317,7 @@ class OBOImporter extends TripalImporter {
       // but with a different dbxref. First let's get that other accession.
       $check_dbxref = new ChadoRecord('dbxref', $check_cvterm->getValue('dbxref_id'));
       $check_db = new ChadoRecord('db', $check_dbxref->getValue('db_id'));
-      $check_accession = $check_db->getValue('name') . ':' . $check_dbxref->getValue('accession');
-
-      // Enable this linee for debugging"
-      //$this->logMessage('The term, !id, "!name", has a name that belongs to another term: !id2.',
-      //  ['!id' => $id, '!name' => $name, '!id2' => $check_accession]);
-      
+      $check_accession = $check_db->getValue('name') . ':' . $check_dbxref->getValue('accession'); 
       
       // Case 1:  The other term that currently has the same name is
       // missing in the OBO file (i.e. no stanza).  So, that means that this
@@ -1310,19 +1331,20 @@ class OBOImporter extends TripalImporter {
       $check_stanza = $this->getCachedTermStanza($check_accession);
       if (!$check_stanza) {
         $new_name = $check_cvterm->getValue('name') . ' (' . $check_accession . ')';
-        
-        // Enable these lines for debugging"
-        //$this->logMessage('The term, !id, it is no longer a valid term.', 
-        //  ['!id' => $check_accession]);   
-        //$this->logMessage('Renaming !id1 to "!new_name".',
-        //  ['!id1' => $check_accession, '!new_name' => $new_name]);
         $check_cvterm->setValue('name', $new_name);
         $check_cvterm->update();
-        
         return TRUE;
       }
-      
-      // Case 2:  The conflicting term is in the OBO file (ie. has a stanza).
+      // Case 2:  The conflicting term is in the OBO file (ie. has a stanza) and
+      // is obsolete and this one is not. Fix it by adding an (obsolete) suffix 
+      // to the name to avoid the conflict.
+      else if ($check_stanza['is_obsolete'] == 'true' and $stanza['is_obsolete'] != 'true') {
+        $new_name = $check_cvterm->getValue('name') . ' (obsolete)';
+        $check_cvterm->setValue('name', $new_name);
+        $check_cvterm->update();
+        return TRUE;
+      }
+      // Case 3:  The conflicting term is in the OBO file (ie. has a stanza).
       // That means that there has been some name swapping between
       // terms. We need to temporarily rename the term so that
       // we don't have a unique constraint violation when we update
@@ -1331,9 +1353,6 @@ class OBOImporter extends TripalImporter {
       // name of the other.
       else {
         $new_name = $check_cvterm->getValue('name') . ' (' . $check_accession . ')';
-        // Enable these lines for debugging"
-        //$this->logMessage('Renaming !id1 to "!new_name".',
-        //  ['!id1' => $check_accession, '!new_name' => $new_name]);
         $check_cvterm->setValue('name', $new_name);
         $check_cvterm->update();
         return TRUE;
@@ -1373,7 +1392,7 @@ class OBOImporter extends TripalImporter {
     
     // If this term belongs to this OBO (not borrowed from another OBO) then
     // remove any relationships, properties, xrefs, and synonyms that this 
-    // term alreadyn has so that they can be re-added.
+    // term already has so that they can be re-added.
     $sql = "
       DELETE FROM {cvterm_relationship}
       WHERE subject_id = :cvterm_id
@@ -1671,9 +1690,10 @@ class OBOImporter extends TripalImporter {
     $stanza['name'][0] = $cvterm->getValue('name');
     $stanza['def'][0] = $cvterm->getValue('definition');
     $stanza['namespace'][0] = $cv->getValue('name');
-    $stanza['is_obsolete'][] = $cvterm->getValue('is_obsolete');
-    $stanza['db_name'][] = $db->name;
-    $stanza['cv_name'][] = $cv->getValue('name');
+    $stanza['is_obsolete'][0] = $cvterm->getValue('is_obsolete') == 1 ? 'true' : '';
+    $stanza['is_relationshiptype'][0] = '';
+    $stanza['db_name'][0] = $db->name;
+    $stanza['cv_name'][0] = $cv->getValue('name');
     return $stanza;
   }
   /**
@@ -1731,9 +1751,9 @@ class OBOImporter extends TripalImporter {
       // If the term belongs to this OBO then let's set the 'db_name'.
       else {
         if (!array_key_exists('namespace', $stanza)) {
-          $stanza['namespace'][] = $this->default_namespace;
+          $stanza['namespace'][0] = $this->default_namespace;
         }
-        $stanza['db_name'][] = $short_name;
+        $stanza['db_name'][0] = $short_name;
       }
       
       // Make sure the db for this term is added to Chado. If it already is
@@ -1743,24 +1763,15 @@ class OBOImporter extends TripalImporter {
     // If there is no DB short name prefix for the id.
     else {
       if (!array_key_exists('namespace', $stanza)) {
-        $stanza['namespace'][] = $this->default_namespace;
+        $stanza['namespace'][0] = $this->default_namespace;
       }
-      $stanza['db_name'][] = $this->default_db;
+      $stanza['db_name'][0] = $this->default_db;
+    }  
+    
+    $stanza['is_relationshiptype'][0] = '';
+    if ($type == 'Typedef') {
+      $stanza['is_relationshiptype'][0] = 'true';
     }
-      
-    if ($this->cache_type == 'table') {
-      // Add the term to the temp table.
-      $values = [
-        'id' => $id,
-        'stanza' => base64_encode(serialize($stanza)),
-        'type' => $type,
-      ];
-      $success = chado_insert_record('tripal_obo_temp', $values);
-      if (!$success) {
-        throw new Exception("Cannot insert stanza into temporary table.");
-      }
-      return;
-    }    
     
     // The is_a field can have constructs like this:  {is_inferred="true"}
     // We need to remove those if they exist.
@@ -1794,7 +1805,22 @@ class OBOImporter extends TripalImporter {
       $stanza[$key] = array_unique($values);
     }
     
-
+    // If we should use the cache_type is to cache in the tripal_obo_temp
+    // table then handle that now.
+    if ($this->cache_type == 'table') {
+      // Add the term to the temp table.
+      $values = [
+        'id' => $id,
+        'stanza' => base64_encode(serialize($stanza)),
+        'type' => $type,
+      ];
+      $success = chado_insert_record('tripal_obo_temp', $values);
+      if (!$success) {
+        throw new Exception("Cannot insert stanza into temporary table.");
+      }
+      return;
+    }  
+    
     // Cache the term stanza
     $this->termStanzaCache['ids'][$id] = $stanza;
     $this->termStanzaCache['count'][$type]++;
@@ -2109,10 +2135,10 @@ class OBOImporter extends TripalImporter {
     // include that term.
     if (!$this->getCachedTermStanza('is_a')) {
       $stanza = [];
-      $stanza['id'][] = 'is_a';
-      $stanza['name'][] = 'is_a';
-      $stanza['namespace'][] = $this->default_namespace;
-      $stanza['db_name'][] = $this->default_db;
+      $stanza['id'][0] = 'is_a';
+      $stanza['name'][0] = 'is_a';
+      $stanza['namespace'][0] = $this->default_namespace;
+      $stanza['db_name'][0] = $this->default_db;
       $this->cacheTermStanza($stanza, 'Typedef');
     }
   }

+ 9 - 8
tripal_chado/includes/setup/tripal_chado.chado_vx_x.inc

@@ -452,14 +452,15 @@ function tripal_chado_add_db2cv_mview_mview() {
   );
 
   $sql = "
-   SELECT DISTINCT CV.cv_id, CV.name as cvname, DB.db_id, DB.name as dbname,
-     COUNT(CVT.cvterm_id) as num_terms
-   FROM cv CV
-     INNER JOIN cvterm CVT on CVT.cv_id = CV.cv_id
-     INNER JOIN dbxref DBX on DBX.dbxref_id = CVT.dbxref_id
-     INNER JOIN db DB on DB.db_id = DBX.db_id
-   GROUP BY CV.cv_id, CV.name, DB.db_id, DB.name
-   ORDER BY DB.name
+    SELECT DISTINCT CV.cv_id, CV.name as cvname, DB.db_id, DB.name as dbname,
+      COUNT(CVT.cvterm_id) as num_terms
+    FROM cv CV
+      INNER JOIN cvterm CVT on CVT.cv_id = CV.cv_id
+      INNER JOIN dbxref DBX on DBX.dbxref_id = CVT.dbxref_id
+      INNER JOIN db DB on DB.db_id = DBX.db_id
+    WHERE CVT.is_relationshiptype = 0 and CVT.is_obsolete = 0
+    GROUP BY CV.cv_id, CV.name, DB.db_id, DB.name
+    ORDER BY DB.name
   ";
 
   // Create the MView

+ 56 - 6
tripal_chado/includes/tripal_chado.vocab_storage.inc

@@ -201,6 +201,7 @@ function tripal_chado_vocab_get_terms($vocabulary, $limit = 25, $element = 0) {
   }
   return $terms;
 }
+
 /**
  * Implements hook_vocab_get_term_children().
  *
@@ -235,12 +236,15 @@ function tripal_chado_vocab_get_term_children($vocabulary, $accession) {
 
   // Get the children.
   $sql = "
-    SELECT DISTINCT subject_id
+    SELECT DISTINCT CVTR.subject_id, CVT.name
     FROM {cvterm_relationship} CVTR
+      INNER JOIN {cvterm} CVT on CVTR.subject_id = CVT.cvterm_id
     WHERE object_id = :object_id
+    ORDER BY CVT.name
   ";
   $results = chado_query($sql, array(':object_id' => $cvterm->cvterm_id));
-  while($cvterm_id = $results->fetchField()) {
+  while($cvterm = $results->fetchObject()) {
+    $cvterm_id = $cvterm->subject_id;
     $match = array('cvterm_id' => $cvterm_id);
     $cvterm = chado_generate_var('cvterm', $match);
     $terms[] = _tripal_chado_format_term_description($cvterm);
@@ -274,7 +278,12 @@ function tripal_chado_vocab_get_term($vocabulary, $accession) {
   if (!$cvterm) {
     return FALSE;
   }
-  $cvterm = chado_expand_var($cvterm, 'field', 'cvterm.definition');
+  $options = ['return_array' => TRUE];
+  $cvterm = chado_expand_var($cvterm, 'field', 'cvterm.definition', $options);
+  $cvterm = chado_expand_var($cvterm, 'table', 'cvterm_dbxref', $options);
+  $cvterm = chado_expand_var($cvterm, 'table', 'cvtermsynonym', $options);
+  $cvterm = chado_expand_var($cvterm, 'table', 'cvterm_relationship', $options);
+  $cvterm = chado_expand_var($cvterm, 'table', 'cvtermprop', $options);
 
   return _tripal_chado_format_term_description($cvterm);
 }
@@ -294,16 +303,57 @@ function _tripal_chado_format_term_description($cvterm) {
   if ($sw_url) {
     $sw_url = preg_replace('/{db}/', $cvterm->dbxref_id->db_id->name, $sw_url);
     $sw_url = preg_replace('/{accession}/', '', $sw_url);
-    $sw_url = url($sw_url, array('absolute' => TRUE));
+    $sw_url = url($sw_url, ['absolute' => TRUE]);
   }
   $vocabulary = tripal_chado_vocab_get_vocabulary($cvterm->dbxref_id->db_id->name);
-  $term = array(
+  $term = [
     'vocabulary' => $vocabulary,
+    'namespace'  => $cvterm->cv_id->name,
     'accession'  => $cvterm->dbxref_id->accession,
     'name'       => $cvterm->name,
     'url'        => chado_get_dbxref_url($cvterm->dbxref_id),
     'definition' => (isset($cvterm->definition)) ? $cvterm->definition : '',
-  );
+  ];
+  
+  // Is this term in any relationships as the subject?
+  if (property_exists($cvterm, 'cvterm_relationship')) {
+    $relationships = $cvterm->cvterm_relationship->subject_id;
+    if ($relationships) {
+      foreach ($relationships as $relationship) {
+        $term['relationship'][] = $relationship->type_id->name . ' ' . $relationship->object_id->dbxref_id->db_id->name . ':' . $relationship->object_id->dbxref_id->accession;
+      }
+    }
+  }
+  
+  // Does this term have synonyms?
+  if (property_exists($cvterm, 'cvtermsynonym')) {
+    $synonyms = $cvterm->cvtermsynonym->cvterm_id;
+    if ($synonyms) {
+      foreach ($synonyms as $synonym) {
+        $term['synonym'][] = $synonym->synonym;
+      }
+    }
+  }
+  
+  // Does this term have any cross refs?
+  if (property_exists($cvterm, 'cvterm_dbxref')) {
+    $xrefs = $cvterm->cvterm_dbxref;
+    if ($xrefs) {
+      foreach ($xrefs as $xref) {
+        $term['cross reference'][] = $xref->dbxref_id->db_id->name . ':' . $xref->dbxref_id->accession;
+      }
+    }
+  }
+  
+  // Does this term have any properties?
+  if (property_exists($cvterm, 'cvtermprop')) {
+    $props = $cvterm->cvtermprop->cvterm_id;
+    if ($props) {
+      foreach ($props as $prop) {
+        $term[$prop->type_id->name][] = property_exists($prop, 'value') ? $prop->value : '';
+      }
+    }
+  }
   return $term;
 }
 

+ 30 - 1
tripal_chado/tripal_chado.install

@@ -1770,4 +1770,33 @@ function tripal_chado_update_7333() {
     $error = $e->getMessage();
     throw new DrupalUpdateException('Could not perform update: '. $error);
   }
-}
+}
+
+/**
+ * SQL Fix for the db2cv_mview materialized view.
+ */
+function tripal_chado_update_7334() {
+  
+  try {
+    $query = '
+       SELECT DISTINCT CV.cv_id, CV.name as cvname, DB.db_id, DB.name as dbname,
+         COUNT(CVT.cvterm_id) as num_terms
+       FROM cv CV
+         INNER JOIN cvterm CVT on CVT.cv_id = CV.cv_id
+         INNER JOIN dbxref DBX on DBX.dbxref_id = CVT.dbxref_id
+         INNER JOIN db DB on DB.db_id = DBX.db_id
+       WHERE CVT.is_relationshiptype = 0 and CVT.is_obsolete = 0
+       GROUP BY CV.cv_id, CV.name, DB.db_id, DB.name
+       ORDER BY DB.name
+    ';
+    $mview_id = tripal_get_mview_id('db2cv_mview');
+    if($mview_id) {
+      $sql = "UPDATE {tripal_mviews} set query = :query WHERE mview_id = :mview_id";
+      db_query($sql, [':query' => $query, ':mview_id' => $mview_id]);
+    }
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}