Skip to content

Commit

Permalink
Merge pull request #26 from sql-bi/marco-fixTSort
Browse files Browse the repository at this point in the history
Fix stack overflow error
  • Loading branch information
marcosqlbi authored Sep 8, 2022
2 parents 8887a82 + f397f5d commit 9082e18
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 15 deletions.
8 changes: 8 additions & 0 deletions src/Dax.Template/Exceptions/CircularDependencyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Dax.Template.Exceptions
{
public class CircularDependencyException : TemplateException
{
public CircularDependencyException(string? variableName, string? daxExpressionmessage)
: base($"Circulare dependency in variable definition {variableName??"[undefined]"} with DAX expression: {daxExpressionmessage??"[undefined]"}") { }
}
}
47 changes: 33 additions & 14 deletions src/Dax.Template/Extensions/TSort.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Dax.Template.Syntax;
using Dax.Template.Exceptions;

namespace Dax.Template.Extensions
{

public static partial class Extensions
{

public static IEnumerable<(T item, int level)> TSort<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>?> dependencies, bool onlyAddLevel = true) where T : Syntax.IDependencies<Syntax.DaxBase>
{
var sorted = new List<(T item, int level)>();
Expand All @@ -18,25 +20,26 @@ public static partial class Extensions
return sorted;
}

foreach (var item in source.Where(n=>(!onlyAddLevel) || n.AddLevel))
foreach (var item in source.Where(n => (!onlyAddLevel) || n.AddLevel))
{
Visit(item, visited, sorted, dependencies);
}

if (!sorted.Any())
{
return sorted;
}

// Add the dependencies required in each level (usually row variables)
var result = new List<(T item, int level)>();
int min = sorted.Min( element => element.level );
int max = sorted.Max( element => element.level );
for (int currentLoopLevel = min; currentLoopLevel <= max; currentLoopLevel++) {
int min = sorted.Min(element => element.level);
int max = sorted.Max(element => element.level);
for (int currentLoopLevel = min; currentLoopLevel <= max; currentLoopLevel++)
{
result.AddRange(sorted.Where(element => element.level == currentLoopLevel && element.item.AddLevel));
var referencedVariables =
from element in sorted
where element.level == currentLoopLevel && element.item.AddLevel == false
where element.level == currentLoopLevel && element.item.AddLevel == false
select element;
var referencedDependencies =
(from element in sorted
Expand All @@ -50,12 +53,12 @@ select element.item as Syntax.IDependencies<Syntax.DaxBase>
result.InsertRange(
result.IndexOf(pos),
from element in sorted
where element.level < currentLoopLevel
&& element.item.AddLevel == false
&& previousVariables.Any(item => object.ReferenceEquals(item.item,element.item))
&& !result.Any(existingItem => object.ReferenceEquals(existingItem.item,element.item) && existingItem.level == currentLoopLevel)
where element.level < currentLoopLevel
&& element.item.AddLevel == false
&& previousVariables.Any(item => object.ReferenceEquals(item.item, element.item))
&& !result.Any(existingItem => object.ReferenceEquals(existingItem.item, element.item) && existingItem.level == currentLoopLevel)
&& element.item is not Syntax.IGlobalScope
select (element.item, currentLoopLevel )
select (element.item, currentLoopLevel)
);
result.InsertRange(
result.IndexOf(pos),
Expand Down Expand Up @@ -84,6 +87,11 @@ from element in referencedVariables
return result;
}

/// <summary>
/// Maximum number of nested calls in VisitDependencies
/// </summary>
private const int MAX_NESTED_CALLS = 1000;

private static void Visit<T>(T item, HashSet<T> visited, List<(T, int level)> sorted, Func<T, IEnumerable<T>?> dependencies) where T : Syntax.IDependencies<Syntax.DaxBase>
{
if (!visited.Contains(item))
Expand All @@ -105,18 +113,29 @@ private static void Visit<T>(T item, HashSet<T> visited, List<(T, int level)> so
}
}

private static int VisitDependencies<T>(T item, HashSet<T> visited, List<(T, int level)> sorted, Func<T, IEnumerable<T>?> dependencies, int level = 0) where T : Syntax.IDependencies<Syntax.DaxBase>
private static int VisitDependencies<T>(T item, HashSet<T> visited, List<(T, int level)> sorted, Func<T, IEnumerable<T>?> dependencies, int level = 0, int nestedCalls = 0) where T : Syntax.IDependencies<Syntax.DaxBase>
{
var allDependencies = dependencies(item);
// var dependenciesListAddLevel = allDependencies?.Where(d => d.AddLevel == true);

if (nestedCalls > MAX_NESTED_CALLS)
{
string? varName = (item as IDaxName)?.DaxName.ToString();
throw new CircularDependencyException(varName, "{STACK OVERFLOW: check complex dependencies}");
}

if (allDependencies?.Contains(item) == true)
{
throw new CircularDependencyException((item as IDaxName)?.DaxName.ToString(), item.Expression);
}

level += item.AddLevel ? 1 : 0;
int maxLevel = level;
if (allDependencies != null)
{
foreach (var dep in allDependencies)
{
var nestedLevel = VisitDependencies(dep, visited, sorted, dependencies, level);
var nestedLevel = VisitDependencies(dep, visited, sorted, dependencies, level, ++nestedCalls);
if (nestedLevel > maxLevel)
{
maxLevel = nestedLevel;
Expand Down
2 changes: 2 additions & 0 deletions src/Dax.Template/Syntax/DaxElement.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace Dax.Template.Syntax
{
using System.Xml.Linq;

/// <summary>
/// Internal use to create automatic DAX code in templates
/// This could be partial code, it has no name because it is assigned internally
Expand Down
1 change: 1 addition & 0 deletions src/Dax.Template/Syntax/IDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public interface IDependencies<T> where T : DaxBase
public bool IgnoreAutoDependency { get; init; }
public IDependencies<T>[]? Dependencies { get; set; }
public string? Expression { get; set; }

public string GetDebugInfo();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Dax.Template/Syntax/Var.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public abstract class Var : DaxBase, IDependencies<DaxBase>, IDaxName, IDaxComme
public string[]? Comments { get; set; }
public string DaxName { get { return Name; } }

public IDependencies<DaxBase>[]? Dependencies { get; set; }
public IDependencies<DaxBase>[]? Dependencies { get; set; }
public string GetDebugInfo() { return $"VAR {Name}: {Expression}"; }
public override string ToString()
{
Expand Down

0 comments on commit 9082e18

Please sign in to comment.