diff --git a/src/sempy_labs/_model_bpa.py b/src/sempy_labs/_model_bpa.py index ac94f719..777d7e55 100644 --- a/src/sempy_labs/_model_bpa.py +++ b/src/sempy_labs/_model_bpa.py @@ -274,12 +274,17 @@ def translate_using_spark(rule_file): tom.all_columns(), lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name), ), + "Calculated Column": ( + tom.all_calculated_columns(), + lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name), + ), "Measure": (tom.all_measures(), lambda obj: obj.Name), "Hierarchy": ( tom.all_hierarchies(), lambda obj: format_dax_object_name(obj.Parent.Name, obj.Name), ), "Table": (tom.model.Tables, lambda obj: obj.Name), + "Calculated Table": (tom.all_calculated_tables(), lambda obj: obj.Name), "Role": (tom.model.Roles, lambda obj: obj.Name), "Model": (tom.model, lambda obj: obj.Model.Name), "Calculation Item": ( @@ -322,6 +327,10 @@ def translate_using_spark(rule_file): x = [nm(obj) for obj in tom.all_hierarchies() if expr(obj, tom)] elif scope == "Table": x = [nm(obj) for obj in tom.model.Tables if expr(obj, tom)] + elif scope == "Calculated Table": + x = [ + nm(obj) for obj in tom.all_calculated_tables() if expr(obj, tom) + ] elif scope == "Relationship": x = [nm(obj) for obj in tom.model.Relationships if expr(obj, tom)] elif scope == "Role": @@ -332,6 +341,12 @@ def translate_using_spark(rule_file): x = [ nm(obj) for obj in tom.all_calculation_items() if expr(obj, tom) ] + elif scope == "Calculated Column": + x = [ + nm(obj) + for obj in tom.all_calculated_columns() + if expr(obj, tom) + ] if len(x) > 0: new_data = { diff --git a/src/sempy_labs/_model_bpa_rules.py b/src/sempy_labs/_model_bpa_rules.py index f5295fdb..f524263b 100644 --- a/src/sempy_labs/_model_bpa_rules.py +++ b/src/sempy_labs/_model_bpa_rules.py @@ -565,7 +565,12 @@ def model_bpa_rules( ), ( "DAX Expressions", - "Measure", + [ + "Measure", + "Calculated Table", + "Calculated Column", + "Calculation Item", + ], "Error", "Column references should be fully qualified", lambda obj, tom: any( @@ -576,7 +581,12 @@ def model_bpa_rules( ), ( "DAX Expressions", - "Measure", + [ + "Measure", + "Calculated Table", + "Calculated Column", + "Calculation Item", + ], "Error", "Measure references should be unqualified", lambda obj, tom: any( diff --git a/src/sempy_labs/tom/_model.py b/src/sempy_labs/tom/_model.py index 2479e644..a2bf6287 100644 --- a/src/sempy_labs/tom/_model.py +++ b/src/sempy_labs/tom/_model.py @@ -3275,17 +3275,28 @@ def depends_on(self, object, dependencies: pd.DataFrame): """ import Microsoft.AnalysisServices.Tabular as TOM - objType = object.ObjectType - objName = object.Name - objParentName = object.Parent.Name + obj_type = object.ObjectType + obj_name = object.Name - if objType == TOM.ObjectType.Table: - objParentName = objName + if object.ObjectType == TOM.ObjectType.CalculationItem: + obj_parent_name = object.Parent.Table.Name + else: + obj_parent_name = object.Parent.Name + + if obj_type == TOM.ObjectType.Table: + obj_parent_name = obj_name + object_types = ["Table", "Calc Table"] + elif obj_type == TOM.ObjectType.Column: + object_types = ["Column", "Calc Column"] + elif obj_type == TOM.ObjectType.CalculationItem: + object_types = ["Calculation Item"] + else: + object_types = [str(obj_type)] fil = dependencies[ - (dependencies["Object Type"] == str(objType)) - & (dependencies["Table Name"] == objParentName) - & (dependencies["Object Name"] == objName) + (dependencies["Object Type"].isin(object_types)) + & (dependencies["Table Name"] == obj_parent_name) + & (dependencies["Object Name"] == obj_name) ] meas = ( fil[fil["Referenced Object Type"] == "Measure"]["Referenced Object"] @@ -3365,6 +3376,41 @@ def referenced_by(self, object, dependencies: pd.DataFrame): if t.Name in tbls: yield t + def _get_expression(self, object): + """ + Helper function to get the expression for any given TOM object. + """ + + import Microsoft.AnalysisServices.Tabular as TOM + + valid_objects = [ + TOM.ObjectType.Measure, + TOM.ObjectType.Table, + TOM.ObjectType.Column, + TOM.ObjectType.CalculationItem, + ] + + if object.ObjectType not in valid_objects: + raise ValueError( + f"{icons.red_dot} The 'object' parameter must be one of these types: {valid_objects}." + ) + + if object.ObjectType == TOM.ObjectType.Measure: + expr = object.Expression + elif object.ObjectType == TOM.ObjectType.Table: + part = next(p for p in object.Partitions) + if part.SourceType == TOM.PartitionSourceType.Calculated: + expr = part.Source.Expression + elif object.ObjectType == TOM.ObjectType.Column: + if object.Type == TOM.ColumnType.Calculated: + expr = object.Expression + elif object.ObjectType == TOM.ObjectType.CalculationItem: + expr = object.Expression + else: + return + + return expr + def fully_qualified_measures( self, object: "TOM.Measure", dependencies: pd.DataFrame ): @@ -3389,15 +3435,16 @@ def fully_qualified_measures( dependencies["Object Name"] == dependencies["Parent Node"] ] + expr = self._get_expression(object=object) + for obj in self.depends_on(object=object, dependencies=dependencies): if obj.ObjectType == TOM.ObjectType.Measure: - if (f"{obj.Parent.Name}[{obj.Name}]" in object.Expression) or ( - format_dax_object_name(obj.Parent.Name, obj.Name) - in object.Expression + if (f"{obj.Parent.Name}[{obj.Name}]" in expr) or ( + format_dax_object_name(obj.Parent.Name, obj.Name) in expr ): yield obj - def unqualified_columns(self, object: "TOM.Column", dependencies: pd.DataFrame): + def unqualified_columns(self, object, dependencies: pd.DataFrame): """ Obtains all unqualified column references for a given object. @@ -3419,6 +3466,8 @@ def unqualified_columns(self, object: "TOM.Column", dependencies: pd.DataFrame): dependencies["Object Name"] == dependencies["Parent Node"] ] + expr = self._get_expression(object=object) + def create_pattern(tableList, b): patterns = [ r"(?