Skip to content

Commit 0ee6b34

Browse files
committed
Refactor SqlQuery to allow selecting from multiple tables and therefore being able to do inner joins via the where clause. Still needs more testing.
1 parent 7fa72ae commit 0ee6b34

13 files changed

Lines changed: 2015 additions & 754 deletions

SQLitePCL.pretty.Orm/DatabaseConnection.Query.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using System.Diagnostics.Contracts;
44

5+
using SQLitePCL.pretty.Orm.Sql;
6+
57
namespace SQLitePCL.pretty.Orm
68
{
79
public static partial class DatabaseConnection

SQLitePCL.pretty.Orm/Interfaces.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics.Contracts;
44

5-
namespace SQLitePCL.pretty.Orm
5+
namespace SQLitePCL.pretty.Orm.Sql
66
{
77
/// <summary>
88
/// Marker interface to describe an object whose ToString() method returns valid SQL.

SQLitePCL.pretty.Orm/SQLitePCL.pretty.Orm.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@
161161
<Compile Include="SqlQuery.OrderBy.cs">
162162
<DependentUpon>SqlQuery.cs</DependentUpon>
163163
</Compile>
164+
<Compile Include="SqlQuery.Select.cs">
165+
<DependentUpon>SqlQuery.cs</DependentUpon>
166+
</Compile>
164167
<Compile Include="SqlQuery.Where.cs">
165168
<DependentUpon>SqlQuery.cs</DependentUpon>
166169
</Compile>
@@ -178,7 +181,10 @@
178181
<Compile Include="DatabaseConnection.Query.cs">
179182
<DependentUpon>DatabaseConnection.cs</DependentUpon>
180183
</Compile>
181-
<Compile Include="SqlQuery.Select.cs">
184+
<Compile Include="SqlMethods.cs">
185+
<DependentUpon>SqlQuery.cs</DependentUpon>
186+
</Compile>
187+
<Compile Include="SqlCompiler.cs">
182188
<DependentUpon>SqlQuery.cs</DependentUpon>
183189
</Compile>
184190
</ItemGroup>
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
using SQLitePCL.pretty.Orm.Attributes;
5+
using System.Linq;
6+
7+
namespace SQLitePCL.pretty.Orm.Sql
8+
{
9+
internal static partial class SqlCompiler
10+
{
11+
private static string CompileExpr(this Expression This)
12+
{
13+
if (This is BinaryExpression)
14+
{
15+
var bin = (BinaryExpression)This;
16+
17+
var leftExpr = bin.Left.CompileExpr();
18+
var rightExpr = bin.Right.CompileExpr();
19+
20+
if (rightExpr == "NULL" && bin.NodeType == ExpressionType.Equal)
21+
{
22+
if (bin.NodeType == ExpressionType.Equal)
23+
{
24+
return "(" + leftExpr + "IS NULL)";
25+
}
26+
else if (rightExpr == "NULL" && bin.NodeType == ExpressionType.NotEqual)
27+
{
28+
return "(" + leftExpr + "IS NOT NULL)";
29+
}
30+
}
31+
32+
return "(" + leftExpr + " " + GetSqlName(bin) + " " + rightExpr + ")";
33+
}
34+
else if (This is ParameterExpression)
35+
{
36+
var param = (ParameterExpression)This;
37+
return ":" + param.Name;
38+
}
39+
else if (This is MemberExpression)
40+
{
41+
var member = (MemberExpression)This;
42+
43+
if (member.Expression != null && member.Expression.NodeType == ExpressionType.Parameter)
44+
{
45+
// This is a column in the table, output the column name
46+
var tableName = TableMapping.Get(member.Expression.Type).TableName;
47+
var columnName = ((PropertyInfo) member.Member).GetColumnName();
48+
return "\"" + tableName + "\".\"" + columnName + "\"";
49+
}
50+
else
51+
{
52+
return member.EvaluateExpression().ConvertToSQLiteValue().ToSqlString();
53+
}
54+
}
55+
else if (This.NodeType == ExpressionType.Not)
56+
{
57+
var operandExpr = ((UnaryExpression)This).Operand;
58+
return "NOT(" + operandExpr.CompileExpr() + ")";
59+
}
60+
else if (This is ConstantExpression)
61+
{
62+
return This.EvaluateExpression().ConvertToSQLiteValue().ToSqlString();
63+
}
64+
else if (This is MethodCallExpression)
65+
{
66+
var call = (MethodCallExpression)This;
67+
var args = new String[call.Arguments.Count];
68+
69+
var obj = call.Object != null ? call.Object.CompileExpr() : null;
70+
71+
for (var i = 0; i < args.Length; i++)
72+
{
73+
args[i] = call.Arguments[i].CompileExpr();
74+
}
75+
76+
if (call.Method.Name == "Like" && args.Length == 2)
77+
{
78+
return "(" + args[0] + " LIKE " + args[1] + ")";
79+
}
80+
else if (call.Method.Name == "Contains" && args.Length == 2)
81+
{
82+
return "(" + args[1] + " IN " + args[0] + ")";
83+
}
84+
else if (call.Method.Name == "Contains" && args.Length == 1)
85+
{
86+
if (call.Object != null && call.Object.Type == typeof(string))
87+
{
88+
return "(" + obj + " LIKE ('%' || " + args[0] + " || '%'))";
89+
}
90+
else
91+
{
92+
return "(" + args[0] + " IN " + obj + ")";
93+
}
94+
}
95+
else if (call.Method.Name == "StartsWith" && args.Length == 1)
96+
{
97+
return "(" + obj + " LIKE (" + args[0] + " || '%'))";
98+
}
99+
else if (call.Method.Name == "EndsWith" && args.Length == 1)
100+
{
101+
return "(" + obj + " LIKE ('%' || " + args[0] + "))";
102+
}
103+
else if (call.Method.Name == "Equals" && args.Length == 1)
104+
{
105+
return "(" + obj + " = (" + args[0] + "))";
106+
}
107+
else if (call.Method.Name == "Is" && args.Length == 2)
108+
{
109+
return "(" + args[0] + " IS " + args[1] + ")";
110+
}
111+
else if (call.Method.Name == "IsNot" && args.Length == 2)
112+
{
113+
return "(" + args[0] + " IS NOT " + args[1] + ")";
114+
}
115+
}
116+
else if (This.NodeType == ExpressionType.Convert)
117+
{
118+
var u = (UnaryExpression)This;
119+
var ty = u.Type;
120+
var value = EvaluateExpression(u.Operand);
121+
122+
return value.ConvertTo(ty).ConvertToSQLiteValue().ToSqlString();
123+
}
124+
else if (This.NodeType == ExpressionType.Default)
125+
{
126+
var d = (DefaultExpression)This;
127+
var ty = d.Type;
128+
if (ty == typeof(void))
129+
{
130+
return "";
131+
}
132+
}
133+
else if (This.NodeType == ExpressionType.Lambda)
134+
{
135+
var expr = (LambdaExpression) This;
136+
return CompileExpr(expr.Body);
137+
}
138+
139+
throw new NotSupportedException("Cannot compile: " + This.NodeType.ToString());
140+
}
141+
142+
private static object EvaluateExpression(this Expression expr)
143+
{
144+
if (expr is ConstantExpression)
145+
{
146+
var c = (ConstantExpression) expr;
147+
return c.Value;
148+
}
149+
else if (expr is MemberExpression)
150+
{
151+
var memberExpr = (MemberExpression) expr;
152+
var obj = EvaluateExpression(memberExpr.Expression);
153+
154+
if (memberExpr.Member is PropertyInfo)
155+
{
156+
var m = (PropertyInfo) memberExpr.Member;
157+
return m.GetValue(obj, null);
158+
}
159+
160+
else if (memberExpr.Member is FieldInfo)
161+
{
162+
var m = (FieldInfo) memberExpr.Member;
163+
return m.GetValue(obj);
164+
}
165+
}
166+
167+
throw new NotSupportedException("Cannot compile: " + expr.NodeType.ToString());
168+
}
169+
170+
private static string ToSqlString(this ISQLiteValue value)
171+
{
172+
switch (value.SQLiteType)
173+
{
174+
case SQLiteType.Null:
175+
return "NULL";
176+
case SQLiteType.Text:
177+
case SQLiteType.Blob:
178+
return "\"" + value.ToString() + "\"";
179+
default:
180+
return value.ToString();
181+
}
182+
}
183+
184+
private static ISQLiteValue ConvertToSQLiteValue(this object This)
185+
{
186+
if (This == null) { return SQLiteValue.Null; }
187+
188+
Type t = This.GetType();
189+
190+
if (typeof(string) == t) { return ((string) This).ToSQLiteValue(); }
191+
else if (
192+
(typeof(Int32) == t)
193+
|| (typeof(Boolean) == t)
194+
|| (typeof(Byte) == t)
195+
|| (typeof(UInt16) == t)
196+
|| (typeof(Int16) == t)
197+
|| (typeof(sbyte) == t)
198+
|| (typeof(Int64) == t)
199+
|| (typeof(UInt32) == t)) { return ((long)(Convert.ChangeType(This, typeof(long)))).ToSQLiteValue(); }
200+
else if ((typeof(double) == t) || (typeof(float) == t) || (typeof(decimal) == t)) { return ((double)(Convert.ChangeType(This, typeof(double)))).ToSQLiteValue(); }
201+
else if (typeof(byte[]) == t) { return ((byte[]) This).ToSQLiteValue(); }
202+
else if (t.GetTypeInfo().ImplementedInterfaces.Contains(typeof(ISQLiteValue))) { return (ISQLiteValue) This; }
203+
else if (This is TimeSpan) { return ((TimeSpan) This).ToSQLiteValue(); }
204+
else if (This is DateTime) { return ((DateTime) This).ToSQLiteValue(); }
205+
else if (This is DateTimeOffset) { return ((DateTimeOffset) This).ToSQLiteValue(); }
206+
else if (This is Guid) { return ((Guid) This).ToSQLiteValue(); }
207+
//else if (obj is Stream) { return ((Stream) obj); }
208+
else if (This is Uri) { return ((Uri) This).ToSQLiteValue(); }
209+
else
210+
{
211+
throw new ArgumentException("Invalid type conversion" + t);
212+
}
213+
}
214+
215+
private static string GetSqlName(Expression prop)
216+
{
217+
var n = prop.NodeType;
218+
219+
switch (n)
220+
{
221+
case ExpressionType.GreaterThan: return ">";
222+
case ExpressionType.GreaterThanOrEqual: return ">=";
223+
case ExpressionType.LessThan: return "<";
224+
case ExpressionType.LessThanOrEqual: return "<=";
225+
case ExpressionType.And: return "&";
226+
case ExpressionType.AndAlso: return "and";
227+
case ExpressionType.Or: return "|";
228+
case ExpressionType.OrElse: return "or";
229+
case ExpressionType.Equal: return "=";
230+
case ExpressionType.NotEqual: return "!=";
231+
default:
232+
throw new NotSupportedException ("Cannot get SQL for: " + n);
233+
}
234+
}
235+
}
236+
}
237+

SQLitePCL.pretty.Orm/SqlMethods.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
3+
namespace SQLitePCL.pretty.Orm
4+
{
5+
/// <summary>
6+
/// Methods that are intended to be used <see cref="SQLitePCL.pretty.Orm.Sql.WhereClause&lt;T&gt;"/> expressions.
7+
/// </summary>
8+
public static class SqlMethods
9+
{
10+
/// <summary>
11+
/// SQLite IS expression.
12+
/// </summary>
13+
/// <returns>This method always throws a <see cref="System.NotSupportedException"/>.</returns>
14+
/// <param name="This">This.</param>
15+
/// <param name="other">Other.</param>
16+
/// <typeparam name="T">The type.</typeparam>
17+
public static bool Is<T>(this T This, T other = null)
18+
where T: class
19+
{
20+
throw new NotSupportedException("Function should only be used in SQL expressions.");
21+
}
22+
23+
/// <summary>
24+
/// SQLite IS expression.
25+
/// </summary>
26+
/// <returns>This method always throws a <see cref="System.NotSupportedException"/>.</returns>
27+
/// <param name="This">This.</param>
28+
/// <param name="other">Other.</param>
29+
/// <typeparam name="T">The type.</typeparam>
30+
public static bool Is<T>(this Nullable<T> This, Nullable<T> other = null)
31+
where T: struct
32+
{
33+
throw new NotSupportedException("Function should only be used in SQL expressions.");
34+
}
35+
36+
/// <summary>
37+
/// SQLite IS NOT expression.
38+
/// </summary>
39+
/// <returns>This method always throws a <see cref="System.NotSupportedException"/>.</returns>
40+
/// <param name="This">This.</param>
41+
/// <param name="other">Other.</param>
42+
/// <typeparam name="T">The type.</typeparam>
43+
public static bool IsNot<T>(this T This, T other = null)
44+
where T: class
45+
{
46+
throw new NotSupportedException("Function should only be used in SQL expressions.");
47+
}
48+
49+
/// <summary>
50+
/// SQLite IS NOT expression.
51+
/// </summary>
52+
/// <returns>This method always throws a <see cref="System.NotSupportedException"/>.</returns>
53+
/// <param name="This">This.</param>
54+
/// <param name="other">Other.</param>
55+
/// <typeparam name="T">The type.</typeparam>
56+
public static bool IsNot<T>(this Nullable<T> This, Nullable<T> other = null)
57+
where T: struct
58+
{
59+
throw new NotSupportedException("Function should only be used in SQL expressions.");
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)