ChadoSchema.inc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. <?php
  2. /**
  3. * Provides an application programming interface (API) for describing Chado tables.
  4. *
  5. * If you need the Drupal-style array definition for any table, use the following:
  6. * @code
  7. $chado_schema = new \ChadoSchema();
  8. $table_schema = $chado_schema->getTableSchema($table_name);
  9. * @endcode
  10. *
  11. * where the variable $table contains the name of the table you want to
  12. * retireve. The getTableSchema method determines the appropriate version of
  13. * Chado and uses the Drupal hook infrastructure to call the appropriate
  14. * hook function to retrieve the table schema.
  15. *
  16. * Additionally, here are some other examples of how to use this class:
  17. * @code
  18. // Retrieve the schema array for the organism table in chado 1.2
  19. $chado_schema = new \ChadoSchema('1.2');
  20. $table_schema = $chado_schema->getTableSchema('organism');
  21. // Retrieve all chado tables.
  22. $chado_schema = new \ChadoSchema();
  23. $tables = $chado_schema->getTableNames();
  24. $base_tables = $chado_schema->getbaseTables();
  25. // Check the feature.type_id foreign key constraint
  26. $chado_schema = new \ChadoSchema();
  27. $exists = $chado_schema ->checkFKConstraintExists('feature','type_id');
  28. // Check Sequence exists
  29. $chado_schema = new \ChadoSchema();
  30. $exists = $chado_schema->checkSequenceExists('organism','organism_id');
  31. // Or just check the primary key directly
  32. $compliant = $chado_schema->checkPrimaryKey('organism');
  33. * @endcode
  34. */
  35. class ChadoSchema {
  36. /**
  37. * @var string
  38. * The current version for this site. E.g. "1.3".
  39. */
  40. protected $version = '';
  41. /**
  42. * @var string
  43. * The name of the schema chado was installed in.
  44. */
  45. protected $schema_name = 'chado';
  46. /**
  47. * The ChadoSchema constructor.
  48. *
  49. * @param string $version
  50. * The current version for this site. E.g. "1.3". If a version is not provided, the
  51. * version of the current database will be looked up.
  52. */
  53. public function __construct($version = NULL, $schema_name = NULL) {
  54. // Set the version of the schema.
  55. if ($version === NULL) {
  56. $this->version = chado_get_version(TRUE);
  57. }
  58. else {
  59. $this->version = $version;
  60. }
  61. // Set the name of the schema.
  62. if ($schema_name === NULL) {
  63. $this->schema_name = chado_get_schema_name('chado');
  64. }
  65. else {
  66. $this->schema_name = $schema_name;
  67. }
  68. // Check functions require the chado schema be local and installed...
  69. // So lets check that now...
  70. if (!chado_is_local()) {
  71. tripal_report_error(
  72. 'ChadoSchema',
  73. TRIPAL_NOTICE,
  74. 'The ChadoSchema class requires chado be installed within the drupal database
  75. in a separate schema for any compliance checking functionality.'
  76. );
  77. }
  78. if (!chado_is_installed()) {
  79. tripal_report_error(
  80. 'ChadoSchema',
  81. TRIPAL_NOTICE,
  82. 'The ChadoSchema class requires chado be installed
  83. for any compliance checking functionality.'
  84. );
  85. }
  86. }
  87. /**
  88. * Returns the version number of the Chado this object references.
  89. *
  90. * @returns
  91. * The version of Chado
  92. */
  93. public function getVersion() {
  94. return $this->version;
  95. }
  96. /**
  97. * Retrieve the name of the PostgreSQL schema housing Chado.
  98. *
  99. * @return
  100. * The name of the schema.
  101. */
  102. public function getSchemaName() {
  103. return $this->schema_name;
  104. }
  105. /**
  106. * Retrieves the list of tables in the Chado schema. By default it only returns
  107. * the default Chado tables, but can return custom tables added to the
  108. * Chado schema if requested.
  109. *
  110. * @param $include_custom
  111. * Optional. Set as TRUE to include any custom tables created in the
  112. * Chado schema. Custom tables are added to Chado using the
  113. * tripal_chado_chado_create_table() function.
  114. *
  115. * @returns
  116. * An associative array where the key and value pairs are the Chado table names.
  117. */
  118. public function getTableNames($include_custom = FALSE) {
  119. $tables = array();
  120. if ($this->version == '1.3') {
  121. $tables_v1_3 = tripal_chado_chado_get_v1_3_tables();
  122. foreach ($tables_v1_3 as $table) {
  123. $tables[$table] = $table;
  124. }
  125. }
  126. if ($this->version == '1.2') {
  127. $tables_v1_2 = tripal_chado_chado_get_v1_2_tables();
  128. foreach ($tables_v1_2 as $table) {
  129. $tables[$table] = $table;
  130. }
  131. }
  132. if ($this->version == '1.11' or $this->version == '1.11 or older') {
  133. $tables_v1_11 = tripal_chado_chado_get_v1_11_tables();
  134. foreach ($tables_v1_11 as $table) {
  135. $tables[$table] = $table;
  136. }
  137. }
  138. // now add in the custom tables too if requested
  139. if ($include_custom) {
  140. $sql = "SELECT table FROM {tripal_custom_tables}";
  141. $resource = db_query($sql);
  142. foreach ($resource as $r) {
  143. $tables[$r->table] = $r->table;
  144. }
  145. }
  146. asort($tables);
  147. return $tables;
  148. }
  149. /**
  150. * Retrieves the chado tables Schema API array.
  151. *
  152. * @param $table
  153. * The name of the table to retrieve. The function will use the appopriate
  154. * Tripal chado schema API hooks (e.g. v1.11 or v1.2).
  155. *
  156. * @returns
  157. * A Drupal Schema API array defining the table.
  158. */
  159. public function getTableSchema($table) {
  160. // first get the chado version.
  161. $v = $this->version;
  162. // get the table array from the proper chado schema
  163. $v = preg_replace("/\./", "_", $v); // reformat version for hook name
  164. // Call the module_invoke_all.
  165. $hook_name = "chado_schema_v" . $v . "_" . $table;
  166. $table_arr = module_invoke_all($hook_name);
  167. // If the module_invoke_all returned nothing then let's make sure there isn't
  168. // An API call we can call directly. The only time this occurs is
  169. // during an upgrade of a major Drupal version and tripal_core is disabled.
  170. if ((!$table_arr or !is_array($table_arr)) and
  171. function_exists('tripal_chado_' . $hook_name)) {
  172. $api_hook = "tripal_chado_" . $hook_name;
  173. $table_arr = $api_hook();
  174. }
  175. // if the table_arr is empty then maybe this is a custom table
  176. if (!is_array($table_arr) or count($table_arr) == 0) {
  177. $table_arr = $this->getCustomTableSchema($table);
  178. }
  179. return $table_arr;
  180. }
  181. /**
  182. * Retrieves the schema array for the specified custom table.
  183. *
  184. * @param $table
  185. * The name of the table to create.
  186. *
  187. * @return
  188. * A Drupal-style Schema API array definition of the table. Returns
  189. * FALSE on failure.
  190. */
  191. public function getCustomTableSchema($table) {
  192. $sql = "SELECT schema FROM {tripal_custom_tables} WHERE table_name = :table_name";
  193. $results = db_query($sql, array(':table_name' => $table));
  194. $custom = $results->fetchObject();
  195. if (!$custom) {
  196. return FALSE;
  197. }
  198. else {
  199. return unserialize($custom->schema);
  200. }
  201. }
  202. /**
  203. * Returns all chado base tables.
  204. *
  205. * Base tables are those that contain the primary record for a data type. For
  206. * example, feature, organism, stock, are all base tables. Other tables
  207. * include linker tables (which link two or more base tables), property tables,
  208. * and relationship tables. These provide additional information about
  209. * primary data records and are therefore not base tables. This function
  210. * retreives only the list of tables that are considered 'base' tables.
  211. *
  212. * @return
  213. * An array of base table names.
  214. *
  215. * @ingroup tripal_chado_schema_api
  216. */
  217. function getBaseTables() {
  218. // Initialize the base tables with those tables that are missing a type.
  219. // Ideally they should have a type, but that's for a future version of Chado.
  220. $base_tables = array('organism', 'project', 'analysis', 'biomaterial',
  221. 'eimage', 'assay');
  222. // We'll use the cvterm table to guide which tables are base tables. Typically
  223. // base tables (with a few exceptions) all have a type. Iterate through the
  224. // referring tables.
  225. $schema = $this->getTableSchema('cvterm');
  226. $referring = $schema['referring_tables'];
  227. foreach ($referring as $tablename) {
  228. // Ignore the cvterm tables, relationships, chadoprop tables.
  229. if ($tablename == 'cvterm_dbxref' || $tablename == 'cvterm_relationship' ||
  230. $tablename == 'cvtermpath' || $tablename == 'cvtermprop' || $tablename == 'chadoprop' ||
  231. $tablename == 'cvtermsynonym' || preg_match('/_relationship$/', $tablename) ||
  232. preg_match('/_cvterm$/', $tablename) ||
  233. // Ignore prop tables
  234. preg_match('/prop$/', $tablename) || preg_match('/prop_.+$/', $tablename) ||
  235. // Ignore nd_tables
  236. preg_match('/^nd_/', $tablename)) {
  237. continue;
  238. }
  239. else {
  240. array_push($base_tables, $tablename);
  241. }
  242. }
  243. // Remove any linker tables that have snuck in. Linker tables are those
  244. // whose foreign key constraints link to two or more base table.
  245. $final_list = array();
  246. foreach ($base_tables as $i => $tablename) {
  247. // A few tables break our rule and seems to look
  248. // like a linking table, but we want to keep it as a base table.
  249. if ($tablename == 'biomaterial' or $tablename == 'assay' or $tablename == 'arraydesign') {
  250. $final_list[] = $tablename;
  251. continue;
  252. }
  253. // Remove the phenotype table. It really shouldn't be a base table as
  254. // it is meant to store individual phenotype measurements.
  255. if ($tablename == 'phenotype') {
  256. continue;
  257. }
  258. $num_links = 0;
  259. $schema = $this->getTableSchema($tablename);
  260. $fkeys = $schema['foreign keys'];
  261. foreach ($fkeys as $fkid => $details) {
  262. $fktable = $details['table'];
  263. if (in_array($fktable, $base_tables)) {
  264. $num_links++;
  265. }
  266. }
  267. if ($num_links < 2) {
  268. $final_list[] = $tablename;
  269. }
  270. }
  271. // Now add in the cvterm table to the list.
  272. $final_list[] = 'cvterm';
  273. // Sort the tables and return the list.
  274. sort($final_list);
  275. return $final_list;
  276. }
  277. /**
  278. * Get information about which Chado base table a cvterm is mapped to.
  279. *
  280. * Vocbulary terms that represent content types in Tripal must be mapped to
  281. * Chado tables. A cvterm can only be mapped to one base table in Chado.
  282. * This function will return an object that contains the chado table and
  283. * foreign key field to which the cvterm is mapped. The 'chado_table' property
  284. * of the returned object contains the name of the table, and the 'chado_field'
  285. * property contains the name of the foreign key field (e.g. type_id), and the
  286. * 'cvterm' property contains a cvterm object.
  287. *
  288. * @params
  289. * An associative array that contains the following keys:
  290. * - cvterm_id: the cvterm ID value for the term.
  291. * - vocabulary: the short name for the vocabulary (e.g. SO, GO, PATO)
  292. * - accession: the accession for the term.
  293. * - bundle_id: the ID for the bundle to which a term is associated.
  294. * The 'vocabulary' and 'accession' must be used together, the 'cvterm_id' can
  295. * be used on it's own.
  296. * @return
  297. * An object containing the chado_table and chado_field properties or NULL if
  298. * if no mapping was found for the term.
  299. */
  300. public function getCvtermMapping($params) {
  301. return chado_get_cvterm_mapping($params);
  302. }
  303. /**
  304. * Check that any given Chado table exists.
  305. *
  306. * This function is necessary because Drupal's db_table_exists() function will
  307. * not look in any other schema but the one where Drupal is installed
  308. *
  309. * @param $table
  310. * The name of the chado table whose existence should be checked.
  311. *
  312. * @return
  313. * TRUE if the table exists in the chado schema and FALSE if it does not.
  314. */
  315. public function checkTableExists($table) {
  316. return chado_table_exists($table);
  317. }
  318. /**
  319. * Check that any given column in a Chado table exists.
  320. *
  321. * This function is necessary because Drupal's db_field_exists() will not
  322. * look in any other schema but the one were Drupal is installed
  323. *
  324. * @param $table
  325. * The name of the chado table.
  326. * @param $column
  327. * The name of the column in the chado table.
  328. *
  329. * @return
  330. * TRUE if the column exists for the table in the chado schema and
  331. * FALSE if it does not.
  332. *
  333. * @ingroup tripal_chado_schema_api
  334. */
  335. public function checkColumnExists($table, $column) {
  336. return chado_column_exists($table, $column);
  337. }
  338. /**
  339. * Check that any given column in a Chado table exists.
  340. *
  341. * This function is necessary because Drupal's db_field_exists() will not
  342. * look in any other schema but the one were Drupal is installed
  343. *
  344. * @param $table
  345. * The name of the chado table.
  346. * @param $column
  347. * The name of the column in the chado table.
  348. * @param $type
  349. * (OPTIONAL) The PostgreSQL type to check for. If not supplied it will be
  350. * looked up via the schema (PREFERRED).
  351. *
  352. * @return
  353. * TRUE if the column type matches what we expect and
  354. * FALSE if it does not.
  355. *
  356. * @ingroup tripal_chado_schema_api
  357. */
  358. public function checkColumnType($table, $column, $expected_type = NULL) {
  359. // Ensure this column exists before moving forward.
  360. if (!$this->checkColumnExists($table, $column)) {
  361. tripal_report_error(
  362. 'ChadoSchema',
  363. TRIPAL_WARNING,
  364. 'Unable to check the type of !table!column since it doesn\'t appear to exist in your site database.',
  365. array('!column' => $column, '!table' => $table)
  366. );
  367. return FALSE;
  368. }
  369. // Look up the type using the Schema array.
  370. if ($expected_type === NULL) {
  371. $schema = $this->getTableSchema($table, $column);
  372. if (is_array($schema) AND isset($schema['fields'][$column])) {
  373. $expected_type = $schema['fields'][$column]['type'];
  374. }
  375. else {
  376. tripal_report_error(
  377. 'ChadoSchema',
  378. TRIPAL_WARNING,
  379. 'Unable to check the type of !table!column due to being unable to find the schema definition.',
  380. array('!column' => $column, '!table' => $table)
  381. );
  382. return FALSE;
  383. }
  384. }
  385. // There is some flexibility in the expected type...
  386. // Fix that here.
  387. switch ($expected_type) {
  388. case 'int':
  389. $expected_type = 'integer';
  390. break;
  391. case 'serial':
  392. $expected_type = 'integer';
  393. break;
  394. case 'varchar':
  395. $expected_type = 'character varying';
  396. break;
  397. case 'datetime':
  398. $expected_type = 'timestamp without time zone';
  399. break;
  400. case 'char':
  401. $expected_type = 'character';
  402. break;
  403. }
  404. // Grab the type from the current database.
  405. $query = 'SELECT data_type
  406. FROM information_schema.columns
  407. WHERE
  408. table_name = :table AND
  409. column_name = :column AND
  410. table_schema = :schema
  411. ORDER BY ordinal_position
  412. LIMIT 1';
  413. $type = db_query($query,
  414. array(':table' => $table, ':column' => $column, ':schema' => $this->schema_name))->fetchField();
  415. // Finally we do the check!
  416. if ($type === $expected_type) {
  417. return TRUE;
  418. }
  419. elseif (($expected_type == 'float') AND (($type == 'double precision') OR ($type == 'real'))) {
  420. return TRUE;
  421. }
  422. elseif ($type == 'smallint' AND $expected_type == 'integer') {
  423. return TRUE;
  424. }
  425. elseif ($type == 'bigint' AND $expected_type == 'integer') {
  426. return TRUE;
  427. }
  428. else {
  429. return FALSE;
  430. }
  431. }
  432. /**
  433. * Check that any given sequence in a Chado table exists.
  434. *
  435. * @param table
  436. * The name of the table the sequence is used in.
  437. * @param column
  438. * The name of the column the sequence is used to populate.
  439. *
  440. * @return
  441. * TRUE if the seqeuence exists in the chado schema and FALSE if it does not.
  442. *
  443. * @ingroup tripal_chado_schema_api
  444. */
  445. public function checkSequenceExists($table, $column) {
  446. $prefixed_table = $this->schema_name.'.'.$table;
  447. $sequence_name = db_query('SELECT pg_get_serial_sequence(:table, :column);',
  448. array(':table' => $prefixed_table, ':column' => $column))->fetchField();
  449. // Remove prefixed table from sequence name
  450. $sequence_name = str_replace($this->schema_name.'.', '', $sequence_name);
  451. return chado_sequence_exists($sequence_name);
  452. }
  453. /**
  454. * Check that the primary key exists, has a sequence and a constraint.
  455. *
  456. * @param $table
  457. * The table you want to check the primary key for.
  458. * @param $column
  459. * (OPTIONAL) The name of the primary key column.
  460. *
  461. * @return
  462. * TRUE if the primary key meets all the requirements and false otherwise.
  463. */
  464. public function checkPrimaryKey($table, $column = NULL) {
  465. // If they didn't supply the column, then we can look it up.
  466. if ($column === NULL) {
  467. $table_schema = $this->getTableSchema($table);
  468. $column = $table_schema['primary key'][0];
  469. }
  470. // If there is no primary key then we can't check it.
  471. // It neither passes nore fails validation.
  472. if (empty($column)) {
  473. tripal_report_error(
  474. 'ChadoSchema',
  475. TRIPAL_NOTICE,
  476. 'Cannot check the validity of the primary key for "!table" since there is no record of one.',
  477. array('!table' => $table)
  478. );
  479. return NULL;
  480. }
  481. // Check the column exists.
  482. $column_exists = $this->checkColumnExists($table, $column);
  483. if (!$column_exists) {
  484. return FALSE;
  485. }
  486. // First check that the sequence exists.
  487. $sequence_exists = $this->checkSequenceExists($table, $column);
  488. if (!$sequence_exists) {
  489. return FALSE;
  490. }
  491. // Next check the constraint is there.
  492. $constraint_exists = chado_query(
  493. "SELECT 1
  494. FROM information_schema.table_constraints
  495. WHERE table_name=:table AND constraint_type = 'PRIMARY KEY'",
  496. array(':table' => $table))->fetchField();
  497. if (!$constraint_exists) {
  498. return FALSE;
  499. }
  500. return TRUE;
  501. }
  502. /**
  503. * Check that the constraint exists.
  504. *
  505. * @param $table
  506. * The table the constraint applies to.
  507. * @param $constraint_name
  508. * The name of the constraint you want to check.
  509. * @param $type
  510. * The type of constraint. Should be one of "PRIMARY KEY", "UNIQUE", or "FOREIGN KEY".
  511. *
  512. * @return
  513. * TRUE if the constraint exists and false otherwise.
  514. */
  515. function checkConstraintExists($table, $constraint_name, $type) {
  516. // Next check the constraint is there.
  517. $constraint_exists = chado_query(
  518. "SELECT 1
  519. FROM information_schema.table_constraints
  520. WHERE table_name=:table AND constraint_type = :type AND constraint_name = :name",
  521. array(':table' => $table, ':name' => $constraint_name, ':type' => $type))->fetchField();
  522. if (!$constraint_exists) {
  523. return FALSE;
  524. }
  525. return TRUE;
  526. }
  527. /**
  528. * Check the foreign key constrain specified exists.
  529. *
  530. * @param $base_table
  531. * The name of the table the foreign key resides in. E.g. 'feature' for
  532. * the feature.type_id => cvterm.cvterm_id foreign key.
  533. * @param $base_column
  534. * The name of the column that is a foreign key in. E.g. 'type_id' for
  535. * the feature.type_id => cvterm.cvterm_id foreign key.
  536. *
  537. * @return
  538. * TRUE if the constraint exists and false otherwise.
  539. */
  540. function checkFKConstraintExists($base_table, $base_column) {
  541. // Since we don't have a constraint name, we have to use the known pattern for
  542. // creating these names in order to make this check.
  543. // This is due to PostgreSQL not storing column information for constraints
  544. // in the information_schema tables.
  545. $constraint_name = $base_table . '_' . $base_column . '_fkey';
  546. return $this->checkConstraintExists($base_table, $constraint_name, 'FOREIGN KEY');
  547. }
  548. /**
  549. * A Chado-aware replacement for the db_index_exists() function.
  550. *
  551. * @param $table
  552. * The table to be altered.
  553. * @param $name
  554. * The name of the index.
  555. */
  556. function checkIndexExists($table, $name) {
  557. return chado_index_exists($table, $name);
  558. }
  559. }