diff --git a/Framework.sln b/Framework.sln index 29d006e..78d6321 100644 --- a/Framework.sln +++ b/Framework.sln @@ -41,6 +41,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.Validation", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.Expressions", "src\Connected.Expressions\Connected.Expressions.csproj", "{E597528B-95CA-4ACB-A44B-2C0737084581}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connected.Data.Tests", "src\Connected.Data.Tests\Connected.Data.Tests.csproj", "{C6520F60-774E-41B1-8B17-7EF0B825BF6A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D6F579A8-2BDD-4E47-BF39-FB8650E5039E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connected.Expressions.Tests", "src\Connected.Expressions.Tests\Connected.Expressions.Tests.csproj", "{9D4ED240-112A-467D-BCD7-2989DD172B83}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,10 +129,22 @@ Global {E597528B-95CA-4ACB-A44B-2C0737084581}.Debug|Any CPU.Build.0 = Debug|Any CPU {E597528B-95CA-4ACB-A44B-2C0737084581}.Release|Any CPU.ActiveCfg = Release|Any CPU {E597528B-95CA-4ACB-A44B-2C0737084581}.Release|Any CPU.Build.0 = Release|Any CPU + {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6520F60-774E-41B1-8B17-7EF0B825BF6A}.Release|Any CPU.Build.0 = Release|Any CPU + {9D4ED240-112A-467D-BCD7-2989DD172B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D4ED240-112A-467D-BCD7-2989DD172B83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D4ED240-112A-467D-BCD7-2989DD172B83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D4ED240-112A-467D-BCD7-2989DD172B83}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C6520F60-774E-41B1-8B17-7EF0B825BF6A} = {D6F579A8-2BDD-4E47-BF39-FB8650E5039E} + {9D4ED240-112A-467D-BCD7-2989DD172B83} = {D6F579A8-2BDD-4E47-BF39-FB8650E5039E} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C63B5A75-C99A-45D7-A691-26E568A51740} EndGlobalSection diff --git a/src/Connected.Data.Tests/Connected.Data.Tests.csproj b/src/Connected.Data.Tests/Connected.Data.Tests.csproj new file mode 100644 index 0000000..fb7938d --- /dev/null +++ b/src/Connected.Data.Tests/Connected.Data.Tests.csproj @@ -0,0 +1,28 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + + diff --git a/src/Connected.Data.Tests/Translation/TranslatorTests.cs b/src/Connected.Data.Tests/Translation/TranslatorTests.cs new file mode 100644 index 0000000..bfa4e26 --- /dev/null +++ b/src/Connected.Data.Tests/Translation/TranslatorTests.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Connected.Expressions; +using Connected.Data.Sql; +using Connected.Expressions.Translation; +using System.Linq.Expressions; +using Connected.Data.Storage; +using System.Collections; +using System.Reflection; +using Connected.Entities; + +namespace Connected.Data.Tests.Translation; + +[TestClass] +public class TranslatorTests +{ + [TestMethod] + public void Translator_GeneratesBasicSelectExpression_UsingTSQL() + { + var context = new ExpressionCompilationContext(new TSqlLanguage()); + + var translator = new Translator(context); + + var data = SampleData.All(); + + var dataQueryable = data.AsQueryable(); + + var query = (from x in dataQueryable select x); + + var operatorMethodInfo = QueryableMethods.SingleOrDefaultWithoutPredicate; + + if (operatorMethodInfo.IsGenericMethod) + { + operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2 + ? operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity), typeof(DummyEntity).GetGenericArguments().Single()) + : operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity)); + } + + var expr = Expression.Call(instance: null, method: operatorMethodInfo, arguments: new[] { query.Expression }); + + var result = translator.Translate(expr) as ProjectionExpression; + + var select = result.Select; + + Assert.IsInstanceOfType(select, typeof(SelectExpression)); + Assert.IsInstanceOfType(select.From, typeof(TableExpression)); + Assert.IsNull(select.Where, "Where should be null"); + Assert.IsNull(select.Take, "Take should be null"); + Assert.IsNull(select.Skip, "Skip should be null"); + Assert.AreEqual(0, select.GroupBy.Count); + Assert.IsFalse(select.IsReverse); + } + + [TestMethod] + public void Translator_GeneratesSelectExpression_WithWhere_UsingTSQL() + { + var context = new ExpressionCompilationContext(new TSqlLanguage()); + + var translator = new Translator(context); + + var data = SampleData.All(); + + var dataQueryable = data.AsQueryable(); + + var query = (from x in dataQueryable where x.Id == 1 select x); + + var operatorMethodInfo = QueryableMethods.SingleOrDefaultWithoutPredicate; + + if (operatorMethodInfo.IsGenericMethod) + { + operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2 + ? operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity), typeof(DummyEntity).GetGenericArguments().Single()) + : operatorMethodInfo.MakeGenericMethod(typeof(DummyEntity)); + } + + var expr = Expression.Call(instance: null, method: operatorMethodInfo, arguments: new[] { query.Expression }); + + var result = translator.Translate(expr) as ProjectionExpression; + + var select = result.Select; + + Assert.IsInstanceOfType(select, typeof(SelectExpression)); + Assert.IsInstanceOfType(select.From, typeof(TableExpression)); + Assert.IsNotNull(select.Where, "Where should not be null"); + Assert.IsNull(select.Take, "Take should be null"); + Assert.IsNull(select.Skip, "Skip should be null"); + Assert.AreEqual(0, select.GroupBy.Count); + Assert.IsFalse(select.IsReverse); + } +} + +public record DummyEntity : Entity; + +internal static class SampleData +{ + public static List All() + { + return new List + { + new DummyEntity{ Id = 1}, + new DummyEntity{ Id = 2}, + new DummyEntity{ Id = 3}, + new DummyEntity{ Id = 4}, + new DummyEntity{ Id = 5} + }; + } +} + +internal static class QueryableMethods +{ + static QueryableMethods() + { + var queryableMethodGroups = typeof(Queryable) + .GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) + .GroupBy(mi => mi.Name) + .ToDictionary(e => e.Key, l => l.ToList()); + + SingleWithoutPredicate = GetMethod(nameof(Queryable.Single), 1, types => new[] { typeof(IQueryable<>).MakeGenericType(types[0]) }); + SingleOrDefaultWithoutPredicate = GetMethod(nameof(Queryable.SingleOrDefault), 1, types => new[] { typeof(IQueryable<>).MakeGenericType(types[0]) }); + + MethodInfo GetMethod(string name, int genericParameterCount, Func parameterGenerator) + { + return queryableMethodGroups[name].Single(mi => ((genericParameterCount == 0 && !mi.IsGenericMethod) + || (mi.IsGenericMethod && mi.GetGenericArguments().Length == genericParameterCount)) + && mi.GetParameters().Select(e => e.ParameterType).SequenceEqual(parameterGenerator(mi.IsGenericMethod ? mi.GetGenericArguments() : Array.Empty()))); + } + } + + public static MethodInfo SingleWithoutPredicate { get; } + public static MethodInfo SingleOrDefaultWithoutPredicate { get; } +} diff --git a/src/Connected.Data.Tests/Usings.cs b/src/Connected.Data.Tests/Usings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/src/Connected.Data.Tests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file