From dc51a543a3f93e865a75b4607c1ff1645b045445 Mon Sep 17 00:00:00 2001 From: puchacz Date: Mon, 23 Sep 2024 19:56:56 +0200 Subject: [PATCH] another tests & fixes --- Musoq.Evaluator.Tests/CrossApplyCteTests.cs | 323 ++++++++++++++++++ Musoq.Evaluator.Tests/CrossApplyMixedTests.cs | 139 ++++++++ .../CrossApplySelfPropertyTests.cs | 1 - Musoq.Evaluator.Tests/CteTests.cs | 159 ++++----- Musoq.Evaluator.Tests/OuterApplyCteTests.cs | 323 ++++++++++++++++++ Musoq.Evaluator.Tests/OuterApplyMixedTests.cs | 147 +++++++- Musoq.Evaluator/Tables/TransitionSchema.cs | 13 +- Musoq.Evaluator/Tables/VariableTable.cs | 9 +- ...uildMetadataAndInferTypeTraverseVisitor.cs | 5 +- .../BuildMetadataAndInferTypeVisitor.cs | 111 ++++-- 10 files changed, 1076 insertions(+), 154 deletions(-) create mode 100644 Musoq.Evaluator.Tests/CrossApplyCteTests.cs create mode 100644 Musoq.Evaluator.Tests/OuterApplyCteTests.cs diff --git a/Musoq.Evaluator.Tests/CrossApplyCteTests.cs b/Musoq.Evaluator.Tests/CrossApplyCteTests.cs new file mode 100644 index 00000000..c8de47c6 --- /dev/null +++ b/Musoq.Evaluator.Tests/CrossApplyCteTests.cs @@ -0,0 +1,323 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Musoq.Evaluator.Tests.Schema.Generic; +using Musoq.Plugins.Attributes; + +namespace Musoq.Evaluator.Tests; + +[TestClass] +public class CrossApplyCteTests : GenericEntityTestBase +{ + private class CrossApplyClass1 + { + public string City { get; set; } + + public string Country { get; set; } + + public int Population { get; set; } + } + + private class CrossApplyClass2 + { + public string Country { get; set; } + + public decimal Money { get; set; } + + public string Month { get; set; } + } + + private class CrossApplyClass3 + { + public string Name { get; set; } + + [BindablePropertyAsTable] + public string[] Skills { get; set; } + } + + [TestMethod] + public void WhenSchemaMethodCrossAppliedWithAnotherSchema_WithinCte_ShouldPass() + { + const string query = @" +with p as ( + select a.City, a.Country, a.Population, b.Country, b.Money, b.Month from #schema.first() a cross apply #schema.second(a.Country) b +) +select [a.City], [a.Country], [a.Population], [b.Country], [b.Money], [b.Month] from p"; + + var firstSource = new List + { + new() {City = "City1", Country = "Country1", Population = 100}, + new() {City = "City2", Country = "Country1", Population = 200}, + new() {City = "City3", Country = "Country2", Population = 300} + }.ToArray(); + + var secondSource = new List + { + new() {Country = "Country1", Money = 1000, Month = "January"}, + new() {Country = "Country1", Money = 2000, Month = "February"}, + new() {Country = "Country2", Money = 3000, Month = "March"} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource, + secondSource, + null, + null, + null, + (parameters, source) => new ObjectRowsSource(source.Rows.Where(f => (string) f["Country"] == (string) parameters[0]).ToArray())); + + var table = vm.Run(); + + Assert.AreEqual(6, table.Columns.Count()); + Assert.AreEqual("a.City", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Country", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("a.Population", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(int), table.Columns.ElementAt(2).ColumnType); + Assert.AreEqual("b.Country", table.Columns.ElementAt(3).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(3).ColumnType); + Assert.AreEqual("b.Money", table.Columns.ElementAt(4).ColumnName); + Assert.AreEqual(typeof(decimal), table.Columns.ElementAt(4).ColumnType); + Assert.AreEqual("b.Month", table.Columns.ElementAt(5).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(5).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("City1", table[0].Values[0]); + Assert.AreEqual("Country1", table[0].Values[1]); + Assert.AreEqual(100, table[0].Values[2]); + Assert.AreEqual("Country1", table[0].Values[3]); + Assert.AreEqual(1000m, table[0].Values[4]); + + Assert.AreEqual("City1", table[1].Values[0]); + Assert.AreEqual("Country1", table[1].Values[1]); + Assert.AreEqual(100, table[1].Values[2]); + Assert.AreEqual("Country1", table[1].Values[3]); + Assert.AreEqual(2000m, table[1].Values[4]); + + Assert.AreEqual("City2", table[2].Values[0]); + Assert.AreEqual("Country1", table[2].Values[1]); + Assert.AreEqual(200, table[2].Values[2]); + Assert.AreEqual("Country1", table[2].Values[3]); + Assert.AreEqual(1000m, table[2].Values[4]); + + Assert.AreEqual("City2", table[3].Values[0]); + Assert.AreEqual("Country1", table[3].Values[1]); + Assert.AreEqual(200, table[3].Values[2]); + Assert.AreEqual("Country1", table[3].Values[3]); + Assert.AreEqual(2000m, table[3].Values[4]); + + Assert.AreEqual("City3", table[4].Values[0]); + Assert.AreEqual("Country2", table[4].Values[1]); + Assert.AreEqual(300, table[4].Values[2]); + Assert.AreEqual("Country2", table[4].Values[3]); + Assert.AreEqual(3000m, table[4].Values[4]); + } + + [TestMethod] + public void WhenSchemaMethodCrossAppliedWithAnotherSchema_UsesCte_ShouldPass() + { + const string query = @" +with p as ( + select + f.City as City, + f.Country as Country, + f.Population as Population + from #schema.first() f +) +select a.City, a.Country, a.Population, b.Country, b.Money, b.Month from p a cross apply #schema.second(a.Country) b"; + + var firstSource = new List + { + new() {City = "City1", Country = "Country1", Population = 100}, + new() {City = "City2", Country = "Country1", Population = 200}, + new() {City = "City3", Country = "Country2", Population = 300} + }.ToArray(); + + var secondSource = new List + { + new() {Country = "Country1", Money = 1000, Month = "January"}, + new() {Country = "Country1", Money = 2000, Month = "February"}, + new() {Country = "Country2", Money = 3000, Month = "March"} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource, + secondSource, + null, + null, + null, + (parameters, source) => new ObjectRowsSource(source.Rows.Where(f => (string) f["Country"] == (string) parameters[0]).ToArray())); + + var table = vm.Run(); + + Assert.AreEqual(6, table.Columns.Count()); + Assert.AreEqual("a.City", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Country", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("a.Population", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(int), table.Columns.ElementAt(2).ColumnType); + Assert.AreEqual("b.Country", table.Columns.ElementAt(3).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(3).ColumnType); + Assert.AreEqual("b.Money", table.Columns.ElementAt(4).ColumnName); + Assert.AreEqual(typeof(decimal), table.Columns.ElementAt(4).ColumnType); + Assert.AreEqual("b.Month", table.Columns.ElementAt(5).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(5).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("City1", table[0].Values[0]); + Assert.AreEqual("Country1", table[0].Values[1]); + Assert.AreEqual(100, table[0].Values[2]); + Assert.AreEqual("Country1", table[0].Values[3]); + Assert.AreEqual(1000m, table[0].Values[4]); + + Assert.AreEqual("City1", table[1].Values[0]); + Assert.AreEqual("Country1", table[1].Values[1]); + Assert.AreEqual(100, table[1].Values[2]); + Assert.AreEqual("Country1", table[1].Values[3]); + Assert.AreEqual(2000m, table[1].Values[4]); + + Assert.AreEqual("City2", table[2].Values[0]); + Assert.AreEqual("Country1", table[2].Values[1]); + Assert.AreEqual(200, table[2].Values[2]); + Assert.AreEqual("Country1", table[2].Values[3]); + Assert.AreEqual(1000m, table[2].Values[4]); + + Assert.AreEqual("City2", table[3].Values[0]); + Assert.AreEqual("Country1", table[3].Values[1]); + Assert.AreEqual(200, table[3].Values[2]); + Assert.AreEqual("Country1", table[3].Values[3]); + Assert.AreEqual(2000m, table[3].Values[4]); + + Assert.AreEqual("City3", table[4].Values[0]); + Assert.AreEqual("Country2", table[4].Values[1]); + Assert.AreEqual(300, table[4].Values[2]); + Assert.AreEqual("Country2", table[4].Values[3]); + Assert.AreEqual(3000m, table[4].Values[4]); + } + + [TestMethod] + public void WhenSchemaMethodCrossAppliedSelfProperty_WithinCte_ShouldPass() + { + const string query = @" +with p as ( + select a.Name, b.Value from #schema.first() a cross apply a.Skills b +) +select [a.Name], [b.Value] from p"; + + var firstSource = new List + { + new() {Name = "Name1", Skills = ["Skill1", "Skill2", "Skill3"]}, + new() {Name = "Name2", Skills = ["Skill4", "Skill5", "Skill6"]}, + new() {Name = "Name3", Skills = ["Skill7", "Skill8", "Skill9"]} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource); + + var table = vm.Run(); + + Assert.AreEqual(2, table.Columns.Count()); + + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + + Assert.AreEqual("b.Value", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + + Assert.AreEqual(9, table.Count); + + Assert.AreEqual("Name1", table[0].Values[0]); + Assert.AreEqual("Skill1", table[0].Values[1]); + + Assert.AreEqual("Name1", table[1].Values[0]); + Assert.AreEqual("Skill2", table[1].Values[1]); + + Assert.AreEqual("Name1", table[2].Values[0]); + Assert.AreEqual("Skill3", table[2].Values[1]); + + Assert.AreEqual("Name2", table[3].Values[0]); + Assert.AreEqual("Skill4", table[3].Values[1]); + + Assert.AreEqual("Name2", table[4].Values[0]); + Assert.AreEqual("Skill5", table[4].Values[1]); + + Assert.AreEqual("Name2", table[5].Values[0]); + Assert.AreEqual("Skill6", table[5].Values[1]); + + Assert.AreEqual("Name3", table[6].Values[0]); + Assert.AreEqual("Skill7", table[6].Values[1]); + + Assert.AreEqual("Name3", table[7].Values[0]); + Assert.AreEqual("Skill8", table[7].Values[1]); + + Assert.AreEqual("Name3", table[8].Values[0]); + Assert.AreEqual("Skill9", table[8].Values[1]); + } + + [TestMethod] + public void WhenSchemaMethodCrossAppliedSelfProperty_UsesCte_ShouldPass() + { + const string query = @" +with first as ( + select a.Name as Name, a.Skills as Skills from #schema.first() a +) +select a.Name, b.Value from first a cross apply a.Skills b"; + + var firstSource = new List + { + new() {Name = "Name1", Skills = ["Skill1", "Skill2", "Skill3"]}, + new() {Name = "Name2", Skills = ["Skill4", "Skill5", "Skill6"]}, + new() {Name = "Name3", Skills = ["Skill7", "Skill8", "Skill9"]} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource); + + var table = vm.Run(); + + Assert.AreEqual(2, table.Columns.Count()); + + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + + Assert.AreEqual("b.Value", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + + Assert.AreEqual(9, table.Count); + + Assert.AreEqual("Name1", table[0].Values[0]); + Assert.AreEqual("Skill1", table[0].Values[1]); + + Assert.AreEqual("Name1", table[1].Values[0]); + Assert.AreEqual("Skill2", table[1].Values[1]); + + Assert.AreEqual("Name1", table[2].Values[0]); + Assert.AreEqual("Skill3", table[2].Values[1]); + + Assert.AreEqual("Name2", table[3].Values[0]); + Assert.AreEqual("Skill4", table[3].Values[1]); + + Assert.AreEqual("Name2", table[4].Values[0]); + Assert.AreEqual("Skill5", table[4].Values[1]); + + Assert.AreEqual("Name2", table[5].Values[0]); + Assert.AreEqual("Skill6", table[5].Values[1]); + + Assert.AreEqual("Name3", table[6].Values[0]); + Assert.AreEqual("Skill7", table[6].Values[1]); + + Assert.AreEqual("Name3", table[7].Values[0]); + Assert.AreEqual("Skill8", table[7].Values[1]); + + Assert.AreEqual("Name3", table[8].Values[0]); + Assert.AreEqual("Skill9", table[8].Values[1]); + } +} \ No newline at end of file diff --git a/Musoq.Evaluator.Tests/CrossApplyMixedTests.cs b/Musoq.Evaluator.Tests/CrossApplyMixedTests.cs index ff5af59e..8837ac9d 100644 --- a/Musoq.Evaluator.Tests/CrossApplyMixedTests.cs +++ b/Musoq.Evaluator.Tests/CrossApplyMixedTests.cs @@ -384,4 +384,143 @@ where a.Budget > 400000 Assert.AreEqual("IT", table[0][0]); Assert.AreEqual(2, table[0][1]); } + + private class CrossApplyClass6 + { + public string Name { get; set; } + + public string Surname { get; set; } + + public int Id { get; set; } + } + + private class CrossApplyClass7 + { + public int Id { get; set; } + + [BindablePropertyAsTable] + public string[] Skills { get; set; } + } + + [TestMethod] + public void CrossApply_InnerJoinAndUseProperty_ShouldPass() + { + const string query = @" + select + a.Name, + a.Surname, + c.Value + from #schema.first() a + inner join #schema.second() b on a.Id = b.Id + cross apply b.Skills c"; + + var firstSource = new CrossApplyClass6[] + { + new() {Name = "John", Surname = "Doe", Id = 1}, + new() {Name = "Jane", Surname = "Smith", Id = 2}, + new() {Name = "Alice", Surname = "Johnson", Id = 3} + }; + + var secondSource = new CrossApplyClass7[] + { + new() {Id = 1, Skills = ["C#", "JavaScript"]}, + new() {Id = 2, Skills = ["Java"]}, + new() {Id = 3, Skills = ["Communication", "Negotiation"]} + }; + + var vm = CreateAndRunVirtualMachine(query, firstSource, secondSource); + + var table = vm.Run(); + + Assert.AreEqual(3, table.Columns.Count()); + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Surname", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("c.Value", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(2).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("John", table[0][0]); + Assert.AreEqual("Doe", table[0][1]); + Assert.AreEqual("C#", table[0][2]); + + Assert.AreEqual("John", table[1][0]); + Assert.AreEqual("Doe", table[1][1]); + Assert.AreEqual("JavaScript", table[1][2]); + + Assert.AreEqual("Jane", table[2][0]); + Assert.AreEqual("Smith", table[2][1]); + Assert.AreEqual("Java", table[2][2]); + + Assert.AreEqual("Alice", table[3][0]); + Assert.AreEqual("Johnson", table[3][1]); + Assert.AreEqual("Communication", table[3][2]); + + Assert.AreEqual("Alice", table[4][0]); + Assert.AreEqual("Johnson", table[4][1]); + Assert.AreEqual("Negotiation", table[4][2]); + } + + [TestMethod] + public void CrossApply_LeftJoinAndUseProperty_ShouldPass() + { + const string query = @" + select + a.Name, + a.Surname, + c.Value + from #schema.first() a + left outer join #schema.second() b on a.Id = b.Id + cross apply b.Skills c"; + + var firstSource = new CrossApplyClass6[] + { + new() {Name = "John", Surname = "Doe", Id = 1}, + new() {Name = "Jane", Surname = "Smith", Id = 2}, + new() {Name = "Alice", Surname = "Johnson", Id = 3} + }; + + var secondSource = new CrossApplyClass7[] + { + new() {Id = 1, Skills = ["C#", "JavaScript"]}, + new() {Id = 2, Skills = ["Java"]}, + new() {Id = 3, Skills = ["Communication", "Negotiation"]} + }; + + var vm = CreateAndRunVirtualMachine(query, firstSource, secondSource); + + var table = vm.Run(); + + Assert.AreEqual(3, table.Columns.Count()); + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Surname", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("c.Value", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(2).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("John", table[0][0]); + Assert.AreEqual("Doe", table[0][1]); + Assert.AreEqual("C#", table[0][2]); + + Assert.AreEqual("John", table[1][0]); + Assert.AreEqual("Doe", table[1][1]); + Assert.AreEqual("JavaScript", table[1][2]); + + Assert.AreEqual("Jane", table[2][0]); + Assert.AreEqual("Smith", table[2][1]); + Assert.AreEqual("Java", table[2][2]); + + Assert.AreEqual("Alice", table[3][0]); + Assert.AreEqual("Johnson", table[3][1]); + Assert.AreEqual("Communication", table[3][2]); + + Assert.AreEqual("Alice", table[4][0]); + Assert.AreEqual("Johnson", table[4][1]); + Assert.AreEqual("Negotiation", table[4][2]); + } } \ No newline at end of file diff --git a/Musoq.Evaluator.Tests/CrossApplySelfPropertyTests.cs b/Musoq.Evaluator.Tests/CrossApplySelfPropertyTests.cs index 34a27237..2ac69369 100644 --- a/Musoq.Evaluator.Tests/CrossApplySelfPropertyTests.cs +++ b/Musoq.Evaluator.Tests/CrossApplySelfPropertyTests.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; using Musoq.Evaluator.Tests.Schema.Generic; using Musoq.Plugins.Attributes; diff --git a/Musoq.Evaluator.Tests/CteTests.cs b/Musoq.Evaluator.Tests/CteTests.cs index a1c79299..26a0e855 100644 --- a/Musoq.Evaluator.Tests/CteTests.cs +++ b/Musoq.Evaluator.Tests/CteTests.cs @@ -60,14 +60,13 @@ public void SimpleCteWithStarTest() var sources = new Dictionary> { { - "#A", new[] - { + "#A", [ new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400), new BasicEntity("KATOWICE", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350) - } + ] } }; @@ -105,14 +104,13 @@ public void SimpleCteWithGroupingTest() var sources = new Dictionary> { { - "#A", new[] - { + "#A", [ new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400), new BasicEntity("KATOWICE", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350) - } + ] } }; @@ -142,14 +140,13 @@ public void SimpleCteWithGrouping2Test() var sources = new Dictionary> { { - "#A", new[] - { + "#A", [ new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400), new BasicEntity("KATOWICE", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350) - } + ] } }; @@ -179,20 +176,18 @@ public void SimpleCteWithUnionTest() { { "#A", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("KATOWICE", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350) - } + ] } }; @@ -231,20 +226,18 @@ public void SimpleCteWithUnionAllTest() { { "#A", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("KATOWICE", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350) - } + ] } }; @@ -283,21 +276,19 @@ public void SimpleCteWithExceptTest() { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("KATOWICE", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350) - } + ] } }; @@ -324,22 +315,20 @@ public void SimpleCteWithIntersectTest() { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350), new BasicEntity("HELSINKI", "FINLAND", 500) - } + ] } }; @@ -373,22 +362,20 @@ with p as ( { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350), new BasicEntity("HELSINKI", "FINLAND", 500) - } + ] } }; @@ -422,22 +409,20 @@ with p as ( { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350), new BasicEntity("HELSINKI", "FINLAND", 500) - } + ] } }; @@ -480,23 +465,21 @@ with p as ( { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350), new BasicEntity("TOKYO", "JAPAN", 500), new BasicEntity("HELSINKI", "FINLAND", 500) - } + ] } }; @@ -531,29 +514,26 @@ with p as ( { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350), new BasicEntity("HELSINKI", "FINLAND", 500) - } + ] }, { "#C", - new[] - { + [ new BasicEntity("NEW YORK", "USA", 250) - } + ] } }; @@ -598,22 +578,20 @@ with p as ( { { "#A", - new[] - { + [ new BasicEntity("HELSINKI", "FINLAND", 500), new BasicEntity("WARSAW", "POLAND", 500), new BasicEntity("CZESTOCHOWA", "POLAND", 400) - } + ] }, { "#B", - new[] - { + [ new BasicEntity("WARSAW", "POLAND", 250), new BasicEntity("BERLIN", "GERMANY", 250), new BasicEntity("MUNICH", "GERMANY", 350), new BasicEntity("HELSINKI", "FINLAND", 500) - } + ] } }; @@ -697,17 +675,15 @@ from first a { { "#A", - new[] - { - new BasicEntity("First"), - } + [ + new BasicEntity("First") + ] }, { "#B", - new[] - { - new BasicEntity("Second"), - } + [ + new BasicEntity("Second") + ] } }; @@ -739,17 +715,15 @@ from first a { { "#A", - new[] - { - new BasicEntity("First"), - } + [ + new BasicEntity("First") + ] }, { "#B", - new[] - { - new BasicEntity("Second"), - } + [ + new BasicEntity("Second") + ] } }; @@ -787,24 +761,21 @@ c.Name as Name { { "#A", - new[] - { - new BasicEntity("First"), - } + [ + new BasicEntity("First") + ] }, { "#B", - new[] - { - new BasicEntity("Second"), - } + [ + new BasicEntity("Second") + ] }, { "#C", - new[] - { - new BasicEntity("Third"), - } + [ + new BasicEntity("Third") + ] } }; diff --git a/Musoq.Evaluator.Tests/OuterApplyCteTests.cs b/Musoq.Evaluator.Tests/OuterApplyCteTests.cs new file mode 100644 index 00000000..0f2457b1 --- /dev/null +++ b/Musoq.Evaluator.Tests/OuterApplyCteTests.cs @@ -0,0 +1,323 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Musoq.Evaluator.Tests.Schema.Generic; +using Musoq.Plugins.Attributes; + +namespace Musoq.Evaluator.Tests; + +[TestClass] +public class OuterApplyCteTests : GenericEntityTestBase +{ + private class OuterApplyClass1 + { + public string City { get; set; } + + public string Country { get; set; } + + public int Population { get; set; } + } + + private class OuterApplyClass2 + { + public string Country { get; set; } + + public decimal Money { get; set; } + + public string Month { get; set; } + } + + private class OuterApplyClass3 + { + public string Name { get; set; } + + [BindablePropertyAsTable] + public string[] Skills { get; set; } + } + + [TestMethod] + public void WhenSchemaMethodOuterAppliedWithAnotherSchema_WithinCte_ShouldPass() + { + const string query = @" +with p as ( + select a.City, a.Country, a.Population, b.Country, b.Money, b.Month from #schema.first() a outer apply #schema.second(a.Country) b +) +select [a.City], [a.Country], [a.Population], [b.Country], [b.Money], [b.Month] from p"; + + var firstSource = new List + { + new() {City = "City1", Country = "Country1", Population = 100}, + new() {City = "City2", Country = "Country1", Population = 200}, + new() {City = "City3", Country = "Country2", Population = 300} + }.ToArray(); + + var secondSource = new List + { + new() {Country = "Country1", Money = 1000, Month = "January"}, + new() {Country = "Country1", Money = 2000, Month = "February"}, + new() {Country = "Country2", Money = 3000, Month = "March"} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource, + secondSource, + null, + null, + null, + (parameters, source) => new ObjectRowsSource(source.Rows.Where(f => (string) f["Country"] == (string) parameters[0]).ToArray())); + + var table = vm.Run(); + + Assert.AreEqual(6, table.Columns.Count()); + Assert.AreEqual("a.City", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Country", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("a.Population", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(int), table.Columns.ElementAt(2).ColumnType); + Assert.AreEqual("b.Country", table.Columns.ElementAt(3).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(3).ColumnType); + Assert.AreEqual("b.Money", table.Columns.ElementAt(4).ColumnName); + Assert.AreEqual(typeof(decimal?), table.Columns.ElementAt(4).ColumnType); + Assert.AreEqual("b.Month", table.Columns.ElementAt(5).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(5).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("City1", table[0].Values[0]); + Assert.AreEqual("Country1", table[0].Values[1]); + Assert.AreEqual(100, table[0].Values[2]); + Assert.AreEqual("Country1", table[0].Values[3]); + Assert.AreEqual(1000m, table[0].Values[4]); + + Assert.AreEqual("City1", table[1].Values[0]); + Assert.AreEqual("Country1", table[1].Values[1]); + Assert.AreEqual(100, table[1].Values[2]); + Assert.AreEqual("Country1", table[1].Values[3]); + Assert.AreEqual(2000m, table[1].Values[4]); + + Assert.AreEqual("City2", table[2].Values[0]); + Assert.AreEqual("Country1", table[2].Values[1]); + Assert.AreEqual(200, table[2].Values[2]); + Assert.AreEqual("Country1", table[2].Values[3]); + Assert.AreEqual(1000m, table[2].Values[4]); + + Assert.AreEqual("City2", table[3].Values[0]); + Assert.AreEqual("Country1", table[3].Values[1]); + Assert.AreEqual(200, table[3].Values[2]); + Assert.AreEqual("Country1", table[3].Values[3]); + Assert.AreEqual(2000m, table[3].Values[4]); + + Assert.AreEqual("City3", table[4].Values[0]); + Assert.AreEqual("Country2", table[4].Values[1]); + Assert.AreEqual(300, table[4].Values[2]); + Assert.AreEqual("Country2", table[4].Values[3]); + Assert.AreEqual(3000m, table[4].Values[4]); + } + + [TestMethod] + public void WhenSchemaMethodOuterAppliedWithAnotherSchema_UsesCte_ShouldPass() + { + const string query = @" +with p as ( + select + f.City as City, + f.Country as Country, + f.Population as Population + from #schema.first() f +) +select a.City, a.Country, a.Population, b.Country, b.Money, b.Month from p a outer apply #schema.second(a.Country) b"; + + var firstSource = new List + { + new() {City = "City1", Country = "Country1", Population = 100}, + new() {City = "City2", Country = "Country1", Population = 200}, + new() {City = "City3", Country = "Country2", Population = 300} + }.ToArray(); + + var secondSource = new List + { + new() {Country = "Country1", Money = 1000, Month = "January"}, + new() {Country = "Country1", Money = 2000, Month = "February"}, + new() {Country = "Country2", Money = 3000, Month = "March"} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource, + secondSource, + null, + null, + null, + (parameters, source) => new ObjectRowsSource(source.Rows.Where(f => (string) f["Country"] == (string) parameters[0]).ToArray())); + + var table = vm.Run(); + + Assert.AreEqual(6, table.Columns.Count()); + Assert.AreEqual("a.City", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Country", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("a.Population", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(int), table.Columns.ElementAt(2).ColumnType); + Assert.AreEqual("b.Country", table.Columns.ElementAt(3).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(3).ColumnType); + Assert.AreEqual("b.Money", table.Columns.ElementAt(4).ColumnName); + Assert.AreEqual(typeof(decimal?), table.Columns.ElementAt(4).ColumnType); + Assert.AreEqual("b.Month", table.Columns.ElementAt(5).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(5).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("City1", table[0].Values[0]); + Assert.AreEqual("Country1", table[0].Values[1]); + Assert.AreEqual(100, table[0].Values[2]); + Assert.AreEqual("Country1", table[0].Values[3]); + Assert.AreEqual(1000m, table[0].Values[4]); + + Assert.AreEqual("City1", table[1].Values[0]); + Assert.AreEqual("Country1", table[1].Values[1]); + Assert.AreEqual(100, table[1].Values[2]); + Assert.AreEqual("Country1", table[1].Values[3]); + Assert.AreEqual(2000m, table[1].Values[4]); + + Assert.AreEqual("City2", table[2].Values[0]); + Assert.AreEqual("Country1", table[2].Values[1]); + Assert.AreEqual(200, table[2].Values[2]); + Assert.AreEqual("Country1", table[2].Values[3]); + Assert.AreEqual(1000m, table[2].Values[4]); + + Assert.AreEqual("City2", table[3].Values[0]); + Assert.AreEqual("Country1", table[3].Values[1]); + Assert.AreEqual(200, table[3].Values[2]); + Assert.AreEqual("Country1", table[3].Values[3]); + Assert.AreEqual(2000m, table[3].Values[4]); + + Assert.AreEqual("City3", table[4].Values[0]); + Assert.AreEqual("Country2", table[4].Values[1]); + Assert.AreEqual(300, table[4].Values[2]); + Assert.AreEqual("Country2", table[4].Values[3]); + Assert.AreEqual(3000m, table[4].Values[4]); + } + + [TestMethod] + public void WhenSchemaMethodOuterAppliedSelfProperty_WithinCte_ShouldPass() + { + const string query = @" +with p as ( + select a.Name, b.Value from #schema.first() a outer apply a.Skills b +) +select [a.Name], [b.Value] from p"; + + var firstSource = new List + { + new() {Name = "Name1", Skills = ["Skill1", "Skill2", "Skill3"]}, + new() {Name = "Name2", Skills = ["Skill4", "Skill5", "Skill6"]}, + new() {Name = "Name3", Skills = ["Skill7", "Skill8", "Skill9"]} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource); + + var table = vm.Run(); + + Assert.AreEqual(2, table.Columns.Count()); + + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + + Assert.AreEqual("b.Value", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + + Assert.AreEqual(9, table.Count); + + Assert.AreEqual("Name1", table[0].Values[0]); + Assert.AreEqual("Skill1", table[0].Values[1]); + + Assert.AreEqual("Name1", table[1].Values[0]); + Assert.AreEqual("Skill2", table[1].Values[1]); + + Assert.AreEqual("Name1", table[2].Values[0]); + Assert.AreEqual("Skill3", table[2].Values[1]); + + Assert.AreEqual("Name2", table[3].Values[0]); + Assert.AreEqual("Skill4", table[3].Values[1]); + + Assert.AreEqual("Name2", table[4].Values[0]); + Assert.AreEqual("Skill5", table[4].Values[1]); + + Assert.AreEqual("Name2", table[5].Values[0]); + Assert.AreEqual("Skill6", table[5].Values[1]); + + Assert.AreEqual("Name3", table[6].Values[0]); + Assert.AreEqual("Skill7", table[6].Values[1]); + + Assert.AreEqual("Name3", table[7].Values[0]); + Assert.AreEqual("Skill8", table[7].Values[1]); + + Assert.AreEqual("Name3", table[8].Values[0]); + Assert.AreEqual("Skill9", table[8].Values[1]); + } + + [TestMethod] + public void WhenSchemaMethodOuterAppliedSelfProperty_UsesCte_ShouldPass() + { + const string query = @" +with first as ( + select a.Name as Name, a.Skills as Skills from #schema.first() a +) +select a.Name, b.Value from first a outer apply a.Skills b"; + + var firstSource = new List + { + new() {Name = "Name1", Skills = ["Skill1", "Skill2", "Skill3"]}, + new() {Name = "Name2", Skills = ["Skill4", "Skill5", "Skill6"]}, + new() {Name = "Name3", Skills = ["Skill7", "Skill8", "Skill9"]} + }.ToArray(); + + var vm = CreateAndRunVirtualMachine( + query, + firstSource); + + var table = vm.Run(); + + Assert.AreEqual(2, table.Columns.Count()); + + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + + Assert.AreEqual("b.Value", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + + Assert.AreEqual(9, table.Count); + + Assert.AreEqual("Name1", table[0].Values[0]); + Assert.AreEqual("Skill1", table[0].Values[1]); + + Assert.AreEqual("Name1", table[1].Values[0]); + Assert.AreEqual("Skill2", table[1].Values[1]); + + Assert.AreEqual("Name1", table[2].Values[0]); + Assert.AreEqual("Skill3", table[2].Values[1]); + + Assert.AreEqual("Name2", table[3].Values[0]); + Assert.AreEqual("Skill4", table[3].Values[1]); + + Assert.AreEqual("Name2", table[4].Values[0]); + Assert.AreEqual("Skill5", table[4].Values[1]); + + Assert.AreEqual("Name2", table[5].Values[0]); + Assert.AreEqual("Skill6", table[5].Values[1]); + + Assert.AreEqual("Name3", table[6].Values[0]); + Assert.AreEqual("Skill7", table[6].Values[1]); + + Assert.AreEqual("Name3", table[7].Values[0]); + Assert.AreEqual("Skill8", table[7].Values[1]); + + Assert.AreEqual("Name3", table[8].Values[0]); + Assert.AreEqual("Skill9", table[8].Values[1]); + } +} \ No newline at end of file diff --git a/Musoq.Evaluator.Tests/OuterApplyMixedTests.cs b/Musoq.Evaluator.Tests/OuterApplyMixedTests.cs index 49475a12..d3e393fd 100644 --- a/Musoq.Evaluator.Tests/OuterApplyMixedTests.cs +++ b/Musoq.Evaluator.Tests/OuterApplyMixedTests.cs @@ -33,7 +33,7 @@ private class OuterApplyClass2 } [TestMethod] - public void OuterApply_SchemaAndProperty_WithNestedProperty() + public void OuterApply_SchemaAndProperty_WithNestedProperty_ShouldPass() { const string query = @" select @@ -137,7 +137,7 @@ private class OuterApplyClass4 } [TestMethod] - public void OuterApply_SchemaAndMethod_WithComplexObjects() + public void OuterApply_SchemaAndMethod_WithComplexObjects_ShouldPass() { const string query = @" select @@ -262,7 +262,7 @@ public class ComplexType2 } [TestMethod] - public void OuterApply_PropertyAndMethod_WithFiltering() + public void OuterApply_PropertyAndMethod_WithFiltering_ShouldPass() { const string query = @" select @@ -327,7 +327,7 @@ outer apply a.Distinct(b.Skills) c } [TestMethod] - public void OuterApply_PropertyAndMethod_GroupBy_WithFiltering() + public void OuterApply_PropertyAndMethod_GroupBy_WithFiltering_ShouldPass() { const string query = @" select @@ -383,4 +383,143 @@ where a.Budget > 400000 Assert.AreEqual("IT", table[0][0]); Assert.AreEqual(2, table[0][1]); } + + private class OuterApplyClass6 + { + public string Name { get; set; } + + public string Surname { get; set; } + + public int Id { get; set; } + } + + private class OuterApplyClass7 + { + public int Id { get; set; } + + [BindablePropertyAsTable] + public string[] Skills { get; set; } + } + + [TestMethod] + public void OuterApply_InnerJoinAndUseProperty_ShouldPass() + { + const string query = @" + select + a.Name, + a.Surname, + c.Value + from #schema.first() a + inner join #schema.second() b on a.Id = b.Id + outer apply b.Skills c"; + + var firstSource = new OuterApplyClass6[] + { + new() {Name = "John", Surname = "Doe", Id = 1}, + new() {Name = "Jane", Surname = "Smith", Id = 2}, + new() {Name = "Alice", Surname = "Johnson", Id = 3} + }; + + var secondSource = new OuterApplyClass7[] + { + new() {Id = 1, Skills = ["C#", "JavaScript"]}, + new() {Id = 2, Skills = ["Java"]}, + new() {Id = 3, Skills = ["Communication", "Negotiation"]} + }; + + var vm = CreateAndRunVirtualMachine(query, firstSource, secondSource); + + var table = vm.Run(); + + Assert.AreEqual(3, table.Columns.Count()); + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Surname", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("c.Value", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(2).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("John", table[0][0]); + Assert.AreEqual("Doe", table[0][1]); + Assert.AreEqual("C#", table[0][2]); + + Assert.AreEqual("John", table[1][0]); + Assert.AreEqual("Doe", table[1][1]); + Assert.AreEqual("JavaScript", table[1][2]); + + Assert.AreEqual("Jane", table[2][0]); + Assert.AreEqual("Smith", table[2][1]); + Assert.AreEqual("Java", table[2][2]); + + Assert.AreEqual("Alice", table[3][0]); + Assert.AreEqual("Johnson", table[3][1]); + Assert.AreEqual("Communication", table[3][2]); + + Assert.AreEqual("Alice", table[4][0]); + Assert.AreEqual("Johnson", table[4][1]); + Assert.AreEqual("Negotiation", table[4][2]); + } + + [TestMethod] + public void OuterApply_LeftJoinAndUseProperty_ShouldPass() + { + const string query = @" + select + a.Name, + a.Surname, + c.Value + from #schema.first() a + left outer join #schema.second() b on a.Id = b.Id + outer apply b.Skills c"; + + var firstSource = new OuterApplyClass6[] + { + new() {Name = "John", Surname = "Doe", Id = 1}, + new() {Name = "Jane", Surname = "Smith", Id = 2}, + new() {Name = "Alice", Surname = "Johnson", Id = 3} + }; + + var secondSource = new OuterApplyClass7[] + { + new() {Id = 1, Skills = ["C#", "JavaScript"]}, + new() {Id = 2, Skills = ["Java"]}, + new() {Id = 3, Skills = ["Communication", "Negotiation"]} + }; + + var vm = CreateAndRunVirtualMachine(query, firstSource, secondSource); + + var table = vm.Run(); + + Assert.AreEqual(3, table.Columns.Count()); + Assert.AreEqual("a.Name", table.Columns.ElementAt(0).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(0).ColumnType); + Assert.AreEqual("a.Surname", table.Columns.ElementAt(1).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(1).ColumnType); + Assert.AreEqual("c.Value", table.Columns.ElementAt(2).ColumnName); + Assert.AreEqual(typeof(string), table.Columns.ElementAt(2).ColumnType); + + Assert.AreEqual(5, table.Count); + + Assert.AreEqual("John", table[0][0]); + Assert.AreEqual("Doe", table[0][1]); + Assert.AreEqual("C#", table[0][2]); + + Assert.AreEqual("John", table[1][0]); + Assert.AreEqual("Doe", table[1][1]); + Assert.AreEqual("JavaScript", table[1][2]); + + Assert.AreEqual("Jane", table[2][0]); + Assert.AreEqual("Smith", table[2][1]); + Assert.AreEqual("Java", table[2][2]); + + Assert.AreEqual("Alice", table[3][0]); + Assert.AreEqual("Johnson", table[3][1]); + Assert.AreEqual("Communication", table[3][2]); + + Assert.AreEqual("Alice", table[4][0]); + Assert.AreEqual("Johnson", table[4][1]); + Assert.AreEqual("Negotiation", table[4][2]); + } } \ No newline at end of file diff --git a/Musoq.Evaluator/Tables/TransitionSchema.cs b/Musoq.Evaluator/Tables/TransitionSchema.cs index 1e9c5c75..70b72efe 100644 --- a/Musoq.Evaluator/Tables/TransitionSchema.cs +++ b/Musoq.Evaluator/Tables/TransitionSchema.cs @@ -7,19 +7,12 @@ namespace Musoq.Evaluator.Tables { - internal class TransitionSchema : SchemaBase + internal class TransitionSchema(string name, ISchemaTable table) + : SchemaBase(name, CreateLibrary()) { - private readonly ISchemaTable _table; - - public TransitionSchema(string name, ISchemaTable table) - : base(name, CreateLibrary()) - { - _table = table; - } - public override ISchemaTable GetTableByName(string name, RuntimeContext runtimeContext, params object[] parameters) { - return _table; + return table; } public override RowSource GetRowSource(string name, RuntimeContext interCommunicator, params object[] parameters) diff --git a/Musoq.Evaluator/Tables/VariableTable.cs b/Musoq.Evaluator/Tables/VariableTable.cs index bb38ce03..8b6ecc99 100644 --- a/Musoq.Evaluator/Tables/VariableTable.cs +++ b/Musoq.Evaluator/Tables/VariableTable.cs @@ -3,14 +3,9 @@ namespace Musoq.Evaluator.Tables { - internal class VariableTable : ISchemaTable + internal class VariableTable(ISchemaColumn[] columns) : ISchemaTable { - public VariableTable(ISchemaColumn[] columns) - { - Columns = columns; - } - - public ISchemaColumn[] Columns { get; } + public ISchemaColumn[] Columns { get; } = columns; public ISchemaColumn GetColumnByName(string name) { diff --git a/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeTraverseVisitor.cs b/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeTraverseVisitor.cs index 193c5d0e..d7f375c9 100644 --- a/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeTraverseVisitor.cs +++ b/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeTraverseVisitor.cs @@ -346,7 +346,8 @@ public void Visit(ApplyFromNode node) apply.Source.Accept(this); apply.With.Accept(this); - var firstTableSymbol = Scope.ScopeSymbolTable.GetSymbol(Scope[apply.Source.Id]); + var sourceId = apply.Source is JoinFromNode ? MetaAttributes.ProcessedQueryId : apply.Source.Id; + var firstTableSymbol = Scope.ScopeSymbolTable.GetSymbol(Scope[sourceId]); var secondTableSymbol = Scope.ScopeSymbolTable.GetSymbol(Scope[apply.With.Id]); switch (apply.ApplyType) @@ -361,7 +362,7 @@ public void Visit(ApplyFromNode node) throw new ArgumentOutOfRangeException(); } - var id = $"{Scope[apply.Source.Id]}{Scope[apply.With.Id]}"; + var id = $"{Scope[sourceId]}{Scope[apply.With.Id]}"; Scope.ScopeSymbolTable.AddSymbol(id, firstTableSymbol.MergeSymbols(secondTableSymbol)); Scope[MetaAttributes.ProcessedQueryId] = id; diff --git a/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeVisitor.cs b/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeVisitor.cs index 81664d1a..e774e184 100644 --- a/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeVisitor.cs +++ b/Musoq.Evaluator/Visitors/BuildMetadataAndInferTypeVisitor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Dynamic; using System.Linq; @@ -35,6 +36,9 @@ namespace Musoq.Evaluator.Visitors { public class BuildMetadataAndInferTypeVisitor : IAwareExpressionVisitor { + private static readonly WhereNode AllTrueWhereNode = + new(new EqualityNode(new IntegerNode("1", "s"), new IntegerNode("1", "s"))); + private readonly ISchemaProvider _provider; private readonly IReadOnlyDictionary> _positionalEnvironmentVariables; private readonly List _refreshMethods = new(); @@ -63,6 +67,12 @@ public class BuildMetadataAndInferTypeVisitor : IAwareExpressionVisitor private readonly IDictionary _aliasToSchemaFromNodeMap = new Dictionary(); + + private readonly IDictionary _aliasMapToInMemoryTableMap = + new Dictionary(); + + private readonly IDictionary _variableTables + = new Dictionary(); private readonly List _groupByFields = []; private readonly List _nullSuspiciousTypes; @@ -71,6 +81,8 @@ public class BuildMetadataAndInferTypeVisitor : IAwareExpressionVisitor private readonly IDictionary _schemaFromInfo = new Dictionary(); + private QueryPart _queryPart; + private int _setKey; private int _schemaFromKey; private uint _positionalEnvironmentVariablesKey; @@ -368,8 +380,8 @@ public void Visit(IsNullNode node) public void Visit(AccessRefreshAggreationScoreNode node) { VisitAccessMethod(node, - (token, node1, exargs, arg3, alias, _) => - new AccessRefreshAggreationScoreNode(token, node1 as ArgsListNode, exargs, node.CanSkipInjectSource, + (token, node1, exArgs, arg3, alias, _) => + new AccessRefreshAggreationScoreNode(token, node1 as ArgsListNode, exArgs, node.CanSkipInjectSource, arg3, alias)); } @@ -743,11 +755,6 @@ public void Visit(TakeNode node) Nodes.Push(new TakeNode((IntegerNode) node.Expression)); } - private static readonly WhereNode AllTrueWhereNode = - new(new EqualityNode(new IntegerNode("1", "s"), new IntegerNode("1", "s"))); - - private QueryPart _queryPart; - public void Visit(SchemaFromNode node) { var schema = _provider.GetSchema(node.Schema); @@ -755,8 +762,7 @@ public void Visit(SchemaFromNode node) _queryAlias = AliasGenerator.CreateAliasIfEmpty(node.Alias, _generatedAliases, _schemaFromKey.ToString()); _generatedAliases.Add(_queryAlias); - var aliasedSchemaFromNode = new Parser.SchemaFromNode(node.Schema, node.Method, (ArgsListNode) Nodes.Pop(), - _queryAlias, node.QueryId); + var aliasedSchemaFromNode = new Parser.SchemaFromNode(node.Schema, node.Method, (ArgsListNode) Nodes.Pop(), _queryAlias, node.QueryId); var isDesc = _currentScope.Name == "Desc"; var table = !isDesc ? schema.GetTableByName( @@ -802,44 +808,61 @@ public void Visit(SchemaMethodFromNode node) public void Visit(PropertyFromNode node) { - var schemaFrom = _aliasToSchemaFromNodeMap[node.SourceAlias]; - var schema = _provider.GetSchema(schemaFrom.Schema); + ISchema schema; + ISchemaTable table; + if (_aliasToSchemaFromNodeMap.TryGetValue(node.SourceAlias, out var schemaFrom)) + { + schemaFrom = _aliasToSchemaFromNodeMap[node.SourceAlias]; + schema = _provider.GetSchema(schemaFrom.Schema); + + table = schema.GetTableByName( + schemaFrom.Method, + new RuntimeContext( + CancellationToken.None, + _columns[schemaFrom.Alias + _schemaFromKey].Select((f, i) => new SchemaColumn(f, i, typeof(object))).ToArray(), + _positionalEnvironmentVariables.TryGetValue(_schemaFromInfo[schemaFrom.Alias].PositionalEnvironmentVariableKey, out var variable) + ? variable + : new Dictionary(), + (schemaFrom, Array.Empty(), AllTrueWhereNode) + ), + schemaFrom.Parameters); + } + else + { + var name = _aliasMapToInMemoryTableMap[node.SourceAlias]; + table = _variableTables[name]; + schema = new TransitionSchema(name, table); + } _queryAlias = AliasGenerator.CreateAliasIfEmpty(node.Alias, _generatedAliases, _schemaFromKey.ToString()); _generatedAliases.Add(_queryAlias); - var referencedTable = schema.GetTableByName( - schemaFrom.Method, - new RuntimeContext( - CancellationToken.None, - _columns[schemaFrom.Alias + _schemaFromKey].Select((f, i) => new SchemaColumn(f, i, typeof(object))).ToArray(), - _positionalEnvironmentVariables.TryGetValue(_schemaFromInfo[schemaFrom.Alias].PositionalEnvironmentVariableKey, out var variable) - ? variable - : new Dictionary(), - (schemaFrom, Array.Empty(), AllTrueWhereNode) - ), - schemaFrom.Parameters); - _schemaFromArgs.Clear(); - var targetColumn = referencedTable.GetColumnByName(node.PropertyName); + var targetColumn = table.GetColumnByName(node.PropertyName); if (targetColumn == null) { - PrepareAndThrowUnknownColumnExceptionMessage(node.PropertyName, referencedTable.Columns); + PrepareAndThrowUnknownColumnExceptionMessage(node.PropertyName, table.Columns); return; } - var propertyInfo = referencedTable.Metadata.TableEntityType.GetProperty(targetColumn.ColumnName); - if (propertyInfo?.GetCustomAttribute() == null && IsGenericEnumerable(propertyInfo!.PropertyType, out var elementType) && !elementType.IsPrimitive) + var propertyInfo = table.Metadata.TableEntityType.GetProperty(targetColumn.ColumnName); + var bindablePropertyAsTableAttribute = propertyInfo?.GetCustomAttribute(); + if ( + bindablePropertyAsTableAttribute != null && + !IsGenericEnumerable(propertyInfo!.PropertyType, out var elementType) && + !IsArray(propertyInfo.PropertyType!, out elementType) && + !elementType.IsPrimitive && elementType != typeof(string) + ) { throw new NotSupportedException("Column must be marked as BindablePropertyAsTable."); } AddAssembly(targetColumn.ColumnType.Assembly); - var table = TurnTypeIntoTable(targetColumn.ColumnType); - var tableSymbol = new TableSymbol(_queryAlias, schema, table, !string.IsNullOrEmpty(node.Alias)); + var nestedTable = TurnTypeIntoTable(targetColumn.ColumnType); + var tableSymbol = new TableSymbol(_queryAlias, schema, nestedTable, !string.IsNullOrEmpty(node.Alias)); _currentScope.ScopeSymbolTable.AddSymbol(_queryAlias, tableSymbol); _currentScope[node.Id] = _queryAlias; @@ -957,6 +980,8 @@ public void Visit(InMemoryTableFromNode node) _currentScope.ScopeSymbolTable.AddSymbol(_queryAlias, new TableSymbol(_queryAlias, tableSchemaPair.Schema, tableSchemaPair.Table, node.Alias == _queryAlias)); _currentScope[node.Id] = _queryAlias; + + _aliasMapToInMemoryTableMap.Add(_queryAlias, node.VariableName); Nodes.Push(new Parser.InMemoryTableFromNode(node.VariableName, _queryAlias)); } @@ -976,10 +1001,10 @@ public void Visit(ApplyFromNode node) { var appliedTable = (FromNode) Nodes.Pop(); var source = (FromNode) Nodes.Pop(); - var joinedFrom = new Parser.ApplyFromNode(source, appliedTable, node.ApplyType); - _identifier = joinedFrom.Alias; + var appliedFrom = new Parser.ApplyFromNode(source, appliedTable, node.ApplyType); + _identifier = appliedFrom.Alias; _schemaFromArgs.Clear(); - Nodes.Push(joinedFrom); + Nodes.Push(appliedFrom); } public void Visit(ExpressionFromNode node) @@ -1066,6 +1091,7 @@ public void Visit(QueryNode node) _schemaFromArgs.Clear(); _aliasToSchemaFromNodeMap.Clear(); _schemaFromInfo.Clear(); + _aliasMapToInMemoryTableMap.Clear(); } public void Visit(JoinInMemoryWithSourceTableFromNode node) @@ -1200,6 +1226,8 @@ public void Visit(MultiStatementNode node) public void Visit(CteExpressionNode node) { + _variableTables.Clear(); + var sets = new CteInnerExpressionNode[node.InnerExpression.Length]; var set = Nodes.Pop(); @@ -1222,6 +1250,8 @@ public void Visit(CteInnerExpressionNode node) var table = new VariableTable(collector.CollectedFieldNames); _currentScope.Parent.ScopeSymbolTable.AddSymbol(node.Name, new TableSymbol(node.Name, new TransitionSchema(node.Name, table), table, false)); + + _variableTables.Add(node.Name, table); Nodes.Push(new CteInnerExpressionNode(set, node.Name)); } @@ -1318,9 +1348,7 @@ private void VisitAccessMethod( AccessMethodNode node, Func func) { - var args = Nodes.Pop() as ArgsListNode; - - if (args is null) + if (Nodes.Pop() is not ArgsListNode args) throw new NotSupportedException($"Cannot resolve method {node.Name}. Arguments are null."); var groupArgs = new List {typeof(string)}; @@ -1434,7 +1462,7 @@ private void VisitAccessMethod( } else { - accessMethod = func(node.FToken, args, new ArgsListNode(Array.Empty()), method, alias, + accessMethod = func(node.FToken, args, new ArgsListNode([]), method, alias, canSkipInjectSource); } @@ -1595,6 +1623,7 @@ public void QueryBegins() public void QueryEnds() { + _identifier = null; } public void SetTheMostInnerIdentifierOfDotNode(IdentifierNode node) @@ -2000,5 +2029,15 @@ private static bool IsGenericEnumerable(Type type, out Type elementType) return false; } + + private static bool IsArray(Type type, out Type elementType) + { + elementType = null; + + if (!type.IsArray) return false; + + elementType = type.GetElementType(); + return true; + } } } \ No newline at end of file