-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathWhereClauseEpressionVisitor.cs
More file actions
154 lines (139 loc) · 5.57 KB
/
WhereClauseEpressionVisitor.cs
File metadata and controls
154 lines (139 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Internals
{
// TODO: This is not in use, yet and will need more work to be functional. Therefore, this class will be ignored by code coverage tools. Don't forget to remove the exclusion when you start using it.
/// <summary>
/// A simplified visitor to translate a WHERE clause LambdaExpression into a SQL string.
/// This must be used by your IMigrationsModelDiffer, not the SQL generator.
/// </summary>
public class WhereClauseExpressionVisitor(IEntityType sourceEntityType) : ExpressionVisitor
{
private readonly StringBuilder sqlBuilder = new();
/// <summary>
/// Translates the provided expression into a SQL WHERE clause.
/// </summary>
public string Translate(Expression expression)
{
sqlBuilder.Clear();
Visit(expression);
return sqlBuilder.ToString();
}
// Handles expressions like x.Property == "value"
protected override Expression VisitBinary(BinaryExpression node)
{
sqlBuilder.Append('(');
// Visit the left side (e.g., x.Property)
Visit(node.Left);
// Add the SQL operator
sqlBuilder.Append(GetSqlOperator(node.NodeType));
// Visit the right side (e.g., "value")
Visit(node.Right);
sqlBuilder.Append(')');
return node;
}
// Handles member access, like x.Ticker
protected override Expression VisitMember(MemberExpression node)
{
// We only care about properties of the entity
if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter)
{
if (node.Member is PropertyInfo propertyInfo)
{
// Find the property on the EF Core model
IProperty? property = sourceEntityType.FindProperty(propertyInfo.Name);
if (property != null)
{
StoreObjectIdentifier storeIdentifier = StoreObjectIdentifier.Table(sourceEntityType.GetTableName()!, sourceEntityType.GetSchema());
// Get the *database column name*
string? columnName = property.GetColumnName(storeIdentifier);
// Append the quoted column name
sqlBuilder.Append($"\"{columnName}\"");
return node;
}
}
}
// Fallback for other member access (e.g., accessing a variable)
// This will try to evaluate it and treat it as a constant.
if (TryEvaluate(node, out object? value))
{
AppendSqlLiteral(value);
return node;
}
throw new NotSupportedException($"Member access '{node.Member.Name}' is not supported.");
}
// Handles constants, like "MCRS" or 100
protected override Expression VisitConstant(ConstantExpression node)
{
AppendSqlLiteral(node.Value);
return node;
}
// Tries to "compile" part of the expression to get its value
private static bool TryEvaluate(Expression expression, out object? value)
{
try
{
value = Expression.Lambda(expression).Compile().DynamicInvoke();
return true;
}
catch
{
value = null;
return false;
}
}
// Helper to format a .NET value as a SQL literal
private void AppendSqlLiteral(object? value)
{
if (value == null)
{
sqlBuilder.Append("NULL");
}
else if (value is string str)
{
// Simple string quoting; for production, you might need more robust escaping
sqlBuilder.Append($"'{str.Replace("'", "''")}'");
}
else if (value is bool b)
{
sqlBuilder.Append(b ? "TRUE" : "FALSE");
}
else if (value is int || value is long || value is double || value is float || value is decimal)
{
sqlBuilder.Append(value.ToString());
}
else
{
throw new NotSupportedException($"Value of type '{value.GetType().Name}' is not supported.");
}
}
// Helper to map ExpressionType to a SQL operator
private static string GetSqlOperator(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal:
return " = ";
case ExpressionType.NotEqual:
return " <> ";
case ExpressionType.GreaterThan:
return " > ";
case ExpressionType.GreaterThanOrEqual:
return " >= ";
case ExpressionType.LessThan:
return " < ";
case ExpressionType.LessThanOrEqual:
return " <= ";
case ExpressionType.AndAlso:
return " AND ";
case ExpressionType.OrElse:
return " OR ";
default:
throw new NotSupportedException($"Operator '{type}' is not supported.");
}
}
}
}