diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst index 89afa67da80..539375070e7 100644 --- a/doc/source/admin/galaxy_options.rst +++ b/doc/source/admin/galaxy_options.rst @@ -1222,12 +1222,13 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Description: - Whether to enable the tool document cache. This cache stores - expanded XML strings. Enabling the tool cache results in slightly - faster startup times. The tool cache is backed by a SQLite - database, which cannot be stored on certain network disks. The - cache location is configurable with the ``tool_cache_data_dir`` - tag in tool config files. + This option is deprecated, and the tool document cache will be + removed in the next release. Whether to enable the tool document + cache. This cache stores expanded XML strings. Enabling the tool + cache results in slightly faster startup times. The tool cache is + backed by a SQLite database, which cannot be stored on certain + network disks. The cache location is configurable with the + ``tool_cache_data_dir`` tag in tool config files. :Default: ``false`` :Type: bool diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample index 53f4e5a91dc..27f517a2e4b 100644 --- a/lib/galaxy/config/sample/galaxy.yml.sample +++ b/lib/galaxy/config/sample/galaxy.yml.sample @@ -935,12 +935,13 @@ galaxy: # generated commands run in sh. #default_job_shell: /bin/bash - # Whether to enable the tool document cache. This cache stores - # expanded XML strings. Enabling the tool cache results in slightly - # faster startup times. The tool cache is backed by a SQLite database, - # which cannot be stored on certain network disks. The cache location - # is configurable with the ``tool_cache_data_dir`` tag in tool config - # files. + # This option is deprecated, and the tool document cache will be + # removed in the next release. Whether to enable the tool document + # cache. This cache stores expanded XML strings. Enabling the tool + # cache results in slightly faster startup times. The tool cache is + # backed by a SQLite database, which cannot be stored on certain + # network disks. The cache location is configurable with the + # ``tool_cache_data_dir`` tag in tool config files. #enable_tool_document_cache: false # Directory in which the toolbox search index is stored. The value of diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index 173ca3ab233..e798a0a310d 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -887,6 +887,8 @@ mapping: default: false required: false desc: | + This option is deprecated, and the tool document cache will be removed + in the next release. Whether to enable the tool document cache. This cache stores expanded XML strings. Enabling the tool cache results in slightly faster startup times. The tool cache is backed by a SQLite database, which cannot diff --git a/lib/galaxy/dependencies/conditional-requirements.txt b/lib/galaxy/dependencies/conditional-requirements.txt index 64faffe237b..dcc846e2016 100644 --- a/lib/galaxy/dependencies/conditional-requirements.txt +++ b/lib/galaxy/dependencies/conditional-requirements.txt @@ -56,7 +56,7 @@ pygithub influxdb # Deep learning packages for tool recommendation -tensorflow==2.9.3 +tensorflow==2.15.1 # weasyprint has problematic requirements (cairo, Pango, see: # https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#linux) diff --git a/lib/galaxy/model/migrations/alembic/versions_gxy/a91ea1d97111_update_trigger_to_use_clock_timestamp_.py b/lib/galaxy/model/migrations/alembic/versions_gxy/a91ea1d97111_update_trigger_to_use_clock_timestamp_.py new file mode 100644 index 00000000000..905719763fb --- /dev/null +++ b/lib/galaxy/model/migrations/alembic/versions_gxy/a91ea1d97111_update_trigger_to_use_clock_timestamp_.py @@ -0,0 +1,77 @@ +"""Update postgresql trigger to use clock_timestamp function + +NOTE: This migration will not be applied on SQLIte. + +Revision ID: a91ea1d97111 +Revises: f070559879f1 +Create Date: 2025-06-09 12:21:53.427419 + +""" + +from alembic import op + +from galaxy.model.migrations.util import _is_sqlite + +# revision identifiers, used by Alembic. +revision = "a91ea1d97111" +down_revision = "f070559879f1" +branch_labels = None +depends_on = None + + +def upgrade(): + if not _is_sqlite(): + create_functions_and_triggers("clock_timestamp()") + + +def downgrade(): + if not _is_sqlite(): + create_functions_and_triggers("CURRENT_TIMESTAMP") + + +def create_functions_and_triggers(timestamp): + version_info = op.get_bind().engine.dialect.server_version_info + # For offline mode (version_info is None), we assume that version > 10 + if version_info and version_info[0] > 10 or not version_info: + trigger_fn = statement_trigger_fn + else: + trigger_fn = row_trigger_fn + + for id_field in ["history_id", "id"]: + function_name = f"fn_audit_history_by_{id_field}" + stmt = trigger_fn(function_name, id_field, timestamp) + op.execute(stmt) + + +def statement_trigger_fn(function_name, id_field, timestamp): + return f""" + CREATE OR REPLACE FUNCTION {function_name}() + RETURNS TRIGGER + LANGUAGE 'plpgsql' + AS $BODY$ + BEGIN + INSERT INTO history_audit (history_id, update_time) + SELECT DISTINCT {id_field}, {timestamp} AT TIME ZONE 'UTC' + FROM new_table + WHERE {id_field} IS NOT NULL + ON CONFLICT DO NOTHING; + RETURN NULL; + END; + $BODY$ + """ + + +def row_trigger_fn(function_name, id_field, timestamp): + return f""" + CREATE OR REPLACE FUNCTION {function_name}() + RETURNS TRIGGER + LANGUAGE 'plpgsql' + AS $BODY$ + BEGIN + INSERT INTO history_audit (history_id, update_time) + VALUES (NEW.{id_field}, {timestamp} AT TIME ZONE 'UTC') + ON CONFLICT DO NOTHING; + RETURN NULL; + END; + $BODY$ + """ diff --git a/lib/galaxy/model/migrations/dbscript.py b/lib/galaxy/model/migrations/dbscript.py index 0d0b4962352..04b88411e97 100644 --- a/lib/galaxy/model/migrations/dbscript.py +++ b/lib/galaxy/model/migrations/dbscript.py @@ -46,8 +46,8 @@ REVISION_TAGS = { "24.1": "04288b6a5b25", "release_24.2": "a4c3ef999ab5", "24.2": "a4c3ef999ab5", - "release_25.0": "f070559879f1", - "25.0": "f070559879f1", + "release_25.0": "a91ea1d97111", + "25.0": "a91ea1d97111", } diff --git a/lib/galaxy/model/triggers/history_update_time_field.py b/lib/galaxy/model/triggers/history_update_time_field.py deleted file mode 100644 index 076a2aa4835..00000000000 --- a/lib/galaxy/model/triggers/history_update_time_field.py +++ /dev/null @@ -1,160 +0,0 @@ -""" -Database trigger installation and removal -""" - -from galaxy.model.triggers.utils import execute_statements - - -def install_timestamp_triggers(engine): - """ - Install update_time propagation triggers for history table - """ - statements = get_timestamp_install_sql(engine.name) - execute_statements(engine, statements) - - -def drop_timestamp_triggers(engine): - """ - Remove update_time propagation triggers for history table - """ - statements = get_timestamp_drop_sql(engine.name) - execute_statements(engine, statements) - - -def get_timestamp_install_sql(variant): - """ - Generate a list of SQL statements for installation of timestamp triggers - """ - - sql = get_timestamp_drop_sql(variant) - - if "postgres" in variant: - # PostgreSQL has a separate function definition and a trigger - # assignment. The first two statements the functions, and - # the later assign those functions to triggers on tables - - fn_name = "update_history_update_time" - sql.append(build_pg_timestamp_fn(fn_name, "history", source_key="history_id")) - sql.append(build_pg_trigger("history_dataset_association", fn_name)) - sql.append(build_pg_trigger("history_dataset_collection_association", fn_name)) - - else: - # Other database variants are more granular. Requiring separate - # statements for INSERT/UPDATE/DELETE, and the body of the trigger - # is not necessarily reusable with a function - - for operation in ["INSERT", "UPDATE", "DELETE"]: - # change hda -> update history - sql.append( - build_timestamp_trigger(operation, "history_dataset_association", "history", source_key="history_id") - ) - - # change hdca -> update history - sql.append( - build_timestamp_trigger( - operation, "history_dataset_collection_association", "history", source_key="history_id" - ) - ) - - return sql - - -def get_timestamp_drop_sql(variant): - """ - Generate a list of statements to drop the timestamp update triggers - """ - - sql = [] - - if "postgres" in variant: - sql.append("DROP FUNCTION IF EXISTS update_history_update_time() CASCADE;") - else: - for operation in ["INSERT", "UPDATE", "DELETE"]: - for when in ["BEFORE", "AFTER"]: - sql.append(build_drop_trigger(operation, "history_dataset_association", when)) - sql.append(build_drop_trigger(operation, "history_dataset_collection_association", when)) - - return sql - - -def build_pg_timestamp_fn(fn_name, target_table, source_key, target_key="id"): - """Generates a PostgreSQL history update timestamp function""" - - return f""" - CREATE OR REPLACE FUNCTION {fn_name}() - RETURNS trigger - LANGUAGE 'plpgsql' - AS $BODY$ - BEGIN - IF (TG_OP = 'DELETE') THEN - UPDATE {target_table} - SET update_time = (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - WHERE {target_key} = OLD.{source_key}; - RETURN OLD; - ELSEIF (TG_OP = 'UPDATE') THEN - UPDATE {target_table} - SET update_time = (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - WHERE {target_key} = NEW.{source_key} OR {target_key} = OLD.{source_key}; - RETURN NEW; - ELSIF (TG_OP = 'INSERT') THEN - UPDATE {target_table} - SET update_time = (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - WHERE {target_key} = NEW.{source_key}; - RETURN NEW; - END IF; - END; - $BODY$; - """ - - -def build_pg_trigger(source_table, fn_name, when="AFTER"): - """Assigns a PostgreSQL trigger to indicated table, calling user-defined function""" - when_initial = when.lower()[0] - trigger_name = f"trigger_{source_table}_{when_initial}iudr" - return f""" - CREATE TRIGGER {trigger_name} - {when} INSERT OR DELETE OR UPDATE - ON {source_table} - FOR EACH ROW - EXECUTE PROCEDURE {fn_name}(); - """ - - -def build_timestamp_trigger(operation, source_table, target_table, source_key, target_key="id", when="AFTER"): - """Creates a non-PostgreSQL update_time trigger""" - - trigger_name = get_trigger_name(operation, source_table, when) - - # three different update clauses depending on update/insert/delete - clause = "" - if operation == "DELETE": - clause = f"{target_key} = OLD.{source_key}" - elif operation == "UPDATE": - clause = f"{target_key} = NEW.{source_key} OR {target_key} = OLD.{source_key}" - else: - clause = f"{target_key} = NEW.{source_key}" - - return f""" - CREATE TRIGGER {trigger_name} - {when} {operation} - ON {source_table} - FOR EACH ROW - BEGIN - UPDATE {target_table} - SET update_time = CURRENT_TIMESTAMP - WHERE {clause}; - END; - """ - - -def build_drop_trigger(operation, source_table, when="AFTER"): - """Drops a non-PostgreSQL trigger by name""" - trigger_name = get_trigger_name(operation, source_table, when) - return f"DROP TRIGGER IF EXISTS {trigger_name}" - - -def get_trigger_name(operation, source_table, when): - """Non-PostgreSQL trigger name""" - op_initial = operation.lower()[0] - when_initial = when.lower()[0] - return f"trigger_{source_table}_{when_initial}{op_initial}r" diff --git a/lib/galaxy/model/triggers/update_audit_table.py b/lib/galaxy/model/triggers/update_audit_table.py index 5af2ab3567c..9aa02b60e3e 100644 --- a/lib/galaxy/model/triggers/update_audit_table.py +++ b/lib/galaxy/model/triggers/update_audit_table.py @@ -1,4 +1,4 @@ -from galaxy.model.triggers.utils import execute_statements +from sqlalchemy import DDL # function name prefix fn_prefix = "fn_audit_history_by" @@ -56,7 +56,7 @@ def _postgres_install(engine): AS $BODY$ BEGIN INSERT INTO history_audit (history_id, update_time) - SELECT DISTINCT {id_field}, CURRENT_TIMESTAMP AT TIME ZONE 'UTC' + SELECT DISTINCT {id_field}, clock_timestamp() AT TIME ZONE 'UTC' FROM new_table WHERE {id_field} IS NOT NULL ON CONFLICT DO NOTHING; @@ -75,7 +75,7 @@ def _postgres_install(engine): AS $BODY$ BEGIN INSERT INTO history_audit (history_id, update_time) - VALUES (NEW.{id_field}, CURRENT_TIMESTAMP AT TIME ZONE 'UTC') + VALUES (NEW.{id_field}, clock_timestamp() AT TIME ZONE 'UTC') ON CONFLICT DO NOTHING; RETURN NULL; END; @@ -174,3 +174,11 @@ def get_trigger_name(label, operation, when, statement=False): when_initial = when.lower()[0] rs = "s" if statement else "r" return f"trigger_{label}_{when_initial}{op_initial}{rs}" + + +def execute_statements(engine, raw_sql): + statements = raw_sql if isinstance(raw_sql, list) else [raw_sql] + with engine.begin() as connection: + for sql in statements: + cmd = DDL(sql) + connection.execute(cmd) diff --git a/lib/galaxy/model/triggers/utils.py b/lib/galaxy/model/triggers/utils.py deleted file mode 100644 index 6644d601697..00000000000 --- a/lib/galaxy/model/triggers/utils.py +++ /dev/null @@ -1,9 +0,0 @@ -from sqlalchemy import DDL - - -def execute_statements(engine, raw_sql): - statements = raw_sql if isinstance(raw_sql, list) else [raw_sql] - with engine.begin() as connection: - for sql in statements: - cmd = DDL(sql) - connection.execute(cmd)