def _construct_resolved_attributes(self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext']) -> 'ResolvedAttributeSetBuilder':
        # find and cache the complete set of attributes
        # attributes definitions originate from and then get modified by subsequent re-defintions from (in this order):
        # the datatype used as an attribute, traits applied to that datatype,
        # the purpose of the attribute, any traits applied to the attribute.
        from cdm.resolvedmodel import AttributeResolutionContext, ResolvedAttribute, ResolvedAttributeSetBuilder

        from .cdm_attribute_resolution_guidance_def import CdmAttributeResolutionGuidanceDefinition

        rasb = ResolvedAttributeSetBuilder()
        rasb.ras.attribute_context = under

        # add this attribute to the set
        # make a new one and apply any traits
        new_att = ResolvedAttribute(res_opt, self, self.name, under)
        rasb.own_one(new_att)
        rts = self._fetch_resolved_traits(res_opt)

        # this context object holds all of the info about what needs to happen to resolve these attributes.
        # make a copy and add defaults if missing
        res_guide_with_default = None
        if self.resolution_guidance is not None:
            res_guide_with_default = self.resolution_guidance.copy(res_opt)
        else:
            res_guide_with_default = CdmAttributeResolutionGuidanceDefinition(self.ctx)

        # rename_format is not currently supported for type attributes
        res_guide_with_default.rename_format = None

        res_guide_with_default._update_attribute_defaults(None)
        arc = AttributeResolutionContext(res_opt, res_guide_with_default, rts)

        # TODO: remove the resolution guidance if projection is being used
        # from the traits of the datatype, purpose and applied here, see if new attributes get generated
        rasb.apply_traits(arc)
        rasb.generate_applier_attributes(arc, False)  # false = don't apply these traits to added things
        # this may have added symbols to the dependencies, so merge them
        res_opt._symbol_ref_set._merge(arc.res_opt._symbol_ref_set)

        if self.projection:
            proj_directive = ProjectionDirective(res_opt, self)
            proj_ctx = self.projection._construct_projection_context(proj_directive, under, rasb.ras)

            ras = self.projection._extract_resolved_attributes(proj_ctx)
            rasb.ras = ras

        return rasb
Example #2
0
    def _construct_resolved_attributes(
        self,
        res_opt: 'ResolveOptions',
        under: Optional['CdmAttributeContext'] = None
    ) -> 'ResolvedAttributeSetBuilder':
        from cdm.resolvedmodel import AttributeResolutionContext, ResolvedAttribute, ResolvedAttributeSetBuilder, ResolvedTrait
        from cdm.utilities import AttributeContextParameters, AttributeResolutionDirectiveSet

        from .cdm_attribute_resolution_guidance_def import CdmAttributeResolutionGuidanceDefinition
        from .cdm_object import CdmObject

        rasb = ResolvedAttributeSetBuilder()
        ctx_ent = self.entity
        under_att = under
        acp_ent = None

        ctx_ent_obj_def = ctx_ent.fetch_object_definition(res_opt)
        if ctx_ent_obj_def.object_type == CdmObjectType.PROJECTION_DEF:
            # A Projection

            proj_directive = ProjectionDirective(res_opt, self, ctx_ent)
            proj_def = ctx_ent_obj_def
            projCtx = proj_def._construct_projection_context(
                proj_directive, under)

            ras = proj_def._extract_resolved_attributes(projCtx)
            rasb.ras = ras
        else:
            # An Entity Reference

            if under_att:
                # make a context for this attribute that holds the attributes that come up from the entity
                acp_ent = AttributeContextParameters(
                    under=under_att,
                    type=CdmAttributeContextType.ENTITY,
                    name=ctx_ent.fetch_object_definition_name(),
                    regarding=ctx_ent,
                    include_traits=True)

            rts_this_att = self._fetch_resolved_traits(res_opt)

            # this context object holds all of the info about what needs to happen to resolve these attributes.
            # make a copy and add defaults if missing
            res_guide_with_default = None
            if self.resolution_guidance is not None:
                res_guide_with_default = self.resolution_guidance.copy(res_opt)
            else:
                res_guide_with_default = CdmAttributeResolutionGuidanceDefinition(
                    self.ctx)

            res_guide_with_default._update_attribute_defaults(self.name)

            arc = AttributeResolutionContext(res_opt, res_guide_with_default,
                                             rts_this_att)

            # complete cheating but is faster.
            # this purpose will remove all of the attributes that get collected here, so dumb and slow to go get them
            rel_info = self._get_relationship_info(arc.res_opt, arc)

            if rel_info.is_by_ref:
                # make the entity context that a real recursion would have give us
                if under:
                    under = rasb.ras.create_attribute_context(res_opt, acp_ent)

                # if selecting from one of many attributes, then make a context for each one
                if under and rel_info.selects_one:
                    # the right way to do this is to get a resolved entity from the embedded entity and then
                    # look through the attribute context hierarchy for non-nested entityReferenceAsAttribute nodes
                    # that seems like a disaster waiting to happen given endless looping, etc.
                    # for now, just insist that only the top level entity attributes declared in the ref entity will work
                    ent_pick_from = self.entity.fetch_object_definition(
                        res_opt)
                    atts_pick = ent_pick_from.attributes
                    if ent_pick_from and atts_pick:
                        for attribute in atts_pick:
                            if attribute.object_type == CdmObjectType.ENTITY_ATTRIBUTE_DEF:
                                # a table within a table. as expected with a selects_one attribute
                                # since this is by ref, we won't get the atts from the table, but we do need the traits that hold the key
                                # these are the same contexts that would get created if we recursed
                                # first this attribute
                                acp_ent_att = AttributeContextParameters(
                                    under=under,
                                    type=CdmAttributeContextType.
                                    ATTRIBUTE_DEFINITION,
                                    name=attribute.
                                    fetch_object_definition_name(),
                                    regarding=attribute,
                                    include_traits=True)
                                pick_under = rasb.ras.create_attribute_context(
                                    res_opt, acp_ent_att)
                                # and the entity under that attribute
                                pick_ent = attribute.entity
                                pick_ent_type = CdmAttributeContextType.PROJECTION if pick_ent.fetch_object_definition(
                                    res_opt
                                ).object_type == CdmObjectType.PROJECTION_DEF else CdmAttributeContextType.ENTITY
                                acp_ent_att_ent = AttributeContextParameters(
                                    under=pick_under,
                                    type=pick_ent_type,
                                    name=pick_ent.fetch_object_definition_name(
                                    ),
                                    regarding=pick_ent,
                                    include_traits=True)
                                rasb.ras.create_attribute_context(
                                    res_opt, acp_ent_att_ent)

                # if we got here because of the max depth, need to impose the directives to make the trait work as expected
                if rel_info.max_depth_exceeded:
                    if not arc.res_opt.directives:
                        arc.res_opt.directives = AttributeResolutionDirectiveSet(
                        )
                    arc.res_opt.directives.add('referenceOnly')
            else:
                res_link = CdmObject._copy_resolve_options(res_opt)
                res_link._symbol_ref_set = res_opt._symbol_ref_set
                res_link._relationship_depth = rel_info.next_depth
                rasb.merge_attributes(
                    self.entity._fetch_resolved_attributes(res_link, acp_ent))

            # from the traits of purpose and applied here, see if new attributes get generated
            rasb.ras.attribute_context = under_att
            rasb.apply_traits(arc)
            rasb.generate_applier_attributes(
                arc, True)  # True = apply the prepared traits to new atts
            # this may have added symbols to the dependencies, so merge them
            res_opt._symbol_ref_set._merge(arc.res_opt._symbol_ref_set)

            # use the traits for linked entity identifiers to record the actual foreign key links
            if rasb.ras and rasb.ras._set and rel_info.is_by_ref:
                for att in rasb.ras._set:
                    reqd_trait = att.resolved_traits.find(
                        res_opt, 'is.linkedEntity.identifier')
                    if not reqd_trait:
                        continue

                    if not reqd_trait.parameter_values:
                        logger.warning(
                            self._TAG, self.ctx,
                            'is.linkedEntity.identifier does not support arguments'
                        )
                        continue

                    ent_references = []
                    att_references = []

                    def add_entity_reference(entity_ref: 'CdmEntityReference',
                                             namespace: str):
                        ent_def = entity_ref.fetch_object_definition(res_opt)
                        required_trait = entity_ref._fetch_resolved_traits(
                            res_opt).find(res_opt, 'is.identifiedBy')
                        if required_trait and ent_def:
                            att_ref = required_trait.parameter_values.fetch_parameter_value(
                                'attribute').value
                            att_name = att_ref.named_reference.split('/')[-1]
                            # path should be absolute and without a namespace
                            relative_ent_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                                ent_def.at_corpus_path, ent_def.in_document)
                            if relative_ent_path.startswith(namespace + ':'):
                                relative_ent_path = relative_ent_path[
                                    len(namespace) + 1:]
                            ent_references.append(relative_ent_path)
                            att_references.append(att_name)

                    if rel_info.selects_one:
                        ent_pick_from = self.entity.fetch_object_definition(
                            res_opt)
                        atts_pick = ent_pick_from.attributes if ent_pick_from else None
                        if atts_pick:
                            for attribute in atts_pick:
                                if attribute.object_type == CdmObjectType.ENTITY_ATTRIBUTE_DEF:
                                    add_entity_reference(
                                        attribute.entity,
                                        self.in_document.namespace)
                    else:
                        add_entity_reference(self.entity,
                                             self.in_document.namespace)

                    c_ent = self.ctx.corpus.make_object(
                        CdmObjectType.CONSTANT_ENTITY_DEF)
                    c_ent.entity_shape = self.ctx.corpus.make_ref(
                        CdmObjectType.ENTITY_REF, 'entityGroupSet', True)
                    c_ent.constant_values = [[
                        entity_ref, att_references[idx]
                    ] for idx, entity_ref in enumerate(ent_references)]
                    param = self.ctx.corpus.make_ref(CdmObjectType.ENTITY_REF,
                                                     c_ent, False)
                    reqd_trait.parameter_values.update_parameter_value(
                        res_opt, 'entityReferences', param)

            # a 'structured' directive wants to keep all entity attributes together in a group
            if arc and arc.res_opt.directives and arc.res_opt.directives.has(
                    'structured'):
                ra_sub = ResolvedAttribute(rts_this_att.res_opt, rasb.ras,
                                           self.name,
                                           rasb.ras.attribute_context)
                if rel_info.is_array:
                    # put a resolved trait on this att group, yuck,
                    #  hope I never need to do this again and then need to make a function for this
                    tr = self.ctx.corpus.make_object(CdmObjectType.TRAIT_REF,
                                                     'is.linkedEntity.array',
                                                     True)
                    t = tr.fetch_object_definition(res_opt)
                    rt = ResolvedTrait(t, None, [], [])
                    ra_sub.resolved_traits = ra_sub.resolved_traits.merge(
                        rt, True)
                rasb = ResolvedAttributeSetBuilder()
                rasb.own_one(ra_sub)

        return rasb