microsoft / Kusto-Query-Language

Kusto Query Language is a simple and productive language for querying Big Data.
Apache License 2.0
562 stars 104 forks source link

`NullReferenceException` when running `ParseAndAnalyze` #109

Closed Tayham closed 1 year ago

Tayham commented 1 year ago

We have created the Kusto GlobalState and when using that GlobalState it works for most of our functions, but some functions of ours have been throwing this error when running KustoCode.ParseAndAnalyze(kustoFunction, globalState):

Exception thrown during KQL validation of Kql\Support\Functions\MetricCollection\MetricCollection_identityAndSelect.csl
testhost Error: 0 : System.NullReferenceException: Object reference not set to an instance of an object.
   at Kusto.Language.Symbols.Parameter.get_Tabularity()
   at Kusto.Language.Binding.Binder.GetArgumentsAsLocals(Signature signature, IReadOnlyList`1 arguments, IReadOnlyList`1 argumentTypes)
   at Kusto.Language.Binding.Binder.GetCallSiteInfo(Signature signature, IReadOnlyList`1 arguments, IReadOnlyList`1 argumentTypes)
   at Kusto.Language.Binding.Binder.GetFunctionCallExpansion(Signature signature, IReadOnlyList`1 arguments, IReadOnlyList`1 argumentTypes, LocalScope outerScope)
   at Kusto.Language.Binding.Binder.GetComputedFunctionCallResult(Signature signature, IReadOnlyList`1 arguments, IReadOnlyList`1 argumentTypes)
   at Kusto.Language.Binding.Binder.GetFunctionCallResult(Signature signature, IReadOnlyList`1 arguments, IReadOnlyList`1 argumentTypes, IReadOnlyList`1 argumentParameters, SyntaxElement location, List`1 diagnostics)
   at Kusto.Language.Binding.Binder.GetFunctionCallResult(Signature signature, IReadOnlyList`1 arguments, IReadOnlyList`1 argumentTypes, SyntaxElement location, List`1 diagnostics)
   at Kusto.Language.Binding.Binder.BindFunctionCall(FunctionCallExpression functionCall, FunctionSymbol fn)
   at Kusto.Language.Binding.Binder.TreeBinder.BindNode(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitFunctionCallExpression(FunctionCallExpression node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitLetStatement(LetStatement node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Syntax.DefaultSyntaxVisitor.VisitFunctionDeclaration(FunctionDeclaration node)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitFunctionDeclaration(FunctionDeclaration node)
   at Kusto.Language.Syntax.FunctionDeclaration.Accept(SyntaxVisitor visitor)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Syntax.DefaultSyntaxVisitor.VisitCustom(CustomNode node)
   at Kusto.Language.Syntax.CustomNode.Accept(SyntaxVisitor visitor)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Syntax.DefaultSyntaxVisitor.VisitCustomCommand(CustomCommand node)
   at Kusto.Language.Syntax.CustomCommand.Accept(SyntaxVisitor visitor)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitChildren(SyntaxNode node)
   at Kusto.Language.Binding.Binder.TreeBinder.DefaultVisit(SyntaxNode node)
   at Kusto.Language.Syntax.DefaultSyntaxVisitor.VisitExpressionStatement(ExpressionStatement node)
   at Kusto.Language.Syntax.ExpressionStatement.Accept(SyntaxVisitor visitor)
   at Kusto.Language.Binding.Binder.TreeBinder.VisitCommandBlock(CommandBlock node)
   at Kusto.Language.Syntax.CommandBlock.Accept(SyntaxVisitor visitor)
   at Kusto.Language.Binding.Binder.TryBind(SyntaxTree tree, GlobalState globals, LocalBindingCache localBindingCache, Action`2 semanticInfoSetter, CancellationToken cancellationToken)
   at Kusto.Language.KustoCode.Create(String text, GlobalState globals, LexicalToken[] tokens, List`1 tokenStarts, Boolean analyze, CancellationToken cancellationToken)
   at Kusto.Language.KustoCode.ParseAndAnalyze(String text, GlobalState globals, CancellationToken cancellationToken)

Here are the referenced functions:

MetricCollection_identityAndSelect

// returns table with _Identity, DimensionsBag, MinArray, MaxArray, CounArray
// where MinAray, MaxArray, and CountArray are arrays with the min, max, and count values corresponding to the passed minColumns, maxColumns, and countColumns
.create-or-alter function with (folder = "MetricCollection", docstring = "returns table with _Identity, DimensionsBag, MinArray, MaxArray, CountArray where MinAray, MaxArray, and CountArray are arrays with the min, max, and count values corresponding to the passed minColumns, maxColumns, and countColumns", skipvalidation = "true") MetricCollection_identityAndSelect(filteredAggregate: (*), dimensionColumns: dynamic, minColumns: dynamic, maxColumns: dynamic, countColumns: dynamic)
{
    let knownColumns = array_concat(minColumns, maxColumns, countColumns, dimensionColumns);
    let narrowed = 
        MetricCollection_narrow(filteredAggregate, knownColumns);
    narrowed
        | summarize
            _Identity = binary_all_xor(iif(Column in (dimensionColumns), hash_many(Column, Value), 0)), // we won't care about order, and this will be pretty unique in practice - although not un-predictable
            DimensionsBag = make_bag_if(bag_pack(Column, Value), Column in (dimensionColumns)),
            MinArray = make_list_if(todouble(Value), Column in (minColumns)),
            MaxArray = make_list_if(todouble(Value), Column in (maxColumns)),
            CountArray = make_list_if(tolong(Value), Column in (countColumns))
            by Row
        | project-away Row
}

MetricCollection_narrow

// narrows (aka. tidy's) a table into Row, Column, Value without using narrow()
//
// narrow() is pretty bad perf wise, thus this function existing
.create-or-alter function with (folder="MetricCollection", docstring="narrows (aka. tidy's) a table into Row, Column, Value without using narrow()", skipvalidation="true") MetricCollection_narrow(table: (*), knownColumns:dynamic) {
    let allColumns = toscalar((table | getschema | summarize make_list(ColumnName)));
    let toRemoveColumns = set_difference(allColumns, knownColumns);
    let baggedData =
        table
        | serialize
        | project Row = row_number(), PackedKnownColumns = bag_remove_keys(pack_all(), toRemoveColumns);
    let expandedData =
        baggedData
        | mv-expand bagexpansion=array PackedKnownColumns
        | project Row, Column=tostring(PackedKnownColumns[0]), Value=tostring(PackedKnownColumns[1]);
    expandedData
}

Please let me know if you need any more additional information. Thanks!

mattwar commented 1 year ago

Are you analyzing one of these create-or-alter commands or are you analyzing a query that references one or more of these functions that exist in the database?

I'm going to need a more specific example of a query you are passing to ParseAndAnalyze to reproduce this problem. I've tried inventing a scenario and it does not repro.

Tayham commented 1 year ago

We are parsing the MetricCollection_identityAndSelect create-or-alter command with a global state that contains the MetricCollection_narrow function.

mattwar commented 1 year ago

This issue was resolved offline.