tripal_chado/src/Plugin/TripalIdSpace/ChadoIdSpace.php

Summary

Maintainability
B
6 hrs
Test Coverage
B
88%
<?php

namespace Drupal\tripal_chado\Plugin\TripalIdSpace;

use Drupal\tripal\TripalVocabTerms\TripalIdSpaceBase;
use Drupal\tripal\TripalVocabTerms\TripalTerm;

/**
 * Chado Implementation of TripalIdSpaceBase
 *
 *  @TripalIdSpace(
 *    id = "chado_id_space",
 *    label = @Translation("Vocabulary IDSpace in Chado"),
 *  )
 */
class ChadoIdSpace extends TripalIdSpaceBase {

  /**
   * Holds the default vacabulary name.
   *
   * @var string
   */
  protected $default_vocabulary = NULL;

  /**
   * The definition for the `db` table of Chado.
   *
   * @var array
   */
  protected $db_def = NULL;


  /**
   * An instance of the TripalLogger.
   *
   * @var \Drupal\tripal\Services\TripalLogger
   */
  protected $messageLogger = NULL;

  /**
   * A simple boolean to prevent Chado queries if the ID space isn't valid.
   *
   * @var bool
   */
  protected $is_valid = False;


  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    // Instantiate the TripalLogger
    $this->messageLogger = \Drupal::service('tripal.logger');

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Get the chado definition for the `db` table.
    $this->db_def = $chado->schema()->getTableDef('db', ['Source' => 'file']);
  }


  /**
   * {@inheritdoc}
   */
  public function isValid() {

    // Make sure the name of this ID Space does not exceeed the allowed size in Chado.
    $name = $this->getName();

    if (!empty($name) AND (strlen($name) > $this->db_def['fields']['name']['size'])) {
      $this->messageLogger->error('ChadoIdSpace: The IdSpace name must not be longer than @size characters. ' +
          'The value provided was: @value',
          ['@size' => $this->db_def['fields']['name']['size'],
           '@value' => $this->getName()]);
      return;
    }

    $this->is_valid = True;

    return $this->is_valid;
  }

  /**
   * {@inheritdoc}
   */
  public function recordExists() {
    $db = $this->loadIdSpace();
    if ($db and $db['name'] == $this->getName()) {
      return True;
    }
    return False;
  }


  /**
   * {@inheritdoc}
   */
  public function create() {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Check if the record already exists in the database, if it
    // doesn't then insert it.  We don't yet have the description,
    // URL prefix, etc but that's okay, the name is all that is
    // required to create a record in the `db` table.
    $db = $this->loadIdSpace();
    if (!$db) {
      $query = $chado->insert('1:db')
        ->fields(['name' => $this->getName()]);
      $query->execute();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function destroy(){
    // The destroy function is meant to delete the ID space.
    // But, because CVs and DBs are so critical to almost all
    // data in Chado we don't want to remove the records.
    // Let's let the collection be deleted as far as
    // Tripal is concerned but leave the record in Chado.
    // So, do nothing here.
  }

  /**
   * Loads an ID Space record from Chado.
   *
   * This function queries the `db` table of Chado to get the values
   * for the ID space.
   *
   * @return array
   *   An associative array containing the columns of the `db1 table
   *   of Chado or NULL if the db could not be found.
   */
  protected function loadIdSpace() {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Get the Chado `db` record for this ID space.
    $query = $chado->select('1:db', 'db')
      ->condition('db.name', $this->getName(), '=')
      ->fields('db', ['name', 'url', 'urlprefix', 'description']);
    $result = $query->execute();
    if (!$result) {
      return NULL;
    }
    return $result->fetchAssoc();

  }

  /**
   * {@inheritdoc}
   */
  public function getParent($child){

    // Don't get values for an ID space that isn't valid.
    if (!$this->is_valid) {
      return NULL;
    }

  }

  /**
   * {@inheritdoc}
   */
  public function getChildren($parent = NULL){

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Don't get values for an ID space that isn't valid.
    if (!$this->is_valid) {
      return NULL;
    }

    $terms = [];

    $cvterm = $this->getChadoCVTerm($parent);

    $query = $chado->select('1:cvterm_relationship', 'CVTR');
    $query->join('1:cvterm', 'CVTSUB', '"CVTR".subject_id = "CVTSUB".cvterm_id');
    $query->join('1:cvterm', 'CVTTYPE', '"CVTR".type_id = "CVTTYPE".cvterm_id');
    $query->join('1:dbxref', 'DBXSUB', '"CVTSUB".dbxref_id = "DBXSUB".dbxref_id');
    $query->join('1:dbxref', 'DBXTYPE', '"CVTTYPE".dbxref_id = "DBXTYPE".dbxref_id');
    $query->join('1:db', 'DBSUB', '"DBSUB".db_id = "DBXSUB".db_id');
    $query->join('1:cv', 'CVSUB', '"CVSUB".cv_id = "CVTSUB".cv_id');
    $query->join('1:db', 'DBTYPE', '"DBTYPE".db_id = "DBXTYPE".db_id');
    $query->join('1:cv', 'CVTYPE', '"CVTYPE".cv_id = "CVTTYPE".cv_id');
    $query->fields('CVTSUB', ['name'])
      ->fields('DBXSUB', ['accession'])
      ->fields('DBSUB', ['name'])
      ->fields('CVSUB', ['name'])
      ->fields('CVTTYPE', ['name'])
      ->fields('DBXTYPE', ['accession'])
      ->fields('DBTYPE', ['name'])
      ->fields('CVTYPE', ['name'])
      ->condition('CVTR.object_id', $cvterm->cvterm_id, '=');
    $children = $query->execute();
    while ($child = $children->fetchObject()) {
      $child_term = new TripalTerm([
        'name' => $child->name,
        'accession' => $child->accession,
        'idSpace' => $child->DBSUB_name,
        'vocabulary' => $child->CVSUB_name
      ]);
      $type_term = new TripalTerm([
        'name' => $child->CVTTYPE_name,
        'accession' => $child->DBXTYPE_accession,
        'idSpace' => $child->DBTYPE_name,
        'vocabulary' => $child->CVTYPE_name
      ]);
      $terms[] = [$child_term, $type_term];
    }
    return $terms;
  }

  /**
   * {@inheritdoc}
   */
  public function getTerm($accession, $options = []) {

    if (!$this->is_valid) {
      return NULL;
    }

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Get the term record.
    $query = $chado->select('1:cvterm', 'CVT');
    $query->join('1:dbxref', 'DBX', '"CVT".dbxref_id = "DBX".dbxref_id');
    $query->join('1:cv', 'CV', '"CV".cv_id = "CVT".cv_id');
    $query->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
    $query->fields('CVT', ['cvterm_id', 'name', 'definition', 'is_obsolete', 'is_relationshiptype'])
      ->condition('DB.name', $this->getName(), '=')
      ->condition('DBX.accession', $accession, '=');
    $cvterm = $query->execute()->fetchObject();
    // @debug print "CVTERM looked up by ChadoIdSpace->getTerm() in db: ".$chado->getSchemaName().". " . print_r($cvterm, TRUE) . "\n";

    if (!$cvterm) {
      return NULL;
    }
    $term =  new TripalTerm([
      'name' => $cvterm->name,
      'definition' => $cvterm->definition,
      'accession' => $accession,
      'idSpace' => $this->getName(),
      'vocabulary' => $this->getDefaultVocabulary(),
      'is_obsolete' => $cvterm->is_obsolete == 1 ? True : False,
      'is_relationship_type' => $cvterm->is_relationshiptype == 1 ? True : False,
    ]);

    // Set the internal ID.
    $term->setInternalId($cvterm->cvterm_id);

    // Set the boolean values for the term.
    if ($cvterm->is_obsolete) {
      $term->isObsolete(True);
    }
    if ($cvterm->is_relationshiptype) {
      $term->isRelationshipType(True);
    }

    // Are there synonyms?
    if (!array_key_exists('includes', $options) or in_array('synonyms', $options['includes'])) {
      $query = $chado->select('1:cvtermsynonym', 'CVTS');
      $query->leftJoin('1:cvterm', 'CVT', '"CVT".cvterm_id = "CVTS".type_id');
      $query->leftJoin('1:dbxref', 'DBX', '"DBX".dbxref_id = "CVT".dbxref_id');
      $query->leftJoin('1:cv', 'CV', '"CV".cv_id = "CVT".cv_id');
      $query->leftJoin('1:db', 'DB', '"DB".db_id = "DBX".db_id');
      $query->fields('CVTS', ['synonym', 'type_id']);
      $query->fields('CVT', ['name', 'definition']);
      $query->fields('DBX', ['accession']);
      $query->fields('CV', ['name']);
      $query->fields('DB', ['name']);
      $query->condition('CVTS.cvterm_id', $cvterm->cvterm_id, '=');
      $synonyms = $query->execute();
      while ($synonym = $synonyms->fetchObject()) {
        $type_term = NULL;
        if ($synonym->type_id) {
          $type_term = new TripalTerm([
            'name' => $synonym->name,
            'definition' => $synonym->definition,
            'accession' => $synonym->accession,
            'idSpace' => $synonym->DB_name,
            'vocabulary' => $synonym->CV_name,
          ]);
        }
        $term->addSynonym($synonym->synonym, $type_term);
      }
    }

    // Are there alt IDs?
    if (!array_key_exists('includes', $options) or in_array('altIds', $options['includes'])) {
      $query = $chado->select('1:cvterm_dbxref', 'CVTDBX');
      $query->join('1:dbxref', 'DBX', '"CVTDBX".dbxref_id = "DBX".dbxref_id');
      $query->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
      $query->fields('DBX', ['accession'])
        ->fields('DB', ['name'])
        ->condition('CVTDBX.cvterm_id', $cvterm->cvterm_id, '=');
      $alt_ids = $query->execute();
      while ($alt_id = $alt_ids->fetchObject()) {
        $term->addAltId($alt_id->name, $alt_id->accession);
      }
    }

    // Are there properties?
    if (!array_key_exists('includes', $options) or in_array('properties', $options['includes'])) {
      $query = $chado->select('1:cvtermprop', 'CVTP');
      $query->join('1:cvterm', 'CVT', '"CVTP".type_id = "CVT".cvterm_id');
      $query->join('1:dbxref', 'DBX', '"CVT".dbxref_id = "DBX".dbxref_id');
      $query->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
      $query->join('1:cv', 'CV', '"CV".cv_id = "CVT".cv_id');
      $query->fields('CVT', ["name"])
        ->fields('CVTP', ['value'])
        ->fields('DBX', ['accession'])
        ->fields('DB', ['name'])
        ->fields('CV', ['name'])
        ->condition('CVTP.cvterm_id', $cvterm->cvterm_id, '=')
        ->orderBy('CVTP.type_id', 'ASC')
        ->orderBy('CVTP.rank', 'ASC');
      $properties = $query->execute();
      while ($property = $properties->fetchObject()) {
        $prop_term = new TripalTerm([
          'name' => $property->name,
          'accession' => $property->accession,
          'idSpace' => $property->DB_name,
          'vocabulary' => $property->CV_name
        ]);
        $term->addProperty($prop_term, $property->value);
      }
    }

    // Are there parents?
    if (!array_key_exists('includes', $options) or in_array('parents', $options['includes'])) {
      $query = $chado->select('1:cvterm_relationship', 'CVTR');
      $query->join('1:cvterm', 'CVTOBJ', '"CVTR".object_id = "CVTOBJ".cvterm_id');
      $query->join('1:cvterm', 'CVTTYPE', '"CVTR".type_id = "CVTTYPE".cvterm_id');
      $query->join('1:dbxref', 'DBXOBJ', '"CVTOBJ".dbxref_id = "DBXOBJ".dbxref_id');
      $query->join('1:dbxref', 'DBXTYPE', '"CVTTYPE".dbxref_id = "DBXTYPE".dbxref_id');
      $query->join('1:db', 'DBOBJ', '"DBOBJ".db_id = "DBXOBJ".db_id');
      $query->join('1:cv', 'CVOBJ', '"CVOBJ".cv_id = "CVTOBJ".cv_id');
      $query->join('1:db', 'DBTYPE', '"DBTYPE".db_id = "DBXTYPE".db_id');
      $query->join('1:cv', 'CVTYPE', '"CVTYPE".cv_id = "CVTTYPE".cv_id');
      $query->fields('CVTOBJ', ['name'])
        ->fields('DBXOBJ', ['accession'])
        ->fields('DBOBJ', ['name'])
        ->fields('CVOBJ', ['name'])
        ->fields('CVTTYPE', ['name'])
        ->fields('DBXTYPE', ['accession'])
        ->fields('DBTYPE', ['name'])
        ->fields('CVTYPE', ['name'])
        ->condition('CVTR.subject_id', $cvterm->cvterm_id, '=');
      $parents = $query->execute();
      while ($parent = $parents->fetchObject()) {
        $parent_term = new TripalTerm([
          'name' => $parent->name,
          'accession' => $parent->accession,
          'idSpace' => $parent->DBOBJ_name,
          'vocabulary' => $parent->CVOBJ_name
        ]);
        $type_term = new TripalTerm([
          'name' => $parent->CVTTYPE_name,
          'accession' => $parent->DBXTYPE_accession,
          'idSpace' => $parent->DBTYPE_name,
          'vocabulary' => $parent->CVTYPE_name
        ]);
        $term->addParent($parent_term, $type_term);
      }
    }

    return $term;
  }

  /**
   * {@inheritdoc}
   */
  public function getTerms($name, $options = []) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // The list of terms to return
    $terms = [];

    // Build the query for matching via the `cvterm.name` column.
    $query1 = $chado->select('1:cvterm', 'CVT');
    $query1->join('1:cv', 'CV', '"CV".cv_id = "CVT".cv_id');
    $query1->join('1:dbxref', 'DBX', '"DBX".dbxref_id = "CVT".dbxref_id');
    $query1->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
    $query1->fields('CVT', ['name', 'definition', 'cvterm_id']);
    $query1->fields('DBX', ['accession']);
    $query1->fields('CV', ['name']);
    $query1->condition('DB.name', $this->getName(), '=');
    if (array_key_exists('exact', $options) and $options['exact'] === True) {
      $query1->condition('CVT.name', $name, '=');
    }
    else {
      $query1->condition('CVT.name', $name . '%', 'LIKE');
    }
    $results = $query1->execute();
    while ($cvterm = $results->fetchObject()) {
      $term = new TripalTerm([
        'name' => $cvterm->name,
        'idSpace' => $this->getName(),
        'vocabulary' => $cvterm->CV_name,
        'definition' => $cvterm->definition,
        'accession' => $cvterm->accession
      ]);
      $terms[$cvterm->name][$term->getTermId()] = $term;
    }

    // Build the query for matching via the `cvtermsynonym.synonym` column.
    $query2 = $chado->select('1:cvtermsynonym', 'CS');
    $query2->join('1:cvterm', 'CVT', '"CVT".cvterm_id = "CS".cvterm_id');
    $query2->join('1:cv', 'CV', '"CV".cv_id = "CVT".cv_id');
    $query2->join('1:dbxref', 'DBX', '"DBX".dbxref_id = "CVT".dbxref_id');
    $query2->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
    $query2->fields('CVT', ['name', 'definition', 'cvterm_id']);
    $query2->fields('DBX', ['accession']);
    $query2->fields('CV', ['name']);
    $query2->fields('CS', ['synonym']);
    $query2->condition('DB.name', $this->getName(), '=');
    if (array_key_exists('exact', $options) and $options['exact'] === True) {
      $query2->condition('CS.synonym', $name, '=');
    }
    else {
      $query2->condition('CS.synonym', $name . '%', 'LIKE');
    }
    $results = $query2->execute();
    while ($cvterm = $results->fetchObject()) {
      $term = new TripalTerm([
        'name' => $cvterm->name,
        'idSpace' => $this->getName(),
        'vocabulary' => $cvterm->CV_name,
        'definition' => $cvterm->definition,
        'accession' => $cvterm->accession
      ]);
      $terms[$cvterm->synonym][$term->getTermId()] = $term;
    }
    return $terms;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultVocabulary(){
    return $this->getDefaultVocabCache();
  }

  /**
   * {@inheritdoc}
   */
  public function saveTerm($term, $options = []) {

    // Don't save terms that aren't valid
    if (!$term->isValid()) {
      $this->messageLogger->error(t('ChadoIdSpace::saveTerm(). The term, "@term" is not valid and cannot be saved. It must include a name, accession, IdSpace and vocabulary.',
          ['@term' => $term->getIdSpace() . ':' . $term->getAccession()]));
      return False;
    }

    // Make sure the idSpace matches.
    if ($this->getName() != $term->getIdSpace()) {
      $this->messageLogger->error(t('ChadoIdSpace::saveTerm(). The term, "@term", does not have the same ID space as this one.',
          ['@term' => $term->getIdSpace() . ':' . $term->getAccession()]));
      return False;
    }

    // Get easy to use boolean variables.
    $fail_if_exists = False;
    if (array_key_exists('failIfExists', $options)) {
      $fail_if_exists = $options['failIfExists'];
    }

    // Does the term exist? If not do an insert, if so, do an update.
    $cvterm = $this->getChadoCVTerm($term);
    if (!$cvterm) {
      if (!$this->insertTerm($term, $options)) {
        return False;
      }
    }
    if ($cvterm and $fail_if_exists) {
      return False;
    }
    if ($cvterm and !$fail_if_exists) {
      if (!$this->updateTerm($term, $cvterm, $options)) {
        return False;
      }
    }

    // Set the internal ID.
    $cvterm = $this->getChadoCVTerm($term);
    $term->setInternalId($cvterm->cvterm_id);

    return True;
  }

  /**
   * Retrieve a record from the Chado cv table.
   *
   * @param TripalTerm $term
   *   The TripalTerm object to save.
   *
   * @return object
   *   The cv record in object form.
   */
  protected function getChadoCV(TripalTerm $term) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    $result = $chado->select('1:cv', 'CV')
      ->fields('CV', ['cv_id', 'name', 'definition'])
      ->condition('name', $term->getVocabulary(), '=')
      ->execute();
    if(!$result) {
      return NULL;
    }
    return $result->fetchObject();
  }

  /**
   * Retrieve a record from the Chado db table.
   *
   * @param TripalTerm $term
   *   The TripalTerm object to save.
   * @return object
   *   The db record in object form.
   */
  protected function getChadoDB(TripalTerm $term) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    $result = $chado->select('1:db', 'DB')
      ->fields('DB', ['db_id', 'name', 'description'])
      ->condition('name', $term->getIdSpace(), '=')
      ->execute();
    if(!$result) {
      return NULL;
    }
    return $result->fetchObject();
  }

  /**
   * Retrieve a record from the Chado dbxref table.
   *
   * @param TripalTerm $term
   *   The TripalTerm object to save.
   * @return object
   *   The dbxref record in object form.
   */
  protected function getChadoDBXref(TripalTerm $term) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    $db = $this->getChadoDB($term);
    $result = $chado->select('1:dbxref', 'DBX')
      ->fields('DBX', ['dbxref_id', 'db_id', 'accession', 'version' ,'description'])
      ->condition('db_id', $db->db_id, '=')
      ->condition('accession', $term->getAccession(), '=')
      ->execute();
    if (!$result) {
      return NULL;
    }
    return $result->fetchObject();
  }

  /**
   * Retreives a record from the Cahdo dbxref table using the term ID.
   *
   * @param string $term_id
   *   The term ID (e.g. GO:0044708).
   */
  protected function getChadoDBXrefbyTermID(string $term_id) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    list($db, $accession) = explode(':', $term_id);
    $query = $chado->select('1:dbxref', 'DBX');
    $query->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
    $result = $query->fields('DBX', ['dbxref_id', 'db_id', 'accession', 'version' ,'description'])
      ->condition('DB.name', $db, '=')
      ->condition('DBX.accession', $accession, '=')
      ->execute();
    if(!$result) {
      return NULL;
    }
    return $result->fetchObject();
  }

  /**
   * Adds a record from the Cahdo dbxref table using the term ID.
   *
   * The database record must already exist.
   *
   * @param string $term_id
   *   The term ID (e.g. GO:0044708).
   *
   * @return object|NULL
   *   The dbxref Object.
   */
  protected function insertChadoDBxrefbyTermID(string $term_id) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    list($db, $accession) = explode(':', $term_id);
    $result = $chado->select('1:db', 'DB')
      ->fields('DB', ['db_id'])
      ->condition('name', $db, '=')
      ->execute();
    if (!$result) {
      return NULL;
    }

    $db_id = $result->fetchField();
    $chado->insert('1:dbxref')
      ->fields([
        'db_id' => $db_id,
        'accession' => $accession,
      ])
      ->execute();
    return $this->getChadoDBXrefbyTermID($term_id);
  }

  /**
   * Retrieve a record from the Chado cvterm table.
   *
   * This function uses the db.name (IdSpace), cv.name (vocabulary)
   * and dbxref.accession values to uniquely identify a term in Chado.
   *
   * @param TripalTerm $term
   *   The TripalTerm object to save.
   * @return object
   *   The cvterm record in object form.
   */
  protected function getChadoCVTerm(TripalTerm $term) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    $query = $chado->select('1:cvterm', 'CVT');
    $query->join('1:dbxref', 'DBX', '"DBX".dbxref_id = "CVT".dbxref_id');
    $query->join('1:cv', 'CV', '"CV".cv_id = "CVT".cv_id');
    $query->join('1:db', 'DB', '"DB".db_id = "DBX".db_id');
    $query->fields('CVT', ['cv_id', 'cvterm_id', 'definition', 'is_obsolete', 'is_relationshiptype'])
      ->fields('DBX', ['dbxref_id', 'accession'])
      ->condition('DB.name', $term->getIdSpace(), '=')
      ->condition('CV.name', $term->getVocabulary(), '=')
      ->condition('DBX.accession', $term->getAccession(), '=');
    $result = $query->execute();
    if (!$result) {
      return NULL;
    }
    return $result->fetchObject();
  }


  /**
   * Inserts a new term into Chado.
   *
   * The term should be checked that it does not exist
   * prior to calling this function.
   *
   * @param Drupal\tripal\TripalVocabTerms\TripalTerm $term
   *   The term object to update
   *
   * @param array $options
   *   The options passed to the saveTerm() function.
   *
   * @return boolean
   *   True if the insert was successful, false otherwise.
   */
  protected function insertTerm(TripalTerm $term, $options) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    try {
      // The CV and DB records should already exist.
      $cv = $this->getChadoCV($term);
      $db = $this->getChadoDB($term);

      // The DBXref may already exist even though the
      // term does not.  So, check first and if not then
      // add it.
      $dbxref = $this->getChadoDBXref($term);
      if (!$dbxref) {

        $chado->insert('1:dbxref')
          ->fields([
            'db_id' => $db->db_id,
            'accession' => $term->getAccession(),
          ])
        ->execute();
        $dbxref = $this->getChadoDBXref($term);
      }

      // Add the CVterm record.
      $chado->insert('1:cvterm')
        ->fields([
          'cv_id' => $cv->cv_id,
          'dbxref_id' => $dbxref->dbxref_id,
          'name' => $term->getName(),
          'definition' => $term->getDefinition(),
          'is_obsolete' => $term->isObsolete() ? 1 : 0,
          'is_relationshiptype' => $term->isRelationshipType() ? 1 : 0,
        ])
        ->execute();
      $cvterm = $this->getChadoCVTerm($term);
      if (!$cvterm) {
        return False;
      }

      // Now save the term attributes.
      if (!$this->saveTermAttributes($term, $cvterm, $options)) {
        return False;
      }

    }
    catch (Exception $e) {
      $this->messageLogger->error('ChadoIdSpace::insertTerm(). could not insert the cvterm record: @message',
          ['@message' => $e->getMessage()]);
      return False;
    }
    return True;
  }

  /**
   *
   * @param TripalTerm $term
   * @param object $cvterm
   * @param array $options
   * @return bool
   */
  protected function saveTermAttributes(TripalTerm $term, object $cvterm, array $options) : bool {

    $update_parent = False;
    if (array_key_exists('updateParent', $options)) {
      $update_parent = $options['updateParent'];
    }

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Add in synonyms.ount($syns->chado->delete('1:cvtermsynonym')->condition('cvterm_id', $cvterm->cvterm_id)->execute();
    $chado->delete('1:cvtermsynonym')->condition('cvterm_id', $cvterm->cvterm_id)->execute();
    foreach ($term->getSynonyms() as $synonym => $type_term) {
      $query = $chado->insert('1:cvtermsynonym');
      if ($type_term) {
        $type_cvterm = $this->getChadoCVTerm($type_term);
        $query->fields([
          'cvterm_id' => $cvterm->cvterm_id,
          'synonym' => $synonym,
          'type_id' => $type_cvterm->cvterm_id,
        ]);
      }
      else {
        $query->fields([
          'cvterm_id' => $cvterm->cvterm_id,
          'synonym' => $synonym,
        ]);
      }
      $query->execute();
    }

    // Add in the properties
    $chado->delete('1:cvtermprop')->condition('cvterm_id', $cvterm->cvterm_id)->execute();
    foreach ($term->getProperties() as $term_id => $properties) {
      foreach  ($properties as $rank => $tuple) {
        $type_term = $this->getChadoCVTerm($tuple[0]);
        if (!$type_term) {
          return False;
        }
        $value = $tuple[1];
        $chado->insert('1:cvtermprop')
          ->fields([
            'cvterm_id' => $cvterm->cvterm_id,
            'type_id' => $type_term->cvterm_id,
            'value' => $value,
            'rank' => $rank
          ])
          ->execute();
      }
    }

    // Add in the alternate IDs.
    $chado->delete('1:cvterm_dbxref')->condition('cvterm_id', $cvterm->cvterm_id)->execute();
    foreach ($term->getAltIds() as $term_id) {
      $alt_dbxref = $this->getChadoDBXrefbyTermID($term_id);
      if (!$alt_dbxref) {
        $alt_dbxref = $this->insertChadoDBxrefbyTermID($term_id);
        if (!$alt_dbxref) {
          return False;
        }
      }
      $chado->insert('1:cvterm_dbxref')
        ->fields([
          'cvterm_id' => $cvterm->cvterm_id,
          'dbxref_id' => $alt_dbxref->dbxref_id,
        ])
        ->execute();
    }

    // Add in the parents.
    $chado->delete('1:cvterm_relationship')->condition('subject_id', $cvterm->cvterm_id)->execute();
    foreach ($term->getParents() as $term_id => $tuple) {
      $parent_term = $tuple[0];
      $rel_term = $tuple[1];
      $parent_term = $this->getChadoCVTerm($parent_term);
      if (!$parent_term) {
        return False;
      }
      $rel_cvterm = $this->getChadoCVTerm($rel_term);
      if (!$rel_cvterm) {
        return False;
      }
      $chado->insert('1:cvterm_relationship')
        ->fields([
          'type_id' => $rel_cvterm->cvterm_id,
          'subject_id' => $cvterm->cvterm_id,
          'object_id' => $parent_term->cvterm_id,
        ])
        ->execute();

      // Update the parent if requested to do so.
      if ($update_parent) {
        $parent_idSpace = $parent_term->getIdSpaceObject();
        $parent_idSpace->saveTerm($parent_term, ['failIfExists' => $options['failIfExists']]);
      }
    }

    return True;
  }

  /**
   * Updates an existing term in Chado.
   *
   * The term should be checked that it already exists
   * prior to execution of this function.
   *
   * @param Drupal\tripal\TripalVocabTerms\TripalTerm $term
   *   The term object to update
   * @param object $cvterm
   *   The record object for the term to update from the Chado cvterm table.
   * @param array $options
   *   The options passed to the saveTerm() function.
   *
   * @return boolean
   *   True if the update was successful, false otherwise.
   */
  protected function updateTerm(TripalTerm $term, object &$cvterm, array $options) {

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    try {
      $chado->update('1:cvterm')
        ->fields([
          'name' => $term->getName(),
          'definition' => $term->getDefinition(),
          'is_obsolete' => $term->isObsolete() ? 1 : 0,
          'is_relationshiptype' => $term->isRelationshipType() ? 1 : 0,
        ])
        ->condition('cvterm_id', $cvterm->cvterm_id)
        ->execute();
      $cvterm = $this->getChadoCVTerm($term);

      $this->saveTermAttributes($term, $cvterm, $options);
    }
    catch (Exception $e) {
      $this->messageLogger->error('ChadoIdSpace: could not update the cvterm record: @message',
          ['@message' => $e->getMessage()]);
      return False;
    }
    return True;
  }

  /**
   * {@inheritdoc}
   */
  public function removeTerm($accession) {

  }

  /**
   * {@inheritdoc}
   */
  public function getURLPrefix() {
    $db = $this->loadIdSpace();
    if (!$db) {
      return NULL;
    }
    return $db['urlprefix'];
  }

  /**
   * {@inheritdoc}
   */
  public function setURLPrefix($prefix) {

    // Don't set a value for an ID space that isn't valid.
    if (!$this->is_valid) {
      return False;
    }

    // Make sure the URL prefix is good.
    if (empty($prefix)) {
      $this->messageLogger->error('ChadoIdSpace: No URL prefix for the vocabulary ID Space was provided when setURLPrefix() was called.');
      return False;
    }
    if (strlen($prefix) > $this->db_def['fields']['urlprefix']['size']) {
      $this->messageLogger->error('ChadoIdSpace: The URL prefix for the vocabulary ID Space must not be longer than @size characters. ' +
          'The value provided was: @value',
          ['@size' => $this->db_def['fields']['urlprefix']['size'],
            '@value' => $prefix]);
      return False;
    }

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Update the record in the Chado `db` table.
    $query = $chado->update('1:db')
      ->fields(['urlprefix' => $prefix])
      ->condition('name', $this->getName(), '=');
    $num_updated = $query->execute();
    if ($num_updated != 1) {
      $this->messageLogger->error('ChadoIdSpace: The URL prefix could not be updated for the vocabulary ID Space.');
      return False;
    }
    return True;
  }


  /**
   * {@inheritdoc}
   */
  public function getDescription() {
    $db = $this->loadIdSpace();
    if (!$db) {
      return NULL;
    }
    return $db['description'];

  }

  /**
   * {@inheritdoc}
   */
  public function setDescription($description) {

    // Don't set a value for an ID space that isn't valid.
    if (!$this->is_valid) {
      return False;
    }


    // Make sure the description is not too long.
    if (empty($description)) {
      $this->messageLogger->error('ChadoIdSpace: You must provide a description when calling setDescription().',
          ['@size' => $this->db_def['fields']['description']['size'],
           '@value' => $description]);
      return False;
    }
    if (strlen($description) > $this->db_def['fields']['description']['size']) {
      $this->messageLogger->error('ChadoIdSpace: The description for the vocabulary ID space must not be longer than @size characters. ' +
          'The value provided was: @value',
          ['@size' => $this->db_def['fields']['description']['size'],
           '@value' => $description]);
      return False;
    }

    // Instantiate a TripalDBX connection for Chado.
    $chado = \Drupal::service('tripal_chado.database');

    // Update the record in the Chado `db` table.
    $query = $chado->update('1:db')
       ->fields(['description' => $description])
       ->condition('name', $this->getName(), '=');
    $num_updated = $query->execute();
    if ($num_updated != 1) {
      $this->messageLogger->error('ChadoIdSpace: The description could not be updated for the vocabulary ID Space.');
      return False;
    }
    return True;

  }

  /**
   * Retrieves from the Drupal cache the default vocabulary for this space..
   *
   * @return string
   *   The deffault vocabulary name.
   */
  protected function getDefaultVocabCache() {
    $cid = 'chado_id_space_' . $this->getName() . '_default_vocab';
    $default_vocab = '';
    if ($cache = \Drupal::cache()->get($cid)) {
      $default_vocab = $cache->data;
    }
    else {
      // If we couldn't find the cached vocabulary name then
      // we should do a lookup. The cache must have been cleared.
      // We'll pick the first entered cv record as the default.
      $chado = \Drupal::service('tripal_chado.database');
      $query = $chado->select('1:db2cv_mview', 'D2C');
      $query->fields('D2C', ['cvname']);
      $query->condition('dbname', $this->getName());
      $query->orderBy('cv_id');
      $results = $query->execute();
      $result = $results->fetchObject();
      if ($result) {
        $default_vocab = $result->cvname;
        $this->setDefaultVocabCache($default_vocab);
      }
    }

    return $default_vocab;
  }

  /**
   * Sets in the Drupal cache the default vocabulary.
   *
   * @param string $vocabulary
   *   The default vocabulary name.
   */
  protected function setDefaultVocabCache($vocabulary) {
    $cid = 'chado_id_space_' . $this->getName() . '_default_vocab';
    \Drupal::cache()->set($cid, $vocabulary);
  }

  /**
   * {@inheritdoc}
   */
  public function setDefaultVocabulary($name) {
    $retval = parent::setDefaultVocabulary($name);
    if ($retval === True) {
      $this->default_vocabulary = $name;
    }
    $this->setDefaultVocabCache($name);
    return $retval;
  }
}