def _create_new_resolved_attribute( proj_ctx: 'ProjectionContext', attr_ctx_under: 'CdmAttributeContext', target_attr: 'CdmAttribute', override_default_name: Optional[str] = None, added_simple_ref_traits: Optional[List[str]] = None ) -> 'ResolvedAttribute': """ Projections require a new resolved attribute to be created multiple times This function allows us to create new resolved attributes based on a input attribute """ new_res_attr = ResolvedAttribute( proj_ctx._projection_directive._res_opt, target_attr, override_default_name if override_default_name else target_attr.get_name(), attr_ctx_under) if added_simple_ref_traits is not None: for trait in added_simple_ref_traits: if target_attr.applied_traits.item(trait) == None: target_attr.applied_traits.append(trait, True) res_trait_set = target_attr._fetch_resolved_traits( proj_ctx._projection_directive._res_opt) # Create deep a copy of traits to avoid conflicts in case of parameters if res_trait_set is not None: new_res_attr.resolved_traits = res_trait_set.deep_copy() return new_res_attr
def _create_new_resolved_attribute( proj_ctx: 'ProjectionContext', attr_ctx_under: 'CdmAttributeContext', target_attr_or_resolved_attr: Union['CdmAttribute', 'ResolvedAttribute'], override_default_name: Optional[str] = None, added_simple_ref_traits: Optional[List[str]] = None ) -> 'ResolvedAttribute': """ Projections require a new resolved attribute to be created multiple times This function allows us to create new resolved attributes based on a input attribute """ from cdm.objectmodel import CdmAttribute target_attr = target_attr_or_resolved_attr.copy() \ if isinstance(target_attr_or_resolved_attr, CdmAttribute) else target_attr_or_resolved_attr.target.copy() new_res_attr = ResolvedAttribute( proj_ctx._projection_directive._res_opt, target_attr, override_default_name if override_default_name else target_attr.get_name(), attr_ctx_under) target_attr.in_document = proj_ctx._projection_directive._owner.in_document if isinstance(target_attr_or_resolved_attr, CdmAttribute): if added_simple_ref_traits is not None: for trait in added_simple_ref_traits: if target_attr.applied_traits.item(trait) == None: target_attr.applied_traits.append(trait, True) res_trait_set = target_attr._fetch_resolved_traits( proj_ctx._projection_directive._res_opt) # Create deep a copy of traits to avoid conflicts in case of parameters if res_trait_set is not None: new_res_attr.resolved_traits = res_trait_set.deep_copy() else: new_res_attr.resolved_traits = target_attr_or_resolved_attr.resolved_traits.deep_copy( ) if added_simple_ref_traits is not None: for trait in added_simple_ref_traits: tr = CdmTraitReference(target_attr.ctx, trait, True, False) new_res_attr.resolved_traits = new_res_attr.resolved_traits.merge_set( tr._fetch_resolved_traits()) return new_res_attr
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 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 acp_ent_att_ent = AttributeContextParameters( under=pick_under, type=CdmAttributeContextType.ENTITY, 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
def _append_projection_attribute_state(self, proj_ctx: 'ProjectionContext', proj_output_set: 'ProjectionAttributeStateSet', attr_ctx: 'CdmAttributeContext') -> 'ProjectionAttributeStateSet': # Create a new attribute context for the operation attr_ctx_op_alter_traits_param = AttributeContextParameters() attr_ctx_op_alter_traits_param._under = attr_ctx attr_ctx_op_alter_traits_param._type = CdmAttributeContextType.OPERATION_ALTER_TRAITS attr_ctx_op_alter_traits_param._name = 'operation/index{}/{}'.format(self._index, self.get_name()) attr_ctx_op_alter_traits = CdmAttributeContext._create_child_under(proj_ctx._projection_directive._res_opt, attr_ctx_op_alter_traits_param) # Get the top-level attribute names of the selected attributes to apply # We use the top-level names because the applyTo list may contain a previous name our current resolved attributes had top_level_selected_attribute_names = ProjectionResolutionCommonUtil._get_top_list( proj_ctx, self.apply_to) if self.apply_to is not None else None # type: Dict[str, str] # Iterate through all the PAS in the PASSet generated from the projection source's resolved attributes for current_PAS in proj_ctx._current_attribute_state_set._states: # Check if the current projection attribute state's resolved attribute is in the list of selected attributes # If this attribute is not in the list, then we are including it in the output without changes if top_level_selected_attribute_names is None or current_PAS._current_resolved_attribute.resolved_name in top_level_selected_attribute_names: # Create a new attribute context for the new artifact attribute we will create attr_ctx_new_attr_param = AttributeContextParameters() attr_ctx_new_attr_param._under = attr_ctx_op_alter_traits attr_ctx_new_attr_param._type = CdmAttributeContextType.ATTRIBUTE_DEFINITION attr_ctx_new_attr_param._name = current_PAS._current_resolved_attribute.resolved_name attr_ctx_new_attr = CdmAttributeContext._create_child_under(proj_ctx._projection_directive._res_opt, attr_ctx_new_attr_param) new_res_attr = None if isinstance(current_PAS._current_resolved_attribute.target, ResolvedAttributeSet): # Attribute group # Create a copy of resolved attribute set res_attr_new_copy = current_PAS._current_resolved_attribute.target.copy() new_res_attr = ResolvedAttribute(proj_ctx._projection_directive._res_opt, res_attr_new_copy, current_PAS._current_resolved_attribute.resolved_name, attr_ctx_new_attr) # the resolved attribute group obtained from previous projection operation may have a different set of traits comparing to the resolved attribute target. # We would want to take the set of traits from the resolved attribute. new_res_attr.resolved_traits = current_PAS._current_resolved_attribute.resolved_traits.deep_copy() elif isinstance(current_PAS._current_resolved_attribute.target, CdmAttribute): # Entity Attribute or Type Attribute new_res_attr = self._create_new_resolved_attribute( proj_ctx, attr_ctx_new_attr, current_PAS._current_resolved_attribute, current_PAS._current_resolved_attribute.resolved_name) else: logger.error(self.ctx, self._TAG, CdmOperationAlterTraits._append_projection_attribute_state.__name__, self.at_corpus_path, CdmLogCode.ERR_PROJ_UNSUPPORTED_SOURCE, str(current_PAS._current_resolved_attribute.object_type), self.get_name()) proj_output_set._add(current_PAS) break new_res_attr.resolved_traits = new_res_attr.resolved_traits.merge_set(self._resolved_new_traits(proj_ctx, current_PAS)) self._remove_traits_in_new_attribute(proj_ctx._projection_directive._res_opt, new_res_attr) # Create a projection attribute state for the new attribute with new applied traits by creating a copy of the current state # Copy() sets the current state as the previous state for the new one new_PAS = current_PAS._copy() # Update the resolved attribute to be the new attribute we created new_PAS._current_resolved_attribute = new_res_attr proj_output_set._add(new_PAS) else: # Pass through proj_output_set._add(current_PAS) return proj_output_set
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_object import CdmObject rasb = ResolvedAttributeSetBuilder() ctx_ent = self.entity under_att = under acp_ent = None if not res_opt._in_circular_reference: arc = self._fetch_att_res_context(res_opt) # 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) res_opt._depth_info.current_depth = rel_info.next_depth res_opt._depth_info.max_depth_exceeded = rel_info.max_depth_exceeded res_opt._depth_info.max_depth = rel_info.max_depth ctx_ent_obj_def = ctx_ent.fetch_object_definition(res_opt) if ctx_ent_obj_def and ctx_ent_obj_def.object_type == CdmObjectType.PROJECTION_DEF: # A Projection # if the max depth is exceeded it should not try to execute the projection if not res_opt._depth_info.max_depth_exceeded: proj_directive = ProjectionDirective( res_opt, self, ctx_ent) proj_def = ctx_ent_obj_def proj_ctx = proj_def._construct_projection_context( proj_directive, under) rasb._resolved_attribute_set = proj_def._extract_resolved_attributes( proj_ctx, under_att) 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) if rel_info.is_by_ref: # make the entity context that a real recursion would have give us if under: under = rasb._resolved_attribute_set.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._resolved_attribute_set.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._resolved_attribute_set.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 = res_opt.copy() res_link._symbol_ref_set = res_opt._symbol_ref_set rasb.merge_attributes( self.entity._fetch_resolved_attributes( res_link, acp_ent)) # need to pass up max_depth_exceeded if it was hit if res_link._depth_info.max_depth_exceeded: res_opt._depth_info = res_link._depth_info._copy() # from the traits of purpose and applied here, see if new attributes get generated rasb._resolved_attribute_set.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._resolved_attribute_set and rasb._resolved_attribute_set._set and rel_info.is_by_ref: for att in rasb._resolved_attribute_set._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._ctx, self._TAG, CdmEntityAttributeDefinition. _construct_resolved_attributes.__name__, self.at_corpus_path, CdmLogCode. WARN_IDENTIFIER_ARGUMENTS_NOT_SUPPORTED) 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] absolute_ent_path = self.ctx.corpus.storage.create_absolute_corpus_path( ent_def.at_corpus_path, ent_def.in_document) ent_references.append(absolute_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'): # make one resolved attribute with a name from this entityAttribute that contains the set # of atts we just put together. ra_sub = ResolvedAttribute(arc.traits_to_apply.res_opt, rasb._resolved_attribute_set, self.name, under_att) if rel_info.is_array: # put a resolved trait on this att group # 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) depth = rasb._resolved_attribute_set._depth_traveled rasb = ResolvedAttributeSetBuilder() rasb._resolved_attribute_set.attribute_context = ra_sub.att_ctx # this got set to null with the new builder rasb.own_one(ra_sub) rasb._resolved_attribute_set._depth_traveled = depth # how ever they got here, mark every attribute from this entity attribute as now being 'owned' by this entityAtt rasb._resolved_attribute_set._set_attribute_ownership(self.name) rasb._resolved_attribute_set._depth_traveled += 1 return rasb