ChadoSchema.inc 15 KB

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