Browse Source

Merge pull request #641 from tripal/t3-chado_compliance_checking

ChadoSchema class and chado compliance checking
Lacey-Anne Sanderson 6 years ago
parent
commit
63ba7cf112

+ 1 - 0
docs/dev_guide.rst

@@ -9,6 +9,7 @@ Developer's Guide
    dev_guide/introduction
    dev_guide/data_structures
    dev_guide/best_practices
+   dev_guide/chado
    dev_guide/custom_modules
    dev_guide/custom_field
    dev_guide/custom_data_loader

+ 296 - 0
docs/dev_guide/chado.rst

@@ -0,0 +1,296 @@
+Accessing Chado
+================
+
+Primarily biological data made available to Tripal is stored in the GMOD Chado
+schema. As such, you will likely need to interact with Chado at some point.
+Tripal has developed a number of API functions and classes to make this
+interaction easier and more generic.
+
+The Chado Query API
+--------------------
+
+Provides an API for querying of chado including inserting, updating, deleting and selecting from specific chado tables. There is also a generic function, ``chado_query()``, to execute and SQL statement on chado. It is ideal to use these functions to interact with chado in order to keep your module compatible with both local & external chado databases. Furthermore, it ensures connection to the chado database is taken care of for you.
+
+Generic Queries to a specifc chado table
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Selecting Records
+""""""""""""""""""
+
+``chado_select_record( [table name], [columns to select], [specify record to select], [options*] )``
+
+This function allows you to select various columns from the specified chado table. Although you can only select from a single table, you can specify the record to select using values from related tables through use of a nested array. For example, the following code shows you how to select the name and uniquename of a feature based on it's type and source organism.
+
+.. code-block:: php
+
+  $values =  array(
+    'organism_id' => array(
+      'genus' => 'Citrus',
+      'species' => 'sinensis',
+    ),
+    'type_id' => array (
+      'cv_id' => array (
+        'name' => 'sequence',
+      ),
+      'name' => 'gene',
+      'is_obsolete' => 0
+    ),
+  );
+
+  $result = chado_select_record(
+    'feature',                      // table to select from
+    array('name', 'uniquename'),    // columns to select
+    $values                         // record to select (see variable defn. above)
+  );
+
+Inserting Records
+""""""""""""""""""
+
+``chado_insert_record( [table name], [values to insert], [options*] )``
+
+This function allows you to insert a single record into a specific table. The values to insert are specified using an associative array where the keys are the column names to insert into and they point to the value to be inserted into that column. If the column is a foreign key, the key will point to an array specifying the record in the foreign table and then the primary key of that record will be inserted in the column. For example, the following code will insert a feature and for the type_id, the cvterm.cvterm_id of the cvterm record will be inserted and for the organism_id, the organism.organism_id of the organism_record will be inserted.
+
+.. code-block:: php
+
+  $values =  array(
+    'organism_id' => array(
+        'genus' => 'Citrus',
+        'species' => 'sinensis',
+     ),
+    'name' => 'orange1.1g000034m.g',
+    'uniquename' => 'orange1.1g000034m.g',
+    'type_id' => array (
+        'cv_id' => array (
+           'name' => 'sequence',
+        ),
+        'name' => 'gene',
+        'is_obsolete' => 0
+     ),
+  );
+  $result = chado_insert_record(
+    'feature',             // table to insert into
+    $values                // values to insert
+  );
+
+Updating Records
+""""""""""""""""""
+
+``chado_update_record( [table name], [specify record to update], [values to change], [options*] )``
+
+This function allows you to update records in a specific chado table. The record(s) you wish to update are specified the same as in the select function above and the values to be update are specified the same as the values to be inserted were. For example, the following code species that a feature with a given uniquename, organism_id, and type_id (the unique constraint for the feature table) will be updated with a new name, and the type changed from a gene to an mRNA.
+
+.. code-block:: php
+
+  $umatch = array(
+    'organism_id' => array(
+      'genus' => 'Citrus',
+      'species' => 'sinensis',
+    ),
+    'uniquename' => 'orange1.1g000034m.g7',
+    'type_id' => array (
+      'cv_id' => array (
+        'name' => 'sequence',
+      ),
+      'name' => 'gene',
+      'is_obsolete' => 0
+    ),
+  );
+  $uvalues = array(
+    'name' => 'orange1.1g000034m.g',
+    'type_id' => array (
+      'cv_id' => array (
+        'name' => 'sequence',
+      ),
+      'name' => 'mRNA',
+      'is_obsolete' => 0
+    ),
+  );
+  $result = chado_update_record('feature',$umatch,$uvalues);
+
+Deleting Records
+"""""""""""""""""
+
+``chado_delete_record( [table name], [specify records to delete], [options*] )``
+
+This function allows you to delete records from a specific chado table. The record(s) to delete are specified the same as the record to select/update was above. For example, the following code will delete all genes from the organism Citrus sinensis.
+
+.. code-block:: php
+
+  $values =  array(
+    'organism_id' => array(
+        'genus' => 'Citrus',
+        'species' => 'sinensis',
+     ),
+    'type_id' => array (
+        'cv_id' => array (
+           'name' => 'sequence',
+        ),
+        'name' => 'gene',
+        'is_obsolete' => 0
+     ),
+  );
+  $result = chado_select_record(
+     'feature',                      // table to select from
+     $values                         // records to delete (see variable defn. above)
+  );
+
+Generic Queries for any SQL
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Often it is necessary to select from more then one table in chado or to execute other complex queries that cannot be handled efficiently by the above functions. It is for this reason that the ``chado_query( [sql string], [arguments to sub-in to the sql] )`` function was created. This function allows you to execute any SQL directly on the chado database and should be used with care. If any user input will be used in the query make sure to put a placeholder in your SQL string and then define the value in the arguments array. This will make sure that the user input is sanitized and safe through type-checking and escaping. The following code shows an example of how to use user input resulting from a form and would be called with the form submit function.
+
+.. code-block:: php
+
+  $sql = "SELECT F.name, CVT.name as type_name, ORG.common_name
+           FROM feature F
+           LEFT JOIN cvterm CVT ON F.type_id = CVT.cvterm_id
+           LEFT JOIN organism ORG ON F.organism_id = ORG.organism_id
+           WHERE
+             F.uniquename = :feature_uniquename";
+  $args = array( ':feature_uniquename' => $form_state['values']['uniquename'] );
+  $result = chado_query( $sql, $args );
+  foreach ($result as $r) { [Do something with the records here] }
+
+If you are going to need more then a couple fields, you might want to use the Chado Variables API (specifically ``chado_generate_var()``) to select all of the common fields needed including following foreign keys.
+
+Loading of Variables from chado data
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+These functions, ``chado_generate_var()`` and ``chado_expand_var()``, generate objects containing the full details of a record(s) in chado. These should be used in all theme templates.
+
+This differs from the objects returned by ``chado_select_record`` in so far as all foreign key relationships have been followed meaning you have more complete details. Thus this function should be used whenever you need a full variable and ``chado_select_record`` should be used if you only case about a few columns.
+
+The initial variable is generated by the ``chado_generate_var([table], [filter criteria], [optional options])`` function. An example of how to use this function is:
+
+.. code-block:: php
+
+  $values = array(
+    'name' => 'Medtr4g030710'
+  );
+  $features = chado_generate_var('feature', $values);
+
+This will return an object if there is only one feature with the name Medtr4g030710 or it will return an array of feature objects if more than one feature has that name.
+
+Some tables and fields are excluded by default. To have those tables & fields added to your variable you can use the ``chado_expand_var([chado variable], [type], [what to expand], [optional options])`` function. An example of how to use this function is:
+
+.. code-block:: php
+
+  // Get a chado object to be expanded
+  $values = array(
+    'name' => 'Medtr4g030710'
+  );
+
+  $features = chado_generate_var('feature', $values);
+
+  // Expand the organism node
+  $feature = chado_expand_var($feature, 'node', 'organism');
+
+  // Expand the feature.residues field
+  $feature = chado_expand_var($feature, 'field', 'feature.residues');
+
+  // Expand the feature properties (featureprop table)
+  $feature = chado_expand_var($feature, 'table', 'featureprop');
+
+
+The Chado Schema API
+--------------------
+
+The Chado Schema API provides an application programming interface (API) for describing Chado tables, accessing these descriptions and checking for compliancy of your current database to the chado schema. This API consists of the ChadoSchema class which provides methods for interacting with the Chado Schema API and a collection of supporting functions, one for each table in Chado, which describe each version of the Chado schema. Each function simply returns a Drupal style array that defines the table.
+
+Ensuring columns Tables & Columns exist
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Generally you can assume the tables and columns in the Chado schema have been unaltered. That said, there are still cases where you might want to check that specific tables and columns exist. For example, when using a custom table, it is best practice to ensure it is there before querying as it can be removed through the administrative interface.
+
+To check the existence of a specific table and column, you can use the following:
+
+.. code-block:: php
+
+  $chado_schema = new \ChadoSchema();
+
+  // Check that the organism_feature_count custom table exists.
+  $table_name = 'organism_feature_count';
+  $table_exists = $chado_schema->checkTableExists($table_name);
+
+  if ($table_exists) {
+
+    // Check that the organism_feature_count.feature_id column exists.
+    $column_name = 'feature_id';
+    $column_exists = $chado_schema->checkColumnExists($table_name, $column_name);
+
+    if ($column_exists) {
+
+      [ do your query, etc. here ]
+
+    } else { [warn the admin using tripal_repot_error()] }
+  } else { [warn the admin using tripal_repot_error()] }
+
+Checking the Schema Version
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you are using chado tables specific to a given version of Chado, it is best practice to check the chado version of the current site before querying those tables. You can use the following query to do this:
+
+.. code-block:: php
+
+  $chado_schema = new \ChadoSchema();
+  $version = $chado_schema-getVersion();
+  if ($version == '1.3') {
+    [do your chado v1.3 specific querying here]
+  } else { [warn the admin using tripal_report_error() ] }
+
+
+Retrieving a list of tables
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To retrieve a list of Chado tables, you can use the following:
+
+.. code-block:: php
+
+  $chado_schema = new \ChadoSchema();
+
+  // All Chado Tables including custom tables
+  $all_tables = $chado_schema->getTableNames(TRUE);
+
+  // All Chado Tables without custom tables
+  $all_tables = $chado_schema->getTableNames();
+
+  // Chado tables designated as Base Tables by Tripal.
+  $base_tables = $chado_schema->getBaseTables();
+
+
+Ensuring your Chado instance is compliant
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Checking compliancy of your Chado instance with the released Chado Schema is a great way to **confirm an upgrade has gone flawlessly**. Additionally, while it is not recommended, sometimes customizations to the Chado schema may be necessary. In these cases, you should **ensure backwards compatibility** through compliance checking to confirm Tripal will work as expected.
+
+Chado compliancy testing is provided with Tripal's automated PHPUnit testing. As such, to test compliancy of your specific Chado instance, you first need to install Composer. Luckily this can be as easy as:
+
+.. code-block:: bash
+
+  php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+  php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+  php composer-setup.php
+  php -r "unlink('composer-setup.php');"
+
+Once you have Composer, you need to install PHPUnit. This is installed locally within your Tripal repository. The following bash snippet shows you how to both install composer locally and run compliance checking.
+
+.. code-block:: php
+
+  cd [DRUPAL_ROOT]/sites/all/modules/tripal
+  composer up
+
+  # Now run compliance checking
+  ./vendor/bin/phpunit --group chado-compliance
+
+Schema Definition
+^^^^^^^^^^^^^^^^^^
+
+To retrieve the schema definition for a specific table, you can execute the following:
+
+.. code-block:: php
+
+  $table_name = 'feature';
+  $chado_schema = new \ChadoSchema();
+  $table_schema = $chado_schema->getTableSchema($table_name);
+
+The resulting ``$table_schema`` variable contains a Drupal-style array describing the schema definition of the table specified by ``$table_name``. This is a great tool when trying to develop generic queries, since you can extract information about an unknown table and use it to build a query for that table. For more information on the format of this array, see `the Drupal Schema API documentation <https://api.drupal.org/api/drupal/includes%21database%21schema.inc/group/schemaapi/7.x>`_.

+ 137 - 0
tests/tripal_chado/api/ChadoComplianceTest.php

@@ -0,0 +1,137 @@
+<?php
+namespace Tests\tripal_chado\api;
+
+use StatonLab\TripalTestSuite\DBTransaction;
+use StatonLab\TripalTestSuite\TripalTestCase;
+
+module_load_include('inc', 'tripal_chado', 'api/ChadoSchema');
+
+/**
+ * Tests the current Chado Database is compliant with the schema definition used by Tripal
+ */
+class ChadoComplianceTest extends TripalTestCase {
+  // Uncomment to auto start and rollback db transactions per test method.
+  use DBTransaction;
+
+  /**
+   * DataProvider, a list of all chado tables.
+   *
+   * @return array
+   */
+  public function chadoTableProvider() {
+
+    $chado_schema = new \ChadoSchema();
+    $version = $chado_schema->getVersion();
+
+    $dataset = [];
+    foreach ($chado_schema->getTableNames() as $table_name) {
+      $dataset[] = [$version, $table_name];
+    }
+
+    return $dataset;
+  }
+
+  /**
+   * Tests Compliance for a given table.
+   *
+   * The following is tested:
+   *   1. The table exists in the correct schema.
+   *   2. It has all the fields we expect.
+   *   3. Each field is the type we expect.
+   *   4. It has all the constraints we expect.
+   *   5. Each constraint consists of the columns we expect.
+   *
+   * @dataProvider chadoTableProvider
+   *
+   * @group api
+   * @group chado
+   * @group chado-compliance
+   */
+  public function testTableCompliance($schema_version, $table_name) {
+
+    // Create the ChadoSchema class to aid in testing.
+    $chado_schema = new \ChadoSchema();
+    $version = $chado_schema->getVersion();
+    $schema_name = $chado_schema->getSchemaName();
+
+    // Check #1: The table exists in the correct schema.
+    $this->assertTrue(
+      $chado_schema->checkTableExists($table_name),
+      t('"!table_name" should exist in the "!chado" schema v!version.',
+        array('!table_name' => $table_name, '!chado' => $schema_name, '!version' => $version))
+    );
+
+    // Retrieve the schema for this table.
+    $table_schema = $chado_schema->getTableSchema($table_name);
+
+    // For each column in this table...
+    foreach ($table_schema['fields'] as $column_name => $column_details) {
+
+      // Check #2: The given field exists in the table.
+      $this->assertTrue(
+        $chado_schema->checkColumnExists($table_name, $column_name),
+        t('The column "!column" must exist in "!table" for chado v!version.',
+          array('!column' => $column_name, '!table' => $table_name, '!version' => $version))
+      );
+
+      // Check #3: The field is the type we expect.
+      $this->assertTrue(
+        $chado_schema->checkColumnType($table_name, $column_name, $column_details['type']),
+        t('The column "!table.!column" must be of type "!type" for chado v!version.',
+          array('!column' => $column_name, '!table' => $table_name,
+            '!version' => $version, '!type' => $column_details['type']))
+      );
+    }
+
+    // There are three types of constraints:
+    // primary key, unique keys, and foreign keys.
+    //.......................................
+
+    // For the primary key:
+    // Check #4: The constraint exists.
+    if (isset($table_schema['primary key'][0]) AND !empty($table_schema['primary key'][0])) {
+      $pkey_column = $table_schema['primary key'][0];
+      $this->assertTrue(
+        $chado_schema->checkPrimaryKey($table_name, $pkey_column),
+        t('The column "!table.!column" must be a primary key with an associated sequence and constraint for chado v!version.',
+          array('!column' => $pkey_column, '!table' => $table_name, '!version' => $version))
+      );
+    }
+
+    // For each unique key:
+    foreach ($table_schema['unique keys'] as $constraint_name => $columns) {
+      // @debug print "Check '$constraint_name' for '$table_name': ".implode(', ', $columns).".\n";
+
+      // Check #4: The constraint exists.
+      $this->assertTrue(
+        $chado_schema->checkConstraintExists($table_name, $constraint_name, 'UNIQUE'),
+        t('The unique constraint "!name" for "!table" must exist for chado v!version.',
+          array('!name' => $constraint_name, '!table' => $table_name, '!version' => $version))
+      );
+
+      // Check #5: The constraint consists of the columns we expect.
+      // @todo
+    }
+
+    // For each foreign key:
+    foreach ($table_schema['foreign keys'] as $fk_table => $details) {
+      foreach ($details['columns'] as $base_column => $fk_column) {
+        // @debug print "Check '$table_name.$base_column =>  $fk_table.$fk_column ' foreign key.";
+
+        // Check #4: The constraint exists.
+        $constraint_name = $table_name . '_' . $base_column . '_fkey';
+        $this->assertTrue(
+          $chado_schema->checkFKConstraintExists($table_name, $base_column),
+          t('The foreign key constraint "!name" for "!table.!column" => "!fktable.!fkcolumn" must exist for chado v!version.',
+            array('!name' => $constraint_name,
+              '!table' => $table_name, '!column' => $base_column,
+              '!fktable' => $fk_table, '!fkcolumn' => $fk_column,
+              '!version' => $version))
+        );
+
+        // Check #5: The constraint consists of the columns we expect.
+        // @todo
+      }
+    }
+  }
+}

+ 313 - 0
tests/tripal_chado/api/ChadoSchemaTest.php

@@ -0,0 +1,313 @@
+<?php
+namespace Tests\tripal_chado\api;
+
+use StatonLab\TripalTestSuite\DBTransaction;
+use StatonLab\TripalTestSuite\TripalTestCase;
+use Faker\Factory;
+
+module_load_include('inc', 'tripal_chado', 'api/ChadoSchema');
+
+/**
+ * Tests the ChadoSchema class.
+ *
+ * @todo test "Check" functions in the ChadoSchema class.
+ */
+class ChadoSchemaTest extends TripalTestCase {
+  use DBTransaction;
+
+  /**
+   * Tests that the class can be initiated with or without a record specified
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testInitClass() {
+
+    // Test with no parameters.
+    $chado_schema = new \ChadoSchema();
+    $this->assertNotNull($chado_schema);
+
+    // Test with version.
+    $chado_schema = new \ChadoSchema('1.3');
+    $this->assertNotNull($chado_schema);
+  }
+
+  /**
+   * Tests the ChadoSchema->getVersion() method.
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testGetVersion() {
+
+    // Generate a fake version.
+    $faker = Factory::create();
+    $version = $faker->randomFloat(2, 1, 5);
+
+    // Check version can be retrieved when we set it.
+    $chado_schema = new \ChadoSchema($version);
+    $retrieved_version = $chado_schema->getVersion();
+    $this->assertEquals(
+      $version,
+      $retrieved_version,
+      t('The version retrieved via ChadoSchema->getVersion, "!ret", should equal that set, "!set"',
+        array('!ret' => $retrieved_version, '!set' => $version))
+    );
+
+    // @todo Check version can be retrieved when it's looked up?
+  }
+
+  /**
+   * Tests the ChadoSchema->getSchemaName() method.
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testGetSchemaName() {
+
+    // Generate a fake version.
+    $faker = Factory::create();
+    $version = $faker->randomFloat(2, 1, 5);
+    $schema_name = $faker->word();
+
+    // Check the schema name can be retrieved when we set it.
+    $chado_schema = new \ChadoSchema($version, $schema_name);
+    $retrieved_schema = $chado_schema->getSchemaName();
+    $this->assertEquals(
+      $schema_name,
+      $retrieved_schema,
+      t('The schema name retrieved via ChadoSchema->getSchemaName, "!ret", should equal that set, "!set"',
+        array('!ret' => $retrieved_schema, '!set' => $schema_name))
+    );
+
+    // @todo Check schema name can be retrieved when it's looked up?
+  }
+
+  /**
+   * Tests ChadoSchema->getTableNames() method.
+   *
+   * @dataProvider knownTableProvider
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testGetTableNames($version, $known_tables) {
+
+    // Check: Known tables for a given version are returned.
+    $chado_schema = new \ChadoSchema($version);
+    $returned_tables = $chado_schema->getTableNames();
+
+    foreach ($known_tables as $table_name) {
+      $this->assertArrayHasKey(
+        $table_name,
+        $returned_tables,
+        t('The table, "!known", should exist in the returned tables list for version !version.',
+          array(':known' => $table_name, ':version' => $version))
+      );
+    }
+  }
+
+  /**
+   * Tests ChadoSchema->getTableSchema() method.
+   *
+   * @dataProvider chadoTableProvider
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testGetTableSchema($version, $table_name) {
+
+    // Check: a schema is returned that matches what we expect.
+    $chado_schema = new \ChadoSchema($version);
+
+    $table_schema = $chado_schema->getTableSchema($table_name);
+
+    $this->assertNotEmpty(
+      $table_schema,
+      t('Returned schema for "!table" in chado v!version should not be empty.',
+        array('!table' => $table_name, '!version' => $version))
+    );
+
+    $this->assertArrayHasKey(
+      'fields',
+      $table_schema,
+      t('The schema array for "!table" should have columns listed in an "fields" array',
+        array('!table' => $table_name))
+    );
+
+    // Instead of asserting these keys exist. Lets assert that if they do exist,
+    // they match the expected format.
+
+    if (isset($table_schema['primary key'])) {
+      $this->assertTrue(is_array($table_schema['primary key']),
+	t('The primary key of the Tripal Schema definition for "!table" must be an array.',
+          array('!table' => $table_name)));
+  
+    }
+
+    $this->assertArrayHasKey(
+      'foreign keys',
+      $table_schema,
+      t('The schema array for "!table" should have foreign keys listed in an "foreign keys" array',
+        array('!table' => $table_name))
+    );
+
+  }
+
+  /**
+   * Tests ChadoSchema->getCustomTableSchema() method.
+   *
+   * @dataProvider knownCustomTableProvider
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testGetCustomTableSchema($table_name) {
+
+    // Check: a schema is returned that matches what we expect.
+    $chado_schema = new \ChadoSchema();
+    $table_schema = $chado_schema->getCustomTableSchema($table_name);
+
+    $this->assertNotEmpty(
+      $table_schema,
+      t('Returned schema for "!table" in chado v!version should not be empty.',
+        array('!table' => $table_name, '!version' => $version))
+    );
+
+    $this->assertArrayHasKey(
+      'fields',
+      $table_schema,
+      t('The schema array for "!table" should have columns listed in an "fields" array',
+        array('!table' => $table_name))
+    );
+
+    // NOTE: Other then ensuring fields are set, we can't test further since all other
+    // keys are technically optional and these arrays are set by admins.
+
+  }
+
+  /**
+   * Tests ChadoSchema->getBaseTables() method.
+   *
+   * @dataProvider knownBaseTableProvider
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+  public function testGetBaseTables($version, $known_tables) {
+
+    // Check: Known base tables for a given version are returned.
+    $chado_schema = new \ChadoSchema($version);
+    $returned_tables = $chado_schema->getBaseTables();
+
+    foreach ($known_tables as $table_name) {
+
+      $found = false;
+
+      foreach ($returned_tables as $check_table ){
+
+        if ($check_table == $table_name){
+          $found = True;
+        }
+      }
+      $this->assertTrue($found, "{$table_name} was not returned by getBaseTables for Chado v {$version}");
+    }
+
+  }
+
+  /**
+   * Tests ChadoSchema->getCvtermMapping() method.
+   *
+   * @dataProvider chadoTableProvider
+   *
+   * @group api
+   * @group chado
+   * @group chado-schema
+   */
+ // public function testGetCvtermMapping($version, $table_name) {
+
+    //
+//    // Ideally we would create a new chado table + mapping and then test this pulls it out
+//    // since admin can re-map terms. However, that's more then I meant to bite off right
+//    // now...
+//
+//    // @todo Test that known terms match the tables we expect.
+//
+//    // @todo Test that a non-existent term throws an error.
+//
+//    // @todo Test that an fake unmapped term returns no mapping.
+ // }
+
+  /**
+   * Data Provider: returns known tables specific to a given chado version.
+   *
+   * @return array
+   */
+   public function knownTableProvider() {
+    // chado version, array of 3 tables specific to version.
+
+    return [
+      ['1.2', ['cell_line_relationship', 'cvprop', 'chadoprop']],
+      ['1.3', ['analysis_cvterm', 'dbprop', 'organism_pub']],
+    ];
+   }
+
+  /**
+   * Data Provider: returns known tables specific to a given chado version.
+   *
+   * @return array
+   */
+   public function knownBaseTableProvider() {
+    // chado version, array of 3 tables specific to version.
+
+    return [
+      ['1.2', ['organism', 'feature', 'stock', 'project','analysis', 'phylotree']],
+      ['1.3', ['organism', 'feature', 'stock', 'project','analysis', 'phylotree']],
+    ];
+   }
+
+  /**
+   * Data Provider: returns known custom tables specific to a given chado version.
+   *
+   * NOTE: These tables are provided by core Tripal so we should be able to
+   *  depend on them. Also, for the same reason, chado version doesn't matter.
+   *
+   * @return array
+   */
+   public function knownCustomTableProvider() {
+
+    return [
+      ['library_feature_count'],
+      ['organism_feature_count'],
+      ['tripal_gff_temp'],
+    ];
+   }
+
+  /**
+   * DataProvider, a list of all chado tables.
+   *
+   * @return array
+   */
+  public function chadoTableProvider() {
+
+    // Provide the table list for all versions.
+    $dataset = [];
+    foreach (array('1.11','1.2','1.3') as $version) {
+      $chado_schema = new \ChadoSchema();
+      $version = $chado_schema->getVersion();
+
+      foreach ($chado_schema->getTableNames() as $table_name) {
+        $dataset[] = [$version, $table_name];
+      }
+    }
+
+    return $dataset;
+  }
+}

+ 629 - 0
tripal_chado/api/ChadoSchema.inc

@@ -0,0 +1,629 @@
+<?php
+/**
+ * Provides an application programming interface (API) for describing Chado tables.
+ *
+ * If you need the Drupal-style array definition for any table, use the following:
+ * @code
+
+    $chado_schema = new \ChadoSchema();
+    $table_schema = $chado_schema->getTableSchema($table_name);
+
+ * @endcode
+ *
+ * where the variable $table contains the name of the table you want to
+ * retireve.  The getTableSchema method determines the appropriate version of
+ * Chado and uses the Drupal hook infrastructure to call the appropriate
+ * hook function to retrieve the table schema.
+ *
+ * Additionally, here are some other examples of how to use this class:
+ * @code
+
+    // Retrieve the schema array for the organism table in chado 1.2
+    $chado_schema = new \ChadoSchema('1.2');
+    $table_schema = $chado_schema->getTableSchema('organism');
+
+    // Retrieve all chado tables.
+    $chado_schema = new \ChadoSchema();
+    $tables = $chado_schema->getTableNames();
+    $base_tables = $chado_schema->getbaseTables();
+
+    // Check the feature.type_id foreign key constraint
+    $chado_schema = new \ChadoSchema();
+    $exists = $chado_schema ->checkFKConstraintExists('feature','type_id');
+
+    // Check Sequence exists
+    $chado_schema = new \ChadoSchema();
+    $exists = $chado_schema->checkSequenceExists('organism','organism_id');
+    // Or just check the primary key directly
+    $compliant = $chado_schema->checkPrimaryKey('organism');
+
+ * @endcode
+ */
+class ChadoSchema {
+
+  /**
+   * @var string
+   *   The current version for this site. E.g. "1.3".
+   */
+  protected $version = '';
+
+  /**
+   * @var string
+   *   The name of the schema chado was installed in.
+   */
+  protected $schema_name = 'chado';
+
+  /**
+   * The ChadoSchema constructor.
+   *
+   * @param string $version
+   *   The current version for this site. E.g. "1.3". If a version is not provided, the
+   *   version of the current database will be looked up.
+   */
+  public function __construct($version = NULL, $schema_name = NULL) {
+
+    // Set the version of the schema.
+    if ($version === NULL) {
+      $this->version = chado_get_version(TRUE);
+    }
+    else {
+      $this->version = $version;
+    }
+
+    // Set the name of the schema.
+    if ($schema_name === NULL) {
+      $this->schema_name = chado_get_schema_name('chado');
+    }
+    else {
+      $this->schema_name = $schema_name;
+    }
+
+    // Check functions require the chado schema be local and installed...
+    // So lets check that now...
+    if (!chado_is_local()) {
+      tripal_report_error(
+        'ChadoSchema',
+        TRIPAL_NOTICE,
+        'The ChadoSchema class requires chado be installed within the drupal database
+          in a separate schema for any compliance checking functionality.'
+      );
+    }
+    if (!chado_is_installed()) {
+      tripal_report_error(
+        'ChadoSchema',
+        TRIPAL_NOTICE,
+        'The ChadoSchema class requires chado be installed
+          for any compliance checking functionality.'
+      );
+    }
+  }
+
+  /**
+   * Returns the version number of the Chado this object references.
+   *
+   * @returns
+   *   The version of Chado
+   */
+  public function getVersion() {
+    return $this->version;
+  }
+
+  /**
+   * Retrieve the name of the PostgreSQL schema housing Chado.
+   *
+   * @return
+   *   The name of the schema.
+   */
+  public function getSchemaName() {
+    return $this->schema_name;
+  }
+
+  /**
+   * Retrieves the list of tables in the Chado schema.  By default it only returns
+   * the default Chado tables, but can return custom tables added to the
+   * Chado schema if requested.
+   *
+   * @param $include_custom
+   *   Optional.  Set as TRUE to include any custom tables created in the
+   *   Chado schema. Custom tables are added to Chado using the
+   *   tripal_chado_chado_create_table() function.
+   *
+   * @returns
+   *   An associative array where the key and value pairs are the Chado table names.
+   */
+  public function getTableNames($include_custom = FALSE) {
+
+    $tables = array();
+    if ($this->version == '1.3') {
+      $tables_v1_3 = tripal_chado_chado_get_v1_3_tables();
+      foreach ($tables_v1_3 as $table) {
+        $tables[$table] = $table;
+      }
+    }
+    if ($this->version == '1.2') {
+      $tables_v1_2 = tripal_chado_chado_get_v1_2_tables();
+      foreach ($tables_v1_2 as $table) {
+        $tables[$table] = $table;
+      }
+    }
+    if ($this->version == '1.11' or $this->version == '1.11 or older') {
+      $tables_v1_11 = tripal_chado_chado_get_v1_11_tables();
+      foreach ($tables_v1_11 as $table) {
+        $tables[$table] = $table;
+      }
+    }
+
+    // now add in the custom tables too if requested
+    if ($include_custom) {
+      $sql = "SELECT table FROM {tripal_custom_tables}";
+      $resource = db_query($sql);
+
+      foreach ($resource as $r) {
+        $tables[$r->table] = $r->table;
+      }
+    }
+
+    asort($tables);
+    return $tables;
+
+  }
+
+  /**
+   * Retrieves the chado tables Schema API array.
+   *
+   * @param $table
+   *   The name of the table to retrieve.  The function will use the appopriate
+   *   Tripal chado schema API hooks (e.g. v1.11 or v1.2).
+   *
+   * @returns
+   *   A Drupal Schema API array defining the table.
+   */
+  public function getTableSchema($table) {
+
+    // first get the chado version.
+    $v = $this->version;
+
+    // get the table array from the proper chado schema
+    $v = preg_replace("/\./", "_", $v); // reformat version for hook name
+
+    // Call the module_invoke_all.
+    $hook_name = "chado_schema_v" . $v . "_" . $table;
+    $table_arr = module_invoke_all($hook_name);
+
+    // If the module_invoke_all returned nothing then let's make sure there isn't
+    // An API call we can call directly.  The only time this occurs is
+    // during an upgrade of a major Drupal version and tripal_core is disabled.
+    if ((!$table_arr or !is_array($table_arr)) and
+          function_exists('tripal_chado_' . $hook_name)) {
+      $api_hook = "tripal_chado_" . $hook_name;
+      $table_arr = $api_hook();
+    }
+
+    // if the table_arr is empty then maybe this is a custom table
+    if (!is_array($table_arr) or count($table_arr) == 0) {
+      $table_arr = $this->getCustomTableSchema($table);
+    }
+
+    return $table_arr;
+
+  }
+
+  /**
+   * Retrieves the schema array for the specified custom table.
+   *
+   * @param $table
+   *   The name of the table to create.
+   *
+   * @return
+   *   A Drupal-style Schema API array definition of the table. Returns
+   *   FALSE on failure.
+   */
+  public function getCustomTableSchema($table) {
+
+    $sql = "SELECT schema FROM {tripal_custom_tables} WHERE table_name = :table_name";
+    $results = db_query($sql, array(':table_name' => $table));
+    $custom = $results->fetchObject();
+    if (!$custom) {
+      return FALSE;
+    }
+    else {
+      return unserialize($custom->schema);
+    }
+  }
+
+  /**
+   *  Returns all chado base tables.
+   *
+   *  Base tables are those that contain the primary record for a data type. For
+   *  example, feature, organism, stock, are all base tables.  Other tables
+   *  include linker tables (which link two or more base tables), property tables,
+   *  and relationship tables.  These provide additional information about
+   *  primary data records and are therefore not base tables.  This function
+   *  retreives only the list of tables that are considered 'base' tables.
+   *
+   *  @return
+   *    An array of base table names.
+   *
+   *  @ingroup tripal_chado_schema_api
+   */
+  function getBaseTables() {
+
+    // Initialize the base tables with those tables that are missing a type.
+    // Ideally they should have a type, but that's for a future version of Chado.
+    $base_tables = array('organism', 'project', 'analysis', 'biomaterial',
+      'eimage', 'assay');
+
+    // We'll use the cvterm table to guide which tables are base tables. Typically
+    // base tables (with a few exceptions) all have a type.  Iterate through the
+    // referring tables.
+    $schema = $this->getTableSchema('cvterm');
+    $referring = $schema['referring_tables'];
+    foreach ($referring as $tablename) {
+
+      // Ignore the cvterm tables, relationships, chadoprop tables.
+      if ($tablename == 'cvterm_dbxref' || $tablename == 'cvterm_relationship' ||
+          $tablename == 'cvtermpath' || $tablename == 'cvtermprop' || $tablename == 'chadoprop' ||
+          $tablename == 'cvtermsynonym' || preg_match('/_relationship$/', $tablename) ||
+          preg_match('/_cvterm$/', $tablename) ||
+          // Ignore prop tables
+          preg_match('/prop$/', $tablename) || preg_match('/prop_.+$/', $tablename) ||
+          // Ignore nd_tables
+          preg_match('/^nd_/', $tablename)) {
+        continue;
+      }
+      else {
+        array_push($base_tables, $tablename);
+      }
+    }
+
+    // Remove any linker tables that have snuck in.  Linker tables are those
+    // whose foreign key constraints link to two or more base table.
+    $final_list = array();
+    foreach ($base_tables as $i => $tablename) {
+      // A few tables break our rule and seems to look
+      // like a linking table, but we want to keep it as a base table.
+      if ($tablename == 'biomaterial' or $tablename == 'assay' or $tablename == 'arraydesign') {
+        $final_list[] = $tablename;
+        continue;
+      }
+
+      // Remove the phenotype table. It really shouldn't be a base table as
+      // it is meant to store individual phenotype measurements.
+      if ($tablename == 'phenotype') {
+        continue;
+      }
+      $num_links = 0;
+      $schema = $this->getTableSchema($tablename);
+      $fkeys = $schema['foreign keys'];
+      foreach ($fkeys as $fkid => $details) {
+        $fktable = $details['table'];
+        if (in_array($fktable, $base_tables)) {
+          $num_links++;
+        }
+      }
+      if ($num_links < 2) {
+        $final_list[] = $tablename;
+      }
+    }
+
+    // Now add in the cvterm table to the list.
+    $final_list[] = 'cvterm';
+
+    // Sort the tables and return the list.
+    sort($final_list);
+    return $final_list;
+
+  }
+
+  /**
+   * Get information about which Chado base table a cvterm is mapped to.
+   *
+   * Vocbulary terms that represent content types in Tripal must be mapped to
+   * Chado tables.  A cvterm can only be mapped to one base table in Chado.
+   * This function will return an object that contains the chado table and
+   * foreign key field to which the cvterm is mapped.  The 'chado_table' property
+   * of the returned object contains the name of the table, and the 'chado_field'
+   * property contains the name of the foreign key field (e.g. type_id), and the
+   * 'cvterm' property contains a cvterm object.
+   *
+   * @params
+   *   An associative array that contains the following keys:
+   *     - cvterm_id:  the cvterm ID value for the term.
+   *     - vocabulary: the short name for the vocabulary (e.g. SO, GO, PATO)
+   *     - accession:  the accession for the term.
+   *     - bundle_id:  the ID for the bundle to which a term is associated.
+   *   The 'vocabulary' and 'accession' must be used together, the 'cvterm_id' can
+   *   be used on it's own.
+   * @return
+   *   An object containing the chado_table and chado_field properties or NULL if
+   *   if no mapping was found for the term.
+   */
+  public function getCvtermMapping($params) {
+    return chado_get_cvterm_mapping($params);
+  }
+
+  /**
+   * Check that any given Chado table exists.
+   *
+   * This function is necessary because Drupal's db_table_exists() function will
+   * not look in any other schema but the one where Drupal is installed
+   *
+   * @param $table
+   *   The name of the chado table whose existence should be checked.
+   *
+   * @return
+   *   TRUE if the table exists in the chado schema and FALSE if it does not.
+   */
+  public function checkTableExists($table) {
+    return chado_table_exists($table);
+  }
+
+  /**
+   * Check that any given column in a Chado table exists.
+   *
+   * This function is necessary because Drupal's db_field_exists() will not
+   * look in any other schema but the one were Drupal is installed
+   *
+   * @param $table
+   *   The name of the chado table.
+   * @param $column
+   *   The name of the column in the chado table.
+   *
+   * @return
+   *   TRUE if the column exists for the table in the chado schema and
+   *   FALSE if it does not.
+   *
+   * @ingroup tripal_chado_schema_api
+   */
+  public function checkColumnExists($table, $column) {
+    return chado_column_exists($table, $column);
+  }
+
+  /**
+   * Check that any given column in a Chado table exists.
+   *
+   * This function is necessary because Drupal's db_field_exists() will not
+   * look in any other schema but the one were Drupal is installed
+   *
+   * @param $table
+   *   The name of the chado table.
+   * @param $column
+   *   The name of the column in the chado table.
+   * @param $type
+   *   (OPTIONAL) The PostgreSQL type to check for. If not supplied it will be
+   *   looked up via the schema (PREFERRED).
+   *
+   * @return
+   *   TRUE if the column type matches what we expect and
+   *   FALSE if it does not.
+   *
+   * @ingroup tripal_chado_schema_api
+   */
+  public function checkColumnType($table, $column, $expected_type = NULL) {
+
+    // Ensure this column exists before moving forward.
+    if (!$this->checkColumnExists($table, $column)) {
+      tripal_report_error(
+        'ChadoSchema',
+        TRIPAL_WARNING,
+        'Unable to check the type of !table!column since it doesn\'t appear to exist in your site database.',
+        array('!column' => $column, '!table' => $table)
+      );
+      return FALSE;
+    }
+
+    // Look up the type using the Schema array.
+    if ($expected_type === NULL) {
+      $schema = $this->getTableSchema($table, $column);
+
+      if (is_array($schema) AND isset($schema['fields'][$column])) {
+        $expected_type = $schema['fields'][$column]['type'];
+      }
+      else {
+        tripal_report_error(
+          'ChadoSchema',
+          TRIPAL_WARNING,
+          'Unable to check the type of !table!column due to being unable to find the schema definition.',
+          array('!column' => $column, '!table' => $table)
+        );
+        return FALSE;
+      }
+    }
+
+    // There is some flexibility in the expected type...
+    // Fix that here.
+    switch ($expected_type) {
+      case 'int':
+        $expected_type = 'integer';
+        break;
+      case 'serial':
+        $expected_type = 'integer';
+        break;
+      case 'varchar':
+        $expected_type = 'character varying';
+        break;
+      case 'datetime':
+        $expected_type = 'timestamp without time zone';
+        break;
+      case 'char':
+        $expected_type = 'character';
+        break;
+    }
+
+    // Grab the type from the current database.
+    $query = 'SELECT data_type
+              FROM information_schema.columns
+              WHERE
+                table_name = :table AND
+                column_name = :column AND
+                table_schema = :schema
+              ORDER  BY ordinal_position
+              LIMIT 1';
+    $type = db_query($query,
+      array(':table' => $table, ':column' => $column, ':schema' => $this->schema_name))->fetchField();
+
+    // Finally we do the check!
+    if ($type === $expected_type) {
+      return TRUE;
+    }
+    elseif (($expected_type == 'float') AND (($type == 'double precision') OR ($type == 'real'))) {
+      return TRUE;
+    }
+    elseif ($type == 'smallint' AND $expected_type == 'integer') {
+      return TRUE;
+    }
+    elseif ($type == 'bigint' AND $expected_type == 'integer') {
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Check that any given sequence in a Chado table exists.
+   *
+   * @param table
+   *   The name of the table the sequence is used in.
+   * @param column
+   *   The name of the column the sequence is used to populate.
+   *
+   * @return
+   *   TRUE if the seqeuence exists in the chado schema and FALSE if it does not.
+   *
+   * @ingroup tripal_chado_schema_api
+   */
+  public function checkSequenceExists($table, $column) {
+
+    $prefixed_table = $this->schema_name.'.'.$table;
+    $sequence_name = db_query('SELECT pg_get_serial_sequence(:table, :column);',
+      array(':table' => $prefixed_table, ':column' => $column))->fetchField();
+
+
+    // Remove prefixed table from sequence name
+    $sequence_name = str_replace($this->schema_name.'.', '', $sequence_name);
+
+    return chado_sequence_exists($sequence_name);
+  }
+
+  /**
+   * Check that the primary key exists, has a sequence and a constraint.
+   *
+   * @param $table
+   *   The table you want to check the primary key for.
+   * @param $column
+   *   (OPTIONAL) The name of the primary key column.
+   *
+   * @return
+   *   TRUE if the primary key meets all the requirements and false otherwise.
+   */
+  public function checkPrimaryKey($table, $column = NULL) {
+
+    // If they didn't supply the column, then we can look it up.
+    if ($column === NULL) {
+      $table_schema = $this->getTableSchema($table);
+      $column = $table_schema['primary key'][0];
+    }
+
+    // If there is no primary key then we can't check it.
+    // It neither passes nore fails validation.
+    if (empty($column)) {
+      tripal_report_error(
+        'ChadoSchema',
+        TRIPAL_NOTICE,
+        'Cannot check the validity of the primary key for "!table" since there is no record of one.',
+        array('!table' => $table)
+      );
+      return NULL;
+    }
+
+    // Check the column exists.
+    $column_exists = $this->checkColumnExists($table, $column);
+    if (!$column_exists) {
+      return FALSE;
+    }
+
+    // First check that the sequence exists.
+    $sequence_exists = $this->checkSequenceExists($table, $column);
+    if (!$sequence_exists) {
+      return FALSE;
+    }
+
+    // Next check the constraint is there.
+    $constraint_exists = chado_query(
+      "SELECT 1
+      FROM information_schema.table_constraints
+      WHERE table_name=:table AND constraint_type = 'PRIMARY KEY'",
+      array(':table' => $table))->fetchField();
+    if (!$constraint_exists) {
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Check that the constraint exists.
+   *
+   * @param $table
+   *   The table the constraint applies to.
+   * @param $constraint_name
+   *   The name of the constraint you want to check.
+   * @param $type
+   *   The type of constraint. Should be one of "PRIMARY KEY", "UNIQUE", or "FOREIGN KEY".
+   *
+   * @return
+   *   TRUE if the constraint exists and false otherwise.
+   */
+  function checkConstraintExists($table, $constraint_name, $type) {
+
+    // Next check the constraint is there.
+    $constraint_exists = chado_query(
+      "SELECT 1
+      FROM information_schema.table_constraints
+      WHERE table_name=:table AND constraint_type = :type AND constraint_name = :name",
+      array(':table' => $table, ':name' => $constraint_name, ':type' => $type))->fetchField();
+    if (!$constraint_exists) {
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Check the foreign key constrain specified exists.
+   *
+   * @param $base_table
+   *   The name of the table the foreign key resides in. E.g. 'feature' for
+   *     the feature.type_id => cvterm.cvterm_id foreign key.
+   * @param $base_column
+   *   The name of the column that is a foreign key in. E.g. 'type_id' for
+   *     the feature.type_id => cvterm.cvterm_id foreign key.
+   *
+   * @return
+   *   TRUE if the constraint exists and false otherwise.
+   */
+  function checkFKConstraintExists($base_table, $base_column) {
+
+
+    // Since we don't have a constraint name, we have to use the known pattern for
+    // creating these names in order to make this check.
+    // This is due to PostgreSQL not storing column information for constraints
+    // in the information_schema tables.
+    $constraint_name = $base_table . '_' . $base_column . '_fkey';
+
+    return $this->checkConstraintExists($base_table, $constraint_name, 'FOREIGN KEY');
+  }
+
+  /**
+   * A Chado-aware replacement for the db_index_exists() function.
+   *
+   * @param $table
+   *   The table to be altered.
+   * @param $name
+   *   The name of the index.
+   */
+  function checkIndexExists($table, $name) {
+    return chado_index_exists($table, $name);
+  }
+}

+ 27 - 3
tripal_chado/api/tripal_chado.schema_v1.2.api.inc

@@ -2820,6 +2820,7 @@ function tripal_chado_chado_schema_v1_2_control() {
     'primary key' => array(
       0 => 'control_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'control_idx1' => array(
         0 => 'type_id',
@@ -3858,6 +3859,7 @@ function tripal_chado_chado_schema_v1_2_eimage() {
     'primary key' => array(
       0 => 'eimage_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
     ),
     'table' => 'eimage',
@@ -6559,6 +6561,7 @@ function tripal_chado_chado_schema_v1_2_featuremap_pub() {
     'primary key' => array(
       0 => 'featuremap_pub_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'featuremap_pub_idx1' => array(
         0 => 'featuremap_id',
@@ -6633,6 +6636,7 @@ function tripal_chado_chado_schema_v1_2_featurepos() {
     'primary key' => array(
       0 => 'featurepos_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'featurepos_idx1' => array(
         0 => 'featuremap_id',
@@ -6878,6 +6882,7 @@ function tripal_chado_chado_schema_v1_2_featurerange() {
     'primary key' => array(
       0 => 'featurerange_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'featurerange_idx1' => array(
         0 => 'featuremap_id',
@@ -7789,6 +7794,7 @@ function tripal_chado_chado_schema_v1_2_magedocumentation() {
     'primary key' => array(
       0 => 'magedocumentation_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'magedocumentation_idx1' => array(
         0 => 'mageml_id',
@@ -7855,6 +7861,7 @@ function tripal_chado_chado_schema_v1_2_mageml() {
     'primary key' => array(
       0 => 'mageml_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
     ),
     'table' => 'mageml',
@@ -7900,6 +7907,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment() {
     'primary key' => array(
       0 => 'nd_experiment_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'cvterm' => array(
         'table' => 'cvterm',
@@ -7965,6 +7973,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment_contact() {
     'primary key' => array(
       0 => 'nd_experiment_contact_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'contact' => array(
         'table' => 'contact',
@@ -8020,6 +8029,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment_dbxref() {
     'primary key' => array(
       0 => 'nd_experiment_dbxref_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'dbxref' => array(
         'table' => 'dbxref',
@@ -8197,6 +8207,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment_project() {
     'primary key' => array(
       0 => 'nd_experiment_project_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'project' => array(
         'table' => 'project',
@@ -8252,6 +8263,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment_protocol() {
     'primary key' => array(
       0 => 'nd_experiment_protocol_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'nd_experiment' => array(
         'table' => 'nd_experiment',
@@ -8381,6 +8393,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment_stock() {
     'primary key' => array(
       0 => 'nd_experiment_stock_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'cvterm' => array(
         'table' => 'cvterm',
@@ -8445,6 +8458,7 @@ function tripal_chado_chado_schema_v1_2_nd_experiment_stock_dbxref() {
     'primary key' => array(
       0 => 'nd_experiment_stock_dbxref_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'dbxref' => array(
         'table' => 'dbxref',
@@ -8663,8 +8677,8 @@ function tripal_chado_chado_schema_v1_2_nd_geolocation() {
     'primary key' => array(
       0 => 'nd_geolocation_id',
     ),
-    'foreign keys' => array(
-    ),
+    'unique keys' => array(),
+    'foreign keys' => array(),
     'table' => 'nd_geolocation',
     'referring_tables' => array(
       0 => 'nd_experiment',
@@ -8846,6 +8860,7 @@ function tripal_chado_chado_schema_v1_2_nd_protocol_reagent() {
     'primary key' => array(
       0 => 'nd_protocol_reagent_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'cvterm' => array(
         'table' => 'cvterm',
@@ -8986,6 +9001,7 @@ function tripal_chado_chado_schema_v1_2_nd_reagent() {
     'primary key' => array(
       0 => 'nd_reagent_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'cvterm' => array(
         'table' => 'cvterm',
@@ -9044,6 +9060,7 @@ function tripal_chado_chado_schema_v1_2_nd_reagent_relationship() {
     'primary key' => array(
       0 => 'nd_reagent_relationship_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'cvterm' => array(
         'table' => 'cvterm',
@@ -10513,6 +10530,7 @@ function tripal_chado_chado_schema_v1_2_phylotree() {
     'primary key' => array(
       0 => 'phylotree_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'phylotree_idx1' => array(
         0 => 'phylotree_id',
@@ -11123,6 +11141,7 @@ function tripal_chado_chado_schema_v1_2_protocolparam() {
     'primary key' => array(
       0 => 'protocolparam_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'protocolparam_idx1' => array(
         0 => 'protocol_id',
@@ -12645,6 +12664,7 @@ function tripal_chado_chado_schema_v1_2_stock_relationship_cvterm() {
     'primary key' => array(
       0 => 'stock_relationship_cvterm_id',
     ),
+    'unique keys' => array(),
     'foreign keys' => array(
       'cvterm' => array(
         'table' => 'cvterm',
@@ -13331,6 +13351,7 @@ function tripal_chado_chado_schema_v1_2_studydesign() {
     'primary key' => array(
       0 => 'studydesign_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'studydesign_idx1' => array(
         0 => 'study_id',
@@ -13479,6 +13500,7 @@ function tripal_chado_chado_schema_v1_2_studyfactor() {
     'primary key' => array(
       0 => 'studyfactor_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'studyfactor_idx1' => array(
         0 => 'studydesign_id',
@@ -13560,6 +13582,7 @@ function tripal_chado_chado_schema_v1_2_studyfactorvalue() {
     'primary key' => array(
       0 => 'studyfactorvalue_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'studyfactorvalue_idx1' => array(
         0 => 'studyfactor_id',
@@ -13954,6 +13977,7 @@ function tripal_chado_chado_schema_v1_2_treatment() {
     'primary key' => array(
       0 => 'treatment_id',
     ),
+    'unique keys' => array(),
     'indexes' => array(
       'treatment_idx1' => array(
         0 => 'biomaterial_id',
@@ -14179,4 +14203,4 @@ function tripal_chado_chado_get_v1_2_tables() {
     'treatment'
    );
    return $tables;
-}
+}

+ 19 - 17
tripal_chado/api/tripal_chado.schema_v1.3.api.inc

@@ -4118,7 +4118,7 @@ function tripal_chado_chado_schema_v1_3_cvtermprop() {
       ),
     ),
     'unique keys' => array(
-      'cvterm_id_type_id_value_rank' => array(
+      'cvtermprop_cvterm_id_type_id_value_rank_key' => array(
         0 => 'cvterm_id',
         1 => 'type_id',
         2 => 'value',
@@ -7019,13 +7019,14 @@ function tripal_chado_chado_schema_v1_3_featuremap_dbxref() {
         'default' => 'true',
       ),
     ),
-    // TODO: this unique constraint is missing from the actual Chado schema.
-    // It should be included.
     'unique keys' => array(
+      /* @todo: this unique constraint is missing from the actual Chado schema.
+          It should be included.
       'feature_dbxref_c1' => array(
         0 => 'featuremap_id',
         1 => 'dbxref_id',
       ),
+      */
     ),
     'indexes' => array(
       'featuremap_dbxref_idx1' => array(
@@ -7252,13 +7253,14 @@ function tripal_chado_chado_schema_v1_3_featuremap_pub() {
         'not null' => TRUE,
       ),
     ),
-    // TODO: this unique constraint is missing from the actual Chado schema.
-    // It should be included.
     'unique keys' => array(
+      /* @todo: this unique constraint is missing from the actual Chado schema.
+          It should be included.
       'feature_pub_c1' => array(
         0 => 'featuremap_id',
         1 => 'pub_id',
       ),
+      */
     ),
     'indexes' => array(
       'featuremap_pub_idx1' => array(
@@ -9936,7 +9938,7 @@ function tripal_chado_chado_schema_v1_3_materialized_view() {
       ),
     ),
     'unique keys' => array(
-      'name' => array(
+      'materialized_view_name_key' => array(
         0 => 'name',
       ),
     ),
@@ -11123,7 +11125,7 @@ function tripal_chado_chado_schema_v1_3_nd_protocol() {
       ),
     ),
     'unique keys' => array(
-      'name' => array(
+      'nd_protocol_name_key' => array(
         0 => 'name',
       ),
     ),
@@ -13094,11 +13096,11 @@ function tripal_chado_chado_schema_v1_3_phylonode() {
       ),
     ),
     'unique keys' => array(
-      'phylotree_id_left_idx' => array(
+      'phylonode_phylotree_id_left_idx_key' => array(
         0 => 'phylotree_id',
         1 => 'left_idx',
       ),
-      'phylotree_id_right_idx' => array(
+      'phylonode_phylotree_id_right_idx_key' => array(
         0 => 'phylotree_id',
         1 => 'right_idx',
       ),
@@ -13189,7 +13191,7 @@ function tripal_chado_chado_schema_v1_3_phylonode_dbxref() {
       ),
     ),
     'unique keys' => array(
-      'phylonode_id_dbxref_id' => array(
+      'phylonode_dbxref_phylonode_id_dbxref_id_key' => array(
         0 => 'phylonode_id',
         1 => 'dbxref_id',
       ),
@@ -13262,7 +13264,7 @@ function tripal_chado_chado_schema_v1_3_phylonode_organism() {
       ),
     ),
     'unique keys' => array(
-      'phylonode_id' => array(
+      'phylonode_organism_phylonode_id_key' => array(
         0 => 'phylonode_id',
       ),
     ),
@@ -13346,7 +13348,7 @@ function tripal_chado_chado_schema_v1_3_phylonodeprop() {
       ),
     ),
     'unique keys' => array(
-      'phylonode_id_type_id_value_rank' => array(
+      'phylonodeprop_phylonode_id_type_id_value_rank_key' => array(
         0 => 'phylonode_id',
         1 => 'type_id',
         2 => 'value',
@@ -13421,7 +13423,7 @@ function tripal_chado_chado_schema_v1_3_phylonode_pub() {
       ),
     ),
     'unique keys' => array(
-      'phylonode_id_pub_id' => array(
+      'phylonode_pub_phylonode_id_pub_id_key' => array(
         0 => 'phylonode_id',
         1 => 'pub_id',
       ),
@@ -13515,7 +13517,7 @@ function tripal_chado_chado_schema_v1_3_phylonode_relationship() {
       0 => 'phylonode_relationship_id',
     ),
     'unique keys' => array(
-      'subject_id_object_id_type_id' => array(
+      'phylonode_relationship_subject_id_object_id_type_id_key' => array(
         0 => 'subject_id',
         1 => 'object_id',
         2 => 'type_id',
@@ -13768,7 +13770,7 @@ function tripal_chado_chado_schema_v1_3_phylotree_pub() {
       ),
     ),
     'unique keys' => array(
-      'phylotree_id_pub_id' => array(
+      'phylotree_pub_phylotree_id_pub_id_key' => array(
         0 => 'phylotree_id',
         1 => 'pub_id',
       ),
@@ -17676,7 +17678,7 @@ function tripal_chado_chado_schema_v1_3_studyprop() {
       0 => 'studyprop_id',
     ),
     'unique keys' => array(
-      'study_id_type_id_rank' => array(
+      'studyprop_study_id_type_id_rank_key' => array(
         0 => 'study_id',
         1 => 'type_id',
         2 => 'rank',
@@ -17756,7 +17758,7 @@ function tripal_chado_chado_schema_v1_3_studyprop_feature() {
       0 => 'studyprop_feature_id',
     ),
     'unique keys' => array(
-      'studyprop_id_feature_id' => array(
+      'studyprop_feature_studyprop_id_feature_id_key' => array(
         0 => 'studyprop_id',
         1 => 'feature_id',
       ),