-
Notifications
You must be signed in to change notification settings - Fork 346
Moving dependency scan to CORE #2827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
06697b5
Draft
anderson-joyle 5e4eae4
Suppressing comment format.
anderson-joyle 8332602
Fix.
anderson-joyle ee6a3f1
Fixing record visitor.
anderson-joyle 6f1a3a6
PR feedback.
anderson-joyle 4a6b07c
PR feedback.
anderson-joyle a82fd03
Fix.
anderson-joyle f99ac94
PR feedback.
anderson-joyle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
296 changes: 296 additions & 0 deletions
296
src/libraries/Microsoft.PowerFx.Core/IR/Visitors/DependencyVisitor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using Microsoft.PowerFx.Core.IR.Nodes; | ||
using Microsoft.PowerFx.Core.IR.Symbols; | ||
using Microsoft.PowerFx.Core.Types; | ||
using Microsoft.PowerFx.Core.Utils; | ||
using Microsoft.PowerFx.Types; | ||
using static Microsoft.PowerFx.Syntax.PrettyPrintVisitor; | ||
|
||
namespace Microsoft.PowerFx.Core.IR | ||
{ | ||
// IR has already: | ||
// - resolved everything to logical names. | ||
// - resolved implicit ThisRecord | ||
internal sealed class DependencyVisitor : IRNodeVisitor<DependencyVisitor.RetVal, DependencyVisitor.DependencyContext> | ||
{ | ||
// Track reults. | ||
public DependencyInfo Info { get; private set; } = new DependencyInfo(); | ||
|
||
public DependencyVisitor() | ||
{ | ||
} | ||
|
||
public override RetVal Visit(TextLiteralNode node, DependencyContext context) | ||
{ | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(NumberLiteralNode node, DependencyContext context) | ||
{ | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(BooleanLiteralNode node, DependencyContext context) | ||
{ | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(DecimalLiteralNode node, DependencyContext context) | ||
{ | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(ColorLiteralNode node, DependencyContext context) | ||
{ | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(RecordNode node, DependencyContext context) | ||
{ | ||
// Visit all field values in case there are CallNodes. Field keys should be handled by the function caller. | ||
foreach (var kv in node.Fields) | ||
{ | ||
kv.Value.Accept(this, context); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public override RetVal Visit(ErrorNode node, DependencyContext context) | ||
{ | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(LazyEvalNode node, DependencyContext context) | ||
{ | ||
return node.Child.Accept(this, context); | ||
} | ||
|
||
private readonly Dictionary<int, FormulaType> _scopeTypes = new Dictionary<int, FormulaType>(); | ||
|
||
public override RetVal Visit(CallNode node, DependencyContext context) | ||
{ | ||
if (node.Scope != null) | ||
{ | ||
// Functions with more complex scoping will be handled by the function itself. | ||
var arg0 = node.Args[0]; | ||
_scopeTypes[node.Scope.Id] = arg0.IRContext.ResultType; | ||
} | ||
|
||
node.Function.ComposeDependencyInfo(node, this, context); | ||
|
||
return null; | ||
} | ||
|
||
public override RetVal Visit(BinaryOpNode node, DependencyContext context) | ||
{ | ||
node.Left.Accept(this, context); | ||
anderson-joyle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
node.Right.Accept(this, context); | ||
return null; | ||
} | ||
|
||
public override RetVal Visit(UnaryOpNode node, DependencyContext context) | ||
{ | ||
return node.Child.Accept(this, context); | ||
} | ||
|
||
public override RetVal Visit(ScopeAccessNode node, DependencyContext context) | ||
{ | ||
// Could be a symbol from RowScope. | ||
// Price in "LookUp(t1,Price=255)" | ||
if (node.Value is ScopeAccessSymbol sym) | ||
{ | ||
if (_scopeTypes.TryGetValue(sym.Parent.Id, out var type)) | ||
{ | ||
// Ignore ThisRecord scopeaccess node. e.g. Summarize(table, f1, Sum(ThisGroup, f2)) where ThisGroup should be ignored. | ||
if (type is TableType tableType && tableType.TryGetFieldType(sym.Name.Value, out _)) | ||
{ | ||
AddDependency(tableType.TableSymbolName, sym.Name.Value); | ||
|
||
return null; | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
// field // IR will implicity recognize as ThisRecod.field | ||
// ThisRecord.field // IR will get type of ThisRecord | ||
// First(Remote).Data // IR will get type on left of dot. | ||
public override RetVal Visit(RecordFieldAccessNode node, DependencyContext context) | ||
{ | ||
node.From.Accept(this, context); | ||
|
||
var ltype = node.From.IRContext.ResultType; | ||
if (ltype is RecordType ltypeRecord) | ||
{ | ||
// Logical name of the table on left side. | ||
// This will be null for non-dataverse records | ||
var tableLogicalName = ltypeRecord.TableSymbolName; | ||
if (tableLogicalName != null) | ||
{ | ||
var fieldLogicalName = node.Field.Value; | ||
AddDependency(tableLogicalName, fieldLogicalName); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public override RetVal Visit(ResolvedObjectNode node, DependencyContext context) | ||
{ | ||
if (node.IRContext.ResultType is AggregateType aggregateType) | ||
{ | ||
AddDependency(aggregateType.TableSymbolName, null); | ||
} | ||
|
||
CheckResolvedObjectNodeValue(node, context); | ||
|
||
return null; | ||
} | ||
|
||
public void CheckResolvedObjectNodeValue(ResolvedObjectNode node, DependencyContext context) | ||
{ | ||
if (node.Value is NameSymbol sym) | ||
{ | ||
if (sym.Owner is SymbolTableOverRecordType symTable) | ||
{ | ||
RecordType type = symTable.Type; | ||
var tableLogicalName = type.TableSymbolName; | ||
|
||
if (symTable.IsThisRecord(sym)) | ||
{ | ||
// "ThisRecord". Whole entity | ||
AddDependency(type.TableSymbolName, null); | ||
return; | ||
} | ||
|
||
// on current table | ||
var fieldLogicalName = sym.Name; | ||
|
||
AddDependency(type.TableSymbolName, fieldLogicalName); | ||
} | ||
} | ||
} | ||
|
||
public override RetVal Visit(SingleColumnTableAccessNode node, DependencyContext context) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public override RetVal Visit(ChainingNode node, DependencyContext context) | ||
{ | ||
foreach (var child in node.Nodes) | ||
{ | ||
child.Accept(this, context); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public override RetVal Visit(AggregateCoercionNode node, DependencyContext context) | ||
{ | ||
foreach (var kv in node.FieldCoercions) | ||
{ | ||
kv.Value.Accept(this, context); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public class RetVal | ||
{ | ||
} | ||
|
||
public class DependencyContext | ||
{ | ||
public DependencyContext() | ||
{ | ||
} | ||
} | ||
|
||
// if fieldLogicalName, then we're taking a dependency on entire record. | ||
public void AddDependency(string tableLogicalName, string fieldLogicalName) | ||
{ | ||
if (tableLogicalName == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (!Info.Dependencies.ContainsKey(tableLogicalName)) | ||
{ | ||
Info.Dependencies[tableLogicalName] = new HashSet<string>(); | ||
} | ||
|
||
if (fieldLogicalName != null) | ||
{ | ||
Info.Dependencies[tableLogicalName].Add(fieldLogicalName); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Capture Dataverse field-level reads and writes within a formula. | ||
/// </summary> | ||
public class DependencyInfo | ||
{ | ||
#pragma warning disable CS1570 // XML comment has badly formed XML | ||
/// <summary> | ||
/// A dictionary of field logical names on related records, indexed by the related entity logical name. | ||
/// </summary> | ||
/// <example> | ||
/// On account, the formula "Name & 'Primary Contact'.'Full Name'" would return | ||
/// "contact" => { "fullname" } | ||
/// The formula "Name & 'Primary Contact'.'Full Name' & Sum(Contacts, 'Number Of Childeren')" would return | ||
/// "contact" => { "fullname", "numberofchildren" }. | ||
/// </example> | ||
public Dictionary<string, HashSet<string>> Dependencies { get; set; } | ||
|
||
public DependencyInfo() | ||
{ | ||
Dependencies = new Dictionary<string, HashSet<string>>(); | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
StringBuilder sb = new StringBuilder(); | ||
DumpHelper(sb, Dependencies); | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
private static void DumpHelper(StringBuilder sb, Dictionary<string, HashSet<string>> dict) | ||
{ | ||
if (dict != null) | ||
{ | ||
foreach (var kv in dict) | ||
{ | ||
sb.Append("Entity"); | ||
sb.Append(" "); | ||
sb.Append(kv.Key); | ||
sb.Append(": "); | ||
|
||
bool first = true; | ||
foreach (var x in kv.Value) | ||
{ | ||
if (!first) | ||
{ | ||
sb.Append(", "); | ||
} | ||
|
||
first = false; | ||
sb.Append(x); | ||
} | ||
|
||
sb.AppendLine("; "); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.