From 931e5668a646b3d6ab94118c7d91ed63de353a52 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sun, 8 Jan 2017 17:49:50 -0800 Subject: [PATCH 01/32] New DB structure --- qiita_db/support_files/patches/47.sql | 401 ++ qiita_db/support_files/qiita-db.dbs | 481 +- qiita_db/support_files/qiita-db.html | 6819 ++++++++++--------------- 3 files changed, 3309 insertions(+), 4392 deletions(-) create mode 100644 qiita_db/support_files/patches/47.sql diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql new file mode 100644 index 000000000..6617a1137 --- /dev/null +++ b/qiita_db/support_files/patches/47.sql @@ -0,0 +1,401 @@ +-- Jan 5, 2017 +-- Move the analysis to the plugin system. This is a major rewrite of the +-- database backend that supports the analysis pipeline. The code is commented +-- with details on the changes implemented here, but here is an overview of +-- the changes needed to make this transformation: +-- 1) Set new data structures to support the analysis as plugins +-- 2) Create a new type plugin to define the diversity types +-- 3) Create the new commands on the existing QIIME plugin to execute the +-- existing analyses (beta div, taxa summaries and alpha rarefaction) +-- 4) Transfer all the data in the old structures to the plugin structures +-- 5) Delete old structures + +-- Create the new data structures + +-- Table that links the analysis with the initial set of artifacts +CREATE TABLE qiita.analysis_artifact ( + analysis_id bigint NOT NULL, + artifact_id bigint NOT NULL, + CONSTRAINT idx_analysis_artifact_0 PRIMARY KEY (analysis_id, artifact_id) +); +CREATE INDEX idx_analysis_artifact_analysis ON qiita.analysis_artifact (analysis_id); +CREATE INDEX idx_analysis_artifact_artifact ON qiita.analysis_artifact (artifact_id); +ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_analysis FOREIGN KEY ( analysis_id ) REFERENCES qiita.analysis( analysis_id ); +ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_artifact FOREIGN KEY ( artifact_id ) REFERENCES qiita.artifact( artifact_id ); + +DO $do$ +DECLARE + divtype_id bigint; + validate_id bigint; + html_summary_id bigint; + dm_at_id bigint; + rc_at_id bigint; + ts_at_id bigint; + qiime_id bigint; + sum_taxa_id bigint; + bdiv_id bigint; + arare_id bigint; + srare_id bigint; + st_cp_id bigint; + bdiv_cp_id bigint; + arare_cp_id bigint; + srare_cp_id bigint; + biom_at_id bigint; + ts_co_id bigint; + dm_co_id bigint; + rc_co_id bigint; + sr_co_id bigint; + analysis RECORD; + biom_data RECORD; + job_data RECORD; + initial_biom_id bigint; + rarefaction_job_id UUID; + params json; + rarefied_biom_id bigint; + cmd_id bigint; + input_artifact_id bigint; + proc_job_id UUID; + output_artifact_type_id bigint; + output_artifact_id bigint; + cmd_out_id bigint; + log_id bigint; + output_filepath_id bigint; + tree_fp varchar; +BEGIN + -- The new commands that we are going to add generate new artifact types + -- these new artifact types are going to be added to a different plugin + -- In interest of time and given that the artifact type system is going to + -- change in the near future, we feel that the easiest way to transfer + -- the current analyses results is by creating 3 different types of + -- artifacts: (1) distance matrix -> which will include the distance matrix + -- the principal coordinates and the emperor plots; (2) rarefaction + -- curves -> which will include all the files generated by alpha rarefaction + -- and (3) taxonomy summary, which will include all the files generated + -- by summarize_taxa_through_plots.py + + -- Step 1: Create the new type plugin + INSERT INTO qiita.software (name, version, description, environment_script, start_script, software_type_id) + VALUES ('Diversity types', '0.1.0', 'Diversity artifacts type plugin', 'source activate qiita', 'start_diversity_types', 2) + RETURNING software_id INTO divtype_id; + + -- Step 2: Create the validate and HTML generator commands + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (divtype_id, 'Validate', 'Validates a new artifact of the given diversity type') + RETURNING command_id INTO validate_id; + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (divtype_id, 'Generate HTML summary', 'Generates the HTML summary of a given diversity type') + RETURNING command_id INTO html_summary_id; + + -- Step 3: Add the parameters for the previous commands + INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required) + VALUES (validate_id, 'template', 'prep_template', True), + (validate_id, 'files', 'string', True), + (validate_id, 'artifact_type', 'string', True), + (html_summary_id, 'input_data', 'artifact', True); + + -- Step 4: Add the new artifact types + INSERT INTO qiita.artifact_type (artifact_type, description, can_be_submitted_to_ebi, can_be_submitted_to_vamps) + VALUES ('distance_matrix', 'Distance matrix holding pairwise distance between samples', False, False) + RETURNING artifact_type_id INTO dm_at_id; + INSERT INTO qiita.artifact_type (artifact_type, description, can_be_submitted_to_ebi, can_be_submitted_to_vamps) + VALUES ('rarefaction_curves', 'Rarefaction curves', False, False) + RETURNING artifact_type_id INTO rc_at_id; + INSERT INTO qiita.artifact_type (artifact_type, description, can_be_submitted_to_ebi, can_be_submitted_to_vamps) + VALUES ('taxa_summary', 'Taxa summary plots', False, False) + RETURNING artifact_type_id INTO ts_at_id; + + -- Step 5: Associate each artifact with the filetypes that it accepts + -- At this time we are going to add them as directories, just as it is done + -- right now. We can make it fancier with the new type system. + -- Magic number 8: the filepath_type_id for the directory + INSERT INTO qiita.artifact_type_filepath_type (artifact_type_id, filepath_type_id, required) + VALUES (dm_at_id, 8, True), + (rc_at_id, 8, True), + (ts_at_id, 8, True); + + -- Step 6: Associate the plugin with the types that it defines + INSERT INTO qiita.software_artifact_type (software_id, artifact_type_id) + VALUES (divtype_id, dm_at_id), + (divtype_id, rc_at_id), + (divtype_id, ts_at_id); + + -- Step 7: Create the new entries for the data directory + INSERT INTO qiita.data_directory (data_type, mountpoint, subdirectory, active) + VALUES ('distance_matrix', 'distance_matrix', true, true), + ('rarefaction_curves', 'rarefaction_curves', true, true), + ('taxa_summary', 'taxa_summary', true, true); + + -- Create the new commands that execute the current analyses. In qiita, + -- the only commands that where available are Summarize Taxa, Beta + -- Diversity and Alpha Rarefaction. The system was executing rarefaction + -- by default, but it should be a different step in the analysis process + -- so we are going to create a command for it too. These commands are going + -- to be part of the QIIME plugin, so we are going to first retrieve the + -- id of the QIIME 1.9.1 plugin, which for sure exists cause it was added + -- in patch 33 and there is no way of removing plugins + + -- Step 1: Get the QIIME plugin id + SELECT software_id FROM qiita.software + WHERE name = 'QIIME' AND version = '1.9.1' + INTO qiime_id; + + -- Step 2: Insert the new commands in the software_command table + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (qiime_id, 'Summarize Taxa', 'Plots taxonomy summaries at different taxonomy levels') + RETURNING command_id INTO sum_taxa_id; + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (qiime_id, 'Beta Diversity', 'Computes and plots beta diversity results') + RETURNING command_id INTO bdiv_id; + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (qiime_id, 'Alpha Rarefaction', 'Computes and plots alpha rarefaction results') + RETURNING command_id INTO arare_id; + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (qiime_id, 'Single Rarefaction', 'Rarefies the input table by random sampling without replacement') + RETURNING command_id INTO srare_id; + + -- Step 3: Insert the parameters for each command + INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) + -- Summarize Taxa + VALUES (sum_taxa_id, 'metadata_category', 'string', False, ''), + (sum_taxa_id, 'sort', 'bool', False, 'False'), + -- Beta Diversity + (bdiv_id, 'tree', 'string', False, ''), + (bdiv_id, 'metrics', 'mchoice:["abund_jaccard","binary_chisq","binary_chord","binary_euclidean","binary_hamming","binary_jaccard","binary_lennon","binary_ochiai","binary_otu_gain","binary_pearson","binary_sorensen_dice","bray_curtis","bray_curtis_faith","bray_curtis_magurran","canberra","chisq","chord","euclidean","gower","hellinger","kulczynski","manhattan","morisita_horn","pearson","soergel","spearman_approx","specprof","unifrac","unifrac_g","unifrac_g_full_tree","unweighted_unifrac","unweighted_unifrac_full_tree","weighted_normalized_unifrac","weighted_unifrac"]', False, '["binary_jaccard","bray_curtis"]'), + -- Alpha rarefaction + (arare_id, 'tree', 'string', False, ''), + (arare_id, 'num_steps', 'integer', False, 10), + (arare_id, 'min_rare_depth', 'integer', False, 10), + (arare_id, 'max_rare_depth', 'integer', False, 'Default'), + -- Single rarefaction + (srare_id, 'depth', 'integer', True, NULL), + (srare_id, 'subsample_multinomial', 'bool', False, 'False'); + + INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) + VALUES (sum_taxa_id, 'biom_table', 'artifact', True, NULL) + RETURNING command_parameter_id INTO st_cp_id; + INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) + VALUES (bdiv_id, 'biom_table', 'artifact', True, NULL) + RETURNING command_parameter_id INTO bdiv_cp_id; + INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) + VALUES (arare_id, 'biom_table', 'artifact', True, NULL) + RETURNING command_parameter_id INTO arare_cp_id; + INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) + VALUES (srare_id, 'biom_table', 'artifact', True, NULL) + RETURNING command_parameter_id INTO srare_cp_id; + + -- Step 4: Connect the artifact parameters with the artifact types that + -- they accept + SELECT artifact_type_id INTO biom_at_id + FROM qiita.artifact_type + WHERE artifact_type = 'BIOM'; + INSERT INTO qiita.parameter_artifact_type (command_parameter_id, artifact_type_id) + VALUES (st_cp_id, biom_at_id), + (bdiv_cp_id, biom_at_id), + (arare_cp_id, biom_at_id), + (srare_cp_id, biom_at_id); + + -- Step 5: Add the outputs of the command. + INSERT INTO qiita.command_output (name, command_id, artifact_type_id) + VALUES ('taxa_summary', sum_taxa_id, ts_at_id) + RETURNING command_output_id INTO ts_co_id; + INSERT INTO qiita.command_output (name, command_id, artifact_type_id) + VALUES ('distance_matrix', bdiv_id, dm_at_id) + RETURNING command_output_id INTO dm_co_id; + INSERT INTO qiita.command_output (name, command_id, artifact_type_id) + VALUES ('rarefaction_curves', arare_id, rc_at_id) + RETURNING command_output_id INTO rc_co_id; + INSERT INTO qiita.command_output (name, command_id, artifact_type_id) + VALUES ('rarefied_table', srare_id, biom_at_id) + RETURNING command_output_id INTO sr_co_id; + + -- At this point we are ready to start transferring the data from the old + -- structures to the new structures. Overview of the procedure: + -- Step 1: Add initial set of artifacts up to rarefied table + -- Step 2: Transfer the "analisys jobs" to processing jobs and create + -- the analysis artifacts + -- Fun fact: after exploring the data on the database, we realized that + -- there are a lot of inconsistencies in the data. Unfortunately, this + -- makes the process of trasnferring the data from the old structure + -- to the new one a bit more challenging, as we will need to handle + -- different special cases. + + -- Special case 1: there are jobs in the database that do not contain + -- any information about the options used to process those parameters. + -- However, these jobs do not have any results and all are marked either + -- as queued or error, although no error log has been saved. Since these + -- jobs are mainly useleess, we are going to remove them from the system + DELETE FROM qiita.analysis_job + WHERE job_id IN (SELECT job_id FROM qiita.job WHERE options = '{}'); + DELETE FROM qiita.job WHERE options = '{}'; + + -- Special case 2: there are a fair amount of jobs (719 last time I + -- checked) that are not attached to any analysis. Not sure how this + -- can happen, but these orphan jobs can't be accessed from anywhere + -- in the interface. Remove them from the system. Note that we are + -- unlinking the files but we are not removing them from the filepath + -- table. We will do that on the patch 47.py using the + -- purge_filepaths function, as it will make sure that those files are + -- not used anywhere else + DELETE FROM qiita.job_results_filepath WHERE job_id IN ( + SELECT job_id FROM qiita.job J WHERE NOT EXISTS ( + SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id)); + DELETE FROM qiita.job J WHERE NOT EXISTS ( + SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id); + + -- Loop through all the analysis + FOR analysis IN + SELECT * FROM qiita.analysis + LOOP + + -- Step 1: Add the inital set of artifacts. An analysis starts with + -- a set of artifacts. The initial set of artifacts are biom tables + -- generated by subseting the BIOM tables from the studies. However, + -- the old strucures where not storing these tables, since the first + -- step that they did was rarefy the tables. In the new analysis + -- pipeline, that will not be the case. Thus, there are 3 substeps + -- to successfully add the inital set of artifacts: (1) create a + -- placeholder artifact for the non-rarefied OTU table; (2) create + -- a single rarefaction job that rarefies such table; and (3) create + -- the artifact resulting from the single rarefaction step + FOR biom_data IN + SELECT * + FROM qiita.analysis_filepath + JOIN qiita.filepath USING (filepath_id) + JOIN qiita.filepath_type USING (filepath_type_id) + WHERE analysis_id = analysis.analysis_id + AND filepath_type = 'biom' + LOOP + -- Step 1.1: Create the placeholder artifact + -- Step 1.1.a: Add the row in the artifact table + -- Magic number 4: Visibility -> sandbox + INSERT INTO qiita.artifact (generated_timestamp, command_id, data_type_id, command_parameters, visibility_id, artifact_type_id, submitted_to_vamps) + VALUES (analysis.timestamp, NULL, biom_data.data_type_id, NULL, 4, biom_at_id, False) + RETURNING artifact_id INTO initial_biom_id; + -- Step 1.1.b: Associate the artifact with the analysis + INSERT INTO qiita.analysis_artifact (analysis_id, artifact_id) + VALUES (analysis.analysis_id, initial_biom_id); + + -- Step 1.2: Create the single rarefaction job + -- Step 1.2.a: Add the row in the procesisng job table + -- depth 1000 -> the depth information is not stored in the database + -- We will update the correct value in patch 47.py, since we can + -- obtain this value from the BIOM tables + params := ('{"depth":1000,"subsample_multinomial":false,"biom_table":' || initial_biom_id::varchar || '}')::json; + -- magic number 3: status -> success + INSERT INTO qiita.processing_job (email, command_id, command_parameters, processing_job_status_id) + VALUES (analysis.email, srare_id, params, 3) + RETURNING processing_job_id INTO rarefaction_job_id; + -- Step 1.2.b: Link the job with the input artifact + INSERT INTO qiita.artifact_processing_job (artifact_id, processing_job_id) + VALUES (initial_biom_id, rarefaction_job_id); + + -- Step 1.3: Create the rarefied artifact + -- Step 1.3.a: Add the row in the artifact table + -- Magic number 4: Visibility -> sandbox + INSERT INTO qiita.artifact (generated_timestamp, command_id, data_type_id, command_parameters, visibility_id, artifact_type_id, submitted_to_vamps) + VALUES (analysis.timestamp, srare_id, biom_data.data_type_id, params, 4, biom_at_id, False) + RETURNING artifact_id INTO rarefied_biom_id; + -- Step 1.3.b: Link the artifact with its file + INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) + VALUES (rarefied_biom_id, biom_data.filepath_id); + -- Step 1.3.c: Link the artifact with its parent + INSERT INTO qiita.parent_artifact (artifact_id, parent_id) + VALUES (rarefied_biom_id, initial_biom_id); + -- Step 1.3.d: Link the artifact as the job output + INSERT INTO qiita.artifact_output_processing_job (artifact_id, processing_job_id, command_output_id) + VALUES (rarefied_biom_id, rarefaction_job_id, sr_co_id); + + -- Step 2: Transfer all the "analysis" jobs that used this biom + -- table as input to the new structure + FOR job_data IN + SELECT * + FROM qiita.job + WHERE reverse(split_part(reverse(options::json->>'--otu_table_fp'), '/', 1)) = biom_data.filepath + LOOP + -- Step 2.1: Define which command the current job executed and + -- which are the parameters of this job + IF job_data.command_id = 1 THEN + -- Taxa summaries + cmd_id := sum_taxa_id; + params := ('{"biom_table":'|| rarefied_biom_id::varchar ||',"metadata_category":"","sort":false}')::json; + output_artifact_type_id := ts_at_id; + cmd_out_id := ts_co_id; + ELSIF job_data.command_id = 2 THEN + -- Beta diversity + cmd_id := bdiv_id; + params := ('{"biom_table":'|| rarefied_biom_id::varchar ||',"tree":"'|| (job_data.options::json->'tree_fp')::varchar ||'","metrics":["unweighted_unifrac","weighted_unifrac"]}')::json; + output_artifact_type_id := dm_at_id; + cmd_out_id := dm_co_id; + ELSE + -- Alpha rarefaction + cmd_id := arare_id; + params := ('{"biom_table":'|| rarefied_biom_id::varchar ||',"tree":"'|| (job_data.options::json->'tree_fp')::varchar ||'","num_steps":"10","min_rare_depth":"10","max_rare_depth":"Default"}')::json; + output_artifact_type_id := rc_at_id; + cmd_out_id := rc_co_id; + END IF; + + -- Step 2.2: Create the job + -- Step 2.2.a: Add the row in the processing job table + -- Magic number 3: status -> success + INSERT INTO qiita.processing_job (email, command_id, command_parameters, processing_job_status_id) + VALUES (analysis.email, cmd_id, params, 3) + RETURNING processing_job_id INTO proc_job_id; + -- Step 2.2.b: Link the job with the input artifact + INSERT INTO qiita.artifact_processing_job (artifact_id, processing_job_id) + VALUES (rarefied_biom_id, proc_job_id); + + + -- Step 2.3: Check if the executed job has results and add them + IF EXISTS(SELECT * FROM qiita.job_results_filepath WHERE job_id = job_data.job_id) THEN + -- There are results for the current job + -- Step 2.3.a: Add the row in the artifact table + -- Magic number 4: Visibility -> sandbox + INSERT INTO qiita.artifact (generated_timestamp, command_id, data_type_id, command_parameters, visibility_id, artifact_type_id, submitted_to_vamps) + VALUES (analysis.timestamp, cmd_id, biom_data.data_type_id, params, 4, output_artifact_type_id, False) + RETURNING artifact_id INTO output_artifact_id; + -- Step 2.3.b: Link the artifact with its file + SELECT filepath_id INTO output_filepath_id FROM qiita.job_results_filepath WHERE job_id = job_data.job_id; + INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) + VALUES (output_artifact_id, output_filepath_id); + -- Step 2.3.c: Link the artifact with its parent + INSERT INTO qiita.parent_artifact (artifact_id, parent_id) + VALUES (output_artifact_id, rarefied_biom_id); + -- Step 2.3.d: Link the artifact as the job output + INSERT INTO qiita.artifact_output_processing_job (artifact_id, processing_job_id, command_output_id) + VALUES (output_artifact_id, proc_job_id, cmd_out_id); + ELSE + -- There are no results on the current job, so mark it as + -- error + IF job_data.log_id IS NULL THEN + -- Magic number 2 - we are not using any other severity + -- level, so keep using number 2 + INSERT INTO qiita.logging (time, severity_id, msg) + VALUES (analysis.timestamp, 2, "Unknown error - patch 47") + RETURNING logging_id into log_id; + ELSE + log_id := job_data.log_id; + END IF; + + UPDATE qiita.processing_job + SET processing_job_status_id = 4, logging_id = log_id + WHERE processing_job_id = proc_job_id; + END IF; + END LOOP; + END LOOP; + END LOOP; +END $do$; + +-- Delete old structures that are not used anymore +DROP TABLE qiita.collection_job; +DROP TABLE qiita.collection_analysis; +DROP TABLE qiita.collection_users; +DROP TABLE qiita.collection; +DROP TABLE qiita.collection_status; +DROP TABLE qiita.analysis_workflow; +DROP TABLE qiita.analysis_chain; +DROP TABLE qiita.analysis_job; +DROP TABLE qiita.job_results_filepath; +DROP TABLE qiita.job; +DROP TABLE qiita.job_status; +DROP TABLE qiita.command_data_type; +DROP TABLE qiita.command; diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index 889d80d87..7231549f7 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -47,25 +47,24 @@ - - Keeps track of the chain of analysis edits. Tracks what previous analysis a given analysis came from.If a given analysis is not in child_id, it is the root of the chain. - - - - +
+ + + + - - + + - - - + + + - - + + - - + +
@@ -96,31 +95,6 @@
- - Holds information for a one-to-many relation of analysis to the jobs in it - - - - - - - - - - - - - - - - - - - - - - -
Controls what analyses are visible on what portals @@ -197,17 +171,6 @@
- - Stores what step in_production analyses are on. - - - - - - - - -
Represents data in the system @@ -367,101 +330,6 @@
- - Tracks a group of analyses and important jobs for an overarching goal. - - - - - - 1 - - - - - - - - - - - - - - - - -
- - Matches collection to analyses as one to many. - - - - - - - - - - - - - - - - - - -
- - Matches collection important jobs as one to many. - - - - - - - - - - - - - - - - - - -
- - - - - - -
- - Allows sharing of a collection - - - - - - - - - - - - - - - - - - -
Table relates a column with a controlled vocabulary. @@ -500,51 +368,6 @@
- - Available commands for jobs - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
@@ -859,96 +682,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - Holds connection between jobs and the result filepaths - - - - - - - - - - - - - - - - - - -
- - - - - - - - - -
@@ -1841,113 +1574,94 @@ Controlled Vocabulary]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + analysis tables - - - - - - - - + @@ -1986,13 +1700,6 @@ Controlled Vocabulary]]> - - - - - - - diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index 5ec6aeb0c..6c8e29959 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -45,13 +45,13 @@ } .bordered th { - background-color: #d7d7ea; - background-image: -webkit-gradient(linear, left top, left bottom, from(#f4f4fb), to(#d7d7ea)); - background-image: -webkit-linear-gradient(top, #f4f4fb, #d7d7ea); - background-image: -moz-linear-gradient(top, #f4f4fb, #d7d7ea); - background-image: -ms-linear-gradient(top, #f4f4fb, #d7d7ea); - background-image: -o-linear-gradient(top, #f4f4fb, #d7d7ea); - background-image: linear-gradient(top, #f4f4fb, #d7d7ea); + background-color: #dce9f9; + background-image: -webkit-gradient(linear, left top, left bottom, from(#ebf3fc), to(#dce9f9)); + background-image: -webkit-linear-gradient(top, #ebf3fc, #dce9f9); + background-image: -moz-linear-gradient(top, #ebf3fc, #dce9f9); + background-image: -ms-linear-gradient(top, #ebf3fc, #dce9f9); + background-image: -o-linear-gradient(top, #ebf3fc, #dce9f9); + background-image: linear-gradient(top, #ebf3fc, #dce9f9); -webkit-box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; -moz-box-shadow:0 1px 0 rgba(255,255,255,.8) inset; box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; @@ -97,27 +97,26 @@ - + - - - - - - - - + + + + + + + @@ -150,23 +149,26 @@ - + - - - - - - - + - + + + + + + + @@ -181,16 +183,14 @@ - - + + - - + - - + @@ -242,1309 +242,971 @@ d='M 0,0 3,3 M 0,3 3,0 z'/> - + - + - - + + - - Project qiita @ DbSchema + + Project qiita Layout qiita - Hint: Schema comments available as mouse-over tooltips. + Hint: Read the column comments as mouse-over tooltips. + Generated using DbSchema - -Group_analyses - + +Group_analyses + - -Group_users - + +Group_users + - -Group_study - + +Group_study + - -Group_vocabularies - + +Group_vocabularies + - -Group_ontology - + +Group_ontology + - -Group_logging - + +Group_logging + - -Group_filepaths - - - - -Group_collection - + +Group_filepaths + - -Group_messages - + +Group_messages + - -Group_artifact - + +Group_artifact + - -Group_template - + +Group_template + - -Group_processing - + +Group_processing + - -Group_publication - + +Group_publication + - -Group_oauth2 - + +Group_oauth2 + - + Foreign Key fk_controlled_vocab_values controlled_vocab_values references controlled_vocab ( controlled_vocab_id ) -controlled_vocab_id - Foreign Key fk_job_results_filepath - job_results_filepath references job ( job_id ) - -job_id - Foreign Key fk_analysis_job_analysis - analysis_job references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_job_job - analysis_job references job ( job_id ) - -job_id +controlled_vocab_id Foreign Key fk_column_controlled_vocabularies column_controlled_vocabularies references mixs_field_description ( column_name ) -column_name +column_name Foreign Key fk_column_controlled_vocab2 column_controlled_vocabularies references controlled_vocab ( controlled_vocab_id ) -controlled_vocab_id - Foreign Key fk_analysis_users_analysis - analysis_users references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_users_user - analysis_users references qiita_user ( email ) - -email +controlled_vocab_id Foreign Key fk_logging_severity logging references severity ( severity_id ) -severity_id - Foreign Key fk_command_data_type - command_data_type references command ( command_id ) - -command_id - Foreign Key fk_command_data_type_0 - command_data_type references data_type ( data_type_id ) - -data_type_id - Foreign Key fk_analysis_filepath - analysis_filepath references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_filepath_0 - analysis_filepath references filepath ( filepath_id ) - -filepath_id - Foreign Key fk_analysis_filepath_1 - analysis_filepath references data_type ( data_type_id ) - -data_type_id - Foreign Key fk_analysis_workflow - analysis_workflow references analysis ( analysis_id ) - -analysis_id +severity_id Foreign Key fk_column_ontology column_ontology references mixs_field_description ( column_name ) -column_name +column_name Foreign Key fk_user_user_level qiita_user references user_level ( user_level_id ) -user_level_id +user_level_id Foreign Key fk_filepath filepath references filepath_type ( filepath_type_id ) -filepath_type_id +filepath_type_id Foreign Key fk_filepath_0 filepath references checksum_algorithm ( checksum_algorithm_id ) -checksum_algorithm_id +checksum_algorithm_id Foreign Key fk_filepath_data_directory filepath references data_directory ( data_directory_id ) -data_directory_id +data_directory_id Foreign Key fk_term_ontology term references ontology ( ontology_id ) -ontology_id - Foreign Key fk_collection_job - collection_job references collection ( collection_id ) - -collection_id - Foreign Key fk_collection_job_0 - collection_job references job ( job_id ) - -job_id - Foreign Key fk_collection_analysis - collection_analysis references collection ( collection_id ) - -collection_id - Foreign Key fk_collection_analysis_0 - collection_analysis references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_collection - collection references qiita_user ( email ) - -email - Foreign Key fk_collection_0 - collection references collection_status ( collection_status_id ) - -collection_status_id - Foreign Key fk_collection_user - collection_users references collection ( collection_id ) - -collection_id - Foreign Key fk_collection_user_email - collection_users references qiita_user ( email ) - -email +ontology_id Foreign Key fk_study_prep_template_study study_prep_template references study ( study_id ) -study_id +study_id Foreign Key fk_study_prep_template_pt study_prep_template references prep_template ( prep_template_id ) -prep_template_id +prep_template_id Foreign Key fk_prep_template prep_template_sample references prep_template ( prep_template_id ) -prep_template_id - Foreign Key fk_analysis_portal - analysis_portal references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_portal_0 - analysis_portal references portal_type ( portal_type_id ) - -portal_type_id +prep_template_id Foreign Key fk_message_user message_user references message ( message_id ) -message_id +message_id Foreign Key fk_message_user_0 message_user references qiita_user ( email ) -email +email Foreign Key fk_filepath_id prep_template_filepath references filepath ( filepath_id ) -filepath_id +filepath_id Foreign Key fk_prep_template_id prep_template_filepath references prep_template ( prep_template_id ) -prep_template_id +prep_template_id Foreign Key fk_study_id sample_template_filepath references study ( study_id ) -study_id +study_id Foreign Key fk_filepath_id sample_template_filepath references filepath ( filepath_id ) -filepath_id +filepath_id Foreign Key fk_prep_template_data_type prep_template references data_type ( data_type_id ) -data_type_id +data_type_id Foreign Key fk_prep_template_artifact prep_template references artifact ( artifact_id ) -artifact_id - Foreign Key fk_analysis_sample_analysis - analysis_sample references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_sample - analysis_sample references study_sample ( sample_id ) - -sample_id - Foreign Key fk_analysis_sample_artifact - analysis_sample references artifact ( artifact_id ) - -artifact_id +artifact_id Foreign Key fk_parent_artifact_artifact parent_artifact references artifact ( artifact_id ) -artifact_id +artifact_id Foreign Key fk_parent_artifact_parent parent_artifact references artifact ( parent_id -> artifact_id ) -parent_id +parent_id Foreign Key fk_artifact_filepath_artifact artifact_filepath references artifact ( artifact_id ) -artifact_id +artifact_id Foreign Key fk_artifact_filepath_filepath artifact_filepath references filepath ( filepath_id ) -filepath_id +filepath_id Foreign Key fk_study_users_study study_users references study ( study_id ) -study_id +study_id Foreign Key fk_study_users_user study_users references qiita_user ( email ) -email +email Foreign Key fk_study_user study references qiita_user ( email ) -email +email Foreign Key fk_study_study_emp_person study references study_person ( emp_person_id -> study_person_id ) -emp_person_id +emp_person_id Foreign Key fk_study_study_lab_person study references study_person ( lab_person_id -> study_person_id ) -lab_person_id +lab_person_id Foreign Key fk_study_study_pi_person study references study_person ( principal_investigator_id -> study_person_id ) -principal_investigator_id +principal_investigator_id Foreign Key fk_study_timeseries_type study references timeseries_type ( timeseries_type_id ) -timeseries_type_id +timeseries_type_id Foreign Key fk_study_experimental_factor study_experimental_factor references study ( study_id ) -study_id +study_id Foreign Key fk_study_environmental_package study_environmental_package references study ( study_id ) -study_id +study_id Foreign Key fk_study_environmental_package_0 study_environmental_package references environmental_package ( environmental_package_name ) -environmental_package_name +environmental_package_name Foreign Key fk_investigation_study investigation_study references investigation ( investigation_id ) -investigation_id +investigation_id Foreign Key fk_investigation_study_study investigation_study references study ( study_id ) -study_id +study_id Foreign Key fk_investigation_study_person investigation references study_person ( contact_person_id -> study_person_id ) -contact_person_id +contact_person_id Foreign Key fk_study_portal study_portal references study ( study_id ) -study_id +study_id Foreign Key fk_study_portal_0 study_portal references portal_type ( portal_type_id ) -portal_type_id +portal_type_id Foreign Key fk_software_publication software_publication references software ( software_id ) -software_id +software_id Foreign Key fk_software_publication_0 software_publication references publication ( publication_doi -> doi ) -publication_doi +publication_doi Foreign Key fk_required_sample_info_study study_sample references study ( study_id ) -study_id +study_id Foreign Key fk_oauth_software_software oauth_software references software ( software_id ) -software_id +software_id Foreign Key fk_oauth_software oauth_software references oauth_identifiers ( client_id ) -client_id - Foreign Key fk_analysis_analysis_status - analysis references analysis_status ( analysis_status_id ) - -analysis_status_id - Foreign Key fk_analysis_chain - analysis_chain references analysis ( parent_id -> analysis_id ) - -parent_id - Foreign Key fk_analysis_chain_0 - analysis_chain references analysis ( child_id -> analysis_id ) - -child_id +client_id Foreign Key fk_ebi_run_accession ebi_run_accession references study_sample ( sample_id ) -sample_id +sample_id Foreign Key fk_ebi_run_accession_artifact ebi_run_accession references artifact ( artifact_id ) -artifact_id +artifact_id Foreign Key fk_artifact_visibility artifact references visibility ( visibility_id ) -visibility_id +visibility_id Foreign Key fk_artifact_filetype artifact references artifact_type ( artifact_type_id ) -artifact_type_id +artifact_type_id Foreign Key fk_artifact_soft_command artifact references software_command ( command_id ) -command_id +command_id Foreign Key fk_artifact_data_type artifact references data_type ( data_type_id ) -data_type_id +data_type_id Foreign Key fk_artifact_type_filepath_type artifact_type_filepath_type references artifact_type ( artifact_type_id ) -artifact_type_id +artifact_type_id Foreign Key fk_artifact_type_filepath_type_0 artifact_type_filepath_type references filepath_type ( filepath_type_id ) -filepath_type_id +filepath_type_id Foreign Key fk_study_artifact_study study_artifact references study ( study_id ) -study_id +study_id Foreign Key fk_study_artifact_artifact study_artifact references artifact ( artifact_id ) -artifact_id +artifact_id Foreign Key fk_command_parameter command_parameter references software_command ( command_id ) -command_id +command_id Foreign Key fk_parameter_artifact_type parameter_artifact_type references command_parameter ( command_parameter_id ) -command_parameter_id +command_parameter_id Foreign Key fk_parameter_artifact_type_0 parameter_artifact_type references artifact_type ( artifact_type_id ) -artifact_type_id +artifact_type_id Foreign Key fk_command_output command_output references software_command ( command_id ) -command_id - Foreign Key fk_command_output_0 - command_output references artifact_type ( artifact_type_id ) - -artifact_type_id +command_id Foreign Key fk_default_parameter_set default_parameter_set references software_command ( command_id ) -command_id +command_id Foreign Key fk_default_workflow_software default_workflow references software ( software_id ) -software_id +software_id Foreign Key fk_default_workflow_command default_workflow_node references software_command ( command_id ) -command_id +command_id Foreign Key fk_default_workflow_command_0 default_workflow_node references default_parameter_set ( default_parameter_set_id ) -default_parameter_set_id +default_parameter_set_id Foreign Key fk_default_workflow_command_1 default_workflow_node references default_workflow ( default_workflow_id ) -default_workflow_id +default_workflow_id Foreign Key fk_default_workflow_edge default_workflow_edge references default_workflow_node ( parent_id -> default_workflow_node_id ) -parent_id +parent_id Foreign Key fk_default_workflow_edge_0 default_workflow_edge references default_workflow_node ( child_id -> default_workflow_node_id ) -child_id +child_id Foreign Key fk_artifact_processing_job artifact_processing_job references artifact ( artifact_id ) -artifact_id +artifact_id Foreign Key fk_artifact_processing_job_0 artifact_processing_job references processing_job ( processing_job_id ) -processing_job_id +processing_job_id Foreign Key fk_artifact_output_processing_job artifact_output_processing_job references artifact ( artifact_id ) -artifact_id +artifact_id Foreign Key fk_artifact_output_processing_job_0 artifact_output_processing_job references processing_job ( processing_job_id ) -processing_job_id +processing_job_id Foreign Key fk_artifact_output_processing_job_1 artifact_output_processing_job references command_output ( command_output_id ) -command_output_id +command_output_id Foreign Key fk_reference_sequence_filepath reference references filepath ( sequence_filepath -> filepath_id ) -sequence_filepath +sequence_filepath Foreign Key fk_reference_taxonomy_filepath reference references filepath ( taxonomy_filepath -> filepath_id ) -taxonomy_filepath - Foreign Key fk_reference_tree_filepath - reference references filepath ( tree_filepath -> filepath_id ) - -tree_filepath +taxonomy_filepath Foreign Key fk_parent_processing_job parent_processing_job references processing_job ( parent_id -> processing_job_id ) -parent_id +parent_id Foreign Key fk_parent_processing_job_0 parent_processing_job references processing_job ( child_id -> processing_job_id ) -child_id +child_id Foreign Key fk_processing_job_workflow processing_job_workflow references qiita_user ( email ) -email +email Foreign Key fk_processing_job_workflow_roots_0 processing_job_workflow_root references processing_job ( processing_job_id ) -processing_job_id +processing_job_id Foreign Key fk_processing_job_workflow_roots processing_job_workflow_root references processing_job_workflow ( processing_job_workflow_id ) -processing_job_workflow_id +processing_job_workflow_id Foreign Key fk_prep_template_processing_job prep_template_processing_job references prep_template ( prep_template_id ) -prep_template_id +prep_template_id Foreign Key fk_prep_template_processing_job_0 prep_template_processing_job references processing_job ( processing_job_id ) -processing_job_id +processing_job_id Foreign Key fk_software_artifact_type software_artifact_type references artifact_type ( artifact_type_id ) -artifact_type_id +artifact_type_id Foreign Key fk_software_artifact_type_sw software_artifact_type references software ( software_id ) -software_id +software_id Foreign Key fk_software_software_type software references software_type ( software_type_id ) -software_type_id +software_type_id Foreign Key fk_soft_command_software software_command references software ( software_id ) -software_id - Foreign Key fk_job_function - job references command ( command_id ) - -command_id - Foreign Key fk_job_job_status_id - job references job_status ( job_status_id ) - -job_status_id - Foreign Key fk_job_data_type - job references data_type ( data_type_id ) - -data_type_id - Foreign Key fk_job - job references logging ( log_id -> logging_id ) - -log_id - Foreign Key fk_job_reference - job references reference ( input_file_reference_id -> reference_id ) - -input_file_reference_id - Foreign Key fk_processing_job_qiita_user - processing_job references qiita_user ( email ) - -email +software_id Foreign Key fk_processing_job processing_job references software_command ( command_id ) -command_id +command_id Foreign Key fk_processing_job_status processing_job references processing_job_status ( processing_job_status_id ) -processing_job_status_id +processing_job_status_id + Foreign Key fk_processing_job_logging + processing_job references logging ( logging_id ) + +logging_id Foreign Key fk_default_workflow_edge_connections default_workflow_edge_connections references command_output ( parent_output_id -> command_output_id ) -parent_output_id +parent_output_id Foreign Key fk_default_workflow_edge_connections_0 default_workflow_edge_connections references command_parameter ( child_input_id -> command_parameter_id ) -child_input_id +child_input_id Foreign Key fk_default_workflow_edge_connections_1 default_workflow_edge_connections references default_workflow_edge ( default_workflow_edge_id ) -default_workflow_edge_id +default_workflow_edge_id Foreign Key fk_processing_job_validator processing_job_validator references processing_job ( processing_job_id ) -processing_job_id +processing_job_id Foreign Key fk_processing_job_validator_0 processing_job_validator references processing_job ( validator_id -> processing_job_id ) -validator_id +validator_id Foreign Key fk_study_publication_study study_publication references study ( study_id ) -study_id - - -controlled_vocab_valuesTable qiita.controlled_vocab_values - Primary Key ( vocab_value_id ) -vocab_value_idvocab_value_id bigserial not null - Index ( controlled_vocab_id ) -controlled_vocab_idcontrolled_vocab_id bigint not null -References controlled_vocab ( controlled_vocab_id ) - termterm varchar not null - order_byorder_by varchar not null - default_itemdefault_item varchar - - - - -job_results_filepathTable qiita.job_results_filepath -Holds connection between jobs and the result filepaths - Primary Key ( job_id, filepath_id ) Index ( job_id ) -job_idjob_id bigint not null -References job ( job_id ) - Primary Key ( job_id, filepath_id ) Index ( filepath_id ) -filepath_idfilepath_id bigint not null -References filepath ( filepath_id ) - - - - -analysis_jobTable qiita.analysis_job -Holds information for a one-to-many relation of analysis to the jobs in it - Primary Key ( analysis_id, job_id ) Index ( analysis_id ) -analysis_idanalysis_id bigint not null -Id of the analysis -References analysis ( analysis_id ) - Primary Key ( analysis_id, job_id ) Index ( job_id ) -job_idjob_id bigint not null -Id for a job that is part of the analysis -References job ( job_id ) +study_id + Foreign Key fk_analysis_user + analysis references qiita_user ( email ) + +email + Foreign Key fk_analysis_analysis_status + analysis references analysis_status ( analysis_status_id ) + +analysis_status_id + Foreign Key fk_analysis + analysis references portal_type ( portal_type_id ) + +portal_type_id + Foreign Key fk_analysis_users_analysis + analysis_users references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_users_user + analysis_users references qiita_user ( email ) + +email + Foreign Key fk_analysis_portal + analysis_portal references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_portal_0 + analysis_portal references portal_type ( portal_type_id ) + +portal_type_id + Foreign Key fk_analysis_filepath + analysis_filepath references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_filepath_0 + analysis_filepath references filepath ( filepath_id ) + +filepath_id + Foreign Key fk_analysis_filepath_1 + analysis_filepath references data_type ( data_type_id ) + +data_type_id + Foreign Key fk_analysis_sample_analysis + analysis_sample references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_sample + analysis_sample references study_sample ( sample_id ) + +sample_id + Foreign Key fk_analysis_sample_artifact + analysis_sample references artifact ( artifact_id ) + +artifact_id + Foreign Key fk_analysis_artifact_analysis + analysis_artifact references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_artifact_artifact + analysis_artifact references artifact ( artifact_id ) + +artifact_id + + +controlled_vocab_valuesTable qiita.controlled_vocab_values + Primary Key ( vocab_value_id ) +vocab_value_idvocab_value_id bigserial not null + Index ( controlled_vocab_id ) +controlled_vocab_idcontrolled_vocab_id bigint not null +References controlled_vocab ( controlled_vocab_id ) + termterm varchar not null + order_byorder_by varchar not null + default_itemdefault_item varchar - - -column_controlled_vocabulariesTable qiita.column_controlled_vocabularies + + +column_controlled_vocabulariesTable qiita.column_controlled_vocabularies Table relates a column with a controlled vocabulary. - Primary Key ( controlled_vocab_id, column_name ) Index ( controlled_vocab_id ) -controlled_vocab_idcontrolled_vocab_id bigserial not null -References controlled_vocab ( controlled_vocab_id ) - Primary Key ( controlled_vocab_id, column_name ) Index ( column_name ) -column_namecolumn_name varchar not null -References mixs_field_description ( column_name ) + Primary Key ( controlled_vocab_id, column_name ) Index ( controlled_vocab_id ) +controlled_vocab_idcontrolled_vocab_id bigserial not null +References controlled_vocab ( controlled_vocab_id ) + Primary Key ( controlled_vocab_id, column_name ) Index ( column_name ) +column_namecolumn_name varchar not null +References mixs_field_description ( column_name ) - - -mixs_field_descriptionTable qiita.mixs_field_description - Primary Key ( column_name ) -column_namecolumn_name varchar not null -Referred by column_controlled_vocabularies ( column_name ) +<rect class='table' x='1665' y='1688' width='135' height='135' rx='7' ry='7' /> +<path d='M 1665.50 1714.50 L 1665.50 1695.50 Q 1665.50 1688.50 1672.50 1688.50 L 1792.50 1688.50 Q 1799.50 1688.50 1799.50 1695.50 L 1799.50 1714.50 L1665.50 1714.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#mixs_field_description'><text x='1671' y='1702' class='tableTitle'>mixs_field_description</text><title>Table qiita.mixs_field_description + Primary Key ( column_name ) +column_namecolumn_name varchar not null +Referred by column_controlled_vocabularies ( column_name ) Referred by column_ontology ( column_name ) - data_typedata_type varchar not null - desc_or_valuedesc_or_value varchar not null - definitiondefinition varchar not null - min_lengthmin_length integer - activeactive integer not null - - - - -analysis_usersTable qiita.analysis_users -Links analyses to the users they are shared with - Primary Key ( analysis_id, email ) Index ( analysis_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - Primary Key ( analysis_id, email ) Index ( email ) -emailemail varchar not null -References qiita_user ( email ) - - - - -commandTable qiita.command -Available commands for jobs - Primary Key ( command_id ) -command_idcommand_id bigserial not null -Unique identifier for function -Referred by command_data_type ( command_id ) -Referred by job ( command_id ) - namename varchar not null - commandcommand varchar not null -What command to call to run this function - inputinput varchar not null -JSON of input options for the command - requiredrequired varchar not null -JSON of required options for the command - optionaloptional varchar not null -JSON of optional options for command - outputoutput varchar not null -JSON of output options for the command + data_typedata_type varchar not null + desc_or_valuedesc_or_value varchar not null + definitiondefinition varchar not null + min_lengthmin_length integer + activeactive integer not null - - -loggingTable qiita.logging - Primary Key ( logging_id ) -logging_idlogging_id bigserial not null -Referred by job ( log_id -> logging_id ) -Referred by processing_job ( logging_id ) - timetime timestamp not null +<rect class='table' x='1305' y='1943' width='105' height='120' rx='7' ry='7' /> +<path d='M 1305.50 1969.50 L 1305.50 1950.50 Q 1305.50 1943.50 1312.50 1943.50 L 1402.50 1943.50 Q 1409.50 1943.50 1409.50 1950.50 L 1409.50 1969.50 L1305.50 1969.50 ' style='fill:url(#tableHeaderGradient1); stroke:none;' /> +<a xlink:href='#logging'><text x='1337' y='1957' class='tableTitle'>logging</text><title>Table qiita.logging + Primary Key ( logging_id ) +logging_idlogging_id bigserial not null +Referred by processing_job ( logging_id ) + timetime timestamp not null Time the error was thrown - Index ( severity_id ) -severity_idseverity_id integer not null -References severity ( severity_id ) - msgmsg varchar not null + <use id='nn' x='1307' y='2007' xlink:href='#nn'/><a xlink:href='#logging.severity_id'><use id='idx' x='1307' y='2006' xlink:href='#idx'/><title>Index ( severity_id ) +severity_idseverity_id integer not null +References severity ( severity_id ) + msgmsg varchar not null Error message thrown - informationinformation varchar + <a xlink:href='#logging.information'><text x='1323' y='2047'>information</text><title>information varchar Other applicable information (depending on error) - - - -command_data_typeTable qiita.command_data_type - Primary Key ( command_id, data_type_id ) Index ( command_id ) -command_idcommand_id bigint not null -References command ( command_id ) - Primary Key ( command_id, data_type_id ) Index ( data_type_id ) -data_type_iddata_type_id bigint not null -References data_type ( data_type_id ) - - - - -analysis_filepathTable qiita.analysis_filepath -Stores link between analysis and the data file used for the analysis. - Index ( analysis_id ) Primary Key ( analysis_id, filepath_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - Index ( filepath_id ) Primary Key ( analysis_id, filepath_id ) -filepath_idfilepath_id bigint not null -References filepath ( filepath_id ) - Index ( data_type_id ) -data_type_iddata_type_id bigint -References data_type ( data_type_id ) - - - - -analysis_workflowTable qiita.analysis_workflow -Stores what step in_production analyses are on. - Primary Key ( analysis_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - stepstep integer not null - - - -column_ontologyTable qiita.column_ontology +<rect class='table' x='1650' y='1913' width='150' height='105' rx='7' ry='7' /> +<path d='M 1650.50 1939.50 L 1650.50 1920.50 Q 1650.50 1913.50 1657.50 1913.50 L 1792.50 1913.50 Q 1799.50 1913.50 1799.50 1920.50 L 1799.50 1939.50 L1650.50 1939.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#column_ontology'><text x='1678' y='1927' class='tableTitle'>column_ontology</text><title>Table qiita.column_ontology This table relates a column with an ontology. - Primary Key ( column_name, ontology_short_name ) Index ( column_name ) -column_namecolumn_name varchar not null -References mixs_field_description ( column_name ) - Primary Key ( column_name, ontology_short_name ) -ontology_short_nameontology_short_name varchar not null - bioportal_idbioportal_id integer not null - ontology_branch_idontology_branch_id varchar + Primary Key ( column_name, ontology_short_name ) Index ( column_name ) +column_namecolumn_name varchar not null +References mixs_field_description ( column_name ) + Primary Key ( column_name, ontology_short_name ) +ontology_short_nameontology_short_name varchar not null + bioportal_idbioportal_id integer not null + ontology_branch_idontology_branch_id varchar - - -controlled_vocabTable qiita.controlled_vocab - Primary Key ( controlled_vocab_id ) -controlled_vocab_idcontrolled_vocab_id bigserial not null -Referred by column_controlled_vocabularies ( controlled_vocab_id ) +<rect class='table' x='1410' y='1523' width='150' height='75' rx='7' ry='7' /> +<path d='M 1410.50 1549.50 L 1410.50 1530.50 Q 1410.50 1523.50 1417.50 1523.50 L 1552.50 1523.50 Q 1559.50 1523.50 1559.50 1530.50 L 1559.50 1549.50 L1410.50 1549.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#controlled_vocab'><text x='1438' y='1537' class='tableTitle'>controlled_vocab</text><title>Table qiita.controlled_vocab + Primary Key ( controlled_vocab_id ) +controlled_vocab_idcontrolled_vocab_id bigserial not null +Referred by column_controlled_vocabularies ( controlled_vocab_id ) Referred by controlled_vocab_values ( controlled_vocab_id ) - controlled_vocabcontrolled_vocab varchar not null - - - - -ontologyTable qiita.ontology - Primary Key ( ontology_id ) -ontology_idontology_id bigint not null -Referred by term ( ontology_id ) - Unique Index ( ontology ) -ontologyontology varchar not null - fully_loadedfully_loaded bool not null - fullnamefullname varchar - query_urlquery_url varchar - source_urlsource_url varchar - definitiondefinition text - load_dateload_date date not null + controlled_vocabcontrolled_vocab varchar not null - - -qiita_userTable qiita.qiita_user +<rect class='table' x='210' y='203' width='150' height='195' rx='7' ry='7' /> +<path d='M 210.50 229.50 L 210.50 210.50 Q 210.50 203.50 217.50 203.50 L 352.50 203.50 Q 359.50 203.50 359.50 210.50 L 359.50 229.50 L210.50 229.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#qiita_user'><text x='258' y='217' class='tableTitle'>qiita_user</text><title>Table qiita.qiita_user Holds all user information - Primary Key ( email ) -emailemail varchar not null -Referred by analysis ( email ) + <use id='nn' x='212' y='237' xlink:href='#nn'/><a xlink:href='#qiita_user.email'><use id='pk' x='212' y='236' xlink:href='#pk'/><title>Primary Key ( email ) +emailemail varchar not null +Referred by analysis ( email ) Referred by analysis_users ( email ) -Referred by collection ( email ) -Referred by collection_users ( email ) Referred by message_user ( email ) Referred by processing_job ( email ) Referred by processing_job_workflow ( email ) Referred by study ( email ) Referred by study_users ( email ) - Index ( user_level_id ) -user_level_iduser_level_id integer not null default 5 + <use id='nn' x='212' y='252' xlink:href='#nn'/><a xlink:href='#qiita_user.user_level_id'><use id='idx' x='212' y='251' xlink:href='#idx'/><title>Index ( user_level_id ) +user_level_iduser_level_id integer not null default 5 user level -References user_level ( user_level_id ) - passwordpassword varchar not null - namename varchar - affiliationaffiliation varchar - addressaddress varchar - phonephone varchar - user_verify_codeuser_verify_code varchar +<a xlink:href='#qiita_user.user_level_id'><use id='fk' x='348' y='251' xlink:href='#fk'/><title>References user_level ( user_level_id ) + passwordpassword varchar not null + namename varchar + affiliationaffiliation varchar + addressaddress varchar + phonephone varchar + user_verify_codeuser_verify_code varchar Code for initial user email verification - pass_reset_codepass_reset_code varchar + <a xlink:href='#qiita_user.pass_reset_code'><text x='228' y='367'>pass_reset_code</text><title>pass_reset_code varchar Randomly generated code for password reset - pass_reset_timestamppass_reset_timestamp timestamp + <a xlink:href='#qiita_user.pass_reset_timestamp'><text x='228' y='382'>pass_reset_timestamp</text><title>pass_reset_timestamp timestamp Time the reset code was generated - - -prep_yTable qiita.prep_y +<rect class='table' x='1140' y='188' width='90' height='75' rx='7' ry='7' /> +<path d='M 1140.50 214.50 L 1140.50 195.50 Q 1140.50 188.50 1147.50 188.50 L 1222.50 188.50 Q 1229.50 188.50 1229.50 195.50 L 1229.50 214.50 L1140.50 214.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#prep_y'><text x='1166' y='202' class='tableTitle'>prep_y</text><title>Table qiita.prep_y Information on how raw data y was prepared (prep template)Linked by y being raw_data_id from raw data table. - Primary Key ( sample_id ) -sample_idsample_id varchar not null - datadata bigint + <use id='nn' x='1142' y='222' xlink:href='#nn'/><a xlink:href='#prep_y.sample_id'><use id='pk' x='1142' y='221' xlink:href='#pk'/><title>Primary Key ( sample_id ) +sample_idsample_id varchar not null + datadata bigint STUFFFFF - - -checksum_algorithmTable qiita.checksum_algorithm - Primary Key ( checksum_algorithm_id ) -checksum_algorithm_idchecksum_algorithm_id bigserial not null -Referred by filepath ( checksum_algorithm_id ) - Unique Index ( name ) -namename varchar not null + + +checksum_algorithmTable qiita.checksum_algorithm + Primary Key ( checksum_algorithm_id ) +checksum_algorithm_idchecksum_algorithm_id bigserial not null +Referred by filepath ( checksum_algorithm_id ) + Unique Index ( name ) +namename varchar not null - - -user_levelTable qiita.user_level +<rect class='table' x='45' y='188' width='105' height='90' rx='7' ry='7' /> +<path d='M 45.50 214.50 L 45.50 195.50 Q 45.50 188.50 52.50 188.50 L 142.50 188.50 Q 149.50 188.50 149.50 195.50 L 149.50 214.50 L45.50 214.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#user_level'><text x='70' y='202' class='tableTitle'>user_level</text><title>Table qiita.user_level Holds available user levels - Primary Key ( user_level_id ) -user_level_iduser_level_id serial not null -Referred by qiita_user ( user_level_id ) - Unique Index ( name ) -namename varchar not null + <use id='nn' x='47' y='222' xlink:href='#nn'/><a xlink:href='#user_level.user_level_id'><use id='pk' x='47' y='221' xlink:href='#pk'/><title>Primary Key ( user_level_id ) +user_level_iduser_level_id serial not null +Referred by qiita_user ( user_level_id ) + Unique Index ( name ) +namename varchar not null One of the user levels (admin, user, guest, etc) - descriptiondescription text not null - - - - -job_statusTable qiita.job_status - Primary Key ( job_status_id ) -job_status_idjob_status_id bigserial not null -Referred by job ( job_status_id ) - Unique Index ( status ) -statusstatus varchar not null + descriptiondescription text not null - - -severityTable qiita.severity - Primary Key ( severity_id ) -severity_idseverity_id serial not null -Referred by logging ( severity_id ) - Unique Index ( severity ) -severityseverity varchar not null + + +severityTable qiita.severity + Primary Key ( severity_id ) +severity_idseverity_id serial not null +Referred by logging ( severity_id ) + Unique Index ( severity ) +severityseverity varchar not null - - -filepathTable qiita.filepath - Primary Key ( filepath_id ) -filepath_idfilepath_id bigserial not null -Referred by analysis_filepath ( filepath_id ) +<rect class='table' x='675' y='728' width='165' height='135' rx='7' ry='7' /> +<path d='M 675.50 754.50 L 675.50 735.50 Q 675.50 728.50 682.50 728.50 L 832.50 728.50 Q 839.50 728.50 839.50 735.50 L 839.50 754.50 L675.50 754.50 ' style='fill:url(#tableHeaderGradient1); stroke:none;' /> +<a xlink:href='#filepath'><text x='737' y='742' class='tableTitle'>filepath</text><title>Table qiita.filepath + Primary Key ( filepath_id ) +filepath_idfilepath_id bigserial not null +Referred by analysis_filepath ( filepath_id ) Referred by artifact_filepath ( filepath_id ) -Referred by job_results_filepath ( filepath_id ) Referred by prep_template_filepath ( filepath_id ) Referred by reference ( sequence_filepath -> filepath_id ) Referred by reference ( taxonomy_filepath -> filepath_id ) Referred by reference ( tree_filepath -> filepath_id ) Referred by sample_template_filepath ( filepath_id ) - filepathfilepath varchar not null - Index ( filepath_type_id ) -filepath_type_idfilepath_type_id bigint not null -References filepath_type ( filepath_type_id ) - checksumchecksum varchar not null - checksum_algorithm_idchecksum_algorithm_id bigint not null -References checksum_algorithm ( checksum_algorithm_id ) - Index ( data_directory_id ) -data_directory_iddata_directory_id bigserial -References data_directory ( data_directory_id ) + filepathfilepath varchar not null + Index ( filepath_type_id ) +filepath_type_idfilepath_type_id bigint not null +References filepath_type ( filepath_type_id ) + checksumchecksum varchar not null + checksum_algorithm_idchecksum_algorithm_id bigint not null +References checksum_algorithm ( checksum_algorithm_id ) + Index ( data_directory_id ) +data_directory_iddata_directory_id bigserial +References data_directory ( data_directory_id ) - - -termTable qiita.term - Primary Key ( term_id ) -term_idterm_id bigserial not null - Index ( ontology_id ) -ontology_idontology_id bigint not null -References ontology ( ontology_id ) - old_term_idold_term_id bigint default NULL +<rect class='table' x='1905' y='1823' width='105' height='210' rx='7' ry='7' /> +<path d='M 1905.50 1849.50 L 1905.50 1830.50 Q 1905.50 1823.50 1912.50 1823.50 L 2002.50 1823.50 Q 2009.50 1823.50 2009.50 1830.50 L 2009.50 1849.50 L1905.50 1849.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#term'><text x='1945' y='1837' class='tableTitle'>term</text><title>Table qiita.term + Primary Key ( term_id ) +term_idterm_id bigserial not null + Index ( ontology_id ) +ontology_idontology_id bigint not null +References ontology ( ontology_id ) + old_term_idold_term_id bigint default NULL Identifier used in the old system, we are keeping this for consistency - termterm varchar not null - identifieridentifier varchar - definitiondefinition varchar - namespacenamespace varchar - is_obsoleteis_obsolete bool default 'false' - is_root_termis_root_term bool - is_leafis_leaf bool - user_defineduser_defined bool not null default False + <use id='nn' x='1907' y='1902' xlink:href='#nn'/><a xlink:href='#term.term'><text x='1923' y='1912'>term</text><title>term varchar not null + identifieridentifier varchar + definitiondefinition varchar + namespacenamespace varchar + is_obsoleteis_obsolete bool default 'false' + is_root_termis_root_term bool + is_leafis_leaf bool + user_defineduser_defined bool not null default False Whether or not this term was defined by a user - - - -analysis_statusTable qiita.analysis_status - Primary Key ( analysis_status_id ) -analysis_status_idanalysis_status_id bigserial not null -Referred by analysis ( analysis_status_id ) - Unique Index ( status ) -statusstatus varchar not null - - - - -collection_jobTable qiita.collection_job -Matches collection important jobs as one to many. - Index ( collection_id ) Primary Key ( collection_id, job_id ) -collection_idcollection_id bigint not null -References collection ( collection_id ) - Index ( job_id ) Primary Key ( collection_id, job_id ) -job_idjob_id bigint not null -References job ( job_id ) - - - - -collection_analysisTable qiita.collection_analysis -Matches collection to analyses as one to many. - Primary Key ( collection_id, analysis_id ) Index ( collection_id ) -collection_idcollection_id bigint not null -References collection ( collection_id ) - Primary Key ( collection_id, analysis_id ) Index ( analysis_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - - - - -collection_statusTable qiita.collection_status - Primary Key ( collection_status_id ) -collection_status_idcollection_status_id bigserial not null -Referred by collection ( collection_status_id ) - statusstatus varchar(1) not null - - - - -collectionTable qiita.collection -Tracks a group of analyses and important jobs for an overarching goal. - Primary Key ( collection_id ) -collection_idcollection_id bigserial not null -Referred by collection_analysis ( collection_id ) -Referred by collection_job ( collection_id ) -Referred by collection_users ( collection_id ) - Index ( email ) -emailemail varchar not null -References qiita_user ( email ) - namename varchar(100) not null - descriptiondescription varchar - Index ( collection_status_id ) -collection_status_idcollection_status_id bigint not null default 1 -References collection_status ( collection_status_id ) - - - - -collection_usersTable qiita.collection_users -Allows sharing of a collection - Primary Key ( collection_id, email ) Index ( collection_id ) -collection_idcollection_id bigint not null -References collection ( collection_id ) - Primary Key ( collection_id, email ) Index ( email ) -emailemail varchar not null -References qiita_user ( email ) - - - -study_prep_templateTable qiita.study_prep_template +<rect class='table' x='1500' y='413' width='135' height='75' rx='7' ry='7' /> +<path d='M 1500.50 439.50 L 1500.50 420.50 Q 1500.50 413.50 1507.50 413.50 L 1627.50 413.50 Q 1634.50 413.50 1634.50 420.50 L 1634.50 439.50 L1500.50 439.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#study_prep_template'><text x='1511' y='427' class='tableTitle'>study_prep_template</text><title>Table qiita.study_prep_template links study to its prep templates - Primary Key ( study_id, prep_template_id ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, prep_template_id ) Index ( prep_template_id ) -prep_template_idprep_template_id bigint not null -References prep_template ( prep_template_id ) + Primary Key ( study_id, prep_template_id ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, prep_template_id ) Index ( prep_template_id ) +prep_template_idprep_template_id bigint not null +References prep_template ( prep_template_id ) - - -prep_template_sampleTable qiita.prep_template_sample - Primary Key ( prep_template_id, sample_id ) Index ( prep_template_id ) -prep_template_idprep_template_id bigint not null +<rect class='table' x='1245' y='188' width='180' height='90' rx='7' ry='7' /> +<path d='M 1245.50 214.50 L 1245.50 195.50 Q 1245.50 188.50 1252.50 188.50 L 1417.50 188.50 Q 1424.50 188.50 1424.50 195.50 L 1424.50 214.50 L1245.50 214.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#prep_template_sample'><text x='1274' y='202' class='tableTitle'>prep_template_sample</text><title>Table qiita.prep_template_sample + Primary Key ( prep_template_id, sample_id ) Index ( prep_template_id ) +prep_template_idprep_template_id bigint not null The prep template identifier -References prep_template ( prep_template_id ) - Index ( sample_id ) Index ( sample_id ) Primary Key ( prep_template_id, sample_id ) -sample_idsample_id varchar not null - ebi_experiment_accessionebi_experiment_accession varchar - - - - -portal_typeTable qiita.portal_type -What portals are available to show a study in - Primary Key ( portal_type_id ) -portal_type_idportal_type_id bigserial not null -Referred by analysis ( portal_type_id ) -Referred by analysis_portal ( portal_type_id ) -Referred by study_portal ( portal_type_id ) - portalportal varchar not null - portal_descriptionportal_description varchar not null - - - - -analysis_portalTable qiita.analysis_portal -Controls what analyses are visible on what portals - Index ( analysis_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - Index ( portal_type_id ) -portal_type_idportal_type_id bigint not null -References portal_type ( portal_type_id ) +References prep_template ( prep_template_id ) + Index ( sample_id ) Index ( sample_id ) Primary Key ( prep_template_id, sample_id ) +sample_idsample_id varchar not null + ebi_experiment_accessionebi_experiment_accession varchar - - -message_userTable qiita.message_user - Primary Key ( email, message_id ) Index ( email ) -emailemail varchar not null -References qiita_user ( email ) - Primary Key ( email, message_id ) Index ( message_id ) -message_idmessage_id bigint not null -References message ( message_id ) - readread bool not null default 'false' +<rect class='table' x='420' y='188' width='105' height='90' rx='7' ry='7' /> +<path d='M 420.50 214.50 L 420.50 195.50 Q 420.50 188.50 427.50 188.50 L 517.50 188.50 Q 524.50 188.50 524.50 195.50 L 524.50 214.50 L420.50 214.50 ' style='fill:url(#tableHeaderGradient3); stroke:none;' /> +<a xlink:href='#message_user'><text x='434' y='202' class='tableTitle'>message_user</text><title>Table qiita.message_user + Primary Key ( email, message_id ) Index ( email ) +emailemail varchar not null +References qiita_user ( email ) + Primary Key ( email, message_id ) Index ( message_id ) +message_idmessage_id bigint not null +References message ( message_id ) + readread bool not null default 'false' Whether the message has been read or not. - - -messageTable qiita.message - Primary Key ( message_id ) -message_idmessage_id bigserial not null -Referred by message_user ( message_id ) - messagemessage varchar not null - message_timemessage_time timestamp not null default current_timestamp - expirationexpiration timestamp + + +messageTable qiita.message + Primary Key ( message_id ) +message_idmessage_id bigserial not null +Referred by message_user ( message_id ) + messagemessage varchar not null + message_timemessage_time timestamp not null default current_timestamp + expirationexpiration timestamp - - -sample_xTable qiita.sample_x +<rect class='table' x='1545' y='143' width='165' height='90' rx='7' ry='7' /> +<path d='M 1545.50 169.50 L 1545.50 150.50 Q 1545.50 143.50 1552.50 143.50 L 1702.50 143.50 Q 1709.50 143.50 1709.50 150.50 L 1709.50 169.50 L1545.50 169.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#sample_x'><text x='1602' y='157' class='tableTitle'>sample_x</text><title>Table qiita.sample_x data for samples in study x (sample template)x is the study_id from study tableMAKE SURE sample_id IS FK TO sample_id IN required_sample_info TABLE - Primary Key ( sample_id ) -sample_idsample_id varchar not null - descriptiondescription varchar not null - other_mapping_columnsother_mapping_columns varchar + <use id='nn' x='1547' y='177' xlink:href='#nn'/><a xlink:href='#sample_x.sample_id'><use id='pk' x='1547' y='176' xlink:href='#pk'/><title>Primary Key ( sample_id ) +sample_idsample_id varchar not null + descriptiondescription varchar not null + other_mapping_columnsother_mapping_columns varchar Represents whatever other columns go with this study - - -prep_template_filepathTable qiita.prep_template_filepath - Primary Key ( prep_template_id, filepath_id ) Index ( prep_template_id ) -prep_template_idprep_template_id bigint not null -References prep_template ( prep_template_id ) - Primary Key ( prep_template_id, filepath_id ) Index ( filepath_id ) -filepath_idfilepath_id bigint not null -References filepath ( filepath_id ) + + +prep_template_filepathTable qiita.prep_template_filepath + Primary Key ( prep_template_id, filepath_id ) Index ( prep_template_id ) +prep_template_idprep_template_id bigint not null +References prep_template ( prep_template_id ) + Primary Key ( prep_template_id, filepath_id ) Index ( filepath_id ) +filepath_idfilepath_id bigint not null +References filepath ( filepath_id ) - - -sample_template_filepathTable qiita.sample_template_filepath - Primary Key ( study_id, filepath_id ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, filepath_id ) Index ( filepath_id ) -filepath_idfilepath_id bigint not null -References filepath ( filepath_id ) + + +sample_template_filepathTable qiita.sample_template_filepath + Primary Key ( study_id, filepath_id ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, filepath_id ) Index ( filepath_id ) +filepath_idfilepath_id bigint not null +References filepath ( filepath_id ) - - -prep_templateTable qiita.prep_template - Primary Key ( prep_template_id ) -prep_template_idprep_template_id bigserial not null -Referred by prep_template_filepath ( prep_template_id ) +<rect class='table' x='1215' y='368' width='150' height='120' rx='7' ry='7' /> +<path d='M 1215.50 394.50 L 1215.50 375.50 Q 1215.50 368.50 1222.50 368.50 L 1357.50 368.50 Q 1364.50 368.50 1364.50 375.50 L 1364.50 394.50 L1215.50 394.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#prep_template'><text x='1251' y='382' class='tableTitle'>prep_template</text><title>Table qiita.prep_template + Primary Key ( prep_template_id ) +prep_template_idprep_template_id bigserial not null +Referred by prep_template_filepath ( prep_template_id ) Referred by prep_template_processing_job ( prep_template_id ) Referred by prep_template_sample ( prep_template_id ) Referred by study_prep_template ( prep_template_id ) - Index ( data_type_id ) -data_type_iddata_type_id bigint not null -References data_type ( data_type_id ) - preprocessing_statuspreprocessing_status varchar not null default 'not_preprocessed' - investigation_typeinvestigation_type varchar + <use id='nn' x='1217' y='417' xlink:href='#nn'/><a xlink:href='#prep_template.data_type_id'><use id='idx' x='1217' y='416' xlink:href='#idx'/><title>Index ( data_type_id ) +data_type_iddata_type_id bigint not null +References data_type ( data_type_id ) + preprocessing_statuspreprocessing_status varchar not null default 'not_preprocessed' + investigation_typeinvestigation_type varchar The investigation type (e.g., one of the values from EBI's set of known types) - Index ( artifact_id ) -artifact_idartifact_id bigint -References artifact ( artifact_id ) - - - - -analysis_sampleTable qiita.analysis_sample - Index ( analysis_id ) Primary Key ( analysis_id, artifact_id, sample_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - Index ( artifact_id ) Primary Key ( analysis_id, artifact_id, sample_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) - Index ( sample_id ) Primary Key ( analysis_id, artifact_id, sample_id ) -sample_idsample_id varchar not null -References study_sample ( sample_id ) + Index ( artifact_id ) +artifact_idartifact_id bigint +References artifact ( artifact_id ) - - -parent_artifactTable qiita.parent_artifact - Primary Key ( artifact_id, parent_id ) Index ( artifact_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) - Primary Key ( artifact_id, parent_id ) Index ( parent_id ) -parent_idparent_id bigint not null -References artifact ( parent_id -> artifact_id ) + + +parent_artifactTable qiita.parent_artifact + Primary Key ( artifact_id, parent_id ) Index ( artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + Primary Key ( artifact_id, parent_id ) Index ( parent_id ) +parent_idparent_id bigint not null +References artifact ( parent_id -> artifact_id ) - - -artifact_filepathTable qiita.artifact_filepath - Primary Key ( artifact_id, filepath_id ) Index ( artifact_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) - Primary Key ( artifact_id, filepath_id ) Index ( filepath_id ) -filepath_idfilepath_id bigint not null -References filepath ( filepath_id ) + + +artifact_filepathTable qiita.artifact_filepath + Primary Key ( artifact_id, filepath_id ) Index ( artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + Primary Key ( artifact_id, filepath_id ) Index ( filepath_id ) +filepath_idfilepath_id bigint not null +References filepath ( filepath_id ) - - -study_usersTable qiita.study_users +<rect class='table' x='1800' y='128' width='90' height='75' rx='7' ry='7' /> +<path d='M 1800.50 154.50 L 1800.50 135.50 Q 1800.50 128.50 1807.50 128.50 L 1882.50 128.50 Q 1889.50 128.50 1889.50 135.50 L 1889.50 154.50 L1800.50 154.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#study_users'><text x='1812' y='142' class='tableTitle'>study_users</text><title>Table qiita.study_users Links shared studies to users they are shared with - Primary Key ( study_id, email ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, email ) Index ( email ) -emailemail varchar not null -References qiita_user ( email ) + Primary Key ( study_id, email ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, email ) Index ( email ) +emailemail varchar not null +References qiita_user ( email ) - - -studyTable qiita.study - Primary Key ( study_id ) -study_idstudy_id bigserial not null +<rect class='table' x='1815' y='248' width='180' height='375' rx='7' ry='7' /> +<path d='M 1815.50 274.50 L 1815.50 255.50 Q 1815.50 248.50 1822.50 248.50 L 1987.50 248.50 Q 1994.50 248.50 1994.50 255.50 L 1994.50 274.50 L1815.50 274.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#study'><text x='1890' y='262' class='tableTitle'>study</text><title>Table qiita.study + Primary Key ( study_id ) +study_idstudy_id bigserial not null Unique name for study -Referred by investigation_study ( study_id ) +<a xlink:href='#study.study_id'><use id='ref' x='1983' y='281' xlink:href='#ref'/><title>Referred by investigation_study ( study_id ) Referred by sample_template_filepath ( study_id ) Referred by study_artifact ( study_id ) Referred by study_environmental_package ( study_id ) @@ -1554,295 +1216,233 @@ Referred by study_publication ( study_id ) Referred by study_sample ( study_id ) Referred by study_users ( study_id ) - Index ( email ) -emailemail varchar not null + <use id='nn' x='1817' y='297' xlink:href='#nn'/><a xlink:href='#study.email'><use id='idx' x='1817' y='296' xlink:href='#idx'/><title>Index ( email ) +emailemail varchar not null Email of study owner -References qiita_user ( email ) - Index ( emp_person_id ) -emp_person_idemp_person_id bigint -References study_person ( emp_person_id -> study_person_id ) - first_contactfirst_contact timestamp not null default current_timestamp - fundingfunding varchar - Index ( timeseries_type_id ) -timeseries_type_idtimeseries_type_id bigint not null +<a xlink:href='#study.email'><use id='fk' x='1983' y='296' xlink:href='#fk'/><title>References qiita_user ( email ) + Index ( emp_person_id ) +emp_person_idemp_person_id bigint +References study_person ( emp_person_id -> study_person_id ) + first_contactfirst_contact timestamp not null default current_timestamp + fundingfunding varchar + Index ( timeseries_type_id ) +timeseries_type_idtimeseries_type_id bigint not null What type of timeseries this study is (or is not) Controlled Vocabulary -References timeseries_type ( timeseries_type_id ) - Index ( lab_person_id ) -lab_person_idlab_person_id bigint -References study_person ( lab_person_id -> study_person_id ) - metadata_completemetadata_complete bool not null - mixs_compliantmixs_compliant bool not null - most_recent_contactmost_recent_contact timestamp - number_samples_collectednumber_samples_collected integer - number_samples_promisednumber_samples_promised integer - Index ( principal_investigator_id ) -principal_investigator_idprincipal_investigator_id bigint not null -References study_person ( principal_investigator_id -> study_person_id ) - reprocessreprocess bool not null - spatial_seriesspatial_series bool - Unique Index ( study_title ) -study_titlestudy_title varchar not null - study_aliasstudy_alias varchar not null - study_descriptionstudy_description text not null - study_abstractstudy_abstract text not null - vamps_idvamps_id varchar - ebi_study_accessionebi_study_accession varchar - ebi_submission_statusebi_submission_status varchar not null default 'not_submitted' +References timeseries_type ( timeseries_type_id ) + Index ( lab_person_id ) +lab_person_idlab_person_id bigint +References study_person ( lab_person_id -> study_person_id ) + metadata_completemetadata_complete bool not null + mixs_compliantmixs_compliant bool not null + most_recent_contactmost_recent_contact timestamp + number_samples_collectednumber_samples_collected integer + number_samples_promisednumber_samples_promised integer + Index ( principal_investigator_id ) +principal_investigator_idprincipal_investigator_id bigint not null +References study_person ( principal_investigator_id -> study_person_id ) + reprocessreprocess bool not null + spatial_seriesspatial_series bool + Unique Index ( study_title ) +study_titlestudy_title varchar not null + study_aliasstudy_alias varchar not null + study_descriptionstudy_description text not null + study_abstractstudy_abstract text not null + vamps_idvamps_id varchar + ebi_study_accessionebi_study_accession varchar + ebi_submission_statusebi_submission_status varchar not null default 'not_submitted' - - -study_personTable qiita.study_person +<rect class='table' x='2100' y='188' width='120' height='135' rx='7' ry='7' /> +<path d='M 2100.50 214.50 L 2100.50 195.50 Q 2100.50 188.50 2107.50 188.50 L 2212.50 188.50 Q 2219.50 188.50 2219.50 195.50 L 2219.50 214.50 L2100.50 214.50 ' style='fill:url(#tableHeaderGradient1); stroke:none;' /> +<a xlink:href='#study_person'><text x='2123' y='202' class='tableTitle'>study_person</text><title>Table qiita.study_person Contact information for the various people involved in a study - Primary Key ( study_person_id ) -study_person_idstudy_person_id bigserial not null -Referred by investigation ( contact_person_id -> study_person_id ) + <use id='nn' x='2102' y='222' xlink:href='#nn'/><a xlink:href='#study_person.study_person_id'><use id='pk' x='2102' y='221' xlink:href='#pk'/><title>Primary Key ( study_person_id ) +study_person_idstudy_person_id bigserial not null +Referred by investigation ( contact_person_id -> study_person_id ) Referred by study ( emp_person_id -> study_person_id ) Referred by study ( lab_person_id -> study_person_id ) Referred by study ( principal_investigator_id -> study_person_id ) - Unique Index ( name, affiliation ) -namename varchar not null - emailemail varchar not null - Unique Index ( name, affiliation ) -affiliationaffiliation varchar not null + <use id='nn' x='2102' y='237' xlink:href='#nn'/><a xlink:href='#study_person.name'><use id='unq' x='2102' y='236' xlink:href='#unq'/><title>Unique Index ( name, affiliation ) +namename varchar not null + emailemail varchar not null + Unique Index ( name, affiliation ) +affiliationaffiliation varchar not null The institution with which this person is affiliated - addressaddress varchar(100) - phonephone varchar + addressaddress varchar(100) + phonephone varchar - - -study_experimental_factorTable qiita.study_experimental_factor +<rect class='table' x='2100' y='443' width='165' height='75' rx='7' ry='7' /> +<path d='M 2100.50 469.50 L 2100.50 450.50 Q 2100.50 443.50 2107.50 443.50 L 2257.50 443.50 Q 2264.50 443.50 2264.50 450.50 L 2264.50 469.50 L2100.50 469.50 ' style='fill:url(#tableHeaderGradient1); stroke:none;' /> +<a xlink:href='#study_experimental_factor'><text x='2110' y='457' class='tableTitle'>study_experimental_factor</text><title>Table qiita.study_experimental_factor EFO ontological link of experimental factors to studies - Primary Key ( study_id, efo_id ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, efo_id ) -efo_idefo_id bigint not null + Primary Key ( study_id, efo_id ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, efo_id ) +efo_idefo_id bigint not null - - -study_environmental_packageTable qiita.study_environmental_package +<rect class='table' x='2040' y='83' width='195' height='75' rx='7' ry='7' /> +<path d='M 2040.50 109.50 L 2040.50 90.50 Q 2040.50 83.50 2047.50 83.50 L 2227.50 83.50 Q 2234.50 83.50 2234.50 90.50 L 2234.50 109.50 L2040.50 109.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#study_environmental_package'><text x='2056' y='97' class='tableTitle'>study_environmental_package</text><title>Table qiita.study_environmental_package Holds the 1 to many relationship between the study and the environmental_package - Primary Key ( study_id, environmental_package_name ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, environmental_package_name ) Index ( environmental_package_name ) -environmental_package_nameenvironmental_package_name varchar not null -References environmental_package ( environmental_package_name ) + Primary Key ( study_id, environmental_package_name ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, environmental_package_name ) Index ( environmental_package_name ) +environmental_package_nameenvironmental_package_name varchar not null +References environmental_package ( environmental_package_name ) - - -environmental_packageTable qiita.environmental_package - Primary Key ( environmental_package_name ) -environmental_package_nameenvironmental_package_name varchar not null +<rect class='table' x='2310' y='83' width='195' height='75' rx='7' ry='7' /> +<path d='M 2310.50 109.50 L 2310.50 90.50 Q 2310.50 83.50 2317.50 83.50 L 2497.50 83.50 Q 2504.50 83.50 2504.50 90.50 L 2504.50 109.50 L2310.50 109.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#environmental_package'><text x='2344' y='97' class='tableTitle'>environmental_package</text><title>Table qiita.environmental_package + Primary Key ( environmental_package_name ) +environmental_package_nameenvironmental_package_name varchar not null The name of the environmental package -Referred by study_environmental_package ( environmental_package_name ) - metadata_tablemetadata_table varchar not null +<a xlink:href='#environmental_package.environmental_package_name'><use id='ref' x='2493' y='116' xlink:href='#ref'/><title>Referred by study_environmental_package ( environmental_package_name ) + metadata_tablemetadata_table varchar not null Contains the name of the table that contains the pre-defined metadata columns for the environmental package - - -investigation_studyTable qiita.investigation_study - Primary Key ( investigation_id, study_id ) Index ( investigation_id ) -investigation_idinvestigation_id bigint not null -References investigation ( investigation_id ) - Primary Key ( investigation_id, study_id ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) + + +investigation_studyTable qiita.investigation_study + Primary Key ( investigation_id, study_id ) Index ( investigation_id ) +investigation_idinvestigation_id bigint not null +References investigation ( investigation_id ) + Primary Key ( investigation_id, study_id ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) - - -investigationTable qiita.investigation +<rect class='table' x='2310' y='293' width='165' height='105' rx='7' ry='7' /> +<path d='M 2310.50 319.50 L 2310.50 300.50 Q 2310.50 293.50 2317.50 293.50 L 2467.50 293.50 Q 2474.50 293.50 2474.50 300.50 L 2474.50 319.50 L2310.50 319.50 ' style='fill:url(#tableHeaderGradient1); stroke:none;' /> +<a xlink:href='#investigation'><text x='2358' y='307' class='tableTitle'>investigation</text><title>Table qiita.investigation Overarching investigation information.An investigation comprises one or more individual studies. - Primary Key ( investigation_id ) -investigation_idinvestigation_id bigserial not null -Referred by investigation_study ( investigation_id ) - investigation_nameinvestigation_name varchar not null - investigation_descriptioninvestigation_description varchar not null + <use id='nn' x='2312' y='327' xlink:href='#nn'/><a xlink:href='#investigation.investigation_id'><use id='pk' x='2312' y='326' xlink:href='#pk'/><title>Primary Key ( investigation_id ) +investigation_idinvestigation_id bigserial not null +Referred by investigation_study ( investigation_id ) + investigation_nameinvestigation_name varchar not null + investigation_descriptioninvestigation_description varchar not null Describes the overarching goal of the investigation - Index ( contact_person_id ) -contact_person_idcontact_person_id bigint -References study_person ( contact_person_id -> study_person_id ) + Index ( contact_person_id ) +contact_person_idcontact_person_id bigint +References study_person ( contact_person_id -> study_person_id ) - - -study_portalTable qiita.study_portal +<rect class='table' x='1875' y='38' width='120' height='75' rx='7' ry='7' /> +<path d='M 1875.50 64.50 L 1875.50 45.50 Q 1875.50 38.50 1882.50 38.50 L 1987.50 38.50 Q 1994.50 38.50 1994.50 45.50 L 1994.50 64.50 L1875.50 64.50 ' style='fill:url(#tableHeaderGradient3); stroke:none;' /> +<a xlink:href='#study_portal'><text x='1901' y='52' class='tableTitle'>study_portal</text><title>Table qiita.study_portal Controls what studies are visible on what portals - Primary Key ( study_id, portal_type_id ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, portal_type_id ) Index ( portal_type_id ) -portal_type_idportal_type_id bigint not null -References portal_type ( portal_type_id ) - - - - -data_typeTable qiita.data_type - Primary Key ( data_type_id ) -data_type_iddata_type_id bigserial not null -Referred by analysis_filepath ( data_type_id ) -Referred by artifact ( data_type_id ) -Referred by command_data_type ( data_type_id ) -Referred by job ( data_type_id ) -Referred by prep_template ( data_type_id ) - Unique Index ( data_type ) -data_typedata_type varchar not null -Data type (16S, metabolome, etc) the job will use + Primary Key ( study_id, portal_type_id ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, portal_type_id ) Index ( portal_type_id ) +portal_type_idportal_type_id bigint not null +References portal_type ( portal_type_id ) - - -software_publicationTable qiita.software_publication - Index ( software_id ) Primary Key ( software_id, publication_doi ) -software_idsoftware_id bigint not null -References software ( software_id ) - Index ( publication_doi ) Primary Key ( software_id, publication_doi ) -publication_doipublication_doi varchar not null -References publication ( publication_doi -> doi ) + + +software_publicationTable qiita.software_publication + Index ( software_id ) Primary Key ( software_id, publication_doi ) +software_idsoftware_id bigint not null +References software ( software_id ) + Index ( publication_doi ) Primary Key ( software_id, publication_doi ) +publication_doipublication_doi varchar not null +References publication ( publication_doi -> doi ) - - -visibilityTable qiita.visibility - Primary Key ( visibility_id ) -visibility_idvisibility_id bigserial not null -Referred by artifact ( visibility_id ) - Unique Index ( visibility ) -visibilityvisibility varchar not null - visibility_descriptionvisibility_description varchar not null + + +visibilityTable qiita.visibility + Primary Key ( visibility_id ) +visibility_idvisibility_id bigserial not null +Referred by artifact ( visibility_id ) + Unique Index ( visibility ) +visibilityvisibility varchar not null + visibility_descriptionvisibility_description varchar not null - - -study_sampleTable qiita.study_sample +<rect class='table' x='1365' y='83' width='150' height='105' rx='7' ry='7' /> +<path d='M 1365.50 109.50 L 1365.50 90.50 Q 1365.50 83.50 1372.50 83.50 L 1507.50 83.50 Q 1514.50 83.50 1514.50 90.50 L 1514.50 109.50 L1365.50 109.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#study_sample'><text x='1403' y='97' class='tableTitle'>study_sample</text><title>Table qiita.study_sample Required info for each sample. One row is one sample. - Primary Key ( sample_id ) -sample_idsample_id varchar not null -Referred by analysis_sample ( sample_id ) + <use id='nn' x='1367' y='117' xlink:href='#nn'/><a xlink:href='#study_sample.sample_id'><use id='pk' x='1367' y='116' xlink:href='#pk'/><title>Primary Key ( sample_id ) +sample_idsample_id varchar not null +Referred by analysis_sample ( sample_id ) Referred by ebi_run_accession ( sample_id ) - Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - ebi_sample_accessionebi_sample_accession varchar - biosample_accessionbiosample_accession varchar + Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + ebi_sample_accessionebi_sample_accession varchar + biosample_accessionbiosample_accession varchar - - -publicationTable qiita.publication - Primary Key ( doi ) -doidoi varchar not null -Referred by software_publication ( publication_doi -> doi ) - pubmed_idpubmed_id varchar + + +publicationTable qiita.publication + Primary Key ( doi ) +doidoi varchar not null +Referred by software_publication ( publication_doi -> doi ) + pubmed_idpubmed_id varchar - - -timeseries_typeTable qiita.timeseries_type - Primary Key ( timeseries_type_id ) -timeseries_type_idtimeseries_type_id bigserial not null -Referred by study ( timeseries_type_id ) - Unique Index ( timeseries_type, intervention_type ) -timeseries_typetimeseries_type varchar not null - Unique Index ( timeseries_type, intervention_type ) -intervention_typeintervention_type varchar not null default 'None' + + +timeseries_typeTable qiita.timeseries_type + Primary Key ( timeseries_type_id ) +timeseries_type_idtimeseries_type_id bigserial not null +Referred by study ( timeseries_type_id ) + Unique Index ( timeseries_type, intervention_type ) +timeseries_typetimeseries_type varchar not null + Unique Index ( timeseries_type, intervention_type ) +intervention_typeintervention_type varchar not null default 'None' - - -oauth_identifiersTable qiita.oauth_identifiers - Primary Key ( client_id ) -client_idclient_id varchar(50) not null -Referred by oauth_software ( client_id ) - client_secretclient_secret varchar(255) + + +oauth_identifiersTable qiita.oauth_identifiers + Primary Key ( client_id ) +client_idclient_id varchar(50) not null +Referred by oauth_software ( client_id ) + client_secretclient_secret varchar(255) - - -oauth_softwareTable qiita.oauth_software - Primary Key ( software_id, client_id ) Index ( software_id ) -software_idsoftware_id bigint not null -References software ( software_id ) - Primary Key ( software_id, client_id ) Index ( client_id ) -client_idclient_id bigint not null -References oauth_identifiers ( client_id ) - - - - -analysisTable qiita.analysis -Holds analysis information - Primary Key ( analysis_id ) -analysis_idanalysis_id bigserial not null -Unique identifier for analysis -Referred by analysis_chain ( parent_id -> analysis_id ) -Referred by analysis_chain ( child_id -> analysis_id ) -Referred by analysis_filepath ( analysis_id ) -Referred by analysis_job ( analysis_id ) -Referred by analysis_portal ( analysis_id ) -Referred by analysis_sample ( analysis_id ) -Referred by analysis_users ( analysis_id ) -Referred by analysis_workflow ( analysis_id ) -Referred by collection_analysis ( analysis_id ) - Index ( email ) -emailemail varchar not null -Email for user who owns the analysis -References qiita_user ( email ) - namename varchar not null -Name of the analysis - descriptiondescription varchar not null - Index ( analysis_status_id ) -analysis_status_idanalysis_status_id bigint not null -References analysis_status ( analysis_status_id ) - pmidpmid varchar -PMID of paper from the analysis - timestamptimestamp timestamptz default current_timestamp - dfltdflt bool not null default false - Index ( portal_type_id ) -portal_type_idportal_type_id bigint not null -References portal_type ( portal_type_id ) - - - - -analysis_chainTable qiita.analysis_chain -Keeps track of the chain of analysis edits. Tracks what previous analysis a given analysis came from.If a given analysis is not in child_id, it is the root of the chain. - Index ( parent_id ) Primary Key ( parent_id, child_id ) -parent_idparent_id bigint not null -References analysis ( parent_id -> analysis_id ) - Index ( child_id ) Primary Key ( parent_id, child_id ) -child_idchild_id bigint not null -References analysis ( child_id -> analysis_id ) + + +oauth_softwareTable qiita.oauth_software + Primary Key ( software_id, client_id ) Index ( software_id ) +software_idsoftware_id bigint not null +References software ( software_id ) + Primary Key ( software_id, client_id ) Index ( client_id ) +client_idclient_id bigint not null +References oauth_identifiers ( client_id ) - - -ebi_run_accessionTable qiita.ebi_run_accession - Primary Key ( sample_id, artifact_id, ebi_run_accession ) Index ( sample_id ) -sample_idsample_id varchar not null -References study_sample ( sample_id ) - Primary Key ( sample_id, artifact_id, ebi_run_accession ) Index ( artifact_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) - Primary Key ( sample_id, artifact_id, ebi_run_accession ) -ebi_run_accessionebi_run_accession bigint not null + + +ebi_run_accessionTable qiita.ebi_run_accession + Primary Key ( sample_id, artifact_id, ebi_run_accession ) Index ( sample_id ) +sample_idsample_id varchar not null +References study_sample ( sample_id ) + Primary Key ( sample_id, artifact_id, ebi_run_accession ) Index ( artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + Primary Key ( sample_id, artifact_id, ebi_run_accession ) +ebi_run_accessionebi_run_accession bigint not null - - -artifactTable qiita.artifact +<rect class='table' x='1200' y='923' width='150' height='180' rx='7' ry='7' /> +<path d='M 1200.50 949.50 L 1200.50 930.50 Q 1200.50 923.50 1207.50 923.50 L 1342.50 923.50 Q 1349.50 923.50 1349.50 930.50 L 1349.50 949.50 L1200.50 949.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#artifact'><text x='1256' y='937' class='tableTitle'>artifact</text><title>Table qiita.artifact Represents data in the system - Primary Key ( artifact_id ) -artifact_idartifact_id bigserial not null -Referred by analysis_sample ( artifact_id ) + <use id='nn' x='1202' y='957' xlink:href='#nn'/><a xlink:href='#artifact.artifact_id'><use id='pk' x='1202' y='956' xlink:href='#pk'/><title>Primary Key ( artifact_id ) +artifact_idartifact_id bigserial not null +Referred by analysis_sample ( artifact_id ) Referred by artifact_filepath ( artifact_id ) Referred by artifact_output_processing_job ( artifact_id ) Referred by artifact_processing_job ( artifact_id ) @@ -1850,399 +1450,364 @@ Referred by parent_artifact ( artifact_id ) Referred by parent_artifact ( parent_id -> artifact_id ) Referred by prep_template ( artifact_id ) -Referred by study_artifact ( artifact_id ) - namename varchar(35) not null - generated_timestampgenerated_timestamp timestamp not null - Index ( command_id ) -command_idcommand_id bigint -References software_command ( command_id ) - command_parameterscommand_parameters varchar +Referred by study_artifact ( artifact_id ) +Referred by analysis_artifact ( artifact_id ) + namename varchar(35) not null + generated_timestampgenerated_timestamp timestamp not null + Index ( command_id ) +command_idcommand_id bigint +References software_command ( command_id ) + command_parameterscommand_parameters varchar Varchar in dbschema but is JSON in the postgresDB - Index ( visibility_id ) -visibility_idvisibility_id bigint not null + <use id='nn' x='1202' y='1032' xlink:href='#nn'/><a xlink:href='#artifact.visibility_id'><use id='idx' x='1202' y='1031' xlink:href='#idx'/><title>Index ( visibility_id ) +visibility_idvisibility_id bigint not null If the artifact is sandbox, awaiting_for_approval, private or public -References visibility ( visibility_id ) - Index ( artifact_type_id ) -artifact_type_idartifact_type_id integer -References artifact_type ( artifact_type_id ) - Index ( data_type_id ) -data_type_iddata_type_id bigint not null -References data_type ( data_type_id ) - submitted_to_vampssubmitted_to_vamps bool not null default 'FALSE' +References visibility ( visibility_id ) + Index ( artifact_type_id ) +artifact_type_idartifact_type_id integer +References artifact_type ( artifact_type_id ) + Index ( data_type_id ) +data_type_iddata_type_id bigint not null +References data_type ( data_type_id ) + submitted_to_vampssubmitted_to_vamps bool not null default 'FALSE' - - -artifact_typeTable qiita.artifact_type +<rect class='table' x='1410' y='923' width='195' height='120' rx='7' ry='7' /> +<path d='M 1410.50 949.50 L 1410.50 930.50 Q 1410.50 923.50 1417.50 923.50 L 1597.50 923.50 Q 1604.50 923.50 1604.50 930.50 L 1604.50 949.50 L1410.50 949.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#artifact_type'><text x='1474' y='937' class='tableTitle'>artifact_type</text><title>Table qiita.artifact_type Type of artifact - Primary Key ( artifact_type_id ) -artifact_type_idartifact_type_id bigserial not null -Referred by artifact ( artifact_type_id ) + <use id='nn' x='1412' y='957' xlink:href='#nn'/><a xlink:href='#artifact_type.artifact_type_id'><use id='pk' x='1412' y='956' xlink:href='#pk'/><title>Primary Key ( artifact_type_id ) +artifact_type_idartifact_type_id bigserial not null +Referred by artifact ( artifact_type_id ) Referred by artifact_type_filepath_type ( artifact_type_id ) Referred by command_output ( artifact_type_id ) Referred by parameter_artifact_type ( artifact_type_id ) Referred by software_artifact_type ( artifact_type_id ) - Unique Index ( artifact_type ) -artifact_typeartifact_type varchar not null - descriptiondescription varchar not null - can_be_submitted_to_ebican_be_submitted_to_ebi bool not null default 'FALSE' - can_be_submitted_to_vampscan_be_submitted_to_vamps bool not null default 'FALSE' + Unique Index ( artifact_type ) +artifact_typeartifact_type varchar not null + descriptiondescription varchar not null + can_be_submitted_to_ebican_be_submitted_to_ebi bool not null default 'FALSE' + can_be_submitted_to_vampscan_be_submitted_to_vamps bool not null default 'FALSE' - - -filepath_typeTable qiita.filepath_type - Primary Key ( filepath_type_id ) -filepath_type_idfilepath_type_id bigserial not null -Referred by artifact_type_filepath_type ( filepath_type_id ) +<rect class='table' x='855' y='683' width='120' height='75' rx='7' ry='7' /> +<path d='M 855.50 709.50 L 855.50 690.50 Q 855.50 683.50 862.50 683.50 L 967.50 683.50 Q 974.50 683.50 974.50 690.50 L 974.50 709.50 L855.50 709.50 ' style='fill:url(#tableHeaderGradient1); stroke:none;' /> +<a xlink:href='#filepath_type'><text x='880' y='697' class='tableTitle'>filepath_type</text><title>Table qiita.filepath_type + Primary Key ( filepath_type_id ) +filepath_type_idfilepath_type_id bigserial not null +Referred by artifact_type_filepath_type ( filepath_type_id ) Referred by filepath ( filepath_type_id ) - Unique Index ( filepath_type ) -filepath_typefilepath_type varchar + Unique Index ( filepath_type ) +filepath_typefilepath_type varchar - - -data_directoryTable qiita.data_directory - Primary Key ( data_directory_id ) -data_directory_iddata_directory_id bigserial not null -Referred by filepath ( data_directory_id ) - data_typedata_type varchar not null - mountpointmountpoint varchar not null - subdirectorysubdirectory bool not null default FALSE - activeactive bool not null + + +data_directoryTable qiita.data_directory + Primary Key ( data_directory_id ) +data_directory_iddata_directory_id bigserial not null +Referred by filepath ( data_directory_id ) + data_typedata_type varchar not null + mountpointmountpoint varchar not null + subdirectorysubdirectory bool not null default FALSE + activeactive bool not null - - -artifact_type_filepath_typeTable qiita.artifact_type_filepath_type - Primary Key ( artifact_type_id, filepath_type_id ) Index ( artifact_type_id ) -artifact_type_idartifact_type_id bigint not null -References artifact_type ( artifact_type_id ) - Primary Key ( artifact_type_id, filepath_type_id ) Index ( filepath_type_id ) -filepath_type_idfilepath_type_id bigint not null -References filepath_type ( filepath_type_id ) - requiredrequired bool not null default 'TRUE' + + +artifact_type_filepath_typeTable qiita.artifact_type_filepath_type + Primary Key ( artifact_type_id, filepath_type_id ) Index ( artifact_type_id ) +artifact_type_idartifact_type_id bigint not null +References artifact_type ( artifact_type_id ) + Primary Key ( artifact_type_id, filepath_type_id ) Index ( filepath_type_id ) +filepath_type_idfilepath_type_id bigint not null +References filepath_type ( filepath_type_id ) + requiredrequired bool not null default 'TRUE' - - -study_artifactTable qiita.study_artifact - Primary Key ( study_id, artifact_id ) Index ( study_id ) -study_idstudy_id bigint not null -References study ( study_id ) - Primary Key ( study_id, artifact_id ) Index ( artifact_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) + + +study_artifactTable qiita.study_artifact + Primary Key ( study_id, artifact_id ) Index ( study_id ) +study_idstudy_id bigint not null +References study ( study_id ) + Primary Key ( study_id, artifact_id ) Index ( artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) - - -command_parameterTable qiita.command_parameter - Primary Key ( command_parameter_id ) -command_parameter_idcommand_parameter_id bigserial not null -Referred by default_workflow_edge_connections ( child_input_id -> command_parameter_id ) +<rect class='table' x='645' y='1433' width='165' height='135' rx='7' ry='7' /> +<path d='M 645.50 1459.50 L 645.50 1440.50 Q 645.50 1433.50 652.50 1433.50 L 802.50 1433.50 Q 809.50 1433.50 809.50 1440.50 L 809.50 1459.50 L645.50 1459.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#command_parameter'><text x='670' y='1447' class='tableTitle'>command_parameter</text><title>Table qiita.command_parameter + Primary Key ( command_parameter_id ) +command_parameter_idcommand_parameter_id bigserial not null +Referred by default_workflow_edge_connections ( child_input_id -> command_parameter_id ) Referred by parameter_artifact_type ( command_parameter_id ) - Index ( command_id ) Unique Index ( command_id, parameter_name ) -command_idcommand_id bigint not null -References software_command ( command_id ) - Unique Index ( command_id, parameter_name ) -parameter_nameparameter_name varchar not null - parameter_typeparameter_type varchar not null - requiredrequired bool not null - default_valuedefault_value varchar + Index ( command_id ) Unique Index ( command_id, parameter_name ) +command_idcommand_id bigint not null +References software_command ( command_id ) + Unique Index ( command_id, parameter_name ) +parameter_nameparameter_name varchar not null + parameter_typeparameter_type varchar not null + requiredrequired bool not null + default_valuedefault_value varchar - - -parameter_artifact_typeTable qiita.parameter_artifact_type - Primary Key ( command_parameter_id, artifact_type_id ) Index ( command_parameter_id ) -command_parameter_idcommand_parameter_id bigserial not null -References command_parameter ( command_parameter_id ) - Primary Key ( command_parameter_id, artifact_type_id ) Index ( artifact_type_id ) -artifact_type_idartifact_type_id bigint not null -References artifact_type ( artifact_type_id ) + + +parameter_artifact_typeTable qiita.parameter_artifact_type + Primary Key ( command_parameter_id, artifact_type_id ) Index ( command_parameter_id ) +command_parameter_idcommand_parameter_id bigserial not null +References command_parameter ( command_parameter_id ) + Primary Key ( command_parameter_id, artifact_type_id ) Index ( artifact_type_id ) +artifact_type_idartifact_type_id bigint not null +References artifact_type ( artifact_type_id ) - - -command_outputTable qiita.command_output - Primary Key ( command_output_id ) -command_output_idcommand_output_id bigserial not null -Referred by artifact_output_processing_job ( command_output_id ) +<rect class='table' x='465' y='1553' width='150' height='105' rx='7' ry='7' /> +<path d='M 465.50 1579.50 L 465.50 1560.50 Q 465.50 1553.50 472.50 1553.50 L 607.50 1553.50 Q 614.50 1553.50 614.50 1560.50 L 614.50 1579.50 L465.50 1579.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#command_output'><text x='492' y='1567' class='tableTitle'>command_output</text><title>Table qiita.command_output + Primary Key ( command_output_id ) +command_output_idcommand_output_id bigserial not null +Referred by artifact_output_processing_job ( command_output_id ) Referred by default_workflow_edge_connections ( parent_output_id -> command_output_id ) - Unique Index ( name, command_id ) -namename varchar not null - Unique Index ( name, command_id ) Index ( command_id ) -command_idcommand_id bigint not null -References software_command ( command_id ) - Index ( artifact_type_id ) -artifact_type_idartifact_type_id bigint not null -References artifact_type ( artifact_type_id ) + Unique Index ( name, command_id ) +namename varchar not null + Unique Index ( name, command_id ) Index ( command_id ) +command_idcommand_id bigint not null +References software_command ( command_id ) + Index ( artifact_type_id ) +artifact_type_idartifact_type_id bigint not null +References artifact_type ( artifact_type_id ) - - -default_parameter_setTable qiita.default_parameter_set - Primary Key ( default_parameter_set_id ) -default_parameter_set_iddefault_parameter_set_id bigserial not null -Referred by default_workflow_node ( default_parameter_set_id ) - Index ( command_id ) Unique Index ( command_id, parameter_set_name ) -command_idcommand_id bigint not null -References software_command ( command_id ) - Unique Index ( command_id, parameter_set_name ) -parameter_set_nameparameter_set_name varchar not null - parameter_setparameter_set varchar not null +<rect class='table' x='930' y='1553' width='165' height='105' rx='7' ry='7' /> +<path d='M 930.50 1579.50 L 930.50 1560.50 Q 930.50 1553.50 937.50 1553.50 L 1087.50 1553.50 Q 1094.50 1553.50 1094.50 1560.50 L 1094.50 1579.50 L930.50 1579.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#default_parameter_set'><text x='952' y='1567' class='tableTitle'>default_parameter_set</text><title>Table qiita.default_parameter_set + Primary Key ( default_parameter_set_id ) +default_parameter_set_iddefault_parameter_set_id bigserial not null +Referred by default_workflow_node ( default_parameter_set_id ) + Index ( command_id ) Unique Index ( command_id, parameter_set_name ) +command_idcommand_id bigint not null +References software_command ( command_id ) + Unique Index ( command_id, parameter_set_name ) +parameter_set_nameparameter_set_name varchar not null + parameter_setparameter_set varchar not null This is a varchar here - but is of type JSON in postgresql. - - -default_workflowTable qiita.default_workflow - Primary Key ( default_workflow_id ) -default_workflow_iddefault_workflow_id bigserial not null -Referred by default_workflow_node ( default_workflow_id ) - Unique Index ( software_id, name ) Index ( software_id ) -software_idsoftware_id bigint not null -References software ( software_id ) - Unique Index ( software_id, name ) -namename varchar not null + + +default_workflowTable qiita.default_workflow + Primary Key ( default_workflow_id ) +default_workflow_iddefault_workflow_id bigserial not null +Referred by default_workflow_node ( default_workflow_id ) + Unique Index ( software_id, name ) Index ( software_id ) +software_idsoftware_id bigint not null +References software ( software_id ) + Unique Index ( software_id, name ) +namename varchar not null - - -default_workflow_nodeTable qiita.default_workflow_node - Primary Key ( default_workflow_node_id ) -default_workflow_node_iddefault_workflow_node_id bigserial not null -Referred by default_workflow_edge ( parent_id -> default_workflow_node_id ) +<rect class='table' x='930' y='1703' width='180' height='105' rx='7' ry='7' /> +<path d='M 930.50 1729.50 L 930.50 1710.50 Q 930.50 1703.50 937.50 1703.50 L 1102.50 1703.50 Q 1109.50 1703.50 1109.50 1710.50 L 1109.50 1729.50 L930.50 1729.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#default_workflow_node'><text x='958' y='1717' class='tableTitle'>default_workflow_node</text><title>Table qiita.default_workflow_node + Primary Key ( default_workflow_node_id ) +default_workflow_node_iddefault_workflow_node_id bigserial not null +Referred by default_workflow_edge ( parent_id -> default_workflow_node_id ) Referred by default_workflow_edge ( child_id -> default_workflow_node_id ) - Index ( default_workflow_id ) -default_workflow_iddefault_workflow_id bigint not null -References default_workflow ( default_workflow_id ) - Index ( command_id ) -command_idcommand_id bigint not null -References software_command ( command_id ) - Index ( default_parameter_set_id ) -default_parameter_set_iddefault_parameter_set_id bigint not null -References default_parameter_set ( default_parameter_set_id ) + Index ( default_workflow_id ) +default_workflow_iddefault_workflow_id bigint not null +References default_workflow ( default_workflow_id ) + Index ( command_id ) +command_idcommand_id bigint not null +References software_command ( command_id ) + Index ( default_parameter_set_id ) +default_parameter_set_iddefault_parameter_set_id bigint not null +References default_parameter_set ( default_parameter_set_id ) - - -default_workflow_edgeTable qiita.default_workflow_edge - Primary Key ( default_workflow_edge_id ) -default_workflow_edge_iddefault_workflow_edge_id bigserial not null -Referred by default_workflow_edge_connections ( default_workflow_edge_id ) - Index ( parent_id ) -parent_idparent_id bigint not null -References default_workflow_node ( parent_id -> default_workflow_node_id ) - Index ( child_id ) -child_idchild_id bigint not null -References default_workflow_node ( child_id -> default_workflow_node_id ) + + +default_workflow_edgeTable qiita.default_workflow_edge + Primary Key ( default_workflow_edge_id ) +default_workflow_edge_iddefault_workflow_edge_id bigserial not null +Referred by default_workflow_edge_connections ( default_workflow_edge_id ) + Index ( parent_id ) +parent_idparent_id bigint not null +References default_workflow_node ( parent_id -> default_workflow_node_id ) + Index ( child_id ) +child_idchild_id bigint not null +References default_workflow_node ( child_id -> default_workflow_node_id ) - - -artifact_processing_jobTable qiita.artifact_processing_job - Primary Key ( artifact_id, processing_job_id ) Index ( artifact_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) - Primary Key ( artifact_id, processing_job_id ) Index ( processing_job_id ) -processing_job_idprocessing_job_id bigint not null -References processing_job ( processing_job_id ) + + +artifact_processing_jobTable qiita.artifact_processing_job + Primary Key ( artifact_id, processing_job_id ) Index ( artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + Primary Key ( artifact_id, processing_job_id ) Index ( processing_job_id ) +processing_job_idprocessing_job_id bigint not null +References processing_job ( processing_job_id ) - - -artifact_output_processing_jobTable qiita.artifact_output_processing_job - Index ( artifact_id ) -artifact_idartifact_id bigint not null -References artifact ( artifact_id ) - Index ( processing_job_id ) -processing_job_idprocessing_job_id bigint not null -References processing_job ( processing_job_id ) - Index ( command_output_id ) -command_output_idcommand_output_id bigint not null -References command_output ( command_output_id ) + + +artifact_output_processing_jobTable qiita.artifact_output_processing_job + Index ( artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + Index ( processing_job_id ) +processing_job_idprocessing_job_id bigint not null +References processing_job ( processing_job_id ) + Index ( command_output_id ) +command_output_idcommand_output_id bigint not null +References command_output ( command_output_id ) - - -referenceTable qiita.reference - Primary Key ( reference_id ) -reference_idreference_id bigserial not null -Referred by job ( input_file_reference_id -> reference_id ) - reference_namereference_name varchar not null - reference_versionreference_version varchar - Index ( sequence_filepath ) -sequence_filepathsequence_filepath bigint not null -References filepath ( sequence_filepath -> filepath_id ) - Index ( taxonomy_filepath ) -taxonomy_filepathtaxonomy_filepath bigint -References filepath ( taxonomy_filepath -> filepath_id ) - Index ( tree_filepath ) -tree_filepathtree_filepath bigint -References filepath ( tree_filepath -> filepath_id ) + + +referenceTable qiita.reference + Primary Key ( reference_id ) +reference_idreference_id bigserial not null + reference_namereference_name varchar not null + reference_versionreference_version varchar + Index ( sequence_filepath ) +sequence_filepathsequence_filepath bigint not null +References filepath ( sequence_filepath -> filepath_id ) + Index ( taxonomy_filepath ) +taxonomy_filepathtaxonomy_filepath bigint +References filepath ( taxonomy_filepath -> filepath_id ) + Index ( tree_filepath ) +tree_filepathtree_filepath bigint +References filepath ( tree_filepath -> filepath_id ) - - -processing_job_statusTable qiita.processing_job_status - Primary Key ( processing_job_status_id ) -processing_job_status_idprocessing_job_status_id bigserial not null -Referred by processing_job ( processing_job_status_id ) - processing_job_statusprocessing_job_status varchar not null - processing_job_status_descriptionprocessing_job_status_description varchar not null + + +processing_job_statusTable qiita.processing_job_status + Primary Key ( processing_job_status_id ) +processing_job_status_idprocessing_job_status_id bigserial not null +Referred by processing_job ( processing_job_status_id ) + processing_job_statusprocessing_job_status varchar not null + processing_job_status_descriptionprocessing_job_status_description varchar not null - - -parent_processing_jobTable qiita.parent_processing_job - Primary Key ( parent_id, child_id ) Index ( parent_id ) -parent_idparent_id bigint not null -References processing_job ( parent_id -> processing_job_id ) - Primary Key ( parent_id, child_id ) Index ( child_id ) -child_idchild_id bigint not null -References processing_job ( child_id -> processing_job_id ) + + +parent_processing_jobTable qiita.parent_processing_job + Primary Key ( parent_id, child_id ) Index ( parent_id ) +parent_idparent_id bigint not null +References processing_job ( parent_id -> processing_job_id ) + Primary Key ( parent_id, child_id ) Index ( child_id ) +child_idchild_id bigint not null +References processing_job ( child_id -> processing_job_id ) - - -processing_job_workflowTable qiita.processing_job_workflow - Primary Key ( processing_job_workflow_id ) -processing_job_workflow_idprocessing_job_workflow_id bigserial not null -Referred by processing_job_workflow_root ( processing_job_workflow_id ) - Index ( email ) -emailemail varchar not null -References qiita_user ( email ) - namename varchar + + +processing_job_workflowTable qiita.processing_job_workflow + Primary Key ( processing_job_workflow_id ) +processing_job_workflow_idprocessing_job_workflow_id bigserial not null +Referred by processing_job_workflow_root ( processing_job_workflow_id ) + Index ( email ) +emailemail varchar not null +References qiita_user ( email ) + namename varchar - - -processing_job_workflow_rootTable qiita.processing_job_workflow_root - Index ( processing_job_workflow_id ) Primary Key ( processing_job_workflow_id, processing_job_id ) -processing_job_workflow_idprocessing_job_workflow_id bigint not null -References processing_job_workflow ( processing_job_workflow_id ) - Index ( processing_job_id ) Primary Key ( processing_job_workflow_id, processing_job_id ) -processing_job_idprocessing_job_id bigint not null -References processing_job ( processing_job_id ) + + +processing_job_workflow_rootTable qiita.processing_job_workflow_root + Index ( processing_job_workflow_id ) Primary Key ( processing_job_workflow_id, processing_job_id ) +processing_job_workflow_idprocessing_job_workflow_id bigint not null +References processing_job_workflow ( processing_job_workflow_id ) + Index ( processing_job_id ) Primary Key ( processing_job_workflow_id, processing_job_id ) +processing_job_idprocessing_job_id bigint not null +References processing_job ( processing_job_id ) - - -prep_template_processing_jobTable qiita.prep_template_processing_job - Primary Key ( prep_template_id, processing_job_id ) Index ( prep_template_id ) -prep_template_idprep_template_id bigint not null -References prep_template ( prep_template_id ) - Primary Key ( prep_template_id, processing_job_id ) Index ( processing_job_id ) -processing_job_idprocessing_job_id bigint not null -References processing_job ( processing_job_id ) + + +prep_template_processing_jobTable qiita.prep_template_processing_job + Primary Key ( prep_template_id, processing_job_id ) Index ( prep_template_id ) +prep_template_idprep_template_id bigint not null +References prep_template ( prep_template_id ) + Primary Key ( prep_template_id, processing_job_id ) Index ( processing_job_id ) +processing_job_idprocessing_job_id bigint not null +References processing_job ( processing_job_id ) - - -software_artifact_typeTable qiita.software_artifact_type +<rect class='table' x='570' y='1343' width='135' height='75' rx='7' ry='7' /> +<path d='M 570.50 1369.50 L 570.50 1350.50 Q 570.50 1343.50 577.50 1343.50 L 697.50 1343.50 Q 704.50 1343.50 704.50 1350.50 L 704.50 1369.50 L570.50 1369.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#software_artifact_type'><text x='578' y='1357' class='tableTitle'>software_artifact_type</text><title>Table qiita.software_artifact_type In case that the software is of type "type plugin", it holds the artifact types that such software can validate and generate the summary. - Primary Key ( software_id, artifact_type_id ) Index ( software_id ) -software_idsoftware_id bigint not null -References software ( software_id ) - Primary Key ( software_id, artifact_type_id ) Index ( artifact_type_id ) -artifact_type_idartifact_type_id bigint not null -References artifact_type ( artifact_type_id ) + Primary Key ( software_id, artifact_type_id ) Index ( software_id ) +software_idsoftware_id bigint not null +References software ( software_id ) + Primary Key ( software_id, artifact_type_id ) Index ( artifact_type_id ) +artifact_type_idartifact_type_id bigint not null +References artifact_type ( artifact_type_id ) - - -software_typeTable qiita.software_type - Primary Key ( software_type_id ) -software_type_idsoftware_type_id bigserial not null -Referred by software ( software_type_id ) - software_typesoftware_type varchar not null - descriptiondescription varchar not null + + +software_typeTable qiita.software_type + Primary Key ( software_type_id ) +software_type_idsoftware_type_id bigserial not null +Referred by software ( software_type_id ) + software_typesoftware_type varchar not null + descriptiondescription varchar not null - - -softwareTable qiita.software - Primary Key ( software_id ) -software_idsoftware_id bigserial not null -Referred by default_workflow ( software_id ) +<rect class='table' x='930' y='1328' width='135' height='165' rx='7' ry='7' /> +<path d='M 930.50 1354.50 L 930.50 1335.50 Q 930.50 1328.50 937.50 1328.50 L 1057.50 1328.50 Q 1064.50 1328.50 1064.50 1335.50 L 1064.50 1354.50 L930.50 1354.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#software'><text x='974' y='1342' class='tableTitle'>software</text><title>Table qiita.software + Primary Key ( software_id ) +software_idsoftware_id bigserial not null +Referred by default_workflow ( software_id ) Referred by oauth_software ( software_id ) Referred by software_artifact_type ( software_id ) Referred by software_command ( software_id ) Referred by software_publication ( software_id ) - namename varchar not null - versionversion varchar not null - descriptiondescription varchar not null - environment_scriptenvironment_script varchar not null - start_scriptstart_script varchar not null - Index ( software_type_id ) -software_type_idsoftware_type_id bigint not null -References software_type ( software_type_id ) - activeactive bool not null default 'True' + namename varchar not null + versionversion varchar not null + descriptiondescription varchar not null + environment_scriptenvironment_script varchar not null + start_scriptstart_script varchar not null + Index ( software_type_id ) +software_type_idsoftware_type_id bigint not null +References software_type ( software_type_id ) + activeactive bool not null default 'True' - - -software_commandTable qiita.software_command - Primary Key ( command_id ) -command_idcommand_id bigserial not null -Referred by artifact ( command_id ) +<rect class='table' x='705' y='1613' width='120' height='120' rx='7' ry='7' /> +<path d='M 705.50 1639.50 L 705.50 1620.50 Q 705.50 1613.50 712.50 1613.50 L 817.50 1613.50 Q 824.50 1613.50 824.50 1620.50 L 824.50 1639.50 L705.50 1639.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#software_command'><text x='712' y='1627' class='tableTitle'>software_command</text><title>Table qiita.software_command + Primary Key ( command_id ) +command_idcommand_id bigserial not null +Referred by artifact ( command_id ) Referred by command_output ( command_id ) Referred by command_parameter ( command_id ) Referred by default_parameter_set ( command_id ) Referred by default_workflow_node ( command_id ) -Referred by job ( input_file_software_command_id -> command_id ) Referred by processing_job ( command_id ) - namename varchar not null - Index ( software_id ) -software_idsoftware_id bigint not null -References software ( software_id ) - descriptiondescription varchar not null - activeactive bool not null default 'True' - - - - -jobTable qiita.job - Primary Key ( job_id ) -job_idjob_id bigserial not null -Unique identifier for job -Referred by analysis_job ( job_id ) -Referred by collection_job ( job_id ) -Referred by job_results_filepath ( job_id ) - Index ( data_type_id ) -data_type_iddata_type_id bigint not null -What datatype (16s, metabolome, etc) job is run on. -References data_type ( data_type_id ) - Index ( job_status_id ) -job_status_idjob_status_id bigint not null -References job_status ( job_status_id ) - Index ( command_id ) -command_idcommand_id bigint not null -The Qiime or other function being run (alpha diversity, etc) -References command ( command_id ) - optionsoptions varchar -Holds all options set for the job as a json string - Index ( log_id ) -log_idlog_id bigint -Reference to error if status is error -References logging ( log_id -> logging_id ) - Index ( input_file_reference_id ) -input_file_reference_idinput_file_reference_id bigint -References reference ( input_file_reference_id -> reference_id ) - Index ( input_file_software_command_id ) -input_file_software_command_idinput_file_software_command_id bigint -References software_command ( input_file_software_command_id -> command_id ) + namename varchar not null + Index ( software_id ) +software_idsoftware_id bigint not null +References software ( software_id ) + descriptiondescription varchar not null + activeactive bool not null default 'True' - - -processing_jobTable qiita.processing_job - Primary Key ( processing_job_id ) -processing_job_idprocessing_job_id bigserial not null +<rect class='table' x='510' y='1733' width='180' height='180' rx='7' ry='7' /> +<path d='M 510.50 1759.50 L 510.50 1740.50 Q 510.50 1733.50 517.50 1733.50 L 682.50 1733.50 Q 689.50 1733.50 689.50 1740.50 L 689.50 1759.50 L510.50 1759.50 ' style='fill:url(#tableHeaderGradient2); stroke:none;' /> +<a xlink:href='#processing_job'><text x='558' y='1747' class='tableTitle'>processing_job</text><title>Table qiita.processing_job + Primary Key ( processing_job_id ) +processing_job_idprocessing_job_id bigserial not null Using bigserial in DBSchema - however in the postgres DB this column is of type UUID. -Referred by artifact_output_processing_job ( processing_job_id ) +<a xlink:href='#processing_job.processing_job_id'><use id='ref' x='678' y='1766' xlink:href='#ref'/><title>Referred by artifact_output_processing_job ( processing_job_id ) Referred by artifact_processing_job ( processing_job_id ) Referred by parent_processing_job ( parent_id -> processing_job_id ) Referred by parent_processing_job ( child_id -> processing_job_id ) @@ -2250,113 +1815,255 @@ Referred by processing_job_validator ( processing_job_id ) Referred by processing_job_validator ( validator_id -> processing_job_id ) Referred by processing_job_workflow_root ( processing_job_id ) - Index ( email ) -emailemail varchar not null + <use id='nn' x='512' y='1782' xlink:href='#nn'/><a xlink:href='#processing_job.email'><use id='idx' x='512' y='1781' xlink:href='#idx'/><title>Index ( email ) +emailemail varchar not null The user that launched the job -References qiita_user ( email ) - Index ( command_id ) -command_idcommand_id bigint not null +<a xlink:href='#processing_job.email'><use id='fk' x='678' y='1781' xlink:href='#fk'/><title>References qiita_user ( email ) + Index ( command_id ) +command_idcommand_id bigint not null The command launched -References software_command ( command_id ) - command_parameterscommand_parameters varchar not null +<a xlink:href='#processing_job.command_id'><use id='fk' x='678' y='1796' xlink:href='#fk'/><title>References software_command ( command_id ) + command_parameterscommand_parameters varchar not null The parameters used in the command in JSON format - Index ( processing_job_status_id ) -processing_job_status_idprocessing_job_status_id bigint not null -References processing_job_status ( processing_job_status_id ) - Index ( logging_id ) -logging_idlogging_id bigint + <use id='nn' x='512' y='1827' xlink:href='#nn'/><a xlink:href='#processing_job.processing_job_status_id'><use id='idx' x='512' y='1826' xlink:href='#idx'/><title>Index ( processing_job_status_id ) +processing_job_status_idprocessing_job_status_id bigint not null +References processing_job_status ( processing_job_status_id ) + Index ( logging_id ) +logging_idlogging_id bigint In case of failure, point to the log entry that holds more information about the error -References logging ( logging_id ) - heartbeatheartbeat timestamp +<a xlink:href='#processing_job.logging_id'><use id='fk' x='678' y='1841' xlink:href='#fk'/><title>References logging ( logging_id ) + heartbeatheartbeat timestamp The last heartbeat received by this job - stepstep varchar - pendingpending varchar + stepstep varchar + pendingpending varchar - - -default_workflow_edge_connectionsTable qiita.default_workflow_edge_connections - Primary Key ( default_workflow_edge_id, parent_output_id, child_input_id ) Index ( default_workflow_edge_id ) -default_workflow_edge_iddefault_workflow_edge_id bigint not null -References default_workflow_edge ( default_workflow_edge_id ) - Primary Key ( default_workflow_edge_id, parent_output_id, child_input_id ) Index ( parent_output_id ) -parent_output_idparent_output_id bigint not null -References command_output ( parent_output_id -> command_output_id ) - Primary Key ( default_workflow_edge_id, parent_output_id, child_input_id ) Index ( child_input_id ) -child_input_idchild_input_id bigint not null -References command_parameter ( child_input_id -> command_parameter_id ) + + +default_workflow_edge_connectionsTable qiita.default_workflow_edge_connections + Primary Key ( default_workflow_edge_id, parent_output_id, child_input_id ) Index ( default_workflow_edge_id ) +default_workflow_edge_iddefault_workflow_edge_id bigint not null +References default_workflow_edge ( default_workflow_edge_id ) + Primary Key ( default_workflow_edge_id, parent_output_id, child_input_id ) Index ( parent_output_id ) +parent_output_idparent_output_id bigint not null +References command_output ( parent_output_id -> command_output_id ) + Primary Key ( default_workflow_edge_id, parent_output_id, child_input_id ) Index ( child_input_id ) +child_input_idchild_input_id bigint not null +References command_parameter ( child_input_id -> command_parameter_id ) - - -processing_job_validatorTable qiita.processing_job_validator - Index ( processing_job_id ) Primary Key ( processing_job_id, validator_id ) -processing_job_idprocessing_job_id bigint not null -References processing_job ( processing_job_id ) - Index ( validator_id ) Primary Key ( processing_job_id, validator_id ) -validator_idvalidator_id bigint not null -References processing_job ( validator_id -> processing_job_id ) - artifact_infoartifact_info varchar + + +processing_job_validatorTable qiita.processing_job_validator + Index ( processing_job_id ) Primary Key ( processing_job_id, validator_id ) +processing_job_idprocessing_job_id bigint not null +References processing_job ( processing_job_id ) + Index ( validator_id ) Primary Key ( processing_job_id, validator_id ) +validator_idvalidator_id bigint not null +References processing_job ( validator_id -> processing_job_id ) + artifact_infoartifact_info varchar - - -study_publicationTable qiita.study_publication - study_idstudy_id bigint not null -References study ( study_id ) - publicationpublication varchar not null - is_doiis_doi bool + + +study_publicationTable qiita.study_publication + study_idstudy_id bigint not null +References study ( study_id ) + publicationpublication varchar not null + is_doiis_doi bool + + + + +analysis_statusTable qiita.analysis_status + Primary Key ( analysis_status_id ) +analysis_status_idanalysis_status_id bigserial not null +Referred by analysis ( analysis_status_id ) + Unique Index ( status ) +statusstatus varchar not null + + + + +analysisTable qiita.analysis +Holds analysis information + Primary Key ( analysis_id ) +analysis_idanalysis_id bigserial not null +Unique identifier for analysis +Referred by analysis_filepath ( analysis_id ) +Referred by analysis_portal ( analysis_id ) +Referred by analysis_sample ( analysis_id ) +Referred by analysis_users ( analysis_id ) +Referred by analysis_artifact ( analysis_id ) + Index ( email ) +emailemail varchar not null +Email for user who owns the analysis +References qiita_user ( email ) + namename varchar not null +Name of the analysis + descriptiondescription varchar not null + Index ( analysis_status_id ) +analysis_status_idanalysis_status_id bigint not null +References analysis_status ( analysis_status_id ) + pmidpmid varchar +PMID of paper from the analysis + timestamptimestamp timestamptz default current_timestamp + dfltdflt bool not null default false + Index ( portal_type_id ) +portal_type_idportal_type_id bigint not null +References portal_type ( portal_type_id ) + + + + +analysis_usersTable qiita.analysis_users +Links analyses to the users they are shared with + Primary Key ( analysis_id, email ) Index ( analysis_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Primary Key ( analysis_id, email ) Index ( email ) +emailemail varchar not null +References qiita_user ( email ) + + + + +analysis_portalTable qiita.analysis_portal +Controls what analyses are visible on what portals + Index ( analysis_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Index ( portal_type_id ) +portal_type_idportal_type_id bigint not null +References portal_type ( portal_type_id ) + + + + +analysis_filepathTable qiita.analysis_filepath +Stores link between analysis and the data file used for the analysis. + Index ( analysis_id ) Primary Key ( analysis_id, filepath_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Index ( filepath_id ) Primary Key ( analysis_id, filepath_id ) +filepath_idfilepath_id bigint not null +References filepath ( filepath_id ) + Index ( data_type_id ) +data_type_iddata_type_id bigint +References data_type ( data_type_id ) + + + + +analysis_sampleTable qiita.analysis_sample + Index ( analysis_id ) Primary Key ( analysis_id, artifact_id, sample_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Index ( artifact_id ) Primary Key ( analysis_id, artifact_id, sample_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + Index ( sample_id ) Primary Key ( analysis_id, artifact_id, sample_id ) +sample_idsample_id varchar not null +References study_sample ( sample_id ) + + + + +portal_typeTable qiita.portal_type +What portals are available to show a study in + Primary Key ( portal_type_id ) +portal_type_idportal_type_id bigserial not null +Referred by analysis ( portal_type_id ) +Referred by analysis_portal ( portal_type_id ) +Referred by study_portal ( portal_type_id ) + portalportal varchar not null + portal_descriptionportal_description varchar not null + + + + +ontologyTable qiita.ontology + Primary Key ( ontology_id ) +ontology_idontology_id bigint not null +Referred by term ( ontology_id ) + Unique Index ( ontology ) +ontologyontology varchar not null + fully_loadedfully_loaded bool not null + fullnamefullname varchar + query_urlquery_url varchar + source_urlsource_url varchar + definitiondefinition text + load_dateload_date date not null + + + + +analysis_artifactTable qiita.analysis_artifact + Index ( analysis_id ) Primary Key ( analysis_id, artifact_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Index ( artifact_id ) Primary Key ( analysis_id, artifact_id ) +artifact_idartifact_id bigint not null +References artifact ( artifact_id ) + + + + +data_typeTable qiita.data_type + Primary Key ( data_type_id ) +data_type_iddata_type_id bigserial not null +Referred by analysis_filepath ( data_type_id ) +Referred by artifact ( data_type_id ) +Referred by prep_template ( data_type_id ) + Unique Index ( data_type ) +data_typedata_type varchar not null +Data type (16S, metabolome, etc) the job will use

- + - - + - - + - - + - - + - - - + + - + - + - + @@ -2366,44 +2073,42 @@

Table controlled_vocab_values
Table controlled_vocab_values
* vocab_value_id bigserial bigserial NOT NULL
* controlled_vocab_id bigint bigint NOT NULL
* term varchar varchar NOT NULL
* order_by varchar varchar NOT NULL
  default_item varchar
Indexes
Pkpk_controlled_vocab_values
Indexes
pk_controlled_vocab_values primary key ON vocab_value_id
 idx_controlled_vocab_values
idx_controlled_vocab_values ON controlled_vocab_id
Foreign Keys
Foreign Keys
 fk_controlled_vocab_valuesfk_controlled_vocab_values ( controlled_vocab_id ) ref controlled_vocab (controlled_vocab_id)
- - + + - - - + + - - - + + - - - + + + - - + + - - + + - + - - + + - - + + @@ -2412,44 +2117,42 @@

Table job_results_filepath
Holds connection between jobs and the result filepaths
Table column_controlled_vocabularies
Table relates a column with a controlled vocabulary.
*job_id bigint controlled_vocab_id bigserial NOT NULL
*filepath_id bigint column_name varchar NOT NULL
Indexes
Pkidx_job_results_filepath ON job_id, filepath_id
Indexes
idx_column_controlled_vocabularies primary key ON controlled_vocab_id, column_name
 idx_job_results_filepath_0 ON job_id
idx_column_controlled_vocabularies_0 ON column_name
 idx_job_results_filepath_1 ON filepath_id
idx_column_controlled_vocabularies_1 ON controlled_vocab_id
Foreign Keys
Foreign Keys
 fk_job_results_filepath ( job_id ) ref job (job_id) fk_column_controlled_vocabularies ( column_name ) ref mixs_field_description (column_name)
 fk_job_results_filepath_0 ( filepath_id ) ref filepath (filepath_id) fk_column_controlled_vocab2 ( controlled_vocab_id ) ref controlled_vocab (controlled_vocab_id)
- - + - - - - + + + - - - - - - - - + + - - + + + - - + + + - - - + + - - + + + + + + + @@ -2458,44 +2161,47 @@

Table analysis_job
Holds information for a one-to-many relation of analysis to the jobs in it
Table mixs_field_description
*analysis_id bigint Id of the analysis column_name varchar NOT NULL
*job_id bigint Id for a job that is part of the analysis
Indexes
Pkidx_analysis_jobs ON analysis_id, job_iddata_type varchar NOT NULL
 idx_analysis_job ON analysis_id
desc_or_value varchar NOT NULL
 idx_analysis_job_0 ON job_id
definition varchar NOT NULL
Foreign Keys
 fk_analysis_job_analysis ( analysis_id ) ref analysis (analysis_id) min_length integer
 fk_analysis_job_job ( job_id ) ref job (job_id) active integer NOT NULL
Indexes
pk_mixs_field_description primary key ON column_name
- - + - - - + + - - - - + + + - - - + + + - - - + + + + - - + + + + + + + + - - - - + + + - - + + @@ -2504,48 +2210,43 @@

Table column_controlled_vocabularies
Table relates a column with a controlled vocabulary.
Table logging
*controlled_vocab_id bigserial logging_id bigserial NOT NULL
*column_name varchar time timestamp NOT NULL Time the error was thrown
Indexes
Pkidx_column_controlled_vocabularies ON controlled_vocab_id, column_name
severity_id integer NOT NULL
 idx_column_controlled_vocabularies_0 ON column_name
msg varchar NOT NULL Error message thrown
 idx_column_controlled_vocabularies_1 ON controlled_vocab_id
information varchar Other applicable information (depending on error)
Indexes
idx_logging_0 ON severity_id
Foreign Keys
 fk_column_controlled_vocabularies ( column_name ) ref mixs_field_description (column_name)
pk_logging primary key ON logging_id
Foreign Keys
 fk_column_controlled_vocab2 ( controlled_vocab_id ) ref controlled_vocab (controlled_vocab_id) fk_logging_severity ( severity_id ) ref severity (severity_id)
- + + - - - + + - - - + + - - - + + - - + - - - - + + + - - - - + + - - - + + + + @@ -2554,435 +2255,22 @@

Table mixs_field_description
Table column_ontology
This table relates a column with an ontology.
*column_name varchar column_name varchar NOT NULL
*data_type varchar ontology_short_name varchar NOT NULL
*desc_or_value varchar bioportal_id integer NOT NULL
*definitionontology_branch_id varchar
 min_length integer
Indexes
idx_column_ontology primary key ON column_name, ontology_short_name
*active integer
idx_column_ontology_0 ON column_name
Indexes
Pkpk_mixs_field_description ON column_name
Foreign Keys
fk_column_ontology ( column_name ) ref mixs_field_description (column_name)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis_users
Links analyses to the users they are shared with
*analysis_id bigint
*email varchar
Indexes
Pkidx_analysis_users ON analysis_id, email
 idx_analysis_users_analysis ON analysis_id
 idx_analysis_users_email ON email
Foreign Keys
 fk_analysis_users_analysis ( analysis_id ) ref analysis (analysis_id)
 fk_analysis_users_user ( email ) ref qiita_user (email)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table command
Available commands for jobs
*command_id bigserial Unique identifier for function
*name varchar
*command varchar What command to call to run this function
*input varchar JSON of input options for the command
*required varchar JSON of required options for the command
*optional varchar JSON of optional options for command
*output varchar JSON of output options for the command
Indexes
Pkpk_command ON command_id
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table logging
*logging_id bigserial
*time timestamp Time the error was thrown
*severity_id integer
*msg varchar Error message thrown
 information varchar Other applicable information (depending on error)
Indexes
 idx_logging_0 ON severity_id
Pkpk_logging ON logging_id
Foreign Keys
 fk_logging_severity ( severity_id ) ref severity (severity_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table command_data_type
*command_id bigint
*data_type_id bigint
Indexes
Pkidx_command_data_type ON command_id, data_type_id
 idx_command_data_type_0 ON command_id
 idx_command_data_type_1 ON data_type_id
Foreign Keys
 fk_command_data_type ( command_id ) ref command (command_id)
 fk_command_data_type_0 ( data_type_id ) ref data_type (data_type_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis_filepath
Stores link between analysis and the data file used for the analysis.
*analysis_id bigint
*filepath_id bigint
 data_type_id bigint
Indexes
 idx_analysis_filepath ON analysis_id
 idx_analysis_filepath_0 ON filepath_id
Pkidx_analysis_filepath_1 ON analysis_id, filepath_id
 idx_analysis_filepath_2 ON data_type_id
Foreign Keys
 fk_analysis_filepath ( analysis_id ) ref analysis (analysis_id)
 fk_analysis_filepath_0 ( filepath_id ) ref filepath (filepath_id)
 fk_analysis_filepath_1 ( data_type_id ) ref data_type (data_type_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis_workflow
Stores what step in_production analyses are on.
*analysis_id bigint
*step integer
Indexes
Pkpk_analysis_workflow ON analysis_id
Foreign Keys
 fk_analysis_workflow ( analysis_id ) ref analysis (analysis_id)
- -

- - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table column_ontology
This table relates a column with an ontology.
Table controlled_vocab
*column_name varchar
*ontology_short_name varchar
*bioportal_id integer
 ontology_branch_id varchar
Indexes
Pkidx_column_ontology ON column_name, ontology_short_name
 idx_column_ontology_0 ON column_name
Foreign Keys
 fk_column_ontology ( column_name ) ref mixs_field_description (column_name)
- -

- - - - - - - - - - - - - - + - - - - - - -
Table controlled_vocab
* controlled_vocab_id bigserial
*controlled_vocab varchar bigserial NOT NULL
Indexes
Pkpk_controlled_vocabularies ON controlled_vocab_id
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + + @@ -2991,401 +2279,73 @@

Table ontology
*ontology_id bigint
*ontology varchar
*fully_loaded bool
 fullname varchar
 query_url varchar
 source_url varchar
 definition text
*load_date date
Indexes
Pkpk_ontology ON ontology_id
controlled_vocab varchar NOT NULL
Uidx_ontology ON ontology
Indexes
pk_controlled_vocabularies primary key ON controlled_vocab_id
- + - - + - - + - - + - - - - - - - - - - - - - - - - - - - - - - - -
Table qiita_user
Table qiita_user
Holds all user information
* email varchar varchar NOT NULL
* user_level_id integer DEFO 5 integer NOT NULL DEFO 5 user level
* password varchar varchar NOT NULL
  name varchar
  affiliation varchar
  address varchar
  phone varchar
  user_verify_code varchar Code for initial user email verification
  pass_reset_code varchar Randomly generated code for password reset
  pass_reset_timestamp timestamp Time the reset code was generated
Indexes
Pkpk_user ON email
 idx_user ON user_level_id
Foreign Keys
 fk_user_user_level ( user_level_id ) ref user_level (user_level_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - -
Table prep_y
Information on how raw data y was prepared (prep template)Linked by y being raw_data_id from raw data table.
*sample_id varchar
 data bigint STUFFFFF
Indexes
Pkpk_prep_y ON sample_id
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table checksum_algorithm
*checksum_algorithm_id bigserial
*name varchar
Indexes
Pkpk_checksum_algorithm ON checksum_algorithm_id
Uidx_checksum_algorithm ON name
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table user_level
Holds available user levels
*user_level_id serial
*name varchar One of the user levels (admin, user, guest, etc)
*description text
Indexes
Pkpk_user_level ON user_level_id
Uidx_user_level ON name
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table job_status
*job_status_id bigserial
*status varchar
Indexes
Pkpk_job_status ON job_status_id
Uidx_job_status_0 ON status
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table severity
*severity_id serial
*severity varchar
Indexes
Pkpk_severity ON severity_id
Uidx_severity ON severity
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table filepath
*filepath_id bigserial
*filepath varchar
*filepath_type_id bigint
*checksum varchar
*checksum_algorithm_id bigint
 data_directory_id bigserial
Indexes
Pkpk_filepath ON filepath_id
 idx_filepath ON filepath_type_id
 idx_filepath_0 ON data_directory_id
Foreign Keys
 fk_filepath ( filepath_type_id ) ref filepath_type (filepath_type_id)
 fk_filepath_0 ( checksum_algorithm_id ) ref checksum_algorithm (checksum_algorithm_id)
 fk_filepath_data_directory ( data_directory_id ) ref data_directory (data_directory_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + - + - - + + @@ -3394,28 +2354,23 @@

Table term
*term_id bigserial
*ontology_id bigint
 old_term_id bigint DEFO NULL Identifier used in the old system, we are keeping this for consistency
*term varchar
 identifier varchar
 definition varchar
 namespace varchar
 is_obsolete bool DEFO 'false'
 is_root_term bool
 is_leaf bool
*user_defined bool DEFO False Whether or not this term was defined by a user
Indexes
Pkpk_term ON term_id
Indexes
pk_user primary key ON email
 idx_term ON ontology_id
idx_user ON user_level_id
Foreign Keys
Foreign Keys
 fk_term_ontology ( ontology_id ) ref ontology (ontology_id) fk_user_user_level ( user_level_id ) ref user_level (user_level_id)
- + + - - - + + - - - - - - - - - + + + - - + + + @@ -3424,44 +2379,26 @@

Table analysis_status
Table prep_y
Information on how raw data y was prepared (prep template)Linked by y being raw_data_id from raw data table.
*analysis_status_id bigserial sample_id varchar NOT NULL
*status varchar
Indexes
Pkpk_analysis_status ON analysis_status_id data bigint STUFFFFF
Uidx_analysis_status ON status
Indexes
pk_prep_y primary key ON sample_id
- - + - - - + + - - - - - - - - - - - - - - - - + + - - - - + + + - - - + + @@ -3470,44 +2407,32 @@

Table collection_job
Matches collection important jobs as one to many.
Table checksum_algorithm
*collection_id bigint checksum_algorithm_id bigserial NOT NULL
*job_id bigint
Indexes
 idx_collection_job ON collection_id
 idx_collection_job_0 ON job_id
Pkidx_collection_job_1 ON collection_id, job_idname varchar NOT NULL
Foreign Keys
 fk_collection_job ( collection_id ) ref collection (collection_id)
Indexes
pk_checksum_algorithm primary key ON checksum_algorithm_id
 fk_collection_job_0 ( job_id ) ref job (job_id)
idx_checksum_algorithm unique ON name
- - + + - - - + + - - - - - - - - - - - - - + + + - - + + + - - - - + + + - - - + + @@ -3516,24 +2441,26 @@

Table collection_analysis
Matches collection to analyses as one to many.
Table user_level
Holds available user levels
*collection_id bigint user_level_id serial NOT NULL
*analysis_id bigint
Indexes
Pkidx_collection_analysis ON collection_id, analysis_id
 idx_collection_analysis_0 ON collection_id name varchar NOT NULL One of the user levels (admin, user, guest, etc)
 idx_collection_analysis_1 ON analysis_id
description text NOT NULL
Foreign Keys
 fk_collection_analysis ( collection_id ) ref collection (collection_id)
Indexes
pk_user_level primary key ON user_level_id
 fk_collection_analysis_0 ( analysis_id ) ref analysis (analysis_id)
idx_user_level unique ON name
- + - - - + + - - - + + + + + + + - - - + + @@ -3542,62 +2469,66 @@

Table collection_status
Table severity
*collection_status_id bigserial severity_id serial NOT NULL
*status varchar( 1 ) severity varchar NOT NULL
Indexes
pk_severity primary key ON severity_id
Indexes
Pkpk_collection_status ON collection_status_id
idx_severity unique ON severity
- - + - - - + + - - - + + - - - + + - - - + + + + + + + - - - + + - - - + + + - - + + - - + + - + - - + + + + + + + - - + + @@ -3606,44 +2537,77 @@

Table collection
Tracks a group of analyses and important jobs for an overarching goal.
Table filepath
*collection_id bigserial filepath_id bigserial NOT NULL
*email varchar filepath varchar NOT NULL
*name varchar( 100 ) filepath_type_id bigint NOT NULL
 description varchar checksum varchar NOT NULL
checksum_algorithm_id bigint NOT NULL
*collection_status_id bigint DEFO 1 data_directory_id bigserial
Indexes
Pkpk_collection ON collection_id
Indexes
pk_filepath primary key ON filepath_id
 idx_collection ON email
idx_filepath ON filepath_type_id
 idx_collection_0 ON collection_status_id
idx_filepath_0 ON data_directory_id
Foreign Keys
Foreign Keys
 fk_collection ( email ) ref qiita_user (email) fk_filepath ( filepath_type_id ) ref filepath_type (filepath_type_id)
fk_filepath_0 ( checksum_algorithm_id ) ref checksum_algorithm (checksum_algorithm_id)
 fk_collection_0 ( collection_status_id ) ref collection_status (collection_status_id) fk_filepath_data_directory ( data_directory_id ) ref data_directory (data_directory_id)
- - + - - - + + + + + + + + + + + + + + + + + - - + - - - + + + - - + + + - - + + + - - - + + - - + + + + + + + + + + + + + + + + + + + + + + @@ -3652,43 +2616,41 @@

Table collection_users
Allows sharing of a collection
Table term
*collection_id bigint term_id bigserial NOT NULL
ontology_id bigint NOT NULL
old_term_id bigint DEFO NULL Identifier used in the old system, we are keeping this for consistency
term varchar NOT NULL
*emailidentifier varchar
Indexes
Pkidx_collection_user ON collection_id, email
definition varchar
 idx_collection_user_0 ON collection_id
namespace varchar
 idx_collection_user_1 ON email
is_obsolete bool DEFO 'false'
Foreign Keys
 fk_collection_user ( collection_id ) ref collection (collection_id) is_root_term bool
 fk_collection_user_email ( email ) ref qiita_user (email) is_leaf bool
user_defined bool NOT NULL DEFO False Whether or not this term was defined by a user
Indexes
pk_term primary key ON term_id
idx_term ON ontology_id
Foreign Keys
fk_term_ontology ( ontology_id ) ref ontology (ontology_id)
- + - - + - - + - - + + - + - + - + - + - + @@ -3698,47 +2660,44 @@

Table study_prep_template
Table study_prep_template
links study to its prep templates
* study_id bigint bigint NOT NULL
* prep_template_id bigint bigint NOT NULL
Indexes
Pkidx_study_raw_data
Indexes
idx_study_raw_data primary key ON study_id, prep_template_id
 idx_study_raw_data_0
idx_study_raw_data_0 ON study_id
 idx_study_raw_data_1
idx_study_raw_data_1 ON prep_template_id
Foreign Keys
Foreign Keys
 fk_study_prep_template_studyfk_study_prep_template_study ( study_id ) ref study (study_id)
 fk_study_prep_template_ptfk_study_prep_template_pt ( prep_template_id ) ref prep_template (prep_template_id)
- + - - + - - + - - - + + - + - + - + - + - + @@ -3748,123 +2707,45 @@

Table prep_template_sample
Table prep_template_sample
* prep_template_id bigint bigint NOT NULL The prep template identifier
* sample_id varchar varchar NOT NULL
  ebi_experiment_accession varchar
Indexes
 idx_common_prep_info_0
Indexes
idx_common_prep_info_0 ON sample_id
 idx_common_prep_info_0
idx_common_prep_info_0 ON sample_id
Pkidx_common_prep_info
idx_common_prep_info primary key ON prep_template_id, sample_id
 idx_common_prep_info_1
idx_common_prep_info_1 ON prep_template_id
Foreign Keys
Foreign Keys
 fk_prep_templatefk_prep_template ( prep_template_id ) ref prep_template (prep_template_id)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table portal_type
What portals are available to show a study in
*portal_type_id bigserial
*portal varchar
*portal_description varchar
Indexes
Pkpk_portal_type ON portal_type_id
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis_portal
Controls what analyses are visible on what portals
*analysis_id bigint
*portal_type_id bigint
Indexes
 idx_analysis_portal ON analysis_id
 idx_analysis_portal_0 ON portal_type_id
Foreign Keys
 fk_analysis_portal ( analysis_id ) ref analysis (analysis_id)
 fk_analysis_portal_0 ( portal_type_id ) ref portal_type (portal_type_id)
- -

- - - + - - + - - + - - + - - + + - + - + - + - + - + @@ -3874,35 +2755,31 @@

Table message_user
Table message_user
* email varchar varchar NOT NULL
* message_id bigint bigint NOT NULL
* read bool DEFO 'false' bool NOT NULL DEFO 'false' Whether the message has been read or not.
Indexes
Pkidx_message_user
Indexes
idx_message_user primary key ON email, message_id
 idx_message_user_0
idx_message_user_0 ON message_id
 idx_message_user_1
idx_message_user_1 ON email
Foreign Keys
Foreign Keys
 fk_message_userfk_message_user ( message_id ) ref message (message_id)
 fk_message_user_0fk_message_user_0 ( email ) ref qiita_user (email)
- + - - + - - + - - + - - - + + @@ -3912,30 +2789,27 @@

Table message
Table message
* message_id bigserial bigserial NOT NULL
* message varchar varchar NOT NULL
* message_time timestamp DEFO current_timestamp timestamp NOT NULL DEFO current_timestamp
  expiration timestamp
Indexes
Pkpk_message
Indexes
pk_message primary key ON message_id
- + - - + - - + - - - + + @@ -3945,42 +2819,40 @@

Table sample_x
Table sample_x
data for samples in study x (sample template)x is the study_id from study tableMAKE SURE sample_id IS FK TO sample_id IN required_sample_info TABLE
* sample_id varchar varchar NOT NULL
* description varchar varchar NOT NULL
  other_mapping_columns varchar Represents whatever other columns go with this study
Indexes
Pkpk_study_x_y
Indexes
pk_study_x_y primary key ON sample_id
- + - - + - - + - - + + - + - + - + - + - + @@ -3990,106 +2862,41 @@

Table prep_template_filepath
Table prep_template_filepath
* prep_template_id bigint bigint NOT NULL
* filepath_id bigint bigint NOT NULL
Indexes
Pkidx_prep_template_filepath
Indexes
idx_prep_template_filepath primary key ON prep_template_id, filepath_id
 idx_prep_template_filepath
idx_prep_template_filepath ON filepath_id
 idx_prep_template_filepath
idx_prep_template_filepath ON prep_template_id
Foreign Keys
Foreign Keys
 fk_filepath_idfk_filepath_id ( filepath_id ) ref filepath (filepath_id)
 fk_prep_template_idfk_prep_template_id ( prep_template_id ) ref prep_template (prep_template_id)
- + - - + - - + - - + + - + - - - - - - - - - - - - - - - - -
Table sample_template_filepath
Table sample_template_filepath
* study_id bigint bigint NOT NULL
* filepath_id bigint bigint NOT NULL
Indexes
Pkidx_sample_template_filepath
Indexes
idx_sample_template_filepath primary key ON study_id, filepath_id
 idx_sample_template_filepath_0
idx_sample_template_filepath_0 ON study_id
 idx_sample_template_filepath_1 ON filepath_id
Foreign Keys
 fk_study_id ( study_id ) ref study (study_id)
 fk_filepath_id ( filepath_id ) ref filepath (filepath_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - + + - - + + @@ -4098,57 +2905,55 @@

Table prep_template
*prep_template_id bigserial
*data_type_id bigint
*preprocessing_status varchar DEFO 'not_preprocessed'
 investigation_type varchar The investigation type (e.g., one of the values from EBI's set of known types)
 artifact_id bigint
Indexes
Pkpk_prep_template ON prep_template_id
 idx_prep_template ON data_type_id
 idx_prep_template_0 ON artifact_id
idx_sample_template_filepath_1 ON filepath_id
Foreign Keys
Foreign Keys
 fk_prep_template_data_type ( data_type_id ) ref data_type (data_type_id) fk_study_id ( study_id ) ref study (study_id)
 fk_prep_template_artifact ( artifact_id ) ref artifact (artifact_id) fk_filepath_id ( filepath_id ) ref filepath (filepath_id)
- + - - - + + - - - + + - - - + + - - - - + + + + - - + + + - - + + + - - + + - - - - + + + - - + + - + @@ -4158,42 +2963,40 @@

Table analysis_sample
Table prep_template
*analysis_id bigint prep_template_id bigserial NOT NULL
*artifact_id bigint data_type_id bigint NOT NULL
*sample_id varchar preprocessing_status varchar NOT NULL DEFO 'not_preprocessed'
Indexes
 idx_analysis_sample ON analysis_id
investigation_type varchar The investigation type (e.g., one of the values from EBI's set of known types)
 idx_analysis_sample_0 ON artifact_id
artifact_id bigint
 idx_analysis_sample_1 ON sample_id
Indexes
pk_prep_template primary key ON prep_template_id
Pkpk_analysis_sample ON analysis_id, artifact_id, sample_id
idx_prep_template ON data_type_id
Foreign Keys
 fk_analysis_sample_analysis ( analysis_id ) ref analysis (analysis_id)
idx_prep_template_0 ON artifact_id
Foreign Keys
 fk_analysis_sample ( sample_id ) ref study_sample (sample_id) fk_prep_template_data_type ( data_type_id ) ref data_type (data_type_id)
 fk_analysis_sample_artifactfk_prep_template_artifact ( artifact_id ) ref artifact (artifact_id)
- + - - + - - + - - + + - + - + - + - + - + @@ -4203,42 +3006,40 @@

Table parent_artifact
Table parent_artifact
* artifact_id bigint bigint NOT NULL
* parent_id bigint bigint NOT NULL
Indexes
Pkidx_parent_artifact
Indexes
idx_parent_artifact primary key ON artifact_id, parent_id
 idx_parent_artifact
idx_parent_artifact ON artifact_id
 idx_parent_artifact
idx_parent_artifact ON parent_id
Foreign Keys
Foreign Keys
 fk_parent_artifact_artifactfk_parent_artifact_artifact ( artifact_id ) ref artifact (artifact_id)
 fk_parent_artifact_parentfk_parent_artifact_parent ( parent_id ) ref artifact (artifact_id)
- + - - + - - + - - + + - + - + - + - + - + @@ -4248,43 +3049,41 @@

Table artifact_filepath
Table artifact_filepath
* artifact_id bigint bigint NOT NULL
* filepath_id bigint bigint NOT NULL
Indexes
Pkidx_artifact_filepath
Indexes
idx_artifact_filepath primary key ON artifact_id, filepath_id
 idx_artifact_filepath
idx_artifact_filepath ON artifact_id
 idx_artifact_filepath
idx_artifact_filepath ON filepath_id
Foreign Keys
Foreign Keys
 fk_artifact_filepath_artifactfk_artifact_filepath_artifact ( artifact_id ) ref artifact (artifact_id)
 fk_artifact_filepath_filepathfk_artifact_filepath_filepath ( filepath_id ) ref filepath (filepath_id)
- + - - + - - + - - + + - + - + - + - + - + @@ -4294,194 +3093,172 @@

Table study_users
Table study_users
Links shared studies to users they are shared with
* study_id bigint bigint NOT NULL
* email varchar varchar NOT NULL
Indexes
Pkidx_study_users
Indexes
idx_study_users primary key ON study_id, email
 idx_study_users_0
idx_study_users_0 ON study_id
 idx_study_users_1
idx_study_users_1 ON email
Foreign Keys
Foreign Keys
 fk_study_users_studyfk_study_users_study ( study_id ) ref study (study_id)
 fk_study_users_userfk_study_users_user ( email ) ref qiita_user (email)
- + - - + - - + - - - + - - - + - - - + - - + - - - - - + - - + - - - + - - + - - + - - + - - - - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4491,52 +3268,46 @@

Table study
Table study
* study_id bigserial bigserial NOT NULL Unique name for study
* email varchar varchar NOT NULL Email of study owner
  emp_person_id bigint
* first_contact timestamp DEFO current_timestamp timestamp NOT NULL DEFO current_timestamp
  funding varchar
* timeseries_type_id bigint bigint NOT NULL What type of timeseries this study is (or is not) Controlled Vocabulary
  lab_person_id bigint
* metadata_complete bool bool NOT NULL
* mixs_compliant bool bool NOT NULL
  most_recent_contact timestamp
  number_samples_collected integer
  number_samples_promised integer
* principal_investigator_id bigint bigint NOT NULL
* reprocess bool bool NOT NULL
  spatial_series bool
* study_title varchar varchar NOT NULL
* study_alias varchar varchar NOT NULL
* study_description text text NOT NULL
* study_abstract text text NOT NULL
  vamps_id varchar
  ebi_study_accession varchar
* ebi_submission_status varchar DEFO 'not_submitted' varchar NOT NULL DEFO 'not_submitted'
Indexes
Pkpk_study
Indexes
pk_study primary key ON study_id
 idx_study
idx_study ON email
 idx_study_1
idx_study_1 ON emp_person_id
 idx_study_2
idx_study_2 ON lab_person_id
 idx_study_3
idx_study_3 ON principal_investigator_id
 idx_study_4
idx_study_4 ON timeseries_type_id
Uunique_study_title
unique_study_title unique ON study_title
Foreign Keys
Foreign Keys
 fk_study_userfk_study_user ( email ) ref qiita_user (email)
 fk_study_study_emp_personfk_study_study_emp_person ( emp_person_id ) ref study_person (study_person_id)
 fk_study_study_lab_personfk_study_study_lab_person ( lab_person_id ) ref study_person (study_person_id)
 fk_study_study_pi_personfk_study_study_pi_person ( principal_investigator_id ) ref study_person (study_person_id)
 fk_study_timeseries_typefk_study_timeseries_type ( timeseries_type_id ) ref timeseries_type (timeseries_type_id)
- + - - + - - + - - + - - + - - - - + + - + @@ -4546,34 +3317,32 @@

Table study_person
Table study_person
Contact information for the various people involved in a study
* study_person_id bigserial bigserial NOT NULL
* name varchar varchar NOT NULL
* email varchar varchar NOT NULL
* affiliation varchar varchar NOT NULL The institution with which this person is affiliated
  address varchar( 100 )
  phone varchar
Indexes
Pkpk_study_person
Indexes
pk_study_person primary key ON study_person_id
Uidx_study_person
idx_study_person unique ON name, affiliation
- + - - + - - + - - + + - + - + - + @@ -4583,43 +3352,41 @@

Table study_experimental_factor
Table study_experimental_factor
EFO ontological link of experimental factors to studies
* study_id bigint bigint NOT NULL
* efo_id bigint bigint NOT NULL
Indexes
Pkidx_study_experimental_factor
Indexes
idx_study_experimental_factor primary key ON study_id, efo_id
 idx_study_experimental_factor_0
idx_study_experimental_factor_0 ON study_id
Foreign Keys
Foreign Keys
 fk_study_experimental_factorfk_study_experimental_factor ( study_id ) ref study (study_id)
- + - - + - - + - - + + - + - + - + - + - + @@ -4629,23 +3396,21 @@

Table study_environmental_package
Table study_environmental_package
Holds the 1 to many relationship between the study and the environmental_package
* study_id bigint bigint NOT NULL
* environmental_package_name varchar varchar NOT NULL
Indexes
Pkpk_study_environmental_package
Indexes
pk_study_environmental_package primary key ON study_id, environmental_package_name
 idx_study_environmental_package
idx_study_environmental_package ON study_id
 idx_study_environmental_package_0
idx_study_environmental_package_0 ON environmental_package_name
Foreign Keys
Foreign Keys
 fk_study_environmental_packagefk_study_environmental_package ( study_id ) ref study (study_id)
 fk_study_environmental_package_0fk_study_environmental_package_0 ( environmental_package_name ) ref environmental_package (environmental_package_name)
- + - - + - - + - - + + @@ -4655,42 +3420,40 @@

Table environmental_package
Table environmental_package
* environmental_package_name varchar varchar NOT NULL The name of the environmental package
* metadata_table varchar varchar NOT NULL Contains the name of the table that contains the pre-defined metadata columns for the environmental package
Indexes
Pkpk_environmental_package
Indexes
pk_environmental_package primary key ON environmental_package_name
- + - - + - - + - - + + - + - + - + - + - + @@ -4700,46 +3463,42 @@

Table investigation_study
Table investigation_study
* investigation_id bigint bigint NOT NULL
* study_id bigint bigint NOT NULL
Indexes
Pkidx_investigation_study
Indexes
idx_investigation_study primary key ON investigation_id, study_id
 idx_investigation_study_investigation
idx_investigation_study_investigation ON investigation_id
 idx_investigation_study_study
idx_investigation_study_study ON study_id
Foreign Keys
Foreign Keys
 fk_investigation_studyfk_investigation_study ( investigation_id ) ref investigation (investigation_id)
 fk_investigation_study_studyfk_investigation_study_study ( study_id ) ref study (study_id)
- + - - + - - + - - + - - - + + - + - + - + @@ -4749,43 +3508,41 @@

Table investigation
Table investigation
Overarching investigation information.An investigation comprises one or more individual studies.
* investigation_id bigserial bigserial NOT NULL
* investigation_name varchar varchar NOT NULL
* investigation_description varchar varchar NOT NULL Describes the overarching goal of the investigation
  contact_person_id bigint
Indexes
Pkpk_investigation
Indexes
pk_investigation primary key ON investigation_id
 idx_investigation
idx_investigation ON contact_person_id
Foreign Keys
Foreign Keys
 fk_investigation_study_personfk_investigation_study_person ( contact_person_id ) ref study_person (study_person_id)
- + - - + - - + - - + + - + - + - + - + - + @@ -4795,72 +3552,40 @@

Table study_portal
Table study_portal
Controls what studies are visible on what portals
* study_id bigint bigint NOT NULL
* portal_type_id bigint bigint NOT NULL
Indexes
Pkpk_study_portal
Indexes
pk_study_portal primary key ON study_id, portal_type_id
 idx_study_portal
idx_study_portal ON study_id
 idx_study_portal_0
idx_study_portal_0 ON portal_type_id
Foreign Keys
Foreign Keys
 fk_study_portalfk_study_portal ( study_id ) ref study (study_id)
 fk_study_portal_0fk_study_portal_0 ( portal_type_id ) ref portal_type (portal_type_id)
- - - - - - - - - - - - - - - - - - - - - - - - - -
Table data_type
*data_type_id bigserial
*data_type varchar Data type (16S, metabolome, etc) the job will use
Indexes
Pkpk_data_type ON data_type_id
Uidx_data_type ON data_type
- -

- - - + - - + - - + - - + + - + - + - + - + - + @@ -4870,33 +3595,30 @@

Table software_publication
Table software_publication
* software_id bigint bigint NOT NULL
* publication_doi varchar varchar NOT NULL
Indexes
 idx_software_publication
Indexes
idx_software_publication ON software_id
 idx_software_publication
idx_software_publication ON publication_doi
Pkidx_software_publication_0
idx_software_publication_0 primary key ON software_id, publication_doi
Foreign Keys
Foreign Keys
 fk_software_publicationfk_software_publication ( software_id ) ref software (software_id)
 fk_software_publication_0fk_software_publication_0 ( publication_doi ) ref publication (doi)
- + - - + - - + - - + - - + + - + @@ -4906,46 +3628,42 @@

Table visibility
Table visibility
* visibility_id bigserial bigserial NOT NULL
* visibility varchar varchar NOT NULL
* visibility_description varchar varchar NOT NULL
Indexes
Pkpk_visibility
Indexes
pk_visibility primary key ON visibility_id
Uidx_visibility
idx_visibility unique ON visibility
- + - - + - - + - - - - + + - + - + - + @@ -4955,23 +3673,21 @@

Table study_sample
Table study_sample
Required info for each sample. One row is one sample.
* sample_id varchar varchar NOT NULL
* study_id bigint bigint NOT NULL
  ebi_sample_accession varchar
  biosample_accession varchar
Indexes
 idx_required_sample_info
Indexes
idx_required_sample_info ON study_id
Pkidx_required_sample_info_1
idx_required_sample_info_1 primary key ON sample_id
Foreign Keys
Foreign Keys
 fk_required_sample_info_studyfk_required_sample_info_study ( study_id ) ref study (study_id)
- + - - + - - - + + @@ -4981,33 +3697,30 @@

Table publication
Table publication
* doi varchar varchar NOT NULL
  pubmed_id varchar
Indexes
Pkpk_publication
Indexes
pk_publication primary key ON doi
- + - - + - - + - - + - - + + - + @@ -5017,23 +3730,21 @@

Table timeseries_type
Table timeseries_type
* timeseries_type_id bigserial bigserial NOT NULL
* timeseries_type varchar varchar NOT NULL
* intervention_type varchar DEFO 'None' varchar NOT NULL DEFO 'None'
Indexes
Pkpk_timeseries_type
Indexes
pk_timeseries_type primary key ON timeseries_type_id
Uidx_timeseries_type
idx_timeseries_type unique ON timeseries_type, intervention_type
- + - - + - - - + + @@ -5043,43 +3754,230 @@

Table oauth_identifiers
Table oauth_identifiers
* client_id varchar( 50 ) varchar( 50 ) NOT NULL
  client_secret varchar( 255 )
Indexes
Pkpk_oauth_identifiers
Indexes
pk_oauth_identifiers primary key ON client_id
- + - - + - - + - - + + - + - + - - - - + + + + + + + + + + + + +
Table oauth_software
Table oauth_software
* software_id bigint bigint NOT NULL
* client_id bigint bigint NOT NULL
Indexes
Pkidx_oauth_software
Indexes
idx_oauth_software primary key ON software_id, client_id
 idx_oauth_software
idx_oauth_software ON software_id
 idx_oauth_software
idx_oauth_software ON client_id
Foreign Keys
 fk_oauth_software_software ( software_id ) ref software (software_id)
Foreign Keys
fk_oauth_software_software ( software_id ) ref software (software_id)
fk_oauth_software ( client_id ) ref oauth_identifiers (client_id)
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table ebi_run_accession
sample_id varchar NOT NULL
artifact_id bigint NOT NULL
ebi_run_accession bigint NOT NULL
Indexes
idx_ebi_run_accession primary key ON sample_id, artifact_id, ebi_run_accession
idx_ebi_run_accession ON sample_id
idx_ebi_run_accession ON artifact_id
Foreign Keys
fk_ebi_run_accession ( sample_id ) ref study_sample (sample_id)
fk_ebi_run_accession_artifact ( artifact_id ) ref artifact (artifact_id)
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table artifact
Represents data in the system
artifact_id bigserial NOT NULL
name varchar( 35 ) NOT NULL
generated_timestamp timestamp NOT NULL
command_id bigint
command_parameters varchar Varchar in dbschema but is JSON in the postgresDB
visibility_id bigint NOT NULL If the artifact is sandbox, awaiting_for_approval, private or public
artifact_type_id integer
data_type_id bigint NOT NULL
submitted_to_vamps bool NOT NULL DEFO 'FALSE'
Indexes
pk_artifact primary key ON artifact_id
idx_artifact_0 ON visibility_id
idx_artifact_1 ON artifact_type_id
idx_artifact ON command_id
idx_artifact_2 ON data_type_id
Foreign Keys
fk_artifact_visibility ( visibility_id ) ref visibility (visibility_id)
fk_artifact_filetype ( artifact_type_id ) ref artifact_type (artifact_type_id)
fk_artifact_soft_command ( command_id ) ref software_command (command_id)
fk_artifact_data_type ( data_type_id ) ref data_type (data_type_id)
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + @@ -5088,95 +3986,65 @@

Table artifact_type
Type of artifact
artifact_type_id bigserial NOT NULL
artifact_type varchar NOT NULL
description varchar NOT NULL
can_be_submitted_to_ebi bool NOT NULL DEFO 'FALSE'
can_be_submitted_to_vamps bool NOT NULL DEFO 'FALSE'
Indexes
pk_filetype primary key ON artifact_type_id
 fk_oauth_software ( client_id ) ref oauth_identifiers (client_id)
idx_filetype unique ON artifact_type
- - + - - - - - - - - - - - - - - - - + + + - - + - - - - + + + - - - - - - - - - - + + + +
Table analysis
Holds analysis information
Table filepath_type
*analysis_id bigserial Unique identifier for analysis
*email varchar Email for user who owns the analysis
*name varchar Name of the analysis filepath_type_id bigserial NOT NULL
*descriptionfilepath_type varchar
*analysis_status_id bigint
Indexes
pk_filepath_type primary key ON filepath_type_id
 pmid varchar PMID of paper from the analysis
 timestamp timestamptz DEFO current_timestamp
idx_filepath_type unique ON filepath_type
+ +

+ + + + + - - - + + - - - - - - - - - - - - - - - - + + - - + + + - - - + + - - + + - - - + + + @@ -5185,44 +4053,46 @@

Table data_directory
*dflt bool DEFO false data_directory_id bigserial NOT NULL
*portal_type_id bigint
Indexes
Pkpk_analysis ON analysis_id
 idx_analysis_email ON email
 idx_analysis_status_id ON analysis_status_iddata_type varchar NOT NULL
 idx_analysis ON portal_type_id
mountpoint varchar NOT NULL
Foreign Keys
 fk_analysis_user ( email ) ref qiita_user (email) subdirectory bool NOT NULL DEFO FALSE
 fk_analysis_analysis_status ( analysis_status_id ) ref analysis_status (analysis_status_id) active bool NOT NULL
 fk_analysis ( portal_type_id ) ref portal_type (portal_type_id)
Indexes
pk_data_directory primary key ON data_directory_id
- - + - - - + + - - - + + - - - + + + - - + + + - - + + - + + + + + - - + + - - + + @@ -5231,48 +4101,40 @@

Table analysis_chain
Keeps track of the chain of analysis edits. Tracks what previous analysis a given analysis came from.If a given analysis is not in child_id, it is the root of the chain.
Table artifact_type_filepath_type
*parent_id bigint artifact_type_id bigint NOT NULL
*child_id bigint filepath_type_id bigint NOT NULL
Indexes
 idx_analysis_chain ON parent_id
required bool NOT NULL DEFO 'TRUE'
 idx_analysis_chain_0 ON child_id
Indexes
idx_artifact_type_filepath_type primary key ON artifact_type_id, filepath_type_id
Pkidx_analysis_chain_1 ON parent_id, child_id
idx_artifact_type_filepath_type ON artifact_type_id
Foreign Keys
idx_artifact_type_filepath_type ON filepath_type_id
Foreign Keys
 fk_analysis_chain ( parent_id ) ref analysis (analysis_id) fk_artifact_type_filepath_type ( artifact_type_id ) ref artifact_type (artifact_type_id)
 fk_analysis_chain_0 ( child_id ) ref analysis (analysis_id) fk_artifact_type_filepath_type_0 ( filepath_type_id ) ref filepath_type (filepath_type_id)
- + - - - - - - - - - + + - - - + + - - - + + + - - + + - + - + - - + + - + @@ -5282,104 +4144,99 @@

Table ebi_run_accession
Table study_artifact
*sample_id varchar
*artifact_id bigint study_id bigint NOT NULL
*ebi_run_accession bigint artifact_id bigint NOT NULL
Indexes
Pkidx_ebi_run_accession ON sample_id, artifact_id, ebi_run_accession
Indexes
idx_study_artifact primary key ON study_id, artifact_id
 idx_ebi_run_accession ON sample_id
idx_study_artifact ON study_id
 idx_ebi_run_accession
idx_study_artifact ON artifact_id
Foreign Keys
Foreign Keys
 fk_ebi_run_accession ( sample_id ) ref study_sample (sample_id) fk_study_artifact_study ( study_id ) ref study (study_id)
 fk_ebi_run_accession_artifactfk_study_artifact_artifact ( artifact_id ) ref artifact (artifact_id)
- - + - - - + + - - - + + - - - + + - - - + + - - - - - - - - - - + + + - - - + + - - - - + + + - - - - + + - - - + + - - + + + + - - + +
Table artifact
Represents data in the system
Table command_parameter
*artifact_id bigserial command_parameter_id bigserial NOT NULL
*name varchar( 35 ) command_id bigint NOT NULL
*generated_timestamp timestamp parameter_name varchar NOT NULL
 command_id bigint parameter_type varchar NOT NULL
 command_parameters varchar Varchar in dbschema but is JSON in the postgresDB
*visibility_id bigint If the artifact is sandbox, awaiting_for_approval, private or public required bool NOT NULL
 artifact_type_id integer default_value varchar
*data_type_id bigint
Indexes
idx_command_parameter ON command_id
*submitted_to_vamps bool DEFO 'FALSE'
pk_command_parameter primary key ON command_parameter_id
Indexes
Pkpk_artifact ON artifact_id
idx_command_parameter_0 unique ON command_id, parameter_name
 idx_artifact_0 ON visibility_id
Foreign Keys
fk_command_parameter ( command_id ) ref software_command (command_id)
 idx_artifact_1 ON artifact_type_id
+ +

+ + + + + + + + - - + + + - - + + + - - - - + + - - - + + + - - + + - - + + @@ -5388,47 +4245,55 @@

Table parameter_artifact_type
command_parameter_id bigserial NOT NULL
 idx_artifact ON command_id
artifact_type_id bigint NOT NULL
 idx_artifact_2 ON data_type_id
Indexes
idx_parameter_artifact_type primary key ON command_parameter_id, artifact_type_id
Foreign Keys
 fk_artifact_visibility ( visibility_id ) ref visibility (visibility_id)
idx_parameter_artifact_type ON command_parameter_id
 fk_artifact_filetype ( artifact_type_id ) ref artifact_type (artifact_type_id)
idx_parameter_artifact_type ON artifact_type_id
Foreign Keys
 fk_artifact_soft_command ( command_id ) ref software_command (command_id) fk_parameter_artifact_type ( command_parameter_id ) ref command_parameter (command_parameter_id)
 fk_artifact_data_type ( data_type_id ) ref data_type (data_type_id) fk_parameter_artifact_type_0 ( artifact_type_id ) ref artifact_type (artifact_type_id)
- - + - - - + + - - - + + - - - + + - - - + + - - - - + + + + + + + - - + + + + + - - + + + + + + + + + @@ -5437,28 +4302,46 @@

Table artifact_type
Type of artifact
Table command_output
*artifact_type_id bigserial command_output_id bigserial NOT NULL
*artifact_type varchar name varchar NOT NULL
*description varchar command_id bigint NOT NULL
*can_be_submitted_to_ebi bool DEFO 'FALSE' artifact_type_id bigint NOT NULL
*can_be_submitted_to_vamps bool DEFO 'FALSE'
Indexes
pk_command_output primary key ON command_output_id
idx_command_output unique ON name, command_id
Indexes
Pkpk_filetype
idx_command_output ON command_id
idx_command_output ON artifact_type_id
Uidx_filetype ON artifact_type
Foreign Keys
fk_command_output ( command_id ) ref software_command (command_id)
fk_command_output_0 ( artifact_type_id ) ref artifact_type (artifact_type_id)
- + - - - + + - - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + @@ -5467,42 +4350,41 @@

Table filepath_type
Table default_parameter_set
*filepath_type_id bigserial default_parameter_set_id bigserial NOT NULL
 filepath_type varchar command_id bigint NOT NULL
Indexes
Pkpk_filepath_type ON filepath_type_id
parameter_set_name varchar NOT NULL
Uidx_filepath_type ON filepath_type
parameter_set varchar NOT NULL This is a varchar here - but is of type JSON in postgresql.
Indexes
pk_default_parameter_set primary key ON default_parameter_set_id
idx_default_parameter_set ON command_id
idx_default_parameter_set_0 unique ON command_id, parameter_set_name
Foreign Keys
fk_default_parameter_set ( command_id ) ref software_command (command_id)
- + - - - + + - - - + + - - - + + - - - - + + + - - - - + + - - - + + + + + + + + @@ -5511,49 +4393,60 @@

Table data_directory
Table default_workflow
*data_directory_id bigserial default_workflow_id bigserial NOT NULL
*data_type varchar software_id bigint NOT NULL
*mountpoint varchar name varchar NOT NULL
*subdirectory bool DEFO FALSE
Indexes
pk_default_workflow primary key ON default_workflow_id
*active bool
idx_default_workflow unique ON software_id, name
Indexes
Pkpk_data_directory ON data_directory_id
idx_default_workflow ON software_id
Foreign Keys
fk_default_workflow_software ( software_id ) ref software (software_id)
- + - - - + + + + + + + + + + + + + + + + + - - - - + + + - - - - + + - - - + + - - + + - - + + + + - - - + + - - + + @@ -5562,43 +4455,46 @@

Table artifact_type_filepath_type
Table default_workflow_node
*artifact_type_id bigint default_workflow_node_id bigserial NOT NULL
default_workflow_id bigint NOT NULL
command_id bigint NOT NULL
default_parameter_set_id bigint NOT NULL
*filepath_type_id bigint
Indexes
idx_default_workflow_command ON command_id
*required bool DEFO 'TRUE'
idx_default_workflow_command ON default_parameter_set_id
Indexes
Pkidx_artifact_type_filepath_type ON artifact_type_id, filepath_type_id
idx_default_workflow_command_0 ON default_workflow_id
 idx_artifact_type_filepath_type ON artifact_type_id
pk_default_workflow_command primary key ON default_workflow_node_id
 idx_artifact_type_filepath_type ON filepath_type_id
Foreign Keys
fk_default_workflow_command ( command_id ) ref software_command (command_id)
Foreign Keys
 fk_artifact_type_filepath_type ( artifact_type_id ) ref artifact_type (artifact_type_id) fk_default_workflow_command_0 ( default_parameter_set_id ) ref default_parameter_set (default_parameter_set_id)
 fk_artifact_type_filepath_type_0 ( filepath_type_id ) ref filepath_type (filepath_type_id) fk_default_workflow_command_1 ( default_workflow_id ) ref default_workflow (default_workflow_id)
- + - - - + + - - - + + - - - + + + - - + + + - - + + + + + + - + - - + + - - + + @@ -5607,62 +4503,41 @@

Table study_artifact
Table default_workflow_edge
*study_id bigint default_workflow_edge_id bigserial NOT NULL
*artifact_id bigint parent_id bigint NOT NULL
Indexes
Pkidx_study_artifact ON study_id, artifact_id
child_id bigint NOT NULL
 idx_study_artifact ON study_id
Indexes
pk_default_workflow_edge primary key ON default_workflow_edge_id
 idx_study_artifact ON artifact_id
idx_default_workflow_edge ON parent_id
idx_default_workflow_edge ON child_id
Foreign Keys
Foreign Keys
 fk_study_artifact_study ( study_id ) ref study (study_id) fk_default_workflow_edge ( parent_id ) ref default_workflow_node (default_workflow_node_id)
 fk_study_artifact_artifact ( artifact_id ) ref artifact (artifact_id) fk_default_workflow_edge_0 ( child_id ) ref default_workflow_node (default_workflow_node_id)
- + - - - + + - - - + + - - - - + + + - - - - + + - - - - + + + - - - - - - - - - - - - - - - - + + - - - + + @@ -5671,43 +4546,51 @@

Table command_parameter
Table artifact_processing_job
*command_parameter_id bigserial artifact_id bigint NOT NULL
*command_id bigint processing_job_id bigint NOT NULL
*parameter_name varchar
Indexes
idx_artifact_processing_job primary key ON artifact_id, processing_job_id
*parameter_type varchar
idx_artifact_processing_job ON artifact_id
*required bool
idx_artifact_processing_job ON processing_job_id
Foreign Keys
 default_value varchar
Indexes
 idx_command_parameter ON command_id
Pkpk_command_parameter ON command_parameter_id
Uidx_command_parameter_0 ON command_id, parameter_namefk_artifact_processing_job ( artifact_id ) ref artifact (artifact_id)
Foreign Keys
 fk_command_parameter ( command_id ) ref software_command (command_id) fk_artifact_processing_job_0 ( processing_job_id ) ref processing_job (processing_job_id)
- + - - - + + - - - + + - - - + + + - - + + + - - + + + + + + - + - - + + - - + + + + + + + @@ -5716,59 +4599,70 @@

Table parameter_artifact_type
Table artifact_output_processing_job
*command_parameter_id bigserial artifact_id bigint NOT NULL
*artifact_type_id bigint processing_job_id bigint NOT NULL
Indexes
Pkidx_parameter_artifact_type ON command_parameter_id, artifact_type_id
command_output_id bigint NOT NULL
 idx_parameter_artifact_type ON command_parameter_id
Indexes
idx_artifact_output_processing_job ON artifact_id
 idx_parameter_artifact_type ON artifact_type_id
idx_artifact_output_processing_job ON processing_job_id
idx_artifact_output_processing_job ON command_output_id
Foreign Keys
Foreign Keys
 fk_parameter_artifact_type ( command_parameter_id ) ref command_parameter (command_parameter_id) fk_artifact_output_processing_job ( artifact_id ) ref artifact (artifact_id)
 fk_parameter_artifact_type_0 ( artifact_type_id ) ref artifact_type (artifact_type_id) fk_artifact_output_processing_job_0 ( processing_job_id ) ref processing_job (processing_job_id)
fk_artifact_output_processing_job_1 ( command_output_id ) ref command_output (command_output_id)
- + - - - + + - - + + + + + + - - + + + + + + - - + - - - + + + - - + + - - + + - - + + - + - - + + - - + + + + + + + @@ -5777,50 +4671,27 @@

Table command_output
Table reference
*command_output_id bigserial reference_id bigserial NOT NULL
*namereference_name varchar NOT NULL
reference_version varchar
*command_idsequence_filepath bigint NOT NULL
taxonomy_filepath bigint
*artifact_type_idtree_filepath bigint
Indexes
Pkpk_command_output ON command_output_id
Indexes
pk_reference primary key ON reference_id
Uidx_command_output ON name, command_id
idx_reference ON sequence_filepath
 idx_command_output ON command_id
idx_reference_0 ON taxonomy_filepath
 idx_command_output ON artifact_type_id
idx_reference_1 ON tree_filepath
Foreign Keys
Foreign Keys
 fk_command_output ( command_id ) ref software_command (command_id) fk_reference_sequence_filepath ( sequence_filepath ) ref filepath (filepath_id)
 fk_command_output_0 ( artifact_type_id ) ref artifact_type (artifact_type_id) fk_reference_taxonomy_filepath ( taxonomy_filepath ) ref filepath (filepath_id)
fk_reference_tree_filepath ( tree_filepath ) ref filepath (filepath_id)
- + - - - - - - - - - + + - - - + + - - - - - - - - - - - - - - - - + + - - - - + + + @@ -5829,44 +4700,41 @@

Table default_parameter_set
Table processing_job_status
*default_parameter_set_id bigserial
*command_id bigint processing_job_status_id bigserial NOT NULL
*parameter_set_name varchar processing_job_status varchar NOT NULL
*parameter_set varchar This is a varchar here - but is of type JSON in postgresql.
Indexes
Pkpk_default_parameter_set ON default_parameter_set_id
 idx_default_parameter_set ON command_id
Uidx_default_parameter_set_0 ON command_id, parameter_set_nameprocessing_job_status_description varchar NOT NULL
Foreign Keys
 fk_default_parameter_set ( command_id ) ref software_command (command_id)
Indexes
pk_processing_job_status primary key ON processing_job_status_id
- + - - - + + - - - + + - - - - + + + - - - + + - - + + - - + + + + - - - + + @@ -5875,64 +4743,37 @@

Table default_workflow
Table parent_processing_job
*default_workflow_id bigserial parent_id bigint NOT NULL
*software_id bigint child_id bigint NOT NULL
*name varchar
Indexes
idx_parent_processing_job primary key ON parent_id, child_id
Indexes
Pkpk_default_workflow ON default_workflow_id
idx_parent_processing_job ON parent_id
Uidx_default_workflow ON software_id, name
idx_parent_processing_job ON child_id
 idx_default_workflow ON software_id
Foreign Keys
fk_parent_processing_job ( parent_id ) ref processing_job (processing_job_id)
Foreign Keys
 fk_default_workflow_software ( software_id ) ref software (software_id) fk_parent_processing_job_0 ( child_id ) ref processing_job (processing_job_id)
- + - - - - - - - - - + + - - - + + - - - - - - - - - - - - - - - - - - - - + + - - - - + + + - - - + + + - - + + @@ -5941,49 +4782,41 @@

Table default_workflow_node
Table processing_job_workflow
*default_workflow_node_id bigserial
*default_workflow_id bigint processing_job_workflow_id bigserial NOT NULL
*command_id bigint email varchar NOT NULL
*default_parameter_set_id bigint
Indexes
 idx_default_workflow_command ON command_id
 idx_default_workflow_command ON default_parameter_set_id
 idx_default_workflow_command_0 ON default_workflow_id
Pkpk_default_workflow_command ON default_workflow_node_idname varchar
Foreign Keys
 fk_default_workflow_command ( command_id ) ref software_command (command_id)
Indexes
pk_processing_job_workflow primary key ON processing_job_workflow_id
 fk_default_workflow_command_0 ( default_parameter_set_id ) ref default_parameter_set (default_parameter_set_id)
idx_processing_job_workflow ON email
Foreign Keys
 fk_default_workflow_command_1 ( default_workflow_id ) ref default_workflow (default_workflow_id) fk_processing_job_workflow ( email ) ref qiita_user (email)
- + - - - - - - - - - + + - - - + + - - - + + + - - + + - - + + - + - - + + - - + + @@ -5992,42 +4825,40 @@

Table default_workflow_edge
Table processing_job_workflow_root
*default_workflow_edge_id bigserial
*parent_id bigint processing_job_workflow_id bigint NOT NULL
*child_id bigint processing_job_id bigint NOT NULL
Indexes
Pkpk_default_workflow_edge ON default_workflow_edge_id
Indexes
idx_processing_job_workflow_roots ON processing_job_workflow_id
 idx_default_workflow_edge ON parent_id
idx_processing_job_workflow_roots ON processing_job_id
 idx_default_workflow_edge ON child_id
idx_processing_job_workflow_roots_0 primary key ON processing_job_workflow_id, processing_job_id
Foreign Keys
Foreign Keys
 fk_default_workflow_edge ( parent_id ) ref default_workflow_node (default_workflow_node_id) fk_processing_job_workflow_roots_0 ( processing_job_id ) ref processing_job (processing_job_id)
 fk_default_workflow_edge_0 ( child_id ) ref default_workflow_node (default_workflow_node_id) fk_processing_job_workflow_roots ( processing_job_workflow_id ) ref processing_job_workflow (processing_job_workflow_id)
- + - - - + + - - - + + - - - + + + - - + + - + - + - - + + - + @@ -6037,54 +4868,42 @@

Table artifact_processing_job
Table prep_template_processing_job
*artifact_id bigint prep_template_id bigint NOT NULL
*processing_job_id bigint processing_job_id bigint NOT NULL
Indexes
Pkidx_artifact_processing_job ON artifact_id, processing_job_id
Indexes
idx_prep_template_processing_job primary key ON prep_template_id, processing_job_id
 idx_artifact_processing_job ON artifact_id
idx_prep_template_processing_job ON prep_template_id
 idx_artifact_processing_job
idx_prep_template_processing_job ON processing_job_id
Foreign Keys
Foreign Keys
 fk_artifact_processing_job ( artifact_id ) ref artifact (artifact_id) fk_prep_template_processing_job ( prep_template_id ) ref prep_template (prep_template_id)
 fk_artifact_processing_job_0fk_prep_template_processing_job_0 ( processing_job_id ) ref processing_job (processing_job_id)
- + + - - - - - - - - - + + - - - - - - - - + + - - + + + - - + + - - - - + + + - - + + - - + + @@ -6093,108 +4912,91 @@

Table artifact_output_processing_job
Table software_artifact_type
In case that the software is of type "type plugin", it holds the artifact types that such software can validate and generate the summary.
*artifact_id bigint
*processing_job_id bigint software_id bigint NOT NULL
*command_output_id bigint
Indexes
 idx_artifact_output_processing_job ON artifact_idartifact_type_id bigint NOT NULL
 idx_artifact_output_processing_job ON processing_job_id
Indexes
idx_software_artifact_type primary key ON software_id, artifact_type_id
 idx_artifact_output_processing_job ON command_output_id
idx_software_artifact_type ON artifact_type_id
Foreign Keys
 fk_artifact_output_processing_job ( artifact_id ) ref artifact (artifact_id)
idx_software_artifact_type ON software_id
Foreign Keys
 fk_artifact_output_processing_job_0 ( processing_job_id ) ref processing_job (processing_job_id) fk_software_artifact_type ( artifact_type_id ) ref artifact_type (artifact_type_id)
 fk_artifact_output_processing_job_1 ( command_output_id ) ref command_output (command_output_id) fk_software_artifact_type_sw ( software_id ) ref software (software_id)
- + - - - + + - - - + + - - - + + - - - - + + + + +
Table reference
Table software_type
*reference_id bigserial software_type_id bigserial NOT NULL
*reference_name varchar software_type varchar NOT NULL
 reference_version varchar description varchar NOT NULL
*sequence_filepath bigint
Indexes
pk_software_type primary key ON software_type_id
+ +

+ + + + + - - - + + - - - - - - - - - - - - + + - - + + + - - + + + - - - + + - - + + - - + + - -
Table software
 taxonomy_filepath bigint software_id bigserial NOT NULL
 tree_filepath bigint
Indexes
Pkpk_reference ON reference_id
 idx_reference ON sequence_filepathname varchar NOT NULL
 idx_reference_0 ON taxonomy_filepath
version varchar NOT NULL
 idx_reference_1 ON tree_filepath
description varchar NOT NULL
Foreign Keys
 fk_reference_sequence_filepath ( sequence_filepath ) ref filepath (filepath_id) environment_script varchar NOT NULL
 fk_reference_taxonomy_filepath ( taxonomy_filepath ) ref filepath (filepath_id) start_script varchar NOT NULL
 fk_reference_tree_filepath ( tree_filepath ) ref filepath (filepath_id) software_type_id bigint NOT NULL
- -

- - - - - - - - + + - - - - + + + - - - - + + - - - + + + + @@ -6203,43 +5005,47 @@

Table processing_job_status
*processing_job_status_id bigserial active bool NOT NULL DEFO 'True'
*processing_job_status varchar
Indexes
pk_software primary key ON software_id
*processing_job_status_description varchar
idx_software ON software_type_id
Indexes
Pkpk_processing_job_status ON processing_job_status_id
Foreign Keys
fk_software_software_type ( software_type_id ) ref software_type (software_type_id)
- + - - - + + - - - + + - - - + + + - - + + + - - + + + - - - - + + + + + + + + - - + + @@ -6248,85 +5054,151 @@

Table parent_processing_job
Table software_command
*parent_id bigint command_id bigserial NOT NULL
*child_id bigint name varchar NOT NULL
Indexes
Pkidx_parent_processing_job ON parent_id, child_id
software_id bigint NOT NULL
 idx_parent_processing_job ON parent_id
description varchar NOT NULL
 idx_parent_processing_job ON child_id
active bool NOT NULL DEFO 'True'
Foreign Keys
 fk_parent_processing_job ( parent_id ) ref processing_job (processing_job_id)
Indexes
pk_soft_command primary key ON command_id
idx_soft_command ON software_id
Foreign Keys
 fk_parent_processing_job_0 ( child_id ) ref processing_job (processing_job_id) fk_soft_command_software ( software_id ) ref software (software_id)
- + - - - + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + - - + - - - + + + - + - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + +
Table processing_job_workflow
Table processing_job
*processing_job_workflow_id bigserial processing_job_id bigserial NOT NULL Using bigserial in DBSchema - however in the postgres DB this column is of type UUID.
email varchar NOT NULL The user that launched the job
command_id bigint NOT NULL The command launched
command_parameters varchar NOT NULL The parameters used in the command in JSON format
processing_job_status_id bigint NOT NULL
*emaillogging_id bigint In case of failure, point to the log entry that holds more information about the error
heartbeat timestamp The last heartbeat received by this job
step varchar
 namepending varchar
Indexes
Pkpk_processing_job_workflow ON processing_job_workflow_id
Indexes
pk_processing_job primary key ON processing_job_id
 idx_processing_job_workflow
idx_processing_job ON email
Foreign Keys
idx_processing_job ON command_id
idx_processing_job_0 ON processing_job_status_id
idx_processing_job_1 ON logging_id
Foreign Keys
 fk_processing_job_workflowfk_processing_job_qiita_user ( email ) ref qiita_user (email)
fk_processing_job ( command_id ) ref software_command (command_id)
fk_processing_job_status ( processing_job_status_id ) ref processing_job_status (processing_job_status_id)
fk_processing_job_logging ( logging_id ) ref logging (logging_id)


- + - - - + + - - - + + - - - + + + - - + + + - - + + + + + + - + + + + + - - + + - - + + + + + + + @@ -6335,89 +5207,76 @@

Table processing_job_workflow_root
Table default_workflow_edge_connections
*processing_job_workflow_id bigint default_workflow_edge_id bigint NOT NULL
*processing_job_id bigint parent_output_id bigint NOT NULL
Indexes
 idx_processing_job_workflow_roots ON processing_job_workflow_id
child_input_id bigint NOT NULL
 idx_processing_job_workflow_roots ON processing_job_id
Indexes
idx_default_workflow_edge_connections primary key ON default_workflow_edge_id, parent_output_id, child_input_id
Pkidx_processing_job_workflow_roots_0 ON processing_job_workflow_id, processing_job_id
idx_default_workflow_edge_connections ON parent_output_id
idx_default_workflow_edge_connections ON child_input_id
Foreign Keys
idx_default_workflow_edge_connections_0 ON default_workflow_edge_id
Foreign Keys
 fk_processing_job_workflow_roots_0 ( processing_job_id ) ref processing_job (processing_job_id) fk_default_workflow_edge_connections ( parent_output_id ) ref command_output (command_output_id)
 fk_processing_job_workflow_roots ( processing_job_workflow_id ) ref processing_job_workflow (processing_job_workflow_id) fk_default_workflow_edge_connections_0 ( child_input_id ) ref command_parameter (command_parameter_id)
fk_default_workflow_edge_connections_1 ( default_workflow_edge_id ) ref default_workflow_edge (default_workflow_edge_id)
- + - - - + + - - - + + - - - + + + - - + + + - - + + - - - - + + + - + + + + + +
Table prep_template_processing_job
Table processing_job_validator
*prep_template_id bigint processing_job_id bigint NOT NULL
*processing_job_id bigint validator_id bigint NOT NULL
Indexes
Pkidx_prep_template_processing_job ON prep_template_id, processing_job_id
artifact_info varchar
 idx_prep_template_processing_job ON prep_template_id
Indexes
idx_processing_job_validator ON processing_job_id
 idx_prep_template_processing_job ON processing_job_id
idx_processing_job_validator ON validator_id
Foreign Keys
 fk_prep_template_processing_job ( prep_template_id ) ref prep_template (prep_template_id)
idx_processing_job_validator_0 primary key ON processing_job_id, validator_id
Foreign Keys
 fk_prep_template_processing_job_0fk_processing_job_validator ( processing_job_id ) ref processing_job (processing_job_id)
fk_processing_job_validator_0 ( validator_id ) ref processing_job (processing_job_id)


- - + - - - + + - - - - - - - - - - - - - - - - + + - - - + + + - - + + @@ -6426,30 +5285,26 @@

Table software_artifact_type
In case that the software is of type "type plugin", it holds the artifact types that such software can validate and generate the summary.
Table study_publication
*software_id bigint study_id bigint NOT NULL
*artifact_type_id bigint
Indexes
Pkidx_software_artifact_type ON software_id, artifact_type_id
 idx_software_artifact_type ON artifact_type_id
 idx_software_artifact_type ON software_idpublication varchar NOT NULL
Foreign Keys
 fk_software_artifact_type ( artifact_type_id ) ref artifact_type (artifact_type_id) is_doi bool
Foreign Keys
 fk_software_artifact_type_sw ( software_id ) ref software (software_id) fk_study_publication_study ( study_id ) ref study (study_id)
- + - - - + + - - - + + - - - - + + + - - - + + @@ -6458,70 +5313,86 @@

Table software_type
Table analysis_status
*software_type_id bigserial analysis_status_id bigserial NOT NULL
*software_type varchar status varchar NOT NULL
*description varchar
Indexes
pk_analysis_status primary key ON analysis_status_id
Indexes
Pkpk_software_type ON software_type_id
idx_analysis_status unique ON status
- + + - - - + + + + + + + + + + + + + + + + + + + + + + - - + - + - - - + + - - - + + - - - + + - - - - + + + - - - - + + - - - - + + - - - + + - - + + + + - - - + + + + + + + @@ -6530,52 +5401,42 @@

Table software
Table analysis
Holds analysis information
*software_id bigserial analysis_id bigserial NOT NULL Unique identifier for analysis
email varchar NOT NULL Email for user who owns the analysis
name varchar NOT NULL Name of the analysis
description varchar NOT NULL
analysis_status_id bigint NOT NULL
*namepmid varchar PMID of paper from the analysis
*version varchar timestamp timestamptz DEFO current_timestamp
*description varchar dflt bool NOT NULL DEFO false
*environment_script varchar portal_type_id bigint NOT NULL
*start_script varchar
Indexes
pk_analysis primary key ON analysis_id
*software_type_id bigint
idx_analysis_email ON email
*active bool DEFO 'True'
idx_analysis_status_id ON analysis_status_id
Indexes
Pkpk_software ON software_id
idx_analysis ON portal_type_id
 idx_software ON software_type_id
Foreign Keys
fk_analysis_user ( email ) ref qiita_user (email)
Foreign Keys
 fk_software_software_type ( software_type_id ) ref software_type (software_type_id) fk_analysis_analysis_status ( analysis_status_id ) ref analysis_status (analysis_status_id)
fk_analysis ( portal_type_id ) ref portal_type (portal_type_id)
- + + - - - - - - - - - + + - - - + + - - - - + + + - - - - + + - - - + + - - + + + + - - - + + @@ -6584,220 +5445,183 @@

Table software_command
Table analysis_users
Links analyses to the users they are shared with
*command_id bigserial
*name varchar analysis_id bigint NOT NULL
*software_id bigint email varchar NOT NULL
*description varchar
Indexes
idx_analysis_users primary key ON analysis_id, email
*active bool DEFO 'True'
idx_analysis_users_analysis ON analysis_id
Indexes
Pkpk_soft_command ON command_id
idx_analysis_users_email ON email
 idx_soft_command ON software_id
Foreign Keys
fk_analysis_users_analysis ( analysis_id ) ref analysis (analysis_id)
Foreign Keys
 fk_soft_command_software ( software_id ) ref software (software_id) fk_analysis_users_user ( email ) ref qiita_user (email)
- + + - - - - + + + - - - - + + + - - - - + + + + + + + + - - - - + + + - - - - + + + + +
Table job
Table analysis_portal
Controls what analyses are visible on what portals
*job_id bigserial Unique identifier for job analysis_id bigint NOT NULL
*data_type_id bigint What datatype (16s, metabolome, etc) job is run on. portal_type_id bigint NOT NULL
*job_status_id bigint
Indexes
idx_analysis_portal ON analysis_id
idx_analysis_portal_0 ON portal_type_id
Foreign Keys
*command_id bigint The Qiime or other function being run (alpha diversity, etc) fk_analysis_portal ( analysis_id ) ref analysis (analysis_id)
 options varchar Holds all options set for the job as a json string fk_analysis_portal_0 ( portal_type_id ) ref portal_type (portal_type_id)
+ +

+ + + + + + - - - - + + + - - - + + - - + - - - + + + - - + + - - + + - + - - - - - - - - - - - - - + - - + + - - + + - + - - - - - - - - - - - - - - -
Table analysis_filepath
Stores link between analysis and the data file used for the analysis.
 log_id bigint Reference to error if status is error analysis_id bigint NOT NULL
 input_file_reference_id bigint filepath_id bigint NOT NULL
 input_file_software_command_iddata_type_id bigint
Indexes
Pkpk_job ON job_id
Indexes
idx_analysis_filepath ON analysis_id
 idx_job_command ON command_id
idx_analysis_filepath_0 ON filepath_id
 idx_job_status ON job_status_id
idx_analysis_filepath_1 primary key ON analysis_id, filepath_id
 idx_job_type
idx_analysis_filepath_2 ON data_type_id
 idx_job ON log_id
 idx_job_0 ON input_file_reference_id
 idx_job_1 ON input_file_software_command_id
Foreign Keys
Foreign Keys
 fk_job_function ( command_id ) ref command (command_id) fk_analysis_filepath ( analysis_id ) ref analysis (analysis_id)
 fk_job_job_status_id ( job_status_id ) ref job_status (job_status_id) fk_analysis_filepath_0 ( filepath_id ) ref filepath (filepath_id)
 fk_job_data_typefk_analysis_filepath_1 ( data_type_id ) ref data_type (data_type_id)
 fk_job ( log_id ) ref logging (logging_id)
 fk_job_reference ( input_file_reference_id ) ref reference (reference_id)
 fk_job_software_command ( input_file_software_command_id ) ref software_command (command_id)


- + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + - - - + + - - - - + + + - - - + + - - + + - - + + - - + + + + - - + + + - - - + + + +
Table processing_job
Table analysis_sample
*processing_job_id bigserial Using bigserial in DBSchema - however in the postgres DB this column is of type UUID.
*email varchar The user that launched the job
*command_id bigint The command launched
*command_parameters varchar The parameters used in the command in JSON format
*processing_job_status_id bigint analysis_id bigint NOT NULL
 logging_id bigint In case of failure, point to the log entry that holds more information about the error
 heartbeat timestamp The last heartbeat received by this job artifact_id bigint NOT NULL
 step varchar sample_id varchar NOT NULL
 pending varchar
Indexes
idx_analysis_sample ON analysis_id
Indexes
Pkpk_processing_job ON processing_job_id
idx_analysis_sample_0 ON artifact_id
 idx_processing_job ON email
idx_analysis_sample_1 ON sample_id
 idx_processing_job ON command_id
pk_analysis_sample primary key ON analysis_id, artifact_id, sample_id
 idx_processing_job_0 ON processing_job_status_id
Foreign Keys
fk_analysis_sample_analysis ( analysis_id ) ref analysis (analysis_id)
 idx_processing_job_1 ON logging_id
fk_analysis_sample ( sample_id ) ref study_sample (sample_id)
Foreign Keys
 fk_processing_job_qiita_user ( email ) ref qiita_user (email) fk_analysis_sample_artifact ( artifact_id ) ref artifact (artifact_id)
+ +

+ + + + + + - - + + - - + + - - + + + + + + + @@ -6806,58 +5630,56 @@

Table portal_type
What portals are available to show a study in
 fk_processing_job ( command_id ) ref software_command (command_id) portal_type_id bigserial NOT NULL
 fk_processing_job_status ( processing_job_status_id ) ref processing_job_status (processing_job_status_id) portal varchar NOT NULL
 fk_processing_job_logging ( logging_id ) ref logging (logging_id) portal_description varchar NOT NULL
Indexes
pk_portal_type primary key ON portal_type_id
- + - - - + + - - - + + - - - + + - - - + + + - - + + + - - + + + - - + + + - - - + + - - - + + + - - - + + @@ -6866,49 +5688,41 @@

Table default_workflow_edge_connections
Table ontology
*default_workflow_edge_id bigint ontology_id bigint NOT NULL
*parent_output_id bigint ontology varchar NOT NULL
*child_input_id bigint fully_loaded bool NOT NULL
Indexes
Pkidx_default_workflow_edge_connections ON default_workflow_edge_id, parent_output_id, child_input_id
fullname varchar
 idx_default_workflow_edge_connections ON parent_output_id
query_url varchar
 idx_default_workflow_edge_connections ON child_input_id
source_url varchar
 idx_default_workflow_edge_connections_0 ON default_workflow_edge_id
definition text
Foreign Keys
 fk_default_workflow_edge_connections ( parent_output_id ) ref command_output (command_output_id) load_date date NOT NULL
 fk_default_workflow_edge_connections_0 ( child_input_id ) ref command_parameter (command_parameter_id)
Indexes
pk_ontology primary key ON ontology_id
 fk_default_workflow_edge_connections_1 ( default_workflow_edge_id ) ref default_workflow_edge (default_workflow_edge_id)
idx_ontology unique ON ontology
- + - - - - - - - - - + + - - - + + - - - + + + - - + + - - + + - + - - + + - - + + @@ -6917,31 +5731,26 @@

Table processing_job_validator
Table analysis_artifact
*processing_job_id bigint
*validator_id bigint analysis_id bigint NOT NULL
 artifact_info varchar artifact_id bigint NOT NULL
Indexes
 idx_processing_job_validator ON processing_job_id
Indexes
idx_analysis_artifact ON analysis_id
 idx_processing_job_validator ON validator_id
idx_analysis_artifact ON artifact_id
Pkidx_processing_job_validator_0 ON processing_job_id, validator_id
idx_analysis_artifact_0 primary key ON analysis_id, artifact_id
Foreign Keys
Foreign Keys
 fk_processing_job_validator ( processing_job_id ) ref processing_job (processing_job_id) fk_analysis_artifact_analysis ( analysis_id ) ref analysis (analysis_id)
 fk_processing_job_validator_0 ( validator_id ) ref processing_job (processing_job_id) fk_analysis_artifact_artifact ( artifact_id ) ref artifact (artifact_id)
- + - - - + + - - - - + + + - - - - + + + - - - - + + From a2b883e5f7f470d3a2e074eb8bc3977b0a0a3946 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sun, 8 Jan 2017 21:41:28 -0800 Subject: [PATCH 02/32] Adding python patch --- qiita_db/support_files/patches/47.sql | 3 ++ .../patches/python_patches/47.py | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 qiita_db/support_files/patches/python_patches/47.py diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 6617a1137..5d7c4f5b7 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -274,6 +274,9 @@ BEGIN -- Step 1.1.b: Associate the artifact with the analysis INSERT INTO qiita.analysis_artifact (analysis_id, artifact_id) VALUES (analysis.analysis_id, initial_biom_id); + -- Step 1.1.c: Link the artifact with its file + INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) + VALUES (initial_biom_id, biom_data.filepath_id); -- Step 1.2: Create the single rarefaction job -- Step 1.2.a: Add the row in the procesisng job table diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py new file mode 100644 index 000000000..8e402026f --- /dev/null +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -0,0 +1,50 @@ +# There are 2 things that need to be done in this patch: +# 1) Correct the rarefaction depth of the existing jobs and artifcats +# 2) Purge filepaths + +from json import dumps + +from biom import load_table + +from qiita_db.util import purge_filepaths +from qiita_db.artifact import Artifact +from qiita_db.sql_connection import TRN + + +# 1) Correct the rarefaction depth of the existing jobs and artifcats +with TRN: + sql = """SELECT command_id + FROM qiita.software_command + WHERE name = 'Single Rarefaction'""" + TRN.add(sql) + cmd_id = TRN.execute_fetchlast() + + sql = "SELECT artifact_id FROM qiita.artifact WHERE command_id = %s" + TRN.add(sql, [cmd_id]) + + sql_update_artifact = """UPDATE qiita.artifact + SET command_parameters = %s + WHERE artifact_id = %s""" + sql_update_job = """UPDATE qiita.processing_job + SET command_parameters = %s + WHERE processing_job_id = ( + SELECT processing_job_id + FROM qiita.artifact_output_processing_job + WHERE artifact_id = %s)""" + + for a_id in TRN.execute_fetchflatten(): + a = Artifact(a_id) + params = a.processing_parameters.values + # load the biom table to check the rarefaction depth + # Magic numbers: since we added the artifacts on the patch 47.sql, we + # know that these artifacts have only 1 file, hence the first 0. + # Each element of the filepath list is a 3-tuple with filepath_id, + # filepath and filepath type, and we are interested in the filepath + # (index 1) + t = load_table(a.filepaths[0][1]) + params['depth'] = t[:, 0].sum() + TRN.add(sql_update_artifact, [dumps(params), a_id]) + TRN.add(sql_update_job, [dumps(params), a_id]) + +# 2) Purge filepaths +purge_filepaths() From e737f64f4633dc4937a6d53296b3f60057c5c3ac Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sun, 8 Jan 2017 21:52:48 -0800 Subject: [PATCH 03/32] Adding a biom so we can actually execute the patch --- .../test_data/analysis/1_analysis_18S.biom | Bin 0 -> 1109874 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/qiita_db/support_files/test_data/analysis/1_analysis_18S.biom b/qiita_db/support_files/test_data/analysis/1_analysis_18S.biom index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..362529c07135f0b207500c571721c2a1fc519b19 100644 GIT binary patch literal 1109874 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
Table study_publication
Table data_type
*study_id bigint data_type_id bigserial NOT NULL
*publication varchar data_type varchar NOT NULL Data type (16S, metabolome, etc) the job will use
 is_doi bool
Indexes
pk_data_type primary key ON data_type_id
Foreign Keys
 fk_study_publication_study ( study_id ) ref study (study_id)
idx_data_type unique ON data_type
A9oD-#Qj7q za4i41^OC;MYkj8-Iq4Xg)f>#x|5%+FMKY z4QXKyf?gZfVCc1R4e|7@4$J#a_x?)yn}0*Y>sR&e`oo~7JUV-Rj#D}B84jFZe%Ssjb(!cM@kI?1gIqoJpGJAYVzD^4B*J^AbWVEStR z8w;E@55@tf^@;J&vvG;*%Y@J`aebK>$+vSP-!8yeG$!yLmsh@?-TkZ*3Z5npIATpg?wWD><=9EL08w= zIQ{oZ4*<@}|AC=@V*U?`^zUHcwE8{-daWJ~gfg=Z z+|9zWlge4ZRlEYa;!*HqxK#LOyYxT@M_~Q|^BD2I$FeH?H9=?px35JIZek+z5UC zdm-JgvCe8Nw7FjwprhWKLZ0gW)OD_#LqDo|m$qA=x9aXz;8^~}bJ^Q~WAmM#n^(>) zZwHR$pW|qF$sNG4I92_3fjS4+F>Y$#Io@KH^>1)xG$+ z{YQbLIJ-WVKIdrdm`WimQ$FV59G|$)d>lBx?>zyz6|X0Ov*PuXhfDpD*3GAZqrT?G zms>ZVfu6?ic*Oh4XMv-AjpNDPS3Vc%&+~>qFFe-Aj` zkNNNPybnFA$K3OY51_Z|;6vb89fC^b|@9+CA(vR-|5#S#|s`aBQ5pI{qK@Jia_m|AqFsI3@VuJ8sYOGavL+Pi{Q9edPSmQ~c8Y zkb8d`GjajoXg{6KgB%W#Sr9lYZwqz%cHncQOE4G$@iY?u4CrgEX ziSuP?;Iw|Y4D?oAFAE&ijq{85@ya||F62x55Pj~n((jiK`8ck^w*qi1{^B@S1Wp_0 zN|8J(1IO~1`<~)eJiW^!+g}tXuL`|ZPpd&s=Z((Zy@%c_Ssik!Qy1T+#$rw9?)bpR z8ql*ih;_IoaLoVQdt+;bJYqc822P9TI*~l<8a(TTJmR?42ae4P@w{(?NWKjt`8ER1 znztK=eB!*_B=k>=|E9oc@!t%3HgCoII4*!S>vgI=4T-JxgmOuS$10UTTBbN8z~p{F=G zKg9QidI3lE<>tMID<81*29D}qi)SC;w0Lgq>HYX@1HBfXZJ}r5;`^n_`y<-{N6#hQ zxU%=~=yz(iho03hUr#FQ#17u|v~_1k;8@#nJI)`;hW+R%O{q`bP5Ah5m{2 zp&xLR=QQrp`rjWomcQKZUk!ks{MYJWV91l3SA(Fp;y)NTR_D3>>=5X!aSa8I{C05{ zzxQewa8!S8eAWG{+xHBI-kVBHS8xF_PasP#v|@SrU1wCCGJCZ2af8*)uZ_R zxdq@T4{luI_vcQH^shejPdt~I1{{lb?i{G->C?RNbE4_cTl0QK=#Mz>8-Qc+7wf46 z9P3B9b$5@DN6h2Q&@b`(85=#E%cFSC-{f64jhA%J-|XQWpXgtUcU{LP=5ZEq^t|8k z<-QMdPw3ga$$cMYYv_*{_qNcVF|$h9GYtN+5qO{h14#zpl)KoaVch|GmBIX!+j%t6)9z~yfu8PPUA%Mm*M~w+`EdR)ePww|4g-$G zNnC#q5BbFP_lQWoBP02a0*>O~##z_U*wWfEy|Fl_lq5%oJd;MZv=vLKK8-qlK;@W7 zzm5(0#Bm-6oK~O5L$B5636cJs80pVRz_Gd#`^m{6pV%)?iR3#qiRGgt+1icmai-BYPMw~a707w42xQqA2mjb8V z7he|gAP(g_)Dk-L<-pPYD_w7-^Z6@)W8)Opiz|U+`>Nc1-c^x2R~tOnK+oz++;3b9 z9DB|*eDE&adtMj%!Ox8=>(=$aQ9fLJIIgm8-2j}n-rNX1%fGnZ+ytD~H*bcX^?}?z zaox-F;0&Ir}g6}pr<%GfAakdGy6&4tiJbDq+d@PemxV(^DJk~m zitGP?v*P+6aIBAr?*S#aF}3FFe84gP#Qr-!a9SV9lH>koVBN@J^JV|R|2IjrIL{A5 zEzVRI+@9~V@U>J4`Q`Fl-G{kj9=r~gP`q58#C;LMnozvb`6S&B()~WmJ3r5>^n2RJ z&~+Uj$5m!1y+>)~o$6TYhjf0z{Nm58D$lp5FEhV5u5vI&@wWUTe;A**f2aJ?-*moY zo8iV%bFomYLH)FpIRg26XUcd za1?(x&iwqr%vcLJimUT4kHgGZ8#tODjxRkA>Rbc)I{547e_hB~oWyah2ONu&IIi`9 zqdLs;FYL!Q2>HbQ*oMGSzjAzRzg3*PQOLvemFbym44gI2O@O1kRO6lhp3Y5yv&Oku z=$|;w%|rjhy59mg>I=@lT-|pLdBnQkGW1KV`>lYZ`g8FY?+3eteB!!D^=RdxEBGky zZk(e_^)rj@?QPj{W^h-P^9{?QfFC0(qx$3}3o{La4mJy1?J z40<-7t9mzUhC^@F_XyxaovtDCYO_7k9|@jH95jwgE8 zasK4$cxUKY-o*Iq0vzqT9Z&9i5+^~==Baq!y(@6!x8o7lQ#`ItEWdUGAMMK>U+%eG zd6%42_S=3P?H>9g)=>dCwm%y+dUWTS8px;OufIR5huj*+G~l#x6rpGF75mL};Mo2m z_grp9q(2S7QN6pkqK@H`!1!(`!FR^|O0FLU_ zjZfV79T@V7{rVu_v~eCB$#aOob7;sTj_)ww*!<|<+#Y@}#Ni>IIKCr*WAPI2XOE2J zI|?|KPqE(}9m#i0B;T>Xv3e8N&EtS$;}zG<<3s<%`_L1B>x93q-bVDT>Ac5@_;1Uf zlYq1QIT<*sPn-grRx=by25@XV;`}=kIIVA< z1-;g%&W4`Fm*1yU?q|*cj@2W_Rqkib1&+m6%=dY~vGpW(?r=WzG=6QpyZ|^FzrS8y z2tD&hjQ2&}^&F4*yzye-SiHo2&n3XI@#XeCmqJf|`|I6h&}(_S9D2%!vlrjbzXG_G z@z;$@d_Vt6=vkb^K6e#x+Pt_LdMj_&07v8Z^L8!tEY4ydy3V_v;}QGN^}w<5iFI%T zaLeGYqzaQ)vv2h+V5pOWofTOy0;}Ac$@hEWGdi5CeY`w~UkMD7h-}xcd*Au{zzgm4g z8S;ts^%QX0{COJs74bLCuk?AuXP{?(iRX>a0!MLle4<~^0mte-_xbeap{KfbJk|FC z+)Q`I=ftzXh$biYCd69xnAKdtbVYtgOo~0msHC_P>{bTO5BKU+!}@uRu=o z)%lfs&-+#At@ykKoR+uOp{Ma@;}X7K;SJzueRKY#=SlQE43$W|De(!aNcx$)-i``?G2>dx_uYM5JJYMa&IUTG{8Kk%;O?780E|CIqB z0%zs%BjB_=ejM_Mec=<}ta3OmB+uu-u{?|O{R`k&97O-W4Ee-$ z^DE$3yvCQ@x5&5Pw>-X%yxup!Er7o>@Lc^{kAHe)y>h43#U%L-dYa$PuDqUgloS6w z^faHGeO*H-ohYt=W)#XK4)X(W{`G%^oa)or*Y>LE-o<_ONA~Y0=xO}!`eO>MGsYAP zohJ+&lO#VwPj%qz^Y`2E;1}Si4>_Kpjm4>LrD9`a!Rda5p8AL5N%!9^*{^F?Mv)}H z0Y`n<@r-LNrtfdLkNdfp{2ubuwKNsVhlz~m58(WG{OQS^ADQGYPwuXpN&beM>doQ3 zbNKQ_{{fDTe^ikk2RYrp&{LcpPdU#>;s2o5>fk@#%Vr^Ob>g9XE~&uA?bQ z=7W9(@HsyQ&2^uNZYtGhFIW@R{2@;^uV=ZWr9Ei@=qbJw#|1q(#c?6XSFT>yf1fbB z^umh$B9OCrFse{&MqyOvK_|t2QOH>yC-t9Gn$ba@22Za(IY|~%>=%ce#VvQPLvdRI zI99(y3#GPpTqar4>Y=)SprE=TZ=~373^^Z1!`#Nk%0>+wn<)01 zLeA&+Z;HGm&;4#{3zwxLh++B?tL)6%U^59n$BQ=jbV$*C{&f}G}!!;f*Br1D)C6|=i?Z^gb3yso;Y$LHg=iv4z;oW{GoC#Ui55MECl?~aOnE#z#xtlw7y)>pBw3$H(-FtgCq zk=8=hJn5&{_lKP3lN)bc>sfFR5CqYv7Z1ro7dtxH&LpS1-90(^TY#MUsKe*(E2cuv z_J?IX?l0@1Ulsk_*_Y2NU4AOR+E~d_l1u}h))(i8k8ew-j9;s6V;!=k7c9jP#=wILe#j$@_~sX#6&@ zox@c&X6}?DNlpTe`l_=ZQ*`&}#pZT=ShAdmlS4l7zWEg3Sl{5d%Kg!)z_GZ9^X@d@ z*nQ5hxoz0bJJ(K!-mm*JJUPYXOvqVW#JV{PINGN<`+mjQE$+hx<#?P8J;lxCqxzhP zHd79I4shfj`Ef4f%n$w^LWSWx;8;IrxVGec=&8P)AGvk^0_a))&8_>eIb@ISv;?Z;j&;;IwgE3O&V%>iaTJPVu`Oa#la$e)tOD*!V}33R9`= zCRYkPg^ut(^(w`Wt07;d9FD~K?Z2Oqu0fNp0d8&J+&JnA?l5dxp}nJRor$INvyw&k zdsI}}*Lv5ZcwFbnDIV8*a>C!>$q9d>Cnx+(o}BPELms<7yhY);6>?TDW7=Dbv)b{) z;Iv@hrr6&e+Kcn|4&bP+-8gdd_fF`k-@5U;&!40#a(TVIOS%5tkh8k0tuIpAD$_Q( zN3p*ba_W!ny5jxreZbMYcK(mttJu6(u{bm9LHB$7l)nc&Ipyy`$hGl4q}V+SIq!4Q zdCDV-{iBex@m4?QLCJP=;xWbkamd+yr}zETWs)a=qw%?M4|6{OS2-hYJvsUR ziYF)kUxl2-UA*slO|gF+avGPr9{)a}N(H?E9Ls-s-_g-H)jbAjeG__CNBQ_*>b|A; z@iyeNemOttTIOU=H8UUHfu8N}YxnFZ)zd=ruE$Gpd(V?o+}?+rez)?QZ zc;Wj%GOEuMKRyrbD(A|n3tuR9UwU#H?^m9j#``totS-fV@Qq^sE#y|bz5~vR*Z07& zIv4jdKLDrgXMTj<-$(uA$tey$L(byB_v@7Qot4>-yTjpsj4PUBDJPubn`_41#D@bf{={1^M- z{J^nwsLsvO+1WcSDX+&**Im$)({&f}3GuPhC-Ukr9*$jbG&7M z^Ygzf#S@<&E;slU6uT8eyWD-|N{ZdehTSUAvwoJlKV8+ZU(K*z z9eSQO>Ak5nfTOX7%}WKF}5wV?O&wKn9|`Q|!`{ko8|eSmoGy`EyfKIClvi1%9? z0LRA9aBazkA&)o@H&XoA7;;(%UEPWAYi**~ZyH`-oX49f_M1b_>ONP$TR^YXVQ1(m zKAHb)|F@-b-K`+E>aPoMlxJ6e;`<&o9?tn8K3D1r9L3x5jY&TqSNUoJtSjAsV{sW+ zsBUYM?$G;n-2-x~u6hDTam)Ou-0!y~y*z$5p3Jk}p4{1G@;;EWc#7x!TLZ_}m14U) z;cjU!HoF&Yw}IZ;ziq4dv7IL;zqj|~M1wR20^dQv%wxOU2ljdr|S)cobv4OxpSOh zirsL?t$d6Cj^%@`*KNs2=vh7Tbpv-zNis_DV<*U&|GE35(VpIoyLMXo$~aEv$9TLH zud$w-;x!I(Hg0jhKOQ*NKRB*(jz7V>4qbnuC#UQ0?8yngizg@iB*hK$XE){gQ$qVX_mb5dguilsw7X(o2<^+~U)e20_4-qRqj9?N16SU% z*L(aFw`rc7;#P#5^|!(9XO9Zmy*wRqx{f+sv7h0|>3R*GoUT`boXvx5e=ya1zhe*J z*nP>Uf;(MksjttT2+xFG>mQAt-d%rSF} z&gx0r2eksn=FccMZRu-iD)Xn!yAEBy-ILSxJ3L;8ue{e?DMpvq*}zd8oqYwDUVJa; z**Y?`g_;T@nxptJ7jo-7ZJuJkH{|4>8%J*6v5#W6FXYsZ)A93jxc#7K{UCP^w?Fi3 z+~WFqfO7o10`|vS}{jr{$;&z-Tr??&O$q9dgCnx-gkhA_J zuHz>u_9sJ5@$>8Q6vggT$XPv!{p2){-}zlTk9CvNp||`$L-FIx(2u^Yg=ur~qDQ4p z&H|3+t@ERAp|QDGeycx8&W4`WF&Bs2dVLP`y#M1pm#inA8~QP-)xFSJUPr3+ejaet z=P8fphxTGTFHr0+gq+6b{Lj75bP@Dy|C)QB>0;=uJX``C^&#hfcE8uq(Ug1d;?j^$ zJny>hfA z)i>Kzz@r;Nf698i$95z1G+t+)`FWEkch}G4H+yoz-{Q#$e=FoP4u==_SGNJj=NI2! z-5&C=_lzpxPwr6sxHGgD`{Z4U{oSFxIF5TfoQuQgj`Xz+`UElZb}w*LCoV48bB7-I zVB>v?ANNB}^TpZao-;iF{lZzclJxw&Hl=(Ja+U{i{0{*~b?)r5_o_GN@q0Y~Fc`Kq{%w#NmYwqnOL@3tj*0yvsC zDNhC0mLyMxJmT|~r+{O1TkGEarDY{~8hUHKKLZ@iL+4MH!Dk_-e9-*yKezRqV*h+- zpFU4_`K%HDQ>SrZq@r6!1?Q$|9Q1HfunkH*B{_sW1UGW`&)`1Z$r-VCyx6akKc_W z>o4g8nDj~IyU;I!dF`%0a(K@!$$QFm-w&@VzDM~1aBTczi?gaCyM>2;q}FH_|Fvk&pkQS&ljGY>gP*OPWZ1pIpM#C+#26E ziv72cv-^~6f8oE!@SS4+J>--}=dXBw_ycg{r}Muy`=rDyx0@}0dMT^0AAw_aEAB^s zQvCVZyN>fm?4Q3V_P<76|2N>MF5P&<^NQcS>rg)b@Z^-wKRsTD&+_4a{^Kvj{%=oC z*Zaql)AjxhuP5$L|EJjh2RY@{U5~AcY=)w-`u%l22&{b1uh=gDIrV>c{oHx*f{NWj zo}A*euqUTDEdn{^I~}L=Jq(>ZoUAboMB8kHnj4mh^|so>h&NS245#aBE(SwZn*MabDW#C`Wl ziv7xv)4s;l%iuy2t=dVl3iR~-Q!cNWepSd>J?Ea!tfpLVb;y~&;``b}3O;6VLu0!){9Z&A;v$|W?<8^p3-s=HJadq~D%Wibnhn|f? zT#q(T{MZn3HqYw{jnhim&&JSUrSo_r#eQSRS>1{Iu1&n_xN&eCZGVzYLq2gIy%}(< zPcU3tvN`m;KBV_Lwg3))D)YKC1Z*E#zMn^j*%ErXuXN)n>v6xo74-c2(tTSO;P9t% z{hH98^6sjx(&mz`ihVc8Szg3*l$td{_G6g zx-Q&BvEMai`@1RjyQge_kD#4GJiOn>!VIZ~9>vX#2U{O2D@GsavH8LD@<|Qk_)~Z< zr8s_l1DwXSA8^(#pTFgV><>MbPsO}#!S&Su$Z6iVc1L&VKE0|-*Ivc;2P*9k0?zAH z`FF}#of;gs@7>oL67W;MhXy$Hdsu*z{_p@N{Sm;a|4!el8zUj7{yRIbZtMv;TMxZ+ zr%_6~qk;4INZ&~q13k8n=-<#jFPFo~=~&45{FUDO9H;m(9ys&g+n-K=oZ{p9KeCy< zOE?j7Hs8hPcP1(APZrvDkN3Jar%HQ~DS_OzkNfDQTg+1fUK;Od0Z!vRJ-|tSuK*|g z3~=gym>)9~yISBZF5-1w9rW1v7T$9ypOa++e)4x_fRn$o0-W^g1Dy180Z#g}1Dy2d z0O!v?O5agvQ0yClvv_*zUnp>S z$l3hx=69RYemihhf5rX%Jm|5y&-C)ie8}1S?o;OuDw|7>k(BSzEQooyUa}B!{3)*= zivpbDw>NOsZ?7)wquA{WoW)(dUf)l#-`{BW0LAV=;B3B$-zPc3Ha+eLwXC=;Ke>uM+{VczW?T333`I=Z`xN znN>axIT>>D*TqA;KXeN8@Tc7VsR2%P>$Cu;d3QQ+)}FX-oT1pC8QY8go~78I4V=|2 zao>0j^e9h?meL5oCi6}WAFQh=R?lwp8I}o{Qb8Jpoc%@xL*i> z#t0H^V~6gZ1lb-TL((pFr-E6)R7rr2K|;MCp~0Z#2*37pL{ z@x1>k#s2C5r}nN1aBA;bp*?9If1P4~J#ZEuc3o4s{<#5qlrOG*rk77{gq+O_F|Th@ z{J1&5so%E*IQ9G104M$10-W@32hPStT=(u!?C%6_<@;UGqr7(Qd*5%lJK!h3?+I}7 z``!R2{rdu(^zR35jpqZ<)5h~b$XS2I@60@;`0;Rnli!a7IQji(fRp}X0Z#gl183(D z@_Xvgqr7zXz8==ICxdpV{igz)+J722i!<97R_e+#iv6>|**T;5Jm_;RKTI#5ylb@op3?sN0Z#GyAiybJ9|kz- ze-z-P|8anm{wD!W`kw|k>3E)B}V;=FG_y@(0AAz&FE51kY6ZF{rY)DhH zi*o5QDfu7dbRFf!p&x%g{AZ>8Ux2f5VDAT4uCRWE9*wgbf2P;o_#5QxxsRGE_iLNK zEA9Rf;FM>71~}!}U%=UTvH4dSufL(k#*69YlYe3!@jJx-Dt`PI@H@Y~_v4cerRy@* zzW09oVvvKTeBHlzfK$Jh0B+?+2gQC#;B0)wbz`YO&$aK~XKZS2Yjr<>P&}r|7dy5z z^w|5+?)l-y!Y97V_Zs2ve8oDpiekSiaEg;_Ppo6BDfS)X_Sw9w z%-_|ar_J9r0)FcEngLGzUJE!qzv=X=yUlR-sTKWofU|M(_hoQ(UB!Mq;4Dwryspgu z^`Xb+AJfYx8$izHQ?Xuq$?GzGL&cAc0-WNsaez~tHVJUj-!#BUf3pB5{mlcM^tS-c z^G~`@x@Dl}?8WC>w}KwaNAZ66)`~yd1UUJ>ZGe;i+of!8d&RyJaMu6Qxkm6_+zyKU zj=*VO>HPK1qdF^gRfb&`$k}@Bo$GaloXrRSekJBZH>LgV0Zwu55#SW(p1@h0#OJPh zL66l5rq`bD9rK9wZYRZ$oq@A?EWQV`i(x#9;;3eVg2 zi+M`l8wt+4`a_S^MW&Ze20+f@A+93>p~v!r>9scwf;^1F-~gvM3<1ve6XN$Bhbs2N zfLrruIP~Z|$hBX1FSdpMc2Hr1GXi=n4lMsG>*+}7u{bcj_WYhP5AMU(F3eY!t7H`P zC|(qo(Xl<_ulO;>@MCPiPkA{mz^UEwz$s2nUpzOPpx93Y&f+KEe}*2dGp>E;xpNBk z;N#>#&-qh2mkMrVOo1Mo@8dGFinlw8PZUjsoaT|^@#_3E$hCSs-LT)wu+JFwGYtD$ z$SGdVe{o%|gI<_V*#LL;g>+A51~}!xEZ}S$#X4WF*ymE(&xRiL%e60lmt{^$z6Qmg zM&K;3i*>a4`+4m7mnOx&Ilw7?EdfsPn;W+$&ZAbvJ`dblAKDcAcHmZBod-SkTxs9x zs?JsOfz!CS{)*?73zYU30%!BhyKlNkvD-Vg^XBP3kZbdFU&z@xMDhKT{gn3h2hQRw zp6ea}J&K1L=i$xn{u-V!D_=hz7_>uqa!`O%o*WFE&ogQNeu!d!C~#~2J4~@ZJiy7{ zBLbZKJu<*a|EK^b{iA`i{)y|sF^c`M0Z#257vR+1@c~ZyCj>a@p9q}!C!UX-6zDm7 z@qFZD=uw`y_QmV@Qxtzr1>fz_S5Ruxx+Bso*D zJ1f8`u4e~0#q}KER$R|j?9T(v>hGU%X2t#%;B36b>)Bfs z``duC@#gcryfe97vA-j>ua@4Qxl^&fE4CNgzgw}t2e_6m_d-td(8b+*p65Qp{(i&$ z0m#!nm-Ha?!~O9?z}Yy8_i-ME9@Qn+AFfw^595)b9m>N;1Dx{kG2m3k3iYaR|KxE+ z{|Vsi98-Kx;7P^)Dc~&c#C7j!=&|c6@ApBTft<~Iai93C;>UBbAL2R8^MW7EbK2ZX z$EDvregSf-3x)Xc?-sqNwEt4vzIdJevSR-Va4r8|RqS3f>|R&w-Ux8Yt2YCj^6D+% zT3p_SobDSsJMVi=??A5g?_J2X_3%Bz{(Zy#1ITHqI~KKTi9cHe3E=-%%4(}7c-P~3hF_*|U5 z??L5YN|_gdX|n z`ccg57QAn@R3LZb=Uoph9mt)%_r1hrASeH5o-Z5V&aN1+<$zoBe0k`x_~ZKsCC66? z_^I6$1Dx7j2{`R*3VG_7qUXg|j@upEnk_$`ovae@Q@g7MIJLW4fRld504M#`fm?a8 zhGM@aa5g{0{q9=OqkWBQUp$vwJ7|aeUnjuH|8;>|dAT0+w7gs&a&{fxua7&obDvNu z{@Xxle?#E3-aCJb&k1$!+PQcHu#sZ7aez}lHwkd+=cd3}Klwhnd;?=M=#f9pzEFP~ z>31(Shn&?9?yRz2c7h(O157WU>;O6QTioC72tBqAGQIYE=b#E zYtW91hxdK%ZjjTwaDI68xI5(9y3zykupajea2khRz$rgmd*XXEy%qbNVtesEz|Mi5 zYrk-Rl0N-c`Vkm6vv+|W8&C0iX;}u$dpM~~4J-p}N2YM_o#C5tx@uzRlj_Zeb zpS7Q2-yb-OgZJFz0L5+~aI3xyQtSr@xbwSMw}%8c#eHailm4&(C;j2TsV+EuoRgIX zXawYJo{+rg$4JQ8xQO4?-cxCR6mS}U*S?siqZRuxz*(M&&x?(P9;;j4@6wG6_$hAV z1DxVE0XXZoxNc2^9@PbB&-KdZK$BuVvF=S){Fwqg?fKSJg=bnyKc+*E)eZPNqj-;N zuYjN8nF(-;=Zv^r@p;=?=&^Wmy>dM3pvUfOiqD^Bp-1~@=MTPblJ_1#oe4cYkIO%5 zSl*?$?^6ct7WgW^FIxF&+Ir|w{9Jsz^(hB=SXXBQXL(ngFX4TIIf{J)a5mq>_iY=Y zNB+5fFuf#cf}G;!{1?v^o1w?X%hxMbktAt>9-GId>y-S$rs5B>BF1x}NBcbI*HHHx z$%~2)YdNk~$XWl`{-iQcdFZjaR?}Qhr-Vt;207J>LjQ7wpKNX~-B)UdJRF~S0Zw^8 zKfs;e#hxz!&f+LO*S}D)Uj&@xgSam4t=R7q;N`czNe}2SXl?*CD`Jf2v#SvQ7C~_iKvfxAc?bQ0UP* z>HOe&$zhPQaTMQsJzVkQ2;eOL#dYjR=uzC9AFi(D+v>BGZwVC=>!_d|8rP!(oW}JS z;4F{$I#r%H$13*6#r9%dI$p6qA;8Js69b(5Jqb9~C#Ub#&yy9qQ-HJa7WW&cD)y%V zXX7+7m#=l7;;WsLZEJJC30%4rce-MK25_o}&TsMjtk}M^-)hORuT{D4zeA7R zkE!Un{5U7zr+A+m;1uuk0-W^E4{*}I063eEUj4jKvAYO38y~U0T&&n%0-WWsc%SuB z=u!SSzrFA0Ul#MQ^X1BQ_2tlGb%E*SlPgm4TxsxJ1v!h0n73C$FRXLd0B3nSxqq&4 zMr)2e0PG%?xHk5KspXUFAZK;1SSP~wBCdxXT`#%#j&vWpYs}(Bv>PC&_`7`azCU~; zlU_+9(Oc(vfX_7=#kc;2e`aT{=|!_NPqncDiokMw#I@pi@jjsU0m zbZ3Cme7Xxb^H<#0+^yK(6WfdD{r3iXu6^p{61kmc?)vNW7n>C4)wOu{yV@~zKP#)eK+8D{ukccD3s0OJb5qRr+&X5 z;MDIA0-W?e3~I9>0!c!}p0Uqg@Tp0gL$zI)A9Mr`RWW@Pg%$dmqQW`|T=8-hCyKmgdEv$L3GryEr8+mqUv~PI>0yAb!_v z3FuM0+&Gr?3WtInVjl7S%97CId6=8k=#H4&7veIrGD|_u#+9ExQn!+&gZ7+1T(5ji zxlEv^^>10|Y5iL+_KRJIl*64Y4?VuVl-`Rh-AY{na(~=hT`X-l=<+sM5qjY~TPdae z%FtuuKBOT#BVYWj-r#5{g|!4t2mDt@d6oQ=~+w}#ca6!gCM-Vu5%9!xKvtRC}- zzdO2y;>Vf+PIYIk0H?aMw$NVj)G_!3<~oY~x&cn@try_b-ul2P-<`hqUHA=Zwoog*U_1#S?&12w{l(C4tms2 z$HVoK?ICCV5!bCw&x(3+v1n;8xtn zD)!@2wm%+v)L$15@qPXYfu75^!ag8+u5lvtsD3y;_d7O)W6jF5jfI&oDfUm?KTn2U z7@sMzKjQa7r$UeN$oVrg*VxqTetakA-g?MSgPg@#JTILdwC8xZUU|Q|SIo!uOO<_Q z26`-im|i}a0Xg-X=67v?J3oqrr7plpKO5krKNC2OyVD<%Yj9tSE*$t(%2hH8dMtip z>ND;dC0{zpt%sb(+wq9&X)fmRu3u+E9`<)mfRq0Xz$t!(cKLfMjf!1UZ0GHRniabi z;8vc@g`Re<)(Sa`mw0ZHhaTJi_idh2*GzZd+fwqh$2{J9NAqHyKJBe@>YE$t+#auz z3G<;x@pj|D^^ygUTk%;4JuNg;_zRK6pE zcBuU$1Dx7F3OMZ_oxXQ|bF^Z2On_56#|AjHa~yD%m#ofK>hbZ=WBJYW^2rI1Qyg5o z#eGoevcED`Qe4_XV$u{SepB>!HWiPjB730djU9LHu6U zjnE6%g_~kOD%Zc2%lYJH=&|)~bhfp{JwR1jpl%8HX&i42a2m(k;&#P(c02UgI6%)m zw&i}f?T&z-+PyQtsolHccB>oQFTe%IZpq!yV{tD$FIxIl7uV%`0)A@u-TM=GHn-Kc<`xv=Qut=f+t8zVLF4=maJH{>?{T_o33pG+eE~T~7nkoskK*U-eLd9s z_n^n-Z)rWi2)(cP@d0pF-^KgHA3~4L?VKOted3RxNAY+0?$x1>1G(ez>d+^U(>mbn zz26`D6!I{hp8;q6_v+W@irp8$sb8)=@wuNbp~v#GqUYY0`U-N|zc`-a{-j6ep2ZEy z*Gl`}0H^VFcH%nyZJ_7kCO%K~9rSp9OXug`EB^ct+l%x4N9a+!oj=8Wev8{&7G56v z334H>E%|cQaPQGWkHvLN;V8IyMs8-VE|dHWIgPiAPqFU*65y`=BK|9IHm|T>^-INX z(4)8);#rCl^iTnRhaSskZ=e5%;>Vwc-Cv5`-vLhj{U^YwzyAg}>Hintq@Umc66&|p z=XJJxqO}Tyk}M0oFb>NBXK@tg?edEK3UT{x zU+uPu^uw3<;Mj`LWAkHFeKyzR{?q7o1j$N}vv`QtXDcgytOA_ov5SMZZmg=|5yJCxV!1UTjO zx+&XV4|)_|XYbX?^#gutcY^?@b~gmh;?AxcD}{9<=uzEr_DrumzcJ(#7uT+MUtkl( zk4~JTSAZJgLpr5E9kND@a~6h9rF~=EBw_X zNw$F=#n1W0^~!m*ZJ_7kEIu!^9rXBk@#lrMkNx6#TpsvN&3U#MEK>jRw4Pd;DDnNtHj$_H&; z^@ScAZ}FVDU!dps#P8|%haTkxjsJiEr|};c;G{n&z)62_fRp}^04M#Sz}YzY_ieCK z8wNcVFJG^itw}N*dMy6Mc!kfAj!^s=37o~7y~kS~zGP3uepGBP&d<@%qj?kN^%#YJ zEO4qf1-nc`o%{Yo3)P6Clj8zET)acQV#LM=?a+8l2yhy&iNI+b3hhh3=RQfXpA4Lh zk67QQK#%61Yu|exVQRoneoqT<@_RaPmS_E17gh=@jqF~EeI~%Ey%_;c?bQZ2>DL7~ z>1Tnnyc6g7Oz5%m)WY)_^!brwR=`i~)(1GXn+tH#pB><&KL zu|E(v8$Yq0AEekH9N^^dApuVQ9tzxw^I?kp;lSzsgKH1>|9Ted^%08Qk#W2HoSVv9 za+G3!G;kItab6z-Jyw6b_jitsdBk`hr}%MvZ11j@!?z%ki4YyPWrb6IO*RGobBhm^Yc3(XXmtK zc_HKO40x%%y8@iryW42*9>wln;A}j_>!bS=`}+f&{Cpt5$xZDn;zN4* zLVO;ET#N4`O8bw-cHa5HV~XA5z*)YsbH(ya*@`o7_D2es5F$dB~{_xpDXQB`+xLzX+Vg zQQTL&1U;&^u6^(MoR=YIc`IHwz5+dMJYG%7^BVNn`s!VOz79Db*QUbrFTnxo8_;9@ z^4{Bf6LJ@&rm z&jXzN|02N2|1W`)Urt}VzWEAzR0o{B_&wdPp~vz`tXtnekIi?c*Pj0taux@^uPqnA z@1RHi7yQaJpKj6II(z*Z7iv2IZDIRVd%IkAs z`}(V5_Zx7kH_p!6zx@t5)lJtwZ~yiO-_Zcm*#!i_Z29fp&wLds6FS0xX+>WHngXFBAnt( z>k;7;XPR$>lRou>aME8Fc}xB~eK8-_gI;LAK5*)%vlrKu4WLKk;oA51lN$#7X1GZ|#l6F$;cL2`DiCsTeuCsT9 z9?fUhKGVx5odbUIyDGrR?=AsO`dtH@^t%CPeu{bA9ePy%oV~Yy?g2UV!?o+ZuU9(l zOcGWPit`}+eoZgM&)&e9z4$!BPSB&gc7Be@wz=o7>vMF=y{NHspy&J*_i4L8FUMe8FK|EPRGTRYY81P^ z3f@n_`z!bW;IzKF_UoHl+j0XH{Xq&oSiy%V_)rBOrr^UBe1w9JRPa3&d=zl1S1t~9 z?gQFQ?QNxj7_HciQSh+}K2E{MEBFKjpQzxI6nwIRPf>6@23X#QQGQQT^h-~tRQ8vo zzn7wq^p5yvK%b4nEcZ})Yh5l^l+-GGbqbEpIz;WvRP<*lc)fy`yo&ohThX7R;0+4i zsNhWsPW6lG9gRbaqCZ!`TNOO7;B5-tuHf?&e7=G&Q1FEczDU9MR`7ikd|w6MPr>(B z@B zyHdfgQt+!4{2B$nR>7}R@aq-)2H-5e8`~R-cNvQLeWPM`lY-x@;I}CFtqOjdg5R#- zcPRLs3VxS@->u;HDEPe!exHKhuiy_T_=5`mkb*y~;EyQyqrf}jugmY2!Z~fOaPy=w zTUY!s94C7W@?|S}xhD6RBkkiJ2OjP_o>1^775pg$e_FwxQSfIK{5b`GUcp~b@D~;Q zB?W(3!Cz7ER~7s<1%F+^-%#*375ps)e_O%dQSf&a{5{|+;DgtRFMm8|IatgIip&TSCD* zDEN{JzLbJ5t>DWj__7MVoPsZ};43KjiVD7xg0HOLt0?%Y3ci|xcU17z6?_c^UlTa1 zUo+bqaWUsoW-Y~TZ3SOP!Piyr^%Q)41>ZoyH&pPAfU|k;?O!)m>^4#GO%;4I1>ao3 zw@~mc6?`iN-&(=9QSfaQd^-i-Ucoyl_znubqk?x<@G1rGqTpQ>yqkh|SMVMR-c!MQ zDR^%M-$}uDR`6XEd{+hEO~H3p@I4f~TEY7$cnxrE9qFss^;7Wv3O+!=2P*g=1s|;7 zLlhhjOGM|y!xa7Dz*oY0T-X=ly4gLQmn0(;yO9dMr-F}C@X-oBM#0A__&5b0uiz6D ze4>I+Qt-(NK1IQ&0%z9^3l_Tf_Hzvj-KI2?OjGQpEBIato>A}_3SO(=bqbzU@RF78_`V9hpMvkN;0Gx9fxy|msy5TulFPe?2@99L2Pt+3EBGM_ zeyD;Urr?Jw_z?={gDENcG*?u?QKEvH3a4(}|GfDE0V)w9uKce7|D)?gx z{w{CD6T zD{;za8?sIAmjqkg-7oiEUtw3BB!4LOe=7K23jVi(|D)jlD)@g2o-AJKNOZqJ!53HX zB^11af-kAyODXu$3ciejFRS3oDfsdVzJh|UsNgFp_{s{tih{4I;HxQkM+IM9!Pijm zH5Gg<1z%gi*HQ3w6?{DfUthsDQ1A^Ed?N+lSiv_@@J$tbGX>vV!M9NGErG9EnwE(> zk8H`fA1rmhZ{~jbvzA_`Otw<&w^s0N6nt9+-%i1|SMW{>zJr4A2%KFHyCaEw;k(tv zK6X~@suaA7f_GK$ZVKLA!FvE_=jOG|^-Xx1IZ1j#zI0{WiqApxQuul+_)ZGGvx4uU z;JYgLZopYTW;SFOl=EtL#cmG;uU7Cr3SOh&eHFZ)g7;VO0SZ1)!3Qb$U@XRw*o{!|kqW-2f{#-0(F#6B!N)52I0YZC;1d*lqJmFS@W~23 zMZu>k_%sEduHbtqct*izD0r=c*C}`wIJ*vRZ_SqOniMAQOvP@Ng4Zi}PQhm@_#6dq zQ1C_tZ&L7P1#eOCxeDH@;CTgaQ}A{LpQqsS6?}n$FI4bF3ck03@1x-RD)@fD*?K&) zxwSFpo{lbjjz8I7u{%J)4^;4j6#QTXKSaR~Rq(?U{BQ+7Lcxzz@S_y`Xazq;!H-q& z;}raO1wTQ-PgL-e6#QfbKSjY$Rq)dk{B#9BL&48f@Us;BYz03@!OvCj^A!Ai1;0SS zFI4c06#QZZzeK?=Rq)Fc{Bi}qLcy<8@T(O3YT&C^_P@oSWNywhE^KHnee0+ox<;|T zR>7}R@aq-)1_i%S!EXZ2{4U&=tIIa!+FIRDh}7af_RWg@Eed|Cg5Rd#w=4J^3Vx@8 z-=*MpEBHMMey@Vxr{MPkXU`|rx*dJ4Y2 zf^VSU8vYRI%F(INOJL@40QR*lnTU zTPpZg3cj_1Z=>MbD)@E^zP*BXQt%xVd`AWEtl(7&-bKN?DtI^GY+M^N`TF+KX>gKs zSL}KycuxiIrQp34d?y9pS;2Qv@Ld&rH{h(_t@XJ@rJpKwmk7Hnc6%szHE?#nCDSk? zlk`#aYZSb%g7;JK{t7-o!3Qe%AO#-`oYj%mY;EZq6@@FQA&T8l1s|s1!xemlf{#@2 zJr#VEf{#}4F$z9b!N)21cm_!DlIWy@Ka}v${~rzsoXPv74jd4GP|<;7tnNtl%vQK3Bn86+ExtZ3^D5;PVuG zzJf1M@P!J#NWu43@O>0~Uj^S!!S`420~Gu~1wTl^4_5F)6#P&HKTN?7SMVbg{73~q zO2Lm-@M9GGSOq^0ID77|G3(w+py$|*SL{wu@DmmMBn3ZN!B0`}Qx*I)1wUQE&rt9) z75pp(KU=}iQSfsW{5%CeU%@X>@Cz0EA_c!#!7owpOBMVw1;1RuuTbzS75pj%zgoes zQSfUO{5l1{Ucqlr@EaBUCI!D)!EaIUTNV5^1;1Uv?*Puu4_lk_+2(mU_enH&YJaC< zcNcJWj-PX1TWxH1U)6A*(Ms-C?Cw$UdlmdX1;1axA5icI75pIue^|jEQSe6<{4oW8 zT*04E@Fx}gDFuI8!Jkp^XBGT81%F<_Ur_KD75pUye_6p_QSet4{51uCUBTZ_@HZ9w zEd_sD!QWBvcNP3S1%F?`KTz-w75pOw|5(94QSeWJvwAScoo6)W+`YX*J@`zq`y4pi z_qV$5nc%6oB>4hzs!uJ&@8cJK2qssTZOi7rgq-#*b4Z>m-EjOW;CJ@@bMAST+IDB` zuA{#;?7xAWp2KzaBU@dOEB_8n;bPqV`_|z34sxv@-y8NnK;E&`T;lvF{T^Me_!Dl0 zP!uIU8azKi&imuu(XMT4Zd_PfW=WF&89YCy2PeE%@)|Ad_K zFwB#`4Ew(!x8nMb!SgTVT0j0X?2`_q1kw7j806Y~SsZfS4{1D>FnBt|Jf+_Qnvtn> zvlp{7NtQHtmWp}A`M0#evkc@~T$VNLmy7)n`?I{kvqH=x_Gd+dXC=t#IZ&6cHT4b6 zt<8m}xN}LevSGhU?1$K&RSlliVji(S9SxqAju-`nkcfXHQS^u{% z?6-uRjf)u1tqh*6V?VrjZe!SQo6?W%44&;{KfHK!GVFJV{SfnXM}wzx%p=CN%HZje zlBcV|(=Fx^`_a*LH(q>u8uqF&@}LvoNcClw!Bd}-Cui`?hFt5<9K*f=ayAdWb+ys3Z-U&)+h&8O1#+z)a}E2}*blMZ z<_(@U$gT67c7tbLN`K}XJPRP#;}&Asmy&0HgXaLqwY)vh zusD*Wz-$VShtPKW;R5Zi1ZUn^#wEHtcVS{Sfoy zR)goZm`9vHw;Mcnq~y8N;JFKOEiQK(_V+-pjqkmN{e6&I``Y^ro(EF$JZSJd1i99q zhYkBj4Esk7`^OCX#|`@@AZPIu^YlrB=c$-S%+se0o@Y|>JZtbg7xReydfwo9A?6YL z^`gP^66D(bl9vtpS0LB&>{Y}5HN*aO!~PA(S)Pb-c+=o{D<#j{2G2VJkF@^1Yw)}$ z@JQ?5`v%VkDS19Lcs@$W^RdD6NlKni4W7>+*YfUj!~Tnuetc>0e3g>tYlG*Tm`AJ& z-x@sM#XRDC_}<|8A?6YN`O)C{339eBd;8=68TLQNeu(|~#o+lh_QTsZ{btzzZrJ}} z*#8N+Rj2+kc>Xs0_{XsS7jo;o>OX@gS+aBhZuMs|=&8@Cq~uw`;OUT(XGw!+sgyiR z8$8QEuGOby4g2LFXY zZFV1{DL&=6iDADfYnLZ3{pn}$^iRn%z~C7e^N9T#Wbh1zT&r_K4Ev!e{TOEO3{S~3!r&R1l4nnY zXB6bxJREJ*YvVl8u%DFDkI4ql6vL0HhW)gZeoQxb z_JW*^uXnwhG3;l=eu(v;*5Ij&dBpL}8ay*o^2{=L>LJ(iBxl&qHtgpZ_6?9*`QB*o zG^ONeHh5Z6^2{}OS_K|yUz#^~+921)r`@ohXV}j->=!_8jn6`ZXA$IV|L46=vA1Er zPf9=bHF)-eT#M)ahW!C4{W#FzIS6uAkGyk)gAMybAlLeFs9}E?{u3>*(?1wns=Nmj1#6047Uuf`L1i99qiw*lr zQu=YJ!E;$kp34oMD^l`YY4BVHIg3j__sg(3dP@9i!~UAs4{@BYHF&Oz{qXYbdc*#P z*bgyJZZvpqg4}xU@n(bP7RXs#z4h!?!~QnNt@Y@3gXa!`M_NbjGw4W0*LKfLjI(6E09a%+4ZHh3OM$@8ee^H@rr z#|@q*Qt~`$@H_>%HqK8Q_Rkph&l>j6L9Xpro;U1YfZQ727Y&}5Qu4fP@Vt_e=T(E} zwU|e&*RLBqZ^S%ey?xW*c?)uFoZdF<-+`RvnKzH$HSFJmoLz@`=kV_v_8&mb@TTGX9dW$d|T15UkP%nKPwwNt3a-;r>h$Fs|kKcdEe3CSzX|f zt{2uYc-DlR<(Zc!YZ>-yr}SeTgJ<28JnI=e>qD;fX9L52!<2q(WbkYZxt6b+81|dS z_TIj0GsAv!$gO6`XYgz<@JREglfknCOi$&-zTLXH3m=Llsx?mp8hF$1{gd8Q}PTlcm@kRQr-|!<6`4zfrM$jr;Uxrl`~0 zmKkldKL&C(pT&NRHF(CwJYu~cZ}3bIc%<=}Xz)x*$ursDnUa!cs=+fYCC_w&XD`UD zeR{^=nGyRVj(e@aQqf)A334_s z#dtIuJS~v3`vl_mkLDUYt+791zT^#_wwOokSG&P84{|NO^9}n2Dg9Vz@GOe`@Yemk z4f}m!Kg781Yw+wB^N4ZX-{3hQCC`Bd&p`r@v|b-<@EjuWNbA9&(9_n*!ysq*;;oa1 z8-5%CxiycDGVq8Bpcs_$%i^u1N{THzx;<$Wi z@O+h$=WB!Ko0L4?8a&@Y&c?@^H{TofKg9Ol`2J|v{}kJM?f=iP{~2;N@5FKW#o+lh zCC_gL&+jRD{xEp{jCsVo|I6U{JLVDN^N+#vZ_Fe5^Pjqx_&k8Y*m>(+|JS(N-S=r!O1#)eC zS2gTc6a0{_D>@oHs|!5RzGMx9XHCep{oz`M{o0V@PvLjf`i!cnO4c##)`gtqmsgk9 zGwjzl>^Cs%H-y~E&y5V8jbk1$KQ}RWHciR1nZdJp%p=abEexJ5V;=E7;8q6D){txC zyba{q_2IU$y?1@MozedGkh5_X$ETCQvqQ`y#&t)7r*q6B`cq}_bV}>Gtl9FdvgJ-vtJi8k_d&E3q{#6@1ePSLl|7r}L zzA1V789e<39%(%sVDJnSc%=1kkij!JCC?CpXDH-sUU}==FvEVh;D;2K5eCmlfk%qV zo(9h-fk*OZw81k*;F0_pYw(PVdBpiJ-r$*#l4qj9Gb!c~=hb9`XG%(*sRqw9$hCdy zbi;nHlzwCko*6NZI8L<&PaWi1f3k-COv8SbVP6lqwU5XdJhLHZb-`P2=NR@4kZW;h zH0+xox9-C=8$2zLTk)7{@U*7n$s0UvkZW;iH|*!7^kcrkvmhnULW5^f%p=a5y$zmy zVji(B?Q8Jt2RX}IZ#~=JusVJZDM+~7GP<`J*6 zjx>0VO38Dy!E+4cEH3?XGu#jCWePtUo6R3<*dLeDkK+xV6H@Y=2tDSnS5Hqe+BrFH zN6e>F44zYC9&w(XX7HR2IqQ!%&(1LH&ou1MGVITWoYi$P9_JW5=R!``^@Z=p_8C>x zxp&vndw=H{_UFfbi1EC@;JGj*&qW5$#W9Zr?u1gTZrSN}ihxo|_@p_93?z_P3_= z<2HlmcEgW54EsA%`f-=Rb9YLfdkmg?Q}Wzr@Z2Bsi2473!Si6uBj*1@2G7GOc^)x% z9!<&fn8EXS%p=C<34`ZJ$hCR!lwtpL?1vcNXAGWaA!qf)TTh=e?4OVQ5aaQJ!SiBD zo|g=smm$~U`if!yYV3y?*Vhc5*HiMmVeq^Oxph7ImcjFON`Kxlc;1Cvi|>1e{riUf z2ZsHJkXz3We`N4{Z20ksVgD)Q+PwM9u>Tx#YajWA!SiKGp05m^uVWrD@4qp4zKwar zdhngW^F8D&zTUp)2gCkH$gMp2$>8}P|HOWH^Dg1 z$A$*aMk#qVHh4CHob|`6vzr?Bn;G_-8}?ho_Fg^O(y-qOa&5kBZP;%Ux9`=DZ4LYF zAh*`&D$Leo*f})SCX4p@U?Y()kmtmhV>}MGE zwUAr!tTTABkZXB0)3BeF(vNzBCkMGU9|^lko04ZggJ*xp zwY)t5a=O1d3-?zC)QlLqSn*CP|L-Kx&P^6C-KI`h{JnZb{F*&}#yPR_8=uKl{lM!= zCJ&B_R33!IJUc+H&A%ND`_7PS zaji1!yFhN`YgdD(8{{nC#60P4@brLOi$_nxz8B;y9^N@!Z^M2k$XP$cJlome*(K%? zq<9W7c!ml*QapwkJi{T^=FJGheq>5N_B41#K~DP)w{8u{HpV|NHQL}A zlhU8D2G2OiwYZEo>?fr3W1_(`DJ9QjgJ()go~Z`Uw3Ixhr$zPamc3H)ly>1d&y18j zwb0Y{vvrVbrjK|Fvzuec(`GIMC^w+ z&PN(NN2TOB+Tb}RCC{-2&vB4j`-S5To)aKn3V+?cV?^JoBstNrI|*`a{+(>tp8~ly zuBRIIr$Nrv3vpaeH+ar4{5aFFKMQhg-8Ixj^`To=RvN;@qEMn0?1h$#d&(6 z!E+Jh%n$Fn;9|r663DH(bE&~|8RS|#FE{M3fZU45l?KmMh96fO_SdBJ<648~I>_0$ zc>9>^4f`7)x5nj0gXbp5SzN{Wdb7cEOG=(w4W8Q|*Yf0c!~PD#{!YXGF37ET+->mO zlal9NgXccTwLH1suzvt@t3MAKJP*Y@Vm*4;;CTdcZG0a!>>o?%$KwXi6OgmGdUf$h z!~Ut*-kZNq8}`pYZjH;c2G4VlYwO(ehW!gE{dm#fc_}5&%LdOYDS2KscwU2?jjuO8 zuN(Gnr1axWgXgW3JZ~F3@5DUf{Cn5nc`xP>>;3x%&j%@aJ~VhfO3CxF!ShK2R&9MI+a%)`wFnIoiT&wqg8TNleuJz*|!~S2$ zSwF= z7u$RJxx8V&0_1Ew#Cf)&!Lw3Io|O%rRUp^mxvF8mT5Rvdv!h|ZI^--KVxF&I@T?j8 z;f>2$hW*-*Yx8{_!+u@Jt$wU$@T{MbX9I(0!<0N589W<9uFcm?4Es$X*YayK!+vw4 z{VfdpEg`qYWh;YcYr~Ii4Et?U`mvqCv%SD0-9PSR@a!P)NcWF-Gel#ihh&h!GZ=D~FJga& z7(96BIMMnu%&;F0xi&5%4EvFgvwn#CfISVKQHCF*4f`=E{TOTTjDuW@=Xk?@LhOe) zt`iNONimNY*U1LYl$b~KXR5(7E#?vZnQrjxm69i8@XUyL#D3KpJasXT*srX?GZS)l zKXS~l9!WCGu&Ynme$L>T4S7dg2Wrn@%`tcyVt>TAHX1xlkZbd(*|2YcT&qiS4f|He zt-6#qc-m6(v>QD0Qu53v8uzw=TXE}ps`II~>7(6RNZe7o=Wbmw<(w|if zo>f!wtY+|ZjCsWQu)4vsM$9AT@tOwDS}~94&)NphIx&yv&$&4I`+W?a8i7Z8ZmTc!ma9C6Tise) zpUGxQKtIT}{OE7^F#vLF9Uo}$41(M`#~p0&41t{Gt62Yr8a%@wXL;tWQ^O7W5wRcQ zdO6bI*)t{2D1&EoN}e$W&)Aea;|!khkZXB2!LXkg`yr0=B!g#i%p;D|6oY4K%p>|U z&ET1yl4mc2Clm9C{hDF$)TZRAGkCHod1gXSt5>riXYIg78iZ)-K|^M-w!Vc#Cxdv$l7VLu;oR{zDkUtsVoOv$sz z;MqGR&prmvzL0C@>svbjaC$RD7QL41?!P$gO#Emces&?2q`I!#M`exv@Xu_?~C*oFDUub@l>-=fapr z^yeaj=i-=0^yd$s;qZ^D@PY zz~;8h)Y5g}HHIJ8LT=^pbq3G%kZb+9!LYwEw)g7FO@{r=hWr-Dt$yBW@Z6S?=XQhV zj+8uirqsL3Xzy;wSH^ikv-{rj!nWp?`pm3MgZmQqJ&;qr)fT^Ro@;2xl??8Moa(l- zclqk%Me8KF5Arp^@9g_#8`?50!OLRz$9@c3xS+YVxwf{*^Z?{^?%;Svx76pFX3;P6 z+a!HXn$6nZSK zBXh0UhKBZ1WFAY&^SHtD1mrBf)eUa+!uUQ3J*o?Zan7_iAbJRj>)}(-qxtIkH+gh{ zE#H!B&19!O9rG}`%NW#`XP~F$&9jhedGj3PR-B)Qo)+g9AlLfyBIGPCVt-zOp4Ok2 zA=moz3gp%}y$U_L-|5DwpPOt=UAy!y=HF|Ov$%-s%jTK?T;9zK?|G#UA*X&j`|2Ts#`o-1RVpAKL2mWyW9V7q_6hW;&N+YjxN!};m3$iW z4a>IGFKlonK1W5`x%e6M$o~Rgv5uq8e-61eZeJMoUmEsbK~D47wO`$o$+kAT+|OpR z$=3nDi|^<@qpCW0MSQ=3+=}nF(6i$E9rUcc`5t=YxAUu}zO}jVCT4Mx{SdTQ=vQs7 zj#jNqryiyJ_z`*(f5(%{<*GVWbuY<(g4`PK|3Po*l3C(-h7ETgr2ILhAHNuW{0ceE z6W1SBFSApB3)*q*GkIlw_#JvIzT&#|2lQy(IlfW#xkb%0O5siZ4BFG;`xo@I`2L;J zpMMO0{)L?8iSvi{Q^mc(e~|Nh;rp!w8xAXv7lWRa$BRSHit`fC)8gDA<`MUCOG1zJ z&)df>1-aF~rJ-l_ZyD%m@m@CehsL8gKbM1?#YbEpmXG}zna{S@H8(alW$G&H=?c)J zJagmk)zK9pCx2Z&d;8^;Ah+VPa_o<|{;d+USIEbP=Db_i>WcZeD&#buoj(=1o35)t z&f+7^(~i)i{dsYoHrExmjg_l`Bw0Nr-x|*f1sEMk)C=PRX}P zO1@2@XXV*u(9_0o^FUt6_xgtB*5;OmOuj8iwt$?~r_pV#*_Og3@4~iBvSms?whGz_ z*QKo?r#x|fOdj2mX>BX7{G|(}ZJ@VSWxbwUQ{PtK+>~RvJ~i1EdXz8WJl+m+Z60qA zIg77Y=Q=@e^@>0Jn%s;lyX|U1L9|26C)R}>p+|9Wal-YuUpPC*e4>9<(Bt{uQ1H*E zOS(YM+Anp59{v>eOU0gd1Hj@X_OCnim_OomQ4i>8^`Ix@G``LcZ(ZmGIgPIy|B>|z zYjb&bt?%k#ws-6Ymt)kDWG91X=af9VKu_z>u8?c}*$r~a3)dh2oCXzccj(bLR`Q1m zVX13-K#z@=m_OCfW8+mVfG^J^Ss=Q)|}gO zA)|-KeBwME0X^0~aX&FKCEuRV)7GI;kX!jZ8hTcKkAa>wj$@(6`YG-Q#>M`L>(hAX zS?kjT=uto1d=}3mCdU5tX>XlV-`r5=KDI!knk18;$Lbxsj?7M-9P==_OAeevP6_m! zUt{XCt&PnM+2RQZZKS3`kMgomXWZFCYx@jZ`bxe{GyIw!wC8w4zxINjHJ+K6PaMw~ zDfwzs^3?@;E^gwvMHYHg4_tq}bBmdfQ(PU7INxVMkL}C6>&E(^okD)`^Qj!5{8#`z%F{4^7DBG&&mzcK zT=@PfyEo*tKD%)m-?vLu=PLNQ59Dkfi21iK^w@Zd`{n&o^6j6J?*Qm&@i`E3Ej|aO z^ygscY4ht4$Z5TC~!^?b2qUMg&sTS9Mz{=RdN_`Hcq1|H9VI&9CDT~ zOkSQ&$q~?F<0bAVkBs@m{p3+GpI8TvhMrajkAd8q7nR7A>&CIrV|nD&Z@M5j4tmH6fN*gtWeT?{>K zo?Q~iT^;i7M_mdz<(I4X-u^?Zx_bEA+HD-WKzSe%uZ{EidnYobtxSv2tGT ze7_TNHVzfJEBbfEeu(|K8+uxQ?n%jWFZ9?tRLqOQ<0SmPzcIWbv+CUg(6jRP zLFnO6Id2{UVEOYf^elfKfgT?>-1jT4*^ffb#@)M5^%&%A9*Ft#IP|P@lP91@ei!N+ z-(k^x=_jGb>YR6d_Y~yTe(h=KvHfx1(VeO~cgB2s26EOvah^X5J@VVl(+Qa-cU~Tx z|2~(}ujir1>P~SV`2ukA*Ttv!d{|fb`y%A{Q_h>00I+_D^Xp~kY4huqlsvB*Jg-4+ z&4bsW$HsYdO{YPfdUY#x_>Gi2Z$giak9fZGR?wb{uXjEFHsmy4+&Guy*rLA!In5Vm zUpTKRT^+hx6z@V#^Viu|P6td5D-Wgi6XVqVepdU=2TVaz99*MF3f z@8gtwpQPmbG$r3>(6i$AIrOxA{~{&Nmj=&QF^^bBzJ?zDl;`g^0Id1)E%dBB|1QvT z`5>M@eh)p0Bl0ymfBXS@G%p-qWqo&3^+(9rK4!$oKHaKfX$gTJ+2|boaVtkf@ z9_yF5KUx}kG!L9#-Z}F!F^?FZWn;g@`$@||&+DgfUb8&(*!tq_$0*TPi2W1i--^&< z@%Nr*T?uleOX9|_m>4SuxQkB_UnOO`t3r?MKgH(iyT3)*#jVx3wadTbth z>&XU?vplNET~cnC(w~iDf5hjIH-;X?p-?xa_Zc>Uo;Hq~rsUZSdNdCx-!}&y6PhoNT4|u{CfO4{^S413jLP(s|pq(9_n*?I5RoDCA9s zz0b3KN`E@V{)p@U4nccvyes+TVzVRUTK;v8{Sot}3VLi`)TeLHPF=eM{H}d=KPx-6 zE95M$OztKC?lX6To|i8|-RK_kiR)Pp=&^AZ`_&VA+C1o$lBYNHSRRYlH#On}^)PZNW7+&FpXOp_qD@?|pgw0xNYxix>L zLXV9%pJ(Ouds^(5m_O5@r`6-VAlKrYfn1C849Ho(#Oumh=&`&J*O@x#S>u+4URc*> z0=LF%R_u?sAE=M{#5m_-KJof%HuS9i&58NMI^U4ezeeb3dC&wojdNig<@W=cA*Xs* zsAv2=p%%!!Jg7VuR9+wELXYLQc+S`gJvNSF-1E@W`qc(G&C75<)ebq!OL0FnFZM^g zo}Ujr7ANm|enHG5_G=;ZSe(TD&!UukdqYo)(>{>1IQ462&zG-j_l2D8SNm7j^y;-A zaBtj&edhkqV{sPe{{gWdY&^144~%)3yt4m22zplj91J}zE{8x)b;jjSaRom#z}Cdsy zV|h}%t~m}k%P%%=*{R1vPH}VbVDieio&Y^MPjvBh@7(t8)T3XQV*F2poW_gBJr-~AIkB@+ z@|_Jm8ebPbZ@+v_Ab0U+H^4V-Z?+KbNLGc zerNBUH(Ug{HU1YvkBz_hoWUhApSZ4E3O#lmDZYPjSvXwpTnRnaPp(&j;C^8!BwOiA!l)}$X#0B6tq)}v#@^N3_TVXas9jndRBhk3OzP% z;{M{cK+pMCc<#V`PN4XJL-Af+q43@gJ$jzX#jE(8knUZ2cP_~9fSlr~_47{XS^c~# zCEwjC`R;+96}Nk#r>(d5L2m8u?}r|{?k#>7;Q`>(|6-gf_h-uAXLt~Ll>aW?-u~es z$gOxk9JJ?n#JqY0dMrNT{Cza$6X)+^&|~w@J3o0Ga@IfZdB!JVf5bZXB=oF2e=6n^ z`}Z{TXnb6}#Qr@KwClzZ^9wgzQ9+-D9_5SU^Yu_cpMxG7PjMgjJoG58j?df2y^xaU zMd-2mS(^9o=OxHlK8W#u8G2UyUrEXLD)hAZ_ZsBnw~KqF-nu&UI^xn$Ly!WD56ja-|M^3^|RP%Uf^X^$Fyx zUt<1$3O&{@u2=5YXV7E)66gEp&|~{9@&54_v45res(674pSw@KgdUrpg9mi&+-XSv zPBp{2bx*#6+{&Y`p=agMH!1nPP09Bi^l063<2YtWRp;b;;55E&{Hljc9MG#567~nk zt$6xiEMxoe+YCuFDo4{{b4Ca+va{0u!d&x`9Po=Qo6ft+2(7M>67^(%0z zAHP9Q>&NdYdH#SN>yLQe{%4?97#Hci(!YXs3+s*aUg_V^v*z1B&|~w~TPOYv+H-!1 z^Xxz9g>^MqvQ!@^ubjPCHx`4OweQu9#SQx<4Eqj{v+)+!xh0{;>W+B5w-oePT*W%E zH1xDOu?*x|T$Y7gi_3DcAG}VM*NNq0KfL|K3PC$=eCT?+$gyH?M_-?orj^JP2eY4c_Km`7Z9Izf+( zpSZs606nX|>=^Tj^`&#{pEz%-pvU6n&6_Te(>!%~C7#c8g`Rdk(+zSq|HORg4m}nx zF&}!Qz9~E{h(*%QGe)J{Tcv07ANsMb0GAn?uT`FQ0$jjhX<$hYe>u| zevfM?^sMJ)hCz?6TU)f<=DhW#AFz5#L; zpUES$h3})5zTbz(h?7R>v3bq)T%=2yO@=?shCeN_Km0yoc|DvP`@{9h>tQSO*m#Ni zt332rUGVl-ZIEmE-wt`0|MP%T++6VI< z^NDr;q}V@ke{eGNSbxR+!6`8xuZQJ%a4PiJJm7led2kx^ta)&H%!j(=fB4{xlzeBV z%{7Q!fnKaegp)dHGE)f*xC^#*FSWzBAp=xES(qow@{g5YOm- z;ib?E@`v=w`Ewccto*qgdK5p`FY$TlD}r{lb@WQ;S?lOk(4%p6eBOTeYRK7mdHb$w zAot=WJg;*t^jQDI>#OUa$KoZ{_v@j@)(@=OA7VYf8G2ehzXfvY zw~KFK-j(x&zTP;b^q~AMw5_)0XeJZ6tPeYHc|6TvQ`#H}*Zq1Wt zp~vz_oF~sg&+6Cn&iuhwv%C@K>FdyA@fGhQykYqBrs2<9kW+rT{`l96*zvs$JsL;XFJBKU_B+tC z^5>BKJ=&`761JG;|D4EK8*bn_Y)sMPs@joA*Xx`^WhW7>A4UW z{}IEgdUyU5IGd+6L%VnGOh1_Q8RS-6K8GHQi})PD7to`1xG*n@zbi7cA-f=#Bws?# z#%uDZ=6p6+kQc@t2RvUvkL?>rjvCdestV_OUqc?w^KXEAc~e>E%hw&>LXY*w+YdNT ziYQQ7Vg3O*T_?Hz6~Bl1CvX}MXE%CYrgP6O z-Af7dS0HzGp8RjfSwF<<;eViKT@U{YJvLrqz5Wk+lyA;2abJ}zRq6-nx&DdQd5b}h z)r|rCI&X2vEx(os+SB~%kdkjn=&`to=O#-*kMh^`lkKCjQ-wG*tR)ij_SK|JCCFs%m>f$Bt|5k<`n>WSh{8vfo&#KVV z`n4M5Z2pwy_l%79oJvRNQ69Vah;?A~lzy!NJu6OYrsP{ICEwc6V|mKIzm{DGa$3(^ zKfUkXuN(U#)`#_=$KoaK=hlz;#OtmN0zK!~=!H%017$7sxHc|r$Tox??Pm-1lz&HZ zBgkpI3p`~x_H>1cwQ->5c*JwwO`yl?R`LBFaBT{?mFJs5kJb<8SMfTfN9P_r3;t}L z(w{91f3}32)lo6tTS1TF==|}{OSX>v5$DY|&|`TZ&YNvh@@g)XIg77% z{k$9GT0C|S_+5Xzaoz)Rs>d!K#rIgM3Pr*FtA;$hzUTv-#X-#f8t7^3W8auZjAuXS zv3QF0zCZM|cnpAC%a4JvKVrWIK~L+~V92eyJOp~IU*h+jhC+|(w;QKneL7Wlai7F4 z-a;4_$lW*>-@ETQw5nTS9EU^B#!HOzh}a)7Z$}1t#d%%1zAV27xhM2!Je*(N_28)3 zFEQSup{K=r4CGdw84EoYZ*jdC7wBpI8xK9Le-l#rH8E(<#YyyQQp_jr8zw`K)eZ0d z{FIbDQ=!M^i+7)S8st&wpxxYv(_8kh6Gsbv6sRm0vTVXXV!{=&|`I=6yZ%Sii;VogDP6=M!f`k6rH+ zzh6HGIQgyRdjs^?czW}>5ppX|O+kBFzBfaU)xB{8M|bMey;JvM2DLyQu1|A;TlKdU zdMqBr>%Tm3Ha=p#Y=a)f&Gkd9m+jEA;xaGh6YIcy=uyAjcopZp?OxB*ae;x~7=zQ(CtUYi4I37H=y-on0 z>JOgt-V?zy@%Hxbld}GJ_g^Q2XXdTcKKhiLc1{i0IW23)8}HM>GxhV1hi8Bnir1N0 zd)|1R1)lZ?=`Xc?n~Z>GL$2e4^s|~=y8k(ln||-D-?>?Ty!AT|JZ%^8r*;k`&s5Hb zTgsTHQ>)}khA`EJLIAMb%(=a{p(KfOx}3=*Iha7-JR3kJ>Z%C;mzxN!87fj z`YywLIsLjnr(X|%XYwI^e*sUJ9|TYHU-BW%!&vuF*4}o-&d%<}cKtv{G3ihbXYDyW z8O(+G2zc85tX&@kFVwD&IpxOv$9JFTaq!&r!4tr>eMEn9ve4>^zXDO3@+9OM&$!>z z*p5f_55?#6tew<3+!t9p-gV-a;F)@P=drKA z3&rVc@U(wO{Zh~MzsdS#-|wmR=WoF??QeP2_Wv%YU*8A(`T=sy`?%eF)r7+}_yU*ccB+a?86|DE{u0 zBkg5))i6hYfTwvY_Gs<*3bTe+~l=w&uY8>4PL0-|8aQYk9Ym~FL*jXO8va+ z&;P&+<#&V$)U?0%zSk_^X*?4D3LW@?v*MIlvv$1aPyYkY#LebWHEy$kXX0jg)rOqi zDOddR-jA9CJdH=37ryUK%?TbG@8*JB^G58X#=E&8SHD?1&I4Yk9p?@BGhe`;`61VO zi$C7^U;*&hIJY3=to;{)+~kqB{TB|{StO^OMZpWzYccRxy%u-Mi{s;aUt|gJG%uvx zQ_lsK%=+Wq$1LTPCw9EwyTREq_RlqaP$x`n#teVr#YFWR$`-j!R)4Y-VO0A#PfSipdYXp*VS_bN}u*mWTf^|STBGwtq;N3X0sZ$DWdJTsnn&mlJePyLtr)y^4Z zr#!5F?5J6iHG}W{JK-AC!VMC&2;y!$9! zkB96njs6s}w+wiicT(S^UG+WUWg*wR6~B`2qAmwq>lf#Z?{_kn2hYUGJ5H^T)82~U znRz8-myB>I< z{O*;t=j|`+gJ*^dY5Pk2Q}yhf^~?76>bQXBj*OkuJ*mF1WBOZiKT`32kn21r zek4eL;M%Tn{-xH(#X-WN_F%|EaT?;-7dy#4rJ=x0J-qYPFz`&A zz5Q>v!((|n0z5Zww+0?+r;$!MQXg+S;W27(JvSQm*g9cM)-Uh6dTiD&@49*%c-pRR z|J(+0)<4HXuH%mA*VfsxE}sCowyVUaCa-miXrjXtJ2pS7dAu!np*-FWJk1la=RGf) z1fI4t{Ojzl-uMwG>}k75e7yawG7Cg@f8p(KyTYEvF}CMhmleP>?dM&WHDvws&U=kndzM$t zuqN1=I3B=Jjq_i`%igz7mBJl2n!A=mv!?9T*q&RT-pw4;$%hwErJ zhnKXY=l=S1@HBs=Ua5Ul3*;srY#giWj8^cpzl)vJI-?D86CdyR)DE83U+he5F12@c zmKvMmM(coF{gry9)(yKu&iY#?h$@S$ujRXYtuH>xZ|$?ggHSk9Xg*cTRizfXDj9zB%pem(#EP zbJ{xqJm%MdIqe(-p5}?Pzju5)IH$csa@so-JeCiKLC*5waLBd(lHaNCJRAYJ$pi0q z9*zXhj3eH8<*2MZ@4Rv}c&5L2^Yj?-)PIS4>U`x`$hBRhe(Cc&jBv++r{hMg{Td3D zi;9({{P8*cI>9NoW-tBykrTnw{x1HR@xD0aB*<<2Yx6~IJ~%n2U#Eb_;&m$Ip>{ki z>zB73PtR%ZjGXq)1drwWS&*}QKO1tBSKfK*9Pr%z!MTC*&kK})KIEbJUI3nHcW-18Qy-NlYriWYH|^{lKdu6g^@po-+PNlR=h~cht_#??9&*h~iA#dq z;NTM11i8_{m4B0iEB|KTtbg4Cxyggnxy`MRv+?XU$W30G@vb(W-40%;{qD%x^X|j% z1W)~T^XV?gSw7wE$VESO9(xbuT5pMGy?x$rFXSw)_d%}yiGC`s_dE8bd=t0glm{RW z#q~k(QvKBPp2tI3d*1QqVTTvTC3Q|XutInQJgvXfFSQSO6mmBHJO;UmPip*m+_5iy zr1lL@Wc5?=eG>9iTx#P|bszK;c%kv@>3~1a1pIl{=Z~kIo&(RcySJU52T$|U&7T*t zc1(WN`sIt@Y5m2HbAP$AlYR+2vz~Hzm685s@Iw9k74XJ?*zNh*+ z{$KOc&7U_QH~E*!pEm>gZw2(bKi&-#F!nzPH`J1yB2}*zrDJ z`z~wGdw%jgcyr^g*h~H1?GM1+eEt!5D1UwekBtjIXYF{``@eu^+RJ-x{%h8rH?MyS z`1L#FtiAro`r~b{KXdx?7kF-*{{|jv?|+E?zraKN;Xm+9e7*f4nx#SvwfijK8GGJ! z->l$;>i0kJO#e!~zcyP=JF{o~^7e~4zzfxLPVjWTm453zSDFhvJFeON(%g_QhISA; z-tYa-174~~1;|wIDa`?tO1!ZSc&vW_i`;ck6(s z?HR8hs^24&=gjLm<;CM;RbI6cMe8{{v17}tj%&Sg`n7)6UL|iE3aR(nHvlg*uJz7o zuTM^UeZgaS-Y;v%o9F!cSk zfIlMx{@`+Y6!K>@?6~XmF~H4u>OJ2d3!Z6b%d3urqN*+J-y@UwoZ9*KW&WHORYVP^8TYPJEQG#+MATq-uB?J{N5pJ$2s^$V2Tn1w8dv;*?r<>MvA8{A(maOd?yze9Kn)S;&9u=H& z#V_x8)R5C&BX}lHz3cQQ@HDPcUwvMb%>6~k&3LQwB&nuC9?H*YS%19w*$f`bgA(NG zuf!>}Z`ciTb6%c&FLPk>D~eGx9ddU(X#pOpM=N+HuHJgIfye664mqnwN7f(jx@>pw zG+!kimRIfgo#2J~M;Sa*KkxX~1)lov=1n)`EN^CH{qgpTJ-~C@YbJ0O-yX=-KQ~YI zggn&Vdx2--;cf4|!87so)@L8^OnW8$(tS^G-<`Ho!jX>@x_OR2HDEBVgT1(2J1jqZ`}UEtS} ztLxbd!83W`-8WtYp2@G&zVYI$9dCYJlJ(2`9{Z)>h1%~jk3HYI{qn3mJI^O;wRkCd z1$ZW3Ew8#BxDve3cy(3QKkt6<>YVnj$!YJ}oc68*&mE7i2hQ^I2FO`{-UzwIF&_U@ z?>8gjH-V>dtc?$P;8jW0o53@A>K$)xamuaPOW%*Y6}(V>-3FfNN8a(__MHCR0Ulc) z-074jar2&w-36YukJK}@@4Y)~$NRp@J>Y3v<9PYLZ*nhq_Ic~*zC)|;liUZnn@9IM z_T&2a-s5-xJk1x@ZVzVtNZvc_gMH*f#E*x8huZrQ@IvkVXij^N<+S%Wc-o&NPP$J_ zddd@!oAFoW$(Zyc~pl29QzWN)PDG7$hCinzBhkg0gv_T zS0UHW?y30_E00gX3you+foJ0E zowq&*kHzT=$XT4eggn$gz5-9XWg zkh}fnH{hZ6{2e?~Z|^zGAK+H3sfV}U%`$7HVOT!Q3OUR3|3Pl*<<0ZivUXB=K6}=VcYd1# zJQklhA=i3KyLjifxt#Lialp6UoZI1vJ+EK$fEUWcdBF>{-+bU{J;Xn6`^}&A&l~>* z99~>s-|r_b=#A^f{TdqxU`Y6~NPR zQQ9;4uJww*L+!Xy)*o-)uk5o|o9~i2Iv&|p0nfCfcV1i-yii`OmbK@dZ&uG~Zw>JD zd|Kj_+}B?dxb_Q)cWS?|7UVi^$9lPyn z_B6J{F7?Xk&-&n*_VUJe1BWN^@t&*q2G5<3`{eYeFL*3I{U8s;r$6yy0Ps-17zkcy zeA^H_lgHlk&yBM7yua7DvCltGKidR6({A2=wrSQs@A$h})}A*HHV04R63;Kb_p`PD zPy3VPXX>14%bb2~1s=V}AuOT_@3=P;B26-r7 zhl6M8=N-pKfT!)^j&oZ>&c?ZsS%2&~d37Eb1)j-I%c}-_biki60e{8@^v4DCw}ITm z*ZNtl&v@`c^_c*k84tYuZDQ7*x4&%*p5}i%?v}c`dJUVW=RJvsSw9u0?40$- z$ZO}7yMPy(M|REH^PX20z%%FM zzuR6)&Vh=RrwP zKc@0^FUUiAu{U_3yx0dk)1Kb=?+YFq|Mtu2*Zw*EIv}UL19RFt2s~3yZ#y3BvDYFi zvG(+_aCAu4o;PlXX6^N1tBGkM}2ACCmjE+VhUTCxOS-2PbFkc-IG~1niue)6QuQ zkIe_CgQw$_}qx7aAWf0ng;O zcYkqd)}G~6C-Td({(0N)a_~&NCUlgN-=QzHBqv0bdGHE{$MW+^@HDP&eqNRJ%Nw_= z!DD%FO-?)42JBpy)6Vq)J2yaX+Rr;a-3XqEw|9KH2|PW2lz0!W>ph_F&A|1!ox~T< z?fUeKTk00bHU4g1-wHX)>)Rk_{p)tfjsM>Mbq9Ew_u|Lop{3S_&QfI|Un*2Cgxm?9 zj#pBj6faKbn0Xg?roVaD3wLMz^WGo42RzeW-tYh33!c^^_HSIVNtThatx3*wMXYG0O@{z26-v0Y2c&z_EmbK$;&&RWV zdH1nTfS0PL=REXD@JxGp&qJRAkM)D6vwnH|!80B^;|iTKD*NKXlxHE=^8m@O9Y)o~ zH_R*lo`XD;56@@)@y6)|@U;HYFVpu(u#_cr@U(wOJyYwJ zS0GQdpXd4ctKfy=@tWh8l;@4d>sfo=dGZbL^m(b+OFbulGi%4&FWv%=^^3P5chBkG z0j}*4kL$kQU3}NkpN8)=z2}rWRrk3C*%ixC$or7%{Xo&H>~p)zNg{p#x%LOqA6{rF z722ki5(^)aaz6sD?In6n9#zW!nCO4v;G&=S^QnVtJwF3({H%U2IxgvRqW48W?@OZh z6>#lO;-7tfT&bLR{58@4#-ra{iW6TZm}zJn(f<}a%|EGcz2QaQW$lbB6x&L29uu$Q zs(yS={P+Pli`$Qo>$*eAuf{cQke?ve{u{S*_4DG@(xd2SQvNT%O&q=c{|cVT)B5)2 zwxZNAb|d=Dv9I~^yMt@K`~h6oGqK+t?eSN^yPA7yVT=9*Py4Ot8(tLs1-bFxn+Jb` zXX1$WF4~d``~$g(V|*T){2HloC;AsWlMnd*gfzGG+5aHd@j~h~y408)o5sQgQ@q{ra3_!0{*2_$>%P&y9qS-_x$Vpsp2P2t1oNmEYZtdF5zf z$G*1PA`Y(QF6!XQU(CUkzqo@de+dUy{*n%^{G}XR`AY*g?Qh3P4MVgH(O(w0iR18| zj^=j#+>aVq4m`~dDL*+skr$}i;uoxzhg|R5h~IMGrgyXgDR;%3<*r2ZR|c;6EamCF z*rcnkLiAS6mYa+dGH=K!tM>9JS`9o?-%-WxM*M1F6s-=q=2cwZ=F0Q9BxGwquKr29 z>ZT>f3~@2tt!qMV;x%@3-wmU+fa6a!-`56U#_cifjfKvpQhTYXI=DsAIz)e6;OdVX zuk|3;@ksoQ$9uUq6aPZJ%*^PQy}&d1X7^#5F46knnfTRsk}n%TzI3g>SMEnjJ5;|i zQMDFDy}>u*U_4Khrj}Z|Tg65n$j$gYtk^EU4b@tRD^SuUL0|CHf2n7hhxK1S@Jzp$ zJh4i}r`N%`J>H3&RY zj~Y(~z{)ZKt8&Y5c@qYFr)$IsU}+YSMti z9bEJhd<1Y)4{y0!gU8Ap3Hef$bcxG#zh{#4p;6%Jxu5tkxUNs{KBKdKc>CWN@N^sz zebY_lE_JcFZJO*0bo?3%zF8*@X^Wq)ITOG*@bIUaFWUex@xlJC@_!q5VQ8W`g6PLQqu4WwvqaMgZQi5k z!K10r)m>g``*=ocE5>UuRB*4XfA#IHh4kPlw)O@Of2#G{2Y_iW?|x@rqQ9SmYk%M0 z!8P9x$XVWjME@Y*q4qeK=pO>y&NsFFYgaWv4+YP(r&)iLqr)8gTJOUhT@t-tUq{TN%i)`6|dBDy5GNiMoHZW^R9mDHxy$^De58mfGo#$W8xE+A+C@r=4H&=6Q!FcE)wKcgV`VR9WFyhNb8Q@bIU)e|gct zHU2L-xVG2J4zB!H99;RYI=J#*18(Lo?>^vlqW^}2Yk6-vxR&=8aMLc{bCS2g(>xb_ z?>Xf=S$l&z3r#(p_HbKb|1Nl%PhxLSp{1=@IW>r)_p)}9c6QG@-vdodvjwvv>VHD|i}DDc|tQ(f=I# z>i298u71z%;L4xF!IeL!gDZb72Uq^wz)iip`7;lArae;YlzDU7nUDA}zk{pa3plv? zy&&*V9xX)l7Y1(XJx-2xTH=MEj7mBjMT-#qMIBuIUChDN-^GF3{EP2r$8XiuM*S#S z0z9)`8D2N3*Z9%>HcawzNytOv(^BA>_NqQVDwLyWX~;Ft)&FH2T>W1*TdrA$Rl^l6 z2c8)(46nPqeAdp`nFT#gtl3$C_^~2zbB>rk|AmQ_z%%`DkbD!hE7?%VMrmcpHO^B1 z;S=lY`}W=_`D{|O3Mqe8;3jXp{be=qO#D;*Wp&6+{#M=(lBB3bA&S-j&&)G5o@B(D zS%1trp_W-|foI}jc;#qq$hF<#c-VV^>p-sgqIt0{aFdToJ)Le8tp}c|hg_enJ-_V* zx#=Gx<@%OuCR(5Pu>o+C7v6hLy@`Gw2iN}4*TJ>k{eYYGjQ4q4f1*FY!L_`B4zA^G z=-|rV$ibDrF>sToroC%hK#yTS9K~ zLC#T|EAM@p4zd+^nz!P|;NsMl?ir<0{A(VCI>=3adhd}90?*Cwdf*x#sekI+YcS** zXDPpOAH*K2NIxCo*w;7?b#RU2FvqU&z2`~8!BhW4e@Ih7E;?0uPBg-?ujOv-;9BlT z;3i()=YXS#{%8l+^2Rv0mNzzAo_D=H4m>k{jOr5ywXx^ZvG>Q~wY z&(&KY*YQU5>)U6Rs?P!1AlE$5_HK7@(M#5|9S*Mi-GQ4t_vU3M(Jwo=me=LrT3$DB zlNYsneYI{7%>Ym1C;ByBIg0jxobCH(lJa|ioApbbeC1!h?iG)5wX6Sof~S5;`Dq^7 zX)mW7t^eK*uJzvsxQ-XXul?SW*&gpp^!Ed9+RNOBtF1To2T$`Sj-MQLH+CgIz*UY8 zaO|t!2RgX=eGqVshw#1q;$ZN!-^JzD-mB;?ACk2*IKE_AnMP~<>rn8teZ`KP19z5+ z)+dGr>#smtwh;5KHCgEXYmVz2oHB zSv#rc!sle|c-!w>@Ju_T_O<6h?&kmb4zBHRLAG4)z4Z%;{zbq|9KGwWi;4avz%{;- zH>vZ_OCdM?9rDT&>N3Z!=H2BEuJygb!IgidgDd|k2Uq^p4zB!bfSZ1lT3=jC^saMo zE$4a%*K%$EZu*Zm|8FGvH#xYLce8_QdA9)9_iKeedb92P|ZsO+MU)>F!_9KnMJ-|)Ad*|tU!87B1jVCd? zFYAZ5{q6@(_f_IY{GEzYx%wl?l?HeqYfpHstwp)G*lFH>d=NahUJn5`^{U=$s7@YI z)Wbyo5#XkslX1y?F88QYj?`yd{9!Zjs{5kHz%%;{d%v=J5A$*GOnX~i^n@dqylpMX zDP(hdTT}cZpnF2)DLosut4L9X%Ae)6<~YkoWf+~k*cJ@G7fI!=gw951LOsGWnF z_36vtnY^#@B!6Ck+~kS(TTA|@ z<>(v8O}kF0Ubi*_=C{O;?;Kp?{Jn!~oPPkW?JWG%y5dKo_Y-h4j^KNfl}`IJo1fg7-PlJm9%; zp4Y*(UFLIeZI}6hYrKW;&4UHNGjSZ(-cqQ1Z?POj3qo$<=-uxxl(kde-61QN8KtIp zs$Up#?Z4vB;P#gGUHbN`!0B=#G8$cLfJm ze^&%<@?=C=zjmdaSK_!5(O=oYwY*gvT+3S(xcV>r)Ov9>$k}>v^??2wq}(-un{mp! z?q7@OukGMk&vhJJ>$xs)R?qby*Y*}Y`97%JJV>5Z)!y{#1)h#iQf_KKT_19j|5bUM z{~KiWQ|D#9A=ma7Ka#k(@4fc{&*XK&lkaaQS<)AB({6QBOH*Fwi2lY7uH|jw;9B0Mz(e`98PVSy zxM}Cqy^1Y}-j-RtYWv3xvlY>+18&+gwci}%$fcfjjfJ+ZW_eYu*ieY-Avfzv?|xx$ z)=rHl?~MF8<**iWD&DycNXc**Xewk1=Y;;|(?UVG2hG+FF`#t$oxjg=lMnG=% z2i1E}_C5Novv%q#Uu-Beb$1q`k&v5qOtr@-$ld%N?ckbkV;o%bZ7gsTH}8Bg4m=%) zM8C$9`C=Q$wf{@G-hMeAJgrxf5Ar@pE6xEXK(6&t|0g=Q`oFD%D}Or&SNkp6$i;vBo@~XLsgRrb zp(4jQ(6p={-t+xt@HEdf9;K|l*$3AaP`eTR>A+2Yvg1oNqAf(f)uUhN&fZsSb9mza zxaMM8k1~@>%hE3GPC1$n9S*Mb-yOJ~j|pGi7fH6GvNEiVV4dJ;KM?)xd!!BZ2P$U-aL26#5V>^-kNAlLCHu3sts6is*KV>B}%ck`kL zxcVo0sdK449lNpraz4^odEKMCbuY-xIs)@XSvp$n0`T78EmxUt;``v{d@`*PQ}zL0 z>nnbx`pdqMv;MLlV6N%nQ4zA^#?BH6?DZsVt zoB`ad<1ijol1pFBJQF-KFUIG06_RAuS&(a-rQFo|_-scm<)_xi=Rj`qVzfM{X)CTI zUqS2cSZTzF5lQ}@3!b)@*5f?jtX-gAPpOR`Bx9r?)xfX?$;Y zaE5%H8W$@Fe+jsWn^~7shxzDb@N^y#eH|Z?+2$3Q80CG2eA3C`D|53JF+n=lZ-;cpF z{##!3iDO^m_Njwwxu4}M_wzuxUqBv;|Cd=irk%>sSCG4L``W?P|8E>z>-(*PEB`y- zrk{A%G2avY9~@lE`_aL*yq|!ZxYQ@RX8fRN#B*Q^|mM!!I={a?yWJxBPJ zl>eK9tG~ZHxcd7?ww%%O-v7*aITQQ(C(-*0xal|EaqMr$zLaZt-Q|BE4~_r-f@k_) zYX9&bU=Z{hzShQ-S-~^?((vSca>z}5 zyz}F1;MscF_b+FMT>X{yOwDU^K+g96b3*R+uelst^I>k_rv8cF?tQ&^i2l3|uK72g zgKK&7JGk-}aB$@>2;BJRUAHU*o@t-d`*#aF_O;wa99+v?6u8M#@B7V*5&gv-T+3U+ z!L__4ft&pCeou8N@XYhb_&%BaB0X*yu9n1WxzA@wB=RzBg=y4=F@TxuJu^n z!L{BiIJojx1aA7VcfYa{czO3LD}%4=Ao0Vyk6MNJwJLDa4^!*=)rj8eIm=xGJQM#3 z@n?cd=8PmBzSacK#K*f&U5og$HgKC)=66nXdWzN|`s-%(z2n+?;IVP77vv@{z0X0` zCw^=I+{VH8-M-#LzmJ1!UiNix&C7nkL;m&$PxD0dllbU!n*munwx3lu00W618)o&* zyi=Q>HX`~P=hWW>JTrcpa>~)Bj(v^aW)80L+Z?#byV`Tnn%mJ9;FT#8;OcihaP>#{se6)xiQW(g*K&qBxRx^vxb}bHd(XRu z6a5hmuH|j*;9A~D;3l8F>(o);nLIGOax@xp<9CwJ?)QJk5I@Fd{V;h}ON?>g>G&=6 z7$aY(Dp&64R^D}v^I@B;J@5KuJb2o!VsFCq_{Bo})X@Y-F7ZsQyCy=;)?M2|?#`3j zIk>jRB;cX-#r8yh2jI4U)$UbRhm~kYqCXk9X~+7O_HtLJ{7OJgJBoG!&%~|Dix&w~ zvVM5?(>sG_=7-dGx^{uw<|kgdjSrElF6|1Q$RLFH-Cwi%S`O_S^=%@B$&5k_Qmj_Wr{or*8a%~?s zZ+C;7K9>8_{68=bOw&bEfEt};#>EM~TY|~wkSCV9;iywdYIQF&N zJsn)j-3z#B&t!adpX2Nep86;H$@f=v|EwE;eZW)yr2N#qfPEdg*ol`uc#piRxF6&u zKU4GR{*ddvGSN@HcW?mYns-|N107uJe-Lmpj>P?6{i%*42ZN{mR`d<893A4=*K!Ya za4q*Rf*%gt_+#E*s@-ot0zBi7;gzE!J$8KWs~iQMX;<&Oc{F&skCgfivEPF}26BzN z*7I24=KasshQ9s#_KzoH`F9-AJ07^ngVcAzPat|HI=HsONe-_5o$TPsKgGe7e=2Y@ zetE}{)4((HSL(Yrr$erBmvZ$UX6n11XE^1FAIWoTd8{GVVv^pM#PLkXne2dV2TGSAl2pD)l{x zt0B+(y9d{RuX&^OxfZyY7rf77uOs@`d-Q$3YjgwAztO=p{x>D5V3c=v&)c`fy*^5QxFe#k@hc>p|OI`Yz|(b4 zY)6jgTJ`3NmeFi+u1M%B?uK27| zj^_Du4ler1eDu77EB^%tSN@B@)eqrU^UO4V^b*m3890mYD@5;A;F?cTuJ?E0UIS14 z6Tj=_CyM3s@3lK>am8PEcw#SJCuV*R;tk@@n+~pVd&|K!Zf^rO?d;t*yhHTg1#bF@ z_qo`6ME`x@CO=c(75e~kGY+NR2mBCnjjPnxdp`6LDgR^O=3E5lLk-Ez58XR`Li9h) z>Tg%({WSd_ltvT{C)*)+RJ|Tqx$aKZ{V4FSYGtIV_(bt!@;%OKOJ28e>u4F z|8{WY|Ks4w{}*^DuKyAJ2#MmxaTW*H@@55Y^1MJ-34^e;(lKkML9H6!Q|j`GA}H4r`GI zvhlBf*P<3h^MhyBOI2Q3`u75mn>_ZO!z~D&`YHaWe&25)$W1%i`@+?!cVX~!+z~sL zr^i3hB3XOh-|JX3YtQnk_7-z^5@+vw^NTw?@o#ufQ)j!p&YJqY=q13@_Sbe^(!sTz zmvV6BFYVyUUk13zLwWvGl* zvjXr?`>qIHsC`!gFSK4-8N3yx4)H&!pIN78e}{1u@J-yj_Ze0NPuox9zM4nh_uP7Q z@XUB*&l93GAUE;y-XB>LJQFX=tLE=o;HkfwKWjU<)?*#VuJFC<$92I|KjZv~pXbCU zLe-Z~B<|~h7izCw#Gmzn>-Zu5r1mWv5WU{O)j!emKELk+o}ME~JyO5V)E9D_7ru7w zNBroYQ-1)_9|&CYMe32-e{Bf4-S^2GC0KlRw{GOv75$_?Z0z9L{+j^TaY6X9ACZlu z+{m>HyiJMzX23)7+MMWb0X)=hTY{%~A^xZCF>IB!<9*IrNBkI+Q@@_*4|Z^k`w$1$ zxDU;i=Uv|p1JBf3*QLp;qQfCK@i+GwtJ7&T0z8v9hSy!*8gd<9#DDKNFcLf+U$j1> z9Q&f5x*s~4=#2qx+TR<8u|$6ya8rNp`PMc>e>`xL|FuJ%TKJ*~M1Lai&^mZq@XY!u zbzgKl$W1-$JW)M&o&=tb4-)?g#c9PZeeqy>j~(B8$UA`Nw&RY#O&)m9MJ9u1#?c9Q z1u+?KcFO6;6ynFu4zB%a7YEn;+ZDL=JASWJenPZpnlUO6{RZIL&!oK6yweCdJ5O(d z+~l!${wb33r)KrdzNNNOp9Y?3f5R(B%~?Cr#uv*B^E#esls4-R~+B z{Vw3x2FLfq>iRdx@0=6<4B$FW3qLiE?m_fs0yp*a?nirw{+_^1|M7ljdN1%yyvMbe z?@aIQ*w=R6$HBFo_XTe9%zGZVAJO06!L_^t99+vg5V(nN>b}51MDJh+*K!VVa4qLh z2Uq@K4zB#efwS@F2*}y^b7VmOD9E+mQf^&a`^+9$I419J9Syn3AG1EFjiASXr~O#{ zKNh&jAMd{TIHG@iwtR2?o&cViM-$$R?v`W^bRy&$Cy9G<-Z-F7<#*9eBITb9T<0^< zOMRd26ry*kgKImV=HMEq(}A1yhWGi-8Q|IWvd?$UbnI)nXF0f*dp2;>zTWtr1D=U* z>UrHFuKcSVT=~~Hxbm+BZu+J7`%c$^XWFYS{%I=xzSH%Pn|3$r zh1$B}2Jp=MV0h)|MvooeJ)xVjc2ej1H)rjbbx_TpTe5ZxuN>V9x#pL~Gg-vSe>5J$ z>c@_nB?0Y!qsYX=yWX1>hICw)c1ikON9A#E_4h+fzx1yA@mba?U)vY2nN@N1U)LL2 zFXf}U)p`j(HUG^8e^p=f>f)hO?^Bt5j`w%-bo?~!U%hWxm{y3iKWRLq{M0&IpA%`^ zrTo_k{dd2KnRaFALnnF?Ft`?@gIF zOlX$9n|?oXdFUJe%{sq!-)05ywEZOx;8pJA>-liV&PtBIVkb5JtPHv4N8G-?=kBY3 z=f-1I;M%^TKY3((cT2IotGTl@Gg)xO&sJ9>`l|ys?Gd*(&H-!dwKc%g{6=1Y*WJ1% zZ(v%Kngd>`;kJA0qk_XSVeLF}dO1@+7NmDq6`ygzs*|3?+&c0q*~ zMFSjpJPssV0Hn-7$W1@2$nl-N$|}0{yVs(hXlw*Mw|y(Wk}J;-A$QB$gy?Mw+~kF~ zzMFw(+QIUw?XWp`+7HB!)V#L^zSp)D%JX>yR9aH(5RW>iSCgqNFaP@PP zgR7sT9bEZi99;Qh9bEb299;R^01u5L!nej389n;3Fekz_#S^d=b zT7+Ev(!8DO;F`D7fSdOBu0xuMehIk7QOdL9XSM(D2A&xo<8?qB?Q$%d4tc2kTfo!$ zX+2tjn{|ZuUR)b^W*(P&4CSuoVr`C#Ded5ydU?mS4)Ao}BL1YF2k#EK`m62H>EK$A zBp4zy`IBn@F6f#3Nwt4B+P@@1q@D{XPb`#zFY0{ob*VoBU1fD~^NQ)Z6=9_jvH;$6qNwb5oB$-BQe#VJSQc&dYI`KLL!@=teg<(~mu+fn$b z_t?%PdS?MQ_4KY^&L;ZjIJo+Gu7j(e=K(i)={=V`pXgrz+_bCreW449{zbqw|HR+a zI{RYCm&0H2+uSEhe*z|^TmqgMAME|C>O6WWcy69umeu#p!Sfrp0=~buB%qm^=xdwzrgz{Zr+4OrC<=#4$CGJWa}f26!khpC$Uw zIk?8_c?Z{cy#U;_mv^22B6y~~EU(&LFM${G|7E9K@n=F&R!gNe{j_q$Rjdx$G*n%O$XO<-vS<*SKlW3?>M-Y_pXC$ zdG7(&b`ie!ce&pu`X2z-`ip+5zkdih>+c^yZra=X`*t6Lr+$k6!;75_@;%2=XM7IQ z3i&7a%l!Eia^}xxkZV3@{62SZjo%l*P5#I8Z{~XvUxH`)yWz=qPa!vP@UFMNCVqV5 z;Oh6c4z7NG2VDITerlfo9&(MN=%wcQA0Rh*>OCj=k(B=va8qCJy#F)N{{^`E=jPq7 zkh6CF4RTZe)O`CpIf3gqBYf|<#$4c; z_}6vHqeFerGdJYS?|C3+e$NZJ$ph~?Xg=`NU#W-XRma8o!87^b9bXmzPvfO=Sum?F z1D@F*Opsq?bXGA@v@Cdby!D- zN*+7*oO)%*O&)s3rB%Rl^KexM*F0Ly!8MOo&z4uc4^yhVLY<5UYk;TYoam>%Kenc0 zU&~#~!L{7Aft&V9;#IwrjXl{q;Ay*ve(JgNx>-B5-o|%;99S^-Fvz! zuSmu&tWWed0B+jJJ6`lA`h9?#`g_lZ`V#$qz)ilH`K@-}q(68jeuh_$20(7=;k~CZ z5Il`j-2S!u9Nn!O=JaPH@Z7xJIO~UbUZAZQMVo+U>QmP$Nh9AcEValTfK9V@Vm~|N zmuuyfv6`LDz*`3Muhhq^JBAlK3X%kgQiZTN_*#F-ll1SOLhjaUOW-<>#d;I#>-+ZE zXrsjNR*=tG^COl=b-+!2c-N1Ez|%aCdg%KN9rF8C)emCTJ3R5j&V$v!3 z{lw#;4ld;<_%Pr)?+V|0emb1!k8p4;Z)*qF@_Ah|n1Dv|eH-UJrKI-{y(~Fcv&*7xCZpyITJo2cF4i!z)MIWc~78v>l~Bsd=pla&1SgR}r}BN2&V-Qz6&!O!QLs3#LJC<`eI} zwV9M(0EPN<A9|yKeAgk;5rc;0=XH#yz9?Hi64gn zXZdqD(K{lmm-?N}BOST;U00ObGkS1v6y&;IlKR*8>C-1Vnv{2pV=v}w3digFV<9)~ zZ}z#h1>hI~mP2PCFvvUS`njfM+x>&CLZtSuvx@_1CS zt!vL>p)vrS4Y_H*df#`?&jC-z6|K*?z)e1l9o=`s=sdzdA9$$#7ZCjm9bDsmk%Oy$ z7Xvr-^XA7T;ORIh`l;{pUz)Y!J)gJ?JkxID3ti2H9y~U>9CCYZ>H9s2E5LK(eWgcV ze($-nyFq(+Wubi)(Z3qF=7Yp^h8}hj;%^kn5KDK^Bp8*5#|M!sc?*$&}NB0r^`yE{C`+$RM zeIEp_{ZaU-_2ffD?_uD^Pw#s25%9DhNV%zdp^rjt;+>2ejWRe^H~SK^$A}+~XZ`Sw zJ5Lb(Cmmel^^}8ayq*Sb`jvNHd4}je>)=}6a}KWMJrCT(**hM-0G`RmVG_WSe$V7Z z$W1@4{r*mE86Lev{CL^H)$dmvT>X9(xbfTDF0X;7`6v3R`QUZOzLxujgKN2OX3O>F zBCN%vdt=d|+yc&t7jX6<;_j~{_&^2qSY z(Z`V6Jo3Gd`3ZPB&TIR8n)SnbzV{i?{~Wl+OY$K#UVQ;M+edu~dC32-Ncmp_H+k*# z{~PdhT$B1+UiEq2w^@7Me)1jh=X(d&{_%r@Yy5u%&f@nI(fb*=iJSL1!Y|;N=fJ7o zPxv)!2m64=o^ov)6a7Z~_&uxdT@U<0^#25|?IQ8=p4bx2z0V;QCi;s2xBDG@KPvU8dr_jl81SWQ>($Enz4$NRpO4>9ss&#- zixWFb05|#OeIB$Vcv@d+hcpjBCF{3`WYkZT@ET)pG@+TgkI zStq9->k|F-fSd8o``oJ+cxJq+oFlbX?g_7-^}{=_Z9x3!om0OL(eDdf^H1V9eo+73 zQ9r`(58T8($xC=?Z^?N{H=kT_}e_uVKW*Do*AE#exUoeQI36$*JuaV zc#Uyz<&Sl6<&Oid?IwKheUxp8{`joEH_s;!{fWTM_&0fIsa1M*^2TH3WI=LlTky0U z#D7C8N83Se#*+!n#XTxN+ln=6oM@AXAKN>)#%%`&*SPHnT-#0fseR;R$kiXw^R7>J z0#EZ#?4*9za|$VcXW%BkyyrT*Wc{$b>bhoE@HD>SM=H+?kgK0kpVWPc2FOhwdG~jX z;A#KYcs4n>#=q#`%AcAo&zpDCz|(q(zU4*Dj(sh+5&U?9p8#C%OG`N|h3-yybXzLLC7nq0P9pfp1V4q~rxN@$f}c+CGYEbr!OtT2 z*#tj_;O7$jJc6GOT<2Yh_Y8UStEn_qz9LqLq6>)Lg#^Ed;1?785`te!@XH8(Il-?W z_?5s-K9}0c-7SS;C7-V%dRG(t8iHR-@aqVEJ;84v_>Ba=iQqRA{1)IkUq~Kww3phN ziZkQO@Wtp>$W5LUBum=lcY)+WVies*?A}iBI|zO!!S5pY-2}e}xSsDwecBt!joqD? zneHWe_YwSl;B!^dBNBU!@pG032>(IgHeT`)ZTk$lR@)}=dWh&fOz=kt{wTp8BlzP4 ze}dpo68tHGKTYsw2>vX=pCkD51b>0xFB1GEg1=1gR|x(p!Cxcz>jZy;;BONAErP#I z@OKFQF2Ua;`1=I^fZ!hjH~pZsQ0nR|VchtL=zUD^Pk@{C=G2zr%<6jcQ^?Kwp}j?J z0k)MYl+TFW&k6no!M`N;NKAZTY`T_@b3x!1HpeJ_)i4?nc%+={8xhiM)2PW z{s+PTB=}zh|C`|d5d2?)|3`5BP_^#EWqwFzidg`-_^br~AHioM`0NCqgWz)#d@h2| zP4Ibun|`)OsjI8h*j!ns&r9^?Bl!FTUx45X5_}r3 zf-enR&p#x;3e%=_7N-@ul7Vd*qPHx;mm~P{1Yd#RD-wJqg0D>QRS3Q+!B->r>I7ed z;A;|mErPENT+d^q9m;sF8^1|dScmAXOYrpw-izSt6MO@L_a=BBg7+nOKZ5rs_yB?r zB>08|--zHF6MPebZ%XjZ2);SNw;=eI1mB9_bp#(o@Opv|CioD74<-08f)6M72!d}- z@R0-`1>Ee@1oqPH`_cOm$$1TPT0f#8h28@(eZJI6^xA;y^JSTzI!dh_$uCZnrpg-=a;6vmYbW|01mB(D zodhovybHMPpN;X)L$p*DGTlUP2Eq3r_)LQL5PVO9??v#v3BC`(_a*p#z)gRX9~o?$ z)=;SIkoPBg2N3)~f*(Zig9&~J!4D<)VFW*%;71VrNP-_l@S_QS48e~j_;Cb3p5P}C z{6vDEMDUXdehR@)CHQH;ZNF-1lrP@M0d4%A@aaVF41%9Y@UsYhHo?y!__+i>kKpGM z`~resNbrjYelfu>A^4>Pzl`9Q6Z{H-UkTjoujB7>bTr35HYNYAB6?Q?w|USxBR*_v zlp!{XuF1-qiZcpr1v%#L6#2D~o97}kN^SA=nz+;`x{j24J;84v_>Ba=iQqRA{1)J5 z{W!HyZY(8-?BegOMDI3&-%jv52!1EQ?;`l!1iy#i_Y(X*g5OW@2MGQkaGPg^#^$#6 zrplABhlt+81b>9!j}rVb;3nTD)YtXtvtjQf-J-{d-V+3WlHgAf{Aq$eL-1z_{v5%d zC-@5lf05uX5&UI>ze4a=3H};z(~mlvd&=!im7l?gUMG5Q05|7S@>+XYHVfUI@dNue ziQZcTf1BX%5d2+&zen)*3H|}WKP31^1pk=epAh_0f`3Nv&k6no!M`N;NKAZ zTY`T_@b3x!1HpeJ_)i4?nc%+={8xhiM)2PW{s+PTB=}zh|C`|d5d2?)|3~m>w#v8) zar_)R0cIiitOWlb!Dl1*>;#{K;ByjuE`rZZ@Ogln`7QmNg6Pdh@c9Y80Kpd|_(B9< z7`PdSTjN2v3p@Wsh~AE%C)P2u7qBn@(^#mVG@FBp>x^kM_oa%0AuRIwTO7w;SH*s#4-}{ygY-Io) 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 Date: Thu, 12 Jan 2017 13:00:11 -0800 Subject: [PATCH 04/32] Fixing the patch to correctly transfer the information from the old structure to the new one --- qiita_db/support_files/patches/47.sql | 426 +---------- .../patches/python_patches/47.py | 724 +++++++++++++++++- 2 files changed, 721 insertions(+), 429 deletions(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 5d7c4f5b7..bb9f2d8f0 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -1,14 +1,18 @@ -- Jan 5, 2017 -- Move the analysis to the plugin system. This is a major rewrite of the --- database backend that supports the analysis pipeline. The code is commented --- with details on the changes implemented here, but here is an overview of --- the changes needed to make this transformation: --- 1) Set new data structures to support the analysis as plugins --- 2) Create a new type plugin to define the diversity types --- 3) Create the new commands on the existing QIIME plugin to execute the --- existing analyses (beta div, taxa summaries and alpha rarefaction) --- 4) Transfer all the data in the old structures to the plugin structures --- 5) Delete old structures +-- database backend that supports the analysis pipeline. +-- After exploring the data on the database, we realized that +-- there are a lot of inconsistencies in the data. Unfortunately, this +-- makes the process of trasnferring the data from the old structure +-- to the new one a bit more challenging, as we will need to handle +-- different special cases. Furthermore, all the information needed is not +-- present in the database, since it requires checking BIOM files. Due to these +-- reason, the vast majority of the data transfer is done in the python patch +-- 47.py + +-- In this file we are just creating the new data structures. The old +-- datastructure will be dropped in the python patch once all data has been +-- transferred. -- Create the new data structures @@ -23,382 +27,28 @@ CREATE INDEX idx_analysis_artifact_artifact ON qiita.analysis_artifact (artifact ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_analysis FOREIGN KEY ( analysis_id ) REFERENCES qiita.analysis( analysis_id ); ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_artifact FOREIGN KEY ( artifact_id ) REFERENCES qiita.artifact( artifact_id ); -DO $do$ -DECLARE - divtype_id bigint; - validate_id bigint; - html_summary_id bigint; - dm_at_id bigint; - rc_at_id bigint; - ts_at_id bigint; - qiime_id bigint; - sum_taxa_id bigint; - bdiv_id bigint; - arare_id bigint; - srare_id bigint; - st_cp_id bigint; - bdiv_cp_id bigint; - arare_cp_id bigint; - srare_cp_id bigint; - biom_at_id bigint; - ts_co_id bigint; - dm_co_id bigint; - rc_co_id bigint; - sr_co_id bigint; - analysis RECORD; - biom_data RECORD; - job_data RECORD; - initial_biom_id bigint; - rarefaction_job_id UUID; - params json; - rarefied_biom_id bigint; - cmd_id bigint; - input_artifact_id bigint; - proc_job_id UUID; - output_artifact_type_id bigint; - output_artifact_id bigint; - cmd_out_id bigint; - log_id bigint; - output_filepath_id bigint; - tree_fp varchar; -BEGIN - -- The new commands that we are going to add generate new artifact types - -- these new artifact types are going to be added to a different plugin - -- In interest of time and given that the artifact type system is going to - -- change in the near future, we feel that the easiest way to transfer - -- the current analyses results is by creating 3 different types of - -- artifacts: (1) distance matrix -> which will include the distance matrix - -- the principal coordinates and the emperor plots; (2) rarefaction - -- curves -> which will include all the files generated by alpha rarefaction - -- and (3) taxonomy summary, which will include all the files generated - -- by summarize_taxa_through_plots.py - - -- Step 1: Create the new type plugin - INSERT INTO qiita.software (name, version, description, environment_script, start_script, software_type_id) - VALUES ('Diversity types', '0.1.0', 'Diversity artifacts type plugin', 'source activate qiita', 'start_diversity_types', 2) - RETURNING software_id INTO divtype_id; - - -- Step 2: Create the validate and HTML generator commands - INSERT INTO qiita.software_command (software_id, name, description) - VALUES (divtype_id, 'Validate', 'Validates a new artifact of the given diversity type') - RETURNING command_id INTO validate_id; - INSERT INTO qiita.software_command (software_id, name, description) - VALUES (divtype_id, 'Generate HTML summary', 'Generates the HTML summary of a given diversity type') - RETURNING command_id INTO html_summary_id; - - -- Step 3: Add the parameters for the previous commands - INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required) - VALUES (validate_id, 'template', 'prep_template', True), - (validate_id, 'files', 'string', True), - (validate_id, 'artifact_type', 'string', True), - (html_summary_id, 'input_data', 'artifact', True); - - -- Step 4: Add the new artifact types - INSERT INTO qiita.artifact_type (artifact_type, description, can_be_submitted_to_ebi, can_be_submitted_to_vamps) - VALUES ('distance_matrix', 'Distance matrix holding pairwise distance between samples', False, False) - RETURNING artifact_type_id INTO dm_at_id; - INSERT INTO qiita.artifact_type (artifact_type, description, can_be_submitted_to_ebi, can_be_submitted_to_vamps) - VALUES ('rarefaction_curves', 'Rarefaction curves', False, False) - RETURNING artifact_type_id INTO rc_at_id; - INSERT INTO qiita.artifact_type (artifact_type, description, can_be_submitted_to_ebi, can_be_submitted_to_vamps) - VALUES ('taxa_summary', 'Taxa summary plots', False, False) - RETURNING artifact_type_id INTO ts_at_id; - - -- Step 5: Associate each artifact with the filetypes that it accepts - -- At this time we are going to add them as directories, just as it is done - -- right now. We can make it fancier with the new type system. - -- Magic number 8: the filepath_type_id for the directory - INSERT INTO qiita.artifact_type_filepath_type (artifact_type_id, filepath_type_id, required) - VALUES (dm_at_id, 8, True), - (rc_at_id, 8, True), - (ts_at_id, 8, True); - - -- Step 6: Associate the plugin with the types that it defines - INSERT INTO qiita.software_artifact_type (software_id, artifact_type_id) - VALUES (divtype_id, dm_at_id), - (divtype_id, rc_at_id), - (divtype_id, ts_at_id); - - -- Step 7: Create the new entries for the data directory - INSERT INTO qiita.data_directory (data_type, mountpoint, subdirectory, active) - VALUES ('distance_matrix', 'distance_matrix', true, true), - ('rarefaction_curves', 'rarefaction_curves', true, true), - ('taxa_summary', 'taxa_summary', true, true); - - -- Create the new commands that execute the current analyses. In qiita, - -- the only commands that where available are Summarize Taxa, Beta - -- Diversity and Alpha Rarefaction. The system was executing rarefaction - -- by default, but it should be a different step in the analysis process - -- so we are going to create a command for it too. These commands are going - -- to be part of the QIIME plugin, so we are going to first retrieve the - -- id of the QIIME 1.9.1 plugin, which for sure exists cause it was added - -- in patch 33 and there is no way of removing plugins - - -- Step 1: Get the QIIME plugin id - SELECT software_id FROM qiita.software - WHERE name = 'QIIME' AND version = '1.9.1' - INTO qiime_id; - - -- Step 2: Insert the new commands in the software_command table - INSERT INTO qiita.software_command (software_id, name, description) - VALUES (qiime_id, 'Summarize Taxa', 'Plots taxonomy summaries at different taxonomy levels') - RETURNING command_id INTO sum_taxa_id; - INSERT INTO qiita.software_command (software_id, name, description) - VALUES (qiime_id, 'Beta Diversity', 'Computes and plots beta diversity results') - RETURNING command_id INTO bdiv_id; - INSERT INTO qiita.software_command (software_id, name, description) - VALUES (qiime_id, 'Alpha Rarefaction', 'Computes and plots alpha rarefaction results') - RETURNING command_id INTO arare_id; - INSERT INTO qiita.software_command (software_id, name, description) - VALUES (qiime_id, 'Single Rarefaction', 'Rarefies the input table by random sampling without replacement') - RETURNING command_id INTO srare_id; - - -- Step 3: Insert the parameters for each command - INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) - -- Summarize Taxa - VALUES (sum_taxa_id, 'metadata_category', 'string', False, ''), - (sum_taxa_id, 'sort', 'bool', False, 'False'), - -- Beta Diversity - (bdiv_id, 'tree', 'string', False, ''), - (bdiv_id, 'metrics', 'mchoice:["abund_jaccard","binary_chisq","binary_chord","binary_euclidean","binary_hamming","binary_jaccard","binary_lennon","binary_ochiai","binary_otu_gain","binary_pearson","binary_sorensen_dice","bray_curtis","bray_curtis_faith","bray_curtis_magurran","canberra","chisq","chord","euclidean","gower","hellinger","kulczynski","manhattan","morisita_horn","pearson","soergel","spearman_approx","specprof","unifrac","unifrac_g","unifrac_g_full_tree","unweighted_unifrac","unweighted_unifrac_full_tree","weighted_normalized_unifrac","weighted_unifrac"]', False, '["binary_jaccard","bray_curtis"]'), - -- Alpha rarefaction - (arare_id, 'tree', 'string', False, ''), - (arare_id, 'num_steps', 'integer', False, 10), - (arare_id, 'min_rare_depth', 'integer', False, 10), - (arare_id, 'max_rare_depth', 'integer', False, 'Default'), - -- Single rarefaction - (srare_id, 'depth', 'integer', True, NULL), - (srare_id, 'subsample_multinomial', 'bool', False, 'False'); - - INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) - VALUES (sum_taxa_id, 'biom_table', 'artifact', True, NULL) - RETURNING command_parameter_id INTO st_cp_id; - INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) - VALUES (bdiv_id, 'biom_table', 'artifact', True, NULL) - RETURNING command_parameter_id INTO bdiv_cp_id; - INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) - VALUES (arare_id, 'biom_table', 'artifact', True, NULL) - RETURNING command_parameter_id INTO arare_cp_id; - INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required, default_value) - VALUES (srare_id, 'biom_table', 'artifact', True, NULL) - RETURNING command_parameter_id INTO srare_cp_id; - - -- Step 4: Connect the artifact parameters with the artifact types that - -- they accept - SELECT artifact_type_id INTO biom_at_id - FROM qiita.artifact_type - WHERE artifact_type = 'BIOM'; - INSERT INTO qiita.parameter_artifact_type (command_parameter_id, artifact_type_id) - VALUES (st_cp_id, biom_at_id), - (bdiv_cp_id, biom_at_id), - (arare_cp_id, biom_at_id), - (srare_cp_id, biom_at_id); - - -- Step 5: Add the outputs of the command. - INSERT INTO qiita.command_output (name, command_id, artifact_type_id) - VALUES ('taxa_summary', sum_taxa_id, ts_at_id) - RETURNING command_output_id INTO ts_co_id; - INSERT INTO qiita.command_output (name, command_id, artifact_type_id) - VALUES ('distance_matrix', bdiv_id, dm_at_id) - RETURNING command_output_id INTO dm_co_id; - INSERT INTO qiita.command_output (name, command_id, artifact_type_id) - VALUES ('rarefaction_curves', arare_id, rc_at_id) - RETURNING command_output_id INTO rc_co_id; - INSERT INTO qiita.command_output (name, command_id, artifact_type_id) - VALUES ('rarefied_table', srare_id, biom_at_id) - RETURNING command_output_id INTO sr_co_id; - - -- At this point we are ready to start transferring the data from the old - -- structures to the new structures. Overview of the procedure: - -- Step 1: Add initial set of artifacts up to rarefied table - -- Step 2: Transfer the "analisys jobs" to processing jobs and create - -- the analysis artifacts - -- Fun fact: after exploring the data on the database, we realized that - -- there are a lot of inconsistencies in the data. Unfortunately, this - -- makes the process of trasnferring the data from the old structure - -- to the new one a bit more challenging, as we will need to handle - -- different special cases. - - -- Special case 1: there are jobs in the database that do not contain - -- any information about the options used to process those parameters. - -- However, these jobs do not have any results and all are marked either - -- as queued or error, although no error log has been saved. Since these - -- jobs are mainly useleess, we are going to remove them from the system - DELETE FROM qiita.analysis_job - WHERE job_id IN (SELECT job_id FROM qiita.job WHERE options = '{}'); - DELETE FROM qiita.job WHERE options = '{}'; - - -- Special case 2: there are a fair amount of jobs (719 last time I - -- checked) that are not attached to any analysis. Not sure how this - -- can happen, but these orphan jobs can't be accessed from anywhere - -- in the interface. Remove them from the system. Note that we are - -- unlinking the files but we are not removing them from the filepath - -- table. We will do that on the patch 47.py using the - -- purge_filepaths function, as it will make sure that those files are - -- not used anywhere else - DELETE FROM qiita.job_results_filepath WHERE job_id IN ( - SELECT job_id FROM qiita.job J WHERE NOT EXISTS ( - SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id)); - DELETE FROM qiita.job J WHERE NOT EXISTS ( - SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id); - - -- Loop through all the analysis - FOR analysis IN - SELECT * FROM qiita.analysis - LOOP - - -- Step 1: Add the inital set of artifacts. An analysis starts with - -- a set of artifacts. The initial set of artifacts are biom tables - -- generated by subseting the BIOM tables from the studies. However, - -- the old strucures where not storing these tables, since the first - -- step that they did was rarefy the tables. In the new analysis - -- pipeline, that will not be the case. Thus, there are 3 substeps - -- to successfully add the inital set of artifacts: (1) create a - -- placeholder artifact for the non-rarefied OTU table; (2) create - -- a single rarefaction job that rarefies such table; and (3) create - -- the artifact resulting from the single rarefaction step - FOR biom_data IN - SELECT * - FROM qiita.analysis_filepath - JOIN qiita.filepath USING (filepath_id) - JOIN qiita.filepath_type USING (filepath_type_id) - WHERE analysis_id = analysis.analysis_id - AND filepath_type = 'biom' - LOOP - -- Step 1.1: Create the placeholder artifact - -- Step 1.1.a: Add the row in the artifact table - -- Magic number 4: Visibility -> sandbox - INSERT INTO qiita.artifact (generated_timestamp, command_id, data_type_id, command_parameters, visibility_id, artifact_type_id, submitted_to_vamps) - VALUES (analysis.timestamp, NULL, biom_data.data_type_id, NULL, 4, biom_at_id, False) - RETURNING artifact_id INTO initial_biom_id; - -- Step 1.1.b: Associate the artifact with the analysis - INSERT INTO qiita.analysis_artifact (analysis_id, artifact_id) - VALUES (analysis.analysis_id, initial_biom_id); - -- Step 1.1.c: Link the artifact with its file - INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) - VALUES (initial_biom_id, biom_data.filepath_id); - - -- Step 1.2: Create the single rarefaction job - -- Step 1.2.a: Add the row in the procesisng job table - -- depth 1000 -> the depth information is not stored in the database - -- We will update the correct value in patch 47.py, since we can - -- obtain this value from the BIOM tables - params := ('{"depth":1000,"subsample_multinomial":false,"biom_table":' || initial_biom_id::varchar || '}')::json; - -- magic number 3: status -> success - INSERT INTO qiita.processing_job (email, command_id, command_parameters, processing_job_status_id) - VALUES (analysis.email, srare_id, params, 3) - RETURNING processing_job_id INTO rarefaction_job_id; - -- Step 1.2.b: Link the job with the input artifact - INSERT INTO qiita.artifact_processing_job (artifact_id, processing_job_id) - VALUES (initial_biom_id, rarefaction_job_id); - - -- Step 1.3: Create the rarefied artifact - -- Step 1.3.a: Add the row in the artifact table - -- Magic number 4: Visibility -> sandbox - INSERT INTO qiita.artifact (generated_timestamp, command_id, data_type_id, command_parameters, visibility_id, artifact_type_id, submitted_to_vamps) - VALUES (analysis.timestamp, srare_id, biom_data.data_type_id, params, 4, biom_at_id, False) - RETURNING artifact_id INTO rarefied_biom_id; - -- Step 1.3.b: Link the artifact with its file - INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) - VALUES (rarefied_biom_id, biom_data.filepath_id); - -- Step 1.3.c: Link the artifact with its parent - INSERT INTO qiita.parent_artifact (artifact_id, parent_id) - VALUES (rarefied_biom_id, initial_biom_id); - -- Step 1.3.d: Link the artifact as the job output - INSERT INTO qiita.artifact_output_processing_job (artifact_id, processing_job_id, command_output_id) - VALUES (rarefied_biom_id, rarefaction_job_id, sr_co_id); - - -- Step 2: Transfer all the "analysis" jobs that used this biom - -- table as input to the new structure - FOR job_data IN - SELECT * - FROM qiita.job - WHERE reverse(split_part(reverse(options::json->>'--otu_table_fp'), '/', 1)) = biom_data.filepath - LOOP - -- Step 2.1: Define which command the current job executed and - -- which are the parameters of this job - IF job_data.command_id = 1 THEN - -- Taxa summaries - cmd_id := sum_taxa_id; - params := ('{"biom_table":'|| rarefied_biom_id::varchar ||',"metadata_category":"","sort":false}')::json; - output_artifact_type_id := ts_at_id; - cmd_out_id := ts_co_id; - ELSIF job_data.command_id = 2 THEN - -- Beta diversity - cmd_id := bdiv_id; - params := ('{"biom_table":'|| rarefied_biom_id::varchar ||',"tree":"'|| (job_data.options::json->'tree_fp')::varchar ||'","metrics":["unweighted_unifrac","weighted_unifrac"]}')::json; - output_artifact_type_id := dm_at_id; - cmd_out_id := dm_co_id; - ELSE - -- Alpha rarefaction - cmd_id := arare_id; - params := ('{"biom_table":'|| rarefied_biom_id::varchar ||',"tree":"'|| (job_data.options::json->'tree_fp')::varchar ||'","num_steps":"10","min_rare_depth":"10","max_rare_depth":"Default"}')::json; - output_artifact_type_id := rc_at_id; - cmd_out_id := rc_co_id; - END IF; - - -- Step 2.2: Create the job - -- Step 2.2.a: Add the row in the processing job table - -- Magic number 3: status -> success - INSERT INTO qiita.processing_job (email, command_id, command_parameters, processing_job_status_id) - VALUES (analysis.email, cmd_id, params, 3) - RETURNING processing_job_id INTO proc_job_id; - -- Step 2.2.b: Link the job with the input artifact - INSERT INTO qiita.artifact_processing_job (artifact_id, processing_job_id) - VALUES (rarefied_biom_id, proc_job_id); - - - -- Step 2.3: Check if the executed job has results and add them - IF EXISTS(SELECT * FROM qiita.job_results_filepath WHERE job_id = job_data.job_id) THEN - -- There are results for the current job - -- Step 2.3.a: Add the row in the artifact table - -- Magic number 4: Visibility -> sandbox - INSERT INTO qiita.artifact (generated_timestamp, command_id, data_type_id, command_parameters, visibility_id, artifact_type_id, submitted_to_vamps) - VALUES (analysis.timestamp, cmd_id, biom_data.data_type_id, params, 4, output_artifact_type_id, False) - RETURNING artifact_id INTO output_artifact_id; - -- Step 2.3.b: Link the artifact with its file - SELECT filepath_id INTO output_filepath_id FROM qiita.job_results_filepath WHERE job_id = job_data.job_id; - INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) - VALUES (output_artifact_id, output_filepath_id); - -- Step 2.3.c: Link the artifact with its parent - INSERT INTO qiita.parent_artifact (artifact_id, parent_id) - VALUES (output_artifact_id, rarefied_biom_id); - -- Step 2.3.d: Link the artifact as the job output - INSERT INTO qiita.artifact_output_processing_job (artifact_id, processing_job_id, command_output_id) - VALUES (output_artifact_id, proc_job_id, cmd_out_id); - ELSE - -- There are no results on the current job, so mark it as - -- error - IF job_data.log_id IS NULL THEN - -- Magic number 2 - we are not using any other severity - -- level, so keep using number 2 - INSERT INTO qiita.logging (time, severity_id, msg) - VALUES (analysis.timestamp, 2, "Unknown error - patch 47") - RETURNING logging_id into log_id; - ELSE - log_id := job_data.log_id; - END IF; - - UPDATE qiita.processing_job - SET processing_job_status_id = 4, logging_id = log_id - WHERE processing_job_id = proc_job_id; - END IF; - END LOOP; - END LOOP; - END LOOP; -END $do$; - --- Delete old structures that are not used anymore -DROP TABLE qiita.collection_job; -DROP TABLE qiita.collection_analysis; -DROP TABLE qiita.collection_users; -DROP TABLE qiita.collection; -DROP TABLE qiita.collection_status; -DROP TABLE qiita.analysis_workflow; -DROP TABLE qiita.analysis_chain; -DROP TABLE qiita.analysis_job; -DROP TABLE qiita.job_results_filepath; -DROP TABLE qiita.job; -DROP TABLE qiita.job_status; -DROP TABLE qiita.command_data_type; -DROP TABLE qiita.command; +-- We can handle some of the special cases here, so we simplify the work in the +-- python patch + +-- Special case 1: there are jobs in the database that do not contain +-- any information about the options used to process those parameters. +-- However, these jobs do not have any results and all are marked either +-- as queued or error, although no error log has been saved. Since these +-- jobs are mainly useleess, we are going to remove them from the system +DELETE FROM qiita.analysis_job + WHERE job_id IN (SELECT job_id FROM qiita.job WHERE options = '{}'); +DELETE FROM qiita.job WHERE options = '{}'; + +-- Special case 2: there are a fair amount of jobs (719 last time I +-- checked) that are not attached to any analysis. Not sure how this +-- can happen, but these orphan jobs can't be accessed from anywhere +-- in the interface. Remove them from the system. Note that we are +-- unlinking the files but we are not removing them from the filepath +-- table. We will do that on the patch 47.py using the +-- purge_filepaths function, as it will make sure that those files are +-- not used anywhere else +DELETE FROM qiita.job_results_filepath WHERE job_id IN ( + SELECT job_id FROM qiita.job J WHERE NOT EXISTS ( + SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id)); +DELETE FROM qiita.job J WHERE NOT EXISTS ( + SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id); diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index 8e402026f..90a008615 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -1,50 +1,692 @@ -# There are 2 things that need to be done in this patch: -# 1) Correct the rarefaction depth of the existing jobs and artifcats -# 2) Purge filepaths +# The code is commented with details on the changes implemented here, +# but here is an overview of the changes needed to transfer the analysis +# data to the plugins structure: +# 1) Create a new type plugin to define the diversity types +# 2) Create the new commands on the existing QIIME plugin to execute the +# existing analyses (beta div, taxa summaries and alpha rarefaction) +# 3) Transfer all the data in the old structures to the plugin structures +# 4) Delete old structures -from json import dumps +from os.path import join, exists, basename +from os import makedirs +from json import loads -from biom import load_table +from biom import load_table, Table +from biom.util import biom_open -from qiita_db.util import purge_filepaths -from qiita_db.artifact import Artifact from qiita_db.sql_connection import TRN +from qiita_db.util import (get_db_files_base_dir, purge_filepaths, + get_mountpoint, compute_checksum) +from qiita_db.artifact import Artifact + +# Create some aux functions that are going to make the code more modular +# and easier to understand, since there is a fair amount of work to do to +# trasnfer the data from the old structure to the new one + + +def create_non_rarefied_biom_artifact(analysis, biom_data, rarefied_table): + """Creates the initial non-rarefied BIOM artifact of the analysis + + Parameters + ---------- + analysis : dict + Dictionary with the analysis information + biom_data : dict + Dictionary with the biom file information + rarefied_table : biom.Table + The rarefied BIOM table + + Returns + ------- + int + The id of the new artifact + """ + # The non rarefied biom artifact is the initial biom table of the analysis. + # This table does not currently exist anywhere, so we need to actually + # create the BIOM file. To create this BIOM file we need: (1) the samples + # and artifacts they come from and (2) whether the samples where + # renamed or not. (1) is on the database, but we need to inferr (2) from + # the existing rarefied BIOM table. Fun, fun... + + with TRN: + # Get the samples included in the BIOM table grouped by artifact id + # Note that the analysis contains a BIOM table per data type included + # in it, and the table analysis_sample does not differentiate between + # datatypes, so we need to check the data type in the artifact table + sql = """SELECT artifact_id, array_agg(sample_id) + FROM qiita.analysis_sample + JOIN qiita.artifact USING (artifact_id) + WHERE analysis_id = %s AND data_type_id = %s + GROUP BY artifact_id""" + TRN.add(sql, [analysis['analysis_id'], biom_data['data_type_id']]) + samples_by_artifact = TRN.execute_fetchlast() + + # Create an empty BIOM table to be the new master table + new_table = Table([], [], []) + ids_map = {} + for a_id, samples in samples_by_artifact: + # Get the filepath of the BIOM table from the artifact + artifact = Artifact(a_id) + biom_fp = None + for _, fp, fp_type in artifact.filepaths: + if fp_type == 'biom': + biom_fp = fp + # Note that we are sure that the biom table exists for sure, so + # no need to check if biom_fp is undefined + biom_table = load_table(biom_fp) + biom_table.filter(samples, axis='sample', inplace=True) + new_table = new_table.merge(biom_table) + ids_map.update({sid: "%d.%s" % (a_id, sid) + for sid in biom_table.ids()}) + + # Check if we need to rename the sample ids in the biom table + new_table_ids = set(new_table.ids()) + if not new_table_ids.issuperset(rarefied_table.ids()): + # We need to rename the sample ids + new_table.update_ids(ids_map, 'sample', True, True) + + sql = """INSERT INTO qiita.artifact + (generated_timestamp, data_type_id, visibility_id, + artifact_type_id, submitted_to_vamps) + VALUES (%s, %s, %s, %s, %s) + RETURNING artifact_id""" + # Magic number 4 -> visibility sandbox + # Magix number 7 -> biom artifact type + TRN.add(sql, [analysis['timestamp'], biom_data['data_type_id'], + 4, 7, False]) + artifact_id = TRN.execute_fetchlast() + # Associate the artifact with the analysis + sql = """INSERT INTO qiita.analysis_artifact + (analysis_id, artifact_id) + VALUES (%s, %s)""" + TRN.add(sql, [analysis['analysis_id'], artifact_id]) + # Link the artifact with its file + dd_id, mp = get_mountpoint('BIOM')[0] + dir_fp = join(get_db_files_base_dir(), mp, str(artifact_id)) + if not exists(dir_fp): + makedirs(dir_fp) + new_table_fp = join(dir_fp, "biom_table.biom") + with biom_open(new_table_fp, 'w') as f: + new_table.to_hdf5(f, "Generated by Qiita") + + sql = """INSERT INTO qiita.filepath + (filepath, filepath_type_id, checksum, + checksum_algorithm_id, data_directory_id) + VALUES (%s, %s, %s, %s, %s) + RETURNING filepath_id""" + # Magic number 7 -> filepath_type_id = 'biom' + # Magic number 1 -> the checksum algorithm id + TRN.add(sql, [basename(new_table_fp), 7, + compute_checksum(new_table_fp), 1, dd_id]) + fp_id = TRN.execute_fetchlast() + sql = """INSERT INTO qiita.artifact_filepath + (artifact_id, filepath_id) + VALUES (%s, %s)""" + TRN.add(sql, [artifact_id, fp_id]) + TRN.execute() + + return artifact_id + + +def create_rarefaction_job(depth, biom_artifact_id, analysis, srare_cmd_id): + """Create a new rarefaction job + + Parameters + ---------- + depth : int + The rarefaction depth + biom_artifact_id : int + The artifact id of the input rarefaction biom table + analysis : dict + Dictionary with the analysis information + srare_cmd_id : int + The command id of the single rarefaction command + + Returns + ------- + job_id : str + The job id + params : str + The job parameters + """ + # Add the row in the procesisng job table + params = ('{"depth":%d,"subsample_multinomial":false,"biom_table":%s}' + % (depth, biom_artifact_id)) + with TRN: + # magic number 3: status -> success + sql = """INSERT INTO qiita.processing_job + (email, command_id, command_parameters, + processing_job_status_id) + VALUES (%s, %s, %s, %s) + RETURNING processing_job_id""" + TRN.add(sql, [analysis['email'], srare_cmd_id, params, 3]) + job_id = TRN.execute_fetchlast() + # Step 1.2.b: Link the job with the input artifact + sql = """INSERT INTO qiita.artifact_processing_job + (artifact_id, processing_job_id) + VALUES (%s, %s)""" + TRN.add(sql, [biom_artifact_id, job_id]) + TRN.execute() + return job_id, params + + +def transfer_file_to_artifact(a_timestamp, command_id, data_type_id, params, + artifact_type_id, filepath_id): + """Creates a new artifact with the given filepath id + + Parameters + ---------- + a_timestamp : datetime.datetime + The generated timestamp of the artifact + command_id : int + The command id of the artifact + data_type_id : int + The data type id of the artifact + params : str + The parameters of the artifact + artifact_type_id : int + The artifact type + filepath_id : int + The filepath id + + Returns + ------- + int + The artifact id + """ + with TRN: + # Add the row in the artifact table + # Magic number 4: Visibility -> sandbox + sql = """INSERT INTO qiita.artifact + (generated_timestamp, command_id, data_type_id, + command_parameters, visibility_id, artifact_type_id, + submitted_to_vamps) + VALUES (%s, %s, %s, %s, %s, %s, %s) + RETURNING artifact_id""" + TRN.add(sql, [a_timestamp, command_id, data_type_id, params, 4, + artifact_type_id, False]) + artifact_id = TRN.execute_fetchlast() + # Link the artifact with its file + sql = """INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) + VALUES (%s, %s)""" + TRN.add(sql, [artifact_id, filepath_id]) + + return artifact_id + + +def create_rarefied_biom_artifact(analysis, srare_cmd_id, biom_data, params, + parent_biom_artifact_id, rarefaction_job_id, + srare_cmd_out_id): + """Creates the rarefied biom artifact + + Parameters + ---------- + analysis : dict + The analysis information + srare_cmd_id : int + The command id of "Single Rarefaction" + biom_data : dict + The biom information + params : str + The processing parameters + parent_biom_artifact_id : int + The parent biom artifact id + rarefaction_job_id : str + The job id of the rarefaction job + srare_cmd_out_id : int + The id of the single rarefaction output + + Returns + ------- + int + The artifact id + """ + with TRN: + # Transfer the file to an artifact + # Magic number 7: artifact type -> biom + artifact_id = transfer_file_to_artifact( + analysis['timestamp'], srare_cmd_id, biom_data['data_type_id'], + params, 7, biom_data['filepath_id']) + # Link the artifact with its parent + sql = """INSERT INTO qiita.parent_artifact (artifact_id, parent_id) + VALUES (%s, %s)""" + TRN.add(sql, [artifact_id, parent_biom_artifact_id]) + # Link the artifact as the job output + sql = """INSERT INTO qiita.artifact_output_processing_job + (artifact_id, processing_job_id, command_output_id) + VALUES (%s, %s, %s)""" + TRN.add(sql, [artifact_id, rarefaction_job_id, srare_cmd_out_id]) + return artifact_id + + +def create_rarefaction_artifacts_and_job(analysis, biom_data, rarefied_table, + depth, srare_cmd_id, + srare_cmd_out_id): + """Creates the initial artifact and the rarefaction job + + Parameters + ---------- + analysis : dict + The analysis information + biom_data : dict + The biom information + rarefied_table : biom.Table + The rarefied table + depth : int + The table rarefaction depth + srare_cmd_id : int + The command id of "Single Rarefaction" + srare_cmd_out_id : int + The id of the single rarefaction output + + Returns + ------- + int + The new artifact id + """ + # Create the initial unrarefied artifact + initial_biom_artifact_id = create_non_rarefied_biom_artifact( + analysis, biom_data, rarefied_table) + + # Create the rarefaction job + rarefaction_job_id, params = create_rarefaction_job( + depth, initial_biom_artifact_id, analysis, srare_cmd_id) + + # Create the rarefied artifact + rarefied_biom_artifact_id = create_rarefied_biom_artifact( + analysis, srare_cmd_id, biom_data, params, initial_biom_artifact_id, + rarefaction_job_id, srare_cmd_out_id) + + return rarefied_biom_artifact_id + +def transfer_job(analysis, command_id, params, input_artifact_id, job_data, + cmd_out_id, biom_data, output_artifact_type_id): + """Transfers the job from the old structure to the plugin structure -# 1) Correct the rarefaction depth of the existing jobs and artifcats + Parameters + ---------- + analysis : dict + The analysis information + command_id : int + The id of the command executed + params : str + The parameters used in the job + input_artifact_id : int + The id of the input artifact + job_data : dict + The job information + cmd_out_id : int + The id of the command's output + biom_data : dict + The biom information + output_artifact_type_id : int + The type of the output artifact + """ + with TRN: + # Create the job + # Add the row in the processing job table + # Magic number 3: status -> success + sql = """INSERT INTO qiita.processing_job + (email, command_id, command_parameters, + processing_job_status_id) + VALUES (%s, %s, %s, %s) + RETURNING processing_job_id""" + TRN.add(sql, [analysis['email'], command_id, params, 3]) + job_id = TRN.execute_fetchlast() + + # Link the job with the input artifact + sql = """INSERT INTO qiita.artifact_processing_job + (artifact_id, processing_job_id) + VALUES (rarefied_biom_id, proc_job_id)""" + TRN.add(sql, [input_artifact_id, job_id]) + + # Check if the executed job has results and add them + sql = """SELECT EXISTS(SELECT * + FROM qiita.job_results_filepath + WHERE job_id = %s)""" + TRN.add(sql, [job_data['job_id']]) + if TRN.execute_fetchlast(): + # There are results for the current job. + # Transfer the job files to a new artifact + sql = """SELECT filepath_id + FROM qiita.job_results_filepath + WHERE job_id = %s""" + TRN.add(sql, job_data['job_id']) + filepath_id = TRN.execute_fetchlast() + artifact_id = transfer_file_to_artifact( + analysis['timestamp'], command_id, biom_data['data_type_id'], + params, output_artifact_type_id, filepath_id) + + # Link the artifact with its parent + sql = """INSERT INTO qiita.parent_artifact (artifact_id, parent_id) + VALUES (%s, %s)""" + TRN.add(sql, [artifact_id, input_artifact_id]) + # Link the artifact as the job output + sql = """INSERT INTO qiita.artifact_output_processing_job + (artifact_id, processing_job_id, command_output_id) + VALUES (%s, %s, %s)""" + TRN.add(sql, [artifact_id, job_id, cmd_out_id]) + TRN.exeucte() + else: + # There are no results on the current job, so mark it as + # error + if job_data.log_id is None: + # Magic number 2 - we are not using any other severity + # level, so keep using number 2 + sql = """INSERT INTO qiita.logging (time, severity_id, msg) + VALUES (%s, %s, %s) + RETURNING logging_id""" + TRN.add(sql, [analysis['timestamp'], 2, + "Unknown error - patch 47"]) + else: + log_id = job_data['log_id'] + + # Magic number 4 -> status -> error + sql = """UPDATE qiita.processing_job + SET processing_job_status_id = 4, logging_id = %s + WHERE processing_job_id = %s""" + TRN.add(sql, [log_id, job_id]) + + +# The new commands that we are going to add generate new artifact types. +# These new artifact types are going to be added to a different plugin. +# In interest of time and given that the artifact type system is going to +# change in the near future, we feel that the easiest way to transfer +# the current analyses results is by creating 3 different types of +# artifacts: (1) distance matrix -> which will include the distance matrix, +# the principal coordinates and the emperor plots; (2) rarefaction +# curves -> which will include all the files generated by alpha rarefaction +# and (3) taxonomy summary, which will include all the files generated +# by summarize_taxa_through_plots.py + +# Step 1: Create the new type +with TRN: + # Magic number 2 -> The "artifact definition" software type + sql = """INSERT INTO qiita.software + (name, version, description, environment_script, start_script, + software_type_id) + VALUES ('Diversity types', '0.1.0', + 'Diversity artifacts type plugin', + 'source activate qiita', 'start_diversity_types', 2) + RETURNING software_id""" + TRN.add(sql) + divtype_id = TRN.execute_fetchlast() + + # Step 2: Create the validate and HTML generator commands + sql = """INSERT INTO qiita.software_command (software_id, name, description) + VALUES (%s, %s, %s) + RETURNING command_id""" + TRN.add(sql, [divtype_id, 'Validate', + 'Validates a new artifact of the given diversity type']) + validate_cmd_id = TRN.execute_fetchlast() + TRN.add(sql, [divtype_id, 'Generate HTML summary', + 'Generates the HTML summary of a given diversity type']) + html_summary_cmd_id = TRN.execute_fetchlast() + + # Step 3: Add the parameters for the previous commands + sql = """INSERT INTO qiita.command_parameter + (command_id, parameter_name, parameter_type, required) + VALUES (validate_id, 'template', 'prep_template', True)""" + sql_args = [(validate_cmd_id, 'files', 'string', True), + (validate_cmd_id, 'artifact_type', 'string', True), + (html_summary_cmd_id, 'input_data', 'artifact', True)] + TRN.add(sql, sql_args, many=True) + + # Step 4: Add the new artifact types + sql = """INSERT INTO qiita.artifact_type ( + artifact_type, description, can_be_submitted_to_ebi, + can_be_submitted_to_vamps) + VALUES (%s, %s, %s, %s) + RETURNING artifact_type_id""" + TRN.add(sql, ['distance_matrix', 'Distance matrix holding pairwise ' + 'distance between samples', False, False]) + dm_atype_id = TRN.execute_fetchlast() + TRN.add(sql, ['rarefaction_curves', 'Rarefaction curves', False, False]) + rc_atype_id = TRN.execute_fetchlast() + TRN.add(sql, ['taxa_summary', 'Taxa summary plots', False, False]) + ts_atype_id = TRN.execute_fetchlast() + + # Step 5: Associate each artifact with the filetypes that it accepts + # At this time we are going to add them as directories, just as it is done + # right now. We can make it fancier with the new type system. + # Magic number 8: the filepath_type_id for the directory + sql = """INSERT INTO qiita.artifact_type_filepath_type + (artifact_type_id, filepath_type_id, required) + VALUES (%s, %s, %s)""" + sql_args = [[dm_atype_id, 8, True], + [rc_atype_id, 8, True], + [ts_atype_id, 8, True]] + TRN.add(sql, sql_args, many=True) + + # Step 6: Associate the plugin with the types that it defines + sql = """INSERT INTO qiita.software_artifact_type + (software_id, artifact_type_id) + VALUES (%s, %s, %s)""" + sql_args = [[divtype_id, dm_atype_id], + [divtype_id, rc_atype_id], + [divtype_id, ts_atype_id]] + TRN.add(sql, sql_args, many=True) + + # Step 7: Create the new entries for the data directory + sql = """INSERT INTO qiita.data_directory + (data_type, mountpoint, subdirectory, active) + VALUES (%s, %s, %s, %s)""" + sql_args = [['distance_matrix', 'distance_matrix', True, True], + ['rarefaction_curves', 'rarefaction_curves', True, True], + ['taxa_summary', 'taxa_summary', True, True]] + TRN.add(sql, sql_args, many=True) + + # Create the new commands that execute the current analyses. In qiita, + # the only commands that where available are Summarize Taxa, Beta + # Diversity and Alpha Rarefaction. The system was executing rarefaction + # by default, but it should be a different step in the analysis process + # so we are going to create a command for it too. These commands are going + # to be part of the QIIME plugin, so we are going to first retrieve the + # id of the QIIME 1.9.1 plugin, which for sure exists cause it was added + # in patch 33 and there is no way of removing plugins + + # Step 1: Get the QIIME plugin id + sql = """SELECT software_id + FROM qiita.software + WHERE name = 'QIIME' AND version = '1.9.1'""" + TRN.add(sql) + qiime_id = TRN.execute_fetchlast() + + # Step 2: Insert the new commands in the software_command table + sql = """INSERT INTO qiita.software_command (software_id, name, description) + VALUES (%s, %s, %s) + RETURNING command_id""" + TRN.add(sql, [qiime_id, 'Summarize Taxa', 'Plots taxonomy summaries at ' + 'different taxonomy levels']) + sum_taxa_cmd_id = TRN.execute_fetchlast() + TRN.add(sql, [qiime_id, 'Beta Diversity', + 'Computes and plots beta diversity results']) + bdiv_cmd_id = TRN.execute_fetchlast() + TRN.add(sql, [qiime_id, 'Alpha Rarefaction', + 'Computes and plots alpha rarefaction results']) + arare_cmd_id = TRN.execute_fetchlast() + TRN.add(sql, [qiime_id, 'Single Rarefaction', + 'Rarefies the input table by random sampling without ' + 'replacement']) + srare_cmd_id = TRN.execute_fetchlast() + + # Step 3: Insert the parameters for each command + sql = """INSERT INTO qiita.command_parameter + (command_id, parameter_name, parameter_type, required, + default_value) + VALUES (%s, %s, %s, %s, %s) + RETURNING command_parameter_id""" + sql_args = [ + # Summarize Taxa + (sum_taxa_cmd_id, 'metadata_category', 'string', False, ''), + (sum_taxa_cmd_id, 'sort', 'bool', False, 'False'), + # Beta Diversity + (bdiv_cmd_id, 'tree', 'string', False, ''), + (bdiv_cmd_id, 'metrics', + 'mchoice:["abund_jaccard","binary_chisq","binary_chord",' + '"binary_euclidean","binary_hamming","binary_jaccard",' + '"binary_lennon","binary_ochiai","binary_otu_gain","binary_pearson",' + '"binary_sorensen_dice","bray_curtis","bray_curtis_faith",' + '"bray_curtis_magurran","canberra","chisq","chord","euclidean",' + '"gower","hellinger","kulczynski","manhattan","morisita_horn",' + '"pearson","soergel","spearman_approx","specprof","unifrac",' + '"unifrac_g","unifrac_g_full_tree","unweighted_unifrac",' + '"unweighted_unifrac_full_tree","weighted_normalized_unifrac",' + '"weighted_unifrac"]', False, '["binary_jaccard","bray_curtis"]'), + # Alpha rarefaction + (arare_cmd_id, 'tree', 'string', False, ''), + (arare_cmd_id, 'num_steps', 'integer', False, 10), + (arare_cmd_id, 'min_rare_depth', 'integer', False, 10), + (arare_cmd_id, 'max_rare_depth', 'integer', False, 'Default'), + # Single rarefaction + (srare_cmd_id, 'depth', 'integer', True, None), + (srare_cmd_id, 'subsample_multinomial', 'bool', False, 'False') + ] + TRN.add(sql, sql_args, many=True) + + TRN.add(sql, [sum_taxa_cmd_id, 'biom_table', 'artifact', True, None]) + sum_taxa_cmd_param_id = TRN.execute_fetchlast() + TRN.add(sql, [bdiv_cmd_id, 'biom_table', 'artifact', True, None]) + bdiv_cmd_param_id = TRN.execute_fetchlast() + TRN.add(sql, [arare_cmd_id, 'biom_table', 'artifact', True, None]) + arare_cmd_param_id = TRN.execute_fetchlast() + TRN.add(sql, [srare_cmd_id, 'biom_table', 'artifact', True, None]) + srare_cmd_param_id = TRN.execute_fetchlast() + + # Step 4: Connect the artifact parameters with the artifact types that + # they accept + sql = """SELECT artifact_type_id + FROM qiita.artifact_type + WHERE artifact_type = 'BIOM'""" + TRN.add(sql) + biom_atype_id = TRN.execute_fetchlast() + + sql = """INSERT INTO qiita.parameter_artifact_type + (command_parameter_id, artifact_type_id) + VALUES (%s, %s)""" + sql_args = [[sum_taxa_cmd_param_id, biom_atype_id], + [bdiv_cmd_param_id, biom_atype_id], + [arare_cmd_param_id, biom_atype_id], + [srare_cmd_param_id, biom_atype_id]] + TRN.add(sql, sql_args, many=True) + + # Step 5: Add the outputs of the command. + sql = """INSERT INTO qiita.command_output + (name, command_id, artifact_type_id) + VALUES (%s, %s, %s) + RETURNING command_output_id""" + TRN.add(sql, ['taxa_summary', sum_taxa_cmd_id, ts_atype_id]) + sum_taxa_cmd_out_id = TRN.execute_fetchlast() + TRN.add(sql, ['distance_matrix', bdiv_cmd_id, dm_atype_id]) + bdiv_cmd_out_id = TRN.execute_fetchlast() + TRN.add(sql, ['rarefaction_curves', arare_cmd_id, rc_atype_id]) + arare_cmd_out_id = TRN.execute_fetchlast() + TRN.add(sql, ['rarefied_table', srare_cmd_id, biom_atype_id]) + srare_cmd_out_id = TRN.execute_fetchlast() + +# At this point we are ready to start transferring the data from the old +# structures to the new structures. Overview of the procedure: +# Step 1: Add initial set of artifacts up to rarefied table +# Step 2: Transfer the "analisys jobs" to processing jobs and create +# the analysis artifacts +db_dir = get_db_files_base_dir() with TRN: - sql = """SELECT command_id - FROM qiita.software_command - WHERE name = 'Single Rarefaction'""" + sql = "SELECT * FROM qiita.analysis" TRN.add(sql) - cmd_id = TRN.execute_fetchlast() - - sql = "SELECT artifact_id FROM qiita.artifact WHERE command_id = %s" - TRN.add(sql, [cmd_id]) - - sql_update_artifact = """UPDATE qiita.artifact - SET command_parameters = %s - WHERE artifact_id = %s""" - sql_update_job = """UPDATE qiita.processing_job - SET command_parameters = %s - WHERE processing_job_id = ( - SELECT processing_job_id - FROM qiita.artifact_output_processing_job - WHERE artifact_id = %s)""" - - for a_id in TRN.execute_fetchflatten(): - a = Artifact(a_id) - params = a.processing_parameters.values - # load the biom table to check the rarefaction depth - # Magic numbers: since we added the artifacts on the patch 47.sql, we - # know that these artifacts have only 1 file, hence the first 0. - # Each element of the filepath list is a 3-tuple with filepath_id, - # filepath and filepath type, and we are interested in the filepath - # (index 1) - t = load_table(a.filepaths[0][1]) - params['depth'] = t[:, 0].sum() - TRN.add(sql_update_artifact, [dumps(params), a_id]) - TRN.add(sql_update_job, [dumps(params), a_id]) - -# 2) Purge filepaths + analysis_info = TRN.execute_fetchindex() + + # Loop through all the analysis + for analysis in analysis_info: + # Step 1: Add the inital set of artifacts. An analysis starts with + # a set of BIOM artifacts. + sql = """SELECT * + FROM qiita.analysis_filepath + JOIN qiita.filepath USING (filepath_id) + JOIN qiita.filepath_type USING (filepath_type_id) + WHERE analysis_id = %s AND filepath_type = 'biom'""" + TRN.add(sql, [analysis['analysis_id']]) + analysis_bioms = TRN.execute_fetchindex() + + # Loop through all the biom tables associated with the current analysis + # so we can create the initial set of artifacts + for biom_data in analysis_bioms: + # Get the path of the BIOM table + sql = """SELECT filepath, mountpoint + FROM qiita.filepath + JOIN qiita.data_directory USING (data_directory_id) + WHERE filepath_id = %s""" + TRN.add(sql, biom_data['filepath_id']) + # Magic number 0: There is only a single row in the query result + fp_info = TRN.execute_fetchindex()[0] + filepath = join(db_dir, fp_info['mountpoint'], fp_info['filepath']) + + # We need to check if the BIOM table has been rarefied or not + table = load_table(filepath) + depths = set(table.sum(axis='sample')) + if len(depths) == 1: + # The BIOM table was rarefied + initial_biom_id = create_rarefaction_artifacts_and_job( + analysis, biom_data, table, depths.pop(), srare_cmd_id, + srare_cmd_out_id) + else: + # The BIOM table was not rarefied, use current table as initial + initial_biom_id = transfer_file_to_artifact() + + # Loop through all the jobs that used this biom table as input + sql = """SELECT * + FROM qiita.job + WHERE reverse(split_part(reverse( + options::json->>'--otu_table_fp'), '/', 1)) = %s""" + TRN.add(sql, [filepath]) + analysis_jobs = TRN.execute_fetchindex + for job_data in analysis_jobs: + # Identify which command the current job exeucted + if job_data['command_id'] == 1: + # Taxa summaries + cmd_id = sum_taxa_cmd_id + params = ('{"biom_table":%d,"metadata_category":"",' + '"sort":false}' % initial_biom_id) + output_artifact_type_id = ts_atype_id + cmd_out_id = sum_taxa_cmd_out_id + elif job_data['command_id'] == 2: + # Beta diversity + cmd_id = bdiv_cmd_id + tree_fp = loads(job_data['options'])['--tree_fp'] + params = ('{"biom_table":%d,"tree":"%s","metrics":' + '["unweighted_unifrac","weighted_unifrac"]}' + % (initial_biom_id, tree_fp)) + output_artifact_type_id = dm_atype_id + cmd_out_id = bdiv_cmd_out_id + else: + # Alpha rarefaction + cmd_id = arare_cmd_id + tree_fp = loads(job_data['options'])['--tree_fp'] + params = ('{"biom_table":%d,"tree":"%s","num_steps":"10",' + '"min_rare_depth":"10",' + '"max_rare_depth":"Default"}' + % (initial_biom_id, tree_fp)) + output_artifact_type_id = rc_atype_id + cmd_out_id = arare_cmd_out_id + + transfer_job() + +# Delete old structures that are not used anymore +with TRN: + TRN.add("DROP TABLE qiita.collection_job") + TRN.add("DROP TABLE qiita.collection_analysis") + TRN.add("DROP TABLE qiita.collection_users") + TRN.add("DROP TABLE qiita.collection") + TRN.add("DROP TABLE qiita.collection_status") + TRN.add("DROP TABLE qiita.analysis_workflow") + TRN.add("DROP TABLE qiita.analysis_chain") + TRN.add("DROP TABLE qiita.analysis_job") + TRN.add("DROP TABLE qiita.job_results_filepath") + TRN.add("DROP TABLE qiita.job") + TRN.add("DROP TABLE qiita.job_status") + TRN.add("DROP TABLE qiita.command_data_type") + TRN.add("DROP TABLE qiita.command") + TRN.execute() + +# Purge filepaths purge_filepaths() From 331012d846069f9ba68275a5cd8fa22d5c69563e Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 13:14:55 -0800 Subject: [PATCH 05/32] Fixing patch --- qiita_db/support_files/patches/python_patches/47.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index 90a008615..55bbfef54 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -59,7 +59,7 @@ def create_non_rarefied_biom_artifact(analysis, biom_data, rarefied_table): WHERE analysis_id = %s AND data_type_id = %s GROUP BY artifact_id""" TRN.add(sql, [analysis['analysis_id'], biom_data['data_type_id']]) - samples_by_artifact = TRN.execute_fetchlast() + samples_by_artifact = TRN.execute_fetchindex() # Create an empty BIOM table to be the new master table new_table = Table([], [], []) @@ -427,7 +427,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, # Step 3: Add the parameters for the previous commands sql = """INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required) - VALUES (validate_id, 'template', 'prep_template', True)""" + VALUES (%s, %s, %s, %s)""" sql_args = [(validate_cmd_id, 'files', 'string', True), (validate_cmd_id, 'artifact_type', 'string', True), (html_summary_cmd_id, 'input_data', 'artifact', True)] @@ -462,7 +462,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, # Step 6: Associate the plugin with the types that it defines sql = """INSERT INTO qiita.software_artifact_type (software_id, artifact_type_id) - VALUES (%s, %s, %s)""" + VALUES (%s, %s)""" sql_args = [[divtype_id, dm_atype_id], [divtype_id, rc_atype_id], [divtype_id, ts_atype_id]] @@ -616,7 +616,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, FROM qiita.filepath JOIN qiita.data_directory USING (data_directory_id) WHERE filepath_id = %s""" - TRN.add(sql, biom_data['filepath_id']) + TRN.add(sql, [biom_data['filepath_id']]) # Magic number 0: There is only a single row in the query result fp_info = TRN.execute_fetchindex()[0] filepath = join(db_dir, fp_info['mountpoint'], fp_info['filepath']) From eabe25d80f1ac9a2b222831f82a52a23985d5061 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 13:39:40 -0800 Subject: [PATCH 06/32] Fixing patch and a few other bits to make the patch run successfully --- qiita_db/environment_manager.py | 2 +- .../patches/python_patches/47.py | 56 ++++--------------- qiita_db/test/test_util.py | 13 +++-- qiita_db/util.py | 2 +- 4 files changed, 22 insertions(+), 51 deletions(-) diff --git a/qiita_db/environment_manager.py b/qiita_db/environment_manager.py index 7a3aa3499..b6981f045 100644 --- a/qiita_db/environment_manager.py +++ b/qiita_db/environment_manager.py @@ -405,4 +405,4 @@ def patch(patches_dir=PATCHES_DIR, verbose=False, test=False): if verbose: print('\t\tApplying python patch %s...' % py_patch_filename) - execfile(py_patch_fp) + execfile(py_patch_fp, {}) diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index 55bbfef54..2a20ced93 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -259,47 +259,6 @@ def create_rarefied_biom_artifact(analysis, srare_cmd_id, biom_data, params, return artifact_id -def create_rarefaction_artifacts_and_job(analysis, biom_data, rarefied_table, - depth, srare_cmd_id, - srare_cmd_out_id): - """Creates the initial artifact and the rarefaction job - - Parameters - ---------- - analysis : dict - The analysis information - biom_data : dict - The biom information - rarefied_table : biom.Table - The rarefied table - depth : int - The table rarefaction depth - srare_cmd_id : int - The command id of "Single Rarefaction" - srare_cmd_out_id : int - The id of the single rarefaction output - - Returns - ------- - int - The new artifact id - """ - # Create the initial unrarefied artifact - initial_biom_artifact_id = create_non_rarefied_biom_artifact( - analysis, biom_data, rarefied_table) - - # Create the rarefaction job - rarefaction_job_id, params = create_rarefaction_job( - depth, initial_biom_artifact_id, analysis, srare_cmd_id) - - # Create the rarefied artifact - rarefied_biom_artifact_id = create_rarefied_biom_artifact( - analysis, srare_cmd_id, biom_data, params, initial_biom_artifact_id, - rarefaction_job_id, srare_cmd_out_id) - - return rarefied_biom_artifact_id - - def transfer_job(analysis, command_id, params, input_artifact_id, job_data, cmd_out_id, biom_data, output_artifact_type_id): """Transfers the job from the old structure to the plugin structure @@ -626,8 +585,17 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, depths = set(table.sum(axis='sample')) if len(depths) == 1: # The BIOM table was rarefied - initial_biom_id = create_rarefaction_artifacts_and_job( - analysis, biom_data, table, depths.pop(), srare_cmd_id, + # Create the initial unrarefied artifact + initial_biom_artifact_id = create_non_rarefied_biom_artifact( + analysis, biom_data, table) + # Create the rarefaction job + rarefaction_job_id, params = create_rarefaction_job( + depths.pop(), initial_biom_artifact_id, analysis, + srare_cmd_id) + # Create the rarefied artifact + rarefied_biom_artifact_id = create_rarefied_biom_artifact( + analysis, srare_cmd_id, biom_data, params, + initial_biom_artifact_id, rarefaction_job_id, srare_cmd_out_id) else: # The BIOM table was not rarefied, use current table as initial @@ -639,7 +607,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, WHERE reverse(split_part(reverse( options::json->>'--otu_table_fp'), '/', 1)) = %s""" TRN.add(sql, [filepath]) - analysis_jobs = TRN.execute_fetchindex + analysis_jobs = TRN.execute_fetchindex() for job_data in analysis_jobs: # Identify which command the current job exeucted if job_data['command_id'] == 1: diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py index 419e02d6a..6383bc6ef 100644 --- a/qiita_db/test/test_util.py +++ b/qiita_db/test/test_util.py @@ -8,7 +8,7 @@ from unittest import TestCase, main from tempfile import mkstemp -from os import close, remove +from os import close, remove, makedirs from os.path import join, exists, basename from shutil import rmtree from datetime import datetime @@ -315,17 +315,20 @@ def _common_purge_filpeaths_test(self): removed_fps = [ join(raw_data_mp, '2_sequences_barcodes.fastq.gz'), - join(raw_data_mp, '2_sequences.fastq.gz')] + join(raw_data_mp, '2_sequences.fastq.gz'), + join(raw_data_mp, 'directory_test')] - for fp in removed_fps: + for fp in removed_fps[:-1]: with open(fp, 'w') as f: f.write('\n') + makedirs(removed_fps[-1]) sql = """INSERT INTO qiita.filepath (filepath, filepath_type_id, checksum, checksum_algorithm_id, data_directory_id) VALUES ('2_sequences_barcodes.fastq.gz', 3, '852952723', 1, 5), - ('2_sequences.fastq.gz', 1, '852952723', 1, 5) + ('2_sequences.fastq.gz', 1, '852952723', 1, 5), + ('directory_test', 8, '852952723', 1, 5) RETURNING filepath_id""" fp_ids = self.conn_handler.execute_fetchall(sql) @@ -337,7 +340,7 @@ def _common_purge_filpeaths_test(self): for fp in removed_fps: self.assertTrue(exists(fp)) - exp_count = qdb.util.get_count("qiita.filepath") - 2 + exp_count = qdb.util.get_count("qiita.filepath") - 3 qdb.util.purge_filepaths() diff --git a/qiita_db/util.py b/qiita_db/util.py index ade911f0a..dabb5047c 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -756,7 +756,7 @@ def purge_filepaths(): # Remove the data fp = join(get_mountpoint_path_by_id(dd_id), fp) if exists(fp): - if fp_type is 'directory': + if fp_type == 'directory': func = rmtree else: func = remove From 0c40bc0ccb244afd7d4946053a675e37425407e5 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 13:40:32 -0800 Subject: [PATCH 07/32] These files are no longer needed --- qiita_db/support_files/test_data/job/1_job_result.txt | 1 - qiita_db/support_files/test_data/job/2_test_folder/testfile.txt | 1 - 2 files changed, 2 deletions(-) delete mode 100644 qiita_db/support_files/test_data/job/1_job_result.txt delete mode 100644 qiita_db/support_files/test_data/job/2_test_folder/testfile.txt diff --git a/qiita_db/support_files/test_data/job/1_job_result.txt b/qiita_db/support_files/test_data/job/1_job_result.txt deleted file mode 100644 index 82ff6f913..000000000 --- a/qiita_db/support_files/test_data/job/1_job_result.txt +++ /dev/null @@ -1 +0,0 @@ -job1result.txt \ No newline at end of file diff --git a/qiita_db/support_files/test_data/job/2_test_folder/testfile.txt b/qiita_db/support_files/test_data/job/2_test_folder/testfile.txt deleted file mode 100644 index 418d6c385..000000000 --- a/qiita_db/support_files/test_data/job/2_test_folder/testfile.txt +++ /dev/null @@ -1 +0,0 @@ -DATA \ No newline at end of file From 602acc3e2c84832c3e68d1ee6105fd53d77b329e Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 13:52:07 -0800 Subject: [PATCH 08/32] Removing unused code --- qiita_db/analysis.py | 253 --------------------------------- qiita_db/test/test_analysis.py | 154 -------------------- 2 files changed, 407 deletions(-) diff --git a/qiita_db/analysis.py b/qiita_db/analysis.py index d4d957429..45b575aeb 100644 --- a/qiita_db/analysis.py +++ b/qiita_db/analysis.py @@ -1067,256 +1067,3 @@ def _add_file(self, filename, filetype, data_type=None): VALUES (%s, %s{1})""".format(col, dtid) qdb.sql_connection.TRN.add(sql, [self._id, fpid]) qdb.sql_connection.TRN.execute() - - -class Collection(qdb.base.QiitaStatusObject): - """ - Analysis overview object to track a multi-analysis collection. - - Attributes - ---------- - name: str - Name of the Collection - description: str - Description of what the collection is investigating - owner: User object - Owner of the Collection - analyses: list of Analysis Objects - all analyses that are part of the collection - highlights : list of Job objects - Important job results related to the collection - - Methods - ------- - add_analysis - remove_analysis - highlight_job - remove_highlight - share - unshare - """ - _table = "collection" - _analysis_table = "collection_analysis" - _highlight_table = "collection_job" - _share_table = "collection_users" - - def _status_setter_checks(self): - r"""Perform a check to make sure not setting status away from public - """ - if self.check_status(("public", )): - raise qdb.exceptions.QiitaDBStatusError( - "Illegal operation on public collection!") - - @classmethod - def create(cls, owner, name, description=None): - """Creates a new collection on the database - - Parameters - ---------- - owner : User object - Owner of the collection - name : str - Name of the collection - description : str, optional - Brief description of the collecton's overarching goal - """ - with qdb.sql_connection.TRN: - sql = """INSERT INTO qiita.{0} (email, name, description) - VALUES (%s, %s, %s) - RETURNING collection_id""".format(cls._table) - qdb.sql_connection.TRN.add(sql, [owner.id, name, description]) - c_id = qdb.sql_connection.TRN.execute_fetchlast() - - return cls(c_id) - - @classmethod - def delete(cls, id_): - """Deletes a collection from the database - - Parameters - ---------- - id_ : int - ID of the collection to delete - - Raises - ------ - QiitaDBStatusError - Trying to delete a public collection - """ - with qdb.sql_connection.TRN: - if cls(id_).status == "public": - raise qdb.exceptions.QiitaDBStatusError( - "Can't delete public collection!") - - sql = "DELETE FROM qiita.{0} WHERE collection_id = %s" - for table in (cls._analysis_table, cls._highlight_table, - cls._share_table, cls._table): - qdb.sql_connection.TRN.add(sql.format(table), [id_]) - - qdb.sql_connection.TRN.execute() - - # --- Properties --- - @property - def name(self): - with qdb.sql_connection.TRN: - sql = "SELECT name FROM qiita.{0} WHERE collection_id = %s".format( - self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return qdb.sql_connection.TRN.execute_fetchlast() - - @name.setter - def name(self, value): - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """UPDATE qiita.{0} SET name = %s - WHERE collection_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [value, self._id]) - qdb.sql_connection.TRN.execute() - - @property - def description(self): - with qdb.sql_connection.TRN: - sql = """SELECT description FROM qiita.{0} - WHERE collection_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return qdb.sql_connection.TRN.execute_fetchlast() - - @description.setter - def description(self, value): - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """UPDATE qiita.{0} SET description = %s - WHERE collection_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [value, self._id]) - qdb.sql_connection.TRN.execute() - - @property - def owner(self): - with qdb.sql_connection.TRN: - sql = """SELECT email FROM qiita.{0} - WHERE collection_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return qdb.user.User(qdb.sql_connection.TRN.execute_fetchlast()) - - @property - def analyses(self): - with qdb.sql_connection.TRN: - sql = """SELECT analysis_id FROM qiita.{0} - WHERE collection_id = %s""".format(self._analysis_table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return [Analysis(aid) - for aid in qdb.sql_connection.TRN.execute_fetchflatten()] - - @property - def highlights(self): - with qdb.sql_connection.TRN: - sql = """SELECT job_id FROM qiita.{0} - WHERE collection_id = %s""".format(self._highlight_table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return [qdb.job.Job(jid) - for jid in qdb.sql_connection.TRN.execute_fetchflatten()] - - @property - def shared_with(self): - with qdb.sql_connection.TRN: - sql = """SELECT email FROM qiita.{0} - WHERE collection_id = %s""".format(self._share_table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return [qdb.user.User(uid) - for uid in qdb.sql_connection.TRN.execute_fetchflatten()] - - # --- Functions --- - def add_analysis(self, analysis): - """Adds an analysis to the collection object - - Parameters - ---------- - analysis : Analysis object - """ - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """INSERT INTO qiita.{0} (analysis_id, collection_id) - VALUES (%s, %s)""".format(self._analysis_table) - qdb.sql_connection.TRN.add(sql, [analysis.id, self._id]) - qdb.sql_connection.TRN.execute() - - def remove_analysis(self, analysis): - """Remove an analysis from the collection object - - Parameters - ---------- - analysis : Analysis object - """ - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """DELETE FROM qiita.{0} - WHERE analysis_id = %s - AND collection_id = %s""".format(self._analysis_table) - qdb.sql_connection.TRN.add(sql, [analysis.id, self._id]) - qdb.sql_connection.TRN.execute() - - def highlight_job(self, job): - """Marks a job as important to the collection - - Parameters - ---------- - job : Job object - """ - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """INSERT INTO qiita.{0} (job_id, collection_id) - VALUES (%s, %s)""".format(self._highlight_table) - qdb.sql_connection.TRN.add(sql, [job.id, self._id]) - qdb.sql_connection.TRN.execute() - - def remove_highlight(self, job): - """Removes job importance from the collection - - Parameters - ---------- - job : Job object - """ - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """DELETE FROM qiita.{0} - WHERE job_id = %s - AND collection_id = %s""".format(self._highlight_table) - qdb.sql_connection.TRN.add(sql, [job.id, self._id]) - qdb.sql_connection.TRN.execute() - - def share(self, user): - """Shares the collection with another user - - Parameters - ---------- - user : User object - """ - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """INSERT INTO qiita.{0} (email, collection_id) - VALUES (%s, %s)""".format(self._share_table) - qdb.sql_connection.TRN.add(sql, [user.id, self._id]) - qdb.sql_connection.TRN.execute() - - def unshare(self, user): - """Unshares the collection with another user - - Parameters - ---------- - user : User object - """ - with qdb.sql_connection.TRN: - self._status_setter_checks() - - sql = """DELETE FROM qiita.{0} - WHERE email = %s - AND collection_id = %s""".format(self._share_table) - qdb.sql_connection.TRN.add(sql, [user.id, self._id]) - qdb.sql_connection.TRN.execute() diff --git a/qiita_db/test/test_analysis.py b/qiita_db/test/test_analysis.py index eb1380af7..d920eba02 100644 --- a/qiita_db/test/test_analysis.py +++ b/qiita_db/test/test_analysis.py @@ -764,159 +764,5 @@ def test_add_file(self): analysis._add_file('testfile.txt', 'plain_text', '18S') self.assertEqual(analysis.all_associated_filepath_ids, {new_id}) - -@qiita_test_checker() -class TestCollection(TestCase): - def setUp(self): - self.collection = qdb.analysis.Collection(1) - - def test_create(self): - user = qdb.user.User('test@foo.bar') - obs = qdb.analysis.Collection.create( - user, 'TestCollection2', 'Some desc') - - self.assertEqual(obs.name, 'TestCollection2') - self.assertEqual(obs.description, 'Some desc') - self.assertEqual(obs.owner, user) - self.assertEqual(obs.analyses, []) - self.assertEqual(obs.highlights, []) - self.assertEqual(obs.shared_with, []) - - def test_create_no_desc(self): - user = qdb.user.User('test@foo.bar') - obs = qdb.analysis.Collection.create(user, 'Test Collection2') - - self.assertEqual(obs.name, 'Test Collection2') - self.assertEqual(obs.description, None) - self.assertEqual(obs.owner, user) - self.assertEqual(obs.analyses, []) - self.assertEqual(obs.highlights, []) - self.assertEqual(obs.shared_with, []) - - def test_delete(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2') - qdb.analysis.Collection.delete(obs.id) - with self.assertRaises(qdb.exceptions.QiitaDBUnknownIDError): - qdb.analysis.Collection(obs.id) - - def test_delete_public(self): - collection = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2') - collection.status = 'public' - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - qdb.analysis.Collection.delete(collection.id) - - # This should not raise an error - qdb.analysis.Collection(collection.id) - - def test_retrieve_name(self): - obs = self.collection.name - exp = "TEST_COLLECTION" - self.assertEqual(obs, exp) - - def test_set_name(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2') - self.assertEqual(obs.name, "Test Collection2") - obs.name = "NeW NaMe 123" - self.assertEqual(obs.name, "NeW NaMe 123") - - def test_set_name_public(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2') - obs.status = "public" - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - obs.name = "FAILBOAT" - - def test_retrieve_desc(self): - obs = self.collection.description - exp = "collection for testing purposes" - self.assertEqual(obs, exp) - - def test_set_desc(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - self.assertEqual(obs.description, 'SomeDesc') - obs.description = "NeW DeSc 123" - self.assertEqual(obs.description, "NeW DeSc 123") - - def test_set_desc_public(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - obs.status = "public" - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - obs.description = "FAILBOAT" - - def test_retrieve_owner(self): - obs = self.collection.owner - exp = qdb.user.User("test@foo.bar") - self.assertEqual(obs, exp) - - def test_retrieve_analyses(self): - obs = self.collection.analyses - exp = [qdb.analysis.Analysis(1)] - self.assertEqual(obs, exp) - - def test_retrieve_highlights(self): - obs = self.collection.highlights - exp = [qdb.job.Job(1)] - self.assertEqual(obs, exp) - - def test_retrieve_shared_with(self): - obs = self.collection.shared_with - exp = [qdb.user.User("shared@foo.bar")] - self.assertEqual(obs, exp) - - def test_add_analysis(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - obs.add_analysis(qdb.analysis.Analysis(2)) - obs = obs.analyses - exp = [qdb.analysis.Analysis(2)] - self.assertEqual(obs, exp) - - def test_remove_analysis(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - obs.add_analysis(qdb.analysis.Analysis(2)) - self.assertEqual(obs.analyses, [qdb.analysis.Analysis(2)]) - obs.remove_analysis(qdb.analysis.Analysis(2)) - self.assertEqual(obs.analyses, []) - - def test_highlight_job(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - obs.add_analysis(qdb.analysis.Analysis(1)) - obs.highlight_job(qdb.job.Job(2)) - exp = [qdb.job.Job(2)] - self.assertEqual(obs.highlights, exp) - - def test_remove_highlight(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - obs.add_analysis(qdb.analysis.Analysis(1)) - obs.highlight_job(qdb.job.Job(1)) - self.assertEqual(obs.highlights, [qdb.job.Job(1)]) - obs.remove_highlight(qdb.job.Job(1)) - self.assertEqual(obs.highlights, []) - - def test_share(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - user = qdb.user.User("admin@foo.bar") - obs.share(user) - self.assertEqual(obs.shared_with, [user]) - - def test_unshare(self): - obs = qdb.analysis.Collection.create( - qdb.user.User('test@foo.bar'), 'Test Collection2', 'SomeDesc') - user = qdb.user.User("admin@foo.bar") - obs.share(user) - self.assertEqual(obs.shared_with, [user]) - obs.unshare(user) - self.assertEqual(obs.shared_with, []) - - if __name__ == "__main__": main() From ad4deea419af2aa3e852fd82ce1b797939f7497f Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 13:58:20 -0800 Subject: [PATCH 09/32] Droping analysis status table --- qiita_db/support_files/patches/47.sql | 1 + .../patches/python_patches/47.py | 1 + qiita_db/support_files/qiita-db.dbs | 17 +- qiita_db/support_files/qiita-db.html | 314 ++++++++---------- 4 files changed, 138 insertions(+), 195 deletions(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index bb9f2d8f0..d53c3ec1c 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -27,6 +27,7 @@ CREATE INDEX idx_analysis_artifact_artifact ON qiita.analysis_artifact (artifact ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_analysis FOREIGN KEY ( analysis_id ) REFERENCES qiita.analysis( analysis_id ); ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_artifact FOREIGN KEY ( artifact_id ) REFERENCES qiita.artifact( artifact_id ); + -- We can handle some of the special cases here, so we simplify the work in the -- python patch diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index 2a20ced93..04e08338b 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -654,6 +654,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, TRN.add("DROP TABLE qiita.job_status") TRN.add("DROP TABLE qiita.command_data_type") TRN.add("DROP TABLE qiita.command") + TRN.add("DROP TABLE analysis_status") TRN.execute() # Purge filepaths diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index 7231549f7..3138939c0 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -40,9 +40,6 @@ - - - @@ -140,16 +137,6 @@
- - - - - - - - - -
Links analyses to the users they are shared with @@ -1643,8 +1630,6 @@ Controlled Vocabulary]]> - - @@ -1653,11 +1638,11 @@ Controlled Vocabulary]]> + analysis tables - diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index 6c8e29959..7b189e2c0 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -702,7 +702,7 @@ parent_processing_job references processing_job ( child_id -> processing_job_id ) child_id + parent_processing_job references processing_job ( child_id -> processing_job_id )' style='fill:#a1a0a0;'>child_id Foreign Key fk_processing_job_workflow processing_job_workflow references qiita_user ( email ) @@ -747,7 +747,12 @@ software_command references software ( software_id ) software_id + software_command references software ( software_id )' style='fill:#a1a0a0;'>software_id + Foreign Key fk_processing_job_qiita_user + processing_job references qiita_user ( email ) + +email Foreign Key fk_processing_job processing_job references software_command ( command_id ) @@ -792,22 +797,7 @@ study_publication references study ( study_id ) study_id - Foreign Key fk_analysis_user - analysis references qiita_user ( email ) - -email - Foreign Key fk_analysis_analysis_status - analysis references analysis_status ( analysis_status_id ) - -analysis_status_id - Foreign Key fk_analysis - analysis references portal_type ( portal_type_id ) - -portal_type_id + study_publication references study ( study_id )' style='fill:#a1a0a0;'>study_id Foreign Key fk_analysis_users_analysis analysis_users references analysis ( analysis_id ) @@ -867,7 +857,17 @@ analysis_artifact references artifact ( artifact_id ) artifact_id + analysis_artifact references artifact ( artifact_id )' style='fill:#a1a0a0;'>artifact_id + Foreign Key fk_analysis_user + analysis references qiita_user ( email ) + +email + Foreign Key fk_analysis + analysis references portal_type ( portal_type_id ) + +portal_type_id controlled_vocab_valuesTable qiita.controlled_vocab_values @@ -1442,7 +1442,8 @@ Represents data in the system Primary Key ( artifact_id ) artifact_idartifact_id bigserial not null -Referred by analysis_sample ( artifact_id ) +<a xlink:href='#artifact.artifact_id'><use id='ref' x='1338' y='956' xlink:href='#ref'/><title>Referred by analysis_artifact ( artifact_id ) +Referred by analysis_sample ( artifact_id ) Referred by artifact_filepath ( artifact_id ) Referred by artifact_output_processing_job ( artifact_id ) Referred by artifact_processing_job ( artifact_id ) @@ -1450,8 +1451,7 @@ Referred by parent_artifact ( artifact_id ) Referred by parent_artifact ( parent_id -> artifact_id ) Referred by prep_template ( artifact_id ) -Referred by study_artifact ( artifact_id ) -Referred by analysis_artifact ( artifact_id ) +Referred by study_artifact ( artifact_id ) namename varchar(35) not null generated_timestampgenerated_timestamp timestamp not null Index ( command_id ) @@ -1872,47 +1872,6 @@ publicationpublication varchar not null is_doiis_doi bool - - - -analysis_statusTable qiita.analysis_status - Primary Key ( analysis_status_id ) -analysis_status_idanalysis_status_id bigserial not null -Referred by analysis ( analysis_status_id ) - Unique Index ( status ) -statusstatus varchar not null - - - - -analysisTable qiita.analysis -Holds analysis information - Primary Key ( analysis_id ) -analysis_idanalysis_id bigserial not null -Unique identifier for analysis -Referred by analysis_filepath ( analysis_id ) -Referred by analysis_portal ( analysis_id ) -Referred by analysis_sample ( analysis_id ) -Referred by analysis_users ( analysis_id ) -Referred by analysis_artifact ( analysis_id ) - Index ( email ) -emailemail varchar not null -Email for user who owns the analysis -References qiita_user ( email ) - namename varchar not null -Name of the analysis - descriptiondescription varchar not null - Index ( analysis_status_id ) -analysis_status_idanalysis_status_id bigint not null -References analysis_status ( analysis_status_id ) - pmidpmid varchar -PMID of paper from the analysis - timestamptimestamp timestamptz default current_timestamp - dfltdflt bool not null default false - Index ( portal_type_id ) -portal_type_idportal_type_id bigint not null -References portal_type ( portal_type_id ) - @@ -2019,6 +1978,36 @@ data_typedata_type varchar not null Data type (16S, metabolome, etc) the job will use + + + +analysisTable qiita.analysis +Holds analysis information + Primary Key ( analysis_id ) +analysis_idanalysis_id bigserial not null +Unique identifier for analysis +Referred by analysis_artifact ( analysis_id ) +Referred by analysis_filepath ( analysis_id ) +Referred by analysis_portal ( analysis_id ) +Referred by analysis_sample ( analysis_id ) +Referred by analysis_users ( analysis_id ) + Index ( email ) +emailemail varchar not null +Email for user who owns the analysis +References qiita_user ( email ) + namename varchar not null +Name of the analysis + descriptiondescription varchar not null + Index ( analysis_status_id ) +analysis_status_idanalysis_status_id bigint not null + pmidpmid varchar +PMID of paper from the analysis + timestamptimestamp timestamptz default current_timestamp + dfltdflt bool not null default false + Index ( portal_type_id ) +portal_type_idportal_type_id bigint not null +References portal_type ( portal_type_id ) +

@@ -5282,122 +5271,6 @@
-

- - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis_status
analysis_status_id bigserial NOT NULL
status varchar NOT NULL
Indexes
pk_analysis_status primary key ON analysis_status_id
idx_analysis_status unique ON status
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis
Holds analysis information
analysis_id bigserial NOT NULL Unique identifier for analysis
email varchar NOT NULL Email for user who owns the analysis
name varchar NOT NULL Name of the analysis
description varchar NOT NULL
analysis_status_id bigint NOT NULL
pmid varchar PMID of paper from the analysis
timestamp timestamptz DEFO current_timestamp
dflt bool NOT NULL DEFO false
portal_type_id bigint NOT NULL
Indexes
pk_analysis primary key ON analysis_id
idx_analysis_email ON email
idx_analysis_status_id ON analysis_status_id
idx_analysis ON portal_type_id
Foreign Keys
fk_analysis_user ( email ) ref qiita_user (email)
fk_analysis_analysis_status ( analysis_status_id ) ref analysis_status (analysis_status_id)
fk_analysis ( portal_type_id ) ref portal_type (portal_type_id)
-

@@ -5756,4 +5629,87 @@
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table analysis
Holds analysis information
analysis_id bigserial NOT NULL Unique identifier for analysis
email varchar NOT NULL Email for user who owns the analysis
name varchar NOT NULL Name of the analysis
description varchar NOT NULL
analysis_status_id bigint NOT NULL
pmid varchar PMID of paper from the analysis
timestamp timestamptz DEFO current_timestamp
dflt bool NOT NULL DEFO false
portal_type_id bigint NOT NULL
Indexes
pk_analysis primary key ON analysis_id
idx_analysis_email ON email
idx_analysis_status_id ON analysis_status_id
idx_analysis ON portal_type_id
Foreign Keys
fk_analysis_user ( email ) ref qiita_user (email)
fk_analysis ( portal_type_id ) ref portal_type (portal_type_id)
+ \ No newline at end of file From aa3e96fbe6ecdb057b58c622d204c05ce9422524 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 14:30:32 -0800 Subject: [PATCH 10/32] Linking the analysis with all the artifacts --- .../patches/python_patches/47.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index 04e08338b..c85cb594a 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -170,12 +170,15 @@ def create_rarefaction_job(depth, biom_artifact_id, analysis, srare_cmd_id): return job_id, params -def transfer_file_to_artifact(a_timestamp, command_id, data_type_id, params, - artifact_type_id, filepath_id): +def transfer_file_to_artifact(analysis_id, a_timestamp, command_id, + data_type_id, params, artifact_type_id, + filepath_id): """Creates a new artifact with the given filepath id Parameters ---------- + analysis_id : int + The analysis id to attach the artifact a_timestamp : datetime.datetime The generated timestamp of the artifact command_id : int @@ -210,6 +213,11 @@ def transfer_file_to_artifact(a_timestamp, command_id, data_type_id, params, sql = """INSERT INTO qiita.artifact_filepath (artifact_id, filepath_id) VALUES (%s, %s)""" TRN.add(sql, [artifact_id, filepath_id]) + # Link the artifact with the analysis + sql = """INSERT INTO qiita.analysis_artifact + (analysis_id, artifact_id) + VALUES (%s, %s)""" + TRN.add(sql, [analysis_id, artifact_id]) return artifact_id @@ -245,8 +253,8 @@ def create_rarefied_biom_artifact(analysis, srare_cmd_id, biom_data, params, # Transfer the file to an artifact # Magic number 7: artifact type -> biom artifact_id = transfer_file_to_artifact( - analysis['timestamp'], srare_cmd_id, biom_data['data_type_id'], - params, 7, biom_data['filepath_id']) + analysis['analysis_id'], analysis['timestamp'], srare_cmd_id, + biom_data['data_type_id'], params, 7, biom_data['filepath_id']) # Link the artifact with its parent sql = """INSERT INTO qiita.parent_artifact (artifact_id, parent_id) VALUES (%s, %s)""" @@ -314,8 +322,9 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, TRN.add(sql, job_data['job_id']) filepath_id = TRN.execute_fetchlast() artifact_id = transfer_file_to_artifact( - analysis['timestamp'], command_id, biom_data['data_type_id'], - params, output_artifact_type_id, filepath_id) + analysis['analysis_id'], analysis['timestamp'], command_id, + biom_data['data_type_id'], params, output_artifact_type_id, + filepath_id) # Link the artifact with its parent sql = """INSERT INTO qiita.parent_artifact (artifact_id, parent_id) @@ -639,8 +648,15 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, transfer_job() -# Delete old structures that are not used anymore with TRN: + # Unlink the analysis from the biom table filepaths + # Magic number 7 -> biom filepath type + sql = """DELETE FROM qiita.analysis_filepath + WHERE filepath_id IN (SELECT filepath_id + FROM qiita.filepath + WHERE filepath_type_id = 7)""" + TRN.add(sql) + # Delete old structures that are not used anymore TRN.add("DROP TABLE qiita.collection_job") TRN.add("DROP TABLE qiita.collection_analysis") TRN.add("DROP TABLE qiita.collection_users") From d222e08f1f23be388c7c7b578c0a36b21a7050c1 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 14:32:56 -0800 Subject: [PATCH 11/32] Fixing typo --- qiita_db/support_files/patches/47.sql | 4 ++++ qiita_db/support_files/patches/python_patches/47.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index d53c3ec1c..1347acff4 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -27,6 +27,10 @@ CREATE INDEX idx_analysis_artifact_artifact ON qiita.analysis_artifact (artifact ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_analysis FOREIGN KEY ( analysis_id ) REFERENCES qiita.analysis( analysis_id ); ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_artifact FOREIGN KEY ( artifact_id ) REFERENCES qiita.artifact( artifact_id ); +-- Droping the analysis status column cause now it depends on the artifacts +-- status, like the study does. +ALTER TABLE qiita.analysis DROP COLUMN analysis_status_id; + -- We can handle some of the special cases here, so we simplify the work in the -- python patch diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index c85cb594a..22242d5c8 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -670,7 +670,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, TRN.add("DROP TABLE qiita.job_status") TRN.add("DROP TABLE qiita.command_data_type") TRN.add("DROP TABLE qiita.command") - TRN.add("DROP TABLE analysis_status") + TRN.add("DROP TABLE qiita.analysis_status") TRN.execute() # Purge filepaths From 39f6beb2c9a4dc4de5ddb6be3d8ba3614bb2de24 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 14:34:30 -0800 Subject: [PATCH 12/32] Fixing HTML and dbschema files --- qiita_db/support_files/qiita-db.dbs | 4 --- qiita_db/support_files/qiita-db.html | 41 ++++++++++------------------ 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index 3138939c0..141fe19db 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -14,7 +14,6 @@ - @@ -31,9 +30,6 @@ - - - diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index 7b189e2c0..5f4def7d9 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -807,7 +807,7 @@ analysis_users references qiita_user ( email ) email + analysis_users references qiita_user ( email )' style='fill:#a1a0a0;'>email Foreign Key fk_analysis_portal analysis_portal references analysis ( analysis_id ) @@ -817,7 +817,7 @@ analysis_portal references portal_type ( portal_type_id ) portal_type_id + analysis_portal references portal_type ( portal_type_id )' style='fill:#a1a0a0;'>portal_type_id Foreign Key fk_analysis_filepath analysis_filepath references analysis ( analysis_id ) @@ -832,7 +832,7 @@ analysis_filepath references data_type ( data_type_id ) data_type_id + analysis_filepath references data_type ( data_type_id )' style='fill:#a1a0a0;'>data_type_id Foreign Key fk_analysis_sample_analysis analysis_sample references analysis ( analysis_id ) @@ -847,7 +847,7 @@ analysis_sample references artifact ( artifact_id ) artifact_id + analysis_sample references artifact ( artifact_id )' style='fill:#a1a0a0;'>artifact_id Foreign Key fk_analysis_artifact_analysis analysis_artifact references analysis ( analysis_id ) @@ -1979,14 +1979,14 @@ Data type (16S, metabolome, etc) the job will use - - -analysisTable qiita.analysis +<rect class='table' x='210' y='758' width='120' height='165' rx='7' ry='7' /> +<path d='M 210.50 784.50 L 210.50 765.50 Q 210.50 758.50 217.50 758.50 L 322.50 758.50 Q 329.50 758.50 329.50 765.50 L 329.50 784.50 L210.50 784.50 ' style='fill:url(#tableHeaderGradient0); stroke:none;' /> +<a xlink:href='#analysis'><text x='248' y='772' class='tableTitle'>analysis</text><title>Table qiita.analysis Holds analysis information Primary Key ( analysis_id ) analysis_idanalysis_id bigserial not null Unique identifier for analysis -Referred by analysis_artifact ( analysis_id ) +<a xlink:href='#analysis.analysis_id'><use id='ref' x='318' y='791' xlink:href='#ref'/><title>Referred by analysis_artifact ( analysis_id ) Referred by analysis_filepath ( analysis_id ) Referred by analysis_portal ( analysis_id ) Referred by analysis_sample ( analysis_id ) @@ -1994,19 +1994,17 @@ <use id='nn' x='212' y='807' xlink:href='#nn'/><a xlink:href='#analysis.email'><use id='idx' x='212' y='806' xlink:href='#idx'/><title>Index ( email ) emailemail varchar not null Email for user who owns the analysis -References qiita_user ( email ) +References qiita_user ( email ) namename varchar not null Name of the analysis descriptiondescription varchar not null - Index ( analysis_status_id ) -analysis_status_idanalysis_status_id bigint not null - pmidpmid varchar + <a xlink:href='#analysis.pmid'><text x='228' y='862'>pmid</text><title>pmid varchar PMID of paper from the analysis - timestamptimestamp timestamptz default current_timestamp - dfltdflt bool not null default false - Index ( portal_type_id ) -portal_type_idportal_type_id bigint not null -References portal_type ( portal_type_id ) + timestamptimestamp timestamptz default current_timestamp + dfltdflt bool not null default false + Index ( portal_type_id ) +portal_type_idportal_type_id bigint not null +References portal_type ( portal_type_id ) @@ -5656,11 +5654,6 @@ varchar NOT NULL - - analysis_status_id - bigint NOT NULL - - pmid varchar @@ -5690,10 +5683,6 @@ ON email - idx_analysis_status_id - ON analysis_status_id - - idx_analysis ON portal_type_id From 4dd357c2aa9ad5df918b132280e0ca382acefd2b Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Thu, 12 Jan 2017 22:30:22 -0800 Subject: [PATCH 13/32] Adding analyisis jobs --- qiita_db/support_files/patches/47.sql | 14 ++++ qiita_db/support_files/qiita-db.dbs | 22 ++++++ qiita_db/support_files/qiita-db.html | 97 ++++++++++++++++++++++----- 3 files changed, 115 insertions(+), 18 deletions(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 1347acff4..6dacc0bce 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -31,6 +31,20 @@ ALTER TABLE qiita.analysis_artifact ADD CONSTRAINT fk_analysis_artifact_artifact -- status, like the study does. ALTER TABLE qiita.analysis DROP COLUMN analysis_status_id; +-- Create a table to link the analysis with the jobs that create the initial +-- artifacts +CREATE TABLE qiita.analysis_processing_job ( + analysis_id bigint NOT NULL, + processing_job_id uuid NOT NULL, + CONSTRAINT idx_analysis_processing_job PRIMARY KEY ( analysis_id, processing_job_id ) + ) ; + +CREATE INDEX idx_analysis_processing_job_analysis ON qiita.analysis_processing_job ( analysis_id ) ; +CREATE INDEX idx_analysis_processing_job_pj ON qiita.analysis_processing_job ( processing_job_id ) ; +ALTER TABLE qiita.analysis_processing_job ADD CONSTRAINT fk_analysis_processing_job FOREIGN KEY ( analysis_id ) REFERENCES qiita.analysis( analysis_id ) ; +ALTER TABLE qiita.analysis_processing_job ADD CONSTRAINT fk_analysis_processing_job_pj FOREIGN KEY ( processing_job_id ) REFERENCES qiita.processing_job( processing_job_id ) ; + + -- We can handle some of the special cases here, so we simplify the work in the -- python patch diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index 141fe19db..d311409b0 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -105,6 +105,26 @@ + + + + + + + + + + + + + + + + + + + +
@@ -1635,6 +1655,7 @@ Controlled Vocabulary]]> + analysis tables @@ -1643,6 +1664,7 @@ Controlled Vocabulary]]> + diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index 5f4def7d9..c7e812b54 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -657,7 +657,7 @@ default_workflow_edge references default_workflow_node ( child_id -> default_workflow_node_id ) child_id + default_workflow_edge references default_workflow_node ( child_id -> default_workflow_node_id )' style='fill:#a1a0a0;'>child_id Foreign Key fk_artifact_processing_job artifact_processing_job references artifact ( artifact_id ) @@ -667,7 +667,7 @@ artifact_processing_job references processing_job ( processing_job_id ) processing_job_id + artifact_processing_job references processing_job ( processing_job_id )' style='fill:#a1a0a0;'>processing_job_id Foreign Key fk_artifact_output_processing_job artifact_output_processing_job references artifact ( artifact_id ) @@ -682,17 +682,12 @@ artifact_output_processing_job references command_output ( command_output_id ) command_output_id + artifact_output_processing_job references command_output ( command_output_id )' style='fill:#a1a0a0;'>command_output_id Foreign Key fk_reference_sequence_filepath reference references filepath ( sequence_filepath -> filepath_id ) sequence_filepath - Foreign Key fk_reference_taxonomy_filepath - reference references filepath ( taxonomy_filepath -> filepath_id ) - -taxonomy_filepath + reference references filepath ( sequence_filepath -> filepath_id )' style='fill:#a1a0a0;'>sequence_filepath Foreign Key fk_parent_processing_job parent_processing_job references processing_job ( parent_id -> processing_job_id ) @@ -702,11 +697,11 @@ parent_processing_job references processing_job ( child_id -> processing_job_id ) child_id + parent_processing_job references processing_job ( child_id -> processing_job_id )' style='fill:#a1a0a0;'>child_id Foreign Key fk_processing_job_workflow processing_job_workflow references qiita_user ( email ) -email Foreign Key fk_processing_job_workflow_roots_0 processing_job_workflow_root references processing_job ( processing_job_id ) @@ -722,7 +717,7 @@ prep_template_processing_job references prep_template ( prep_template_id ) prep_template_id + prep_template_processing_job references prep_template ( prep_template_id )' style='fill:#a1a0a0;'>prep_template_id Foreign Key fk_prep_template_processing_job_0 prep_template_processing_job references processing_job ( processing_job_id ) @@ -747,7 +742,7 @@ software_command references software ( software_id ) software_id + software_command references software ( software_id )' style='fill:#a1a0a0;'>software_id Foreign Key fk_processing_job_qiita_user processing_job references qiita_user ( email ) @@ -757,11 +752,11 @@ processing_job references software_command ( command_id ) command_id + processing_job references software_command ( command_id )' style='fill:#a1a0a0;'>command_id Foreign Key fk_processing_job_status processing_job references processing_job_status ( processing_job_status_id ) -processing_job_status_id Foreign Key fk_processing_job_logging processing_job references logging ( logging_id ) @@ -867,7 +862,17 @@ analysis references portal_type ( portal_type_id ) portal_type_id + analysis references portal_type ( portal_type_id )' style='fill:#a1a0a0;'>portal_type_id + Foreign Key fk_analysis_processing_job + analysis_processing_job references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_processing_job_pj + analysis_processing_job references processing_job ( processing_job_id ) + +processing_job_id controlled_vocab_valuesTable qiita.controlled_vocab_values @@ -1814,7 +1819,8 @@ Referred by prep_template_processing_job ( processing_job_id ) Referred by processing_job_validator ( processing_job_id ) Referred by processing_job_validator ( validator_id -> processing_job_id ) -Referred by processing_job_workflow_root ( processing_job_id ) +Referred by processing_job_workflow_root ( processing_job_id ) +Referred by analysis_processing_job ( processing_job_id ) Index ( email ) emailemail varchar not null The user that launched the job @@ -1990,7 +1996,8 @@ Referred by analysis_filepath ( analysis_id ) Referred by analysis_portal ( analysis_id ) Referred by analysis_sample ( analysis_id ) -Referred by analysis_users ( analysis_id ) +Referred by analysis_users ( analysis_id ) +Referred by analysis_processing_job ( analysis_id ) Index ( email ) emailemail varchar not null Email for user who owns the analysis @@ -2006,6 +2013,17 @@ portal_type_idportal_type_id bigint not null References portal_type ( portal_type_id ) + + + +analysis_processing_jobTable qiita.analysis_processing_job + Primary Key ( analysis_id, processing_job_id ) Index ( analysis_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Primary Key ( analysis_id, processing_job_id ) Index ( processing_job_id ) +processing_job_idprocessing_job_id varchar not null +References processing_job ( processing_job_id ) +

@@ -5701,4 +5719,47 @@
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table analysis_processing_job
analysis_id bigint NOT NULL
processing_job_id varchar NOT NULL
Indexes
idx_analysis_processing_job primary key ON analysis_id, processing_job_id
idx_analysis_processing_job ON analysis_id
idx_analysis_processing_job ON processing_job_id
Foreign Keys
fk_analysis_processing_job ( analysis_id ) ref analysis (analysis_id)
fk_analysis_processing_job_pj ( processing_job_id ) ref processing_job (processing_job_id)
+ \ No newline at end of file From b24833da82caa167f4652034eaf3bce789ccdb90 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Fri, 13 Jan 2017 10:02:48 -0800 Subject: [PATCH 14/32] Extending the artifact to work with the analysis --- qiita_db/artifact.py | 277 ++++++++++++++++++++++----------- qiita_db/test/test_artifact.py | 108 ++++++++++--- 2 files changed, 270 insertions(+), 115 deletions(-) diff --git a/qiita_db/artifact.py b/qiita_db/artifact.py index 49e932d3d..58c3929ee 100644 --- a/qiita_db/artifact.py +++ b/qiita_db/artifact.py @@ -11,6 +11,7 @@ from itertools import chain from datetime import datetime from os import remove +from collections import Counter import networkx as nx @@ -208,17 +209,22 @@ def copy(cls, artifact, prep_template): @classmethod def create(cls, filepaths, artifact_type, name=None, prep_template=None, - parents=None, processing_parameters=None, move_files=True): + parents=None, processing_parameters=None, move_files=True, + analysis=None, data_type=None): r"""Creates a new artifact in the system The parameters depend on how the artifact was generated: - If the artifact was uploaded by the user, the parameter - `prep_template` should be provided and the parameters `parents` and - `processing_parameters` should not be provided. + `prep_template` should be provided and the parameters `parents`, + `processing_parameters` and `analysis` should not be provided. - If the artifact was generated by processing one or more artifacts, the parameters `parents` and `processing_parameters` - should be provided and the parameter `prep_template` should not - be provided. + should be provided and the parameters `prep_template` and + `analysis` should not be provided. + - If the artifact is the initial artifact of the analysis, the + parameters `analysis` and `data_type` should be provided and the + parameters `prep_template`, `parents` and `processing_parameters` + should not be provided. Parameters ---------- @@ -232,16 +238,25 @@ def create(cls, filepaths, artifact_type, name=None, prep_template=None, prep_template : qiita_db.metadata_template.PrepTemplate, optional If the artifact is being uploaded by the user, the prep template to which the artifact should be linked to. If not provided, - `parents` should be provided. + `parents` or `analysis` should be provided. parents : iterable of qiita_db.artifact.Artifact, optional The list of artifacts from which the new artifact has been - generated. If not provided, `prep_template` should be provided. + generated. If not provided, `prep_template` or `analysis` + should be provided. processing_parameters : qiita_db.software.Parameters, optional The processing parameters used to generate the new artifact from `parents`. It is required if `parents` is provided. It should - not be provided if `prep_template` is provided. + not be provided if `processing_parameters` is not provided. move_files : bool, optional If False the files will not be moved but copied + analysis : qiita_db.analysis.Analysis, optional + If the artifact is the inital artifact of an analysis, the analysis + to which the artifact belongs to. If not provided, `prep_template` + or `parents` should be provided. + data_type : str + The data_type of the artifact in the `analysis`. It is required if + `analysis` is provided. It should not be provided if `analysis` is + not provided. Returns ------- @@ -269,107 +284,163 @@ def create(cls, filepaths, artifact_type, name=None, prep_template=None, raise qdb.exceptions.QiitaDBArtifactCreationError( "at least one filepath is required.") - # Parents or prep template must be provided, but not both - if parents and prep_template: + # Check that the combination of parameters is correct + counts = Counter([bool(parents or processing_parameters), + prep_template is not None, + bool(analysis or data_type)]) + if counts[True] != 1: + # More than one parameter has been provided raise qdb.exceptions.QiitaDBArtifactCreationError( - "parents or prep_template should be provided but not both") - elif not (parents or prep_template): + "One and only one of parents, prep template or analysis must " + "be provided") + elif bool(parents) != bool(processing_parameters): + # When provided, parents and processing parameters both should be + # provided (this is effectively doing an XOR) raise qdb.exceptions.QiitaDBArtifactCreationError( - "at least parents or prep_template must be provided") - elif parents and not processing_parameters: - # If parents is provided, processing parameters should also be - # provided + "When provided, both parents and processing parameters should " + "be provided") + elif bool(analysis) != bool(data_type): + # When provided, analysis and data_type both should be + # provided (this is effectively doing an XOR) raise qdb.exceptions.QiitaDBArtifactCreationError( - "if parents is provided, processing_parameters should also be" - "provided.") - elif prep_template and processing_parameters: - # If prep_template is provided, processing_parameters should not be - # provided - raise qdb.exceptions.QiitaDBArtifactCreationError( - "if prep_template is provided, processing_parameters should " - "not be provided.") + "When provided, both analysis and data_type should " + "be provided") + + # There are three different ways of creating an Artifact, but all of + # them execute a set of common operations. Declare functions to avoid + # code duplication. These functions should not be used outside of the + # create function, hence declaring them here + def _common_creation_steps(atype, cmd_id, data_type, cmd_parameters, + fps, mv_files): + gen_timestamp = datetime.now() + visibility_id = qdb.util.convert_to_id("sandbox", "visibility") + atype_id = qdb.util.convert_to_id(atype, "artifact_type") + dtype_id = qdb.util.convert_to_id(data_type, "data_type") + # Create the artifact row in the artifact table + sql = """INSERT INTO qiita.artifact + (generated_timestamp, command_id, data_type_id, + command_parameters, visibility_id, + artifact_type_id, submitted_to_vamps) + VALUES (%s, %s, %s, %s, %s, %s, %s) + RETURNING artifact_id""" + sql_args = [gen_timestamp, cmd_id, dtype_id, + cmd_parameters, visibility_id, atype_id, False] + qdb.sql_connection.TRN.add(sql, sql_args) + a_id = qdb.sql_connection.TRN.execute_fetchlast() + # Associate the artifact with its filepaths + fp_ids = qdb.util.insert_filepaths( + fps, a_id, atype, "filepath", + move_files=mv_files, copy=(not mv_files)) + sql = """INSERT INTO qiita.artifact_filepath + (artifact_id, filepath_id) + VALUES (%s, %s)""" + sql_args = [[a_id, fp_id] for fp_id in fp_ids] + qdb.sql_connection.TRN.add(sql, sql_args, many=True) + qdb.sql_connection.TRN.execute() - timestamp = datetime.now() + return cls(a_id) - with qdb.sql_connection.TRN: - visibility_id = qdb.util.convert_to_id("sandbox", "visibility") - artifact_type_id = qdb.util.convert_to_id( - artifact_type, "artifact_type") + def _associate_with_study(instance, study_id): + # Associate the artifact with the study + sql = """INSERT INTO qiita.study_artifact + (study_id, artifact_id) + VALUES (%s, %s)""" + sql_args = [study_id, instance.id] + qdb.sql_connection.TRN.add(sql, sql_args) + qdb.sql_connection.TRN.execute() - if parents: - # Check that all parents belong to the same study - studies = {p.study.id for p in parents} - if len(studies) > 1: - raise qdb.exceptions.QiitaDBArtifactCreationError( - "parents from multiple studies provided: %s" - % ', '.join(studies)) - study_id = studies.pop() + def _associate_with_analysis(instance, analysis_id): + # Associate the artifact with the analysis + sql = """INSERT INTO qiita.analysis_artifact + (analysis_id, artifact_id) + VALUES (%s, %s)""" + sql_args = [analysis_id, instance.id] + qdb.sql_connection.TRN.add(sql, sql_args) + qdb.sql_connection.TRN.execute() - # Check that all parents have the same data type + with qdb.sql_connection.TRN: + if parents: dtypes = {p.data_type for p in parents} - if len(dtypes) > 1: + # If an artifact has parents, it can be either from the + # processing pipeline or the analysis pipeline. Decide which + # one here + studies = set() + analyses = set() + for p in parents: + s = p.study + a = p.analysis + if s is not None: + studies.add(s.id) + if a is not None: + analyses.add(a.id) + + # The first 2 cases should never happen, but it doesn't hurt + # to check them + len_studies = len(studies) + len_analyses = len(analyses) + if len_studies > 0 and len_analyses > 0: raise qdb.exceptions.QiitaDBArtifactCreationError( - "parents have multiple data types: %s" - % ", ".join(dtypes)) - dtype_id = qdb.util.convert_to_id(dtypes.pop(), "data_type") - - # Create the artifact - sql = """INSERT INTO qiita.artifact - (generated_timestamp, command_id, data_type_id, - command_parameters, visibility_id, - artifact_type_id, submitted_to_vamps) - VALUES (%s, %s, %s, %s, %s, %s, %s) - RETURNING artifact_id""" - sql_args = [timestamp, processing_parameters.command.id, - dtype_id, processing_parameters.dump(), - visibility_id, artifact_type_id, False] - qdb.sql_connection.TRN.add(sql, sql_args) - a_id = qdb.sql_connection.TRN.execute_fetchlast() + "All the parents from an artifact should be either " + "from the analysis pipeline or all from the processing" + " pipeline") + elif len_studies > 1 or len_studies > 1: + raise qdb.exceptions.QiitaDBArtifactCreationError( + "Parents from multiple studies/analyses provided. " + "Analyses: %s. Studies: %s." + % (', '.join(analyses), ', '.join(studies))) + elif len_studies == 1: + # This artifact is part of the processing pipeline + study_id = studies.pop() + # In the processing pipeline, artifacts can have only + # one dtype + if len(dtypes) > 1: + raise qdb.exceptions.QiitaDBArtifactCreationError( + "parents have multiple data types: %s" + % ", ".join(dtypes)) + + instance = _common_creation_steps( + artifact_type, processing_parameters.command.id, + dtypes.pop(), processing_parameters.dump(), filepaths, + move_files) + + _associate_with_study(instance, study_id) + else: + # This artifact is part of the analysis pipeline + analysis_id = analyses.pop() + # In the processing pipeline, artifact parents can have + # more than one data type + data_type = ("Multiomic" + if len(dtypes) > 1 else dtypes.pop()) + instance = _common_creation_steps( + artifact_type, processing_parameters.command.id, + data_type, processing_parameters.dump(), filepaths, + move_files) + _associate_with_analysis(instance, analysis_id) # Associate the artifact with its parents sql = """INSERT INTO qiita.parent_artifact (artifact_id, parent_id) VALUES (%s, %s)""" - sql_args = [(a_id, p.id) for p in parents] + sql_args = [(instance.id, p.id) for p in parents] qdb.sql_connection.TRN.add(sql, sql_args, many=True) - instance = cls(a_id) - else: - dtype_id = qdb.util.convert_to_id(prep_template.data_type(), - "data_type") - # Create the artifact - sql = """INSERT INTO qiita.artifact - (generated_timestamp, visibility_id, - artifact_type_id, data_type_id, - submitted_to_vamps) - VALUES (%s, %s, %s, %s, %s) - RETURNING artifact_id""" - sql_args = [timestamp, visibility_id, artifact_type_id, - dtype_id, False] - qdb.sql_connection.TRN.add(sql, sql_args) - a_id = qdb.sql_connection.TRN.execute_fetchlast() - + elif prep_template: + # This artifact is uploaded by the user in the + # processing pipeline + instance = _common_creation_steps( + artifact_type, None, prep_template.data_type(), None, + filepaths, move_files) # Associate the artifact with the prep template - instance = cls(a_id) prep_template.artifact = instance - study_id = prep_template.study_id - - # Associate the artifact with the study - sql = """INSERT INTO qiita.study_artifact (study_id, artifact_id) - VALUES (%s, %s)""" - sql_args = [study_id, a_id] - qdb.sql_connection.TRN.add(sql, sql_args) - - # Associate the artifact with its filepaths - fp_ids = qdb.util.insert_filepaths( - filepaths, a_id, artifact_type, "filepath", - move_files=move_files, copy=(not move_files)) - sql = """INSERT INTO qiita.artifact_filepath - (artifact_id, filepath_id) - VALUES (%s, %s)""" - sql_args = [[a_id, fp_id] for fp_id in fp_ids] - qdb.sql_connection.TRN.add(sql, sql_args, many=True) - qdb.sql_connection.TRN.execute() + # Associate the artifact with the study + _associate_with_study(instance, prep_template.study_id) + else: + # This artifact is an initial artifact of an analysis + instance = _common_creation_steps( + artifact_type, None, data_type, None, filepaths, + move_files) + # Associate the artifact with the analysis + analysis.add_artifact(instance) if name: instance.name = name @@ -1072,15 +1143,33 @@ def study(self): Returns ------- - qiita_db.study.Study - The study that owns the artifact + qiita_db.study.Study or None + The study that owns the artifact, if any """ with qdb.sql_connection.TRN: sql = """SELECT study_id FROM qiita.study_artifact WHERE artifact_id = %s""" qdb.sql_connection.TRN.add(sql, [self.id]) - return qdb.study.Study(qdb.sql_connection.TRN.execute_fetchlast()) + res = qdb.sql_connection.TRN.execute_fetchindex() + return qdb.study.Study(res[0][0]) if res else None + + @property + def analysis(self): + """The analysis to which the artifact belongs to + + Returns + ------- + qiita_db.analysis.Analysis or None + The analysis that owns the artifact, if any + """ + with qdb.sql_connection.TRN: + sql = """SELECT analysis_id + FROM qiita.analysis_artifact + WHERE artifact_id = %s""" + qdb.sql_connection.TRN.add(sql, [self.id]) + res = qdb.sql_connection.TRN.execute_fetchindex() + return qdb.analysis.Analysis(res[0][0]) if res else None def jobs(self, cmd=None, status=None): """Jobs that used this artifact as input diff --git a/qiita_db/test/test_artifact.py b/qiita_db/test/test_artifact.py index cc0432da9..2423e9d4e 100644 --- a/qiita_db/test/test_artifact.py +++ b/qiita_db/test/test_artifact.py @@ -48,7 +48,11 @@ def test_create_type(self): exp = [['BIOM', 'BIOM table'], ['Demultiplexed', 'Demultiplexed and QC sequeneces'], ['FASTA', None], ['FASTA_Sanger', None], ['FASTQ', None], - ['SFF', None], ['per_sample_FASTQ', None]] + ['SFF', None], ['per_sample_FASTQ', None], + ['distance_matrix', 'Distance matrix holding pairwise ' + 'distance between samples'], + ['rarefaction_curves', 'Rarefaction curves'], + ['taxa_summary', 'Taxa summary plots']] self.assertItemsEqual(obs, exp) qdb.artifact.Artifact.create_type( @@ -60,6 +64,10 @@ def test_create_type(self): ['Demultiplexed', 'Demultiplexed and QC sequeneces'], ['FASTA', None], ['FASTA_Sanger', None], ['FASTQ', None], ['SFF', None], ['per_sample_FASTQ', None], + ['distance_matrix', 'Distance matrix holding pairwise ' + 'distance between samples'], + ['rarefaction_curves', 'Rarefaction curves'], + ['taxa_summary', 'Taxa summary plots'], ['NewType', 'NewTypeDesc']] self.assertItemsEqual(obs, exp) @@ -392,6 +400,12 @@ def test_prep_templates(self): def test_study(self): self.assertEqual(qdb.artifact.Artifact(1).study, qdb.study.Study(1)) + self.assertIsNone(qdb.artifact.Artifact(9).study) + + def test_analysis(self): + self.assertEqual(qdb.artifact.Artifact(9).analysis, + qdb.analysis.Analysis(1)) + self.assertIsNone(qdb.artifact.Artifact(1).analysis) def test_jobs(self): obs = qdb.artifact.Artifact(1).jobs() @@ -550,7 +564,6 @@ def test_copy(self): with open(fp, 'w') as f: f.write("\n") self._clean_up_files.append(fp) - fp_count = qdb.util.get_count('qiita.filepath') before = datetime.now() obs = qdb.artifact.Artifact.copy(src, self.prep_template) @@ -568,12 +581,11 @@ def test_copy(self): path_builder = partial(join, db_dir, str(obs.id)) exp_fps = [] for fp_id, fp, fp_type in src.filepaths: - fp_count += 1 new_fp = path_builder(basename(fp)) - exp_fps.append((fp_count, new_fp, fp_type)) + exp_fps.append((new_fp, fp_type)) self._clean_up_files.append(new_fp) - self.assertEqual(obs.filepaths, exp_fps) + self.assertEqual([(a, b) for _, a, b in obs.filepaths], exp_fps) self.assertEqual(obs.parents, []) self.assertEqual(obs.prep_templates, [self.prep_template]) @@ -591,7 +603,20 @@ def test_create_error(self): self.filepaths_root, "FASTQ", prep_template=self.prep_template, parents=[qdb.artifact.Artifact(1)]) - # no prep template no parents + # analysis and prep_template + with self.assertRaises(qdb.exceptions.QiitaDBArtifactCreationError): + qdb.artifact.Artifact.create( + self.filepaths_root, "BIOM", prep_template=self.prep_template, + analysis=qdb.analysis.Analysis(1)) + + # Analysis and parents + with self.assertRaises(qdb.exceptions.QiitaDBArtifactCreationError): + qdb.artifact.Artifact.create( + self.filepaths_root, "BIOM", + parents=[qdb.artifact.Artifact(1)], + analysis=qdb.analysis.Analysis(1)) + + # no prep template no parents no analysis with self.assertRaises(qdb.exceptions.QiitaDBArtifactCreationError): qdb.artifact.Artifact.create(self.filepaths_root, "FASTQ") @@ -601,6 +626,11 @@ def test_create_error(self): self.filepaths_root, "FASTQ", parents=[qdb.artifact.Artifact(1)]) + # analysis no data type + with self.assertRaises(qdb.exceptions.QiitaDBArtifactCreationError): + qdb.artifact.Artifact.create( + self.filepaths_root, "BIOM", analysis=qdb.analysis.Analysis(1)) + # prep template and processing parameters parameters = qdb.software.Parameters.from_default_params( qdb.software.DefaultParameters(1), {'input_data': 1}) @@ -609,6 +639,12 @@ def test_create_error(self): self.filepaths_root, "FASTQ", prep_template=self.prep_template, processing_parameters=parameters) + # prep template and data type + with self.assertRaises(qdb.exceptions.QiitaDBArtifactCreationError): + qdb.artifact.Artifact.create( + self.filepaths_root, "FASTQ", prep_template=self.prep_template, + data_type="Multiomic") + # different data types new = qdb.artifact.Artifact.create( self.filepaths_root, "FASTQ", prep_template=self.prep_template) @@ -621,7 +657,6 @@ def test_create_error(self): processing_parameters=parameters) def test_create_root(self): - fp_count = qdb.util.get_count('qiita.filepath') before = datetime.now() obs = qdb.artifact.Artifact.create( self.filepaths_root, "FASTQ", prep_template=self.prep_template, @@ -638,11 +673,9 @@ def test_create_root(self): db_fastq_dir = qdb.util.get_mountpoint('FASTQ')[0][1] path_builder = partial(join, db_fastq_dir, str(obs.id)) exp_fps = [ - (fp_count + 1, path_builder(basename(self.fp1)), - "raw_forward_seqs"), - (fp_count + 2, path_builder(basename(self.fp2)), "raw_barcodes"), - ] - self.assertEqual(obs.filepaths, exp_fps) + (path_builder(basename(self.fp1)), "raw_forward_seqs"), + (path_builder(basename(self.fp2)), "raw_barcodes")] + self.assertEqual([(a, b) for _, a, b in obs.filepaths], exp_fps) self.assertEqual(obs.parents, []) self.assertEqual(obs.prep_templates, [self.prep_template]) @@ -655,9 +688,41 @@ def test_create_root(self): obs.is_submitted_to_vamps self.assertEqual(obs.study, qdb.study.Study(1)) + self.assertIsNone(obs.analysis) + + def test_create_root_analysis(self): + before = datetime.now() + obs = qdb.artifact.Artifact.create( + self.filepaths_biom, "BIOM", name='Test artifact analysis', + analysis=qdb.analysis.Analysis(1), data_type="16S") + self.assertEqual(obs.name, 'Test artifact analysis') + self.assertTrue(before < obs.timestamp < datetime.now()) + self.assertIsNone(obs.processing_parameters) + self.assertEqual(obs.visibility, 'sandbox') + self.assertEqual(obs.artifact_type, "BIOM") + self.assertEqual(obs.data_type, "16S") + self.assertFalse(obs.can_be_submitted_to_ebi) + self.assertFalse(obs.can_be_submitted_to_vamps) + + db_fastq_dir = qdb.util.get_mountpoint('BIOM')[0][1] + path_builder = partial(join, db_fastq_dir, str(obs.id)) + exp_fps = [(path_builder(basename(self.fp4)), "biom")] + self.assertEqual([(a, b) for _, a, b in obs.filepaths], exp_fps) + self.assertEqual(obs.parents, []) + self.assertEqual(obs.prep_templates, []) + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + obs.ebi_run_accessions + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + obs.is_submitted_to_vamps + + self.assertIsNone(obs.study) + self.assertEqual(obs.analysis, qdb.analysis.Analysis(1)) def test_create_processed(self): - fp_count = qdb.util.get_count('qiita.filepath') exp_params = qdb.software.Parameters.from_default_params( qdb.software.DefaultParameters(1), {'input_data': 1}) before = datetime.now() @@ -677,9 +742,9 @@ def test_create_processed(self): db_demultiplexed_dir = qdb.util.get_mountpoint('Demultiplexed')[0][1] path_builder = partial(join, db_demultiplexed_dir, str(obs.id)) - exp_fps = [(fp_count + 1, path_builder(basename(self.fp3)), + exp_fps = [(path_builder(basename(self.fp3)), "preprocessed_fasta")] - self.assertEqual(obs.filepaths, exp_fps) + self.assertEqual([(a, b) for _, a, b in obs.filepaths], exp_fps) self.assertEqual(obs.parents, [qdb.artifact.Artifact(1)]) self.assertEqual( obs.prep_templates, @@ -687,9 +752,9 @@ def test_create_processed(self): self.assertEqual(obs.ebi_run_accessions, dict()) self.assertEqual(obs.study, qdb.study.Study(1)) self.assertFalse(exists(self.filepaths_processed[0][0])) + self.assertIsNone(obs.analysis) def test_create_copy_files(self): - fp_count = qdb.util.get_count('qiita.filepath') exp_params = qdb.software.Parameters.from_default_params( qdb.software.DefaultParameters(1), {'input_data': 1}) before = datetime.now() @@ -709,9 +774,9 @@ def test_create_copy_files(self): db_demultiplexed_dir = qdb.util.get_mountpoint('Demultiplexed')[0][1] path_builder = partial(join, db_demultiplexed_dir, str(obs.id)) - exp_fps = [(fp_count + 1, path_builder(basename(self.fp3)), + exp_fps = [(path_builder(basename(self.fp3)), "preprocessed_fasta")] - self.assertEqual(obs.filepaths, exp_fps) + self.assertEqual([(a, b) for _, a, b in obs.filepaths], exp_fps) self.assertEqual(obs.parents, [qdb.artifact.Artifact(1)]) self.assertEqual( obs.prep_templates, @@ -719,9 +784,9 @@ def test_create_copy_files(self): self.assertEqual(obs.ebi_run_accessions, dict()) self.assertEqual(obs.study, qdb.study.Study(1)) self.assertTrue(exists(self.filepaths_processed[0][0])) + self.assertIsNone(obs.analysis) def test_create_biom(self): - fp_count = qdb.util.get_count('qiita.filepath') before = datetime.now() cmd = qdb.software.Command(3) exp_params = qdb.software.Parameters.from_default_params( @@ -747,12 +812,13 @@ def test_create_biom(self): db_biom_dir = qdb.util.get_mountpoint('BIOM')[0][1] path_builder = partial(join, db_biom_dir, str(obs.id)) - exp_fps = [(fp_count + 1, path_builder(basename(self.fp4)), 'biom')] - self.assertEqual(obs.filepaths, exp_fps) + exp_fps = [(path_builder(basename(self.fp4)), 'biom')] + self.assertEqual([(a, b) for _, a, b in obs.filepaths], exp_fps) self.assertEqual(obs.parents, [qdb.artifact.Artifact(2)]) self.assertEqual(obs.prep_templates, [qdb.metadata_template.prep_template.PrepTemplate(1)]) self.assertEqual(obs.study, qdb.study.Study(1)) + self.assertIsNone(obs.analysis) def test_delete_error_public(self): test = qdb.artifact.Artifact.create( From 8b7f222448bacf2161fc9bfa65d0e6823a217c2e Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Fri, 13 Jan 2017 10:03:13 -0800 Subject: [PATCH 15/32] Allowing multiomics datatype --- qiita_db/support_files/patches/47.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 6dacc0bce..35f010dc3 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -71,3 +71,7 @@ DELETE FROM qiita.job_results_filepath WHERE job_id IN ( SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id)); DELETE FROM qiita.job J WHERE NOT EXISTS ( SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id); + +-- In the analysis pipeline, an artifact can have mutliple datatypes +-- (e.g. procrustes). Allow this by creating a new data_type being "multiomic" +INSERT INTO qiita.data_type (data_type) VALUES ('Multiomic'); From b381993e7102b50b6cff50c08524077ec7053134 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Fri, 13 Jan 2017 12:14:49 -0800 Subject: [PATCH 16/32] Adding private_job_submitter and modifying proc job handler to use it --- qiita_db/handlers/processing_job.py | 12 ++++-------- qiita_db/processing_job.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/qiita_db/handlers/processing_job.py b/qiita_db/handlers/processing_job.py index efd5a94bd..84efc21af 100644 --- a/qiita_db/handlers/processing_job.py +++ b/qiita_db/handlers/processing_job.py @@ -11,7 +11,6 @@ from tornado.web import HTTPError -from qiita_core.qiita_settings import qiita_config import qiita_db as qdb from .oauth2 import OauthBaseHandler, authenticate_oauth @@ -59,13 +58,10 @@ def _job_completer(job_id, payload): completing the job """ import qiita_db as qdb - cmd = "%s '%s' %s %s '%s'" % (qiita_config.private_launcher, - qiita_config.qiita_env, 'complete_job', - job_id, payload) - std_out, std_err, return_value = qdb.processing_job._system_call(cmd) - if return_value != 0: - error = ("Can't submit private task 'complete job:\n" - "Std output:%s\nStd error:%s'" % (std_out, std_err)) + + success, error = qdb.processing_job.private_job_submitter( + "Complete job %s" % job_id, 'complete_job', [job_id, payload]) + if not success: job = qdb.processing_job.ProcessingJob(job_id) job.complete(False, error=error) diff --git a/qiita_db/processing_job.py b/qiita_db/processing_job.py index 4e99d3b5b..a950c47d9 100644 --- a/qiita_db/processing_job.py +++ b/qiita_db/processing_job.py @@ -69,6 +69,30 @@ def _job_submitter(job, cmd): job.complete(False, error=error) +def private_job_submitter(job_name, command, args): + """Submits a private job + + Parameters + ---------- + job_name : str + The name of the job + command: str + The private command to be executed + args: list of str + The arguments to the private command + """ + + cmd = "%s '%s' %s %s" % (qiita_config.private_launcher, + qiita_config.qiita_env, command, + ' '.join("'%s'" % a for a in args)) + std_out, std_err, return_value = _system_call(cmd) + error = "" + if return_value != 0: + error = ("Can't submit private task '%s':\n" + "Std output:%s\nStd error: %s" % (command, std_out, std_err)) + return (return_value == 0), error + + class ProcessingJob(qdb.base.QiitaObject): r"""Models a job that executes a command in a set of artifacts From ab65fa2a866a2c5bda0518f8fb9c69cd5646a0fb Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Fri, 13 Jan 2017 12:45:54 -0800 Subject: [PATCH 17/32] Adding logging column to the analysis --- qiita_db/support_files/patches/47.sql | 5 +- qiita_db/support_files/qiita-db.dbs | 9 +- qiita_db/support_files/qiita-db.html | 170 +++++++++++++++----------- 3 files changed, 109 insertions(+), 75 deletions(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 6dacc0bce..829ed7063 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -44,7 +44,10 @@ CREATE INDEX idx_analysis_processing_job_pj ON qiita.analysis_processing_job ( p ALTER TABLE qiita.analysis_processing_job ADD CONSTRAINT fk_analysis_processing_job FOREIGN KEY ( analysis_id ) REFERENCES qiita.analysis( analysis_id ) ; ALTER TABLE qiita.analysis_processing_job ADD CONSTRAINT fk_analysis_processing_job_pj FOREIGN KEY ( processing_job_id ) REFERENCES qiita.processing_job( processing_job_id ) ; - +-- Add a logging column in the analysis +ALTER TABLE qiita.analysis ADD logging_id bigint ; +CREATE INDEX idx_analysis_0 ON qiita.analysis ( logging_id ) ; +ALTER TABLE qiita.analysis ADD CONSTRAINT fk_analysis_logging FOREIGN KEY ( logging_id ) REFERENCES qiita.logging( logging_id ) ; -- We can handle some of the special cases here, so we simplify the work in the -- python patch diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index d311409b0..5cc6f40b5 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -24,6 +24,7 @@ false
+ @@ -33,12 +34,18 @@ + + + + + + @@ -1654,8 +1661,8 @@ Controlled Vocabulary]]> - + analysis tables diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index c7e812b54..76c8daed1 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -577,11 +577,11 @@ artifact references software_command ( command_id ) command_id + artifact references software_command ( command_id )' style='fill:#a1a0a0;'>command_id Foreign Key fk_artifact_data_type artifact references data_type ( data_type_id ) -data_type_id Foreign Key fk_artifact_type_filepath_type artifact_type_filepath_type references artifact_type ( artifact_type_id ) @@ -827,7 +827,7 @@ analysis_filepath references data_type ( data_type_id ) data_type_id + analysis_filepath references data_type ( data_type_id )' style='fill:#a1a0a0;'>data_type_id Foreign Key fk_analysis_sample_analysis analysis_sample references analysis ( analysis_id ) @@ -842,7 +842,7 @@ analysis_sample references artifact ( artifact_id ) artifact_id + analysis_sample references artifact ( artifact_id )' style='fill:#a1a0a0;'>artifact_id Foreign Key fk_analysis_artifact_analysis analysis_artifact references analysis ( analysis_id ) @@ -852,7 +852,17 @@ analysis_artifact references artifact ( artifact_id ) artifact_id + analysis_artifact references artifact ( artifact_id )' style='fill:#a1a0a0;'>artifact_id + Foreign Key fk_analysis_processing_job + analysis_processing_job references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_processing_job_pj + analysis_processing_job references processing_job ( processing_job_id ) + +processing_job_id Foreign Key fk_analysis_user analysis references qiita_user ( email ) @@ -862,17 +872,12 @@ analysis references portal_type ( portal_type_id ) portal_type_id - Foreign Key fk_analysis_processing_job - analysis_processing_job references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_processing_job_pj - analysis_processing_job references processing_job ( processing_job_id ) + analysis references portal_type ( portal_type_id )' style='fill:#a1a0a0;'>portal_type_id + Foreign Key fk_analysis_logging + analysis references logging ( logging_id ) -processing_job_id +logging_id controlled_vocab_valuesTable qiita.controlled_vocab_values @@ -917,7 +922,9 @@ loggingTable qiita.logging Primary Key ( logging_id ) logging_idlogging_id bigserial not null -Referred by processing_job ( logging_id ) +Referred by processing_job ( logging_id ) +Referred by analysis ( logging_id ) +Referred by analysis ( logging_id ) timetime timestamp not null Time the error was thrown Index ( severity_id ) @@ -1812,15 +1819,15 @@ Primary Key ( processing_job_id ) processing_job_idprocessing_job_id bigserial not null Using bigserial in DBSchema - however in the postgres DB this column is of type UUID. -Referred by artifact_output_processing_job ( processing_job_id ) +<a xlink:href='#processing_job.processing_job_id'><use id='ref' x='678' y='1766' xlink:href='#ref'/><title>Referred by analysis_processing_job ( processing_job_id ) +Referred by artifact_output_processing_job ( processing_job_id ) Referred by artifact_processing_job ( processing_job_id ) Referred by parent_processing_job ( parent_id -> processing_job_id ) Referred by parent_processing_job ( child_id -> processing_job_id ) Referred by prep_template_processing_job ( processing_job_id ) Referred by processing_job_validator ( processing_job_id ) Referred by processing_job_validator ( validator_id -> processing_job_id ) -Referred by processing_job_workflow_root ( processing_job_id ) -Referred by analysis_processing_job ( processing_job_id ) +Referred by processing_job_workflow_root ( processing_job_id ) Index ( email ) emailemail varchar not null The user that launched the job @@ -1984,8 +1991,19 @@ data_typedata_type varchar not null Data type (16S, metabolome, etc) the job will use + + + +analysis_processing_jobTable qiita.analysis_processing_job + Primary Key ( analysis_id, processing_job_id ) Index ( analysis_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Primary Key ( analysis_id, processing_job_id ) Index ( processing_job_id ) +processing_job_idprocessing_job_id varchar not null +References processing_job ( processing_job_id ) + - + analysisTable qiita.analysis Holds analysis information @@ -1995,9 +2013,9 @@ Referred by analysis_artifact ( analysis_id ) Referred by analysis_filepath ( analysis_id ) Referred by analysis_portal ( analysis_id ) +Referred by analysis_processing_job ( analysis_id ) Referred by analysis_sample ( analysis_id ) -Referred by analysis_users ( analysis_id ) -Referred by analysis_processing_job ( analysis_id ) +Referred by analysis_users ( analysis_id ) Index ( email ) emailemail varchar not null Email for user who owns the analysis @@ -2012,17 +2030,9 @@ Index ( portal_type_id ) portal_type_idportal_type_id bigint not null References portal_type ( portal_type_id ) - - - - -analysis_processing_jobTable qiita.analysis_processing_job - Primary Key ( analysis_id, processing_job_id ) Index ( analysis_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - Primary Key ( analysis_id, processing_job_id ) Index ( processing_job_id ) -processing_job_idprocessing_job_id varchar not null -References processing_job ( processing_job_id ) + Index ( logging_id ) +logging_idlogging_id bigint +References logging ( logging_id ) @@ -5645,6 +5655,49 @@
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table analysis_processing_job
analysis_id bigint NOT NULL
processing_job_id varchar NOT NULL
Indexes
idx_analysis_processing_job primary key ON analysis_id, processing_job_id
idx_analysis_processing_job ON analysis_id
idx_analysis_processing_job ON processing_job_id
Foreign Keys
fk_analysis_processing_job ( analysis_id ) ref analysis (analysis_id)
fk_analysis_processing_job_pj ( processing_job_id ) ref processing_job (processing_job_id)
+

@@ -5692,6 +5745,11 @@ + + + + + @@ -5705,6 +5763,10 @@ + + + + @@ -5716,47 +5778,9 @@ - -
bigint NOT NULL
logging_id bigint
Indexes
pk_analysis primary key ON analysis_id ON portal_type_id
idx_analysis_0 ON logging_id
Foreign Keys
fk_analysis_user ( portal_type_id ) ref portal_type (portal_type_id)
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + From c4c8420903024bfcbd228b5122d02036f725aaf5 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sat, 14 Jan 2017 18:23:11 -0800 Subject: [PATCH 18/32] Adding datatype to the analysis-processing job table --- qiita_db/support_files/patches/47.sql | 14 ++- qiita_db/support_files/qiita-db.dbs | 3 +- qiita_db/support_files/qiita-db.html | 143 +++++++++++++------------- 3 files changed, 89 insertions(+), 71 deletions(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index dfde381b5..80c545b43 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -35,7 +35,8 @@ ALTER TABLE qiita.analysis DROP COLUMN analysis_status_id; -- artifacts CREATE TABLE qiita.analysis_processing_job ( analysis_id bigint NOT NULL, - processing_job_id uuid NOT NULL, + processing_job_id uuid NOT NULL, + datatype varchar NOT NULL, CONSTRAINT idx_analysis_processing_job PRIMARY KEY ( analysis_id, processing_job_id ) ) ; @@ -78,3 +79,14 @@ DELETE FROM qiita.job J WHERE NOT EXISTS ( -- In the analysis pipeline, an artifact can have mutliple datatypes -- (e.g. procrustes). Allow this by creating a new data_type being "multiomic" INSERT INTO qiita.data_type (data_type) VALUES ('Multiomic'); + + +-- The valdiate command from BIOM will have an extra parameter, analysis +-- Magic number -> 4 BIOM command_id -> known for sure since it was added in +-- patch 36.sql +INSERT INTO qiita.command_parameter (command_id, parameter_name, parameter_type, required) + VALUES (4, 'analysis', 'analysis', FALSE); +-- The template comand now becomes optional, since it can be added either to +-- an analysis or to a prep template. command_parameter_id known from patch +-- 36.sql +UPDATE qiita.command_parameter SET required = FALSE WHERE command_parameter_id = 34; diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index 5cc6f40b5..97249dbee 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -115,6 +115,7 @@
Table analysis_processing_job
analysis_id bigint NOT NULL
processing_job_id varchar NOT NULL
Indexes
idx_analysis_processing_job primary key ON analysis_id, processing_job_id
idx_analysis_processing_job ON analysis_id
idx_analysis_processing_job ON processing_job_id
Foreign Keys
fk_analysis_processing_job ( analysis_id ) ref analysis (analysis_id)
fk_analysis_processing_job_pj ( processing_job_id ) ref processing_job (processing_job_id) fk_analysis_logging ( logging_id ) ref logging (logging_id)
+ @@ -1661,8 +1662,8 @@ Controlled Vocabulary]]> - + analysis tables diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index 76c8daed1..44bc7c3ed 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -852,17 +852,7 @@ analysis_artifact references artifact ( artifact_id ) artifact_id - Foreign Key fk_analysis_processing_job - analysis_processing_job references analysis ( analysis_id ) - -analysis_id - Foreign Key fk_analysis_processing_job_pj - analysis_processing_job references processing_job ( processing_job_id ) - -processing_job_id + analysis_artifact references artifact ( artifact_id )' style='fill:#a1a0a0;'>artifact_id Foreign Key fk_analysis_user analysis references qiita_user ( email ) @@ -877,7 +867,17 @@ analysis references logging ( logging_id ) logging_id + analysis references logging ( logging_id )' style='fill:#a1a0a0;'>logging_id + Foreign Key fk_analysis_processing_job + analysis_processing_job references analysis ( analysis_id ) + +analysis_id + Foreign Key fk_analysis_processing_job_pj + analysis_processing_job references processing_job ( processing_job_id ) + +processing_job_id controlled_vocab_valuesTable qiita.controlled_vocab_values @@ -922,9 +922,8 @@ loggingTable qiita.logging Primary Key ( logging_id ) logging_idlogging_id bigserial not null -Referred by processing_job ( logging_id ) -Referred by analysis ( logging_id ) -Referred by analysis ( logging_id ) +Referred by analysis ( logging_id ) +Referred by processing_job ( logging_id ) timetime timestamp not null Time the error was thrown Index ( severity_id ) @@ -1991,17 +1990,6 @@ data_typedata_type varchar not null Data type (16S, metabolome, etc) the job will use - - - -analysis_processing_jobTable qiita.analysis_processing_job - Primary Key ( analysis_id, processing_job_id ) Index ( analysis_id ) -analysis_idanalysis_id bigint not null -References analysis ( analysis_id ) - Primary Key ( analysis_id, processing_job_id ) Index ( processing_job_id ) -processing_job_idprocessing_job_id varchar not null -References processing_job ( processing_job_id ) - @@ -2034,6 +2022,18 @@ logging_idlogging_id bigint References logging ( logging_id ) + + + +analysis_processing_jobTable qiita.analysis_processing_job + Primary Key ( analysis_id, processing_job_id ) Index ( analysis_id ) +analysis_idanalysis_id bigint not null +References analysis ( analysis_id ) + Primary Key ( analysis_id, processing_job_id ) Index ( processing_job_id ) +processing_job_idprocessing_job_id varchar not null +References processing_job ( processing_job_id ) + data_typedata_type varchar not null +

@@ -5655,49 +5655,6 @@
-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table analysis_processing_job
analysis_id bigint NOT NULL
processing_job_id varchar NOT NULL
Indexes
idx_analysis_processing_job primary key ON analysis_id, processing_job_id
idx_analysis_processing_job ON analysis_id
idx_analysis_processing_job ON processing_job_id
Foreign Keys
fk_analysis_processing_job ( analysis_id ) ref analysis (analysis_id)
fk_analysis_processing_job_pj ( processing_job_id ) ref processing_job (processing_job_id)
-

@@ -5786,4 +5743,52 @@
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table analysis_processing_job
analysis_id bigint NOT NULL
processing_job_id varchar NOT NULL
data_type varchar NOT NULL
Indexes
idx_analysis_processing_job primary key ON analysis_id, processing_job_id
idx_analysis_processing_job ON analysis_id
idx_analysis_processing_job ON processing_job_id
Foreign Keys
fk_analysis_processing_job ( analysis_id ) ref analysis (analysis_id)
fk_analysis_processing_job_pj ( processing_job_id ) ref processing_job (processing_job_id)
+ \ No newline at end of file From 0cd9f27e23218cc35b495b4c1a715bfebaf66e68 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sat, 14 Jan 2017 22:52:16 -0800 Subject: [PATCH 19/32] Adding REST endpoint to access the analysis metadata --- qiita_db/handlers/analysis.py | 69 ++++++++++++ qiita_db/handlers/tests/test_analysis.py | 106 ++++++++++++++++++ .../test_data/analysis/1_analysis_mapping.txt | 6 + qiita_pet/webserver.py | 4 +- 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 qiita_db/handlers/analysis.py create mode 100644 qiita_db/handlers/tests/test_analysis.py diff --git a/qiita_db/handlers/analysis.py b/qiita_db/handlers/analysis.py new file mode 100644 index 000000000..4a311a512 --- /dev/null +++ b/qiita_db/handlers/analysis.py @@ -0,0 +1,69 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014--, The Qiita Development Team. +# +# Distributed under the terms of the BSD 3-clause License. +# +# The full license is in the file LICENSE, distributed with this software. +# ----------------------------------------------------------------------------- + +from tornado.web import HTTPError + +import qiita_db as qdb +from .oauth2 import OauthBaseHandler, authenticate_oauth + + +def _get_analysis(a_id): + """Returns the analysis with the given `a_id` if it exists + + Parameters + ---------- + a_id : str + The analysis id + + Returns + ------- + qiita_db.analysis.Analysis + The requested analysis + + Raises + ------ + HTTPError + If the analysis does not exist, with error code 404 + If there is a problem instantiating the analysis, with error code 500 + """ + try: + a_id = int(a_id) + a = qdb.analysis.Analysis(a_id) + except qdb.exceptions.QiitaDBUnknownIDError: + raise HTTPError(404) + except Exception as e: + raise HTTPError(500, 'Error instantiating analysis %s: %s' + % (a_id, str(e))) + return a + + +class APIAnalysisMetadataHandler(OauthBaseHandler): + @authenticate_oauth + def get(self, analysis_id): + """Retrieves the analysis metadata + + Parameters + ---------- + analysis_id : str + The id of the analysis whose information is being retrieved + + Returns + ------- + dict + The contents of the analysis keyed by sample id + """ + with qdb.sql_connection.TRN: + a = _get_analysis(analysis_id) + mf_fp = a.mapping_file + response = None + if mf_fp is not None: + df = qdb.metadata_template.util.load_template_to_dataframe( + mf_fp, index='#SampleID') + response = df.to_dict(orient='index') + + self.write(response) diff --git a/qiita_db/handlers/tests/test_analysis.py b/qiita_db/handlers/tests/test_analysis.py new file mode 100644 index 000000000..8a2340204 --- /dev/null +++ b/qiita_db/handlers/tests/test_analysis.py @@ -0,0 +1,106 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014--, The Qiita Development Team. +# +# Distributed under the terms of the BSD 3-clause License. +# +# The full license is in the file LICENSE, distributed with this software. +# ----------------------------------------------------------------------------- + +from unittest import main, TestCase +from json import loads + +from tornado.web import HTTPError + +from qiita_db.handlers.tests.oauthbase import OauthTestingBase +from qiita_db.handlers.analysis import _get_analysis +import qiita_db as qdb + + +class UtilTests(TestCase): + def test_get_analysis(self): + obs = _get_analysis(1) + exp = qdb.analysis.Analysis(1) + self.assertEqual(obs, exp) + + # It doesn't eist + with self.assertRaises(HTTPError): + _get_analysis(100) + + +class APIAnalysisMetadataHandlerTests(OauthTestingBase): + def test_get_does_not_exist(self): + obs = self.get('/qiita_db/analysis/100/metadata/', headers=self.header) + self.assertEqual(obs.code, 404) + + def test_get_no_header(self): + obs = self.get('/qiita_db/analysis/1/metadata/') + self.assertEqual(obs.code, 400) + + def test_get(self): + obs = self.get('/qiita_db/analysis/1/metadata/', headers=self.header) + self.assertEqual(obs.code, 200) + + obs = loads(obs.body) + exp = ['1.SKM4.640180', '1.SKB8.640193', '1.SKD8.640184', + '1.SKM9.640192', '1.SKB7.640196'] + self.assertItemsEqual(obs, exp) + + exp = {'platform': 'Illumina', 'longitude': '95.5088566087', + 'experiment_center': 'ANL', 'center_name': 'ANL', + 'run_center': 'ANL', 'run_prefix': 's_G1_L001_sequences', + 'sample_type': 'ENVO:soil', + 'common_name': 'rhizosphere metagenome', 'samp_size': '.25,g', + 'has_extracted_data': 'True', 'water_content_soil': '0.101', + 'target_gene': '16S rRNA', + 'env_feature': 'ENVO:plant-associated habitat', + 'sequencing_meth': 'Sequencing by synthesis', + 'Description': 'Cannabis Soil Microbiome', 'run_date': '8/1/12', + 'qiita_owner': 'Dude', 'altitude': '0.0', + 'BarcodeSequence': 'TCGACCAAACAC', + 'env_biome': 'ENVO:Temperate grasslands, savannas, and ' + 'shrubland biome', + 'texture': '63.1 sand, 17.7 silt, 19.2 clay', + 'pcr_primers': 'FWD:GTGCCAGCMGCCGCGGTAA; ' + 'REV:GGACTACHVGGGTWTCTAAT', + 'experiment_title': 'Cannabis Soil Microbiome', + 'library_construction_protocol': + 'This analysis was done as in Caporaso et al 2011 Genome ' + 'research. The PCR primers (F515/R806) were developed ' + 'against the V4 region of the 16S rRNA (both bacteria and ' + 'archaea), which we determined would yield optimal ' + 'community clustering with reads of this length using a ' + 'procedure similar to that of ref. 15. [For reference, ' + 'this primer pair amplifies the region 533_786 in the ' + 'Escherichia coli strain 83972 sequence (greengenes ' + 'accession no. prokMSA_id:470367).] The reverse PCR primer ' + 'is barcoded with a 12-base error-correcting Golay code to ' + 'facilitate multiplexing of up to 1,500 samples per lane, ' + 'and both PCR primers contain sequencer adapter regions.', + 'experiment_design_description': + 'micro biome of soil and rhizosphere of cannabis plants ' + 'from CA', + 'study_center': 'CCME', 'physical_location': 'ANL', + 'qiita_prep_id': '1', 'taxon_id': '939928', + 'has_physical_specimen': 'True', 'ph': '6.82', + 'description_duplicate': 'Bucu Rhizo', + 'qiita_study_alias': 'Cannabis Soils', 'sample_center': 'ANL', + 'elevation': '114.0', 'illumina_technology': 'MiSeq', + 'assigned_from_geo': 'n', + 'collection_timestamp': '2011-11-11 13:00:00', + 'latitude': '31.7167821863', + 'LinkerPrimerSequence': 'GTGCCAGCMGCCGCGGTAA', + 'qiita_principal_investigator': 'PIDude', 'host_taxid': '3483', + 'samp_salinity': '7.44', 'host_subject_id': '1001:D2', + 'target_subfragment': 'V4', 'season_environment': 'winter', + 'temp': '15.0', 'emp_status': 'EMP', + 'country': 'GAZ:United States of America', + 'instrument_model': 'Illumina MiSeq', + 'qiita_study_title': 'Identification of the Microbiomes for ' + 'Cannabis Soils', + 'tot_nitro': '1.3', 'depth': '0.15', + 'anonymized_name': 'SKM4', 'tot_org_carb': '3.31'} + self.assertEqual(obs['1.SKM4.640180'], exp) + + +if __name__ == '__main__': + main() diff --git a/qiita_db/support_files/test_data/analysis/1_analysis_mapping.txt b/qiita_db/support_files/test_data/analysis/1_analysis_mapping.txt index e69de29bb..d228b81ba 100644 --- a/qiita_db/support_files/test_data/analysis/1_analysis_mapping.txt +++ b/qiita_db/support_files/test_data/analysis/1_analysis_mapping.txt @@ -0,0 +1,6 @@ +#SampleID BarcodeSequence LinkerPrimerSequence center_name emp_status experiment_center experiment_design_description experiment_title illumina_technology instrument_model library_construction_protocol pcr_primers platform run_center run_date run_prefix samp_size sample_center sequencing_meth study_center target_gene target_subfragment qiita_prep_id altitude anonymized_name assigned_from_geo collection_timestamp common_name country depth description_duplicate elevation env_biome env_feature has_extracted_data has_physical_specimen host_subject_id host_taxid latitude longitude ph physical_location samp_salinity sample_type season_environment taxon_id temp texture tot_nitro tot_org_carb water_content_soil qiita_study_title qiita_study_alias qiita_owner qiita_principal_investigator Description +1.SKM4.640180 TCGACCAAACAC GTGCCAGCMGCCGCGGTAA ANL EMP ANL micro biome of soil and rhizosphere of cannabis plants from CA Cannabis Soil Microbiome MiSeq Illumina MiSeq This analysis was done as in Caporaso et al 2011 Genome research. The PCR primers (F515/R806) were developed against the V4 region of the 16S rRNA (both bacteria and archaea), which we determined would yield optimal community clustering with reads of this length using a procedure similar to that of ref. 15. [For reference, this primer pair amplifies the region 533_786 in the Escherichia coli strain 83972 sequence (greengenes accession no. prokMSA_id:470367).] The reverse PCR primer is barcoded with a 12-base error-correcting Golay code to facilitate multiplexing of up to 1,500 samples per lane, and both PCR primers contain sequencer adapter regions. FWD:GTGCCAGCMGCCGCGGTAA; REV:GGACTACHVGGGTWTCTAAT Illumina ANL 8/1/12 s_G1_L001_sequences .25,g ANL Sequencing by synthesis CCME 16S rRNA V4 1 0.0 SKM4 n 2011-11-11 13:00:00 rhizosphere metagenome GAZ:United States of America 0.15 Bucu Rhizo 114.0 ENVO:Temperate grasslands, savannas, and shrubland biome ENVO:plant-associated habitat True True 1001:D2 3483 31.7167821863 95.5088566087 6.82 ANL 7.44 ENVO:soil winter 939928 15.0 63.1 sand, 17.7 silt, 19.2 clay 1.3 3.31 0.101 Identification of the Microbiomes for Cannabis Soils Cannabis Soils Dude PIDude Cannabis Soil Microbiome +1.SKB8.640193 AGCGCTCACATC GTGCCAGCMGCCGCGGTAA ANL EMP ANL micro biome of soil and rhizosphere of cannabis plants from CA Cannabis Soil Microbiome MiSeq Illumina MiSeq This analysis was done as in Caporaso et al 2011 Genome research. The PCR primers (F515/R806) were developed against the V4 region of the 16S rRNA (both bacteria and archaea), which we determined would yield optimal community clustering with reads of this length using a procedure similar to that of ref. 15. [For reference, this primer pair amplifies the region 533_786 in the Escherichia coli strain 83972 sequence (greengenes accession no. prokMSA_id:470367).] The reverse PCR primer is barcoded with a 12-base error-correcting Golay code to facilitate multiplexing of up to 1,500 samples per lane, and both PCR primers contain sequencer adapter regions. FWD:GTGCCAGCMGCCGCGGTAA; REV:GGACTACHVGGGTWTCTAAT Illumina ANL 8/1/12 s_G1_L001_sequences .25,g ANL Sequencing by synthesis CCME 16S rRNA V4 1 0.0 SKB8 n 2011-11-11 13:00:00 root metagenome GAZ:United States of America 0.15 Burmese root 114.0 ENVO:Temperate grasslands, savannas, and shrubland biome ENVO:plant-associated habitat True True 1001:M7 3483 74.0894932572 65.3283470202 6.94 ANL 7.15 ENVO:soil winter 1118232 15.0 64.6 sand, 17.6 silt, 17.8 clay 1.41 5.0 0.164 Identification of the Microbiomes for Cannabis Soils Cannabis Soils Dude PIDude Cannabis Soil Microbiome +1.SKD8.640184 TGAGTGGTCTGT GTGCCAGCMGCCGCGGTAA ANL EMP ANL micro biome of soil and rhizosphere of cannabis plants from CA Cannabis Soil Microbiome MiSeq Illumina MiSeq This analysis was done as in Caporaso et al 2011 Genome research. The PCR primers (F515/R806) were developed against the V4 region of the 16S rRNA (both bacteria and archaea), which we determined would yield optimal community clustering with reads of this length using a procedure similar to that of ref. 15. [For reference, this primer pair amplifies the region 533_786 in the Escherichia coli strain 83972 sequence (greengenes accession no. prokMSA_id:470367).] The reverse PCR primer is barcoded with a 12-base error-correcting Golay code to facilitate multiplexing of up to 1,500 samples per lane, and both PCR primers contain sequencer adapter regions. FWD:GTGCCAGCMGCCGCGGTAA; REV:GGACTACHVGGGTWTCTAAT Illumina ANL 8/1/12 s_G1_L001_sequences .25,g ANL Sequencing by synthesis CCME 16S rRNA V4 1 0.0 SKD8 n 2011-11-11 13:00:00 root metagenome GAZ:United States of America 0.15 Diesel Root 114.0 ENVO:Temperate grasslands, savannas, and shrubland biome ENVO:plant-associated habitat True True 1001:D9 3483 57.571893782 32.5563076447 6.8 ANL 7.1 ENVO:soil winter 1118232 15.0 66 sand, 16.3 silt, 17.7 clay 1.51 4.32 0.178 Identification of the Microbiomes for Cannabis Soils Cannabis Soils Dude PIDude Cannabis Soil Microbiome +1.SKM9.640192 AGCAGGCACGAA GTGCCAGCMGCCGCGGTAA ANL EMP ANL micro biome of soil and rhizosphere of cannabis plants from CA Cannabis Soil Microbiome MiSeq Illumina MiSeq This analysis was done as in Caporaso et al 2011 Genome research. The PCR primers (F515/R806) were developed against the V4 region of the 16S rRNA (both bacteria and archaea), which we determined would yield optimal community clustering with reads of this length using a procedure similar to that of ref. 15. [For reference, this primer pair amplifies the region 533_786 in the Escherichia coli strain 83972 sequence (greengenes accession no. prokMSA_id:470367).] The reverse PCR primer is barcoded with a 12-base error-correcting Golay code to facilitate multiplexing of up to 1,500 samples per lane, and both PCR primers contain sequencer adapter regions. FWD:GTGCCAGCMGCCGCGGTAA; REV:GGACTACHVGGGTWTCTAAT Illumina ANL 8/1/12 s_G1_L001_sequences .25,g ANL Sequencing by synthesis CCME 16S rRNA V4 1 0.0 SKM9 n 2011-11-11 13:00:00 root metagenome GAZ:United States of America 0.15 Bucu Roots 114.0 ENVO:Temperate grasslands, savannas, and shrubland biome ENVO:plant-associated habitat True True 1001:B8 3483 12.7065957714 84.9722975792 6.82 ANL 7.44 ENVO:soil winter 1118232 15.0 63.1 sand, 17.7 silt, 19.2 clay 1.3 3.31 0.101 Identification of the Microbiomes for Cannabis Soils Cannabis Soils Dude PIDude Cannabis Soil Microbiome +1.SKB7.640196 CGGCCTAAGTTC GTGCCAGCMGCCGCGGTAA ANL EMP ANL micro biome of soil and rhizosphere of cannabis plants from CA Cannabis Soil Microbiome MiSeq Illumina MiSeq This analysis was done as in Caporaso et al 2011 Genome research. The PCR primers (F515/R806) were developed against the V4 region of the 16S rRNA (both bacteria and archaea), which we determined would yield optimal community clustering with reads of this length using a procedure similar to that of ref. 15. [For reference, this primer pair amplifies the region 533_786 in the Escherichia coli strain 83972 sequence (greengenes accession no. prokMSA_id:470367).] The reverse PCR primer is barcoded with a 12-base error-correcting Golay code to facilitate multiplexing of up to 1,500 samples per lane, and both PCR primers contain sequencer adapter regions. FWD:GTGCCAGCMGCCGCGGTAA; REV:GGACTACHVGGGTWTCTAAT Illumina ANL 8/1/12 s_G1_L001_sequences .25,g ANL Sequencing by synthesis CCME 16S rRNA V4 1 0.0 SKB7 n 2011-11-11 13:00:00 root metagenome GAZ:United States of America 0.15 Burmese root 114.0 ENVO:Temperate grasslands, savannas, and shrubland biome ENVO:plant-associated habitat True True 1001:M8 3483 13.089194595 92.5274472082 6.94 ANL 7.15 ENVO:soil winter 1118232 15.0 64.6 sand, 17.6 silt, 17.8 clay 1.41 5.0 0.164 Identification of the Microbiomes for Cannabis Soils Cannabis Soils Dude PIDude Cannabis Soil Microbiome diff --git a/qiita_pet/webserver.py b/qiita_pet/webserver.py index 42952d478..acdce5d39 100644 --- a/qiita_pet/webserver.py +++ b/qiita_pet/webserver.py @@ -55,6 +55,7 @@ from qiita_db.handlers.plugin import ( PluginHandler, CommandHandler, CommandListHandler, CommandActivateHandler, ReloadPluginAPItestHandler) +from qiita_db.handlers.analysis import APIAnalysisMetadataHandler from qiita_pet import uimodules from qiita_db.util import get_mountpoint if qiita_config.portal == "QIITA": @@ -164,7 +165,8 @@ def __init__(self): CommandActivateHandler), (r"/qiita_db/plugins/(.*)/(.*)/commands/(.*)/", CommandHandler), (r"/qiita_db/plugins/(.*)/(.*)/commands/", CommandListHandler), - (r"/qiita_db/plugins/(.*)/(.*)/", PluginHandler) + (r"/qiita_db/plugins/(.*)/(.*)/", PluginHandler), + (r"/qiita_db/analysis/(.*)/metadata/", APIAnalysisMetadataHandler) ] if qiita_config.portal == "QIITA": # Add portals editing pages only on main portal From 82075132d3614640fe71a36f856255cf1575b00a Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sun, 15 Jan 2017 12:44:04 -0800 Subject: [PATCH 20/32] Adding private jobs to plugin --- qiita_db/support_files/patches/47.sql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 829ed7063..89b985853 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -74,3 +74,22 @@ DELETE FROM qiita.job_results_filepath WHERE job_id IN ( SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id)); DELETE FROM qiita.job J WHERE NOT EXISTS ( SELECT * FROM qiita.analysis_job AJ WHERE J.job_id = AJ.job_id); + +-- We are going to add a new special software type, and a new software. +-- This is going to be used internally by Qiita, so submit the private jobs. +-- This is needed for the analysis. +INSERT INTO qiita.software_type (software_type, description) + VALUES ('private', 'Internal Qiita jobs'); + +DO $do$ +DECLARE + qiita_sw_id bigint; +BEGIN + INSERT INTO qiita.software (name, version, description, environment_script, start_script, software_type_id, active) + VALUES ('Qiita', 'alpha', 'Internal Qiita jobs', 'source activate qiita', 'qiita-private', 3, True) + RETURNING software_id INTO qiita_sw_id; + + INSERT INTO qiita.software_command (software_id, name, description) + VALUES (qiita_sw_id, '%s', '%s') + RETURNING coommand_id INTO +END $do$ From f307cd43ddfd8947cc93aba5c67f313b01615124 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sun, 15 Jan 2017 12:44:57 -0800 Subject: [PATCH 21/32] Fixing typo --- qiita_db/support_files/patches/47.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 80c545b43..daa603975 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -36,7 +36,7 @@ ALTER TABLE qiita.analysis DROP COLUMN analysis_status_id; CREATE TABLE qiita.analysis_processing_job ( analysis_id bigint NOT NULL, processing_job_id uuid NOT NULL, - datatype varchar NOT NULL, + data_type varchar NOT NULL, CONSTRAINT idx_analysis_processing_job PRIMARY KEY ( analysis_id, processing_job_id ) ) ; From b3936c8f6bd30803ee0702fbb9b5d2f6204b5809 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:07:25 -0800 Subject: [PATCH 22/32] Fixing the processing jobs complete --- qiita_db/processing_job.py | 84 +++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/qiita_db/processing_job.py b/qiita_db/processing_job.py index a950c47d9..47d404907 100644 --- a/qiita_db/processing_job.py +++ b/qiita_db/processing_job.py @@ -376,30 +376,49 @@ def release(self): qdb.sql_connection.TRN.add(sql, [self.id]) a_info = qdb.sql_connection.TRN.execute_fetchlast() - atype = a_info['artifact_type'] - filepaths = a_info['filepaths'] provenance = loads(self.parameters.values['provenance']) job = ProcessingJob(provenance['job']) - parents = job.input_artifacts - params = job.parameters + if 'data_type' in a_info: + # This job is resulting from a private job + parents = None + params = None + cmd_out_id = None + data_type = a_info['data_type'] + analysis = qdb.analysis.Analysis( + job.parameters.values['analysis']) + a_info = a_info['artifact_data'] + else: + # This job is resulting from a plugin job + parents = job.input_artifacts + params = job.parameters + cmd_out_id = provenance['cmd_out_id'] + analysis = None + data_type = None # Create the artifact + atype = a_info['artifact_type'] + filepaths = a_info['filepaths'] a = qdb.artifact.Artifact.create( filepaths, atype, parents=parents, - processing_parameters=params) + processing_parameters=params, + analysis=analysis, data_type=data_type) - cmd_out_id = provenance['cmd_out_id'] - mapping = {cmd_out_id: a.id} self._set_status('success') + mapping = {} + if cmd_out_id is not None: + mapping = {cmd_out_id: a.id} + return mapping def release_validators(self): """Allows all the validator job spawned by this job to complete""" with qdb.sql_connection.TRN: - if self.command.software.type != 'artifact transformation': + if self.command.software.type not in ('artifact transformation', + 'private'): raise qdb.exceptions.QiitaDBOperationNotPermittedError( - "Only artifact transformation jobs can release validators") + "Only artifact transformation and private jobs can " + "release validators") # Check if all the validators are ready by checking that there is # no validator processing job whose status is not waiting @@ -429,16 +448,17 @@ def release_validators(self): vjob = ProcessingJob(jid) mapping.update(vjob.release()) - sql = """INSERT INTO - qiita.artifact_output_processing_job - (artifact_id, processing_job_id, - command_output_id) - VALUES (%s, %s, %s)""" - sql_args = [[aid, self.id, outid] - for outid, aid in viewitems(mapping)] - qdb.sql_connection.TRN.add(sql, sql_args, many=True) - - self._update_and_launch_children(mapping) + if mapping: + sql = """INSERT INTO + qiita.artifact_output_processing_job + (artifact_id, processing_job_id, + command_output_id) + VALUES (%s, %s, %s)""" + sql_args = [[aid, self.id, outid] + for outid, aid in viewitems(mapping)] + qdb.sql_connection.TRN.add(sql, sql_args, many=True) + + self._update_and_launch_children(mapping) self._set_status('success') else: self.step = "Validating outputs (%d remaining)" % remaining @@ -468,6 +488,9 @@ def _complete_artifact_definition(self, artifact_data): # The artifact is a result from a previous job provenance = loads(job_params['provenance']) job = ProcessingJob(provenance['job']) + if provenance.get('data_type') is not None: + artifact_data = {'data_type': provenance['data_type'], + 'artifact_data': artifact_data} sql = """UPDATE qiita.processing_job_validator SET artifact_info = %s @@ -479,11 +502,26 @@ def _complete_artifact_definition(self, artifact_data): self._set_status('waiting') job.release_validators() else: - # The artifact is uploaded by the user - pt = qdb.metadata_template.prep_template.PrepTemplate( - job_params['template']) + # The artifact is uploaded by the user or is the initial + # artifact of an analysis + if job_params['analysis'] is not None: + pt = None + an = qdb.analysis.Analysis(job_params['analysis']) + sql = """SELECT data_type + FROM qiita.analysis_processing_job + WHERE analysis_id = %s + AND processing_job_id = %s""" + qdb.sql_connection.TRN.add(sql, [an.id, self.id]) + data_type = qdb.sql_connection.TRN.execute_fetchlast() + else: + pt = qdb.metadata_template.prep_template.PrepTemplate( + job_params['template']) + an = None + data_type = None + qdb.artifact.Artifact.create( - filepaths, atype, prep_template=pt) + filepaths, atype, prep_template=pt, analysis=an, + data_type=data_type) self._set_status('success') def _complete_artifact_transformation(self, artifacts_data): From 19c3f7ddc662c2fb54aaa9f4b96fc74c5aad83d9 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:10:52 -0800 Subject: [PATCH 23/32] Removing the old job code --- qiita_db/__init__.py | 3 +- qiita_db/job.py | 611 ------------------ qiita_db/test/test_processing_job.py | 922 --------------------------- 3 files changed, 1 insertion(+), 1535 deletions(-) delete mode 100644 qiita_db/job.py delete mode 100644 qiita_db/test/test_processing_job.py diff --git a/qiita_db/__init__.py b/qiita_db/__init__.py index a5c095bb5..e9e71d654 100644 --- a/qiita_db/__init__.py +++ b/qiita_db/__init__.py @@ -16,7 +16,6 @@ import environment_manager import exceptions import investigation -import job import logger import meta_util import ontology @@ -32,7 +31,7 @@ __version__ = "0.2.0-dev" __all__ = ["analysis", "artifact", "base", "commands", "environment_manager", - "exceptions", "investigation", "job", "logger", "meta_util", + "exceptions", "investigation", "logger", "meta_util", "ontology", "portal", "reference", "search", "software", "sql_connection", "study", "user", "util", "metadata_template", "processing_job", "private"] diff --git a/qiita_db/job.py b/qiita_db/job.py deleted file mode 100644 index 2cb016717..000000000 --- a/qiita_db/job.py +++ /dev/null @@ -1,611 +0,0 @@ -r""" -Data objects (:mod: `qiita_db.data`) -==================================== - -..currentmodule:: qiita_db.data - -This module provides functionality for creating, running, and storing results -of jobs in an analysis. It also provides the ability to query what commmands -are available for jobs, as well as the options for these commands. - -Classes -------- - -..autosummary:: - :toctree: generated/ - - Job - Command -""" -# ----------------------------------------------------------------------------- -# Copyright (c) 2014--, The Qiita Development Team. -# -# Distributed under the terms of the BSD 3-clause License. -# -# The full license is in the file LICENSE, distributed with this software. -# ----------------------------------------------------------------------------- -from __future__ import division -from json import loads -from os.path import join, relpath -from glob import glob -from functools import partial -from collections import defaultdict - -import qiita_db as qdb - - -class Job(qdb.base.QiitaStatusObject): - """ - Job object to access to the Qiita Job information - - Attributes - ---------- - datatype - command - options - results - error - - Methods - ------- - set_error - add_results - """ - _table = "job" - - def _lock_job(self): - """Raises QiitaDBStatusError if study is public""" - if self.check_status(("completed", "error")): - raise qdb.exceptions.QiitaDBStatusError( - "Can't change status of finished job!") - - def _status_setter_checks(self): - r"""Perform a check to make sure not setting status away from completed - or errored - """ - self._lock_job() - - @staticmethod - def get_commands(): - """returns commands available with the options as well - - Returns - ------- - list of command objects - """ - return Command.create_list() - - @classmethod - def exists(cls, datatype, command, options, analysis, - input_file_reference, input_file_software_command, - return_existing=False): - """Checks if the given job already exists - - Parameters - ---------- - datatype : str - Datatype the job is operating on - command : str - The name of the command run on the data - options : dict - Options for the command in the format {option: value} - analysis : Analysis object - The analysis the job will be attached to on creation - input_file_reference : Reference object - The reference object used to create the input file - input_file_software_command: Software.Command object - The software command object used to create the input file - return_existing : bool, optional - If True, function will return the instatiated Job object for the - matching job. Default False - - Returns - ------- - bool - Whether the job exists or not - Job or None, optional - If return_existing is True, the Job object of the matching job or - None if none exists - """ - with qdb.sql_connection.TRN: - # check passed arguments and grab analyses for matching jobs - datatype_id = qdb.util.convert_to_id(datatype, "data_type") - sql = "SELECT command_id FROM qiita.command WHERE name = %s" - qdb.sql_connection.TRN.add(sql, [command]) - command_id = qdb.sql_connection.TRN.execute_fetchlast() - - opts_json = qdb.util.params_dict_to_json(options) - sql = """SELECT DISTINCT analysis_id, job_id - FROM qiita.analysis_job - JOIN qiita.{0} USING (job_id) - WHERE data_type_id = %s - AND command_id = %s - AND options = %s - AND input_file_reference_id = %s - AND input_file_software_command_id = %s - """.format(cls._table) - rid = (input_file_reference.id - if input_file_reference is not None else None) - cid = (input_file_software_command.id - if input_file_software_command is not None else None) - qdb.sql_connection.TRN.add( - sql, [datatype_id, command_id, opts_json, rid, cid]) - analyses = qdb.sql_connection.TRN.execute_fetchindex() - - if not analyses and return_existing: - # stop looking since we have no possible matches - return False, None - elif not analyses: - return False - - # build the samples dict as list of samples keyed to - # their artifact_id - sql = """SELECT artifact_id, array_agg( - sample_id ORDER BY sample_id) - FROM qiita.analysis_sample - WHERE analysis_id = %s GROUP BY artifact_id""" - qdb.sql_connection.TRN.add(sql, [analysis.id]) - samples = dict(qdb.sql_connection.TRN.execute_fetchindex()) - - # check passed analyses' samples dict against all found analyses - matched_job = None - for aid, jid in analyses: - # build the samples dict for a found analysis - qdb.sql_connection.TRN.add(sql, [aid]) - comp_samples = dict( - qdb.sql_connection.TRN.execute_fetchindex()) - - # compare samples and stop checking if a match is found - matched_samples = samples == comp_samples - if matched_samples: - matched_job = jid - break - - if return_existing: - return matched_samples, (cls(matched_job) if matched_job - else None) - - return matched_samples - - @classmethod - def delete(cls, jobid): - """Removes a job and all files attached to it - - Parameters - ---------- - jobid : int - ID of the job to delete - - Notes - ----- - This function will remove a job from all analyses it is attached to in - analysis_job table, as well as the job itself from the job table. All - files and references to files for the job will be removed from the - filepath and job_results_filepath tables. All the job's files on the - filesystem will also be removed. - """ - with qdb.sql_connection.TRN: - # store filepath info for later use - sql = """SELECT filepath, filepath_id - FROM qiita.filepath - JOIN qiita.job_results_filepath USING (filepath_id) - WHERE job_id = %s""" - args = [jobid] - qdb.sql_connection.TRN.add(sql, args) - filepaths = qdb.sql_connection.TRN.execute_fetchindex() - - # remove fiepath links in DB - sql = "DELETE FROM qiita.job_results_filepath WHERE job_id = %s" - qdb.sql_connection.TRN.add(sql, args) - - sql = "DELETE FROM qiita.filepath WHERE filepath_id IN %s" - qdb.sql_connection.TRN.add(sql, [tuple(fp[1] for fp in filepaths)]) - - # remove job - sql = "DELETE FROM qiita.analysis_job WHERE job_id = %s" - qdb.sql_connection.TRN.add(sql, args) - sql = "DELETE FROM qiita.collection_job WHERE job_id = %s" - qdb.sql_connection.TRN.add(sql, args) - sql = "DELETE FROM qiita.job WHERE job_id = %s" - qdb.sql_connection.TRN.add(sql, args) - - qdb.sql_connection.TRN.execute() - - @classmethod - def create(cls, datatype, command, options, analysis, - input_file_reference, input_file_software_command, - return_existing=False): - """Creates a new job on the database - - Parameters - ---------- - datatype : str - The datatype in which this job applies - command : str - The name of the command executed in this job - analysis : Analysis object - The analysis which this job belongs to - input_file_reference : Reference object - The reference object used to create the input file - input_file_software_command: Software.Command object - The software command object used to create the input file - return_existing : bool, optional - If True, returns an instantiated Job object pointing to an already - existing job with the given parameters. Default False - - Returns - ------- - Job object - The newly created job - - Raises - ------ - QiitaDBDuplicateError - return_existing is False and an exact duplicate of the job already - exists in the DB. - """ - with qdb.sql_connection.TRN: - analysis_sql = """INSERT INTO qiita.analysis_job - (analysis_id, job_id) VALUES (%s, %s)""" - exists, job = cls.exists(datatype, command, options, analysis, - input_file_reference, - input_file_software_command, - return_existing=True) - - if exists: - if return_existing: - # add job to analysis - qdb.sql_connection.TRN.add( - analysis_sql, [analysis.id, job.id]) - qdb.sql_connection.TRN.execute() - return job - else: - raise qdb.exceptions.QiitaDBDuplicateError( - "Job", "datatype: %s, command: %s, options: %s, " - "analysis: %s" - % (datatype, command, options, analysis.id)) - - # Get the datatype and command ids from the strings - datatype_id = qdb.util.convert_to_id(datatype, "data_type") - sql = "SELECT command_id FROM qiita.command WHERE name = %s" - qdb.sql_connection.TRN.add(sql, [command]) - command_id = qdb.sql_connection.TRN.execute_fetchlast() - opts_json = qdb.util.params_dict_to_json(options) - - # Create the job and return it - sql = """INSERT INTO qiita.{0} (data_type_id, job_status_id, - command_id, options, - input_file_reference_id, - input_file_software_command_id) - VALUES (%s, %s, %s, %s, %s, %s) - RETURNING job_id""".format(cls._table) - rid = (input_file_reference.id - if input_file_reference is not None else None) - cid = (input_file_software_command.id - if input_file_software_command is not None else None) - qdb.sql_connection.TRN.add( - sql, [datatype_id, 1, command_id, opts_json, rid, cid]) - job_id = qdb.sql_connection.TRN.execute_fetchlast() - - # add job to analysis - qdb.sql_connection.TRN.add(analysis_sql, [analysis.id, job_id]) - qdb.sql_connection.TRN.execute() - - return cls(job_id) - - @property - def datatype(self): - with qdb.sql_connection.TRN: - sql = """SELECT data_type - FROM qiita.data_type - WHERE data_type_id = ( - SELECT data_type_id - FROM qiita.{0} - WHERE job_id = %s)""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return qdb.sql_connection.TRN.execute_fetchlast() - - @property - def command(self): - """Returns the command of the job as (name, command) - - Returns - ------- - str - command run by the job - """ - with qdb.sql_connection.TRN: - sql = """SELECT name, command - FROM qiita.command - WHERE command_id = ( - SELECT command_id - FROM qiita.{0} - WHERE job_id = %s)""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - # We only want the first row (the only one present) - return qdb.sql_connection.TRN.execute_fetchindex()[0] - - @property - def options(self): - """Options used in the job - - Returns - ------- - dict - options in the format {option: setting} - """ - with qdb.sql_connection.TRN: - sql = """SELECT options FROM qiita.{0} - WHERE job_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - db_opts = qdb.sql_connection.TRN.execute_fetchlast() - opts = loads(db_opts) if db_opts else {} - - sql = """SELECT command, output - FROM qiita.command - WHERE command_id = ( - SELECT command_id - FROM qiita.{0} - WHERE job_id = %s)""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - db_comm = qdb.sql_connection.TRN.execute_fetchindex()[0] - - out_opt = loads(db_comm[1]) - _, mp, _ = qdb.util.get_mountpoint('job', retrieve_subdir=True)[0] - join_f = partial(join, mp) - for k in out_opt: - opts[k] = join_f("%s_%s_%s" % (self._id, db_comm[0], - k.strip("-"))) - return opts - - @options.setter - def options(self, opts): - """ Sets the options for the job - - Parameters - ---------- - opts: dict - The options for the command in format {option: value} - """ - with qdb.sql_connection.TRN: - # make sure job is editable - self._lock_job() - - # JSON the options dictionary - opts_json = qdb.util.params_dict_to_json(opts) - # Add the options to the job - sql = """UPDATE qiita.{0} SET options = %s - WHERE job_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [opts_json, self._id]) - qdb.sql_connection.TRN.execute() - - @property - def results(self): - """List of job result filepaths - - Returns - ------- - list - Filepaths to the result files - """ - # Select results filepaths and filepath types from the database - with qdb.sql_connection.TRN: - _, basedir = qdb.util.get_mountpoint('job')[0] - sql = """SELECT filepath, filepath_type - FROM qiita.filepath - JOIN qiita.filepath_type USING (filepath_type_id) - JOIN qiita.job_results_filepath USING (filepath_id) - WHERE job_id = %s""" - qdb.sql_connection.TRN.add(sql, [self._id]) - results = qdb.sql_connection.TRN.execute_fetchindex() - - def add_html(basedir, check_dir, result_fps): - for res in glob(join(basedir, check_dir, "*.htm")) + \ - glob(join(basedir, check_dir, "*.html")): - result_fps.append(relpath(res, basedir)) - - # create new list, with relative paths from db base - result_fps = [] - for fp in results: - if fp[1] == "directory": - # directory, so all html files in it are results - # first, see if we have any in the main directory - add_html(basedir, fp[0], result_fps) - # now do all subdirectories - add_html(basedir, join(fp[0], "*"), result_fps) - else: - # result is exact filepath given - result_fps.append(fp[0]) - return result_fps - - @property - def error(self): - """String with an error message, if the job failed - - Returns - ------- - str or None - error message/traceback for a job, or None if none exists - """ - with qdb.sql_connection.TRN: - sql = "SELECT log_id FROM qiita.{0} WHERE job_id = %s".format( - self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - logging_id = qdb.sql_connection.TRN.execute_fetchlast() - return (qdb.logger.LogEntry(logging_id) - if logging_id is not None else None) - -# --- Functions --- - def set_error(self, msg): - """Logs an error for the job - - Parameters - ---------- - msg : str - Error message/stacktrace if available - """ - with qdb.sql_connection.TRN: - log_entry = qdb.logger.LogEntry.create( - 'Runtime', msg, info={'job': self._id}) - self._lock_job() - - err_id = qdb.util.convert_to_id('error', 'job_status', 'status') - # attach the error to the job and set to error - sql = """UPDATE qiita.{0} SET log_id = %s, job_status_id = %s - WHERE job_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [log_entry.id, err_id, self._id]) - qdb.sql_connection.TRN.execute() - - def add_results(self, results): - """Adds a list of results to the results - - Parameters - ---------- - results : list of tuples - filepath information to add to job, in format - [(filepath, type), ...] - Where type is the filepath type of the filepath passed - - Notes - ----- - Curently available file types are: - biom, directory, plain_text - """ - with qdb.sql_connection.TRN: - self._lock_job() - # convert all file type text to file type ids - res_ids = [(fp, qdb.util.convert_to_id(fptype, "filepath_type")) - for fp, fptype in results] - file_ids = qdb.util.insert_filepaths( - res_ids, self._id, self._table, "filepath", move_files=False) - - # associate filepaths with job - sql = """INSERT INTO qiita.{0}_results_filepath - (job_id, filepath_id) - VALUES (%s, %s)""".format(self._table) - qdb.sql_connection.TRN.add( - sql, [[self._id, fid] for fid in file_ids], many=True) - qdb.sql_connection.TRN.execute() - - -class Command(object): - """Holds all information on the commands available - - This will be an in-memory representation because the command table is - considerably more static than other objects tables, changing only with new - QIIME releases. - - Attributes - ---------- - name - command - input_opts - required_opts - optional_opts - output_opts - """ - @classmethod - def create_list(cls): - """Creates list of all available commands - - Returns - ------- - list of Command objects - """ - with qdb.sql_connection.TRN: - qdb.sql_connection.TRN.add("SELECT * FROM qiita.command") - commands = qdb.sql_connection.TRN.execute_fetchindex() - # create the list of command objects - return [cls(c["name"], c["command"], c["input"], c["required"], - c["optional"], c["output"]) for c in commands] - - @classmethod - def get_commands_by_datatype(cls, datatypes=None): - """Returns the commands available for all or a subset of the datatypes - - Parameters - ---------- - datatypes : list of str, optional - List of the datatypes to get commands for. Default is all datatypes - - Returns - ------- - dict of lists of Command objects - Returns commands in the format {datatype: [com name1, com name2]} - - Notes - ----- - If no datatypes are passed, the function will default to returning all - datatypes available. - """ - with qdb.sql_connection.TRN: - # get the ids of the datatypes to get commands for - if datatypes is not None: - datatype_info = [(qdb.util.convert_to_id(dt, "data_type"), dt) - for dt in datatypes] - else: - sql = "SELECT data_type_id, data_type from qiita.data_type" - qdb.sql_connection.TRN.add(sql) - datatype_info = qdb.sql_connection.TRN.execute_fetchindex() - - commands = defaultdict(list) - # get commands for each datatype - sql = """SELECT C.* - FROM qiita.command C - JOIN qiita.command_data_type USING (command_id) - WHERE data_type_id = %s""" - for dt_id, dt in datatype_info: - qdb.sql_connection.TRN.add(sql, [dt_id]) - comms = qdb.sql_connection.TRN.execute_fetchindex() - for comm in comms: - commands[dt].append(cls(comm["name"], comm["command"], - comm["input"], - comm["required"], - comm["optional"], - comm["output"])) - return commands - - def __eq__(self, other): - if type(self) != type(other): - return False - if self.name != other.name: - return False - if self.command != other.command: - return False - if self.input_opts != other.input_opts: - return False - if self.output_opts != other.output_opts: - return False - if self.required_opts != other.required_opts: - return False - if self.optional_opts != other.optional_opts: - return False - return True - - def __ne__(self, other): - return not self.__eq__(other) - - def __init__(self, name, command, input_opts, required_opts, - optional_opts, output_opts): - """Creates the command object - - Parameters: - name : str - Name of the command - command: str - python command to run - input_opts : str - JSON of input options for the command - required_opts : str - JSON of required options for the command - optional_opts : str - JSON of optional options for the command - output_opts : str - JSON of output options for the command - """ - self.name = name - self.command = command - self.input_opts = loads(input_opts) - self.required_opts = loads(required_opts) - self.optional_opts = loads(optional_opts) - self.output_opts = loads(output_opts) diff --git a/qiita_db/test/test_processing_job.py b/qiita_db/test/test_processing_job.py deleted file mode 100644 index 6ad60ee04..000000000 --- a/qiita_db/test/test_processing_job.py +++ /dev/null @@ -1,922 +0,0 @@ -# ----------------------------------------------------------------------------- -# Copyright (c) 2014--, The Qiita Development Team. -# -# Distributed under the terms of the BSD 3-clause License. -# -# The full license is in the file LICENSE, distributed with this software. -# ----------------------------------------------------------------------------- - -from unittest import TestCase, main -from datetime import datetime -from os.path import join -from os import close -from tempfile import mkstemp -from json import dumps -from time import sleep - -import networkx as nx -import pandas as pd - -import qiita_db as qdb -from qiita_core.util import qiita_test_checker -from qiita_core.qiita_settings import qiita_config - - -def _create_job(): - job = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), - qdb.software.Parameters.load( - qdb.software.Command(2), - values_dict={"min_seq_len": 100, "max_seq_len": 1000, - "trim_seq_length": False, "min_qual_score": 25, - "max_ambig": 6, "max_homopolymer": 6, - "max_primer_mismatch": 0, - "barcode_type": "golay_12", - "max_barcode_errors": 1.5, - "disable_bc_correction": False, - "qual_score_window": 0, "disable_primers": False, - "reverse_primers": "disable", - "reverse_primer_mismatches": 0, - "truncate_ambi_bases": False, "input_data": 1})) - return job - - -@qiita_test_checker() -class ProcessingJobUtilTest(TestCase): - def test_system_call(self): - obs_out, obs_err, obs_status = qdb.processing_job._system_call( - 'echo "Test system call stdout"') - - self.assertEqual(obs_out, "Test system call stdout\n") - self.assertEqual(obs_err, "") - self.assertEqual(obs_status, 0) - - def test_system_call_error(self): - obs_out, obs_err, obs_status = qdb.processing_job._system_call( - '>&2 echo "Test system call stderr"; exit 1') - self.assertEqual(obs_out, "") - self.assertEqual(obs_err, "Test system call stderr\n") - self.assertEqual(obs_status, 1) - - def test_job_submitter(self): - # The cmd parameter of the function should be the command that - # actually executes the function. However, in order to avoid executing - # a expensive command, we are just going to pass some other command. - # In case of success, nothing happens, so we just run it and see that - # it doesn't raise an error - job = _create_job() - cmd = 'echo "Test system call stdout"' - qdb.processing_job._job_submitter(job, cmd) - - def test_job_submitter_error(self): - # Same comment as above, but here we are going to force failure, and - # check that the job is updated correctly - job = _create_job() - cmd = '>&2 echo "Test system call stderr"; exit 1' - qdb.processing_job._job_submitter(job, cmd) - self.assertEqual(job.status, 'error') - exp = ("Error submitting job '%s':\nStd output:\nStd error:" - "Test system call stderr\n" % job.id) - self.assertEqual(job.log.msg, exp) - - -@qiita_test_checker() -class ProcessingJobTest(TestCase): - def setUp(self): - self.tester1 = qdb.processing_job.ProcessingJob( - "063e553b-327c-4818-ab4a-adfe58e49860") - self.tester2 = qdb.processing_job.ProcessingJob( - "bcc7ebcd-39c1-43e4-af2d-822e3589f14d") - self.tester3 = qdb.processing_job.ProcessingJob( - "b72369f9-a886-4193-8d3d-f7b504168e75") - self.tester4 = qdb.processing_job.ProcessingJob( - "d19f76ee-274e-4c1b-b3a2-a12d73507c55") - self._clean_up_files = [] - - def _get_all_job_ids(self): - sql = "SELECT processing_job_id FROM qiita.processing_job" - with qdb.sql_connection.TRN: - qdb.sql_connection.TRN.add(sql) - return qdb.sql_connection.TRN.execute_fetchflatten() - - def _wait_for_job(self, job): - while job.status not in ('error', 'success'): - sleep(0.5) - sleep(0.5) - - def test_exists(self): - self.assertTrue(qdb.processing_job.ProcessingJob.exists( - "063e553b-327c-4818-ab4a-adfe58e49860")) - self.assertTrue(qdb.processing_job.ProcessingJob.exists( - "bcc7ebcd-39c1-43e4-af2d-822e3589f14d")) - self.assertTrue(qdb.processing_job.ProcessingJob.exists( - "b72369f9-a886-4193-8d3d-f7b504168e75")) - self.assertTrue(qdb.processing_job.ProcessingJob.exists( - "d19f76ee-274e-4c1b-b3a2-a12d73507c55")) - self.assertFalse(qdb.processing_job.ProcessingJob.exists( - "d19f76ee-274e-4c1b-b3a2-b12d73507c55")) - self.assertFalse(qdb.processing_job.ProcessingJob.exists( - "some-other-string")) - - def test_user(self): - exp_user = qdb.user.User('test@foo.bar') - self.assertEqual(self.tester1.user, exp_user) - self.assertEqual(self.tester2.user, exp_user) - exp_user = qdb.user.User('shared@foo.bar') - self.assertEqual(self.tester3.user, exp_user) - self.assertEqual(self.tester4.user, exp_user) - - def test_command(self): - cmd1 = qdb.software.Command(1) - cmd2 = qdb.software.Command(2) - cmd3 = qdb.software.Command(3) - self.assertEqual(self.tester1.command, cmd1) - self.assertEqual(self.tester2.command, cmd2) - self.assertEqual(self.tester3.command, cmd1) - self.assertEqual(self.tester4.command, cmd3) - - def test_parameters(self): - json_str = ( - '{"max_bad_run_length":3,"min_per_read_length_fraction":0.75,' - '"sequence_max_n":0,"rev_comp_barcode":false,' - '"rev_comp_mapping_barcodes":false,"rev_comp":false,' - '"phred_quality_threshold":3,"barcode_type":"golay_12",' - '"max_barcode_errors":1.5,"input_data":1,"phred_offset":"auto"}') - exp_params = qdb.software.Parameters.load(qdb.software.Command(1), - json_str=json_str) - self.assertEqual(self.tester1.parameters, exp_params) - - json_str = ( - '{"min_seq_len":100,"max_seq_len":1000,"trim_seq_length":false,' - '"min_qual_score":25,"max_ambig":6,"max_homopolymer":6,' - '"max_primer_mismatch":0,"barcode_type":"golay_12",' - '"max_barcode_errors":1.5,"disable_bc_correction":false,' - '"qual_score_window":0,"disable_primers":false,' - '"reverse_primers":"disable","reverse_primer_mismatches":0,' - '"truncate_ambi_bases":false,"input_data":1}') - exp_params = qdb.software.Parameters.load(qdb.software.Command(2), - json_str=json_str) - self.assertEqual(self.tester2.parameters, exp_params) - - json_str = ( - '{"max_bad_run_length":3,"min_per_read_length_fraction":0.75,' - '"sequence_max_n":0,"rev_comp_barcode":false,' - '"rev_comp_mapping_barcodes":true,"rev_comp":false,' - '"phred_quality_threshold":3,"barcode_type":"golay_12",' - '"max_barcode_errors":1.5,"input_data":1,"phred_offset":"auto"}') - exp_params = qdb.software.Parameters.load(qdb.software.Command(1), - json_str=json_str) - self.assertEqual(self.tester3.parameters, exp_params) - - json_str = ( - '{"reference":1,"sortmerna_e_value":1,"sortmerna_max_pos":10000,' - '"similarity":0.97,"sortmerna_coverage":0.97,"threads":1,' - '"input_data":2}') - exp_params = qdb.software.Parameters.load(qdb.software.Command(3), - json_str=json_str) - self.assertEqual(self.tester4.parameters, exp_params) - - def test_input_artifacts(self): - exp = [qdb.artifact.Artifact(1)] - self.assertEqual(self.tester1.input_artifacts, exp) - self.assertEqual(self.tester2.input_artifacts, exp) - self.assertEqual(self.tester3.input_artifacts, exp) - exp = [qdb.artifact.Artifact(2)] - self.assertEqual(self.tester4.input_artifacts, exp) - - def test_status(self): - self.assertEqual(self.tester1.status, 'queued') - self.assertEqual(self.tester2.status, 'running') - self.assertEqual(self.tester3.status, 'success') - self.assertEqual(self.tester4.status, 'error') - - def test_generate_cmd(self): - obs = self.tester1._generate_cmd() - exp = ('qiita-plugin-launcher "source activate qiita" ' - '"start_target_gene" "%s" ' - '"063e553b-327c-4818-ab4a-adfe58e49860" "%s"' - % (qiita_config.base_url, - join(qdb.util.get_work_base_dir(), - "063e553b-327c-4818-ab4a-adfe58e49860"))) - self.assertEqual(obs, exp) - - def test_submit(self): - # In order to test a success, we need to actually run the job, which - # will mean to run split libraries, for example. - pass - - def test_log(self): - self.assertIsNone(self.tester1.log) - self.assertIsNone(self.tester2.log) - self.assertIsNone(self.tester3.log) - self.assertEqual(self.tester4.log, qdb.logger.LogEntry(1)) - - def test_heartbeat(self): - self.assertIsNone(self.tester1.heartbeat) - self.assertEqual(self.tester2.heartbeat, - datetime(2015, 11, 22, 21, 00, 00)) - self.assertEqual(self.tester3.heartbeat, - datetime(2015, 11, 22, 21, 15, 00)) - self.assertEqual(self.tester4.heartbeat, - datetime(2015, 11, 22, 21, 30, 00)) - - def test_step(self): - self.assertIsNone(self.tester1.step) - self.assertEqual(self.tester2.step, 'demultiplexing') - self.assertIsNone(self.tester3.step) - self.assertEqual(self.tester4.step, 'generating demux file') - - def test_children(self): - self.assertEqual(list(self.tester1.children), []) - self.assertEqual(list(self.tester3.children), [self.tester4]) - - def test_update_and_launch_children(self): - # In order to test a success, we need to actually run the children - # jobs, which will mean to run split libraries, for example. - pass - - def test_create(self): - exp_command = qdb.software.Command(1) - json_str = ( - '{"input_data": 1, "max_barcode_errors": 1.5, ' - '"barcode_type": "golay_12", "max_bad_run_length": 3, ' - '"rev_comp": false, "phred_quality_threshold": 3, ' - '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' - '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' - '"phred_offset": "auto"}') - exp_params = qdb.software.Parameters.load(exp_command, - json_str=json_str) - exp_user = qdb.user.User('test@foo.bar') - obs = qdb.processing_job.ProcessingJob.create(exp_user, exp_params) - self.assertEqual(obs.user, exp_user) - self.assertEqual(obs.command, exp_command) - self.assertEqual(obs.parameters, exp_params) - self.assertEqual(obs.status, 'in_construction') - self.assertEqual(obs.log, None) - self.assertEqual(obs.heartbeat, None) - self.assertEqual(obs.step, None) - self.assertTrue(obs in qdb.artifact.Artifact(1).jobs()) - - def test_set_status(self): - job = _create_job() - self.assertEqual(job.status, 'in_construction') - job._set_status('queued') - self.assertEqual(job.status, 'queued') - job._set_status('running') - self.assertEqual(job.status, 'running') - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - job._set_status('queued') - job._set_status('error') - self.assertEqual(job.status, 'error') - job._set_status('running') - self.assertEqual(job.status, 'running') - job._set_status('success') - self.assertEqual(job.status, 'success') - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - job._set_status('running') - - def test_submit_error(self): - job = _create_job() - job._set_status('queued') - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - job.submit() - - def test_complete_multiple_outputs(self): - # This test performs the test of multiple functions at the same - # time. "release", "release_validators" and - # "_set_validator_jobs" are tested here for correct execution. - # Those functions are designed to work together, so it becomes - # really hard to test each of the functions individually for - # successfull execution. - # We need to create a new command with multiple outputs, since - # in the test DB there is no command with such characteristics - cmd = qdb.software.Command.create( - qdb.software.Software(1), - "TestCommand", "Test command", - {'input': ['artifact:["Demultiplexed"]', None]}, - {'out1': 'BIOM', 'out2': 'BIOM'}) - job = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), - qdb.software.Parameters.load( - cmd, - values_dict={"input": 1})) - job._set_status("running") - - fd, fp1 = mkstemp(suffix="_table.biom") - self._clean_up_files.append(fp1) - close(fd) - with open(fp1, 'w') as f: - f.write('\n') - - fd, fp2 = mkstemp(suffix="_table.biom") - self._clean_up_files.append(fp2) - close(fd) - with open(fp2, 'w') as f: - f.write('\n') - - # `job` has 2 output artifacts. Each of these artifacts needs to be - # validated by 2 different validation jobs. We are creating those jobs - # here, and add in the 'procenance' parameter that links the original - # jobs with the validator jobs. - params = qdb.software.Parameters.load( - qdb.software.Command(4), - values_dict={'template': 1, 'files': fp1, - 'artifact_type': 'BIOM', - 'provenance': dumps( - {'job': job.id, - 'cmd_out_id': qdb.util.convert_to_id( - 'out1', "command_output", "name")})}) - user = qdb.user.User('test@foo.bar') - obs1 = qdb.processing_job.ProcessingJob.create(user, params) - obs1._set_status('running') - params = qdb.software.Parameters.load( - qdb.software.Command(4), - values_dict={'template': 1, 'files': fp2, - 'artifact_type': 'BIOM', - 'provenance': dumps( - {'job': job.id, - 'cmd_out_id': qdb.util.convert_to_id( - 'out1', "command_output", "name")})}) - obs2 = qdb.processing_job.ProcessingJob.create(user, params) - obs2._set_status('running') - # Make sure that we link the original job with its validator jobs - job._set_validator_jobs([obs1, obs2]) - - artifact_data_1 = {'filepaths': [(fp1, 'biom')], - 'artifact_type': 'BIOM'} - # Complete one of the validator jobs. This jobs should store all the - # information about the new artifact, but it does not create it. The - # job then goes to a "waiting" state, where it waits until all the - # validator jobs are completed. - obs1._complete_artifact_definition(artifact_data_1) - self.assertEqual(obs1.status, 'waiting') - self.assertEqual(job.status, 'running') - - # When we complete the second validation job, the previous validation - # job is realeaed from its waiting state. All jobs then create the - # artifacts in a single transaction, so either all of them successfully - # complete, or all of them fail. - artifact_data_2 = {'filepaths': [(fp2, 'biom')], - 'artifact_type': 'BIOM'} - obs2._complete_artifact_definition(artifact_data_2) - self.assertEqual(obs1.status, 'success') - self.assertEqual(obs2.status, 'success') - self.assertEqual(job.status, 'success') - - def test_complete_artifact_definition(self): - job = _create_job() - job._set_status('running') - fd, fp = mkstemp(suffix="_table.biom") - self._clean_up_files.append(fp) - close(fd) - with open(fp, 'w') as f: - f.write('\n') - - artifact_data = {'filepaths': [(fp, 'biom')], - 'artifact_type': 'BIOM'} - params = qdb.software.Parameters.load( - qdb.software.Command(4), - values_dict={'template': 1, 'files': fp, - 'artifact_type': 'BIOM', - 'provenance': dumps( - {'job': job.id, - 'cmd_out_id': 3})} - ) - obs = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), params) - job._set_validator_jobs([obs]) - obs._complete_artifact_definition(artifact_data) - self.assertEqual(job.status, 'success') - # Upload case implicitly tested by "test_complete_type" - - def test_complete_artifact_transformation(self): - # Implicitly tested by "test_complete" - pass - - def test_complete_no_artifact_data(self): - job = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), - qdb.software.Parameters.load( - qdb.software.Command(5), - values_dict={"input_data": 1})) - job._set_status('running') - job.complete(True) - self.assertEqual(job.status, 'success') - - job = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), - qdb.software.Parameters.load( - qdb.software.Command(5), - values_dict={"input_data": 1})) - job._set_status('running') - job.complete(False, error='Some Error') - self.assertEqual(job.status, 'error') - - def test_complete_type(self): - fd, fp = mkstemp(suffix="_table.biom") - self._clean_up_files.append(fp) - close(fd) - with open(fp, 'w') as f: - f.write('\n') - - exp_artifact_count = qdb.util.get_count('qiita.artifact') + 1 - artifacts_data = {'ignored': {'filepaths': [(fp, 'biom')], - 'artifact_type': 'BIOM'}} - metadata_dict = { - 'SKB8.640193': {'center_name': 'ANL', - 'primer': 'GTGCCAGCMGCCGCGGTAA', - 'barcode': 'GTCCGCAAGTTA', - 'run_prefix': "s_G1_L001_sequences", - 'platform': 'ILLUMINA', - 'instrument_model': 'Illumina MiSeq', - 'library_construction_protocol': 'AAAA', - 'experiment_design_description': 'BBBB'}} - metadata = pd.DataFrame.from_dict(metadata_dict, orient='index', - dtype=str) - pt = qdb.metadata_template.prep_template.PrepTemplate.create( - metadata, qdb.study.Study(1), "16S") - self._clean_up_files.extend([ptfp for _, ptfp in pt.get_filepaths()]) - params = qdb.software.Parameters.load( - qdb.software.Command(4), - values_dict={'template': pt.id, 'files': fp, - 'artifact_type': 'BIOM'}) - obs = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), params) - obs._set_status('running') - obs.complete(True, artifacts_data=artifacts_data) - self.assertEqual(obs.status, 'success') - self.assertEqual(qdb.util.get_count('qiita.artifact'), - exp_artifact_count) - self._clean_up_files.extend( - [afp for _, afp, _ in - qdb.artifact.Artifact(exp_artifact_count).filepaths]) - - def test_complete_success(self): - fd, fp = mkstemp(suffix='_table.biom') - self._clean_up_files.append(fp) - close(fd) - with open(fp, 'w') as f: - f.write('\n') - - artifacts_data = {'OTU table': {'filepaths': [(fp, 'biom')], - 'artifact_type': 'BIOM'}} - - job = _create_job() - job._set_status('running') - alljobs = set(self._get_all_job_ids()) - - job.complete(True, artifacts_data=artifacts_data) - # When completing the previous job, it creates a new job that needs - # to validate the BIOM table that is being added as new artifact. - # Hence, this job is still in running state until the validation job - # is completed. Note that this is tested by making sure that the status - # of this job is running, and that we have one more job than before - # (see assertEqual with len of all jobs) - self.assertEqual(job.status, 'running') - - obsjobs = set(self._get_all_job_ids()) - - self.assertEqual(len(obsjobs), len(alljobs) + 1) - self._wait_for_job(job) - - def test_complete_failure(self): - job = _create_job() - job.complete(False, error="Job failure") - self.assertEqual(job.status, 'error') - self.assertEqual(job.log, - qdb.logger.LogEntry.newest_records(numrecords=1)[0]) - self.assertEqual(job.log.msg, 'Job failure') - - # Test the artifact definition case - job = _create_job() - job._set_status('running') - - params = qdb.software.Parameters.load( - qdb.software.Command(4), - values_dict={'template': 1, 'files': 'ignored', - 'artifact_type': 'BIOM', - 'provenance': dumps( - {'job': job.id, - 'cmd_out_id': 3})} - ) - obs = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), params) - obs.complete(False, error="Validation failure") - self.assertEqual(obs.status, 'error') - self.assertEqual(obs.log.msg, 'Validation failure') - - self.assertEqual(job.status, 'error') - self.assertEqual(job.log.msg, 'Validation failure') - - def test_complete_error(self): - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - self.tester1.complete(True, artifacts_data={}) - - def test_set_error(self): - job1 = _create_job() - job1._set_status('queued') - job2 = _create_job() - job2._set_status('running') - - for t in [job1, job2]: - t._set_error('Job failure') - self.assertEqual(t.status, 'error') - self.assertEqual( - t.log, qdb.logger.LogEntry.newest_records(numrecords=1)[0]) - - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - self.tester3._set_error("Job failure") - - def test_update_heartbeat_state(self): - job = _create_job() - job._set_status('running') - before = datetime.now() - job.update_heartbeat_state() - self.assertTrue(before < job.heartbeat < datetime.now()) - - job = _create_job() - job._set_status('queued') - before = datetime.now() - job.update_heartbeat_state() - self.assertTrue(before < job.heartbeat < datetime.now()) - self.assertEqual(job.status, 'running') - - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - self.tester3.update_heartbeat_state() - - def test_step_setter(self): - job = _create_job() - job._set_status('running') - job.step = 'demultiplexing' - self.assertEqual(job.step, 'demultiplexing') - job.step = 'generating demux file' - self.assertEqual(job.step, 'generating demux file') - - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - self.tester1.step = 'demultiplexing' - - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - self.tester3.step = 'demultiplexing' - - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - self.tester4.step = 'demultiplexing' - - def test_update_children(self): - # Create a workflow so we can test this functionality - exp_command = qdb.software.Command(1) - json_str = ( - '{"input_data": 1, "max_barcode_errors": 1.5, ' - '"barcode_type": "golay_12", "max_bad_run_length": 3, ' - '"rev_comp": false, "phred_quality_threshold": 3, ' - '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' - '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' - '"phred_offset": "auto"}') - exp_params = qdb.software.Parameters.load(exp_command, - json_str=json_str) - exp_user = qdb.user.User('test@foo.bar') - name = "Test processing workflow" - - tester = qdb.processing_job.ProcessingWorkflow.from_scratch( - exp_user, exp_params, name=name) - - parent = tester.graph.nodes()[0] - connections = {parent: {'demultiplexed': 'input_data'}} - dflt_params = qdb.software.DefaultParameters(10) - tester.add(dflt_params, connections=connections) - # we could get the child using tester.graph.nodes()[1] but networkx - # doesn't assure order so using the actual graph to get the child - child = nx.topological_sort(tester.graph)[1] - - mapping = {1: 3} - obs = parent._update_children(mapping) - exp = [child] - self.assertTrue(obs, exp) - self.assertEqual(child.input_artifacts, - [qdb.artifact.Artifact(3)]) - - def test_outputs(self): - job = _create_job() - job._set_status('running') - - QE = qdb.exceptions - with self.assertRaises(QE.QiitaDBOperationNotPermittedError): - job.outputs - - fd, fp = mkstemp(suffix="_table.biom") - self._clean_up_files.append(fp) - close(fd) - with open(fp, 'w') as f: - f.write('\n') - - artifact_data = {'filepaths': [(fp, 'biom')], 'artifact_type': 'BIOM'} - params = qdb.software.Parameters.load( - qdb.software.Command(4), - values_dict={'template': 1, 'files': fp, - 'artifact_type': 'BIOM', - 'provenance': dumps( - {'job': job.id, - 'cmd_out_id': 3})} - ) - obs = qdb.processing_job.ProcessingJob.create( - qdb.user.User('test@foo.bar'), params) - job._set_validator_jobs([obs]) - exp_artifact_count = qdb.util.get_count('qiita.artifact') + 1 - obs._complete_artifact_definition(artifact_data) - self.assertEqual(job.status, 'success') - - obs = job.outputs - self.assertEqual( - obs, {'OTU table': qdb.artifact.Artifact(exp_artifact_count)}) - self._clean_up_files.extend( - [afp for _, afp, _ in - qdb.artifact.Artifact(exp_artifact_count).filepaths]) - - def test_processing_job_worflow(self): - # testing None - job = qdb.processing_job.ProcessingJob( - "063e553b-327c-4818-ab4a-adfe58e49860") - self.assertIsNone(job.processing_job_worflow) - - # testing actual workflow - job = qdb.processing_job.ProcessingJob( - "b72369f9-a886-4193-8d3d-f7b504168e75") - self.assertEqual(job.processing_job_worflow, - qdb.processing_job.ProcessingWorkflow(1)) - - -@qiita_test_checker() -class ProcessingWorkflowTests(TestCase): - def test_name(self): - self.assertEqual(qdb.processing_job.ProcessingWorkflow(1).name, - 'Testing processing workflow') - - def test_user(self): - self.assertEqual(qdb.processing_job.ProcessingWorkflow(1).user, - qdb.user.User('shared@foo.bar')) - - def test_graph(self): - obs = qdb.processing_job.ProcessingWorkflow(1).graph - self.assertTrue(isinstance(obs, nx.DiGraph)) - exp_nodes = [ - qdb.processing_job.ProcessingJob( - 'b72369f9-a886-4193-8d3d-f7b504168e75'), - qdb.processing_job.ProcessingJob( - 'd19f76ee-274e-4c1b-b3a2-a12d73507c55')] - self.assertItemsEqual(obs.nodes(), exp_nodes) - self.assertEqual(obs.edges(), [(exp_nodes[0], exp_nodes[1])]) - - def test_graph_only_root(self): - obs = qdb.processing_job.ProcessingWorkflow(2).graph - self.assertTrue(isinstance(obs, nx.DiGraph)) - exp_nodes = [ - qdb.processing_job.ProcessingJob( - 'ac653cb5-76a6-4a45-929e-eb9b2dee6b63')] - self.assertItemsEqual(obs.nodes(), exp_nodes) - self.assertEqual(obs.edges(), []) - - def test_raise_if_not_in_construction(self): - # We just need to test that the execution continues (i.e. no raise) - tester = qdb.processing_job.ProcessingWorkflow(2) - tester._raise_if_not_in_construction() - - def test_raise_if_not_in_construction_error(self): - tester = qdb.processing_job.ProcessingWorkflow(1) - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - tester._raise_if_not_in_construction() - - def test_submit(self): - # In order to test a success, we need to actually run the jobs, which - # will mean to run split libraries, for example. - pass - - def test_from_default_workflow(self): - exp_user = qdb.user.User('test@foo.bar') - dflt_wf = qdb.software.DefaultWorkflow(1) - req_params = {qdb.software.Command(1): {'input_data': 1}} - name = "Test processing workflow" - - obs = qdb.processing_job.ProcessingWorkflow.from_default_workflow( - exp_user, dflt_wf, req_params, name=name) - self.assertEqual(obs.name, name) - self.assertEqual(obs.user, exp_user) - obs_graph = obs.graph - self.assertTrue(isinstance(obs_graph, nx.DiGraph)) - self.assertEqual(len(obs_graph.nodes()), 2) - obs_edges = obs_graph.edges() - self.assertEqual(len(obs_edges), 1) - obs_src = obs_edges[0][0] - obs_dst = obs_edges[0][1] - self.assertTrue(isinstance(obs_src, qdb.processing_job.ProcessingJob)) - self.assertTrue(isinstance(obs_dst, qdb.processing_job.ProcessingJob)) - self.assertTrue(obs_src.command, qdb.software.Command(1)) - self.assertTrue(obs_dst.command, qdb.software.Command(1)) - obs_params = obs_dst.parameters.values - exp_params = { - 'input_data': [obs_src.id, u'demultiplexed'], - 'reference': 1, - 'similarity': 0.97, - 'sortmerna_coverage': 0.97, - 'sortmerna_e_value': 1, - 'sortmerna_max_pos': 10000, - 'threads': 1} - self.assertEqual(obs_params, exp_params) - - def test_from_default_workflow_error(self): - with self.assertRaises(qdb.exceptions.QiitaDBError) as err: - qdb.processing_job.ProcessingWorkflow.from_default_workflow( - qdb.user.User('test@foo.bar'), qdb.software.DefaultWorkflow(1), - {}, name="Test name") - - exp = ('Provided required parameters do not match the initial set of ' - 'commands for the workflow. Command(s) "Split libraries FASTQ"' - ' are missing the required parameter set.') - self.assertEqual(str(err.exception), exp) - - req_params = {qdb.software.Command(1): {'input_data': 1}, - qdb.software.Command(2): {'input_data': 2}} - - with self.assertRaises(qdb.exceptions.QiitaDBError) as err: - qdb.processing_job.ProcessingWorkflow.from_default_workflow( - qdb.user.User('test@foo.bar'), qdb.software.DefaultWorkflow(1), - req_params, name="Test name") - exp = ('Provided required parameters do not match the initial set of ' - 'commands for the workflow. Paramters for command(s) ' - '"Split libraries" have been provided, but they are not the ' - 'initial commands for the workflow.') - self.assertEqual(str(err.exception), exp) - - def test_from_scratch(self): - exp_command = qdb.software.Command(1) - json_str = ( - '{"input_data": 1, "max_barcode_errors": 1.5, ' - '"barcode_type": "golay_12", "max_bad_run_length": 3, ' - '"rev_comp": false, "phred_quality_threshold": 3, ' - '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' - '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' - '"phred_offset": "auto"}') - exp_params = qdb.software.Parameters.load(exp_command, - json_str=json_str) - exp_user = qdb.user.User('test@foo.bar') - name = "Test processing workflow" - - obs = qdb.processing_job.ProcessingWorkflow.from_scratch( - exp_user, exp_params, name=name) - self.assertEqual(obs.name, name) - self.assertEqual(obs.user, exp_user) - obs_graph = obs.graph - self.assertTrue(isinstance(obs_graph, nx.DiGraph)) - nodes = obs_graph.nodes() - self.assertEqual(len(nodes), 1) - self.assertEqual(nodes[0].parameters, exp_params) - self.assertEqual(obs_graph.edges(), []) - - def test_add(self): - exp_command = qdb.software.Command(1) - json_str = ( - '{"input_data": 1, "max_barcode_errors": 1.5, ' - '"barcode_type": "golay_12", "max_bad_run_length": 3, ' - '"rev_comp": false, "phred_quality_threshold": 3, ' - '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' - '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' - '"phred_offset": "auto"}') - exp_params = qdb.software.Parameters.load(exp_command, - json_str=json_str) - exp_user = qdb.user.User('test@foo.bar') - name = "Test processing workflow" - - obs = qdb.processing_job.ProcessingWorkflow.from_scratch( - exp_user, exp_params, name=name) - - parent = obs.graph.nodes()[0] - connections = {parent: {'demultiplexed': 'input_data'}} - dflt_params = qdb.software.DefaultParameters(10) - obs.add(dflt_params, connections=connections) - - obs_graph = obs.graph - self.assertTrue(isinstance(obs_graph, nx.DiGraph)) - obs_nodes = obs_graph.nodes() - self.assertEqual(len(obs_nodes), 2) - obs_edges = obs_graph.edges() - self.assertEqual(len(obs_edges), 1) - obs_src = obs_edges[0][0] - obs_dst = obs_edges[0][1] - self.assertEqual(obs_src, parent) - self.assertTrue(isinstance(obs_dst, qdb.processing_job.ProcessingJob)) - obs_params = obs_dst.parameters.values - exp_params = { - 'input_data': [parent.id, u'demultiplexed'], - 'reference': 1, - 'similarity': 0.97, - 'sortmerna_coverage': 0.97, - 'sortmerna_e_value': 1, - 'sortmerna_max_pos': 10000, - 'threads': 1} - self.assertEqual(obs_params, exp_params) - - # Adding a new root job - # This also tests that the `graph` property returns the graph correctly - # when there are root nodes that don't have any children - dflt_params = qdb.software.DefaultParameters(1) - obs.add(dflt_params, req_params={'input_data': 1}) - - obs_graph = obs.graph - self.assertTrue(isinstance(obs_graph, nx.DiGraph)) - root_obs_nodes = obs_graph.nodes() - self.assertEqual(len(root_obs_nodes), 3) - obs_edges = obs_graph.edges() - self.assertEqual(len(obs_edges), 1) - obs_new_jobs = set(root_obs_nodes) - set(obs_nodes) - self.assertEqual(len(obs_new_jobs), 1) - obs_job = obs_new_jobs.pop() - exp_params = {'barcode_type': u'golay_12', - 'input_data': 1, - 'max_bad_run_length': 3, - 'max_barcode_errors': 1.5, - 'min_per_read_length_fraction': 0.75, - 'phred_quality_threshold': 3, - 'rev_comp': False, - 'rev_comp_barcode': False, - 'rev_comp_mapping_barcodes': False, - 'sequence_max_n': 0, - 'phred_offset': 'auto'} - self.assertEqual(obs_job.parameters.values, exp_params) - - def test_add_error(self): - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - qdb.processing_job.ProcessingWorkflow(1).add({}, None) - - def test_remove(self): - exp_command = qdb.software.Command(1) - json_str = ( - '{"input_data": 1, "max_barcode_errors": 1.5, ' - '"barcode_type": "golay_12", "max_bad_run_length": 3, ' - '"rev_comp": false, "phred_quality_threshold": 3, ' - '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' - '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0,' - '"phred_offset": "auto"}') - exp_params = qdb.software.Parameters.load(exp_command, - json_str=json_str) - exp_user = qdb.user.User('test@foo.bar') - name = "Test processing workflow" - - tester = qdb.processing_job.ProcessingWorkflow.from_scratch( - exp_user, exp_params, name=name) - - parent = tester.graph.nodes()[0] - connections = {parent: {'demultiplexed': 'input_data'}} - dflt_params = qdb.software.DefaultParameters(10) - tester.add(dflt_params, connections=connections) - - self.assertEqual(len(tester.graph.nodes()), 2) - tester.remove(tester.graph.edges()[0][1]) - - g = tester.graph - obs_nodes = g.nodes() - self.assertEqual(len(obs_nodes), 1) - self.assertEqual(obs_nodes[0], parent) - self.assertEqual(g.edges(), []) - - # Test with cascade = true - exp_user = qdb.user.User('test@foo.bar') - dflt_wf = qdb.software.DefaultWorkflow(1) - req_params = {qdb.software.Command(1): {'input_data': 1}} - name = "Test processing workflow" - - tester = qdb.processing_job.ProcessingWorkflow.from_default_workflow( - exp_user, dflt_wf, req_params, name=name) - - tester.remove(tester.graph.edges()[0][0], cascade=True) - - self.assertEqual(tester.graph.nodes(), []) - - def test_remove_error(self): - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - qdb.processing_job.ProcessingWorkflow(1).remove( - qdb.processing_job.ProcessingJob( - 'b72369f9-a886-4193-8d3d-f7b504168e75')) - - exp_user = qdb.user.User('test@foo.bar') - dflt_wf = qdb.software.DefaultWorkflow(1) - req_params = {qdb.software.Command(1): {'input_data': 1}} - name = "Test processing workflow" - - tester = qdb.processing_job.ProcessingWorkflow.from_default_workflow( - exp_user, dflt_wf, req_params, name=name) - - with self.assertRaises( - qdb.exceptions.QiitaDBOperationNotPermittedError): - tester.remove(tester.graph.edges()[0][0]) - - -if __name__ == '__main__': - main() From 055b53ebf211da02c9cb3d49fa274610692fe127 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:32:29 -0800 Subject: [PATCH 24/32] Oops removed the wrong file --- qiita_db/test/test_job.py | 393 ------------ qiita_db/test/test_processing_job.py | 922 +++++++++++++++++++++++++++ 2 files changed, 922 insertions(+), 393 deletions(-) delete mode 100644 qiita_db/test/test_job.py create mode 100644 qiita_db/test/test_processing_job.py diff --git a/qiita_db/test/test_job.py b/qiita_db/test/test_job.py deleted file mode 100644 index 2d097e70c..000000000 --- a/qiita_db/test/test_job.py +++ /dev/null @@ -1,393 +0,0 @@ -# ----------------------------------------------------------------------------- -# Copyright (c) 2014--, The Qiita Development Team. -# -# Distributed under the terms of the BSD 3-clause License. -# -# The full license is in the file LICENSE, distributed with this software. -# ----------------------------------------------------------------------------- - -from unittest import TestCase, main -from os import remove, mkdir -from os.path import join, exists, isdir -from shutil import rmtree -from datetime import datetime - -from qiita_core.util import qiita_test_checker -import qiita_db as qdb - - -@qiita_test_checker() -class JobTest(TestCase): - """Tests that the job object works as expected""" - - def setUp(self): - self._delete_path = [] - - # creating a new job for testing - self.options = {"option1": False, "option2": 25, "option3": "NEW"} - _, self._job_folder = qdb.util.get_mountpoint("job")[0] - - self.job_create_params = ("18S", "Alpha Rarefaction", - self.options, qdb.analysis.Analysis(1), - qdb.reference.Reference(1), - qdb.software.Command(3)) - self.job = qdb.job.Job.create(*self.job_create_params) - self.job_id = self.job.id - - # adding filepaths - # file - self.fp = join(self._job_folder, "%d_job_result.txt" % self.job_id) - if not exists(self.fp): - with open(self.fp, 'w') as f: - f.write("DATA") - self.job.add_results([(self.fp, "plain_text")]) - # folder - self.dfp = join(self._job_folder, "my_folder") - self._delete_path.append(self.dfp) - self.ffp = join(self.dfp, "%d_file_in_folder.html" % self.job_id) - if not exists(self.dfp): - mkdir(self.dfp) - if not exists(self.ffp): - with open(self.ffp, 'w') as f: - f.write("DATA") - self.job.add_results([(self.dfp, "directory")]) - - def tearDown(self): - for path in self._delete_path: - if exists(path): - if isdir(path): - rmtree(path) - else: - remove(path) - - if qdb.job.Job.exists(*self.job_create_params): - qdb.job.Job.delete(self.job_id) - - def test_exists(self): - """tests that existing job returns true""" - # need to insert matching sample data into analysis 2 - self.conn_handler.execute( - "DELETE FROM qiita.analysis_sample WHERE analysis_id = 2") - sql = """INSERT INTO qiita.analysis_sample - (analysis_id, artifact_id, sample_id) - VALUES (2, 4, '1.SKB8.640193'), (2, 4, '1.SKD8.640184'), - (2, 4, '1.SKB7.640196'), (2, 4, '1.SKM9.640192'), - (2, 4, '1.SKM4.640180')""" - self.conn_handler.execute(sql) - self.assertTrue(qdb.job.Job.exists( - "18S", "Beta Diversity", {"--otu_table_fp": 1, "--mapping_fp": 1}, - qdb.analysis.Analysis(1), qdb.reference.Reference(2), - qdb.software.Command(3))) - - def test_exists_return_jobid(self): - """tests that existing job returns true""" - # need to insert matching sample data into analysis 2 - self.conn_handler.execute( - "DELETE FROM qiita.analysis_sample WHERE analysis_id = 2") - sql = """INSERT INTO qiita.analysis_sample - (analysis_id, artifact_id, sample_id) - VALUES (2, 4, '1.SKB8.640193'), (2, 4, '1.SKD8.640184'), - (2, 4, '1.SKB7.640196'), (2, 4, '1.SKM9.640192'), - (2, 4, '1.SKM4.640180')""" - self.conn_handler.execute(sql) - exists, jid = qdb.job.Job.exists( - "18S", "Beta Diversity", {"--otu_table_fp": 1, "--mapping_fp": 1}, - qdb.analysis.Analysis(1), qdb.reference.Reference(2), - qdb.software.Command(3), return_existing=True) - self.assertTrue(exists) - self.assertEqual(jid, qdb.job.Job(2)) - - def test_exists_noexist_options(self): - """tests that non-existant job with bad options returns false""" - # need to insert matching sample data into analysis 2 - # makes sure failure is because options and not samples - self.conn_handler.execute( - "DELETE FROM qiita.analysis_sample WHERE analysis_id = 2") - sql = """INSERT INTO qiita.analysis_sample - (analysis_id, artifact_id, sample_id) - VALUES (2, 4, '1.SKB8.640193'), (2, 4, '1.SKD8.640184'), - (2, 4, '1.SKB7.640196'), (2, 4, '1.SKM9.640192'), - (2, 4, '1.SKM4.640180')""" - self.conn_handler.execute(sql) - self.assertFalse(qdb.job.Job.exists( - "18S", "Beta Diversity", {"--otu_table_fp": 1, "--mapping_fp": 27}, - qdb.analysis.Analysis(1), qdb.reference.Reference(2), - qdb.software.Command(3))) - - def test_exists_noexist_return_jobid(self): - """tests that non-existant job with bad samples returns false""" - exists, jid = qdb.job.Job.exists( - "16S", "Beta Diversity", {"--otu_table_fp": 1, "--mapping_fp": 27}, - qdb.analysis.Analysis(1), qdb.reference.Reference(2), - qdb.software.Command(3), return_existing=True) - self.assertFalse(exists) - self.assertEqual(jid, None) - - def test_get_commands(self): - exp = [ - qdb.job.Command('Summarize Taxa', - 'summarize_taxa_through_plots.py', - '{"--otu_table_fp":null}', '{}', - '{"--mapping_category":null, "--mapping_fp":null,' - '"--sort":null}', '{"--output_dir":null}'), - qdb.job.Command('Beta Diversity', - 'beta_diversity_through_plots.py', - '{"--otu_table_fp":null,"--mapping_fp":null}', - '{}', - '{"--tree_fp":null,"--color_by_all_fields":null,' - '"--seqs_per_sample":null}', - '{"--output_dir":null}'), - qdb.job.Command('Alpha Rarefaction', 'alpha_rarefaction.py', - '{"--otu_table_fp":null,"--mapping_fp":null}', - '{}', - '{"--tree_fp":null,"--num_steps":null,' - '"--min_rare_depth":null,"--max_rare_depth":null,' - '"--retain_intermediate_files":false}', - '{"--output_dir":null}') - ] - self.assertEqual(qdb.job.Job.get_commands(), exp) - - def test_create(self): - """Makes sure creation works as expected""" - # make first job - new = qdb.job.Job.create("18S", "Alpha Rarefaction", {"opt1": 4}, - qdb.analysis.Analysis(1), - qdb.reference.Reference(1), - qdb.software.Command(3)) - new_id = new.id - # + 1 cause it should be one higher than the one created in setUp - self.assertEqual(new_id, self.job_id + 1) - - # make sure job inserted correctly - obs = self.conn_handler.execute_fetchall("SELECT * FROM qiita.job " - "WHERE job_id = %d" % new_id) - exp = [[new_id, 2, 1, 3, '{"opt1":4}', None, 1, 3]] - - self.assertEqual(obs, exp) - # make sure job added to analysis correctly - obs = self.conn_handler.execute_fetchall("SELECT * FROM " - "qiita.analysis_job WHERE " - "job_id = %d" % new_id) - exp = [[1, new_id]] - self.assertEqual(obs, exp) - - # make second job with diff datatype and command to test column insert - new = qdb.job.Job.create("16S", "Beta Diversity", {"opt1": 4}, - qdb.analysis.Analysis(1), - qdb.reference.Reference(2), - qdb.software.Command(3)) - new_id = new.id - self.assertEqual(new_id, self.job_id + 2) - # make sure job inserted correctly - obs = self.conn_handler.execute_fetchall("SELECT * FROM qiita.job " - "WHERE job_id = %d" % new_id) - exp = [[new_id, 1, 1, 2, '{"opt1":4}', None, 2, 3]] - self.assertEqual(obs, exp) - # make sure job added to analysis correctly - obs = self.conn_handler.execute_fetchall("SELECT * FROM " - "qiita.analysis_job WHERE " - "job_id = %d" % new_id) - exp = [[1, new_id]] - self.assertEqual(obs, exp) - - def test_create_exists(self): - """Makes sure creation doesn't duplicate a job""" - with self.assertRaises(qdb.exceptions.QiitaDBDuplicateError): - qdb.job.Job.create( - "18S", "Beta Diversity", - {"--otu_table_fp": 1, "--mapping_fp": 1}, - qdb.analysis.Analysis(1), qdb.reference.Reference(2), - qdb.software.Command(3)) - - def test_create_exists_return_existing(self): - """Makes sure creation doesn't duplicate a job by returning existing""" - analysis = qdb.analysis.Analysis.create( - qdb.user.User("demo@microbio.me"), "new", "desc") - sql = """INSERT INTO qiita.analysis_sample - (analysis_id, artifact_id, sample_id) - VALUES ({0}, 4, '1.SKB8.640193'), ({0}, 4, '1.SKD8.640184'), - ({0}, 4, '1.SKB7.640196'), ({0}, 4, '1.SKM9.640192'), - ({0}, 4, '1.SKM4.640180')""".format(analysis.id) - self.conn_handler.execute(sql) - new = qdb.job.Job.create( - "18S", "Beta Diversity", {"--otu_table_fp": 1, "--mapping_fp": 1}, - analysis, qdb.reference.Reference(2), qdb.software.Command(3), - return_existing=True) - self.assertEqual(new.id, self.job_id + 1) - - def test_retrieve_datatype(self): - """Makes sure datatype retrieval is correct""" - self.assertEqual(self.job.datatype, '18S') - - def test_retrieve_command(self): - """Makes sure command retrieval is correct""" - self.assertEqual(self.job.command, ['Alpha Rarefaction', - 'alpha_rarefaction.py']) - - def test_retrieve_options(self): - exp = { - '--output_dir': join(self._job_folder, - ('%d_alpha_rarefaction.py_output_dir' % - self.job_id)), - 'option2': 25, - 'option3': 'NEW', - 'option1': False} - self.assertEqual(self.job.options, exp) - - def test_set_options(self): - new = qdb.job.Job.create("18S", "Alpha Rarefaction", - {"opt1": 4}, qdb.analysis.Analysis(1), - qdb.reference.Reference(2), - qdb.software.Command(3)) - new.options = self.options - self.options['--output_dir'] = join( - self._job_folder, - '%d_alpha_rarefaction.py_output_dir' % new.id) - self.assertEqual(new.options, self.options) - - def test_retrieve_results_folder(self): - exp = ['%d_job_result.txt' % self.job_id, - 'my_folder/%d_file_in_folder.html' % self.job_id] - self.assertEqual(self.job.results, exp) - - def test_retrieve_results_empty(self): - new = qdb.job.Job.create("18S", "Beta Diversity", {"opt1": 4}, - qdb.analysis.Analysis(1), - qdb.reference.Reference(2), - qdb.software.Command(3)) - self.assertEqual(new.results, []) - - def test_set_error(self): - before = datetime.now() - self.job.set_error("TESTERROR") - after = datetime.now() - self.assertEqual(self.job.status, "error") - - error = self.job.error - - self.assertEqual(error.severity, 2) - self.assertEqual(error.msg, 'TESTERROR') - self.assertTrue(before < error.time < after) - - def test_retrieve_error_blank(self): - self.assertEqual(self.job.error, None) - - def test_set_error_completed(self): - self.job.status = "error" - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - self.job.set_error("TESTERROR") - - def test_retrieve_error_exists(self): - self.job.set_error("TESTERROR") - self.assertEqual(self.job.error.msg, "TESTERROR") - - def test_add_results(self): - curr_id = self.conn_handler.execute_fetchone( - "SELECT last_value FROM qiita.filepath_filepath_id_seq")[0] - fp = join(self._job_folder, "%d_job_result_new.txt" % self.job_id) - if not exists(fp): - with open(fp, 'w') as f: - f.write("DATA") - - self.job.add_results([(fp, "plain_text")]) - - # make sure files attached to job properly - obs = self.conn_handler.execute_fetchall( - "SELECT * FROM qiita.job_results_filepath WHERE job_id = " - "%d" % self.job_id) - - # the job now has 3 files, thus the - 1 and + 1 - self.assertEqual(obs, [[self.job_id, curr_id - 1], - [self.job_id, curr_id], - [self.job_id, curr_id + 1]]) - - def test_add_results_dir(self): - curr_id = self.conn_handler.execute_fetchone( - "SELECT last_value FROM qiita.filepath_filepath_id_seq")[0] - - dfp = join(self._job_folder, "my_new_folder") - ffp = join(dfp, "%d_file_in_new_folder.txt" % self.job_id) - if not exists(dfp): - mkdir(dfp) - if not exists(ffp): - with open(ffp, 'w') as f: - f.write("DATA") - self.job.add_results([(dfp, "directory")]) - - obs = self.conn_handler.execute_fetchall( - "SELECT * FROM qiita.job_results_filepath WHERE job_id = " - "%d" % self.job_id) - - # the job now has 3 files, thus the - 1 and + 1 - self.assertEqual(obs, [[self.job_id, curr_id - 1], - [self.job_id, curr_id], - [self.job_id, curr_id + 1]]) - - def test_add_results_completed(self): - self.job.status = "completed" - with self.assertRaises(qdb.exceptions.QiitaDBStatusError): - self.job.add_results([("/fake/dir/", "directory")]) - - -@qiita_test_checker() -class CommandTest(TestCase): - def setUp(self): - com1 = qdb.job.Command( - 'Summarize Taxa', 'summarize_taxa_through_plots.py', - '{"--otu_table_fp":null}', '{}', - '{"--mapping_category":null, "--mapping_fp":null,' - '"--sort":null}', '{"--output_dir":null}') - com2 = qdb.job.Command( - 'Beta Diversity', 'beta_diversity_through_plots.py', - '{"--otu_table_fp":null,"--mapping_fp":null}', '{}', - '{"--tree_fp":null,"--color_by_all_fields":null,' - '"--seqs_per_sample":null}', '{"--output_dir":null}') - com3 = qdb.job.Command( - 'Alpha Rarefaction', 'alpha_rarefaction.py', - '{"--otu_table_fp":null,"--mapping_fp":null}', '{}', - '{"--tree_fp":null,"--num_steps":null,' - '"--min_rare_depth"' - ':null,"--max_rare_depth":null,' - '"--retain_intermediate_files":false}', - '{"--output_dir":null}') - self.all_comms = { - "16S": [com1, com2, com3], - "18S": [com1, com2, com3], - "ITS": [com2, com3], - "Proteomic": [com2, com3], - "Metabolomic": [com2, com3], - "Metagenomic": [com2, com3], - } - - self._delete_path = [] - - def tearDown(self): - for path in self._delete_path: - if exists(path): - if isdir(path): - rmtree(path) - else: - remove(path) - - def test_get_commands_by_datatype(self): - obs = qdb.job.Command.get_commands_by_datatype() - self.assertEqual(obs, self.all_comms) - obs = qdb.job.Command.get_commands_by_datatype(["16S", "Metabolomic"]) - exp = {k: self.all_comms[k] for k in ('16S', 'Metabolomic')} - self.assertEqual(obs, exp) - - def test_equal(self): - commands = qdb.job.Command.create_list() - self.assertTrue(commands[1] == commands[1]) - self.assertFalse(commands[1] == commands[2]) - self.assertFalse(commands[1] == qdb.job.Job(1)) - - def test_not_equal(self): - commands = qdb.job.Command.create_list() - self.assertFalse(commands[1] != commands[1]) - self.assertTrue(commands[1] != commands[2]) - self.assertTrue(commands[1] != qdb.job.Job(1)) - - -if __name__ == "__main__": - main() diff --git a/qiita_db/test/test_processing_job.py b/qiita_db/test/test_processing_job.py new file mode 100644 index 000000000..6ad60ee04 --- /dev/null +++ b/qiita_db/test/test_processing_job.py @@ -0,0 +1,922 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014--, The Qiita Development Team. +# +# Distributed under the terms of the BSD 3-clause License. +# +# The full license is in the file LICENSE, distributed with this software. +# ----------------------------------------------------------------------------- + +from unittest import TestCase, main +from datetime import datetime +from os.path import join +from os import close +from tempfile import mkstemp +from json import dumps +from time import sleep + +import networkx as nx +import pandas as pd + +import qiita_db as qdb +from qiita_core.util import qiita_test_checker +from qiita_core.qiita_settings import qiita_config + + +def _create_job(): + job = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), + qdb.software.Parameters.load( + qdb.software.Command(2), + values_dict={"min_seq_len": 100, "max_seq_len": 1000, + "trim_seq_length": False, "min_qual_score": 25, + "max_ambig": 6, "max_homopolymer": 6, + "max_primer_mismatch": 0, + "barcode_type": "golay_12", + "max_barcode_errors": 1.5, + "disable_bc_correction": False, + "qual_score_window": 0, "disable_primers": False, + "reverse_primers": "disable", + "reverse_primer_mismatches": 0, + "truncate_ambi_bases": False, "input_data": 1})) + return job + + +@qiita_test_checker() +class ProcessingJobUtilTest(TestCase): + def test_system_call(self): + obs_out, obs_err, obs_status = qdb.processing_job._system_call( + 'echo "Test system call stdout"') + + self.assertEqual(obs_out, "Test system call stdout\n") + self.assertEqual(obs_err, "") + self.assertEqual(obs_status, 0) + + def test_system_call_error(self): + obs_out, obs_err, obs_status = qdb.processing_job._system_call( + '>&2 echo "Test system call stderr"; exit 1') + self.assertEqual(obs_out, "") + self.assertEqual(obs_err, "Test system call stderr\n") + self.assertEqual(obs_status, 1) + + def test_job_submitter(self): + # The cmd parameter of the function should be the command that + # actually executes the function. However, in order to avoid executing + # a expensive command, we are just going to pass some other command. + # In case of success, nothing happens, so we just run it and see that + # it doesn't raise an error + job = _create_job() + cmd = 'echo "Test system call stdout"' + qdb.processing_job._job_submitter(job, cmd) + + def test_job_submitter_error(self): + # Same comment as above, but here we are going to force failure, and + # check that the job is updated correctly + job = _create_job() + cmd = '>&2 echo "Test system call stderr"; exit 1' + qdb.processing_job._job_submitter(job, cmd) + self.assertEqual(job.status, 'error') + exp = ("Error submitting job '%s':\nStd output:\nStd error:" + "Test system call stderr\n" % job.id) + self.assertEqual(job.log.msg, exp) + + +@qiita_test_checker() +class ProcessingJobTest(TestCase): + def setUp(self): + self.tester1 = qdb.processing_job.ProcessingJob( + "063e553b-327c-4818-ab4a-adfe58e49860") + self.tester2 = qdb.processing_job.ProcessingJob( + "bcc7ebcd-39c1-43e4-af2d-822e3589f14d") + self.tester3 = qdb.processing_job.ProcessingJob( + "b72369f9-a886-4193-8d3d-f7b504168e75") + self.tester4 = qdb.processing_job.ProcessingJob( + "d19f76ee-274e-4c1b-b3a2-a12d73507c55") + self._clean_up_files = [] + + def _get_all_job_ids(self): + sql = "SELECT processing_job_id FROM qiita.processing_job" + with qdb.sql_connection.TRN: + qdb.sql_connection.TRN.add(sql) + return qdb.sql_connection.TRN.execute_fetchflatten() + + def _wait_for_job(self, job): + while job.status not in ('error', 'success'): + sleep(0.5) + sleep(0.5) + + def test_exists(self): + self.assertTrue(qdb.processing_job.ProcessingJob.exists( + "063e553b-327c-4818-ab4a-adfe58e49860")) + self.assertTrue(qdb.processing_job.ProcessingJob.exists( + "bcc7ebcd-39c1-43e4-af2d-822e3589f14d")) + self.assertTrue(qdb.processing_job.ProcessingJob.exists( + "b72369f9-a886-4193-8d3d-f7b504168e75")) + self.assertTrue(qdb.processing_job.ProcessingJob.exists( + "d19f76ee-274e-4c1b-b3a2-a12d73507c55")) + self.assertFalse(qdb.processing_job.ProcessingJob.exists( + "d19f76ee-274e-4c1b-b3a2-b12d73507c55")) + self.assertFalse(qdb.processing_job.ProcessingJob.exists( + "some-other-string")) + + def test_user(self): + exp_user = qdb.user.User('test@foo.bar') + self.assertEqual(self.tester1.user, exp_user) + self.assertEqual(self.tester2.user, exp_user) + exp_user = qdb.user.User('shared@foo.bar') + self.assertEqual(self.tester3.user, exp_user) + self.assertEqual(self.tester4.user, exp_user) + + def test_command(self): + cmd1 = qdb.software.Command(1) + cmd2 = qdb.software.Command(2) + cmd3 = qdb.software.Command(3) + self.assertEqual(self.tester1.command, cmd1) + self.assertEqual(self.tester2.command, cmd2) + self.assertEqual(self.tester3.command, cmd1) + self.assertEqual(self.tester4.command, cmd3) + + def test_parameters(self): + json_str = ( + '{"max_bad_run_length":3,"min_per_read_length_fraction":0.75,' + '"sequence_max_n":0,"rev_comp_barcode":false,' + '"rev_comp_mapping_barcodes":false,"rev_comp":false,' + '"phred_quality_threshold":3,"barcode_type":"golay_12",' + '"max_barcode_errors":1.5,"input_data":1,"phred_offset":"auto"}') + exp_params = qdb.software.Parameters.load(qdb.software.Command(1), + json_str=json_str) + self.assertEqual(self.tester1.parameters, exp_params) + + json_str = ( + '{"min_seq_len":100,"max_seq_len":1000,"trim_seq_length":false,' + '"min_qual_score":25,"max_ambig":6,"max_homopolymer":6,' + '"max_primer_mismatch":0,"barcode_type":"golay_12",' + '"max_barcode_errors":1.5,"disable_bc_correction":false,' + '"qual_score_window":0,"disable_primers":false,' + '"reverse_primers":"disable","reverse_primer_mismatches":0,' + '"truncate_ambi_bases":false,"input_data":1}') + exp_params = qdb.software.Parameters.load(qdb.software.Command(2), + json_str=json_str) + self.assertEqual(self.tester2.parameters, exp_params) + + json_str = ( + '{"max_bad_run_length":3,"min_per_read_length_fraction":0.75,' + '"sequence_max_n":0,"rev_comp_barcode":false,' + '"rev_comp_mapping_barcodes":true,"rev_comp":false,' + '"phred_quality_threshold":3,"barcode_type":"golay_12",' + '"max_barcode_errors":1.5,"input_data":1,"phred_offset":"auto"}') + exp_params = qdb.software.Parameters.load(qdb.software.Command(1), + json_str=json_str) + self.assertEqual(self.tester3.parameters, exp_params) + + json_str = ( + '{"reference":1,"sortmerna_e_value":1,"sortmerna_max_pos":10000,' + '"similarity":0.97,"sortmerna_coverage":0.97,"threads":1,' + '"input_data":2}') + exp_params = qdb.software.Parameters.load(qdb.software.Command(3), + json_str=json_str) + self.assertEqual(self.tester4.parameters, exp_params) + + def test_input_artifacts(self): + exp = [qdb.artifact.Artifact(1)] + self.assertEqual(self.tester1.input_artifacts, exp) + self.assertEqual(self.tester2.input_artifacts, exp) + self.assertEqual(self.tester3.input_artifacts, exp) + exp = [qdb.artifact.Artifact(2)] + self.assertEqual(self.tester4.input_artifacts, exp) + + def test_status(self): + self.assertEqual(self.tester1.status, 'queued') + self.assertEqual(self.tester2.status, 'running') + self.assertEqual(self.tester3.status, 'success') + self.assertEqual(self.tester4.status, 'error') + + def test_generate_cmd(self): + obs = self.tester1._generate_cmd() + exp = ('qiita-plugin-launcher "source activate qiita" ' + '"start_target_gene" "%s" ' + '"063e553b-327c-4818-ab4a-adfe58e49860" "%s"' + % (qiita_config.base_url, + join(qdb.util.get_work_base_dir(), + "063e553b-327c-4818-ab4a-adfe58e49860"))) + self.assertEqual(obs, exp) + + def test_submit(self): + # In order to test a success, we need to actually run the job, which + # will mean to run split libraries, for example. + pass + + def test_log(self): + self.assertIsNone(self.tester1.log) + self.assertIsNone(self.tester2.log) + self.assertIsNone(self.tester3.log) + self.assertEqual(self.tester4.log, qdb.logger.LogEntry(1)) + + def test_heartbeat(self): + self.assertIsNone(self.tester1.heartbeat) + self.assertEqual(self.tester2.heartbeat, + datetime(2015, 11, 22, 21, 00, 00)) + self.assertEqual(self.tester3.heartbeat, + datetime(2015, 11, 22, 21, 15, 00)) + self.assertEqual(self.tester4.heartbeat, + datetime(2015, 11, 22, 21, 30, 00)) + + def test_step(self): + self.assertIsNone(self.tester1.step) + self.assertEqual(self.tester2.step, 'demultiplexing') + self.assertIsNone(self.tester3.step) + self.assertEqual(self.tester4.step, 'generating demux file') + + def test_children(self): + self.assertEqual(list(self.tester1.children), []) + self.assertEqual(list(self.tester3.children), [self.tester4]) + + def test_update_and_launch_children(self): + # In order to test a success, we need to actually run the children + # jobs, which will mean to run split libraries, for example. + pass + + def test_create(self): + exp_command = qdb.software.Command(1) + json_str = ( + '{"input_data": 1, "max_barcode_errors": 1.5, ' + '"barcode_type": "golay_12", "max_bad_run_length": 3, ' + '"rev_comp": false, "phred_quality_threshold": 3, ' + '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' + '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' + '"phred_offset": "auto"}') + exp_params = qdb.software.Parameters.load(exp_command, + json_str=json_str) + exp_user = qdb.user.User('test@foo.bar') + obs = qdb.processing_job.ProcessingJob.create(exp_user, exp_params) + self.assertEqual(obs.user, exp_user) + self.assertEqual(obs.command, exp_command) + self.assertEqual(obs.parameters, exp_params) + self.assertEqual(obs.status, 'in_construction') + self.assertEqual(obs.log, None) + self.assertEqual(obs.heartbeat, None) + self.assertEqual(obs.step, None) + self.assertTrue(obs in qdb.artifact.Artifact(1).jobs()) + + def test_set_status(self): + job = _create_job() + self.assertEqual(job.status, 'in_construction') + job._set_status('queued') + self.assertEqual(job.status, 'queued') + job._set_status('running') + self.assertEqual(job.status, 'running') + with self.assertRaises(qdb.exceptions.QiitaDBStatusError): + job._set_status('queued') + job._set_status('error') + self.assertEqual(job.status, 'error') + job._set_status('running') + self.assertEqual(job.status, 'running') + job._set_status('success') + self.assertEqual(job.status, 'success') + with self.assertRaises(qdb.exceptions.QiitaDBStatusError): + job._set_status('running') + + def test_submit_error(self): + job = _create_job() + job._set_status('queued') + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + job.submit() + + def test_complete_multiple_outputs(self): + # This test performs the test of multiple functions at the same + # time. "release", "release_validators" and + # "_set_validator_jobs" are tested here for correct execution. + # Those functions are designed to work together, so it becomes + # really hard to test each of the functions individually for + # successfull execution. + # We need to create a new command with multiple outputs, since + # in the test DB there is no command with such characteristics + cmd = qdb.software.Command.create( + qdb.software.Software(1), + "TestCommand", "Test command", + {'input': ['artifact:["Demultiplexed"]', None]}, + {'out1': 'BIOM', 'out2': 'BIOM'}) + job = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), + qdb.software.Parameters.load( + cmd, + values_dict={"input": 1})) + job._set_status("running") + + fd, fp1 = mkstemp(suffix="_table.biom") + self._clean_up_files.append(fp1) + close(fd) + with open(fp1, 'w') as f: + f.write('\n') + + fd, fp2 = mkstemp(suffix="_table.biom") + self._clean_up_files.append(fp2) + close(fd) + with open(fp2, 'w') as f: + f.write('\n') + + # `job` has 2 output artifacts. Each of these artifacts needs to be + # validated by 2 different validation jobs. We are creating those jobs + # here, and add in the 'procenance' parameter that links the original + # jobs with the validator jobs. + params = qdb.software.Parameters.load( + qdb.software.Command(4), + values_dict={'template': 1, 'files': fp1, + 'artifact_type': 'BIOM', + 'provenance': dumps( + {'job': job.id, + 'cmd_out_id': qdb.util.convert_to_id( + 'out1', "command_output", "name")})}) + user = qdb.user.User('test@foo.bar') + obs1 = qdb.processing_job.ProcessingJob.create(user, params) + obs1._set_status('running') + params = qdb.software.Parameters.load( + qdb.software.Command(4), + values_dict={'template': 1, 'files': fp2, + 'artifact_type': 'BIOM', + 'provenance': dumps( + {'job': job.id, + 'cmd_out_id': qdb.util.convert_to_id( + 'out1', "command_output", "name")})}) + obs2 = qdb.processing_job.ProcessingJob.create(user, params) + obs2._set_status('running') + # Make sure that we link the original job with its validator jobs + job._set_validator_jobs([obs1, obs2]) + + artifact_data_1 = {'filepaths': [(fp1, 'biom')], + 'artifact_type': 'BIOM'} + # Complete one of the validator jobs. This jobs should store all the + # information about the new artifact, but it does not create it. The + # job then goes to a "waiting" state, where it waits until all the + # validator jobs are completed. + obs1._complete_artifact_definition(artifact_data_1) + self.assertEqual(obs1.status, 'waiting') + self.assertEqual(job.status, 'running') + + # When we complete the second validation job, the previous validation + # job is realeaed from its waiting state. All jobs then create the + # artifacts in a single transaction, so either all of them successfully + # complete, or all of them fail. + artifact_data_2 = {'filepaths': [(fp2, 'biom')], + 'artifact_type': 'BIOM'} + obs2._complete_artifact_definition(artifact_data_2) + self.assertEqual(obs1.status, 'success') + self.assertEqual(obs2.status, 'success') + self.assertEqual(job.status, 'success') + + def test_complete_artifact_definition(self): + job = _create_job() + job._set_status('running') + fd, fp = mkstemp(suffix="_table.biom") + self._clean_up_files.append(fp) + close(fd) + with open(fp, 'w') as f: + f.write('\n') + + artifact_data = {'filepaths': [(fp, 'biom')], + 'artifact_type': 'BIOM'} + params = qdb.software.Parameters.load( + qdb.software.Command(4), + values_dict={'template': 1, 'files': fp, + 'artifact_type': 'BIOM', + 'provenance': dumps( + {'job': job.id, + 'cmd_out_id': 3})} + ) + obs = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), params) + job._set_validator_jobs([obs]) + obs._complete_artifact_definition(artifact_data) + self.assertEqual(job.status, 'success') + # Upload case implicitly tested by "test_complete_type" + + def test_complete_artifact_transformation(self): + # Implicitly tested by "test_complete" + pass + + def test_complete_no_artifact_data(self): + job = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), + qdb.software.Parameters.load( + qdb.software.Command(5), + values_dict={"input_data": 1})) + job._set_status('running') + job.complete(True) + self.assertEqual(job.status, 'success') + + job = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), + qdb.software.Parameters.load( + qdb.software.Command(5), + values_dict={"input_data": 1})) + job._set_status('running') + job.complete(False, error='Some Error') + self.assertEqual(job.status, 'error') + + def test_complete_type(self): + fd, fp = mkstemp(suffix="_table.biom") + self._clean_up_files.append(fp) + close(fd) + with open(fp, 'w') as f: + f.write('\n') + + exp_artifact_count = qdb.util.get_count('qiita.artifact') + 1 + artifacts_data = {'ignored': {'filepaths': [(fp, 'biom')], + 'artifact_type': 'BIOM'}} + metadata_dict = { + 'SKB8.640193': {'center_name': 'ANL', + 'primer': 'GTGCCAGCMGCCGCGGTAA', + 'barcode': 'GTCCGCAAGTTA', + 'run_prefix': "s_G1_L001_sequences", + 'platform': 'ILLUMINA', + 'instrument_model': 'Illumina MiSeq', + 'library_construction_protocol': 'AAAA', + 'experiment_design_description': 'BBBB'}} + metadata = pd.DataFrame.from_dict(metadata_dict, orient='index', + dtype=str) + pt = qdb.metadata_template.prep_template.PrepTemplate.create( + metadata, qdb.study.Study(1), "16S") + self._clean_up_files.extend([ptfp for _, ptfp in pt.get_filepaths()]) + params = qdb.software.Parameters.load( + qdb.software.Command(4), + values_dict={'template': pt.id, 'files': fp, + 'artifact_type': 'BIOM'}) + obs = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), params) + obs._set_status('running') + obs.complete(True, artifacts_data=artifacts_data) + self.assertEqual(obs.status, 'success') + self.assertEqual(qdb.util.get_count('qiita.artifact'), + exp_artifact_count) + self._clean_up_files.extend( + [afp for _, afp, _ in + qdb.artifact.Artifact(exp_artifact_count).filepaths]) + + def test_complete_success(self): + fd, fp = mkstemp(suffix='_table.biom') + self._clean_up_files.append(fp) + close(fd) + with open(fp, 'w') as f: + f.write('\n') + + artifacts_data = {'OTU table': {'filepaths': [(fp, 'biom')], + 'artifact_type': 'BIOM'}} + + job = _create_job() + job._set_status('running') + alljobs = set(self._get_all_job_ids()) + + job.complete(True, artifacts_data=artifacts_data) + # When completing the previous job, it creates a new job that needs + # to validate the BIOM table that is being added as new artifact. + # Hence, this job is still in running state until the validation job + # is completed. Note that this is tested by making sure that the status + # of this job is running, and that we have one more job than before + # (see assertEqual with len of all jobs) + self.assertEqual(job.status, 'running') + + obsjobs = set(self._get_all_job_ids()) + + self.assertEqual(len(obsjobs), len(alljobs) + 1) + self._wait_for_job(job) + + def test_complete_failure(self): + job = _create_job() + job.complete(False, error="Job failure") + self.assertEqual(job.status, 'error') + self.assertEqual(job.log, + qdb.logger.LogEntry.newest_records(numrecords=1)[0]) + self.assertEqual(job.log.msg, 'Job failure') + + # Test the artifact definition case + job = _create_job() + job._set_status('running') + + params = qdb.software.Parameters.load( + qdb.software.Command(4), + values_dict={'template': 1, 'files': 'ignored', + 'artifact_type': 'BIOM', + 'provenance': dumps( + {'job': job.id, + 'cmd_out_id': 3})} + ) + obs = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), params) + obs.complete(False, error="Validation failure") + self.assertEqual(obs.status, 'error') + self.assertEqual(obs.log.msg, 'Validation failure') + + self.assertEqual(job.status, 'error') + self.assertEqual(job.log.msg, 'Validation failure') + + def test_complete_error(self): + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + self.tester1.complete(True, artifacts_data={}) + + def test_set_error(self): + job1 = _create_job() + job1._set_status('queued') + job2 = _create_job() + job2._set_status('running') + + for t in [job1, job2]: + t._set_error('Job failure') + self.assertEqual(t.status, 'error') + self.assertEqual( + t.log, qdb.logger.LogEntry.newest_records(numrecords=1)[0]) + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + self.tester3._set_error("Job failure") + + def test_update_heartbeat_state(self): + job = _create_job() + job._set_status('running') + before = datetime.now() + job.update_heartbeat_state() + self.assertTrue(before < job.heartbeat < datetime.now()) + + job = _create_job() + job._set_status('queued') + before = datetime.now() + job.update_heartbeat_state() + self.assertTrue(before < job.heartbeat < datetime.now()) + self.assertEqual(job.status, 'running') + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + self.tester3.update_heartbeat_state() + + def test_step_setter(self): + job = _create_job() + job._set_status('running') + job.step = 'demultiplexing' + self.assertEqual(job.step, 'demultiplexing') + job.step = 'generating demux file' + self.assertEqual(job.step, 'generating demux file') + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + self.tester1.step = 'demultiplexing' + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + self.tester3.step = 'demultiplexing' + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + self.tester4.step = 'demultiplexing' + + def test_update_children(self): + # Create a workflow so we can test this functionality + exp_command = qdb.software.Command(1) + json_str = ( + '{"input_data": 1, "max_barcode_errors": 1.5, ' + '"barcode_type": "golay_12", "max_bad_run_length": 3, ' + '"rev_comp": false, "phred_quality_threshold": 3, ' + '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' + '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' + '"phred_offset": "auto"}') + exp_params = qdb.software.Parameters.load(exp_command, + json_str=json_str) + exp_user = qdb.user.User('test@foo.bar') + name = "Test processing workflow" + + tester = qdb.processing_job.ProcessingWorkflow.from_scratch( + exp_user, exp_params, name=name) + + parent = tester.graph.nodes()[0] + connections = {parent: {'demultiplexed': 'input_data'}} + dflt_params = qdb.software.DefaultParameters(10) + tester.add(dflt_params, connections=connections) + # we could get the child using tester.graph.nodes()[1] but networkx + # doesn't assure order so using the actual graph to get the child + child = nx.topological_sort(tester.graph)[1] + + mapping = {1: 3} + obs = parent._update_children(mapping) + exp = [child] + self.assertTrue(obs, exp) + self.assertEqual(child.input_artifacts, + [qdb.artifact.Artifact(3)]) + + def test_outputs(self): + job = _create_job() + job._set_status('running') + + QE = qdb.exceptions + with self.assertRaises(QE.QiitaDBOperationNotPermittedError): + job.outputs + + fd, fp = mkstemp(suffix="_table.biom") + self._clean_up_files.append(fp) + close(fd) + with open(fp, 'w') as f: + f.write('\n') + + artifact_data = {'filepaths': [(fp, 'biom')], 'artifact_type': 'BIOM'} + params = qdb.software.Parameters.load( + qdb.software.Command(4), + values_dict={'template': 1, 'files': fp, + 'artifact_type': 'BIOM', + 'provenance': dumps( + {'job': job.id, + 'cmd_out_id': 3})} + ) + obs = qdb.processing_job.ProcessingJob.create( + qdb.user.User('test@foo.bar'), params) + job._set_validator_jobs([obs]) + exp_artifact_count = qdb.util.get_count('qiita.artifact') + 1 + obs._complete_artifact_definition(artifact_data) + self.assertEqual(job.status, 'success') + + obs = job.outputs + self.assertEqual( + obs, {'OTU table': qdb.artifact.Artifact(exp_artifact_count)}) + self._clean_up_files.extend( + [afp for _, afp, _ in + qdb.artifact.Artifact(exp_artifact_count).filepaths]) + + def test_processing_job_worflow(self): + # testing None + job = qdb.processing_job.ProcessingJob( + "063e553b-327c-4818-ab4a-adfe58e49860") + self.assertIsNone(job.processing_job_worflow) + + # testing actual workflow + job = qdb.processing_job.ProcessingJob( + "b72369f9-a886-4193-8d3d-f7b504168e75") + self.assertEqual(job.processing_job_worflow, + qdb.processing_job.ProcessingWorkflow(1)) + + +@qiita_test_checker() +class ProcessingWorkflowTests(TestCase): + def test_name(self): + self.assertEqual(qdb.processing_job.ProcessingWorkflow(1).name, + 'Testing processing workflow') + + def test_user(self): + self.assertEqual(qdb.processing_job.ProcessingWorkflow(1).user, + qdb.user.User('shared@foo.bar')) + + def test_graph(self): + obs = qdb.processing_job.ProcessingWorkflow(1).graph + self.assertTrue(isinstance(obs, nx.DiGraph)) + exp_nodes = [ + qdb.processing_job.ProcessingJob( + 'b72369f9-a886-4193-8d3d-f7b504168e75'), + qdb.processing_job.ProcessingJob( + 'd19f76ee-274e-4c1b-b3a2-a12d73507c55')] + self.assertItemsEqual(obs.nodes(), exp_nodes) + self.assertEqual(obs.edges(), [(exp_nodes[0], exp_nodes[1])]) + + def test_graph_only_root(self): + obs = qdb.processing_job.ProcessingWorkflow(2).graph + self.assertTrue(isinstance(obs, nx.DiGraph)) + exp_nodes = [ + qdb.processing_job.ProcessingJob( + 'ac653cb5-76a6-4a45-929e-eb9b2dee6b63')] + self.assertItemsEqual(obs.nodes(), exp_nodes) + self.assertEqual(obs.edges(), []) + + def test_raise_if_not_in_construction(self): + # We just need to test that the execution continues (i.e. no raise) + tester = qdb.processing_job.ProcessingWorkflow(2) + tester._raise_if_not_in_construction() + + def test_raise_if_not_in_construction_error(self): + tester = qdb.processing_job.ProcessingWorkflow(1) + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + tester._raise_if_not_in_construction() + + def test_submit(self): + # In order to test a success, we need to actually run the jobs, which + # will mean to run split libraries, for example. + pass + + def test_from_default_workflow(self): + exp_user = qdb.user.User('test@foo.bar') + dflt_wf = qdb.software.DefaultWorkflow(1) + req_params = {qdb.software.Command(1): {'input_data': 1}} + name = "Test processing workflow" + + obs = qdb.processing_job.ProcessingWorkflow.from_default_workflow( + exp_user, dflt_wf, req_params, name=name) + self.assertEqual(obs.name, name) + self.assertEqual(obs.user, exp_user) + obs_graph = obs.graph + self.assertTrue(isinstance(obs_graph, nx.DiGraph)) + self.assertEqual(len(obs_graph.nodes()), 2) + obs_edges = obs_graph.edges() + self.assertEqual(len(obs_edges), 1) + obs_src = obs_edges[0][0] + obs_dst = obs_edges[0][1] + self.assertTrue(isinstance(obs_src, qdb.processing_job.ProcessingJob)) + self.assertTrue(isinstance(obs_dst, qdb.processing_job.ProcessingJob)) + self.assertTrue(obs_src.command, qdb.software.Command(1)) + self.assertTrue(obs_dst.command, qdb.software.Command(1)) + obs_params = obs_dst.parameters.values + exp_params = { + 'input_data': [obs_src.id, u'demultiplexed'], + 'reference': 1, + 'similarity': 0.97, + 'sortmerna_coverage': 0.97, + 'sortmerna_e_value': 1, + 'sortmerna_max_pos': 10000, + 'threads': 1} + self.assertEqual(obs_params, exp_params) + + def test_from_default_workflow_error(self): + with self.assertRaises(qdb.exceptions.QiitaDBError) as err: + qdb.processing_job.ProcessingWorkflow.from_default_workflow( + qdb.user.User('test@foo.bar'), qdb.software.DefaultWorkflow(1), + {}, name="Test name") + + exp = ('Provided required parameters do not match the initial set of ' + 'commands for the workflow. Command(s) "Split libraries FASTQ"' + ' are missing the required parameter set.') + self.assertEqual(str(err.exception), exp) + + req_params = {qdb.software.Command(1): {'input_data': 1}, + qdb.software.Command(2): {'input_data': 2}} + + with self.assertRaises(qdb.exceptions.QiitaDBError) as err: + qdb.processing_job.ProcessingWorkflow.from_default_workflow( + qdb.user.User('test@foo.bar'), qdb.software.DefaultWorkflow(1), + req_params, name="Test name") + exp = ('Provided required parameters do not match the initial set of ' + 'commands for the workflow. Paramters for command(s) ' + '"Split libraries" have been provided, but they are not the ' + 'initial commands for the workflow.') + self.assertEqual(str(err.exception), exp) + + def test_from_scratch(self): + exp_command = qdb.software.Command(1) + json_str = ( + '{"input_data": 1, "max_barcode_errors": 1.5, ' + '"barcode_type": "golay_12", "max_bad_run_length": 3, ' + '"rev_comp": false, "phred_quality_threshold": 3, ' + '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' + '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' + '"phred_offset": "auto"}') + exp_params = qdb.software.Parameters.load(exp_command, + json_str=json_str) + exp_user = qdb.user.User('test@foo.bar') + name = "Test processing workflow" + + obs = qdb.processing_job.ProcessingWorkflow.from_scratch( + exp_user, exp_params, name=name) + self.assertEqual(obs.name, name) + self.assertEqual(obs.user, exp_user) + obs_graph = obs.graph + self.assertTrue(isinstance(obs_graph, nx.DiGraph)) + nodes = obs_graph.nodes() + self.assertEqual(len(nodes), 1) + self.assertEqual(nodes[0].parameters, exp_params) + self.assertEqual(obs_graph.edges(), []) + + def test_add(self): + exp_command = qdb.software.Command(1) + json_str = ( + '{"input_data": 1, "max_barcode_errors": 1.5, ' + '"barcode_type": "golay_12", "max_bad_run_length": 3, ' + '"rev_comp": false, "phred_quality_threshold": 3, ' + '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' + '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0, ' + '"phred_offset": "auto"}') + exp_params = qdb.software.Parameters.load(exp_command, + json_str=json_str) + exp_user = qdb.user.User('test@foo.bar') + name = "Test processing workflow" + + obs = qdb.processing_job.ProcessingWorkflow.from_scratch( + exp_user, exp_params, name=name) + + parent = obs.graph.nodes()[0] + connections = {parent: {'demultiplexed': 'input_data'}} + dflt_params = qdb.software.DefaultParameters(10) + obs.add(dflt_params, connections=connections) + + obs_graph = obs.graph + self.assertTrue(isinstance(obs_graph, nx.DiGraph)) + obs_nodes = obs_graph.nodes() + self.assertEqual(len(obs_nodes), 2) + obs_edges = obs_graph.edges() + self.assertEqual(len(obs_edges), 1) + obs_src = obs_edges[0][0] + obs_dst = obs_edges[0][1] + self.assertEqual(obs_src, parent) + self.assertTrue(isinstance(obs_dst, qdb.processing_job.ProcessingJob)) + obs_params = obs_dst.parameters.values + exp_params = { + 'input_data': [parent.id, u'demultiplexed'], + 'reference': 1, + 'similarity': 0.97, + 'sortmerna_coverage': 0.97, + 'sortmerna_e_value': 1, + 'sortmerna_max_pos': 10000, + 'threads': 1} + self.assertEqual(obs_params, exp_params) + + # Adding a new root job + # This also tests that the `graph` property returns the graph correctly + # when there are root nodes that don't have any children + dflt_params = qdb.software.DefaultParameters(1) + obs.add(dflt_params, req_params={'input_data': 1}) + + obs_graph = obs.graph + self.assertTrue(isinstance(obs_graph, nx.DiGraph)) + root_obs_nodes = obs_graph.nodes() + self.assertEqual(len(root_obs_nodes), 3) + obs_edges = obs_graph.edges() + self.assertEqual(len(obs_edges), 1) + obs_new_jobs = set(root_obs_nodes) - set(obs_nodes) + self.assertEqual(len(obs_new_jobs), 1) + obs_job = obs_new_jobs.pop() + exp_params = {'barcode_type': u'golay_12', + 'input_data': 1, + 'max_bad_run_length': 3, + 'max_barcode_errors': 1.5, + 'min_per_read_length_fraction': 0.75, + 'phred_quality_threshold': 3, + 'rev_comp': False, + 'rev_comp_barcode': False, + 'rev_comp_mapping_barcodes': False, + 'sequence_max_n': 0, + 'phred_offset': 'auto'} + self.assertEqual(obs_job.parameters.values, exp_params) + + def test_add_error(self): + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + qdb.processing_job.ProcessingWorkflow(1).add({}, None) + + def test_remove(self): + exp_command = qdb.software.Command(1) + json_str = ( + '{"input_data": 1, "max_barcode_errors": 1.5, ' + '"barcode_type": "golay_12", "max_bad_run_length": 3, ' + '"rev_comp": false, "phred_quality_threshold": 3, ' + '"rev_comp_barcode": false, "rev_comp_mapping_barcodes": false, ' + '"min_per_read_length_fraction": 0.75, "sequence_max_n": 0,' + '"phred_offset": "auto"}') + exp_params = qdb.software.Parameters.load(exp_command, + json_str=json_str) + exp_user = qdb.user.User('test@foo.bar') + name = "Test processing workflow" + + tester = qdb.processing_job.ProcessingWorkflow.from_scratch( + exp_user, exp_params, name=name) + + parent = tester.graph.nodes()[0] + connections = {parent: {'demultiplexed': 'input_data'}} + dflt_params = qdb.software.DefaultParameters(10) + tester.add(dflt_params, connections=connections) + + self.assertEqual(len(tester.graph.nodes()), 2) + tester.remove(tester.graph.edges()[0][1]) + + g = tester.graph + obs_nodes = g.nodes() + self.assertEqual(len(obs_nodes), 1) + self.assertEqual(obs_nodes[0], parent) + self.assertEqual(g.edges(), []) + + # Test with cascade = true + exp_user = qdb.user.User('test@foo.bar') + dflt_wf = qdb.software.DefaultWorkflow(1) + req_params = {qdb.software.Command(1): {'input_data': 1}} + name = "Test processing workflow" + + tester = qdb.processing_job.ProcessingWorkflow.from_default_workflow( + exp_user, dflt_wf, req_params, name=name) + + tester.remove(tester.graph.edges()[0][0], cascade=True) + + self.assertEqual(tester.graph.nodes(), []) + + def test_remove_error(self): + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + qdb.processing_job.ProcessingWorkflow(1).remove( + qdb.processing_job.ProcessingJob( + 'b72369f9-a886-4193-8d3d-f7b504168e75')) + + exp_user = qdb.user.User('test@foo.bar') + dflt_wf = qdb.software.DefaultWorkflow(1) + req_params = {qdb.software.Command(1): {'input_data': 1}} + name = "Test processing workflow" + + tester = qdb.processing_job.ProcessingWorkflow.from_default_workflow( + exp_user, dflt_wf, req_params, name=name) + + with self.assertRaises( + qdb.exceptions.QiitaDBOperationNotPermittedError): + tester.remove(tester.graph.edges()[0][0]) + + +if __name__ == '__main__': + main() From 46c322d5967753eefad2381407f3bc41503ca0db Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:34:12 -0800 Subject: [PATCH 25/32] Removing QiitaStatusObject because it is not used --- qiita_db/base.py | 104 ------------------------------------- qiita_db/investigation.py | 2 +- qiita_db/test/test_base.py | 54 ------------------- 3 files changed, 1 insertion(+), 159 deletions(-) diff --git a/qiita_db/base.py b/qiita_db/base.py index 084333616..b2fadaccc 100644 --- a/qiita_db/base.py +++ b/qiita_db/base.py @@ -14,7 +14,6 @@ :toctree: generated/ QiitaObject - QiitaStatusObject """ # ----------------------------------------------------------------------------- @@ -220,106 +219,3 @@ def __hash__(self): def id(self): r"""The object id on the storage system""" return self._id - - -class QiitaStatusObject(QiitaObject): - r"""Base class for any qiita_db object with a status property - - Attributes - ---------- - status - - Methods - ------- - check_status - _status_setter_checks - """ - - @property - def status(self): - r"""String with the current status of the analysis""" - # Get the DB status of the object - with qdb.sql_connection.TRN: - sql = """SELECT status FROM qiita.{0}_status - WHERE {0}_status_id = ( - SELECT {0}_status_id FROM qiita.{0} - WHERE {0}_id = %s)""".format(self._table) - qdb.sql_connection.TRN.add(sql, [self._id]) - return qdb.sql_connection.TRN.execute_fetchlast() - - def _status_setter_checks(self): - r"""Perform any extra checks that needed to be done before setting the - object status on the database. Should be overwritten by the subclasses - """ - raise qdb.exceptions.QiitaDBNotImplementedError() - - @status.setter - def status(self, status): - r"""Change the status of the analysis - - Parameters - ---------- - status: str - The new object status - """ - with qdb.sql_connection.TRN: - # Perform any extra checks needed before - # we update the status in the DB - self._status_setter_checks() - - # Update the status of the object - sql = """UPDATE qiita.{0} SET {0}_status_id = ( - SELECT {0}_status_id FROM qiita.{0}_status - WHERE status = %s) - WHERE {0}_id = %s""".format(self._table) - qdb.sql_connection.TRN.add(sql, [status, self._id]) - qdb.sql_connection.TRN.execute() - - def check_status(self, status, exclude=False): - r"""Checks status of object. - - Parameters - ---------- - status: iterable - Iterable of statuses to check against. - exclude: bool, optional - If True, will check that database status is NOT one of the statuses - passed. Default False. - - Returns - ------- - bool - True if the object status is in the desired set of statuses. False - otherwise. - - Notes - ----- - This assumes the following database setup is in place: For a given - cls._table setting, such as "table", there is a corresponding table - with the name "table_status" holding the status entries allowed. This - table has a column called "status" that holds the values corresponding - to what is passed as status in this function and a column - "table_status_id" corresponding to the column of the same name in - "table". - - Table setup: - foo: foo_status_id ----> foo_status: foo_status_id, status - """ - with qdb.sql_connection.TRN: - # Get all available statuses - sql = "SELECT DISTINCT status FROM qiita.{0}_status".format( - self._table) - qdb.sql_connection.TRN.add(sql) - # We need to access to the results of the last SQL query, - # hence indexing using -1 - avail_status = [ - x[0] for x in qdb.sql_connection.TRN.execute_fetchindex()] - - # Check that all the provided status are valid status - if set(status).difference(avail_status): - raise ValueError("%s are not valid status values" - % set(status).difference(avail_status)) - - # Get the DB status of the object - dbstatus = self.status - return dbstatus not in status if exclude else dbstatus in status diff --git a/qiita_db/investigation.py b/qiita_db/investigation.py index 9f04ada0f..a2c07b1c2 100644 --- a/qiita_db/investigation.py +++ b/qiita_db/investigation.py @@ -23,7 +23,7 @@ REQUIRED_KEYS = {"name", "description", "contact_person"} -class Investigation(qdb.base.QiitaStatusObject): +class Investigation(qdb.base.QiitaObject): """ Study object to access to the Qiita Study information diff --git a/qiita_db/test/test_base.py b/qiita_db/test/test_base.py index b22002bc5..d001e5f1a 100644 --- a/qiita_db/test/test_base.py +++ b/qiita_db/test/test_base.py @@ -84,59 +84,5 @@ def test_not_equal_type(self): self.assertNotEqual(self.tester, new) -@qiita_test_checker() -class QiitaStatusObjectTest(TestCase): - """Tests that the QiitaStatusObject class functions act correctly""" - - def setUp(self): - # We need an actual subclass in order to test the equality functions - self.tester = qdb.analysis.Analysis(1) - - def test_status(self): - """Correctly returns the status of the object""" - self.assertEqual(self.tester.status, "in_construction") - - def test_check_status_single(self): - """check_status works passing a single status""" - self.assertTrue(self.tester.check_status(["in_construction"])) - self.assertFalse(self.tester.check_status(["queued"])) - - def test_check_status_exclude_single(self): - """check_status works passing a single status and the exclude flag""" - self.assertTrue(self.tester.check_status(["public"], exclude=True)) - self.assertFalse(self.tester.check_status(["in_construction"], - exclude=True)) - - def test_check_status_list(self): - """check_status work passing a list of status""" - self.assertTrue(self.tester.check_status( - ["in_construction", "queued"])) - self.assertFalse(self.tester.check_status( - ["public", "queued"])) - - def test_check_status_exclude_list(self): - """check_status work passing a list of status and the exclude flag""" - self.assertTrue(self.tester.check_status( - ["public", "queued"], exclude=True)) - self.assertFalse(self.tester.check_status( - ["in_construction", "queued"], exclude=True)) - - def test_check_status_unknown_status(self): - """check_status raises an error if an invalid status is provided""" - with self.assertRaises(ValueError): - self.tester.check_status(["foo"]) - - with self.assertRaises(ValueError): - self.tester.check_status(["foo"], exclude=True) - - def test_check_status_unknown_status_list(self): - """check_status raises an error if an invalid status list is provided - """ - with self.assertRaises(ValueError): - self.tester.check_status(["foo", "bar"]) - - with self.assertRaises(ValueError): - self.tester.check_status(["foo", "bar"], exclude=True) - if __name__ == '__main__': main() From 4d2e14ded1c429f6076a54649c3a85023fcb6cf8 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:45:15 -0800 Subject: [PATCH 26/32] fixing metautil --- qiita_db/meta_util.py | 17 ++++++++++++----- qiita_db/test/test_meta_util.py | 15 +++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/qiita_db/meta_util.py b/qiita_db/meta_util.py index 441eca8ae..1f8956db5 100644 --- a/qiita_db/meta_util.py +++ b/qiita_db/meta_util.py @@ -109,17 +109,24 @@ def get_accessible_filepath_ids(user): filepath_ids.update({fid for fid, _ in pt.get_filepaths()}) # Then add the filepaths of the sample template - filepath_ids.update( - {fid - for fid, _ in artifact.study.sample_template.get_filepaths()}) + study = artifact.study + if study: + filepath_ids.update( + {fid + for fid, _ in study.sample_template.get_filepaths()}) # Next, analyses # Same as before, there are public, private, and shared analyses = qdb.analysis.Analysis.get_by_status('public') | \ user.private_analyses | user.shared_analyses - for analysis in analyses: - filepath_ids.update(analysis.all_associated_filepath_ids) + if analyses: + sql = """SELECT filepath_id + FROM qiita.analysis_filepath + WHERE analysis_id IN %s""" + sql_args = tuple([a.id for a in analyses]) + qdb.sql_connection.TRN.add(sql, [sql_args]) + filepath_ids.update(qdb.sql_connection.TRN.execute_fetchflatten()) return filepath_ids diff --git a/qiita_db/test/test_meta_util.py b/qiita_db/test/test_meta_util.py index 653b65237..4feec4d12 100644 --- a/qiita_db/test/test_meta_util.py +++ b/qiita_db/test/test_meta_util.py @@ -46,13 +46,13 @@ def test_get_accessible_filepath_ids(self): obs = qdb.meta_util.get_accessible_filepath_ids( qdb.user.User('shared@foo.bar')) self.assertItemsEqual(obs, { - 1, 2, 3, 4, 5, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}) + 1, 2, 3, 4, 5, 9, 12, 16, 17, 18, 19, 20, 21}) # Now shared should not have access to the study files self._unshare_studies() obs = qdb.meta_util.get_accessible_filepath_ids( qdb.user.User('shared@foo.bar')) - self.assertItemsEqual(obs, {16, 14, 15, 13}) + self.assertItemsEqual(obs, {16}) # Now shared should not have access to any files self._unshare_analyses() @@ -64,10 +64,11 @@ def test_get_accessible_filepath_ids(self): self._set_artifact_public() obs = qdb.meta_util.get_accessible_filepath_ids( qdb.user.User('shared@foo.bar')) - self.assertEqual(obs, {1, 2, 3, 4, 5, 9, 12, 17, 18, 19, 20, 21}) + self.assertEqual( + obs, {1, 2, 3, 4, 5, 9, 12, 15, 16, 17, 18, 19, 20, 21, 22}) # Test that it doesn't break: if the SampleTemplate hasn't been added - exp = {1, 2, 3, 4, 5, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21} + exp = {1, 2, 3, 4, 5, 9, 12, 15, 16, 17, 18, 19, 20, 21, 22} obs = qdb.meta_util.get_accessible_filepath_ids( qdb.user.User('test@foo.bar')) self.assertEqual(obs, exp) @@ -99,9 +100,11 @@ def test_get_accessible_filepath_ids(self): self.assertEqual(obs, exp) # admin should have access to everything - count = self.conn_handler.execute_fetchone("SELECT count(*) FROM " - "qiita.filepath")[0] + count = self.conn_handler.execute_fetchone( + "SELECT last_value FROM qiita.filepath_filepath_id_seq")[0] exp = set(range(1, count + 1)) + exp.discard(13) + exp.discard(14) obs = qdb.meta_util.get_accessible_filepath_ids( qdb.user.User('admin@foo.bar')) self.assertEqual(obs, exp) From befb4e24238e300828c305f32093c75006ee5106 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:51:57 -0800 Subject: [PATCH 27/32] Fixing porntal, setup and sql tests --- qiita_db/portal.py | 11 ++--------- qiita_db/test/test_portal.py | 7 ------- qiita_db/test/test_setup.py | 15 --------------- qiita_db/test/test_sql.py | 20 -------------------- 4 files changed, 2 insertions(+), 51 deletions(-) diff --git a/qiita_db/portal.py b/qiita_db/portal.py index 659a50bac..8ddebad65 100644 --- a/qiita_db/portal.py +++ b/qiita_db/portal.py @@ -91,14 +91,10 @@ def create(cls, portal, desc): SELECT email FROM qiita.qiita_user LOOP INSERT INTO qiita.analysis - (email, name, description, dflt, - analysis_status_id) - VALUES (eml, eml || '-dflt', 'dflt', true, 1) + (email, name, description, dflt) + VALUES (eml, eml || '-dflt', 'dflt', true) RETURNING analysis_id INTO aid; - INSERT INTO qiita.analysis_workflow (analysis_id, step) - VALUES (aid, 2); - INSERT INTO qiita.analysis_portal (analysis_id, portal_type_id) VALUES (aid, pid); @@ -162,9 +158,6 @@ def delete(portal): DELETE FROM qiita.analysis_portal WHERE analysis_id = aid; - DELETE FROM qiita.analysis_workflow - WHERE analysis_id = aid; - DELETE FROM qiita.analysis_sample WHERE analysis_id = aid; diff --git a/qiita_db/test/test_portal.py b/qiita_db/test/test_portal.py index 53b729fef..abe849d10 100644 --- a/qiita_db/test/test_portal.py +++ b/qiita_db/test/test_portal.py @@ -72,13 +72,6 @@ def test_remove_portal(self): qdb.portal.Portal.delete("QIITA") qdb.portal.Portal.create("NEWPORTAL2", "SOMEDESC") - # Add analysis to this new portal and make sure error raised - qiita_config.portal = "NEWPORTAL2" - qdb.analysis.Analysis.create( - qdb.user.User("test@foo.bar"), "newportal analysis", "desc") - qiita_config.portal = "QIITA" - with self.assertRaises(qdb.exceptions.QiitaDBError): - qdb.portal.Portal.delete("NEWPORTAL2") # Add study to this new portal and make sure error raised info = { diff --git a/qiita_db/test/test_setup.py b/qiita_db/test/test_setup.py index 7dce0da6b..60dfd78f0 100644 --- a/qiita_db/test/test_setup.py +++ b/qiita_db/test/test_setup.py @@ -62,18 +62,9 @@ def test_prep_1(self): def test_reference(self): self.assertEqual(get_count("qiita.reference"), 2) - def test_job(self): - self.assertEqual(get_count("qiita.job"), 3) - def test_analysis(self): self.assertEqual(get_count("qiita.analysis"), 10) - def test_analysis_job(self): - self.assertEqual(get_count("qiita.analysis_job"), 3) - - def test_analysis_workflow(self): - self.assertEqual(get_count("qiita.analysis_workflow"), 2) - def test_analysis_filepath(self): self.assertEqual(get_count("qiita.analysis_filepath"), 2) @@ -83,12 +74,6 @@ def test_analysis_sample(self): def test_analysis_users(self): self.assertEqual(get_count("qiita.analysis_users"), 1) - def test_job_results_filepath(self): - self.assertEqual(get_count("qiita.job_results_filepath"), 2) - - def test_command_data_type(self): - self.assertEqual(get_count("qiita.command_data_type"), 14) - def test_ontology(self): self.assertTrue(check_count('qiita.ontology', 1)) diff --git a/qiita_db/test/test_sql.py b/qiita_db/test/test_sql.py index 3693dc336..12070577e 100644 --- a/qiita_db/test/test_sql.py +++ b/qiita_db/test/test_sql.py @@ -20,26 +20,6 @@ def tearDown(self): if exists(fp): remove(fp) - def test_collection_job_trigger_bad_insert(self): - # make sure an incorrect job raises an error - with self.assertRaises(ValueError): - self.conn_handler.execute( - 'INSERT INTO qiita.collection_job (collection_id, job_id) ' - 'VALUES (1, 3)') - obs = self.conn_handler.execute_fetchall( - 'SELECT * FROM qiita.collection_job') - self.assertNotIn([[1, 3]], obs) - - def test_collection_job_trigger(self): - # make sure a correct job inserts successfully - self.conn_handler.execute( - 'INSERT INTO qiita.collection_job (collection_id, job_id) ' - 'VALUES (1, 2)') - obs = self.conn_handler.execute_fetchall( - 'SELECT * FROM qiita.collection_job') - exp = [[1, 1], [1, 2]] - self.assertEqual(obs, exp) - def test_find_artifact_roots_is_root(self): """Correctly returns the root if the artifact is already the root""" sql = "SELECT * FROM qiita.find_artifact_roots(%s)" From 09f272230e371e000fb980075dbc156d20cdc6e6 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 02:57:55 -0800 Subject: [PATCH 28/32] Fixing user and util --- qiita_db/test/test_util.py | 15 +++++-------- qiita_db/user.py | 5 ++--- qiita_db/util.py | 43 -------------------------------------- 3 files changed, 7 insertions(+), 56 deletions(-) diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py index 6383bc6ef..4c22e8f57 100644 --- a/qiita_db/test/test_util.py +++ b/qiita_db/test/test_util.py @@ -87,7 +87,7 @@ def test_convert_to_id(self): self.assertEqual( qdb.util.convert_to_id("directory", "filepath_type"), 8) self.assertEqual( - qdb.util.convert_to_id("running", "analysis_status", "status"), 3) + qdb.util.convert_to_id("private", "visibility", "visibility"), 3) self.assertEqual( qdb.util.convert_to_id("EMP", "portal_type", "portal"), 2) @@ -99,7 +99,9 @@ def test_convert_to_id_bad_value(self): def test_get_artifact_types(self): obs = qdb.util.get_artifact_types() exp = {'SFF': 1, 'FASTA_Sanger': 2, 'FASTQ': 3, 'FASTA': 4, - 'per_sample_FASTQ': 5, 'Demultiplexed': 6, 'BIOM': 7} + 'per_sample_FASTQ': 5, 'Demultiplexed': 6, 'BIOM': 7, + 'distance_matrix': 8L, 'rarefaction_curves': 9L, + 'taxa_summary': 10L} self.assertEqual(obs, exp) obs = qdb.util.get_artifact_types(key_by_id=True) @@ -133,7 +135,7 @@ def test_get_data_types(self): """Tests that get_data_types works with valid arguments""" obs = qdb.util.get_data_types() exp = {'16S': 1, '18S': 2, 'ITS': 3, 'Proteomic': 4, 'Metabolomic': 5, - 'Metagenomic': 6} + 'Metagenomic': 6, 'Multiomic': 7} self.assertEqual(obs, exp) obs = qdb.util.get_data_types(key='data_type_id') @@ -665,13 +667,6 @@ def test_filepath_ids_to_rel_paths(self): self.assertEqual(obs, exp) - def test_check_access_to_analysis_result(self): - obs = qdb.util.check_access_to_analysis_result('test@foo.bar', - '1_job_result.txt') - exp = [13] - - self.assertEqual(obs, exp) - def test_add_message(self): count = qdb.util.get_count('qiita.message') + 1 user = qdb.user.User.create('new@test.bar', 'password') diff --git a/qiita_db/user.py b/qiita_db/user.py index 415cbaa63..3c9335c3d 100644 --- a/qiita_db/user.py +++ b/qiita_db/user.py @@ -318,9 +318,8 @@ def verify_code(cls, email, code, code_type): qdb.sql_connection.TRN.add(sql) an_sql = """INSERT INTO qiita.analysis - (email, name, description, dflt, - analysis_status_id) - VALUES (%s, %s, %s, %s, 1) + (email, name, description, dflt) + VALUES (%s, %s, %s, %s) RETURNING analysis_id""" ap_sql = """INSERT INTO qiita.analysis_portal (analysis_id, portal_type_id) diff --git a/qiita_db/util.py b/qiita_db/util.py index dabb5047c..ac2480bb8 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -1063,49 +1063,6 @@ def get_pubmed_ids_from_dois(doi_ids): for row in qdb.sql_connection.TRN.execute_fetchindex()} -def check_access_to_analysis_result(user_id, requested_path): - """Get filepath IDs for a particular requested_path, if user has access - - This function is only applicable for analysis results. - - Parameters - ---------- - user_id : str - The ID (email address) that identifies the user - requested_path : str - The path that the user requested - - Returns - ------- - list of int - The filepath IDs associated with the requested path - """ - with qdb.sql_connection.TRN: - # Get all filepath ids associated with analyses that the user has - # access to where the filepath is the base_requested_fp from above. - # There should typically be only one matching filepath ID, but for - # safety we allow for the possibility of multiple. - sql = """SELECT fp.filepath_id - FROM qiita.analysis_job aj JOIN ( - SELECT analysis_id FROM qiita.analysis A - JOIN qiita.analysis_status stat - ON A.analysis_status_id = stat.analysis_status_id - WHERE stat.analysis_status_id = 6 - UNION - SELECT analysis_id FROM qiita.analysis_users - WHERE email = %s - UNION - SELECT analysis_id FROM qiita.analysis WHERE email = %s - ) ids ON aj.analysis_id = ids.analysis_id - JOIN qiita.job_results_filepath jrfp ON - aj.job_id = jrfp.job_id - JOIN qiita.filepath fp ON jrfp.filepath_id = fp.filepath_id - WHERE fp.filepath = %s""" - qdb.sql_connection.TRN.add(sql, [user_id, user_id, requested_path]) - - return qdb.sql_connection.TRN.execute_fetchflatten() - - def infer_status(statuses): """Infers an object status from the statuses passed in From d1cdc8e95ebcbc7edad032769cea3732d1aa12b7 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 03:09:40 -0800 Subject: [PATCH 29/32] Fixing qiita_db --- qiita_db/metadata_template/test/test_prep_template.py | 4 ---- qiita_db/metadata_template/test/test_sample_template.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/qiita_db/metadata_template/test/test_prep_template.py b/qiita_db/metadata_template/test/test_prep_template.py index d4d6734e0..729147e2f 100644 --- a/qiita_db/metadata_template/test/test_prep_template.py +++ b/qiita_db/metadata_template/test/test_prep_template.py @@ -876,8 +876,6 @@ def _common_creation_checks(self, pt, fp_count): # prep and qiime files have been created filepaths = pt.get_filepaths() self.assertEqual(len(filepaths), 2) - self.assertEqual(filepaths[0][0], fp_count + 2) - self.assertEqual(filepaths[1][0], fp_count + 1) def test_create(self): """Creates a new PrepTemplate""" @@ -998,8 +996,6 @@ def test_create_warning(self): # prep and qiime files have been created filepaths = pt.get_filepaths() self.assertEqual(len(filepaths), 2) - self.assertEqual(filepaths[0][0], fp_count + 2) - self.assertEqual(filepaths[1][0], fp_count + 1) def test_create_investigation_type_error(self): """Create raises an error if the investigation_type does not exists""" diff --git a/qiita_db/metadata_template/test/test_sample_template.py b/qiita_db/metadata_template/test/test_sample_template.py index 8808ecc0f..3515a6ed8 100644 --- a/qiita_db/metadata_template/test/test_sample_template.py +++ b/qiita_db/metadata_template/test/test_sample_template.py @@ -1320,7 +1320,7 @@ def test_get_filepath(self): # change based on time and the same functionality is being tested # in data.py exp_id = self.conn_handler.execute_fetchone( - "SELECT count(1) FROM qiita.filepath")[0] + 1 + "SELECT last_value FROM qiita.filepath_filepath_id_seq")[0] + 1 st = qdb.metadata_template.sample_template.SampleTemplate.create( self.metadata, self.new_study) self.assertEqual(st.get_filepaths()[0][0], exp_id) From 43eb1ed844585447145f2bd430723df704cf7a44 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Mon, 16 Jan 2017 13:26:15 -0800 Subject: [PATCH 30/32] Addressing @antgonza's comments --- qiita_db/support_files/patches/47.sql | 2 +- .../patches/python_patches/47.py | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/qiita_db/support_files/patches/47.sql b/qiita_db/support_files/patches/47.sql index 829ed7063..231dac4d2 100644 --- a/qiita_db/support_files/patches/47.sql +++ b/qiita_db/support_files/patches/47.sql @@ -3,7 +3,7 @@ -- database backend that supports the analysis pipeline. -- After exploring the data on the database, we realized that -- there are a lot of inconsistencies in the data. Unfortunately, this --- makes the process of trasnferring the data from the old structure +-- makes the process of transferring the data from the old structure -- to the new one a bit more challenging, as we will need to handle -- different special cases. Furthermore, all the information needed is not -- present in the database, since it requires checking BIOM files. Due to these diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index 22242d5c8..f71b420fb 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -648,6 +648,7 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, transfer_job() +errors = [] with TRN: # Unlink the analysis from the biom table filepaths # Magic number 7 -> biom filepath type @@ -656,22 +657,24 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, FROM qiita.filepath WHERE filepath_type_id = 7)""" TRN.add(sql) - # Delete old structures that are not used anymore - TRN.add("DROP TABLE qiita.collection_job") - TRN.add("DROP TABLE qiita.collection_analysis") - TRN.add("DROP TABLE qiita.collection_users") - TRN.add("DROP TABLE qiita.collection") - TRN.add("DROP TABLE qiita.collection_status") - TRN.add("DROP TABLE qiita.analysis_workflow") - TRN.add("DROP TABLE qiita.analysis_chain") - TRN.add("DROP TABLE qiita.analysis_job") - TRN.add("DROP TABLE qiita.job_results_filepath") - TRN.add("DROP TABLE qiita.job") - TRN.add("DROP TABLE qiita.job_status") - TRN.add("DROP TABLE qiita.command_data_type") - TRN.add("DROP TABLE qiita.command") - TRN.add("DROP TABLE qiita.analysis_status") TRN.execute() + # Delete old structures that are not used anymore + tables = ["collection_job", "collection_analysis", "collection_users", + "collection", "collection_status", "analysis_workflow", + "analysis_chain", "analysis_job", "job_results_filepath", "job", + "job_status", "command_data_type", "command", "analysis_status"] + for table in tables: + TRN.add("DROP TABLE qiita.%s" % table) + try: + TRN.execute() + except Exception as e: + errors.append("Error deleting table %s: %s" % (table, str(e))) + # Purge filepaths -purge_filepaths() +try: + purge_filepaths() +except Exception as e: + errors.append("Error purging filepaths: %s" % str(e)) + +print "\n".join(errors) From d016833de866506f91082b37181fafd3144eb100 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Sun, 29 Jan 2017 17:34:48 -0800 Subject: [PATCH 31/32] Taking into account non-phylogenetic metrics in beta diversity --- .../support_files/patches/python_patches/47.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/qiita_db/support_files/patches/python_patches/47.py b/qiita_db/support_files/patches/python_patches/47.py index f71b420fb..43f1b65a9 100644 --- a/qiita_db/support_files/patches/python_patches/47.py +++ b/qiita_db/support_files/patches/python_patches/47.py @@ -630,9 +630,14 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, # Beta diversity cmd_id = bdiv_cmd_id tree_fp = loads(job_data['options'])['--tree_fp'] - params = ('{"biom_table":%d,"tree":"%s","metrics":' - '["unweighted_unifrac","weighted_unifrac"]}' - % (initial_biom_id, tree_fp)) + if tree_fp: + params = ('{"biom_table":%d,"tree":"%s","metrics":' + '["unweighted_unifrac","weighted_unifrac"]}' + % (initial_biom_id, tree_fp)) + else: + params = ('{"biom_table":%d,"metrics":["bray_curtis",' + '"gower","canberra","pearson"]}' + % initial_biom_id) output_artifact_type_id = dm_atype_id cmd_out_id = bdiv_cmd_out_id else: @@ -646,7 +651,9 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, output_artifact_type_id = rc_atype_id cmd_out_id = arare_cmd_out_id - transfer_job() + transfer_job(analysis, cmd_id, params, initial_biom_id, + job_data, cmd_out_id, biom_data, + output_artifact_type_id) errors = [] with TRN: @@ -677,4 +684,5 @@ def transfer_job(analysis, command_id, params, input_artifact_id, job_data, except Exception as e: errors.append("Error purging filepaths: %s" % str(e)) -print "\n".join(errors) +if errors: + print "\n".join(errors) From a80c0ac0749598f71e5525dd8c4b7ba1638249a8 Mon Sep 17 00:00:00 2001 From: Jose Navas Date: Wed, 1 Feb 2017 07:31:03 -0800 Subject: [PATCH 32/32] Addressing @antgonza's comments --- qiita_db/handlers/tests/test_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_db/handlers/tests/test_analysis.py b/qiita_db/handlers/tests/test_analysis.py index 8a2340204..2c20c64a3 100644 --- a/qiita_db/handlers/tests/test_analysis.py +++ b/qiita_db/handlers/tests/test_analysis.py @@ -22,7 +22,7 @@ def test_get_analysis(self): exp = qdb.analysis.Analysis(1) self.assertEqual(obs, exp) - # It doesn't eist + # It doesn't exist with self.assertRaises(HTTPError): _get_analysis(100)