mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'release_25.0' into dev
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+77
@@ -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$
|
||||
"""
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user