123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652 |
- <?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 = [];
- 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, [':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 = [
- '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 = [];
- 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.',
- ['!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.',
- ['!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,
- [
- ':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);',
- [':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.',
- ['!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'",
- [':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",
- [
- ':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);
- }
- }
|