From b7aee726c625844214be56a01fa774eed191817e Mon Sep 17 00:00:00 2001 From: dasatomic Date: Thu, 20 May 2021 16:40:48 +0200 Subject: [PATCH] Support for functors that accept different types. Slight refactoring. --- QueryProcessing/ArithmeticFunctions.cs | 143 ++++++++++++++++++ QueryProcessing/FuncCallMapper.cs | 64 ++++++++ QueryProcessing/IFunctionCall.cs | 10 ++ QueryProcessing/ProjectOpBuilder.cs | 93 +----------- QueryProcessing/QueryProcessingAccessors.cs | 34 ----- .../E2EQueryExecutionTests/SimpleE2ETests.cs | 32 ++++ 6 files changed, 255 insertions(+), 121 deletions(-) create mode 100644 QueryProcessing/ArithmeticFunctions.cs create mode 100644 QueryProcessing/FuncCallMapper.cs create mode 100644 QueryProcessing/IFunctionCall.cs diff --git a/QueryProcessing/ArithmeticFunctions.cs b/QueryProcessing/ArithmeticFunctions.cs new file mode 100644 index 0000000..f062e2f --- /dev/null +++ b/QueryProcessing/ArithmeticFunctions.cs @@ -0,0 +1,143 @@ +using MetadataManager; +using PageManager; +using System; +using System.Linq; + +namespace QueryProcessing +{ + static class FunctorArgChecks + { + public static void CheckInputArguments(MetadataColumn[] sourceArguments, ColumnType[] acceptedColumnTypes) + { + if (sourceArguments.Length != acceptedColumnTypes.Length) + { + throw new ArgumentException("Invalid number of arguments"); + } + + foreach (MetadataColumn md in sourceArguments) + { + if (!acceptedColumnTypes.Any(acc => md.ColumnType.ColumnType == acc)) + { + throw new ArgumentException($"Type {md.ColumnType.ColumnType} is not accepted by Add functor"); + } + } + } + } + + public static class AddFunctorOutputMappingHandler + { + public static MetadataColumn GetMetadataInfoForOutput(Sql.columnSelect.Func func, MetadataColumn[] metadataColumns) + { + Sql.scalarArgs args = func.Item.Item2; + + if (!args.IsArgs2) + { + throw new ArgumentException("Add accepts two args"); + } + + var args2 = ((Sql.scalarArgs.Args2)args).Item; + Sql.value argOne = args2.Item1; + Sql.value argTwo = args2.Item2; + + if (!argOne.IsId || !argTwo.IsId) + { + throw new NotImplementedException("Currently we only support ids in as arguments"); + } + + Sql.value.Id idOne = (Sql.value.Id)argOne; + Sql.value.Id idTwo = (Sql.value.Id)argTwo; + + ColumnInfo argOneMd = QueryProcessingAccessors.GetMetadataColumn(idOne.Item, metadataColumns).ColumnType; + ColumnInfo argTwoMd = QueryProcessingAccessors.GetMetadataColumn(idTwo.Item, metadataColumns).ColumnType; + + // both need to be double or int. + // TODO: Need generic way to express this. + if (!((argOneMd.ColumnType == ColumnType.Double || argOneMd.ColumnType == ColumnType.Int) && + argTwoMd.ColumnType == ColumnType.Double || argTwoMd.ColumnType == ColumnType.Int)) + { + throw new ArgumentException("Invalid argument type for add"); + } + + if (argOneMd.ColumnType == ColumnType.Double || argTwoMd.ColumnType == ColumnType.Double) + { + // If one of them is double map result to double. + return new MetadataColumn(0, 0, "ADD_Result", new ColumnInfo(ColumnType.Double)); + } + else + { + return new MetadataColumn(0, 0, "ADD_Result", new ColumnInfo(ColumnType.Int)); + } + } + + public static IFunctionCall MapToFunctor(MetadataColumn arg1, MetadataColumn arg2) + { + return ((arg1.ColumnType.ColumnType, arg2.ColumnType.ColumnType)) switch + { + (ColumnType.Int, ColumnType.Int) => new AddFunctorInt(), + (ColumnType.Int, ColumnType.Double) => new AddFunctorIntDouble(), + (ColumnType.Double, ColumnType.Int) => new AddFunctorDoubleInt(), + (ColumnType.Double, ColumnType.Double) => new AddFunctorDouble(), + _ => throw new ArgumentException("Invalid type"), + }; + } + } + + public class AddFunctorInt : IFunctionCall + { + public void ExecCompute(RowHolder inputRowHolder, RowHolder outputRowHolder, MetadataColumn[] sourceArguments, int outputPosition) + { + FunctorArgChecks.CheckInputArguments(sourceArguments, new[] { ColumnType.Int, ColumnType.Int }); + int argOneExtracted = inputRowHolder.GetField(sourceArguments[0].ColumnId); + int argTwoExtracted = inputRowHolder.GetField(sourceArguments[1].ColumnId); + + int res = argOneExtracted + argTwoExtracted; + + outputRowHolder.SetField(outputPosition, res); + } + } + + public class AddFunctorDouble : IFunctionCall + { + public void ExecCompute(RowHolder inputRowHolder, RowHolder outputRowHolder, MetadataColumn[] sourceArguments, int outputPosition) + { + FunctorArgChecks.CheckInputArguments(sourceArguments, new[] { ColumnType.Double, ColumnType.Double}); + + double argOneExtracted = inputRowHolder.GetField(sourceArguments[0].ColumnId); + double argTwoExtracted = inputRowHolder.GetField(sourceArguments[1].ColumnId); + + double res = argOneExtracted + argTwoExtracted; + + outputRowHolder.SetField(outputPosition, res); + } + } + + public class AddFunctorDoubleInt : IFunctionCall + { + public void ExecCompute(RowHolder inputRowHolder, RowHolder outputRowHolder, MetadataColumn[] sourceArguments, int outputPosition) + { + FunctorArgChecks.CheckInputArguments(sourceArguments, new[] { ColumnType.Double, ColumnType.Int}); + + double argOneExtracted = inputRowHolder.GetField(sourceArguments[0].ColumnId); + double argTwoExtracted = inputRowHolder.GetField(sourceArguments[1].ColumnId); + + double res = argOneExtracted + argTwoExtracted; + + outputRowHolder.SetField(outputPosition, res); + } + } + + public class AddFunctorIntDouble : IFunctionCall + { + public void ExecCompute(RowHolder inputRowHolder, RowHolder outputRowHolder, MetadataColumn[] sourceArguments, int outputPosition) + { + FunctorArgChecks.CheckInputArguments(sourceArguments, new[] { ColumnType.Double, ColumnType.Int}); + + double argOneExtracted = inputRowHolder.GetField(sourceArguments[0].ColumnId); + double argTwoExtracted = inputRowHolder.GetField(sourceArguments[1].ColumnId); + + double res = argOneExtracted + argTwoExtracted; + + outputRowHolder.SetField(outputPosition, res); + } + } +} diff --git a/QueryProcessing/FuncCallMapper.cs b/QueryProcessing/FuncCallMapper.cs new file mode 100644 index 0000000..86aecce --- /dev/null +++ b/QueryProcessing/FuncCallMapper.cs @@ -0,0 +1,64 @@ +using MetadataManager; +using PageManager; +using System; +using System.Collections.Generic; +using System.Text; + +namespace QueryProcessing +{ + /// + /// Responsible for mapping function calls from syntax tree to functor class. + /// + public static class FuncCallMapper + { + public static MetadataColumn GetMetadataInfoForOutput(Sql.columnSelect.Func func, MetadataColumn[] sourceInput) + { + Sql.FuncType funcType = func.Item.Item1; + + if (funcType.IsAdd) + { + return AddFunctorOutputMappingHandler.GetMetadataInfoForOutput(func, sourceInput); + } + + throw new NotImplementedException(); + } + + public static Action BuildFunctor(Sql.columnSelect.Func func, int outputPosition, MetadataColumn[] sourceColumns) + { + Sql.FuncType funcType = func.Item.Item1; + Sql.scalarArgs args = func.Item.Item2; + + if (funcType.IsAdd) + { + Sql.scalarArgs.Args2 argsExtracted = (Sql.scalarArgs.Args2)args; + Sql.value arg1 = argsExtracted.Item.Item1; + Sql.value arg2 = argsExtracted.Item.Item2; + + if (!arg1.IsId || !arg2.IsId) + { + // TODO: + throw new Exception("Only support for ids as function arguments"); + } + + Sql.value.Id arg1Id = (Sql.value.Id)(arg1); + Sql.value.Id arg2Id = (Sql.value.Id)(arg2); + + MetadataColumn mc1 = QueryProcessingAccessors.GetMetadataColumn(arg1Id.Item, sourceColumns); + MetadataColumn mc2 = QueryProcessingAccessors.GetMetadataColumn(arg2Id.Item, sourceColumns); + var functor = AddFunctorOutputMappingHandler.MapToFunctor(mc1, mc2); + + return (RowHolder inputRh, RowHolder outputRh) => + { + functor.ExecCompute( + inputRh, + outputRh, + new MetadataColumn[] { mc1, mc2 }, + outputPosition + ); + }; + } + + throw new NotImplementedException(); + } + } +} diff --git a/QueryProcessing/IFunctionCall.cs b/QueryProcessing/IFunctionCall.cs new file mode 100644 index 0000000..0a57425 --- /dev/null +++ b/QueryProcessing/IFunctionCall.cs @@ -0,0 +1,10 @@ +using MetadataManager; +using PageManager; + +namespace QueryProcessing +{ + public interface IFunctionCall + { + void ExecCompute(RowHolder inputRowHolder, RowHolder outputRowHolder, MetadataColumn[] sourceArguments, int outputPosition); + } +} diff --git a/QueryProcessing/ProjectOpBuilder.cs b/QueryProcessing/ProjectOpBuilder.cs index ad27a9f..fe9052b 100644 --- a/QueryProcessing/ProjectOpBuilder.cs +++ b/QueryProcessing/ProjectOpBuilder.cs @@ -60,32 +60,8 @@ private MetadataColumn[] GetOutputSchema(Sql.columnSelect[] columns, IPhysicalOp } else if (column.IsFunc) { - var func = ((Sql.columnSelect.Func)column).Item; - Sql.FuncType funcType = func.Item1; - // TODO: Map func type to the right function. - // This should go to new class. - Sql.scalarArgs args = func.Item2; - - if (funcType.IsAdd) - { - if (!args.IsArgs2) - { - throw new Exception("Sum requires 2 arguments"); - } - - var args2 = ((Sql.scalarArgs.Args2)args).Item; - Sql.value argOne = args2.Item1; - Sql.value argTwo = args2.Item2; - // TODO: Some rules should be applied here to determine output type. - // TODO: For now always return int. - // TODO is keeping 0, 0 here ok? - result[pos] = new MetadataColumn(0, 0, "ADD_Result", new ColumnInfo(ColumnType.Int)); - } - else - { - // TODO: - throw new NotImplementedException(); - } + var func = ((Sql.columnSelect.Func)column); + result[pos] = FuncCallMapper.GetMetadataInfoForOutput(func, source.GetOutputColumns()); } pos++; @@ -112,32 +88,8 @@ private MetadataColumn[] GetOutputSchema(Sql.columnSelect[] columns, IPhysicalOp } else if (c.IsFunc) { - var func = ((Sql.columnSelect.Func)c).Item; - - Sql.FuncType funcType = func.Item1; - // TODO: Map func type to the right function. - // This should go to new class. - Sql.scalarArgs args = func.Item2; - - if (funcType.IsAdd) - { - if (!args.IsArgs2) - { - throw new Exception("Sum requires 2 arguments"); - } - - var args2 = ((Sql.scalarArgs.Args2)args).Item; - Sql.value argOne = args2.Item1; - Sql.value argTwo = args2.Item2; - // TODO: Some rules should be applied here to determine output type. - // TODO: For now always return int. - return (null, new ColumnInfo(ColumnType.Int)); - } - else - { - // TODO: - throw new NotImplementedException(); - } + var func = ((Sql.columnSelect.Func)c); + return (null, FuncCallMapper.GetMetadataInfoForOutput(func, source.GetOutputColumns()).ColumnType); } else { @@ -161,41 +113,8 @@ private Action ExecuteComputeOnRowHolder(IEnumerable - { - QueryProcessingAccessors.ApplyFuncInPlace( - new MetadataColumn[] { mc1, mc2 }, - outputPosition, - inputRh, - outputRh, - funcType - ); - }); - } + var func = ((Sql.columnSelect.Func)select); + listOfActions.Add(FuncCallMapper.BuildFunctor(func, outputPosition, sourceColumns)); } // Execute all the actions. diff --git a/QueryProcessing/QueryProcessingAccessors.cs b/QueryProcessing/QueryProcessingAccessors.cs index 424521f..ff538ef 100644 --- a/QueryProcessing/QueryProcessingAccessors.cs +++ b/QueryProcessing/QueryProcessingAccessors.cs @@ -104,40 +104,6 @@ public static IComparable MetadataColumnRowsetHolderFetcher(MetadataColumn mc, R } } - /// - /// Applies given function to given row holder. - /// - /// List of source metadata columns, ordered by scalar args. First arg maps to sourcesMds[0] and so on. Null if arg is not an id. - /// Target positions in output rowholder. - /// Input rowholder - /// Func type. - /// Arguments - /// Output rowholder. - public static void ApplyFuncInPlace(MetadataColumn[] sourcesMds, int outputPosition, RowHolder inputRowHolder, RowHolder outputRowHolder, Sql.FuncType funcType) - { - if (funcType.IsAdd) - { - MetadataColumn columnOneMd = sourcesMds[0]; - MetadataColumn columnTwoMd = sourcesMds[1]; - - if (columnOneMd.ColumnType.ColumnType != ColumnType.Int || columnTwoMd.ColumnType.ColumnType != ColumnType.Int) - { - // TODO: - throw new Exception("Only support for ints in sum"); - } - - int argOneExtracted = inputRowHolder.GetField(columnOneMd.ColumnId); - int argTwoExtracted = inputRowHolder.GetField(columnTwoMd.ColumnId); - - int res = argOneExtracted + argTwoExtracted; - outputRowHolder.SetField(outputPosition, res); - } - else - { - throw new NotImplementedException(); - } - } - // TODO: This is just bad. // It is very hard to keep all type -> agg mappings. // Needs refactoring. diff --git a/tests/E2EQueryExecutionTests/SimpleE2ETests.cs b/tests/E2EQueryExecutionTests/SimpleE2ETests.cs index 12f0482..b0799a5 100644 --- a/tests/E2EQueryExecutionTests/SimpleE2ETests.cs +++ b/tests/E2EQueryExecutionTests/SimpleE2ETests.cs @@ -222,6 +222,38 @@ public async Task SelectWithFunc() await tran.Commit(); } + await using (ITransaction tran = this.logManager.CreateTransaction(pageManager, "GET_ROWS")) + { + string query = @"SELECT ADD(a, b) FROM T1"; + RowHolder[] result = await this.queryEntryGate.Execute(query, tran).ToArrayAsync(); + Assert.AreEqual(20, result.Length); + + int i = 0; + foreach (var row in result) + { + Assert.AreEqual(i + 1.1, row.GetField(0)); + i++; + } + + await tran.Commit(); + } + + await using (ITransaction tran = this.logManager.CreateTransaction(pageManager, "GET_ROWS")) + { + string query = @"SELECT ADD(b, a) FROM T1"; + RowHolder[] result = await this.queryEntryGate.Execute(query, tran).ToArrayAsync(); + Assert.AreEqual(20, result.Length); + + int i = 0; + foreach (var row in result) + { + Assert.AreEqual(i + 1.1, row.GetField(0)); + i++; + } + + await tran.Commit(); + } + await using (ITransaction tran = this.logManager.CreateTransaction(pageManager, "GET_ROWS")) { string query = @"SELECT TOP 5 ADD(a, a) FROM T1";