From 20121c85a1c49f46e5194007de055e5ff79ca297 Mon Sep 17 00:00:00 2001 From: leminh98 Date: Tue, 2 May 2023 08:48:44 -0700 Subject: [PATCH] Query: Adds TRIM string system function support in LINQ (#3833) * add trim support * Added some test coverage * address reviews --------- Co-authored-by: Minh Le --- .../StringBuiltinFunctions.cs | 43 ++++++ ...ationBaselineTests.TestStringFunctions.xml | 128 ++++++++++++++++-- .../LinqTranslationBaselineTests.cs | 19 ++- 3 files changed, 177 insertions(+), 13 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs index 781eb8d20a..b04bd58c3c 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs @@ -348,6 +348,45 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method } } + private class StringVisitTrim : SqlBuiltinFunctionVisitor + { + public StringVisitTrim() + : base("TRIM", + false, + null) + { + } + + protected override SqlScalarExpression VisitImplicit(MethodCallExpression methodCallExpression, TranslationContext context) + { + bool validInNet = false; + bool validInNetCore = false; + + if (methodCallExpression.Arguments.Count == 1 && + methodCallExpression.Arguments[0].NodeType == ExpressionType.Constant && + methodCallExpression.Arguments[0].Type == typeof(char[])) + { + char[] argumentsExpressions = (char[])((ConstantExpression)methodCallExpression.Arguments[0]).Value; + if (argumentsExpressions.Length == 0) + { + validInNet = true; + } + } + else if (methodCallExpression.Arguments.Count == 0) + { + validInNetCore = true; + } + + if (validInNet || validInNetCore) + { + SqlScalarExpression str = ExpressionToSql.VisitScalarExpression(methodCallExpression.Object, context); + return SqlFunctionCallScalarExpression.CreateBuiltin(SqlFunctionCallScalarExpression.Names.Trim, str); + } + + return null; + } + } + static StringBuiltinFunctions() { StringBuiltinFunctionDefinitions = new Dictionary @@ -415,6 +454,10 @@ static StringBuiltinFunctions() "TrimEnd", new StringVisitTrimEnd() }, + { + "Trim", + new StringVisitTrim() + }, { "StartsWith", new SqlStringWithComparisonVisitor(SqlFunctionCallScalarExpression.Names.Startswith) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringFunctions.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringFunctions.xml index a9fc581461..4ccc9d34ca 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringFunctions.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringFunctions.xml @@ -363,6 +363,28 @@ FROM root]]> + + + + + + doc.StringField.Replace(c, a))]]> + + + + + + + + + doc.StringField.Replace("str", "str2"))]]> + + + @@ -379,34 +401,45 @@ FROM root]]> - - doc.StringField.TrimStart())]]> + + doc.StringField.Trim())]]> - - doc.StringField.Replace(c, a))]]> + + " abc ".Trim())]]> - - doc.StringField.Replace("str", "str2"))]]> + + doc.StringField.Trim(new [] {}))]]> + + + + + + " abc ".Trim(new [] {}))]]> + + + @@ -418,6 +451,83 @@ FROM root]]> + + + + + + " abc ".TrimEnd())]]> + + + + + + + + + doc.StringField.TrimEnd(new [] {}))]]> + + + + + + + + + " abc ".TrimEnd(new [] {}))]]> + + + + + + + + + doc.StringField.TrimStart())]]> + + + + + + + + + " abc ".TrimStart())]]> + + + + + + + + + doc.StringField.TrimStart(new [] {}))]]> + + + + + + + + + " abc ".TrimStart(new [] {}))]]> + + + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index 14aadd354f..76bd0ffc81 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -744,15 +744,26 @@ public void TestStringFunctions() new LinqTestInput("IndexOf string w/ startIndex", b => getQuery(b).Select(doc => doc.StringField.IndexOf("str", 0))), // Count new LinqTestInput("Count", b => getQuery(b).Select(doc => doc.StringField.Count())), - // ToLower - new LinqTestInput("ToLower", b => getQuery(b).Select(doc => doc.StringField.ToLower())), - // TrimStart - new LinqTestInput("TrimStart", b => getQuery(b).Select(doc => doc.StringField.TrimStart())), // Replace new LinqTestInput("Replace char", b => getQuery(b).Select(doc => doc.StringField.Replace('c', 'a'))), new LinqTestInput("Replace string", b => getQuery(b).Select(doc => doc.StringField.Replace("str", "str2"))), + // ToLower + new LinqTestInput("ToLower", b => getQuery(b).Select(doc => doc.StringField.ToLower())), + // Trim + new LinqTestInput("Trim", b => getQuery(b).Select(doc => doc.StringField.Trim())), + new LinqTestInput("Trim with Literal", b => getQuery(b).Select(doc => " abc ".Trim())), + new LinqTestInput("Trim with EmptyCharArray", b => getQuery(b).Select(doc => doc.StringField.Trim(new char[]{ }))), + new LinqTestInput("Trim with Literal and EmptyCharArray", b => getQuery(b).Select(doc => " abc ".Trim(new char[]{ }))), // TrimEnd new LinqTestInput("TrimEnd", b => getQuery(b).Select(doc => doc.StringField.TrimEnd())), + new LinqTestInput("TrimEnd with Literal", b => getQuery(b).Select(doc => " abc ".TrimEnd())), + new LinqTestInput("TrimEnd with EmptyCharArray", b => getQuery(b).Select(doc => doc.StringField.TrimEnd(new char[]{ }))), + new LinqTestInput("TrimEnd with Literal and EmptyCharArray", b => getQuery(b).Select(doc => " abc ".TrimEnd(new char[]{ }))), + // TrimStart + new LinqTestInput("TrimStart", b => getQuery(b).Select(doc => doc.StringField.TrimStart())), + new LinqTestInput("TrimStart with Literal", b => getQuery(b).Select(doc => " abc ".TrimStart())), + new LinqTestInput("TrimStart with EmptyCharArray", b => getQuery(b).Select(doc => doc.StringField.TrimStart(new char[]{ }))), + new LinqTestInput("TrimStart with Literal and EmptyCharArray", b => getQuery(b).Select(doc => " abc ".TrimStart(new char[]{ }))), //StartsWith new LinqTestInput("StartsWith", b => getQuery(b).Select(doc => doc.StringField.StartsWith("str"))), new LinqTestInput("String constant StartsWith", b => getQuery(b).Select(doc => "str".StartsWith(doc.StringField))),