# Buggy code
def collect_dynamic_outputs(
    job_context,
    output_collections,
):
    # unmapped outputs do not correspond to explicit outputs of the tool, they were inferred entirely
    # from the tool provided metadata (e.g. galaxy.json).
    for unnamed_output_dict in job_context.tool_provided_metadata.get_unnamed_outputs():
        assert "destination" in unnamed_output_dict
        assert "elements" in unnamed_output_dict
        destination = unnamed_output_dict["destination"]
        elements = unnamed_output_dict["elements"]

        assert "type" in destination
        destination_type = destination["type"]
        assert destination_type in ["library_folder", "hdca", "hdas"]

        # three destination types we need to handle here - "library_folder" (place discovered files in a library folder),
        # "hdca" (place discovered files in a history dataset collection), and "hdas" (place discovered files in a history
        # as stand-alone datasets).
        if destination_type == "library_folder":
            # populate a library folder (needs to be already have been created)
            library_folder = job_context.get_library_folder(destination)
            persist_elements_to_folder(job_context, elements, library_folder)
        elif destination_type == "hdca":
            # create or populate a dataset collection in the history
            assert "collection_type" in unnamed_output_dict
            object_id = destination.get("object_id")
            if object_id:
                hdca = job_context.get_hdca(object_id)
            else:
                name = unnamed_output_dict.get("name", "unnamed collection")
                collection_type = unnamed_output_dict["collection_type"]
                collection_type_description = (
                    COLLECTION_TYPE_DESCRIPTION_FACTORY.for_collection_type(
                        collection_type
                    )
                )
                structure = UninitializedTree(collection_type_description)
                hdca = job_context.create_hdca(name, structure)
            error_message = unnamed_output_dict.get("error_message")
            if error_message:
                hdca.collection.handle_population_failed(error_message)
            else:
                persist_elements_to_hdca(
                    job_context, elements, hdca, collector=DEFAULT_DATASET_COLLECTOR
                )
        elif destination_type == "hdas":
            persist_hdas(
                elements, job_context, final_job_state=job_context.final_job_state
            )

    for name, has_collection in output_collections.items():
        output_collection_def = job_context.output_collection_def(name)
        if not output_collection_def:
            continue

        if not output_collection_def.dynamic_structure:
            continue

        # Could be HDCA for normal jobs or a DC for mapping
        # jobs.
        if hasattr(has_collection, "collection"):
            collection = has_collection.collection
        else:
            collection = has_collection

        # We are adding dynamic collections, which may be precreated, but their actually state is still new!
        collection.populated_state = collection.populated_states.NEW

        try:
            collection_builder = builder.BoundCollectionBuilder(collection)
            dataset_collectors = [
                dataset_collector(description)
                for description in output_collection_def.dataset_collector_descriptions
            ]
            output_name = output_collection_def.name
            filenames = job_context.find_files(
                output_name, collection, dataset_collectors
            )
            job_context.populate_collection_elements(
                collection,
                collection_builder,
                filenames,
                name=output_collection_def.name,
                metadata_source_name=output_collection_def.metadata_source,
                final_job_state=job_context.final_job_state,
            )
            collection_builder.populate()
        except Exception:
            log.exception("Problem gathering output collection.")
            collection.handle_population_failed(
                "Problem building datasets for collection."
            )

        job_context.add_dataset_collection(has_collection)


# Traceback (sqlalchemy.exc.IntegrityError)
Nov 01 14:58:58 sn04.bi.uni-freiburg.de python[1827028]: galaxy.jobs DEBUG 2020-11-01 14:58:58,991 (12059080) Working directory for job is: /data/dnb03/galaxy_db/job_working_directory/012/059/12059080
Nov 01 14:58:58 sn04.bi.uni-freiburg.de python[1847375]: galaxy.jobs DEBUG 2020-11-01 14:58:58,998 (12059086) Working directory for job is: /data/dnb03/galaxy_db/job_working_directory/012/059/12059086
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1847375]: galaxy.objectstore DEBUG 2020-11-01 14:58:59,011 Using preferred backend 'files10' for creation of Dataset 27240484
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: galaxy.job_execution.output_collect ERROR 2020-11-01 14:58:59,036 Problem gathering output collection.
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: Traceback (most recent call last):
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/base.py", line 1284, in _execute_context
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: cursor, statement, parameters, context
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/default.py", line 590, in do_execute
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: cursor.execute(statement, parameters)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: psycopg2.errors.NotNullViolation: null value in column "dataset_collection_id" violates not-null constraint
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: DETAIL:  Failing row contains (13330967, null, 30637828, null, null, 0, ERR4597396__single).
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: The above exception was the direct cause of the following exception:
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: Traceback (most recent call last):
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/server/lib/galaxy/job_execution/output_collect.py", line 156, in collect_dynamic_outputs
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: final_job_state=job_context.final_job_state,
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/server/lib/galaxy/model/store/discover.py", line 286, in populate_collection_elements
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: self.flush()
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/server/lib/galaxy/job_execution/output_collect.py", line 214, in flush
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: self.sa_session.flush()
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/scoping.py", line 163, in do
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: return getattr(self.registry(), name)(*args, **kwargs)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/session.py", line 2523, in flush
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: self._flush(objects)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/session.py", line 2664, in _flush
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: transaction.rollback(_capture_exception=True)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/util/langhelpers.py", line 69, in __exit__
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: exc_value, with_traceback=exc_tb,
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/util/compat.py", line 178, in raise_
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: raise exception
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/session.py", line 2624, in _flush
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: flush_context.execute()
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/unitofwork.py", line 422, in execute
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: rec.execute(self)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/unitofwork.py", line 589, in execute
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: uow,
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/persistence.py", line 236, in save_obj
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: update,
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/orm/persistence.py", line 995, in _emit_update_statements
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: statement, multiparams
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/base.py", line 1020, in execute
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: return meth(self, multiparams, params)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/sql/elements.py", line 298, in _execute_on_connection
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: return connection._execute_clauseelement(self, multiparams, params)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_clauseelement
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: distilled_params,
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/base.py", line 1324, in _execute_context
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: e, statement, parameters, cursor, context
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/base.py", line 1518, in _handle_dbapi_exception
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: sqlalchemy_exception, with_traceback=exc_info[2], from_=e
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/util/compat.py", line 178, in raise_
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: raise exception
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/base.py", line 1284, in _execute_context
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: cursor, statement, parameters, context
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/sqlalchemy/engine/default.py", line 590, in do_execute
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: cursor.execute(statement, parameters)
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: sqlalchemy.exc.IntegrityError: (psycopg2.errors.NotNullViolation) null value in column "dataset_collection_id" violates not-null constraint
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: DETAIL:  Failing row contains (13330967, null, 30637828, null, null, 0, ERR4597396__single).
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: [SQL: UPDATE dataset_collection_element SET dataset_collection_id=%(dataset_collection_id)s WHERE dataset_collection_element.id = %(dataset_collection_element_id)s]
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: [parameters: {'dataset_collection_id': None, 'dataset_collection_element_id': 13330967}]
Nov 01 14:58:59 sn04.bi.uni-freiburg.de python[1819184]: (Background on this error at: http://sqlalche.me/e/gkpj)

# Fixed code
def collect_dynamic_outputs(
    job_context,
    output_collections,
):
    # unmapped outputs do not correspond to explicit outputs of the tool, they were inferred entirely
    # from the tool provided metadata (e.g. galaxy.json).
    for unnamed_output_dict in job_context.tool_provided_metadata.get_unnamed_outputs():
        assert "destination" in unnamed_output_dict
        assert "elements" in unnamed_output_dict
        destination = unnamed_output_dict["destination"]
        elements = unnamed_output_dict["elements"]

        assert "type" in destination
        destination_type = destination["type"]
        assert destination_type in ["library_folder", "hdca", "hdas"]

        # three destination types we need to handle here - "library_folder" (place discovered files in a library folder),
        # "hdca" (place discovered files in a history dataset collection), and "hdas" (place discovered files in a history
        # as stand-alone datasets).
        if destination_type == "library_folder":
            # populate a library folder (needs to be already have been created)
            library_folder = job_context.get_library_folder(destination)
            persist_elements_to_folder(job_context, elements, library_folder)
        elif destination_type == "hdca":
            # create or populate a dataset collection in the history
            assert "collection_type" in unnamed_output_dict
            object_id = destination.get("object_id")
            if object_id:
                hdca = job_context.get_hdca(object_id)
            else:
                name = unnamed_output_dict.get("name", "unnamed collection")
                collection_type = unnamed_output_dict["collection_type"]
                collection_type_description = (
                    COLLECTION_TYPE_DESCRIPTION_FACTORY.for_collection_type(
                        collection_type
                    )
                )
                structure = UninitializedTree(collection_type_description)
                hdca = job_context.create_hdca(name, structure)
            error_message = unnamed_output_dict.get("error_message")
            if error_message:
                hdca.collection.handle_population_failed(error_message)
            else:
                persist_elements_to_hdca(
                    job_context, elements, hdca, collector=DEFAULT_DATASET_COLLECTOR
                )
        elif destination_type == "hdas":
            persist_hdas(
                elements, job_context, final_job_state=job_context.final_job_state
            )

    for name, has_collection in output_collections.items():
        output_collection_def = job_context.output_collection_def(name)
        if not output_collection_def:
            continue

        if not output_collection_def.dynamic_structure:
            continue

        # Could be HDCA for normal jobs or a DC for mapping
        # jobs.
        if hasattr(has_collection, "collection"):
            collection = has_collection.collection
        else:
            collection = has_collection

        # We are adding dynamic collections, which may be precreated, but their actually state is still new!
        collection.populated_state = collection.populated_states.NEW

        try:
            collection_builder = builder.BoundCollectionBuilder(collection)
            dataset_collectors = [
                dataset_collector(description)
                for description in output_collection_def.dataset_collector_descriptions
            ]
            output_name = output_collection_def.name
            filenames = job_context.find_files(
                output_name, collection, dataset_collectors
            )
            job_context.populate_collection_elements(
                collection,
                collection_builder,
                filenames,
                name=output_collection_def.name,
                metadata_source_name=output_collection_def.metadata_source,
                final_job_state=job_context.final_job_state,
            )
        except Exception:
            log.exception("Problem gathering output collection.")
            collection.handle_population_failed(
                "Problem building datasets for collection."
            )

        job_context.add_dataset_collection(has_collection)