Skip to content

Commit 5ec8874

Browse files
committed
Handle IsNullOrWhiteSpace extension method calls
1 parent 6f943a4 commit 5ec8874

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

src/PgKeyValueDB/SqlExpressionVisitor.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,38 @@ private static string GetCommonNumericType(Type type1, Type type2)
8383

8484
protected override Expression VisitMethodCall(MethodCallExpression node)
8585
{
86+
// Handle IsNullOrWhiteSpace extension method calls
87+
if (node.Method.Name == nameof(string.IsNullOrWhiteSpace) &&
88+
node.Method.IsStatic &&
89+
node.Arguments.Count == 1 &&
90+
node.Arguments[0].Type == typeof(string))
91+
{
92+
var argument = node.Arguments[0];
93+
94+
// Check if it's a constant/closure variable (not a property access on the entity)
95+
if (argument.NodeType == ExpressionType.Constant ||
96+
(argument.NodeType == ExpressionType.MemberAccess &&
97+
((MemberExpression)argument).Expression?.NodeType == ExpressionType.Constant))
98+
{
99+
// For constants/variables, evaluate the IsNullOrWhiteSpace call and use the result
100+
var value = Expression.Lambda(argument).Compile().DynamicInvoke();
101+
var isNullOrWhiteSpace = string.IsNullOrWhiteSpace(value?.ToString());
102+
AddParameter(isNullOrWhiteSpace, typeof(bool));
103+
return node;
104+
}
105+
else
106+
{
107+
// For property access on the entity, generate the JSON path check
108+
// This handles cases like string.IsNullOrWhiteSpace(u.SomeProperty)
109+
whereClause.Append("(");
110+
Visit(argument);
111+
whereClause.Append(" is null or trim(");
112+
Visit(argument);
113+
whereClause.Append(") = '')");
114+
return node;
115+
}
116+
}
117+
86118
// Special handling for enum ToString()
87119
if (node.Method.Name == nameof(ToString) && node.Object?.Type.IsEnum == true)
88120
{

test/PgKeyValueDB.Tests/PgKeyValueDBTest.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44

55
namespace Wololo.PgKeyValueDB.Tests;
66

7+
// Extension method to simulate upstream usage pattern
8+
public static class StringExtensions
9+
{
10+
public static bool IsNullOrWhiteSpace(this string? value)
11+
{
12+
return string.IsNullOrWhiteSpace(value);
13+
}
14+
}
15+
716
public enum UserStatus
817
{
918
Active,
@@ -31,6 +40,7 @@ public class UserProfile : UserProfileBase1
3140
public override string? Id { get; set; } // Add this line
3241
public string? Name { get; set; }
3342
public string? DisplayName { get; set; }
43+
public string? Email { get; set; }
3444
public int Age { get; set; }
3545
public UserStatus Status { get; set; }
3646
public UserRole Role { get; set; }
@@ -801,4 +811,59 @@ public async Task FilterByHasValueTest()
801811
Assert.AreEqual(1, usersWithoutVerificationStatus.Count);
802812
Assert.AreEqual("Charlie", usersWithoutVerificationStatus[0].Name);
803813
}
814+
815+
[TestMethod]
816+
public async Task FilterWithExtensionMethodIsNullOrWhiteSpaceTest()
817+
{
818+
var key1 = nameof(FilterWithExtensionMethodIsNullOrWhiteSpaceTest) + "1";
819+
var key2 = nameof(FilterWithExtensionMethodIsNullOrWhiteSpaceTest) + "2";
820+
var key3 = nameof(FilterWithExtensionMethodIsNullOrWhiteSpaceTest) + "3";
821+
var pid = nameof(FilterWithExtensionMethodIsNullOrWhiteSpaceTest);
822+
823+
var user1 = new UserProfile { Name = "Alice", DisplayName = "Alice Display", Email = "[email protected]" };
824+
var user2 = new UserProfile { Name = "Bob", DisplayName = null, Email = null };
825+
var user3 = new UserProfile { Name = "Charlie", DisplayName = "", Email = "" };
826+
827+
await kv.UpsertAsync(key1, user1, pid);
828+
await kv.UpsertAsync(key2, user2, pid);
829+
await kv.UpsertAsync(key3, user3, pid);
830+
831+
// Test extension method with different closure variable values
832+
833+
// Test 1: Non-null/non-empty string - should return true
834+
string? validEmail = "alice";
835+
Expression<Func<UserProfile, bool>> query1 = u => !validEmail.IsNullOrWhiteSpace();
836+
var result1 = await kv.GetListAsync(pid, query1).ToListAsync();
837+
Assert.AreEqual(3, result1.Count, "!validEmail.IsNullOrWhiteSpace() should return all users");
838+
839+
// Test 2: Empty string - should return false
840+
string? emptyEmail = "";
841+
Expression<Func<UserProfile, bool>> query2 = u => !emptyEmail.IsNullOrWhiteSpace();
842+
var result2 = await kv.GetListAsync(pid, query2).ToListAsync();
843+
Assert.AreEqual(0, result2.Count, "!emptyEmail.IsNullOrWhiteSpace() should return no users");
844+
845+
// Test 3: Null string - should return false
846+
string? nullEmail = null;
847+
Expression<Func<UserProfile, bool>> query3 = u => !nullEmail.IsNullOrWhiteSpace();
848+
var result3 = await kv.GetListAsync(pid, query3).ToListAsync();
849+
Assert.AreEqual(0, result3.Count, "!nullEmail.IsNullOrWhiteSpace() should return no users");
850+
851+
// Test 4: Whitespace string - should return false
852+
string? whitespaceEmail = " ";
853+
Expression<Func<UserProfile, bool>> query4 = u => !whitespaceEmail.IsNullOrWhiteSpace();
854+
var result4 = await kv.GetListAsync(pid, query4).ToListAsync();
855+
Assert.AreEqual(0, result4.Count, "!whitespaceEmail.IsNullOrWhiteSpace() should return no users");
856+
857+
// Test 5: Complex expression combining extension method with property checks
858+
Expression<Func<UserProfile, bool>> query5 = u =>
859+
!validEmail.IsNullOrWhiteSpace() && u.Name == "Alice";
860+
var result5 = await kv.GetListAsync(pid, query5).ToListAsync();
861+
Assert.AreEqual(1, result5.Count, "Combined expression should find Alice");
862+
Assert.AreEqual("Alice", result5[0].Name);
863+
864+
// Test 6: Verify the static method version still works
865+
Expression<Func<UserProfile, bool>> query6 = u => string.IsNullOrWhiteSpace(u.DisplayName);
866+
var result6 = await kv.GetListAsync(pid, query6).ToListAsync();
867+
Assert.AreEqual(2, result6.Count, "Static method should find Bob and Charlie (null/empty DisplayName)");
868+
}
804869
}

0 commit comments

Comments
 (0)