Skip to content

Commit

Permalink
Auto-sync from Azure-Kusto-Service
Browse files Browse the repository at this point in the history
  • Loading branch information
Kusto Build System committed Nov 11, 2024
1 parent 4d32af4 commit bac5550
Show file tree
Hide file tree
Showing 15 changed files with 551 additions and 201 deletions.
164 changes: 126 additions & 38 deletions src/Kusto.Language/Binder/Binder_FunctionCalls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,31 @@ private void GetArgumentsAndTypes(
}
}

/// <summary>
/// Gets the parameters that correspond to the arguments.
/// </summary>
private void GetArgumentParameters(
FunctionCallExpression functionCall,
List<Parameter> parameters)
{
if (functionCall.ReferencedSignature is Signature signature)
{
var arguments = s_expressionListPool.AllocateFromPool();
var argumentTypes = s_typeListPool.AllocateFromPool();

try
{
GetArgumentsAndTypes(functionCall, arguments, argumentTypes);
signature.GetArgumentParameters(arguments, parameters);
}
finally
{
s_expressionListPool.ReturnToPool(arguments);
s_typeListPool.ReturnToPool(argumentTypes);
}
}
}

/// <summary>
/// Gets the result information for the function call or operator invocation when invoked with the specified arguments.
/// </summary>
Expand Down Expand Up @@ -1694,6 +1719,10 @@ void AddExpansionToCache(CallSiteInfo callsite, FunctionCallExpansion expansion)
{
TryGetFunctionBodyFacts(callsite.Signature, out var funFacts);

// record number of times each signature is expanded
_localBindingCache.FunctionExpansionCounts.AddOrUpdate(callsite.Signature, 1, c => c + 1);

#if true // Temporarily disable
// if there is a call to unqualified table(t) then it may require resolving using dynamic scope, so don't cache anywhere
if (funFacts != null && funFacts.HasUnqualifiedTableCall)
return;
Expand All @@ -1702,6 +1731,11 @@ void AddExpansionToCache(CallSiteInfo callsite, FunctionCallExpansion expansion)
// might need to rethink this if memory consumption is shown to be an issue
var shouldCacheGlobally = IsDatabaseSymbolSignature(callsite.Signature)
&& (funFacts != null && !funFacts.HasVariableReturnType);
#else
// only add database functions that are not variable in nature to global cache
// might need to rethink this if memory consumption is shown to be an issue
var shouldCacheGlobally = IsDatabaseSymbolSignature(callsite.Signature);
#endif

if (shouldCacheGlobally)
{
Expand Down Expand Up @@ -1860,15 +1894,7 @@ internal FunctionBodyFacts GetOrComputeFunctionBodyFacts(Signature signature, Sy
{
if (!TryGetFunctionBodyFacts(signature, out var facts))
{
var bodyFacts = ComputeFunctionBodyFlags(signature, body);

var nonVariableReturnType = (bodyFacts & FunctionBodyFlags.VariableReturn) == 0
? GetBodyResultType(body) ?? ErrorSymbol.Instance
: null;

var hasSyntaxErrors = HasSyntaxErrors(body);

facts = new FunctionBodyFacts(bodyFacts, nonVariableReturnType, hasSyntaxErrors);
facts = ComputeFunctionBodyFacts(signature, body);
SetFunctionBodyFacts(signature, facts);
}

Expand Down Expand Up @@ -1899,12 +1925,18 @@ internal void SetFunctionBodyFacts(Signature signature, FunctionBodyFacts facts)
}
}

private FunctionBodyFlags ComputeFunctionBodyFlags(Signature signature, SyntaxNode body)
private FunctionBodyFacts ComputeFunctionBodyFacts(Signature signature, SyntaxNode body)
{
var result = FunctionBodyFlags.None;
var result = FunctionBodyFacts.Default;
var isTabular = GetBodyResultType(body) is TableSymbol;

// look for explicit calls to table(), database() or cluster() like functions
// if the function returns a table, mark all tabular parameters as dependent
if (isTabular && signature.Parameters.Any(p => p.IsTabular))
{
result = result.WithDependentParameters(result.DependentParameters.ToSafeList().AddItems(signature.Parameters.Where(p => p.IsTabular)));
}

// identify dependent parameters and other function body facts
SyntaxElement.WalkNodes(
body,
fnBefore: node =>
Expand All @@ -1915,57 +1947,80 @@ private FunctionBodyFlags ComputeFunctionBodyFlags(Signature signature, SyntaxNo
if (fc.ReferencedSymbol == Functions.Table)
{
// distinguish between database(d).table(t) vs just table(t)
// since table(t) can see variables in dynamic scope
// since table(t) can see local variables from dynamic scope
if (fc.Parent is PathExpression p && p.Selector == fc)
{
result |= FunctionBodyFlags.QualifiedTable;
result = result.WithHasQualifiedTableCall(true);
}
else
{
// unqualified table calls (even with literal arguments) can be dependent on the call site since
// the names can reference local tabular variables in outer scopes
result |= FunctionBodyFlags.UnqualifiedTable | FunctionBodyFlags.VariableReturn;
// the names can reference tabular variables in outer scopes
result = result.WithHasUnqualifiedTableCall(true);
}
}
else if (fc.ReferencedSymbol == Functions.ExternalTable)
{
result |= FunctionBodyFlags.ExternalTable;
result = result.WithHasExternalTableCall(true);
}
else if (fc.ReferencedSymbol == Functions.MaterializedView)
{
result |= FunctionBodyFlags.MaterializedView;
result = result.WithHasMaterializedViewCall(true);
}
else if (fc.ReferencedSymbol == Functions.Database)
{
result |= FunctionBodyFlags.Database;
result = result.WithHasDatabaseCall(true);
}
else if (fc.ReferencedSymbol == Functions.Cluster)
{
result |= FunctionBodyFlags.Cluster;
result = result.WithHasClusterCall(true);
}

// if the argument is not a literal, then the function likely has a variable return schema
// note: it might not, but that would require full flow analysis of result type back to inputs.
// if the first argument is not a literal, then it might be being supplied by a parameter to the containing function
// which means this function could be being called multiple times with different arguments.
var isLiteral = fc.ArgumentList.Expressions.Count > 0 && fc.ArgumentList.Expressions[0].Element.IsLiteral;
if (!isLiteral && isTabular)
{
result |= FunctionBodyFlags.VariableReturn;
var arg = fc.ArgumentList.Expressions[0].Element;
if (GetReferencedParameter(signature, arg) is Parameter p)
{
result = result.WithDependentParameters(result.DependentParameters.ToSafeList().AddItem(p));
}
}
}
else if (
node is Expression ex
&& ex.ReferencedSignature is Signature sig
&& !IsSymbolLookupFunction(sig.Symbol))
{
var flags = GetFunctionBodyFlags(ex);
var facts = GetFunctionBodyFacts(ex);

// if the calling function has no parameters and the called function does not contain unqualified calls to table() function, then don't considered it having a variable return
if (signature.Parameters.Count == 0 && (flags & FunctionBodyFlags.UnqualifiedTable) == 0)
result = result.CombineCalledFunction(facts);

// translate dependent parameters from the called function to parameters of the calling function
if (facts.DependentParameters.Count > 0
&& ex is FunctionCallExpression fcall
&& isTabular)
{
flags &= ~FunctionBodyFlags.VariableReturn;
}
var fcallParameters = s_parameterListPool.AllocateFromPool();
GetArgumentParameters(fcall, fcallParameters);

for (int i = 0; i < fcall.ArgumentList.Expressions.Count; i++)
{
var arg = fcall.ArgumentList.Expressions[i].Element;
if (facts.DependentParameters.Contains(fcallParameters[i]))
{
// this argument is dependent therefore any parameter that feeds it is also dependent
// note: these arguments should be constrained to only constant expressions due to requirements of symbol lookup functions.
if (GetReferencedParameter(signature, arg) is Parameter p)
{
result = result.WithDependentParameters(result.DependentParameters.ToSafeList().AddItem(p));
}
}
}

result |= flags;
s_parameterListPool.ReturnToPool(fcallParameters);
}
}
},
fnAfter: node =>
Expand All @@ -1974,7 +2029,8 @@ node is Expression ex
{
foreach (var alt in node.Alternates)
{
result |= ComputeFunctionBodyFlags(signature, alt);
var facts = ComputeFunctionBodyFacts(signature, alt);
result = result.CombineCalledFunction(facts);
}
}
},
Expand All @@ -1983,13 +2039,45 @@ node is Expression ex
|| (!(node is FunctionDeclaration) && !(node is FunctionBody))
);

// the function returns a table and at least one parameter is a tabular
if (isTabular && signature.Parameters.Any(p => p.IsTabular))
var nonVariableReturnType = !result.HasVariableReturnType
? GetBodyResultType(body) ?? ErrorSymbol.Instance
: null;

result = result.WithNonVariableReturnType(nonVariableReturnType);

var hasSyntaxErrors = HasSyntaxErrors(body);
result = result.WithHasSyntaxErrors(hasSyntaxErrors);

return result;
}

/// <summary>
/// Gets the parameter referenced by the expression.
/// Sees through simple let-variable reassignments.
/// </summary>
private static Parameter GetReferencedParameter(Signature signature, Expression expression)
{
if (expression is SimpleNamedExpression sne)
expression = sne.Expression;

switch (expression.ReferencedSymbol)
{
result |= FunctionBodyFlags.VariableReturn;
case ParameterSymbol p:
return signature.GetParameter(p.Name);
case VariableSymbol v:
if (v.Source != null
&& v.Source.Tree != expression.Tree)
{
// this is a variable in place of a parameter symbol used for expansion binding to carry constant values.
return signature.GetParameter(v.Name);
}

return v.Source != null
? GetReferencedParameter(signature, v.Source)
: null;
}

return result;
return null;
}

private static bool IsSymbolLookupFunction(Symbol symbol) =>
Expand All @@ -2000,9 +2088,9 @@ private static bool IsSymbolLookupFunction(Symbol symbol) =>
|| symbol == Functions.Cluster;

/// <summary>
/// Gets the <see cref="FunctionBodyFlags"/> for the function invocation
/// Gets the <see cref="FunctionBodyFacts"/> for the function invocation
/// </summary>
private FunctionBodyFlags GetFunctionBodyFlags(Expression expr)
private FunctionBodyFacts GetFunctionBodyFacts(Expression expr)
{
if (expr.ReferencedSignature is Signature signature)
{
Expand Down Expand Up @@ -2034,10 +2122,10 @@ private FunctionBodyFlags GetFunctionBodyFlags(Expression expr)
TryGetFunctionBodyFacts(signature, out funFacts);
}

return funFacts?.Flags ?? FunctionBodyFlags.None;
return funFacts ?? FunctionBodyFacts.Default;
}

return FunctionBodyFlags.None;
return FunctionBodyFacts.Default;
}
}
}
75 changes: 69 additions & 6 deletions src/Kusto.Language/Binder/Binder_Names.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ private static bool IsFunctionCallName(SyntaxNode name)
/// </summary>
public bool IsInsideCurrentDatabaseFunctionBody(SyntaxNode location)
{
if (IsInsideCurrentDatabaseFunctionSymbol())
if (GetCurrentDatabaseFunctionName(location) != null)
return true;

if (IsInsideCreateFunctionCommand(location))
Expand All @@ -235,11 +235,24 @@ public bool IsInsideCurrentDatabaseFunctionBody(SyntaxNode location)
return false;
}

/// <summary>
/// Gets the name of the current database function declaration the location is inside of.
/// </summary>
private string GetCurrentDatabaseFunctionName(SyntaxNode location)
{
if (GetCurrentDatabaseFunction() is FunctionSymbol fn)
{
return fn.Name;
}

return GetCreateFunctionCommandName(location);
}

/// <summary>
/// Returns true if the function symbol we are currently analyzing
/// is from the current database.
/// </summary>
private bool IsInsideCurrentDatabaseFunctionSymbol()
private FunctionSymbol GetCurrentDatabaseFunction()
{
var binder = this;

Expand All @@ -248,7 +261,7 @@ private bool IsInsideCurrentDatabaseFunctionSymbol()
if (binder._currentFunction != null
&& _globals.GetDatabase(binder._currentFunction) == _currentDatabase)
{
return true;
return binder._currentFunction;
}

// try outer binder in case the current function is a local function
Expand All @@ -259,7 +272,7 @@ private bool IsInsideCurrentDatabaseFunctionSymbol()
continue;
}

return false;
return null;
}
}

Expand Down Expand Up @@ -296,6 +309,53 @@ private static bool IsInsideCreateFunctionCommand(SyntaxNode location)
}
}

/// <summary>
/// Returns the name of the function that the create function command is creating.
/// </summary>
private static string GetCreateFunctionCommandName(SyntaxNode location)
{
// get the node from in the original tree
// in case we are analyzing a tree fragment.
location = location.GetOriginalNode();

while (true)
{
var functionDeclaration = location.GetFirstAncestor<FunctionDeclaration>();
if (functionDeclaration != null)
{
if (functionDeclaration.Parent is CustomNode cn)
return GetFunctionName(cn);

location = functionDeclaration;
continue;
}

var functionBody = location.GetFirstAncestor<FunctionBody>();
if (functionBody != null)
{
if (functionBody.Parent is CustomNode cn)
return GetFunctionName(cn);
location = functionBody;
continue;
}

return null;
}
}

/// <summary>
/// Gets the text of the custom node named 'FunctionName'
/// </summary>
private static string GetFunctionName(CustomNode node)
{
if (node.GetFirstDescendant<SyntaxElement>(cn => cn.NameInParent == "FunctionName") is SyntaxElement element
&& element.GetFirstDescendantOrSelf<Name>() is Name name)
{
return name.SimpleName;
}
return null;
}

private static bool IsInsideControlCommand(SyntaxNode location)
{
// so far, only control commands use CustomNode
Expand Down Expand Up @@ -409,9 +469,12 @@ private bool GetMatchingSymbolsInNormalScope(string name, SymbolMatch match, Syn
{
// don't match the database functions that have same name as database tables
// if we are inside declaration of a database function
if (IsInsideCurrentDatabaseFunctionBody(location) &&
_currentDatabase.GetAnyTable(name) != null)
if ((_pathScope == null || _pathScope == _currentDatabase)
&& GetCurrentDatabaseFunctionName(location) == name
&& _currentDatabase.GetAnyTable(name) != null)
{
// don't allow match to function this code is inside of
// match same name table instead.
match &= ~SymbolMatch.Function;
}

Expand Down
Loading

0 comments on commit bac5550

Please sign in to comment.