Exemplo n.º 1
0
    def resolve_by_ui(args: dict, info: graphene.ResolveInfo,
                      ui: str) -> ModelDescriptor:
        """Retrieves a `ModelDescriptor` object through its UI.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            ui (str): The UI of the `ModelDescriptor` to retrieve.

        Returns:
             DescriptorModel: The retrieved `ModelDescriptor` object or `None`
                if no match was not found.
        """

        # Retrieve the query on `ModelDescriptor`.
        query = TypeDescriptor.get_query(info=info,
                                         )  # type: sqlalchemy.orm.query.Query

        # Filter to the `ModelDescriptor` record matching `ui`.
        query = query.filter(ModelDescriptor.ui == ui)

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelDescriptor,
        )

        obj = query.first()

        return obj
Exemplo n.º 2
0
    def resolve_by_group_name(args: dict, info: graphene.ResolveInfo,
                              group_name: str) -> List[ModelHealthTopic]:
        """ Retrieves `ModelHealthTopic` record objects through their group
            name.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            group_name (str): The name of the health-topic group to retrieve
                health-topics for.

        Returns:
             List[ModelHealthTopic]: The retrieved `ModelHealthTopic` record
                objects or an empty list if no matches were found.
        """

        # Retrieve the query on `ModelHealthTopic`.
        query = TypeHealthTopic.get_query(
            info=info)  # type: sqlalchemy.orm.query.Query

        # Filter to the `ModelHealthTopic` records matching the given
        # group.
        query = query.join(ModelHealthTopic.health_topic_groups)
        query = query.filter(ModelHealthTopicGroup.name == group_name)

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(info=info,
                                       query=query,
                                       orm_class=ModelHealthTopic)

        objs = query.all()

        return objs
Exemplo n.º 3
0
    def resolve_by_synonym(
        args: Dict,
        info: graphene.ResolveInfo,
        synonym: str,
        limit: Union[int, None] = None,
    ) -> List[ModelDescriptor]:
        """Retrieves a list of `ModelDescriptor` objects with a tree-number
        prefix-matching `tree_number_prefix`.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            synonym (str): The synonym query by which to perform the search.
            limit (int, optional): The number of closest-matching descriptors
                to return. Defaults to `None`.

        Returns:
             list[DescriptorModel]: The list of matched `ModelDescriptor`
                objects or an empty list if no match was found.
        """

        # Retrieve the session out of the context as the `get_query` method
        # automatically selects the model.
        session = info.context.get("session")  # type: sqlalchemy.orm.Session

        # Define a function to calculate the maximum similarity between a
        # descriptor's synonyms and the synonym query.
        func_similarity = sqlalchemy_func.max(
            sqlalchemy_func.similarity(
                ModelDescriptorSynonym.synonym,
                synonym,
            )).label("synonym_similarity")

        # Query out `ModelDescriptor`.
        query = session.query(ModelDescriptor)  # type: sqlalchemy.orm.Query
        query = query.join(ModelDescriptor.synonyms)
        query = query.filter(ModelDescriptorSynonym.synonym.op("%%")(synonym))
        query = query.order_by(func_similarity.desc())
        query = query.group_by(ModelDescriptor.descriptor_id)

        if limit is not None:
            query = query.limit(limit=limit)

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelDescriptor,
        )

        objs = query.all()

        return objs
Exemplo n.º 4
0
    def resolve_by_search_uuid(
        args: dict,
        info: graphene.ResolveInfo,
        auth0_user_id: str,
        search_uuid: str,
    ) -> ModelSearch:
        """ Retrieves a `ModelUser` record object through its Auth0 IDs.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            auth0_user_id (str): The Auth0 ID of the user to which the search
                belongs.
            search_uuid (uuid.UUID): The search UUID for which the
                `ModelSearch` record object will be retrieved.

        Returns:
             ModelSearch: The retrieved `ModelSearch` record object or `None`
                if no matches were found.
        """

        # Cleanup the Auth0 user ID.
        auth0_user_id = clean_auth0_user_id(auth0_user_id=str(auth0_user_id))

        # Check that the requesting user is authorized to retrieve the user with
        # the given Auth0 ID.
        check_auth(info=info, auth0_user_id=auth0_user_id)

        # Retrieve the query on `ModelSearch`.
        query = TypeSearch.get_query(
            info=info,
        )  # type: sqlalchemy.orm.query.Query

        # Filter to the `ModelSearch` record matching the `search_uuid`.
        query = query.filter(ModelSearch.search_uuid == search_uuid)

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelSearch,
        )

        obj = query.one_or_none()

        # Raise an exception if the requested search could not be found.
        if not obj:
            msg = "Search with UUID '{}' could not be found."
            msg_fmt = msg.format(search_uuid)
            raise graphql.GraphQLError(message=msg_fmt)

        return obj
Exemplo n.º 5
0
    def resolve_by_tree_number_prefix(
            args: dict, info: graphene.ResolveInfo,
            tree_number_prefix: str) -> List[ModelDescriptor]:
        """Retrieves a list of `ModelDescriptor` objects with a tree-number
        prefix-matching `tree_number_prefix`.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            tree_number_prefix (str): The tree-number prefix to match against.

        Returns:
             list[DescriptorModel]: The list of matched `ModelDescriptor`
                objects or an empty list if no match was found.
        """

        # Retrieve the session out of the context as the `get_query` method
        # automatically selects the model.
        session = info.context.get("session")  # type: sqlalchemy.orm.Session

        # Query out `ModelDescriptor`.
        query = session.query(ModelDescriptor)  # type: sqlalchemy.orm.Query
        query = query.join(ModelDescriptor.tree_numbers)
        # Filter down to `ModelDescriptor` objects with a tree-number
        # prefix-matching `tree_number_prefix`.
        query = query.filter(
            ModelTreeNumber.tree_number.like(
                "{}%".format(tree_number_prefix)), )

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelDescriptor,
        )

        objs = query.all()

        return objs
Exemplo n.º 6
0
    def resolve_by_id(
        args: dict,
        info: graphene.ResolveInfo,
        citation_ids: List[int],
    ) -> List[ModelCitation]:
        """Retrieves `ModelCitation` record objects through their IDs.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            citation_ids (List[str]): The IDs for which `ModelCitation` record
                objects will be retrieved.

        Returns:
             List[ModelCitation]: The retrieved `ModelCitation` record objects
                or an empty list if no matches were found.
        """

        # Retrieve the query on `ModelCitation`.
        query = TypeCitation.get_query(
            info=info,
        )  # type: sqlalchemy.orm.query.Query

        # Filter to the `ModelCitation` records matching any of the
        # `citation_ids`.
        query = query.filter(ModelCitation.citation_id.in_(citation_ids))

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelCitation,
        )

        objs = query.all()

        return objs
Exemplo n.º 7
0
    def resolve_filter(
        args: dict,
        info: graphene.ResolveInfo,
        study_ids: List[int],
        overall_statuses: Optional[List[EnumOverallStatus]] = None,
        cities: Optional[List[str]] = None,
        states: Optional[List[str]] = None,
        countries: Optional[List[str]] = None,
        facility_canonical_ids: Optional[List[int]] = None,
        current_location_longitude: Optional[float] = None,
        current_location_latitude: Optional[float] = None,
        distance_max_km: Optional[int] = None,
        intervention_types: Optional[List[EnumIntervention]] = None,
        phases: Optional[List[EnumPhase]] = None,
        study_types: Optional[List[EnumStudy]] = None,
        gender: Optional[EnumGender] = None,
        year_beg: Optional[int] = None,
        year_end: Optional[int] = None,
        age_beg: Optional[int] = None,
        age_end: Optional[int] = None,
        order_by: Optional[str] = None,
        order: Optional[TypeEnumOrder] = None,
        offset: Optional[int] = None,
        limit: Optional[int] = None,
    ) -> List[ModelStudy]:

        # Retrieve the session out of the context as the `get_query` method
        # automatically selects the model.
        session = info.context.get("session")  # type: sqlalchemy.orm.Session

        query = session.query(ModelStudy)  # type: sqlalchemy.orm.query.Query

        # Limit query to fields requested in the GraphQL query adding
        # `load_only` and `joinedload` options as required.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelStudy,
        )

        # Apply the different optional filters to the query.
        query = TypeStudies._apply_query_filters(
            query=query,
            study_ids=study_ids,
            overall_statuses=overall_statuses,
            cities=cities,
            states=states,
            countries=countries,
            facility_canonical_ids=facility_canonical_ids,
            current_location_longitude=current_location_longitude,
            current_location_latitude=current_location_latitude,
            distance_max_km=distance_max_km,
            intervention_types=intervention_types,
            phases=phases,
            study_types=study_types,
            gender=gender,
            year_beg=year_beg,
            year_end=year_end,
            age_beg=age_beg,
            age_end=age_end,
        )

        # Apply order (if defined).
        if order_by:
            # Convert the order-by field to snake-case. This allows for fields
            # to be defined in camel-case but won't error-out if the fields are
            # already in snake-case.
            order_by = to_snake_case(order_by)

            if order and order == TypeEnumOrder.DESC.value:
                query = query.order_by(getattr(ModelStudy, order_by).desc())
            else:
                query = query.order_by(getattr(ModelStudy, order_by).asc())

        query = query.group_by(ModelStudy.study_id)

        # Apply offset (if defined).
        if offset:
            query = query.offset(offset=offset)

        # Apply limit (if defined).
        if limit:
            query = query.limit(limit=limit)

        objs = query.all()

        return objs
Exemplo n.º 8
0
    def resolve_search(
        args: dict,
        info: graphene.ResolveInfo,
        mesh_descriptor_ids: List[int],
        gender: Optional[EnumGender] = None,
        year_beg: Optional[int] = None,
        year_end: Optional[int] = None,
        age_beg: Optional[int] = None,
        age_end: Optional[int] = None,
        do_include_children: Optional[bool] = True,
    ) -> List[ModelStudy]:
        """Retrieves a list of `ModelStudy` objects matching several optional
        filters.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            mesh_descriptor_ids (List[int]): A list of MeSH descriptor IDs of
                the descriptors tagged against the study.
            gender (Optional[EnumGender] = None): The patient gender to search
                by.
            year_beg (Optional[int] = None): The minimum year the start date a
                matched `ModelStudy` may have.
            year_end (Optional[int] = None): The maximum year the start date a
                matched `ModelStudy` may have.
            age_beg (Optional[int] = None): The minimum eligibility age date a
                matched `ModelStudy` may have.
            age_end (Optional[int] = None): The maximum eligibility age date a
                matched `ModelStudy` may have.
            do_include_children (Optional[bool] = True): Whether to search for
                and include in the search the children MeSH descriptors of the
                provided descriptors.

        Returns:
             List[StudyModel]: The list of matched `ModelStudy` objects or an
                empty list if no match was found.
        """

        # Retrieve the session out of the context as the `get_query` method
        # automatically selects the model.
        session = info.context.get("session")  # type: sqlalchemy.orm.Session

        # If the search is to account for the provided descriptors and their
        # children then find all the children descriptor IDs. Otherwise only
        # use the provided ones.
        if do_include_children:
            # Retrieve all tree-numbers for the specified MeSH descriptors.
            query_tns = session.query(
                ModelTreeNumber.tree_number,
                ModelDescriptorTreeNumber.descriptor_id,
            )
            query_tns = query_tns.join(ModelTreeNumber.descriptor_tree_numbers)
            query_tns = query_tns.filter(
                ModelDescriptorTreeNumber.descriptor_id.in_(
                    mesh_descriptor_ids, ), )
            # Retrieve the tree-numbers and associate them with each provided
            # descriptor ID in `map_descriptor_tns`.
            map_descriptor_tns = {}
            tree_numbers_all = []
            for tree_number, descriptor_id in query_tns.all():
                map_descriptor_tns.setdefault(
                    descriptor_id,
                    [],
                ).append(tree_number)
                tree_numbers_all.append(tree_number)

            # Deduplicate the tree-numbers.
            tree_numbers_all = list(set(tree_numbers_all))

            # If no tree-numbers have been found return an empty list.
            if not tree_numbers_all:
                return []

            # Query out the IDs of all children descriptors of all provided
            # descriptors based on the retrieved tree-numbers.
            query_descs = session.query(
                ModelDescriptor.descriptor_id,
                ModelTreeNumber.tree_number,
            )
            query_descs = query_descs.join(ModelDescriptor.tree_numbers)
            # Children descriptors are found by retrieving all descriptors
            # with any tree number prefixed by one of the previously found
            # tree-numbers.
            query_descs = query_descs.filter(
                ModelTreeNumber.tree_number.like(
                    sqlalchemy.any_(
                        postgresql.array(
                            tuple([
                                "{}%".format(tn) for tn in tree_numbers_all
                            ])))))

            # Retrieve the children descriptor IDs and associate them with each
            # provided descriptor ID in `map_descriptor_children`.
            map_descriptor_children = {}
            for child_descriptor_id, tree_number in query_descs.all():
                for descriptor_id, tree_numbers in map_descriptor_tns.items():
                    for tree_number_prefix in tree_numbers:
                        if tree_number.startswith(tree_number_prefix):
                            map_descriptor_children.setdefault(
                                descriptor_id,
                                [descriptor_id],
                            ).append(child_descriptor_id)
            # Unique the descriptor IDs under each provided descriptor ID.
            for descriptor_id, tree_numbers in map_descriptor_children.items():
                map_descriptor_children[descriptor_id] = list(
                    set(tree_numbers))
        else:
            map_descriptor_children = {
                descriptor_id: [descriptor_id]
                for descriptor_id in mesh_descriptor_ids
            }

        # If no descriptor IDs have been found return an empty list.
        if not map_descriptor_children:
            return []

        # Create a function to aggregate the MeSH descriptor IDs of queried
        # studies into an array.
        array_descriptors = sqlalchemy_func.array_agg(
            ModelDescriptor.descriptor_id, )

        # Find all clinical-trial studies associated with the MeSH descriptors
        # found prior.
        query = session.query(ModelStudy)

        # Filter studies by associated mesh-descriptors.
        query = query.join(ModelStudy.descriptors)

        # Filter studies the year of their start-date.
        if year_beg:
            query = query.filter(
                ModelStudy.start_date >= datetime.date(year_beg, 1, 1))
        if year_end:
            query = query.filter(
                ModelStudy.start_date <= datetime.date(year_end, 12, 31))

        # Join on the `eligibility` relationship if any of the fiters that
        # require it are defined.
        if gender or age_beg or age_end:
            query = query.join(ModelStudy.eligibility)

        # Apply a gender filter if defined.
        if gender:
            if gender == EnumGender.ALL:
                query = query.filter(
                    ModelEligibility.gender == EnumGender.ALL.value, )
            elif gender in [EnumGender.FEMALE, EnumGender.MALE]:
                _value = EnumGender.get_member(value=str(gender))
                query = query.filter(
                    sqlalchemy.or_(
                        ModelEligibility.gender == EnumGender.ALL.value,
                        ModelEligibility.gender == _value,
                    ))

        # Filter studies by eligibility age.
        if age_beg or age_end:
            query = TypeStudies._apply_age_filter(query=query,
                                                  age_beg=age_beg,
                                                  age_end=age_end)

        # Group by study ID assembling the MeSH descriptor IDs of each study
        # into arrays. Each study will pass the filters if it has at least one
        # of the descriptors under the provided descriptors (which will be
        # multiple if children descriptors are used). This is achieved through
        # the overlap `&&` operator which returns `true` if one array shares at
        # least one element with the other.
        query = query.group_by(ModelStudy.study_id)
        query = query.having(
            sqlalchemy.and_(*[
                array_descriptors.op("&&")(sqlalchemy_func.cast(
                    descriptor_ids,
                    postgresql.ARRAY(postgresql.BIGINT),
                )) for descriptor_ids in map_descriptor_children.values()
            ]))

        # Limit query to fields requested in the GraphQL query.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelStudy,
        )

        objs = query.all()

        return objs
Exemplo n.º 9
0
    def resolve_search(
        args: dict,
        info: graphene.ResolveInfo,
        mesh_descriptor_ids: List[int],
        year_beg: Optional[int] = None,
        year_end: Optional[int] = None,
        do_include_children: Optional[bool] = True,
    ) -> List[ModelCitation]:
        """Retrieves a list of `ModelCitation` objects matching several optional
        filters.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            mesh_descriptor_ids (List[int]): A list of MeSH descriptor IDs of
                the descriptors tagged against the study.
            year_beg (Optional[int]): The minimum year the publication date the
                article of a matched `ModelCitation` may have.
            year_end (Optional[int]): The maximum year the publication date the
                article of a matched `ModelCitation` may have.
            do_include_children (Optional[bool]): Whether to search for and
                include in the search the children MeSH descriptors of the
                provided descriptors.

        Returns:
             List[ModelCitation]: The list of matched `ModelCitation` objects
                or an empty list if no match was found.
        """

        # Retrieve the session out of the context as the `get_query` method
        # automatically selects the model.
        session = info.context.get("session")  # type: sqlalchemy.orm.Session

        # If the search is to account for the provided descriptors and their
        # children then find all the children descriptor IDs. Otherwise only
        # use the provided ones.
        if do_include_children:
            # Retrieve all tree-numbers for the specified MeSH descriptors.
            query_tns = session.query(
                ModelTreeNumber.tree_number,
                ModelDescriptorTreeNumber.descriptor_id,
            )
            query_tns = query_tns.join(ModelTreeNumber.descriptor_tree_numbers)
            query_tns = query_tns.filter(
                ModelDescriptorTreeNumber.descriptor_id.in_(
                    mesh_descriptor_ids,
                ),
            )
            # Retrieve the tree-numbers and associate them with each provided
            # descriptor ID in `map_descriptor_tns`.
            map_descriptor_tns = {}
            tree_numbers_all = []
            for tree_number, descriptor_id in query_tns.all():
                map_descriptor_tns.setdefault(
                    descriptor_id,
                    [],
                ).append(tree_number)
                tree_numbers_all.append(tree_number)

            # Deduplicate the tree-numbers.
            tree_numbers_all = list(set(tree_numbers_all))

            # If no tree-numbers have been found return an empty list.
            if not tree_numbers_all:
                return []

            # Query out the IDs of all children descriptors of all provided
            # descriptors based on the retrieved tree-numbers.
            query_descs = session.query(
                ModelDescriptor.descriptor_id,
                ModelTreeNumber.tree_number,
            )
            query_descs = query_descs.join(ModelDescriptor.tree_numbers)
            # Children descriptors are found by retrieving all descriptors
            # with any tree number prefixed by one of the previously found
            # tree-numbers.
            query_descs = query_descs.filter(
                ModelTreeNumber.tree_number.like(
                    sqlalchemy.any_(postgresql.array(
                        tuple([
                            "{}%".format(tn)
                            for tn in tree_numbers_all
                        ])
                    ))
                )
            )

            # Retrieve the children descriptor IDs and associate them with each
            # provided descriptor ID in `map_descriptor_children`.
            map_descriptor_children = {}
            for child_descriptor_id, tree_number in query_descs.all():
                for descriptor_id, tree_numbers in map_descriptor_tns.items():
                    for tree_number_prefix in tree_numbers:
                        if tree_number.startswith(tree_number_prefix):
                            map_descriptor_children.setdefault(
                                descriptor_id,
                                [descriptor_id],
                            ).append(child_descriptor_id)
            # Unique the descriptor IDs under each provided descriptor ID.
            for descriptor_id, tree_numbers in map_descriptor_children.items():
                map_descriptor_children[descriptor_id] = list(set(tree_numbers))
        else:
            map_descriptor_children = {
                descriptor_id: [descriptor_id]
                for descriptor_id in mesh_descriptor_ids
            }

        # If no descriptor IDs have been found return an empty list.
        if not map_descriptor_children:
            return []

        # Create a function to aggregate the MeSH descriptor IDs of queried
        # citations into an array.
        array_descriptors = sqlalchemy_func.array_agg(
            ModelDescriptor.descriptor_id,
        )

        # Find all PubMed citations associated with the MeSH descriptors found
        # prior.
        query = session.query(ModelCitation)

        # Filter citations by associated mesh-descriptors.
        query = query.join(ModelCitation.descriptors)

        if year_beg or year_end:
            query = query.join(ModelCitation.article)

        # Filter studies the year of their start-date.
        if year_beg:
            query = query.filter(
                ModelArticle.publication_year >= year_beg
            )
        if year_end:
            query = query.filter(
                ModelArticle.publication_year <= year_end
            )

        # Group by citation ID assembling the MeSH descriptor IDs of each study
        # into arrays. Each citation will pass the filters if it has at least
        # one of the descriptors under the provided descriptors (which will be
        # multiple if children descriptors are used). This is achieved through
        # the overlap `&&` operator which returns `true` if one array shares at
        # least one element with the other.
        query = query.group_by(ModelCitation.citation_id)
        query = query.having(
            sqlalchemy.and_(
                *[
                    array_descriptors.op("&&")(
                        sqlalchemy_func.cast(
                            descriptor_ids,
                            postgresql.ARRAY(postgresql.BIGINT),
                        )
                    )
                    for descriptor_ids in map_descriptor_children.values()
                ]
            )
        )

        # Limit query to fields requested in the GraphQL query.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelCitation,
        )

        objs = query.all()

        return objs
Exemplo n.º 10
0
    def resolve_count_citations_by_qualifier(
        args: dict,
        info: graphene.ResolveInfo,
        citation_ids: List[int],
        limit: Optional[int] = None,
    ) -> List[TypeCountCitationsQualifier]:
        """Creates a list of `TypeCountCitationsQualifier` objects with the
        number of citations per qualifier.

        Args:
            args (dict): The resolver arguments.
            info (graphene.ResolveInfo): The resolver info.
            citation_ids (List[int]): A list of citation IDs.
            limit (Optional[int]): The number of results to return. Defaults to
                `None` in which case all results are returned.

        Returns:
             list[TypeCountCitationsQualifier]: The list of
                `TypeCountCitationsQualifier` objects with the results of the
                aggregation.
        """

        # Retrieve the session out of the context as the `get_query` method
        # automatically selects the model.
        session = info.context.get("session")  # type: sqlalchemy.orm.Session

        # Define the `COUNT(citations.citation_id)` function.
        func_count_citations = sqlalchemy_func.count(
            sqlalchemy_func.distinct(
                ModelCitationDescriptorQualifier.citation_id), )

        # Query out the count of citations by country.
        query = session.query(
            ModelQualifier,
            func_count_citations,
        )  # type: sqlalchemy.orm.Query
        query = query.join(
            ModelCitationDescriptorQualifier,
            ModelCitationDescriptorQualifier.qualifier_id ==
            ModelQualifier.qualifier_id,
        )
        query = query.filter(
            ModelCitationDescriptorQualifier.citation_id.in_(citation_ids), )
        # Group by qualifier.
        query = query.group_by(ModelQualifier.qualifier_id, )
        # Order by the number of studies.
        query = query.order_by(func_count_citations.desc())

        # Extract the fields requested in the GraphQL query.
        fields = extract_requested_fields(
            info=info,
            fields=info.field_asts,
            do_convert_to_snake_case=True,
        )["count_citations_by_qualifier"]["qualifier"]

        # Limit query to `ModelQualifier` fields requested in the GraphQL
        # query.
        query = apply_requested_fields(
            info=info,
            query=query,
            orm_class=ModelQualifier,
            fields={"qualifier": fields},
        )

        # Apply limit (if defined).
        if limit:
            query = query.limit(limit=limit)

        results = query.all()

        # Wrap the results of the aggregation in `TypeCountCitationsQualifier`
        # objects.
        objs = [
            TypeCountCitationsQualifier(qualifier=result[0],
                                        count_citations=result[1])
            for result in results
        ]

        return objs