Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 403 Support for UPPER/LOWER (and other data provider functions) #454

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using RepoDb.DataProviderFunctions;
using RepoDb.DataProviderFunctions.DataProviderFunctionBuilders;
using System;
using System.Linq.Expressions;
using RepoDb.Extensions;

namespace RepoDb.UnitTests.DataProviderFunctions {
[TestClass]
public class DataProviderFunctionExtratorTest {
[TestMethod]
public void TestSingleCall() {
// arrange
var mockDataProviderFunctionBuilder = new Mock<IDataProviderFunctionBuilder>();
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToUpper")).Returns(true);

Expression<Func<string, string>> inputExpr = s => "".ToUpper();

// act
var dataProviderFunctionExtractor = new DataProviderFunctionExtractor(mockDataProviderFunctionBuilder.Object);
dataProviderFunctionExtractor.Extract((MethodCallExpression)inputExpr.Body);

// assert
Assert.AreEqual(1, dataProviderFunctionExtractor.ExtractedDataProviderFunctions.Count);
}

[TestMethod]
public void TestChainedCallAllProviderFunctions() {
// arrange
var mockDataProviderFunctionBuilder = new Mock<IDataProviderFunctionBuilder>();
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToUpper")).Returns(true);
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToLower")).Returns(true);

Expression<Func<string, string>> inputExpr = s => "".ToUpper().ToLower();

// act
var dataProviderFunctionExtractor = new DataProviderFunctionExtractor(mockDataProviderFunctionBuilder.Object);
dataProviderFunctionExtractor.Extract((MethodCallExpression)inputExpr.Body);

// assert
Assert.AreEqual(2, dataProviderFunctionExtractor.ExtractedDataProviderFunctions.Count);
}


[TestMethod]
public void TestChainedCallNotAllProviderFunctions() {
// arrange
var mockDataProviderFunctionBuilder = new Mock<IDataProviderFunctionBuilder>();
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToUpper")).Returns(true);
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToLower")).Returns(true);

Expression<Func<string, string>> inputExpr = s => "".ToUpper().Substring(0).ToLower();
// act
var dataProviderFunctionExtractor = new DataProviderFunctionExtractor(mockDataProviderFunctionBuilder.Object);
dataProviderFunctionExtractor.Extract((MethodCallExpression)inputExpr.Body);

// assert
Assert.AreEqual(2, dataProviderFunctionExtractor.ExtractedDataProviderFunctions.Count);
}


[TestMethod]
public void TestMemberExpression() {
// arrange
var mockDataProviderFunctionBuilder = new Mock<IDataProviderFunctionBuilder>();
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToUpper")).Returns(true);
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToLower")).Returns(true);

var testClass = new DataProviderFunctionExtractorTestClass();
Expression<Func<string, string>> inputExpr = s => testClass.FirstName.ToUpper().Substring(0).ToLower();

// act
var dataProviderFunctionExtractor = new DataProviderFunctionExtractor(mockDataProviderFunctionBuilder.Object);
dataProviderFunctionExtractor.Extract((MethodCallExpression)inputExpr.Body);

// assert
Assert.AreEqual(2, dataProviderFunctionExtractor.ExtractedDataProviderFunctions.Count);
Assert.IsNotNull(dataProviderFunctionExtractor.MemberExpression);
Assert.AreEqual("FirstName", dataProviderFunctionExtractor.MemberExpression.ToMember().Member.Name) ;
}

[TestMethod]
public void TestMemberExpressionEmbeddedType() {
// arrange
var mockDataProviderFunctionBuilder = new Mock<IDataProviderFunctionBuilder>();
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToUpper")).Returns(true);
mockDataProviderFunctionBuilder.Setup(b => b.IsDataProviderFunction("ToLower")).Returns(true);

var testClass = new DataProviderFunctionExtractorEmbeddedTestClass();
Expression<Func<string, string>> inputExpr = s => testClass.EmbeddedStringType.FirstName.ToUpper().Substring(0).ToLower();

// act
var dataProviderFunctionExtractor = new DataProviderFunctionExtractor(mockDataProviderFunctionBuilder.Object);
dataProviderFunctionExtractor.Extract((MethodCallExpression)inputExpr.Body);

// assert
Assert.AreEqual(2, dataProviderFunctionExtractor.ExtractedDataProviderFunctions.Count);
Assert.IsNotNull(dataProviderFunctionExtractor.MemberExpression);
Assert.AreEqual("FirstName", dataProviderFunctionExtractor.MemberExpression.ToMember().Member.Name) ;
}
}

internal class DataProviderFunctionExtractorTestClass {
public string FirstName { get; set; }
}

internal class DataProviderFunctionExtractorEmbeddedTestClass {
public DataProviderFunctionExtractorTestClass EmbeddedStringType { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace RepoDb.UnitTests {
public partial class QueryGroupTest
{
[TestMethod]
public void TestQueryGroupParseExpressionStringContainsWithToUpperFromClassProperty()
{
// Setup
var @class = new QueryGroupTestExpressionClass
{
PropertyString = "A"
};
var parsed = QueryGroup.Parse<QueryGroupTestExpressionClass>(c => c.PropertyString.ToUpper().Contains("A"));

// Act
var actual = parsed.GetString(m_dbSetting);
var expected = "(UPPER([PropertyString]) LIKE @PropertyString)";

// Assert
Assert.AreEqual(expected, actual);
}

[TestMethod]
public void TestQueryGroupParseExpressionStringContainsWithToLowerFromClassProperty()
{
// Setup
var @class = new QueryGroupTestExpressionClass
{
PropertyString = "A"
};
var parsed = QueryGroup.Parse<QueryGroupTestExpressionClass>(c => c.PropertyString.ToLower().Contains("A"));

// Act
var actual = parsed.GetString(m_dbSetting);
var expected = "(LOWER([PropertyString]) LIKE @PropertyString)";

// Assert
Assert.AreEqual(expected, actual);
}

[TestMethod]
public void TestQueryGroupParseExpressionStringContainsWithToLowerToUpperFromClassProperty()
{
// Setup
var @class = new QueryGroupTestExpressionClass
{
PropertyString = "A"
};
var parsed = QueryGroup.Parse<QueryGroupTestExpressionClass>(c => c.PropertyString.ToLower().ToUpper().Contains("A"));

// Act
var actual = parsed.GetString(m_dbSetting);
var expected = "(UPPER(LOWER([PropertyString])) LIKE @PropertyString)";

// Assert
Assert.AreEqual(expected, actual);
}

[TestMethod]
public void TestQueryGroupParseExpressionStringToUpperFromClassProperty()
{
// Setup
var @class = new QueryGroupTestExpressionClass
{
PropertyString = "A"
};
var parsed = QueryGroup.Parse<QueryGroupTestExpressionClass>(c => c.PropertyString.ToUpper() == "A");

// Act
var actual = parsed.GetString(m_dbSetting);
var expected = "(UPPER([PropertyString]) = @PropertyString)";

// Assert
Assert.AreEqual(expected, actual);
}

[TestMethod]
public void TestQueryGroupParseExpressionStringToLowerFromClassProperty()
{
// Setup
var @class = new QueryGroupTestExpressionClass
{
PropertyString = "A"
};
var parsed = QueryGroup.Parse<QueryGroupTestExpressionClass>(c => c.PropertyString.ToLower() == "A");

// Act
var actual = parsed.GetString(m_dbSetting);
var expected = "(LOWER([PropertyString]) = @PropertyString)";

// Assert
Assert.AreEqual(expected, actual);
}

[TestMethod]
public void TestQueryGroupParseExpressionStringToLowerToUpperFromClassProperty()
{
// Setup
var @class = new QueryGroupTestExpressionClass
{
PropertyString = "A"
};
var parsed = QueryGroup.Parse<QueryGroupTestExpressionClass>(c => c.PropertyString.ToLower().ToUpper() != "A");

// Act
var actual = parsed.GetString(m_dbSetting);
var expected = "(UPPER(LOWER([PropertyString])) <> @PropertyString)";

// Assert
Assert.AreEqual(expected, actual);
}
}
}
2 changes: 2 additions & 0 deletions RepoDb/RepoDb.Tests/RepoDb.UnitTests/RepoDb.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
<Compile Include="CustomObjects\CustomDbParameter.cs" />
<Compile Include="CustomObjects\CustomDbParameterCollection.cs" />
<Compile Include="CustomObjects\CustomDbTransaction.cs" />
<Compile Include="DataProviderFunctions\DataProviderFunctionExtractorTest.cs" />
<Compile Include="DbHelpers\DbHelperTest.cs" />
<Compile Include="Enumerations\ConjunctionTextTest.cs" />
<Compile Include="Enumerations\OperationTextTest.cs" />
Expand Down Expand Up @@ -129,6 +130,7 @@
<Compile Include="QueryGroups\QueryGroupParseExpressionForSingleTypeTest.cs" />
<Compile Include="QueryGroups\QueryGroupParseExpressionForStartsWithMethodTest.cs" />
<Compile Include="QueryGroups\QueryGroupParseExpressionForStringTypeTest.cs" />
<Compile Include="QueryGroups\QueryGroupParseExpressionForToUpperToLowerTest.cs" />
<Compile Include="QueryGroups\QueryGroupParseExpressionOperationsTest.cs" />
<Compile Include="QueryGroups\QueryGroupParseExpressionValuesTest.cs" />
<Compile Include="Resolvers\ClientTypeToDbTypeResolverTest.cs" />
Expand Down
37 changes: 37 additions & 0 deletions RepoDb/RepoDb/DataProviderFunctions/DataProviderFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq.Expressions;

namespace RepoDb.DataProviderFunctions {
/// <summary>
/// An object that represents a data provider-level function that is intended
/// to be applied to the column/field itself during statement-generation.
/// </summary>
public class DataProviderFunction {

/// <summary>
/// Name of server-side SQL function
/// </summary>
public readonly string Name;

/// <summary>
/// Optional input arguments for the data provider function
/// </summary>
public readonly IEnumerable<Expression> Arguments;

/// <summary>
/// Creates a new instance of <see cref="DataProviderFunction"/> object.
/// </summary>
public DataProviderFunction(string functionName, IEnumerable<Expression> arguments) : this(functionName) {
Arguments = arguments;
}


/// <summary>
/// Creates a new instance of <see cref="DataProviderFunction"/> object.
/// </summary>
public DataProviderFunction(string functionName) {
Name = functionName;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using RepoDb.DataProviderFunctions.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace RepoDb.DataProviderFunctions.DataProviderFunctionBuilders {

/// <summary>
/// Base class for building data provider functions
/// </summary>
public abstract class BaseDataProviderFunctionBuilder : IDataProviderFunctionBuilder {

/// <summary>
///
/// </summary>
public IEnumerable<DataProviderFunction> DataProviderFunctions { get; protected set; }

/// <summary>
///
/// </summary>
public string FieldName { get; protected set; }

/// <summary>
/// Contains a dictionary of supported data provider functions:
/// key = function name
/// value = Decorator delegate that will transform input field.
/// </summary>
protected Dictionary<string, Func<string, IEnumerable<Expression>, string>> Vocabulary = new Dictionary<string, Func<string, IEnumerable<Expression>, string>>();

/// <summary>
/// Initializes Vocabulary
/// </summary>
protected BaseDataProviderFunctionBuilder() {
Vocabulary.Add("ToUpper", (fieldName, exprArgs) => string.Format("UPPER({0})", fieldName));
Vocabulary.Add("ToLower", (fieldName, exprArgs) => string.Format("LOWER({0})", fieldName));
/* Just a proof of concept for accommodating parameterized server-side functions
Vocabulary.Add("Substring", (fieldName, exprArgs) => string.Format("SUBSTRING({0}, {1},{2})", fieldName,
((ConstantExpression)exprArgs[0]).Value,
((ConstantExpression)exprArgs[1]).Value);
*/
}

/// <summary>
/// Convenience ctor
/// </summary>
/// <param name="fieldName"></param>
/// <param name="dataProviderFunctions"></param>
public BaseDataProviderFunctionBuilder(string fieldName,
IEnumerable<DataProviderFunction> dataProviderFunctions) : this() {
FieldName = fieldName;
DataProviderFunctions = dataProviderFunctions;
}

/// <summary>
///
/// </summary>
/// <returns></returns>
public string Build() {
if (DataProviderFunctions?.Any() == false) {
return FieldName;
}
string fieldName = FieldName;
foreach (var dataProviderFunc in DataProviderFunctions) {
VerifyFunctionSupport(dataProviderFunc);
Decorate(ref fieldName, dataProviderFunc);
}
return fieldName;
}

/// <summary>
///
/// </summary>
/// <param name="functionName"></param>
/// <returns></returns>
public virtual bool IsDataProviderFunction(string functionName) {
return Vocabulary.ContainsKey(functionName);
}

#region privates
private void VerifyFunctionSupport(DataProviderFunction dataProviderFunc) {
if (!IsDataProviderFunction(dataProviderFunc.Name)) {
// We shouldn't be here if IsDataProviderFunction was also used during extraction of functions.
throw new NotSupportedFunctionException(dataProviderFunc.Name);
}
}

private void Decorate(ref string fieldName, DataProviderFunction dataProviderFunc) {
try {
fieldName = Vocabulary[dataProviderFunc.Name](fieldName, dataProviderFunc.Arguments);
}
catch (Exception exc) {
throw new DataProviderFunctionDecoratorException(fieldName, dataProviderFunc.Name, exc);
}
}
#endregion
}
}
Loading