parse m expressions for dependencies

This commit is contained in:
Christian Wade 2019-12-30 23:21:07 -08:00
parent 353bab30c5
commit f884719731
3 changed files with 186 additions and 36 deletions

View File

@ -576,7 +576,8 @@ namespace BismNormalizer.TabularCompare
string dataDir = amoServer.ServerProperties["DataDir"].Value;
if (dataDir.EndsWith("\\")) dataDir = dataDir.Substring(0, dataDir.Length - 1);
string commandStatement = String.Format("SystemGetSubdirs '{0}'", dataDir);
XmlNodeList rows = Core.Comparison.ExecuteXmlaCommand(amoServer, "", commandStatement);
bool foundFault = false;
XmlNodeList rows = Core.Comparison.ExecuteXmlaCommand(amoServer, "", commandStatement, ref foundFault);
string dbDir = "";
foreach (XmlNode row in rows)

View File

@ -522,7 +522,8 @@ namespace BismNormalizer.TabularCompare.Core
public static int FindRowCount(Microsoft.AnalysisServices.Core.Server server, string tableName, string databaseName)
{
string dax = String.Format("EVALUATE ROW( \"RowCount\", COUNTROWS('{0}'))", tableName);
XmlNodeList rows = ExecuteXmlaCommand(server, databaseName, dax);
bool foundFault = false;
XmlNodeList rows = ExecuteXmlaCommand(server, databaseName, dax, ref foundFault);
foreach (XmlNode row in rows)
{
@ -552,7 +553,7 @@ namespace BismNormalizer.TabularCompare.Core
/// <param name="server"></param>
/// <param name="commandStatement"></param>
/// <returns>XmlNodeList containing results of the command execution.</returns>
public static XmlNodeList ExecuteXmlaCommand(Microsoft.AnalysisServices.Core.Server server, string catalog, string commandStatement)
public static XmlNodeList ExecuteXmlaCommand(Microsoft.AnalysisServices.Core.Server server, string catalog, string commandStatement, ref bool foundFault)
{
XmlWriter xmlWriter = server.StartXmlaRequest(XmlaRequestType.Undefined);
WriteSoapEnvelopeWithCommandStatement(xmlWriter, server.SessionID, catalog, commandStatement);
@ -567,6 +568,12 @@ namespace BismNormalizer.TabularCompare.Core
nsmgr.AddNamespace("myns1", "urn:schemas-microsoft-com:xml-analysis");
nsmgr.AddNamespace("myns2", "urn:schemas-microsoft-com:xml-analysis:rowset");
XmlNodeList rows = documentResponse.SelectNodes("//myns1:ExecuteResponse/myns1:return/myns2:root/myns2:row", nsmgr);
if (rows.Count == 0 && documentResponse.GetElementsByTagName("faultcode").Count > 0)
{
foundFault = true;
}
return rows;
}

View File

@ -7,6 +7,8 @@ using Tom = Microsoft.AnalysisServices.Tabular;
using Amo = Microsoft.AnalysisServices;
using Newtonsoft.Json.Linq;
using BismNormalizer.TabularCompare.Core;
using System.Web.UI.WebControls.WebParts;
using System.Text.RegularExpressions;
namespace BismNormalizer.TabularCompare.TabularMetadata
{
@ -72,7 +74,6 @@ namespace BismNormalizer.TabularCompare.TabularMetadata
//Don't need try to load from project here as will already be done before instantiated Comparison
throw new Amo.ConnectionException($"Could not connect to database {_connectionInfo.DatabaseName}");
}
InitializeCalcDependencies();
}
//Shell model
@ -148,9 +149,18 @@ namespace BismNormalizer.TabularCompare.TabularMetadata
{
_cultures.Add(new Culture(this, culture));
}
if (_connectionInfo.UseBimFile)
{
InitializeCalcDependenciesFromM();
}
else
{
InitializeCalcDependenciesFromServer();
}
}
private void InitializeCalcDependencies()
private void InitializeCalcDependenciesFromServer()
{
_calcDependencies.Clear();
string command =
@ -159,8 +169,15 @@ namespace BismNormalizer.TabularCompare.TabularMetadata
"NOT (OBJECT_TYPE = REFERENCED_OBJECT_TYPE AND " +
" [TABLE] = REFERENCED_TABLE AND" +
" OBJECT = REFERENCED_OBJECT);"; //Ignore recursive M expression dependencies
XmlNodeList rows = Core.Comparison.ExecuteXmlaCommand(_server, _connectionInfo.DatabaseName, command);
bool foundFault = false;
XmlNodeList rows = Core.Comparison.ExecuteXmlaCommand(_server, _connectionInfo.DatabaseName, command, ref foundFault);
if (foundFault)
{
InitializeCalcDependenciesFromM();
}
else
{
foreach (XmlNode row in rows)
{
string objectType = "";
@ -197,6 +214,131 @@ namespace BismNormalizer.TabularCompare.TabularMetadata
);
}
}
}
private struct MObject
{
public string ObjectType;
public string TableName;
public string ObjectName;
public string Expression;
public MObject(string objectType, string tableName, string objectName, string expression)
{
ObjectType = objectType;
TableName = tableName;
ObjectName = objectName;
Expression = expression;
}
}
private void InitializeCalcDependenciesFromM()
{
//TODO: Doesn't deal with structured data sources (DSRs) yet
_calcDependencies.Clear();
List<MObject> mObjects = new List<MObject>();
//Add table partitions to mObjects collection
foreach (Table table in _tables)
{
foreach (Partition partition in table.TomTable.Partitions)
{
if (partition.SourceType == PartitionSourceType.M)
{
mObjects.Add(
new MObject(
objectType: "PARTITION",
tableName: table.Name,
objectName: partition.Name,
expression: ((MPartitionSource)partition.Source).Expression
)
);
}
}
}
//Add other M expressions to mObjects collection
foreach (Expression expression in _expressions)
{
mObjects.Add(
new MObject(
objectType: "M_EXPRESSION",
tableName: "",
objectName: expression.Name,
expression: expression.TomExpression.Expression
)
);
}
char[] delimiterChars = { ' ', ',', ':', '\t', '\n', '[', ']', '(', ')', '{', '}' };
List<string> keywords = new List<string>() { "let", "in" }; //TODO: need list of all M keywords
foreach (MObject mObject in mObjects)
{
string regex = "(#\"(.*?)\")";
//Expression with double quote references removed
string expressionRegex = Regex.Replace(mObject.Expression, regex, "");
string[] words = expressionRegex.Split(delimiterChars);
foreach (MObject referencedMObject in mObjects)
{
bool foundDependency = false;
if (!( //Ignore circular dependencies
mObject.ObjectName == referencedMObject.ObjectName &&
mObject.ObjectType == referencedMObject.ObjectType &&
mObject.TableName == referencedMObject.TableName
))
{
if ( //if M_EXPRESSION name contains spaces or is a keyword, only need to check for occurrence like #"My Query" or #"let"
(referencedMObject.ObjectType == "M_EXPRESSION" && (referencedMObject.ObjectName.Contains(" ") || keywords.Contains(referencedMObject.ObjectName))) &&
(mObject.Expression.Contains("\"" + referencedMObject.ObjectName + "\""))
)
{
foundDependency = true;
}
else if ( //if table name contains spaces or is a keyword, only need to check for occurrence like #"My Query" or #"let"
(referencedMObject.ObjectType == "PARTITION" && (referencedMObject.TableName.Contains(" ") || keywords.Contains(referencedMObject.TableName))) &&
(mObject.Expression.Contains("\"" + referencedMObject.TableName + "\""))
)
{
foundDependency = true;
}
else
{
foreach (string word in words)
{
if (
(referencedMObject.ObjectType == "M_EXPRESSION" && word == referencedMObject.ObjectName && !keywords.Contains(referencedMObject.ObjectName)) ||
(referencedMObject.ObjectType == "PARTITION" && word == referencedMObject.TableName && !keywords.Contains(referencedMObject.TableName))
)
{
foundDependency = true;
}
}
}
if (foundDependency)
{
_calcDependencies.Add(
new CalcDependency(
this,
objectType: mObject.ObjectType,
tableName: mObject.TableName,
objectName: mObject.ObjectName,
expression: mObject.Expression,
referencedObjectType: referencedMObject.ObjectType,
referencedTableName: referencedMObject.TableName,
referencedObjectName: referencedMObject.ObjectName,
referencedExpression: referencedMObject.Expression
)
);
}
}
}
}
}
/// <summary>
/// Disconnect from the SSAS server.